mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-29 21:38:53 -04:00
Compare commits
663
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
714030458b | ||
|
|
411a728282 | ||
|
|
dddb44a9fd | ||
|
|
ede8bdf0e3 | ||
|
|
ef79cf79e3 | ||
|
|
2b0128aed0 | ||
|
|
c9d148ec9c | ||
|
|
e611bf4a77 | ||
|
|
8093822140 | ||
|
|
f695ec15f9 | ||
|
|
1f6c791ce6 | ||
|
|
570805fdf1 | ||
|
|
ddecb08bc7 | ||
|
|
4d7cfcdf61 | ||
|
|
e56dd6ef64 | ||
|
|
ffe005511d | ||
|
|
1e634a20b8 | ||
|
|
f9f4745e93 | ||
|
|
c700674289 | ||
|
|
c4f0032eee | ||
|
|
d92986016b | ||
|
|
ab57e01242 | ||
|
|
0da1c740a4 | ||
|
|
a7979aa80c | ||
|
|
59804a4e08 | ||
|
|
072c67acad | ||
|
|
60bbe221de | ||
|
|
5f06dc214b | ||
|
|
a6607e585b | ||
|
|
20584f4507 | ||
|
|
9660f1f724 | ||
|
|
c8cbace800 | ||
|
|
a616a24923 | ||
|
|
12035dd589 | ||
|
|
9627b80b01 | ||
|
|
4e9cecc854 | ||
|
|
9d4b19b185 | ||
|
|
aee96d06aa | ||
|
|
23e3c585f7 | ||
|
|
c22a88079f | ||
|
|
2225402082 | ||
|
|
9197207431 | ||
|
|
47c7f7079c | ||
|
|
eb0c1dddfd | ||
|
|
e3dbfe6ff8 | ||
|
|
fac207f36b | ||
|
|
379e794848 | ||
|
|
9081a4b131 | ||
|
|
dc6d75b8ee | ||
|
|
bb297a4448 | ||
|
|
a93922c438 | ||
|
|
f7d21c80d7 | ||
|
|
9248d26fd4 | ||
|
|
b5ac54cd16 | ||
|
|
dc703e67f7 | ||
|
|
0930721865 | ||
|
|
644ff1772e | ||
|
|
3c7e0bebb6 | ||
|
|
0c0eea3876 | ||
|
|
5535140bb7 | ||
|
|
f9dd1448e5 | ||
|
|
ecf5d57ad2 | ||
|
|
8663888abc | ||
|
|
f452cd9a05 | ||
|
|
4fe9cd7fd6 | ||
|
|
9b0ca1c16b | ||
|
|
052ff59f57 | ||
|
|
40e5ef1c46 | ||
|
|
5025977d23 | ||
|
|
922006afe7 | ||
|
|
46cc7cb04d | ||
|
|
fa3e3bbb1b | ||
|
|
afbdcce337 | ||
|
|
2c1d11ed11 | ||
|
|
b88adf0479 | ||
|
|
b1066671f5 | ||
|
|
387e85d4ad | ||
|
|
1fa113f5ac | ||
|
|
77ab58d823 | ||
|
|
1a9e736d8b | ||
|
|
f680682f61 | ||
|
|
4d93d1244d | ||
|
|
6a7d5d2133 | ||
|
|
98397cd1da | ||
|
|
fc5a610394 | ||
|
|
504df5d998 | ||
|
|
3b4d3ebf50 | ||
|
|
aa39c1906e | ||
|
|
2e460e2ca2 | ||
|
|
a07ff7f442 | ||
|
|
5374569ea1 | ||
|
|
af9c1196bf | ||
|
|
09d5543469 | ||
|
|
49e4dd0298 | ||
|
|
c8e408c50a | ||
|
|
e8a0467643 | ||
|
|
2dd0b66b83 | ||
|
|
68374017e1 | ||
|
|
120520f51d | ||
|
|
e8dea9773a | ||
|
|
7b31e6bcc4 | ||
|
|
c6ffca639f | ||
|
|
c491a1f7ca | ||
|
|
0b333b743d | ||
|
|
95e79865e5 | ||
|
|
0e2860cd90 | ||
|
|
3259bf032c | ||
|
|
116205cdf1 | ||
|
|
43277d18dc | ||
|
|
86153419ee | ||
|
|
2b4d9c451c | ||
|
|
7f0e2fa9d8 | ||
|
|
e165095bd5 | ||
|
|
b8708d6e10 | ||
|
|
ef536390ca | ||
|
|
28befab173 | ||
|
|
c142439d5b | ||
|
|
1ca7335654 | ||
|
|
9197d9cdcd | ||
|
|
f75813e64d | ||
|
|
1f617c76a4 | ||
|
|
ebf573aa7a | ||
|
|
75363da470 | ||
|
|
cce4b932cc | ||
|
|
0973e1ced0 | ||
|
|
e7b5c84024 | ||
|
|
4001cc2a89 | ||
|
|
6e57d77d10 | ||
|
|
2206d96a6e | ||
|
|
33f05858f1 | ||
|
|
8443b88c16 | ||
|
|
a4f204a5a3 | ||
|
|
31b6be2164 | ||
|
|
0dbf73067b | ||
|
|
775703e1f5 | ||
|
|
bcdfef4886 | ||
|
|
a19adb463b | ||
|
|
b71208299f | ||
|
|
136b5824b0 | ||
|
|
5004edbbb6 | ||
|
|
d8bdd6329a | ||
|
|
82d56637a0 | ||
|
|
7232a43816 | ||
|
|
098eb7d14b | ||
|
|
835b70ed48 | ||
|
|
9d828a86fb | ||
|
|
910551ee70 | ||
|
|
9beee548d4 | ||
|
|
f9189b265f | ||
|
|
6f3894f27b | ||
|
|
bbc9a5e62e | ||
|
|
94765da04a | ||
|
|
8caa82f123 | ||
|
|
8e1fcc6f14 | ||
|
|
21c4d0233d | ||
|
|
2f87d53af6 | ||
|
|
131ef31492 | ||
|
|
b6b067b1d3 | ||
|
|
546f10eca7 | ||
|
|
542839ba69 | ||
|
|
efab1bba08 | ||
|
|
8f986bc326 | ||
|
|
e155d32aaf | ||
|
|
743e0b7d66 | ||
|
|
1518d703f9 | ||
|
|
af9d851d67 | ||
|
|
20ca91f1c9 | ||
|
|
6fb3c0e49a | ||
|
|
5cb4ff5f5f | ||
|
|
d035c08a5c | ||
|
|
fda7b13f50 | ||
|
|
fb4aaed220 | ||
|
|
c5e1b37ead | ||
|
|
2be9d303ea | ||
|
|
99ac6d213a | ||
|
|
f595724971 | ||
|
|
0511ba147b | ||
|
|
760f48d850 | ||
|
|
bf296b4921 | ||
|
|
6f4aae8b7d | ||
|
|
01e5e74e64 | ||
|
|
b6565a5dee | ||
|
|
49886389cf | ||
|
|
643bf9068b | ||
|
|
53f3258246 | ||
|
|
1e9895ec84 | ||
|
|
52dacb03ae | ||
|
|
85b0f3f393 | ||
|
|
a04a91bf24 | ||
|
|
5e814f7ab5 | ||
|
|
2780f491d6 | ||
|
|
2d3ec7aff1 | ||
|
|
cb7baf2696 | ||
|
|
5cc069df78 | ||
|
|
9222476c1f | ||
|
|
f220755f99 | ||
|
|
d55bbf237c | ||
|
|
83a9d44c3a | ||
|
|
c5a802eaf6 | ||
|
|
2e27875460 | ||
|
|
cd66887faa | ||
|
|
d665c87c21 | ||
|
|
044d3102b6 | ||
|
|
c421a93b06 | ||
|
|
2865aa2272 | ||
|
|
7c4aae3e9d | ||
|
|
d9f671f2a8 | ||
|
|
69733b530b | ||
|
|
d4471c30bc | ||
|
|
185f7e03f5 | ||
|
|
a45eb0eb21 | ||
|
|
ed67142379 | ||
|
|
13169c6848 | ||
|
|
8be134ee2f | ||
|
|
8c3206a413 | ||
|
|
4d4c071b95 | ||
|
|
b82c27754f | ||
|
|
63afec184e | ||
|
|
65699f045e | ||
|
|
d02441a273 | ||
|
|
f76ab784b5 | ||
|
|
a566e36754 | ||
|
|
ef1479fde6 | ||
|
|
d6c7b271e4 | ||
|
|
df9acfa0d1 | ||
|
|
9d7fc8435e | ||
|
|
ab4492ac5c | ||
|
|
cb78dc8d88 | ||
|
|
020adabe26 | ||
|
|
08aca7a274 | ||
|
|
2a9ef2b9fe | ||
|
|
90dc489547 | ||
|
|
5ec5cfbc4a | ||
|
|
1ef65449a8 | ||
|
|
c6ff1ea72e | ||
|
|
cbfa8d3e78 | ||
|
|
5098ff3cfd | ||
|
|
7c32b07d3f | ||
|
|
b2142fed1d | ||
|
|
fef21516e9 | ||
|
|
6fba0f55c1 | ||
|
|
95d43de79f | ||
|
|
0be617b8ab | ||
|
|
c02d22f449 | ||
|
|
846b645856 | ||
|
|
25d93a771c | ||
|
|
f16c782336 | ||
|
|
ed0f315e41 | ||
|
|
d20b03f4c5 | ||
|
|
a3920413ac | ||
|
|
92bccf620f | ||
|
|
15f8a9ee71 | ||
|
|
dfb9ee755e | ||
|
|
68f4117e29 | ||
|
|
f74a24a056 | ||
|
|
e5750cbba0 | ||
|
|
c6482c82cd | ||
|
|
b4c23a9132 | ||
|
|
c470894bd3 | ||
|
|
a940e32f8d | ||
|
|
b6b59eccad | ||
|
|
b520b94fae | ||
|
|
9af99d16f7 | ||
|
|
2cc17d3280 | ||
|
|
20b2bcaa5a | ||
|
|
d1ada605fd | ||
|
|
d37f73626d | ||
|
|
73a94cb02e | ||
|
|
5a48945e50 | ||
|
|
476448a2ce | ||
|
|
bcba4f7fb7 | ||
|
|
20e9608ff3 | ||
|
|
3e5733ceaf | ||
|
|
af29c99b03 | ||
|
|
60d596a24f | ||
|
|
3f993e0466 | ||
|
|
9e3d22ee54 | ||
|
|
e86cb1981f | ||
|
|
e94f265adb | ||
|
|
b3fc35240a | ||
|
|
d74618bddb | ||
|
|
8b54a8d459 | ||
|
|
c17104a0fd | ||
|
|
88ca3ebbf3 | ||
|
|
4003456e5c | ||
|
|
1d21a52d28 | ||
|
|
2d49c62f85 | ||
|
|
20fac724f6 | ||
|
|
0807d73646 | ||
|
|
3013302f35 | ||
|
|
879e7bef21 | ||
|
|
557623dfe2 | ||
|
|
a28d1e176d | ||
|
|
f3c52db70d | ||
|
|
525575b0ba | ||
|
|
a55efeb378 | ||
|
|
da18b082a6 | ||
|
|
7511facebc | ||
|
|
1486f5cfc2 | ||
|
|
f8a16e5081 | ||
|
|
558e2d89ff | ||
|
|
ea7b688655 | ||
|
|
fdafa3435c | ||
|
|
f17c90f6ea | ||
|
|
1dc45669bb | ||
|
|
f3658614a4 | ||
|
|
cad39d8802 | ||
|
|
422f60f587 | ||
|
|
8190ddf0e9 | ||
|
|
af281d0bde | ||
|
|
00905b8c32 | ||
|
|
79b3cb8611 | ||
|
|
797e908621 | ||
|
|
3df3c395de | ||
|
|
b2f682ebb1 | ||
|
|
754e9bc158 | ||
|
|
2df8504139 | ||
|
|
2838f97c56 | ||
|
|
68c75deeeb | ||
|
|
3bc2659740 | ||
|
|
89bba8a67a | ||
|
|
cbd5cdaf92 | ||
|
|
5b9d5cbb55 | ||
|
|
6d6e13be83 | ||
|
|
899bec77b6 | ||
|
|
6dd8325772 | ||
|
|
e9bffd3ad7 | ||
|
|
ff890307c2 | ||
|
|
dda9d3e790 | ||
|
|
1acc02a2ec | ||
|
|
460c472c55 | ||
|
|
69c3b6142d | ||
|
|
9a036a4c01 | ||
|
|
0fbe206893 | ||
|
|
3cea6dd175 | ||
|
|
2fa053f833 | ||
|
|
ea4f858919 | ||
|
|
2aa4ab4b52 | ||
|
|
113977990e | ||
|
|
d86021e2a1 | ||
|
|
79faadef59 | ||
|
|
9b6629db7f | ||
|
|
d151a1ce65 | ||
|
|
d9ca7b483d | ||
|
|
708f7fc1b8 | ||
|
|
621db85845 | ||
|
|
b09066c580 | ||
|
|
7c45f28827 | ||
|
|
9c1b2cae22 | ||
|
|
16b2f81bd4 | ||
|
|
1969af65b9 | ||
|
|
3bd3ce5688 | ||
|
|
108f0e3d88 | ||
|
|
4344c127eb | ||
|
|
881a87216b | ||
|
|
1d473b82af | ||
|
|
6e0c005587 | ||
|
|
5955547c6f | ||
|
|
7a3a1cb8de | ||
|
|
1674cb0913 | ||
|
|
152ce4e923 | ||
|
|
3a6d024217 | ||
|
|
3b722068b0 | ||
|
|
f2fcc5ba79 | ||
|
|
db0fb8e287 | ||
|
|
dae36a67d1 | ||
|
|
6f99862288 | ||
|
|
0377747e1e | ||
|
|
6bc974f90f | ||
|
|
aca53de259 | ||
|
|
41bde38727 | ||
|
|
a4b7ee3b38 | ||
|
|
154b653c57 | ||
|
|
04489bc969 | ||
|
|
25499b3e7a | ||
|
|
bb6bda54dc | ||
|
|
9f3dbdc2d8 | ||
|
|
932148338c | ||
|
|
e0d0c38232 | ||
|
|
a0e732dc6e | ||
|
|
309f674853 | ||
|
|
ea20c2aa4c | ||
|
|
da5272c473 | ||
|
|
dc705c7720 | ||
|
|
a785e574e0 | ||
|
|
4b236b0268 | ||
|
|
1cd86ae5d6 | ||
|
|
b7489a0b14 | ||
|
|
f7554425dc | ||
|
|
9bfc577444 | ||
|
|
217d4cda42 | ||
|
|
08daa43aae | ||
|
|
184d889831 | ||
|
|
c6e0f431ae | ||
|
|
93daeab840 | ||
|
|
f132b297c6 | ||
|
|
858d2e908a | ||
|
|
b61e0b0c82 | ||
|
|
4d9a6e71c9 | ||
|
|
a0010de504 | ||
|
|
cb73a30e23 | ||
|
|
905d63e098 | ||
|
|
1ea8ab7dcc | ||
|
|
a45d21b279 | ||
|
|
3ca6d350fe | ||
|
|
2997ce8457 | ||
|
|
ff279d8310 | ||
|
|
9a7536b068 | ||
|
|
bd973e702e | ||
|
|
ede1851d34 | ||
|
|
cdb8ee0d8e | ||
|
|
e7d86a382c | ||
|
|
eb911df830 | ||
|
|
1c30acc53c | ||
|
|
32b0699235 | ||
|
|
e9c6580230 | ||
|
|
f893eb48fd | ||
|
|
232e2f18bf | ||
|
|
c266c99710 | ||
|
|
2a0e8f6d47 | ||
|
|
e6bd8ec529 | ||
|
|
911a95e873 | ||
|
|
447cfc2ae5 | ||
|
|
8dfeb0d260 | ||
|
|
caa7b19833 | ||
|
|
91d6fb62ba | ||
|
|
8f4a5b2015 | ||
|
|
07604c9a3b | ||
|
|
64dd8ae193 | ||
|
|
156ee6f290 | ||
|
|
c0a524a718 | ||
|
|
fe433d1417 | ||
|
|
efe8f34413 | ||
|
|
0dc662d295 | ||
|
|
081e370bfd | ||
|
|
3ef0567287 | ||
|
|
3cad9e5481 | ||
|
|
dc8b7908be | ||
|
|
85085a1eca | ||
|
|
bff59dee6e | ||
|
|
bb5828531a | ||
|
|
cd08e98c28 | ||
|
|
6bb23582c4 | ||
|
|
56ccb43997 | ||
|
|
bf7d40c9b5 | ||
|
|
5abb3d7f97 | ||
|
|
975ad8186a | ||
|
|
26b6419f04 | ||
|
|
331f7ca425 | ||
|
|
f445c05333 | ||
|
|
71c9ac0ff0 | ||
|
|
1404cc0ab5 | ||
|
|
a62485fc1f | ||
|
|
044f3080ff | ||
|
|
28b0cab906 | ||
|
|
82f34d651c | ||
|
|
6ca908a91d | ||
|
|
0948b8da9a | ||
|
|
22e04a53b3 | ||
|
|
df3217b41b | ||
|
|
9ff15a1913 | ||
|
|
89e43945a3 | ||
|
|
bd8dcddf35 | ||
|
|
dd372f3160 | ||
|
|
c56aab28d4 | ||
|
|
ca58801794 | ||
|
|
38fa5e2628 | ||
|
|
fae1ae7124 | ||
|
|
3ec5f76c73 | ||
|
|
5fbbcb5707 | ||
|
|
5bb8aaba55 | ||
|
|
23ba41e981 | ||
|
|
29b049452b | ||
|
|
0b0f665804 | ||
|
|
3cdf28ffdd | ||
|
|
5d53fba2ce | ||
|
|
c6fb18ea51 | ||
|
|
2d851cb134 | ||
|
|
92b0ef84e9 | ||
|
|
dc8d18384c | ||
|
|
f685be0aab | ||
|
|
44c056ecd5 | ||
|
|
dcc707c20d | ||
|
|
14cc851562 | ||
|
|
7223d1fd68 | ||
|
|
6ab5ad52ec | ||
|
|
079b2ae766 | ||
|
|
34eea827e7 | ||
|
|
06c6ee5a9c | ||
|
|
54748c20ea | ||
|
|
75f52e4cf4 | ||
|
|
55446dd4b1 | ||
|
|
02e63b6fc8 | ||
|
|
3aebcf66ec | ||
|
|
580a24eac3 | ||
|
|
07b1b22c2c | ||
|
|
49a5f716b7 | ||
|
|
23078eccba | ||
|
|
8b825d558f | ||
|
|
8a82f35edf | ||
|
|
bec664c073 | ||
|
|
9a7f97f11f | ||
|
|
95b8d62e4a | ||
|
|
3e61424d8f | ||
|
|
0bd7b4dab3 | ||
|
|
8cbada0cf5 | ||
|
|
f0784fec2d | ||
|
|
c1606c6fc9 | ||
|
|
cfe0c5e0a5 | ||
|
|
e0108cc99f | ||
|
|
c0de8a2591 | ||
|
|
b8733eed52 | ||
|
|
4aa30efec7 | ||
|
|
4354c3fb03 | ||
|
|
f25a676bd5 | ||
|
|
a9906762a0 | ||
|
|
7b5b88eca2 | ||
|
|
291682ba52 | ||
|
|
546d403b45 | ||
|
|
c9f40e32a1 | ||
|
|
d642b8dd07 | ||
|
|
549a11116e | ||
|
|
b5468a4a4f | ||
|
|
c5d278ccb7 | ||
|
|
bf5e15de45 | ||
|
|
f4af417267 | ||
|
|
69c04acaeb | ||
|
|
83a6bb188f | ||
|
|
494a8b5e91 | ||
|
|
c8021f2f08 | ||
|
|
8cdf3af047 | ||
|
|
c90a24e1ab | ||
|
|
83e3cfc34e | ||
|
|
133dd4e5b7 | ||
|
|
a8cca14b86 | ||
|
|
9b0f356fa4 | ||
|
|
f2b0a102b6 | ||
|
|
a7d01b230a | ||
|
|
2a7db4b9b8 | ||
|
|
77e5f2501d | ||
|
|
e6513717bd | ||
|
|
0e0c98f693 | ||
|
|
528bfa4e22 | ||
|
|
83c87779d7 | ||
|
|
dc33235144 | ||
|
|
935c658c5f | ||
|
|
0426f4fbf9 | ||
|
|
7930ec52fa | ||
|
|
75bc03fdcb | ||
|
|
57eb306e4f | ||
|
|
bd0511a9fb | ||
|
|
c14f3292aa | ||
|
|
46e771a062 | ||
|
|
4c0095844b | ||
|
|
10a2ef27d6 | ||
|
|
309ff3da13 | ||
|
|
3af91e2a74 | ||
|
|
2e027e3db7 | ||
|
|
fc40fb02c0 | ||
|
|
2c1c0299f9 | ||
|
|
3c7637515c | ||
|
|
08cbbaf68c | ||
|
|
6e911acf01 | ||
|
|
f6d82a1b28 | ||
|
|
702126aeab | ||
|
|
df566e8fcb | ||
|
|
c83e0f33b5 | ||
|
|
f279f936b6 | ||
|
|
f8e20bafd9 | ||
|
|
a5211ca7ff | ||
|
|
0a5abdb331 | ||
|
|
4ed7413dd2 | ||
|
|
40b66c24fc | ||
|
|
2f7bedf001 | ||
|
|
44c31a9ddb | ||
|
|
20a35de3d8 | ||
|
|
cfc8a15db1 | ||
|
|
d5ba22a86c | ||
|
|
a38505f1c5 | ||
|
|
3cf1ba1fb3 | ||
|
|
9f5d595827 | ||
|
|
75673f0204 | ||
|
|
b43a7571ff | ||
|
|
b071dc04d3 | ||
|
|
12f9ff1647 | ||
|
|
3256a302df | ||
|
|
edbe447477 | ||
|
|
2f43910368 | ||
|
|
c2e87e4cfa | ||
|
|
e40d2ee806 | ||
|
|
ba74e7603b | ||
|
|
19c3fe6dc4 | ||
|
|
d0778dd124 | ||
|
|
8fb2b1cf3d | ||
|
|
5c7d1cbe10 | ||
|
|
3348bfc362 | ||
|
|
8db9252434 | ||
|
|
d9d4fe15cc | ||
|
|
8453e2d514 | ||
|
|
4d3070c0c2 | ||
|
|
15f273a9ef | ||
|
|
007c079cd6 | ||
|
|
9851b1e8ad | ||
|
|
c65c2610cd | ||
|
|
2be08a3fcc | ||
|
|
4cdf440eee | ||
|
|
1545ee8fc2 | ||
|
|
a009fabfe4 | ||
|
|
46e4bfdd13 | ||
|
|
e24c82860d | ||
|
|
1291dee1e1 | ||
|
|
69732be09b | ||
|
|
8ce4ed7dee | ||
|
|
33d3cc0104 | ||
|
|
33b6b519ed | ||
|
|
5ec849f4e3 | ||
|
|
6f4a2d2cd5 | ||
|
|
0bca89e77d | ||
|
|
8f9f162011 | ||
|
|
ac3bea3d5e | ||
|
|
178d4f199b | ||
|
|
933819380d | ||
|
|
d68131e1b9 | ||
|
|
ffbc379c0b | ||
|
|
7053d12508 | ||
|
|
d41442200f | ||
|
|
d2564c343e | ||
|
|
d1c19e8837 | ||
|
|
0ec3bfabad | ||
|
|
43ffb04621 | ||
|
|
eeaedf76a9 | ||
|
|
120fb4829a | ||
|
|
12b93e53a7 | ||
|
|
00b17b9764 | ||
|
|
06192f809d | ||
|
|
d09eb0d3ff | ||
|
|
416321679c | ||
|
|
72c4e6e54f | ||
|
|
d3738e3424 | ||
|
|
2e183db903 | ||
|
|
fa174b88dd | ||
|
|
0b4abe6d16 | ||
|
|
759fb0ecd8 | ||
|
|
429e927c3f | ||
|
|
643aed9133 | ||
|
|
e701642de2 | ||
|
|
a3a03e3da6 | ||
|
|
5dd3d5b552 | ||
|
|
1bae246e6c | ||
|
|
c94ef10189 | ||
|
|
dfacf1f9df | ||
|
|
06c5e34f48 | ||
|
|
1ab8b35e98 | ||
|
|
261b61ee6c | ||
|
|
5abf8e1bab | ||
|
|
7c0b463637 | ||
|
|
e156ab2432 | ||
|
|
0b46a7163b | ||
|
|
21b76c008a | ||
|
|
b7e9f8248d | ||
|
|
8b21e76a36 | ||
|
|
515471185f | ||
|
|
628e8f4e5e |
No files matched your search
@@ -157,6 +157,9 @@ build:
|
||||
paths:
|
||||
- neoforge/build/libs
|
||||
- fabric/build/libs
|
||||
exclude:
|
||||
- neoforge/build/libs/*-dev-shadow.jar
|
||||
- fabric/build/libs/*-dev-shadow.jar
|
||||
|
||||
publish:
|
||||
stage: deploy
|
||||
|
||||
+24
-34
@@ -1,41 +1,31 @@
|
||||
# Changelog
|
||||
## [1.8.1 (September 13th, 2026)](#1-8-1)
|
||||
## [1.8.x (MONTH xth, 2026)](#1-8-x)
|
||||
|
||||
### Additions
|
||||
|
||||
### New rideable Pokémon
|
||||
|
||||
### Pokémon Added
|
||||
|
||||
### Model updates for the following Pokémon
|
||||
|
||||
### Animation updates for the following Pokémon
|
||||
- Flapple, Appletun, Dipplin, Hydrapple (placeholder animations only).
|
||||
|
||||
### Changes
|
||||
- Reworked spawn presets and added structure tags for spawns
|
||||
|
||||
### Fixes
|
||||
- Fixed a potential world generation stall caused by Type Gem clusters.
|
||||
- Fixed Pokémon on shoulders not animating if they are visibly holding a held item.
|
||||
- Fixed NPCs getting their own TM progress file.
|
||||
- Fixed odd NeoForge crash due to a dependency being outdated. It will now correctly flag the dependency for being outdated.
|
||||
- Fixed the TM Machine refusing move selections that the `unlockAllMoveDexMovesByDefault` config had made visible, leaving the move slot blank and the craft button unresponsive.
|
||||
- Fixed the Beast Ball not applying its 0.1× penalty to Pokémon that are not Ultra Beasts.
|
||||
- Fixed the Moon Ball reading the world's total elapsed time instead of the time of day, so its bonus applied during the day instead.
|
||||
- Fixed the Love Ball never awarding its 2.5× opposite gender bonus, and matching species by display name instead of identifier.
|
||||
- Fixed the Timer Ball being weaker than a regular Poké Ball on early turns and only reaching 4× on turn 14 instead of turn 10.
|
||||
- Fixed the Luxury Ball only doubling Friendship gains of 1, leaving level up and berry gains unaffected.
|
||||
- Fixed Rufflet, Braviary, and Hisuian Braviary flooding logs with errors about missing sound events.
|
||||
- Fixed Pokémon property typos in Pangoro and Dedenne herd spawn files.
|
||||
- Fixed unintentional watermelon cosmetic for Hisuian Electrode.
|
||||
- Fixed Blastoise having an unintentional passenger seat.
|
||||
- Fixed form typo in Rockruff-Dusk spawn, preventing it from being able to spawn.
|
||||
- Fixed the `stat_compare` and `stat_equal` evolution requirements crashing the game when given `special_attack` or `special_defence`, and made them treat any non-permanent stat as a failed requirement instead of crashing.
|
||||
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait.
|
||||
- Fixed Ampharos' eyes not emitting particles.
|
||||
- Fixed Alolan Raticate's cry not playing.
|
||||
- Fixed several incorrect light-based spawning conditions for ghosts and bugs.
|
||||
- Removed unused, placeholder habitat pools.
|
||||
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to having an expression assigned as a recoil animation.
|
||||
- Fixed Lucario's aura particles not emitting.
|
||||
- Fixed Lycanroc's head movement.
|
||||
- Added head movement to Archaludon.
|
||||
- Fixed Duraludon line not having their sleep animations.
|
||||
- Fixed Diglett line not having their faint animations.
|
||||
- Fixed Kingambit moving its legs while walking.
|
||||
- Fixed blank Cooking Pot recipe book entries and missing tabs on Fabric.
|
||||
- Fixed Minior's "Shields Down" ability not working as intended
|
||||
|
||||
### Developer
|
||||
|
||||
### Molang & Datapacks
|
||||
- Added `q.target_fainted` function to move action effects that checks if any opponents fainted from the move.
|
||||
|
||||
### Particles
|
||||
|
||||
#### New Move Animations
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
- Brazilian Portuguese
|
||||
- Simplified Chinese
|
||||
- Japanese
|
||||
- Updated translations for:
|
||||
@@ -1,9 +0,0 @@
|
||||
- Some kind of particle effect for them? they're meant to have glowing eyes, so maybe some red particles on the eye locator
|
||||
--- global posers? stacked posers?
|
||||
--- red texture seems to be what some people wanted.
|
||||
|
||||
/- shoulder mounts part of it
|
||||
- omni boost
|
||||
- roar when player comes within reach, adjust levels
|
||||
|
||||
- Riding settings to control what the min scale is for a pokemon to be ridable.
|
||||
+5
-2
@@ -1,3 +1,6 @@
|
||||
import utilities.gitBranch
|
||||
import utilities.gitShortCommit
|
||||
|
||||
plugins {
|
||||
id("cobblemon.root-conventions")
|
||||
}
|
||||
@@ -6,6 +9,6 @@ version = "${project.property("mod_version")}+${project.property("mc_version")}"
|
||||
|
||||
val isSnapshot = project.property("snapshot")?.equals("true") == true
|
||||
if (isSnapshot) {
|
||||
val fixedBranchName = versioning.info.branch.substringAfter("/")
|
||||
version = "$version-${fixedBranchName}-${versioning.info.build}"
|
||||
val fixedBranchName = gitBranch().substringAfter("/")
|
||||
version = "$version-${fixedBranchName}-${gitShortCommit()}"
|
||||
}
|
||||
@@ -36,6 +36,7 @@
|
||||
- Added `cobblemon:catch_alpha_pokemon` advancement trigger and `totalAlphaCaptureCount` stat for captured Alpha Pokémon.
|
||||
|
||||
### New rideable Pokémon
|
||||
- Pidgeot
|
||||
- Crobat
|
||||
- Skarmory
|
||||
- Milotic
|
||||
@@ -106,7 +107,9 @@
|
||||
- Avalugg (Hisuian Form)
|
||||
|
||||
### Model updates for the following Pokémon
|
||||
|
||||
- Pidgey
|
||||
- Pidgeotto
|
||||
- Pidgeot
|
||||
- Sandshrew
|
||||
- Sandslash
|
||||
- Nidoking
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Changelog
|
||||
## [1.8.1 (September 13th, 2026)](#1-8-1)
|
||||
|
||||
### Animation updates for the following Pokémon
|
||||
- Flapple, Appletun, Dipplin, Hydrapple (placeholder animations only).
|
||||
|
||||
### Fixes
|
||||
- Fixed a potential world generation stall caused by Type Gem clusters.
|
||||
- Fixed Pokémon on shoulders not animating if they are visibly holding a held item.
|
||||
- Fixed NPCs getting their own TM progress file.
|
||||
- Fixed odd NeoForge crash due to a dependency being outdated. It will now correctly flag the dependency for being outdated.
|
||||
- Fixed the TM Machine refusing move selections that the `unlockAllMoveDexMovesByDefault` config had made visible, leaving the move slot blank and the craft button unresponsive.
|
||||
- Fixed the Beast Ball not applying its 0.1× penalty to Pokémon that are not Ultra Beasts.
|
||||
- Fixed the Moon Ball reading the world's total elapsed time instead of the time of day, so its bonus applied during the day instead.
|
||||
- Fixed the Love Ball never awarding its 2.5× opposite gender bonus, and matching species by display name instead of identifier.
|
||||
- Fixed the Timer Ball being weaker than a regular Poké Ball on early turns and only reaching 4× on turn 14 instead of turn 10.
|
||||
- Fixed the Luxury Ball only doubling Friendship gains of 1, leaving level up and berry gains unaffected.
|
||||
- Fixed Rufflet, Braviary, and Hisuian Braviary flooding logs with errors about missing sound events.
|
||||
- Fixed Pokémon property typos in Pangoro and Dedenne herd spawn files.
|
||||
- Fixed unintentional watermelon cosmetic for Hisuian Electrode.
|
||||
- Fixed Blastoise having an unintentional passenger seat.
|
||||
- Fixed form typo in Rockruff-Dusk spawn, preventing it from being able to spawn.
|
||||
- Fixed the `stat_compare` and `stat_equal` evolution requirements crashing the game when given `special_attack` or `special_defence`, and made them treat any non-permanent stat as a failed requirement instead of crashing.
|
||||
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait.
|
||||
- Fixed Ampharos' eyes not emitting particles.
|
||||
- Fixed Alolan Raticate's cry not playing.
|
||||
- Fixed several incorrect light-based spawning conditions for ghosts and bugs.
|
||||
- Removed unused, placeholder habitat pools.
|
||||
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to having an expression assigned as a recoil animation.
|
||||
- Fixed Lucario's aura particles not emitting.
|
||||
- Fixed Lycanroc's head movement.
|
||||
- Added head movement to Archaludon.
|
||||
- Fixed Duraludon line not having their sleep animations.
|
||||
- Fixed Diglett line not having their faint animations.
|
||||
- Fixed Kingambit moving its legs while walking.
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
- Brazilian Portuguese
|
||||
- Simplified Chinese
|
||||
- Japanese
|
||||
@@ -0,0 +1,211 @@
|
||||
# Changelog
|
||||
## [1.8.2 (October 11th, 2026)](#1-8-2)
|
||||
|
||||
### Additions
|
||||
- The following items can now be placed on the ground as blocks:
|
||||
- King's Rock, Galarica Cuff, Unremarkable Teacup, Masterpiece Teacup, Chipped Pot, Cracked Pot, Sachet, Metal Powder, Quick Powder, Bright Powder, Soft Sand, Electirizer, Magmarizer, Icy Rock, Heat Rock, Smooth Rock, Damp Rock, Destiny Knot, Luminous Moss, Never-Melt Ice, Wide Lens, Scope Lens, and Zoom Lens.
|
||||
- I wasn't finished. Iron Ball, Metal Coat, Throat Spray, Terrain Extender, Medicinal Brew, Moomoo Milk, HP Up, Protein, Iron, Carbos, Calcium, Zinc, PP Up, PP Max, Upgrade, Dubious Disc, and Loaded Dice.
|
||||
- The following items can now be placed on various surfaces:
|
||||
- Galarica Wreath, Magnet, Flame Orb, Life Orb, Toxic Orb, Light Ball, and Smoke Ball.
|
||||
- The following items can now be placed as blocks and have additional functionality:
|
||||
- Ring Target, Metronome, and Eject Button.
|
||||
- The following items can now be worn by Players and Pokémon:
|
||||
- Galarica Wreath, Cleanse Tag, Spell Tag, Sharp Beak, Covert Cloak, Shell Helmet, and Never-Melt Ice.
|
||||
- Wearable items can now be equipped by a player by holding it and right-clicking (or whatever you've rebound use/interact to).
|
||||
- Wearable items that can be placed down are exempt from this behaviour.
|
||||
- Mint Leaves can now be placed on the ground as Leaf Litter, or in Flower Pots.
|
||||
- The recall animation now renders the ball in hand with the player's arm raised.
|
||||
- Added Oricorio Flowers.
|
||||
- Added Terrain Plants, four plants grown from Electric, Grassy, Misty, and Psychic Seeds, which glow under specific environmental conditions and can be farmed for more of their seeds.
|
||||
- Berries can now be placed in Flower Pots.
|
||||
- The evolution tree for a Pokémon can now be viewed in the Pokédex, showing the Pokémon's full family line and what's needed to evolve into each stage.
|
||||
- Added custom sounds to a bunch of items that can be placed as blocks: liquid bottles, pill bottles, powders, and dice.
|
||||
- Added paper as a new cosmetic item for Pikachu.
|
||||
- Burmy changes its cloak after a battle it took part in, based on the biome.
|
||||
- Trash Cloak in the Deep Dark, Nether and End.
|
||||
- Plant Cloak in lush biomes such as forests, jungles, plains and swamps.
|
||||
- Sandy Cloak in sandy biomes, beaches and caves.
|
||||
|
||||
### Pokémon Added
|
||||
#### Generation 4
|
||||
- Snover
|
||||
- Abomasnow
|
||||
- Burmy
|
||||
- Wormadam
|
||||
- Mothim
|
||||
|
||||
#### Generation 5
|
||||
- Tympole
|
||||
- Palpitoad
|
||||
- Seismitoad
|
||||
|
||||
#### Generation 6
|
||||
- Helioptile
|
||||
- Heliolisk
|
||||
|
||||
#### Generation 7
|
||||
- Grubbin
|
||||
- Charjabug
|
||||
- Vikavolt
|
||||
- Minior
|
||||
|
||||
#### Generation 8
|
||||
- Eternatus
|
||||
|
||||
#### Generation 9
|
||||
- Greavard
|
||||
- Houndstone
|
||||
|
||||
### Model updates for the following Pokémon
|
||||
- Sudowoodo
|
||||
- Spheal
|
||||
- Cacnea
|
||||
- Cacturne
|
||||
- Binacle
|
||||
- Barbaracle
|
||||
- Glimmet
|
||||
- Glimmora
|
||||
- Deerling
|
||||
- Sawsbuck (All forms)
|
||||
- Absol
|
||||
- Poliwag
|
||||
- Politoed
|
||||
- Ekans
|
||||
- Oddish
|
||||
- Gloom
|
||||
- Vileplume
|
||||
- Bellossom
|
||||
- Shroomish
|
||||
- Chespin
|
||||
- Quilladin
|
||||
- Chesnaught
|
||||
- Fennekin
|
||||
- Braixen
|
||||
- Delphox
|
||||
- Froakie
|
||||
- Frogadier
|
||||
- Greninja
|
||||
- Shinx
|
||||
- Luxio
|
||||
- Luxray
|
||||
- Slakoth
|
||||
- Riolu
|
||||
- Lucario
|
||||
- Corsola
|
||||
- Mime Jr.
|
||||
|
||||
### Animation updates for the following Pokémon
|
||||
- Treecko (New Shoulder Mount!)
|
||||
- Bonsly (New Shoulder Mount!)
|
||||
- Sudowoodo
|
||||
- Cacnea
|
||||
- Cacturne
|
||||
- Ekans
|
||||
- Arbok
|
||||
- Oddish
|
||||
- Gloom
|
||||
- Vileplume
|
||||
- Bellossom
|
||||
- Pichu
|
||||
- Pikachu
|
||||
- Raichu (+ Alolan)
|
||||
- Greninja
|
||||
- Slakoth
|
||||
|
||||
### Changes
|
||||
- Slowed the speed of Type Gem growth, and revamped their generation to grow in a more crystal-like shape.
|
||||
- Terrain Seeds can now be planted.
|
||||
- Terrain Seeds are now consumed during PvP and NPC battles.
|
||||
- Glow Item Frames can be crafted using Bright Powder as an alternative to Glow Ink Sacs.
|
||||
- Bright Powder can be used on Signs and Hanging Signs to make text glow, as an alternative to Glow Ink Sacs.
|
||||
- Updated various recipes to display in Recipe Groups within the Recipe Book.
|
||||
- Updated most recipes to display more logically and consistently within the Recipe Book's categories.
|
||||
- Updated the following recipes
|
||||
- Never-Melt Ice, Focus Sash, Blunder Policy, Weakness Policy, Blank TM.
|
||||
- Made ride rotations less jittery in multiplayer.
|
||||
- Changes Disc Shelf TMs to use textures instead of tinting and overlays to line up with item display.
|
||||
- Blank TM can now be inserted into the Disc Shelf.
|
||||
- Added a TM creative tab.
|
||||
- Made regional and Vivillon split-evo locations mutually exclusive.
|
||||
- Vivillon's patterns are now viewable as forms with form names and unique Pokédex entries displayed.
|
||||
- Item evolutions now unlock **all** locked evolutions whose requirements are met instead of stopping after the first.
|
||||
- Exceptions being "forced" evolutions which stop after the first one.
|
||||
- Removed Mega Drain as a TM.
|
||||
- PokeSnacks (and other area spawns) will now start the vertical offset checking from the center rather than base coordinate
|
||||
- This will help PokeSnacks to work inside caves or tighter spaces better.
|
||||
|
||||
### Fixes
|
||||
- Type Gem blocks now properly generate clusters off of the block.
|
||||
- Correctly grant "They're Minerals!" advancement when planting a Type Gem.
|
||||
- Shoulder mounted Pokémon now follow player body animations, improving their positioning with custom player animations and default actions.
|
||||
- Shoulder mounted Pokémon now sit slightly higher with chest armor equipped for better alignment, with a configurable blacklist.
|
||||
- Type Gem Blocks now have advancements to give you the recipe in the recipe book (the data for the advancements were inside the recipes somehow??).
|
||||
- Some other recipes without advancements have also been corrected.
|
||||
- Corrected the TM Recipes for Charge Beam and Payday.
|
||||
- Starter Screen Categories will no longer stop rendering if they are bigger than the viewport and you are not viewing the top or bottom edges.
|
||||
- The Random Starter option no longer runs off into other categories or becomes unaccessible due to scroll limits if it causes a new row.
|
||||
- Mochi and Feathers no longer drop glass bottles on use.
|
||||
- Fixed a rare set of concurrency bugs related to reloading packs on NeoForge; fixes a ModernFix compatibility issue.
|
||||
- Removed unnecessary conditions for Mimikyu's standing pose. Hopefully fixes the rare instance of it using its shoulder animations during battle in multiplayer scenarios.
|
||||
- Fixed Rockruff line not blinking.
|
||||
- Fixed Flygon sometimes using its running animation instead of walking.
|
||||
- Fixed Bramblin always sleeping outside of battle.
|
||||
- Fixed Dragonair using its battle idle as a normal idle.
|
||||
- Fixed Lycanroc's head movement.
|
||||
- Added head movement to Archaludon.
|
||||
- Fixed Duraludon line not having any sleep animations.
|
||||
- Fixed Diglett line not having their faint animations.
|
||||
- Fixed Kingambit moving its legs while walking.
|
||||
- Fixed Alolan Diglett's cry not playing.
|
||||
- Fixed shiny alpha Snorlax using the non-shiny texture.
|
||||
- Fixed Crabominable's head breaking when looking around.
|
||||
- Fixed Shiny Dipplin and Hydrapple's textures.
|
||||
- Various structure fixes.
|
||||
- Fixed shiny Pokémon under certain circumstances forgetting taught TMs.
|
||||
- Fixed certain Pokémon sounds sometimes being doubled when played at specific times in their animations.
|
||||
- Fixed ball capture beam origin coming from behind the ball.
|
||||
- Fixed Safari Ball catch rate outside of battle.
|
||||
- Fixed alpha eye (and other) particles not respecting the client particle density setting in video options.
|
||||
- Fixed the TM Machine UI not closing when player is moving away from it.
|
||||
- Moved the config category for alpha/shiny chance config fields for fossil machine and honey log slathering to Spawning.
|
||||
- Fixed crashes for addon species with non-Cobblemon namespaces in evolution, fishing bait, and Pokédex lookups.
|
||||
- Bees will now grow Apricorns and the Galarica Nut Bush, which were in the bee growables tag but never handled, and correctly spend their nectar when they do.
|
||||
- Saccharine Leaves can now be bone mealed.
|
||||
- Fixed equipping a stack of wearable items instead of one item of a stack.
|
||||
- Fixed TM stack consumption in TM Machine screen when a player teaches a move to a Pokémon.
|
||||
- Fixed every Miltank giving mushroom stew, not only mushroom variants.
|
||||
- Medicinal Leeks can now be eaten when they aren't being planted, matching their food properties.
|
||||
- Sweeping attacks no longer set owned Pokémon on fire (e.g. with Fire Aspect) or knock them back. Friendly Fire is now off.
|
||||
- Fixed Pokémon not losing their previous form's signature moves when changing forms (e.g. Rotom keeping Overheat after leaving its Heat form).
|
||||
- Form-specific moves gained from a form change are now only granted if the Pokémon is high enough level to learn them, and no longer create duplicates in benched moves.
|
||||
- Spinda now learns Teeter Dance at level 32 instead of via TM.
|
||||
- Fixed Oricorio Pa'u form description in the Pokédex.
|
||||
- Fixed PC playing sound on close when opened via command.
|
||||
|
||||
### Developer
|
||||
- Evolution requirements can now set `pokedexIcon` and `pokedexTooltip` directly in their JSON to control how they're shown on the Pokédex's evolution tree, for example:
|
||||
```json
|
||||
{
|
||||
"variant": "attack_defence_ratio",
|
||||
"pokedexIcon": "minecraft:diamond_sword",
|
||||
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.attack_defence_ratio.tooltip"
|
||||
}
|
||||
```
|
||||
- Fixed the wrong order of operations in the code flow prior to the `FOSSIL_REVIVED` event causing Pokémon to appear without the entity link.
|
||||
- Fixed TestPartySlot command not correctly comparing the provided spec with the target Pokémon.
|
||||
|
||||
### Molang & Datapacks
|
||||
- A new `negate` variant for evolution requirements has been added, which will negate (invert) the provided inner condition.
|
||||
- Added Molang functions for individual Pokédex ids, wherein a subdex is passed in as a string to `dex_caught_count` etc.
|
||||
- Added a `battle_ended` callback, backed by the new `BATTLE_ENDED` event, which fires whenever a battle ends for any reason, alongside a multitude of flags not previously covered by `BATTLE_VICTORY`.
|
||||
- Added `battle_pokemon` to battle actor structs and `has_faced_opponents` to battle Pokémon structs.
|
||||
- Added the `cobblemon:form_change/bagworm_cloak/plant_cloak`, `sandy_cloak` and `trash_cloak` biome tags, which control Burmy's cloak after battle.
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
- Brazilian Portuguese
|
||||
- Canadian French
|
||||
- Japanese
|
||||
- Korean
|
||||
- Russian
|
||||
- Ukrainian
|
||||
@@ -0,0 +1,107 @@
|
||||
# Changelog
|
||||
## [1.8.3 (October 25th, 2026)](#1-8-3)
|
||||
|
||||
### Additions
|
||||
- Added compatability with AppleSkin.
|
||||
|
||||
### New Rideable Pokémon
|
||||
- Vikavolt
|
||||
- Fearow
|
||||
|
||||
### Particles
|
||||
## New Move Animations
|
||||
- Water Shuriken
|
||||
|
||||
### Cries added/updated for the following Pokémon
|
||||
- Hoppip
|
||||
- Skiploom
|
||||
- Jumpluff
|
||||
- Houndour
|
||||
- Houndoom
|
||||
- Wurmple
|
||||
- Silcoon
|
||||
- Beautifly
|
||||
- Cascoon
|
||||
- Dustox
|
||||
- Surskit
|
||||
- Masquerain
|
||||
- Torkoal
|
||||
- Starly
|
||||
- Staravia
|
||||
- Staraptor
|
||||
- Combee
|
||||
- Vespiquen
|
||||
- Dusknoir
|
||||
- Helioptile
|
||||
- Heliolisk
|
||||
- Grubbin
|
||||
- Charjabug
|
||||
- Vikavolt
|
||||
- Applin (updated)
|
||||
- Flapple (updated)
|
||||
- Appletun
|
||||
- Runerigus
|
||||
- Dipplin
|
||||
|
||||
### Animation updates for the following Pokémon
|
||||
- Vikavolt
|
||||
- Magby
|
||||
- Magmar
|
||||
- Ninetales (+ Alolan)
|
||||
- Spearow
|
||||
- Fearow
|
||||
- Squawkabilly
|
||||
- Mudkip
|
||||
- Marshtomp
|
||||
- Swampert
|
||||
|
||||
### Changes
|
||||
- Changed the Misc icon in the Campfire Pot Recipe Book to Health Candy.
|
||||
- Moved EXP Candies from the Medicine recipe book category to the Misc category.
|
||||
- Removed Dynamic Lights data from the Magmarizer item for consistency with the block.
|
||||
- Added `doPokemonPredation` game rule, which controls aggressiveness of Pokémon with the `hate_entity` brain task defined.
|
||||
- Added `predation_chance` server config value, which controls the possibility of attacking the hated entity found in `hate_entity` brain tasks.
|
||||
- Prehistoric Eroded Pillar generates slightly more frequently.
|
||||
- Togekiss was changed from a jet-style mount to bird-style.
|
||||
|
||||
### Fixes
|
||||
- Fixed datapacks with item interaction evolutions that don't specify an item crashing the world on load.
|
||||
- Fixed the Pokédex evolution tree crashing when a datapack's dex entry is missing an evolution's form.
|
||||
- Fixed the Pokédex evolution tree crashing with a stack overflow on evolutions that loop back on themselves.
|
||||
- Fixed the Pokédex evolution tree crashing when looking at a dex that is not part of the "National Dex" aggregate.
|
||||
- Fixed some display names in the Pokédex evolution tree having unintended font colours.
|
||||
- Fixed evolution trees not building properly when a Pokémon has several Pokédex entries for different regions.
|
||||
- Fixed form-changing evolutions sometimes resolving to the source form in the Pokédex evolution tree.
|
||||
- Broken datapack evolutions are now logged and skipped in the Pokédex evolution tree instead of crashing.
|
||||
- Fixed the `alpha` property having no tab completion in commands such as `/pokespawn`, `/pokegive` and `/pokeedit`, and added the missing `dmax_level`, `gmax_factor` and `tradable` aliases to tab completion.
|
||||
- Fixed friendship booster items not being recognised as such.
|
||||
- Fixed conflicts with other mods that would make use of recipe books in certain ways (Farmer's Delight Cooking Pot).
|
||||
- Fixed Pokémon battles stalling when a sidemod cancels the `THROWN_POKEBALL_HIT` hit event.
|
||||
- Fixed choice feature tab completion in `spawnpokemon/pokegive/pokeedit` commands.
|
||||
- Fixed MoLang that's targeting players intermittedly failing when they have died, requiring them to relog.
|
||||
- Fixed a NeoForge bug where none of the Cobblemon plants/berries could be placed into pots.
|
||||
- Fixed party overlay not hiding when the player opens the Pokédex scanner.
|
||||
- Fixed `hate_entity` task focusing on its own entity, therefore enabling deadly warfare between Zangoose and Seviper.
|
||||
- Prehistoric Frozen Pond, Prehistoric Frozen Spike, and Prehistoric Lush Den now actually generate (oops).
|
||||
- Fixed Frogadier floating in its `battle_idle` animation.
|
||||
- Fixed absence or incorrect mergers in spawning conditions, causing the max height/depth of conditions to use the min value instead.
|
||||
- Added stricter riding checks on server side to prevent possible packet forgery when a player tries to ride Pokémon.
|
||||
- Fixed Pokémon cries and animation sounds played from GUIs, such as the starter selection screen and the Pokédex, being tied to the Master volume slider instead of Friendly Creatures.
|
||||
- Fixed Archeops T-posing during its `air_cry` animation.
|
||||
|
||||
### Developer
|
||||
- Bumped NeoForge to `21.1.195` to get access to MixinExtras `0.5` and the expressions annotation.
|
||||
- New Platform Event `PLAYER_RESPAWN` which is fired when a player respawns (either through death or returning from the end).
|
||||
- Platform Events `SERVER_PLAYER_TICK_PRE` and `SERVER_PLAYER_TICK_POST` no longer fire when the player is dead.
|
||||
- It is now tied much closer to the actual entity rather than the player or their connection.
|
||||
|
||||
### Molang & Datapacks
|
||||
- Fixed `q.pokemon.nature` returning class instance instead of proper string value (`cobblemon:adamant`).
|
||||
- Fixed `q.entity.is_looking_at` result being always `1` for whatever entity passed as the target parameter.
|
||||
- Added hate chance optional parameter to `hate_entity` task. `1.0` will make an entity always attack the target.
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
- Russian
|
||||
- Japanese
|
||||
- Brazilian Portuguese
|
||||
+15
-6
@@ -1,9 +1,8 @@
|
||||
|
||||
import utilities.isSnapshot
|
||||
import utilities.version
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import utilities.gitBranch
|
||||
import utilities.gitCommit
|
||||
|
||||
/*
|
||||
*
|
||||
@@ -72,6 +71,14 @@ tasks.withType<Test> {
|
||||
}
|
||||
}
|
||||
|
||||
// Git info lives in its own output dir (not processResources) so a new commit only rewrites this one file.
|
||||
val writeGitInfo by tasks.registering(WriteProperties::class) {
|
||||
destinationFile = layout.buildDirectory.file("generated/gitInfo/cobblemon-git.properties")
|
||||
property("commit", gitCommit())
|
||||
property("branch", gitBranch())
|
||||
}
|
||||
sourceSets.main { output.dir(mapOf("builtBy" to writeGitInfo), layout.buildDirectory.dir("generated/gitInfo")) }
|
||||
|
||||
tasks.processResources {
|
||||
exclude("data/**/README.md")
|
||||
}
|
||||
@@ -98,11 +105,13 @@ sourceSets {
|
||||
property("modid", "cobblemon")
|
||||
property("version", project.version())
|
||||
property("isSnapshot", if(rootProject.isSnapshot()) "true" else "false")
|
||||
property("gitCommit", versioning.info.commit)
|
||||
property("branch", versioning.info.branch)
|
||||
System.getProperty("buildNumber")?.let { property("buildNumber", it) }
|
||||
property("timestamp", OffsetDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm:ss")) + " UTC")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The platform jars are built from :common:jar, so the remapped common jars are only needed by `publish`
|
||||
tasks.assemble {
|
||||
setDependsOn(dependsOn.filterNot { (it as? TaskProvider<*>)?.name in setOf("remapJar", "remapSourcesJar") })
|
||||
}
|
||||
@@ -8,54 +8,42 @@
|
||||
|
||||
package com.cobblemon.mod.common.mixin;
|
||||
|
||||
import com.cobblemon.mod.common.block.SaccharineLeafBlock;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.animal.Bee;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.BonemealableBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
|
||||
@Mixin(targets = "net.minecraft.world.entity.animal.Bee$BeeGrowCropGoal")
|
||||
public abstract class BeeEntityMixin {
|
||||
|
||||
@Unique
|
||||
private BlockState cobblemon$result = null;
|
||||
|
||||
@Inject(method = "tick()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/block/BonemealableBlock;performBonemeal(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/util/RandomSource;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)V",
|
||||
shift = At.Shift.BY,
|
||||
by = 2
|
||||
),
|
||||
locals = LocalCapture.CAPTURE_FAILHARD
|
||||
)
|
||||
private void injectCustomGrowth(CallbackInfo ci, int i, BlockPos blockPos, BlockState blockState, Block block) {
|
||||
if (block instanceof SaccharineLeafBlock) {
|
||||
|
||||
int age = blockState.getValue(SaccharineLeafBlock.Companion.getAGE());
|
||||
boolean waterlogged = blockState.getValue(BlockStateProperties.WATERLOGGED);
|
||||
|
||||
if (age < 2 && !waterlogged) {
|
||||
this.cobblemon$result = blockState.setValue(SaccharineLeafBlock.Companion.getAGE(), age + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@Shadow(aliases = {"this$0", "field_20373"}) @Final private Bee bee;
|
||||
|
||||
// Grows bonemealable blocks vanilla's hardcoded checks miss, returning the state so it handles the rest.
|
||||
@ModifyVariable(method = "tick", at = @At(value = "LOAD", ordinal = 0), index = 5)
|
||||
private BlockState applyCustomBlockState(BlockState before) {
|
||||
if(this.cobblemon$result != null && before == null) {
|
||||
BlockState result = this.cobblemon$result;
|
||||
this.cobblemon$result = null;
|
||||
return result;
|
||||
private BlockState cobblemon$growBonemealableBlocks(BlockState grownState, @Local(ordinal = 0) BlockState blockState, @Local BlockPos pos) {
|
||||
if (grownState != null || blockState.is(Blocks.CAVE_VINES) || blockState.is(Blocks.CAVE_VINES_PLANT)) {
|
||||
return grownState;
|
||||
}
|
||||
|
||||
return before;
|
||||
if (!(blockState.getBlock() instanceof BonemealableBlock bonemealable)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ServerLevel level = (ServerLevel) this.bee.level();
|
||||
if (!bonemealable.isValidBonemealTarget(level, pos, blockState)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
bonemealable.performBonemeal(level, this.bee.getRandom(), pos, blockState);
|
||||
return level.getBlockState(pos);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.IntTag;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
@@ -45,4 +46,24 @@ public class ChunkStorageMixin {
|
||||
int curVersion = vanillaFixed.contains(VERSION_KEY) ? vanillaFixed.getInt(VERSION_KEY) : 0;
|
||||
return DataFixTypes.CHUNK.update(CobblemonSchemas.getDATA_FIXER(), vanillaFixed, curVersion, CobblemonSchemas.DATA_VERSION);
|
||||
}
|
||||
|
||||
// Our update on the same MC version still needs its own fixes so run them here in that case.
|
||||
@Inject(
|
||||
method = "upgradeChunkTag(Lnet/minecraft/resources/ResourceKey;Ljava/util/function/Supplier;Lnet/minecraft/nbt/CompoundTag;Ljava/util/Optional;)Lnet/minecraft/nbt/CompoundTag;",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void cobblemon$doChunkFixOnCurrentVersion(
|
||||
ResourceKey<Level> levelKey,
|
||||
Supplier<DimensionDataStorage> storage,
|
||||
CompoundTag chunkData,
|
||||
Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey,
|
||||
CallbackInfoReturnable<CompoundTag> cir
|
||||
) {
|
||||
if (ChunkStorage.getVersion(chunkData) != SharedConstants.getCurrentVersion().getDataVersion().getVersion()) {
|
||||
return;
|
||||
}
|
||||
int curVersion = chunkData.contains(VERSION_KEY) ? chunkData.getInt(VERSION_KEY) : 0;
|
||||
cir.setReturnValue(DataFixTypes.CHUNK.update(CobblemonSchemas.getDATA_FIXER(), chunkData, curVersion, CobblemonSchemas.DATA_VERSION));
|
||||
}
|
||||
}
|
||||
@@ -75,9 +75,10 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
|
||||
public @NotNull HitResult pick(double hitDistance, float partialTicks, boolean hitFluids) {
|
||||
Entity vehicle = this.getVehicle();
|
||||
if (vehicle instanceof PokemonEntity pokemonEntity && pokemonEntity.getDelegate() instanceof PokemonClientDelegate delegate) {
|
||||
int seatIndex = pokemonEntity.getPassengers().indexOf(this);
|
||||
Seat seat = pokemonEntity.getSeats().get(seatIndex);
|
||||
MatrixWrapper locator = delegate.getLocatorStates().get(seat.getLocator());
|
||||
// Passenger order isn't seat order, so look the seat up directly
|
||||
Seat seat = pokemonEntity.getSeatForPassenger(this);
|
||||
MatrixWrapper locator = seat != null ? delegate.getLocatorStates().get(seat.getLocator()) : null;
|
||||
if (locator == null) return super.pick(hitDistance, partialTicks, hitFluids);
|
||||
|
||||
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
|
||||
|
||||
@@ -99,7 +100,8 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
|
||||
|
||||
@Unique
|
||||
private Vec3 cobblemon$getEyePosition(float partialTicks, PokemonEntity pokemonEntity, Vec3 locatorOffset) {
|
||||
OrientationController controller = this.getOrientationController();
|
||||
// The ride owns the orientation, the passenger's own controller is never active
|
||||
OrientationController controller = ((OrientationControllable) pokemonEntity).getOrientationController();
|
||||
|
||||
float currEyeHeight = this.getEyeHeight();
|
||||
Vec3 offset = locatorOffset.add(pokemonEntity.position());
|
||||
@@ -107,7 +109,7 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
|
||||
Quaternionf orientation = controller.getRenderOrientation(partialTicks);
|
||||
Vec3 rotatedEyeHeight = new Vec3(orientation.transform(new Vector3f(0f, currEyeHeight - (this.getBbHeight() / 2), 0f)));
|
||||
|
||||
offset.add(rotatedEyeHeight);
|
||||
offset = offset.add(rotatedEyeHeight);
|
||||
}
|
||||
|
||||
return offset;
|
||||
|
||||
@@ -35,13 +35,16 @@ import com.cobblemon.mod.common.util.DataKeys;
|
||||
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityDimensions;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
@@ -50,6 +53,7 @@ import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.food.FoodProperties;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -371,4 +375,33 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
|
||||
this.cobblemon$rideEyePos = rideEyePos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sweeping attacks apply post-attack enchantment effects (e.g. Fire Aspect) to every entity caught
|
||||
* in the sweep, even if the damage itself was blocked. Skip them for Pokémon immune to the attack.
|
||||
*/
|
||||
@WrapOperation(
|
||||
method = "attack",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/enchantment/EnchantmentHelper;doPostAttackEffects(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)V")
|
||||
)
|
||||
private void cobblemon$skipPostAttackEffectsOnImmunePokemon(ServerLevel level, Entity target, DamageSource source, Operation<Void> original) {
|
||||
if (target instanceof PokemonEntity pokemonEntity && pokemonEntity.isInvulnerableTo(source)) {
|
||||
return;
|
||||
}
|
||||
original.call(level, target, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sweeping attacks also knock back every entity caught in the sweep regardless of whether it took damage.
|
||||
*/
|
||||
@WrapOperation(
|
||||
method = "attack",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;knockback(DDD)V")
|
||||
)
|
||||
private void cobblemon$skipKnockbackOnImmunePokemon(LivingEntity target, double strength, double x, double z, Operation<Void> original, @Local DamageSource source) {
|
||||
if (target instanceof PokemonEntity pokemonEntity && pokemonEntity.isInvulnerableTo(source)) {
|
||||
return;
|
||||
}
|
||||
original.call(target, strength, x, z);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -83,7 +83,7 @@ public abstract class CameraMixin implements CameraDuck {
|
||||
}
|
||||
|
||||
// reset the rotation values if it is now not being ridden.
|
||||
MountedCameraRenderer.INSTANCE.setSmoothRotation(null);
|
||||
MountedCameraRenderer.INSTANCE.reset();
|
||||
original.call(instance, x, y, z);
|
||||
}
|
||||
}
|
||||
+7
-17
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.item.crafting.CookingPotShapelessRecipe;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.ClientRecipeBook;
|
||||
import net.minecraft.client.RecipeBookCategories;
|
||||
import net.minecraft.client.gui.screens.recipebook.RecipeCollection;
|
||||
@@ -63,23 +64,12 @@ public abstract class ClientRecipeBookMixin {
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "setupCollections", at = @At("RETURN"))
|
||||
private void addCustomAggregateCategories(Iterable<RecipeHolder<?>> recipes, RegistryAccess registryAccess, CallbackInfo ci) {
|
||||
Map<RecipeBookCategories, List<List<RecipeHolder<?>>>> map = categorizeAndGroupRecipes(recipes);
|
||||
Map<RecipeBookCategories, List<RecipeCollection>> map2 = Maps.newHashMap();
|
||||
ImmutableList.Builder<RecipeCollection> builder = ImmutableList.builder();
|
||||
|
||||
map.forEach((recipeBookCategories, list) -> {
|
||||
List<RecipeCollection> collections = list.stream()
|
||||
.map(listx -> new RecipeCollection(registryAccess, listx))
|
||||
.peek(builder::add)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
map2.put(recipeBookCategories, collections);
|
||||
});
|
||||
RecipeBookCategories.AGGREGATE_CATEGORIES.forEach((recipeBookCategories, list) -> map2.put(recipeBookCategories, (List) list.stream().flatMap((recipeBookCategoriesx) -> ((List) map2.getOrDefault(recipeBookCategoriesx, ImmutableList.of())).stream()).collect(ImmutableList.toImmutableList())));
|
||||
@Inject(method = "setupCollections",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lcom/google/common/collect/ImmutableMap;copyOf(Ljava/util/Map;)Lcom/google/common/collect/ImmutableMap;"
|
||||
))
|
||||
private void addCustomAggregateCategories(Iterable<RecipeHolder<?>> recipes, RegistryAccess registryAccess, CallbackInfo ci, @Local(ordinal = 1) Map<RecipeBookCategories, List<RecipeCollection>> map2) {
|
||||
CobblemonRecipeCategories.Companion.getCustomAggregateCategories().forEach((recipeBookCategories, list) -> map2.put(recipeBookCategories, (List) list.stream().flatMap((recipeBookCategoriesx) -> ((List) map2.getOrDefault(recipeBookCategoriesx, ImmutableList.of())).stream()).collect(ImmutableList.toImmutableList())));
|
||||
|
||||
this.collectionsByTab = ImmutableMap.copyOf(map2);
|
||||
this.allCollections = builder.build();
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,13 @@ package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.OrientationControllable;
|
||||
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
|
||||
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker;
|
||||
import com.cobblemon.mod.common.duck.RidePassenger;
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
|
||||
import net.minecraft.client.model.HumanoidModel;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
@@ -96,4 +98,14 @@ public class HumanoidModelMixin {
|
||||
MountedPlayerRenderer.INSTANCE.animate((HumanoidModel<AbstractClientPlayer>)(Object)this, pokemonEntity, player, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "setupAnim*", at = @At("TAIL"))
|
||||
public void cobblemon$poseRecallArm(LivingEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch, CallbackInfo ci) {
|
||||
if (!(entity instanceof AbstractClientPlayer) || entity.getVehicle() instanceof PokemonEntity) return;
|
||||
float raise = RecallArmAnimationTracker.INSTANCE.raiseFactor(entity.getUUID());
|
||||
if (raise <= 0f) return;
|
||||
this.rightArm.xRot = Mth.lerp(raise, this.rightArm.xRot, (float) (-Math.PI / 2.0));
|
||||
this.rightArm.yRot = Mth.lerp(raise, this.rightArm.yRot, 0f);
|
||||
this.rightArm.zRot = Mth.lerp(raise, this.rightArm.zRot, 0f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.entity.layers.ItemInHandLayer;
|
||||
import net.minecraft.world.entity.HumanoidArm;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(ItemInHandLayer.class)
|
||||
public class ItemInHandLayerMixin {
|
||||
@Inject(method = "renderArmWithItem", at = @At("HEAD"), cancellable = true)
|
||||
private void cobblemon$hideRecallHandItem(LivingEntity entity, ItemStack itemStack, ItemDisplayContext itemDisplayContext, HumanoidArm humanoidArm, PoseStack poseStack, MultiBufferSource multiBufferSource, int light, CallbackInfo ci) {
|
||||
if (humanoidArm == HumanoidArm.RIGHT && RecallArmAnimationTracker.INSTANCE.isRecalling(entity.getUUID())) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -9,10 +9,14 @@
|
||||
package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.OrientationControllable;
|
||||
import com.cobblemon.mod.common.client.render.camera.MountedCameraRenderer;
|
||||
import com.cobblemon.mod.common.client.render.player.FirstPersonRecallBallRenderer;
|
||||
import com.cobblemon.mod.common.item.PokedexItem;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Axis;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.client.renderer.ItemInHandRenderer;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
@@ -31,11 +35,22 @@ public class ItemInHandRendererMixin {
|
||||
if (abstractClientPlayer.isUsingItem() && abstractClientPlayer.getUseItem().getItem() instanceof PokedexItem) {
|
||||
ci.cancel();
|
||||
}
|
||||
else if (interactionHand == InteractionHand.MAIN_HAND && FirstPersonRecallBallRenderer.INSTANCE.render(poseStack, multiBufferSource, j, f)) {
|
||||
ci.cancel();
|
||||
}
|
||||
else if (abstractClientPlayer instanceof OrientationControllable controllable && controllable.getOrientationController().isActive()) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "renderHandsWithItems", at = @At("HEAD"))
|
||||
private void cobblemon$rollHandsWithRide(float partialTicks, PoseStack poseStack, MultiBufferSource.BufferSource buffer, LocalPlayer player, int light, CallbackInfo ci) {
|
||||
float roll = MountedCameraRenderer.INSTANCE.getHandRoll();
|
||||
if (roll != 0f) {
|
||||
poseStack.mulPose(Axis.ZN.rotationDegrees(roll));
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "renderHandsWithItems", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Quaternionf;)V"))
|
||||
private void cobblemon$renderHandswithItems(PoseStack instance, Quaternionf quaternion) {
|
||||
if (!(Minecraft.getInstance().player != null && Minecraft.getInstance().player.getVehicle() instanceof OrientationControllable controllable && controllable.getOrientationController().isActive())) {
|
||||
|
||||
@@ -32,7 +32,6 @@ import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.client.player.Input;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.ClipContext;
|
||||
@@ -50,11 +49,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@Mixin(LocalPlayer.class)
|
||||
public abstract class LocalPlayerMixin extends AbstractClientPlayer implements OrientationControllable, DoubleJump {
|
||||
|
||||
@Unique Matrix3f cobblemon$lastOrientation;
|
||||
@Unique int cobblemon$lastOrientationVehicleId = -1;
|
||||
|
||||
@Unique float cobblemon$lastRideXRot;
|
||||
@Unique float cobblemon$lastRideYRot;
|
||||
@@ -81,30 +82,28 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
|
||||
private void cobblemon$updateRotationMatrixPassenger(CallbackInfo ci) {
|
||||
if (!(this.getVehicle() instanceof OrientationControllable controllableVehicle)) return;
|
||||
var vehicleController = controllableVehicle.getOrientationController();
|
||||
if (!vehicleController.isActive() || vehicleController.getOrientation() == cobblemon$lastOrientation) return;
|
||||
cobblemon$lastOrientation = vehicleController.getOrientation() != null ? new Matrix3f(vehicleController.getOrientation()) : null;
|
||||
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateOrientationPacket(this.getVehicle().getId(), vehicleController.getOrientation()));
|
||||
if (!vehicleController.isActive()) {
|
||||
// Forget the last sent value so the next ride always gets its first orientation sent
|
||||
cobblemon$lastOrientation = null;
|
||||
return;
|
||||
}
|
||||
var orientation = vehicleController.getOrientation();
|
||||
var vehicleId = this.getVehicle().getId();
|
||||
// Compare by value, the stored one is a copy so a reference check never matches
|
||||
if (vehicleId == cobblemon$lastOrientationVehicleId && Objects.equals(orientation, cobblemon$lastOrientation)) return;
|
||||
cobblemon$lastOrientation = orientation != null ? new Matrix3f(orientation) : null;
|
||||
cobblemon$lastOrientationVehicleId = vehicleId;
|
||||
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateOrientationPacket(vehicleId, orientation));
|
||||
}
|
||||
|
||||
@Inject(method = "rideTick", at = @At("HEAD"))
|
||||
private void cobblemon$updateOrientationControllerRideTick(CallbackInfo ci) {
|
||||
if (Minecraft.getInstance().player != (Object)this) return;
|
||||
var driver = (LocalPlayer)(Object)this;
|
||||
var vehicle = driver.getVehicle();
|
||||
if (!(this.getVehicle() instanceof OrientationControllable controllableVehicle)) return;
|
||||
var shouldUseCustomOrientation = cobblemon$shouldUseCustomOrientation((LocalPlayer)(Object)this);
|
||||
var vehicleController = controllableVehicle.getOrientationController();
|
||||
|
||||
// If the player has just switched to riding a custom orientation ride then set their
|
||||
// x and y rots local to the vehicle rots(if the controller wasn't active and will now be active)
|
||||
// This ensures that on transition your camera stays in the same spot
|
||||
if (!vehicleController.isActive() && shouldUseCustomOrientation) {
|
||||
// Set local to the vehicle x and yrot so
|
||||
var playerRotater = (RidePassenger)driver;
|
||||
playerRotater.cobblemon$setRideXRot(Mth.wrapDegrees(driver.getXRot() - vehicle.getXRot()));
|
||||
playerRotater.cobblemon$setRideYRot(Mth.wrapDegrees(driver.getYRot() - vehicle.getYRot()));
|
||||
}
|
||||
|
||||
// Look angles get seeded by the camera on its first rolling frame
|
||||
vehicleController.setActive(shouldUseCustomOrientation);
|
||||
}
|
||||
|
||||
|
||||
+82
-120
@@ -11,6 +11,10 @@ package com.cobblemon.mod.common.mixin.client;
|
||||
import com.cobblemon.mod.common.Cobblemon;
|
||||
import com.cobblemon.mod.common.OrientationControllable;
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController;
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RideSteerInput;
|
||||
import com.cobblemon.mod.common.api.riding.camera.RideLookInput;
|
||||
import com.cobblemon.mod.common.api.riding.camera.RideLookSettings;
|
||||
import com.cobblemon.mod.common.api.riding.camera.RideLookTake;
|
||||
import com.cobblemon.mod.common.client.CobblemonClient;
|
||||
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding;
|
||||
import com.cobblemon.mod.common.client.keybind.keybinds.RidingFreelookBinding;
|
||||
@@ -20,13 +24,12 @@ import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext;
|
||||
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.mojang.blaze3d.Blaze3D;
|
||||
import kotlin.Pair;
|
||||
import kotlin.Unit;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.MouseHandler;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.util.SmoothDouble;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
@@ -42,6 +45,11 @@ import static net.minecraft.util.Mth.lerp;
|
||||
|
||||
@Mixin(MouseHandler.class)
|
||||
public abstract class MouseHandlerMixin {
|
||||
// Entity.turn scales mouse movement to degrees by this
|
||||
@Unique private static final float COBBLEMON$VANILLA_DEGREES_PER_MOUSE_UNIT = 0.15F;
|
||||
// Share of the look offset closed per second as it drifts back to centre. Raise to recentre faster
|
||||
@Unique private static final float COBBLEMON$LOOK_RECENTER_RATE = 5.0F;
|
||||
|
||||
@Shadow private double accumulatedScrollY;
|
||||
@Shadow @Final private Minecraft minecraft;
|
||||
|
||||
@@ -160,74 +168,48 @@ public abstract class MouseHandlerMixin {
|
||||
}
|
||||
|
||||
var vehicleController = controllableVehicle.getOrientationController();
|
||||
var freeLooking = RidingFreelookBinding.INSTANCE.isDown();
|
||||
Pair<Boolean, Boolean> mouseShouldModifyDriver = ((PokemonEntity) vehicle).ifRidingAvailableSupply(new Pair(false, false), (behaviour, settings, state) -> {
|
||||
return behaviour.mouseModifiesDriverRotation(
|
||||
settings,
|
||||
state,
|
||||
(PokemonEntity) vehicle
|
||||
);
|
||||
var pokemonVehicle = (PokemonEntity) vehicle;
|
||||
var playerRotater = (RidePassenger)player;
|
||||
float yawDelta = (float)cursorDeltaX * COBBLEMON$VANILLA_DEGREES_PER_MOUSE_UNIT;
|
||||
float pitchDelta = (float)cursorDeltaY * COBBLEMON$VANILLA_DEGREES_PER_MOUSE_UNIT;
|
||||
|
||||
// Passengers only ever look around
|
||||
if (player.getControlledVehicle() == null) {
|
||||
cobblemon$addLook(player, yawDelta, pitchDelta);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The ride takes what it wants of the mouse to steer with and the rest moves the look. Freelook keeps it all for the look
|
||||
var lookInput = new RideLookInput(yawDelta, pitchDelta, playerRotater.cobblemon$getRideYRot(), playerRotater.cobblemon$getRideXRot());
|
||||
RideLookTake take = RidingFreelookBinding.INSTANCE.isDown() ? RideLookTake.NONE : pokemonVehicle.ifRidingAvailableSupply(RideLookTake.all(lookInput), (behaviour, settings, state) -> {
|
||||
return behaviour.consumeLook(settings, state, pokemonVehicle, lookInput);
|
||||
});
|
||||
float lookYaw = yawDelta - take.getYaw();
|
||||
float lookPitch = pitchDelta - take.getPitch();
|
||||
if (lookYaw != 0.0f || lookPitch != 0.0f) {
|
||||
cobblemon$addLook(player, lookYaw, lookPitch);
|
||||
}
|
||||
|
||||
// Drift the look back to centre on the axes the ride steers with
|
||||
float recenter = min(COBBLEMON$LOOK_RECENTER_RATE * (float)movementTime, 1.0f);
|
||||
if (take.getRecenterYaw()) {
|
||||
playerRotater.cobblemon$setRideYRot(lerp(recenter, Mth.wrapDegrees(playerRotater.cobblemon$getRideYRot()), 0.0f));
|
||||
}
|
||||
if (take.getRecenterPitch()) {
|
||||
playerRotater.cobblemon$setRideXRot(lerp(recenter, Mth.wrapDegrees(playerRotater.cobblemon$getRideXRot()), 0.0f));
|
||||
}
|
||||
|
||||
// Hand the ride its share back in mouse units since that's what every ride's steering is tuned for
|
||||
double rideDeltaX = yawDelta == 0.0f ? 0.0 : cursorDeltaX * (take.getYaw() / yawDelta);
|
||||
double rideDeltaY = pitchDelta == 0.0f ? 0.0 : cursorDeltaY * (take.getPitch() / pitchDelta);
|
||||
var steerInput = cobblemon$getSteerInput(vehicleController, rideDeltaX, rideDeltaY, movementTime);
|
||||
pokemonVehicle.ifRidingAvailable((behaviour, settings, state) -> {
|
||||
behaviour.steer(settings, state, pokemonVehicle, player, steerInput);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// If looking around then modify the players orientation instead and zero the mouse deltas so the mouse doesn't
|
||||
// affect the vehicle orientation when looking around.
|
||||
// If the client is a passenger it will modify these rotations and return
|
||||
//-------------------------------------------------------------------------
|
||||
if (freeLooking || player.getControlledVehicle() == null) {
|
||||
// Modify driver rotations
|
||||
cobblemon$setDriverRotations(player, cursorDeltaX, cursorDeltaY);
|
||||
|
||||
// If the player/passenger is not the driver then return and ignore all vehicle rotation logic
|
||||
if (player.getControlledVehicle() == null) return false;
|
||||
|
||||
// Set to zero so that vehicle rotations don't also get affected by movement.
|
||||
cursorDeltaX = 0.0;
|
||||
cursorDeltaY = 0.0;
|
||||
} else if (mouseShouldModifyDriver.getFirst()) { // If not freelooking then check modification per mouse input dimension
|
||||
cobblemon$setDriverRotations(player, cursorDeltaX, 0.0);
|
||||
cursorDeltaX = 0.0;
|
||||
} else if (mouseShouldModifyDriver.getSecond()) {
|
||||
cobblemon$setDriverRotations(player, 0.0, cursorDeltaY);
|
||||
cursorDeltaY = 0.0;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Smoothly return driver rotations to zero when needed
|
||||
//-------------------------------------------------------------------------
|
||||
var playerRotater = (RidePassenger)player;
|
||||
if( !freeLooking && !mouseShouldModifyDriver.getFirst()){
|
||||
playerRotater.cobblemon$setRideYRot(lerp(min(5.0f * (float)movementTime, 1.0f), Mth.wrapDegrees(playerRotater.cobblemon$getRideYRot()), 0.0f));
|
||||
}
|
||||
if( !freeLooking && !mouseShouldModifyDriver.getSecond()) {
|
||||
playerRotater.cobblemon$setRideXRot(lerp(min(5.0f * (float)movementTime, 1.0f), Mth.wrapDegrees(playerRotater.cobblemon$getRideXRot()), 0.0f));
|
||||
}
|
||||
|
||||
// Send mouse input to be interpreted into rotation deltas by the ride controller
|
||||
Vec3 angVecMouse = cobblemon$getRideMouseRotation(cursorDeltaX, cursorDeltaY, movementTime);
|
||||
|
||||
// Perform Rotation using mouse influenced rotation deltas.
|
||||
vehicleController.rotate(
|
||||
(float) angVecMouse.x,
|
||||
(float) angVecMouse.y,
|
||||
(float) angVecMouse.z
|
||||
);
|
||||
|
||||
// Gather and apply the current rotation deltas
|
||||
var angRot = cobblemon$getAngularVelocity(movementTime);
|
||||
|
||||
// Apply smoothing if requested by the controller.
|
||||
if(cobblemon$shouldUseAngVelSmoothing()) {
|
||||
var yaw = yawSmoother.getNewDeltaValue(angRot.x * 0.5f, d);
|
||||
var pitch = pitchSmoother.getNewDeltaValue(angRot.y * 0.5f, d);
|
||||
var roll = rollSmoother.getNewDeltaValue(angRot.z * 0.5f, d);
|
||||
vehicleController.rotate((float) yaw, (float) pitch, (float) roll);
|
||||
}
|
||||
else {
|
||||
vehicleController.rotate((float) (angRot.x * 10 * d), (float) (angRot.y * 10 * d), (float) (angRot.z * 10 * d));
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
// The camera sets the player's rotation from the look every frame so vanilla turn would only get overwritten
|
||||
return false;
|
||||
}
|
||||
|
||||
@Inject(method = "handleAccumulatedMovement", at = @At("HEAD"))
|
||||
@@ -248,28 +230,9 @@ public abstract class MouseHandlerMixin {
|
||||
this.turnPlayer(cobblemon$timeDelta);
|
||||
}
|
||||
|
||||
/** The ride's share of the mouse put through the riding sensitivity, invert and swap options. */
|
||||
@Unique
|
||||
private Vec3 cobblemon$getAngularVelocity(double deltaTime) {
|
||||
var player = minecraft.player;
|
||||
if (player == null) return Vec3.ZERO;
|
||||
if (!(player instanceof OrientationControllable)) return Vec3.ZERO;
|
||||
|
||||
var playerVehicle = player.getVehicle();
|
||||
if (playerVehicle == null) return Vec3.ZERO;
|
||||
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return Vec3.ZERO;
|
||||
return pokemonEntity.ifRidingAvailableSupply(Vec3.ZERO, (behaviour, settings, state) -> {
|
||||
return behaviour.angRollVel(settings, state, pokemonEntity, player, deltaTime);
|
||||
});
|
||||
}
|
||||
|
||||
@Unique
|
||||
private Vec3 cobblemon$getRideMouseRotation(double mouseX, double mouseY, double deltaTime) {
|
||||
var player = minecraft.player;
|
||||
if (player == null) return Vec3.ZERO;
|
||||
var vehicle = player.getVehicle();
|
||||
if (vehicle == null) return Vec3.ZERO;
|
||||
if (!(vehicle instanceof PokemonEntity pokemonEntity)) return Vec3.ZERO;
|
||||
|
||||
private RideSteerInput cobblemon$getSteerInput(OrientationController controller, double mouseX, double mouseY, double deltaTime) {
|
||||
var invertYaw = Cobblemon.config.getInvertYaw();
|
||||
var invertPitch = Cobblemon.config.getInvertPitch();
|
||||
|
||||
@@ -282,21 +245,18 @@ public abstract class MouseHandlerMixin {
|
||||
|
||||
var pitch = (swapXAndY ? xValue : yValue) * (invertPitch? -1.0f : 1.0f);
|
||||
|
||||
var sensitivity = cobblemon$getRidingSensitivity();
|
||||
return pokemonEntity.ifRidingAvailableSupply(Vec3.ZERO, (behaviour, settings, state) -> {
|
||||
return behaviour.rotationOnMouseXY(
|
||||
settings,
|
||||
state,
|
||||
pokemonEntity,
|
||||
player,
|
||||
pitch,
|
||||
yaw,
|
||||
yMouseSmoother,
|
||||
xMouseSmoother,
|
||||
sensitivity,
|
||||
deltaTime
|
||||
);
|
||||
});
|
||||
return new RideSteerInput(
|
||||
controller,
|
||||
yaw,
|
||||
pitch,
|
||||
xMouseSmoother,
|
||||
yMouseSmoother,
|
||||
cobblemon$getRidingSensitivity(),
|
||||
deltaTime,
|
||||
yawSmoother,
|
||||
pitchSmoother,
|
||||
rollSmoother
|
||||
);
|
||||
}
|
||||
|
||||
@Unique
|
||||
@@ -305,31 +265,33 @@ public abstract class MouseHandlerMixin {
|
||||
return Math.pow(sensitivity, 3);
|
||||
}
|
||||
|
||||
/** Moves the ride look by degrees within the ride's look limits. Same job as vanilla turn but on the look instead of the player. */
|
||||
@Unique
|
||||
private void cobblemon$setDriverRotations(LocalPlayer player, double cursorDeltaX, double cursorDeltaY) {
|
||||
// the vanilla turn() method redone so that it uses rideRotations.
|
||||
float f = (float)cursorDeltaY * 0.15F; // Note: turn() uses xRot for pitch (up/down)
|
||||
float g = (float)cursorDeltaX * 0.15F; // Note: turn() uses yRot for yaw (left/right)
|
||||
private void cobblemon$addLook(LocalPlayer player, float yawDelta, float pitchDelta) {
|
||||
var playerRotater = (RidePassenger)player;
|
||||
playerRotater.cobblemon$setRideXRot(playerRotater.cobblemon$getRideXRot() + f);
|
||||
playerRotater.cobblemon$setRideYRot(playerRotater.cobblemon$getRideYRot() + g);
|
||||
playerRotater.cobblemon$setRideXRot(Mth.clamp(playerRotater.cobblemon$getRideXRot(), -89.0F, 89.0F));
|
||||
playerRotater.cobblemon$setRideYRot(Mth.clamp(playerRotater.cobblemon$getRideYRot(), -105.0F, 105.0F));
|
||||
var lookSettings = cobblemon$getLookSettings(player);
|
||||
playerRotater.cobblemon$setRideXRot(cobblemon$softClamp(playerRotater.cobblemon$getRideXRot(), pitchDelta, lookSettings.getMaxPitch()));
|
||||
playerRotater.cobblemon$setRideYRot(cobblemon$softClamp(playerRotater.cobblemon$getRideYRot(), yawDelta, lookSettings.getMaxYaw()));
|
||||
if (player.getVehicle() != null) {
|
||||
player.getVehicle().onPassengerTurned(player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves an angle by delta while keeping it inside the limit. An angle already past the limit
|
||||
* (the base turning under a low carry look can put it there) isn't snapped back, it just can't go further out.
|
||||
*/
|
||||
@Unique
|
||||
private boolean cobblemon$shouldUseAngVelSmoothing() {
|
||||
var player = minecraft.player;
|
||||
if (player == null) return true;
|
||||
private static float cobblemon$softClamp(float angle, float delta, float limit) {
|
||||
float bound = Math.max(limit, Math.abs(angle));
|
||||
return Mth.clamp(angle + delta, -bound, bound);
|
||||
}
|
||||
|
||||
var playerVehicle = player.getVehicle();
|
||||
if (playerVehicle == null) return true;
|
||||
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return true;
|
||||
return pokemonEntity.ifRidingAvailableSupply(true, (behaviour, settings, state) -> {
|
||||
return behaviour.useAngVelSmoothing(settings, state, pokemonEntity);
|
||||
@Unique
|
||||
private RideLookSettings cobblemon$getLookSettings(LocalPlayer player) {
|
||||
if (!(player.getVehicle() instanceof PokemonEntity pokemonEntity)) return RideLookSettings.DEFAULT;
|
||||
return pokemonEntity.ifRidingAvailableSupply(RideLookSettings.DEFAULT, (behaviour, settings, state) -> {
|
||||
return behaviour.lookSettings(settings, state, pokemonEntity);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+13
-43
@@ -9,55 +9,25 @@
|
||||
package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonRecipeCategories;
|
||||
import net.minecraft.client.ClientRecipeBook;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import com.llamalad7.mixinextras.expression.Definition;
|
||||
import com.llamalad7.mixinextras.expression.Expression;
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.RecipeBookCategories;
|
||||
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
|
||||
import net.minecraft.client.gui.screens.recipebook.RecipeBookPage;
|
||||
import net.minecraft.client.gui.screens.recipebook.RecipeBookTabButton;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(RecipeBookComponent.class)
|
||||
public class RecipeBookComponentMixin {
|
||||
|
||||
@Shadow private ClientRecipeBook book;
|
||||
|
||||
@Shadow protected Minecraft minecraft;
|
||||
|
||||
@Shadow private int width;
|
||||
|
||||
@Shadow private int xOffset;
|
||||
|
||||
@Shadow private int height;
|
||||
|
||||
@Shadow @Final private RecipeBookPage recipeBookPage;
|
||||
|
||||
@Inject(method = "updateTabs", at = @At("HEAD"), cancellable = true)
|
||||
private void customUpdateTabs(CallbackInfo ci) {
|
||||
RecipeBookComponent instance = (RecipeBookComponent) (Object) this;
|
||||
int i = (width - 147) / 2 - xOffset - 30;
|
||||
int j = (height - 166) / 2 + 3;
|
||||
int k = 27;
|
||||
int l = 0;
|
||||
|
||||
for (RecipeBookTabButton recipeBookTabButton : instance.tabButtons) {
|
||||
RecipeBookCategories recipeBookCategories = recipeBookTabButton.getCategory();
|
||||
if (recipeBookCategories != RecipeBookCategories.CRAFTING_SEARCH && recipeBookCategories != RecipeBookCategories.FURNACE_SEARCH && recipeBookCategories != CobblemonRecipeCategories.COOKING_POT_SEARCH.toVanillaCategory()) {
|
||||
if (recipeBookTabButton.updateVisibility(book)) {
|
||||
recipeBookTabButton.setPosition(i, j + 27 * l++);
|
||||
recipeBookTabButton.startAnimation(minecraft);
|
||||
}
|
||||
} else {
|
||||
recipeBookTabButton.visible = true;
|
||||
recipeBookTabButton.setPosition(i, j + k * l++);
|
||||
}
|
||||
}
|
||||
|
||||
ci.cancel();
|
||||
@Definition(id = "recipeBookCategories", local = @Local(type = RecipeBookCategories.class))
|
||||
@Definition(id = "FURNACE_SEARCH", field = "Lnet/minecraft/client/RecipeBookCategories;FURNACE_SEARCH:Lnet/minecraft/client/RecipeBookCategories;")
|
||||
@Expression("recipeBookCategories != FURNACE_SEARCH")
|
||||
@ModifyExpressionValue(
|
||||
method = "updateTabs",
|
||||
at = @At(value = "MIXINEXTRAS:EXPRESSION")
|
||||
)
|
||||
private boolean notCookingPotSearch(boolean original, @Local RecipeBookCategories recipeBookCategories) {
|
||||
return original && recipeBookCategories != CobblemonRecipeCategories.COOKING_POT_SEARCH.toVanillaCategory();
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
@Mixin(Entity.class)
|
||||
public abstract class EntityMixin {
|
||||
|
||||
// How far the clientsent ride eye can sit from the players
|
||||
@Unique private static final double COBBLEMON$MAX_RIDE_EYE_OFFSET = 3.0;
|
||||
|
||||
@Shadow @Nullable public abstract Entity getVehicle();
|
||||
|
||||
@Inject(method = "updateInWaterStateAndDoWaterCurrentPushing", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/Entity;wasTouchingWater:Z", ordinal = 1), cancellable = true)
|
||||
@@ -91,6 +94,9 @@ public abstract class EntityMixin {
|
||||
if (vehicleController == null) return;
|
||||
|
||||
Vec3 customEyePos = ridePassenger.cobblemon$getRideEyePos();
|
||||
Vec3 vanillaEyePos = new Vec3(player.getX(), player.getEyeY(), player.getZ());
|
||||
double maxSqr = COBBLEMON$MAX_RIDE_EYE_OFFSET * COBBLEMON$MAX_RIDE_EYE_OFFSET;
|
||||
if (customEyePos == null || !(customEyePos.distanceToSqr(vanillaEyePos) <= maxSqr)) return;
|
||||
cir.setReturnValue(customEyePos);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,12 @@ object CobblemonBuildDetails {
|
||||
const val VERSION = "{{ version }}"
|
||||
const val SNAPSHOT = {{ isSnapshot }}
|
||||
|
||||
const val GIT_COMMIT = "{{ gitCommit | default("Not Specified") }}"
|
||||
const val BRANCH = "{{ branch | default("Not Specified") }}"
|
||||
// Read at runtime rather than templated in, so a new commit doesn't invalidate compiled code.
|
||||
private val git = java.util.Properties().apply {
|
||||
CobblemonBuildDetails::class.java.getResourceAsStream("/cobblemon-git.properties")?.use(::load)
|
||||
}
|
||||
@JvmField val GIT_COMMIT: String = git.getProperty("commit", "Not Specified")
|
||||
@JvmField val BRANCH: String = git.getProperty("branch", "Not Specified")
|
||||
|
||||
fun smallCommitHash(): String {
|
||||
return this.GIT_COMMIT.substring(0, 7)
|
||||
|
||||
@@ -260,6 +260,9 @@ object Cobblemon {
|
||||
RequestManager.onLogoff(it.player)
|
||||
serverPlayerStructs.remove(it.player.uuid)
|
||||
}
|
||||
PlatformEvents.PLAYER_RESPAWN.subscribe {
|
||||
serverPlayerStructs.remove(it.player.uuid)
|
||||
}
|
||||
PlatformEvents.PLAYER_DEATH.subscribe {
|
||||
PCLinkManager.removeLink(it.player.uuid)
|
||||
battleRegistry.getBattleByParticipatingPlayer(it.player)?.stop()
|
||||
|
||||
@@ -109,4 +109,21 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
|
||||
"poke_snack",
|
||||
BlockEntityType.Builder.of(::PokeSnackBlockEntity, CobblemonBlocks.POKE_SNACK, CobblemonBlocks.POKE_CAKE).build(null)
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val TERRAIN_PLANT: BlockEntityType<TerrainPlantBlockEntity> = this.create(
|
||||
"terrain_plant",
|
||||
BlockEntityType.Builder.of(::TerrainPlantBlockEntity,
|
||||
CobblemonBlocks.VOLTACONIA,
|
||||
CobblemonBlocks.LUCIBELL,
|
||||
CobblemonBlocks.VERDROOT
|
||||
).build(null)
|
||||
)
|
||||
@JvmField
|
||||
val MISTLEAF_FLOWER: BlockEntityType<MistleafFlowerBlockEntity> = this.create(
|
||||
"mistleaf",
|
||||
BlockEntityType.Builder.of(::MistleafFlowerBlockEntity,
|
||||
CobblemonBlocks.MISTLEAF
|
||||
).build(null)
|
||||
)
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.block.MintBlock.MintType
|
||||
import com.cobblemon.mod.common.block.campfirepot.CampfireBlock
|
||||
import com.cobblemon.mod.common.block.campfirepot.CampfirePotBlock
|
||||
import com.cobblemon.mod.common.block.chest.GildedChestBlock
|
||||
import com.cobblemon.mod.common.block.decor.*
|
||||
import com.cobblemon.mod.common.block.general.BaleBlock
|
||||
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
|
||||
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
|
||||
@@ -26,6 +27,7 @@ import com.cobblemon.mod.common.block.sign.CobblemonSignBlock
|
||||
import com.cobblemon.mod.common.block.sign.CobblemonWallHangingSignBlock
|
||||
import com.cobblemon.mod.common.block.sign.CobblemonWallSignBlock
|
||||
import com.cobblemon.mod.common.block.tmmachine.TMMachineBlock
|
||||
import com.cobblemon.mod.common.block.general.MintLitterBlock
|
||||
import com.cobblemon.mod.common.mixin.invoker.BlocksInvoker
|
||||
import com.cobblemon.mod.common.mixin.invoker.DoorBlockInvoker
|
||||
import com.cobblemon.mod.common.mixin.invoker.FireBlockInvoker
|
||||
@@ -621,37 +623,131 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val WEAKNESS_POLICY = create("weakness_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_PAPER_LARGE_SOUNDS).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
|
||||
|
||||
@JvmField
|
||||
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
val RING_TARGET = create("ring_target", RingTargetBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).mapColor(MapColor.NONE).noOcclusion().strength(0.125f)))
|
||||
@JvmField
|
||||
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
val MAGNET = create("magnet", MagnetBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion().strength(0.075f)))
|
||||
@JvmField
|
||||
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
val GALARICA_WREATH = create("galarica_wreath", GalaricaWreathBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noOcclusion().noCollission()))
|
||||
|
||||
@JvmField
|
||||
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
val FLAME_ORB = create("flame_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{15}))
|
||||
@JvmField
|
||||
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
val TOXIC_ORB = create("toxic_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
|
||||
@JvmField
|
||||
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
val LIFE_ORB = create("life_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{7}))
|
||||
@JvmField
|
||||
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
val LIGHT_BALL = create("light_ball", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{13}))
|
||||
@JvmField
|
||||
val SMOKE_BALL = create("smoke_ball", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
|
||||
|
||||
@JvmField
|
||||
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
val KINGS_ROCK = create("kings_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.CENTERED_6x))
|
||||
|
||||
@JvmField
|
||||
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
val GALARICA_CUFF = create("galarica_cuff", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noCollission().noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_10x4))
|
||||
@JvmField
|
||||
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
val UNREMARKABLE_TEACUP = create("unremarkable_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
|
||||
@JvmField
|
||||
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
|
||||
val MASTERPIECE_TEACUP = create("masterpiece_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
|
||||
@JvmField
|
||||
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
|
||||
val CHIPPED_POT = create("chipped_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
|
||||
@JvmField
|
||||
val CRACKED_POT = create("cracked_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
|
||||
@JvmField
|
||||
val ELECTIRIZER = create("electirizer", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.IZER))
|
||||
@JvmField
|
||||
val MAGMARIZER = create("magmarizer", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.IZER))
|
||||
|
||||
@JvmField
|
||||
val SACHET = create("sachet", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
@JvmField
|
||||
val METAL_POWDER = create("metal_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
@JvmField
|
||||
val QUICK_POWDER = create("quick_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
@JvmField
|
||||
val BRIGHT_POWDER = create("bright_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion().lightLevel{3}, DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
@JvmField
|
||||
val SOFT_SAND = create("soft_sand", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
|
||||
@JvmField
|
||||
val NEVER_MELT_ICE = create("never_melt_ice", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().friction(0.989F), DecorativeItemBlock.DecorativeItemBlockType.NEVERMELT))
|
||||
@JvmField
|
||||
val SCOPE_LENS = create("scope_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.SCOPE))
|
||||
@JvmField
|
||||
val WIDE_LENS = create("wide_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.WIDE))
|
||||
@JvmField
|
||||
val ZOOM_LENS = create("zoom_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.ZOOM))
|
||||
|
||||
@JvmField
|
||||
val ICY_ROCK = create("icy_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
|
||||
@JvmField
|
||||
val HEAT_ROCK = create("heat_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
|
||||
@JvmField
|
||||
val SMOOTH_ROCK = create("smooth_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
|
||||
@JvmField
|
||||
val DAMP_ROCK = create("damp_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x6))
|
||||
@JvmField
|
||||
val DESTINY_KNOT = create("destiny_knot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.KNOT))
|
||||
@JvmField
|
||||
val LUMINOUS_MOSS = create("luminous_moss", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.MOSS).mapColor(MapColor.NONE).noOcclusion().lightLevel{5}, DecorativeItemBlock.DecorativeItemBlockType.CENTERED_8x))
|
||||
|
||||
@JvmField
|
||||
val METRONOME = create("metronome", ActivatableDecorationBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion(), ActivatableDecorationBlock.ActivatableDecorationBlockType.METRONOME))
|
||||
@JvmField
|
||||
val EJECT_BUTTON = create("eject_button", EjectButtonBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().strength(0.125f), 40))
|
||||
|
||||
@JvmField
|
||||
val LOADED_DICE = create("loaded_dice", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_DICE_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.DICE))
|
||||
@JvmField
|
||||
val IRON_BALL = create("iron_ball", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.IRON_BALL))
|
||||
@JvmField
|
||||
val METAL_COAT = create("metal_coat", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.METAL_COAT))
|
||||
@JvmField
|
||||
val UPGRADE = create("upgrade", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.UPGRADE))
|
||||
@JvmField
|
||||
val DUBIOUS_DISC = create("dubious_disc", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.UPGRADE))
|
||||
|
||||
@JvmField
|
||||
val THROAT_SPRAY = create("throat_spray", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.THROAT_SPRAY))
|
||||
@JvmField
|
||||
val TERRAIN_EXTENDER = create("terrain_extender", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_METAL_CAN_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.EXTENDER))
|
||||
@JvmField
|
||||
val MOOMOO_MILK = create("moomoo_milk", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_JAR_GLASS_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.GLASS_BOTTLE))
|
||||
@JvmField
|
||||
val MEDICINAL_BREW = create("medicinal_brew", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_JAR_GLASS_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.GLASS_BOTTLE))
|
||||
|
||||
@JvmField
|
||||
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
|
||||
@JvmField
|
||||
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
@JvmField
|
||||
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
@JvmField
|
||||
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
@JvmField
|
||||
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
|
||||
@JvmField
|
||||
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
@JvmField
|
||||
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
@JvmField
|
||||
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
@JvmField
|
||||
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
|
||||
@JvmField
|
||||
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
|
||||
|
||||
@JvmField
|
||||
val DIRE_HIT = create("dire_hit", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
|
||||
@@ -670,6 +766,23 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val X_SPEED = create("x_${Stats.SPEED.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
|
||||
|
||||
@JvmField
|
||||
val HP_UP = create("hp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val PROTEIN = create("protein", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val IRON = create("iron", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val CARBOS = create("carbos", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val CALCIUM = create("calcium", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val ZINC = create("zinc", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val PP_UP = create("pp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val PP_MAX = create("pp_max", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
|
||||
@JvmField
|
||||
val MONITOR = create("monitor", MonitorBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).lightLevel { if (it.getValue(MonitorBlock.SCREEN) != MonitorBlock.MonitorScreen.OFF) 13 else 0 }))
|
||||
@JvmField
|
||||
@@ -685,6 +798,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val PC = create("pc", PCBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F).noOcclusion().lightLevel { if ((it.getValue(PCBlock.ON) as Boolean) && (it.getValue(PCBlock.PART) == PCBlock.PCPart.TOP)) 13 else 0 }))
|
||||
|
||||
@JvmField
|
||||
val TM_MACHINE = create(
|
||||
"tm_machine",
|
||||
TMMachineBlock(
|
||||
@@ -713,7 +827,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
|
||||
@JvmField
|
||||
val PASTURE = create("pasture", PastureBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BROWN).sound(SoundType.WOOD).strength(2F).noOcclusion().pushReaction(PushReaction.BLOCK).lightLevel { if ((it.getValue(PastureBlock.ON) as Boolean) && (it.getValue(PastureBlock.PART) == PastureBlock.PasturePart.TOP)) 13 else 0 }))
|
||||
|
||||
@JvmField
|
||||
val RED_MINT = create("red_mint", MintBlock(MintType.RED, BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_RED).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MINT_SOUNDS).pushReaction(PushReaction.DESTROY)))
|
||||
@JvmField
|
||||
val BLUE_MINT = create("blue_mint", MintBlock(MintType.BLUE, BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BLUE).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MINT_SOUNDS).pushReaction(PushReaction.DESTROY)))
|
||||
@@ -731,29 +845,68 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val PEP_UP_FLOWER = this.create("pep_up_flower", FlowerBlock(MobEffects.LEVITATION, 10F, BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().instabreak().sound(SoundType.GRASS).offsetType(BlockBehaviour.OffsetType.XZ).pushReaction(PushReaction.DESTROY)))
|
||||
@JvmField
|
||||
val POTTED_PEP_UP_FLOWER = this.create("potted_pep_up_flower", BlocksInvoker.createFlowerPotBlock(PEP_UP_FLOWER))
|
||||
@JvmField
|
||||
val HEARTY_GRAINS = this.create("hearty_grains", HeartyGrainsBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.PLANT).noCollission().randomTicks().instabreak()))
|
||||
|
||||
@JvmField
|
||||
val GALARICA_NUT_BUSH = this.create("galarica_nut_bush", NutBushBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).randomTicks().noCollission().sound(SoundType.SWEET_BERRY_BUSH).pushReaction(PushReaction.DESTROY)))
|
||||
|
||||
|
||||
@JvmField
|
||||
val POTTED_RED_APRICORN_SAPLING = this.create("potted_red_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(RED_APRICORN_SAPLING))
|
||||
val RED_MINT_LEAF_LITTER = this.mintLitter("red_mint_leaf_litter", MapColor.COLOR_RED)
|
||||
@JvmField
|
||||
val POTTED_YELLOW_APRICORN_SAPLING = this.create("potted_yellow_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(YELLOW_APRICORN_SAPLING))
|
||||
val POTTED_RED_MINT = this.flowerPot("potted_red_mint", RED_MINT_LEAF_LITTER)
|
||||
@JvmField
|
||||
val POTTED_GREEN_APRICORN_SAPLING = this.create("potted_green_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(GREEN_APRICORN_SAPLING))
|
||||
val BLUE_MINT_LEAF_LITTER = this.mintLitter("blue_mint_leaf_litter", MapColor.COLOR_BLUE)
|
||||
@JvmField
|
||||
val POTTED_BLUE_APRICORN_SAPLING = this.create("potted_blue_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(BLUE_APRICORN_SAPLING))
|
||||
val POTTED_BLUE_MINT = this.flowerPot("potted_blue_mint", BLUE_MINT_LEAF_LITTER)
|
||||
@JvmField
|
||||
val POTTED_PINK_APRICORN_SAPLING = this.create("potted_pink_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(PINK_APRICORN_SAPLING))
|
||||
val CYAN_MINT_LEAF_LITTER = this.mintLitter("cyan_mint_leaf_litter", MapColor.COLOR_CYAN)
|
||||
@JvmField
|
||||
val POTTED_BLACK_APRICORN_SAPLING = this.create("potted_black_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(BLACK_APRICORN_SAPLING))
|
||||
val POTTED_CYAN_MINT = this.flowerPot("potted_cyan_mint", CYAN_MINT_LEAF_LITTER)
|
||||
@JvmField
|
||||
val POTTED_WHITE_APRICORN_SAPLING = this.create("potted_white_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(WHITE_APRICORN_SAPLING))
|
||||
val PINK_MINT_LEAF_LITTER = this.mintLitter("pink_mint_leaf_litter", MapColor.COLOR_PINK)
|
||||
@JvmField
|
||||
val POTTED_SACCHARINE_SAPLING = this.create("potted_saccharine_sapling", BlocksInvoker.createFlowerPotBlock(SACCHARINE_SAPLING))
|
||||
val POTTED_PINK_MINT = this.flowerPot("potted_pink_mint", PINK_MINT_LEAF_LITTER)
|
||||
@JvmField
|
||||
val GREEN_MINT_LEAF_LITTER = this.mintLitter("green_mint_leaf_litter", MapColor.COLOR_GREEN)
|
||||
@JvmField
|
||||
val POTTED_GREEN_MINT = this.flowerPot("potted_green_mint", GREEN_MINT_LEAF_LITTER)
|
||||
@JvmField
|
||||
val WHITE_MINT_LEAF_LITTER = this.mintLitter("white_mint_leaf_litter", MapColor.SNOW)
|
||||
@JvmField
|
||||
val POTTED_WHITE_MINT = this.flowerPot("potted_white_mint", WHITE_MINT_LEAF_LITTER)
|
||||
|
||||
@JvmField
|
||||
val LOBAILA = this.create("lobaila", LobailaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().instabreak().sound(SoundType.GRASS).offsetType(BlockBehaviour.OffsetType.XZ).ignitedByLava().pushReaction(PushReaction.DESTROY)))
|
||||
@JvmField
|
||||
val POMOHANA = this.create("pomohana", PomohanaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().instabreak().sound(SoundType.GRASS).ignitedByLava().pushReaction(PushReaction.DESTROY)))
|
||||
@JvmField
|
||||
val PAUVILLEA = this.create("pauvillea",PauvilleaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY)))
|
||||
@JvmField
|
||||
val WISTENSU = this.create("wistensu", WistensuFlowerHeadBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY).lightLevel{11}))
|
||||
@JvmField
|
||||
val WISTENSU_VINE = this.create("wistensu_vine", WistensuFlowerVineBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY)))
|
||||
|
||||
@JvmField
|
||||
val ELECTRIC_SEED = this.terrainSeed("electric_seed", TerrainSeedBlock.TerrainSeedType.ELECTRIC)
|
||||
@JvmField
|
||||
val PSYCHIC_SEED = this.terrainSeed("psychic_seed", TerrainSeedBlock.TerrainSeedType.PSYCHIC)
|
||||
@JvmField
|
||||
val MISTY_SEED = this.terrainSeed("misty_seed", TerrainSeedBlock.TerrainSeedType.MISTY)
|
||||
@JvmField
|
||||
val GRASSY_SEED = this.terrainSeed("grassy_seed", TerrainSeedBlock.TerrainSeedType.GRASSY)
|
||||
@JvmField
|
||||
val VOLTACONIA = this.terrainPlant("voltaconia") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 8 else 0 }
|
||||
@JvmField
|
||||
val VERDROOT = this.terrainPlant("verdroot") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 5 else 0 }
|
||||
@JvmField
|
||||
val LUCIBELL = this.terrainPlant("lucibell") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 8 else 0 }
|
||||
@JvmField
|
||||
val MISTLEAF = this.create(
|
||||
"mistleaf",
|
||||
MistleafFlowerBlock {
|
||||
if ((it.getValue(MistleafFlowerBlock.GLOWING) >= 1)) it.getValue(MistleafFlowerBlock.GLOWING) + 2 else 0
|
||||
}
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val HEARTY_GRAIN_BALE = this.create("hearty_grain_bale", BaleBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).instrument(NoteBlockInstrument.BANJO).strength(0.5F).sound(CobblemonSounds.HEARTY_GRAIN_BALE_SOUNDS)))
|
||||
@@ -931,6 +1084,165 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val YACHE_BERRY = this.berryBlock("yache")
|
||||
//val BINDING_SOIL = this.create("binding_soil", BindingSoilBlock(BlockBehaviour.Properties.of()))
|
||||
|
||||
@JvmField
|
||||
val POTTED_AGUAV_BERRY = this.flowerPot("potted_aguav_berry", AGUAV_BERRY)
|
||||
@JvmField
|
||||
val POTTED_APICOT_BERRY = this.flowerPot("potted_apicot_berry", APICOT_BERRY)
|
||||
@JvmField
|
||||
val POTTED_ASPEAR_BERRY = this.flowerPot("potted_aspear_berry", ASPEAR_BERRY)
|
||||
@JvmField
|
||||
val POTTED_BABIRI_BERRY = this.flowerPot("potted_babiri_berry", BABIRI_BERRY)
|
||||
@JvmField
|
||||
val POTTED_BELUE_BERRY = this.flowerPot("potted_belue_berry", BELUE_BERRY)
|
||||
@JvmField
|
||||
val POTTED_BLUK_BERRY = this.flowerPot("potted_bluk_berry", BLUK_BERRY)
|
||||
@JvmField
|
||||
val POTTED_CHARTI_BERRY = this.flowerPot("potted_charti_berry", CHARTI_BERRY)
|
||||
@JvmField
|
||||
val POTTED_CHERI_BERRY = this.flowerPot("potted_cheri_berry", CHERI_BERRY)
|
||||
@JvmField
|
||||
val POTTED_CHESTO_BERRY = this.flowerPot("potted_chesto_berry", CHESTO_BERRY)
|
||||
@JvmField
|
||||
val POTTED_CHILAN_BERRY = this.flowerPot("potted_chilan_berry", CHILAN_BERRY)
|
||||
@JvmField
|
||||
val POTTED_CHOPLE_BERRY = this.flowerPot("potted_chople_berry", CHOPLE_BERRY)
|
||||
@JvmField
|
||||
val POTTED_COBA_BERRY = this.flowerPot("potted_coba_berry", COBA_BERRY)
|
||||
@JvmField
|
||||
val POTTED_COLBUR_BERRY = this.flowerPot("potted_colbur_berry", COLBUR_BERRY)
|
||||
@JvmField
|
||||
val POTTED_CORNN_BERRY = this.flowerPot("potted_cornn_berry", CORNN_BERRY)
|
||||
@JvmField
|
||||
val POTTED_CUSTAP_BERRY = this.flowerPot("potted_custap_berry", CUSTAP_BERRY)
|
||||
@JvmField
|
||||
val POTTED_DURIN_BERRY = this.flowerPot("potted_durin_berry", DURIN_BERRY)
|
||||
@JvmField
|
||||
val POTTED_EGGANT_BERRY = this.flowerPot("potted_eggant_berry", EGGANT_BERRY)
|
||||
@JvmField
|
||||
val POTTED_ENIGMA_BERRY = this.flowerPot("potted_enigma_berry", ENIGMA_BERRY)
|
||||
@JvmField
|
||||
val POTTED_FIGY_BERRY = this.flowerPot("potted_figy_berry", FIGY_BERRY)
|
||||
@JvmField
|
||||
val POTTED_GANLON_BERRY = this.flowerPot("potted_ganlon_berry", GANLON_BERRY)
|
||||
@JvmField
|
||||
val POTTED_GREPA_BERRY = this.flowerPot("potted_grepa_berry", GREPA_BERRY)
|
||||
@JvmField
|
||||
val POTTED_HABAN_BERRY = this.flowerPot("potted_haban_berry", HABAN_BERRY)
|
||||
@JvmField
|
||||
val POTTED_HONDEW_BERRY = this.flowerPot("potted_hondew_berry", HONDEW_BERRY)
|
||||
@JvmField
|
||||
val POTTED_HOPO_BERRY = this.flowerPot("potted_hopo_berry", HOPO_BERRY)
|
||||
@JvmField
|
||||
val POTTED_IAPAPA_BERRY = this.flowerPot("potted_iapapa_berry", IAPAPA_BERRY)
|
||||
@JvmField
|
||||
val POTTED_JABOCA_BERRY = this.flowerPot("potted_jaboca_berry", JABOCA_BERRY)
|
||||
@JvmField
|
||||
val POTTED_KASIB_BERRY = this.flowerPot("potted_kasib_berry", KASIB_BERRY)
|
||||
@JvmField
|
||||
val POTTED_KEBIA_BERRY = this.flowerPot("potted_kebia_berry", KEBIA_BERRY)
|
||||
@JvmField
|
||||
val POTTED_KEE_BERRY = this.flowerPot("potted_kee_berry", KEE_BERRY)
|
||||
@JvmField
|
||||
val POTTED_KELPSY_BERRY = this.flowerPot("potted_kelpsy_berry", KELPSY_BERRY)
|
||||
@JvmField
|
||||
val POTTED_LANSAT_BERRY = this.flowerPot("potted_lansat_berry", LANSAT_BERRY)
|
||||
@JvmField
|
||||
val POTTED_LEPPA_BERRY = this.flowerPot("potted_leppa_berry", LEPPA_BERRY)
|
||||
@JvmField
|
||||
val POTTED_LIECHI_BERRY = this.flowerPot("potted_liechi_berry", LIECHI_BERRY)
|
||||
@JvmField
|
||||
val POTTED_LUM_BERRY = this.flowerPot("potted_lum_berry", LUM_BERRY)
|
||||
@JvmField
|
||||
val POTTED_MAGO_BERRY = this.flowerPot("potted_mago_berry", MAGO_BERRY)
|
||||
@JvmField
|
||||
val POTTED_MAGOST_BERRY = this.flowerPot("potted_magost_berry", MAGOST_BERRY)
|
||||
@JvmField
|
||||
val POTTED_MARANGA_BERRY = this.flowerPot("potted_maranga_berry", MARANGA_BERRY)
|
||||
@JvmField
|
||||
val POTTED_MICLE_BERRY = this.flowerPot("potted_micle_berry", MICLE_BERRY)
|
||||
@JvmField
|
||||
val POTTED_NANAB_BERRY = this.flowerPot("potted_nanab_berry", NANAB_BERRY)
|
||||
@JvmField
|
||||
val POTTED_NOMEL_BERRY = this.flowerPot("potted_nomel_berry", NOMEL_BERRY)
|
||||
@JvmField
|
||||
val POTTED_OCCA_BERRY = this.flowerPot("potted_occa_berry", OCCA_BERRY)
|
||||
@JvmField
|
||||
val POTTED_ORAN_BERRY = this.flowerPot("potted_oran_berry", ORAN_BERRY)
|
||||
@JvmField
|
||||
val POTTED_PAMTRE_BERRY = this.flowerPot("potted_pamtre_berry", PAMTRE_BERRY)
|
||||
@JvmField
|
||||
val POTTED_PASSHO_BERRY = this.flowerPot("potted_passho_berry", PASSHO_BERRY)
|
||||
@JvmField
|
||||
val POTTED_PAYAPA_BERRY = this.flowerPot("potted_payapa_berry", PAYAPA_BERRY)
|
||||
@JvmField
|
||||
val POTTED_PECHA_BERRY = this.flowerPot("potted_pecha_berry", PECHA_BERRY)
|
||||
@JvmField
|
||||
val POTTED_PERSIM_BERRY = this.flowerPot("potted_persim_berry", PERSIM_BERRY)
|
||||
@JvmField
|
||||
val POTTED_PETAYA_BERRY = this.flowerPot("potted_petaya_berry", PETAYA_BERRY)
|
||||
@JvmField
|
||||
val POTTED_PINAP_BERRY = this.flowerPot("potted_pinap_berry", PINAP_BERRY)
|
||||
@JvmField
|
||||
val POTTED_POMEG_BERRY = this.flowerPot("potted_pomeg_berry", POMEG_BERRY)
|
||||
@JvmField
|
||||
val POTTED_QUALOT_BERRY = this.flowerPot("potted_qualot_berry", QUALOT_BERRY)
|
||||
@JvmField
|
||||
val POTTED_RABUTA_BERRY = this.flowerPot("potted_rabuta_berry", RABUTA_BERRY)
|
||||
@JvmField
|
||||
val POTTED_RAWST_BERRY = this.flowerPot("potted_rawst_berry", RAWST_BERRY)
|
||||
@JvmField
|
||||
val POTTED_RAZZ_BERRY = this.flowerPot("potted_razz_berry", RAZZ_BERRY)
|
||||
@JvmField
|
||||
val POTTED_RINDO_BERRY = this.flowerPot("potted_rindo_berry", RINDO_BERRY)
|
||||
@JvmField
|
||||
val POTTED_ROSELI_BERRY = this.flowerPot("potted_roseli_berry", ROSELI_BERRY)
|
||||
@JvmField
|
||||
val POTTED_ROWAP_BERRY = this.flowerPot("potted_rowap_berry", ROWAP_BERRY)
|
||||
@JvmField
|
||||
val POTTED_SALAC_BERRY = this.flowerPot("potted_salac_berry", SALAC_BERRY)
|
||||
@JvmField
|
||||
val POTTED_SHUCA_BERRY = this.flowerPot("potted_shuca_berry", SHUCA_BERRY)
|
||||
@JvmField
|
||||
val POTTED_SITRUS_BERRY = this.flowerPot("potted_sitrus_berry", SITRUS_BERRY)
|
||||
@JvmField
|
||||
val POTTED_SPELON_BERRY = this.flowerPot("potted_spelon_berry", SPELON_BERRY)
|
||||
@JvmField
|
||||
val POTTED_STARF_BERRY = this.flowerPot("potted_starf_berry", STARF_BERRY)
|
||||
@JvmField
|
||||
val POTTED_TAMATO_BERRY = this.flowerPot("potted_tamato_berry", TAMATO_BERRY)
|
||||
@JvmField
|
||||
val POTTED_TANGA_BERRY = this.flowerPot("potted_tanga_berry", TANGA_BERRY)
|
||||
@JvmField
|
||||
val POTTED_TOUGA_BERRY = this.flowerPot("potted_touga_berry", TOUGA_BERRY)
|
||||
@JvmField
|
||||
val POTTED_WACAN_BERRY = this.flowerPot("potted_wacan_berry", WACAN_BERRY)
|
||||
@JvmField
|
||||
val POTTED_WATMEL_BERRY = this.flowerPot("potted_watmel_berry", WATMEL_BERRY)
|
||||
@JvmField
|
||||
val POTTED_WEPEAR_BERRY = this.flowerPot("potted_wepear_berry", WEPEAR_BERRY)
|
||||
@JvmField
|
||||
val POTTED_WIKI_BERRY = this.flowerPot("potted_wiki_berry", WIKI_BERRY)
|
||||
@JvmField
|
||||
val POTTED_YACHE_BERRY = this.flowerPot("potted_yache_berry", YACHE_BERRY)
|
||||
@JvmField
|
||||
val POTTED_PEP_UP_FLOWER = this.flowerPot("potted_pep_up_flower", PEP_UP_FLOWER)
|
||||
@JvmField
|
||||
val POTTED_RED_APRICORN_SAPLING = this.flowerPot("potted_red_apricorn_sapling", RED_APRICORN_SAPLING)
|
||||
@JvmField
|
||||
val POTTED_YELLOW_APRICORN_SAPLING = this.flowerPot("potted_yellow_apricorn_sapling", YELLOW_APRICORN_SAPLING)
|
||||
@JvmField
|
||||
val POTTED_GREEN_APRICORN_SAPLING = this.flowerPot("potted_green_apricorn_sapling", GREEN_APRICORN_SAPLING)
|
||||
@JvmField
|
||||
val POTTED_BLUE_APRICORN_SAPLING = this.flowerPot("potted_blue_apricorn_sapling", BLUE_APRICORN_SAPLING)
|
||||
@JvmField
|
||||
val POTTED_PINK_APRICORN_SAPLING = this.flowerPot("potted_pink_apricorn_sapling", PINK_APRICORN_SAPLING)
|
||||
@JvmField
|
||||
val POTTED_BLACK_APRICORN_SAPLING = this.flowerPot("potted_black_apricorn_sapling", BLACK_APRICORN_SAPLING)
|
||||
@JvmField
|
||||
val POTTED_WHITE_APRICORN_SAPLING = this.flowerPot("potted_white_apricorn_sapling", WHITE_APRICORN_SAPLING)
|
||||
@JvmField
|
||||
val POTTED_SACCHARINE_SAPLING = this.flowerPot("potted_saccharine_sapling", SACCHARINE_SAPLING)
|
||||
|
||||
init {
|
||||
/**
|
||||
* Makes all blocks in array flammable by adding them to FireBlock's flammableRegistry.
|
||||
@@ -1032,4 +1344,57 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
}
|
||||
|
||||
fun typeGemClusters() = typeGemClusters.toMap()
|
||||
|
||||
private fun flowerPot(name: String, flower: Block) = this.create(
|
||||
name,
|
||||
FlowerPotBlock(
|
||||
flower,
|
||||
BlockBehaviour.Properties.of()
|
||||
.instabreak()
|
||||
.noOcclusion()
|
||||
.pushReaction(PushReaction.DESTROY)
|
||||
)
|
||||
)
|
||||
|
||||
private fun mintLitter(name: String, color: MapColor) = this.create(
|
||||
name,
|
||||
MintLitterBlock(
|
||||
BlockBehaviour.Properties.of()
|
||||
.sound(CobblemonSounds.MINT_SOUNDS)
|
||||
.mapColor(color)
|
||||
.noOcclusion()
|
||||
.noCollission()
|
||||
.pushReaction(PushReaction.DESTROY)
|
||||
)
|
||||
)
|
||||
|
||||
private fun terrainSeed(name: String, type: TerrainSeedBlock.TerrainSeedType) = this.create(
|
||||
name,
|
||||
TerrainSeedBlock(
|
||||
type,
|
||||
BlockBehaviour.Properties.of()
|
||||
.pushReaction(PushReaction.DESTROY)
|
||||
.ignitedByLava()
|
||||
.mapColor(MapColor.PLANT)
|
||||
.offsetType(BlockBehaviour.OffsetType.XZ)
|
||||
.noCollission()
|
||||
.randomTicks()
|
||||
.instabreak()
|
||||
)
|
||||
)
|
||||
|
||||
private fun terrainPlant(name: String, lightLevel: (BlockState) -> Int) = this.create(
|
||||
name,
|
||||
TerrainPlantBlock(
|
||||
BlockBehaviour.Properties.of()
|
||||
.mapColor(MapColor.PLANT)
|
||||
.noCollission()
|
||||
.instabreak()
|
||||
.sound(SoundType.GRASS)
|
||||
.offsetType(BlockBehaviour.OffsetType.XZ)
|
||||
.ignitedByLava()
|
||||
.pushReaction(PushReaction.DESTROY)
|
||||
.lightLevel(lightLevel)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.mojang.brigadier.arguments.ArgumentType
|
||||
import kotlin.reflect.KClass
|
||||
import net.minecraft.commands.synchronization.ArgumentTypeInfo
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.resources.ResourceKey
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.MinecraftServer
|
||||
@@ -168,12 +169,16 @@ interface CobblemonImplementation {
|
||||
* TODO
|
||||
*
|
||||
* @param identifier
|
||||
* @param reloader
|
||||
* @param reloaderFactory
|
||||
* @param type
|
||||
* @param dependencies
|
||||
*/
|
||||
fun registerResourceReloader(identifier: ResourceLocation, reloader: PreparableReloadListener, type: PackType, dependencies: Collection<ResourceLocation>)
|
||||
|
||||
fun registerResourceReloader(
|
||||
identifier: ResourceLocation,
|
||||
type: PackType,
|
||||
dependencies: Collection<ResourceLocation>,
|
||||
reloaderFactory: (HolderLookup.Provider) -> PreparableReloadListener
|
||||
)
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
|
||||
@@ -472,18 +472,18 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
// Evolution items
|
||||
@JvmField val LINK_CABLE = create("link_cable", LinkCableItem())
|
||||
@JvmField val DRAGON_SCALE = noSettingsItem("dragon_scale")
|
||||
@JvmField val METAL_COAT = noSettingsItem("metal_coat")
|
||||
@JvmField val UPGRADE = noSettingsItem("upgrade")
|
||||
@JvmField val DUBIOUS_DISC = noSettingsItem("dubious_disc")
|
||||
@JvmField val METAL_COAT = itemNameBlockItem("metal_coat", CobblemonBlocks.METAL_COAT)
|
||||
@JvmField val UPGRADE = itemNameBlockItem("upgrade", CobblemonBlocks.UPGRADE)
|
||||
@JvmField val DUBIOUS_DISC = itemNameBlockItem("dubious_disc", CobblemonBlocks.DUBIOUS_DISC)
|
||||
@JvmField val DEEP_SEA_SCALE = noSettingsItem("deep_sea_scale")
|
||||
@JvmField val DEEP_SEA_TOOTH = noSettingsItem("deep_sea_tooth")
|
||||
@JvmField val ELECTIRIZER = noSettingsItem("electirizer")
|
||||
@JvmField val MAGMARIZER = noSettingsItem("magmarizer")
|
||||
@JvmField val ELECTIRIZER = itemNameBlockItem("electirizer", CobblemonBlocks.ELECTIRIZER)
|
||||
@JvmField val MAGMARIZER = itemNameBlockItem("magmarizer", CobblemonBlocks.MAGMARIZER)
|
||||
@JvmField val OVAL_STONE = noSettingsItem("oval_stone")
|
||||
@JvmField val PROTECTOR = noSettingsItem("protector")
|
||||
@JvmField val REAPER_CLOTH = noSettingsItem("reaper_cloth")
|
||||
@JvmField val PRISM_SCALE = noSettingsItem("prism_scale")
|
||||
@JvmField val SACHET = noSettingsItem("sachet")
|
||||
@JvmField val SACHET = itemNameBlockItem("sachet", CobblemonBlocks.SACHET)
|
||||
@JvmField val WHIPPED_DREAM = create("whipped_dream", foodItem(8, 0.3f))
|
||||
@JvmField val STRAWBERRY_SWEET = create("strawberry_sweet", foodItem(6, 0.125f))
|
||||
@JvmField val LOVE_SWEET = create("love_sweet", foodItem(6, 0.125f))
|
||||
@@ -492,22 +492,20 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField val FLOWER_SWEET = create("flower_sweet", foodItem(6, 0.125f))
|
||||
@JvmField val STAR_SWEET = create("star_sweet", foodItem(6, 0.125f))
|
||||
@JvmField val RIBBON_SWEET = create("ribbon_sweet", foodItem(6, 0.125f))
|
||||
@JvmField val CHIPPED_POT = noSettingsItem("chipped_pot")
|
||||
@JvmField val CRACKED_POT = noSettingsItem("cracked_pot")
|
||||
@JvmField val MASTERPIECE_TEACUP = noSettingsItem("masterpiece_teacup")
|
||||
@JvmField val UNREMARKABLE_TEACUP = noSettingsItem("unremarkable_teacup")
|
||||
@JvmField val CHIPPED_POT = itemNameBlockItem("chipped_pot", CobblemonBlocks.CHIPPED_POT)
|
||||
@JvmField val CRACKED_POT = itemNameBlockItem("cracked_pot", CobblemonBlocks.CRACKED_POT)
|
||||
@JvmField val MASTERPIECE_TEACUP = itemNameBlockItem("masterpiece_teacup", CobblemonBlocks.MASTERPIECE_TEACUP)
|
||||
@JvmField val UNREMARKABLE_TEACUP = itemNameBlockItem("unremarkable_teacup", CobblemonBlocks.UNREMARKABLE_TEACUP)
|
||||
@JvmField val SWEET_APPLE = compostableItem("sweet_apple", foodItem(4, 0.3f), 0.65f)
|
||||
@JvmField val TART_APPLE = compostableItem("tart_apple", foodItem(4, 0.3f), 0.65f)
|
||||
@JvmField val SYRUPY_APPLE = compostableItem("syrupy_apple", foodItem(4, 0.3f), 0.65f)
|
||||
@JvmField val GALARICA_CUFF = noSettingsItem("galarica_cuff")
|
||||
@JvmField val GALARICA_WREATH = noSettingsItem("galarica_wreath")
|
||||
@JvmField val GALARICA_CUFF = itemNameBlockItem("galarica_cuff", CobblemonBlocks.GALARICA_CUFF)
|
||||
@JvmField val BLACK_AUGURITE = noSettingsItem("black_augurite")
|
||||
@JvmField val PEAT_BLOCK = noSettingsItem("peat_block")
|
||||
@JvmField val RAZOR_CLAW = noSettingsItem("razor_claw")
|
||||
@JvmField val RAZOR_FANG = noSettingsItem("razor_fang")
|
||||
@JvmField val AUSPICIOUS_ARMOR = heldItem("auspicious_armor")
|
||||
@JvmField val MALICIOUS_ARMOR = heldItem("malicious_armor")
|
||||
@JvmField val SHELL_HELMET = heldItem("shell_helmet")
|
||||
@JvmField val METAL_ALLOY = noSettingsItem("metal_alloy")
|
||||
@JvmField val SCROLL_OF_DARKNESS = noSettingsItem("scroll_of_darkness")
|
||||
@JvmField val SCROLL_OF_WATERS = noSettingsItem("scroll_of_waters")
|
||||
@@ -651,17 +649,17 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val EXPERIENCE_CANDY_XL = candyItem("exp_candy_xl") { _, _ -> CandyItem.DEFAULT_XL_CANDY_YIELD }
|
||||
@JvmField
|
||||
val CALCIUM = create("calcium", VitaminItem(Stats.SPECIAL_ATTACK))
|
||||
val CALCIUM = create("calcium", VitaminItem(Stats.SPECIAL_ATTACK, CobblemonBlocks.CALCIUM))
|
||||
@JvmField
|
||||
val CARBOS = create("carbos", VitaminItem(Stats.SPEED))
|
||||
val CARBOS = create("carbos", VitaminItem(Stats.SPEED, CobblemonBlocks.CARBOS))
|
||||
@JvmField
|
||||
val HP_UP = create("hp_up", VitaminItem(Stats.HP))
|
||||
val HP_UP = create("hp_up", VitaminItem(Stats.HP, CobblemonBlocks.HP_UP))
|
||||
@JvmField
|
||||
val IRON = create("iron", VitaminItem(Stats.DEFENCE))
|
||||
val IRON = create("iron", VitaminItem(Stats.DEFENCE, CobblemonBlocks.IRON))
|
||||
@JvmField
|
||||
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK))
|
||||
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK, CobblemonBlocks.PROTEIN))
|
||||
@JvmField
|
||||
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE))
|
||||
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE, CobblemonBlocks.ZINC))
|
||||
@JvmField
|
||||
val HEALTH_MOCHI = create("health_mochi", MochiItem(Stats.HP))
|
||||
@JvmField
|
||||
@@ -713,7 +711,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val PEP_UP_FLOWER = compostableBlockItem("pep_up_flower", CobblemonBlocks.PEP_UP_FLOWER)
|
||||
@JvmField
|
||||
val MEDICINAL_BREW = noSettingsItem("medicinal_brew")
|
||||
val MEDICINAL_BREW = itemNameBlockItem("medicinal_brew", CobblemonBlocks.MEDICINAL_BREW)
|
||||
@JvmField
|
||||
val REMEDY = compostableItem("remedy", RemedyItem(RemedyItem.NORMAL), 0.65f)
|
||||
@JvmField
|
||||
@@ -721,7 +719,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val SUPERB_REMEDY = compostableItem("superb_remedy", RemedyItem(RemedyItem.SUPERB), 1f)
|
||||
@JvmField
|
||||
val MOOMOO_MILK = create("moomoo_milk", MoomooMilk(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16))))
|
||||
val MOOMOO_MILK = create("moomoo_milk", MoomooMilk(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16)), CobblemonBlocks.MOOMOO_MILK))
|
||||
|
||||
@JvmField
|
||||
val POTION = create("potion", PotionItem(PotionType.POTION, CobblemonBlocks.POTION))
|
||||
@@ -743,34 +741,34 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val MAX_REVIVE = create("max_revive", ReviveItem(max = true))
|
||||
@JvmField
|
||||
val PP_UP = create("pp_up", PPUpItem(1))
|
||||
val PP_UP = create("pp_up", PPUpItem(1, CobblemonBlocks.PP_UP))
|
||||
@JvmField
|
||||
val PP_MAX = create("pp_max", PPUpItem(3))
|
||||
val PP_MAX = create("pp_max", PPUpItem(3, CobblemonBlocks.PP_MAX))
|
||||
|
||||
@JvmField
|
||||
val RED_MINT_SEEDS = mintSeed("red", MintType.RED.getCropBlock())
|
||||
@JvmField
|
||||
val RED_MINT_LEAF = mintLeaf("red", MintLeafItem(MintType.RED))
|
||||
val RED_MINT_LEAF = compostableItemNameBlockItem("red_mint_leaf", CobblemonBlocks.RED_MINT_LEAF_LITTER, 0.50f)
|
||||
@JvmField
|
||||
val BLUE_MINT_SEEDS = mintSeed("blue", MintType.BLUE.getCropBlock())
|
||||
@JvmField
|
||||
val BLUE_MINT_LEAF = mintLeaf("blue", MintLeafItem(MintType.BLUE))
|
||||
val BLUE_MINT_LEAF = compostableItemNameBlockItem("blue_mint_leaf", CobblemonBlocks.BLUE_MINT_LEAF_LITTER, 0.50f)
|
||||
@JvmField
|
||||
val CYAN_MINT_SEEDS = mintSeed("cyan", MintType.CYAN.getCropBlock())
|
||||
@JvmField
|
||||
val CYAN_MINT_LEAF = mintLeaf("cyan", MintLeafItem(MintType.CYAN))
|
||||
val CYAN_MINT_LEAF = compostableItemNameBlockItem("cyan_mint_leaf", CobblemonBlocks.CYAN_MINT_LEAF_LITTER, 0.50f)
|
||||
@JvmField
|
||||
val PINK_MINT_SEEDS = mintSeed("pink", MintType.PINK.getCropBlock())
|
||||
@JvmField
|
||||
val PINK_MINT_LEAF = mintLeaf("pink", MintLeafItem(MintType.PINK))
|
||||
val PINK_MINT_LEAF = compostableItemNameBlockItem("pink_mint_leaf", CobblemonBlocks.PINK_MINT_LEAF_LITTER, 0.50f)
|
||||
@JvmField
|
||||
val GREEN_MINT_SEEDS = mintSeed("green", MintType.GREEN.getCropBlock())
|
||||
@JvmField
|
||||
val GREEN_MINT_LEAF = mintLeaf("green", MintLeafItem(MintType.GREEN))
|
||||
val GREEN_MINT_LEAF = compostableItemNameBlockItem("green_mint_leaf", CobblemonBlocks.GREEN_MINT_LEAF_LITTER, 0.50f)
|
||||
@JvmField
|
||||
val WHITE_MINT_SEEDS = mintSeed("white", MintType.WHITE.getCropBlock())
|
||||
@JvmField
|
||||
val WHITE_MINT_LEAF = mintLeaf("white", MintLeafItem(MintType.WHITE))
|
||||
val WHITE_MINT_LEAF = compostableItemNameBlockItem("white_mint_leaf", CobblemonBlocks.WHITE_MINT_LEAF_LITTER, 0.50f)
|
||||
|
||||
val mints = mutableMapOf<String, MintItem>()
|
||||
|
||||
@@ -922,11 +920,18 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField val CHOICE_SPECS = wearableItem("choice_specs")
|
||||
@JvmField val EXP_SHARE = wearableItem("exp_share")
|
||||
@JvmField val FOCUS_BAND = wearableItem("focus_band")
|
||||
@JvmField val KINGS_ROCK = wearableItem("kings_rock")
|
||||
@JvmField val KINGS_ROCK = wearableBlockItem("kings_rock", CobblemonBlocks.KINGS_ROCK)
|
||||
@JvmField val NEVER_MELT_ICE = wearableBlockItem("never_melt_ice", CobblemonBlocks.NEVER_MELT_ICE)
|
||||
@JvmField val MUSCLE_BAND = wearableItem("muscle_band")
|
||||
@JvmField val ROCKY_HELMET = wearableItem("rocky_helmet")
|
||||
@JvmField val SAFETY_GOGGLES = wearableItem("safety_goggles")
|
||||
@JvmField val WISE_GLASSES = wearableItem("wise_glasses")
|
||||
@JvmField val SHARP_BEAK = wearableItem("sharp_beak")
|
||||
@JvmField val COVERT_CLOAK = wearableItem("covert_cloak")
|
||||
@JvmField val SHELL_HELMET = wearableItem("shell_helmet")
|
||||
@JvmField val GALARICA_WREATH = wearableBlockItem("galarica_wreath", CobblemonBlocks.GALARICA_WREATH)
|
||||
@JvmField val CLEANSE_TAG = wearableBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
|
||||
@JvmField val SPELL_TAG = wearableBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
|
||||
|
||||
// Held Items
|
||||
@JvmField
|
||||
@@ -954,21 +959,17 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val CHOICE_SCARF = heldItem("choice_scarf")
|
||||
@JvmField
|
||||
val CLEANSE_TAG = itemNameBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
|
||||
@JvmField
|
||||
val CLEAR_AMULET = heldItem("clear_amulet")
|
||||
@JvmField
|
||||
val COVERT_CLOAK = heldItem("covert_cloak")
|
||||
@JvmField
|
||||
val DESTINY_KNOT = heldItem("destiny_knot")
|
||||
val DESTINY_KNOT = itemNameBlockItem("destiny_knot", CobblemonBlocks.DESTINY_KNOT)
|
||||
@JvmField
|
||||
val DRAGON_FANG = heldItem("dragon_fang")
|
||||
@JvmField
|
||||
val EJECT_BUTTON = heldItem("eject_button")
|
||||
val EJECT_BUTTON = itemNameBlockItem("eject_button", CobblemonBlocks.EJECT_BUTTON)
|
||||
@JvmField
|
||||
val EJECT_PACK = heldItem("eject_pack")
|
||||
@JvmField
|
||||
val ELECTRIC_SEED = heldItem("electric_seed")
|
||||
val ELECTRIC_SEED = itemNameBlockItem("electric_seed", CobblemonBlocks.ELECTRIC_SEED)
|
||||
@JvmField
|
||||
val EVERSTONE = heldItem("everstone")
|
||||
@JvmField
|
||||
@@ -978,13 +979,13 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val FAIRY_FEATHER = heldItem("fairy_feather")
|
||||
@JvmField
|
||||
val FLAME_ORB = heldItem("flame_orb")
|
||||
val FLAME_ORB = itemNameBlockItem("flame_orb", CobblemonBlocks.FLAME_ORB)
|
||||
@JvmField
|
||||
val FLOAT_STONE = heldItem("float_stone")
|
||||
@JvmField
|
||||
val FOCUS_SASH = heldItem("focus_sash")
|
||||
@JvmField
|
||||
val GRASSY_SEED = heldItem("grassy_seed")
|
||||
val GRASSY_SEED = itemNameBlockItem("grassy_seed", CobblemonBlocks.GRASSY_SEED)
|
||||
@JvmField
|
||||
val GRIP_CLAW = heldItem("grip_claw")
|
||||
@JvmField
|
||||
@@ -992,34 +993,32 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val HEAVY_DUTY_BOOTS = heldItem("heavy_duty_boots")
|
||||
@JvmField
|
||||
val IRON_BALL = heldItem("iron_ball")
|
||||
val IRON_BALL = itemNameBlockItem("iron_ball", CobblemonBlocks.IRON_BALL)
|
||||
@JvmField
|
||||
val LAGGING_TAIL = heldItem("lagging_tail")
|
||||
@JvmField
|
||||
val LEFTOVERS = compostableHeldItem("leftovers", null, 0.50f)
|
||||
@JvmField
|
||||
val LIFE_ORB = heldItem("life_orb")
|
||||
val LIFE_ORB = itemNameBlockItem("life_orb", CobblemonBlocks.LIFE_ORB)
|
||||
@JvmField
|
||||
val LIGHT_BALL = heldItem("light_ball")
|
||||
val LIGHT_BALL = itemNameBlockItem("light_ball", CobblemonBlocks.LIGHT_BALL)
|
||||
@JvmField
|
||||
val LIGHT_CLAY = heldItem("light_clay")
|
||||
@JvmField
|
||||
val LOADED_DICE = heldItem("loaded_dice")
|
||||
val LOADED_DICE = itemNameBlockItem("loaded_dice", CobblemonBlocks.LOADED_DICE)
|
||||
@JvmField
|
||||
val LUCKY_EGG = heldItem("lucky_egg")
|
||||
@JvmField
|
||||
val LUMINOUS_MOSS = heldItem("luminous_moss")
|
||||
val LUMINOUS_MOSS = itemNameBlockItem("luminous_moss", CobblemonBlocks.LUMINOUS_MOSS)
|
||||
@JvmField
|
||||
val MAGNET = heldItem("magnet")
|
||||
val MAGNET = itemNameBlockItem("magnet", CobblemonBlocks.MAGNET)
|
||||
@JvmField
|
||||
val METRONOME = heldItem("metronome")
|
||||
val METRONOME = itemNameBlockItem("metronome", CobblemonBlocks.METRONOME)
|
||||
@JvmField
|
||||
val MIRACLE_SEED = heldItem("miracle_seed")
|
||||
@JvmField
|
||||
val MYSTIC_WATER = heldItem("mystic_water")
|
||||
@JvmField
|
||||
val NEVER_MELT_ICE = heldItem("never_melt_ice")
|
||||
@JvmField
|
||||
val POISON_BARB = heldItem("poison_barb")
|
||||
@JvmField
|
||||
val POWER_ANKLET = heldItem("power_anklet")
|
||||
@@ -1034,7 +1033,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val POWER_WEIGHT = heldItem("power_weight")
|
||||
@JvmField
|
||||
val PSYCHIC_SEED = heldItem("psychic_seed")
|
||||
val PSYCHIC_SEED = itemNameBlockItem("psychic_seed", CobblemonBlocks.PSYCHIC_SEED)
|
||||
@JvmField
|
||||
val PROTECTIVE_PADS = heldItem("protective_pads")
|
||||
@JvmField
|
||||
@@ -1044,13 +1043,11 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val RED_CARD = heldItem("red_card")
|
||||
@JvmField
|
||||
val RING_TARGET = heldItem("ring_target")
|
||||
val RING_TARGET = itemNameBlockItem("ring_target", CobblemonBlocks.RING_TARGET)
|
||||
@JvmField
|
||||
val ROOM_SERVICE = heldItem("room_service")
|
||||
@JvmField
|
||||
val SCOPE_LENS = heldItem("scope_lens")
|
||||
@JvmField
|
||||
val SHARP_BEAK = heldItem("sharp_beak")
|
||||
val SCOPE_LENS = itemNameBlockItem("scope_lens", CobblemonBlocks.SCOPE_LENS)
|
||||
@JvmField
|
||||
val SHED_SHELL = heldItem("shed_shell")
|
||||
@JvmField
|
||||
@@ -1060,21 +1057,19 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val SILVER_POWDER = heldItem("silver_powder")
|
||||
@JvmField
|
||||
val SOFT_SAND = heldItem("soft_sand")
|
||||
val SOFT_SAND = itemNameBlockItem("soft_sand", CobblemonBlocks.SOFT_SAND)
|
||||
@JvmField
|
||||
val SPELL_TAG = itemNameBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
|
||||
@JvmField
|
||||
val SMOKE_BALL = heldItem("smoke_ball")
|
||||
val SMOKE_BALL = itemNameBlockItem("smoke_ball", CobblemonBlocks.SMOKE_BALL)
|
||||
@JvmField
|
||||
val SOOTHE_BELL = heldItem("soothe_bell")
|
||||
@JvmField
|
||||
val STICKY_BARB = heldItem("sticky_barb")
|
||||
@JvmField
|
||||
val TERRAIN_EXTENDER = heldItem("terrain_extender")
|
||||
val TERRAIN_EXTENDER = itemNameBlockItem("terrain_extender", CobblemonBlocks.TERRAIN_EXTENDER)
|
||||
@JvmField
|
||||
val THROAT_SPRAY = heldItem("throat_spray")
|
||||
val THROAT_SPRAY = itemNameBlockItem("throat_spray", CobblemonBlocks.THROAT_SPRAY)
|
||||
@JvmField
|
||||
val TOXIC_ORB = heldItem("toxic_orb")
|
||||
val TOXIC_ORB = itemNameBlockItem("toxic_orb", CobblemonBlocks.TOXIC_ORB)
|
||||
@JvmField
|
||||
val TWISTED_SPOON = heldItem("twisted_spoon")
|
||||
@JvmField
|
||||
@@ -1082,33 +1077,33 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val WEAKNESS_POLICY = itemNameBlockItem("weakness_policy", CobblemonBlocks.WEAKNESS_POLICY)
|
||||
@JvmField
|
||||
val WIDE_LENS = heldItem("wide_lens")
|
||||
val WIDE_LENS = itemNameBlockItem("wide_lens", CobblemonBlocks.WIDE_LENS)
|
||||
@JvmField
|
||||
val ZOOM_LENS = heldItem("zoom_lens")
|
||||
val ZOOM_LENS = itemNameBlockItem("zoom_lens", CobblemonBlocks.ZOOM_LENS)
|
||||
@JvmField
|
||||
val MENTAL_HERB = compostableHeldItem("mental_herb", null, 0.85F)
|
||||
@JvmField
|
||||
val MIRROR_HERB = compostableHeldItem("mirror_herb", null, 0.85F)
|
||||
@JvmField
|
||||
val MISTY_SEED = heldItem("misty_seed")
|
||||
val MISTY_SEED = itemNameBlockItem("misty_seed", CobblemonBlocks.MISTY_SEED)
|
||||
@JvmField
|
||||
val POWER_HERB = compostableHeldItem("power_herb", null, 0.85F)
|
||||
@JvmField
|
||||
val WHITE_HERB = compostableHeldItem("white_herb", null, 0.85F)
|
||||
@JvmField
|
||||
val BRIGHT_POWDER = heldItem("bright_powder")
|
||||
val BRIGHT_POWDER = glowItem("bright_powder", CobblemonBlocks.BRIGHT_POWDER)
|
||||
@JvmField
|
||||
val METAL_POWDER = heldItem("metal_powder")
|
||||
val METAL_POWDER = itemNameBlockItem("metal_powder", CobblemonBlocks.METAL_POWDER)
|
||||
@JvmField
|
||||
val QUICK_POWDER = heldItem("quick_powder")
|
||||
val QUICK_POWDER = itemNameBlockItem("quick_powder", CobblemonBlocks.QUICK_POWDER)
|
||||
@JvmField
|
||||
val DAMP_ROCK = heldItem("damp_rock")
|
||||
val DAMP_ROCK = itemNameBlockItem("damp_rock", CobblemonBlocks.DAMP_ROCK)
|
||||
@JvmField
|
||||
val HEAT_ROCK = heldItem("heat_rock")
|
||||
val HEAT_ROCK = itemNameBlockItem("heat_rock", CobblemonBlocks.HEAT_ROCK)
|
||||
@JvmField
|
||||
val SMOOTH_ROCK = heldItem("smooth_rock")
|
||||
val SMOOTH_ROCK = itemNameBlockItem("smooth_rock", CobblemonBlocks.SMOOTH_ROCK)
|
||||
@JvmField
|
||||
val ICY_ROCK = heldItem("icy_rock")
|
||||
val ICY_ROCK = itemNameBlockItem("icy_rock", CobblemonBlocks.ICY_ROCK)
|
||||
|
||||
// Mulch
|
||||
@JvmField
|
||||
@@ -1563,6 +1558,25 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
//@JvmField
|
||||
//val BINDING_SOIL = blockItem("binding_soil", CobblemonBlocks.BINDING_SOIL)
|
||||
|
||||
// Plants, Flora, and Flowers
|
||||
@JvmField
|
||||
val LOBAILA = compostableBlockItem("lobaila", CobblemonBlocks.LOBAILA, 0.65f)
|
||||
@JvmField
|
||||
val POMOHANA = compostableBlockItem("pomohana", CobblemonBlocks.POMOHANA, 0.3f)
|
||||
@JvmField
|
||||
val PAUVILLEA = compostableBlockItem("pauvillea", CobblemonBlocks.PAUVILLEA, 0.65f)
|
||||
@JvmField
|
||||
val WISTENSU = compostableBlockItem("wistensu", CobblemonBlocks.WISTENSU, 0.5f)
|
||||
|
||||
@JvmField
|
||||
val VOLTACONIA = compostableBlockItem("voltaconia", CobblemonBlocks.VOLTACONIA, 0.85f)
|
||||
@JvmField
|
||||
val VERDROOT = compostableBlockItem("verdroot", CobblemonBlocks.VERDROOT, 0.85f)
|
||||
@JvmField
|
||||
val MISTLEAF = compostableBlockItem("mistleaf", CobblemonBlocks.MISTLEAF, 0.85f)
|
||||
@JvmField
|
||||
val LUCIBELL = compostableBlockItem("lucibell", CobblemonBlocks.LUCIBELL, 0.85f)
|
||||
|
||||
private fun blockItem(name: String, block: Block, rarity: Rarity = Rarity.COMMON): BlockItem = this.create(name, BlockItem(block, Item.Properties().rarity(rarity)))
|
||||
|
||||
private fun itemNameBlockItem(name: String, block: Block, rarity: Rarity = Rarity.COMMON): ItemNameBlockItem = this.create(name, ItemNameBlockItem(block, Item.Properties().rarity(rarity)))
|
||||
@@ -1599,7 +1613,16 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
|
||||
private fun wearableItem(name: String, heldItemRemappedName: String? = null): CobblemonItem = create(
|
||||
name,
|
||||
WearableItem(name).also {
|
||||
WearableHatItem(name).also {
|
||||
wearables.add(it)
|
||||
if (heldItemRemappedName != null) {
|
||||
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
|
||||
}
|
||||
}
|
||||
)
|
||||
private fun wearableBlockItem(name: String, block: Block, heldItemRemappedName: String? = null): WearableBlockItem = create(
|
||||
name,
|
||||
WearableBlockItem(name, block).also {
|
||||
wearables.add(it)
|
||||
if (heldItemRemappedName != null) {
|
||||
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
|
||||
@@ -1756,4 +1779,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
private fun foodItem(nutrition: Int, saturationModifier: Float): Item {
|
||||
return CobblemonItem(Item.Properties().food(FoodProperties.Builder().nutrition(nutrition).saturationModifier(saturationModifier).build()))
|
||||
}
|
||||
|
||||
private fun glowItem(name: String, block: Block): GlowItem = this.create(name, GlowItem(block, Item.Properties()))
|
||||
}
|
||||
@@ -425,6 +425,7 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(MovesRegistrySyncPacket.ID, MovesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(BerryRegistrySyncPacket.ID, BerryRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(SpeciesRegistrySyncPacket.ID, SpeciesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(DexEvolutionDisplaySyncPacket.ID, DexEvolutionDisplaySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(PropertiesCompletionRegistrySyncPacket.ID, PropertiesCompletionRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(StandardSpeciesFeatureSyncPacket.ID, StandardSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(GlobalSpeciesFeatureSyncPacket.ID, GlobalSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.air.HelicopterSetting
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.air.HoverSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.air.JetSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.air.RocketSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.air.BoardSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleSettings
|
||||
@@ -56,6 +57,7 @@ object CobblemonRideSettings : DataRegistry {
|
||||
var hover = HoverSettings()
|
||||
var jet = JetSettings()
|
||||
var rocket = RocketSettings()
|
||||
var board = BoardSettings()
|
||||
|
||||
var horse = HorseSettings()
|
||||
var minekart = MinekartSettings()
|
||||
@@ -75,6 +77,7 @@ object CobblemonRideSettings : DataRegistry {
|
||||
hover = hover,
|
||||
jet = jet,
|
||||
rocket = rocket,
|
||||
board = board,
|
||||
horse = horse,
|
||||
minekart = minekart,
|
||||
vehicle = vehicle,
|
||||
@@ -93,6 +96,7 @@ object CobblemonRideSettings : DataRegistry {
|
||||
hover = loadStyle(manager, "hover", HoverSettings::class.java)
|
||||
jet = loadStyle(manager, "jet", JetSettings::class.java)
|
||||
rocket = loadStyle(manager, "rocket", RocketSettings::class.java)
|
||||
board = loadStyle(manager, "board", BoardSettings::class.java)
|
||||
horse = loadStyle(manager, "horse", HorseSettings::class.java)
|
||||
minekart = loadStyle(manager, "minekart", MinekartSettings::class.java)
|
||||
vehicle = loadStyle(manager, "vehicle", VehicleSettings::class.java)
|
||||
|
||||
@@ -148,6 +148,9 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
@JvmField
|
||||
val MONITOR_GLITCHING = this.create("block.monitor.glitching")
|
||||
|
||||
@JvmField
|
||||
val BRIGHT_POWDER_USE = this.create("block.bright_powder.use")
|
||||
|
||||
@JvmField
|
||||
val FOSSIL_MACHINE_ACTIVATE = this.create("block.fossil_machine.activate")
|
||||
@JvmField
|
||||
@@ -657,5 +660,95 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
SoundEvents.WOOL_FALL
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_POWDER_PLACE = this.create("block.item_block.powder.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_POWDER_BREAK = this.create("block.item_block.powder.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_POWDER_STEP = this.create("block.item_block.powder.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_POWDER_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_POWDER_BREAK,
|
||||
ITEM_BLOCK_POWDER_STEP,
|
||||
ITEM_BLOCK_POWDER_PLACE,
|
||||
ITEM_BLOCK_POWDER_STEP,
|
||||
ITEM_BLOCK_POWDER_STEP
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_LIQUID_PLACE = this.create("block.item_block.bottle_liquid.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_LIQUID_BREAK = this.create("block.item_block.bottle_liquid.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_LIQUID_STEP = this.create("block.item_block.bottle_liquid.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_BREAK,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_STEP,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_PLACE,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_PLACE,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_PLACE
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_PILLS_PLACE = this.create("block.item_block.bottle_pills.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_PILLS_BREAK = this.create("block.item_block.bottle_pills.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_PILLS_STEP = this.create("block.item_block.bottle_pills.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_PILLS_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_BREAK,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_STEP,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_PLACE,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_PLACE,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_PLACE
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_DICE_PLACE = this.create("block.item_block.dice.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_DICE_BREAK = this.create("block.item_block.dice.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_DICE_STEP = this.create("block.item_block.dice.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_DICE_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_DICE_BREAK,
|
||||
ITEM_BLOCK_DICE_STEP,
|
||||
ITEM_BLOCK_DICE_PLACE,
|
||||
ITEM_BLOCK_DICE_STEP,
|
||||
ITEM_BLOCK_DICE_STEP
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_METAL_CAN_PLACE = this.create("block.item_block.metal_can.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_METAL_CAN_BREAK = this.create("block.item_block.metal_can.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_METAL_CAN_STEP = this.create("block.item_block.metal_can.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_METAL_CAN_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_METAL_CAN_BREAK,
|
||||
ITEM_BLOCK_METAL_CAN_STEP,
|
||||
ITEM_BLOCK_METAL_CAN_PLACE,
|
||||
ITEM_BLOCK_METAL_CAN_STEP,
|
||||
ITEM_BLOCK_METAL_CAN_STEP
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_JAR_GLASS_LIQUID_PLACE = this.create("block.item_block.jar_glass_liquid.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_JAR_GLASS_LIQUID_BREAK = this.create("block.item_block.jar_glass_liquid.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_JAR_GLASS_LIQUID_STEP = this.create("block.item_block.jar_glass_liquid.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_JAR_GLASS_LIQUID_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_JAR_GLASS_LIQUID_BREAK,
|
||||
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP,
|
||||
ITEM_BLOCK_JAR_GLASS_LIQUID_PLACE,
|
||||
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP,
|
||||
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP
|
||||
)
|
||||
|
||||
private fun create(name: String): SoundEvent = this.create(name, SoundEvent.createVariableRangeEvent(cobblemonResource(name)))
|
||||
}
|
||||
+31
-7
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
@@ -17,6 +18,7 @@ import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
@@ -34,6 +36,8 @@ import net.minecraft.world.entity.ai.sensing.SensorType
|
||||
class HateEntityTaskConfig : SingleTaskConfig {
|
||||
val entityCondition: Expression = "true".asExpression()
|
||||
val range: ExpressionOrEntityVariable = Either.left("24".asExpression())
|
||||
val chance: Float = Cobblemon.config.predationChance
|
||||
private val coercedChance = chance.coerceIn(0f, 1f)
|
||||
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = emptyList<MoLangConfigVariable>()
|
||||
override fun createTask(
|
||||
@@ -43,24 +47,44 @@ class HateEntityTaskConfig : SingleTaskConfig {
|
||||
val range = range.resolveFloat(behaviourConfigurationContext.runtime)
|
||||
behaviourConfigurationContext.addMemories(
|
||||
MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES,
|
||||
MemoryModuleType.ANGRY_AT
|
||||
MemoryModuleType.ANGRY_AT,
|
||||
MemoryModuleType.ATTACK_TARGET
|
||||
)
|
||||
behaviourConfigurationContext.addSensors(SensorType.NEAREST_LIVING_ENTITIES)
|
||||
return BehaviorBuilder.create { instance ->
|
||||
instance.group(
|
||||
instance.present(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES),
|
||||
instance.registered(MemoryModuleType.ANGRY_AT)
|
||||
).apply(instance) { entities, angryAt ->
|
||||
instance.registered(MemoryModuleType.ANGRY_AT),
|
||||
instance.registered(MemoryModuleType.ATTACK_TARGET),
|
||||
).apply(instance) { entities, angryAt, attackTarget ->
|
||||
Trigger { world, entity, _ ->
|
||||
val target = instance.get(entities).findClosest {
|
||||
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
// bail if gamerule is disabled
|
||||
if (!world.gameRules.getBoolean(CobblemonGameRules.DO_POKEMON_PREDATION)) return@Trigger false
|
||||
val target = instance.get(entities).findClosest { hated ->
|
||||
mainThreadRuntime.withQueryValue("entity", hated.asMostSpecificMoLangValue())
|
||||
return@findClosest mainThreadRuntime.resolveBoolean(entityCondition)
|
||||
}.orElse(null)
|
||||
|
||||
var shouldTrigger = false
|
||||
if (target != null && target.distanceTo(entity) <= range) {
|
||||
angryAt.set(target.uuid)
|
||||
shouldTrigger = when {
|
||||
// set always attacking if activity chance is equal to 1.0
|
||||
coercedChance == 1f -> {
|
||||
angryAt.set(target.uuid)
|
||||
attackTarget.set(target)
|
||||
true
|
||||
}
|
||||
// roll the dice in other way
|
||||
coercedChance != 1f && world.random.nextFloat() < coercedChance -> {
|
||||
angryAt.set(target.uuid)
|
||||
attackTarget.set(target)
|
||||
true
|
||||
}
|
||||
// fallback to peaceful behavior in unattended cases
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
return@Trigger true
|
||||
return@Trigger shouldTrigger
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.battles.model
|
||||
|
||||
/**
|
||||
* How a [PokemonBattle] came to an end.
|
||||
*/
|
||||
enum class BattleEndResult {
|
||||
/** One side defeated the other. */
|
||||
VICTORY,
|
||||
/** A wild Pokémon was captured, ending the battle. */
|
||||
CAPTURE,
|
||||
/** An actor forfeited, giving the victory to the other side. */
|
||||
FORFEIT,
|
||||
/** A player fled from a wild Pokémon. */
|
||||
FLED,
|
||||
/** The battle was stopped without a winner, such as by a command, a player disconnecting or an error. */
|
||||
STOPPED
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.FleeableBattleActor
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleEndedEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleFleeAttemptEvent
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
@@ -124,6 +125,12 @@ open class PokemonBattle(
|
||||
var winners = listOf<BattleActor>()
|
||||
var losers = listOf<BattleActor>()
|
||||
var ended = false
|
||||
/** How the battle ended, or null if it hasn't ended yet. */
|
||||
var endResult: BattleEndResult? = null
|
||||
/** The actor that forfeited the battle, if any. */
|
||||
var forfeitedBy: BattleActor? = null
|
||||
/** Whether a player fled from the battle. */
|
||||
var fled = false
|
||||
// TEMP battle showcase stuff
|
||||
var announcingRules = false
|
||||
var turn: Int = 0
|
||||
@@ -232,6 +239,10 @@ open class PokemonBattle(
|
||||
}
|
||||
|
||||
fun writeShowdownAction(vararg messages: String) {
|
||||
if (messages.any { it.contains("\n") || it.contains("\r") }) {
|
||||
LOGGER.warn("Refusing to forward Showdown action with an embedded line break for battle {}: {}", battleId, messages.joinToString(" | "))
|
||||
return
|
||||
}
|
||||
log(messages.joinToString("\n"))
|
||||
ShowdownService.service.send(battleId, messages.toList().toTypedArray())
|
||||
}
|
||||
@@ -251,6 +262,14 @@ open class PokemonBattle(
|
||||
|
||||
fun end() {
|
||||
ended = true
|
||||
val result = when {
|
||||
winners.isNotEmpty() && showdownMessages.any { "capture" in it } -> BattleEndResult.CAPTURE
|
||||
winners.isNotEmpty() && forfeitedBy != null -> BattleEndResult.FORFEIT
|
||||
winners.isNotEmpty() -> BattleEndResult.VICTORY
|
||||
fled -> BattleEndResult.FLED
|
||||
else -> BattleEndResult.STOPPED
|
||||
}
|
||||
endResult = result
|
||||
val awardToFainted = Cobblemon.config.awardExperienceToFaintedPokemon
|
||||
val awardOnLoss = Cobblemon.config.awardExperienceOnBattleLoss
|
||||
this.actors.forEach { actor ->
|
||||
@@ -332,6 +351,7 @@ open class PokemonBattle(
|
||||
}
|
||||
}
|
||||
}
|
||||
CobblemonEvents.BATTLE_ENDED.post(BattleEndedEvent(this, result, forfeitedBy.takeIf { result == BattleEndResult.FORFEIT }))
|
||||
sendUpdate(BattleEndPacket())
|
||||
BattleRegistry.closeBattle(this)
|
||||
}
|
||||
@@ -502,6 +522,7 @@ open class PokemonBattle(
|
||||
.forEach{it.pokemon.heal()}
|
||||
CobblemonEvents.BATTLE_FLED.post(BattleFledEvent(this, actors.asSequence().filterIsInstance<PlayerBattleActor>().iterator().next()))
|
||||
actors.filterIsInstance<EntityBackedBattleActor<*>>().mapNotNull { it.entity }.forEach { it.sendSystemMessage(battleLang("flee").yellow()) }
|
||||
fled = true
|
||||
stop()
|
||||
}
|
||||
}
|
||||
@@ -539,6 +560,7 @@ open class PokemonBattle(
|
||||
private fun checkForfeit(): Boolean {
|
||||
val forfeit = actors.find { it.responses.any { it is ForfeitActionResponse } }
|
||||
return forfeit?.let {
|
||||
forfeitedBy = it
|
||||
this.dispatchWaiting { this.broadcastChatMessage(battleLang("forfeit", it.getName()).red()) }
|
||||
writeShowdownAction(">forcelose ${it.showdownId}")
|
||||
true
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.data
|
||||
|
||||
import com.cobblemon.mod.common.util.endsWith
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.core.HolderLookup
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.util.concurrent.ExecutionException
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.packs.PackType
|
||||
import net.minecraft.server.packs.resources.ResourceManager
|
||||
|
||||
/**
|
||||
* A [DataRegistry] that consumes JSON files.
|
||||
* Every deserialized instance is attached to an [Identifier].
|
||||
* For example a file under data/mymod/[resourcePath]/entry.json would be backed by the identifier modid:entry.
|
||||
*
|
||||
* @param T The type of the data consumed by this registry.
|
||||
*
|
||||
* @author Licious
|
||||
* @since August 5th, 2022
|
||||
*/
|
||||
interface ServerJsonDataRegistry<T> : DataRegistry {
|
||||
|
||||
/**
|
||||
* The [TypeToken] of type [T].
|
||||
*/
|
||||
val typeToken: TypeToken<T>
|
||||
|
||||
/**
|
||||
* The folder location for the data this registry will consume.
|
||||
*/
|
||||
val resourcePath: String
|
||||
|
||||
override val type: PackType
|
||||
get() = PackType.SERVER_DATA
|
||||
|
||||
override fun reload(manager: ResourceManager) {
|
||||
//NO-OP, handled by reload(manager, registry)
|
||||
}
|
||||
|
||||
fun reload(manager: ResourceManager, registry: HolderLookup.Provider) {
|
||||
val gson = setupParser(registry)
|
||||
val data = hashMapOf<ResourceLocation, T>()
|
||||
manager.listResources(resourcePath) { path -> path.endsWith(JSON_EXTENSION) }.forEach { (identifier, resource) ->
|
||||
if (identifier.namespace == "pixelmon") {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
resource.open().use { stream ->
|
||||
stream.bufferedReader().use { reader ->
|
||||
val resolvedIdentifier = ResourceLocation.fromNamespaceAndPath(identifier.namespace, File(identifier.path).nameWithoutExtension)
|
||||
data[resolvedIdentifier] = parse(reader, resolvedIdentifier, gson)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reload(data, registry)
|
||||
}
|
||||
|
||||
fun parse(reader: BufferedReader, identifier: ResourceLocation, gson: Gson): T {
|
||||
return try {
|
||||
gson.fromJson(reader, typeToken.type)
|
||||
} catch (exception: Exception) {
|
||||
throw ExecutionException("Error loading JSON for data: $identifier", exception)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the creation of [Gson] instances which have adapters that are registry-aware, e.g. [com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter]
|
||||
*/
|
||||
fun setupParser(registry: HolderLookup.Provider): Gson
|
||||
|
||||
/**
|
||||
* Reloads this registry from the deserialized data.
|
||||
*
|
||||
* @param data A map of the data associating an instance to the respective identifier from the [ResourceManager].
|
||||
*/
|
||||
fun reload(data: Map<ResourceLocation, T>, registry: HolderLookup.Provider)
|
||||
|
||||
companion object {
|
||||
const val JSON_EXTENSION = ".json"
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.events.battles.BattleFaintedEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleFleeAttemptEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleStartedEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleEndedEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleVictoryEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.instruction.FormeChangeEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.instruction.MegaEvolutionEvent
|
||||
@@ -139,6 +140,9 @@ object CobblemonEvents {
|
||||
@JvmField
|
||||
val BATTLE_FAINTED = EventObservable<BattleFaintedEvent>()
|
||||
|
||||
@JvmField
|
||||
val BATTLE_ENDED = EventObservable<BattleEndedEvent>()
|
||||
|
||||
// instructions
|
||||
@JvmField
|
||||
val MEGA_EVOLUTION = EventObservable<MegaEvolutionEvent>()
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.events.battles
|
||||
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.bedrockk.molang.runtime.value.MoValue
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
import com.cobblemon.mod.common.api.battles.model.BattleEndResult
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
|
||||
import com.cobblemon.mod.common.util.asArrayValue
|
||||
|
||||
/**
|
||||
* Event fired whenever a [PokemonBattle] ends, regardless of how it ended (victory, fleeing, being stopped, etc).
|
||||
*
|
||||
* [PokemonBattle.winners] and [PokemonBattle.losers] are only populated if the [result] has a winner,
|
||||
* that is [BattleEndResult.VICTORY], [BattleEndResult.CAPTURE] or [BattleEndResult.FORFEIT].
|
||||
*
|
||||
* @param result How the battle ended.
|
||||
* @param forfeitedBy The actor that forfeited, if the [result] is [BattleEndResult.FORFEIT].
|
||||
*/
|
||||
data class BattleEndedEvent(
|
||||
override val battle: PokemonBattle,
|
||||
val result: BattleEndResult,
|
||||
val forfeitedBy: BattleActor?
|
||||
) : BattleEvent {
|
||||
/** Whether one side won the battle, which includes captures and forfeits. */
|
||||
val hasWinner = battle.winners.isNotEmpty()
|
||||
val wasVictory = result == BattleEndResult.VICTORY
|
||||
val wasCapture = result == BattleEndResult.CAPTURE
|
||||
val wasForfeit = result == BattleEndResult.FORFEIT
|
||||
val wasFled = result == BattleEndResult.FLED
|
||||
val wasStopped = result == BattleEndResult.STOPPED
|
||||
|
||||
/** Whether the battle was between players and wild Pokémon. */
|
||||
val isPvW = battle.isPvW
|
||||
/** Whether the battle was between players and NPCs. */
|
||||
val isPvN = battle.isPvN
|
||||
/** Whether the battle was between players. */
|
||||
val isPvP = battle.isPvP
|
||||
|
||||
val context = mutableMapOf<String, MoValue>(
|
||||
"battle" to battle.struct,
|
||||
"result" to StringValue(result.name.lowercase()),
|
||||
"has_winner" to DoubleValue(hasWinner),
|
||||
"was_victory" to DoubleValue(wasVictory),
|
||||
"was_capture" to DoubleValue(wasCapture),
|
||||
"was_forfeit" to DoubleValue(wasForfeit),
|
||||
"was_fled" to DoubleValue(wasFled),
|
||||
"was_stopped" to DoubleValue(wasStopped),
|
||||
"is_pvw" to DoubleValue(isPvW),
|
||||
"is_pvn" to DoubleValue(isPvN),
|
||||
"is_pvp" to DoubleValue(isPvP),
|
||||
"winners" to battle.winners.asArrayValue { it.struct },
|
||||
"losers" to battle.losers.asArrayValue { it.struct },
|
||||
"players" to battle.actors.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
|
||||
"npcs" to battle.actors.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
|
||||
"wild_pokemon" to battle.actors.filter { it.type == ActorType.WILD }.asArrayValue { it.struct }
|
||||
).apply {
|
||||
forfeitedBy?.let { put("forfeited_by", it.struct) }
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ object Fossils: JsonDataRegistry<Fossil> {
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter())
|
||||
.create()
|
||||
|
||||
override val typeToken: TypeToken<Fossil> = TypeToken.get(Fossil::class.java)
|
||||
|
||||
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.gui
|
||||
|
||||
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
|
||||
import net.minecraft.client.gui.components.AbstractWidget
|
||||
import net.minecraft.client.gui.components.Renderable
|
||||
import net.minecraft.client.gui.components.events.GuiEventListener
|
||||
import net.minecraft.client.gui.narration.NarrationElementOutput
|
||||
import net.minecraft.network.chat.Component
|
||||
@@ -55,6 +54,8 @@ abstract class ParentWidget(
|
||||
}
|
||||
|
||||
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
|
||||
if (!active) return false
|
||||
|
||||
return children.any {
|
||||
it.mouseClicked(pMouseX, pMouseY, pButton)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.gui
|
||||
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
interface TreeNode<T> {
|
||||
val children: List<TreeNode<T>>
|
||||
}
|
||||
|
||||
class TreeDrawingNode<T>(
|
||||
val value: T,
|
||||
val children: List<TreeDrawingNode<T>>,
|
||||
) {
|
||||
var x = 0
|
||||
var y = 0
|
||||
var offset = 0
|
||||
}
|
||||
|
||||
fun <T> createDrawingTree(treeNode: TreeNode<T>): TreeDrawingNode<T> {
|
||||
val root = setup(treeNode, 0, mutableListOf<Int>(), mutableListOf<Int>())
|
||||
addMods(root, 0)
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <T> setup(
|
||||
treeNode: TreeNode<T>,
|
||||
depth: Int,
|
||||
nexts: MutableList<Int>,
|
||||
offset: MutableList<Int>,
|
||||
): TreeDrawingNode<T> {
|
||||
val children = treeNode.children.map { child ->
|
||||
setup(child, depth + 1, nexts, offset)
|
||||
}
|
||||
|
||||
val treeDrawingNode = TreeDrawingNode<T>(treeNode as T, children)
|
||||
treeDrawingNode.y = depth
|
||||
|
||||
while (nexts.size <= depth) {
|
||||
nexts.add(0)
|
||||
}
|
||||
|
||||
while (offset.size <= depth) {
|
||||
offset.add(0)
|
||||
}
|
||||
|
||||
var place =
|
||||
if (children.isEmpty()) {
|
||||
nexts[depth]
|
||||
} else if (children.size == 1) {
|
||||
// children.first().x - 1
|
||||
children.first().x
|
||||
} else {
|
||||
// (children[0].x + children[1].x) / 2
|
||||
(children.first().x + children.last().x) / 2
|
||||
}
|
||||
|
||||
offset[depth] = max(offset[depth], nexts[depth] - place)
|
||||
|
||||
if (children.isEmpty()) {
|
||||
treeDrawingNode.x = place
|
||||
} else {
|
||||
treeDrawingNode.x = place + offset[depth]
|
||||
}
|
||||
|
||||
nexts[depth] += 2
|
||||
treeDrawingNode.offset = offset[depth]
|
||||
|
||||
return treeDrawingNode
|
||||
}
|
||||
|
||||
private fun <T> addMods(
|
||||
treeNode: TreeDrawingNode<T>,
|
||||
modSum: Int,
|
||||
) {
|
||||
treeNode.x += modSum
|
||||
val newModSum = modSum + treeNode.offset
|
||||
|
||||
treeNode.children.forEach { child ->
|
||||
addMods(child, newModSum)
|
||||
}
|
||||
}
|
||||
|
||||
data class TreeDrawingBounds(val minX: Int, val maxX: Int, val minY: Int, val maxY: Int) {
|
||||
val width = maxX - minX
|
||||
val height = maxY - minY
|
||||
val xCenter = (maxX + minX) / 2.0
|
||||
val yCenter = (maxY + minY) / 2.0
|
||||
}
|
||||
|
||||
fun <T> calculateBounds(treeDrawingNode: TreeDrawingNode<T>): TreeDrawingBounds {
|
||||
var minX = treeDrawingNode.x
|
||||
var maxX = treeDrawingNode.x
|
||||
var minY = treeDrawingNode.y
|
||||
var maxY = treeDrawingNode.y
|
||||
|
||||
for (child in treeDrawingNode.children) {
|
||||
val childBounds = calculateBounds(child)
|
||||
|
||||
minX = min(minX, childBounds.minX)
|
||||
maxX = max(maxX, childBounds.maxX)
|
||||
minY = min(minY, childBounds.minY)
|
||||
maxY = max(maxY, childBounds.maxY)
|
||||
}
|
||||
|
||||
return TreeDrawingBounds(minX, maxX, minY, maxY)
|
||||
}
|
||||
File renamed without changes.
+24
-20
@@ -10,7 +10,7 @@ package com.cobblemon.mod.common.api.interaction
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.data.ServerJsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
|
||||
@@ -26,6 +26,7 @@ import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.advancements.critereon.ItemPredicate
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
@@ -36,28 +37,31 @@ import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.levelgen.structure.Structure
|
||||
import net.minecraft.world.level.material.Fluid
|
||||
|
||||
object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
|
||||
object PokemonInteractions : ServerJsonDataRegistry<PokemonInteractionSet> {
|
||||
override val id = cobblemonResource("pokemon_interactions")
|
||||
override val type = PackType.SERVER_DATA
|
||||
override fun setupParser(registry: HolderLookup.Provider): Gson {
|
||||
return GsonBuilder()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter(registry))
|
||||
.registerTypeAdapter(Requirement::class.java, CobblemonRequirementAdapter)
|
||||
.registerTypeAdapter(InteractionEffect::class.java, InteractionEffectAdapter)
|
||||
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Structure::class.java).type, StructureLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
|
||||
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
|
||||
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory())
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
}
|
||||
|
||||
override val observable = SimpleObservable<PokemonInteractions>()
|
||||
override val typeToken: TypeToken<PokemonInteractionSet> = TypeToken.get(PokemonInteractionSet::class.java)
|
||||
override val resourcePath = "pokemon_interactions"
|
||||
override val gson: Gson = GsonBuilder()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
|
||||
.registerTypeAdapter(Requirement::class.java, CobblemonRequirementAdapter)
|
||||
.registerTypeAdapter(InteractionEffect::class.java, InteractionEffectAdapter)
|
||||
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Structure::class.java).type, StructureLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
|
||||
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
|
||||
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory())
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
|
||||
val interactions = mutableListOf<PokemonInteractionSet>()
|
||||
|
||||
@@ -65,7 +69,7 @@ object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
|
||||
PokemonInteractionsSyncPacket(interactions).sendToPlayer(player)
|
||||
}
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, PokemonInteractionSet>) {
|
||||
override fun reload(data: Map<ResourceLocation, PokemonInteractionSet>, registry: HolderLookup.Provider) {
|
||||
interactions.clear()
|
||||
val split = data.entries
|
||||
.partition {
|
||||
@@ -87,4 +91,4 @@ object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
|
||||
.flatMap { it.interactions }
|
||||
.firstOrNull(interactionCheck)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
|
||||
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
|
||||
import com.cobblemon.mod.common.util.asArrayValue
|
||||
|
||||
object BattleActorMoLangFunctions : AbstractMoLangFunctionHolder<BattleActor>() {
|
||||
override fun BattleActor.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
|
||||
@@ -25,6 +26,7 @@ object BattleActorMoLangFunctions : AbstractMoLangFunctionHolder<BattleActor>()
|
||||
functions["is_npc"] = { DoubleValue(actor.type == ActorType.NPC) }
|
||||
functions["is_player"] = { DoubleValue(actor.type == ActorType.PLAYER) }
|
||||
functions["is_wild"] = { DoubleValue(actor.type == ActorType.WILD) }
|
||||
functions["battle_pokemon"] = { actor.pokemonList.asArrayValue { it.struct } }
|
||||
|
||||
when (actor) {
|
||||
is NPCBattleActor -> {
|
||||
|
||||
+5
@@ -55,6 +55,11 @@ object GeneralMoLangFunctions {
|
||||
struct.map.remove(variable)
|
||||
DoubleValue.ONE
|
||||
}
|
||||
functions["delete_variables"] = { params ->
|
||||
val struct = params.get<VariableStruct>(0)
|
||||
struct.map.clear()
|
||||
DoubleValue.ONE
|
||||
}
|
||||
functions["get_variable"] = { params ->
|
||||
val struct = params.get<VariableStruct>(0)
|
||||
val variable = params.getString(1)
|
||||
|
||||
+2
-2
@@ -98,14 +98,14 @@ object LivingEntityMoLangFunctions : AbstractMoLangFunctionHolder<LivingEntity>(
|
||||
val targetEntity = params.get<MoValue>(0)
|
||||
val maxDistance = params.getDoubleOrNull(1)?.toFloat() ?: 2.0F
|
||||
|
||||
val entity =
|
||||
val target =
|
||||
if (targetEntity is ObjectValue<*> && targetEntity.obj is Entity) {
|
||||
targetEntity.obj as Entity
|
||||
} else {
|
||||
return@isLookingAt DoubleValue.ZERO
|
||||
}
|
||||
|
||||
DoubleValue(entity.isLookingAt(entity, maxDistance))
|
||||
DoubleValue(entity.isLookingAt(target, maxDistance))
|
||||
}
|
||||
functions["is_living_entity"] = { DoubleValue.ONE }
|
||||
functions["is_flying"] = { DoubleValue(entity.isFallFlying) }
|
||||
|
||||
+22
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
|
||||
import com.cobblemon.mod.common.api.pokedex.CaughtCount
|
||||
import com.cobblemon.mod.common.api.pokedex.CaughtPercent
|
||||
import com.cobblemon.mod.common.api.pokedex.Dexes
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexManager
|
||||
import com.cobblemon.mod.common.api.pokedex.SeenCount
|
||||
@@ -74,6 +75,27 @@ object PokedexMoLangFunctions : AbstractMoLangFunctionHolder<AbstractPokedexMana
|
||||
functions["caught_percent"] = { DoubleValue(pokedex.getGlobalCalculatedValue(CaughtPercent)) }
|
||||
functions["seen_percent"] = { DoubleValue(pokedex.getGlobalCalculatedValue(SeenPercent)) }
|
||||
|
||||
functions["dex_caught_count"] = { params ->
|
||||
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, CaughtCount))
|
||||
else DoubleValue.ZERO
|
||||
}
|
||||
functions["dex_seen_count"] = { params ->
|
||||
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, SeenCount))
|
||||
else DoubleValue.ZERO
|
||||
}
|
||||
functions["dex_caught_percent"] = { params ->
|
||||
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, CaughtPercent).toDouble())
|
||||
else DoubleValue.ZERO
|
||||
}
|
||||
functions["dex_seen_percent"] = { params ->
|
||||
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, SeenPercent).toDouble())
|
||||
else DoubleValue.ZERO
|
||||
}
|
||||
|
||||
when (pokedex) {
|
||||
is PokedexManager -> {
|
||||
functions["player_id"] = { StringValue(pokedex.uuid.toString()) }
|
||||
|
||||
+1
-1
@@ -212,7 +212,7 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
|
||||
// Lol. Lmao, even.
|
||||
// TO-DO: Subscribe to [PokemonSpecies.observable].
|
||||
functions["pre_evolution"] = { pokemon.species.preEvolution ?: DoubleValue.ZERO }
|
||||
functions["nature"] = { StringValue(pokemon.nature.toString()) }
|
||||
functions["nature"] = { StringValue(pokemon.nature.name.toString()) }
|
||||
functions["is_wild"] = { DoubleValue(pokemon.entity?.let { it.ownerUUID == null } == true) }
|
||||
functions["is_shiny"] = { DoubleValue(pokemon.shiny) }
|
||||
functions["is_in_party"] = { DoubleValue(pokemon.storeCoordinates.get()?.store is PartyStore) }
|
||||
|
||||
+24
-7
@@ -8,8 +8,10 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.orientation
|
||||
|
||||
import com.cobblemon.mod.common.util.math.QuatSpring
|
||||
import com.cobblemon.mod.common.util.math.geometry.toDegrees
|
||||
import com.cobblemon.mod.common.util.math.geometry.toRadians
|
||||
import com.cobblemon.mod.common.util.math.simpleSpringDamperQuat
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
@@ -26,6 +28,9 @@ open class OrientationController(val entity: LivingEntity) {
|
||||
val FORWARDS: Vector3f = Vector3f(0.0f, 0.0f, -1.0f)
|
||||
val UP: Vector3f = Vector3f(0.0f, 1.0f, 0.0f)
|
||||
val LEFT: Vector3f = Vector3f(-1.0f, 0.0f, 0.0f)
|
||||
private const val TICK_DT = 1f / 20f // Minecraft server tick rate
|
||||
// Seconds for the render orientation to close half the gap to the real one. Raise for smoother but laggier visuals
|
||||
private const val RENDER_SMOOTH_HALFLIFE = 0.15f
|
||||
}
|
||||
|
||||
open var active: Boolean = false
|
||||
@@ -33,6 +38,8 @@ open class OrientationController(val entity: LivingEntity) {
|
||||
var orientation: Matrix3f? = null
|
||||
private set
|
||||
|
||||
private val renderSpring = QuatSpring()
|
||||
|
||||
private var renderOrientationO: Matrix3f? = null
|
||||
private var renderOrientation: Matrix3f? = null
|
||||
|
||||
@@ -54,6 +61,8 @@ open class OrientationController(val entity: LivingEntity) {
|
||||
orientation = null
|
||||
renderOrientationO = null
|
||||
renderOrientation = null
|
||||
renderSpring.quat.identity()
|
||||
renderSpring.angVel.set(0f, 0f, 0f)
|
||||
active = false
|
||||
}
|
||||
|
||||
@@ -77,18 +86,26 @@ open class OrientationController(val entity: LivingEntity) {
|
||||
return oldQuat
|
||||
}
|
||||
|
||||
/** Every tick move the current render orientation towards the last known true orientation **/
|
||||
fun tick() {
|
||||
if (!active || orientation == null) return
|
||||
renderOrientationO = renderOrientation
|
||||
val current = orientation
|
||||
val renderMatrix = this.renderOrientation ?: current
|
||||
val renderQuat = Quaternionf().setFromUnnormalized(renderMatrix)
|
||||
val targetQuat = Quaternionf().setFromUnnormalized(current)
|
||||
val dampingFactor = 0.66f // Smooth interpolation to reduce jitter of orientations recieved from the server.
|
||||
renderQuat.slerp(targetQuat, dampingFactor)
|
||||
val current = orientation!!
|
||||
val targetQuat = Quaternionf().setFromUnnormalized(current)
|
||||
|
||||
if (renderOrientation == null) {
|
||||
// First tick: snap the spring to the target instead of easing in from identity
|
||||
renderSpring.quat.set(targetQuat)
|
||||
renderSpring.angVel.set(0f, 0f, 0f)
|
||||
}
|
||||
|
||||
// Use a "quat spring" to smoothly move the render orientation towards the goal orientation.
|
||||
// It takes into account the orientations angular velocities and should be much more resilient
|
||||
// to sharp changes in goal orientation or late/missed packets.
|
||||
simpleSpringDamperQuat(renderSpring, targetQuat, RENDER_SMOOTH_HALFLIFE, TICK_DT)
|
||||
|
||||
val newRenderOrientation = Matrix3f()
|
||||
renderQuat.get(newRenderOrientation)
|
||||
renderSpring.quat.get(newRenderOrientation)
|
||||
renderOrientation = newRenderOrientation
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ object CatchRateModifiers {
|
||||
* Used by [PokeBalls.SAFARI_BALL].
|
||||
* Checks if the target is not battling, if true boost by *1.5
|
||||
*/
|
||||
val SAFARI = WorldStateModifier { _, entity -> if (!entity.isBattling) 1.5F else 1F }
|
||||
val SAFARI = MultiplierModifier(1.5f) { _, poke -> !poke.entity?.isBattling!! }
|
||||
|
||||
/**
|
||||
* Used by [PokeBalls.PARK_BALL].
|
||||
|
||||
@@ -13,6 +13,10 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.ChoiceSpeciesFeatureProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.WeightedChoiceSpeciesFeatureProvider
|
||||
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData
|
||||
import com.cobblemon.mod.common.pokemon.Gender
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
@@ -72,6 +76,57 @@ abstract class AbstractPokedexManager {
|
||||
} ?: PokedexEntryProgress.UNREGISTERED
|
||||
}
|
||||
|
||||
fun getHighestKnowledgeFor(entry: PokedexEntry, form: PokedexForm?): PokedexEntryProgress {
|
||||
if (form == null) return getHighestKnowledgeFor(entry)
|
||||
|
||||
val speciesRecord = getSpeciesRecord(entry.speciesId) ?: return PokedexEntryProgress.UNREGISTERED
|
||||
val hasAllAspects = entry.conditionAspects.all(speciesRecord::hasAspect)
|
||||
if (!hasAllAspects) {
|
||||
return PokedexEntryProgress.UNREGISTERED
|
||||
}
|
||||
|
||||
return speciesRecord.getFormRecord(form.displayForm)?.knowledge ?: PokedexEntryProgress.UNREGISTERED
|
||||
}
|
||||
|
||||
fun getHighestKnowledgeFor(entry: PokedexEntry, form: PokedexForm?, aspects: Iterable<String>): PokedexEntryProgress {
|
||||
val formKnowledge = getHighestKnowledgeFor(entry, form)
|
||||
|
||||
if (formKnowledge == PokedexEntryProgress.UNREGISTERED) return PokedexEntryProgress.UNREGISTERED
|
||||
|
||||
val seenAspects = getSeenAspects(entry)
|
||||
|
||||
// Aspects that select one of the species' own forms are already covered by the form knowledge check above,
|
||||
// so exempt them (and every other value of the same feature, since defaults like "phony" never appear on a form directly).
|
||||
val species = PokemonSpecies.getByIdentifier(entry.speciesId)
|
||||
val formAspects = species?.forms?.flatMapTo(hashSetOf()) { it.aspects } ?: emptySet()
|
||||
val formSelectingFeatureAspects = species?.let(SpeciesFeatures::getFeaturesFor).orEmpty()
|
||||
.mapNotNull { provider ->
|
||||
val allAspects: Collection<String> = when (provider) {
|
||||
is ChoiceSpeciesFeatureProvider -> provider.getAllAspects()
|
||||
is WeightedChoiceSpeciesFeatureProvider -> provider.getAllAspects().keys
|
||||
else -> return@mapNotNull null
|
||||
}
|
||||
allAspects.takeIf { it.any { aspect -> aspect in formAspects } }
|
||||
}
|
||||
.flatten()
|
||||
.toHashSet()
|
||||
|
||||
val allSeenAspects = aspects
|
||||
.filter { aspect ->
|
||||
if (Dexes.equivalentFormsLookup[aspect] != null) return@filter false
|
||||
if (aspect in formAspects || aspect in formSelectingFeatureAspects) return@filter false
|
||||
if (aspect.uppercase() in setOf(Gender.MALE.name, Gender.FEMALE.name, Gender.GENDERLESS.name)) return@filter false
|
||||
if ("shiny".equals(aspect, ignoreCase = true)) return@filter false
|
||||
|
||||
return@filter true
|
||||
}
|
||||
.all { aspect -> seenAspects.contains(aspect) }
|
||||
|
||||
if (!allSeenAspects) return PokedexEntryProgress.UNREGISTERED
|
||||
|
||||
return formKnowledge
|
||||
}
|
||||
|
||||
fun getEncounteredForms(entry: PokedexEntry) : List<PokedexForm> {
|
||||
return getFormsWithKnowledge(entry, PokedexEntryProgress.SEEN)
|
||||
}
|
||||
|
||||
@@ -40,6 +40,17 @@ object Dexes : JsonDataRegistry<PokedexDef> {
|
||||
//Maps a dex id to its PokedexDef
|
||||
val dexEntryMap = linkedMapOf<ResourceLocation, PokedexDef>()
|
||||
|
||||
val equivalentForms = mapOf(
|
||||
"galar" to listOf("region-bias-galar", "bias-galar", "galar-bias", "galarian", "galar"),
|
||||
"alola" to listOf("region-bias-alola", "bias-alola", "alola-bias", "alolan", "alola"),
|
||||
"hisui" to listOf("region-bias-hisui", "bias-hisui", "hisui-bias", "hisuian", "hisui"),
|
||||
"paldea" to listOf("region-bias-paldea", "bias-paldea", "paldea-bias", "paldean", "paldea"),
|
||||
)
|
||||
|
||||
val equivalentFormsLookup = equivalentForms.flatMap { (key, values) ->
|
||||
values.map { it to key }
|
||||
}.toMap()
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, PokedexDef>) {
|
||||
dexEntryMap.clear()
|
||||
data.entries.sortedBy { it.value.sortOrder }.forEach { (id, def) -> dexEntryMap[id] = def }
|
||||
@@ -49,4 +60,9 @@ object Dexes : JsonDataRegistry<PokedexDef> {
|
||||
override fun sync(player: ServerPlayer) {
|
||||
PokedexDexSyncPacket(dexEntryMap.values).sendToPlayer(player)
|
||||
}
|
||||
|
||||
fun getFormEquivalents(formName: String): List<String> {
|
||||
val key = equivalentFormsLookup[formName.lowercase()]
|
||||
return equivalentForms[key] ?: listOf(formName.lowercase())
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,7 @@ class SpeciesDexRecord {
|
||||
|
||||
fun addAspects(addedAspects: Set<String>) {
|
||||
aspects.addAll(addedAspects)
|
||||
pokedexManager.onSpeciesRecordUpdated(this)
|
||||
}
|
||||
|
||||
/** Returns true if the given Pokémon contains new information. Internal because it's only to be called from [FormDexRecord.wouldBeDifferent]. */
|
||||
|
||||
@@ -8,13 +8,20 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.pokedex.entry
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.net.messages.client.data.DexEntrySyncPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.data.DexEvolutionDisplaySyncPacket
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
@@ -38,6 +45,8 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
|
||||
|
||||
val entries = mutableMapOf<ResourceLocation, PokedexEntry>()
|
||||
|
||||
val evolutionDisplays = mutableListOf<DexEvolutionDisplay>()
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, PokedexEntry>) {
|
||||
data.forEach { _, entry ->
|
||||
entries[entry.id] = entry
|
||||
@@ -50,6 +59,7 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
|
||||
}
|
||||
}
|
||||
}
|
||||
computeEvolutionDisplays()
|
||||
observable.emit(this)
|
||||
}
|
||||
|
||||
@@ -57,5 +67,58 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
|
||||
|
||||
override fun sync(player: ServerPlayer) {
|
||||
DexEntrySyncPacket(entries.values).sendToPlayer(player)
|
||||
DexEvolutionDisplaySyncPacket(evolutionDisplays).sendToPlayer(player)
|
||||
}
|
||||
}
|
||||
|
||||
fun computeEvolutionDisplays() {
|
||||
evolutionDisplays.clear()
|
||||
PokemonSpecies.species.forEach {
|
||||
try {
|
||||
evolutionDisplays.addAll(buildDisplays(it.standardForm) + it.forms.flatMap { form -> buildDisplays(form) })
|
||||
} catch (e: Exception) {
|
||||
Cobblemon.LOGGER.error("Failed to build dex evolution displays for ${it.resourceIdentifier}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun buildDisplays(form: FormData): List<DexEvolutionDisplay> {
|
||||
val result = mutableListOf<DexEvolutionDisplay>()
|
||||
(form.ownEvolutions ?: form.evolutions).forEach { evolution ->
|
||||
val requirements = mutableListOf<DexEvolutionRequirementDisplay>()
|
||||
getToolTipHeaderIcon(evolution)?.let { header ->
|
||||
requirements.add(header)
|
||||
}
|
||||
evolution.requirements.mapNotNull { resolveRequirementData(it) }.forEach { requirement ->
|
||||
requirements.add(requirement)
|
||||
}
|
||||
|
||||
listOfNotNull(evolution.result, evolution.shedder).forEach resultLoop@{ resultProperties ->
|
||||
val resultSpecies = resultProperties.species
|
||||
?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
|
||||
?: return@resultLoop
|
||||
|
||||
// The result's own aspects win, otherwise carry the source form's aspects over
|
||||
// like a real evolution does (Alola-Bias Pichu -> Alola-Bias Pikachu)
|
||||
val resultForm = resultSpecies.getForm(resultProperties.aspects).takeIf { it != resultSpecies.standardForm }
|
||||
?: resultSpecies.getForm(form.aspects.toSet() + resultProperties.aspects)
|
||||
val toAspects = resultProperties.aspects + resultForm.aspects
|
||||
val resultRequirements = if (resultProperties === evolution.shedder) {
|
||||
requirements + CobblemonDexEvolutionRequirementDisplay(CobblemonItems.POKE_BALL, lang("ui.pokedex.evolution.shedder.tooltip"))
|
||||
} else {
|
||||
requirements
|
||||
}
|
||||
|
||||
result.add(DexEvolutionDisplay(
|
||||
fromSpecies = form.species.resourceIdentifier,
|
||||
fromForm = form.name,
|
||||
fromAspects = form.aspects.toSet(),
|
||||
toSpecies = resultSpecies.resourceIdentifier,
|
||||
toForm = resultForm.name,
|
||||
toAspects = toAspects,
|
||||
requirements = resultRequirements,
|
||||
))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
+466
@@ -0,0 +1,466 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.pokedex.entry
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
|
||||
import com.cobblemon.mod.common.api.conditional.RegistryLikeIdentifierCondition
|
||||
import com.cobblemon.mod.common.api.conditional.RegistryLikeTagCondition
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeatureProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
|
||||
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.text.add
|
||||
import com.cobblemon.mod.common.api.text.green
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
|
||||
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
|
||||
import com.cobblemon.mod.common.pokemon.requirements.*
|
||||
import com.cobblemon.mod.common.util.adapters.RegistryAccessResolver
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.core.registries.Registries
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.util.ExtraCodecs
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.level.biome.Biome
|
||||
import net.minecraft.world.level.levelgen.structure.Structure
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
data class DexEvolutionDisplay(
|
||||
val fromSpecies: ResourceLocation,
|
||||
val fromForm: String,
|
||||
val fromAspects: Set<String>,
|
||||
val toSpecies: ResourceLocation,
|
||||
val toForm: String,
|
||||
val toAspects: Set<String>,
|
||||
val requirements: List<DexEvolutionRequirementDisplay>,
|
||||
)
|
||||
|
||||
interface DexEvolutionRequirementDisplay {
|
||||
val icon: Item
|
||||
val tooltip: MutableComponent
|
||||
}
|
||||
|
||||
data class CobblemonDexEvolutionRequirementDisplay(
|
||||
override val icon: Item,
|
||||
override val tooltip: MutableComponent,
|
||||
) : DexEvolutionRequirementDisplay
|
||||
|
||||
data class CobblemonBiomeDexEvolutionRequirementDisplay(
|
||||
override val icon: Item,
|
||||
override val tooltip: MutableComponent,
|
||||
val condition: ExtraCodecs.TagOrElementLocation? = null,
|
||||
val antiCondition: ExtraCodecs.TagOrElementLocation? = null,
|
||||
) : DexEvolutionRequirementDisplay
|
||||
|
||||
fun getToolTipHeaderIcon(evolution: Evolution): DexEvolutionRequirementDisplay? {
|
||||
when (evolution) {
|
||||
is ItemInteractionEvolution -> return getToolTipHeaderIcon(evolution)
|
||||
is TradeEvolution -> return getToolTipHeaderIcon(evolution)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
fun getToolTipHeaderIcon(itemInteractionEvolution: ItemInteractionEvolution): CobblemonDexEvolutionRequirementDisplay? {
|
||||
val item = itemInteractionEvolution.requiredContext.items.orElse(null)?.firstOrNull()?.value() ?: return null
|
||||
return CobblemonDexEvolutionRequirementDisplay(item, lang("ui.pokedex.evolution.item_interact.tooltip", item.description.plainCopy().green()))
|
||||
}
|
||||
|
||||
fun getToolTipHeaderIcon(tradeEvolution: TradeEvolution): CobblemonDexEvolutionRequirementDisplay {
|
||||
val species = tradeEvolution.requiredContext.species
|
||||
val tooltip = if (species == null) {
|
||||
lang("ui.pokedex.evolution.trade.tooltip.with_any_species")
|
||||
} else {
|
||||
PokemonSpecies.getByIdentifier(species.asIdentifierDefaultingNamespace())
|
||||
?.let { lang("ui.pokedex.evolution.trade.tooltip.with_species", it.translatedName.plainCopy().green()) }
|
||||
?: lang("ui.pokedex.evolution.trade.tooltip.with_any_species")
|
||||
}
|
||||
return CobblemonDexEvolutionRequirementDisplay(CobblemonItems.LINK_CABLE, tooltip)
|
||||
}
|
||||
|
||||
fun resolveRequirementData(requirement: Requirement): DexEvolutionRequirementDisplay? {
|
||||
val icon = requirement.pokedexIcon?.let {
|
||||
RegistryAccessResolver.getAccess().registryOrThrow(Registries.ITEM).get(it)
|
||||
}
|
||||
|
||||
val tooltip = requirement.pokedexTooltip?.let {
|
||||
Component.translatable(it)
|
||||
}
|
||||
|
||||
if (icon != null && tooltip != null) {
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
return buildRequirementData(requirement)
|
||||
}
|
||||
|
||||
fun buildRequirementData(requirement: Requirement): DexEvolutionRequirementDisplay? {
|
||||
when (requirement) {
|
||||
is AdvancementRequirement -> return buildAdvancementRequirement(requirement)
|
||||
is BattleCriticalHitsRequirement -> return buildBattleCriticalHitsRequirement(requirement)
|
||||
is BiomeRequirement -> return buildBiomeRequirement(requirement)
|
||||
is BlocksTraveledRequirement -> return buildBlocksTraveledRequirement(requirement)
|
||||
is DamageTakenRequirement -> return buildDamageTakenRequirement(requirement)
|
||||
is DefeatRequirement -> return buildDefeatRequirement(requirement)
|
||||
is FriendshipRequirement -> return buildFriendshipRequirement(requirement)
|
||||
is HeldItemRequirement -> return buildHeldItemRequirement(requirement)
|
||||
is LevelRequirement -> return buildLevelRequirement(requirement)
|
||||
is MoonPhaseRequirement -> return buildMoonPhaseRequirement(requirement)
|
||||
is MoveSetRequirement -> return buildMoveSetRequirement(requirement)
|
||||
is MoveTypeRequirement -> return buildMoveTypeRequirement(requirement)
|
||||
is NegateRequirement -> return buildNegateRequirement(requirement)
|
||||
is OwnerHoldsItemRequirement -> return buildOwnerHoldsItemRequirement(requirement)
|
||||
is PartyMemberRequirement -> return buildPartyMemberRequirement(requirement)
|
||||
is PokemonPropertiesRequirement -> return buildPokemonPropertiesRequirement(requirement)
|
||||
is PropertyRangeRequirement -> return buildPropertyRangeRequirement(requirement)
|
||||
is RecoilRequirement -> return buildRecoilRequirement(requirement)
|
||||
is StatCompareRequirement -> return buildStatCompareRequirement(requirement)
|
||||
is StatEqualRequirement -> return buildStatEqualRequirement(requirement)
|
||||
is StructureRequirement -> return buildStructureRequirement(requirement)
|
||||
is TimeRangeRequirement -> return buildTimeRangeRequirement(requirement)
|
||||
is UseMoveRequirement -> return buildUseMoveRequirement(requirement)
|
||||
is WeatherRequirement -> return buildWeatherRequirement(requirement)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun buildAdvancementRequirement(advancementRequirement: AdvancementRequirement): DexEvolutionRequirementDisplay {
|
||||
val icon = Items.BOOK
|
||||
val identifier = advancementRequirement.requiredAdvancement
|
||||
val advancementLanguageKey = "${identifier.namespace}.${identifier.path.substringAfterLast('/')}"
|
||||
val advancementComponent = Component.translatable("advancements.$advancementLanguageKey").green()
|
||||
val tooltip = lang("ui.pokedex.requirement.advancement.tooltip", advancementComponent)
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildBattleCriticalHitsRequirement(battleCriticalHitsRequirement: BattleCriticalHitsRequirement): DexEvolutionRequirementDisplay {
|
||||
val icon = CobblemonItems.MEDICINAL_LEEK
|
||||
val criticalsComponent = Component.literal(battleCriticalHitsRequirement.amount.toString()).green()
|
||||
val tooltip = lang("ui.pokedex.requirement.battle_critical_hits.tooltip", criticalsComponent)
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
private fun tagDisplayName(registry: String, id: ResourceLocation): MutableComponent {
|
||||
return Component.translatableWithFallback("tag.$registry.${id.namespace}.${id.path.replace('/', '.')}", prettifyPath(id.path))
|
||||
}
|
||||
|
||||
private fun prettifyPath(path: String): String {
|
||||
return path.substringAfterLast('/').removePrefix("is_").split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) }
|
||||
}
|
||||
|
||||
fun buildNegateRequirement(negateRequirement: NegateRequirement): DexEvolutionRequirementDisplay? {
|
||||
val inner = negateRequirement.inner
|
||||
when (inner) {
|
||||
is BiomeRequirement -> buildBiomeRequirement(inner, negated = true)?.let { return it }
|
||||
is StructureRequirement -> buildStructureRequirement(inner, negated = true)?.let { return it }
|
||||
}
|
||||
val innerDisplay = resolveRequirementData(inner) ?: return null
|
||||
return CobblemonDexEvolutionRequirementDisplay(innerDisplay.icon, lang("ui.pokedex.requirement.negate.tooltip", innerDisplay.tooltip))
|
||||
}
|
||||
|
||||
fun buildBiomeRequirement(biomeRequirement: BiomeRequirement, negated: Boolean = false): DexEvolutionRequirementDisplay? {
|
||||
// Negating "in X" is "not in X"; negating both a condition and an anticondition isn't expressible as one line
|
||||
if (negated && biomeRequirement.biomeCondition != null && biomeRequirement.biomeAnticondition != null) return null
|
||||
val condition = if (negated) biomeRequirement.biomeAnticondition else biomeRequirement.biomeCondition
|
||||
val antiCondition = if (negated) biomeRequirement.biomeCondition else biomeRequirement.biomeAnticondition
|
||||
if (condition == null && antiCondition == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val icon = Items.COMPASS
|
||||
val displayName: (RegistryLikeCondition<Biome>) -> MutableComponent = {
|
||||
when (it) {
|
||||
is RegistryLikeTagCondition -> tagDisplayName("worldgen.biome", it.tag.location())
|
||||
is RegistryLikeIdentifierCondition -> Component.translatable("biome.${it.identifier.namespace}.${it.identifier.path}")
|
||||
else -> Component.literal(it.toString())
|
||||
}
|
||||
}
|
||||
|
||||
val tooltip = if (condition != null && antiCondition != null) {
|
||||
lang("ui.pokedex.requirement.biome.both.tooltip", displayName(condition).green(), displayName(antiCondition).green())
|
||||
} else if (condition != null) {
|
||||
lang("ui.pokedex.requirement.biome.tooltip", displayName(condition).green())
|
||||
} else {
|
||||
lang("ui.pokedex.requirement.biome.anticondition.tooltip", displayName(antiCondition!!).green())
|
||||
}
|
||||
|
||||
var conditionConverted: ExtraCodecs.TagOrElementLocation? = null
|
||||
if (condition != null) {
|
||||
val conditionIdentifier = if (condition is RegistryLikeIdentifierCondition) condition.identifier else (condition as RegistryLikeTagCondition).tag.location
|
||||
conditionConverted = ExtraCodecs.TagOrElementLocation(conditionIdentifier, condition is RegistryLikeTagCondition)
|
||||
}
|
||||
|
||||
var antiConditionConverted: ExtraCodecs.TagOrElementLocation? = null
|
||||
if (antiCondition != null) {
|
||||
val conditionIdentifier = if (antiCondition is RegistryLikeIdentifierCondition) antiCondition.identifier else (antiCondition as RegistryLikeTagCondition).tag.location
|
||||
antiConditionConverted = ExtraCodecs.TagOrElementLocation(conditionIdentifier, antiCondition is RegistryLikeTagCondition)
|
||||
}
|
||||
|
||||
return CobblemonBiomeDexEvolutionRequirementDisplay(icon, tooltip, conditionConverted, antiConditionConverted)
|
||||
}
|
||||
|
||||
fun buildBlocksTraveledRequirement(blocksTraveledRequirement: BlocksTraveledRequirement): DexEvolutionRequirementDisplay {
|
||||
val icon = Items.LEATHER_BOOTS
|
||||
val blocksComponent = Component.literal(blocksTraveledRequirement.amount.toString()).green()
|
||||
val tooltip = lang("ui.pokedex.requirement.blocks_traveled.tooltip", blocksComponent)
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildDamageTakenRequirement(damageTakenRequirement: DamageTakenRequirement): DexEvolutionRequirementDisplay {
|
||||
val icon = CobblemonItems.PUNCHING_GLOVE
|
||||
val blocksComponent = Component.literal(damageTakenRequirement.amount.toString()).green()
|
||||
val tooltip = lang("ui.pokedex.requirement.damage_taken.tooltip", blocksComponent)
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildDefeatRequirement(defeatRequirement: DefeatRequirement): DexEvolutionRequirementDisplay? {
|
||||
val description = describePokemonProperties(defeatRequirement.target) ?: return null
|
||||
val icon = CobblemonItems.PUNCHING_GLOVE
|
||||
val defeatedAmount = defeatRequirement.amount
|
||||
|
||||
val descriptionComponent = Component.literal(defeatedAmount.toString()).green().add(" ").add(description)
|
||||
val tooltip = lang("ui.pokedex.requirement.defeat.tooltip", descriptionComponent)
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildFriendshipRequirement(friendshipRequirement: FriendshipRequirement): DexEvolutionRequirementDisplay {
|
||||
val icon = CobblemonItems.SOOTHE_BELL
|
||||
val friendshipAmountComponent = Component.literal(friendshipRequirement.amount.toString()).green()
|
||||
val tooltip = lang("ui.pokedex.requirement.friendship.tooltip", friendshipAmountComponent)
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildHeldItemRequirement(heldItemRequirement: HeldItemRequirement): DexEvolutionRequirementDisplay? {
|
||||
val items = heldItemRequirement.itemCondition.items.getOrNull() ?: return null
|
||||
val item = items.firstOrNull()?.value() ?: return null
|
||||
val itemComponent = items.unwrapKey().map { tag ->
|
||||
val id = tag.location()
|
||||
val fallback = id.path.substringAfterLast('/').split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) }
|
||||
Component.translatableWithFallback("tag.item.${id.namespace}.${id.path.replace('/', '.')}", fallback)
|
||||
}.orElseGet { item.description.plainCopy() }
|
||||
val tooltip = lang("ui.pokedex.requirement.held_item.tooltip", itemComponent.green())
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(item, tooltip)
|
||||
}
|
||||
|
||||
fun buildLevelRequirement(levelRequirement: LevelRequirement): DexEvolutionRequirementDisplay {
|
||||
val minLevel = levelRequirement.minLevel
|
||||
val maxLevel = levelRequirement.maxLevel
|
||||
|
||||
val icon = CobblemonItems.RARE_CANDY
|
||||
val tooltip =
|
||||
if (minLevel == maxLevel || maxLevel == Int.MAX_VALUE)
|
||||
lang("ui.pokedex.requirement.level.tooltip.min", Component.literal(minLevel.toString()).green())
|
||||
else
|
||||
lang("ui.pokedex.requirement.level.tooltip.between", Component.literal(minLevel.toString()).green(), Component.literal(maxLevel.toString()).green())
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildMoonPhaseRequirement(moonPhaseRequirement: MoonPhaseRequirement): DexEvolutionRequirementDisplay {
|
||||
val icon = Items.CLOCK
|
||||
val moonPhaseComponent = moonPhaseRequirement.moonPhase.displayName().plainCopy().green()
|
||||
val tooltip = lang("ui.pokedex.requirement.moon_phase.tooltip", moonPhaseComponent)
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildMoveSetRequirement(moveSetRequirement: MoveSetRequirement): DexEvolutionRequirementDisplay {
|
||||
val icon = CobblemonItems.TECHNICAL_MACHINE
|
||||
val tooltip = lang("ui.pokedex.requirement.has_move.tooltip", moveSetRequirement.move.displayName.plainCopy().green())
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildMoveTypeRequirement(moveTypeRequirement: MoveTypeRequirement): DexEvolutionRequirementDisplay {
|
||||
val type = ElementalTypes.get(moveTypeRequirement.type.name)
|
||||
val icon = when (type) {
|
||||
ElementalTypes.NORMAL -> CobblemonItems.NORMAL_GEM
|
||||
ElementalTypes.FIRE -> CobblemonItems.FIRE_GEM
|
||||
ElementalTypes.WATER -> CobblemonItems.WATER_GEM
|
||||
ElementalTypes.GRASS -> CobblemonItems.GRASS_GEM
|
||||
ElementalTypes.ELECTRIC -> CobblemonItems.ELECTRIC_GEM
|
||||
ElementalTypes.ICE -> CobblemonItems.ICE_GEM
|
||||
ElementalTypes.FIGHTING -> CobblemonItems.FIGHTING_GEM
|
||||
ElementalTypes.POISON -> CobblemonItems.POISON_GEM
|
||||
ElementalTypes.GROUND -> CobblemonItems.GROUND_GEM
|
||||
ElementalTypes.FLYING -> CobblemonItems.FLYING_GEM
|
||||
ElementalTypes.PSYCHIC -> CobblemonItems.PSYCHIC_GEM
|
||||
ElementalTypes.BUG -> CobblemonItems.BUG_GEM
|
||||
ElementalTypes.ROCK -> CobblemonItems.ROCK_GEM
|
||||
ElementalTypes.GHOST -> CobblemonItems.GHOST_GEM
|
||||
ElementalTypes.DRAGON -> CobblemonItems.DRAGON_GEM
|
||||
ElementalTypes.DARK -> CobblemonItems.DARK_GEM
|
||||
ElementalTypes.STEEL -> CobblemonItems.STEEL_GEM
|
||||
ElementalTypes.FAIRY -> CobblemonItems.FAIRY_GEM
|
||||
else -> CobblemonItems.NORMAL_GEM
|
||||
}
|
||||
|
||||
val tooltip = lang("ui.pokedex.requirement.has_move_type.tooltip", moveTypeRequirement.type.displayName.plainCopy().green())
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildOwnerHoldsItemRequirement(ownerHoldsItemRequirement: OwnerHoldsItemRequirement): DexEvolutionRequirementDisplay? {
|
||||
val item = ownerHoldsItemRequirement.itemCondition.items.orElse(null)?.firstOrNull()?.value() ?: return null
|
||||
val tooltip = lang("ui.pokedex.requirement.owner_held_item.tooltip", item.description.plainCopy().green())
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(item, tooltip)
|
||||
}
|
||||
|
||||
fun buildPartyMemberRequirement(partyMemberRequirement: PartyMemberRequirement): DexEvolutionRequirementDisplay? {
|
||||
val description = describePokemonProperties(partyMemberRequirement.target) ?: return null
|
||||
val icon = CobblemonItems.POKE_BALL
|
||||
val tooltip = if (partyMemberRequirement.contains) {
|
||||
lang("ui.pokedex.requirement.party_member.tooltip", description)
|
||||
} else {
|
||||
lang("ui.pokedex.requirement.party_member.anticondition.tooltip", description)
|
||||
}
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildPokemonPropertiesRequirement(pokemonPropertiesRequirement: PokemonPropertiesRequirement): DexEvolutionRequirementDisplay? {
|
||||
val description = describePokemonProperties(pokemonPropertiesRequirement.target) ?: return null
|
||||
val icon = Items.NAME_TAG
|
||||
val tooltip = lang("ui.pokedex.requirement.pokemon_properties.tooltip", description)
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildPropertyRangeRequirement(propertyRangeRequirement: PropertyRangeRequirement): DexEvolutionRequirementDisplay? {
|
||||
val provider = SpeciesFeatures.getFeature(propertyRangeRequirement.feature) as? IntSpeciesFeatureProvider ?: return null
|
||||
val displayName = provider.display?.name?.takeIf { it.isNotBlank() }?.asTranslated()?.green() ?: return null
|
||||
val icon = provider.itemPoints.keys.firstOrNull()?.let { BuiltInRegistries.ITEM.get(it) } ?: Items.NAME_TAG
|
||||
|
||||
val range = propertyRangeRequirement.range
|
||||
val tooltip = lang(
|
||||
"ui.pokedex.requirement.property_range.tooltip",
|
||||
displayName,
|
||||
Component.literal(range.first.toString()).green(),
|
||||
Component.literal(range.last.toString()).green()
|
||||
)
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildRecoilRequirement(recoilRequirement: RecoilRequirement): DexEvolutionRequirementDisplay {
|
||||
val icon = CobblemonItems.LIFE_ORB
|
||||
val recoilComponent = Component.literal(recoilRequirement.amount.toString()).green()
|
||||
val tooltip = lang("ui.pokedex.requirement.recoil.tooltip", recoilComponent)
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildStatCompareRequirement(statCompareRequirement: StatCompareRequirement): DexEvolutionRequirementDisplay {
|
||||
val highStat = Stats.getStat(statCompareRequirement.highStat)
|
||||
val lowStat = Stats.getStat(statCompareRequirement.lowStat)
|
||||
|
||||
val icon = when (highStat) {
|
||||
Stats.HP, Stats.EVASION, Stats.ACCURACY -> CobblemonItems.HEALTH_MOCHI
|
||||
Stats.ATTACK -> CobblemonItems.MUSCLE_MOCHI
|
||||
Stats.DEFENCE -> CobblemonItems.RESIST_MOCHI
|
||||
Stats.SPECIAL_ATTACK -> CobblemonItems.GENIUS_MOCHI
|
||||
Stats.SPECIAL_DEFENCE -> CobblemonItems.CLEVER_MOCHI
|
||||
Stats.SPEED -> CobblemonItems.SWIFT_MOCHI
|
||||
}
|
||||
|
||||
val highStatComponent = highStat.displayName.plainCopy().green()
|
||||
val lowStatComponent = lowStat.displayName.plainCopy().green()
|
||||
val tooltip = lang("ui.pokedex.requirement.stat_compare.tooltip", highStatComponent, lowStatComponent)
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildStatEqualRequirement(statEqualRequirement: StatEqualRequirement): DexEvolutionRequirementDisplay {
|
||||
val statOne = Stats.getStat(statEqualRequirement.statOne)
|
||||
val statTwo = Stats.getStat(statEqualRequirement.statTwo)
|
||||
|
||||
val statOneComponent = statOne.displayName.plainCopy().green()
|
||||
val statTwoComponent = statTwo.displayName.plainCopy().green()
|
||||
|
||||
val icon = CobblemonItems.FRESH_START_MOCHI
|
||||
val tooltip = lang("ui.pokedex.requirement.stat_equal.tooltip", statOneComponent, statTwoComponent)
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildStructureRequirement(structureRequirement: StructureRequirement, negated: Boolean = false): DexEvolutionRequirementDisplay? {
|
||||
if (negated && structureRequirement.structureCondition != null && structureRequirement.structureAnticondition != null) return null
|
||||
val condition = if (negated) structureRequirement.structureAnticondition else structureRequirement.structureCondition
|
||||
val anticondition = if (negated) structureRequirement.structureCondition else structureRequirement.structureAnticondition
|
||||
if (condition == null && anticondition == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val icon = Items.FILLED_MAP
|
||||
val displayName: (RegistryLikeCondition<Structure>) -> MutableComponent = {
|
||||
when (it) {
|
||||
is RegistryLikeTagCondition -> tagDisplayName("worldgen.structure", it.tag.location())
|
||||
is RegistryLikeIdentifierCondition -> Component.literal(prettifyPath(it.identifier.path))
|
||||
else -> Component.literal(it.toString())
|
||||
}
|
||||
}
|
||||
val tooltip = if (condition != null && anticondition != null) {
|
||||
lang("ui.pokedex.requirement.structure.both.tooltip", displayName(condition).green(), displayName(anticondition).green())
|
||||
} else if (condition != null) {
|
||||
lang("ui.pokedex.requirement.structure.tooltip", displayName(condition).green())
|
||||
} else {
|
||||
lang("ui.pokedex.requirement.structure.anticondition.tooltip", displayName(anticondition!!).green())
|
||||
}
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildTimeRangeRequirement(timeRangeRequirement: TimeRangeRequirement): DexEvolutionRequirementDisplay? {
|
||||
val icon = Items.CLOCK
|
||||
val timeRangeComponent = timeRangeRequirement.range.displayName()?.plainCopy() ?: return null
|
||||
val tooltip = lang("ui.pokedex.requirement.time_range.tooltip", timeRangeComponent.green())
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildUseMoveRequirement(useMoveRequirement: UseMoveRequirement): DexEvolutionRequirementDisplay {
|
||||
val icon = CobblemonItems.CHOICE_BAND
|
||||
val tooltip = lang("ui.pokedex.requirement.use_move.tooltip", useMoveRequirement.move.displayName.plainCopy().green())
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
|
||||
fun buildWeatherRequirement(weatherRequirement: WeatherRequirement): DexEvolutionRequirementDisplay {
|
||||
val icon: Item
|
||||
val tooltip: MutableComponent
|
||||
|
||||
if (weatherRequirement.isThundering == true) {
|
||||
icon = Items.LIGHTNING_ROD
|
||||
tooltip = lang("ui.pokedex.requirement.weather.tooltip.thundering")
|
||||
} else {
|
||||
icon = Items.WATER_BUCKET
|
||||
tooltip = lang("ui.pokedex.requirement.weather.tooltip.raining")
|
||||
}
|
||||
|
||||
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.pokedex.entry
|
||||
|
||||
import com.cobblemon.mod.common.api.pokemon.Natures
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeature
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeatureProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.StringSpeciesFeature
|
||||
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
|
||||
import com.cobblemon.mod.common.api.text.add
|
||||
import com.cobblemon.mod.common.api.text.green
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.locale.Language
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.world.item.Items
|
||||
|
||||
fun describePokemonProperties(properties: PokemonProperties): MutableComponent? {
|
||||
if (hasUndescribedProperty(properties)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val descriptors = mutableListOf<MutableComponent>()
|
||||
|
||||
properties.species?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }?.let {
|
||||
descriptors.add(it.translatedName.plainCopy().green())
|
||||
}
|
||||
properties.gender?.let {
|
||||
descriptors.add(lang("gender.${it.name.lowercase()}").green())
|
||||
}
|
||||
properties.nickname?.let {
|
||||
descriptors.add(lang("ui.pokedex.requirement.properties.nickname", it.plainCopy().green()))
|
||||
}
|
||||
properties.type?.let {
|
||||
val typeName = ElementalTypes.get(it)?.displayName?.plainCopy() ?: Component.literal(it)
|
||||
descriptors.add(lang("ui.pokedex.requirement.properties.type", typeName.green()))
|
||||
}
|
||||
properties.nature?.let {
|
||||
val natureName = Natures.getNature(it.asIdentifierDefaultingNamespace())?.displayName?.asTranslated() ?: Component.literal(it)
|
||||
descriptors.add(lang("ui.pokedex.requirement.properties.nature", natureName.green()))
|
||||
}
|
||||
properties.customProperties.forEach { customProperty ->
|
||||
describeCustomProperty(customProperty)?.let { descriptors.add(it) }
|
||||
}
|
||||
|
||||
val heldItemDescriptor = properties.heldItem?.let { heldItem ->
|
||||
val itemId = heldItem.substringBefore('[').trim().asIdentifierDefaultingNamespace()
|
||||
val item = BuiltInRegistries.ITEM.get(itemId)
|
||||
if (item == Items.AIR) null else lang("ui.pokedex.requirement.properties.held_item", item.description.plainCopy().green())
|
||||
}
|
||||
|
||||
if (descriptors.isEmpty() && heldItemDescriptor == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val mainDescriptor = when {
|
||||
descriptors.isEmpty() -> null
|
||||
descriptors.size == 1 -> descriptors[0]
|
||||
else -> {
|
||||
val allButLast = descriptors.dropLast(1).reduce { acc, next -> acc.add(", ").add(next) }
|
||||
lang("ui.pokedex.requirement.properties.and", allButLast, descriptors.last())
|
||||
}
|
||||
}
|
||||
|
||||
return when {
|
||||
mainDescriptor != null && heldItemDescriptor != null -> mainDescriptor.add(" ").add(heldItemDescriptor)
|
||||
mainDescriptor != null -> mainDescriptor
|
||||
else -> heldItemDescriptor
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasUndescribedProperty(properties: PokemonProperties): Boolean {
|
||||
return properties.form != null ||
|
||||
properties.shiny != null ||
|
||||
properties.level != null ||
|
||||
properties.friendship != null ||
|
||||
properties.fullness != null ||
|
||||
properties.pokeball != null ||
|
||||
properties.ability != null ||
|
||||
properties.status != null ||
|
||||
properties.teraType != null ||
|
||||
properties.dmaxLevel != null ||
|
||||
properties.gmaxFactor != null ||
|
||||
properties.tradeable != null ||
|
||||
properties.originalTrainerType != null ||
|
||||
properties.originalTrainer != null ||
|
||||
properties.moves != null ||
|
||||
properties.movesetBuilders != null ||
|
||||
properties.cosmeticItem != null ||
|
||||
properties.scaleModifier != null ||
|
||||
properties.isAlpha != null ||
|
||||
properties.minPerfectIVs != null ||
|
||||
properties.customProperties.any { describeCustomProperty(it) == null }
|
||||
}
|
||||
|
||||
private fun describeCustomProperty(property: CustomPokemonProperty): MutableComponent? {
|
||||
return when (property) {
|
||||
is IntSpeciesFeature -> describeIntSpeciesFeature(property)
|
||||
is StringSpeciesFeature -> describeStringSpeciesFeature(property)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun describeIntSpeciesFeature(property: IntSpeciesFeature): MutableComponent? {
|
||||
val value = Component.literal(property.value.toString())
|
||||
|
||||
val overrideKey = "ui.pokedex.requirement.properties.custom.${property.name}"
|
||||
if (Language.getInstance().has("cobblemon.$overrideKey")) {
|
||||
return lang(overrideKey, value.green())
|
||||
}
|
||||
|
||||
val displayName = (SpeciesFeatures.getFeature(property.name) as? IntSpeciesFeatureProvider)
|
||||
?.display?.name?.takeIf { it.isNotBlank() }
|
||||
?: return null
|
||||
|
||||
return lang("ui.pokedex.requirement.properties.int_feature", value.green(), displayName.asTranslated().green())
|
||||
}
|
||||
|
||||
private fun describeStringSpeciesFeature(property: StringSpeciesFeature): MutableComponent? {
|
||||
val key = "ui.pokedex.requirement.properties.custom.${property.name}"
|
||||
if (!Language.getInstance().has("cobblemon.$key")) {
|
||||
return null
|
||||
}
|
||||
|
||||
val value = PokemonSpecies.getByIdentifier(property.value.asIdentifierDefaultingNamespace())?.translatedName?.plainCopy()
|
||||
?: Component.literal(property.value)
|
||||
|
||||
return lang(key, value.green())
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies.getByIdentifier
|
||||
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffect
|
||||
import com.cobblemon.mod.common.api.pokemon.effect.adapter.ShoulderEffectAdapter
|
||||
import com.cobblemon.mod.common.api.pokemon.egg.EggGroup
|
||||
@@ -128,7 +127,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
|
||||
.registerTypeAdapter(EggGroup::class.java, EggGroupAdapter)
|
||||
.registerTypeAdapter(MobEffect::class.java, RegistryElementAdapter<MobEffect>(BuiltInRegistries::MOB_EFFECT))
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter())
|
||||
.registerTypeAdapter(RidingBehaviourSettings::class.java, RidingBehaviourSettingsAdapter)
|
||||
.registerTypeAdapter(RideSoundSettingsList::class.java, RideSoundSettingsListAdapter)
|
||||
.registerTypeAdapter(ObtainableItemCondition::class.java, ObtainableItemConditionAdapter)
|
||||
|
||||
+16
-2
@@ -15,6 +15,8 @@ import com.cobblemon.mod.common.block.entity.PokeSnackBlockEntity
|
||||
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
|
||||
import com.cobblemon.mod.common.client.render.MatrixWrapper
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.ParticleStatus
|
||||
import net.minecraft.core.particles.BlockParticleOption
|
||||
import net.minecraft.core.particles.ParticleOptions
|
||||
import net.minecraft.core.particles.ParticleTypes
|
||||
@@ -40,13 +42,25 @@ sealed class ParticleData {
|
||||
val chance: Double,
|
||||
val amount: Int,
|
||||
val locatorResolver: (Map<String, MatrixWrapper>) -> List<String>
|
||||
): ParticleData()
|
||||
): ParticleData() {
|
||||
fun amount(): Int = when(Minecraft.getInstance().options.particles().get()) {
|
||||
ParticleStatus.ALL -> amount
|
||||
ParticleStatus.DECREASED -> amount / 2
|
||||
ParticleStatus.MINIMAL -> amount / 4
|
||||
}
|
||||
}
|
||||
|
||||
data class MinecraftParticle(
|
||||
val particle: ParticleOptions,
|
||||
val chance: Double,
|
||||
val amount: Int
|
||||
): ParticleData()
|
||||
): ParticleData() {
|
||||
fun amount(): Int = when(Minecraft.getInstance().options.particles().get()) {
|
||||
ParticleStatus.ALL -> amount
|
||||
ParticleStatus.DECREASED -> amount / 2
|
||||
ParticleStatus.MINIMAL -> amount / 4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,8 @@ package com.cobblemon.mod.common.api.pokemon.requirement
|
||||
import com.cobblemon.mod.common.api.pokemon.evolution.ContextEvolution
|
||||
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import java.util.WeakHashMap
|
||||
|
||||
/**
|
||||
* Represents a universal requirement suitable for various checks on pokemon..
|
||||
@@ -32,4 +34,11 @@ fun interface Requirement {
|
||||
*/
|
||||
fun check(pokemon: Pokemon): Boolean
|
||||
|
||||
val pokedexIcon: ResourceLocation? get() = pokedexIconOverrides[this]
|
||||
val pokedexTooltip: String? get() = pokedexTooltipOverrides[this]
|
||||
|
||||
companion object {
|
||||
val pokedexIconOverrides = WeakHashMap<Requirement, ResourceLocation>()
|
||||
val pokedexTooltipOverrides = WeakHashMap<Requirement, String>()
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.riding.behaviour
|
||||
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController
|
||||
import net.minecraft.util.SmoothDouble
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
/**
|
||||
* One frame of steering for a rolling ride on the client that's driving it.
|
||||
* The mouse values are the share the ride took in consumeLook, already through the riding
|
||||
* sensitivity, invert and swap options and still in mouse units.
|
||||
*/
|
||||
class RideSteerInput(
|
||||
/** The ride's own orientation controller */
|
||||
val controller: OrientationController,
|
||||
val mouseYaw: Double,
|
||||
val mousePitch: Double,
|
||||
val mouseXSmoother: SmoothDouble,
|
||||
val mouseYSmoother: SmoothDouble,
|
||||
/** Vanilla's mouse sensitivity curve, cubed */
|
||||
val sensitivity: Double,
|
||||
/** Seconds since the last frame */
|
||||
val deltaTime: Double,
|
||||
private val yawSmoother: SmoothDouble,
|
||||
private val pitchSmoother: SmoothDouble,
|
||||
private val rollSmoother: SmoothDouble
|
||||
) {
|
||||
companion object {
|
||||
// Share of each push the smoothers get. Lower makes smoothed turning softer
|
||||
const val SMOOTHED_SHARE = 0.5
|
||||
}
|
||||
|
||||
/** Turns the ride by local yaw, pitch and roll degrees right away. */
|
||||
fun rotate(rotation: Vec3) {
|
||||
controller.rotate(rotation.x.toFloat(), rotation.y.toFloat(), rotation.z.toFloat())
|
||||
}
|
||||
|
||||
/** Turns the ride by local yaw, pitch and roll degrees eased through vanilla's mouse smoother so it spreads over a few frames. */
|
||||
fun rotateSmoothed(rotation: Vec3) {
|
||||
controller.rotate(
|
||||
yawSmoother.getNewDeltaValue(rotation.x * SMOOTHED_SHARE, deltaTime).toFloat(),
|
||||
pitchSmoother.getNewDeltaValue(rotation.y * SMOOTHED_SHARE, deltaTime).toFloat(),
|
||||
rollSmoother.getNewDeltaValue(rotation.z * SMOOTHED_SHARE, deltaTime).toFloat()
|
||||
)
|
||||
}
|
||||
}
|
||||
+43
-19
@@ -12,15 +12,20 @@ import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle
|
||||
import com.cobblemon.mod.common.api.riding.camera.RideCameraContext
|
||||
import com.cobblemon.mod.common.api.riding.camera.RideLookInput
|
||||
import com.cobblemon.mod.common.api.riding.camera.RideLookTake
|
||||
import com.cobblemon.mod.common.api.riding.camera.RideLookSettings
|
||||
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
import com.cobblemon.mod.common.api.riding.util.RideFrame
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.util.SmoothDouble
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.Vec2
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.joml.Quaternionf
|
||||
|
||||
/**
|
||||
* Represents the behaviour of a Pokemon when being ridden.
|
||||
@@ -41,30 +46,39 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
|
||||
|
||||
fun pose(settings: Settings, state: State, vehicle: PokemonEntity): PoseType
|
||||
|
||||
/** Ride speed for vanilla's setSpeed. Only reads state, anything that changes state goes in [driverTick]. */
|
||||
fun speed(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float
|
||||
|
||||
/**
|
||||
* Runs once a tick only on the client that's steering, after [tick] and the ride's rotation.
|
||||
* Whatever it works out reaches the server and other players through the riding state sync.
|
||||
*/
|
||||
fun driverTick(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player) {}
|
||||
|
||||
fun clampPassengerRotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity) {}
|
||||
|
||||
fun updatePassengerRotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity) {}
|
||||
|
||||
fun rotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity): Vec2
|
||||
|
||||
/**
|
||||
* Ride velocity for this tick in world space blocks per tick.
|
||||
* [RideFrame.yawToWorld] turns a velocity worked out along the ride's yaw into world space.
|
||||
*/
|
||||
fun velocity(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, input: Vec3): Vec3
|
||||
|
||||
fun angRollVel(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, deltaTime: Double): Vec3
|
||||
/**
|
||||
* True to have the driver's client call [velocity] a second time each tick inside travel.
|
||||
* Vanilla already asks for it once in getRiddenInput and travel always asked again, so every ride's
|
||||
* numbers are tuned around running twice a tick. Return false once a ride is retuned for a single run.
|
||||
*/
|
||||
fun velocityRunsTwicePerTick(settings: Settings, state: State, vehicle: PokemonEntity): Boolean = true
|
||||
|
||||
fun rotationOnMouseXY(
|
||||
settings: Settings,
|
||||
state: State,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
mouseY: Double,
|
||||
mouseX: Double,
|
||||
mouseYSmoother: SmoothDouble,
|
||||
mouseXSmoother: SmoothDouble,
|
||||
sensitivity: Double,
|
||||
deltaTime: Double
|
||||
): Vec3
|
||||
/**
|
||||
* Turns the ride for one frame while it rolls. Runs every frame on the client that's driving with the mouse
|
||||
* share the ride took in [consumeLook]. Rotate through [RideSteerInput.controller] or its helpers.
|
||||
*/
|
||||
fun steer(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, input: RideSteerInput) {}
|
||||
|
||||
fun canJump(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Boolean
|
||||
|
||||
@@ -76,8 +90,6 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
|
||||
|
||||
fun rideFovMultiplier(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float
|
||||
|
||||
fun useAngVelSmoothing(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
|
||||
|
||||
fun inertia(settings: Settings, state: State, vehicle: PokemonEntity): Double
|
||||
|
||||
fun shouldRoll(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
|
||||
@@ -111,10 +123,22 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
|
||||
fun applyRenderRotation(settings: Settings, state: State, vehicle: PokemonEntity, partialTicks: Float) {}
|
||||
|
||||
/**
|
||||
* Determines if the pair of mouse inputs Pair(mouseX, mouseY) get used to modify the driverRotations or the vehicle rotations themselves.
|
||||
* Driver rotations are the rotations used by passengers or a driver that is freelooking. This does not override the freelook button.
|
||||
* How much of this frame's mouse look the ride takes to steer with. What it takes goes through
|
||||
* [steer] and the rest moves the rider's look. Only asked for the driver and never while freelooking.
|
||||
*/
|
||||
fun mouseModifiesDriverRotation(settings: Settings, state: State, vehicle: PokemonEntity): Pair<Boolean, Boolean> = Pair(false, false)
|
||||
fun consumeLook(settings: Settings, state: State, vehicle: PokemonEntity, input: RideLookInput): RideLookTake = RideLookTake.all(input)
|
||||
|
||||
/**
|
||||
* Rotation the rider's camera hangs off while the ride rolls. Called every frame on the client
|
||||
* and the rider's look sits on top of it. The player's disableRoll setting still applies after this.
|
||||
*/
|
||||
fun cameraBase(settings: Settings, state: State, vehicle: PokemonEntity, context: RideCameraContext): Quaternionf = context.defaultBase()
|
||||
|
||||
/** Degrees to roll the first person hands and held items */
|
||||
fun firstPersonHandRoll(settings: Settings, state: State, vehicle: PokemonEntity, context: RideCameraContext): Float = 0f
|
||||
|
||||
/** Look limits and carry for riders while the ride rolls. */
|
||||
fun lookSettings(settings: Settings, state: State, vehicle: PokemonEntity): RideLookSettings = RideLookSettings.DEFAULT
|
||||
|
||||
/**
|
||||
* Used in MoLang functions for querying states for animations or other magical MoLang effects.
|
||||
|
||||
+3
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.riding.behaviour
|
||||
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.air.*
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.land.ClimbBehaviour
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseBehaviour
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleBehaviour
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartBehaviour
|
||||
@@ -35,7 +36,9 @@ object RidingBehaviours {
|
||||
register(MinekartBehaviour.KEY, MinekartBehaviour())
|
||||
register(HoverBehaviour.KEY, HoverBehaviour())
|
||||
register(RocketBehaviour.KEY, RocketBehaviour())
|
||||
register(BoardBehaviour.KEY, BoardBehaviour())
|
||||
register(SubmarineBehaviour.KEY, SubmarineBehaviour())
|
||||
register(ClimbBehaviour.KEY, ClimbBehaviour())
|
||||
register(CompositeBehaviour.KEY, CompositeBehaviour())
|
||||
}
|
||||
|
||||
|
||||
+310
-101
@@ -15,8 +15,13 @@ import com.cobblemon.mod.common.CobblemonRideSettings
|
||||
import com.cobblemon.mod.common.OrientationControllable
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.FORWARDS
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.UP
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.*
|
||||
import com.cobblemon.mod.common.api.riding.camera.RideCameraContext
|
||||
import com.cobblemon.mod.common.api.riding.camera.RideLookInput
|
||||
import com.cobblemon.mod.common.api.riding.camera.RideLookTake
|
||||
import com.cobblemon.mod.common.api.riding.posing.PoseOption
|
||||
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
|
||||
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
@@ -24,7 +29,9 @@ import com.cobblemon.mod.common.api.riding.stats.RidingStat
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import com.cobblemon.mod.common.util.math.FloatSpring
|
||||
import com.cobblemon.mod.common.util.math.geometry.toRadians
|
||||
import com.cobblemon.mod.common.util.math.simpleSpringDamper
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
@@ -34,6 +41,8 @@ import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.Vec2
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.joml.Matrix3f
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
import kotlin.math.*
|
||||
|
||||
@@ -42,6 +51,28 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
|
||||
val KEY = cobblemonResource("air/bird")
|
||||
|
||||
private const val FLY_POSE_SPEED = 0.1
|
||||
// At or below this the bird hovers instead of flying
|
||||
private const val HOVER_SPEED = 0.1
|
||||
// Within this many degrees of straight up or down wings level means nothing so the camera holds its roll
|
||||
private const val VERTICAL_DEADZONE_DEGREES = 15f
|
||||
private val MIN_LEVEL_UP_LENGTH_SQ = sin(VERTICAL_DEADZONE_DEGREES.toRadians()).pow(2)
|
||||
// Below this speed the bird is too slow to bank so its wings always level out
|
||||
private const val NO_BANK_SPEED = 0.2
|
||||
// Degrees before the max roll where rolling further starts easing off. Raise for a softer stop
|
||||
private const val ROLL_CAP_BAND_DEGREES = 20f
|
||||
// Degrees past the turn roll limit over which mouse pitch hands over from world up and down to the bird's own pitch
|
||||
private const val PITCH_BLEND_BAND_DEGREES = 15f
|
||||
// Pitch where mouse pitch starts handing back to the bird's own pitch. By straight up or down it's all the bird's
|
||||
// own so a loop carries on over the top instead of world up pulling it back
|
||||
private const val WORLD_PITCH_FADE_START = 60f
|
||||
// Fastest the wings level out by themselves in degrees per second
|
||||
private const val MAX_ROLL_SETTLE_RATE = 45f
|
||||
// Within this many degrees of level the settle rate eases off so it lands on level instead of overshooting
|
||||
private const val ROLL_SETTLE_ARRIVAL_DEGREES = 35f
|
||||
// How fast the settle rate can change in degrees per second squared. Same as the old per frame cap at 60 fps
|
||||
private const val ROLL_SETTLE_ACCEL = 67.5f
|
||||
// Keeps the turn roll limit from dividing by zero
|
||||
private const val MIN_TURN_ROLL_LIMIT = 1f
|
||||
private const val GLIDE_ENTRY_SPEED = 0.19
|
||||
private const val GLIDE_EXIT_SPEED = 0.13
|
||||
}
|
||||
@@ -84,25 +115,17 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
|
||||
) {
|
||||
if(vehicle.level().isClientSide) {
|
||||
tickStamina(settings, state, vehicle, driver)
|
||||
checkUpsideDown(state, vehicle, driver)
|
||||
checkStaminaDive(state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkUpsideDown(
|
||||
state: BirdState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
) {
|
||||
// Also checks stamina
|
||||
val controller = (vehicle as OrientationControllable).orientationController
|
||||
if (cos(controller.roll.toRadians()) < 0.0) {
|
||||
val upsideDownRate = 1.0 / (2.0 * 20.0) // 2 seconds to max out upsideDown force
|
||||
state.currUpsideDownForce.set( min(1.0, state.currUpsideDownForce.get() + upsideDownRate * abs(cos(controller.roll.toRadians()))))
|
||||
} else if (state.stamina.get() == 0.0f) { // checks stamina depletion and forces dive if depleted
|
||||
val upsideDownRate = 1.0 / (2.0 * 20.0) // 1 seconds to max out
|
||||
state.currUpsideDownForce.set( min(1.0, state.currUpsideDownForce.get() + upsideDownRate))
|
||||
} else { // Its right side up or has stamina so reset it
|
||||
state.currUpsideDownForce.set(0.0)
|
||||
// Out of stamina the bird can't hold itself up so it builds up a dive
|
||||
private fun checkStaminaDive(state: BirdState) {
|
||||
if (state.stamina.get() == 0.0f) {
|
||||
val diveRate = 1.0 / (2.0 * 20.0) // 2 seconds to max out
|
||||
state.staminaDiveForce.set(min(1.0, state.staminaDiveForce.get() + diveRate))
|
||||
} else {
|
||||
state.staminaDiveForce.set(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,8 +184,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
|
||||
//Perform ride velocity update
|
||||
calculateRideSpaceVel(settings, state, vehicle, driver)
|
||||
|
||||
// The downward force used to encourage players to stop flying upside down.
|
||||
val extraDownwardForce = state.currUpsideDownForce.get().pow(2) * -0.3 // 6 blocks a second downward
|
||||
// Pulls the bird down once it's out of stamina
|
||||
val extraDownwardForce = state.staminaDiveForce.get().pow(2) * -0.3 // 6 blocks a second downward
|
||||
|
||||
// Convert the local velocity vector into a world vector
|
||||
val localVelVec = Vector3f(
|
||||
@@ -170,8 +193,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
|
||||
(state.rideVelocity.get().y).toFloat(),
|
||||
state.rideVelocity.get().z.toFloat() * -1.0f // Flip the z axis to make this left handed
|
||||
)
|
||||
// Unrotate preemptively as this vector gets rotatee later down the line in MC logic.
|
||||
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d().yRot(vehicle.yRot.toRadians())
|
||||
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d()
|
||||
worldVelVec = worldVelVec.add(0.0, extraDownwardForce, 0.0) // Add the stamina depletion force to bring the ride down.
|
||||
|
||||
return worldVelVec
|
||||
@@ -282,19 +304,23 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
|
||||
state.rideVelocity.set(newVelocity)
|
||||
}
|
||||
|
||||
override fun angRollVel(
|
||||
private fun rotateOverTime(
|
||||
settings: BirdSettings,
|
||||
state: BirdState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
if (vehicle !is OrientationControllable) return Vec3.ZERO
|
||||
) {
|
||||
if (vehicle !is OrientationControllable) return
|
||||
val controller = (vehicle as OrientationControllable).orientationController
|
||||
|
||||
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalBird.handlingExpr!!)
|
||||
|
||||
val yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble()))
|
||||
// Turn rate tops out at the turn roll limit. Banking further is for show and doesn't turn any harder
|
||||
val turnRollLimit = frameTuning(settings, state, vehicle).turnRollLimit.coerceIn(MIN_TURN_ROLL_LIMIT, 90f)
|
||||
val bankFromLevel = if (abs(controller.roll) > 90f) 180f - abs(controller.roll) else abs(controller.roll)
|
||||
val turnShare = sin(min(bankFromLevel, turnRollLimit).toRadians()) / sin(turnRollLimit.toRadians()) * controller.roll.sign
|
||||
val yawDeltaDeg = deltaTime * handling * turnShare
|
||||
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble()))).coerceIn(-1.0, 1.0)
|
||||
|
||||
// Dampen yaw when upside down
|
||||
@@ -309,8 +335,6 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
|
||||
controller.applyGlobalPitch(pCorr)
|
||||
controller.rotateRoll(rCorr)
|
||||
|
||||
//yaw, pitch, roll
|
||||
return Vec3(0.0, 0.0, 0.0)
|
||||
}
|
||||
|
||||
private fun correctOrientation(
|
||||
@@ -320,45 +344,28 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
|
||||
controller: OrientationController,
|
||||
deltaTime: Double
|
||||
) {
|
||||
// Calculate correcting roll force.
|
||||
val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect ?: globalBird.timeToRollCorrect!!)
|
||||
// If the ride is moving then have the correction rate be an equal proportion of:
|
||||
// - how rolled are you
|
||||
// - how long has it been since last mouse input (higher is stronger correction)
|
||||
// Also the correction is proportional as a whole to how NOT pitched are you so you don't spin endlessly when looking up or down
|
||||
val howRolledAmI = sqrt(abs((cos(controller.roll.toRadians()) - 1.0f) * 0.5f)) // sqrt so it trends towards one pretty quickly
|
||||
val maxRollCorrectionRate =
|
||||
if (state.rideVelocity.get().length() < 0.2) 45.0f
|
||||
else 45.0f * cos(controller.pitch.toRadians()).pow(2) * (
|
||||
howRolledAmI*1.0f +
|
||||
(state.noInputTimeRoll.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
|
||||
)
|
||||
val maxRollForce = maxRollCorrectionRate / 40.0f
|
||||
val rollArrivalDeg = 35.0f
|
||||
val desiredRoll = 0.0f
|
||||
|
||||
// Calculate the signed error (how far and in what direction)
|
||||
val rollError = Mth.wrapDegrees(desiredRoll.angleDifference(controller.roll))
|
||||
// Use the absolute error to calculate the magnitude of the influence
|
||||
val influenceMagnitude = (min(rollArrivalDeg, abs(rollError)) / rollArrivalDeg)
|
||||
val desiredRollForce = maxRollCorrectionRate * influenceMagnitude * -rollError.sign
|
||||
var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
|
||||
// Ensure the 'steering' correction force doesn't exceed our maxForce
|
||||
if (abs(steeredRollForce) > maxRollForce) {
|
||||
steeredRollForce = steeredRollForce.sign * maxRollForce
|
||||
// Wings only level out by themselves when the rider has let go and they're nearly level already,
|
||||
// or when the bird's too slow to bank at all. A bank the rider holds stays where they put it
|
||||
val tuning = frameTuning(settings, state, vehicle)
|
||||
val idle = state.noInputTimeRoll.get() >= tuning.rollSettleDelay
|
||||
val settling = state.rideVelocity.get().length() < NO_BANK_SPEED || (idle && abs(controller.roll) < tuning.rollSettleAngle)
|
||||
val rollError = Mth.wrapDegrees(0.0f.angleDifference(controller.roll))
|
||||
val desiredRollRate = if (settling) {
|
||||
MAX_ROLL_SETTLE_RATE * (min(ROLL_SETTLE_ARRIVAL_DEGREES, abs(rollError)) / ROLL_SETTLE_ARRIVAL_DEGREES) * -rollError.sign
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
// Apply the 'steered' force (its really not a steering force I need a better name)
|
||||
state.currRollCorrectionForce.set(
|
||||
state.currRollCorrectionForce.get() + steeredRollForce
|
||||
)
|
||||
val maxRateChange = ROLL_SETTLE_ACCEL * deltaTime
|
||||
val currentRollRate = state.currRollCorrectionForce.get()
|
||||
state.currRollCorrectionForce.set(currentRollRate + (desiredRollRate - currentRollRate).coerceIn(-maxRateChange, maxRateChange))
|
||||
|
||||
//Calculate correcting pitch force.
|
||||
val maxPitchCorrectionRate = if (state.currUpsideDownForce.get() != 0.0) 15.0f * state.currUpsideDownForce.get().toFloat() else 30.0f
|
||||
val maxPitchCorrectionRate = if (state.staminaDiveForce.get() != 0.0) 15.0f * state.staminaDiveForce.get().toFloat() else 30.0f
|
||||
val maxPitchForce = maxPitchCorrectionRate / 40.0f
|
||||
val pitchArrivalDeg = 35.0f
|
||||
val desiredPitch = if (state.currUpsideDownForce.get() != 0.0) 90.0f else 0.0f
|
||||
val desiredPitch = if (state.staminaDiveForce.get() != 0.0) 90.0f else 0.0f
|
||||
|
||||
if((state.rideVelocity.get().length() < 0.1) || state.currUpsideDownForce.get() != 0.0) {
|
||||
if((state.rideVelocity.get().length() < 0.1) || state.staminaDiveForce.get() != 0.0) {
|
||||
val pitchErrror = Mth.wrapDegrees(desiredPitch.angleDifference(controller.pitch))
|
||||
val arrivalInfluence = (min(pitchArrivalDeg,abs(pitchErrror)) / pitchArrivalDeg)
|
||||
val desiredPitchForce = maxPitchCorrectionRate * arrivalInfluence * -pitchErrror.sign
|
||||
@@ -381,7 +388,18 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
|
||||
}
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
override fun steer(
|
||||
settings: BirdSettings,
|
||||
state: BirdState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: RideSteerInput
|
||||
) {
|
||||
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
|
||||
rotateOverTime(settings, state, vehicle, driver, input.deltaTime)
|
||||
}
|
||||
|
||||
private fun mouseRotation(
|
||||
settings: BirdSettings,
|
||||
state: BirdState,
|
||||
vehicle: PokemonEntity,
|
||||
@@ -394,15 +412,10 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
if (driver !is OrientationControllable) return Vec3.ZERO
|
||||
val controller = (driver as OrientationControllable).orientationController
|
||||
// Begin the roll correction time counter if not enough mouse input
|
||||
// is detected
|
||||
if (mouseX < 1 && abs(mouseY) < 1) {
|
||||
// Time since the rider last moved the mouse, wings only settle once it passes the settle delay
|
||||
if (abs(mouseX) < 1 && abs(mouseY) < 1) {
|
||||
state.noInputTimeRoll.set(state.noInputTimeRoll.get() + deltaTime)
|
||||
} else if (cos(controller.roll.toRadians()) < 0.1) {
|
||||
state.noInputTimeRoll.set(state.noInputTimeRoll.get() + deltaTime * 0.5)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
state.noInputTimeRoll.set(0.0)
|
||||
}
|
||||
|
||||
@@ -443,46 +456,42 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
|
||||
|
||||
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBird.speedExpr!!) / 20.0
|
||||
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult ?: globalBird.mouseInputMult!!)
|
||||
// Applied to mouseX to prevent turning upside down when trying to roll hard.
|
||||
val rollDegreeDebuff = if(mouseX.sign.toFloat() == controller.roll.sign) 1 - 0.9f*sqrt(abs(sin(controller.roll.toRadians())))*sqrt(abs(cos(controller.pitch))) else 1.0f
|
||||
val tuning = frameTuning(settings, state, vehicle)
|
||||
|
||||
//Smooth out mouse input.
|
||||
val smoothingSpeed = 2.0
|
||||
val mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult * rollDegreeDebuff
|
||||
// When upside down apply a pitching force to nudge the player towards a dive. This reduces the 'weirdness'
|
||||
// of being upside down
|
||||
val rolledOverPitchCorrection = -8*sqrt(abs(min(cos(controller.roll.toRadians()),0.0f))) * (cos(controller.pitch.toRadians())).pow(2)
|
||||
val mouseYc = ((mouseY).coerceIn(-45.0, 45.0) * mouseInputMult) + rolledOverPitchCorrection
|
||||
val mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult
|
||||
val mouseYc = (mouseY).coerceIn(-45.0, 45.0) * mouseInputMult
|
||||
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed)
|
||||
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed)
|
||||
val currSpeed = state.rideVelocity.get().length()
|
||||
val currHorzSpeed = state.rideVelocity.get().horizontalDistance()
|
||||
val hoverSpeed = 0.1
|
||||
val hoverSpeed = HOVER_SPEED
|
||||
val pitchDamping = abs(cos(controller.pitch.toRadians())).pow(2) // Helper value used when wanting to discard the influence of another value at steep pitches
|
||||
|
||||
// Yaw locally when at or below hoverSpeed but not when pitched up or rolled over
|
||||
val globalYaw = xInput * (1 - sqrt(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed))).coerceIn(0.0, 1.0)
|
||||
controller.applyGlobalYaw(globalYaw.toFloat())
|
||||
|
||||
// Pitch locally up or down depending upon a number of factors:
|
||||
// - Reduce pitch substantially when rolled and in a steep yaw. This prevents the player from ignoring a slow handling stat
|
||||
// - Do not pitch at all when at or below hover speed
|
||||
// - pitch faster at higher speeds
|
||||
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr ?: globalBird.horizontalPitchExpr!!)
|
||||
var localPitch = yInput * (1 - abs(sin(controller.roll.toRadians())).pow(2) * p * hypot(controller.forwardVector.x.toDouble(), controller.forwardVector.z.toDouble()).toFloat())
|
||||
localPitch *= if (currSpeed > hoverSpeed || yInput > 0.0) RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed).coerceIn(0.2, 1.0)
|
||||
// No pitching at hover speed and faster pitching the faster the bird goes
|
||||
val pitchInput = yInput * if (currSpeed > hoverSpeed || yInput > 0.0) RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed).coerceIn(0.2, 1.0)
|
||||
else 0.0
|
||||
|
||||
// Roll less when slow and not at all when at hoverSpeed or lower.
|
||||
// Apply a roll righting force when trying to pitch hard while rolled sideways. This nudges the player towards
|
||||
// pitching globally
|
||||
val pitchInfluencedRollCorrection = 0.0
|
||||
val howRolledAmI = (( cos(controller.roll.toRadians()) - 1).coerceIn(-1.0f, 0.0f) * -2) //Coerce into how much roll you want to let in in the deadzone
|
||||
// Inside a normal bank mouse pitch is world up and down so pulling up climbs instead of tightening the turn.
|
||||
// Past the turn roll limit, or pointing near straight up or down, it hands over to the bird's own pitch
|
||||
val pastTurnRoll = smoothstep01((abs(controller.roll) - tuning.turnRollLimit) / PITCH_BLEND_BAND_DEGREES)
|
||||
val nearVertical = smoothstep01((abs(controller.pitch) - WORLD_PITCH_FADE_START) / (90f - WORLD_PITCH_FADE_START))
|
||||
val localShare = 1f - (1f - pastTurnRoll) * (1f - nearVertical)
|
||||
controller.applyGlobalPitch((pitchInput * (1f - localShare)).toFloat())
|
||||
|
||||
// Pulling with the bird's own pitch while rolled turns hard so it's held back by handling there
|
||||
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr ?: globalBird.horizontalPitchExpr!!)
|
||||
val localPitch = pitchInput * localShare * (1 - abs(sin(controller.roll.toRadians())).pow(2) * p * hypot(controller.forwardVector.x.toDouble(), controller.forwardVector.z.toDouble()).toFloat())
|
||||
|
||||
// Roll less when slow and not at all when at hoverSpeed or lower
|
||||
val isInputtingTowardsRoll = if ( xInput.sign == controller.roll.sign.toDouble()) 1.0 else 0.0
|
||||
val inputInRolledDir = if (isInputtingTowardsRoll == 1.0) 1.0 else -cos(controller.roll.toRadians()).coerceIn(-1.0f, 0.0f).toDouble()
|
||||
val rollDampen = (1 - howRolledAmI.coerceIn(0.0f, 0.7f) * pitchDamping * inputInRolledDir)
|
||||
val rollForce = if (currHorzSpeed > hoverSpeed) xInput * sqrt(abs(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed)).coerceIn(0.0,1.0)) * rollDampen + pitchInfluencedRollCorrection
|
||||
else 0.0
|
||||
val rollInput = xInput * sqrt(abs(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed)).coerceIn(0.0,1.0))
|
||||
val rollForce = if (currHorzSpeed > hoverSpeed) capRoll(rollInput, controller.roll, tuning.maxRoll) else 0.0
|
||||
|
||||
// When already rolled significantly part of the roll force should turn to local yaw force
|
||||
val localYawForce = rollForce * abs(sin(controller.roll.toRadians())).pow(2) * pitchDamping * isInputtingTowardsRoll
|
||||
@@ -546,14 +555,6 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
|
||||
return 1.0f + normalizedGlideSpeed.pow(2).toFloat() * 0.2f
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(
|
||||
settings: BirdSettings,
|
||||
state: BirdState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun inertia(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): Double {
|
||||
return 0.5
|
||||
}
|
||||
@@ -607,6 +608,139 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
|
||||
|
||||
override fun createDefaultState(settings: BirdSettings) = BirdState()
|
||||
|
||||
/**
|
||||
* Keeps the camera level until the bird rolls past the engage angle, then eases it into the bird's roll.
|
||||
* It levels out again once the bird comes back under the release angle. Yaw and pitch always follow the bird.
|
||||
*/
|
||||
override fun cameraBase(
|
||||
settings: BirdSettings,
|
||||
state: BirdState,
|
||||
vehicle: PokemonEntity,
|
||||
context: RideCameraContext
|
||||
): Quaternionf {
|
||||
val ride = context.defaultBase()
|
||||
val tuning = frameTuning(settings, state, vehicle)
|
||||
val roll = context.data as? BirdCameraRoll ?: BirdCameraRoll().also { context.data = it }
|
||||
|
||||
val forward = FORWARDS.rotate(ride, Vector3f())
|
||||
val up = UP.rotate(ride, Vector3f())
|
||||
|
||||
// Up with wings level. Pointing near straight up or down it flips sides so it's only used away from vertical
|
||||
val levelUp = Vector3f(0f, 1f, 0f).sub(Vector3f(forward).mul(forward.y))
|
||||
val hasLevel = levelUp.lengthSquared() > MIN_LEVEL_UP_LENGTH_SQ
|
||||
if (hasLevel) {
|
||||
levelUp.normalize()
|
||||
val rideRoll = signedAngle(levelUp, up, forward)
|
||||
if (roll.engaged && abs(rideRoll) < tuning.rollRelease) {
|
||||
roll.engaged = false
|
||||
} else if (!roll.engaged && abs(rideRoll) > tuning.rollEngage) {
|
||||
roll.engaged = true
|
||||
}
|
||||
}
|
||||
|
||||
// Camera up lives in world space so the level up flipping over the top of a loop can't drag the camera round
|
||||
val cameraUp = roll.cameraUp?.let { flatten(it, forward) } ?: Vector3f(up)
|
||||
val target = when {
|
||||
roll.engaged -> up
|
||||
hasLevel -> levelUp
|
||||
else -> Vector3f(cameraUp)
|
||||
}
|
||||
|
||||
if (roll.cameraUp == null) {
|
||||
cameraUp.set(target)
|
||||
} else if (context.frameSeconds > 0f) {
|
||||
// Spring the leftover angle so it builds up speed and settles instead of lunging at the start
|
||||
roll.spring.value = signedAngle(target, cameraUp, forward)
|
||||
simpleSpringDamper(roll.spring, 0f, tuning.rollHalflife, context.frameSeconds)
|
||||
cameraUp.set(target).rotateAxis(roll.spring.value * Mth.DEG_TO_RAD, forward.x, forward.y, forward.z)
|
||||
}
|
||||
roll.cameraUp = cameraUp
|
||||
|
||||
return levelFrame(forward, cameraUp)
|
||||
}
|
||||
|
||||
override fun firstPersonHandRoll(
|
||||
settings: BirdSettings,
|
||||
state: BirdState,
|
||||
vehicle: PokemonEntity,
|
||||
context: RideCameraContext
|
||||
): Float {
|
||||
return context.rideRollAroundView() * frameTuning(settings, state, vehicle).handRollShare
|
||||
}
|
||||
|
||||
// Hovering the rider can look up and down freely and around up to the hover limit. Only pushing past it turns the bird
|
||||
override fun consumeLook(
|
||||
settings: BirdSettings,
|
||||
state: BirdState,
|
||||
vehicle: PokemonEntity,
|
||||
input: RideLookInput
|
||||
): RideLookTake {
|
||||
// Only forward speed counts so rising or sinking on the spot is still hovering
|
||||
if (state.rideVelocity.get().horizontalDistance() > HOVER_SPEED) return RideLookTake.all(input)
|
||||
val limit = frameTuning(settings, state, vehicle).hoverLookYawLimit
|
||||
// Never snap a look that's already past the limit, it just can't be pushed further out
|
||||
val bound = max(limit, abs(input.yaw))
|
||||
val target = input.yaw + input.yawDelta
|
||||
val pushed = target - target.coerceIn(-bound, bound)
|
||||
return RideLookTake(pushed, 0f, recenterYaw = false, recenterPitch = false)
|
||||
}
|
||||
|
||||
/** Knobs the bird reads every frame, resolved at most once a tick so MoLang doesn't run per frame. */
|
||||
private fun frameTuning(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): BirdTuning {
|
||||
val cached = state.frameTuning.get()
|
||||
if (cached != null && cached.tick == vehicle.tickCount) return cached
|
||||
val runtime = vehicle.runtime
|
||||
return BirdTuning(
|
||||
tick = vehicle.tickCount,
|
||||
rollEngage = runtime.resolveFloat(settings.cameraRollEngage ?: globalBird.cameraRollEngage!!),
|
||||
rollRelease = runtime.resolveFloat(settings.cameraRollRelease ?: globalBird.cameraRollRelease!!),
|
||||
rollHalflife = runtime.resolveFloat(settings.cameraRollHalflife ?: globalBird.cameraRollHalflife!!),
|
||||
hoverLookYawLimit = runtime.resolveFloat(settings.hoverLookYawLimit ?: globalBird.hoverLookYawLimit!!),
|
||||
turnRollLimit = runtime.resolveFloat(settings.turnRollLimit ?: globalBird.turnRollLimit!!),
|
||||
maxRoll = runtime.resolveFloat(settings.maxRoll ?: globalBird.maxRoll!!),
|
||||
rollSettleAngle = runtime.resolveFloat(settings.rollSettleAngle ?: globalBird.rollSettleAngle!!),
|
||||
rollSettleDelay = runtime.resolveFloat(settings.timeToRollCorrect ?: globalBird.timeToRollCorrect!!),
|
||||
handRollShare = runtime.resolveFloat(settings.handRollShare ?: globalBird.handRollShare!!)
|
||||
).also { state.frameTuning.set(it) }
|
||||
}
|
||||
|
||||
/** Smoothstep of [t] clamped to 0 to 1. */
|
||||
private fun smoothstep01(t: Float): Float {
|
||||
val x = t.coerceIn(0f, 1f)
|
||||
return x * x * (3f - 2f * x)
|
||||
}
|
||||
|
||||
/**
|
||||
* Eases roll that would take the bird further from level to a stop at [maxRoll] degrees.
|
||||
* Rolling back toward level is never held back.
|
||||
*/
|
||||
private fun capRoll(rollDelta: Double, roll: Float, maxRoll: Float): Double {
|
||||
if (rollDelta.sign != roll.sign.toDouble()) return rollDelta
|
||||
val room = (maxRoll - abs(roll)).coerceAtLeast(0f)
|
||||
val ease = (room / ROLL_CAP_BAND_DEGREES).coerceIn(0f, 1f)
|
||||
val eased = rollDelta * ease * ease * (3f - 2f * ease)
|
||||
// Never step past the cap in one frame
|
||||
return eased.coerceIn(-room.toDouble(), room.toDouble())
|
||||
}
|
||||
|
||||
/** Signed degrees from [from] to [to] turning around [axis]. Both have to be perpendicular to it. */
|
||||
private fun signedAngle(from: Vector3f, to: Vector3f, axis: Vector3f): Float {
|
||||
return atan2(Vector3f(from).cross(to).dot(axis), from.dot(to)) * Mth.RAD_TO_DEG
|
||||
}
|
||||
|
||||
/** [vector] with the part along [axis] removed, or null if nothing is left. */
|
||||
private fun flatten(vector: Vector3f, axis: Vector3f): Vector3f? {
|
||||
val flat = Vector3f(vector).sub(Vector3f(axis).mul(vector.dot(axis)))
|
||||
return if (flat.lengthSquared() > 0f) flat.normalize() else null
|
||||
}
|
||||
|
||||
/** Rotation whose forward is [forward] and up is [up]. They have to be unit length and perpendicular. */
|
||||
private fun levelFrame(forward: Vector3f, up: Vector3f): Quaternionf {
|
||||
val right = Vector3f(forward).cross(up)
|
||||
val back = Vector3f(forward).negate()
|
||||
return Quaternionf().setFromNormalized(Matrix3f(right, up, back))
|
||||
}
|
||||
|
||||
override fun asMoLangValue(
|
||||
settings: BirdSettings,
|
||||
state: BirdState,
|
||||
@@ -659,6 +793,39 @@ class BirdSettings : RidingBehaviourSettings {
|
||||
// This is coerced between 1 and 0 or things would get weird.
|
||||
var stamDrainPerRateDive: Expression? = null
|
||||
private set
|
||||
// Degrees of roll before the camera starts rolling with the bird
|
||||
var cameraRollEngage: Expression? = null
|
||||
private set
|
||||
|
||||
// Degrees the bird has to roll back under before the camera levels out. Keep it below engage so it doesn't flicker
|
||||
var cameraRollRelease: Expression? = null
|
||||
private set
|
||||
|
||||
// Seconds the camera roll spring takes to settle. Raise for a lazier camera
|
||||
var cameraRollHalflife: Expression? = null
|
||||
private set
|
||||
|
||||
// Degrees either side the rider can look while hovering before pushing further turns the bird
|
||||
var hoverLookYawLimit: Expression? = null
|
||||
private set
|
||||
|
||||
// Roll in degrees where turning tops out and mouse pitch stops being world up and down
|
||||
var turnRollLimit: Expression? = null
|
||||
private set
|
||||
|
||||
// Furthest the bird can roll from level in degrees. Keep cameraRollEngage just above it so the camera
|
||||
// stays level through normal flying and only rolls in loops and flips. 180 lets it roll all the way over
|
||||
var maxRoll: Expression? = null
|
||||
private set
|
||||
|
||||
// Within this many degrees of level the wings settle back to level once the rider lets go of the mouse
|
||||
var rollSettleAngle: Expression? = null
|
||||
private set
|
||||
|
||||
// Share of the bird's roll the first person hands lean with, 0 to 1
|
||||
var handRollShare: Expression? = null
|
||||
private set
|
||||
|
||||
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
@@ -677,6 +844,14 @@ class BirdSettings : RidingBehaviourSettings {
|
||||
buffer.writeNullableExpression(stamDrainPercRateGlide)
|
||||
buffer.writeNullableExpression(stamDrainPerRateDive)
|
||||
buffer.writeNullableExpression(timeToRollCorrect)
|
||||
buffer.writeNullableExpression(cameraRollEngage)
|
||||
buffer.writeNullableExpression(cameraRollRelease)
|
||||
buffer.writeNullableExpression(cameraRollHalflife)
|
||||
buffer.writeNullableExpression(hoverLookYawLimit)
|
||||
buffer.writeNullableExpression(turnRollLimit)
|
||||
buffer.writeNullableExpression(maxRoll)
|
||||
buffer.writeNullableExpression(rollSettleAngle)
|
||||
buffer.writeNullableExpression(handRollShare)
|
||||
}
|
||||
|
||||
override fun decode(buffer: RegistryFriendlyByteBuf) {
|
||||
@@ -695,6 +870,14 @@ class BirdSettings : RidingBehaviourSettings {
|
||||
stamDrainPercRateGlide = buffer.readNullableExpression()
|
||||
stamDrainPerRateDive = buffer.readNullableExpression()
|
||||
timeToRollCorrect = buffer.readNullableExpression()
|
||||
cameraRollEngage = buffer.readNullableExpression()
|
||||
cameraRollRelease = buffer.readNullableExpression()
|
||||
cameraRollHalflife = buffer.readNullableExpression()
|
||||
hoverLookYawLimit = buffer.readNullableExpression()
|
||||
turnRollLimit = buffer.readNullableExpression()
|
||||
maxRoll = buffer.readNullableExpression()
|
||||
rollSettleAngle = buffer.readNullableExpression()
|
||||
handRollShare = buffer.readNullableExpression()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,7 +888,9 @@ class BirdState : RidingBehaviourState() {
|
||||
var currPitchCorrectionForce = ridingState(0.0, Side.CLIENT)
|
||||
var noInputTimeRoll = ridingState(0.0, Side.CLIENT)
|
||||
var noInputTimePitch = ridingState(0.0, Side.CLIENT)
|
||||
var currUpsideDownForce = ridingState(0.0, Side.CLIENT)
|
||||
var staminaDiveForce = ridingState(0.0, Side.CLIENT)
|
||||
// Client only cache of the knobs read every frame, never synced
|
||||
var frameTuning = ridingState<BirdTuning?>(null, Side.CLIENT)
|
||||
|
||||
override fun encode(buffer: FriendlyByteBuf) {
|
||||
super.encode(buffer)
|
||||
@@ -725,6 +910,7 @@ class BirdState : RidingBehaviourState() {
|
||||
currPitchCorrectionForce.set(0.0, forced = true)
|
||||
noInputTimeRoll.set(0.0, forced = true)
|
||||
noInputTimePitch.set(0.0, forced = true)
|
||||
frameTuning.set(null, forced = true)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
@@ -761,3 +947,26 @@ fun Float.angleDifference(other: Float): Float {
|
||||
while (diff > 180F) diff -= 360F
|
||||
return diff
|
||||
}
|
||||
|
||||
/** Bird knobs resolved for one tick. */
|
||||
data class BirdTuning(
|
||||
val tick: Int,
|
||||
val rollEngage: Float,
|
||||
val rollRelease: Float,
|
||||
val rollHalflife: Float,
|
||||
val hoverLookYawLimit: Float,
|
||||
val turnRollLimit: Float,
|
||||
val maxRoll: Float,
|
||||
val rollSettleAngle: Float,
|
||||
val rollSettleDelay: Float,
|
||||
val handRollShare: Float
|
||||
)
|
||||
|
||||
/** Bird's camera roll smoothing, kept in the camera context between frames. */
|
||||
class BirdCameraRoll {
|
||||
var engaged = false
|
||||
// World space up the camera had last frame, null until the first frame
|
||||
var cameraUp: Vector3f? = null
|
||||
// Degrees left between the camera and where it's heading, plus how fast that's closing
|
||||
val spring = FloatSpring()
|
||||
}
|
||||
+938
@@ -0,0 +1,938 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.riding.behaviour.types.air
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.CobblemonRideSettings
|
||||
import com.cobblemon.mod.common.OrientationControllable
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RideSteerInput
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.Side
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
|
||||
import com.cobblemon.mod.common.api.riding.posing.PoseOption
|
||||
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
|
||||
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
import com.cobblemon.mod.common.api.riding.stats.RidingStat
|
||||
import com.cobblemon.mod.common.api.riding.util.DistanceFieldLayer
|
||||
import com.cobblemon.mod.common.api.riding.util.DistanceFieldTuning
|
||||
import com.cobblemon.mod.common.api.riding.util.PrimitiveShape
|
||||
import com.cobblemon.mod.common.api.riding.util.RideFrame
|
||||
import com.cobblemon.mod.common.api.riding.util.collectPrimitivesFromBlocks
|
||||
import com.cobblemon.mod.common.api.riding.util.estimateGradient
|
||||
import com.cobblemon.mod.common.api.riding.util.sampleField
|
||||
import com.cobblemon.mod.common.api.riding.util.visualizeDistanceField
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.readNullableExpression
|
||||
import com.cobblemon.mod.common.util.readRidingStats
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.resolveDouble
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.writeNullableExpression
|
||||
import com.cobblemon.mod.common.util.writeRidingStats
|
||||
import net.minecraft.client.Minecraft
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sign
|
||||
import kotlin.math.sqrt
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.core.particles.ParticleTypes
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.util.SmoothDouble
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.AABB
|
||||
import net.minecraft.world.phys.Vec2
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import org.joml.Matrix3f
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
|
||||
class BoardBehaviour : RidingBehaviour<BoardSettings, BoardState> {
|
||||
companion object {
|
||||
val KEY = cobblemonResource("air/board")
|
||||
}
|
||||
|
||||
override val key = KEY
|
||||
val globalBoard: BoardSettings
|
||||
get() = CobblemonRideSettings.board
|
||||
|
||||
override fun getRidingStyle(settings: BoardSettings, state: BoardState): RidingStyle {
|
||||
return RidingStyle.LAND
|
||||
}
|
||||
|
||||
val poseProvider = PoseProvider<BoardSettings, BoardState>(PoseType.HOVER)
|
||||
.with(PoseOption(PoseType.FLY) { _, state, vehicle ->
|
||||
return@PoseOption state.walking.get()
|
||||
})
|
||||
|
||||
override fun isActive(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
|
||||
// Need to check other fluids
|
||||
if (vehicle.isInWater || vehicle.isUnderWater) {
|
||||
return@any false
|
||||
}
|
||||
// This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
|
||||
if (it.y.toDouble() == (vehicle.position().y)) {
|
||||
val blockState = vehicle.level().getBlockState(it.below())
|
||||
return@any !blockState.isAir && blockState.fluidState.isEmpty
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun pose(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): PoseType {
|
||||
return this.poseProvider.select(settings, state, vehicle)
|
||||
}
|
||||
|
||||
override fun speed(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Float {
|
||||
return state.rideVelocity.get().length().toFloat()
|
||||
}
|
||||
|
||||
override fun tick(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
) {
|
||||
if (vehicle.level().isClientSide) {
|
||||
handleSprinting(state, driver)
|
||||
inAirCheck(state, vehicle)
|
||||
tickStamina(settings, state, vehicle)
|
||||
state.walking.set(state.rideVelocity.get().horizontalDistance() > 0.1 || driver.zza != 0.0f)
|
||||
//surfDistCheck(state, vehicle)
|
||||
}
|
||||
|
||||
//TODO: should be a config somewhere
|
||||
val debugVisualizeField = true;
|
||||
|
||||
if (false && debugVisualizeField && !vehicle.level().isClientSide && vehicle.tickCount % 5 == 0) {
|
||||
visualizeDistanceField(
|
||||
referenceEntity = vehicle,
|
||||
center = vehicle.position().add(0.0, (vehicle.bbHeight * 0.5f).toDouble(), 0.0),
|
||||
layers = listOf(
|
||||
DistanceFieldLayer(isoValue = 2.5f, color = Vector3f(1f, 0.2f, 0.2f))
|
||||
),
|
||||
shape = PrimitiveShape.ELLIPSOID,
|
||||
horizontalRadius = 4.0,
|
||||
verticalRange = 4.0,
|
||||
sampleSpacing = 0.3,
|
||||
marchStep = 0.15,
|
||||
tuning = DistanceFieldTuning(smoothingRange = 0.25f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check how far the vehicle is from the "surface"
|
||||
*/
|
||||
fun surfDistCheck(
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
) {
|
||||
val center = vehicle.position()
|
||||
val horizontalRadius = 4.0;
|
||||
val verticalRange = 4.0;
|
||||
val level = vehicle.level()
|
||||
|
||||
//if (level !is ServerLevel) return
|
||||
|
||||
// Take a cubic area around the vehicle and gather SDF primitives from it.
|
||||
val searchBox = AABB(
|
||||
center.x - horizontalRadius, center.y - verticalRange, center.z - horizontalRadius,
|
||||
center.x + horizontalRadius, center.y + verticalRange, center.z + horizontalRadius
|
||||
)
|
||||
val primitives = collectPrimitivesFromBlocks(level,
|
||||
searchBox,
|
||||
PrimitiveShape.ELLIPSOID,
|
||||
includeFluids = true
|
||||
)
|
||||
val sp = center.toVector3f() // Sample Point
|
||||
val tuning = DistanceFieldTuning(smoothingRange = 0.25f)
|
||||
val sd = sampleField(primitives, sp.x, sp.y, sp.z, tuning)
|
||||
state.surfDist.set(sd)
|
||||
}
|
||||
|
||||
fun inAirCheck(
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
) {
|
||||
// Check both vertical movement and if there are blocks below.
|
||||
val posBelow = vehicle.blockPosition().below()
|
||||
val blockStateBelow = vehicle.level().getBlockState(posBelow)
|
||||
val isAirOrLiquid = blockStateBelow.isAir || !blockStateBelow.fluidState.isEmpty
|
||||
|
||||
val canSupportEntity = blockStateBelow.isFaceSturdy(vehicle.level(), posBelow, Direction.UP)
|
||||
val standingOnSolid = canSupportEntity && !isAirOrLiquid
|
||||
|
||||
// inAir if not on the ground
|
||||
val inAir = !(vehicle.deltaMovement.y == 0.0 || standingOnSolid)
|
||||
state.inAir.set(true)
|
||||
}
|
||||
|
||||
fun handleSprinting(
|
||||
state: BoardState,
|
||||
driver: Player
|
||||
) {
|
||||
val sprintTimerTickLength = 7 // Emulate vanillas tick timer length
|
||||
var hasDoubleTapped = false
|
||||
|
||||
// If you are not sprinting and you just pressed down and theres no
|
||||
if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && !state.sprinting.get() && state.sprintTickTimer.get() == 0) {
|
||||
state.sprintTickTimer.set(sprintTimerTickLength)
|
||||
} else if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && state.sprintTickTimer.get() != 0) { // If has double tapped
|
||||
hasDoubleTapped = true
|
||||
}else if(!state.sprinting.get() && state.sprintTickTimer.get() > 0) {
|
||||
state.sprintTickTimer.set(state.sprintTickTimer.get() - 1)
|
||||
} else {
|
||||
state.sprintTickTimer.set(0)
|
||||
}
|
||||
|
||||
val tryingToSprint = (Minecraft.getInstance().options.keySprint.isDown() && Minecraft.getInstance().options.keyUp.isDown()) || hasDoubleTapped
|
||||
|
||||
if (state.stamina.get() <= 0.0f || !Minecraft.getInstance().options.keyUp.isDown()) {
|
||||
// If stamina runs out or the player is not holding forward then stop sprinting
|
||||
state.sprinting.set(false)
|
||||
if (state.stamina.get() <= 0.0f) {
|
||||
state.sprintToggleable.set(false)
|
||||
}
|
||||
} else if (!state.sprinting.get() && !state.sprintToggleable.get() && state.stamina.get() > 0.33f) {
|
||||
// If you are not sprinting and sprint is not toggleable and you're not trying to sprint and stamina is above 0.3 then enable the toggle
|
||||
state.sprintToggleable.set(true)
|
||||
} else if (tryingToSprint && state.sprintToggleable.get()) {
|
||||
// If you are trying to sprint and the toggle allows it then start sprinting and disable toggle
|
||||
state.sprinting.set(true)
|
||||
}
|
||||
|
||||
// Take not of whether forward was held last tick
|
||||
state.forwardHeldLastTick.set(Minecraft.getInstance().options.keyUp.isDown())
|
||||
}
|
||||
|
||||
fun tickStamina(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
) {
|
||||
val stam = state.stamina.get()
|
||||
|
||||
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalBoard.infiniteStamina!!)) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalBoard.staminaExpr!!)).toFloat() / 20.0f
|
||||
|
||||
val newStam = if (state.sprinting.get()) {
|
||||
max(0.0f,stam - stamDrainRate)
|
||||
} else {
|
||||
min(1.0f,stam + stamDrainRate * 4)
|
||||
}
|
||||
|
||||
state.stamina.set(newStam.coerceIn(0F, 1F))
|
||||
}
|
||||
|
||||
override fun updatePassengerRotation(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: LivingEntity
|
||||
) { return }
|
||||
|
||||
override fun clampPassengerRotation(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: LivingEntity
|
||||
) { }
|
||||
|
||||
|
||||
override fun rotation(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: LivingEntity
|
||||
): Vec2 {
|
||||
val turnAmount = calcRotAmount(settings, state, vehicle, driver)
|
||||
return Vec2(vehicle.xRot, vehicle.yRot + turnAmount )
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculates the rotation amount given the difference between
|
||||
* the current player y rot and entity y rot. This gives the
|
||||
* affect of influencing a rides rotation through the mouse
|
||||
* without it being instant.
|
||||
*/
|
||||
fun calcRotAmount(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: LivingEntity
|
||||
): Float {
|
||||
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBoard.speedExpr!!)
|
||||
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalBoard.handlingExpr!!)
|
||||
val walkHandlingBoost = 5
|
||||
val maxYawDiff = vehicle.runtime.resolveFloat(settings.lookYawLimit ?: globalBoard.lookYawLimit!!)
|
||||
|
||||
// Normalize the current rotation diff
|
||||
val rotDiff = Mth.wrapDegrees(driver.yRot - vehicle.yRot).coerceIn(-maxYawDiff,maxYawDiff)
|
||||
val rotDiffNorm = rotDiff / maxYawDiff//(rotDiff / maxYawDiff).coerceIn(-0.2f, 0.2f)
|
||||
|
||||
// Take the square root so that the ride levels out quicker when at lower differences between entity
|
||||
// y and driver y
|
||||
// This influences the speed of the turn based on how far in one direction you're looking
|
||||
val minRotMod = 0.4f // Min speed that the rotation modulation converges towards
|
||||
val rotDiffMod = ((sqrt(abs(rotDiffNorm)) * (1.0f - minRotMod)) + minRotMod) * rotDiffNorm.sign
|
||||
|
||||
// Turn rate should be quick when walking and slower when sprinting.
|
||||
// Smoothly move between these based on speed
|
||||
val walkSpeed = getWalkSpeed(vehicle)
|
||||
val w = max(walkSpeed, vehicle.deltaMovement.horizontalDistance())
|
||||
val invRelSpeed = (RidingBehaviour.scaleToRange(w, walkSpeed, topSpeed ) - 1.0f) * -1.0f
|
||||
val turnRate = ((handling.toFloat() / 20.0f) * max(walkHandlingBoost * sqrt(abs(invRelSpeed)), 1.0)).toFloat()
|
||||
|
||||
// Ensure you only ever rotate as much difference as there is between the angles.
|
||||
val turnSpeed = turnRate * rotDiffMod
|
||||
val rotAmount = turnSpeed.coerceIn(-abs(rotDiff), abs(rotDiff))
|
||||
|
||||
return rotAmount
|
||||
}
|
||||
|
||||
private fun spawnGradientDebugParticles(vehicle: PokemonEntity, normal: Vec3) {
|
||||
val level = vehicle.level()
|
||||
val headPos = vehicle.position().add(0.0, vehicle.bbHeight.toDouble(), 0.0)
|
||||
|
||||
val steps = 20
|
||||
val length = 2.0
|
||||
for (i in 0..steps) {
|
||||
val t = i.toDouble() / steps
|
||||
val point = headPos.add(normal.scale(length * t))
|
||||
level.addParticle(ParticleTypes.END_ROD, point.x, point.y, point.z, 0.0, 0.0, 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This ride works along its yaw so its velocity gets turned into world space here
|
||||
override fun velocity(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
|
||||
}
|
||||
|
||||
private fun localVelocity(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
|
||||
return state.rideVelocity.get()
|
||||
}
|
||||
|
||||
private fun calculateRideSpaceVel(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Vec3 {
|
||||
val canSprint = vehicle.runtime.resolveBoolean(settings.canSprint ?: globalBoard.canSprint!!)
|
||||
val canJump = vehicle.runtime.resolveBoolean(settings.canJump ?: globalBoard.canJump!!)
|
||||
val jumpForce = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalBoard.jumpExpr!!) * 0.75
|
||||
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBoard.speedExpr!!)
|
||||
val walkSpeed = getWalkSpeed(vehicle)
|
||||
val topSpeed = if(canSprint && state.sprinting.get()) rideTopSpeed else walkSpeed
|
||||
val accelTime = if(state.sprinting.get()) (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalBoard.accelerationExpr!!) * 20.0) else 0.5 * 20.0
|
||||
val accel = topSpeed / accelTime
|
||||
var activeInput = false
|
||||
|
||||
/******************************************************
|
||||
* Gather the previous velocity and check for horizontal
|
||||
* collisions
|
||||
*****************************************************/
|
||||
var newVelocity = state.rideVelocity.get() //.normalize().scale(vehicle.deltaMovement.horizontalDistance())
|
||||
|
||||
/******************************************************
|
||||
* Gather the previous velocity and check for horizontal
|
||||
* collisions
|
||||
*****************************************************/
|
||||
if (vehicle.horizontalCollision) {
|
||||
var horizontalVec = Vec3(newVelocity.x, 0.0, newVelocity.z)
|
||||
horizontalVec = horizontalVec.normalize().scale(min(horizontalVec.length(), 0.3))
|
||||
newVelocity = Vec3(horizontalVec.x, newVelocity.y, horizontalVec.z)
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
* Speed up and slow down based on input
|
||||
*****************************************************/
|
||||
if (driver.zza != 0.0f) {
|
||||
|
||||
// If on a tight turn then do not speed up past half of top speed in order to turn quicker
|
||||
// Also determine how fast to be slowing down based on how far turned you are
|
||||
val lookYawLimit = vehicle.runtime.resolveFloat(settings.lookYawLimit ?: globalBoard.lookYawLimit!!)
|
||||
val percOfMaxTurnSpeed = abs(Mth.wrapDegrees(driver.yRot - vehicle.yRot) / lookYawLimit) * 100.0f
|
||||
val turnPercThresh = 0.0f
|
||||
val s = min(((percOfMaxTurnSpeed - turnPercThresh) / (100.0f - turnPercThresh)).pow(1),1.0f)
|
||||
val effectiveTopSpeed = if (percOfMaxTurnSpeed > turnPercThresh) topSpeed / max(2.0f*s,1.0f) else topSpeed
|
||||
val turningSlowDown = s * 0.1
|
||||
|
||||
//make sure it can't exceed top speed
|
||||
val forwardInput = when {
|
||||
driver.zza > 0 && newVelocity.z > effectiveTopSpeed -> 0.0
|
||||
driver.zza < 0 && newVelocity.z < (-effectiveTopSpeed / 3.0) -> 0.0
|
||||
else -> driver.zza.sign
|
||||
}
|
||||
|
||||
// Add extra friction if trying to slow down to turn faster
|
||||
if (newVelocity.z > effectiveTopSpeed) {
|
||||
newVelocity = newVelocity.subtract(0.0, 0.0, min(turningSlowDown * newVelocity.z.sign, newVelocity.z))
|
||||
}
|
||||
|
||||
newVelocity = Vec3(
|
||||
newVelocity.x,
|
||||
newVelocity.y,
|
||||
(newVelocity.z + (accel * forwardInput.toDouble())))
|
||||
|
||||
activeInput = true
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
* Gravity logic
|
||||
*****************************************************/
|
||||
// if(state.surfDist.get() > 1.6f) {
|
||||
// val gravity = (9.8 / ( 20.0)) * 0.2 * 0.6
|
||||
// val terminalVel = 2.0
|
||||
// newVelocity = Vec3(newVelocity.x, max(newVelocity.y - gravity, -terminalVel), newVelocity.z)
|
||||
// } else if(state.surfDist.get() > 1.4f && state.surfDist.get() <= 1.6f) {
|
||||
// newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
|
||||
// } else if(state.surfDist.get() <= 1.4f) {
|
||||
// val gravity = -(9.8 / ( 20.0)) * 0.2 * 0.6
|
||||
// val terminalVel = -2.0
|
||||
// newVelocity = Vec3(newVelocity.x, min(newVelocity.y - gravity, -terminalVel), newVelocity.z)
|
||||
// }
|
||||
|
||||
/******************************************************
|
||||
* Hover logic - non-predictive: correct against current position
|
||||
*****************************************************/
|
||||
val correctionGain = 0.5
|
||||
val maxCorrectionSpeed = 0.3
|
||||
val center = vehicle.position()
|
||||
val horizontalRadius = 4.0;
|
||||
val verticalRange = 4.0;
|
||||
val level = vehicle.level()
|
||||
|
||||
// Take a cubic area around the vehicle and gather SDF primitives from it.
|
||||
val searchBox = AABB(
|
||||
center.x - horizontalRadius, center.y - verticalRange, center.z - horizontalRadius,
|
||||
center.x + horizontalRadius, center.y + vehicle.bbHeight, center.z + horizontalRadius
|
||||
)
|
||||
val primitives = collectPrimitivesFromBlocks(level,
|
||||
searchBox,
|
||||
PrimitiveShape.ELLIPSOID,
|
||||
includeFluids = true
|
||||
)
|
||||
val tuning = DistanceFieldTuning(smoothingRange = 0.25f)
|
||||
|
||||
val currentPos = vehicle.position()
|
||||
val currentDist = sampleField(primitives, currentPos.x.toFloat(), currentPos.y.toFloat(), currentPos.z.toFloat(), tuning)
|
||||
val normal = estimateGradient(primitives, currentPos, tuning)
|
||||
state.surfaceNormal.set(normal)
|
||||
//spawnGradientDebugParticles(vehicle, normal) // LETS SEE IT!
|
||||
|
||||
// Only correct when under the hover surface.
|
||||
// Otherwise allow gravity to do its thing
|
||||
// if (currentDist < targetHoverDist) {
|
||||
// val error = currentDist - targetHoverDist
|
||||
// val normalSpeed = newVelocity.dot(normal)
|
||||
// val tangentialVelocity = newVelocity.subtract(normal.scale(normalSpeed))
|
||||
//
|
||||
// val correctionSpeed = (-error * correctionGain).coerceIn(-maxCorrectionSpeed, maxCorrectionSpeed)
|
||||
// newVelocity = tangentialVelocity.add(normal.scale(correctionSpeed))
|
||||
// } else {
|
||||
// val hoverCoolnessLowGravFactor = 0.5
|
||||
// val gravity = (9.8 / ( 20.0)) * 0.2 * 0.6 * hoverCoolnessLowGravFactor
|
||||
// val terminalVel = 2.0 * hoverCoolnessLowGravFactor
|
||||
// newVelocity = Vec3(newVelocity.x, max(newVelocity.y - gravity, -terminalVel), newVelocity.z)
|
||||
// }
|
||||
|
||||
/******************************************************
|
||||
* Hover logic
|
||||
*****************************************************/
|
||||
val hoverActivationDist = 3.0f
|
||||
val targetHoverDist = 1.0f
|
||||
val hoverCoolnessLowGravFactor = 0.70
|
||||
val gravity = (9.8 / 20.0) * 0.2 * 0.6 * hoverCoolnessLowGravFactor
|
||||
val terminalVel = 2.0 * hoverCoolnessLowGravFactor
|
||||
// val repelStrength = 0.05 // tested and was alright
|
||||
// val hoverDamping = 0.15
|
||||
val repelStrength = 0.1
|
||||
val hoverDamping = 0.2
|
||||
|
||||
val surfDist = currentDist
|
||||
|
||||
if (surfDist > hoverActivationDist) {
|
||||
// Free fall - no surface influence yet, same as before.
|
||||
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - gravity, -terminalVel), newVelocity.z)
|
||||
} else {
|
||||
// Getting close - push back toward targetHoverDist, harder the nearer we are.
|
||||
val closeness = ((hoverActivationDist - surfDist) / (hoverActivationDist - targetHoverDist)).coerceIn(0f, 1.5f)
|
||||
val repelForce = closeness * repelStrength
|
||||
val dampingForce = -newVelocity.y * hoverDamping // resists overshoot/bounce
|
||||
|
||||
newVelocity = Vec3(newVelocity.x, newVelocity.y + repelForce + dampingForce - gravity * 0.3, newVelocity.z)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/******************************************************
|
||||
* Ground Friction
|
||||
// *****************************************************/
|
||||
// if( (newVelocity.horizontalDistance() > 0 && vehicle.onGround() && !activeInput) || newVelocity.horizontalDistance() > topSpeed) {
|
||||
// newVelocity = newVelocity.subtract(0.0, 0.0, min(0.03 * newVelocity.z.sign, newVelocity.z))
|
||||
// }
|
||||
|
||||
/******************************************************
|
||||
* Jump Logic
|
||||
*****************************************************/
|
||||
if (state.jumpTicks.get() > 0 || (state.jumpTicks.get() >= 0 && driver.jumping && vehicle.onGround() && canJump && driver.deltaMovement.y <= 0.1)) {
|
||||
// Spread out jumpforce so that variable height jumps are possible
|
||||
val jumpInputTicks = 6
|
||||
if (driver.jumping && (state.jumpTicks.get() >= 0 && state.jumpTicks.get() < jumpInputTicks)) {
|
||||
val appliedJumpForce = ((jumpForce*1.5) / jumpInputTicks) //* (1 - state.jumpTicks.get() / jumpInputTicks)
|
||||
newVelocity = Vec3(newVelocity.x, newVelocity.y + appliedJumpForce, newVelocity.z)
|
||||
state.jumpTicks.set(state.jumpTicks.get() + 1)
|
||||
} else {
|
||||
// Set delay before next jump is possible after hitting ground
|
||||
val tickJumpDelay = 3
|
||||
state.jumpTicks.set(-tickJumpDelay)
|
||||
}
|
||||
} else if (vehicle.onGround() && state.jumpTicks.get() < 0) {
|
||||
// Tick off the delay once reaching the ground
|
||||
state.jumpTicks.set(state.jumpTicks.get() + 1)
|
||||
}
|
||||
|
||||
// Zero out lateral velocity possibly picked up from a controller transition
|
||||
newVelocity = Vec3(0.0, newVelocity.y, newVelocity.z)
|
||||
|
||||
|
||||
return newVelocity
|
||||
}
|
||||
|
||||
private fun getWalkSpeed( vehicle: PokemonEntity ): Double {
|
||||
val walkspeed = vehicle.runtime.resolveDouble(vehicle.behaviour.moving.walk.walkSpeed)
|
||||
val movementSpeed = vehicle.attributes.getValue(Attributes.MOVEMENT_SPEED)
|
||||
val speedModifier = 1.2 * 0.35
|
||||
return walkspeed * movementSpeed * speedModifier
|
||||
|
||||
}
|
||||
|
||||
private fun rotateOverTime(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
) {
|
||||
if (vehicle !is OrientationControllable) return
|
||||
val controller = (vehicle as OrientationControllable).orientationController
|
||||
|
||||
// turning
|
||||
val turnMaxRate = 5.5
|
||||
val turnAccel = 1.0
|
||||
val turnFriction = 0.75
|
||||
|
||||
val targetYawRate = -driver.xxa.toDouble() * turnMaxRate
|
||||
val currentYawRate = state.yawAngularVelocity.get()
|
||||
val newYawRate = if (abs(targetYawRate) > 0.001) {
|
||||
moveToward(currentYawRate, targetYawRate, turnAccel * deltaTime * 20.0)
|
||||
} else {
|
||||
moveToward(currentYawRate, 0.0, turnFriction * deltaTime * 20.0)
|
||||
}
|
||||
state.yawAngularVelocity.set(newYawRate)
|
||||
controller.applyGlobalYaw((newYawRate * deltaTime * 20.0).toFloat())
|
||||
|
||||
// up-vector alignment framerate-independent
|
||||
val upAlignmentRatePerTick = 0.1 // fraction of the remaining gap closed per tick
|
||||
val frameBlend = 1.0 - (1.0 - upAlignmentRatePerTick).pow(deltaTime * 20.0)
|
||||
alignUpToSurfaceNormal(controller, state.surfaceNormal.get(), frameBlend.toFloat())
|
||||
|
||||
// lean into turns (cosmetic local roll)
|
||||
val maxLeanDeg = 35.0
|
||||
val leanPerYawRate = 4.0 * 3.0
|
||||
val leanRatePerTick = 0.25 * 1.5
|
||||
|
||||
val desiredLean = (newYawRate * leanPerYawRate).coerceIn(-maxLeanDeg, maxLeanDeg)
|
||||
val leanFrameBlend = 1.0 - (1.0 - leanRatePerTick).pow(deltaTime * 20.0)
|
||||
val leanError = controller.roll.angleDifference(desiredLean.toFloat())
|
||||
controller.rotateRoll((leanError * leanFrameBlend).toFloat())
|
||||
|
||||
}
|
||||
|
||||
private fun moveToward(current: Double, target: Double, maxDelta: Double): Double {
|
||||
val diff = target - current
|
||||
return if (abs(diff) <= maxDelta) target else current + maxDelta * diff.sign
|
||||
}
|
||||
|
||||
private fun alignUpToSurfaceNormal(controller: OrientationController, targetUp: Vec3, alignmentBlend: Float) {
|
||||
if (targetUp.lengthSqr() < 1e-6) return
|
||||
val targetUpVec = Vector3f(targetUp.x.toFloat(), targetUp.y.toFloat(), targetUp.z.toFloat()).normalize()
|
||||
|
||||
controller.updateOrientation { current ->
|
||||
val currentUp = current.transform(Vector3f(OrientationController.UP), Vector3f())
|
||||
if (currentUp.lengthSquared() < 1e-6f) return@updateOrientation current
|
||||
|
||||
// Full rotation that would exactly align currentUp to targetUp...
|
||||
val fullCorrection = Quaternionf().rotationTo(currentUp, targetUpVec)
|
||||
// ...but only take a partial step toward it, so alignment is smooth, not instant.
|
||||
val steppedCorrection = Quaternionf().slerp(fullCorrection, alignmentBlend)
|
||||
|
||||
// Pre-multiply (world-space correction), same pattern applyGlobalYaw uses.
|
||||
Matrix3f().set(steppedCorrection).mul(current, Matrix3f())
|
||||
}
|
||||
}
|
||||
|
||||
override fun steer(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: RideSteerInput
|
||||
) {
|
||||
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
|
||||
rotateOverTime(settings, state, vehicle, driver, input.deltaTime)
|
||||
}
|
||||
|
||||
private fun mouseRotation(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
mouseY: Double,
|
||||
mouseX: Double,
|
||||
mouseYSmoother: SmoothDouble,
|
||||
mouseXSmoother: SmoothDouble,
|
||||
sensitivity: Double,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
//Smooth out mouse input.
|
||||
val smoothingSpeed = 4
|
||||
val xInput = mouseXSmoother.getNewDeltaValue(mouseX * 0.1, deltaTime * smoothingSpeed);
|
||||
val yInput = mouseYSmoother.getNewDeltaValue(mouseY * 0.1, deltaTime * smoothingSpeed);
|
||||
|
||||
//yaw, pitch, roll
|
||||
return Vec3(0.0, yInput, xInput)
|
||||
}
|
||||
|
||||
override fun canJump(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun setRideBar(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Float {
|
||||
return (state.stamina.get() / 1.0f)
|
||||
}
|
||||
|
||||
override fun jumpForce(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
jumpStrength: Int
|
||||
): Vec3 {
|
||||
return Vec3.ZERO
|
||||
}
|
||||
|
||||
override fun gravity(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
regularGravity: Double
|
||||
): Double {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
override fun rideFovMultiplier(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Float {
|
||||
return if (state.sprinting.get()) 1.15f else 1.0f
|
||||
}
|
||||
|
||||
override fun inertia(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): Double {
|
||||
return 1.0
|
||||
}
|
||||
|
||||
override fun shouldRoll(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun turnOffOnGround(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun dismountOnShift(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun shouldRotatePokemonHead(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun shouldRotateRiderHead(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun maxUpStep(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): Float? {
|
||||
return if (state.inAir.get()) 0.0f else 1.2f
|
||||
}
|
||||
|
||||
override fun getRideSounds(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): RideSoundSettingsList {
|
||||
return settings.rideSounds
|
||||
}
|
||||
|
||||
override fun createDefaultState(settings: BoardSettings) = BoardState()
|
||||
|
||||
override fun asMoLangValue(
|
||||
settings: BoardSettings,
|
||||
state: BoardState,
|
||||
vehicle: PokemonEntity
|
||||
): ObjectValue<RidingBehaviour<BoardSettings, BoardState>> {
|
||||
val canJump = vehicle.runtime.resolveBoolean(settings.canJump ?: globalBoard.canJump!!)
|
||||
val value = super.asMoLangValue(settings, state, vehicle)
|
||||
value.functions.put("sprinting") { DoubleValue(state.sprinting.get()) }
|
||||
value.functions.put("walking") { DoubleValue(state.walking.get()) }
|
||||
value.functions.put("in_air") { DoubleValue(state.inAir.get() || (state.rideVelocity.get().y >= 0.0 && (vehicle.controllingPassenger as? Player)?.jumping == true && canJump) || (state.rideVelocity.get().y > 0.0)) }
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
class BoardSettings : RidingBehaviourSettings {
|
||||
override val key = BoardBehaviour.KEY
|
||||
override val stats = mutableMapOf<RidingStat, IntRange>()
|
||||
|
||||
var infiniteStamina: Expression? = null
|
||||
private set
|
||||
var canJump: Expression? = null
|
||||
private set
|
||||
|
||||
var canSprint: Expression? = null
|
||||
private set
|
||||
|
||||
var lookYawLimit: Expression? = null
|
||||
private set
|
||||
|
||||
var speedExpr: Expression? = null
|
||||
private set
|
||||
|
||||
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
|
||||
var accelerationExpr: Expression? = null
|
||||
private set
|
||||
|
||||
// Between 30 seconds and 10 seconds at the lowest when at full speed.
|
||||
var staminaExpr: Expression? = null
|
||||
private set
|
||||
|
||||
//Between a one block jump and a six block jump
|
||||
var jumpExpr: Expression? = null
|
||||
private set
|
||||
|
||||
var handlingExpr: Expression? = null
|
||||
private set
|
||||
|
||||
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeRidingStats(stats)
|
||||
rideSounds.encode(buffer)
|
||||
buffer.writeNullableExpression(infiniteStamina)
|
||||
buffer.writeNullableExpression(canJump)
|
||||
buffer.writeNullableExpression(canSprint)
|
||||
buffer.writeNullableExpression(lookYawLimit)
|
||||
buffer.writeNullableExpression(speedExpr)
|
||||
buffer.writeNullableExpression(accelerationExpr)
|
||||
buffer.writeNullableExpression(staminaExpr)
|
||||
buffer.writeNullableExpression(jumpExpr)
|
||||
buffer.writeNullableExpression(handlingExpr)
|
||||
buffer.writeNullableExpression(canJump)
|
||||
buffer.writeNullableExpression(canSprint)
|
||||
}
|
||||
|
||||
override fun decode(buffer: RegistryFriendlyByteBuf) {
|
||||
stats.putAll(buffer.readRidingStats())
|
||||
|
||||
|
||||
rideSounds = RideSoundSettingsList.decode(buffer)
|
||||
infiniteStamina = buffer.readNullableExpression()
|
||||
canJump = buffer.readNullableExpression()
|
||||
canSprint = buffer.readNullableExpression()
|
||||
lookYawLimit = buffer.readNullableExpression()
|
||||
speedExpr = buffer.readNullableExpression()
|
||||
accelerationExpr = buffer.readNullableExpression()
|
||||
staminaExpr = buffer.readNullableExpression()
|
||||
jumpExpr = buffer.readNullableExpression()
|
||||
handlingExpr = buffer.readNullableExpression()
|
||||
canJump = buffer.readNullableExpression()
|
||||
canSprint = buffer.readNullableExpression()
|
||||
}
|
||||
}
|
||||
|
||||
class BoardState : RidingBehaviourState() {
|
||||
var sprinting = ridingState(false, Side.CLIENT)
|
||||
var walking = ridingState(false, Side.BOTH)
|
||||
var sprintToggleable = ridingState(false, Side.CLIENT)
|
||||
var inAir = ridingState(false, Side.CLIENT)
|
||||
var jumpTicks = ridingState(0, Side.CLIENT)
|
||||
var sprintTickTimer = ridingState(0, Side.CLIENT)
|
||||
var forwardHeldLastTick = ridingState(false, Side.CLIENT)
|
||||
var surfDist = ridingState(Float.POSITIVE_INFINITY, Side.BOTH)
|
||||
var surfaceNormal = ridingState(Vec3(0.0, 1.0, 0.0), Side.CLIENT)
|
||||
var yawAngularVelocity = ridingState(0.0, Side.CLIENT)
|
||||
|
||||
override fun encode(buffer: FriendlyByteBuf) {
|
||||
super.encode(buffer)
|
||||
buffer.writeBoolean(sprinting.get())
|
||||
buffer.writeBoolean(walking.get())
|
||||
buffer.writeBoolean(sprintToggleable.get())
|
||||
buffer.writeBoolean(inAir.get())
|
||||
buffer.writeInt(jumpTicks.get())
|
||||
buffer.writeInt(sprintTickTimer.get())
|
||||
buffer.writeBoolean(forwardHeldLastTick.get())
|
||||
buffer.writeFloat(surfDist.get())
|
||||
buffer.writeVec3(surfaceNormal.get())
|
||||
buffer.writeDouble(yawAngularVelocity.get())
|
||||
}
|
||||
|
||||
override fun decode(buffer: FriendlyByteBuf) {
|
||||
super.decode(buffer)
|
||||
sprinting.set(buffer.readBoolean(), forced = true)
|
||||
walking.set(buffer.readBoolean(), forced = true)
|
||||
sprintToggleable.set(buffer.readBoolean(), forced = true)
|
||||
inAir.set(buffer.readBoolean(), forced = true)
|
||||
jumpTicks.set(buffer.readInt(), forced = true)
|
||||
sprintTickTimer.set(buffer.readInt(), forced = true)
|
||||
forwardHeldLastTick.set(buffer.readBoolean(), forced = true)
|
||||
surfDist.set(buffer.readFloat(), forced = true)
|
||||
surfaceNormal.set(buffer.readVec3(), forced = true)
|
||||
yawAngularVelocity.set(buffer.readDouble(), forced = true)
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
super.reset()
|
||||
sprinting.set(false, forced = true)
|
||||
walking.set(false, forced = true)
|
||||
sprintToggleable.set(false, forced = true)
|
||||
inAir.set(false, forced = true)
|
||||
jumpTicks.set(0, forced = true)
|
||||
sprintTickTimer.set(0, forced = true)
|
||||
forwardHeldLastTick.set(false, forced = true)
|
||||
surfDist.set(Float.POSITIVE_INFINITY, forced = true)
|
||||
surfaceNormal.set(Vec3(0.0, 1.0, 0.0), forced = true)
|
||||
yawAngularVelocity.set(0.0, forced = true)
|
||||
}
|
||||
|
||||
override fun copy() = BoardState().also {
|
||||
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
|
||||
it.stamina.set(this.stamina.get(), forced = true)
|
||||
it.sprinting.set(this.sprinting.get(), forced = true)
|
||||
it.walking.set(this.sprinting.get(), forced = true)
|
||||
it.sprintToggleable.set(this.sprintToggleable.get(), forced = true)
|
||||
it.inAir.set(this.inAir.get(), forced = true)
|
||||
it.jumpTicks.set(this.jumpTicks.get(), forced = true)
|
||||
it.sprintTickTimer.set(this.sprintTickTimer.get(), forced = true)
|
||||
it.forwardHeldLastTick.set(this.forwardHeldLastTick.get(), forced = true)
|
||||
it.surfDist.set(this.surfDist.get(), forced = true)
|
||||
it.surfaceNormal.set(this.surfaceNormal.get(), forced = true)
|
||||
it.yawAngularVelocity.set(this.yawAngularVelocity.get(), forced = true)
|
||||
}
|
||||
|
||||
override fun shouldSync(previous: RidingBehaviourState): Boolean {
|
||||
if (previous !is BoardState) return false
|
||||
if (previous.sprinting.get() != sprinting.get()) return true
|
||||
if (previous.walking.get() != walking.get()) return true
|
||||
if (previous.sprintToggleable.get() != sprintToggleable.get()) return true
|
||||
if (previous.inAir.get() != inAir.get()) return true
|
||||
if (previous.surfDist.get() != surfDist.get()) return true
|
||||
return super.shouldSync(previous)
|
||||
}
|
||||
}
|
||||
+12
-34
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonRideSettings
|
||||
import com.cobblemon.mod.common.OrientationControllable
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
|
||||
@@ -19,11 +18,11 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
|
||||
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
|
||||
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
import com.cobblemon.mod.common.api.riding.stats.RidingStat
|
||||
import com.cobblemon.mod.common.api.riding.util.RideFrame
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.util.SmoothDouble
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.Vec2
|
||||
@@ -67,7 +66,18 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
|
||||
return Vec2(driver.xRot, driver.yRot)
|
||||
}
|
||||
|
||||
// This ride works along its yaw so its velocity gets turned into world space here
|
||||
override fun velocity(
|
||||
settings: GliderSettings,
|
||||
state: RidingBehaviourState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
|
||||
}
|
||||
|
||||
private fun localVelocity(
|
||||
settings: GliderSettings,
|
||||
state: RidingBehaviourState,
|
||||
vehicle: PokemonEntity,
|
||||
@@ -80,34 +90,6 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
|
||||
return Vec3(xVector, yVector, zVector)
|
||||
}
|
||||
|
||||
override fun angRollVel(
|
||||
settings: GliderSettings,
|
||||
state: RidingBehaviourState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
return Vec3.ZERO
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
settings: GliderSettings,
|
||||
state: RidingBehaviourState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
mouseY: Double,
|
||||
mouseX: Double,
|
||||
mouseYSmoother: SmoothDouble,
|
||||
mouseXSmoother: SmoothDouble,
|
||||
sensitivity: Double,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
if (driver !is OrientationControllable) return Vec3.ZERO
|
||||
|
||||
//Might need to add the smoothing here for default.
|
||||
return Vec3(0.0, mouseY, mouseX)
|
||||
}
|
||||
|
||||
override fun canJump(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Boolean {
|
||||
return false
|
||||
}
|
||||
@@ -149,10 +131,6 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
|
||||
return 1.0f
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun inertia(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Double {
|
||||
return 0.5
|
||||
}
|
||||
|
||||
+26
-6
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonRideSettings
|
||||
import com.cobblemon.mod.common.OrientationControllable
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RideSteerInput
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
|
||||
@@ -20,6 +21,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
|
||||
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
|
||||
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
import com.cobblemon.mod.common.api.riding.stats.RidingStat
|
||||
import com.cobblemon.mod.common.api.riding.util.RideFrame
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
|
||||
@@ -91,12 +93,23 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
|
||||
return Vec2(driver.xRot * 0.5f, driver.yRot)
|
||||
}
|
||||
|
||||
// This ride works along its yaw so its velocity gets turned into world space here
|
||||
override fun velocity(
|
||||
settings: HelicopterSettings,
|
||||
state: RidingBehaviourState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
|
||||
}
|
||||
|
||||
private fun localVelocity(
|
||||
settings: HelicopterSettings,
|
||||
state: RidingBehaviourState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
val runtime = vehicle.runtime
|
||||
|
||||
@@ -120,7 +133,7 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
|
||||
return Vec3(xVel, yVel, zVel)
|
||||
}
|
||||
|
||||
override fun angRollVel(
|
||||
private fun rotationOverTime(
|
||||
settings: HelicopterSettings,
|
||||
state: RidingBehaviourState,
|
||||
vehicle: PokemonEntity,
|
||||
@@ -175,7 +188,18 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
|
||||
return Vec3(0.0, 0.0, 0.0)
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
override fun steer(
|
||||
settings: HelicopterSettings,
|
||||
state: RidingBehaviourState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: RideSteerInput
|
||||
) {
|
||||
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
|
||||
input.rotateSmoothed(rotationOverTime(settings, state, vehicle, driver, input.deltaTime))
|
||||
}
|
||||
|
||||
private fun mouseRotation(
|
||||
settings: HelicopterSettings,
|
||||
state: RidingBehaviourState,
|
||||
vehicle: PokemonEntity,
|
||||
@@ -241,10 +265,6 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
|
||||
return 1.0f
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun inertia(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Double {
|
||||
return 0.1
|
||||
}
|
||||
|
||||
+12
-34
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
|
||||
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
|
||||
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
import com.cobblemon.mod.common.api.riding.stats.RidingStat
|
||||
import com.cobblemon.mod.common.api.riding.util.RideFrame
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.*
|
||||
@@ -26,7 +27,6 @@ import net.minecraft.client.Minecraft
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.util.SmoothDouble
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.level.ClipContext
|
||||
@@ -190,12 +190,23 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
|
||||
return rotAmount
|
||||
}
|
||||
|
||||
// This ride works along its yaw so its velocity gets turned into world space here
|
||||
override fun velocity(
|
||||
settings: HoverSettings,
|
||||
state: HoverState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
|
||||
}
|
||||
|
||||
private fun localVelocity(
|
||||
settings: HoverSettings,
|
||||
state: HoverState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
val retVel = calculateRideSpaceVel(settings, state, vehicle, driver)
|
||||
state.rideVelocity.set(retVel)
|
||||
@@ -283,31 +294,6 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
|
||||
return newVelocity
|
||||
}
|
||||
|
||||
override fun angRollVel(
|
||||
settings: HoverSettings,
|
||||
state: HoverState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
return Vec3.ZERO
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
settings: HoverSettings,
|
||||
state: HoverState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
mouseY: Double,
|
||||
mouseX: Double,
|
||||
mouseYSmoother: SmoothDouble,
|
||||
mouseXSmoother: SmoothDouble,
|
||||
sensitivity: Double,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
return Vec3.ZERO
|
||||
}
|
||||
|
||||
override fun canJump(
|
||||
settings: HoverSettings,
|
||||
state: HoverState,
|
||||
@@ -355,14 +341,6 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
|
||||
return if (state.isBoosting.get()) 1.1f else 1.0f
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(
|
||||
settings: HoverSettings,
|
||||
state: HoverState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun inertia(
|
||||
settings: HoverSettings,
|
||||
state: HoverState,
|
||||
|
||||
+15
-8
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.CobblemonRideSettings
|
||||
import com.cobblemon.mod.common.OrientationControllable
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RideSteerInput
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
|
||||
@@ -28,7 +29,6 @@ import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.math.geometry.toRadians
|
||||
import com.cobblemon.mod.common.util.readNullableExpression
|
||||
import com.cobblemon.mod.common.util.readRidingStats
|
||||
import com.cobblemon.mod.common.util.resolveDouble
|
||||
@@ -176,7 +176,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
|
||||
(state.rideVelocity.get().y).toFloat(),
|
||||
state.rideVelocity.get().z.toFloat() * -1.0f // Flip the z axis to make this left handed? orr.. right handed? idk, flip it though
|
||||
)
|
||||
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d().yRot(vehicle.yRot.toRadians()) // Unrotate preemptively as this vector gets rotate later down the line in MC logic.
|
||||
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d()
|
||||
worldVelVec = Vec3(
|
||||
worldVelVec.x,
|
||||
worldVelVec.y * (1 - (state.noStamTickCnt.get() / penaltyDeccel).coerceIn(0.0f, 1.0f)), // Dampen the upwards movement depending on stamina depletion time
|
||||
@@ -266,7 +266,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
|
||||
|
||||
}
|
||||
|
||||
override fun angRollVel(
|
||||
private fun rotationOverTime(
|
||||
settings: JetSettings,
|
||||
state: JetState,
|
||||
vehicle: PokemonEntity,
|
||||
@@ -292,7 +292,18 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
|
||||
return Vec3(yawForce, 0.0, 0.0)
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
override fun steer(
|
||||
settings: JetSettings,
|
||||
state: JetState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: RideSteerInput
|
||||
) {
|
||||
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
|
||||
input.rotateSmoothed(rotationOverTime(settings, state, vehicle, driver, input.deltaTime))
|
||||
}
|
||||
|
||||
private fun mouseRotation(
|
||||
settings: JetSettings,
|
||||
state: JetState,
|
||||
vehicle: PokemonEntity,
|
||||
@@ -390,10 +401,6 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
|
||||
}
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(settings: JetSettings, state: JetState, vehicle: PokemonEntity): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun inertia(settings: JetSettings, state: JetState, vehicle: PokemonEntity): Double {
|
||||
return 1.0
|
||||
}
|
||||
|
||||
+22
-46
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
|
||||
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
|
||||
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
import com.cobblemon.mod.common.api.riding.stats.RidingStat
|
||||
import com.cobblemon.mod.common.api.riding.util.RideFrame
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.*
|
||||
@@ -27,7 +28,6 @@ import net.minecraft.client.Minecraft
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.util.SmoothDouble
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.Vec2
|
||||
@@ -75,15 +75,19 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Float {
|
||||
// Use this as a "tick" function and check to see if the driver is "boosting"
|
||||
if (vehicle.level().isClientSide) {
|
||||
handleBoosting(settings, state, vehicle, driver)
|
||||
tickStamina(settings, state, vehicle, driver)
|
||||
}
|
||||
|
||||
return state.rideVelocity.get().length().toFloat()
|
||||
}
|
||||
|
||||
override fun driverTick(
|
||||
settings: RocketSettings,
|
||||
state: RocketState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
) {
|
||||
handleBoosting(settings, state, vehicle, driver)
|
||||
tickStamina(settings, state, vehicle, driver)
|
||||
}
|
||||
|
||||
fun handleBoosting(
|
||||
settings: RocketSettings,
|
||||
state: RocketState,
|
||||
@@ -208,12 +212,23 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
|
||||
}
|
||||
|
||||
|
||||
// This ride works along its yaw so its velocity gets turned into world space here
|
||||
override fun velocity(
|
||||
settings: RocketSettings,
|
||||
state: RocketState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
|
||||
}
|
||||
|
||||
private fun localVelocity(
|
||||
settings: RocketSettings,
|
||||
state: RocketState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
val retVel = calculateRideSpaceVel(settings, state, vehicle, driver)
|
||||
state.rideVelocity.set(retVel)
|
||||
@@ -318,37 +333,6 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
|
||||
return newVelocity
|
||||
}
|
||||
|
||||
override fun angRollVel(
|
||||
settings: RocketSettings,
|
||||
state: RocketState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
return Vec3.ZERO
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
settings: RocketSettings,
|
||||
state: RocketState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
mouseY: Double,
|
||||
mouseX: Double,
|
||||
mouseYSmoother: SmoothDouble,
|
||||
mouseXSmoother: SmoothDouble,
|
||||
sensitivity: Double,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
//Smooth out mouse input.
|
||||
val smoothingSpeed = 4
|
||||
val xInput = mouseXSmoother.getNewDeltaValue(mouseX * 0.1, deltaTime * smoothingSpeed);
|
||||
val yInput = mouseYSmoother.getNewDeltaValue(mouseY * 0.1, deltaTime * smoothingSpeed);
|
||||
|
||||
//yaw, pitch, roll
|
||||
return Vec3(0.0, yInput, xInput)
|
||||
}
|
||||
|
||||
override fun canJump(
|
||||
settings: RocketSettings,
|
||||
state: RocketState,
|
||||
@@ -403,14 +387,6 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
|
||||
}
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(
|
||||
settings: RocketSettings,
|
||||
state: RocketState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun inertia(
|
||||
settings: RocketSettings,
|
||||
state: RocketState,
|
||||
|
||||
+81
-51
@@ -11,6 +11,10 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.composite
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.*
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies.CompositeRidingStrategies
|
||||
import com.cobblemon.mod.common.api.riding.camera.RideCameraContext
|
||||
import com.cobblemon.mod.common.api.riding.camera.RideLookInput
|
||||
import com.cobblemon.mod.common.api.riding.camera.RideLookTake
|
||||
import com.cobblemon.mod.common.api.riding.camera.RideLookSettings
|
||||
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
import com.cobblemon.mod.common.api.riding.stats.RidingStat
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
@@ -22,11 +26,11 @@ import com.cobblemon.mod.common.util.writeRidingStats
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.util.SmoothDouble
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.Vec2
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.joml.Quaternionf
|
||||
|
||||
class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
|
||||
companion object {
|
||||
@@ -170,16 +174,6 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
|
||||
}
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(
|
||||
settings: CompositeSettings,
|
||||
state: CompositeState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
|
||||
behaviour.useAngVelSmoothing(behaviourSettings, behaviourState, vehicle)
|
||||
}
|
||||
}
|
||||
|
||||
override fun rideFovMultiplier(
|
||||
settings: CompositeSettings,
|
||||
state: CompositeState,
|
||||
@@ -236,46 +230,6 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
|
||||
}
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
settings: CompositeSettings,
|
||||
state: CompositeState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
mouseY: Double,
|
||||
mouseX: Double,
|
||||
mouseYSmoother: SmoothDouble,
|
||||
mouseXSmoother: SmoothDouble,
|
||||
sensitivity: Double,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
|
||||
behaviour.rotationOnMouseXY(
|
||||
behaviourSettings,
|
||||
behaviourState,
|
||||
vehicle,
|
||||
driver,
|
||||
mouseY,
|
||||
mouseX,
|
||||
mouseYSmoother,
|
||||
mouseXSmoother,
|
||||
sensitivity,
|
||||
deltaTime
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun angRollVel(
|
||||
settings: CompositeSettings,
|
||||
state: CompositeState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
|
||||
behaviour.angRollVel(behaviourSettings, behaviourState, vehicle, driver, deltaTime)
|
||||
}
|
||||
}
|
||||
|
||||
override fun velocity(
|
||||
settings: CompositeSettings,
|
||||
state: CompositeState,
|
||||
@@ -288,6 +242,28 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
|
||||
}
|
||||
}
|
||||
|
||||
override fun steer(
|
||||
settings: CompositeSettings,
|
||||
state: CompositeState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: RideSteerInput
|
||||
) {
|
||||
chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
|
||||
behaviour.steer(behaviourSettings, behaviourState, vehicle, driver, input)
|
||||
}
|
||||
}
|
||||
|
||||
override fun velocityRunsTwicePerTick(
|
||||
settings: CompositeSettings,
|
||||
state: CompositeState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
|
||||
behaviour.velocityRunsTwicePerTick(behaviourSettings, behaviourState, vehicle)
|
||||
}
|
||||
}
|
||||
|
||||
override fun rotation(
|
||||
settings: CompositeSettings,
|
||||
state: CompositeState,
|
||||
@@ -310,6 +286,17 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
|
||||
}
|
||||
}
|
||||
|
||||
override fun driverTick(
|
||||
settings: CompositeSettings,
|
||||
state: CompositeState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
) {
|
||||
chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
|
||||
behaviour.driverTick(behaviourSettings, behaviourState, vehicle, driver)
|
||||
}
|
||||
}
|
||||
|
||||
override fun pose(
|
||||
settings: CompositeSettings,
|
||||
state: CompositeState,
|
||||
@@ -361,6 +348,49 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
|
||||
behaviour.damageOnCollision(behaviourSettings, behaviourState, vehicle, impactVec)
|
||||
}
|
||||
}
|
||||
|
||||
override fun cameraBase(
|
||||
settings: CompositeSettings,
|
||||
state: CompositeState,
|
||||
vehicle: PokemonEntity,
|
||||
context: RideCameraContext
|
||||
): Quaternionf {
|
||||
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
|
||||
behaviour.cameraBase(behaviourSettings, behaviourState, vehicle, context)
|
||||
}
|
||||
}
|
||||
|
||||
override fun firstPersonHandRoll(
|
||||
settings: CompositeSettings,
|
||||
state: CompositeState,
|
||||
vehicle: PokemonEntity,
|
||||
context: RideCameraContext
|
||||
): Float {
|
||||
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
|
||||
behaviour.firstPersonHandRoll(behaviourSettings, behaviourState, vehicle, context)
|
||||
}
|
||||
}
|
||||
|
||||
override fun lookSettings(
|
||||
settings: CompositeSettings,
|
||||
state: CompositeState,
|
||||
vehicle: PokemonEntity
|
||||
): RideLookSettings {
|
||||
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
|
||||
behaviour.lookSettings(behaviourSettings, behaviourState, vehicle)
|
||||
}
|
||||
}
|
||||
|
||||
override fun consumeLook(
|
||||
settings: CompositeSettings,
|
||||
state: CompositeState,
|
||||
vehicle: PokemonEntity,
|
||||
input: RideLookInput
|
||||
): RideLookTake {
|
||||
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
|
||||
behaviour.consumeLook(behaviourSettings, behaviourState, vehicle, input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class CompositeSettings : RidingBehaviourSettings {
|
||||
|
||||
+3
-1
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviours
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
|
||||
import com.cobblemon.mod.common.api.riding.util.RideFrame
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.world.entity.player.Player
|
||||
@@ -63,7 +64,8 @@ object RunStrategy : CompositeRidingStrategy<CompositeSettings> {
|
||||
if (state.activeBehaviour.get() == alternativeSettings.key) return false
|
||||
if (state.lastTransition.get() + 20 >= entity.level().gameTime) return false
|
||||
val alternativeBehaviour = RidingBehaviours.get(alternativeSettings.key)
|
||||
return driver.isSprinting && input.z > 0.5 && alternativeBehaviour.isActive(alternativeSettings, state.alternateBehaviourState, entity)
|
||||
// Ride velocity is world space so check how much of it is forward
|
||||
return driver.isSprinting && RideFrame.worldToYaw(input, entity.yRot).z > 0.5 && alternativeBehaviour.isActive(alternativeSettings, state.alternateBehaviourState, entity)
|
||||
}
|
||||
|
||||
}
|
||||
+797
@@ -0,0 +1,797 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.riding.behaviour.types.land
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonRideSettings
|
||||
import com.cobblemon.mod.common.OrientationControllable
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.UP
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.*
|
||||
import com.cobblemon.mod.common.api.riding.posing.PoseOption
|
||||
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
|
||||
import com.cobblemon.mod.common.api.riding.util.DistanceFieldLayer
|
||||
import com.cobblemon.mod.common.api.riding.util.FieldPrimitive
|
||||
import com.cobblemon.mod.common.api.riding.util.PrimitiveShape
|
||||
import com.cobblemon.mod.common.api.riding.util.collectPrimitivesFromBlocks
|
||||
import com.cobblemon.mod.common.api.riding.util.estimateGradient
|
||||
import com.cobblemon.mod.common.api.riding.util.sampleField
|
||||
import com.cobblemon.mod.common.api.riding.util.visualizeDistanceField
|
||||
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
import com.cobblemon.mod.common.api.riding.stats.RidingStat
|
||||
import com.cobblemon.mod.common.api.riding.util.DistanceFieldTuning
|
||||
import com.cobblemon.mod.common.api.riding.util.RideFrame
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import com.cobblemon.mod.common.util.math.geometry.toRadians
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.core.particles.ParticleTypes
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.util.SmoothDouble
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.AABB
|
||||
import net.minecraft.world.phys.Vec2
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import org.joml.Matrix3f
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
import kotlin.math.*
|
||||
|
||||
class ClimbBehaviour : RidingBehaviour<ClimbSettings, ClimbState> {
|
||||
companion object {
|
||||
val KEY = cobblemonResource("land/climb")
|
||||
}
|
||||
|
||||
override val key = KEY
|
||||
|
||||
override fun getRidingStyle(settings: ClimbSettings, state: ClimbState): RidingStyle {
|
||||
return RidingStyle.LAND
|
||||
}
|
||||
|
||||
val poseProvider = PoseProvider<ClimbSettings, ClimbState>(PoseType.STAND)
|
||||
.with(PoseOption(PoseType.WALK) { _, state, vehicle ->
|
||||
return@PoseOption abs(state.rideVelocity.get().horizontalDistance()) > 0.0
|
||||
})
|
||||
|
||||
override fun isActive(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
|
||||
// Need to check other fluids
|
||||
if (vehicle.isInWater || vehicle.isUnderWater) {
|
||||
return@any false
|
||||
}
|
||||
// This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
|
||||
if (it.y.toDouble() == (vehicle.position().y)) {
|
||||
val blockState = vehicle.level().getBlockState(it.below())
|
||||
return@any !blockState.isAir && blockState.fluidState.isEmpty
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun pose(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): PoseType {
|
||||
return this.poseProvider.select(settings, state, vehicle)
|
||||
}
|
||||
|
||||
override fun speed(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Float {
|
||||
return state.rideVelocity.get().length().toFloat()
|
||||
}
|
||||
|
||||
override fun tick(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
) {
|
||||
handleSprinting(state, driver)
|
||||
inAirCheck(state, vehicle)
|
||||
tickStamina(settings, state, vehicle)
|
||||
|
||||
// attachmentNormal: discrete, physics-facing - which face are we touching, used
|
||||
// for the sticking force (newDownForce) in velocity().
|
||||
// renderSurfaceNormal: smoothed field-gradient - purely cosmetic, used to slowly
|
||||
// rotate the model's up vector in rotateOverTime(). The two are intentionally
|
||||
// separate and should not be conflated.
|
||||
updateAttachment(state, vehicle)
|
||||
updateRenderSurfaceNormal(state, vehicle)
|
||||
|
||||
//TODO: should be a config somewhere
|
||||
val debugVisualizeField = true;
|
||||
|
||||
if (debugVisualizeField && !vehicle.level().isClientSide && vehicle.tickCount % 5 == 0) {
|
||||
visualizeDistanceField(
|
||||
referenceEntity = vehicle,
|
||||
center = vehicle.position().add(0.0, (vehicle.bbHeight * 0.5f).toDouble(), 0.0),
|
||||
layers = listOf(
|
||||
DistanceFieldLayer(isoValue = 0.0f, color = Vector3f(1f, 0.2f, 0.2f)),
|
||||
DistanceFieldLayer(isoValue = 1.0f, color = Vector3f(0.2f, 0.6f, 1f))
|
||||
),
|
||||
horizontalRadius = 2.0,
|
||||
verticalRange = 2.0,
|
||||
sampleSpacing = 0.35,
|
||||
marchStep = 0.15,
|
||||
tuning = DistanceFieldTuning(smoothingRange = 0.5f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the smoothed field-gradient normal used for rendering only (up-vector
|
||||
* alignment in rotateOverTime). Cached here at tick rate since collecting primitives
|
||||
* and sampling the field is real work - actually applying the rotation happens
|
||||
* separately at frame rate, same pattern as HorseBehaviour's surfDist/BoardBehaviour's
|
||||
* surfaceNormal.
|
||||
*/
|
||||
fun updateRenderSurfaceNormal(state: ClimbState, vehicle: PokemonEntity) {
|
||||
val level = vehicle.level()
|
||||
val center = vehicle.position().add(0.0, (vehicle.bbHeight * 0.5f).toDouble(), 0.0)
|
||||
val horizontalRadius = 1.5
|
||||
val verticalRange = 1.5
|
||||
|
||||
val searchBox = AABB(
|
||||
center.x - horizontalRadius, center.y - verticalRange, center.z - horizontalRadius,
|
||||
center.x + horizontalRadius, center.y + verticalRange, center.z + horizontalRadius
|
||||
)
|
||||
|
||||
val primitives: List<FieldPrimitive> = collectPrimitivesFromBlocks(level, searchBox, PrimitiveShape.ELLIPSOID)
|
||||
if (primitives.isEmpty()) return
|
||||
|
||||
val tuning = DistanceFieldTuning(smoothingRange = 0.5f)
|
||||
val gradient = estimateGradient(primitives, center, tuning)
|
||||
state.renderSurfaceNormal.set(gradient)
|
||||
}
|
||||
|
||||
/** Debug viz: draws a short line of particles from the vehicle out along the current attachment normal. */
|
||||
fun spawnAttachmentNormalParticles(vehicle: PokemonEntity, normal: Vec3) {
|
||||
val level = vehicle.level()
|
||||
if (!level.isClientSide) return
|
||||
if (normal.lengthSqr() < 1e-6) return
|
||||
|
||||
val origin = vehicle.position().add(0.0, (vehicle.bbHeight * 0.5f).toDouble(), 0.0)
|
||||
val length = 1.5
|
||||
val steps = 15
|
||||
for (i in 0..steps) {
|
||||
val t = i.toDouble() / steps
|
||||
val point = origin.add(normal.scale(length * t))
|
||||
level.addParticle(ParticleTypes.END_ROD, point.x, point.y, point.z, 0.0, 0.0, 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
fun inAirCheck(
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
) {
|
||||
// Check both vertical movement and if there are blocks below.
|
||||
val posBelow = vehicle.blockPosition().below()
|
||||
val blockStateBelow = vehicle.level().getBlockState(posBelow)
|
||||
val isAirOrLiquid = blockStateBelow.isAir || !blockStateBelow.fluidState.isEmpty
|
||||
|
||||
val canSupportEntity = blockStateBelow.isFaceSturdy(vehicle.level(), posBelow, Direction.UP)
|
||||
val standingOnSolid = canSupportEntity && !isAirOrLiquid
|
||||
|
||||
// inAir if not on the ground
|
||||
val inAir = !(vehicle.deltaMovement.y == 0.0 || standingOnSolid)
|
||||
state.inAir.set(inAir)
|
||||
}
|
||||
|
||||
fun handleSprinting(
|
||||
state: ClimbState,
|
||||
driver: Player
|
||||
) {
|
||||
if (state.sprinting.get()) {
|
||||
state.sprinting.set(driver.isSprinting && state.stamina.get() > 0.0f)
|
||||
} else {
|
||||
// Only allow sprinting to start if over x percentage of stamina left
|
||||
val stamSprintPerc = 25.0f
|
||||
state.sprinting.set(driver.isSprinting && state.stamina.get() > stamSprintPerc / 100.0f)
|
||||
}
|
||||
}
|
||||
|
||||
fun tickStamina(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
) {
|
||||
val stam = state.stamina.get()
|
||||
var newStam = stam
|
||||
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr)).toFloat() / 20.0f
|
||||
|
||||
if (state.sprinting.get()) {
|
||||
newStam = max(0.0f,stam - stamDrainRate)
|
||||
|
||||
} else {
|
||||
newStam = min(1.0f,stam + stamDrainRate * 4)
|
||||
}
|
||||
|
||||
state.stamina.set(newStam)
|
||||
}
|
||||
|
||||
override fun updatePassengerRotation(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: LivingEntity
|
||||
) { return }
|
||||
|
||||
override fun clampPassengerRotation(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: LivingEntity
|
||||
) { }
|
||||
|
||||
|
||||
override fun rotation(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: LivingEntity
|
||||
): Vec2 {
|
||||
return Vec2(vehicle.xRot, vehicle.yRot )
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculates the rotation amount given the difference between
|
||||
* the current player y rot and entity y rot. This gives the
|
||||
* affect of influencing a rides rotation through the mouse
|
||||
* without it being instant.
|
||||
*
|
||||
* THIS IS ADAPTED FOR 3D ORIENTATION
|
||||
*/
|
||||
fun calcRotAmount(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: LivingEntity
|
||||
): Float {
|
||||
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
|
||||
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
|
||||
val walkHandlingBoost = 5
|
||||
val maxYawDiff = vehicle.runtime.resolveFloat(settings.lookYawLimit)
|
||||
|
||||
// Normalize the current rotation diff
|
||||
val controller = (vehicle as? OrientationControllable)?.orientationController
|
||||
if (controller == null || controller.orientation == null) return 0.0f
|
||||
|
||||
val rotDiff = Mth.wrapDegrees(Mth.wrapDegrees(driver.yRot) - controller.yaw).coerceIn(-maxYawDiff,maxYawDiff)
|
||||
val rotDiffNorm = rotDiff / maxYawDiff
|
||||
|
||||
// Take the square root so that the ride levels out quicker when at lower differences between entity
|
||||
// y and driver y
|
||||
// This influences the speed of the turn based on how far in one direction you're looking
|
||||
val minRotMod = 0.4f // Min speed that the rotation modulation converges towards
|
||||
val rotDiffMod = ((sqrt(abs(rotDiffNorm)) * (1.0f - minRotMod)) + minRotMod) * rotDiffNorm.sign
|
||||
|
||||
// Turn rate should be quick when walking and slower when sprinting.
|
||||
// Smoothly move between these based on speed
|
||||
val walkSpeed = getWalkSpeed(vehicle)
|
||||
val w = max(walkSpeed, vehicle.deltaMovement.horizontalDistance())
|
||||
val invRelSpeed = (RidingBehaviour.scaleToRange(w, walkSpeed, topSpeed ) - 1.0f) * -1.0f
|
||||
val turnRate = ((handling.toFloat() / 20.0f) * max(walkHandlingBoost * invRelSpeed, 1.0)).toFloat()
|
||||
|
||||
// Ensure you only ever rotate as much difference as there is between the angles.
|
||||
val turnSpeed = turnRate * rotDiffMod
|
||||
val rotAmount = turnSpeed.coerceIn(-abs(rotDiff), abs(rotDiff))
|
||||
|
||||
return rotAmount
|
||||
}
|
||||
|
||||
|
||||
// This ride works along its yaw so its velocity gets turned into world space here
|
||||
override fun velocity(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
|
||||
}
|
||||
|
||||
private fun localVelocity(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
|
||||
return state.rideVelocity.get()
|
||||
}
|
||||
|
||||
private fun calculateRideSpaceVel(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Vec3 {
|
||||
val globalHorse: HorseSettings = CobblemonRideSettings.horse
|
||||
val canSprint = vehicle.runtime.resolveBoolean(settings.canSprint ?: globalHorse.canSprint!!)
|
||||
val canJump = vehicle.runtime.resolveBoolean(settings.canJump ?: globalHorse.canJump!!)
|
||||
val jumpForce = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalHorse.jumpExpr!!) * 0.75
|
||||
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalHorse.speedExpr!!)
|
||||
val walkSpeed = getWalkSpeed(vehicle)
|
||||
val topSpeed = if(canSprint && state.sprinting.get()) rideTopSpeed else walkSpeed
|
||||
val accelTime = if(state.sprinting.get()) (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalHorse.accelerationExpr!!) * 20.0) else 0.5 * 20.0
|
||||
val accel = topSpeed / accelTime
|
||||
var activeInput = false
|
||||
|
||||
/******************************************************
|
||||
* Gather the previous velocity and check for horizontal
|
||||
* collisions
|
||||
*****************************************************/
|
||||
var newVelocity = state.rideVelocity.get() //.normalize().scale(vehicle.deltaMovement.horizontalDistance())
|
||||
|
||||
/******************************************************
|
||||
* Gather the previous velocity and check for horizontal
|
||||
* collisions
|
||||
*****************************************************/
|
||||
if (vehicle.horizontalCollision) {
|
||||
var horizontalVec = Vec3(newVelocity.x, 0.0, newVelocity.z)
|
||||
horizontalVec = horizontalVec.normalize().scale(min(horizontalVec.length(), 0.3))
|
||||
newVelocity = Vec3(horizontalVec.x, newVelocity.y, horizontalVec.z)
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
* Speed up and slow down based on input
|
||||
*****************************************************/
|
||||
if (driver.zza != 0.0f) {
|
||||
|
||||
// If on a tight turn then do not speed up past half of top speed in order to turn quicker
|
||||
// Also determine how fast to be slowing down based on how far turned you are
|
||||
val lookYawLimit = vehicle.runtime.resolveFloat(settings.lookYawLimit ?: globalHorse.lookYawLimit!!)
|
||||
val percOfMaxTurnSpeed = abs(Mth.wrapDegrees(driver.yRot - vehicle.yRot) / lookYawLimit) * 100.0f
|
||||
val turnPercThresh = 0.0f
|
||||
val s = min(((percOfMaxTurnSpeed - turnPercThresh) / (100.0f - turnPercThresh)).pow(1),1.0f)
|
||||
val effectiveTopSpeed = if (percOfMaxTurnSpeed > turnPercThresh) topSpeed / max(2.0f*s,1.0f) else topSpeed
|
||||
val turningSlowDown = s * 0.1
|
||||
|
||||
//make sure it can't exceed top speed
|
||||
val forwardInput = when {
|
||||
driver.zza > 0 && newVelocity.z > effectiveTopSpeed -> 0.0
|
||||
driver.zza < 0 && newVelocity.z < (-effectiveTopSpeed / 3.0) -> 0.0
|
||||
else -> driver.zza.sign
|
||||
}
|
||||
|
||||
// Add extra friction if trying to slow down to turn faster
|
||||
if (newVelocity.z > effectiveTopSpeed) {
|
||||
newVelocity = newVelocity.subtract(0.0, 0.0, min(turningSlowDown * newVelocity.z.sign, newVelocity.z))
|
||||
}
|
||||
|
||||
newVelocity = Vec3(
|
||||
newVelocity.x,
|
||||
newVelocity.y,
|
||||
(newVelocity.z + (accel * forwardInput.toDouble())))
|
||||
|
||||
activeInput = true
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
* Gravity logic
|
||||
*****************************************************/
|
||||
val isOnTopSurface = state.attachmentNormal.get() == Vec3(0.0, 1.0, 0.0)
|
||||
if (isOnTopSurface) {
|
||||
if (!vehicle.onGround() && state.jumpTicks.get() <= 0) {
|
||||
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.6
|
||||
val terminalVel = 2.0
|
||||
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - gravity, -terminalVel), newVelocity.z)
|
||||
} else if(vehicle.onGround()) {
|
||||
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
|
||||
}
|
||||
} else {
|
||||
// Wall/ceiling - small constant pull to stay stuck, not real gravity.
|
||||
val stickForce = 0.08
|
||||
newVelocity = Vec3(newVelocity.x, newVelocity.y - stickForce, newVelocity.z)
|
||||
}
|
||||
|
||||
|
||||
/******************************************************
|
||||
* Ground Friction
|
||||
*****************************************************/
|
||||
if( (newVelocity.horizontalDistance() > 0 && vehicle.onGround() && !activeInput) || newVelocity.horizontalDistance() > topSpeed) {
|
||||
newVelocity = newVelocity.subtract(0.0, 0.0, min(0.03 * newVelocity.z.sign, newVelocity.z))
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
* Jump Logic
|
||||
*****************************************************/
|
||||
if (state.jumpTicks.get() > 0 || (state.jumpTicks.get() >= 0 && driver.jumping && vehicle.onGround() && canJump && driver.deltaMovement.y <= 0.1)) {
|
||||
// Spread out jumpforce so that variable height jumps are possible
|
||||
val jumpInputTicks = 6
|
||||
if (driver.jumping && (state.jumpTicks.get() >= 0 && state.jumpTicks.get() < jumpInputTicks)) {
|
||||
val appliedJumpForce = ((jumpForce*1.5) / jumpInputTicks) //* (1 - state.jumpTicks.get() / jumpInputTicks)
|
||||
newVelocity = Vec3(newVelocity.x, newVelocity.y + appliedJumpForce, newVelocity.z)
|
||||
state.jumpTicks.set(state.jumpTicks.get() + 1)
|
||||
} else {
|
||||
// Set delay before next jump is possible after hitting ground
|
||||
val tickJumpDelay = 3
|
||||
state.jumpTicks.set(-tickJumpDelay)
|
||||
}
|
||||
} else if (vehicle.onGround() && state.jumpTicks.get() < 0) {
|
||||
// Tick off the delay once reaching the ground
|
||||
state.jumpTicks.set(state.jumpTicks.get() + 1)
|
||||
}
|
||||
|
||||
// Zero out lateral velocity possibly picked up from a controller transition
|
||||
newVelocity = Vec3(0.0, newVelocity.y, newVelocity.z)
|
||||
|
||||
|
||||
return newVelocity
|
||||
}
|
||||
|
||||
private fun getWalkSpeed( vehicle: PokemonEntity ): Double {
|
||||
val walkspeed = vehicle.runtime.resolveDouble(vehicle.behaviour.moving.walk.walkSpeed)
|
||||
val movementSpeed = vehicle.attributes.getValue(Attributes.MOVEMENT_SPEED)
|
||||
val speedModifier = 1.2 * 0.35
|
||||
return walkspeed * movementSpeed * speedModifier
|
||||
|
||||
}
|
||||
|
||||
private fun rotateOverTime(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
) {
|
||||
if (vehicle !is OrientationControllable) return
|
||||
val controller = vehicle.orientationController
|
||||
|
||||
// Smoothly rotate the model's up vector toward the (separately computed,
|
||||
// smoothed) field-gradient normal. Purely cosmetic - does not affect attachment
|
||||
// or movement, which are driven by state.attachmentNormal instead.
|
||||
val upAlignmentRatePerTick = 0.2
|
||||
val frameBlend = 1.0 - (1.0 - upAlignmentRatePerTick).pow(deltaTime * 20.0)
|
||||
alignUpToSurfaceNormal(controller, state.renderSurfaceNormal.get(), frameBlend.toFloat())
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates [controller]'s orientation so its up vector moves a fraction of the way
|
||||
* toward [targetUp] - partial per call, not instant, so alignment settles smoothly
|
||||
* rather than snapping every time the target changes.
|
||||
*/
|
||||
private fun alignUpToSurfaceNormal(controller: OrientationController, targetUp: Vec3, alignmentBlend: Float) {
|
||||
if (targetUp.lengthSqr() < 1e-6) return
|
||||
val targetUpVec = Vector3f(targetUp.x.toFloat(), targetUp.y.toFloat(), targetUp.z.toFloat()).normalize()
|
||||
|
||||
controller.updateOrientation { current ->
|
||||
val currentUp = current.transform(Vector3f(UP), Vector3f())
|
||||
if (currentUp.lengthSquared() < 1e-6f) return@updateOrientation current
|
||||
|
||||
// Full rotation that would exactly align currentUp to targetUp...
|
||||
val fullCorrection = Quaternionf().rotationTo(currentUp, targetUpVec)
|
||||
// ...but only take a partial step toward it, so alignment is smooth, not instant.
|
||||
val steppedCorrection = Quaternionf().slerp(fullCorrection, alignmentBlend)
|
||||
|
||||
Matrix3f().set(steppedCorrection).mul(current, Matrix3f())
|
||||
}
|
||||
}
|
||||
|
||||
override fun steer(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: RideSteerInput
|
||||
) {
|
||||
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
|
||||
rotateOverTime(settings, state, vehicle, driver, input.deltaTime)
|
||||
}
|
||||
|
||||
private fun mouseRotation(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
mouseY: Double,
|
||||
mouseX: Double,
|
||||
mouseYSmoother: SmoothDouble,
|
||||
mouseXSmoother: SmoothDouble,
|
||||
sensitivity: Double,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
if (vehicle !is OrientationControllable) return Vec3.ZERO
|
||||
val vehicleController = vehicle.orientationController
|
||||
|
||||
val smoothingSpeed = 4
|
||||
val xInput = mouseXSmoother.getNewDeltaValue(mouseX * 0.1, deltaTime * smoothingSpeed)
|
||||
val yInput = mouseYSmoother.getNewDeltaValue(mouseY * 0.1, deltaTime * smoothingSpeed)
|
||||
|
||||
//yaw, pitch, roll
|
||||
return Vec3(xInput, 0.0, 0.0)
|
||||
}
|
||||
|
||||
|
||||
override fun canJump(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun setRideBar(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Float {
|
||||
return (state.stamina.get() / 1.0f)
|
||||
}
|
||||
|
||||
override fun jumpForce(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
jumpStrength: Int
|
||||
): Vec3 {
|
||||
return Vec3.ZERO
|
||||
}
|
||||
|
||||
override fun gravity(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
regularGravity: Double
|
||||
): Double {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
override fun rideFovMultiplier(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Float {
|
||||
return 1.0f
|
||||
}
|
||||
|
||||
override fun inertia(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): Double {
|
||||
return 1.0
|
||||
}
|
||||
|
||||
override fun shouldRoll(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun turnOffOnGround(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun dismountOnShift(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun shouldRotatePokemonHead(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun shouldRotateRiderHead(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getRideSounds(
|
||||
settings: ClimbSettings,
|
||||
state: ClimbState,
|
||||
vehicle: PokemonEntity
|
||||
): RideSoundSettingsList {
|
||||
return settings.rideSounds
|
||||
}
|
||||
|
||||
override fun createDefaultState(settings: ClimbSettings) = ClimbState()
|
||||
}
|
||||
|
||||
class ClimbSettings : RidingBehaviourSettings {
|
||||
override val key = ClimbBehaviour.KEY
|
||||
override val stats = mutableMapOf<RidingStat, IntRange>()
|
||||
|
||||
var canJump = "true".asExpression()
|
||||
private set
|
||||
|
||||
var canSprint = "true".asExpression()
|
||||
private set
|
||||
|
||||
var lookYawLimit = "90.0f".asExpression()
|
||||
private set
|
||||
|
||||
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.4)".asExpression()
|
||||
private set
|
||||
|
||||
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
|
||||
var accelerationExpr: Expression =
|
||||
"q.get_ride_stats('ACCELERATION', 'LAND', 0.5, 2.0)".asExpression()
|
||||
private set
|
||||
|
||||
// Between 30 seconds and 10 seconds at the lowest when at full speed.
|
||||
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression()
|
||||
private set
|
||||
|
||||
//Between a one block jump and a six block jump
|
||||
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 1.0, 0.38)".asExpression()
|
||||
private set
|
||||
|
||||
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 180.0, 40.0)".asExpression()
|
||||
private set
|
||||
|
||||
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeResourceLocation(key)
|
||||
buffer.writeRidingStats(stats)
|
||||
rideSounds.encode(buffer)
|
||||
buffer.writeExpression(speedExpr)
|
||||
buffer.writeExpression(accelerationExpr)
|
||||
buffer.writeExpression(staminaExpr)
|
||||
buffer.writeExpression(jumpExpr)
|
||||
buffer.writeExpression(handlingExpr)
|
||||
buffer.writeExpression(canJump)
|
||||
buffer.writeExpression(canSprint)
|
||||
}
|
||||
|
||||
override fun decode(buffer: RegistryFriendlyByteBuf) {
|
||||
stats.putAll(buffer.readRidingStats())
|
||||
rideSounds = RideSoundSettingsList.decode(buffer)
|
||||
speedExpr = buffer.readExpression()
|
||||
accelerationExpr = buffer.readExpression()
|
||||
staminaExpr = buffer.readExpression()
|
||||
jumpExpr = buffer.readExpression()
|
||||
handlingExpr = buffer.readExpression()
|
||||
canJump = buffer.readExpression()
|
||||
canSprint = buffer.readExpression()
|
||||
}
|
||||
}
|
||||
|
||||
class ClimbState : RidingBehaviourState() {
|
||||
var sprinting = ridingState(false, Side.CLIENT)
|
||||
var inAir = ridingState(false, Side.CLIENT)
|
||||
var jumpTicks = ridingState(0, Side.CLIENT)
|
||||
|
||||
// Discrete, physics-facing: which of the 6 faces we're attached to right now.
|
||||
// Drives newDownForce in velocity(). Set by updateAttachment().
|
||||
var attachmentNormal = ridingState(Vec3(0.0, 1.0, 0.0), Side.CLIENT)
|
||||
var attachmentPoint = ridingState(Vec3(0.0, 1.0, 0.0), Side.CLIENT)
|
||||
|
||||
// Smoothed, rendering-facing: the field-gradient normal used only to slowly rotate
|
||||
// the model's visual up vector in rotateOverTime(). Deliberately separate from
|
||||
// attachmentNormal - see comments in tick(). Set by updateRenderSurfaceNormal().
|
||||
var renderSurfaceNormal = ridingState(Vec3(0.0, 1.0, 0.0), Side.CLIENT)
|
||||
|
||||
override fun encode(buffer: FriendlyByteBuf) {
|
||||
super.encode(buffer)
|
||||
buffer.writeBoolean(sprinting.get())
|
||||
buffer.writeBoolean(inAir.get())
|
||||
buffer.writeInt(jumpTicks.get())
|
||||
}
|
||||
|
||||
override fun decode(buffer: FriendlyByteBuf) {
|
||||
super.decode(buffer)
|
||||
sprinting.set(buffer.readBoolean(), forced = true)
|
||||
inAir.set(buffer.readBoolean(), forced = true)
|
||||
jumpTicks.set(buffer.readInt(), forced = true)
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
super.reset()
|
||||
sprinting.set(false, forced = true)
|
||||
inAir.set(false, forced = true)
|
||||
jumpTicks.set(0, forced = true)
|
||||
attachmentNormal.set(Vec3(0.0, 1.0, 0.0), forced = true)
|
||||
attachmentPoint.set(Vec3(0.0, 1.0, 0.0), forced = true)
|
||||
renderSurfaceNormal.set(Vec3(0.0, 1.0, 0.0), forced = true)
|
||||
}
|
||||
|
||||
override fun copy() = ClimbState().also {
|
||||
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
|
||||
it.stamina.set(this.stamina.get(), forced = true)
|
||||
it.sprinting.set(this.sprinting.get(), forced = true)
|
||||
it.inAir.set(this.inAir.get(), forced = true)
|
||||
it.jumpTicks.set(this.jumpTicks.get(), forced = true)
|
||||
it.attachmentNormal.set(this.attachmentNormal.get(), forced = true)
|
||||
it.attachmentPoint.set(this.attachmentNormal.get(), forced = true)
|
||||
it.renderSurfaceNormal.set(this.renderSurfaceNormal.get(), forced = true)
|
||||
}
|
||||
|
||||
override fun shouldSync(previous: RidingBehaviourState): Boolean {
|
||||
if (previous !is ClimbState) return false
|
||||
if (previous.sprinting.get() != sprinting.get()) return true
|
||||
if (previous.inAir.get() != inAir.get()) return true
|
||||
return super.shouldSync(previous)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateAttachment(state: ClimbState, vehicle: PokemonEntity) {
|
||||
val probeMargin = 0.15
|
||||
val currentFace = AttachmentFace.entries.firstOrNull { it.normal == state.attachmentNormal.get() }
|
||||
val face = determineAttachmentFace(vehicle, currentFace, probeMargin)
|
||||
if (face != null) {
|
||||
state.attachmentNormal.set(face.normal)
|
||||
}
|
||||
// face == null means nothing's currently in reach - leave attachmentNormal as
|
||||
// whatever it last was until the jump/detach state takes over.
|
||||
}
|
||||
|
||||
enum class AttachmentFace(val normal: Vec3) {
|
||||
UP(Vec3(0.0, 1.0, 0.0)),
|
||||
DOWN(Vec3(0.0, -1.0, 0.0)),
|
||||
NORTH(Vec3(0.0, 0.0, -1.0)),
|
||||
SOUTH(Vec3(0.0, 0.0, 1.0)),
|
||||
EAST(Vec3(1.0, 0.0, 0.0)),
|
||||
WEST(Vec3(-1.0, 0.0, 0.0))
|
||||
}
|
||||
|
||||
/** Thin slab test on one face of the vehicle's actual bounding box - true if anything solid is touching that side. */
|
||||
fun probeFaceContact(vehicle: PokemonEntity, face: AttachmentFace, probeMargin: Double): Boolean {
|
||||
val box = vehicle.boundingBox
|
||||
val testBox = when (face) {
|
||||
AttachmentFace.UP -> AABB(box.minX, box.maxY, box.minZ, box.maxX, box.maxY + probeMargin, box.maxZ)
|
||||
AttachmentFace.DOWN -> AABB(box.minX, box.minY - probeMargin, box.minZ, box.maxX, box.minY, box.maxZ)
|
||||
AttachmentFace.NORTH -> AABB(box.minX, box.minY, box.minZ - probeMargin, box.maxX, box.maxY, box.minZ)
|
||||
AttachmentFace.SOUTH -> AABB(box.minX, box.minY, box.maxZ, box.maxX, box.maxY, box.maxZ + probeMargin)
|
||||
AttachmentFace.EAST -> AABB(box.maxX, box.minY, box.minZ, box.maxX + probeMargin, box.maxY, box.maxZ)
|
||||
AttachmentFace.WEST -> AABB(box.minX - probeMargin, box.minY, box.minZ, box.minX, box.maxY, box.maxZ)
|
||||
}
|
||||
return !vehicle.level().noCollision(vehicle, testBox)
|
||||
}
|
||||
|
||||
/** Stays on the current face if it's still touching, otherwise scans for a new one. */
|
||||
fun determineAttachmentFace(vehicle: PokemonEntity, currentFace: AttachmentFace?, probeMargin: Double): AttachmentFace? {
|
||||
if (currentFace != null && probeFaceContact(vehicle, currentFace, probeMargin)) {
|
||||
return currentFace
|
||||
}
|
||||
return AttachmentFace.entries.firstOrNull { probeFaceContact(vehicle, it, probeMargin) }
|
||||
}
|
||||
+12
-40
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
|
||||
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
|
||||
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
import com.cobblemon.mod.common.api.riding.stats.RidingStat
|
||||
import com.cobblemon.mod.common.api.riding.util.RideFrame
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
|
||||
@@ -44,7 +45,6 @@ import net.minecraft.core.Direction
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.util.SmoothDouble
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes
|
||||
import net.minecraft.world.entity.player.Player
|
||||
@@ -267,12 +267,23 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
|
||||
}
|
||||
|
||||
|
||||
// This ride works along its yaw so its velocity gets turned into world space here
|
||||
override fun velocity(
|
||||
settings: HorseSettings,
|
||||
state: HorseState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
|
||||
}
|
||||
|
||||
private fun localVelocity(
|
||||
settings: HorseSettings,
|
||||
state: HorseState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
|
||||
return state.rideVelocity.get()
|
||||
@@ -398,37 +409,6 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
|
||||
|
||||
}
|
||||
|
||||
override fun angRollVel(
|
||||
settings: HorseSettings,
|
||||
state: HorseState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
return Vec3.ZERO
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
settings: HorseSettings,
|
||||
state: HorseState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
mouseY: Double,
|
||||
mouseX: Double,
|
||||
mouseYSmoother: SmoothDouble,
|
||||
mouseXSmoother: SmoothDouble,
|
||||
sensitivity: Double,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
//Smooth out mouse input.
|
||||
val smoothingSpeed = 4
|
||||
val xInput = mouseXSmoother.getNewDeltaValue(mouseX * 0.1, deltaTime * smoothingSpeed);
|
||||
val yInput = mouseYSmoother.getNewDeltaValue(mouseY * 0.1, deltaTime * smoothingSpeed);
|
||||
|
||||
//yaw, pitch, roll
|
||||
return Vec3(0.0, yInput, xInput)
|
||||
}
|
||||
|
||||
override fun canJump(
|
||||
settings: HorseSettings,
|
||||
state: HorseState,
|
||||
@@ -475,14 +455,6 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
|
||||
return if (state.sprinting.get()) 1.15f else 1.0f
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(
|
||||
settings: HorseSettings,
|
||||
state: HorseState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun inertia(
|
||||
settings: HorseSettings,
|
||||
state: HorseState,
|
||||
|
||||
+20
-39
@@ -12,7 +12,6 @@ import com.bedrockk.molang.Expression
|
||||
import com.bedrockk.molang.runtime.MoLangMath.lerp
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.CobblemonRideSettings
|
||||
import com.cobblemon.mod.common.OrientationControllable
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
|
||||
@@ -25,6 +24,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
|
||||
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
|
||||
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
import com.cobblemon.mod.common.api.riding.stats.RidingStat
|
||||
import com.cobblemon.mod.common.api.riding.util.RideFrame
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
|
||||
@@ -45,7 +45,6 @@ import kotlin.math.sign
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.util.SmoothDouble
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.Vec2
|
||||
@@ -98,7 +97,15 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Float {
|
||||
return driver.deltaMovement.length().toFloat()
|
||||
}
|
||||
|
||||
override fun driverTick(
|
||||
settings: MinekartSettings,
|
||||
state: MinekartState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
) {
|
||||
val boostMax = vehicle.runtime.resolveFloat(settings.boostLimit ?: globalMinekart.boostLimit!!)
|
||||
//TODO: should this be by how much turn has accumulated or simply time turning... or both?
|
||||
val boostPerSecond = 20.0f
|
||||
@@ -127,7 +134,6 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
|
||||
}
|
||||
|
||||
inAirCheck(state, vehicle)
|
||||
return driver.deltaMovement.length().toFloat()
|
||||
}
|
||||
|
||||
fun inAirCheck(
|
||||
@@ -197,12 +203,23 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
|
||||
|
||||
}
|
||||
|
||||
// This ride works along its yaw so its velocity gets turned into world space here
|
||||
override fun velocity(
|
||||
settings: MinekartSettings,
|
||||
state: MinekartState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
|
||||
}
|
||||
|
||||
private fun localVelocity(
|
||||
settings: MinekartSettings,
|
||||
state: MinekartState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
|
||||
return state.rideVelocity.get()
|
||||
@@ -308,34 +325,6 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
|
||||
return newVelocity
|
||||
}
|
||||
|
||||
override fun angRollVel(
|
||||
settings: MinekartSettings,
|
||||
state: MinekartState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
return Vec3.ZERO
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
settings: MinekartSettings,
|
||||
state: MinekartState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
mouseY: Double,
|
||||
mouseX: Double,
|
||||
mouseYSmoother: SmoothDouble,
|
||||
mouseXSmoother: SmoothDouble,
|
||||
sensitivity: Double,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
if (driver !is OrientationControllable) return Vec3.ZERO
|
||||
|
||||
//Might need to add the smoothing here for default.
|
||||
return Vec3(0.0, mouseY, mouseX)
|
||||
}
|
||||
|
||||
override fun canJump(
|
||||
settings: MinekartSettings,
|
||||
state: MinekartState,
|
||||
@@ -387,14 +376,6 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
|
||||
return fov
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(
|
||||
settings: MinekartSettings,
|
||||
state: MinekartState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun inertia(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): Double {
|
||||
return 1.0
|
||||
}
|
||||
|
||||
+20
-39
@@ -12,7 +12,6 @@ import com.bedrockk.molang.Expression
|
||||
import com.bedrockk.molang.runtime.MoLangMath.lerp
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.CobblemonRideSettings
|
||||
import com.cobblemon.mod.common.OrientationControllable
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
|
||||
@@ -25,6 +24,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
|
||||
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
|
||||
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
import com.cobblemon.mod.common.api.riding.stats.RidingStat
|
||||
import com.cobblemon.mod.common.api.riding.util.RideFrame
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
|
||||
@@ -44,7 +44,6 @@ import kotlin.math.sign
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.util.SmoothDouble
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.Vec2
|
||||
@@ -98,7 +97,15 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
): Float {
|
||||
return driver.deltaMovement.length().toFloat()
|
||||
}
|
||||
|
||||
override fun driverTick(
|
||||
settings: VehicleSettings,
|
||||
state: VehicleState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player
|
||||
) {
|
||||
if (driver.isShiftKeyDown) {
|
||||
state.drifting.set(true)
|
||||
if(driver.zza != 0.0f) {
|
||||
@@ -112,7 +119,6 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
|
||||
}
|
||||
|
||||
inAirCheck(state, vehicle)
|
||||
return driver.deltaMovement.length().toFloat()
|
||||
}
|
||||
|
||||
fun inAirCheck(
|
||||
@@ -172,12 +178,23 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
|
||||
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat())
|
||||
}
|
||||
|
||||
// This ride works along its yaw so its velocity gets turned into world space here
|
||||
override fun velocity(
|
||||
settings: VehicleSettings,
|
||||
state: VehicleState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
|
||||
}
|
||||
|
||||
private fun localVelocity(
|
||||
settings: VehicleSettings,
|
||||
state: VehicleState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
|
||||
return state.rideVelocity.get()
|
||||
@@ -278,34 +295,6 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
|
||||
return newVelocity
|
||||
}
|
||||
|
||||
override fun angRollVel(
|
||||
settings: VehicleSettings,
|
||||
state: VehicleState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
return Vec3.ZERO
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
settings: VehicleSettings,
|
||||
state: VehicleState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
mouseY: Double,
|
||||
mouseX: Double,
|
||||
mouseYSmoother: SmoothDouble,
|
||||
mouseXSmoother: SmoothDouble,
|
||||
sensitivity: Double,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
if (driver !is OrientationControllable) return Vec3.ZERO
|
||||
|
||||
//Might need to add the smoothing here for default.
|
||||
return Vec3(0.0, mouseY, mouseX)
|
||||
}
|
||||
|
||||
override fun canJump(
|
||||
settings: VehicleSettings,
|
||||
state: VehicleState,
|
||||
@@ -352,14 +341,6 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
|
||||
return 1.0f
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(
|
||||
settings: VehicleSettings,
|
||||
state: VehicleState,
|
||||
vehicle: PokemonEntity
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun inertia(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Double {
|
||||
return 1.0
|
||||
}
|
||||
|
||||
+12
-34
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.CobblemonRideSettings
|
||||
import com.cobblemon.mod.common.OrientationControllable
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
|
||||
@@ -23,6 +22,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
|
||||
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
|
||||
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
import com.cobblemon.mod.common.api.riding.stats.RidingStat
|
||||
import com.cobblemon.mod.common.api.riding.util.RideFrame
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
|
||||
@@ -40,7 +40,6 @@ import kotlin.math.min
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.util.SmoothDouble
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.Vec2
|
||||
@@ -133,12 +132,23 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
|
||||
return Vec2(vehicle.xRot, vehicle.yRot + state.deltaRotation.get().toFloat())
|
||||
}
|
||||
|
||||
// This ride works along its yaw so its velocity gets turned into world space here
|
||||
override fun velocity(
|
||||
settings: BoatSettings,
|
||||
state: BoatState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
|
||||
}
|
||||
|
||||
private fun localVelocity(
|
||||
settings: BoatSettings,
|
||||
state: BoatState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
applyStrafeRotation(vehicle, driver, settings, state)
|
||||
|
||||
@@ -259,34 +269,6 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
|
||||
vehicle.setYHeadRot(driver.yRot)
|
||||
}
|
||||
|
||||
override fun angRollVel(
|
||||
settings: BoatSettings,
|
||||
state: BoatState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
return Vec3.ZERO
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
settings: BoatSettings,
|
||||
state: BoatState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
mouseY: Double,
|
||||
mouseX: Double,
|
||||
mouseYSmoother: SmoothDouble,
|
||||
mouseXSmoother: SmoothDouble,
|
||||
sensitivity: Double,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
if (driver !is OrientationControllable) return Vec3.ZERO
|
||||
|
||||
//Might need to add the smoothing here for default.
|
||||
return Vec3(0.0, mouseY, mouseX)
|
||||
}
|
||||
|
||||
override fun canJump(
|
||||
settings: BoatSettings,
|
||||
state: BoatState,
|
||||
@@ -334,10 +316,6 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
|
||||
return if (state.isVehicleSprinting.get()) sprintFov else 1.0f
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun inertia(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity): Double {
|
||||
return 0.5
|
||||
}
|
||||
|
||||
+12
-34
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
|
||||
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.CobblemonRideSettings
|
||||
import com.cobblemon.mod.common.OrientationControllable
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
|
||||
@@ -22,6 +21,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
|
||||
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
|
||||
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
import com.cobblemon.mod.common.api.riding.stats.RidingStat
|
||||
import com.cobblemon.mod.common.api.riding.util.RideFrame
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
|
||||
@@ -29,7 +29,6 @@ import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.readRidingStats
|
||||
import com.cobblemon.mod.common.util.writeRidingStats
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.util.SmoothDouble
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.Vec2
|
||||
@@ -96,12 +95,23 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
|
||||
return Vec2(driver.xRot * 0.5f, driver.yRot)
|
||||
}
|
||||
|
||||
// This ride works along its yaw so its velocity gets turned into world space here
|
||||
override fun velocity(
|
||||
settings: BurstSettings,
|
||||
state: BurstState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
|
||||
}
|
||||
|
||||
private fun localVelocity(
|
||||
settings: BurstSettings,
|
||||
state: BurstState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: Vec3
|
||||
): Vec3 {
|
||||
val f = driver.xxa * 0.05f
|
||||
var g = driver.zza * 0.6f
|
||||
@@ -112,34 +122,6 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
|
||||
return Vec3(f.toDouble(), 0.0, g.toDouble())
|
||||
}
|
||||
|
||||
override fun angRollVel(
|
||||
settings: BurstSettings,
|
||||
state: BurstState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
return Vec3.ZERO
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
settings: BurstSettings,
|
||||
state: BurstState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
mouseY: Double,
|
||||
mouseX: Double,
|
||||
mouseYSmoother: SmoothDouble,
|
||||
mouseXSmoother: SmoothDouble,
|
||||
sensitivity: Double,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
if (driver !is OrientationControllable) return Vec3.ZERO
|
||||
|
||||
//Might need to add the smoothing here for default.
|
||||
return Vec3(0.0, mouseX, mouseY)
|
||||
}
|
||||
|
||||
override fun canJump(
|
||||
settings: BurstSettings,
|
||||
state: BurstState,
|
||||
@@ -186,10 +168,6 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
|
||||
return 1.0f
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(settings: BurstSettings, state: BurstState, vehicle: PokemonEntity): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun inertia(settings: BurstSettings, state: BurstState, vehicle: PokemonEntity): Double {
|
||||
return 0.5
|
||||
}
|
||||
|
||||
+22
-13
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.OrientationControllable
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RideSteerInput
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
|
||||
@@ -54,6 +55,8 @@ import org.joml.Vector3f
|
||||
|
||||
class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
|
||||
companion object {
|
||||
// The correction forces were tuned as degrees per tenth of a second
|
||||
private const val CORRECTION_FORCE_TO_DEGREES_PER_SECOND = 10.0
|
||||
val KEY = cobblemonResource("liquid/dolphin")
|
||||
}
|
||||
|
||||
@@ -201,7 +204,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
|
||||
}
|
||||
|
||||
state.lastVelocity.set(prevVel)
|
||||
return state.lastVelocity.get().yRot(vehicle.yRot.toRadians())
|
||||
return state.lastVelocity.get()
|
||||
} else {
|
||||
if(state.hasBreached.get()) {
|
||||
state.clearMouseSmoothers.set(true)
|
||||
@@ -238,18 +241,17 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
|
||||
val worldVelVec = localVelVec.mul(orientation).toVec3d()
|
||||
state.lastVelocity.set(worldVelVec) // Set last velocity to be used during breaching
|
||||
|
||||
// Unrotate preemptively as this vector gets rotated later down the line in MC logic.
|
||||
return worldVelVec.yRot(vehicle.yRot.toRadians())
|
||||
return worldVelVec
|
||||
}
|
||||
|
||||
override fun angRollVel(
|
||||
private fun rotateOverTime(
|
||||
settings: DolphinSettings,
|
||||
state: DolphinState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
if (vehicle !is OrientationControllable) return Vec3.ZERO
|
||||
) {
|
||||
if (vehicle !is OrientationControllable) return
|
||||
val controller = (vehicle as OrientationControllable).orientationController
|
||||
|
||||
val currSpeed = vehicle.deltaMovement.length()
|
||||
@@ -267,8 +269,8 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
|
||||
// Correct orientation when at low speeds
|
||||
correctOrientation(settings, state, vehicle, controller, driver, deltaTime)
|
||||
|
||||
//yaw, pitch, roll
|
||||
return Vec3(0.0, state.currPitchCorrectionForce.get(), state.currRollCorrectionForce.get())
|
||||
val correction = deltaTime * CORRECTION_FORCE_TO_DEGREES_PER_SECOND
|
||||
controller.rotate(0f, (state.currPitchCorrectionForce.get() * correction).toFloat(), (state.currRollCorrectionForce.get() * correction).toFloat())
|
||||
}
|
||||
|
||||
private fun correctOrientation(
|
||||
@@ -354,7 +356,18 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
|
||||
}
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
override fun steer(
|
||||
settings: DolphinSettings,
|
||||
state: DolphinState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: RideSteerInput
|
||||
) {
|
||||
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
|
||||
rotateOverTime(settings, state, vehicle, driver, input.deltaTime)
|
||||
}
|
||||
|
||||
private fun mouseRotation(
|
||||
settings: DolphinSettings,
|
||||
state: DolphinState,
|
||||
vehicle: PokemonEntity,
|
||||
@@ -487,10 +500,6 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
|
||||
return if (state.boosting.get()) 1.2f else 1.0f
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun inertia(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Double {
|
||||
return if (!vehicle.isInWater && !vehicle.isUnderWater) 1.0 else 0.1
|
||||
}
|
||||
|
||||
+21
-23
@@ -17,14 +17,16 @@ import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.*
|
||||
import com.cobblemon.mod.common.api.riding.camera.RideLookInput
|
||||
import com.cobblemon.mod.common.api.riding.camera.RideLookTake
|
||||
import com.cobblemon.mod.common.api.riding.posing.PoseOption
|
||||
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
|
||||
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
import com.cobblemon.mod.common.api.riding.stats.RidingStat
|
||||
import com.cobblemon.mod.common.api.riding.util.RideFrame
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.math.geometry.toRadians
|
||||
import com.cobblemon.mod.common.util.readNullableExpression
|
||||
import com.cobblemon.mod.common.util.readRidingStats
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
@@ -254,29 +256,17 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
|
||||
*********************************************/
|
||||
// If the driver and vehicles eyes are out of the water but the vehicle is still in it then move only horizontally or downwards as you have surfaced
|
||||
if (state.onSurface.get()) {
|
||||
val worldVelVec = Vec3(currVel.x, min(0.0, currVel.y), currVel.z)
|
||||
return worldVelVec
|
||||
return RideFrame.yawToWorld(Vec3(currVel.x, min(0.0, currVel.y), currVel.z), vehicle.yRot)
|
||||
} else {
|
||||
val vertVec = Vec3(0.0, currVel.y, 0.0)
|
||||
val horzVec = Vec3(currVel.x, 0.0, 0.0)
|
||||
val horzVec = RideFrame.yawToWorld(Vec3(currVel.x, 0.0, 0.0), vehicle.yRot)
|
||||
val localZVelVec = Vector3f(0.0f, 0.0f, currVel.z.toFloat() * -1.0f) // Flip the z axis to make this left handed
|
||||
// Unrotate preemptively as this vector gets rotated later down the line in MC logic.
|
||||
val worldVelVec = localZVelVec.mul(orientation).toVec3d().yRot(vehicle.yRot.toRadians())
|
||||
val worldVelVec = localZVelVec.mul(orientation).toVec3d()
|
||||
// Add back the horizontal and vertical components.
|
||||
return worldVelVec.add(vertVec).add(horzVec)
|
||||
}
|
||||
}
|
||||
|
||||
override fun angRollVel(
|
||||
settings: SubmarineSettings,
|
||||
state: SubmarineState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
return Vec3.ZERO
|
||||
}
|
||||
|
||||
override fun applyRenderRotation(
|
||||
settings: SubmarineSettings,
|
||||
state: SubmarineState,
|
||||
@@ -323,7 +313,17 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
|
||||
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
override fun steer(
|
||||
settings: SubmarineSettings,
|
||||
state: SubmarineState,
|
||||
vehicle: PokemonEntity,
|
||||
driver: Player,
|
||||
input: RideSteerInput
|
||||
) {
|
||||
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
|
||||
}
|
||||
|
||||
private fun mouseRotation(
|
||||
settings: SubmarineSettings,
|
||||
state: SubmarineState,
|
||||
vehicle: PokemonEntity,
|
||||
@@ -432,10 +432,6 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
|
||||
return 1.0f
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun inertia(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Double {
|
||||
return if (!vehicle.isUnderWater) 1.0 else 0.1
|
||||
}
|
||||
@@ -452,8 +448,10 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun mouseModifiesDriverRotation(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Pair<Boolean, Boolean> {
|
||||
return Pair(false, state.onSurface.get())
|
||||
// Surfaced the mouse pitch only moves the look so you can look around without nosing the sub under
|
||||
override fun consumeLook(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity, input: RideLookInput): RideLookTake {
|
||||
if (!state.onSurface.get()) return RideLookTake.all(input)
|
||||
return RideLookTake(input.yawDelta, 0f, recenterYaw = true, recenterPitch = false)
|
||||
}
|
||||
|
||||
override fun shouldRotatePokemonHead(
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.riding.camera
|
||||
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.FORWARDS
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.UP
|
||||
import net.minecraft.util.Mth
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
import kotlin.math.atan2
|
||||
|
||||
/**
|
||||
* What a ride gets to build its camera base from each frame.
|
||||
* One lives per ride on the client so [data] can keep a ride's own camera state between frames.
|
||||
*/
|
||||
class RideCameraContext {
|
||||
companion object {
|
||||
// Ride up this close to the view direction has no readable roll
|
||||
private const val MIN_FLAT_LENGTH_SQ = 1.0E-6f
|
||||
}
|
||||
|
||||
/** Ride orientation exactly as the driver sets it */
|
||||
val rawOrientation = Quaternionf()
|
||||
/** Spring smoothed ride orientation the model and seats are drawn with */
|
||||
val renderOrientation = Quaternionf()
|
||||
/** True when the local player is the one steering */
|
||||
var isDriving = false
|
||||
private set
|
||||
/** Base the camera used last frame, null on the first frame of a ride */
|
||||
var previousBase: Quaternionf? = null
|
||||
private set
|
||||
/** Seconds since the last frame, 0 while paused */
|
||||
var frameSeconds = 0f
|
||||
private set
|
||||
var partialTicks = 0f
|
||||
private set
|
||||
/** Free slot for a ride to keep its own camera state in. Cleared when the ride changes */
|
||||
var data: Any? = null
|
||||
/** Final camera view this frame, base with the look on top */
|
||||
val view = Quaternionf()
|
||||
|
||||
/** Drivers hang straight off the ride. Passengers get the smoothed one since raw packets make it jerky */
|
||||
fun defaultBase(): Quaternionf = Quaternionf(if (isDriving) rawOrientation else renderOrientation)
|
||||
|
||||
internal fun update(raw: Quaternionf, render: Quaternionf, isDriving: Boolean, frameSeconds: Float, partialTicks: Float) {
|
||||
rawOrientation.set(raw)
|
||||
renderOrientation.set(render)
|
||||
this.isDriving = isDriving
|
||||
this.frameSeconds = frameSeconds
|
||||
this.partialTicks = partialTicks
|
||||
}
|
||||
|
||||
/** Degrees the ride's model is rolled from the camera around the view direction */
|
||||
fun rideRollAroundView(): Float {
|
||||
val forward = FORWARDS.rotate(view, Vector3f())
|
||||
val cameraUp = UP.rotate(view, Vector3f())
|
||||
val rideUp = UP.rotate(renderOrientation, Vector3f())
|
||||
rideUp.sub(Vector3f(forward).mul(rideUp.dot(forward)))
|
||||
if (rideUp.lengthSquared() < MIN_FLAT_LENGTH_SQ) return 0f
|
||||
return atan2(Vector3f(cameraUp).cross(rideUp).dot(forward), cameraUp.dot(rideUp)) * Mth.RAD_TO_DEG
|
||||
}
|
||||
|
||||
internal fun updateView(view: Quaternionf) {
|
||||
this.view.set(view)
|
||||
}
|
||||
|
||||
internal fun finishFrame(base: Quaternionf) {
|
||||
previousBase = (previousBase ?: Quaternionf()).set(base)
|
||||
}
|
||||
|
||||
internal fun reset() {
|
||||
view.identity()
|
||||
rawOrientation.identity()
|
||||
renderOrientation.identity()
|
||||
isDriving = false
|
||||
previousBase = null
|
||||
frameSeconds = 0f
|
||||
partialTicks = 0f
|
||||
data = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.riding.camera
|
||||
|
||||
/**
|
||||
* One frame of mouse look in degrees, before the ride takes its share.
|
||||
* Angles are relative to the camera base.
|
||||
*/
|
||||
data class RideLookInput(
|
||||
/** Degrees the mouse moved the look sideways this frame */
|
||||
val yawDelta: Float,
|
||||
/** Degrees the mouse moved the look up or down this frame */
|
||||
val pitchDelta: Float,
|
||||
/** Look yaw before this frame's movement */
|
||||
val yaw: Float,
|
||||
/** Look pitch before this frame's movement */
|
||||
val pitch: Float
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.riding.camera
|
||||
|
||||
/**
|
||||
* How far a rider can look around the camera base and how much of the base's turning the look follows.
|
||||
*/
|
||||
data class RideLookSettings(
|
||||
/** Degrees the look can pitch either side of the camera base */
|
||||
val maxPitch: Float = DEFAULT_MAX_PITCH,
|
||||
/** Degrees the look can yaw either side of the camera base */
|
||||
val maxYaw: Float = DEFAULT_MAX_YAW,
|
||||
/** 0 to 1 share of the base's turning the look follows. 1 turns with the base and 0 holds its direction in the world */
|
||||
val carry: Float = FULL_CARRY
|
||||
) {
|
||||
companion object {
|
||||
// Just short of straight up or down so yaw never goes degenerate
|
||||
const val DEFAULT_MAX_PITCH = 89f
|
||||
// A bit past side on so you can look over your shoulder. Raise to let riders look further back
|
||||
const val DEFAULT_MAX_YAW = 105f
|
||||
const val FULL_CARRY = 1f
|
||||
|
||||
@JvmField
|
||||
val DEFAULT = RideLookSettings()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.riding.camera
|
||||
|
||||
/**
|
||||
* The part of a frame's mouse look a ride takes to steer with, in degrees.
|
||||
* Whatever isn't taken moves the rider's look instead.
|
||||
*/
|
||||
data class RideLookTake(
|
||||
val yaw: Float,
|
||||
val pitch: Float,
|
||||
/** Ease the look back to centre on this axis */
|
||||
val recenterYaw: Boolean,
|
||||
val recenterPitch: Boolean
|
||||
) {
|
||||
companion object {
|
||||
/** Ride takes nothing and the look stays where it's put */
|
||||
@JvmField
|
||||
val NONE = RideLookTake(0f, 0f, recenterYaw = false, recenterPitch = false)
|
||||
|
||||
/** Ride steers with all of it and the look drifts back to centre. How rolling rides have always worked */
|
||||
@JvmStatic
|
||||
fun all(input: RideLookInput) = RideLookTake(input.yawDelta, input.pitchDelta, recenterYaw = true, recenterPitch = true)
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.riding.util
|
||||
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.phys.AABB
|
||||
import kotlin.math.floor
|
||||
|
||||
/**
|
||||
* Walks every block position inside [searchBox] and turns each solid block into one
|
||||
* full-cube [FieldPrimitive].
|
||||
*
|
||||
* @author Jackson
|
||||
* @since July 27th, 2026
|
||||
*/
|
||||
|
||||
fun collectPrimitivesFromBlocks(
|
||||
level: BlockGetter,
|
||||
searchBox: AABB,
|
||||
shape: PrimitiveShape,
|
||||
includeFluids: Boolean = false
|
||||
): List<FieldPrimitive> {
|
||||
val primitives = mutableListOf<FieldPrimitive>()
|
||||
|
||||
val minX = floor(searchBox.minX).toInt()
|
||||
val maxX = floor(searchBox.maxX).toInt()
|
||||
val minY = floor(searchBox.minY).toInt()
|
||||
val maxY = floor(searchBox.maxY).toInt()
|
||||
val minZ = floor(searchBox.minZ).toInt()
|
||||
val maxZ = floor(searchBox.maxZ).toInt()
|
||||
|
||||
val pos = BlockPos.MutableBlockPos()
|
||||
for (x in minX..maxX) {
|
||||
for (y in minY..maxY) {
|
||||
for (z in minZ..maxZ) {
|
||||
pos.set(x, y, z)
|
||||
val state = level.getBlockState(pos)
|
||||
|
||||
val isSolid = !state.getCollisionShape(level, pos).isEmpty
|
||||
val isFluid = includeFluids && !state.fluidState.isEmpty
|
||||
if (!isSolid && !isFluid) continue
|
||||
|
||||
primitives.add(
|
||||
FieldPrimitive(
|
||||
shape = shape,
|
||||
centerX = x + 0.5f, centerY = y + 0.5f, centerZ = z + 0.5f,
|
||||
halfWidth = 0.5f, halfHeight = 0.5f, halfDepth = 0.5f
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return primitives
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.riding.util
|
||||
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sqrt
|
||||
|
||||
/**
|
||||
* Helpful Functions when working with signed distance fields/functions.
|
||||
* https://iquilezles.org/articles/distfunctions/
|
||||
*
|
||||
* @author Jackson
|
||||
* @since July 27th, 2026
|
||||
*/
|
||||
|
||||
/** Which distance formula a primitive uses. */
|
||||
enum class PrimitiveShape {
|
||||
BOX,
|
||||
ROUNDED_BOX,
|
||||
ELLIPSOID
|
||||
}
|
||||
|
||||
/** Tunable constants for the field. */
|
||||
data class DistanceFieldTuning(
|
||||
val smoothingRange: Float = 0.5f,
|
||||
val roundingRadius: Float = 0.08f
|
||||
)
|
||||
|
||||
/** A single shape in the field - a center position, half-extents, and which formula to use. */
|
||||
data class FieldPrimitive(
|
||||
val shape: PrimitiveShape,
|
||||
val centerX: Float, val centerY: Float, val centerZ: Float,
|
||||
val halfWidth: Float, val halfHeight: Float, val halfDepth: Float
|
||||
)
|
||||
|
||||
/** Fast approximate 1/sqrt(x) - the Quake III trick, cheaper than a real sqrt + divide. */
|
||||
fun fastInverseSqrt(value: Float): Float {
|
||||
var x = value
|
||||
val halfX = 0.5f * x
|
||||
var bits = java.lang.Float.floatToIntBits(x)
|
||||
bits = 0x5f3759df - (bits shr 1)
|
||||
x = java.lang.Float.intBitsToFloat(bits)
|
||||
x *= (1.5f - halfX * x * x)
|
||||
return x
|
||||
}
|
||||
|
||||
/** Exact distance from a point to an axis-aligned box. Inputs are relative to the box's own center. */
|
||||
fun boxDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float): Float {
|
||||
val qx = abs(relX) - halfWidth
|
||||
val qy = abs(relY) - halfHeight
|
||||
val qz = abs(relZ) - halfDepth
|
||||
val ox = max(qx, 0f)
|
||||
val oy = max(qy, 0f)
|
||||
val oz = max(qz, 0f)
|
||||
val outside = sqrt(ox * ox + oy * oy + oz * oz)
|
||||
val inside = min(max(qx, max(qy, qz)), 0f)
|
||||
return outside + inside
|
||||
}
|
||||
|
||||
/** Same as boxDistance but with rounded corners/edges https://iquilezles.org/articles/distfunctions/ */
|
||||
fun roundedBoxDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float, roundingRadius: Float): Float =
|
||||
boxDistance(relX, relY, relZ, halfWidth - roundingRadius, halfHeight - roundingRadius, halfDepth - roundingRadius) - roundingRadius
|
||||
|
||||
/** Approximate (lower-bound) distance to an ellipsoid https://iquilezles.org/articles/ellipsoids/. */
|
||||
fun ellipsoidDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float): Float {
|
||||
val px = relX / halfWidth
|
||||
val py = relY / halfHeight
|
||||
val pz = relZ / halfDepth
|
||||
val inverseMagnitude = fastInverseSqrt(px * px + py * py + pz * pz)
|
||||
val trueDistance = sqrt(relX * relX + relY * relY + relZ * relZ)
|
||||
return trueDistance * (1.0f - inverseMagnitude)
|
||||
}
|
||||
|
||||
/** Distance from a point to one primitive, dispatched by its shape. */
|
||||
fun distanceToPrimitive(primitive: FieldPrimitive, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
|
||||
val relX = sampleX - primitive.centerX
|
||||
val relY = sampleY - primitive.centerY
|
||||
val relZ = sampleZ - primitive.centerZ
|
||||
return when (primitive.shape) {
|
||||
PrimitiveShape.BOX -> boxDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth)
|
||||
PrimitiveShape.ROUNDED_BOX -> roundedBoxDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth, tuning.roundingRadius)
|
||||
PrimitiveShape.ELLIPSOID -> ellipsoidDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth)
|
||||
}
|
||||
}
|
||||
|
||||
///** Normalized quadratic smooth-min https://iquilezles.org/articles/smin/ */
|
||||
//fun smoothMinimum(a: Float, b: Float, smoothingRange: Float): Float {
|
||||
// if (smoothingRange <= 0.001f) return min(a, b)
|
||||
// val k = smoothingRange * 4.0f
|
||||
// val h = (0.5f + 0.5f * (b - a) / k).coerceIn(0f, 1f)
|
||||
// return b + (a - b) * h - k * h * (1f - h)
|
||||
//}
|
||||
//
|
||||
///** Folds every primitive's distance together into one field value at a point. */
|
||||
//fun sampleField(primitives: List<FieldPrimitive>, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
|
||||
// var result = Float.POSITIVE_INFINITY
|
||||
// for (primitive in primitives) {
|
||||
// val distance = distanceToPrimitive(primitive, sampleX, sampleY, sampleZ, tuning)
|
||||
// result = if (result == Float.POSITIVE_INFINITY) distance else smoothMinimum(result, distance, tuning.smoothingRange)
|
||||
// }
|
||||
// return result
|
||||
//}
|
||||
|
||||
/** Normalized quadratic smooth-min https://iquilezles.org/articles/smin/ */
|
||||
fun smoothMinimum(a: Float, b: Float, smoothingRange: Float): Float {
|
||||
if (smoothingRange <= 0.001f) return min(a, b)
|
||||
val k = smoothingRange * 4.0f
|
||||
val h = (0.5f + 0.5f * (b - a) / k).coerceIn(0f, 1f)
|
||||
return b + (a - b) * h - k * h * (1f - h)
|
||||
}
|
||||
|
||||
/** Folds nearby primitive distances together into one field value at a point. */
|
||||
fun sampleField(primitives: List<FieldPrimitive>, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
|
||||
if (primitives.isEmpty()) return Float.POSITIVE_INFINITY
|
||||
|
||||
val distances = FloatArray(primitives.size) { distanceToPrimitive(primitives[it], sampleX, sampleY, sampleZ, tuning) }
|
||||
val minDistance = distances.min()
|
||||
|
||||
if (tuning.smoothingRange <= 0.001f) return minDistance
|
||||
|
||||
// Anything more than a few blend-widths past the closest primitive is cut - its
|
||||
// contribution would be negligible in a correct blend anyway, but left in, it still
|
||||
// drags the chained result down the more of them there are.
|
||||
val cutoff = tuning.smoothingRange * 3f
|
||||
|
||||
var result = Float.POSITIVE_INFINITY
|
||||
for (distance in distances) {
|
||||
if (distance - minDistance > cutoff) continue
|
||||
result = if (result == Float.POSITIVE_INFINITY) distance else smoothMinimum(result, distance, tuning.smoothingRange)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** Finite-difference gradient of the field at a point - points away from the surface, magnitude ~1 for a clean field. */
|
||||
fun estimateGradient(primitives: List<FieldPrimitive>, point: Vec3, tuning: DistanceFieldTuning, epsilon: Float = 0.05f): Vec3 {
|
||||
val px = point.x.toFloat()
|
||||
val py = point.y.toFloat()
|
||||
val pz = point.z.toFloat()
|
||||
|
||||
val dx = sampleField(primitives, px + epsilon, py, pz, tuning) - sampleField(primitives, px - epsilon, py, pz, tuning)
|
||||
val dy = sampleField(primitives, px, py + epsilon, pz, tuning) - sampleField(primitives, px, py - epsilon, pz, tuning)
|
||||
val dz = sampleField(primitives, px, py, pz + epsilon, tuning) - sampleField(primitives, px, py, pz - epsilon, tuning)
|
||||
|
||||
val gradient = Vec3(dx.toDouble(), dy.toDouble(), dz.toDouble())
|
||||
return if (gradient.lengthSqr() > 1e-8) gradient.normalize() else Vec3(0.0, 1.0, 0.0)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.riding.util
|
||||
|
||||
import com.cobblemon.mod.common.util.math.geometry.toRadians
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
/**
|
||||
* Turns ride velocities between world space and a ride's yaw local space,
|
||||
* where x is strafe, y is up and z is forward along the ride's yaw.
|
||||
*/
|
||||
object RideFrame {
|
||||
/** Yaw local to world. Same maths PokemonEntity.travel always used so rides come out identical. */
|
||||
@JvmStatic
|
||||
fun yawToWorld(local: Vec3, yawDegrees: Float): Vec3 {
|
||||
val sin = Mth.sin(yawDegrees.toRadians()).toDouble()
|
||||
val cos = Mth.cos(yawDegrees.toRadians()).toDouble()
|
||||
return Vec3(local.x * cos - local.z * sin, local.y, local.z * cos + local.x * sin)
|
||||
}
|
||||
|
||||
/** World to yaw local. Exact inverse of [yawToWorld]. */
|
||||
@JvmStatic
|
||||
fun worldToYaw(world: Vec3, yawDegrees: Float): Vec3 {
|
||||
val sin = Mth.sin(yawDegrees.toRadians()).toDouble()
|
||||
val cos = Mth.cos(yawDegrees.toRadians()).toDouble()
|
||||
return Vec3(world.x * cos + world.z * sin, world.y, world.z * cos - world.x * sin)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.riding.util
|
||||
|
||||
import net.minecraft.core.particles.DustParticleOptions
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.phys.AABB
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.joml.Vector3f
|
||||
|
||||
/** One shell to draw - a target field value, a color, a particle size. */
|
||||
data class DistanceFieldLayer(
|
||||
val isoValue: Float,
|
||||
val color: Vector3f,
|
||||
val particleSize: Float = 1.0f
|
||||
)
|
||||
|
||||
/**
|
||||
* Marches a grid of vertical columns through the field around [center] and spawns a
|
||||
* particle everywhere it crosses one of [layers]' target values. Server-side only.
|
||||
*/
|
||||
fun visualizeDistanceField(
|
||||
referenceEntity: Entity,
|
||||
center: Vec3,
|
||||
layers: List<DistanceFieldLayer>,
|
||||
shape: PrimitiveShape = PrimitiveShape.BOX,
|
||||
horizontalRadius: Double = 4.0,
|
||||
verticalRange: Double = 4.0,
|
||||
sampleSpacing: Double = 0.5,
|
||||
marchStep: Double = 0.1,
|
||||
tuning: DistanceFieldTuning = DistanceFieldTuning()
|
||||
) {
|
||||
val level = referenceEntity.level()
|
||||
if (level !is ServerLevel) return
|
||||
|
||||
val searchBox = AABB(
|
||||
center.x - horizontalRadius, center.y - verticalRange, center.z - horizontalRadius,
|
||||
center.x + horizontalRadius, center.y, center.z + horizontalRadius
|
||||
)
|
||||
|
||||
val primitives = collectPrimitivesFromBlocks(level, searchBox, shape, includeFluids = true)
|
||||
if (primitives.isEmpty()) return
|
||||
|
||||
var x = -horizontalRadius
|
||||
while (x <= horizontalRadius) {
|
||||
var z = -horizontalRadius
|
||||
while (z <= horizontalRadius) {
|
||||
marchColumn(level, primitives, center, x, z, verticalRange, marchStep, layers, tuning)
|
||||
z += sampleSpacing
|
||||
}
|
||||
x += sampleSpacing
|
||||
}
|
||||
}
|
||||
|
||||
private fun marchColumn(
|
||||
level: ServerLevel,
|
||||
primitives: List<FieldPrimitive>,
|
||||
center: Vec3,
|
||||
offsetX: Double,
|
||||
offsetZ: Double,
|
||||
verticalRange: Double,
|
||||
marchStep: Double,
|
||||
layers: List<DistanceFieldLayer>,
|
||||
tuning: DistanceFieldTuning
|
||||
) {
|
||||
val previousValue = FloatArray(layers.size) { Float.NaN }
|
||||
|
||||
var y = verticalRange
|
||||
while (y >= -verticalRange) {
|
||||
val worldX = (center.x + offsetX).toFloat()
|
||||
val worldY = (center.y + y).toFloat()
|
||||
val worldZ = (center.z + offsetZ).toFloat()
|
||||
val fieldValue = sampleField(primitives, worldX, worldY, worldZ, tuning)
|
||||
|
||||
for (layerIndex in layers.indices) {
|
||||
val layer = layers[layerIndex]
|
||||
val previous = previousValue[layerIndex]
|
||||
|
||||
if (!previous.isNaN() && crossedTarget(previous, fieldValue, layer.isoValue)) {
|
||||
val t = (layer.isoValue - previous) / (fieldValue - previous)
|
||||
val crossingY = (y + marchStep) - marchStep * t
|
||||
spawnLayerParticle(level, center.add(offsetX, crossingY, offsetZ), layer)
|
||||
}
|
||||
|
||||
previousValue[layerIndex] = fieldValue
|
||||
}
|
||||
|
||||
y -= marchStep
|
||||
}
|
||||
}
|
||||
|
||||
private fun crossedTarget(previous: Float, current: Float, target: Float): Boolean =
|
||||
(previous - target) * (current - target) < 0f
|
||||
|
||||
private fun spawnLayerParticle(level: ServerLevel, pos: Vec3, layer: DistanceFieldLayer) {
|
||||
val particle = DustParticleOptions(layer.color, layer.particleSize)
|
||||
level.sendParticles(particle, pos.x, pos.y, pos.z, 1, 0.0, 0.0, 0.0, 0.0)
|
||||
}
|
||||
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.api.spawning
|
||||
|
||||
import com.cobblemon.mod.common.api.spawning.TimeRange.Companion.timeRanges
|
||||
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import com.cobblemon.mod.common.util.adapters.IntRangesAdapter
|
||||
|
||||
/**
|
||||
@@ -49,4 +51,25 @@ class TimeRange : IntRanges {
|
||||
|
||||
constructor() : super()
|
||||
constructor(vararg ranges: IntRange) : super(*ranges)
|
||||
|
||||
fun displayName(): MutableComponent? {
|
||||
val key = timeRanges.entries.firstOrNull { entry -> this == entry.value }?.key ?: return null
|
||||
return lang("ui.time_range.$key")
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is TimeRange) return false
|
||||
|
||||
if (other.ranges.size != this.ranges.size) return false
|
||||
other.ranges.forEachIndexed { index, otherRange ->
|
||||
if (this.ranges[index] != otherRange) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return ranges.hashCode()
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -42,8 +42,8 @@ abstract class AreaTypeSpawningCondition<T : AreaSpawnablePosition> : SpawningCo
|
||||
override fun copyFrom(other: SpawningCondition<*>, merger: Merger) {
|
||||
super.copyFrom(other, merger)
|
||||
if (other is AreaTypeSpawningCondition) {
|
||||
merger.mergeSingle(minHeight, other.minHeight)
|
||||
merger.mergeSingle(maxHeight, other.maxHeight)
|
||||
minHeight = merger.mergeSingle(minHeight, other.minHeight)
|
||||
maxHeight = merger.mergeSingle(maxHeight, other.maxHeight)
|
||||
neededNearbyBlocks = merger.merge(neededNearbyBlocks, other.neededNearbyBlocks)?.toMutableList()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.spawning.condition
|
||||
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.world.level.Level
|
||||
|
||||
/**
|
||||
@@ -42,4 +44,7 @@ enum class MoonPhase {
|
||||
fun ofWorld(world: Level): MoonPhase = VALUES[world.moonPhase]
|
||||
}
|
||||
|
||||
fun displayName(): MutableComponent {
|
||||
return lang("ui.moon_phase.${this.name.lowercase()}")
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -46,7 +46,7 @@ abstract class SubmergedTypeSpawningCondition<T : SubmergedSpawnablePosition> :
|
||||
super.copyFrom(other, merger)
|
||||
if (other is SubmergedTypeSpawningCondition) {
|
||||
minDepth = merger.mergeSingle(minDepth, other.minDepth)
|
||||
maxDepth = merger.mergeSingle(minDepth, other.minDepth)
|
||||
maxDepth = merger.mergeSingle(maxDepth, other.maxDepth)
|
||||
fluidIsSource = merger.mergeSingle(fluidIsSource, other.fluidIsSource)
|
||||
fluid = merger.mergeSingle(fluid, other.fluid)
|
||||
}
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ abstract class SurfaceTypeSpawningCondition<T : SurfaceSpawnablePosition> : Area
|
||||
super.copyFrom(other, merger)
|
||||
if (other is SurfaceTypeSpawningCondition) {
|
||||
minDepth = merger.mergeSingle(minDepth, other.minDepth)
|
||||
maxDepth = merger.mergeSingle(minDepth, other.minDepth)
|
||||
maxDepth = merger.mergeSingle(maxDepth, other.maxDepth)
|
||||
fluid = merger.mergeSingle(fluid, other.fluid)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.AlphaLevelMatchingSensor
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.Entity
|
||||
|
||||
@@ -76,7 +77,7 @@ open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (
|
||||
?: return super.affectWeight(detail, spawnablePosition, weight)
|
||||
|
||||
val detailSpecies = pokemonDetail.pokemon.species
|
||||
?.let { PokemonSpecies.getByName(it) }
|
||||
?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
|
||||
?: return super.affectWeight(detail, spawnablePosition, weight)
|
||||
|
||||
val formData = detailSpecies.getForm(pokemonDetail.pokemon.aspects)
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.isBoxLoaded
|
||||
import com.cobblemon.mod.common.util.removeIf
|
||||
import com.cobblemon.mod.common.util.squeezeWithinBounds
|
||||
import com.cobblemon.mod.common.util.toBlockPos
|
||||
import com.cobblemon.mod.common.util.toVec3f
|
||||
import com.cobblemon.mod.common.util.weightedSelection
|
||||
import kotlin.math.max
|
||||
@@ -190,74 +191,61 @@ interface Spawner {
|
||||
}
|
||||
|
||||
fun isValidStartPoint(world: Level, chunk: ChunkAccess, startPos: BlockPos.MutableBlockPos): Boolean {
|
||||
val y = startPos.y
|
||||
if (!world.isLoaded(startPos) || !world.isLoaded(startPos.setY(y + 1))) {
|
||||
val abovePos = startPos.above()
|
||||
if (!world.isLoaded(startPos) || !world.isLoaded(abovePos)) {
|
||||
return false
|
||||
}
|
||||
val mid = chunk.getBlockState(startPos)
|
||||
val above = chunk.getBlockState(abovePos)
|
||||
|
||||
val mid = chunk.getBlockState(startPos.setY(y))
|
||||
val above = chunk.getBlockState(startPos.setY(y + 1))
|
||||
|
||||
// Above must be non-solid
|
||||
if (!above.isPathfindable(PathComputationType.AIR)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Position must be non-air
|
||||
if (mid.isAir) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return above.isPathfindable(PathComputationType.AIR) && mid.isAir
|
||||
}
|
||||
|
||||
fun constrainArea(area: SpawningZoneInput): SpawningZoneInput? {
|
||||
val basePos = BlockPos.MutableBlockPos(area.baseX, area.baseY, area.baseZ)
|
||||
val originalY = area.baseY
|
||||
|
||||
val (chunkX, chunkZ) = Pair(SectionPos.blockToSectionCoord(area.baseX), SectionPos.blockToSectionCoord(area.baseZ))
|
||||
val world = area.world
|
||||
val centerPos = area.getCenter().toBlockPos()
|
||||
|
||||
val (centerChunkX, centerChunkZ) = Pair(SectionPos.blockToSectionCoord(centerPos.x), SectionPos.blockToSectionCoord(centerPos.z))
|
||||
// if the chunk isn't loaded, we don't want to go further & we don't want the getChunk function below to load/create the chunk.
|
||||
if (!area.world.areEntitiesLoaded(ChunkPos.asLong(chunkX, chunkZ))) return null
|
||||
if (!world.areEntitiesLoaded(ChunkPos.asLong(centerChunkX, centerChunkZ))) return null
|
||||
|
||||
val chunk = area.world.getChunk(chunkX, chunkZ, ChunkStatus.FULL) ?: return null
|
||||
val chunk = world.getChunk(centerChunkX, centerChunkZ, ChunkStatus.FULL) ?: return null
|
||||
|
||||
var valid = isValidStartPoint(area.world, chunk, basePos)
|
||||
val probePos = centerPos.mutable()
|
||||
var validY = if (isValidStartPoint(world, chunk, probePos)) centerPos.y else null
|
||||
|
||||
if (!valid) {
|
||||
var offset = 1
|
||||
do {
|
||||
if (isValidStartPoint(area.world, chunk, basePos.setY(originalY + offset))) {
|
||||
valid = true
|
||||
basePos.y = originalY + offset
|
||||
if (validY == null) {
|
||||
for (offset in 1..Cobblemon.config.maxVerticalCorrectionBlocks) {
|
||||
if (isValidStartPoint(world, chunk, probePos.set(centerPos.x, centerPos.y + offset, centerPos.z))) {
|
||||
validY = centerPos.y + offset
|
||||
break
|
||||
} else if (isValidStartPoint(area.world, chunk, basePos.setY(originalY - offset))) {
|
||||
valid = true
|
||||
basePos.y = originalY - offset
|
||||
} else if (isValidStartPoint(world, chunk, probePos.set(centerPos.x, centerPos.y - offset, centerPos.z))) {
|
||||
validY = centerPos.y - offset
|
||||
break
|
||||
}
|
||||
offset++
|
||||
} while (offset <= Cobblemon.config.maxVerticalCorrectionBlocks)
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
val min = area.world.squeezeWithinBounds(basePos)
|
||||
val max = area.world.squeezeWithinBounds(basePos.move(area.length, area.height, area.width))
|
||||
if (area.world.isLoaded(min) && area.world.isLoaded(max) &&
|
||||
min.x < max.x && min.y < max.y && min.z < max.z
|
||||
) {
|
||||
return SpawningZoneInput(
|
||||
cause = area.cause,
|
||||
world = area.world,
|
||||
baseX = min.x,
|
||||
baseY = min.y,
|
||||
baseZ = min.z,
|
||||
length = max.x - min.x,
|
||||
height = max.y - min.y,
|
||||
width = max.z - min.z
|
||||
)
|
||||
}
|
||||
}
|
||||
val targetY = validY ?: return null
|
||||
val newBaseY = (targetY - (area.height / 2)).coerceIn(world.minBuildHeight, world.maxBuildHeight)
|
||||
val minPos = world.squeezeWithinBounds(BlockPos(area.baseX, newBaseY, area.baseZ))
|
||||
val maxPos = world.squeezeWithinBounds(BlockPos(area.baseX + area.length, newBaseY + area.height, area.baseZ + area.width))
|
||||
|
||||
val finalBox = AABB(
|
||||
minPos.x.toDouble(), minPos.y.toDouble(), minPos.z.toDouble(),
|
||||
maxPos.x.toDouble(), maxPos.y.toDouble(), maxPos.z.toDouble()
|
||||
)
|
||||
if (minPos.x < maxPos.x && minPos.y < maxPos.y && minPos.z < maxPos.z && world.isBoxLoaded(finalBox)) {
|
||||
return SpawningZoneInput(
|
||||
cause = area.cause,
|
||||
world = world,
|
||||
baseX = minPos.x,
|
||||
baseY = minPos.y,
|
||||
baseZ = minPos.z,
|
||||
length = maxPos.x - minPos.x,
|
||||
height = maxPos.y - minPos.y,
|
||||
width = maxPos.z - minPos.z
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ object CobblemonItemTags {
|
||||
@JvmField val DESTINY_KNOT = create("held/destiny_knot")
|
||||
@JvmField val EVERSTONE = create("held/everstone")
|
||||
@JvmField val EXPERIENCE_SHARE = create("held/experience_share")
|
||||
@JvmField val IS_FRIENDSHIP_BOOSTER = create("is_friendship_booster")
|
||||
@JvmField val IS_FRIENDSHIP_BOOSTER = create("held/is_friendship_booster")
|
||||
@JvmField val ANY_HELD_ITEM = create("held/is_held_item")
|
||||
@JvmField val BLACKLISTED_ITEMS_TO_HOLD = create("held/blacklisted_items_to_hold")
|
||||
@JvmField val WHITELISTED_ITEMS_TO_HOLD = create("held/whitelisted_items_to_hold")
|
||||
|
||||
@@ -34,6 +34,7 @@ class ElementalType(
|
||||
val resourceLocation: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, "ui/types.png"),
|
||||
val showdownId: String = ShowdownIdentifiable.REGEX.replace(name.lowercase(), ""),
|
||||
val typeGem: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, showdownId + "_gem"),
|
||||
val order: Int = 1000
|
||||
) : ShowdownIdentifiable {
|
||||
|
||||
override fun showdownId(): String {
|
||||
|
||||
@@ -25,7 +25,8 @@ object ElementalTypes {
|
||||
hue = 0xE8E8DA,
|
||||
textureXMultiplier = 0,
|
||||
primaryColor = 0x8FA689,
|
||||
secondaryColor = 0xFFFBE2
|
||||
secondaryColor = 0xFFFBE2,
|
||||
order = 0
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -35,7 +36,8 @@ object ElementalTypes {
|
||||
hue = 0xFF6E21,
|
||||
textureXMultiplier = 1,
|
||||
primaryColor = 0xF92D2D,
|
||||
secondaryColor = 0xF99B1D
|
||||
secondaryColor = 0xF99B1D,
|
||||
order = 1
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -45,7 +47,8 @@ object ElementalTypes {
|
||||
hue = 0x3FA5FF,
|
||||
textureXMultiplier = 2,
|
||||
primaryColor = 0x377AF6,
|
||||
secondaryColor = 0x50DDF9
|
||||
secondaryColor = 0x50DDF9,
|
||||
order = 2
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -55,7 +58,8 @@ object ElementalTypes {
|
||||
hue = 0x62D14F,
|
||||
textureXMultiplier = 3,
|
||||
primaryColor = 0x0EAE95,
|
||||
secondaryColor = 0xC6F554
|
||||
secondaryColor = 0xC6F554,
|
||||
order = 3
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -65,7 +69,8 @@ object ElementalTypes {
|
||||
hue = 0xFFD314,
|
||||
textureXMultiplier = 4,
|
||||
primaryColor = 0xF7AB19,
|
||||
secondaryColor = 0xFFF257
|
||||
secondaryColor = 0xFFF257,
|
||||
order = 4
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -75,7 +80,8 @@ object ElementalTypes {
|
||||
hue = 0x54F2F2,
|
||||
textureXMultiplier = 5,
|
||||
primaryColor = 0x41BAED,
|
||||
secondaryColor = 0x91F4F9
|
||||
secondaryColor = 0x91F4F9,
|
||||
order = 5
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -85,7 +91,8 @@ object ElementalTypes {
|
||||
hue = 0xEF565D,
|
||||
textureXMultiplier = 6,
|
||||
primaryColor = 0xC8356B,
|
||||
secondaryColor = 0xF36A3F
|
||||
secondaryColor = 0xF36A3F,
|
||||
order = 6
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -95,7 +102,8 @@ object ElementalTypes {
|
||||
hue = 0xD651FF,
|
||||
textureXMultiplier = 7,
|
||||
primaryColor = 0x7B41DD,
|
||||
secondaryColor = 0xE568F4
|
||||
secondaryColor = 0xE568F4,
|
||||
order = 7
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -105,7 +113,8 @@ object ElementalTypes {
|
||||
hue = 0xF4A453,
|
||||
textureXMultiplier = 8,
|
||||
primaryColor = 0xE08835,
|
||||
secondaryColor = 0xFFD057
|
||||
secondaryColor = 0xFFD057,
|
||||
order = 8
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -115,7 +124,8 @@ object ElementalTypes {
|
||||
hue = 0xB8B2FF,
|
||||
textureXMultiplier = 9,
|
||||
primaryColor = 0x6083F6,
|
||||
secondaryColor = 0xD5CCFA
|
||||
secondaryColor = 0xD5CCFA,
|
||||
order = 9
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -125,7 +135,8 @@ object ElementalTypes {
|
||||
hue = 0xFF5E9E,
|
||||
textureXMultiplier = 10,
|
||||
primaryColor = 0xE553CE,
|
||||
secondaryColor = 0xF69DD5
|
||||
secondaryColor = 0xF69DD5,
|
||||
order = 10
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -135,7 +146,8 @@ object ElementalTypes {
|
||||
hue = 0xD3D319,
|
||||
textureXMultiplier = 11,
|
||||
primaryColor = 0x9FBE1B,
|
||||
secondaryColor = 0xE9FB5B
|
||||
secondaryColor = 0xE9FB5B,
|
||||
order = 11
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -145,7 +157,8 @@ object ElementalTypes {
|
||||
hue = 0xB7A16E,
|
||||
textureXMultiplier = 12,
|
||||
primaryColor = 0x9F814A,
|
||||
secondaryColor = 0xEECB7A
|
||||
secondaryColor = 0xEECB7A,
|
||||
order = 12
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -155,7 +168,8 @@ object ElementalTypes {
|
||||
hue = 0x9C80F7,
|
||||
textureXMultiplier = 13,
|
||||
primaryColor = 0x4F57DD,
|
||||
secondaryColor = 0xC182F1
|
||||
secondaryColor = 0xC182F1,
|
||||
order = 13
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -165,7 +179,8 @@ object ElementalTypes {
|
||||
hue = 0x7580FF,
|
||||
textureXMultiplier = 14,
|
||||
primaryColor = 0x4E68E2,
|
||||
secondaryColor = 0x47C7F7
|
||||
secondaryColor = 0x47C7F7,
|
||||
order = 14
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -175,7 +190,8 @@ object ElementalTypes {
|
||||
hue = 0x587DA0,
|
||||
textureXMultiplier = 15,
|
||||
primaryColor = 0x465B99,
|
||||
secondaryColor = 0x689EBF
|
||||
secondaryColor = 0x689EBF,
|
||||
order = 15
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -185,7 +201,8 @@ object ElementalTypes {
|
||||
hue = 0xABD1F4,
|
||||
textureXMultiplier = 16,
|
||||
primaryColor = 0x628CB9,
|
||||
secondaryColor = 0xD6F7F3
|
||||
secondaryColor = 0xD6F7F3,
|
||||
order = 16
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -195,18 +212,21 @@ object ElementalTypes {
|
||||
hue = 0xFF7FE5,
|
||||
textureXMultiplier = 17,
|
||||
primaryColor = 0xE2678C,
|
||||
secondaryColor = 0xF5A7B7
|
||||
secondaryColor = 0xF5A7B7,
|
||||
order = 17
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
fun register(name: String, displayName: MutableComponent, hue: Int, textureXMultiplier: Int, primaryColor: Int, secondaryColor: Int): ElementalType {
|
||||
@JvmOverloads
|
||||
fun register(name: String, displayName: MutableComponent, hue: Int, textureXMultiplier: Int, primaryColor: Int, secondaryColor: Int, order: Int = 1000): ElementalType {
|
||||
return ElementalType(
|
||||
name = name,
|
||||
displayName = displayName,
|
||||
hue = hue,
|
||||
textureXMultiplier = textureXMultiplier,
|
||||
primaryColor = primaryColor,
|
||||
secondaryColor = secondaryColor
|
||||
secondaryColor = secondaryColor,
|
||||
order = order
|
||||
).also {
|
||||
allTypes.add(it)
|
||||
}
|
||||
|
||||
@@ -117,6 +117,9 @@ abstract class ShowdownActionResponse(val type: ShowdownActionResponseType) {
|
||||
|
||||
data class MoveActionResponse(var moveName: String, var targetPnx: String? = null, var gimmickID: String? = null): ShowdownActionResponse(ShowdownActionResponseType.MOVE) {
|
||||
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean): Boolean {
|
||||
// Ensure that the gimmick ID the player is sending is valid
|
||||
gimmickID?.let { if (it !in VALID_GIMMICK_IDS) return false }
|
||||
|
||||
if (forceSwitch || showdownMoveSet == null) {
|
||||
return false
|
||||
}
|
||||
@@ -168,6 +171,10 @@ data class MoveActionResponse(var moveName: String, var targetPnx: String? = nul
|
||||
gimmickID = buffer.readNullable { buffer.readString() }
|
||||
return this
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val VALID_GIMMICK_IDS = ShowdownMoveset.Gimmick.entries.map { it.id }.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
data class HealItemActionResponse(var item: String) : ShowdownActionResponse(ShowdownActionResponseType.FORCE_PASS) {
|
||||
@@ -183,7 +190,8 @@ data class HealItemActionResponse(var item: String) : ShowdownActionResponse(Sho
|
||||
}
|
||||
|
||||
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean): Boolean {
|
||||
return !forceSwitch
|
||||
// Previously checked for !forceSwitch, excepts in the case of a forceSwitch, this couldn't legally be sent anyway.
|
||||
return false
|
||||
}
|
||||
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?): String {
|
||||
|
||||
+11
-2
@@ -37,7 +37,6 @@ import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* Format: |move|POKEMON|MOVE|TARGET
|
||||
@@ -77,7 +76,7 @@ class MoveInstruction(
|
||||
val pokemonName = userPokemon.getName()
|
||||
ShowdownInterpreter.lastCauser[battle.battleId] = message
|
||||
// For Spread targets the message data only gives the pnx strings. So we can't know what pokemon are actually targeted until the previous messages have been interpreted
|
||||
val spreadTargetPokemon = spreadTargets.map { battle.activePokemon.firstOrNull() { poke -> poke.getPNX() == it }?.battlePokemon }
|
||||
val spreadTargetPokemon = spreadTargets.map { battle.activePokemon.firstOrNull { poke -> poke.getPNX() == it }?.battlePokemon }
|
||||
|
||||
val targetPokemonEntity = targetPokemon?.entity
|
||||
if (targetPokemonEntity != null) {
|
||||
@@ -137,10 +136,20 @@ class MoveInstruction(
|
||||
|
||||
val subsequentInstructions = instructionSet.findInstructionsCausedBy(this)
|
||||
val missedTargets = subsequentInstructions.filterIsInstance<MissInstruction>().mapNotNull { it.target }
|
||||
val faintedTargets = subsequentInstructions.filterIsInstance<FaintInstruction>().map { it.faintingPokemon }
|
||||
val failed = subsequentInstructions.any { it is FailInstruction }
|
||||
val failedTargets = subsequentInstructions.filterIsInstance<FailInstruction>().mapNotNull { it.target }
|
||||
val hitCountInstruction = subsequentInstructions.filterIsInstance<HitCountInstruction>().firstOrNull()
|
||||
|
||||
runtime.environment.query.addFunction("target_fainted") { params ->
|
||||
if (params.params.size == 0) {
|
||||
return@addFunction DoubleValue(faintedTargets.isNotEmpty())
|
||||
} else {
|
||||
val entityUUID = params.getString(0)
|
||||
return@addFunction DoubleValue(faintedTargets.any { it.entity?.stringUUID == entityUUID })
|
||||
}
|
||||
}
|
||||
|
||||
runtime.environment.query.addFunction("missed") { params ->
|
||||
if (params.params.size == 0) {
|
||||
return@addFunction DoubleValue(missedTargets.isNotEmpty())
|
||||
|
||||
@@ -83,7 +83,8 @@ open class BattlePokemon(
|
||||
"max_health" to Function { DoubleValue(maxHealth.toDouble()) },
|
||||
"ivs" to Function { effectedPokemon.ivs.struct },
|
||||
"nature" to Function { StringValue(effectedPokemon.nature.name.toString()) },
|
||||
"moveset" to Function { effectedPokemon.moveSet.toStruct() }
|
||||
"moveset" to Function { effectedPokemon.moveSet.toStruct() },
|
||||
"has_faced_opponents" to Function { DoubleValue(facedOpponents.isNotEmpty()) }
|
||||
))
|
||||
|
||||
val uuid: UUID
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block.decor
|
||||
|
||||
import com.cobblemon.mod.common.util.rotateShape
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.util.StringRepresentable
|
||||
import net.minecraft.world.InteractionResult
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.block.*
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.level.block.state.properties.BooleanProperty
|
||||
import net.minecraft.world.level.gameevent.GameEvent
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import net.minecraft.world.level.material.Fluids
|
||||
import net.minecraft.world.phys.BlockHitResult
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class ActivatableDecorationBlock(settings: Properties, val type: ActivatableDecorationBlockType) : HorizontalDirectionalBlock(settings), SimpleWaterloggedBlock {
|
||||
enum class ActivatableDecorationBlockType: StringRepresentable {
|
||||
//For basic use, also ignore camelcase errors, it's just dumb
|
||||
CENTERED_8x,
|
||||
//For specific items
|
||||
METRONOME;
|
||||
|
||||
override fun getSerializedName(): String = this.name.lowercase()
|
||||
|
||||
companion object {
|
||||
val CODEC = StringRepresentable.fromEnum(::values)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC: MapCodec<ActivatableDecorationBlock> = RecordCodecBuilder.mapCodec { it.group(
|
||||
propertiesCodec(),
|
||||
ActivatableDecorationBlockType.CODEC.fieldOf("type").forGetter(ActivatableDecorationBlock::type)
|
||||
).apply(it, ::ActivatableDecorationBlock) }
|
||||
val ACTIVE = BooleanProperty.create("active")
|
||||
|
||||
val CENTERED_8x = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.5, 0.75)
|
||||
val METRONOME_HITBOX = Shapes.box(0.25, 0.0, 0.375, 0.75, 0.8125, 0.75)
|
||||
|
||||
fun getShape(type: ActivatableDecorationBlockType): VoxelShape {
|
||||
return when (type) {
|
||||
ActivatableDecorationBlockType.METRONOME -> METRONOME_HITBOX
|
||||
else -> CENTERED_8x
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(HORIZONTAL_FACING, Direction.NORTH)
|
||||
.setValue(ACTIVE, false)
|
||||
.setValue(WATERLOGGED, false)
|
||||
)
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<ActivatableDecorationBlock> = CODEC
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(HORIZONTAL_FACING, ACTIVE, WATERLOGGED)
|
||||
}
|
||||
|
||||
override fun getFluidState(blockState: BlockState): FluidState? = if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState)
|
||||
|
||||
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
|
||||
if (state.getValue(WATERLOGGED)) {
|
||||
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
|
||||
}
|
||||
return if (state.getValue(HORIZONTAL_FACING).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
|
||||
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
|
||||
}
|
||||
|
||||
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
|
||||
val direction = state.getValue(HORIZONTAL_FACING)
|
||||
return rotateShape(Direction.NORTH, direction, getShape(type))
|
||||
}
|
||||
|
||||
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
|
||||
return getCollisionShape(state, level, pos, context).singleEncompassing()
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
|
||||
val fluidState = context.level.getFluidState(context.clickedPos)
|
||||
return super.getStateForPlacement(context)?.setValue(HORIZONTAL_FACING, context.horizontalDirection.opposite)?.setValue(WATERLOGGED, fluidState.type === Fluids.WATER)
|
||||
}
|
||||
|
||||
override fun useWithoutItem(
|
||||
state: BlockState,
|
||||
level: Level,
|
||||
pos: BlockPos,
|
||||
player: Player,
|
||||
hitResult: BlockHitResult
|
||||
): InteractionResult {
|
||||
if (level.isClientSide) {
|
||||
level.setBlockAndUpdate(pos, state.cycle(ACTIVE))
|
||||
return InteractionResult.SUCCESS
|
||||
} else {
|
||||
this.push(state, level, pos, null as Player?)
|
||||
return InteractionResult.CONSUME
|
||||
}
|
||||
}
|
||||
|
||||
fun push(state: BlockState, level: Level, pos: BlockPos?, player: Player?) {
|
||||
var state = state
|
||||
state = state.cycle(ACTIVE)
|
||||
level.setBlock(pos!!, state, 3)
|
||||
level.gameEvent(player, if (state.getValue(ACTIVE)) GameEvent.BLOCK_ACTIVATE else GameEvent.BLOCK_DEACTIVATE, pos)
|
||||
}
|
||||
|
||||
override fun hasAnalogOutputSignal(state: BlockState): Boolean {
|
||||
return true
|
||||
}
|
||||
//TODO: Replace this forced 4 with a value that can be set by the block, only relevant when more blocks of this class are added.
|
||||
override fun getAnalogOutputSignal(state: BlockState, level: Level, pos: BlockPos): Int {
|
||||
return if (level.getBlockState(pos).getValue(ACTIVE) as Boolean) 4 else 0
|
||||
}
|
||||
}
|
||||
Loaded 100 of 3306 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user