Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c346f84844 | ||
|
|
d1b8094539 | ||
|
|
b8d79af911 | ||
|
|
a7198fd324 | ||
|
|
bc69ce91f3 | ||
|
|
1de1d8cdc3 | ||
|
|
087bf75500 | ||
|
|
bac435ba7b | ||
|
|
74a6106eb6 | ||
|
|
5ec993ad3c | ||
|
|
73a03d90da | ||
|
|
e6fda6137e | ||
|
|
afae39acc8 | ||
|
|
37264aff0f | ||
|
|
2b60e6de77 | ||
|
|
4f29e7457c | ||
|
|
04a6ab31f4 | ||
|
|
a2cdc847dd | ||
|
|
0bca678f56 | ||
|
|
067c3607aa | ||
|
|
823c1f6444 | ||
|
|
bfc00635cd | ||
|
|
b2e5b85ac5 | ||
|
|
2c27476954 | ||
|
|
aab78d84fc | ||
|
|
cb632c2a65 | ||
|
|
8d219f1338 | ||
|
|
1e6a4ed984 | ||
|
|
64240c31fc | ||
|
|
d2222c3858 | ||
|
|
c7f31c3e14 | ||
|
|
740e846b2d | ||
|
|
246d00aa3f | ||
|
|
06f9d60595 | ||
|
|
d54439ce14 | ||
|
|
32ee4bb72a | ||
|
|
e7d2a46cc0 | ||
|
|
3ee94ec076 | ||
|
|
589293abc4 | ||
|
|
279865cc7c | ||
|
|
b597b149c3 | ||
|
|
b7f592f2a8 | ||
|
|
70e17c22e7 | ||
|
|
51b18ca0f6 | ||
|
|
a83f243a3b | ||
|
|
f5b088c708 | ||
|
|
dc454cbb68 | ||
|
|
3b99e38da2 | ||
|
|
483af57964 | ||
|
|
a941bdddf3 | ||
|
|
246ba4a93b | ||
|
|
3347c084ed | ||
|
|
8175cee454 | ||
|
|
37d54053a5 | ||
|
|
b8846bd74c | ||
|
|
1716e135c4 | ||
|
|
0ac804cfcb | ||
|
|
aecc132814 | ||
|
|
8b47876dff | ||
|
|
ffc108953a | ||
|
|
0a106174fe | ||
|
|
90210bee3c | ||
|
|
8603cd4ad0 | ||
|
|
7b2c6827c2 | ||
|
|
902b4a6d21 | ||
|
|
a847cb5926 | ||
|
|
050c6f5adc | ||
|
|
fb95fb3cf7 | ||
|
|
d507367411 | ||
|
|
faa19cebcb | ||
|
|
aced374367 | ||
|
|
d424a6f1d3 | ||
|
|
71bb1c32de | ||
|
|
f726fcedb9 | ||
|
|
77344ccc2f | ||
|
|
90bc16be90 | ||
|
|
d1b743a01f | ||
|
|
aed651c0a0 | ||
|
|
4c477e1675 | ||
|
|
d1cdc6789c | ||
|
|
cd3127b983 | ||
|
|
68f5eff048 | ||
|
|
c3d2918a2b | ||
|
|
0432525c06 | ||
|
|
a60f27e282 | ||
|
|
5f0182cb7e | ||
|
|
4a3ae9407b | ||
|
|
766c2838d2 | ||
|
|
4cb95589b4 | ||
|
|
a8d3c2dd52 | ||
|
|
cb5d7b6ad7 | ||
|
|
c7dcc64595 | ||
|
|
6573e08da7 | ||
|
|
cd695ade4a | ||
|
|
55cb413cfc | ||
|
|
4f1bf7fcbb | ||
|
|
154ee80ead | ||
|
|
1c9f9bb198 | ||
|
|
6836343f11 | ||
|
|
aa0d1cd943 | ||
|
|
798f1da8b3 | ||
|
|
d98efb52b6 | ||
|
|
2f27a03f08 | ||
|
|
e33025b1fa | ||
|
|
ade2481f42 | ||
|
|
576e2301ce | ||
|
|
a568d45d75 | ||
|
|
bc19a7e4c1 | ||
|
|
616e438f17 | ||
|
|
9bb11c46d5 | ||
|
|
5f470bb703 | ||
|
|
276f13cea5 | ||
|
|
ac069687f1 | ||
|
|
f248c22eed | ||
|
|
65a5687f92 | ||
|
|
ea50fa534b | ||
|
|
4bd3c4af58 | ||
|
|
283d80f7ed | ||
|
|
0238ca80f0 | ||
|
|
d24c5fe874 | ||
|
|
e65e21f2ff | ||
|
|
e1d417718f | ||
|
|
d4308b0a4f | ||
|
|
bc0d530484 | ||
|
|
56650418d2 | ||
|
|
0543b84d34 | ||
|
|
1f50687a59 | ||
|
|
ed04faf0d8 | ||
|
|
1ed020f3e0 | ||
|
|
7e385d8c94 | ||
|
|
ee509b5438 | ||
|
|
ac9394131c | ||
|
|
d058a306a7 | ||
|
|
b9be0c3fcb | ||
|
|
b59b46568a | ||
|
|
408cbc8c49 | ||
|
|
1537816132 | ||
|
|
1705639950 | ||
|
|
ce32541dd6 | ||
|
|
3ffea4f4dd | ||
|
|
f942d7c34a | ||
|
|
0f1ada4624 | ||
|
|
b4ee4629bf | ||
|
|
d0b64e2d82 | ||
|
|
a8ce3c2175 | ||
|
|
80ae127379 | ||
|
|
bd3a9fb3af | ||
|
|
d0eaccb81b | ||
|
|
1ed2042283 | ||
|
|
0b531d967e | ||
|
|
b0d1aec9ab | ||
|
|
f4d47098b5 | ||
|
|
1989b8aa92 | ||
|
|
948ca61ef3 | ||
|
|
e60aca30ba | ||
|
|
fb5055c39e | ||
|
|
dfb5650121 | ||
|
|
8a0cbf28fe | ||
|
|
a6b9060d57 | ||
|
|
8e57995e44 | ||
|
|
1d8257808c | ||
|
|
e290fe9c75 | ||
|
|
06a50cfeeb | ||
|
|
00888c8ad5 | ||
|
|
e3582ccfb4 | ||
|
|
4c6822b1c6 | ||
|
|
2e6a4c7341 | ||
|
|
8edb5e1013 | ||
|
|
6fb139cbb9 | ||
|
|
c81c95142b | ||
|
|
afde822860 | ||
|
|
474c386535 | ||
|
|
3963e83717 | ||
|
|
7893e365da | ||
|
|
71b9ae8dee | ||
|
|
b87b47d62a | ||
|
|
2bb85718d7 | ||
|
|
ee187d7b89 | ||
|
|
b6a7051b2c | ||
|
|
3532cac278 | ||
|
|
1447ab25b4 | ||
|
|
9a06ca78c6 | ||
|
|
f6ba70634b | ||
|
|
148fd04a93 | ||
|
|
17f60623ee | ||
|
|
c5ece27c11 | ||
|
|
296ccd1a76 | ||
|
|
82a4f71f24 | ||
|
|
2cb1cf2f83 | ||
|
|
f13a4e0e24 | ||
|
|
17e1bbd6a1 | ||
|
|
c36ff0a0fe | ||
|
|
08b58e2f78 | ||
|
|
62d2b8db2d | ||
|
|
3869f7ed93 | ||
|
|
8dbb350230 | ||
|
|
c9c44872d6 | ||
|
|
cae53efedc | ||
|
|
52f9ffb1f6 | ||
|
|
5fac24cee8 | ||
|
|
1954dfec80 | ||
|
|
2eea2b64c2 | ||
|
|
a127a93be8 | ||
|
|
9b32f85817 | ||
|
|
ec139d2677 | ||
|
|
4327be0cf9 | ||
|
|
c25c832b31 | ||
|
|
eabaabc535 | ||
|
|
9acf97f5a0 | ||
|
|
24fc212c76 | ||
|
|
04f88bdd63 | ||
|
|
a4eb58aa57 | ||
|
|
030195e3ff | ||
|
|
d21a52ae68 | ||
|
|
3c1f1dbacb | ||
|
|
6704d4e5ee | ||
|
|
787c4851ff | ||
|
|
bf6e7fafd3 | ||
|
|
ea6df764c0 | ||
|
|
f242de272d | ||
|
|
5232e2e9bc | ||
|
|
ca35898243 | ||
|
|
869457d235 | ||
|
|
6736c8160b | ||
|
|
243c50550d | ||
|
|
fe7d3ef23a | ||
|
|
57ad77fc39 | ||
|
|
d70e7bd1b3 | ||
|
|
254cc931ea | ||
|
|
01fe5cffe8 | ||
|
|
3e18995a37 | ||
|
|
ff161c9c9b | ||
|
|
6b7b4f326d | ||
|
|
f50f7dec29 | ||
|
|
a5def20c58 | ||
|
|
b23204f9c9 | ||
|
|
4972de0aaa | ||
|
|
58a1f3d7b4 | ||
|
|
f516edb858 | ||
|
|
161aa952fa | ||
|
|
3b26a13ad6 | ||
|
|
014189217a | ||
|
|
978debb40c | ||
|
|
5be707811e | ||
|
|
ac44a98f8a | ||
|
|
03ecc2ae8d | ||
|
|
1518379107 | ||
|
|
a58c8c59e1 | ||
|
|
37959b5048 | ||
|
|
cbcc9fc6f1 | ||
|
|
d236aaa7da | ||
|
|
a2cd5a671c | ||
|
|
1d6a45bf4d | ||
|
|
5bfe703ed7 | ||
|
|
fc8485388e | ||
|
|
a5593b3131 | ||
|
|
9541bf3267 | ||
|
|
1037fe0a0e | ||
|
|
a6eea3198a | ||
|
|
b28e2313a8 | ||
|
|
0f0b4e6c86 | ||
|
|
e033467054 | ||
|
|
0f092bf6bf | ||
|
|
66ba762360 | ||
|
|
c7c11838a0 | ||
|
|
301d80fabe | ||
|
|
c88087de29 | ||
|
|
fade5502d9 | ||
|
|
86bb802140 | ||
|
|
6b644b384d | ||
|
|
c370af8914 | ||
|
|
1cd6d2bfc7 | ||
|
|
fc720916fb | ||
|
|
ce55035408 | ||
|
|
d4665ddaeb | ||
|
|
4375c6f667 | ||
|
|
1edeae2a60 | ||
|
|
409f1056b9 | ||
|
|
d8c470db3d | ||
|
|
c642c4b84b | ||
|
|
0b80f7e547 | ||
|
|
d543f45607 | ||
|
|
e5699c1cdc | ||
|
|
f4a7c6e873 | ||
|
|
2b4fdfd219 | ||
|
|
3bef1dad4a | ||
|
|
da50ee59cf | ||
|
|
1a3b655d3f | ||
|
|
1f5f278970 | ||
|
|
0b4eb5239a | ||
|
|
118e8f6205 | ||
|
|
cf126c5906 | ||
|
|
645cf61911 | ||
|
|
6e8985d97e | ||
|
|
19c4c0d50d | ||
|
|
b23110950e | ||
|
|
60c213c44b | ||
|
|
87d5ede3dd | ||
|
|
203e5d89cd | ||
|
|
ce611cc61c | ||
|
|
3df8dea71b | ||
|
|
3cca50b7cc | ||
|
|
1ff5622694 | ||
|
|
f6ad7ff865 | ||
|
|
d69627a9c8 | ||
|
|
411f388bd3 | ||
|
|
5b8d35604a | ||
|
|
619d002bc4 | ||
|
|
7049656f64 | ||
|
|
e6e7c2982d | ||
|
|
54e8763470 | ||
|
|
db34687df0 | ||
|
|
7f008d85ff | ||
|
|
06ebf51078 | ||
|
|
39c5bcd73c | ||
|
|
8f5f158b60 | ||
|
|
3bc1e35125 | ||
|
|
460bedddae | ||
|
|
8b64c315f3 | ||
|
|
7c824a2b72 | ||
|
|
f197ac5077 | ||
|
|
e0be669482 | ||
|
|
82686b0511 | ||
|
|
f4eca30163 | ||
|
|
2814183012 | ||
|
|
bbc3c2a8d6 | ||
|
|
7eeb7feeae | ||
|
|
b2f850c1c8 | ||
|
|
08ea21fff4 | ||
|
|
9dac604a93 | ||
|
|
23db346070 | ||
|
|
d3b7d739bb | ||
|
|
28c0f8ab7d | ||
|
|
fcbe0db568 | ||
|
|
83cefe476e | ||
|
|
690ccbd260 | ||
|
|
63846d53fb | ||
|
|
86cb865742 | ||
|
|
1a0c9b6a82 | ||
|
|
985eb103fe | ||
|
|
aec76a78da | ||
|
|
eb6c134f4b | ||
|
|
17f3edf2be | ||
|
|
9c5bfda20f | ||
|
|
aa0b8d88e8 | ||
|
|
6d545fc1ca | ||
|
|
9a8ad27122 | ||
|
|
9f1dbc9ff0 | ||
|
|
99942748b0 | ||
|
|
64c34230bf | ||
|
|
36881b70be | ||
|
|
3cf9425247 | ||
|
|
9b052478f6 | ||
|
|
339f8339f0 | ||
|
|
db9a802c3f | ||
|
|
b5578e72bd | ||
|
|
e5666e7a82 | ||
|
|
3aea0909a4 | ||
|
|
5adef3336e | ||
|
|
1a96b418bc | ||
|
|
e373e86b60 | ||
|
|
6f3035184c | ||
|
|
7ae8b1957c | ||
|
|
a588213323 | ||
|
|
56b2ba08ad | ||
|
|
824b9718ee | ||
|
|
8540a73885 | ||
|
|
7e6ca7be5e | ||
|
|
ef43f6c087 | ||
|
|
c8a70caf79 | ||
|
|
25ce784a60 | ||
|
|
5358cccb62 | ||
|
|
ba11758e1f | ||
|
|
f0412d6b1d | ||
|
|
c43b3bbb4d | ||
|
|
d826aeeb07 | ||
|
|
f90c6967ce | ||
|
|
f5f87ce1a3 | ||
|
|
f28b269734 | ||
|
|
d545efd843 | ||
|
|
b9cf18adbd | ||
|
|
30650e1f9f | ||
|
|
eaa558a7c8 | ||
|
|
0dc725e513 | ||
|
|
f8682c2123 | ||
|
|
f62e31a1eb | ||
|
|
ea623b08a6 | ||
|
|
a575c6bc3d | ||
|
|
9c102a7850 | ||
|
|
22a1eaab19 | ||
|
|
0118ace5fc | ||
|
|
d4a93abbf7 | ||
|
|
ce3d5a413c | ||
|
|
d0ee3f3e9d | ||
|
|
698fa9dff9 | ||
|
|
d23fd6e23c | ||
|
|
46ae53f565 | ||
|
|
7d4e5892ba | ||
|
|
f9de415020 | ||
|
|
59395a254d | ||
|
|
5d7c6c8d6f | ||
|
|
4d123b6e4b | ||
|
|
54dc2e7f6f | ||
|
|
1d60ba093f | ||
|
|
0fef74dce7 | ||
|
|
6529293d21 | ||
|
|
a1fce624ea | ||
|
|
b08647bb32 | ||
|
|
de9e89f040 | ||
|
|
84a29ebc2a | ||
|
|
37b13ae959 | ||
|
|
1cacbe8306 | ||
|
|
12d224d15a | ||
|
|
a57eee3afc | ||
|
|
b7773f7471 | ||
|
|
57595c999c | ||
|
|
83615b7eb3 | ||
|
|
c53c544a8b | ||
|
|
c7c272c79c | ||
|
|
c759161367 | ||
|
|
19b7b4804f | ||
|
|
3a005cc813 | ||
|
|
a8e16efa48 | ||
|
|
c3ddfce0a7 | ||
|
|
9500f7a1f3 | ||
|
|
83a8d92e3f | ||
|
|
e105c5d4d7 | ||
|
|
3d816f57e2 | ||
|
|
1e6dcfba9b | ||
|
|
e59d755d32 | ||
|
|
f927e47bfe | ||
|
|
48a37b2018 | ||
|
|
c155b5575d | ||
|
|
9c6b8b231a | ||
|
|
92b404dac4 | ||
|
|
8a1c536d68 | ||
|
|
04719d22a5 | ||
|
|
dacc312756 | ||
|
|
ee4a3d46ba | ||
|
|
80b11f1c3d | ||
|
|
ddcf4eea7b | ||
|
|
6a0681e4c6 | ||
|
|
8ebb378da7 | ||
|
|
e92a878074 | ||
|
|
0b97b41c08 | ||
|
|
20fe732cfc | ||
|
|
0575aa29d9 | ||
|
|
29b560d244 | ||
|
|
dcc98b4a54 | ||
|
|
d3ce455391 | ||
|
|
85c6b633d8 | ||
|
|
956b599060 | ||
|
|
f88552f99b | ||
|
|
e2b559f3eb | ||
|
|
39816d33b6 | ||
|
|
0cf2a23fd4 | ||
|
|
c4d8eae47e | ||
|
|
2a5a292386 | ||
|
|
f6b2da6ed6 | ||
|
|
e8a09f5c48 | ||
|
|
e82193cdfd | ||
|
|
87e7cdebf6 | ||
|
|
9ca84d852f | ||
|
|
8194f93d13 | ||
|
|
c158ec33fa | ||
|
|
1e27c7494e | ||
|
|
54a1f8d932 | ||
|
|
275779b20e | ||
|
|
635f29e1ac | ||
|
|
db2001b60a | ||
|
|
b5098a2ad0 | ||
|
|
2102f8ff88 | ||
|
|
d2af0c9332 | ||
|
|
8c939ee276 | ||
|
|
645b8993c8 | ||
|
|
d96799cac7 | ||
|
|
b822b626c4 | ||
|
|
4d9b182fb3 | ||
|
|
88d155eaf4 | ||
|
|
af96469b7a | ||
|
|
65f1e7a3be | ||
|
|
379a566d71 | ||
|
|
f30ff739ed | ||
|
|
16cd2de02f | ||
|
|
22a44bd7e9 | ||
|
|
d37bd4ef03 | ||
|
|
a3d9a10f2e | ||
|
|
7801e3feee | ||
|
|
4a54cb4674 | ||
|
|
a8949cf1f9 | ||
|
|
24fe1c125c | ||
|
|
61db6334f9 | ||
|
|
023160f87a | ||
|
|
c45e34648c | ||
|
|
17fbf7c6f0 | ||
|
|
ec86b7d4bf | ||
|
|
7561eb116f | ||
|
|
6467b97a23 | ||
|
|
6d5909a1e3 | ||
|
|
2bb0d43ca8 | ||
|
|
1fb3f0ebdc | ||
|
|
d56586ecac | ||
|
|
fb2154e2c3 | ||
|
|
3b14d19fdb | ||
|
|
6840c2fdef | ||
|
|
8fc2031806 | ||
|
|
9df8be33e9 | ||
|
|
4795e2e1bc | ||
|
|
c563627b05 | ||
|
|
73ebf7a789 | ||
|
|
a1b3d3d2ca | ||
|
|
8baacbcf90 | ||
|
|
f2e357b749 | ||
|
|
ff2afd546e | ||
|
|
b95900b29a | ||
|
|
b8b7756a45 | ||
|
|
3d6146f551 | ||
|
|
03585fafb0 | ||
|
|
31d81db243 | ||
|
|
4c36db8685 | ||
|
|
e3d0252d75 | ||
|
|
dbe8e57341 | ||
|
|
6f37fd69cd | ||
|
|
b191f4e9fb | ||
|
|
a5a7cc20ba | ||
|
|
02b6c46082 | ||
|
|
881a30cca4 | ||
|
|
d01011f4d0 | ||
|
|
309ed379c7 | ||
|
|
edbf667f45 | ||
|
|
b6bc766adf | ||
|
|
1ecf556cf2 | ||
|
|
6d32073604 | ||
|
|
7cedfb9047 | ||
|
|
a7ab2e37c2 | ||
|
|
4723f91500 | ||
|
|
aa0f5d8a05 | ||
|
|
6202c14215 | ||
|
|
e23aa6b066 | ||
|
|
b0790341c7 | ||
|
|
4f0fa6af4a | ||
|
|
4e24e09924 | ||
|
|
48676067b7 | ||
|
|
6432db9f7d | ||
|
|
2cb28fb384 | ||
|
|
3b59148151 | ||
|
|
d6198631ba | ||
|
|
47daea6195 | ||
|
|
aca66a4bb0 | ||
|
|
e9fe1774c0 | ||
|
|
bb8b8a77c9 | ||
|
|
c692b6b5c1 | ||
|
|
68c6d5d9b2 | ||
|
|
221547aabf | ||
|
|
f6b3591d80 | ||
|
|
f9878d6137 | ||
|
|
94da3b8d21 | ||
|
|
3a2ff9de75 | ||
|
|
b249896316 | ||
|
|
1cb6979b90 | ||
|
|
e983d2c1db | ||
|
|
9ee0cf941b | ||
|
|
8436c93317 | ||
|
|
7f0329acb7 | ||
|
|
83489b7230 | ||
|
|
081a25f04b | ||
|
|
38bdfae0db | ||
|
|
bd3a9e30f1 | ||
|
|
7ce968f467 | ||
|
|
9ac8cc56f0 | ||
|
|
98159e1e0e | ||
|
|
025b5aae74 | ||
|
|
2fddb21585 | ||
|
|
6b75976236 | ||
|
|
fdf419ec8f | ||
|
|
d2ceb2d58a | ||
|
|
6d8311a66c | ||
|
|
18a3d1c22c | ||
|
|
b382a1b2db | ||
|
|
dd17fa138e | ||
|
|
09dd623ea7 | ||
|
|
8c01bb831f | ||
|
|
2cb4372496 | ||
|
|
7597b2d98e | ||
|
|
5ac2729a77 | ||
|
|
bec1fca7a5 | ||
|
|
32d145f3cc | ||
|
|
a5f1a14e21 | ||
|
|
57784b8620 | ||
|
|
a29b3342a1 | ||
|
|
64d6560caf | ||
|
|
2e314e48fb | ||
|
|
e8d1e3766b | ||
|
|
012682268e | ||
|
|
38428e4ea9 | ||
|
|
567132bc0c | ||
|
|
e7209e4000 | ||
|
|
273b520c0b | ||
|
|
0f02313a14 | ||
|
|
3249e4bbb1 | ||
|
|
36aca65003 | ||
|
|
c6635213a2 | ||
|
|
bf6fab3936 | ||
|
|
b76158d1d7 | ||
|
|
916ec97fce | ||
|
|
36b38d36cb | ||
|
|
f1163977e1 | ||
|
|
a7dd6d52c8 | ||
|
|
6898804e83 | ||
|
|
2331603b27 | ||
|
|
c8e9cbbe59 | ||
|
|
d374635648 | ||
|
|
803d3be325 | ||
|
|
50bacbe25e | ||
|
|
94ef9830b7 | ||
|
|
87502f36c4 | ||
|
|
bed511d403 | ||
|
|
4170b00e31 | ||
|
|
3cc90daaf0 | ||
|
|
b0ecd24f42 | ||
|
|
ade5603331 | ||
|
|
34bcefb09f | ||
|
|
dccc9dfbcb | ||
|
|
9a1d6461d9 | ||
|
|
2dc76ffe3e | ||
|
|
87225d8981 | ||
|
|
4bf4101fd1 | ||
|
|
58c6ea0269 | ||
|
|
034bd2eec6 | ||
|
|
a24be04d68 | ||
|
|
0d878d57b7 | ||
|
|
08880e2294 | ||
|
|
1c45654930 | ||
|
|
dae3acb5a0 | ||
|
|
7bb6f8a12e | ||
|
|
b25f3ca3f6 | ||
|
|
610b762188 | ||
|
|
c4c954c898 | ||
|
|
10b70a0e3f | ||
|
|
af437f9d3e | ||
|
|
ca5a96e979 | ||
|
|
a2936ebcff | ||
|
|
c3d7537b3e | ||
|
|
81e5d830c6 | ||
|
|
4b09714ed5 | ||
|
|
fa4a4c7e51 | ||
|
|
493b8bbecf | ||
|
|
9b519daff1 | ||
|
|
a5ba3e7084 | ||
|
|
6d2d146543 | ||
|
|
22d93cb92c | ||
|
|
31c55a9b6f | ||
|
|
5281dbd81c | ||
|
|
8b863070fe | ||
|
|
da1064772d | ||
|
|
8ca1957a60 | ||
|
|
cbab2b7e5c | ||
|
|
c47bd604d5 | ||
|
|
63776ca7a0 | ||
|
|
c66c7170f6 | ||
|
|
e9e4158666 | ||
|
|
b15917898f | ||
|
|
ef3179bfa2 | ||
|
|
f0afb8b3d9 | ||
|
|
17e58b0ce5 | ||
|
|
fb0bfb2d19 | ||
|
|
0d87de157a | ||
|
|
4189da0cc7 | ||
|
|
662ccdc268 | ||
|
|
6779995680 | ||
|
|
c4b6cc7c00 | ||
|
|
07ef404518 | ||
|
|
4c8a8351ac | ||
|
|
17f5ed03d0 | ||
|
|
a3d3c987bc | ||
|
|
6db0567f03 | ||
|
|
3e89499588 | ||
|
|
7982034964 | ||
|
|
8f462aab40 | ||
|
|
ac3628d907 | ||
|
|
4a1e0020f7 | ||
|
|
3c1486e45b | ||
|
|
4563770835 | ||
|
|
c49eed3aaf | ||
|
|
cc80f69c47 | ||
|
|
128366f9c2 | ||
|
|
1d158ae12c | ||
|
|
ddd63ac841 | ||
|
|
118a682451 | ||
|
|
d202efc0a9 | ||
|
|
81a1314493 | ||
|
|
d20673d9bf | ||
|
|
4634dc7539 | ||
|
|
c2db648d25 | ||
|
|
76f1ace6b7 | ||
|
|
a316329359 | ||
|
|
4aa1b804bc | ||
|
|
233d27413d | ||
|
|
05621ccb51 | ||
|
|
ea59981b58 | ||
|
|
960460ec08 | ||
|
|
8d587c5287 | ||
|
|
e0b79c9bab | ||
|
|
6206330b76 | ||
|
|
8e7a2a0356 | ||
|
|
7657cf4f73 | ||
|
|
7b137fbe14 | ||
|
|
46d278ca55 | ||
|
|
4d472d0b36 | ||
|
|
339038956d | ||
|
|
fd442211d3 | ||
|
|
144f857a3b | ||
|
|
80131e57f8 | ||
|
|
30d89a2704 | ||
|
|
5e147545b7 | ||
|
|
e962079cda | ||
|
|
6f934cfbd4 | ||
|
|
47ecef81a6 | ||
|
|
16762ae933 | ||
|
|
ba47f6a833 | ||
|
|
522ffefd0a | ||
|
|
e17ca360df | ||
|
|
a559737106 | ||
|
|
e18d5837af | ||
|
|
65ae67d9ac | ||
|
|
05c77a6156 | ||
|
|
8ae6792785 | ||
|
|
5f67fc167a | ||
|
|
8dd62269ed | ||
|
|
0eee6e8634 | ||
|
|
a16a7c31fe | ||
|
|
a4254df0ca | ||
|
|
21df52daf7 | ||
|
|
5e81e26c69 | ||
|
|
116a93d8cc | ||
|
|
acebc35d3f | ||
|
|
d103629fd2 | ||
|
|
6e2a0cd3b9 | ||
|
|
77490783e4 | ||
|
|
ed28c70c5a | ||
|
|
3f71584a6e | ||
|
|
4f09e56510 | ||
|
|
b534183a50 | ||
|
|
56ea6c7161 | ||
|
|
37f6010381 | ||
|
|
798dd64f84 | ||
|
|
c1acf8494c | ||
|
|
d4601326a7 | ||
|
|
a037af6948 | ||
|
|
976c94cb6b | ||
|
|
422154e4c7 | ||
|
|
517ac94b8d | ||
|
|
b5ec2b28fd | ||
|
|
f45a4b784b | ||
|
|
91dda046d8 | ||
|
|
71fabbd245 | ||
|
|
fa0f29b085 | ||
|
|
49577d2518 | ||
|
|
12de6750de | ||
|
|
4e1b9785a0 | ||
|
|
2f2de32493 | ||
|
|
a38b68ce23 | ||
|
|
7d605f8bf9 | ||
|
|
75134f41e3 | ||
|
|
2bad8ec673 | ||
|
|
1524e9c476 | ||
|
|
c8ad0cd7e7 | ||
|
|
33a4e31b60 | ||
|
|
250724ef1f | ||
|
|
3e1837543d | ||
|
|
1526c2f037 | ||
|
|
408cf8c5ca | ||
|
|
ceb70dbf2d | ||
|
|
6d46992ad7 | ||
|
|
84242194ba | ||
|
|
529e4bef46 | ||
|
|
aa1658bb5d | ||
|
|
068df2b138 | ||
|
|
3499758203 | ||
|
|
7d97df3407 | ||
|
|
1add7ea4cb | ||
|
|
e497cd445a | ||
|
|
22b0e0612a | ||
|
|
e52c63d565 | ||
|
|
fe7f72cb3d | ||
|
|
8187905987 | ||
|
|
11b3043e6c | ||
|
|
8d871ac949 | ||
|
|
8f7a523404 | ||
|
|
bb98992635 | ||
|
|
d3984843e2 | ||
|
|
7a14c51df2 | ||
|
|
a7b023203f | ||
|
|
e382e89e06 | ||
|
|
faf64a6092 | ||
|
|
2b64174cfe | ||
|
|
918282893c | ||
|
|
dbbca02619 | ||
|
|
c50d5841d8 | ||
|
|
5489cfb4b0 | ||
|
|
e421de547f | ||
|
|
aa8d0a9d48 | ||
|
|
075dcdb9e9 | ||
|
|
2ebe8d6434 | ||
|
|
d7c77f5f3f | ||
|
|
7a8dbbb4ce | ||
|
|
b8e1abc80b | ||
|
|
12df9d29f5 | ||
|
|
2df0542631 | ||
|
|
e22b79a47c | ||
|
|
572682a23a | ||
|
|
6241865f95 | ||
|
|
67dbda3113 | ||
|
|
c50d3183e6 | ||
|
|
9eae394ff5 | ||
|
|
76b640984c | ||
|
|
72d142a781 | ||
|
|
af1df22e4d | ||
|
|
66787bfea4 | ||
|
|
c113e68488 | ||
|
|
8284b04426 | ||
|
|
0c4bbef855 | ||
|
|
6fb34b5ee3 | ||
|
|
f345b19d57 | ||
|
|
f1bdaad97d | ||
|
|
f725e5fcba | ||
|
|
2237e1eeca | ||
|
|
bb54f8414e | ||
|
|
99c7d39705 | ||
|
|
bdd27078b5 | ||
|
|
7c3fdbc921 | ||
|
|
9596f08957 | ||
|
|
d9433960cd | ||
|
|
89b6fd56b6 | ||
|
|
10c441e3cd | ||
|
|
461b22efa8 | ||
|
|
57a19909aa | ||
|
|
dc1473919e | ||
|
|
ee20986ee0 | ||
|
|
436aa5ea75 | ||
|
|
e49f761417 | ||
|
|
95e3ab213a | ||
|
|
0c33de61b9 | ||
|
|
91a3fb5031 | ||
|
|
3a46b2c218 | ||
|
|
2f631ddc64 | ||
|
|
2a2ef6ff7d | ||
|
|
d3d94a3614 | ||
|
|
f34cc3072b | ||
|
|
68bff6a40b | ||
|
|
d1a99608a5 | ||
|
|
2a6381d610 | ||
|
|
93dba4f3b8 | ||
|
|
a7e7c2e814 | ||
|
|
6bf62b3f9d | ||
|
|
00be470281 | ||
|
|
57afbe79c2 | ||
|
|
d5f336ce73 | ||
|
|
b79a0b1b6f | ||
|
|
57721d1696 | ||
|
|
b0711528d6 | ||
|
|
7e968fcff9 | ||
|
|
e399130fda | ||
|
|
0083e54f6a | ||
|
|
9512252b72 | ||
|
|
73028d4c2a | ||
|
|
2f5910c436 | ||
|
|
d1197cbc0b | ||
|
|
2ccf699a86 | ||
|
|
d767420814 | ||
|
|
77100300b1 | ||
|
|
5c80fc03e2 | ||
|
|
aa39bb84fe | ||
|
|
a767b3d0fe | ||
|
|
3dbcbc34d0 | ||
|
|
0bdadb3794 | ||
|
|
05b5782cd5 | ||
|
|
0c220a8990 | ||
|
|
da787bf9bf | ||
|
|
57a839d000 | ||
|
|
e77e49c8b1 | ||
|
|
ce6d142bd4 | ||
|
|
f2db63164c | ||
|
|
604b4d39ea | ||
|
|
5d3741f2e5 | ||
|
|
5e21ac94dc | ||
|
|
1d055253d2 | ||
|
|
d56c5520f9 | ||
|
|
89bc893f06 | ||
|
|
acc8d066e5 | ||
|
|
6608268117 | ||
|
|
bdecab25a6 | ||
|
|
81ed97a68b | ||
|
|
1e051deb9e | ||
|
|
38c54e255a | ||
|
|
e7ad38d1df | ||
|
|
67c0abea8f | ||
|
|
85c1543df1 | ||
|
|
f38a1f83e2 | ||
|
|
28785a01f6 | ||
|
|
01c4260763 | ||
|
|
150b3eb178 | ||
|
|
5ab52d37f2 | ||
|
|
ce191fd4c7 | ||
|
|
9a7e349101 | ||
|
|
07a7d2a51a | ||
|
|
d2b192cb07 | ||
|
|
d8e3e93a87 | ||
|
|
af12df38ed | ||
|
|
aee89f1c92 | ||
|
|
7fb8268fae | ||
|
|
8b49eb1526 | ||
|
|
c6cdcf83a9 | ||
|
|
4bf82083b4 | ||
|
|
6579fd8ebe | ||
|
|
4d62ef771a | ||
|
|
7784a3a20c |
No files matched your search
@@ -2,11 +2,7 @@
|
||||
## [1.7.4 (MONTH Xth, 2026)](#1-7-4)
|
||||
|
||||
### Additions
|
||||
- Added config options to award experience to fainted Pokémon for opponents they helped defeat (`awardExperienceToFaintedPokemon`) and to still award experience for defeated enemies even after battle loss/forfeit (`awardExperienceOnBattleLoss`)
|
||||
- Party Overlay Exp Gained Animation will now display the amount of EXP the Pokémon gained
|
||||
- Added a toast to notify players of how they can scroll through their party. Did you know you can hold R and scroll instead of using arrow keys??!
|
||||
- Added new layer property that makes a layer scroll in a direction. "scrolling": {"speedU": 0.1, "speedV": 0.1}
|
||||
- Added missing crops to Botany Pots integration
|
||||
|
||||
### New rideable Pokémon
|
||||
|
||||
@@ -17,42 +13,16 @@
|
||||
### Animation updates for the following Pokémon
|
||||
|
||||
### Changes
|
||||
- Restructured Botany Pots integration folder structure
|
||||
- Decrease default pastured mon wander radius to 32.
|
||||
- Factor in the weight of pokemon when calculating pushing forces between hitboxes.
|
||||
- Remade the Starter Selection Screen with new assets
|
||||
- Refactored berry trees to be less impactful on TPS (more performant)
|
||||
|
||||
### Fixes
|
||||
- Fixes https://gitlab.com/cable-mc/cobblemon/-/issues/1943
|
||||
- Fix occasional crash on retrieving revived fossils
|
||||
- Fixed a compatibility issue with World Game Rules which caused some log spam in specific cases.
|
||||
- Fixed improper validation of Pokémon when performing a trade.
|
||||
- Fixed issue where particle like beam can disappear when you don't see the beginning of the beam or you look away
|
||||
- Fixes https://gitlab.com/cable-mc/cobblemon/-/issues/1943
|
||||
- Fix occasional crash on retrieving revived fossils
|
||||
- Fix brushing not working on Neoforge
|
||||
- [Various Submarine Ride Fixes](https://gitlab.com/cable-mc/cobblemon/-/merge_requests/2157)
|
||||
- Fix Pastured mons despawning and running away.
|
||||
- Fix Apricorn trees crashing when other mods bonemeal it
|
||||
- [Various UFO Ride Improvements](https://gitlab.com/cable-mc/cobblemon/-/merge_requests/2156)
|
||||
- Fixed NeoForge brewing stand unexpectedly crashing users when shift clicking items within it's menu
|
||||
- Fixed NPCs switching pokemon very often during battles
|
||||
- Fixed Campfire pot causing crashes under certain circumstances
|
||||
- Fixed Type Gems not being consumed upon use
|
||||
- Fixed Ancient Pokeball's to their respective modifiers
|
||||
- Fixed a Server warning when recalling Pokemon
|
||||
|
||||
### Developer
|
||||
- Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters
|
||||
- Added `FishingSpawnerFactory` which allows influence registration on fishing encounters
|
||||
- Added `BattleFleeAttemptEvent` fired from `PokemonBattle.checkFleeAttempt`, allowing developers to intercept and control flee attempts
|
||||
|
||||
### Molang & Datapacks
|
||||
- q.item.is_enchanted()
|
||||
- q.item.has_enchantment(minecraft:sharpness, 3)
|
||||
- Added `has_chosen_starter` & `get_starter_uuid` as available Molang functions
|
||||
- Added `marks`, `has_mark`, & `remove_marks` as available Molang functions for pokemon
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
@@ -6,6 +6,7 @@
|
||||
- Fixed players failing to join servers randomly.
|
||||
- Fixed some Pokémon having no stamina for flying.
|
||||
- Fixed `/pctake` not respecting the actual size of the PC.
|
||||
|
||||
### Developer
|
||||
- Poké Ball GUI textures now respect the ball's namespace, enabling proper texture loading for custom balls from other mods.
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ dependencies {
|
||||
// modCompileOnly(libs.flywheelFabric)
|
||||
|
||||
// Showdown
|
||||
modCompileOnly(libs.graal.core)
|
||||
modCompileOnly(libs.graal)
|
||||
|
||||
// Data Storage
|
||||
modCompileOnly(libs.bundles.mongo)
|
||||
|
||||
@@ -15,8 +15,6 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents;
|
||||
import com.cobblemon.mod.common.api.events.item.LeftoversCreatedEvent;
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController;
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies;
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle;
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.air.HoverBehaviour;
|
||||
import com.cobblemon.mod.common.api.storage.party.PartyStore;
|
||||
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore;
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags;
|
||||
@@ -61,7 +59,6 @@ import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@@ -172,32 +169,6 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "getDestroySpeed",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/player/Player;onGround()Z"
|
||||
)
|
||||
)
|
||||
private boolean cobblemon$considerHoverOnGround(Player player) {
|
||||
// If riding a Hover Mount, return true to bypass the 5x slowdown
|
||||
if (player.getVehicle() instanceof PokemonEntity vehicle) {
|
||||
|
||||
var ridingController = vehicle.getRidingController();
|
||||
if (ridingController == null) player.onGround();
|
||||
|
||||
var activeContext = ridingController.getContext();
|
||||
if (activeContext == null) player.onGround();
|
||||
|
||||
if (activeContext.getStyle() == RidingStyle.AIR &&
|
||||
activeContext.getBehaviour().equals(HoverBehaviour.Companion.getKEY())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Otherwise, return the actual value
|
||||
return player.onGround();
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "eat",
|
||||
at = @At(
|
||||
|
||||
@@ -101,7 +101,7 @@ public abstract class ServerEntityMixin {
|
||||
cobblemon$broadcast(new ClientboundUpdateDriverInputPacket(driverInput, entity.getId()));
|
||||
}
|
||||
|
||||
public void cobblemon$broadcast(NetworkPacket<?> packet) {
|
||||
private void cobblemon$broadcast(NetworkPacket<?> packet) {
|
||||
if (!(entity.level() instanceof ServerLevel level)) return;
|
||||
ChunkMap chunkMap = level.getChunkSource().chunkMap;
|
||||
Int2ObjectMap<?> entityMap = ((ChunkMapAccessor) chunkMap).getEntityMap();
|
||||
|
||||
@@ -313,7 +313,7 @@ public abstract class MouseHandlerMixin {
|
||||
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$setRideXRot(Mth.clamp(playerRotater.cobblemon$getRideXRot(), -90.0F, 90.0F));
|
||||
playerRotater.cobblemon$setRideYRot(Mth.clamp(playerRotater.cobblemon$getRideYRot(), -105.0F, 105.0F));
|
||||
if (player.getVehicle() != null) {
|
||||
player.getVehicle().onPassengerTurned(player);
|
||||
|
||||
@@ -14,14 +14,9 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalFloatRef;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
@@ -94,61 +89,4 @@ public abstract class EntityMixin {
|
||||
cir.setReturnValue(customEyePos);
|
||||
}
|
||||
|
||||
|
||||
@WrapOperation(
|
||||
method = "push(Lnet/minecraft/world/entity/Entity;)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;push(DDD)V", ordinal = 0)
|
||||
)
|
||||
public void cobblemon$customPusherLogic(Entity pusher, double x, double y, double z,
|
||||
Operation<Void> original,
|
||||
@Local(argsOnly = true) Entity pushee,
|
||||
@Local(name = "e") double e, @Local(name = "d") double d,
|
||||
@Share("pusherIsPlayerOrPokemon") LocalBooleanRef pusherIsPlayerOrPokemon,
|
||||
@Share("pusheeIsPlayerOrPokemon") LocalBooleanRef pusheeIsPlayerOrPokemon,
|
||||
@Share("pusheeReceivedForce")LocalFloatRef pusheeReceivedForce) {
|
||||
pusherIsPlayerOrPokemon.set(pusher instanceof PokemonEntity || pusher instanceof Player);
|
||||
pusheeIsPlayerOrPokemon.set(pushee instanceof PokemonEntity || pushee instanceof Player);
|
||||
|
||||
if (!pusheeIsPlayerOrPokemon.get() || !pusherIsPlayerOrPokemon.get()) {
|
||||
original.call(pusher, x, y, z);
|
||||
return;
|
||||
}
|
||||
|
||||
/**********************************************
|
||||
* Custom portion of the code:
|
||||
* Handle collisions with weight in mind. Use
|
||||
* the weight value of pokemon to determine
|
||||
* which side gets what portion of the total
|
||||
* force.
|
||||
*********************************************/
|
||||
// Weights in hectograms
|
||||
// Check to see if the pusher or pushee is a player. 200ish pounds. Steve's like a 6'6 miner
|
||||
var pusherWeight = pusher instanceof PokemonEntity pusherPokemon ? pusherPokemon.getPokemon().getSpecies().getWeight() : 900F;
|
||||
var pusheeWeight = pushee instanceof PokemonEntity pusheePokemon ? pusheePokemon.getPokemon().getSpecies().getWeight() : 900F;
|
||||
|
||||
// Calculate the portion of force received for both entities. Bigger receives less from the
|
||||
// collision than the smaller one.
|
||||
var totalWeight = pusherWeight + pusheeWeight;
|
||||
var pusherReceivedForce = pusheeWeight / totalWeight * 2F;
|
||||
pusheeReceivedForce.set((pusherWeight / totalWeight) * 2F);
|
||||
|
||||
original.call(pusher, -d * pusherReceivedForce, 0.0, -e * pusherReceivedForce);
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
method = "push(Lnet/minecraft/world/entity/Entity;)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;push(DDD)V", ordinal = 1)
|
||||
)
|
||||
public void cobblemon$customPusheeLogic(Entity pusher, double x, double y, double z, Operation<Void> original,
|
||||
@Local(name = "e") double e, @Local(name = "d") double d,
|
||||
@Share("pusherIsPlayerOrPokemon") LocalBooleanRef pusherIsPlayerOrPokemon,
|
||||
@Share("pusheeIsPlayerOrPokemon") LocalBooleanRef pusheeIsPlayerOrPokemon,
|
||||
@Share("pusheeReceivedForce")LocalFloatRef pusheeReceivedForce) {
|
||||
if (!pusheeIsPlayerOrPokemon.get() || !pusherIsPlayerOrPokemon.get()) {
|
||||
original.call(pusher, x, y, z);
|
||||
return;
|
||||
}
|
||||
|
||||
original.call(pusher, d * pusheeReceivedForce.get(), 0.0, e * pusheeReceivedForce.get());
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,6 @@ object CobblemonActivities {
|
||||
@JvmField
|
||||
val POKEMON_HERD = register(Activity("pokemon_herd"))
|
||||
@JvmField
|
||||
val POKEMON_BOIDS = register(Activity("pokemon_boid"))
|
||||
@JvmField
|
||||
val POKEMON_POLLINATION = register(Activity("pokemon_pollination"))
|
||||
|
||||
@JvmStatic
|
||||
|
||||
@@ -21,7 +21,6 @@ import net.minecraft.core.UUIDUtil
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import java.util.*
|
||||
import net.minecraft.world.entity.player.Player
|
||||
|
||||
object CobblemonMemories {
|
||||
val memories = mutableMapOf<String, MemoryModuleType<*>>()
|
||||
@@ -93,9 +92,6 @@ object CobblemonMemories {
|
||||
val DISABLE_WALK_TO_WANTED_ITEM = register<Boolean>("disable_walk_to_wanted_item")
|
||||
@JvmField
|
||||
val TIME_TRYING_TO_REACH_WANTED_ITEM = register<Int>("time_trying_to_wanted_item")
|
||||
/** Is the nearest player holding a diamond for debugging purposes? */
|
||||
@JvmField
|
||||
val DIAMOND_DEBUGGER = register<Player>("diamond_debugger")
|
||||
|
||||
@JvmStatic
|
||||
fun <U> register(id: String, codec: Codec<U>): MemoryModuleType<U> {
|
||||
|
||||
@@ -23,7 +23,6 @@ import com.cobblemon.mod.common.client.net.data.CobblemonMechanicsSyncHandler
|
||||
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.data.RideSettingsSyncHandler
|
||||
import com.cobblemon.mod.common.client.net.debug.OpenRidingStatsDebugGUIHandler
|
||||
import com.cobblemon.mod.common.client.net.debug.S2CUpdateObsAvoidHandler
|
||||
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
|
||||
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.PokeSnackBlockParticlesHandler
|
||||
@@ -81,7 +80,6 @@ import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyMoveCallba
|
||||
import com.cobblemon.mod.common.net.messages.client.cooking.SeasoningRegistrySyncPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.cooking.ToggleCookingPotLidPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.data.*
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.ClientboundUpdateObsAvoidPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.RequestOpenRidingStatsDebugGUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
|
||||
@@ -255,11 +253,7 @@ import com.cobblemon.mod.common.net.serverhandling.trade.OfferTradeHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.PerformTradeHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.UpdateTradeOfferHandler
|
||||
import com.cobblemon.mod.common.util.server
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.Entity
|
||||
import com.cobblemon.mod.common.mixin.accessor.ChunkMapAccessor
|
||||
import com.cobblemon.mod.common.mixin.accessor.TrackedEntityAccessor
|
||||
|
||||
/**
|
||||
* Registers Cobblemon network packets.
|
||||
@@ -282,19 +276,6 @@ object CobblemonNetwork {
|
||||
fun sendToAllPlayers(packet: NetworkPacket<*>) = sendPacketToPlayers(server()!!.playerList.players, packet)
|
||||
@JvmStatic
|
||||
fun sendPacketToPlayers(players: Iterable<ServerPlayer>, packet: NetworkPacket<*>) = players.forEach { sendPacketToPlayer(it, packet) }
|
||||
fun sendPacketToTrackingPlayers(entity: Entity, packet: NetworkPacket<*>) {
|
||||
val level = entity.level() as? ServerLevel ?: return
|
||||
|
||||
val chunkMap = level.chunkSource.chunkMap
|
||||
val entityTracker = (chunkMap as ChunkMapAccessor).entityMap[entity.id]
|
||||
|
||||
if (entityTracker is TrackedEntityAccessor) {
|
||||
entityTracker.seenBy.forEach { connection ->
|
||||
sendPacketToPlayer(connection.player, packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val s2cPayloads = generateS2CPacketInfoList()
|
||||
val c2sPayloads = generateC2SPacketInfoList()
|
||||
@@ -502,7 +483,6 @@ object CobblemonNetwork {
|
||||
|
||||
// Debug / cheats
|
||||
list.add(PacketRegisterInfo(CalculateSeatPositionsPacket.ID, CalculateSeatPositionsPacket::decode, CalculateSeatPositionsHandler))
|
||||
list.add(PacketRegisterInfo(ClientboundUpdateObsAvoidPacket.ID, ClientboundUpdateObsAvoidPacket::decode, S2CUpdateObsAvoidHandler))
|
||||
|
||||
// Riding
|
||||
list.add(PacketRegisterInfo(ClientboundUpdateRidingStatePacket.ID, ClientboundUpdateRidingStatePacket::decode, ClientboundUpdateRidingStateHandler))
|
||||
|
||||
@@ -49,9 +49,6 @@ object CobblemonSensors {
|
||||
@JvmField
|
||||
val NEARBY_SWEET_BERRY_BUSH = register("nearby_sweet_berry_bush", ::SweetBerryBushSensor)
|
||||
|
||||
@JvmField
|
||||
val PLAYER_DEBUGGING = register("player_debugging", ::PlayerDebuggingSensor)
|
||||
|
||||
@JvmField
|
||||
val POKEMON_NEARBY_WANTED_ITEM = register("pokemon_nearby_wanted_item", ::PokemonItemSensor)
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* 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.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.CobblemonSensors
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.WakeUpTask
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
|
||||
class BoidsTaskConfig : SingleTaskConfig {
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = emptyList<MoLangConfigVariable>()
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): BehaviorControl<in LivingEntity>? {
|
||||
if (entity !is PokemonEntity) {
|
||||
return null
|
||||
}
|
||||
behaviourConfigurationContext.addMemories(CobblemonMemories.POKEMON_DROWSY, CobblemonMemories.POKEMON_SLEEPING)
|
||||
behaviourConfigurationContext.addSensors(CobblemonSensors.POKEMON_DROWSY)
|
||||
return WrapperLivingEntityTask(WakeUpTask.create(), PokemonEntity::class.java)
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* 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.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.BoidsMoveControl
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
|
||||
class SwitchFromBoidTaskConfig : SingleTaskConfig {
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = emptyList<MoLangConfigVariable>()
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): BehaviorControl<in LivingEntity>? {
|
||||
behaviourConfigurationContext.addMemories(CobblemonMemories.DIAMOND_DEBUGGER)
|
||||
return BehaviorBuilder.create {
|
||||
it.group(
|
||||
it.absent(CobblemonMemories.DIAMOND_DEBUGGER)
|
||||
).apply(it) { _ ->
|
||||
Trigger { world, entity, time ->
|
||||
if(entity !is PokemonEntity) return@Trigger false
|
||||
if (entity.moveControl !is PokemonMoveControl) {
|
||||
entity.moveControl = PokemonMoveControl(entity)
|
||||
}
|
||||
entity.brain.updateActivityFromSchedule(world.dayTime, world.gameTime)
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* 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.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonActivities
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.CobblemonSensors
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.BoidsMoveControl
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
import net.minecraft.world.entity.player.Player
|
||||
|
||||
class SwitchToBoidTaskConfig : SingleTaskConfig {
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = emptyList<MoLangConfigVariable>()
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): BehaviorControl<in LivingEntity>? {
|
||||
behaviourConfigurationContext.addMemories(CobblemonMemories.DIAMOND_DEBUGGER)
|
||||
behaviourConfigurationContext.addSensors(CobblemonSensors.PLAYER_DEBUGGING)
|
||||
return BehaviorBuilder.create {
|
||||
it.group(
|
||||
it.present(CobblemonMemories.DIAMOND_DEBUGGER)
|
||||
).apply(it) { _ ->
|
||||
Trigger { world, entity, time ->
|
||||
if(entity !is PokemonEntity) return@Trigger false
|
||||
val player = entity.brain.getMemory(CobblemonMemories.DIAMOND_DEBUGGER).orElse(null)
|
||||
if (entity.moveControl !is BoidsMoveControl && player is LivingEntity) {
|
||||
entity.moveControl = BoidsMoveControl(entity, player)
|
||||
} else {
|
||||
return@Trigger false
|
||||
}
|
||||
entity.brain.setActiveActivityIfPossible(CobblemonActivities.POKEMON_BOIDS)
|
||||
// SpawnSnowstormEntityParticlePacket(
|
||||
// cobblemonResource("evo_particles"),
|
||||
// entity.id,
|
||||
// listOf("shiny_particles", "middle")).sendToPlayersAround(
|
||||
// entity.x,
|
||||
// entity.y,
|
||||
// entity.z,
|
||||
// 64.0,
|
||||
// entity.level().dimension()
|
||||
// )
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,8 +124,6 @@ interface TaskConfig {
|
||||
cobblemonResource("target_entity") to TargetEntityTaskConfig::class.java,
|
||||
cobblemonResource("memory_aspect") to MemoryAspectTaskConfig::class.java,
|
||||
cobblemonResource("activity_change") to ActivityChangeTaskConfig::class.java,
|
||||
cobblemonResource("switch_from_boid") to SwitchFromBoidTaskConfig::class.java,
|
||||
cobblemonResource("switch_to_boid") to SwitchToBoidTaskConfig::class.java,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangV
|
||||
import com.cobblemon.mod.common.entity.OmniPathingEntity
|
||||
import com.cobblemon.mod.common.entity.ai.CobblemonRandomSurfacePos
|
||||
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
@@ -185,12 +184,6 @@ class WanderTaskConfig : SingleTaskConfig {
|
||||
|
||||
if (targetVec == null) continue
|
||||
|
||||
// Ignore pathing targets outside of the pasture zone if the pokemon is pastured.
|
||||
val tethering = (entity as? PokemonEntity)?.tethering
|
||||
if (tethering?.canRoamTo(BlockPos.containing(targetVec)) == false) {
|
||||
continue
|
||||
}
|
||||
|
||||
pos = applyHeightConstraints(
|
||||
pos = BlockPos.containing(targetVec),
|
||||
minimumHeight = minimumHeight,
|
||||
|
||||
@@ -22,21 +22,18 @@ 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.BattleFledEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleFleeAttemptEvent
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.BattleEvSource
|
||||
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.api.text.white
|
||||
import com.cobblemon.mod.common.api.text.yellow
|
||||
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
|
||||
import com.cobblemon.mod.common.battles.BattleCaptureAction
|
||||
import com.cobblemon.mod.common.battles.BattleFormat
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.battles.BattleSide
|
||||
import com.cobblemon.mod.common.battles.FleeAttemptActionResponse
|
||||
import com.cobblemon.mod.common.battles.ForfeitActionResponse
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import com.cobblemon.mod.common.battles.dispatch.BattleDispatch
|
||||
@@ -51,9 +48,7 @@ import com.cobblemon.mod.common.entity.npc.NPCBattleActor
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleQueueRequestPacket
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.DefeatEvolutionProgress
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
|
||||
import com.cobblemon.mod.common.pokemon.requirements.DefeatRequirement
|
||||
@@ -142,9 +137,6 @@ open class PokemonBattle(
|
||||
val captureActions = mutableListOf<BattleCaptureAction>()
|
||||
|
||||
val majorBattleActions = hashMapOf<UUID, BattleMessage>()
|
||||
|
||||
/** Monotonically increasing counter to track faint ordering within this battle. */
|
||||
var faintCounter = 0
|
||||
val minorBattleActions = hashMapOf<UUID, BattleMessage>()
|
||||
val contextManager = ContextManager()
|
||||
|
||||
@@ -252,23 +244,12 @@ open class PokemonBattle(
|
||||
|
||||
fun end() {
|
||||
ended = true
|
||||
val awardToFainted = Cobblemon.config.awardExperienceToFaintedPokemon
|
||||
val awardOnLoss = Cobblemon.config.awardExperienceOnBattleLoss
|
||||
this.actors.forEach { actor ->
|
||||
val faintedPokemons = actor.pokemonList.filter { it.health <= 0 }
|
||||
actor.getSide().getOppositeSide().actors.forEach { opponent ->
|
||||
val opponentPokemons = if (awardToFainted) opponent.pokemonList else opponent.pokemonList.filter { it.health > 0 }
|
||||
val opponentNonFaintedPokemons = opponent.pokemonList.filter { it.health > 0 }
|
||||
faintedPokemons.forEach { faintedPokemon ->
|
||||
for (opponentPokemon in opponentPokemons) {
|
||||
// Faint-order check: if awarding XP to fainted pokemon, only award for enemies that fainted before this pokemon did
|
||||
if (awardToFainted) {
|
||||
val enemyFaintedAt = faintedPokemon.faintedAt ?: continue
|
||||
val opponentFaintedAt = opponentPokemon.faintedAt
|
||||
if (opponentFaintedAt != null && opponentFaintedAt <= enemyFaintedAt) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for (opponentPokemon in opponentNonFaintedPokemons) {
|
||||
val facedFainted = opponentPokemon.facedOpponents.contains(faintedPokemon)
|
||||
val pokemon = opponentPokemon.effectedPokemon
|
||||
if (facedFainted) {
|
||||
@@ -289,8 +270,7 @@ open class PokemonBattle(
|
||||
else -> continue
|
||||
}
|
||||
val experience = Cobblemon.experienceCalculator.calculate(opponentPokemon, faintedPokemon, multiplier)
|
||||
val enemyTeamWiped = actor.pokemonList.all { it.health <= 0 }
|
||||
if (experience > 0 && (enemyTeamWiped || awardOnLoss)) {
|
||||
if (experience > 0 && actor.pokemonList.all { it.health <= 0 }) {
|
||||
opponent.awardExperience(opponentPokemon, experience)
|
||||
}
|
||||
Cobblemon.evYieldCalculator.calculate(opponentPokemon, faintedPokemon).forEach { (stat, amount) ->
|
||||
@@ -525,7 +505,6 @@ open class PokemonBattle(
|
||||
|
||||
fun checkForInputDispatch() {
|
||||
if (checkForfeit()) return // ignore actors that are still choosing, their choices don't matter anymore
|
||||
if (checkFleeAttempt()) return
|
||||
val readyToInput = (actors.any { !it.mustChoose && it.responses.isNotEmpty() } && actors.none { it.mustChoose })
|
||||
if (readyToInput && captureActions.isEmpty()) {
|
||||
actors.filter { it.responses.isNotEmpty() }.forEach { it.writeShowdownResponse() }
|
||||
@@ -533,20 +512,6 @@ open class PokemonBattle(
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkFleeAttempt(): Boolean {
|
||||
val runner = actors.find { it.responses.any { it is FleeAttemptActionResponse } } as? PlayerBattleActor ?: return false
|
||||
CobblemonEvents.BATTLE_FLEE_ATTEMPT.post(BattleFleeAttemptEvent(this, runner))
|
||||
runner.responses.clear()
|
||||
|
||||
runner.mustChoose = true
|
||||
runner.request?.let { runner.sendUpdate(BattleQueueRequestPacket(it)) }
|
||||
runner.sendUpdate(BattleMakeChoicePacket())
|
||||
|
||||
runner.entity?.sendSystemMessage(battleLang("run_prompt").white())
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/** Forces Showdown to end the battle when a [BattleActor] chooses to forfeit. */
|
||||
private fun checkForfeit(): Boolean {
|
||||
val forfeit = actors.find { it.responses.any { it is ForfeitActionResponse } }
|
||||
|
||||
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.events
|
||||
|
||||
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.BattleVictoryEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.instruction.FormeChangeEvent
|
||||
@@ -111,9 +110,6 @@ object CobblemonEvents {
|
||||
@JvmField
|
||||
val BATTLE_STARTED_POST = EventObservable<BattleStartedEvent.Post>()
|
||||
|
||||
@JvmField
|
||||
val BATTLE_FLEE_ATTEMPT = EventObservable<BattleFleeAttemptEvent>()
|
||||
|
||||
@JvmField
|
||||
val BATTLE_FLED = EventObservable<BattleFledEvent>()
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* 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.MoValue
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import com.cobblemon.mod.common.util.asArrayValue
|
||||
|
||||
/**
|
||||
* Event fired when a [PokemonBattle] is fled by a [PlayerBattleActor].
|
||||
*
|
||||
* @author saultsc
|
||||
* @since March 31th 2026
|
||||
*/
|
||||
|
||||
data class BattleFleeAttemptEvent(
|
||||
override val battle: PokemonBattle,
|
||||
val player: PlayerBattleActor
|
||||
) : BattleEvent {
|
||||
val context = mutableMapOf<String, MoValue>(
|
||||
"battle" to battle.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 }
|
||||
)
|
||||
}
|
||||
@@ -145,7 +145,6 @@ import net.minecraft.world.entity.item.ItemEntity
|
||||
import net.minecraft.world.entity.monster.Monster
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.enchantment.EnchantmentHelper
|
||||
import net.minecraft.world.level.ClipContext
|
||||
import net.minecraft.world.level.GameType
|
||||
import net.minecraft.world.level.Level
|
||||
@@ -872,8 +871,6 @@ object MoLangFunctions {
|
||||
|
||||
DoubleValue(value)
|
||||
}
|
||||
map.put("get_starter_uuid") { _ -> Cobblemon.playerDataManager.getGenericData(player).starterUUID?.let{ StringValue(it.toString()) } ?: DoubleValue.ZERO }
|
||||
map.put("has_chosen_starter") { _ -> DoubleValue(Cobblemon.playerDataManager.getGenericData(player).starterSelected) }
|
||||
}
|
||||
map
|
||||
}
|
||||
@@ -894,15 +891,6 @@ object MoLangFunctions {
|
||||
map.put("is_of") { params -> DoubleValue(holder.`is`(params.getString(0).asIdentifierDefaultingNamespace())) }
|
||||
map.put("is_in") { params -> DoubleValue(holder.`is`(TagKey.create(Registries.ITEM, params.getString(0).replace("#", "").asIdentifierDefaultingNamespace()))) }
|
||||
map.put("is_food") { params -> DoubleValue(stack.has(DataComponents.FOOD)) }
|
||||
map.put("is_enchanted") { params -> DoubleValue(stack.hasFoil()) }
|
||||
map.put("has_enchantment") { params ->
|
||||
val enchantmentId = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
val minLevel = params.getIntOrNull(1) ?: 1
|
||||
val enchantmentRegistry = registryAccess.registryOrThrow(Registries.ENCHANTMENT)
|
||||
val holder = enchantmentRegistry.getHolder(enchantmentId).orElse(null) ?: return@put DoubleValue.ZERO
|
||||
val level = EnchantmentHelper.getItemEnchantmentLevel(holder, stack)
|
||||
return@put DoubleValue(level >= minLevel)
|
||||
}
|
||||
return@mutableListOf map
|
||||
}
|
||||
)
|
||||
@@ -1842,30 +1830,6 @@ object MoLangFunctions {
|
||||
map.put("remove_cosmetic_item") { _ ->
|
||||
pokemon.removeCosmeticItem()
|
||||
}
|
||||
map.put("marks") { _ -> pokemon.marks.asArrayValue { StringValue(it.identifier.toString()) } }
|
||||
map.put("has_mark") { params ->
|
||||
var hasMark = false
|
||||
val identifier = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
val mark = Marks.getByIdentifier(identifier)
|
||||
if (mark != null) {
|
||||
hasMark = pokemon.marks.contains(mark)
|
||||
}
|
||||
|
||||
DoubleValue(hasMark)
|
||||
}
|
||||
map.put("remove_marks") { params ->
|
||||
var removedMark = false
|
||||
for (param in params.params) {
|
||||
val identifier = param.asString().asIdentifierDefaultingNamespace()
|
||||
val mark = Marks.getByIdentifier(identifier)
|
||||
if (mark != null) {
|
||||
pokemon.exchangeMark(mark, false)
|
||||
removedMark = true
|
||||
}
|
||||
}
|
||||
|
||||
DoubleValue(removedMark)
|
||||
}
|
||||
map.put("add_marks") { params ->
|
||||
var appliedMark = false
|
||||
for (param in params.params) {
|
||||
@@ -2059,13 +2023,9 @@ object MoLangFunctions {
|
||||
}
|
||||
}
|
||||
map.put("is_sprinting") {
|
||||
if (pokemonEntity.hasControllingPassenger()) { // If its being ridden then the riding state sets the animation
|
||||
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
|
||||
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
|
||||
return@ifRidingAvailableSupply moValue.functions.get("sprinting")?.apply(MoParams.EMPTY) as? MoValue ?: DoubleValue.ZERO
|
||||
}
|
||||
} else {
|
||||
pokemonEntity.isPokemonSprinting
|
||||
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
|
||||
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
|
||||
return@ifRidingAvailableSupply moValue.functions.get("sprinting")?.apply(MoParams.EMPTY) as? MoValue ?: DoubleValue.ZERO
|
||||
}
|
||||
}
|
||||
map.put("is_drifting") {
|
||||
|
||||
@@ -289,11 +289,11 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
|
||||
createDefault("ancient_great_ball", MultiplierModifier(1.5F), ancient = true)
|
||||
createDefault("ancient_ultra_ball", MultiplierModifier(2F), ancient = true)
|
||||
createDefault("ancient_heavy_ball", throwPower = 0.75f, ancient = true)
|
||||
createDefault("ancient_leaden_ball", MultiplierModifier(1.5F), throwPower = 0.75f, ancient = true)
|
||||
createDefault("ancient_gigaton_ball", MultiplierModifier(2F), throwPower = 0.75f, ancient = true)
|
||||
createDefault("ancient_leaden_ball", throwPower = 0.75f, ancient = true)
|
||||
createDefault("ancient_gigaton_ball", throwPower = 0.75f, ancient = true)
|
||||
createDefault("ancient_feather_ball", throwPower = 2.5f, ancient = true)
|
||||
createDefault("ancient_wing_ball", MultiplierModifier(1.5F), throwPower = 2.5f, ancient = true)
|
||||
createDefault("ancient_jet_ball", MultiplierModifier(2F), throwPower = 2.5f, ancient = true)
|
||||
createDefault("ancient_wing_ball", throwPower = 2.5f, ancient = true)
|
||||
createDefault("ancient_jet_ball", throwPower = 2.5f, ancient = true)
|
||||
createDefault("ancient_origin_ball", GuaranteedModifier(), ancient = true)
|
||||
// Luxury ball effect, low priority as it must be triggered before soothe bell as of gen 4
|
||||
CobblemonEvents.FRIENDSHIP_UPDATED.subscribe(priority = Priority.LOW) { event ->
|
||||
|
||||
@@ -314,11 +314,11 @@ object PokemonSpecies : JsonDataRegistry<Species> {
|
||||
val result = mutableMapOf<String, String>()
|
||||
this.species.forEach {species ->
|
||||
val baseSpecies = ShowdownSpecies(species, null)
|
||||
result[ShowdownIdentifiable.REGEX.replace(baseSpecies.name.lowercase(), "")] = this.gson.toJson(baseSpecies)
|
||||
result[ShowdownIdentifiable.REGEX.replace(baseSpecies.name, "")] = this.gson.toJson(baseSpecies)
|
||||
species.forms.forEach { form ->
|
||||
if (form != species.standardForm) {
|
||||
val formSpecies = ShowdownSpecies(species, form)
|
||||
result[ShowdownIdentifiable.REGEX.replace(formSpecies.name.lowercase(), "")] = this.gson.toJson(formSpecies)
|
||||
result[ShowdownIdentifiable.REGEX.replace(formSpecies.name, "")] = this.gson.toJson(formSpecies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,10 +119,7 @@ class RidingController(
|
||||
private fun canTransitionToLand(): Boolean {
|
||||
if ((entity.isUnderWater)) return false
|
||||
if ((entity.isEyeInFluid(FluidTags.WATER) || entity.isEyeInFluid(FluidTags.LAVA))) return false
|
||||
// If the currently in the air but has no land style then don't transition and render the ride useless.
|
||||
// Hover is a good example for this.
|
||||
val shouldStayInAir = entity.pokemon.riding.behaviours?.get(RidingStyle.LAND) == null && context?.style == RidingStyle.AIR
|
||||
return entity.onGround() && !shouldStayInAir
|
||||
return entity.onGround()
|
||||
}
|
||||
|
||||
private fun canTransitionToLiquid(): Boolean {
|
||||
|
||||
@@ -213,23 +213,10 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
|
||||
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalHover.accelerationExpr!!) * 20.0)
|
||||
|
||||
var newVelocity = state.rideVelocity.get()
|
||||
val unrotateVel = vehicle.deltaMovement.yRot(vehicle.yRot)
|
||||
|
||||
val vehicleFrontVec = Vec3(0.0, 0.0, 1.0)
|
||||
val faceVel = Vec3(vehicle.deltaMovement.x, 0.0, vehicle.deltaMovement.z)
|
||||
|
||||
val d = vehicle.deltaMovement.horizontalDistance()
|
||||
val v2 = Vec3(faceVel.dot(Vec3(1.0, 0.0, 0.0)) * d, newVelocity.y, faceVel.dot(Vec3(0.0, 0.0, 1.0)) * d)
|
||||
|
||||
// Check for collisions and if found, update the speed to reflect it
|
||||
if (vehicle.horizontalCollision) {
|
||||
val smackingIntoWallSpeed = max( vehicle.deltaMovement.horizontalDistance(), 0.2)
|
||||
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z).normalize().scale(smackingIntoWallSpeed).add(0.0, newVelocity.y, 0.0)
|
||||
}
|
||||
|
||||
|
||||
if (vehicle.verticalCollision) {
|
||||
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
|
||||
if (vehicle.verticalCollision || vehicle.horizontalCollision) {
|
||||
state.speed.set(vehicle.deltaMovement.length())
|
||||
}
|
||||
|
||||
// align the velocity vector to be in local vehicle space
|
||||
|
||||
@@ -60,7 +60,7 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
|
||||
val poseProvider = PoseProvider<SubmarineSettings, SubmarineState>(PoseType.FLOAT)
|
||||
.with(PoseOption(PoseType.SWIM) { _, state, entity -> state.rideVelocity.get().z > 0.05 && !state.onSurface.get()})
|
||||
.with(PoseOption(PoseType.STAND) { _, state, entity -> state.rideVelocity.get().z <= 0.05 && state.onSurface.get()})
|
||||
.with(PoseOption(PoseType.SWIM) { _, state, entity -> state.rideVelocity.get().z > 0.05 && state.onSurface.get()})
|
||||
.with(PoseOption(PoseType.STAND) { _, state, entity -> state.rideVelocity.get().z > 0.05 && state.onSurface.get()})
|
||||
|
||||
override fun isActive(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Boolean {
|
||||
return vehicle.isInWater || !vehicle.onGround()
|
||||
@@ -191,17 +191,11 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
|
||||
* Calculate Forward Velocity
|
||||
*********************************************/
|
||||
val currZVel = state.rideVelocity.get().z
|
||||
var newZVel = when {
|
||||
val newZVel = when {
|
||||
driver.zza > 0.0 -> min(topSpeed, currZVel + accel)
|
||||
driver.zza < 0.0 -> max( -strafeSpeed, currZVel - strafeAccel)
|
||||
else -> lerp(currZVel, 0.0, 0.02)
|
||||
driver.zza < 0.0 -> max( -strafeSpeed, currZVel - strafeAccel) //TODO: Might need to be applied in addition to the normal braking force?
|
||||
else -> lerp(currZVel, 0.0, 0.05)
|
||||
}
|
||||
|
||||
// If trying to brake then add the friction to the braking force
|
||||
if (driver.zza != 0.0f && driver.zza.sign.toDouble() != newZVel.sign) {
|
||||
newZVel = lerp(newZVel, 0.0, 0.02)
|
||||
}
|
||||
|
||||
currVel = Vec3(currVel.x, currVel.y, newZVel)
|
||||
|
||||
/*********************************************
|
||||
@@ -212,20 +206,14 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
|
||||
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.4
|
||||
val terminalYVel = 2.0
|
||||
val driverYInput = if(driver.jumping) 1.0 else if (driver.isShiftKeyDown) -1.0 else 0.0
|
||||
var newYVel = when {
|
||||
val newYVel = when {
|
||||
(!vehicle.isInWater) -> max(-terminalYVel, currYVel - gravity)
|
||||
//(vehicle.isInWater && !vehicle.isUnderWater && currYVel > 0.001) -> lerp(currYVel, 0.0, 0.1) // Make sure that yVel is above a threshold before doing this otherwise it'll block submerging.
|
||||
//driverYInput != 0.0 && currYVel > 0.001 && driverYInput.sign != currYVel.sign -> lerp(currYVel, 0.0, 0.05)
|
||||
driverYInput > 0.0 -> min(vertTopSpeed, currYVel + strafeAccel)
|
||||
driverYInput < 0.0 -> max(-vertTopSpeed, currYVel - strafeAccel)
|
||||
else -> lerp(currYVel, 0.0, 0.02)
|
||||
else -> lerp(currYVel, 0.0, 0.05)
|
||||
}
|
||||
|
||||
// If trying to brake then add the friction to the braking force
|
||||
if (driverYInput != 0.0 && driverYInput != newYVel.sign) {
|
||||
newYVel = lerp(newYVel, 0.0, 0.02)
|
||||
}
|
||||
|
||||
currVel = Vec3(currVel.x, newYVel, currVel.z)
|
||||
|
||||
/*********************************************
|
||||
@@ -233,17 +221,11 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
|
||||
*********************************************/
|
||||
val currXVel = state.rideVelocity.get().x
|
||||
val horzTopSpeed = strafeSpeed
|
||||
var newXVel = when {
|
||||
val newXVel = when {
|
||||
driver.xxa > 0.0 -> min(horzTopSpeed, currXVel + strafeAccel)
|
||||
driver.xxa < 0.0 -> max( -horzTopSpeed, currXVel - strafeAccel)
|
||||
else -> lerp(currXVel, 0.0, 0.02)
|
||||
else -> lerp(currXVel, 0.0, 0.01)
|
||||
}
|
||||
|
||||
// If trying to brake then add the friction to the braking force
|
||||
if (driver.xxa != 0.0f && driver.xxa.sign.toDouble() != newZVel.sign) {
|
||||
newXVel = lerp(newXVel, 0.0, 0.02)
|
||||
}
|
||||
|
||||
currVel = Vec3(newXVel, currVel.y, currVel.z)
|
||||
|
||||
// Set the new velocity
|
||||
@@ -323,6 +305,81 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
|
||||
|
||||
}
|
||||
|
||||
private fun correctOrientation(
|
||||
settings: SubmarineSettings,
|
||||
state: SubmarineState,
|
||||
vehicle: PokemonEntity,
|
||||
controller: OrientationController,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
) {
|
||||
|
||||
// TODO: remove if not needed
|
||||
|
||||
// // Calculate correcting roll force.
|
||||
// // 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) 10.0f * abs(cos(controller.pitch.toRadians()))
|
||||
// else 10.0f * cos(controller.pitch.toRadians()).pow(2) * (
|
||||
// howRolledAmI*1.0f +
|
||||
// (state.noInputTime.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
|
||||
// )
|
||||
// val maxRollForce = maxRollCorrectionRate / 40.0f
|
||||
// val rollArrivalDeg = 60.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
|
||||
// }
|
||||
//
|
||||
// // Apply the 'steered' force (its really not a steering force I need a better name)
|
||||
// state.currRollCorrectionForce.set(
|
||||
// state.currRollCorrectionForce.get() + steeredRollForce
|
||||
// )
|
||||
|
||||
//Calculate correcting pitch force.
|
||||
// val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect ?: globalSubmarine.timeToRollCorrect!!)
|
||||
// val maxPitchCorrectionRate = 5.0f * (state.noInputTime.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
|
||||
// val maxPitchForce = maxPitchCorrectionRate / 40.0f
|
||||
// val pitchArrivalDeg = 45.0f
|
||||
// val desiredPitch = 0.0f
|
||||
//
|
||||
// if(state.rideVelocity.get().length() < 0.1 && state.noInputTime.get() > rollCorrectionTimer) {
|
||||
// val pitchErrror = Mth.wrapDegrees(desiredPitch.angleDifference(controller.pitch))
|
||||
// val arrivalInfluence = (min(pitchArrivalDeg,abs(pitchErrror)) / pitchArrivalDeg)
|
||||
// val desiredPitchForce = maxPitchCorrectionRate * arrivalInfluence * -pitchErrror.sign
|
||||
// var steeredPitchForce = desiredPitchForce - state.currPitchCorrectionForce.get()
|
||||
//
|
||||
// // Ensure the 'steering' correction force doesn't exceed our maxForce
|
||||
// if (abs(steeredPitchForce) > maxPitchForce) {
|
||||
// steeredPitchForce = steeredPitchForce.sign * maxPitchForce
|
||||
// }
|
||||
//
|
||||
// // Apply the 'steered' force (its really not a steering force I need a better name)
|
||||
// state.currPitchCorrectionForce.set(
|
||||
// state.currPitchCorrectionForce.get() + steeredPitchForce
|
||||
// )
|
||||
// } else {
|
||||
// state.currPitchCorrectionForce.set(
|
||||
// //reduce rollCorrection gradually
|
||||
// lerp(state.currPitchCorrectionForce.get(), 0.0, deltaTime * 0.98)
|
||||
// )
|
||||
// }
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
settings: SubmarineSettings,
|
||||
state: SubmarineState,
|
||||
|
||||
@@ -54,6 +54,8 @@ class RidingAnimationData(val ride: PokemonEntity)
|
||||
fun update() {
|
||||
val activeRide = ride.hasControllingPassenger()
|
||||
|
||||
if (!activeRide) return
|
||||
|
||||
var currRot = Vec3.ZERO
|
||||
var angDelta = Vec3.ZERO
|
||||
|
||||
@@ -151,16 +153,14 @@ class RidingAnimationData(val ride: PokemonEntity)
|
||||
/******************************************************
|
||||
* Update springs
|
||||
*****************************************************/
|
||||
if (activeRide) {
|
||||
val driver = ride.controllingPassenger
|
||||
val vertInput = when {
|
||||
driver != null && driver.jumping-> 1.0
|
||||
driver != null && driver.isShiftKeyDown -> -1.0
|
||||
else -> 0.0
|
||||
}
|
||||
val input = if(driver != null) Vec3(driver.xxa.toDouble() * -1, vertInput, driver.zza.toDouble()) else Vec3.ZERO
|
||||
driverInputSpring.update(input, stiffness * 2, damping)
|
||||
val driver = ride.controllingPassenger
|
||||
val vertInput = when {
|
||||
driver != null && driver.jumping-> 1.0
|
||||
driver != null && driver.isShiftKeyDown -> -1.0
|
||||
else -> 0.0
|
||||
}
|
||||
val input = if(driver != null) Vec3(driver.xxa.toDouble() * -1, vertInput, driver.zza.toDouble()) else Vec3.ZERO
|
||||
driverInputSpring.update(input, stiffness * 2, damping)
|
||||
rotDeltaSpring.update(angDelta, stiffness, damping)
|
||||
velocitySpring.update(currentVelocity, stiffness, damping)
|
||||
rotSpring.update(currRot, stiffness, damping)
|
||||
|
||||
@@ -44,7 +44,6 @@ import com.cobblemon.mod.common.api.spawning.preset.BestSpawnerConfig
|
||||
import com.cobblemon.mod.common.api.spawning.preset.PokemonSpawnDetailPreset
|
||||
import com.cobblemon.mod.common.api.spawning.selection.SpawningSelector
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.BasicSpawner
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.FishingSpawnerFactory
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.FixedAreaSpawner
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawnerFactory
|
||||
@@ -140,6 +139,19 @@ object BestSpawner {
|
||||
|
||||
fun onServerStarted(server: MinecraftServer) {
|
||||
CobblemonSpawnPools.onServerLoad(server)
|
||||
fishingSpawner = FishingSpawnerFactory.createSharedSpawner()
|
||||
fishingSpawner = BasicSpawner(
|
||||
name = "fishing",
|
||||
spawnPool = CobblemonSpawnPools.WORLD_SPAWN_POOL
|
||||
).also {
|
||||
it.influences.add(
|
||||
BucketMultiplyingInfluence(
|
||||
multipliers = mapOf(
|
||||
"uncommon" to 2.25f,
|
||||
"rare" to 5.5f,
|
||||
"ultra-rare" to 5.5f,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* 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.spawning.spawner
|
||||
|
||||
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
|
||||
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnRules
|
||||
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
|
||||
import com.cobblemon.mod.common.api.spawning.influence.BucketMultiplyingInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.influence.PlayerLevelRangeInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.influence.PlayerLevelRangeInfluence.Companion.TYPICAL_VARIATION
|
||||
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
|
||||
import com.cobblemon.mod.common.util.mutableLazy
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import com.cobblemon.mod.common.api.spawning.BestSpawner;
|
||||
import com.cobblemon.mod.common.api.spawning.position.FishingSpawnablePosition
|
||||
|
||||
|
||||
/**
|
||||
* This isn't really a factory, one FishingSpawner is created as a field on [BestSpawner] on
|
||||
* server start. However, this emulates the systems that PlayerSpawner uses so extenders can
|
||||
* add influences that affect all fishing encounters.
|
||||
*
|
||||
* Responsible for creating [BasicSpawner]s for Fishing with whatever appropriate settings.
|
||||
* You can swap over the spawn pool and the influences here, and it will apply to all newly-generated
|
||||
* [BasicSpawner]s for Fishing. Has a global every-cast influence list, as well as a context-sensitive
|
||||
* influence list.
|
||||
*
|
||||
* @author Andy
|
||||
* @since January 23rd, 2026
|
||||
*/
|
||||
object FishingSpawnerFactory {
|
||||
var spawnPool: SpawnPool by mutableLazy {
|
||||
CobblemonSpawnPools.WORLD_SPAWN_POOL
|
||||
}
|
||||
|
||||
data class Context(
|
||||
val player: ServerPlayer,
|
||||
val rodStack: ItemStack,
|
||||
val lureLevel: Int,
|
||||
val luckOfTheSeaLevel: Int,
|
||||
val world: ServerLevel,
|
||||
val pos: BlockPos,
|
||||
val spawner: BasicSpawner
|
||||
)
|
||||
|
||||
/**
|
||||
* List of influences for a new cast of the fishing rod. Context is provided at
|
||||
* build time in [buildPositionInfluences]
|
||||
*/
|
||||
var positionInfluenceBuilders = mutableListOf<(ctx: Context) -> SpawningInfluence?>(
|
||||
{ ctx -> PlayerLevelRangeInfluence(ctx.player, TYPICAL_VARIATION) },
|
||||
{ ctx -> BucketNormalizingInfluence(tier = ctx.lureLevel + ctx.luckOfTheSeaLevel) }
|
||||
)
|
||||
|
||||
fun createSharedSpawner(): BasicSpawner {
|
||||
return BasicSpawner(
|
||||
name = "fishing",
|
||||
spawnPool = spawnPool
|
||||
).also { spawner ->
|
||||
spawner.influences += BucketMultiplyingInfluence(
|
||||
multipliers = mapOf(
|
||||
"uncommon" to 2.25f,
|
||||
"rare" to 5.5f,
|
||||
"ultra-rare" to 5.5f,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the per-cast influences for [FishingSpawnablePosition]
|
||||
*/
|
||||
fun buildPositionInfluences(ctx: Context): MutableList<SpawningInfluence> {
|
||||
return positionInfluenceBuilders.mapNotNullTo(mutableListOf()) { it(ctx) }
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* 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.spawning.spawner
|
||||
|
||||
import com.cobblemon.mod.common.api.fishing.SpawnBait
|
||||
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
|
||||
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnRules
|
||||
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
|
||||
import com.cobblemon.mod.common.api.spawning.influence.BucketMultiplyingInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.influence.SpawnBaitInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.rules.SpawnRule
|
||||
import com.cobblemon.mod.common.util.mutableLazy
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
|
||||
/**
|
||||
* Responsible for creating [FixedAreaSpawner]s for PokeSnacks with whatever appropriate settings.
|
||||
* You can swap over the spawn pool and the influences here, and it will apply to all newly-generated
|
||||
* [FixedAreaSpawner]s for PokeSnacks.
|
||||
*
|
||||
* @author Andy
|
||||
* @since January 23rd, 2026
|
||||
*/
|
||||
object PokeSnackSpawnerFactory {
|
||||
var spawnPool: SpawnPool by mutableLazy {
|
||||
CobblemonSpawnPools.WORLD_SPAWN_POOL
|
||||
}
|
||||
|
||||
data class Context(
|
||||
val world: ServerLevel,
|
||||
val pos: BlockPos,
|
||||
val horizontalRadius: Int,
|
||||
val verticalRadius: Int,
|
||||
val maxPokemonPerChunk: Float,
|
||||
val name: String,
|
||||
val baitEffects: List<SpawnBait.Effect>
|
||||
)
|
||||
|
||||
/**
|
||||
* The list of influences for a new [FixedAreaSpawner] for a PokeSnack.
|
||||
* The influences will be added to the spawner in the order they are added to this list.
|
||||
*/
|
||||
var influenceBuilders = mutableListOf<(ctx: Context) -> SpawningInfluence?>(
|
||||
{ ctx ->
|
||||
val stackedLureTier = ctx.baitEffects
|
||||
.filter { it.type == SpawnBait.Effects.RARITY_BUCKET }
|
||||
.sumOf { it.value }
|
||||
.toInt()
|
||||
|
||||
if (stackedLureTier <= 0) null else BucketNormalizingInfluence(
|
||||
tier = stackedLureTier,
|
||||
gradient = 0.2F,
|
||||
firstTier = 1.2F
|
||||
)
|
||||
},
|
||||
{ _ ->
|
||||
BucketMultiplyingInfluence(
|
||||
mapOf(
|
||||
"uncommon" to 2.25f,
|
||||
"rare" to 5.5f,
|
||||
"ultra-rare" to 5.5f,
|
||||
)
|
||||
)
|
||||
},
|
||||
{ ctx ->
|
||||
SpawnBaitInfluence(effects = ctx.baitEffects)
|
||||
},
|
||||
)
|
||||
|
||||
fun create(ctx: Context): FixedAreaSpawner {
|
||||
val influences = influenceBuilders.mapNotNull { it(ctx) }
|
||||
|
||||
return FixedAreaSpawner(
|
||||
name = ctx.name,
|
||||
spawnPool = spawnPool,
|
||||
world = ctx.world,
|
||||
position = ctx.pos,
|
||||
horizontalRadius = ctx.horizontalRadius,
|
||||
verticalRadius = ctx.verticalRadius,
|
||||
maxPokemonPerChunk = ctx.maxPokemonPerChunk
|
||||
).also { spawner ->
|
||||
spawner.influences.addAll(influences)
|
||||
|
||||
// TODO(Andy): PokeSnacks currently bypass pokemon spawn rules, should that be true?
|
||||
// Adding this rule will change that
|
||||
spawner.influences.addAll(
|
||||
CobblemonSpawnRules.rules.values
|
||||
.filter(SpawnRule::enabled)
|
||||
.flatMap(SpawnRule::components)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,8 +94,7 @@ enum class ShowdownActionResponseType(val loader: (RegistryFriendlyByteBuf) -> S
|
||||
PASS({ PassActionResponse }),
|
||||
SHIFT({ ShiftActionResponse()}),
|
||||
HEAL_ITEM({ HealItemActionResponse("potion") }),
|
||||
FORFEIT({ ForfeitActionResponse() }),
|
||||
FLEE_ATTEMPT({ FleeAttemptActionResponse() }),
|
||||
FORFEIT({ ForfeitActionResponse() });
|
||||
}
|
||||
|
||||
abstract class ShowdownActionResponse(val type: ShowdownActionResponseType) {
|
||||
@@ -281,11 +280,6 @@ class ForfeitActionResponse : ShowdownActionResponse(ShowdownActionResponseType.
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?) = "forfeit"
|
||||
}
|
||||
|
||||
class FleeAttemptActionResponse : ShowdownActionResponse(ShowdownActionResponseType.FLEE_ATTEMPT) {
|
||||
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean) = true
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?) = "flee-attempt"
|
||||
}
|
||||
|
||||
class ShowdownMoveset {
|
||||
lateinit var moves: List<InBattleMove>
|
||||
var trapped = false
|
||||
|
||||
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.battles
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon.LOGGER
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import net.minecraft.CrashReport
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@@ -38,15 +37,8 @@ class ShowdownThread : Thread("Cobblemon Showdown") {
|
||||
|
||||
override fun run() {
|
||||
LOGGER.info("Starting showdown service...")
|
||||
try {
|
||||
ShowdownService.service.openConnection()
|
||||
LOGGER.info("Showdown has been started!")
|
||||
} catch (e: Exception) {
|
||||
LOGGER.error("Failed to start showdown service!", e)
|
||||
} finally {
|
||||
this.latch.countDown()
|
||||
throw RuntimeException("Showdown service failed to initialize...")
|
||||
}
|
||||
|
||||
ShowdownService.service.openConnection()
|
||||
LOGGER.info("Showdown has been started!")
|
||||
this.latch.countDown()
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import kotlin.collections.associateWith
|
||||
import kotlin.collections.partition
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
|
||||
/**
|
||||
* AI that tries to choose the best move for the given situations. Based off of the Pokemon Trainer Tournament Simulator Github
|
||||
* https://github.com/cRz-Shadows/Pokemon_Trainer_Tournament_Simulator/blob/main/pokemon-showdown/sim/examples/Simulation-test-1.ts#L330
|
||||
@@ -48,7 +49,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
private val hpWeightConsideration = 0.25 // how much HP difference is a consideration for switchins
|
||||
private val hpFractionCoefficient = 0.4 // how much HP differences should be taken into account for switch ins
|
||||
private val boostWeightCoefficient = 1 // the amount of boosts considered a baseline to be removed
|
||||
private val switchOutMatchupThreshold = -2 // todo change this to get it feeling just right (-7 never switches)
|
||||
private val switchOutMatchupThreshold = 0 // todo change this to get it feeling just right (-7 never switches)
|
||||
private val selfKoMoveMatchupThreshold = 0.3
|
||||
private val trickRoomThreshold = 85
|
||||
private val recoveryMoveThreshold = 0.50
|
||||
@@ -218,14 +219,19 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
.filter { it.second.canBeSentOut() }
|
||||
|
||||
if (forceSwitch || activeBattlePokemon.isGone()) {
|
||||
// When forced to switch, pick the Pokemon with the best matchup estimation
|
||||
val bestSwitchScore = availableSwitches.maxOfOrNull { estimateMatchup(activeBattlePokemon, aiSide, battle, it.first) }
|
||||
?: return PassActionResponse
|
||||
val bestSwitch = availableSwitches.firstOrNull { estimateMatchup(activeBattlePokemon, aiSide, battle, it.first) == bestSwitchScore }
|
||||
?: return PassActionResponse
|
||||
if (battle.turn == 1) {
|
||||
val switchTo = activeBattlePokemon.actor.pokemonList.filter { it.canBeSentOut() }.randomOrNull()
|
||||
?: return DefaultActionResponse()
|
||||
switchTo.willBeSwitchedIn = true
|
||||
return SwitchActionResponse(switchTo.uuid)
|
||||
}
|
||||
else {
|
||||
val bestEstimation = availableSwitches.maxByOrNull { estimateMatchup(activeBattlePokemon, aiSide, battle, it.first) }
|
||||
?: return PassActionResponse
|
||||
|
||||
bestSwitch.second.willBeSwitchedIn = true
|
||||
return SwitchActionResponse(bestSwitch.second.uuid)
|
||||
bestEstimation.second.willBeSwitchedIn = true
|
||||
return SwitchActionResponse(bestEstimation.second.uuid)
|
||||
}
|
||||
}
|
||||
if (moveset == null) {
|
||||
return PassActionResponse
|
||||
@@ -253,9 +259,9 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
|
||||
if (!checkSkillLevel()){
|
||||
val move = availableMoves
|
||||
.filter { it.first.target.targetList(activeBattlePokemon)?.isEmpty() != true }
|
||||
.randomOrNull()
|
||||
?: return MoveActionResponse("struggle")
|
||||
.filter { it.first.target.targetList(activeBattlePokemon)?.isEmpty() != true }
|
||||
.randomOrNull()
|
||||
?: return MoveActionResponse("struggle")
|
||||
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
@@ -264,6 +270,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
if (checkSwitchOutSkill() && shouldSwitchOut(aiSide, battle, activeBattlePokemon, moveset)) {
|
||||
considerSwitching(activeBattlePokemon, activeTrackerPokemon, opponents, availableMoves, availableSwitches, battle, aiSide)
|
||||
}
|
||||
activeTrackerPokemon.firstTurn = false
|
||||
|
||||
// If HP is below 30% and not switching out, always use the most damaging move
|
||||
if (activeTrackerPokemon.currentHpPercent < 0.3 && !shouldSwitchOut(aiSide, battle, activeBattlePokemon, moveset)) {
|
||||
@@ -289,6 +296,8 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
return chooseMove(fakeOut.first, activeBattlePokemon, targets.firstOrNull { validTarget != null && validTarget.id == it.battlePokemon!!.uuid})
|
||||
}
|
||||
|
||||
activeTrackerPokemon.firstTurn = false
|
||||
|
||||
|
||||
// Explosion/Self destruct
|
||||
availableMoves.firstOrNull {
|
||||
@@ -306,7 +315,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
|
||||
// Deal with non-weather related field changing effects
|
||||
for (move in availableMoves) {
|
||||
|
||||
|
||||
// Tailwind
|
||||
if (move.first.id == "tailwind" && move.first.id != activeTracker.alliedSide.tailwindCondition &&
|
||||
availableSwitches.size > 2) {
|
||||
@@ -315,21 +324,21 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
|
||||
// Trick room
|
||||
if (move.first.id == "trickroom" && move.first.id != activeTracker.currentRoom
|
||||
&& availableSwitches.count { statEstimationActive(it.first, Stats.SPEED) <= trickRoomThreshold } >= 2) {
|
||||
&& availableSwitches.count { statEstimationActive(it.first, Stats.SPEED) <= trickRoomThreshold } >= 2) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
|
||||
// todo find a way to get list of active screens
|
||||
// Aurora veil
|
||||
if (move.first.id == "auroraveil" && move.first.id != activeTracker.alliedSide.screenCondition
|
||||
&& activeTracker.currentWeather in listOf("hail", "snow")) {
|
||||
&& activeTracker.currentWeather in listOf("hail", "snow")) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
|
||||
// todo find a way to get list of active screens
|
||||
// Light Screen
|
||||
if (move.first.id == "lightscreen" && move.first.id != activeTracker.alliedSide.screenCondition
|
||||
&& opponents.any { (it.species?.baseStats?.get(Stats.SPECIAL_ATTACK) ?: 0) > (it.species?.baseStats?.get(Stats.ATTACK) ?: 0) }
|
||||
&& opponents.any { (it.species?.baseStats?.get(Stats.SPECIAL_ATTACK) ?: 0) > (it.species?.baseStats?.get(Stats.ATTACK) ?: 0) }
|
||||
&& availableSwitches.size > 1) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
@@ -347,13 +356,13 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
for (move in availableMoves) {
|
||||
// Setup
|
||||
if (nOppRemainingMons >= 3 && move.first.id in AIUtility.entryHazards
|
||||
&& !activeTracker.opponentSide.sideHazards.contains(move.first.id)) {
|
||||
&& !activeTracker.opponentSide.sideHazards.contains(move.first.id)) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
|
||||
// Removal
|
||||
if (nRemainingMons >= 2 && move.first.id in AIUtility.antiHazardsMoves
|
||||
&& activeTracker.alliedSide.sideHazards.isNotEmpty()) {
|
||||
&& activeTracker.alliedSide.sideHazards.isNotEmpty()) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
}
|
||||
@@ -363,10 +372,10 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
// Court Change
|
||||
for (move in availableMoves) {
|
||||
if (move.first.id == "courtchange"
|
||||
&& (!AIUtility.entryHazards.none { it in activeTracker.alliedSide.sideHazards }
|
||||
|| setOf("tailwind", "lightscreen", "reflect").any { it in activeTracker.opponentSide.sideHazards })
|
||||
&& setOf("tailwind", "lightscreen", "reflect").none { it in activeTracker.opponentSide.sideHazards }
|
||||
&& AIUtility.entryHazards.none { it in activeTracker.opponentSide.sideHazards }) {
|
||||
&& (!AIUtility.entryHazards.none { it in activeTracker.alliedSide.sideHazards }
|
||||
|| setOf("tailwind", "lightscreen", "reflect").any { it in activeTracker.opponentSide.sideHazards })
|
||||
&& setOf("tailwind", "lightscreen", "reflect").none { it in activeTracker.opponentSide.sideHazards }
|
||||
&& AIUtility.entryHazards.none { it in activeTracker.opponentSide.sideHazards }) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
}
|
||||
@@ -374,7 +383,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
// Strength Sap
|
||||
for (move in availableMoves) {
|
||||
if (move.first.id == "strengthsap" && activeTrackerPokemon.currentHpPercent < 0.5
|
||||
&& activeTrackerPokemon.species!!.baseStats.getOrDefault(Stats.ATTACK, 0) > 80) {
|
||||
&& activeTrackerPokemon.species!!.baseStats.getOrDefault(Stats.ATTACK, 0) > 80) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
}
|
||||
@@ -382,10 +391,10 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
// Belly Drum
|
||||
for (move in availableMoves) {
|
||||
if (move.first.id == "bellydrum"
|
||||
&& (activeTrackerPokemon.currentHpPercent > 0.6
|
||||
&& activeTrackerPokemon.pokemon!!.heldItem().item == CobblemonItems.SITRUS_BERRY
|
||||
|| activeTrackerPokemon.currentHpPercent > 0.8)
|
||||
&& activeTrackerPokemon.boosts.getOrDefault(Stats.ATTACK, 0) < 1) {
|
||||
&& (activeTrackerPokemon.currentHpPercent > 0.6
|
||||
&& activeTrackerPokemon.pokemon!!.heldItem().item == CobblemonItems.SITRUS_BERRY
|
||||
|| activeTrackerPokemon.currentHpPercent > 0.8)
|
||||
&& activeTrackerPokemon.boosts.getOrDefault(Stats.ATTACK, 0) < 1) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
}
|
||||
@@ -395,8 +404,8 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
for (move in availableMoves) {
|
||||
AIUtility.weatherSetupMoves[move.first.id]?.let { requiredWeather ->
|
||||
if (activeTracker.currentWeather != requiredWeather.lowercase() &&
|
||||
!(activeTracker.currentWeather == "primordialsea" && requiredWeather == "raindance") &&
|
||||
!(activeTracker.currentWeather == "desolateland" && requiredWeather == "sunnyday")) {
|
||||
!(activeTracker.currentWeather == "primordialsea" && requiredWeather == "raindance") &&
|
||||
!(activeTracker.currentWeather == "desolateland" && requiredWeather == "sunnyday")) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
}
|
||||
@@ -422,8 +431,8 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
// Stall out side conditions
|
||||
if ((activeTracker.opponentSide.screenCondition != null || activeTracker.opponentSide.tailwindCondition != null) &&
|
||||
(activeTracker.alliedSide.screenCondition == null || activeTracker.alliedSide.tailwindCondition == null) ||
|
||||
opponents.any { it.currentStatus == null } &&
|
||||
activeTrackerPokemon.protectCount == 0 && opponents.none { it.currentAbility?.name == "unseenfist"}) {
|
||||
opponents.any { it.currentStatus == null } &&
|
||||
activeTrackerPokemon.protectCount == 0 && opponents.none { it.currentAbility?.name == "unseenfist"}) {
|
||||
activeTrackerPokemon.protectCount = 3
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
@@ -451,10 +460,10 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
activeTrackerPokemon.firstTurn = false
|
||||
|
||||
val move = availableMoves
|
||||
.filter { it.first.canBeUsed() }
|
||||
.filter { it.first.mustBeUsed() || it.first.target.targetList(activeBattlePokemon)?.isEmpty() != true }
|
||||
.randomOrNull()
|
||||
?: return MoveActionResponse("struggle")
|
||||
.filter { it.first.canBeUsed() }
|
||||
.filter { it.first.mustBeUsed() || it.first.target.targetList(activeBattlePokemon)?.isEmpty() != true }
|
||||
.randomOrNull()
|
||||
?: return MoveActionResponse("struggle")
|
||||
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
@@ -511,10 +520,6 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
return false
|
||||
|
||||
val activeTrackerPokemon = activeTracker.alliedSide.activePokemon.first { it.id == activeBattlePokemon.battlePokemon!!.uuid }
|
||||
|
||||
// Don't switch out if we just switched in
|
||||
if (activeTrackerPokemon.firstTurn)
|
||||
return false
|
||||
val actorTracker = activeTracker.alliedSide.actors.first { activeTrackerPokemon in it.activePokemon }
|
||||
val availableSwitches = actorTracker.party.filter { it.currentHp!! > 0 }
|
||||
val currentAbility = activeTrackerPokemon.pokemon!!.ability
|
||||
@@ -527,7 +532,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
// Only switch if a switch-in is significantly better than the current Pokémon
|
||||
val currentScore = estimateMatchup(activeBattlePokemon, side, battle)
|
||||
val bestSwitchScore = availableSwitches.maxOfOrNull { estimateMatchup(activeBattlePokemon, side, battle, it) } ?: currentScore
|
||||
val improvementThreshold = currentScore.absoluteValue * 0.5 + 3
|
||||
val improvementThreshold = currentScore.absoluteValue * 0.25 + 2
|
||||
if (bestSwitchScore <= currentScore + improvementThreshold) {
|
||||
return false
|
||||
}
|
||||
@@ -562,7 +567,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
if (availableSwitches.isEmpty() || availableSwitches.none { estimateMatchup(activeBattlePokemon, side, battle, it) > 0 })
|
||||
return false
|
||||
|
||||
|
||||
|
||||
|
||||
// ...and a 'good' reason to switch out
|
||||
if (opponentActiveTracker.any { it.boosts.getOrDefault(Stats.ACCURACY, 0) <= accuracySwitchThreshold } ||
|
||||
@@ -990,4 +995,6 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
return MoveActionResponse(move.id, chosenTarget.getPNX())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,7 +132,6 @@ class TrackerActor(val uuid: UUID) {
|
||||
actor.activePokemon.filter { newActive -> activePokemon.none { it.id == newActive.battlePokemon?.uuid } }.forEach {
|
||||
val active = party.firstOrNull { trackMon -> trackMon.id == it.battlePokemon?.uuid } ?: return@forEach
|
||||
party.remove(active)
|
||||
active.firstTurn = true // Reset firstTurn when switching in
|
||||
activePokemon.add(active)
|
||||
}
|
||||
val swappedOut = activePokemon.filter { trackMon -> actor.activePokemon.none { it.battlePokemon?.uuid == trackMon.id } }
|
||||
@@ -146,7 +145,6 @@ class TrackerActor(val uuid: UUID) {
|
||||
actor.activePokemon.filter { newActive -> activePokemon.none { it.id == newActive.battlePokemon?.uuid } }.forEach {
|
||||
val active = party.firstOrNull { trackMon -> trackMon.id == it.battlePokemon?.uuid } ?: return@forEach
|
||||
party.remove(active)
|
||||
active.firstTurn = true // Reset firstTurn when switching in
|
||||
activePokemon.add(active)
|
||||
}
|
||||
val swappedOut = activePokemon.filter { trackMon -> actor.activePokemon.none { it.battlePokemon?.uuid == trackMon.id } }
|
||||
|
||||
@@ -51,7 +51,6 @@ class FaintInstruction(battle: PokemonBattle, val message: BattleMessage) : Inte
|
||||
|
||||
preamble.thenAccept {
|
||||
faintingPokemon.effectedPokemon.currentHealth = 0
|
||||
faintingPokemon.faintedAt = battle.faintCounter++
|
||||
faintingPokemon.sendUpdate()
|
||||
CobblemonEvents.BATTLE_FAINTED.post(BattleFaintedEvent(battle, pokemon, context, pnx))
|
||||
battle.getActorAndActiveSlotFromPNX(pnx).second.battlePokemon = null
|
||||
|
||||
@@ -110,9 +110,6 @@ open class BattlePokemon(
|
||||
/** A set of all the BattlePokemon that they faced during the battle (for exp purposes) */
|
||||
val facedOpponents = mutableSetOf<BattlePokemon>()
|
||||
|
||||
/** The faint sequence number within the battle, or null if this pokemon has not fainted. */
|
||||
var faintedAt: Int? = null
|
||||
|
||||
/**
|
||||
* The [HeldItemManager] backing this [BattlePokemon].
|
||||
*/
|
||||
|
||||
@@ -133,10 +133,7 @@ class ApricornBlock(settings: Properties, val apricorn: Apricorn) : HorizontalDi
|
||||
override fun isBonemealSuccess(world: Level, random: RandomSource, pos: BlockPos, state: BlockState) = true
|
||||
|
||||
override fun performBonemeal(world: ServerLevel, random: RandomSource, pos: BlockPos, state: BlockState) {
|
||||
val curAge = state.getValue(AGE)
|
||||
val newAge = curAge + 1
|
||||
if (newAge > MAX_AGE) return
|
||||
world.setBlock(pos, state.setValue(AGE, newAge), UPDATE_CLIENTS)
|
||||
world.setBlock(pos, state.setValue(AGE, state.getValue(AGE) + 1), 2)
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
@@ -189,17 +186,17 @@ class ApricornBlock(settings: Properties, val apricorn: Apricorn) : HorizontalDi
|
||||
* This uses [Block.dropResources] to handle the drops.
|
||||
* It will also reset the [BlockState] of this block at the given location to the start of growth.
|
||||
*
|
||||
* @param world The [Level] the apricorn is in.
|
||||
* @param world The [World] the apricorn is in.
|
||||
* @param state The [BlockState] of the apricorn.
|
||||
* @param pos The [BlockPos] of the apricorn.
|
||||
* @return The [BlockState] after harvest.
|
||||
*/
|
||||
fun harvest(world: Level, state: BlockState, pos: BlockPos): BlockState {
|
||||
// Uses loot tables, to change the drops use 'data/cobblemon/loot_tables/blocks/<color>_apricorn.json'
|
||||
dropResources(state, world, pos)
|
||||
Block.dropResources(state, world, pos)
|
||||
// Don't use default as we want to keep the facing
|
||||
val resetState = state.setValue(AGE, MIN_AGE)
|
||||
world.setBlock(pos, resetState, UPDATE_CLIENTS)
|
||||
world.setBlock(pos, resetState, Block.UPDATE_CLIENTS)
|
||||
return resetState
|
||||
}
|
||||
|
||||
|
||||
@@ -81,32 +81,7 @@ class BerryBlock(private val berryIdentifier: ResourceLocation, settings: Proper
|
||||
|
||||
override fun isBonemealSuccess(world: Level, random: RandomSource, pos: BlockPos, state: BlockState) = !this.isMaxAge(state)
|
||||
|
||||
// Use Random Tick instead of BlockEntityTicker for performance optimization
|
||||
override fun <T : BlockEntity> getTicker(world: Level, blockState: BlockState, blockWithEntityType: BlockEntityType<T>): BlockEntityTicker<T>? = null
|
||||
|
||||
// Enable Random Tick: only immature and non-rooted berries need to tick
|
||||
override fun isRandomlyTicking(state: BlockState): Boolean {
|
||||
return state.getValue(AGE) < FRUIT_AGE && !state.getValue(IS_ROOTED)
|
||||
}
|
||||
|
||||
// Random Tick handler: uses time compensation to ensure correct growth speed
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun randomTick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
|
||||
val blockEntity = world.getBlockEntity(pos) as? BerryBlockEntity ?: return
|
||||
|
||||
// processTick calculates elapsed time and updates timer, returns whether growth is needed
|
||||
if (blockEntity.processTick(world, pos, state) && !world.getBlockTicks().hasScheduledTick(pos, this)) {
|
||||
// Schedule growth for next tick to avoid tick scheduler conflicts during randomTick
|
||||
world.scheduleTick(pos, this, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Scheduled tick handler: performs the actual growth
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun tick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
|
||||
if (state.getValue(IS_ROOTED)) return
|
||||
growHelper(world, random, pos, state)
|
||||
}
|
||||
override fun <T : BlockEntity> getTicker(world: Level, blockState: BlockState, blockWithEntityType: BlockEntityType<T>): BlockEntityTicker<T>? = createTickerHelper(blockWithEntityType, CobblemonBlockEntities.BERRY, BerryBlockEntity.TICKER)
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
|
||||
@@ -151,15 +151,13 @@ class CampfireBlock(settings: Properties, val isSoul: Boolean) : BaseEntityBlock
|
||||
override fun placeLiquid(level: LevelAccessor, pos: BlockPos, state: BlockState, fluidState: FluidState): Boolean {
|
||||
val blockEntity = level.getBlockEntity(pos)
|
||||
if (fluidState.type === Fluids.WATER && blockEntity is CampfireBlockEntity) {
|
||||
if (level is Level) {
|
||||
if (!level.isClientSide) {
|
||||
removePotItem(blockEntity, state, level, pos, null, true)
|
||||
level.playSoundServer(pos.center, SoundEvents.GENERIC_EXTINGUISH_FIRE, SoundSource.BLOCKS, 1.0F, 1.0F)
|
||||
}
|
||||
|
||||
if (level.isClientSide) for (i in 0..19) MCCampfireBlock.makeParticles(level, pos, false, true)
|
||||
if (!level.isClientSide) {
|
||||
removePotItem(blockEntity, state, level as Level, pos, null, true)
|
||||
level.playSoundServer(pos.center, SoundEvents.GENERIC_EXTINGUISH_FIRE, SoundSource.BLOCKS, 1.0F, 1.0F)
|
||||
}
|
||||
|
||||
if (level.isClientSide) for (i in 0..19) MCCampfireBlock.makeParticles(level as Level, pos, false, true)
|
||||
|
||||
level.scheduleTick(pos, fluidState.type, fluidState.type.getTickDelay(level))
|
||||
return true
|
||||
} else {
|
||||
|
||||
@@ -65,9 +65,6 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
|
||||
private val growthPoints = arrayListOf<ResourceLocation>()
|
||||
var mulchVariant = MulchVariant.NONE
|
||||
|
||||
// For Random Tick compensation: records the game time of last tick
|
||||
var lastTickTime: Long = 0L
|
||||
|
||||
/**
|
||||
* The idea behind the growth point sequence is it's a 16-long string of
|
||||
* hexadecimal numbers. They represent the order of the growth points that
|
||||
@@ -270,7 +267,6 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
|
||||
} catch (ignored: ResourceLocationException) {}
|
||||
}
|
||||
this.mulchDuration = nbt.getInt(MULCH_DURATION)
|
||||
this.lastTickTime = nbt.getLong(LAST_TICK_TIME)
|
||||
this.wasLoading = false
|
||||
if (nbt.contains(GROWTH_POINTS_SEQUENCE)) {
|
||||
growthPointSequence = nbt.getString(GROWTH_POINTS_SEQUENCE)
|
||||
@@ -291,7 +287,6 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
|
||||
nbt.putInt(MULCH_DURATION, mulchDuration)
|
||||
nbt.putString(GROWTH_POINTS_SEQUENCE, growthPointSequence)
|
||||
nbt.putString(MULCH_VARIANT, mulchVariant.toString())
|
||||
nbt.putLong(LAST_TICK_TIME, lastTickTime)
|
||||
}
|
||||
|
||||
override fun setChanged() {
|
||||
@@ -351,39 +346,24 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Random Tick compensation logic.
|
||||
* Calculates elapsed time since last tick and batch-decrements stageTimer.
|
||||
*/
|
||||
fun processTick(world: ServerLevel, pos: BlockPos, state: BlockState): Boolean {
|
||||
if (state.getValue(BerryBlock.IS_ROOTED)) return false
|
||||
|
||||
val currentTime = world.gameTime
|
||||
val elapsed = if (lastTickTime > 0L) {
|
||||
(currentTime - lastTickTime).toInt().coerceAtLeast(1)
|
||||
} else {
|
||||
1 // First tick, only decrement by 1
|
||||
}
|
||||
lastTickTime = currentTime
|
||||
|
||||
// Batch decrement the timer
|
||||
if (stageTimer > 0) {
|
||||
stageTimer = (stageTimer - elapsed).coerceAtLeast(0)
|
||||
}
|
||||
|
||||
// Return whether growth is needed
|
||||
return stageTimer <= 0
|
||||
}
|
||||
|
||||
//Using a similar approach to ComputerCraft, implement this client side
|
||||
interface RenderState : AutoCloseable {
|
||||
var needsRebuild: Boolean
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal val TICKER = BlockEntityTicker<BerryBlockEntity> { world, pos, state, blockEntity ->
|
||||
if (world.isClientSide) return@BlockEntityTicker
|
||||
if (state.getValue(BerryBlock.IS_ROOTED)) return@BlockEntityTicker
|
||||
if (blockEntity.stageTimer >= 0) {
|
||||
blockEntity.stageTimer--
|
||||
}
|
||||
if (blockEntity.stageTimer <= 0) {
|
||||
(state.block as BerryBlock).growHelper(world as ServerLevel, world.random, pos, state)
|
||||
}
|
||||
}
|
||||
//private const val LIFE_CYCLES = "life_cycles"
|
||||
private const val GROWTH_POINTS = "GrowthPoints"
|
||||
private const val LAST_TICK_TIME = "LastTickTime"
|
||||
private const val GROWTH_POINTS_SEQUENCE = "GrowthPointsSequence"
|
||||
private const val GROWTH_TIMER = "GrowthTimer"
|
||||
private const val STAGE_TIMER = "StageTimer"
|
||||
|
||||
@@ -29,7 +29,6 @@ import com.cobblemon.mod.common.api.spawning.influence.SpawnBaitInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.FixedAreaSpawner
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.PokeSnackSpawnerFactory
|
||||
import com.cobblemon.mod.common.block.PokeSnackBlock
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.item.components.BaitEffectsComponent
|
||||
@@ -63,22 +62,41 @@ open class PokeSnackBlockEntity(pos: BlockPos, state: BlockState) :
|
||||
}
|
||||
|
||||
val spawner: FixedAreaSpawner by lazy {
|
||||
val server = level as ServerLevel
|
||||
|
||||
val baitEffects = getBaitEffects()
|
||||
|
||||
val ctx = PokeSnackSpawnerFactory.Context(
|
||||
world = server,
|
||||
pos = blockPos,
|
||||
FixedAreaSpawner(
|
||||
name = "poke_snack_spawner_${level?.dimension()?.location()}}_$blockPos",
|
||||
spawnPool = CobblemonSpawnPools.WORLD_SPAWN_POOL,
|
||||
world = level as ServerLevel,
|
||||
position = blockPos,
|
||||
horizontalRadius = RADIUS,
|
||||
verticalRadius = RADIUS,
|
||||
maxPokemonPerChunk = Cobblemon.config.pokeSnackPokemonPerChunk,
|
||||
name = "poke_snack_spawner_${server.dimension().location()}_$blockPos",
|
||||
baitEffects = baitEffects
|
||||
)
|
||||
maxPokemonPerChunk = Cobblemon.config.pokeSnackPokemonPerChunk
|
||||
).also {
|
||||
it.influences.add(this)
|
||||
|
||||
PokeSnackSpawnerFactory.create(ctx).also { spawner ->
|
||||
spawner.influences.add(this)
|
||||
val baitEffects = getBaitEffects()
|
||||
|
||||
val stackedLureTier = baitEffects
|
||||
.filter { it.type == SpawnBait.Effects.RARITY_BUCKET }
|
||||
.sumOf { it.value }
|
||||
.toInt()
|
||||
|
||||
if (stackedLureTier > 0) {
|
||||
it.influences += BucketNormalizingInfluence(
|
||||
tier = stackedLureTier,
|
||||
gradient = 0.2F,
|
||||
firstTier = 1.2F
|
||||
)
|
||||
}
|
||||
|
||||
it.influences += BucketMultiplyingInfluence(
|
||||
mapOf(
|
||||
"uncommon" to 2.25f,
|
||||
"rare" to 5.5f,
|
||||
"ultra-rare" to 5.5f,
|
||||
)
|
||||
)
|
||||
|
||||
it.influences += SpawnBaitInfluence(effects = baitEffects)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import com.cobblemon.mod.common.block.entity.TintBlockEntity
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattle
|
||||
import com.cobblemon.mod.common.client.gui.PartyOverlay
|
||||
import com.cobblemon.mod.common.client.gui.PartyOverlayDataControl
|
||||
import com.cobblemon.mod.common.client.gui.RideControlsOverlay
|
||||
import com.cobblemon.mod.common.client.gui.RideOverlay
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
|
||||
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotScreen
|
||||
import com.cobblemon.mod.common.client.gui.party.PartyTutorialToasts
|
||||
@@ -112,7 +112,7 @@ object CobblemonClient {
|
||||
val overlay: PartyOverlay by lazy { PartyOverlay() }
|
||||
val battleOverlay: BattleOverlay by lazy { BattleOverlay() }
|
||||
val pokedexUsageContext: PokedexUsageContext by lazy { PokedexUsageContext() }
|
||||
val rideControlsOverlay: RideControlsOverlay by lazy { RideControlsOverlay() }
|
||||
val rideControlsOverlay: RideOverlay by lazy { RideOverlay() }
|
||||
|
||||
fun onLogin() {
|
||||
clientPlayerData = ClientGeneralPlayerData()
|
||||
|
||||
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.gui.drawPosablePortrait
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
import com.cobblemon.mod.common.api.text.darkGray
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
@@ -230,7 +229,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
|
||||
var levelScrollOffset = 0F
|
||||
var levelScrollPositionOffset = 0F
|
||||
var levelScrollSize = 17F
|
||||
if (expGainedData?.oldLevel != null) {
|
||||
if (expGainedData != null && expGainedData.oldLevel != null) {
|
||||
val ticksWithDelta = (expGainedData.ticks + partialDeltaTicks) - (PartyOverlayDataControl.BAR_UPDATE_BEFORE_TIME + PartyOverlayDataControl.BAR_FLASH_TIME)
|
||||
val transition = ticksWithDelta / PartyOverlayDataControl.LEVEL_UP_PORTRAIT_TIME
|
||||
if ((0F..<1F).contains(transition)) {
|
||||
@@ -404,48 +403,10 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
|
||||
blue = expBlue
|
||||
)
|
||||
|
||||
//Handling EXP Text
|
||||
|
||||
var expGained = 0
|
||||
var expTextTransparency = 0F
|
||||
var expTextOffset = 0F
|
||||
|
||||
if (expGainedData != null) {
|
||||
expGained = expGainedData.expGained
|
||||
val ticksWithDelta = expGainedData.ticks + partialDeltaTicks
|
||||
if (ticksWithDelta <= PartyOverlayDataControl.EXP_POPUP_TIME.fadeIn) {
|
||||
val transition = ticksWithDelta / PartyOverlayDataControl.EXP_POPUP_TIME.fadeIn
|
||||
val step = (floor(transition * 3)) + 1
|
||||
expTextTransparency = step * 0.25F
|
||||
expTextOffset = 4 - step
|
||||
} else if (ticksWithDelta <= PartyOverlayDataControl.EXP_POPUP_TIME.fadeIn + PartyOverlayDataControl.EXP_POPUP_TIME.hold) {
|
||||
expTextTransparency = 1F
|
||||
expTextOffset = 0F
|
||||
} else {
|
||||
val adjustedTicks = ticksWithDelta - (PartyOverlayDataControl.EXP_POPUP_TIME.fadeIn + PartyOverlayDataControl.EXP_POPUP_TIME.hold)
|
||||
val transition = adjustedTicks / PartyOverlayDataControl.EXP_POPUP_TIME.fadeOut
|
||||
val step = (floor(transition * 3)) + 1
|
||||
expTextTransparency = 1F - (step * 0.25F)
|
||||
expTextOffset = -step
|
||||
}
|
||||
}
|
||||
|
||||
if (expTextTransparency > 0) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = lang("ui.exp.number", expGained.toString()).bold(),
|
||||
x = panelX + selectedOffsetX + 57F,
|
||||
y = indexY + 17F + expTextOffset,
|
||||
scale = SCALE,
|
||||
shadow = true,
|
||||
opacity = expTextTransparency
|
||||
)
|
||||
}
|
||||
|
||||
//Handling Popups
|
||||
var ticksForMove = -1F
|
||||
var ticksForEvo = -1F
|
||||
|
||||
//Handling Popups
|
||||
if (expGainedData != null) {
|
||||
val ticksWithDelta = expGainedData.ticks + partialDeltaTicks
|
||||
val ticks = ticksWithDelta - (PartyOverlayDataControl.BAR_UPDATE_BEFORE_TIME + PartyOverlayDataControl.BAR_FLASH_TIME)
|
||||
@@ -476,7 +437,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
|
||||
return 0F to 1F
|
||||
} else {
|
||||
val adjustedPopupTicks = ticks - (PartyOverlayDataControl.POPUP_TIME.fadeIn + PartyOverlayDataControl.POPUP_TIME.hold)
|
||||
val transition = adjustedPopupTicks / PartyOverlayDataControl.POPUP_TIME.fadeOut
|
||||
val transition = adjustedPopupTicks / PartyOverlayDataControl.POPUP_TIME.fadeIn
|
||||
val step = (floor(transition * 3)) + 1
|
||||
return (-step) to (1F - (step * 0.25F))
|
||||
}
|
||||
@@ -490,7 +451,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = newEvoPopup,
|
||||
x = (panelX + selectedOffsetX + (if (expTextTransparency > 0 && movePopupTransparency > 0) 78F else 56.5F)) / SCALE,
|
||||
x = (panelX + selectedOffsetX + 56.5F) / SCALE,
|
||||
y = (indexY + 4 + evoPopupYOffset) / SCALE,
|
||||
width = 37,
|
||||
height = 20,
|
||||
@@ -503,7 +464,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
|
||||
matrixStack = matrices,
|
||||
texture = newMovesPopup,
|
||||
x = (panelX + selectedOffsetX + 56.5F) / SCALE,
|
||||
y = (indexY + (if (expTextTransparency > 0) 4 else 17) + movePopupYOffset) / SCALE,
|
||||
y = (indexY + 17 + movePopupYOffset) / SCALE,
|
||||
width = 37,
|
||||
height = 20,
|
||||
scale = SCALE,
|
||||
|
||||
@@ -19,10 +19,10 @@ object PartyOverlayDataControl {
|
||||
const val BAR_UPDATE_BEFORE_TIME = 15
|
||||
const val BAR_UPDATE_AFTER_TIME = 15
|
||||
const val BAR_FLASH_TIME = 2
|
||||
const val BAR_UPDATE_NO_LEVEL_TIME = BAR_UPDATE_BEFORE_TIME + BAR_UPDATE_AFTER_TIME + BAR_FLASH_TIME
|
||||
val POPUP_TIME = FadeTimings(3, 40, 3)
|
||||
const val LEVEL_UP_PORTRAIT_TIME = 10
|
||||
val EXP_POPUP_TIME = FadeTimings(3, BAR_UPDATE_BEFORE_TIME + BAR_FLASH_TIME + POPUP_TIME.total() - 6, 3)
|
||||
|
||||
val BAR_UPDATE_NO_LEVEL_TIME = BAR_UPDATE_BEFORE_TIME + BAR_UPDATE_AFTER_TIME + BAR_FLASH_TIME
|
||||
|
||||
private enum class RenderTarget {
|
||||
NONE,
|
||||
@@ -35,19 +35,21 @@ object PartyOverlayDataControl {
|
||||
|
||||
private var barFillupSound: SoundInstance? = null
|
||||
private var levelUpJingleSound: SoundInstance? = null
|
||||
private var levelUpJingleSoundType: SoundEvent? = null
|
||||
|
||||
fun pokemonGainedExp(pokemonUuid : UUID, oldLevel: Int?, expGained: Int, countOfMovesLearned: Int, countOfEvosUnlocked: Int = 0) {
|
||||
var time = BAR_UPDATE_NO_LEVEL_TIME
|
||||
if (countOfMovesLearned > 0 || countOfEvosUnlocked > 0) {
|
||||
time = BAR_UPDATE_BEFORE_TIME + BAR_FLASH_TIME + POPUP_TIME.total()
|
||||
}
|
||||
|
||||
val data = overlayData[pokemonUuid]
|
||||
val newData = ExpGainedData(oldLevel, expGained, countOfMovesLearned, countOfEvosUnlocked).also { it.ticksMax = BAR_UPDATE_BEFORE_TIME + BAR_FLASH_TIME + POPUP_TIME.total() }
|
||||
val newData = ExpGainedData(oldLevel, expGained, countOfMovesLearned, countOfEvosUnlocked).also { it.ticksMax = time }
|
||||
if (data != null) {
|
||||
if (data.movesGainedData != null) { //Absorb Data
|
||||
if (data.movesGainedData != null) {
|
||||
newData.countOfMovesLearned += data.movesGainedData!!.movesCount
|
||||
data.movesGainedData = null
|
||||
}
|
||||
if (data.evoGainedData != null) { //Absorb Data
|
||||
if (data.evoGainedData != null) {
|
||||
newData.countOfEvosUnlocked += data.evoGainedData!!.evolutionCount
|
||||
data.evoGainedData = null
|
||||
}
|
||||
|
||||
val expData = data.expGainedData
|
||||
@@ -70,7 +72,8 @@ object PartyOverlayDataControl {
|
||||
val data = overlayData[pokemonUuid]
|
||||
val newData = EvolutionGainedData(evoCount).also { it.ticksMax = POPUP_TIME.total() }
|
||||
if (data != null) {
|
||||
if (data.expGainedData != null) {
|
||||
if (data.expGainedData != null && data.expGainedData!!.ticks < BAR_UPDATE_BEFORE_TIME + BAR_FLASH_TIME) {
|
||||
data.expGainedData!!.ticksMax = BAR_UPDATE_BEFORE_TIME + BAR_FLASH_TIME + POPUP_TIME.total()
|
||||
data.expGainedData!!.countOfEvosUnlocked += evoCount
|
||||
return
|
||||
}
|
||||
@@ -90,7 +93,8 @@ object PartyOverlayDataControl {
|
||||
val data = overlayData[pokemonUuid]
|
||||
val newData = MovesGainedData(movesCount).also { it.ticksMax = POPUP_TIME.total() }
|
||||
if (data != null) {
|
||||
if (data.expGainedData != null) {
|
||||
if (data.expGainedData != null && data.expGainedData!!.ticks < BAR_UPDATE_BEFORE_TIME + BAR_FLASH_TIME) {
|
||||
data.expGainedData!!.ticksMax = BAR_UPDATE_BEFORE_TIME + BAR_FLASH_TIME + POPUP_TIME.total()
|
||||
data.expGainedData!!.countOfMovesLearned += movesCount
|
||||
return
|
||||
}
|
||||
@@ -133,28 +137,13 @@ object PartyOverlayDataControl {
|
||||
fun tick(paused: Boolean) {
|
||||
val deletion = mutableListOf<UUID>()
|
||||
if (paused) return
|
||||
|
||||
if (levelUpJingleSound != null && !Minecraft.getInstance().soundManager.isActive(levelUpJingleSound!!)) {
|
||||
levelUpJingleSound = null
|
||||
levelUpJingleSoundType = null
|
||||
}
|
||||
|
||||
overlayData.forEach { (id, data) ->
|
||||
if (data.expGainedData != null) { // We update this in order and render these in order, and as such we don't tick others
|
||||
val expGainedData = data.expGainedData!!
|
||||
expGainedData.ticks += 1
|
||||
if (expGainedData.ticks == BAR_UPDATE_BEFORE_TIME && expGainedData.oldLevel != null) {
|
||||
if (expGainedData.countOfEvosUnlocked > 0) {
|
||||
stopSound(levelUpJingleSound)
|
||||
levelUpJingleSound = playSound(CobblemonSounds.EVOLUTION_NOTIFICATION)
|
||||
levelUpJingleSoundType = CobblemonSounds.EVOLUTION_NOTIFICATION
|
||||
} else {
|
||||
if (levelUpJingleSoundType != CobblemonSounds.EVOLUTION_NOTIFICATION) {
|
||||
stopSound(levelUpJingleSound)
|
||||
levelUpJingleSound = playSound(CobblemonSounds.LEVELUP)
|
||||
levelUpJingleSoundType = CobblemonSounds.LEVELUP
|
||||
}
|
||||
}
|
||||
stopSound(levelUpJingleSound)
|
||||
levelUpJingleSound = playSound(CobblemonSounds.LEVELUP)
|
||||
}
|
||||
if (expGainedData.ticks >= expGainedData.ticksMax) {
|
||||
data.expGainedData = null
|
||||
@@ -177,7 +166,6 @@ object PartyOverlayDataControl {
|
||||
}
|
||||
if (getCurrentRenderTarget(id) == RenderTarget.NONE) deletion.add(id)
|
||||
}
|
||||
|
||||
deletion.forEach {
|
||||
overlayData.remove(it)
|
||||
}
|
||||
@@ -228,7 +216,7 @@ object PartyOverlayDataControl {
|
||||
//SOUNDS
|
||||
private fun playSound(soundEvent: SoundEvent): SoundInstance? {
|
||||
if (!PartyOverlay.canRender()) return null
|
||||
val soundInstance = SimpleSoundInstance.forUI(soundEvent, 1F, 1F)
|
||||
val soundInstance = SimpleSoundInstance.forUI(soundEvent, 1F)
|
||||
Minecraft.getInstance().soundManager.play(soundInstance)
|
||||
return soundInstance
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ fun drawProfilePokemon(
|
||||
a: Float = 1F,
|
||||
headYaw: Float = 0f,
|
||||
headPitch: Float = 0f,
|
||||
blockLight: Int = 13
|
||||
) = drawProfilePokemon(
|
||||
species = renderablePokemon.species.resourceIdentifier,
|
||||
matrixStack = matrixStack,
|
||||
@@ -64,7 +63,6 @@ fun drawProfilePokemon(
|
||||
a = a,
|
||||
headYaw = headYaw,
|
||||
headPitch = headPitch,
|
||||
blockLight = blockLight
|
||||
)
|
||||
|
||||
fun drawProfilePokemon(
|
||||
@@ -84,7 +82,6 @@ fun drawProfilePokemon(
|
||||
a: Float = 1F,
|
||||
headYaw: Float = 0f,
|
||||
headPitch: Float = 0f,
|
||||
blockLight: Int = 13
|
||||
) {
|
||||
RenderSystem.applyModelViewMatrix()
|
||||
matrixStack.scale(scale, scale, -scale)
|
||||
@@ -141,7 +138,7 @@ fun drawProfilePokemon(
|
||||
val light1 = Vector3f(-1F, 1F, 1.0F)
|
||||
val light2 = Vector3f(1.3F, -1F, 1.0F)
|
||||
RenderSystem.setShaderLights(light1, light2)
|
||||
val packedLight = LightTexture.pack(blockLight, 0)
|
||||
val packedLight = LightTexture.pack(11, 7)
|
||||
|
||||
val colour = toHex(r, g, b, a)
|
||||
model.withLayerContext(bufferSource, state, VaryingModelRepository.getLayers(species, state)) {
|
||||
|
||||
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.client.gui
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
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.types.air.*
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
|
||||
@@ -19,6 +21,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleBehaviour
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BoatBehaviour
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BurstBehaviour
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.DolphinBehaviour
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.DolphinState
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
@@ -30,17 +33,28 @@ import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.screens.ChatScreen
|
||||
import net.minecraft.client.gui.screens.Screen
|
||||
import net.minecraft.client.player.Input
|
||||
import net.minecraft.client.player.LocalPlayer
|
||||
import net.minecraft.client.renderer.BiomeColors
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.util.FastColor
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.level.BlockAndTintGetter
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.max
|
||||
import kotlin.math.round
|
||||
|
||||
class RideControlsOverlay : Gui(Minecraft.getInstance()) {
|
||||
class RideOverlay : Gui(Minecraft.getInstance()) {
|
||||
|
||||
companion object {
|
||||
private const val CONTROLS_FADE_FRAMES = 20F
|
||||
private const val BASELINE_FPS = 60F
|
||||
|
||||
private const val MOTION_FADE_FRAMES = 10
|
||||
private const val MOTION_FRAMES = 10
|
||||
private const val MOTION_OVERLAY_MAX_OPACITY = 0.6F
|
||||
|
||||
private const val HALF_SCALE = 0.5F
|
||||
|
||||
private val mouseResource = cobblemonResource("textures/gui/riding/mouse.png")
|
||||
@@ -59,6 +73,8 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
|
||||
private val keyJumpResource = cobblemonResource("textures/gui/riding/key_jump.png")
|
||||
private val keySneakWideResource = cobblemonResource("textures/gui/riding/key_sneak_wide.png")
|
||||
private val keyJumpWideResource = cobblemonResource("textures/gui/riding/key_jump_wide.png")
|
||||
|
||||
private val motionLinesResource = cobblemonResource("textures/gui/riding/motion_lines_overlay.png")
|
||||
}
|
||||
|
||||
private val screenExemptions: List<Class<out Screen>> = listOf(ChatScreen::class.java)
|
||||
@@ -67,6 +83,9 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
|
||||
private var controlsDurationFrames = controlsMaxDurationFrames
|
||||
private var controlsFadeFrames = CONTROLS_FADE_FRAMES
|
||||
|
||||
private var motionFrameIntervals = 0F
|
||||
private var motionFadeFrames = 0
|
||||
|
||||
private var lastMouseX = 0
|
||||
private var lastMouseY = 0
|
||||
|
||||
@@ -81,43 +100,70 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
|
||||
override fun render(context: GuiGraphics, tickCounter: DeltaTracker) {
|
||||
val minecraft = Minecraft.getInstance()
|
||||
|
||||
// Hiding if a Screen is open and not exempt
|
||||
minecraft.screen?.let { screen ->
|
||||
if ((!screenExemptions.contains(screen.javaClass as Class<out Screen>))) return
|
||||
}
|
||||
|
||||
if (minecraft.options.hideGui || minecraft.debugOverlay.showDebugScreen()) return
|
||||
|
||||
minecraft.player?.let { player ->
|
||||
// Hiding if a Screen is open and not exempt
|
||||
minecraft.screen?.let { screen ->
|
||||
if ((!screenExemptions.contains(screen.javaClass as Class<out Screen>))) return
|
||||
}
|
||||
|
||||
if (minecraft.options.hideGui || minecraft.debugOverlay.showDebugScreen()) return
|
||||
|
||||
val riddenEntity = player.vehicle
|
||||
|
||||
if (riddenEntity != null && riddenEntity is PokemonEntity) {
|
||||
// Only show controls to driver
|
||||
if (riddenEntity === player.controlledVehicle) {
|
||||
renderControls(minecraft, context, tickCounter, riddenEntity)
|
||||
}
|
||||
val ridingBehaviourSettings = riddenEntity.ridingController?.context?.settings ?: return
|
||||
val ridingBehaviourState = riddenEntity.ridingController?.context?.state ?: return
|
||||
|
||||
setCurrentRideBehaviour(ridingBehaviourSettings, ridingBehaviourState)
|
||||
|
||||
val tickDelta = tickCounter.realtimeDeltaTicks.takeIf { !minecraft.isPaused } ?: 0F
|
||||
val updateInterval = (tickDelta / 20F) * BASELINE_FPS
|
||||
|
||||
renderMotionOverlay(minecraft, player, riddenEntity, context, updateInterval, ridingBehaviourSettings, ridingBehaviourState)
|
||||
renderRideControls(minecraft, context, updateInterval, ridingBehaviourSettings, ridingBehaviourState)
|
||||
} else {
|
||||
resetControlsOverlayState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderControls(minecraft: Minecraft, context: GuiGraphics, tickCounter: DeltaTracker, riddenEntity: PokemonEntity) {
|
||||
// Update max duration if user has changed config
|
||||
val configDuration = Cobblemon.config.displayControlSeconds * BASELINE_FPS
|
||||
if (controlsMaxDurationFrames != configDuration) {
|
||||
controlsMaxDurationFrames = configDuration
|
||||
resetControlsOverlayState()
|
||||
fun renderMotionOverlay(minecraft: Minecraft, player: LocalPlayer, riddenEntity: Entity, context: GuiGraphics, updateInterval: Float, ridingBehaviourSettings: RidingBehaviourSettings, ridingBehaviourState: RidingBehaviourState) {
|
||||
if (currentBehaviourKey == JetBehaviour.KEY || currentBehaviourKey == DolphinBehaviour.KEY) {
|
||||
val isBoosting = if (currentBehaviourKey == JetBehaviour.KEY) (ridingBehaviourState as JetState).boosting.get()
|
||||
else (ridingBehaviourState as DolphinState).boosting.get()
|
||||
val interval = round(updateInterval).toInt()
|
||||
|
||||
motionFadeFrames = (motionFadeFrames + (interval * (if (isBoosting) 1 else -1))).coerceIn(0, MOTION_FADE_FRAMES)
|
||||
val opacity = (motionFadeFrames * (1F / MOTION_FADE_FRAMES)).coerceIn(0F, MOTION_OVERLAY_MAX_OPACITY)
|
||||
val tint = if (player.isUnderWater && currentBehaviourKey == DolphinBehaviour.KEY) BiomeColors.getAverageWaterColor(minecraft.level as BlockAndTintGetter, riddenEntity.blockPosition()) else 0xFFFFFF
|
||||
|
||||
motionFrameIntervals += updateInterval * (if (minecraft.options.cameraType.isMirrored) -1 else 1)
|
||||
val frameIndex = floor(motionFrameIntervals).toInt() % MOTION_FRAMES
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = motionLinesResource,
|
||||
x = 0,
|
||||
y = 0,
|
||||
textureHeight = 2560,
|
||||
width = minecraft.window.guiScaledWidth,
|
||||
height = minecraft.window.guiScaledHeight,
|
||||
vOffset = 256 * frameIndex,
|
||||
alpha = opacity,
|
||||
red = FastColor.ARGB32.red(tint) / 255F,
|
||||
green = FastColor.ARGB32.green(tint) / 255F,
|
||||
blue = FastColor.ARGB32.blue(tint) / 255F
|
||||
)
|
||||
} else if (motionFadeFrames > 0) {
|
||||
motionFadeFrames = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Don't render if duration config set to 0 or less
|
||||
if (controlsMaxDurationFrames <= 0F) return
|
||||
private fun setCurrentRideBehaviour(ridingBehaviourSettings: RidingBehaviourSettings, ridingBehaviourState: RidingBehaviourState) {
|
||||
val rideBehaviourKey = ridingBehaviourSettings.key
|
||||
|
||||
val rideBehaviourSettings = riddenEntity.ridingController?.context?.settings ?: return
|
||||
val ridingBehaviourState = riddenEntity.ridingController?.context?.state ?: return
|
||||
val rideBehaviourKey = rideBehaviourSettings.key
|
||||
|
||||
// Update current ride behaviour if changed
|
||||
// If composite, get active behaviour instead
|
||||
// Update current ride behaviour if changed.
|
||||
// If behaviour is composite, get active behaviour instead
|
||||
if (rideBehaviourKey == CompositeBehaviour.KEY) {
|
||||
val activeBehaviour = (ridingBehaviourState as CompositeState).activeBehaviour.get()
|
||||
if (currentBehaviourKey != activeBehaviour) {
|
||||
@@ -128,12 +174,21 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
|
||||
currentBehaviourKey = rideBehaviourKey
|
||||
resetControlsOverlayState()
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderRideControls(minecraft: Minecraft, context: GuiGraphics, updateInterval: Float, ridingBehaviourSettings: RidingBehaviourSettings, ridingBehaviourState: RidingBehaviourState) {
|
||||
// Update max duration if user has changed config
|
||||
val configDuration = Cobblemon.config.displayControlSeconds * BASELINE_FPS
|
||||
if (controlsMaxDurationFrames != configDuration) {
|
||||
controlsMaxDurationFrames = configDuration
|
||||
resetControlsOverlayState()
|
||||
}
|
||||
|
||||
// Don't render if duration config set to 0 or less
|
||||
if (controlsMaxDurationFrames <= 0F) return
|
||||
|
||||
// Render elements if entity has ride behaviour and if display duration has not ended yet
|
||||
if (rideBehaviourSettings != null && controlsFadeFrames > 0) {
|
||||
val tickDelta = tickCounter.realtimeDeltaTicks.takeIf { !minecraft.isPaused } ?: 0F
|
||||
val updateInterval = (tickDelta / 20F) * BASELINE_FPS
|
||||
|
||||
if (ridingBehaviourSettings != null && controlsFadeFrames > 0) {
|
||||
controlsDurationFrames = max(controlsDurationFrames - updateInterval, 0F)
|
||||
if (controlsDurationFrames == 0F && controlsFadeFrames > 0) controlsFadeFrames = max(controlsFadeFrames - updateInterval, 0F)
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* 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.client.gui
|
||||
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.components.AbstractWidget
|
||||
import net.minecraft.client.gui.components.events.GuiEventListener
|
||||
import net.minecraft.client.gui.narration.NarrationElementOutput
|
||||
import net.minecraft.network.chat.CommonComponents
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import kotlin.math.floor
|
||||
|
||||
open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int): AbstractWidget(x, y, width, height, CommonComponents.EMPTY) {
|
||||
val children = mutableListOf<Entry>()
|
||||
|
||||
var listHeight = 0
|
||||
var scrollAmount = 0.0
|
||||
|
||||
fun updateScroll(offset: Double) {
|
||||
setScroll(scrollAmount + offset)
|
||||
}
|
||||
|
||||
fun getMaxScroll(): Int {
|
||||
return (listHeight - height).coerceAtLeast(0)
|
||||
}
|
||||
|
||||
fun setScroll(scroll: Double) {
|
||||
scrollAmount = scroll.coerceIn(0.0, getMaxScroll().toDouble())
|
||||
}
|
||||
|
||||
fun removeEntry(index: Int) {
|
||||
listHeight -= children[index].height
|
||||
children.removeAt(index)
|
||||
updatePositions()
|
||||
}
|
||||
|
||||
fun removeEntry(entry: Entry) {
|
||||
listHeight -= entry.height
|
||||
children.remove(entry)
|
||||
updatePositions()
|
||||
}
|
||||
|
||||
fun removeLastEntry() {
|
||||
listHeight -= children.last().height
|
||||
children.removeLast()
|
||||
}
|
||||
|
||||
fun updatePositions() {
|
||||
var stackHeight = 0
|
||||
children.forEach {
|
||||
it.scrolledY = stackHeight
|
||||
stackHeight += it.height
|
||||
}
|
||||
updateScroll(0.0)
|
||||
}
|
||||
|
||||
fun addEntry(entry: Entry) {
|
||||
children.add(entry)
|
||||
entry.scrolledY = listHeight
|
||||
listHeight += entry.height
|
||||
}
|
||||
|
||||
override fun renderWidget(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
|
||||
var stackHeight = -scrollAmount.toInt()
|
||||
guiGraphics.enableScissor(x, y, x + width, y + height)
|
||||
children.forEachIndexed { index, entry ->
|
||||
if (stackHeight in 1..<height || stackHeight + entry.height in 1..<height) {
|
||||
entry.render(guiGraphics, index, x, y + stackHeight, mouseX, mouseY, partialTick)
|
||||
}
|
||||
stackHeight += entry.height
|
||||
}
|
||||
guiGraphics.disableScissor()
|
||||
}
|
||||
|
||||
fun getEntryAtPosition(mouseX: Int, mouseY: Int): Entry? {
|
||||
if (mouseX in x..(x + width) && mouseY in y..(y + height)) {
|
||||
var stackHeight = -scrollAmount
|
||||
children.forEach {
|
||||
if (mouseY.toDouble() in (y + stackHeight)..(y + stackHeight + it.height)) {
|
||||
return it
|
||||
}
|
||||
stackHeight += it.height
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun ensureVisible(index: Int) {
|
||||
val entry = children[index]
|
||||
if (entry.scrolledY - scrollAmount < 0) {
|
||||
setScroll(entry.scrolledY.toDouble())
|
||||
} else if (entry.scrolledY + entry.height > height) {
|
||||
setScroll(entry.scrolledY.toDouble() + entry.height)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseScrolled(mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double): Boolean {
|
||||
updateScroll(-verticalAmount * (listHeight / children.size / 2))
|
||||
return true
|
||||
}
|
||||
|
||||
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
|
||||
val entry = getEntryAtPosition(pMouseX.toInt(), pMouseY.toInt())
|
||||
if (entry != null) {
|
||||
return entry.mouseClicked(pMouseX, pMouseY, pButton)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun mouseReleased(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
|
||||
return children.any {
|
||||
it.mouseReleased(pMouseX, pMouseY, pButton)
|
||||
} || super.mouseReleased(pMouseX, pMouseY, pButton)
|
||||
}
|
||||
|
||||
fun getRowTop(entry: Entry): Int {
|
||||
var stackHeight = -scrollAmount.toInt()
|
||||
children.forEach {
|
||||
if (it == entry) {
|
||||
return y + stackHeight
|
||||
}
|
||||
stackHeight += it.height
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
override fun defaultButtonNarrationText(pNarrationElementOutput: NarrationElementOutput) {}
|
||||
override fun updateWidgetNarration(narrationElementOutput: NarrationElementOutput) {}
|
||||
|
||||
abstract inner class Entry(var height: Int): GuiEventListener {
|
||||
var scrolledY: Int = 0
|
||||
private var widgetFocused = false
|
||||
|
||||
override fun setFocused(focused: Boolean) {
|
||||
this.widgetFocused = focused
|
||||
}
|
||||
|
||||
override fun isFocused(): Boolean = widgetFocused
|
||||
|
||||
abstract fun render(guiGraphics: GuiGraphics, index: Int, x: Int, y: Int, mouseX: Int, mouseY: Int, partialTick: Float)
|
||||
}
|
||||
|
||||
inner class EmptySeparator(height: Int): Entry(height) {
|
||||
override fun render(guiGraphics: GuiGraphics, index: Int, x: Int, y: Int, mouseX: Int, mouseY: Int, partialTick: Float) {}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* 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.client.gui
|
||||
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.components.AbstractWidget
|
||||
import net.minecraft.client.gui.narration.NarrationElementOutput
|
||||
import net.minecraft.client.sounds.SoundManager
|
||||
import net.minecraft.network.chat.CommonComponents
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
class VariableObjectSelectionScrollbar(var parent: VariableObjectSelectionList, x: Int, y: Int, width: Int, height: Int): AbstractWidget(x, y, width, height, CommonComponents.EMPTY) {
|
||||
companion object {
|
||||
private val scrollerResource: ResourceLocation = ResourceLocation("minecraft", "textures/gui/sprites/widget/scroller.png")
|
||||
private val scrollerBackgroundResource: ResourceLocation = ResourceLocation("minecraft", "textures/gui/sprites/widget/scroller_background.png")
|
||||
|
||||
private const val SCROLLER_HEIGHT = 32
|
||||
}
|
||||
|
||||
private var scrolling = false
|
||||
private var scrollHoldOffset = 0.0
|
||||
|
||||
fun scrollbarVisible(): Boolean = parent.getMaxScroll() > 0
|
||||
|
||||
override fun renderWidget(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
|
||||
if (scrollbarVisible()) {
|
||||
val scrollPosition = (parent.scrollAmount / parent.getMaxScroll()) * (height - SCROLLER_HEIGHT)
|
||||
|
||||
blitk(
|
||||
matrixStack = guiGraphics.pose(),
|
||||
texture = scrollerBackgroundResource,
|
||||
x = x,
|
||||
y = y,
|
||||
width = width,
|
||||
height = height,
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = guiGraphics.pose(),
|
||||
texture = scrollerResource,
|
||||
x = x,
|
||||
y = y + scrollPosition,
|
||||
width = width,
|
||||
height = SCROLLER_HEIGHT,
|
||||
textureWidth = 6,
|
||||
textureHeight = SCROLLER_HEIGHT
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseScrolled(mouseX: Double, mouseY: Double, scrollX: Double, scrollY: Double): Boolean {
|
||||
return parent.mouseScrolled(mouseX, mouseY, scrollX, scrollY)
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
if (mouseX.toInt() in x..(x + width) && mouseY.toInt() in y..(y + height)) {
|
||||
if (button == 0) {
|
||||
var scrollPosition = (parent.scrollAmount / parent.getMaxScroll()) * (height - SCROLLER_HEIGHT)
|
||||
if (mouseY !in (y + scrollPosition)..(y + scrollPosition + SCROLLER_HEIGHT)) {
|
||||
val scrollRatio =
|
||||
((mouseY - y - SCROLLER_HEIGHT / 2) / (height - SCROLLER_HEIGHT)).coerceIn(0.0, 1.0)
|
||||
parent.setScroll(scrollRatio * parent.getMaxScroll())
|
||||
scrollPosition = (parent.scrollAmount / parent.getMaxScroll()) * (height - SCROLLER_HEIGHT)
|
||||
}
|
||||
scrollHoldOffset = mouseY - y - scrollPosition
|
||||
scrolling = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, dragX: Double, dragY: Double): Boolean {
|
||||
if (button == 0 && scrolling) {
|
||||
val scrollRatio = ((mouseY - y - scrollHoldOffset) / (height - SCROLLER_HEIGHT)).coerceIn(0.0,1.0)
|
||||
parent.setScroll(scrollRatio * parent.getMaxScroll())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
if (button == 0 && scrolling) {
|
||||
scrolling = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun playDownSound(handler: SoundManager) {}
|
||||
override fun updateWidgetNarration(narrationElementOutput: NarrationElementOutput) {}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.client.gui.battle.subscreen
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
|
||||
import com.cobblemon.mod.common.battles.FleeAttemptActionResponse
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.battle.SingleActionRequest
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
|
||||
@@ -66,7 +65,7 @@ class BattleGeneralActionSelection(
|
||||
|
||||
addOption(rank++, battleLang("ui.run"), BattleGUI.runResource) {
|
||||
CobblemonClient.battle?.minimised = true
|
||||
battleGUI.selectAction(request, FleeAttemptActionResponse())
|
||||
Minecraft.getInstance().player?.displayClientMessage(battleLang("run_prompt"), false)
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -375,8 +375,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
|
||||
matrixStack = matrices,
|
||||
partialTicks = delta,
|
||||
rotation = Quaternionf().fromEulerXYZDegrees(rotationVector),
|
||||
state = state,
|
||||
blockLight = 15
|
||||
state = state
|
||||
)
|
||||
|
||||
matrices.popPose()
|
||||
|
||||
@@ -15,19 +15,18 @@ import com.cobblemon.mod.common.api.gui.ColourLibrary
|
||||
import com.cobblemon.mod.common.api.gui.MultiLineLabelK
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.api.types.ElementalType
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
|
||||
import com.cobblemon.mod.common.client.gui.ExitButton
|
||||
import com.cobblemon.mod.common.client.gui.TypeIcon
|
||||
import com.cobblemon.mod.common.client.gui.VariableObjectSelectionScrollbar
|
||||
import com.cobblemon.mod.common.client.gui.startselection.widgets.CategoryList
|
||||
import com.cobblemon.mod.common.client.gui.startselection.widgets.SelectionButton
|
||||
import com.cobblemon.mod.common.client.gui.startselection.widgets.ExitButton
|
||||
import com.cobblemon.mod.common.client.gui.startselection.widgets.preview.ArrowButton
|
||||
import com.cobblemon.mod.common.client.gui.startselection.widgets.preview.SelectionButton
|
||||
import com.cobblemon.mod.common.client.gui.startselection.widgets.preview.StarterRoundabout
|
||||
import com.cobblemon.mod.common.client.gui.summary.Summary.Companion.PARTY
|
||||
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
|
||||
import com.cobblemon.mod.common.client.gui.summary.widgets.type.DualTypeWidget
|
||||
import com.cobblemon.mod.common.client.gui.summary.widgets.type.SingleTypeWidget
|
||||
import com.cobblemon.mod.common.client.gui.summary.widgets.type.TypeWidget
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.config.starter.RenderableStarterCategory
|
||||
import com.cobblemon.mod.common.net.messages.server.SelectStarterPacket
|
||||
@@ -35,14 +34,13 @@ import com.cobblemon.mod.common.pokemon.RenderablePokemon
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.cobblemon.mod.common.util.math.toRGB
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.components.toasts.Toast
|
||||
import net.minecraft.client.gui.screens.Screen
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.sounds.SoundEvent
|
||||
import java.io.FileNotFoundException
|
||||
import net.minecraft.network.chat.Component
|
||||
|
||||
/**
|
||||
* Starterselection Screen Thingy
|
||||
@@ -51,31 +49,32 @@ import java.io.FileNotFoundException
|
||||
* @since 2022-06-18
|
||||
*/
|
||||
class StarterSelectionScreen(private val categories: List<RenderableStarterCategory>): Screen("cobblemon.ui.starter.title".asTranslated()), CobblemonRenderable {
|
||||
|
||||
companion object {
|
||||
// Size of UI at scale 1
|
||||
private const val BASE_WIDTH = 239
|
||||
private const val BASE_HEIGHT = 197
|
||||
private const val BASE_WIDTH = 200
|
||||
private const val BASE_HEIGHT = 175
|
||||
|
||||
// Resources
|
||||
private val baseResource = cobblemonResource("textures/gui/starterselection/base.png")
|
||||
private val backgroundResource = cobblemonResource("textures/gui/starterselection/background.png")
|
||||
private val backgroundBallResource = cobblemonResource("textures/gui/starterselection/background_poke_ball.png")
|
||||
private val base = cobblemonResource("textures/gui/starterselection/starterselection_base.png")
|
||||
private val baseUnderlay = cobblemonResource("textures/gui/starterselection/starterselection_base_underlay.png")
|
||||
private val baseFrame = cobblemonResource("textures/gui/starterselection/starterselection_base_frame.png")
|
||||
|
||||
private val platformResource = cobblemonResource("textures/gui/starterselection/platform_base.png")
|
||||
private val platformShadow = cobblemonResource("textures/gui/pokedex/platform_shadow.png")
|
||||
private val typeSpacerResource = cobblemonResource("textures/gui/starterselection/type_spacer_single.png")
|
||||
private val ballResource = cobblemonResource("textures/item/poke_balls/poke_ball.png")
|
||||
// Type Backgrounds
|
||||
private val singleTypeBackground = cobblemonResource("textures/gui/starterselection/starterselection_type_slot1.png")
|
||||
private val doubleTypeBackground = cobblemonResource("textures/gui/starterselection/starterselection_type_slot2.png")
|
||||
}
|
||||
|
||||
var currentSelection = 0
|
||||
lateinit var currentCategory: RenderableStarterCategory
|
||||
private var currentSelection = 0
|
||||
private lateinit var currentCategory: RenderableStarterCategory
|
||||
private lateinit var modelWidget: ModelWidget
|
||||
private lateinit var currentPokemon: RenderablePokemon
|
||||
private lateinit var typeWidget: TypeWidget
|
||||
private lateinit var starterRoundaboutCenter: StarterRoundabout
|
||||
private lateinit var starterRoundaboutLeft: StarterRoundabout
|
||||
private lateinit var starterRoundaboutRight: StarterRoundabout
|
||||
|
||||
var ticksElapsed = 0
|
||||
var currentBallBackgroundFrame = 0
|
||||
|
||||
override fun renderBlurredBackground(delta: Float) {}
|
||||
override fun renderBlurredBackground(delta: Float) { }
|
||||
|
||||
override fun renderMenuBackground(context: GuiGraphics) {}
|
||||
|
||||
@@ -96,24 +95,55 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
|
||||
return
|
||||
}
|
||||
|
||||
addRenderableWidget(
|
||||
CategoryList(
|
||||
paneWidth = 71, paneHeight = BASE_HEIGHT - 11,
|
||||
topOffset = 6,
|
||||
entryHeight = 20, entryWidth = 57,
|
||||
categories = categories,
|
||||
listX = x - 2, listY = y + 8,
|
||||
starterSelectionScreen = this
|
||||
)
|
||||
)
|
||||
|
||||
val rightButton = ArrowButton(
|
||||
pX = x + 183, pY = y + 151,
|
||||
pWidth = 9, pHeight = 14,
|
||||
right = true
|
||||
) {
|
||||
right()
|
||||
}
|
||||
|
||||
val leftButton = ArrowButton(
|
||||
pX = x + 72, pY = y + 151,
|
||||
pWidth = 9, pHeight = 14,
|
||||
right = false
|
||||
) {
|
||||
left()
|
||||
}
|
||||
|
||||
addRenderableWidget(rightButton)
|
||||
addRenderableWidget(leftButton)
|
||||
|
||||
currentCategory = categories.first()
|
||||
currentPokemon = currentCategory.pokemon[currentSelection]
|
||||
|
||||
with(currentPokemon) {
|
||||
modelWidget = ModelWidget(
|
||||
pX = x + 6, pY = y + 17,
|
||||
pWidth = 118, pHeight = 100,
|
||||
pX = x + 85, pY = y + 50,
|
||||
pWidth = 102, pHeight = 100,
|
||||
pokemon = this,
|
||||
baseScale = 2.7f,
|
||||
baseScale = 2.0f,
|
||||
playCryOnClick = true,
|
||||
offsetY = -12.0
|
||||
)
|
||||
|
||||
typeWidget = typeWidget(this, x, y)
|
||||
}
|
||||
|
||||
addRenderableWidget(modelWidget)
|
||||
|
||||
val selectionButton = SelectionButton(
|
||||
pX = x + 13, pY = y + 180,
|
||||
pX = x + 106, pY = y + 124,
|
||||
pWidth = SelectionButton.BUTTON_WIDTH, pHeight = SelectionButton.BUTTON_HEIGHT
|
||||
) {
|
||||
CobblemonNetwork.sendToServer(
|
||||
@@ -122,214 +152,182 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
|
||||
selected = currentSelection
|
||||
)
|
||||
)
|
||||
playSound(CobblemonSounds.GUI_CLICK)
|
||||
onClose()
|
||||
Minecraft.getInstance().setScreen(null)
|
||||
}
|
||||
|
||||
addRenderableWidget(selectionButton)
|
||||
|
||||
// Add Exit Button
|
||||
starterRoundaboutCenter = StarterRoundabout(
|
||||
pX = x + 119, pY = height / 2 + 84,
|
||||
pWidth = StarterRoundabout.MODEL_WIDTH, pHeight = StarterRoundabout.MODEL_HEIGHT,
|
||||
pokemon = currentPokemon,
|
||||
rotationVector = this.modelWidget.rotationVector
|
||||
)
|
||||
|
||||
starterRoundaboutLeft = StarterRoundabout(
|
||||
pX = x + 89, pY = height / 2 + 84,
|
||||
pWidth = StarterRoundabout.MODEL_WIDTH, pHeight = StarterRoundabout.MODEL_HEIGHT,
|
||||
pokemon = currentCategory.pokemon[leftOfCurrentSelection()],
|
||||
clickAction = { _, _ -> this.left() },
|
||||
rotationVector = this.modelWidget.rotationVector
|
||||
)
|
||||
|
||||
starterRoundaboutRight = StarterRoundabout(
|
||||
pX = x + 149, pY = height / 2 + 84,
|
||||
pWidth = StarterRoundabout.MODEL_WIDTH, pHeight = StarterRoundabout.MODEL_HEIGHT,
|
||||
pokemon = currentCategory.pokemon[rightOfCurrentSelection()],
|
||||
clickAction = { _, _ -> this.right() },
|
||||
rotationVector = this.modelWidget.rotationVector
|
||||
)
|
||||
|
||||
addRenderableWidget(starterRoundaboutLeft)
|
||||
addRenderableWidget(starterRoundaboutCenter)
|
||||
addRenderableWidget(starterRoundaboutRight)
|
||||
|
||||
addRenderableWidget(
|
||||
ExitButton(pX = x + 210, pY = y + 181) {
|
||||
playSound(CobblemonSounds.GUI_CLICK)
|
||||
onClose()
|
||||
ExitButton(
|
||||
pX = x + 181, pY = y + 2,
|
||||
pWidth = 16, pHeight = 12,
|
||||
pXTexStart = 0, pYTexStart = 0, pYDiffText = 0
|
||||
) {
|
||||
Minecraft.getInstance().setScreen(null)
|
||||
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
}
|
||||
)
|
||||
|
||||
val categoryList = CategoryList(
|
||||
paneWidth = 97, paneHeight = 145,
|
||||
categories = categories,
|
||||
listX = x + 134, listY = y + 27,
|
||||
starterSelectionScreen = this
|
||||
)
|
||||
addRenderableWidget(categoryList)
|
||||
|
||||
addRenderableWidget(VariableObjectSelectionScrollbar(
|
||||
parent = categoryList,
|
||||
x = x + 231,
|
||||
y = y + 27,
|
||||
width = 3,
|
||||
height = 145
|
||||
))
|
||||
}
|
||||
|
||||
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
val matrices = context.pose()
|
||||
val x = (width - BASE_WIDTH) / 2
|
||||
val y = (height - BASE_HEIGHT) / 2
|
||||
|
||||
//Background
|
||||
// Render Underlay
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = backgroundResource,
|
||||
x = x + 6,
|
||||
y = y + 17,
|
||||
width = 118,
|
||||
height = 100
|
||||
texture = baseUnderlay,
|
||||
x = x, y = y,
|
||||
width = BASE_WIDTH, height = BASE_HEIGHT
|
||||
)
|
||||
|
||||
// Render Base
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = backgroundBallResource,
|
||||
x = x + 10.5,
|
||||
y = y + 12.5,
|
||||
width = 109,
|
||||
height = 109,
|
||||
textureHeight = 1744,
|
||||
vOffset = currentBallBackgroundFrame * 109
|
||||
texture = base,
|
||||
x = x, y = y,
|
||||
width = BASE_WIDTH, height = BASE_HEIGHT
|
||||
)
|
||||
|
||||
//Platform
|
||||
// Render Frame
|
||||
val (r, g, b) = currentPokemon.form.primaryType.hue.toRGB()
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = platformResource,
|
||||
x = x + 8.5,
|
||||
y = y + 88,
|
||||
width = 113,
|
||||
height = 32
|
||||
texture = baseFrame,
|
||||
red = r,
|
||||
green = g,
|
||||
blue = b,
|
||||
x = x, y = y,
|
||||
width = BASE_WIDTH, height = BASE_HEIGHT
|
||||
)
|
||||
val typePlatform = getPlatformResource(currentPokemon.form.primaryType)
|
||||
if (typePlatform != null) {
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = typePlatform,
|
||||
x = x + 8.5,
|
||||
y = y + 82,
|
||||
width = 113,
|
||||
height = 30
|
||||
)
|
||||
}
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = platformShadow,
|
||||
x = (x + 43) / 0.5F,
|
||||
y = (y + 93.5) / 0.5F,
|
||||
width = 90,
|
||||
height = 20,
|
||||
scale = 0.5F
|
||||
)
|
||||
|
||||
//Base
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = baseResource,
|
||||
x = x,
|
||||
y = y,
|
||||
width = BASE_WIDTH,
|
||||
height = BASE_HEIGHT
|
||||
)
|
||||
|
||||
//Type Icon
|
||||
TypeIcon(
|
||||
x = x + 65,
|
||||
y = y + 120,
|
||||
type = currentPokemon.form.primaryType,
|
||||
secondaryType = currentPokemon.form.secondaryType,
|
||||
centeredX = true
|
||||
).render(context)
|
||||
|
||||
if (currentPokemon.form.secondaryType == null) {
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = typeSpacerResource,
|
||||
x = x + 47,
|
||||
y = y + 123,
|
||||
width = 36,
|
||||
height = 12
|
||||
)
|
||||
}
|
||||
|
||||
//Name
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = ballResource,
|
||||
x = (x + 4) / 0.5F,
|
||||
y = (y + 3) / 0.5F,
|
||||
width = 16,
|
||||
height = 16,
|
||||
scale = 0.5F
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = currentPokemon.species.translatedName.bold(),
|
||||
x = x + 14,
|
||||
y = y + 2,
|
||||
shadow = true
|
||||
)
|
||||
|
||||
// Pokédex Number
|
||||
// Add preceding zeroes if Pokédex number is less than 4 digits
|
||||
var dexNo = currentPokemon.species.nationalPokedexNumber.toString()
|
||||
while (dexNo.length < 4) {
|
||||
dexNo = "0$dexNo"
|
||||
}
|
||||
dexNo = "#$dexNo"
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = dexNo.text().bold(),
|
||||
x = x + 79,
|
||||
y = y + 1,
|
||||
shadow = true
|
||||
)
|
||||
|
||||
//"Title"
|
||||
// Render Text
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = lang("ui.starter.title").bold(),
|
||||
x = x + 172,
|
||||
y = y + 11,
|
||||
shadow = true,
|
||||
centered = true
|
||||
x = x + 125, y = y + 3F,
|
||||
centered = true,
|
||||
scale = 1.4F,
|
||||
maxCharacterWidth = 120,
|
||||
shadow = true
|
||||
)
|
||||
|
||||
//Description Text
|
||||
val smallTextScale = 0.5F
|
||||
// Render Name
|
||||
val pokemonName = currentPokemon.species.translatedName
|
||||
val scale = 0.8F
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = pokemonName,
|
||||
centered = true,
|
||||
scale = scale,
|
||||
maxCharacterWidth = 50,
|
||||
x = x + 94,
|
||||
y = y + 19.5,
|
||||
shadow = false
|
||||
)
|
||||
|
||||
// Render Description
|
||||
val scale2 = 0.60F
|
||||
matrices.pushPose()
|
||||
matrices.scale(smallTextScale, smallTextScale, 1F)
|
||||
matrices.scale(scale2, scale2, 1F)
|
||||
|
||||
// TODO use all pokedex lines across multiple clickable pages in this screen
|
||||
MultiLineLabelK.create(
|
||||
component = currentPokemon.form.pokedex.first().asTranslated(),
|
||||
width = 114 / smallTextScale,
|
||||
maxLines = 5
|
||||
width = 127,
|
||||
maxLines = 4
|
||||
).renderLeftAligned(
|
||||
context = context,
|
||||
x = (x + 8) / smallTextScale,
|
||||
y = (y + 143) / smallTextScale,
|
||||
ySpacing = 5.5 / smallTextScale,
|
||||
colour = ColourLibrary.WHITE,
|
||||
shadow = true
|
||||
x = (x + 119) / scale2 + 4, y = (y + 18) / scale2 + 4.0,
|
||||
ySpacing = (8.0 / scale2) - 1.25,
|
||||
colour = ColourLibrary.WHITE, shadow = false
|
||||
)
|
||||
matrices.popPose()
|
||||
|
||||
// Render the type background
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = currentPokemon.form.secondaryType?.let { doubleTypeBackground } ?: singleTypeBackground,
|
||||
x = (currentPokemon.form.secondaryType?.let { x + 76.75 } ?: (x + 85.25)), y = y + 29.4,
|
||||
width = currentPokemon.form.secondaryType?.let { 35.25 } ?: 19, height = 19.25
|
||||
)
|
||||
// Render the type widget
|
||||
typeWidget.render(context, mouseX, mouseY, delta)
|
||||
// Render the rest
|
||||
super.render(context, mouseX, mouseY, delta)
|
||||
}
|
||||
|
||||
fun getPlatformResource(type: ElementalType): ResourceLocation? {
|
||||
return try {
|
||||
cobblemonResource("textures/gui/starterselection/starter_platform_base_${type.showdownId}.png")
|
||||
} catch (error: FileNotFoundException) {
|
||||
null
|
||||
}
|
||||
fun changeCategory(category: RenderableStarterCategory) {
|
||||
currentCategory = category
|
||||
currentSelection = 0
|
||||
updateSelection()
|
||||
}
|
||||
|
||||
fun selectPokemon(category: RenderableStarterCategory, slot: Int) {
|
||||
currentCategory = category
|
||||
currentSelection = slot
|
||||
private fun right() {
|
||||
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
currentSelection = rightOfCurrentSelection()
|
||||
updateSelection()
|
||||
}
|
||||
|
||||
private fun rightOfCurrentSelection() : Int = if (currentSelection + 1 <= currentCategory.pokemon.size - 1) currentSelection + 1 else 0
|
||||
|
||||
private fun left() {
|
||||
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
currentSelection = leftOfCurrentSelection()
|
||||
updateSelection()
|
||||
}
|
||||
|
||||
private fun leftOfCurrentSelection() : Int = if (currentSelection - 1 == -1) currentCategory.pokemon.size - 1 else currentSelection - 1
|
||||
|
||||
private fun updateSelection() {
|
||||
currentPokemon = currentCategory.pokemon[currentSelection].also {
|
||||
modelWidget.pokemon = it
|
||||
typeWidget = typeWidget(it, (width - BASE_WIDTH) / 2, (height - BASE_HEIGHT) / 2)
|
||||
}
|
||||
starterRoundaboutLeft.pokemon = currentCategory.pokemon[leftOfCurrentSelection()]
|
||||
starterRoundaboutCenter.pokemon = currentPokemon
|
||||
starterRoundaboutRight.pokemon = currentCategory.pokemon[rightOfCurrentSelection()]
|
||||
}
|
||||
|
||||
override fun tick() {
|
||||
ticksElapsed++
|
||||
|
||||
val delay = 3
|
||||
if (ticksElapsed % delay == 0) currentBallBackgroundFrame++
|
||||
if (currentBallBackgroundFrame == 16) currentBallBackgroundFrame = 0
|
||||
private fun typeWidget(pokemon: RenderablePokemon, x: Int, y: Int) : TypeWidget {
|
||||
return pokemon.form.secondaryType?.let {
|
||||
DualTypeWidget(
|
||||
pX = x + 77, pY = y + 30,
|
||||
pWidth = 18, pHeight = 18,
|
||||
pMessage = Component.literal("What?"),
|
||||
mainType = pokemon.form.primaryType, secondaryType = it
|
||||
)
|
||||
} ?: SingleTypeWidget(
|
||||
pX = x + 85, pY = y + 30,
|
||||
pWidth = 18, pHeight = 18,
|
||||
type = pokemon.form.primaryType,
|
||||
renderText = false
|
||||
)
|
||||
}
|
||||
|
||||
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
|
||||
@@ -342,8 +340,4 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
|
||||
}
|
||||
|
||||
override fun isPauseScreen() = true
|
||||
|
||||
fun playSound(soundEvent: SoundEvent) {
|
||||
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
|
||||
}
|
||||
}
|
||||
@@ -8,212 +8,126 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.startselection.widgets
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
|
||||
import com.cobblemon.mod.common.client.gui.PokemonGUIAnimationStyle
|
||||
import com.cobblemon.mod.common.client.gui.VariableObjectSelectionList
|
||||
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
|
||||
import com.cobblemon.mod.common.client.gui.startselection.StarterSelectionScreen
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
|
||||
import com.cobblemon.mod.common.config.starter.RenderableStarterCategory
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.components.ObjectSelectionList
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
import net.minecraft.network.chat.Component
|
||||
|
||||
class CategoryList(
|
||||
private val paneWidth: Int,
|
||||
private val paneHeight: Int,
|
||||
topOffset: Int,
|
||||
private val entryWidth: Int,
|
||||
entryHeight: Int,
|
||||
private val categories: List<RenderableStarterCategory>,
|
||||
val listX: Int,
|
||||
val listY: Int,
|
||||
private val minecraft: Minecraft = Minecraft.getInstance(),
|
||||
private val starterSelectionScreen: StarterSelectionScreen
|
||||
) : VariableObjectSelectionList(
|
||||
listX,
|
||||
listY,
|
||||
) : ObjectSelectionList<CategoryList.Category>(
|
||||
minecraft,
|
||||
paneWidth,
|
||||
paneHeight
|
||||
paneHeight,
|
||||
topOffset,
|
||||
entryHeight
|
||||
), CobblemonRenderable {
|
||||
|
||||
companion object {
|
||||
private val headerResource = cobblemonResource("textures/gui/starterselection/selection_container_header.png")
|
||||
private val footerResource = cobblemonResource("textures/gui/starterselection/selection_container_footer.png")
|
||||
private val edgeResource = cobblemonResource("textures/gui/starterselection/selection_container_edge.png")
|
||||
private val containerResource = cobblemonResource("textures/gui/starterselection/selection_container.png")
|
||||
|
||||
private val buttonResource = cobblemonResource("textures/gui/starterselection/starter_selection.png")
|
||||
private val buttonActiveResource = cobblemonResource("textures/gui/starterselection/starter_selection_active.png")
|
||||
|
||||
const val MARGIN = 4
|
||||
const val HEADER_HEIGHT = 7
|
||||
const val BUTTON_SEPERATION = 3
|
||||
const val CONTAINER_BUTTON_SIZE = 25
|
||||
const val CONTAINER_WIDTH = 89
|
||||
private const val CATEGORY_BUTTON_WIDTH = 51.5f
|
||||
private const val CATEGORY_BUTTON_HEIGHT = 16f
|
||||
private const val ENTRY_X_OFFSET = 10f
|
||||
private val categoryResource = cobblemonResource("textures/gui/starterselection/starterselection_slot.png")
|
||||
}
|
||||
|
||||
init {
|
||||
addEntry(EmptySeparator(MARGIN))
|
||||
createEntries().forEach {
|
||||
addEntry(it)
|
||||
addEntry(EmptySeparator(MARGIN))
|
||||
}
|
||||
this.x = listX
|
||||
this.y = listY
|
||||
this.correctSize()
|
||||
createEntries().forEach { addEntry(it) }
|
||||
//this.setRenderBackground(false)
|
||||
}
|
||||
|
||||
private fun createEntries() = categories.map { category ->
|
||||
val rows = (category.pokemon.size - 1) / 3 + 1
|
||||
val height = CONTAINER_BUTTON_SIZE * rows + BUTTON_SEPERATION * (rows + 1) + HEADER_HEIGHT + 3
|
||||
Category(category, height)
|
||||
private fun createEntries() = categories.map {
|
||||
Category(it)
|
||||
}
|
||||
|
||||
inner class Category(private val category: RenderableStarterCategory, height: Int) : Entry(height) {
|
||||
override fun renderListBackground(context: GuiGraphics) {}
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
super.renderWidget(context, mouseX, mouseY, delta)
|
||||
correctSize()
|
||||
}
|
||||
|
||||
private fun correctSize() {
|
||||
this.setRectangle(this.paneWidth, this.paneHeight, this.listX, this.listY)
|
||||
}
|
||||
|
||||
private fun scale(n: Int): Int = (this.minecraft.window.guiScale * n).toInt()
|
||||
override fun getRowWidth() = this.entryWidth
|
||||
override fun getScrollbarPosition(): Int {
|
||||
return this.listX + this.width - 5
|
||||
}
|
||||
|
||||
|
||||
inner class Category(private val category: RenderableStarterCategory) : Entry<Category>() {
|
||||
|
||||
override fun render(
|
||||
guiGraphics: GuiGraphics,
|
||||
context: GuiGraphics,
|
||||
index: Int,
|
||||
x: Int,
|
||||
y: Int,
|
||||
x: Int,
|
||||
entryWidth: Int,
|
||||
entryHeight: Int,
|
||||
mouseX: Int,
|
||||
mouseY: Int,
|
||||
partialTick: Float
|
||||
hovered: Boolean,
|
||||
tickDelta: Float
|
||||
) {
|
||||
val rows = (category.pokemon.size - 1) / 3 + 1
|
||||
val matrices = guiGraphics.pose()
|
||||
//Header
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = headerResource,
|
||||
x = x + MARGIN,
|
||||
y = y,
|
||||
width = CONTAINER_WIDTH,
|
||||
height = HEADER_HEIGHT
|
||||
)
|
||||
|
||||
val smallTextScale = 0.5F
|
||||
drawScaledText(
|
||||
context = guiGraphics,
|
||||
text = category.displayNameText.bold(),
|
||||
x = x + MARGIN + CONTAINER_WIDTH / 2,
|
||||
y = y + 2,
|
||||
shadow = true,
|
||||
centered = true,
|
||||
scale = smallTextScale
|
||||
)
|
||||
|
||||
//Container
|
||||
var stackHeight = HEADER_HEIGHT
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = edgeResource,
|
||||
x = x + MARGIN,
|
||||
y = y + stackHeight,
|
||||
width = CONTAINER_WIDTH,
|
||||
height = 1
|
||||
)
|
||||
stackHeight += 1
|
||||
|
||||
val containerInnerHeight = CONTAINER_BUTTON_SIZE * rows + BUTTON_SEPERATION * (rows + 1)
|
||||
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = containerResource,
|
||||
x = x + MARGIN,
|
||||
y = y + stackHeight,
|
||||
width = CONTAINER_WIDTH,
|
||||
height = containerInnerHeight
|
||||
)
|
||||
stackHeight += containerInnerHeight
|
||||
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = edgeResource,
|
||||
x = x + MARGIN,
|
||||
y = y + stackHeight,
|
||||
width = CONTAINER_WIDTH,
|
||||
height = 1
|
||||
)
|
||||
stackHeight += 1
|
||||
|
||||
//Footer
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = footerResource,
|
||||
x = x + MARGIN,
|
||||
y = y + stackHeight,
|
||||
width = CONTAINER_WIDTH,
|
||||
height = 1
|
||||
)
|
||||
|
||||
//Buttons
|
||||
for (index in 0..<category.pokemon.size) {
|
||||
val row = index / 3
|
||||
val column = index % 3
|
||||
|
||||
val startX = x + MARGIN + 5 + column * (CONTAINER_BUTTON_SIZE + 2)
|
||||
val startY = y + HEADER_HEIGHT + 1 + BUTTON_SEPERATION + row * (BUTTON_SEPERATION + CONTAINER_BUTTON_SIZE)
|
||||
|
||||
val active = starterSelectionScreen.currentCategory == category && starterSelectionScreen.currentSelection == index
|
||||
val hovered = getEntryAtPosition(mouseX, mouseY) == this && mouseX in startX..(startX + CONTAINER_BUTTON_SIZE) && mouseY in startY..(startY + CONTAINER_BUTTON_SIZE)
|
||||
|
||||
val matrices = context.pose()
|
||||
val isHovered = mouseX >= x && mouseY >= y && mouseX < x + entryWidth && mouseY < y + (entryHeight - 1)
|
||||
if (isHovered) {
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = if (active) buttonActiveResource else buttonResource,
|
||||
x = startX,
|
||||
y = startY,
|
||||
width = CONTAINER_BUTTON_SIZE,
|
||||
height = CONTAINER_BUTTON_SIZE,
|
||||
textureHeight = CONTAINER_BUTTON_SIZE * 2,
|
||||
vOffset = if (hovered) CONTAINER_BUTTON_SIZE else 0
|
||||
x = x + 2.5f, y = y,
|
||||
texture = categoryResource,
|
||||
width = CATEGORY_BUTTON_WIDTH, height = CATEGORY_BUTTON_HEIGHT,
|
||||
red = 0.75f, green = 0.75f, blue = 0.75f
|
||||
)
|
||||
|
||||
matrices.pushPose()
|
||||
matrices.translate(startX + (CONTAINER_BUTTON_SIZE / 2.0), startY.toDouble() - 6, 0.0)
|
||||
matrices.scale(3.5F, 3.5F, 1F)
|
||||
|
||||
val state = FloatingState()
|
||||
drawProfilePokemon(
|
||||
species = category.pokemon[index].species.resourceIdentifier,
|
||||
} else
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
|
||||
state = state,
|
||||
scale = 4.5F,
|
||||
partialTicks = 0F
|
||||
x = x + 2.5f, y = y,
|
||||
texture = categoryResource,
|
||||
width = CATEGORY_BUTTON_WIDTH, height = CATEGORY_BUTTON_HEIGHT
|
||||
)
|
||||
matrices.popPose()
|
||||
}
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = category.displayNameText,
|
||||
x = x + 28,
|
||||
y = y + 4.5F,
|
||||
scale = 1F,
|
||||
maxCharacterWidth = 50,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
val entryY = getRowTop(this)
|
||||
val hoveredButton = getButtonHovered(x, entryY, mouseX, mouseY)
|
||||
if (hoveredButton != -1) {
|
||||
starterSelectionScreen.selectPokemon(category, hoveredButton)
|
||||
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
starterSelectionScreen.changeCategory(category = category)
|
||||
minecraft.soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getButtonHovered(x: Int, y: Int, mouseX: Double, mouseY: Double): Int {
|
||||
for (index in 0..<category.pokemon.size) {
|
||||
val row = index / 3
|
||||
val column = index % 3
|
||||
|
||||
val startX = x + MARGIN + 5 + column * (CONTAINER_BUTTON_SIZE + 2)
|
||||
val startY = y + HEADER_HEIGHT + 1 + BUTTON_SEPERATION + row * (BUTTON_SEPERATION + CONTAINER_BUTTON_SIZE)
|
||||
|
||||
if (getEntryAtPosition(mouseX.toInt(), mouseY.toInt()) == this && mouseX.toInt() in startX..(startX + CONTAINER_BUTTON_SIZE) && mouseY.toInt() in startY..(startY + CONTAINER_BUTTON_SIZE)) {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
override fun getNarration(): Component {
|
||||
return Component.literal("Yes")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,11 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.startselection.widgets
|
||||
package com.cobblemon.mod.common.client.gui.startselection.widgets.preview
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.gui.ColourLibrary
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
@@ -28,39 +26,45 @@ class SelectionButton(
|
||||
pX: Int, pY: Int,
|
||||
pWidth: Int, pHeight: Int,
|
||||
onPress: OnPress
|
||||
): Button(pX, pY, pWidth, pHeight, Component.literal("SelectionButton"), onPress, DEFAULT_NARRATION),
|
||||
CobblemonRenderable {
|
||||
): Button(pX, pY, pWidth, pHeight, Component.literal("SelectionButton"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
|
||||
|
||||
companion object {
|
||||
private val buttonResource = cobblemonResource("textures/gui/starterselection/choose_button.png")
|
||||
const val BUTTON_WIDTH = 106
|
||||
const val BUTTON_HEIGHT = 14
|
||||
const val TEXT_HEIGHT = 10
|
||||
private val buttonTexture = cobblemonResource("textures/gui/starterselection/starterselection_button.png")
|
||||
const val BUTTON_WIDTH = 56
|
||||
const val BUTTON_HEIGHT = 12
|
||||
private const val SCALE = 0.7f
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager) {}
|
||||
override fun playDownSound(soundManager: SoundManager) {
|
||||
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
}
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
val matrices = context.pose()
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = buttonResource,
|
||||
x = x,
|
||||
y = y,
|
||||
width = BUTTON_WIDTH,
|
||||
height = BUTTON_HEIGHT,
|
||||
textureHeight = BUTTON_HEIGHT * 2,
|
||||
vOffset = if (isHovered) BUTTON_HEIGHT else 0
|
||||
)
|
||||
|
||||
if (isHovered)
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = buttonTexture,
|
||||
x = x + 0.6, y = y + 0.6,
|
||||
width = BUTTON_WIDTH - 0.25, height = BUTTON_HEIGHT - 0.25,
|
||||
red = 0.75f, green = 0.75f, blue = 0.75f
|
||||
)
|
||||
else
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = buttonTexture,
|
||||
x = x + 0.6, y = y + 0.6,
|
||||
width = BUTTON_WIDTH - 0.25, height = BUTTON_HEIGHT - 0.25
|
||||
)
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = lang("ui.starter.choosebutton").bold(),
|
||||
x = x + BUTTON_WIDTH / 2,
|
||||
y = y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2,
|
||||
shadow = true,
|
||||
centered = true
|
||||
text = lang("ui.starter.choosebutton"),
|
||||
x = x + BUTTON_WIDTH / 2, y = y + BUTTON_HEIGHT / 2 - 2.4,
|
||||
colour = ColourLibrary.WHITE,
|
||||
centered = true,
|
||||
maxCharacterWidth = 68,
|
||||
scale = SCALE,
|
||||
shadow = true
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,6 @@ class ModelWidget(
|
||||
var offsetY: Double = 0.0,
|
||||
val playCryOnClick: Boolean = false,
|
||||
val shouldFollowCursor: Boolean = false,
|
||||
val blockLight: Int = 13
|
||||
): SoundlessWidget(pX, pY, pWidth, pHeight, Component.literal("Summary - ModelWidget")) {
|
||||
|
||||
companion object {
|
||||
@@ -116,7 +115,6 @@ class ModelWidget(
|
||||
partialTicks = partialTicks,
|
||||
headYaw = currentYawAndPitch.first,
|
||||
headPitch = currentYawAndPitch.second,
|
||||
blockLight = blockLight
|
||||
)
|
||||
|
||||
heldItemRenderer.renderOnModel(
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* 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.client.net.debug
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.render.debug.LineState
|
||||
import com.cobblemon.mod.common.client.render.debug.SteerForceRenderer
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.ClientboundUpdateObsAvoidPacket
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
object S2CUpdateObsAvoidHandler : ClientNetworkPacketHandler<ClientboundUpdateObsAvoidPacket> {
|
||||
override fun handle(packet: ClientboundUpdateObsAvoidPacket, client: Minecraft) {
|
||||
client.executeIfPossible {
|
||||
//ObjectAvoidanceRenderer.avoidanceLines[packet.entityId] = packet.lines
|
||||
val lineState = SteerForceRenderer.lineStates.getOrPut(packet.entityId) { LineState() }
|
||||
lineState.update(packet.lines)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -309,7 +309,6 @@ class SnowstormParticle(
|
||||
boundingBox = AABB.ofSize(Vec3(x, y, z), radius, radius, radius)
|
||||
if (dx == 0.0 && dy == 0.0 && dz == 0.0) {
|
||||
updatePosition()
|
||||
expandBoundingBoxForCulling(radius)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -367,16 +366,6 @@ class SnowstormParticle(
|
||||
}
|
||||
}
|
||||
updatePosition()
|
||||
expandBoundingBoxForCulling(radius)
|
||||
}
|
||||
|
||||
private fun expandBoundingBoxForCulling(collisionRadius: Double) {
|
||||
val visualSizeX = abs(runtime.resolveDouble(storm.effect.particle.sizeX))
|
||||
val visualSizeY = abs(runtime.resolveDouble(storm.effect.particle.sizeY))
|
||||
val visualExtent = maxOf(visualSizeX, visualSizeY)
|
||||
if (visualExtent > collisionRadius) {
|
||||
boundingBox = AABB.ofSize(Vec3(x, y, z), visualExtent, visualExtent, visualExtent)
|
||||
}
|
||||
}
|
||||
|
||||
fun updatePosition() {
|
||||
|
||||
@@ -290,14 +290,8 @@ class ModelLayer {
|
||||
val emissive: Boolean = false
|
||||
val translucent: Boolean = false
|
||||
val translucent_cull: Boolean = false
|
||||
val scrolling: ScrollingTextureSettings? = null
|
||||
}
|
||||
|
||||
class ScrollingTextureSettings(
|
||||
val speedU: Float = 0.01F,
|
||||
val speedV: Float = 0.01F
|
||||
)
|
||||
|
||||
enum class SpriteType {
|
||||
PORTRAIT,
|
||||
PROFILE
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* 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.client.render.debug
|
||||
|
||||
import com.cobblemon.mod.common.api.net.Encodable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer
|
||||
import net.minecraft.client.Camera
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
/**
|
||||
* Data class currently used for storing line data for rendering in [SteerForceRenderer]
|
||||
*
|
||||
* @author Jackson
|
||||
* @since September 18th, 2025
|
||||
*/
|
||||
data class EntityDebugLine(
|
||||
val vector: Vec3, // Direction/magnitude in world space, relative to entity origin
|
||||
val r: Float,
|
||||
val g: Float,
|
||||
val b: Float,
|
||||
val a: Float,
|
||||
) : Encodable {
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeVec3(vector)
|
||||
buffer.writeFloat(r)
|
||||
buffer.writeFloat(g)
|
||||
buffer.writeFloat(b)
|
||||
buffer.writeFloat(a)
|
||||
}
|
||||
|
||||
companion object {
|
||||
operator fun invoke(vector: Vec3, r: Float = 1f, g: Float = 1f, b: Float = 1f, a: Float = 1f): EntityDebugLine {
|
||||
return EntityDebugLine(vector, r.coerceIn(0f,1f), g.coerceIn(0f,1f), b.coerceIn(0f,1f), a.coerceIn(0f,1f))
|
||||
}
|
||||
|
||||
fun decode(buffer: RegistryFriendlyByteBuf): EntityDebugLine {
|
||||
val vector = buffer.readVec3()
|
||||
return EntityDebugLine(vector, buffer.readFloat(), buffer.readFloat(), buffer.readFloat(), buffer.readFloat())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LineState {
|
||||
/**
|
||||
* The comparison of the previous and current lines relies on the order being consistent
|
||||
*/
|
||||
var previousLines: List<EntityDebugLine> = emptyList()
|
||||
var currentLines: List<EntityDebugLine> = emptyList()
|
||||
var lastReceivedLineTick: Long? = null
|
||||
|
||||
fun tick() {
|
||||
previousLines = currentLines
|
||||
}
|
||||
|
||||
fun update(newLines: List<EntityDebugLine>) {
|
||||
lastReceivedLineTick = Minecraft.getInstance().level?.gameTime
|
||||
currentLines = newLines
|
||||
if (previousLines.isEmpty()) previousLines = newLines
|
||||
}
|
||||
|
||||
// entityPos: interpolated world position of the entity this tick
|
||||
fun draw(vc: VertexConsumer, pose: PoseStack.Pose, partialTick: Float, camera: Camera, entity: PokemonEntity, partialTicks: Float) {
|
||||
previousLines.zip(currentLines).forEach { (prev, curr) ->
|
||||
val iVec = prev.vector.lerp(curr.vector, partialTick.toDouble())
|
||||
val iR = prev.r.lerp(curr.r, partialTick)
|
||||
val iG = prev.g.lerp(curr.g, partialTick)
|
||||
val iB = prev.b.lerp(curr.b, partialTick)
|
||||
val iA = prev.a.lerp(curr.a, partialTick)
|
||||
|
||||
// Interpolated world position — same method used everywhere in the renderer
|
||||
val worldStart = Vec3(
|
||||
Mth.lerp(partialTicks.toDouble(), entity.xOld, entity.x),
|
||||
Mth.lerp(partialTicks.toDouble(), entity.yOld, entity.y),
|
||||
Mth.lerp(partialTicks.toDouble(), entity.zOld, entity.z)
|
||||
)
|
||||
val worldEnd = worldStart.add(iVec)
|
||||
|
||||
val relStart = worldStart.subtract(camera.position)
|
||||
val relEnd = worldEnd.subtract(camera.position)
|
||||
|
||||
// Normal is the normalized direction of the segment
|
||||
val dir = iVec.normalize()
|
||||
val nx = dir.x.toFloat()
|
||||
val ny = dir.y.toFloat()
|
||||
val nz = dir.z.toFloat()
|
||||
|
||||
vc.addVertex(pose.pose(), relStart.x.toFloat(), relStart.y.toFloat(), relStart.z.toFloat())
|
||||
.setColor(iR, iG, iB, iA)
|
||||
.setNormal(pose, nx, ny, nz)
|
||||
|
||||
vc.addVertex(pose.pose(), relEnd.x.toFloat(), relEnd.y.toFloat(), relEnd.z.toFloat())
|
||||
.setColor(iR, iG, iB, iA)
|
||||
.setNormal(pose, nx, ny, nz)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun reset() {
|
||||
previousLines = emptyList()
|
||||
currentLines = emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Float.lerp(end: Float, t: Float): Float = this + (end - this) * t.coerceIn(0f, 1f)
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* 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.client.render.debug
|
||||
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.platform.events.PlatformEvents
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import net.minecraft.client.Camera
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.renderer.MultiBufferSource
|
||||
import net.minecraft.client.renderer.RenderType
|
||||
|
||||
object SteerForceRenderer {
|
||||
|
||||
// Lines to render attached to the uuid of entities.
|
||||
val lineStates = mutableMapOf<Int, LineState>()
|
||||
|
||||
fun registerTickHandler() {
|
||||
// Advance line States
|
||||
PlatformEvents.CLIENT_TICK_PRE.subscribe {
|
||||
lineStates.forEach { uuid, lineState ->
|
||||
lineState.tick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun render(poseStack: PoseStack, multiBufferSource: MultiBufferSource, partialTicks: Float, camera: Camera) {
|
||||
val matrix = poseStack.last().pose()
|
||||
val level = Minecraft.getInstance().level ?: return
|
||||
|
||||
val currTick = level.gameTime
|
||||
val ticksUntilStale = 3
|
||||
|
||||
val keysToRemove = lineStates.keys.filter { entityId ->
|
||||
val entity = level.getEntity(entityId) as? PokemonEntity
|
||||
val lastRecTick = lineStates[entityId]?.lastReceivedLineTick
|
||||
entity == null || (lastRecTick != null && (currTick - lastRecTick) > ticksUntilStale)
|
||||
}
|
||||
keysToRemove.forEach { lineStates.remove(it) }
|
||||
|
||||
// One buffer for all independent line segments
|
||||
val lineBuffer = multiBufferSource.getBuffer(RenderType.lines())
|
||||
|
||||
lineStates.forEach { entityId, lineState ->
|
||||
val entity = level.getEntity(entityId) as? PokemonEntity ?: return@forEach
|
||||
lineState.draw(lineBuffer, poseStack.last(), partialTicks, camera, entity, partialTicks)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
|
||||
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
|
||||
import com.cobblemon.mod.common.client.render.AnimatedModelTextureSupplier
|
||||
import com.cobblemon.mod.common.client.render.ModelLayer
|
||||
import com.cobblemon.mod.common.client.render.ScrollingTextureSettings
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.animation.*
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockActiveAnimation
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockAnimationRepository
|
||||
@@ -49,7 +48,6 @@ import com.mojang.blaze3d.vertex.PoseStack
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer
|
||||
import com.mojang.blaze3d.vertex.VertexFormat
|
||||
import com.mojang.math.Axis
|
||||
import net.minecraft.Util
|
||||
import net.minecraft.client.model.geom.ModelPart
|
||||
import net.minecraft.client.renderer.MultiBufferSource
|
||||
import net.minecraft.client.renderer.RenderStateShard
|
||||
@@ -455,16 +453,15 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
|
||||
val provider = bufferProvider
|
||||
if (provider != null) {
|
||||
for (layer in currentLayers) {
|
||||
val texture = layer.texture?.invoke(currentState ?: FloatingState()) ?: continue
|
||||
val scrolling = layer.scrolling
|
||||
|
||||
val renderLayer: RenderType = if (scrolling != null) {
|
||||
getScrollingLayer(texture, scrolling)
|
||||
} else if (layer.texture is AnimatedModelTextureSupplier && layer.texture.interpolation) {
|
||||
val interpolatedTexture = layer.texture.interpolatedTexture(currentState ?: FloatingState()) ?: continue
|
||||
makeLayer(DynamicStateShard(interpolatedTexture), layer.emissive, layer.translucent, layer.translucent_cull)
|
||||
} else {
|
||||
getLayer(texture, layer.emissive, layer.translucent, layer.translucent_cull)
|
||||
var renderLayer : RenderType
|
||||
if (layer.texture is AnimatedModelTextureSupplier && layer.texture.interpolation) {
|
||||
//Handle Interpolation
|
||||
val texture = layer.texture.interpolatedTexture(currentState ?: FloatingState()) ?: continue
|
||||
renderLayer = makeLayer(DynamicStateShard(texture), layer.emissive, layer.translucent, layer.translucent_cull)
|
||||
}
|
||||
else {
|
||||
val texture = layer.texture?.invoke(currentState ?: FloatingState()) ?: continue
|
||||
renderLayer = getLayer(texture, layer.emissive, layer.translucent, layer.translucent_cull)
|
||||
}
|
||||
val consumer = provider.getBuffer(renderLayer)
|
||||
val tint = layer.tint
|
||||
@@ -503,7 +500,7 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
|
||||
!emissive && translucent && translucentCull -> RenderStateShard.RENDERTYPE_ENTITY_TRANSLUCENT_CULL_SHADER
|
||||
!emissive && translucent -> RenderStateShard.RENDERTYPE_ENTITY_TRANSLUCENT_SHADER
|
||||
!emissive && !translucent -> RenderStateShard.RENDERTYPE_ENTITY_CUTOUT_SHADER
|
||||
else -> RenderStateShard.RENDERTYPE_ENTITY_TRANSLUCENT_EMISSIVE_SHADER
|
||||
else -> RenderStateShard.RENDERTYPE_ENTITY_TRANSLUCENT_EMISSIVE_SHADER // This one should be changed to maybe a custom shader? Translucent stuffs with things
|
||||
}
|
||||
)
|
||||
.setTextureState(texture)
|
||||
@@ -536,12 +533,6 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
|
||||
}
|
||||
}
|
||||
|
||||
fun getScrollingLayer(texture: ResourceLocation, scrolling: ScrollingTextureSettings): RenderType {
|
||||
val gameTime = Util.getMillis().toFloat() / 1000f
|
||||
val offsetU = (gameTime * scrolling.speedU) % 1.0f
|
||||
val offsetV = (gameTime * scrolling.speedV) % 1.0f
|
||||
return RenderType.breezeWind(texture, offsetU, offsetV)
|
||||
}
|
||||
|
||||
/** Applies the given pose's [ModelPartTransformation]s to the model, if there is a matching pose. */
|
||||
fun applyPose(state: PosableState, pose: Pose, intensity: Float) = pose.transformedParts.forEach { it.apply(state, intensity) }
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1
|
||||
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PitchTiltAnimation
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.createTransformation
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BiWingedFrame
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
|
||||
@@ -199,7 +198,6 @@ class PidgeottoModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame,
|
||||
transformTicks = 10,
|
||||
quirks = arrayOf(blink, flyQuirk1, flyQuirk2),
|
||||
animations = arrayOf(
|
||||
PitchTiltAnimation(bone = getPart("body"), maxChangePerTick = 1F),
|
||||
singleBoneLook(),
|
||||
bedrock("pidgeotto", "air_fly")
|
||||
)
|
||||
|
||||
@@ -146,12 +146,6 @@ class CobblemonConfig {
|
||||
@CobblemonConfigField(Category.Battles, lang = "experience_share_multiplier", SERVER)
|
||||
var experienceShareMultiplier = .5
|
||||
|
||||
@CobblemonConfigField(Category.Battles, lang = "award_experience_to_fainted_pokemon", SERVER)
|
||||
var awardExperienceToFaintedPokemon = false
|
||||
|
||||
@CobblemonConfigField(Category.Battles, lang = "award_experience_on_battle_loss", SERVER)
|
||||
var awardExperienceOnBattleLoss = false
|
||||
|
||||
@CobblemonConfigField(Category.Battles, lang = "lucky_egg_multiplier", SERVER)
|
||||
var luckyEggMultiplier = 1.5
|
||||
|
||||
@@ -253,7 +247,7 @@ class CobblemonConfig {
|
||||
var pastureBlockUpdateTicks = 40
|
||||
|
||||
@CobblemonConfigField(Category.World, lang = "pasture_max_wander_distance", SERVER)
|
||||
var pastureMaxWanderDistance = 32
|
||||
var pastureMaxWanderDistance = 64
|
||||
|
||||
@CobblemonConfigField(Category.World, lang = "pasture_max_per_chunk", SERVER)
|
||||
var pastureMaxPerChunk = 4F
|
||||
|
||||
@@ -126,14 +126,6 @@ class StarterConfig {
|
||||
PokemonProperties.parse("Fuecoco level=10"),
|
||||
PokemonProperties.parse("Quaxly level=10"),
|
||||
)
|
||||
),
|
||||
StarterCategory(
|
||||
name = "Special",
|
||||
displayName = "cobblemon.starterselection.category.special",
|
||||
pokemon = mutableListOf(
|
||||
PokemonProperties.parse("Pikachu level=10"),
|
||||
PokemonProperties.parse("Eevee level=10")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.entity.ai
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.cobblemon.mod.common.entity.OmniPathingEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.pokemon.ai.OmniPathNodeMaker
|
||||
import com.cobblemon.mod.common.util.deleteNode
|
||||
import com.cobblemon.mod.common.util.getWaterAndLavaIn
|
||||
@@ -34,7 +35,6 @@ import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.acos
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* A navigator designed to work with the [OmniPathNodeMaker], allowing a path that can cross land, water, and air
|
||||
@@ -49,8 +49,6 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
|
||||
|
||||
var cachedCurrentNode: Node? = null
|
||||
var currentNodeDistance = 0F
|
||||
var segmentIndex: Int = 0
|
||||
var pathFollowTarget: Vec3? = null
|
||||
|
||||
data class NavigationContext(
|
||||
val onHit: (damage: DamageSource) -> Unit = {},
|
||||
@@ -162,65 +160,6 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
|
||||
}
|
||||
|
||||
doStuckDetection(vec3d)
|
||||
|
||||
// Gather the steer target from the segment pathing
|
||||
// TODO: this shouldn't live here
|
||||
val currentPath = path ?: return
|
||||
if (!currentPath.isDone) {
|
||||
segmentIndex = segmentIndex.coerceIn(0, max(currentPath.nodeCount - 2, 0))
|
||||
pathFollowTarget = getPathSteerTarget(currentPath, mob)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a steering target for the current path
|
||||
* This video explains it well: https://www.youtube.com/watch?v=rlZYT-uvmGQ
|
||||
* Uses Reynolds path following for velocity steered entities
|
||||
*/
|
||||
fun getPathSteerTarget(
|
||||
path: Path,
|
||||
mob: Mob
|
||||
): Vec3 {
|
||||
// If this is the final segment then just seek towards the end
|
||||
if (segmentIndex >= path.nodeCount - 1) return path.getNode(path.nodeCount - 1).asVec3().add(0.5, 0.0, 0.5)
|
||||
|
||||
// Determine future position to use in steering calculations
|
||||
val lookAheadDist = 1.0 //TODO: Scale to look ahead a certain amount of ticks probably
|
||||
val futurePos = mob.position().add(mob.deltaMovement.normalize().scale(lookAheadDist))
|
||||
|
||||
// Gather the current segment points
|
||||
var pointS1 = path.getNode(segmentIndex).asVec3().add(0.5, 0.0, 0.5) // Add the 0.5's to get the blockface center as blockpos is on a corner
|
||||
var pointS2 = path.getNode(segmentIndex + 1).asVec3().add(0.5, 0.0, 0.5)
|
||||
|
||||
// Project the future point onto the current segment
|
||||
var vecA = futurePos.subtract(pointS1)
|
||||
var vecB = pointS2.subtract(pointS1)
|
||||
var normB = vecB.normalize()
|
||||
val lenB = vecB.length()
|
||||
var projAB = max(vecA.dot(normB), 0.0) // make at least 0 as negative means we are projecting behind the segment which we don't want
|
||||
|
||||
// If the projection goes off the front of the segment then advance to the next segment and recalculate the projection
|
||||
if (projAB > lenB && segmentIndex < path.nodeCount - 2) {
|
||||
segmentIndex++
|
||||
pointS1 = path.getNode(segmentIndex).asVec3().add(0.5, 0.0, 0.5)
|
||||
pointS2 = path.getNode(segmentIndex + 1).asVec3().add(0.5, 0.0, 0.5)
|
||||
vecA = futurePos.subtract(pointS1)
|
||||
vecB = pointS2.subtract(pointS1)
|
||||
normB = vecB.normalize()
|
||||
projAB = max(vecA.dot(normB), 0.0)
|
||||
}
|
||||
|
||||
// Get the projected point on the segment
|
||||
val projPoint = pointS1.add(normB.scale(projAB ))
|
||||
|
||||
// Determine if the future position is too far from the path by getting its distance from its projected position on the current path segment
|
||||
val futureDistFromPath = projPoint.distanceTo(futurePos)
|
||||
val acceptableDist = mob.bbWidth / 3.0 //TODO: figure out what this really needs to be
|
||||
|
||||
val pushForward = 0.1 // Advance the projected point along the segment to avoid going backwards
|
||||
val seekPoint = if (futureDistFromPath < acceptableDist) futurePos.add(normB.scale(pushForward)) else projPoint.add(normB.scale(pushForward))
|
||||
|
||||
return seekPoint
|
||||
}
|
||||
|
||||
fun isAirborne(world: Level, pos: BlockPos) =
|
||||
@@ -284,8 +223,6 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
|
||||
|
||||
fun moveTo(x: Double, y: Double, z: Double, speed: Double = 1.0, navigationContext: NavigationContext) {
|
||||
this.navigationContext = navigationContext
|
||||
segmentIndex = 0
|
||||
pathFollowTarget = null
|
||||
this.moveTo(x, y, z, speed)
|
||||
}
|
||||
|
||||
@@ -340,8 +277,6 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
|
||||
}
|
||||
|
||||
override fun moveTo(path: Path?, speed: Double): Boolean {
|
||||
segmentIndex = 0
|
||||
pathFollowTarget = null
|
||||
if (path != null && path.nodeCount > 0) {
|
||||
val node = path.getNode(0)!!
|
||||
// pokemonEntity.discard()
|
||||
@@ -464,8 +399,6 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
|
||||
this.cachedCurrentNode = null
|
||||
path = null
|
||||
nodeEvaluator.done()
|
||||
segmentIndex = 0
|
||||
pathFollowTarget = null
|
||||
// In case a path is cancelled instead of completed, check if we should stop flying
|
||||
if (pather.couldStopFlying() && !isAirborne(entity.level(), entity.blockPosition())) {
|
||||
pather.setFlying(false)
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* 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.entity.ai.steering
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacketToTrackingPlayers
|
||||
import com.cobblemon.mod.common.client.render.debug.EntityDebugLine
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.ClientboundUpdateObsAvoidPacket
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
// Runs a list of SteerRules in priority order, consuming a force budget.
|
||||
// High priority rules get their full contribution first. Lower priority
|
||||
// rules only get what budget remains.
|
||||
class SteerArbitrator(
|
||||
val entity: PokemonEntity,
|
||||
val maxForce: Double,
|
||||
val rules: MutableList<SteerRule> = mutableListOf()
|
||||
) {
|
||||
|
||||
private var sortedRules: List<SteerRule> = emptyList()
|
||||
private var dirty = true
|
||||
|
||||
fun addRule(rule: SteerRule) { rules.add(rule); dirty = true }
|
||||
fun removeRule(rule: SteerRule) { rules.remove(rule); dirty = true }
|
||||
fun clearRules() { rules.clear(); dirty = true }
|
||||
|
||||
fun calculate(debug: Boolean = false): Vec3 {
|
||||
if (dirty) {
|
||||
sortedRules = rules.sortedByDescending { it.priority }
|
||||
dirty = false
|
||||
}
|
||||
|
||||
var remaining = maxForce
|
||||
var result = Vec3.ZERO
|
||||
val debugLines = if (debug) mutableListOf<EntityDebugLine>() else null
|
||||
|
||||
for (rule in sortedRules) {
|
||||
if (remaining <= 0.0) break
|
||||
|
||||
val force = rule.calculate() ?: continue
|
||||
val scaled = force.scale(rule.weight.toDouble())
|
||||
val len = scaled.length()
|
||||
if (len <= 0.0) continue
|
||||
|
||||
val contribution = if (len <= remaining) {
|
||||
remaining -= len
|
||||
scaled
|
||||
} else {
|
||||
val partial = remaining
|
||||
remaining = 0.0
|
||||
scaled.normalize().scale(partial)
|
||||
}
|
||||
|
||||
result = result.add(contribution)
|
||||
|
||||
debugLines?.let {
|
||||
val (r, g, b) = rule.debugColor
|
||||
it.add(EntityDebugLine(contribution.scale(50.0), r, g, b, 1.0f))
|
||||
}
|
||||
}
|
||||
|
||||
debugLines?.let {
|
||||
if (result.lengthSqr() > 0.0)
|
||||
it.add(EntityDebugLine(result.scale(50.0), 1f, 1f, 1f, 1.5f))
|
||||
if (it.isNotEmpty())
|
||||
sendPacketToTrackingPlayers(entity, ClientboundUpdateObsAvoidPacket(entity.id, it))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* 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.entity.ai.steering
|
||||
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
// A per-tick steering force contributor evaluated by SteerArbitrator.
|
||||
// Return null from calculate() if the rule has nothing to say this tick.
|
||||
interface SteerRule {
|
||||
|
||||
val entity: PokemonEntity
|
||||
|
||||
// Higher = evaluated first, consumes force budget before lower priority rules.
|
||||
// 90-100: obstacle avoidance
|
||||
// 60-89: threat / boundary
|
||||
// 30-59: social (separation, alignment, cohesion)
|
||||
// 0-29: goal / wander
|
||||
val priority: Int
|
||||
|
||||
// Multiplied against the rule output before budget accounting.
|
||||
val weight: Float
|
||||
|
||||
// RGB 0-1 for the debug line drawn by SteerArbitrator.
|
||||
val debugColor: Triple<Float, Float, Float> get() = Triple(1f, 1f, 1f)
|
||||
|
||||
fun calculate(): Vec3?
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* 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.entity.ai.steering.rules
|
||||
|
||||
import com.cobblemon.mod.common.entity.ai.steering.SteerRule
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
|
||||
import com.cobblemon.mod.common.entity.pokemon.extensions.steerToTarget
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.max
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
import kotlin.math.cos
|
||||
|
||||
// Forward AABB projection obstacle avoidance. Stateful — one instance per entity.
|
||||
class ObstacleAvoidRule(
|
||||
override val entity: PokemonEntity,
|
||||
override val priority: Int = 100,
|
||||
override val weight: Float = 1.0f,
|
||||
override val debugColor: Triple<Float, Float, Float> = Triple(1f, 0f, 0f),
|
||||
val maxForce: Double = 0.02,
|
||||
val lookAheadTicks: Int = 10,
|
||||
val smoothingAlpha: Double = 0.25
|
||||
) : SteerRule {
|
||||
|
||||
private var prevForce: Vec3 = Vec3.ZERO
|
||||
|
||||
override fun calculate(): Vec3? {
|
||||
val topSpeed = (entity.moveControl as? PokemonMoveControl)?.steerSpeed ?: 0.1
|
||||
|
||||
val forwardVec = if (entity.deltaMovement.horizontalDistanceSqr() < 0.001) {
|
||||
val yaw = Math.toRadians(entity.yRot.toDouble())
|
||||
Vec3(-sin(yaw), 0.0, cos(yaw))
|
||||
} else {
|
||||
Vec3(entity.deltaMovement.x, 0.0, entity.deltaMovement.z).normalize()
|
||||
}
|
||||
|
||||
val lookAheadDist = max(entity.deltaMovement.length() * lookAheadTicks, entity.bbWidth / 2.0)
|
||||
val collBox = entity.boundingBox.move(forwardVec.scale(lookAheadDist))
|
||||
val shapes = entity.level().getBlockCollisions(entity, collBox)
|
||||
|
||||
val nearestShape = shapes.minByOrNull { shape ->
|
||||
shape.bounds().center.distanceToSqr(entity.position())
|
||||
}
|
||||
|
||||
if (nearestShape == null) {
|
||||
prevForce = prevForce.lerp(Vec3.ZERO, smoothingAlpha)
|
||||
return if (prevForce.lengthSqr() < 1e-6) null else prevForce
|
||||
}
|
||||
|
||||
val obsCenter = nearestShape.bounds().center
|
||||
val toObs = obsCenter.subtract(entity.position())
|
||||
val projVec = forwardVec.scale(toObs.dot(forwardVec))
|
||||
val lateralComponent = toObs.subtract(projVec)
|
||||
|
||||
val avoidForce = if (lateralComponent.lengthSqr() < 0.001) {
|
||||
// Dead ahead — turn right
|
||||
val rightVec = forwardVec.cross(Vec3(0.0, 1.0, 0.0))
|
||||
entity.steerToTarget(entity.position().add(rightVec), topSpeed, maxForce, horzOnly = true)
|
||||
} else {
|
||||
var avoidanceVec = Vec3(lateralComponent.x * -1.0, 0.0, lateralComponent.z * -1.0)
|
||||
val avoidStrength = sqrt(1.0 - (avoidanceVec.length() / (entity.bbWidth / 2.0 + 0.5)).coerceIn(0.0, 0.7))
|
||||
val avoidancePoint = entity.position().add(avoidanceVec)
|
||||
entity.steerToTarget(avoidancePoint, topSpeed, maxForce * avoidStrength, horzOnly = true)
|
||||
}
|
||||
|
||||
prevForce = prevForce.lerp(avoidForce, smoothingAlpha)
|
||||
return prevForce
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* 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.entity.ai.steering.rules
|
||||
|
||||
import com.cobblemon.mod.common.entity.ai.steering.SteerRule
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
|
||||
import com.cobblemon.mod.common.entity.pokemon.extensions.steerToTarget
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
// Steers toward a target position. Reads speed from PokemonMoveControl.steerSpeed.
|
||||
// Return null from targetProvider to deactivate for a tick.
|
||||
class SeekRule(
|
||||
override val entity: PokemonEntity,
|
||||
override val priority: Int = 10,
|
||||
override val weight: Float = 1.0f,
|
||||
override val debugColor: Triple<Float, Float, Float> = Triple(0f, 1f, 1f),
|
||||
val maxForce: Double,
|
||||
val horzOnly: Boolean = false,
|
||||
val arrivalRange: Double? = null,
|
||||
val targetProvider: (PokemonEntity) -> Vec3?
|
||||
) : SteerRule {
|
||||
|
||||
override fun calculate(): Vec3? {
|
||||
val target = targetProvider(entity) ?: return null
|
||||
val speed = (entity.moveControl as? PokemonMoveControl)?.steerSpeed ?: 0.1
|
||||
return entity.steerToTarget(target, speed, maxForce, arrivalRange, horzOnly)
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* 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.entity.ai.steering.rules
|
||||
|
||||
import com.cobblemon.mod.common.entity.ai.steering.SteerRule
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
|
||||
import com.cobblemon.mod.common.entity.pokemon.extensions.steerToTarget
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.sin
|
||||
|
||||
// Steers toward a point directly ahead of the entity's current velocity/yaw.
|
||||
// Useful as a baseline test rule.
|
||||
class WalkForwardRule(
|
||||
override val entity: PokemonEntity,
|
||||
override val priority: Int = 10,
|
||||
override val weight: Float = 1.0f,
|
||||
override val debugColor: Triple<Float, Float, Float> = Triple(0f, 1f, 0f),
|
||||
val maxForce: Double = 0.02
|
||||
) : SteerRule {
|
||||
|
||||
override fun calculate(): Vec3? {
|
||||
val speed = (entity.moveControl as? PokemonMoveControl)?.steerSpeed ?: 0.1
|
||||
|
||||
val forwardVec = if (entity.deltaMovement.horizontalDistanceSqr() < 0.001) {
|
||||
val yaw = Math.toRadians(entity.yRot.toDouble())
|
||||
Vec3(-sin(yaw), 0.0, cos(yaw))
|
||||
} else {
|
||||
Vec3(entity.deltaMovement.x, 0.0, entity.deltaMovement.z).normalize()
|
||||
}
|
||||
|
||||
val pointAhead = entity.position().add(forwardVec)
|
||||
return entity.steerToTarget(pointAhead, speed, maxForce, horzOnly = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cos(d: Double) = kotlin.math.cos(d)
|
||||
private fun sin(d: Double) = kotlin.math.sin(d)
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* 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.entity.ai.steering.rules
|
||||
|
||||
import com.cobblemon.mod.common.entity.ai.steering.SteerRule
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
|
||||
import com.cobblemon.mod.common.entity.pokemon.extensions.steerToTarget
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.sin
|
||||
import kotlin.random.Random
|
||||
|
||||
// Reynolds wander — projects a sphere ahead and offsets a point on it each tick.
|
||||
// Stateful: holds current wander angles, one instance per entity.
|
||||
// https://slsdo.github.io/steering-behaviors/
|
||||
class WanderRule(
|
||||
override val entity: PokemonEntity,
|
||||
override val priority: Int = 10,
|
||||
override val weight: Float = 1.0f,
|
||||
override val debugColor: Triple<Float, Float, Float> = Triple(1f, 0.5f, 0f),
|
||||
val maxForce: Double = 0.02,
|
||||
val wanderDisp: Double = 15.0,
|
||||
val centerDistance: Double = 4.0,
|
||||
val wanderRadius: Double = 4.0,
|
||||
val horzOnly: Boolean = false //TODO: all of these should be checks if flying or walking
|
||||
) : SteerRule {
|
||||
|
||||
var currHorzWander = 0.0
|
||||
var currVertWander = 0.0
|
||||
|
||||
override fun calculate(): Vec3? {
|
||||
val speed = (entity.moveControl as? PokemonMoveControl)?.steerSpeed ?: 0.1
|
||||
|
||||
val normVec = if (entity.deltaMovement.lengthSqr() > 0.0001) {
|
||||
entity.lookAngle.normalize()
|
||||
} else {
|
||||
Vec3(
|
||||
Random.nextDouble(-1.0, 1.0),
|
||||
Random.nextDouble(-1.0, 1.0),
|
||||
Random.nextDouble(-1.0, 1.0)
|
||||
).normalize()
|
||||
}
|
||||
|
||||
var wanderOff = normVec
|
||||
|
||||
// Clamp vertical wander when getting too steep
|
||||
val currPitchS = sin(Mth.atan2(wanderOff.y, wanderOff.horizontalDistance()))
|
||||
if (currPitchS >= 0.5f || currPitchS <= -0.5f) currVertWander = 0.0
|
||||
|
||||
wanderOff = wanderOff.xRot(Math.toRadians(currVertWander).toFloat())
|
||||
wanderOff = wanderOff.yRot(Math.toRadians(currHorzWander).toFloat())
|
||||
|
||||
val wanderCenter = entity.position().add(normVec.scale(centerDistance))
|
||||
val wanderPoint = wanderCenter.add(wanderOff.scale(wanderRadius))
|
||||
|
||||
// Advance wander angles
|
||||
currVertWander = Mth.wrapDegrees(currVertWander + Random.nextDouble(-wanderDisp, wanderDisp))
|
||||
currHorzWander = Mth.wrapDegrees(currHorzWander + Random.nextDouble(-wanderDisp, wanderDisp))
|
||||
|
||||
val force = entity.steerToTarget(wanderPoint, speed, maxForce)
|
||||
return if (horzOnly) Vec3(force.x, 0.0, force.z) else force
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,6 @@ import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluenc
|
||||
import com.cobblemon.mod.common.api.spawning.influence.PlayerLevelRangeInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.influence.PlayerLevelRangeInfluence.Companion.TYPICAL_VARIATION
|
||||
import com.cobblemon.mod.common.api.spawning.position.FishingSpawnablePosition
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.FishingSpawnerFactory
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.client.sound.EntitySoundTracker
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
@@ -763,6 +762,7 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
|
||||
lureLevel: Int
|
||||
) {
|
||||
val spawner = BestSpawner.fishingSpawner
|
||||
val bucketInfluence = BucketNormalizingInfluence(tier = lureLevel + luckOfTheSeaLevel)
|
||||
|
||||
val spawnCause = FishingSpawnCause(
|
||||
spawner = spawner,
|
||||
@@ -771,21 +771,14 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
|
||||
lureLevel = lureLevel,
|
||||
)
|
||||
|
||||
val ctx = FishingSpawnerFactory.Context(
|
||||
player = player,
|
||||
rodStack = rodItemStack,
|
||||
lureLevel = lureLevel,
|
||||
luckOfTheSeaLevel = luckOfTheSeaLevel,
|
||||
world = level() as ServerLevel,
|
||||
pos = position().toBlockPos(),
|
||||
spawner = spawner
|
||||
)
|
||||
|
||||
val spawnablePosition = FishingSpawnablePosition(
|
||||
cause = spawnCause,
|
||||
world = ctx.world,
|
||||
pos = ctx.pos,
|
||||
influences = FishingSpawnerFactory.buildPositionInfluences(ctx)
|
||||
world = level() as ServerLevel,
|
||||
pos = position().toBlockPos(),
|
||||
influences = mutableListOf(
|
||||
PlayerLevelRangeInfluence(player, TYPICAL_VARIATION),
|
||||
bucketInfluence
|
||||
)
|
||||
)
|
||||
|
||||
val result = spawner.calculateSpawnActionForPosition(
|
||||
|
||||
@@ -164,7 +164,6 @@ import net.minecraft.world.item.ItemUtils
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.LightLayer
|
||||
import net.minecraft.world.level.block.HorizontalDirectionalBlock
|
||||
import net.minecraft.world.level.block.SuspiciousEffectHolder
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.gameevent.GameEvent
|
||||
@@ -175,7 +174,10 @@ import net.minecraft.world.phys.AABB
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import kotlin.math.*
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
@Suppress("unused")
|
||||
open class PokemonEntity(
|
||||
@@ -211,7 +213,6 @@ open class PokemonEntity(
|
||||
@JvmStatic var RIDE_BOOSTS = SynchedEntityData.defineId(PokemonEntity::class.java, RideBoostsDataSerializer)
|
||||
@JvmStatic var RIDE_STAMINA = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.FLOAT)
|
||||
@JvmStatic var SCALE_MODIFIER = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.FLOAT)
|
||||
@JvmStatic val IS_SPRINTING = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.BOOLEAN)
|
||||
|
||||
const val BATTLE_LOCK = "battle"
|
||||
const val EVOLUTION_LOCK = "evolving"
|
||||
@@ -291,9 +292,6 @@ open class PokemonEntity(
|
||||
var shownItem: ItemStack
|
||||
get() = entityData.get(SHOWN_HELD_ITEM)
|
||||
set(value) = entityData.set(SHOWN_HELD_ITEM, value)
|
||||
var isPokemonSprinting: Boolean
|
||||
get() = entityData.get(IS_SPRINTING)
|
||||
set(value) { entityData.set(IS_SPRINTING, value) }
|
||||
|
||||
var lastLightningBoltUUID: UUID? = null
|
||||
|
||||
@@ -462,7 +460,6 @@ open class PokemonEntity(
|
||||
builder.define(RIDE_BOOSTS, emptyMap())
|
||||
builder.define(RIDE_STAMINA, 1F)
|
||||
builder.define(SCALE_MODIFIER, 1F)
|
||||
builder.define(IS_SPRINTING, false)
|
||||
}
|
||||
|
||||
override fun onSyncedDataUpdated(data: EntityDataAccessor<*>) {
|
||||
@@ -625,12 +622,8 @@ open class PokemonEntity(
|
||||
isPokemonFlying = flyDist - flyDistO > 0.005F
|
||||
isPokemonWalking = walkDist - walkDistO > 0.005F
|
||||
|
||||
// TODO: Figure out a better way to allow for this. Or maybe just always do the animations data accumulation its really not that bad
|
||||
if (this.pokemon.species.name.lowercase() == "charizard" || (passengers.isNotEmpty() && level().isClientSide)) {
|
||||
// This is so ugly why am I doing it like this... even if its for a test this is low
|
||||
if (passengers.isNotEmpty() && level().isClientSide) {
|
||||
rideSoundManager.tick()
|
||||
}
|
||||
if (passengers.isNotEmpty() && level().isClientSide) {
|
||||
rideSoundManager.tick()
|
||||
ridingAnimationData.update()
|
||||
} else if (!passengers.isNotEmpty() && level().isClientSide) {
|
||||
rideSoundManager.stop()
|
||||
@@ -684,9 +677,12 @@ open class PokemonEntity(
|
||||
yBodyRot = (spawnDirection * 1000F).toInt() / 1000F
|
||||
}
|
||||
|
||||
jumping = false
|
||||
if (this.tethering != null && !this.tethering!!.box.contains(this.x, this.y, this.z)) {
|
||||
this.tethering = null
|
||||
this.pokemon.recall()
|
||||
}
|
||||
|
||||
checkPastureTether()
|
||||
jumping = false
|
||||
|
||||
//This is so that pokemon in the pasture block are ALWAYS in sync with the pokemon box
|
||||
//Before, pokemon entities in pastures would hold an old ref to a pokemon obj and changes to that would not appear to the underlying file
|
||||
@@ -709,40 +705,6 @@ open class PokemonEntity(
|
||||
schedulingTracker.update(1 / 20F)
|
||||
}
|
||||
|
||||
|
||||
// Check to see if the entity needs recalled or repositioned if pastured.
|
||||
fun checkPastureTether() {
|
||||
// Teleport a pastured mon back to the pasture block if they are out of range
|
||||
if (this.tethering != null && !this.tethering!!.box.contains(this.x, this.y, this.z)) {
|
||||
|
||||
// Gather the ideal position (behind the pastureBlock)
|
||||
val pastureBlockPos = this.tethering!!.pasturePos
|
||||
val state = this.level().getBlockState(pastureBlockPos)
|
||||
|
||||
// Check to ensure we are working with a pastureblock here and it hasn't been broken
|
||||
if (state.block == CobblemonBlocks.PASTURE) {
|
||||
val direction = state.getValue(HorizontalDirectionalBlock.FACING)
|
||||
val width = this.boundingBox.xsize
|
||||
val tpPos = pastureBlockPos.offset(direction.normal.multiply(ceil(width).toInt() + 1)).center
|
||||
val heightAllowance = 4.0 // Allow spawning up to x blocks above the pastureBlock y level
|
||||
|
||||
// Attempt tp and recall if it fails
|
||||
if (!this.randomTeleport(tpPos.x, tpPos.y + heightAllowance, tpPos.z, false)) {
|
||||
// If the teleport was not successful then recall the mon
|
||||
this.tethering = null
|
||||
this.pokemon.recall()
|
||||
} else {
|
||||
// Stop any now invalid navigation post teleport
|
||||
this.navigation.stop()
|
||||
}
|
||||
} else {
|
||||
// If the block is destroyed or something else went wrong.
|
||||
this.tethering = null
|
||||
this.pokemon.recall()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun customServerAiStep() {
|
||||
this.getBrain().tick(level() as ServerLevel, this)
|
||||
// PokemonBrain.updateActivities(this)
|
||||
@@ -2157,9 +2119,7 @@ open class PokemonEntity(
|
||||
override fun stopSeenByPlayer(player: ServerPlayer) {
|
||||
if (this.ownerUUID == player.uuid && tethering == null) {
|
||||
// queuedToDespawn = true
|
||||
if (this.removalReason != RemovalReason.DISCARDED) {
|
||||
this.remove(RemovalReason.DISCARDED)
|
||||
}
|
||||
this.remove(RemovalReason.DISCARDED)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,547 +0,0 @@
|
||||
/*
|
||||
* 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.entity.pokemon.ai
|
||||
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonActivities
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacketToTrackingPlayers
|
||||
import com.cobblemon.mod.common.client.render.debug.EntityDebugLine
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.extensions.*
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.ClientboundUpdateObsAvoidPacket
|
||||
import com.cobblemon.mod.common.util.math.geometry.toRadians
|
||||
import com.cobblemon.mod.common.util.resolveDouble
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.control.MoveControl
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.level.ClipContext
|
||||
import net.minecraft.world.phys.HitResult
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.*
|
||||
import kotlin.random.Random
|
||||
|
||||
class BoidsMoveControl(val pokemonEntity: PokemonEntity, val target: LivingEntity) : MoveControl(pokemonEntity) {
|
||||
companion object {
|
||||
const val VERY_CLOSE = 2.500000277905201E-3
|
||||
const val MAX_SPEED = 0.3 / 2
|
||||
const val MAX_FORCE = 0.02
|
||||
const val PREDATOR_MAX_SPEED = 0.6
|
||||
// const val MAX_SPEED = 0.2
|
||||
// const val MAX_FORCE = 0.013
|
||||
}
|
||||
|
||||
// TODO: might need to hold its own velocity so minecraft doesn't do its nonsense to it?
|
||||
// Can handle collisions ourselves as well
|
||||
var speed: Double? = null
|
||||
var drawLines: MutableList<EntityDebugLine> = mutableListOf()
|
||||
|
||||
// wander vars
|
||||
var currHorzWander = 0.0
|
||||
var currVertWander = 0.0
|
||||
val wanderDisp = 15.0 // The degrees of change from tick to tick in wander angle
|
||||
|
||||
override fun tick() {
|
||||
// Clear since the info is sent and is now stale
|
||||
drawLines.clear()
|
||||
|
||||
// Steering constants
|
||||
val orbitRadius = 16.0
|
||||
val targetPos = target.position().add(0.0, 4.0, 0.0)
|
||||
val arrivalRange = 2.0 // in blocks
|
||||
|
||||
val species = pokemonEntity.pokemon.species.name
|
||||
// Set this to ensure the flying pose is active
|
||||
|
||||
// ignore all the friction stuff from outside
|
||||
speed?.let { pokemonEntity.setDeltaMovement(pokemonEntity.deltaMovement.normalize().scale(it)) }
|
||||
|
||||
// Constrain to rectangular boundary
|
||||
val cubeWidth = 16.0
|
||||
val cubeHeight = 16.0
|
||||
val cubeCenter = target.position().add(0.0, cubeHeight / 2.0, 0.0)
|
||||
//forceRectBoundary(cubeCenter, cubeWidth, cubeHeight)
|
||||
|
||||
// Apply boid forces
|
||||
|
||||
// Obstacle Avoidance
|
||||
val whiskerRange = 1.0
|
||||
val avoidanceAngle = 20.0 // x degrees to the left, right, up, and down
|
||||
//val obsAvoidForce = whisObsAvoid(whiskerRange, avoidanceAngle)
|
||||
|
||||
// if (obsAvoidForce != null) {
|
||||
// pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(obsAvoidForce.scale(0.5))
|
||||
// }
|
||||
|
||||
// TODO: obviously don't hardcode each pokemon. Remove this after testing is finished.
|
||||
if (pokemonEntity.pokemon.species.name.lowercase() == "charizard") {
|
||||
pokemonEntity.setFlying(true)
|
||||
pokemonEntity.resetFallDistance()
|
||||
seekPrey(pokemonEntity)
|
||||
} else if (pokemonEntity.pokemon.species.name.lowercase() == "garchomp") {
|
||||
pokemonEntity.setFlying(false)
|
||||
val walkSpeed = pokemonEntity.runtime.resolveDouble(pokemonEntity.behaviour.moving.walk.walkSpeed) * 1.5
|
||||
val steerForce = pokemonEntity.steerToTarget(target.position(), walkSpeed, walkSpeed / 12)
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(steerForce.x, 0.0, steerForce.z)
|
||||
// speed?.let { this.mob.setZza(speed!!.toFloat()) }
|
||||
} else if (pokemonEntity.pokemon.species.name.lowercase() == "skiddo") {
|
||||
pokemonEntity.setFlying(false)
|
||||
//wander(pokemonEntity,1.0)
|
||||
// val steerForce = pokemonEntity.steerToTargetForce(target.position(), MAX_SPEED, MAX_FORCE)
|
||||
// pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(steerForce.x, 0.0, steerForce.z)
|
||||
aviodPredators(pokemonEntity, includePlayer = true)
|
||||
boid(isFlyingOrSwimming = false)
|
||||
} else {
|
||||
pokemonEntity.setFlying(true)
|
||||
pokemonEntity.resetFallDistance()
|
||||
wander(pokemonEntity,0.8)
|
||||
boid()
|
||||
aviodPredators(pokemonEntity, includePlayer = true)
|
||||
|
||||
// Force towards ground
|
||||
|
||||
|
||||
forceRectBoundary(pokemonEntity, cubeCenter, cubeWidth, cubeHeight)
|
||||
}
|
||||
|
||||
// val avoidForce = avoidTarget(target.position(), MAX_SPEED, MAX_FORCE, 10.0)
|
||||
// pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(avoidForce.scale(1.0))
|
||||
|
||||
// Apply steering force to current movement
|
||||
// val dm = pokemonEntity.deltaMovement
|
||||
// val w_pos = pokemonEntity.position().add(Vec3(dm.x, 0.0, dm.z))
|
||||
// val steerForce = steerToTargetForce(w_pos, MAX_SPEED, MAX_FORCE)
|
||||
// pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(steerForce)
|
||||
|
||||
// Take note of speed again to avoid minecraft's friction logic
|
||||
speed = pokemonEntity.deltaMovement.length() * 0.98
|
||||
|
||||
// Test out what this setting of speed does.
|
||||
//speed?.let { this.mob.speed = speed!!.toFloat() }
|
||||
|
||||
// Limit upwards speed to be half of max speed. The boids kept bumping into the top of the box more so than birds
|
||||
// would. Honestly birds probably mostly want to move in the x,z plane anyhow
|
||||
// pokemonEntity.deltaMovement = Vec3(
|
||||
// pokemonEntity.deltaMovement.x,
|
||||
// min(pokemonEntity.deltaMovement.y, MAX_SPEED * 0.5),
|
||||
// pokemonEntity.deltaMovement.y
|
||||
// ).normalize().scale(speed!!)
|
||||
|
||||
pokemonEntity.orientEntityToTravel(0.4f)
|
||||
|
||||
if (false && drawLines.isNotEmpty()) { //TODO: Re-enable
|
||||
sendPacketToTrackingPlayers(pokemonEntity, ClientboundUpdateObsAvoidPacket(pokemonEntity.id, drawLines))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun aviodPredators(entity: PokemonEntity, includePlayer: Boolean = false) {
|
||||
val fearRadius = 5.0
|
||||
val brain = entity.brain
|
||||
val predators = brain.getMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES).map { nearbyEntities ->
|
||||
nearbyEntities.filterIsInstance<PokemonEntity>().filter { pokemon ->
|
||||
pokemon.brain.activeNonCoreActivity.orElse(null) == CobblemonActivities.POKEMON_BOIDS &&
|
||||
pokemon.pokemon.species.name.lowercase() == "charizard" &&
|
||||
entity.position().subtract(pokemon.position()).length() <= fearRadius
|
||||
}
|
||||
}.orElse(emptyList())
|
||||
|
||||
var totalEvasionVec = Vec3.ZERO
|
||||
var predatorCount = predators.size
|
||||
|
||||
// If the player should be included in avoidance then add the player as a predator.
|
||||
if (includePlayer) {
|
||||
totalEvasionVec = totalEvasionVec.add(entity.avoidTarget(target.position(), MAX_SPEED * 1.2, MAX_FORCE * 2, fearRadius))
|
||||
predatorCount += 1
|
||||
}
|
||||
// Average evasion forces for all predators in the area and apply it to the prey entity
|
||||
if (predatorCount > 0) {
|
||||
for (predator in predators) {
|
||||
totalEvasionVec = totalEvasionVec.add(entity.avoidTarget(predator.position(), MAX_SPEED * 1.2, MAX_FORCE * 2, fearRadius))
|
||||
}
|
||||
totalEvasionVec = totalEvasionVec.scale(1.0 / predatorCount)
|
||||
if (entity.isFlying() || entity.isSwimming) {
|
||||
entity.deltaMovement = entity.deltaMovement.add(totalEvasionVec)
|
||||
} else {
|
||||
entity.deltaMovement = entity.deltaMovement.add(Vec3(totalEvasionVec.x, 0.0, totalEvasionVec.z))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************************************************
|
||||
Seek the closest prey and pursue
|
||||
*************************************************************************************************************/
|
||||
private fun seekPrey(predator: PokemonEntity) {
|
||||
val SeekRadius = 20.0
|
||||
val brain = predator.brain
|
||||
// Do calcs for head height as it looks better this way than a charizard flying crotch first at a group of pidgeys
|
||||
val kindaHeadHeight = predator.position()
|
||||
|
||||
val prey = brain.getMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES).map { nearbyEntities ->
|
||||
nearbyEntities.filterIsInstance<PokemonEntity>().filter { pokemon ->
|
||||
pokemon.brain.activeNonCoreActivity.orElse(null) == CobblemonActivities.POKEMON_BOIDS &&
|
||||
(pokemon.pokemon.species.name.lowercase() == "pidgey" || pokemon.pokemon.species.name.lowercase() == "skiddo") &&
|
||||
kindaHeadHeight.subtract(pokemon.position()).length() <= SeekRadius
|
||||
}
|
||||
}.orElse(emptyList())
|
||||
|
||||
// Find the closest prey and seek towards it
|
||||
val nearestPrey = prey.minByOrNull { pokePrey ->
|
||||
kindaHeadHeight.subtract(pokePrey.position()).length()
|
||||
}
|
||||
nearestPrey?.let { prey ->
|
||||
|
||||
// Do the actual attack
|
||||
// TODO: make this not dumb
|
||||
if(kindaHeadHeight.subtract(nearestPrey.position()).length() < 0.5 + predator.bbWidth + nearestPrey.bbWidth) {
|
||||
//predator.doHurtTarget(nearestPrey)
|
||||
}
|
||||
|
||||
// Steer towards the target
|
||||
val steerForce = predator.steerToTarget(prey.position(), PREDATOR_MAX_SPEED, MAX_FORCE)
|
||||
predator.deltaMovement = predator.deltaMovement.add(steerForce)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*************************************************************************************************************
|
||||
Whisker Obstacle Avoidance
|
||||
This function ray casts in a cone in front of the entity
|
||||
*************************************************************************************************************/
|
||||
private fun whisObsAvoid(range: Double, lookWidth: Double): Vec3? {
|
||||
|
||||
// --- 1. Build the Orientation Matrix ---
|
||||
val forward = pokemonEntity.lookAngle.scale(range)
|
||||
// val tempUp = if (abs(forward.y) > 0.999) Vec3(0.0, 0.0, 1.0) else Vec3(0.0, 1.0, 0.0)
|
||||
// val right = forward.cross(tempUp).normalize()
|
||||
// val up = right.cross(forward).normalize()
|
||||
//
|
||||
// val orientation = Matrix3f()
|
||||
// .setColumn(0, Vector3f(right.x.toFloat(), right.y.toFloat(), right.z.toFloat()))
|
||||
// .setColumn(1, Vector3f(up.x.toFloat(), up.y.toFloat(), up.z.toFloat()))
|
||||
// .setColumn(2, Vector3f(forward.x.toFloat(), forward.y.toFloat(), forward.z.toFloat()))
|
||||
|
||||
// --- 2. Define Whiskers in Simple, Local Space ---
|
||||
val radLookWidth = lookWidth.toRadians().toFloat()
|
||||
// val localWhiskers: List<Vector3f> = listOf(
|
||||
// Vector3f(0f, 0f, 1f), // Forward
|
||||
//// Vector3f(sin(radLookWidth), 0f, cos(radLookWidth)), // Left
|
||||
//// Vector3f(-sin(radLookWidth), 0f, cos(radLookWidth)), // Right
|
||||
//// Vector3f(0f, sin(radLookWidth), cos(radLookWidth)), // Up
|
||||
//// Vector3f(0f, -sin(radLookWidth), cos(radLookWidth)) // Down
|
||||
// )
|
||||
//
|
||||
// // --- 3. Transform Local Whiskers to World Space using the Matrix ---
|
||||
// val whiskerOffsets = localWhiskers.map { localVec ->
|
||||
// orientation.transform(localVec, Vector3f()).toVec3d().scale(range) // Transform and store in a new vector
|
||||
// }
|
||||
|
||||
// Obtain all points to be raycasted to
|
||||
val scaledLookVec = pokemonEntity.deltaMovement.normalize().scale(range)
|
||||
val lookPoints: List<Vec3> = listOf(
|
||||
forward,
|
||||
// forward.yRot(radLookWidth),
|
||||
// forward.yRot(-radLookWidth),
|
||||
// forward.xRot(90.toRadians()),
|
||||
// forward.xRot((-90).toRadians()),
|
||||
)
|
||||
|
||||
|
||||
// iterate through each point and raycast to it starting from the entities pos (may need to be from somewhere else)
|
||||
// If a position is hit then add its avoidance force to the totalAvoidance forces.
|
||||
var totalAvoidance: Vec3? = null
|
||||
val level = pokemonEntity.level()
|
||||
val startPos = pokemonEntity.position()
|
||||
for (point in lookPoints) {
|
||||
val endPos = startPos.add(point)
|
||||
val hit = level.clip(
|
||||
ClipContext(
|
||||
startPos,
|
||||
endPos,
|
||||
ClipContext.Block.OUTLINE, // If this is being weird then go back to COLLIDER
|
||||
ClipContext.Fluid.ANY, // Birds should probably avoid fluid
|
||||
pokemonEntity
|
||||
)
|
||||
)
|
||||
|
||||
// Add the whisker to the debug lines to be drawn
|
||||
drawLines.add(EntityDebugLine(endPos, 1.0f, 0.0f, 0.0f))
|
||||
//visualizeRay(level, startPos, endPos, hit.type != HitResult.Type.MISS)
|
||||
|
||||
if (hit.type != HitResult.Type.MISS) {
|
||||
// Get the aviodance vector as the vector from the block detection point
|
||||
// to the pokemon.
|
||||
// Also inversely scale the size of the vector based on the distance the pokemon
|
||||
// is to the object. This means close obstacles are avoided more strongly
|
||||
val hitDist = hit.location.distanceTo(pokemonEntity.eyePosition)
|
||||
val avoidVec = startPos.subtract(endPos).normalize() //.scale(1.0 - (hitDist / range))
|
||||
|
||||
// Add avoid vec to total
|
||||
totalAvoidance = totalAvoidance?.add(avoidVec) ?: avoidVec
|
||||
}
|
||||
}
|
||||
|
||||
// // Scale all forces down to the max foce
|
||||
// if (totalAvoidance != null) {
|
||||
// // Cap the max total avoidance desire
|
||||
// if (totalAvoidance.length() > MAX_SPEED) totalAvoidance = totalAvoidance.normalize().scale(MAX_SPEED)
|
||||
// // Convert to Reynolds steering force
|
||||
// totalAvoidance = totalAvoidance.subtract(pokemonEntity.deltaMovement)
|
||||
// // Cap the max total avoidance force
|
||||
// if (totalAvoidance.length() > MAX_FORCE) totalAvoidance = totalAvoidance.normalize().scale(MAX_FORCE)
|
||||
// } // Is this next one even cleaner?
|
||||
|
||||
return totalAvoidance?.let { desiredVelocity ->
|
||||
// Cap the desired velocity (using lengthSqr is more efficient than length)
|
||||
val cappedVelocity = if (desiredVelocity.lengthSqr() > MAX_SPEED * MAX_SPEED) {
|
||||
desiredVelocity.normalize().scale(MAX_SPEED)
|
||||
} else {
|
||||
desiredVelocity
|
||||
}
|
||||
|
||||
// Convert to Reynolds steering force
|
||||
val steeringForce = cappedVelocity.subtract(pokemonEntity.deltaMovement)
|
||||
|
||||
// Cap the final steering force
|
||||
if (steeringForce.lengthSqr() > MAX_FORCE * MAX_FORCE) {
|
||||
steeringForce.normalize().scale(MAX_FORCE)
|
||||
} else {
|
||||
steeringForce
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun wander(entity: PokemonEntity, forceScale: Double = 1.0) {
|
||||
// Wander behaviour
|
||||
/*************************************************************************************************************
|
||||
* The wander behaviour is based of Reynolds wander behaviour which can be seen in the below link under wander
|
||||
* This version however is adapted to 3dimensions.
|
||||
* https://slsdo.github.io/steering-behaviors/
|
||||
*************************************************************************************************************/
|
||||
// Get curr steering positions in left/right and up/down offsets
|
||||
val normVec = if(entity.deltaMovement.lengthSqr() > 0.0001) {
|
||||
entity.lookAngle.normalize()
|
||||
} else {
|
||||
Vec3(
|
||||
Random.nextDouble(-1.0, 1.0),
|
||||
Random.nextDouble(-1.0, 1.0),
|
||||
Random.nextDouble(-1.0, 1.0)
|
||||
).normalize()
|
||||
}
|
||||
|
||||
var wanderOff = normVec
|
||||
|
||||
val currPitchS = sin(Mth.atan2(wanderOff.y, wanderOff.horizontalDistance()))
|
||||
if (currPitchS >= 0.5f || currPitchS <= -0.5f) { //TODO: make this y limit class wide
|
||||
this.currVertWander = 0.0
|
||||
}
|
||||
wanderOff = wanderOff.xRot(this.currVertWander.toRadians())
|
||||
wanderOff = wanderOff.yRot(this.currHorzWander.toRadians())
|
||||
|
||||
// Base the center off the current direction of travel
|
||||
val centerDistance = 4.0 // How many blocks the wander 'sphere' is in front of the entity
|
||||
val wanderCenter = entity.position().add(normVec.scale(centerDistance))
|
||||
// Get the wander circle's radius
|
||||
val wanderRadius = 4.0
|
||||
|
||||
val wanderPoint = wanderCenter.add(wanderOff.scale(wanderRadius))
|
||||
|
||||
// change wander points
|
||||
val randVert = Random.nextDouble(-wanderDisp, wanderDisp)
|
||||
val randHorz = Random.nextDouble(-wanderDisp, wanderDisp)
|
||||
currVertWander = Mth.wrapDegrees(randVert + currVertWander)
|
||||
currHorzWander = Mth.wrapDegrees(randHorz + currHorzWander)
|
||||
|
||||
drawLines.add(EntityDebugLine(wanderPoint.subtract(pokemonEntity.position()), 1.0f, 0.0f, 0.0f, 1.0f)) // Add WanderPoint
|
||||
drawLines.add(EntityDebugLine(wanderCenter.subtract(pokemonEntity.position()), 0.0f, 1.0f, 0.0f, 1.0f)) // Add wander center
|
||||
val wanderForce = entity.steerToTarget(wanderPoint, MAX_SPEED, MAX_FORCE)
|
||||
if(pokemonEntity.isFlying() || pokemonEntity.isSwimming) {
|
||||
entity.deltaMovement = pokemonEntity.deltaMovement.add(wanderForce.scale(forceScale))
|
||||
} else {
|
||||
entity.deltaMovement = pokemonEntity.deltaMovement.add(Vec3(wanderForce.x, 0.0, wanderForce.z).scale(forceScale))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun boid(isFlyingOrSwimming: Boolean = true) {
|
||||
// TODO: maybe move this to a sensor that lives spins up on the boid activity creation
|
||||
// Get a list of other boid entities in this entity's vicinity
|
||||
val brain = pokemonEntity.brain
|
||||
val boids = brain.getMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES).map { nearbyEntities ->
|
||||
nearbyEntities.filterIsInstance<PokemonEntity>().filter { pokemon ->
|
||||
pokemon.brain.activeNonCoreActivity.orElse(null) == CobblemonActivities.POKEMON_BOIDS
|
||||
}
|
||||
}.orElse(emptyList())
|
||||
|
||||
// separation
|
||||
val separationForce = separation(boids)?.scale(0.8)
|
||||
separationForce?.let { force ->
|
||||
if( isFlyingOrSwimming) {
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(force)
|
||||
} else {
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(Vec3(force.x, 0.0, force.z))
|
||||
}
|
||||
}
|
||||
// alignment
|
||||
val alignmentForce = alignment(boids)?.scale(0.0)
|
||||
alignmentForce?.let { force ->
|
||||
if( isFlyingOrSwimming) {
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(force)
|
||||
} else {
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(Vec3(force.x, 0.0, force.z))
|
||||
}
|
||||
}
|
||||
|
||||
// cohesion
|
||||
val cohesionForce = cohesion(boids)?.scale(0.6)
|
||||
cohesionForce?.let { force ->
|
||||
if( isFlyingOrSwimming) {
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(force)
|
||||
} else {
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(Vec3(force.x, 0.0, force.z))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun separation(boids: List<PokemonEntity>): Vec3? {
|
||||
var seperationVec: Vec3? = null
|
||||
|
||||
// Gather the total separation vec of all close entities
|
||||
boids.forEach { boid ->
|
||||
val toTargetVec = pokemonEntity.position().subtract(boid.position())
|
||||
val desiredSeperation = boid.bbWidth * 2.0 // Base the seperation distance on the model width
|
||||
val currDist = toTargetVec.length()
|
||||
if (currDist < desiredSeperation) {
|
||||
if (seperationVec == null) seperationVec = Vec3.ZERO
|
||||
// Make the seperation force inversely proprotional to the serperation distance
|
||||
// Also, scale the force by the bbwidth of the model so
|
||||
seperationVec = seperationVec.add(toTargetVec.normalize().scale((1.0 - (currDist / desiredSeperation)) ))
|
||||
}
|
||||
}
|
||||
|
||||
if (seperationVec != null) {
|
||||
//if (seperationVec.length() > MAX_SPEED) seperationVec = seperationVec.normalize().scale(MAX_SPEED)
|
||||
seperationVec = seperationVec.normalize().scale(MAX_SPEED)
|
||||
seperationVec = seperationVec.subtract(pokemonEntity.deltaMovement)
|
||||
if (seperationVec.length() > MAX_FORCE) {
|
||||
seperationVec = seperationVec.normalize().scale(MAX_FORCE)
|
||||
}
|
||||
}
|
||||
return seperationVec
|
||||
}
|
||||
|
||||
private fun alignment(boids: List<PokemonEntity>): Vec3? {
|
||||
val neighborDist = 4.0
|
||||
var summedVel: Vec3? = null
|
||||
var alignmentVec: Vec3? = null
|
||||
var boidCount = 0
|
||||
|
||||
// Gather the average position of the boid
|
||||
boids.forEach { boid ->
|
||||
val vecToOther = boid.position().subtract(this.pokemonEntity.position())
|
||||
val distToOther = vecToOther.length()
|
||||
val widthOfViewInDeg = 180
|
||||
val isInSightLine = pokemonEntity.deltaMovement.normalize().dot(vecToOther) > cos((widthOfViewInDeg / 2.0).toRadians())
|
||||
|
||||
if (isInSightLine && distToOther < neighborDist) {
|
||||
if (summedVel == null) summedVel = Vec3.ZERO
|
||||
summedVel = summedVel.add(boid.deltaMovement.normalize())
|
||||
boidCount++
|
||||
}
|
||||
}
|
||||
|
||||
if (summedVel != null) {
|
||||
// Get the average vel/alignment by dividing by the number of boids sampled
|
||||
val averageVel = summedVel.scale(1.0 / boidCount)
|
||||
|
||||
//alignmentVec = if (averageVel.length() > MAX_SPEED) averageVel.normalize().scale(MAX_SPEED) else averageVel
|
||||
alignmentVec = averageVel.normalize().scale(MAX_SPEED)
|
||||
alignmentVec = alignmentVec.subtract(pokemonEntity.deltaMovement)
|
||||
if (alignmentVec.length() > MAX_FORCE) {
|
||||
alignmentVec = alignmentVec.normalize().scale(MAX_FORCE)
|
||||
}
|
||||
}
|
||||
return alignmentVec
|
||||
}
|
||||
|
||||
private fun cohesion(boids: List<PokemonEntity>): Vec3? {
|
||||
//val neighborDist = 5.0
|
||||
var summedPos: Vec3? = null
|
||||
var cohesionVec: Vec3? = null
|
||||
var boidCount = 0
|
||||
val cohesionRadius = 8.0
|
||||
|
||||
// Gather the average position of the boid
|
||||
boids.forEach { boid ->
|
||||
val vecToOther = boid.position().subtract(this.pokemonEntity.position())
|
||||
val distToOther = vecToOther.length()
|
||||
val widthOfViewInDeg = 360
|
||||
val isInSightLine = pokemonEntity.deltaMovement.normalize().dot(vecToOther) > cos((widthOfViewInDeg / 2.0).toRadians())
|
||||
|
||||
if (isInSightLine && distToOther < cohesionRadius) { // Only use members that are within a certain radius and eyeline
|
||||
if (summedPos == null) summedPos = Vec3.ZERO
|
||||
summedPos = summedPos.add(boid.position())
|
||||
boidCount++
|
||||
}
|
||||
}
|
||||
|
||||
if (summedPos != null) {
|
||||
// Get the average position by dividing by the number of boids sampled
|
||||
val averagePos = summedPos.scale(1.0 / boidCount)
|
||||
// Get the vector towards that average position and scale
|
||||
cohesionVec = averagePos.subtract(pokemonEntity.position())
|
||||
//if (cohesionVec.length() > MAX_SPEED) cohesionVec = cohesionVec.normalize().scale(MAX_SPEED)
|
||||
cohesionVec = cohesionVec.normalize().scale(MAX_SPEED)
|
||||
cohesionVec = cohesionVec.subtract(pokemonEntity.deltaMovement)
|
||||
if (cohesionVec.length() > MAX_FORCE) {
|
||||
cohesionVec = cohesionVec.normalize().scale(MAX_FORCE)
|
||||
}
|
||||
}
|
||||
return cohesionVec
|
||||
}
|
||||
|
||||
private fun forceRectBoundary(entity: PokemonEntity, basePos: Vec3, rectWidth: Double, rectHeight: Double) {
|
||||
val maxBoundaryForce = MAX_FORCE //* 2.0
|
||||
val correctiveForce = getRectCorrectiveForce(basePos, rectWidth, rectHeight)
|
||||
if (correctiveForce != null) {
|
||||
//val correctiveForce = steerToTargetForce(correctivePos, speedLimit, maxForce)
|
||||
var steer = correctiveForce.subtract(entity.deltaMovement)
|
||||
if (steer.length() > maxBoundaryForce) {
|
||||
steer = steer.normalize().scale(maxBoundaryForce)
|
||||
}
|
||||
entity.setDeltaMovement(entity.deltaMovement.add(steer))
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the desired force to correct the entity back into the rectangle area
|
||||
private fun getRectCorrectiveForce( basePos: Vec3, rectWidth: Double, rectHeight: Double): Vec3? {
|
||||
val entityPos = pokemonEntity.position()
|
||||
val posFromBase = entityPos.subtract(basePos)
|
||||
val cubeHalfWidth = rectWidth / 2.0 // Useful as the basePoint is in the middle of the cube
|
||||
val cubeHalfHeight = rectHeight / 2.0
|
||||
|
||||
// Check the three possible faces for exceeding the boundary (x, y, and z faces of cube)
|
||||
val xExceed = max(0.0, abs(posFromBase.x) - cubeHalfWidth)
|
||||
val yExceed = max(0.0, abs(posFromBase.y) - cubeHalfHeight)
|
||||
val zExceed = max(0.0, abs(posFromBase.z) - cubeHalfWidth)
|
||||
|
||||
return if ( xExceed == 0.0 && yExceed == 0.0 && zExceed == 0.0) {
|
||||
null
|
||||
} else {
|
||||
var desiredPos = pokemonEntity.position().add(pokemonEntity.deltaMovement) // Add delta movement so we aren't slowing this guy down
|
||||
if (xExceed != 0.0) desiredPos = Vec3( basePos.x, desiredPos.y, desiredPos.z)
|
||||
if (yExceed != 0.0) desiredPos = Vec3( desiredPos.x, basePos.y, desiredPos.z)
|
||||
if (zExceed != 0.0) desiredPos = Vec3( desiredPos.x, desiredPos.y, basePos.z)
|
||||
return desiredPos.subtract(pokemonEntity.position()).normalize().scale(MAX_SPEED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//val desiredScale = if (distToTarget > slowDownRadius) speedLimit else (distToTarget/slowDownRadius).pow(2) * speedLimit
|
||||
//desiredVec = desiredVec.normalize().scale(desiredScale)
|
||||
@@ -9,47 +9,30 @@
|
||||
package com.cobblemon.mod.common.entity.pokemon.ai
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacketToTrackingPlayers
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
|
||||
import com.cobblemon.mod.common.client.render.debug.EntityDebugLine
|
||||
import com.cobblemon.mod.common.client.render.debug.EntityDebugLine.Companion.invoke
|
||||
import com.cobblemon.mod.common.entity.PlatformType
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.ai.OmniPathNavigation
|
||||
import com.cobblemon.mod.common.entity.ai.steering.SteerArbitrator
|
||||
import com.cobblemon.mod.common.entity.ai.steering.rules.ObstacleAvoidRule
|
||||
import com.cobblemon.mod.common.entity.ai.steering.rules.SeekRule
|
||||
import com.cobblemon.mod.common.entity.ai.steering.rules.WanderRule
|
||||
import com.cobblemon.mod.common.entity.ai.steering.rules.WalkForwardRule
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.extensions.*
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.ClientboundUpdateObsAvoidPacket
|
||||
import com.cobblemon.mod.common.util.getWaterAndLavaIn
|
||||
import com.cobblemon.mod.common.util.math.geometry.toDegrees
|
||||
import com.cobblemon.mod.common.util.math.geometry.toRadians
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.tags.BlockTags
|
||||
import net.minecraft.tags.FluidTags
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes
|
||||
import net.minecraft.world.entity.ai.control.MoveControl
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import net.minecraft.world.level.pathfinder.PathType
|
||||
import net.minecraft.world.phys.AABB
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
import kotlin.random.Random
|
||||
|
||||
class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemonEntity) {
|
||||
companion object {
|
||||
@@ -67,41 +50,6 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
|
||||
var bankUpwardsBlocksPerTick = 0F
|
||||
var bankRightDegreesPerTick = 1F
|
||||
|
||||
var obsCorrectDir = 0F
|
||||
var drawLines: MutableList<EntityDebugLine> = mutableListOf()
|
||||
var prevForce: Vec3? = null
|
||||
|
||||
var steerSpeed = this.gatherTopSpeed(pokemonEntity)
|
||||
private val steerArbitrator by lazy {
|
||||
val steerArbitrator = SteerArbitrator(
|
||||
entity = pokemonEntity,
|
||||
maxForce = 0.02,
|
||||
rules = mutableListOf(
|
||||
ObstacleAvoidRule(
|
||||
entity = pokemonEntity,
|
||||
debugColor = Triple(1f, 0f, 0f),
|
||||
),
|
||||
SeekRule(
|
||||
entity = pokemonEntity,
|
||||
priority = 10,
|
||||
maxForce = 0.02,
|
||||
debugColor = Triple(0f, 1f, 0f),
|
||||
horzOnly = true,
|
||||
targetProvider = { e ->
|
||||
(e.navigation as? OmniPathNavigation)?.pathFollowTarget //TODO: maybe change this seems silly. Probably want a rule that checks explicitly?
|
||||
}
|
||||
),
|
||||
WanderRule(
|
||||
entity = pokemonEntity,
|
||||
debugColor = Triple(0f, 1f, 1f),
|
||||
horzOnly = true
|
||||
)
|
||||
)
|
||||
)
|
||||
steerArbitrator
|
||||
}
|
||||
|
||||
|
||||
fun startBanking(
|
||||
forwardBlocksPerTick: Float = 0F,
|
||||
upwardsBlocksPerTick: Float = 0F,
|
||||
@@ -125,7 +73,6 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
|
||||
}
|
||||
|
||||
override fun tick() {
|
||||
mob.setDiscardFriction(false) // ignored while trying to move but if stopped or doing something else this needs reenabled
|
||||
if (banking && operation != Operation.WAIT) {
|
||||
banking = false
|
||||
}
|
||||
@@ -155,7 +102,6 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
|
||||
val baseSpeed = mob.getAttributeValue(Attributes.MOVEMENT_SPEED).toFloat() * this.speedModifier.toFloat()
|
||||
val adjustedSpeed = baseSpeed * mediumSpeed
|
||||
|
||||
|
||||
if (banking) {
|
||||
pokemonEntity.yRot += bankRightDegreesPerTick
|
||||
pokemonEntity.travel(Vec3(0.0, bankUpwardsBlocksPerTick.toDouble(), bankForwardBlocksPerTick.toDouble()).scale(mediumSpeed.toDouble()))
|
||||
@@ -164,142 +110,11 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
|
||||
}
|
||||
if (bankDurationTicks == 0) {
|
||||
banking = false
|
||||
|
||||
operation = Operation.WAIT
|
||||
}
|
||||
}
|
||||
// Here we call the wander on every pokemon no matter what then they wander then leave
|
||||
// Just ignore all operatations and do whatever is being tested.
|
||||
if (true) {
|
||||
// val horzOnly = true
|
||||
//
|
||||
// var topSpeed = gatherTopSpeed(pokemonEntity)
|
||||
// var maxForce = 0.02 // Arbitrarily chosen as of right now.
|
||||
// pokemonEntity.isPokemonSprinting = false
|
||||
//
|
||||
// // grab forward travel vector direction
|
||||
// val forwardVec = if (pokemonEntity.deltaMovement.horizontalDistanceSqr() < 0.001) {
|
||||
// // No meaningful velocity so derive from yaw orientation instead
|
||||
// val yaw = Math.toRadians(pokemonEntity.yRot.toDouble())
|
||||
// Vec3(-sin(yaw), 0.0, cos(yaw))
|
||||
// } else {
|
||||
// if (horzOnly) Vec3(pokemonEntity.deltaMovement.x, 0.0, pokemonEntity.deltaMovement.z).normalize()
|
||||
// else pokemonEntity.deltaMovement.normalize()
|
||||
// }
|
||||
// val pointAhead = pokemonEntity.position().add(forwardVec)
|
||||
// val aheadForce = pokemonEntity.steerToTarget(pointAhead, topSpeed, maxForce, horzOnly = horzOnly)
|
||||
//
|
||||
//
|
||||
//// val player = pokemonEntity.level().getNearestPlayer(pokemonEntity, 10.0) ?: return
|
||||
//// // Flee direction — away from player, flattened to XZ
|
||||
//// val toPlayerVec = pokemonEntity.position().subtract(player.position())
|
||||
////
|
||||
//// if (toPlayerVec.length() < 6.0) {
|
||||
//// pokemonEntity.isPokemonSprinting = true
|
||||
////// topSpeed = 0.4 // hardcoded high value
|
||||
////// maxForce = 0.04
|
||||
//// topSpeed *= 2.0f
|
||||
//// maxForce *= 2
|
||||
//// }
|
||||
////
|
||||
//// val fleePoint = pokemonEntity.position().add(toPlayerVec)
|
||||
//// val aheadForce = pokemonEntity.steerToTarget(fleePoint, topSpeed, maxForce, horzOnly = horzOnly)
|
||||
//
|
||||
// // For now just move forward by seeking towards a point ahead
|
||||
// val lookAheadTicks = 10 // How far in the future of this entities current movement to look ahead for collisions
|
||||
// val lookAheadDist = max(pokemonEntity.deltaMovement.length() * lookAheadTicks, pokemonEntity.bbWidth / 2.0) // At least always look ahead the width of the mons own hitbox
|
||||
//
|
||||
// // Get the collision box
|
||||
// val inflationAmount = 0.0 //pokemonEntity.bbWidth / 5.0 //TODO: is this needed? it seems to help a bit keeping them off walls but would stop them from entering gaps they truly could
|
||||
// val collBox = pokemonEntity.boundingBox.move(forwardVec.scale(lookAheadDist)).inflate(inflationAmount, 0.0, inflationAmount) // Inflate a bit
|
||||
// val shapes = pokemonEntity.level().getBlockCollisions(pokemonEntity, collBox)
|
||||
//
|
||||
// // Gather the nearest shape
|
||||
// val nearestShape = shapes.minByOrNull { shape ->
|
||||
// shape.bounds().center.distanceToSqr(pokemonEntity.position())
|
||||
// }
|
||||
//
|
||||
// var avoidancForce = Vec3.ZERO
|
||||
//
|
||||
// if (nearestShape != null) {
|
||||
// var hasLeftForce = false
|
||||
// var hasRightForce = false
|
||||
// val right = Vec3(-forwardVec.z, 0.0, forwardVec.x) //TODO: expand to 3 dimensions
|
||||
//
|
||||
// val obsCenter = nearestShape.bounds().center
|
||||
// val toObs = obsCenter.subtract(pokemonEntity.position())
|
||||
//
|
||||
// // Get the lateral avoidance point to seek to (the avoidance force really)
|
||||
// // It is the negated vector of toObs - Proj(toObs->forwardVec)
|
||||
// val projVec = forwardVec.scale(toObs.dot(forwardVec)) // Can do this because forwardVec is normalized
|
||||
// val lateralComponent = toObs.subtract(projVec) // TODO: moved back so that two opposing forces turn the dude around
|
||||
//
|
||||
// avoidancForce = if (lateralComponent.lengthSqr() < 0.001) { // If its dead ahead then just turn right
|
||||
// val rightVec = forwardVec.cross(Vec3(0.0,1.0,0.0))
|
||||
// obsCorrectDir = 1F
|
||||
// pokemonEntity.steerToTarget(pokemonEntity.position().add(rightVec), topSpeed, maxForce, horzOnly = horzOnly)
|
||||
// } else {
|
||||
// // Scale the strength of the avoidance vec based off how close to being dead center it is
|
||||
//
|
||||
// var avoidanceVec = lateralComponent.scale(-1.0)
|
||||
// if (horzOnly) avoidanceVec = Vec3(avoidanceVec.x, 0.0, avoidanceVec.z)
|
||||
// //var avoidStrength = (abs(toObs.dot(avoidanceVec)) / (pokemonEntity.bbWidth / 2.0) ).coerceIn(0.0, 1.0)
|
||||
//
|
||||
// // 0.5 to add half the block width allowance since if it catches the corner of a block it still gets the center
|
||||
// var avoidStrength = sqrt(1 - (avoidanceVec.length() / (pokemonEntity.bbWidth / 2.0 + 0.5) ).coerceIn(0.0, 0.7))
|
||||
// // Track which side this force is pushing toward
|
||||
// //val newObsCorrectDir = if (avoidanceVec.normalize().dot(right) > 0) 1F else -1f
|
||||
//
|
||||
// // If the new direction is opposite the last direction then invert it
|
||||
//// if (obsCorrectDir == 0F) {
|
||||
//// obsCorrectDir = newObsCorrectDir
|
||||
//// } else if (newObsCorrectDir != obsCorrectDir) {
|
||||
//// avoidanceVec = avoidanceVec.scale(-1.0)
|
||||
//// //avoidStrength = 1.0
|
||||
//// }
|
||||
//
|
||||
// val avoidancePoint = pokemonEntity.position().add(avoidanceVec)
|
||||
// pokemonEntity.steerToTarget(avoidancePoint, topSpeed, maxForce * avoidStrength, horzOnly = horzOnly)
|
||||
// }
|
||||
// } else {
|
||||
// obsCorrectDir = 0F
|
||||
// }
|
||||
//
|
||||
// // accumulte forces
|
||||
// // TODO: steertoTarget also checks stuff like this. Should that be different? The forces could compound here
|
||||
// val accumForce = aheadForce.scale(0.5).add(avoidancForce.scale(1.0))
|
||||
// var finalForce = if( avoidancForce == Vec3.ZERO) aheadForce
|
||||
// else avoidancForce
|
||||
//
|
||||
// prevForce?.let { finalForce = it.lerp(finalForce, 0.1) }
|
||||
// prevForce = finalForce
|
||||
//
|
||||
// pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(finalForce)
|
||||
//
|
||||
// // Must be called to make sure we have complete control over our entities velocity.
|
||||
// mob.speed = 0f
|
||||
// mob.setZza(0f)
|
||||
// mob.setXxa(0f)
|
||||
// mob.setDiscardFriction(true)
|
||||
// pokemonEntity.orientEntityToTravel(1.0f)
|
||||
// pokemonEntity.setFlying(false)
|
||||
//
|
||||
// drawLines.add(EntityDebugLine(forwardVec))
|
||||
// if (drawLines.isNotEmpty()) {
|
||||
// sendPacketToTrackingPlayers(pokemonEntity, ClientboundUpdateObsAvoidPacket(pokemonEntity.id, drawLines.toList()))
|
||||
// drawLines.clear()
|
||||
// }
|
||||
|
||||
steerSpeed = gatherTopSpeed(pokemonEntity)
|
||||
val force = steerArbitrator.calculate(debug = true)
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(force)
|
||||
mob.speed = 0f
|
||||
mob.setZza(0f)
|
||||
mob.setXxa(0f)
|
||||
mob.setDiscardFriction(true)
|
||||
pokemonEntity.orientEntityToTravel(1.0f)
|
||||
|
||||
} else if (operation == Operation.STRAFE) {
|
||||
if (operation == Operation.STRAFE) {
|
||||
var movingDistanceTotal = Mth.sqrt(strafeForwards * strafeForwards + strafeRight * strafeRight)
|
||||
if (movingDistanceTotal < 1.0f) {
|
||||
movingDistanceTotal = 1.0f
|
||||
@@ -346,14 +161,10 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
|
||||
val horizontalDistanceFromTarget = xDist * xDist + zDist * zDist
|
||||
val closeHorizontally = horizontalDistanceFromTarget < VERY_CLOSE
|
||||
if (!closeHorizontally) {
|
||||
// NEW
|
||||
pokemonEntity.orientEntityToTravel(1.0f)
|
||||
|
||||
// OLD
|
||||
// val angleToTarget = Mth.atan2(zDist, xDist).toDegrees() - 90.0f
|
||||
// val currentMovingAngle = mob.yRot
|
||||
// val steppedAngle = Mth.approachDegrees(currentMovingAngle, angleToTarget, 100 * mediumSpeed)
|
||||
// mob.yRot = steppedAngle
|
||||
val angleToTarget = Mth.atan2(zDist, xDist).toDegrees() - 90.0f
|
||||
val currentMovingAngle = mob.yRot
|
||||
val steppedAngle = Mth.approachDegrees(currentMovingAngle, angleToTarget, 100 * mediumSpeed)
|
||||
mob.yRot = steppedAngle
|
||||
}
|
||||
|
||||
val (inWater, inLava) = mob.level().getWaterAndLavaIn(mob.boundingBox)
|
||||
@@ -380,101 +191,15 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
|
||||
|
||||
val scale = min(adjustedSpeed.toDouble(), fullDistance.length())
|
||||
|
||||
val maxForce = 0.02 // 0.02
|
||||
|
||||
// Gather the point to be steered to
|
||||
val nav = pokemonEntity.navigation
|
||||
nav.pathFollowTarget?.let { rt ->
|
||||
wantedX = rt.x
|
||||
wantedZ = rt.z
|
||||
wantedY = rt.y
|
||||
}
|
||||
// + 0.05 for dealing with swimming out of water, they otherwise get stuck on the lip
|
||||
val steerForce = pokemonEntity.steerToTarget(Vec3(wantedX, refine(wantedY + 0.05), wantedZ), scale, maxForce)
|
||||
mob.deltaMovement = mob.deltaMovement.add(steerForce)
|
||||
|
||||
//mob.deltaMovement = direction.scale(scale)
|
||||
mob.deltaMovement = direction.scale(scale)
|
||||
|
||||
xDist = fullDistance.x
|
||||
zDist = fullDistance.z
|
||||
yDist = fullDistance.y
|
||||
} else {
|
||||
// division is to slow the speed down a bit so they don't overshoot when they get there.
|
||||
var forwardSpeed = adjustedSpeed
|
||||
var forceDiv = 1 / 5.0 // Portion of max speed that force can apply every tick
|
||||
var minForce = 0.02 // If its not at least this it breaks quite a few things. looking at you snorlax
|
||||
|
||||
val finalGoal: BlockPos? = pokemonEntity.navigation.path?.target
|
||||
val distanceToFinalGoal = finalGoal?.let {
|
||||
pokemonEntity.position().distanceTo(it.center).toFloat()
|
||||
} ?: 0.0f
|
||||
|
||||
if (distanceToFinalGoal > 8f) { //TODO: scale this to hitbox size
|
||||
pokemonEntity.isPokemonSprinting = true
|
||||
forwardSpeed *= 1.4f
|
||||
forceDiv = 1 / 10.0
|
||||
minForce *= 1.0 //TODO: figure out what to actually do for this
|
||||
} else {
|
||||
pokemonEntity.isPokemonSprinting = false
|
||||
}
|
||||
|
||||
// New way
|
||||
val blockFriction = mob.level()
|
||||
.getBlockState(mob.getBlockPosBelowThatAffectsMyMovement())
|
||||
.block.getFriction()
|
||||
val frictionInfluencedSpeed = if (mob.onGround()) {
|
||||
forwardSpeed * (0.21600002f / (blockFriction * blockFriction * blockFriction))
|
||||
} else {
|
||||
0.02f // getFlyingSpeed() default for non-player controlled. Also hopefully our checks above this caught this case
|
||||
}
|
||||
|
||||
|
||||
val baseForce = (forwardSpeed * 0.98) * frictionInfluencedSpeed
|
||||
|
||||
// We find where the equilibrium between vanilla friction and the input force usually is to get a topSpeed for this mon.
|
||||
// Could be replaced in the future with direct topSpeed calculations in the pokemon species form
|
||||
val frictionAppliedAfter = blockFriction * 0.91
|
||||
val topSpeed = baseForce / (1 - frictionAppliedAfter)
|
||||
val currSpeed = pokemonEntity.deltaMovement.length()
|
||||
|
||||
//TODO: is this needed?
|
||||
// if (!pokemonEntity.isPokemonSprinting && currSpeed > topSpeed) { // TODO: this might mess up some stuff like tnt pushing mobs around
|
||||
// pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.normalize().scale(lerp(currSpeed, topSpeed, 0.1))
|
||||
// }
|
||||
|
||||
// Get a force that looks natural
|
||||
val maxForce = minForce //max(baseForce * forceDiv, minForce)
|
||||
|
||||
// Gather the point to be steered to
|
||||
val nav = pokemonEntity.navigation
|
||||
nav.pathFollowTarget?.let { rt ->
|
||||
wantedX = rt.x
|
||||
wantedZ = rt.z
|
||||
wantedY = rt.y
|
||||
}
|
||||
|
||||
val steerForce = pokemonEntity.steerToTarget(
|
||||
Vec3(wantedX, wantedY, wantedZ),
|
||||
topSpeed, // Is never reached as minecraft relies on top speed being obtained when force=friction
|
||||
maxForce,
|
||||
horzOnly = true
|
||||
)
|
||||
|
||||
// If this happens a lot then we have issues
|
||||
val mdt = LivingEntity.MIN_MOVEMENT_DISTANCE
|
||||
if ( abs(steerForce.x) < mdt || abs(steerForce.y) < mdt || abs(steerForce.z) < mdt) {
|
||||
//Cobblemon.LOGGER.info("INFO: Steer Force dimension smaller than minimum movement distance value") //TODO: figure this out
|
||||
}
|
||||
|
||||
pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(steerForce)
|
||||
//pokemonEntity.deltaMovement = pokemonEntity.deltaMovement.add(Vec3(0.0103766, 0.0, 0.0))
|
||||
mob.speed = 0f
|
||||
mob.setZza(0f)
|
||||
mob.setXxa(0f)
|
||||
mob.setDiscardFriction(true)
|
||||
|
||||
// Old way
|
||||
// mob.speed = forwardSpeed
|
||||
val forwardSpeed = min(adjustedSpeed, max(horizontalDistanceFromTarget.toFloat() / 2, 0.15F))
|
||||
mob.speed = forwardSpeed
|
||||
}
|
||||
|
||||
if (!verticalHandled) {
|
||||
@@ -555,41 +280,6 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
|
||||
}
|
||||
}
|
||||
|
||||
fun gatherTopSpeed( pokemonEntity: PokemonEntity): Double {
|
||||
val behaviour = pokemonEntity.behaviour
|
||||
val mediumSpeed = runtime.resolveFloat(if (pokemonEntity.getCurrentPoseType() in setOf(PoseType.FLY, PoseType.HOVER)) {
|
||||
behaviour.moving.fly.flySpeedHorizontal
|
||||
} else if (pokemonEntity.isEyeInFluid(FluidTags.WATER) || pokemonEntity.isEyeInFluid(FluidTags.LAVA)) {
|
||||
behaviour.moving.swim.swimSpeed
|
||||
} else {
|
||||
behaviour.moving.walk.walkSpeed
|
||||
}) * 2.5F //TODO: idk why this is 2.5 but I think we're stuck with it
|
||||
|
||||
val baseSpeed = mob.getAttributeValue(Attributes.MOVEMENT_SPEED).toFloat() * 0.4f //TODO: does this ever need to not be hardcoded?
|
||||
val adjustedSpeed = baseSpeed * mediumSpeed
|
||||
|
||||
val forwardSpeed = adjustedSpeed
|
||||
|
||||
val blockFriction = mob.level()
|
||||
.getBlockState(mob.getBlockPosBelowThatAffectsMyMovement())
|
||||
.block.getFriction()
|
||||
val frictionInfluencedSpeed = if (mob.onGround()) {
|
||||
forwardSpeed * (0.21600002f / (blockFriction * blockFriction * blockFriction))
|
||||
} else {
|
||||
0.02f // getFlyingSpeed() default for non-player controlled
|
||||
}
|
||||
|
||||
// Force applied every tick through the usual vanilla route
|
||||
val baseForce = (forwardSpeed * 0.98) * frictionInfluencedSpeed
|
||||
|
||||
// We find where the equilibrium between vanilla friction and the input force usually is to get a topSpeed for this mon.
|
||||
// Could be replaced in the future with direct topSpeed calculations in the pokemon species form
|
||||
val frictionAppliedAfter = blockFriction * 0.91
|
||||
val topSpeed = baseForce / (1 - frictionAppliedAfter)
|
||||
|
||||
return topSpeed
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the ideal water level y position of a given pokemon during battle.
|
||||
* This is separate for the eye height check and is dependent on if the pokemon is rafted or swimming.
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* 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.entity.pokemon.ai.sensors
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.sensing.Sensor
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.entity.player.Player
|
||||
|
||||
class PlayerDebuggingSensor : Sensor<LivingEntity>(SCAN_INTERVAL) {
|
||||
|
||||
override fun doTick(level: ServerLevel, entity: LivingEntity) {
|
||||
var diamondHolder: LivingEntity? = null
|
||||
diamondHolder = entity.brain.getMemory(CobblemonMemories.DIAMOND_DEBUGGER).orElse(null)?.let {
|
||||
if(it.mainHandItem.`is`(Items.DIAMOND)) {
|
||||
it
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
if (diamondHolder == null) {
|
||||
val nearEntities = entity.brain.getMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES).orElse(mutableListOf())
|
||||
diamondHolder = nearEntities?.find { entity ->
|
||||
entity.mainHandItem.`is`(Items.DIAMOND)
|
||||
}
|
||||
}
|
||||
|
||||
if (diamondHolder != null && diamondHolder is Player) {
|
||||
entity.brain.setMemory(CobblemonMemories.DIAMOND_DEBUGGER, diamondHolder)
|
||||
} else {
|
||||
entity.brain.eraseMemory(CobblemonMemories.DIAMOND_DEBUGGER)
|
||||
}
|
||||
}
|
||||
|
||||
override fun requires(): Set<MemoryModuleType<*>> {
|
||||
return setOf(
|
||||
CobblemonMemories.DIAMOND_DEBUGGER,
|
||||
MemoryModuleType.NEAREST_LIVING_ENTITIES
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SCAN_INTERVAL = 10
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
//
|
||||
///*
|
||||
// * 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.entity.pokemon.ai.tasks;
|
||||
//
|
||||
//import com.cobblemon.mod.common.CobblemonMemories
|
||||
//import com.cobblemon.mod.common.api.pokemon.status.Statuses
|
||||
//import com.cobblemon.mod.common.api.storage.party.PartyStore
|
||||
//import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
//import net.minecraft.world.entity.ai.behavior.OneShot
|
||||
//import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
//import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
//import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
//
|
||||
//object BoidsTask {
|
||||
//fun create(): OneShot<PokemonEntity> {
|
||||
// return BehaviorBuilder.create {
|
||||
// it.group(
|
||||
// it.absent(CobblemonMemories.POKEMON_DROWSY)
|
||||
// ).apply(it) { _ ->
|
||||
// Trigger { world, entity, _ ->
|
||||
// if (entity.pokemon.status?.status == Statuses.SLEEP && entity.pokemon.storeCoordinates.get()?.store !is PartyStore) {
|
||||
// entity.pokemon.status = null
|
||||
// entity.brain.eraseMemory(CobblemonMemories.POKEMON_SLEEPING)
|
||||
// entity.brain.useDefaultActivity()
|
||||
// return@Trigger true
|
||||
// } else {
|
||||
// return@Trigger false
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//}
|
||||
@@ -1,111 +0,0 @@
|
||||
package com.cobblemon.mod.common.entity.pokemon.extensions
|
||||
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.math.geometry.toDegrees
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sign
|
||||
|
||||
|
||||
/*************************************************************************************************************
|
||||
Steering to Target
|
||||
Detailed in: https://www.red3d.com/cwr/steer/gdc99/
|
||||
*************************************************************************************************************/
|
||||
fun PokemonEntity.steerToTarget(
|
||||
targetPos: Vec3,
|
||||
maxSpeed: Double,
|
||||
maxForce: Double,
|
||||
arrivalRange: Double? = null,
|
||||
horzOnly: Boolean = false
|
||||
): Vec3 {
|
||||
val cPos = if (horzOnly) Vec3(this.position().x, 0.0, this.position().z) else this.position()
|
||||
val tPos = if (horzOnly) Vec3(targetPos.x, 0.0, targetPos.z) else targetPos
|
||||
|
||||
// Ensure we can't have a negative range here
|
||||
val absArrivalRange = arrivalRange?.let { abs(arrivalRange) }
|
||||
|
||||
// Calculate the vector from the pokemons current position to the
|
||||
// target position. Set its desired magnitude to max speed or a
|
||||
// portion of it if within the arrivalRange
|
||||
val toTargetVec = tPos.subtract(cPos)
|
||||
|
||||
val distToTarget = toTargetVec.length()
|
||||
val desiredSpeed = if (absArrivalRange != null && distToTarget < absArrivalRange) (distToTarget/absArrivalRange) * maxSpeed else maxSpeed
|
||||
val desiredVec = toTargetVec.normalize().scale(desiredSpeed)
|
||||
|
||||
var steer = if (horzOnly) desiredVec.subtract(Vec3(this.deltaMovement.x, 0.0, this.deltaMovement.z))
|
||||
else desiredVec.subtract(this.deltaMovement)
|
||||
|
||||
if (steer.length() > maxForce) {
|
||||
steer = steer.normalize().scale(maxForce)
|
||||
}
|
||||
|
||||
return steer
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*************************************************************************************************************
|
||||
Steering Target Avoidance
|
||||
Detailed in: https://www.red3d.com/cwr/steer/gdc99/
|
||||
*************************************************************************************************************/
|
||||
fun PokemonEntity.avoidTarget(
|
||||
targetPos: Vec3,
|
||||
maxSpeed: Double,
|
||||
maxForce: Double,
|
||||
avoidanceRange: Double? = null
|
||||
): Vec3 {
|
||||
|
||||
// Calculate the vector from the targets current position to the
|
||||
// pokemon position. Set its desired magnitude to max speed scaled
|
||||
// linearly from 1.0 when right next to the target and 0.0 when on the
|
||||
// very edge of the avoidance Range
|
||||
var fromTargetVec = this.position().subtract(targetPos)
|
||||
|
||||
// Calculate distance to the target
|
||||
val distToTarget = fromTargetVec.length()
|
||||
if (distToTarget < 1.0E-4) {fromTargetVec = Vec3(0.0, 1.0, 0.0)} // Ensure the avoidance vector can't be ZERO
|
||||
|
||||
// Return no force otherwise a desired vector will be calculated that slows the velocity.
|
||||
if (avoidanceRange != null && distToTarget >= avoidanceRange) return Vec3.ZERO
|
||||
|
||||
// Scale the avoidance force linearly
|
||||
val desiredSpeed = when {
|
||||
avoidanceRange == null -> maxSpeed // If no avoidance range then just always flee as fast as you can
|
||||
avoidanceRange == 0.0 -> maxSpeed // Avoid weird divide by zero nonsense
|
||||
distToTarget < avoidanceRange -> (1.0 - (distToTarget / avoidanceRange)) * maxSpeed
|
||||
else -> 0.0
|
||||
}
|
||||
//TODO: determine if this should be scaled by distance. Probably not
|
||||
val desiredVec = fromTargetVec.normalize() //.scale(desiredSpeed)
|
||||
|
||||
// Steer the pokemon towards the desired vector
|
||||
var steer = desiredVec.subtract(this.deltaMovement)
|
||||
if (steer.length() > maxForce) {
|
||||
steer = steer.normalize().scale(maxForce)
|
||||
}
|
||||
|
||||
return steer
|
||||
}
|
||||
|
||||
/*************************************************************************************************************
|
||||
Lerp the mobs yRot to its direction of travel
|
||||
*************************************************************************************************************/
|
||||
fun PokemonEntity.orientEntityToTravel(k: Float = 1.0f) {
|
||||
val interpAmount = k.coerceIn(0.0f, 1.0f) //TODO: This probably shouldn't be a lerp like this.
|
||||
if (this.deltaMovement.length() < 0.01) return //LivingEntity.MIN_MOVEMENT_DISTANCE) return
|
||||
|
||||
// Ensure the entity is facing the direction it is moving in both pitch and yaw
|
||||
val normVec = this.deltaMovement.normalize()
|
||||
val targetAngle = Mth.atan2(normVec.z, normVec.x).toDegrees() - 90.0f
|
||||
val currAngle = this.yRot
|
||||
|
||||
val angleDiff = Mth.wrapDegrees(targetAngle - currAngle)
|
||||
val maxPerTickRotation = 30.0f // set really high to ignore for now 15.0f // TODO: is something like this needed or should it be a clientside spring or something?
|
||||
val effRot = min(abs(angleDiff), maxPerTickRotation) * sign(angleDiff)
|
||||
this.yRot = currAngle + (effRot * interpAmount)
|
||||
//entity.xRot = asin(normVec.y.toFloat()).toDegrees()
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* 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.net.messages.client.debug
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.client.render.debug.EntityDebugLine
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.cobblemon.mod.common.util.writeCollection
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
/**
|
||||
* Packet sent to the client to update the obstacle avoidance lines to be rendered
|
||||
* while debugging
|
||||
*
|
||||
* @author Jackson
|
||||
* @since September 15th, 2025
|
||||
*/
|
||||
class ClientboundUpdateObsAvoidPacket(val entityId: Int, val lines: List<EntityDebugLine>) : NetworkPacket<ClientboundUpdateObsAvoidPacket> {
|
||||
override val id: ResourceLocation = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeInt(entityId)
|
||||
buffer.writeCollection(lines) { _, linePair ->
|
||||
linePair.encode(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("obstacle_avoidance_lines")
|
||||
|
||||
fun decode(buffer: RegistryFriendlyByteBuf): ClientboundUpdateObsAvoidPacket {
|
||||
val entityId = buffer.readInt()
|
||||
val lines = buffer.readList { buf ->
|
||||
EntityDebugLine.decode(buf as RegistryFriendlyByteBuf)
|
||||
}
|
||||
return ClientboundUpdateObsAvoidPacket(entityId, lines)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,8 +77,7 @@ object PokemonBrain {
|
||||
CobblemonSensors.NEARBY_GROWABLE_CROPS,
|
||||
CobblemonSensors.NEARBY_BEE_HIVE,
|
||||
CobblemonSensors.NEARBY_FLOWER,
|
||||
CobblemonSensors.NEARBY_SWEET_BERRY_BUSH,
|
||||
CobblemonSensors.PLAYER_DEBUGGING
|
||||
CobblemonSensors.NEARBY_SWEET_BERRY_BUSH
|
||||
|
||||
// CobblemonSensors.BATTLING_POKEMON,
|
||||
// CobblemonSensors.NPC_BATTLING
|
||||
@@ -180,7 +179,6 @@ object PokemonBrain {
|
||||
MemoryModuleType.HURT_BY_ENTITY,
|
||||
MemoryModuleType.NEAREST_PLAYERS,
|
||||
MemoryModuleType.NEAREST_VISIBLE_PLAYER,
|
||||
MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER,
|
||||
MemoryModuleType.ANGRY_AT,
|
||||
MemoryModuleType.ATTACK_COOLING_DOWN,
|
||||
CobblemonMemories.POKEMON_DROWSY,
|
||||
@@ -203,8 +201,7 @@ object PokemonBrain {
|
||||
CobblemonMemories.HERD_LEADER,
|
||||
CobblemonMemories.HERD_SIZE,
|
||||
CobblemonMemories.ATTACK_TARGET_DATA,
|
||||
CobblemonMemories.NEARBY_SWEET_BERRY_BUSH,
|
||||
CobblemonMemories.DIAMOND_DEBUGGER
|
||||
CobblemonMemories.NEARBY_SWEET_BERRY_BUSH
|
||||
)
|
||||
|
||||
private fun coreTasks(pokemon: Pokemon) = buildList<Pair<Int, BehaviorControl<in PokemonEntity>>> {
|
||||
|
||||
@@ -200,10 +200,6 @@
|
||||
{
|
||||
"condition": "q.is_holding_item",
|
||||
"animation": "q.bedrock('charizard', 'hold_item')"
|
||||
},
|
||||
{
|
||||
"condition": "!q.is_ridden",
|
||||
"animation": "q.pitch_tilt('body', 0.8)"
|
||||
}
|
||||
],
|
||||
"quirks": [
|
||||
@@ -221,10 +217,9 @@
|
||||
"q.look('neck4', 0.35, 0.35, 5, -5, 15, -15)",
|
||||
"q.look('head', 0.4, 0.25, 5, -5, 10, -10)",
|
||||
"q.bedrock('charizard', 'air_fly')",
|
||||
"q.bedrock('charizard', 'ride_air_fly')",
|
||||
{
|
||||
"condition": "!q.is_ridden",
|
||||
"animation": "q.pitch_tilt('body', 0.8)"
|
||||
"condition": "q.is_ridden",
|
||||
"animation": "q.bedrock('charizard', 'ride_air_fly')"
|
||||
},
|
||||
{
|
||||
"condition": "q.is_holding_item",
|
||||
|
||||
@@ -65,9 +65,7 @@
|
||||
},
|
||||
"hover": {
|
||||
"poseTypes": ["HOVER"],
|
||||
"animations": [
|
||||
"q.look('neck')", "q.bedrock('pidgeot', 'air_idle')"
|
||||
],
|
||||
"animations": ["q.look('neck')", "q.bedrock('pidgeot', 'air_idle')"],
|
||||
"transformedParts": [
|
||||
{
|
||||
"part": "wing_open_left",
|
||||
@@ -93,14 +91,7 @@
|
||||
"fly": {
|
||||
"poseTypes": ["FLY"],
|
||||
"condition": "!q.is_gliding",
|
||||
"animations": [
|
||||
"q.look('neck')",
|
||||
"q.bedrock('pidgeot', 'air_idle')",
|
||||
{
|
||||
"condition": "!q.is_ridden",
|
||||
"animation": "q.pitch_tilt('body', 0.8)"
|
||||
}
|
||||
],
|
||||
"animations": ["q.look('neck')", "q.bedrock('pidgeot', 'air_idle')"],
|
||||
"transformedParts": [
|
||||
{
|
||||
"part": "wing_open_left",
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"item.cobblemon.npc_editor": "NPC's Trainer Card",
|
||||
|
||||
"item.cobblemon.link_cable": "Link Cable",
|
||||
"item.cobblemon.link_cable.tooltip": "Evolves Pokémon that require trading",
|
||||
"item.cobblemon.link_cable.tooltip": "Evolves pokemon that require trading",
|
||||
"item.cobblemon.dragon_scale": "Dragon Scale",
|
||||
"item.cobblemon.dragon_scale.tooltip": "Evolves Seadra into Kingdra when held while trading",
|
||||
"item.cobblemon.kings_rock": "Kings Rock",
|
||||
@@ -423,7 +423,7 @@
|
||||
"item.cobblemon.razor_fang.tooltip_1": "When the holder successfully inflicts damage, the target may also flinch",
|
||||
"item.cobblemon.razor_fang.tooltip_2": "Evolves Gligar into Gliscor when held while leveling up during the night",
|
||||
"item.cobblemon.red_card": "Red Card",
|
||||
"item.cobblemon.red_card.tooltip_1": "Pokémon that damage the holder are forced to switch out",
|
||||
"item.cobblemon.red_card.tooltip_1": "Pokemon that damage the holder are forced to switch out",
|
||||
"item.cobblemon.red_card.tooltip_2": "Consumed after use",
|
||||
"item.cobblemon.ring_target": "Ring Target",
|
||||
"item.cobblemon.ring_target.tooltip": "The holder's type immunities are removed",
|
||||
@@ -489,7 +489,7 @@
|
||||
"item.cobblemon.white_herb.tooltip_1": "Restores any lowered stat in battle",
|
||||
"item.cobblemon.white_herb.tooltip_2": "Consumed after use",
|
||||
"item.cobblemon.bright_powder": "Bright Powder",
|
||||
"item.cobblemon.bright_powder.tooltip": "Reduces the accuracy of enemy Pokémon's moves by 10%",
|
||||
"item.cobblemon.bright_powder.tooltip": "Reduces the accuracy of enemy pokemon's moves by 10%",
|
||||
"item.cobblemon.metal_powder": "Metal Powder",
|
||||
"item.cobblemon.metal_powder.tooltip": "Raises an untransformed Ditto's Defense and Sp. Def by 50%",
|
||||
"item.cobblemon.quick_powder": "Quick Powder",
|
||||
@@ -2122,7 +2122,6 @@
|
||||
"cobblemon.ui.status.psn": "PSN",
|
||||
"cobblemon.ui.status.slp": "SLP",
|
||||
"cobblemon.ui.status.tox": "PSN",
|
||||
"cobblemon.ui.exp.number": "+%1$s §oEXP",
|
||||
"cobblemon.battle.ui.fight": "Fight",
|
||||
"cobblemon.battle.ui.switch": "Switch",
|
||||
"cobblemon.battle.ui.capture": "Catch",
|
||||
@@ -2494,10 +2493,6 @@
|
||||
"cobblemon.config.ui.allow_experience_from_pvp.tooltip": "Whether to allow Exp. gain from PvP battles.",
|
||||
"cobblemon.config.ui.experience_share_multiplier": "Experience Share Multiplier",
|
||||
"cobblemon.config.ui.experience_share_multiplier.tooltip": "Multiplier of how much of the total Exp is given to a Pokémon holding an Exp. Share.",
|
||||
"cobblemon.config.ui.award_experience_to_fainted_pokemon": "Award Exp. to Fainted Pokémon",
|
||||
"cobblemon.config.ui.award_experience_to_fainted_pokemon.tooltip": "Whether fainted Pokémon receive Exp. for opponents they helped defeat before fainting.",
|
||||
"cobblemon.config.ui.award_experience_on_battle_loss": "Award Exp. on Battle Loss",
|
||||
"cobblemon.config.ui.award_experience_on_battle_loss.tooltip": "Whether Pokémon receive Exp. for opponents they defeated even if the battle is lost. Requires 'Award Exp. to Fainted Pokémon' to be enabled for fainted Pokémon to benefit.",
|
||||
"cobblemon.config.ui.lucky_egg_multiplier": "Lucky Egg Multiplier",
|
||||
"cobblemon.config.ui.lucky_egg_multiplier.tooltip": "Multiplier of how much of the total Exp is given to a Pokémon holding a Lucky Egg.",
|
||||
"cobblemon.config.ui.allow_spectating": "Allow Spectating",
|
||||
@@ -2608,7 +2603,7 @@
|
||||
"cobblemon.config.ui.summary_profile_animations.always_animate.tooltip": "Entire party animates unless fainted",
|
||||
|
||||
"cobblemon.config.ui.third_person_view_bobbing": "Third Person View Bobbing",
|
||||
"cobblemon.config.ui.third_person_view_bobbing.tooltip": "If true then use default Minecraft behavior to follow directly behind the player's head. If false then follow a fixed offset from the Pokémon.",
|
||||
"cobblemon.config.ui.third_person_view_bobbing.tooltip": "If true then use default Minecraft behavior to follow directly behind the player's head. If false then follow a fixed offset from the pokemon.",
|
||||
"cobblemon.config.ui.invert_pitch": "Invert Pitch",
|
||||
"cobblemon.config.ui.invert_pitch.tooltip": "Invert the direction for pitch (up and down).",
|
||||
"cobblemon.config.ui.invert_yaw": "Invert Yaw",
|
||||
@@ -3408,7 +3403,6 @@
|
||||
"cobblemon.starterselection.category.galar": "Galar",
|
||||
"cobblemon.starterselection.category.hisui_bias": "Hisui",
|
||||
"cobblemon.starterselection.category.paldea": "Paldea",
|
||||
"cobblemon.starterselection.category.special": "Special",
|
||||
|
||||
"cobblemon.stat.hp.name": "HP",
|
||||
"cobblemon.stat.attack.name": "Attack",
|
||||
@@ -3897,9 +3891,6 @@
|
||||
"cobblemon.behaviour.pokemon_bee.name": "Pollinates Flowers",
|
||||
"cobblemon.behaviour.pokemon_bee.desc": "Pollinates flowers and adds honey to hives and Saccharine tree leaves.",
|
||||
|
||||
"cobblemon.behaviour.pokemon_boids.name": "Boids",
|
||||
"cobblemon.behaviour.pokemon_boids.desc": "Boids Around.",
|
||||
|
||||
"cobblemon.behaviour.pokemon_picks_up_items.name": "Picks Up Items",
|
||||
"cobblemon.behaviour.pokemon_picks_up_items.desc": "The Pokémon will pick up items that it cares about.",
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@
|
||||
"sounds": [ { "name": "cobblemon:item/pokedex/pokedex_scan_zoom_increment", "volume": 0.7 } ]
|
||||
},
|
||||
"evolution.notification": {
|
||||
"sounds": [ { "name": "cobblemon:evolution/evolution_notification", "volume": 1.0 } ]
|
||||
"sounds": [ { "name": "cobblemon:evolution/evolution_notification", "volume": 0.7 } ]
|
||||
},
|
||||
"evolution.full": {
|
||||
"sounds": [ { "name": "cobblemon:evolution/evolution_full", "volume": 0.9 } ]
|
||||
@@ -173,7 +173,7 @@
|
||||
"sounds": [ { "name": "cobblemon:gui/levelup_start", "volume": 0.15 } ]
|
||||
},
|
||||
"gui.levelup": {
|
||||
"sounds": [ { "name": "cobblemon:gui/levelup", "volume": 0.55 } ]
|
||||
"sounds": [ { "name": "cobblemon:gui/levelup", "volume": 0.65 } ]
|
||||
},
|
||||
"gui.click": {
|
||||
"sounds": [ "cobblemon:gui/click" ]
|
||||
|
||||
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 248 B |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 299 B |
|
Before Width: | Height: | Size: 946 B |
|
Before Width: | Height: | Size: 124 B |
|
Before Width: | Height: | Size: 117 B |
|
Before Width: | Height: | Size: 505 B |
|
Before Width: | Height: | Size: 145 B |
|
Before Width: | Height: | Size: 576 B |
|
Before Width: | Height: | Size: 798 B |
|
Before Width: | Height: | Size: 786 B |
|
Before Width: | Height: | Size: 853 B |
|
Before Width: | Height: | Size: 746 B |
|
Before Width: | Height: | Size: 689 B |
|
Before Width: | Height: | Size: 851 B |
|
Before Width: | Height: | Size: 785 B |