mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-22 15:16:25 -04:00
Compare commits
351
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d54e51fb9 | ||
|
|
e3c1fff02a | ||
|
|
c672a4d690 | ||
|
|
e64df48441 | ||
|
|
7511facebc | ||
|
|
1486f5cfc2 | ||
|
|
558e2d89ff | ||
|
|
ea7b688655 | ||
|
|
fdafa3435c | ||
|
|
f17c90f6ea | ||
|
|
1dc45669bb | ||
|
|
f3658614a4 | ||
|
|
422f60f587 | ||
|
|
8190ddf0e9 | ||
|
|
acd375f533 | ||
|
|
af281d0bde | ||
|
|
754e9bc158 | ||
|
|
2df8504139 | ||
|
|
2838f97c56 | ||
|
|
68c75deeeb | ||
|
|
9076c659de | ||
|
|
9292504f8c | ||
|
|
5b9d5cbb55 | ||
|
|
e9bffd3ad7 | ||
|
|
ff890307c2 | ||
|
|
dda9d3e790 | ||
|
|
1acc02a2ec | ||
|
|
460c472c55 | ||
|
|
69c3b6142d | ||
|
|
ad484abf56 | ||
|
|
fc1c7cf6ca | ||
|
|
9a036a4c01 | ||
|
|
3cea6dd175 | ||
|
|
2fa053f833 | ||
|
|
ea4f858919 | ||
|
|
2aa4ab4b52 | ||
|
|
113977990e | ||
|
|
d86021e2a1 | ||
|
|
79faadef59 | ||
|
|
9b6629db7f | ||
|
|
d151a1ce65 | ||
|
|
d9ca7b483d | ||
|
|
708f7fc1b8 | ||
|
|
621db85845 | ||
|
|
b09066c580 | ||
|
|
7c45f28827 | ||
|
|
9c1b2cae22 | ||
|
|
16b2f81bd4 | ||
|
|
33abca9b43 | ||
|
|
ea3c051772 | ||
|
|
3bd3ce5688 | ||
|
|
108f0e3d88 | ||
|
|
4344c127eb | ||
|
|
881a87216b | ||
|
|
1d473b82af | ||
|
|
5955547c6f | ||
|
|
7a3a1cb8de | ||
|
|
1674cb0913 | ||
|
|
152ce4e923 | ||
|
|
3a6d024217 | ||
|
|
f2fcc5ba79 | ||
|
|
db0fb8e287 | ||
|
|
dae36a67d1 | ||
|
|
6f99862288 | ||
|
|
0377747e1e | ||
|
|
6bc974f90f | ||
|
|
aca53de259 | ||
|
|
41bde38727 | ||
|
|
a4b7ee3b38 | ||
|
|
4cde730259 | ||
|
|
2bcce06318 | ||
|
|
930c3568bb | ||
|
|
ffe480b12f | ||
|
|
154b653c57 | ||
|
|
04489bc969 | ||
|
|
25499b3e7a | ||
|
|
bb6bda54dc | ||
|
|
9f3dbdc2d8 | ||
|
|
932148338c | ||
|
|
e0d0c38232 | ||
|
|
a0e732dc6e | ||
|
|
309f674853 | ||
|
|
ea20c2aa4c | ||
|
|
da5272c473 | ||
|
|
dc705c7720 | ||
|
|
a785e574e0 | ||
|
|
4b236b0268 | ||
|
|
1cd86ae5d6 | ||
|
|
b7489a0b14 | ||
|
|
f7554425dc | ||
|
|
9bfc577444 | ||
|
|
217d4cda42 | ||
|
|
08daa43aae | ||
|
|
184d889831 | ||
|
|
c6e0f431ae | ||
|
|
93daeab840 | ||
|
|
f132b297c6 | ||
|
|
858d2e908a | ||
|
|
b61e0b0c82 | ||
|
|
4d9a6e71c9 | ||
|
|
a0010de504 | ||
|
|
cb73a30e23 | ||
|
|
3ca6d350fe | ||
|
|
2997ce8457 | ||
|
|
ff279d8310 | ||
|
|
9a7536b068 | ||
|
|
bd973e702e | ||
|
|
ede1851d34 | ||
|
|
cdb8ee0d8e | ||
|
|
e7d86a382c | ||
|
|
eb911df830 | ||
|
|
1c30acc53c | ||
|
|
32b0699235 | ||
|
|
e9c6580230 | ||
|
|
f893eb48fd | ||
|
|
232e2f18bf | ||
|
|
c266c99710 | ||
|
|
2a0e8f6d47 | ||
|
|
e6bd8ec529 | ||
|
|
911a95e873 | ||
|
|
447cfc2ae5 | ||
|
|
8dfeb0d260 | ||
|
|
caa7b19833 | ||
|
|
91d6fb62ba | ||
|
|
8f4a5b2015 | ||
|
|
07604c9a3b | ||
|
|
64dd8ae193 | ||
|
|
156ee6f290 | ||
|
|
c0a524a718 | ||
|
|
fe433d1417 | ||
|
|
0dc662d295 | ||
|
|
081e370bfd | ||
|
|
3ef0567287 | ||
|
|
3cad9e5481 | ||
|
|
dc8b7908be | ||
|
|
85085a1eca | ||
|
|
bff59dee6e | ||
|
|
bb5828531a | ||
|
|
cd08e98c28 | ||
|
|
bf7d40c9b5 | ||
|
|
5abb3d7f97 | ||
|
|
975ad8186a | ||
|
|
26b6419f04 | ||
|
|
331f7ca425 | ||
|
|
71c9ac0ff0 | ||
|
|
1404cc0ab5 | ||
|
|
6ca908a91d | ||
|
|
0948b8da9a | ||
|
|
22e04a53b3 | ||
|
|
df3217b41b | ||
|
|
9ff15a1913 | ||
|
|
89e43945a3 | ||
|
|
bd8dcddf35 | ||
|
|
dd372f3160 | ||
|
|
c56aab28d4 | ||
|
|
ca58801794 | ||
|
|
38fa5e2628 | ||
|
|
fae1ae7124 | ||
|
|
3ec5f76c73 | ||
|
|
5bb8aaba55 | ||
|
|
23ba41e981 | ||
|
|
29b049452b | ||
|
|
0b0f665804 | ||
|
|
3cdf28ffdd | ||
|
|
5d53fba2ce | ||
|
|
c6fb18ea51 | ||
|
|
2d851cb134 | ||
|
|
92b0ef84e9 | ||
|
|
dc8d18384c | ||
|
|
f685be0aab | ||
|
|
44c056ecd5 | ||
|
|
dcc707c20d | ||
|
|
14cc851562 | ||
|
|
7223d1fd68 | ||
|
|
6ab5ad52ec | ||
|
|
079b2ae766 | ||
|
|
34eea827e7 | ||
|
|
06c6ee5a9c | ||
|
|
54748c20ea | ||
|
|
75f52e4cf4 | ||
|
|
55446dd4b1 | ||
|
|
02e63b6fc8 | ||
|
|
3aebcf66ec | ||
|
|
07b1b22c2c | ||
|
|
49a5f716b7 | ||
|
|
23078eccba | ||
|
|
8b825d558f | ||
|
|
8a82f35edf | ||
|
|
bec664c073 | ||
|
|
9a7f97f11f | ||
|
|
95b8d62e4a | ||
|
|
3e61424d8f | ||
|
|
0bd7b4dab3 | ||
|
|
8cbada0cf5 | ||
|
|
f0784fec2d | ||
|
|
c1606c6fc9 | ||
|
|
cfe0c5e0a5 | ||
|
|
e0108cc99f | ||
|
|
c0de8a2591 | ||
|
|
b8733eed52 | ||
|
|
4aa30efec7 | ||
|
|
4354c3fb03 | ||
|
|
f25a676bd5 | ||
|
|
a9906762a0 | ||
|
|
7b5b88eca2 | ||
|
|
291682ba52 | ||
|
|
546d403b45 | ||
|
|
c9f40e32a1 | ||
|
|
d642b8dd07 | ||
|
|
549a11116e | ||
|
|
b5468a4a4f | ||
|
|
c5d278ccb7 | ||
|
|
bf5e15de45 | ||
|
|
f4af417267 | ||
|
|
69c04acaeb | ||
|
|
83a6bb188f | ||
|
|
494a8b5e91 | ||
|
|
c8021f2f08 | ||
|
|
8cdf3af047 | ||
|
|
c90a24e1ab | ||
|
|
83e3cfc34e | ||
|
|
133dd4e5b7 | ||
|
|
a8cca14b86 | ||
|
|
9b0f356fa4 | ||
|
|
f2b0a102b6 | ||
|
|
a7d01b230a | ||
|
|
2a7db4b9b8 | ||
|
|
77e5f2501d | ||
|
|
e6513717bd | ||
|
|
0e0c98f693 | ||
|
|
528bfa4e22 | ||
|
|
83c87779d7 | ||
|
|
dc33235144 | ||
|
|
935c658c5f | ||
|
|
0426f4fbf9 | ||
|
|
7930ec52fa | ||
|
|
75bc03fdcb | ||
|
|
57eb306e4f | ||
|
|
bd0511a9fb | ||
|
|
c14f3292aa | ||
|
|
46e771a062 | ||
|
|
4c0095844b | ||
|
|
10a2ef27d6 | ||
|
|
309ff3da13 | ||
|
|
3af91e2a74 | ||
|
|
2e027e3db7 | ||
|
|
fc40fb02c0 | ||
|
|
2c1c0299f9 | ||
|
|
3c7637515c | ||
|
|
08cbbaf68c | ||
|
|
6e911acf01 | ||
|
|
f6d82a1b28 | ||
|
|
702126aeab | ||
|
|
df566e8fcb | ||
|
|
c83e0f33b5 | ||
|
|
f279f936b6 | ||
|
|
f8e20bafd9 | ||
|
|
a5211ca7ff | ||
|
|
0a5abdb331 | ||
|
|
4ed7413dd2 | ||
|
|
40b66c24fc | ||
|
|
2f7bedf001 | ||
|
|
44c31a9ddb | ||
|
|
20a35de3d8 | ||
|
|
cfc8a15db1 | ||
|
|
d5ba22a86c | ||
|
|
a38505f1c5 | ||
|
|
3cf1ba1fb3 | ||
|
|
9f5d595827 | ||
|
|
75673f0204 | ||
|
|
b43a7571ff | ||
|
|
b071dc04d3 | ||
|
|
12f9ff1647 | ||
|
|
3256a302df | ||
|
|
edbe447477 | ||
|
|
2f43910368 | ||
|
|
c2e87e4cfa | ||
|
|
e40d2ee806 | ||
|
|
ba74e7603b | ||
|
|
19c3fe6dc4 | ||
|
|
d0778dd124 | ||
|
|
8fb2b1cf3d | ||
|
|
5c7d1cbe10 | ||
|
|
3348bfc362 | ||
|
|
8db9252434 | ||
|
|
d9d4fe15cc | ||
|
|
8453e2d514 | ||
|
|
4d3070c0c2 | ||
|
|
15f273a9ef | ||
|
|
007c079cd6 | ||
|
|
9851b1e8ad | ||
|
|
c65c2610cd | ||
|
|
2be08a3fcc | ||
|
|
4cdf440eee | ||
|
|
1545ee8fc2 | ||
|
|
a009fabfe4 | ||
|
|
46e4bfdd13 | ||
|
|
e24c82860d | ||
|
|
1291dee1e1 | ||
|
|
69732be09b | ||
|
|
8ce4ed7dee | ||
|
|
33d3cc0104 | ||
|
|
33b6b519ed | ||
|
|
5ec849f4e3 | ||
|
|
6f4a2d2cd5 | ||
|
|
0bca89e77d | ||
|
|
8f9f162011 | ||
|
|
ac3bea3d5e | ||
|
|
178d4f199b | ||
|
|
933819380d | ||
|
|
d68131e1b9 | ||
|
|
ffbc379c0b | ||
|
|
7053d12508 | ||
|
|
d41442200f | ||
|
|
d2564c343e | ||
|
|
d1c19e8837 | ||
|
|
0ec3bfabad | ||
|
|
43ffb04621 | ||
|
|
eeaedf76a9 | ||
|
|
120fb4829a | ||
|
|
12b93e53a7 | ||
|
|
00b17b9764 | ||
|
|
06192f809d | ||
|
|
d09eb0d3ff | ||
|
|
416321679c | ||
|
|
72c4e6e54f | ||
|
|
d3738e3424 | ||
|
|
2e183db903 | ||
|
|
fa174b88dd | ||
|
|
0b4abe6d16 | ||
|
|
759fb0ecd8 | ||
|
|
429e927c3f | ||
|
|
643aed9133 | ||
|
|
e701642de2 | ||
|
|
a3a03e3da6 | ||
|
|
5dd3d5b552 | ||
|
|
1bae246e6c | ||
|
|
c94ef10189 | ||
|
|
dfacf1f9df | ||
|
|
06c5e34f48 | ||
|
|
1ab8b35e98 | ||
|
|
261b61ee6c | ||
|
|
5abf8e1bab | ||
|
|
7c0b463637 | ||
|
|
e156ab2432 | ||
|
|
0b46a7163b | ||
|
|
21b76c008a | ||
|
|
b7e9f8248d | ||
|
|
8b21e76a36 | ||
|
|
515471185f | ||
|
|
628e8f4e5e |
@@ -157,6 +157,9 @@ build:
|
||||
paths:
|
||||
- neoforge/build/libs
|
||||
- fabric/build/libs
|
||||
exclude:
|
||||
- neoforge/build/libs/*-dev-shadow.jar
|
||||
- fabric/build/libs/*-dev-shadow.jar
|
||||
|
||||
publish:
|
||||
stage: deploy
|
||||
|
||||
+148
-33
@@ -1,41 +1,156 @@
|
||||
# Changelog
|
||||
## [1.8.1 (September 13th, 2026)](#1-8-1)
|
||||
## [1.8.2 (MONTH xth, 2026)](#1-8-2)
|
||||
|
||||
### Additions
|
||||
- The following items can now be placed on the ground as blocks:
|
||||
- King's Rock, Galarica Cuff, Unremarkable Teacup, Masterpiece Teacup, Chipped Pot, Cracked Pot, Sachet, Metal Powder, Quick Powder, Bright Powder, Soft Sand, Electirizer, Magmarizer, Icy Rock, Heat Rock, Smooth Rock, Damp Rock, Destiny Knot, Luminous Moss, Never-Melt Ice, Wide Lens, Scope Lens, and Zoom Lens.
|
||||
- Iron Ball, Metal Coat, Throat Spray, Terrain Extender, Medicinal Brew, Moomoo Milk, HP Up, Protein, Iron, Carbos, Calcium, Zinc, PP Up, PP Max, Upgrade, Dubious Disc, and Loaded Dice.
|
||||
- The following items can now be placed on various surfaces:
|
||||
- Galarica Wreath, Magnet, Flame Orb, Life Orb, Toxic Orb, Light Ball, and Smoke Ball.
|
||||
- The following items can now be placed as blocks and have additional functionality:
|
||||
- Ring Target, Metronome, and Eject Button.
|
||||
- The following items can now be worn by Players and Pokémon:
|
||||
- Galarica Wreath, Cleanse Tag, Spell Tag, Sharp Beak, Covert Cloak, Shell Helmet, and Never-Melt Ice.
|
||||
- Wearable items can now be equipped by a player by holding it and right-clicking (or rebound use/interact)
|
||||
- Wearable items that can be placed down are exempt from this behaviour.
|
||||
- Mint Leaves can now be placed on the ground as Leaf Litter, or in Flower Pots
|
||||
- Added Oricorio Flowers
|
||||
- Added Terrain Plants
|
||||
- Berries can now be placed in Flower Pots
|
||||
- The evolution tree can now be viewed on the Pokédex, showing a Pokémon's full family line and what's needed to evolve into each stage.
|
||||
- Added custom sounds to a bunch of items that can be placed as blocks: liquid bottles, pill bottles, powders and dice.
|
||||
|
||||
### Pokémon Added
|
||||
#### Generation 4
|
||||
- Snover
|
||||
- Abomasnow
|
||||
- Burmy
|
||||
- Wormadam
|
||||
- Mothim
|
||||
|
||||
#### Generation 5
|
||||
- Tympole
|
||||
- Palpitoad
|
||||
- Seismitoad
|
||||
|
||||
#### Generation 6
|
||||
- Helioptile
|
||||
- Heliolisk
|
||||
|
||||
#### Generation 7
|
||||
- Grubbin
|
||||
- Charjabug
|
||||
- Vikavolt
|
||||
- Minior
|
||||
|
||||
#### Generation 8
|
||||
- Eternatus
|
||||
|
||||
#### Generation 9
|
||||
- Greavard
|
||||
- Houndstone
|
||||
|
||||
### Model updates for the following Pokémon
|
||||
- Sudowoodo
|
||||
- Spheal
|
||||
- Cacnea
|
||||
- Cacturne
|
||||
- Binacle
|
||||
- Barbaracle
|
||||
- Glimmet
|
||||
- Glimmora
|
||||
- Deerling
|
||||
- Sawsbuck (All forms)
|
||||
- Absol
|
||||
- Poliwag
|
||||
- Politoed
|
||||
- Ekans
|
||||
- Oddish
|
||||
- Gloom
|
||||
- Vileplume
|
||||
- Bellossom
|
||||
- Shroomish
|
||||
|
||||
### Animation updates for the following Pokémon
|
||||
- Flapple, Appletun, Dipplin, Hydrapple (placeholder animations only).
|
||||
- Treecko (New Shoulder Mount!)
|
||||
- Bonsly (New Shoulder Mount!)
|
||||
- Sudowoodo
|
||||
- Cacnea
|
||||
- Cacturne
|
||||
- Ekans
|
||||
- Arbok
|
||||
- Oddish
|
||||
- Gloom
|
||||
- Vileplume
|
||||
- Bellossom
|
||||
|
||||
### Changes
|
||||
- Terrain Seeds can now be planted
|
||||
- Terrain Seeds are now consumed during PvP and NPC battles
|
||||
- Glow Item Frames can be crafted using Bright Powder as an alternative to Glow Ink Sacs
|
||||
- Bright Powder can be used on Signs and Hanging Signs to make text glow, as an alternative to Glow Ink Sacs
|
||||
- Updated various recipes to display in Recipe Groups within the Recipe Book
|
||||
- Updated most recipes to display more logically/consistently within the Recipe Book's categories
|
||||
- Updated the following recipes
|
||||
- Never-Melt Ice, Focus Sash, Blunder Policy, Weakness Policy, Blank TM
|
||||
- Make ride rotations less jittery in multiplayer.
|
||||
- Changes Disc Shelf TMs to use textures instead of tinting and overlays to line up with item display
|
||||
- Blank TM can now be inserted into the Disc Shelf
|
||||
- Added TM creative tab
|
||||
- Recall animation now renders ball in hand with the player arm raised.
|
||||
- Made regional and Vivillon split-evo locations now mutually exclusive
|
||||
- Item evolutions now unlock **all** locked evolutions whose requirements are met instead of stopping after the first.
|
||||
- Exceptions being "forced" evolutions which will continue to stop after the first one
|
||||
|
||||
### Fixes
|
||||
- Fixed a potential world generation stall caused by Type Gem clusters.
|
||||
- Fixed Pokémon on shoulders not animating if they are visibly holding a held item.
|
||||
- Fixed NPCs getting their own TM progress file.
|
||||
- Fixed odd NeoForge crash due to a dependency being outdated. It will now correctly flag the dependency for being outdated.
|
||||
- Fixed the TM Machine refusing move selections that the `unlockAllMoveDexMovesByDefault` config had made visible, leaving the move slot blank and the craft button unresponsive.
|
||||
- Fixed the Beast Ball not applying its 0.1× penalty to Pokémon that are not Ultra Beasts.
|
||||
- Fixed the Moon Ball reading the world's total elapsed time instead of the time of day, so its bonus applied during the day instead.
|
||||
- Fixed the Love Ball never awarding its 2.5× opposite gender bonus, and matching species by display name instead of identifier.
|
||||
- Fixed the Timer Ball being weaker than a regular Poké Ball on early turns and only reaching 4× on turn 14 instead of turn 10.
|
||||
- Fixed the Luxury Ball only doubling Friendship gains of 1, leaving level up and berry gains unaffected.
|
||||
- Fixed Rufflet, Braviary, and Hisuian Braviary flooding logs with errors about missing sound events.
|
||||
- Fixed Pokémon property typos in Pangoro and Dedenne herd spawn files.
|
||||
- Fixed unintentional watermelon cosmetic for Hisuian Electrode.
|
||||
- Fixed Blastoise having an unintentional passenger seat.
|
||||
- Fixed form typo in Rockruff-Dusk spawn, preventing it from being able to spawn.
|
||||
- Fixed the `stat_compare` and `stat_equal` evolution requirements crashing the game when given `special_attack` or `special_defence`, and made them treat any non-permanent stat as a failed requirement instead of crashing.
|
||||
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait.
|
||||
- Fixed Ampharos' eyes not emitting particles.
|
||||
- Fixed Alolan Raticate's cry not playing.
|
||||
- Fixed several incorrect light-based spawning conditions for ghosts and bugs.
|
||||
- Removed unused, placeholder habitat pools.
|
||||
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to having an expression assigned as a recoil animation.
|
||||
- Fixed Lucario's aura particles not emitting.
|
||||
- Fixed Lycanroc's head movement.
|
||||
- Added head movement to Archaludon.
|
||||
- Fixed Duraludon line not having their sleep animations.
|
||||
- Fixed Diglett line not having their faint animations.
|
||||
- Fixed Kingambit moving its legs while walking.
|
||||
- Correctly grants "They're Minerals!" advancement when planting a type gem
|
||||
- Shoulder mounted Pokémon now follow player body animations, improving their positioning with custom player animations and default actions.
|
||||
- Shoulder mounted Pokémon now sit slightly higher with chest armor equipped for better alignment, with a configurable blacklist.
|
||||
- Type Gem Blocks now have advancements to give you the recipe in the recipe book (the data for the advancements were inside the recipes somehow??)
|
||||
- Some other recipes without advancements have also been corrected
|
||||
- Corrected the TM Recipes for Charge Beam and Payday
|
||||
- Type Gem blocks now properly generate clusters off of the block.
|
||||
- Starter Screen Categories will no longer stop rendering if they are bigger than the viewport, and you are not viewing top or bottom edges
|
||||
- Random Starter option now properly handled in height calculation, so now it won't run off into other categories or be un-accessible due to scroll limits if it causes a new row
|
||||
- Mochi and Feathers no longer drop glass bottles on use
|
||||
- Fixed a rare set of concurrency bugs related to reloading packs on NeoForge; fixes a ModernFix issue
|
||||
- Removed unnecessary conditions for Mimikyu's standing pose. Hopefully fixes the rare instance of it using its shoulder animations during battle in multiplayer scenarios.
|
||||
- Fixed Rockruff line not blinking
|
||||
- Fixed Flygon sometimes using its running animation instead of walking
|
||||
- Fixed Bramblin always sleeping outside of battle
|
||||
- Fixed Dragonair using its battle idle as a normal idle
|
||||
- Fixed Lycanroc's head movement
|
||||
- Added head movement to Archaludon
|
||||
- Fixed Duraludon line not having any sleep animations
|
||||
- Fixed Diglett line not having their faint animations
|
||||
- Fixed Kingambit moving its legs while walking
|
||||
- Fixed Alolan Diglett's cry not playing
|
||||
- Fixed shiny alpha Snorlax using the non-shiny texture
|
||||
- Various Structure fixes
|
||||
- Fixed shiny Pokémon under certain circumstances forgetting taught TMs
|
||||
- Fixed certain Pokémon sounds sometimes being doubled when played at specific times in their animations
|
||||
- Fixed ball capture beam origin coming from behind the ball.
|
||||
- Fixed Safari Ball catch rate outside of battle
|
||||
|
||||
### Developer
|
||||
- Evolution requirements can now set `pokedexIcon` and `pokedexTooltip` directly in their JSON to control how they're shown on the Pokédex's evolution tree, for example:
|
||||
```json
|
||||
{
|
||||
"variant": "attack_defence_ratio",
|
||||
"pokedexIcon": "minecraft:diamond_sword",
|
||||
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.attack_defence_ratio.tooltip"
|
||||
}
|
||||
```
|
||||
- Fixed wrong order of operations in the code flow prior to the `FOSSIL_REVIVED` event causing pokemon to appear without entity link
|
||||
- Fixed TestPartySlot command not correctly comparing the provided spec with the target Pokémon
|
||||
|
||||
### Molang & Datapacks
|
||||
- A new `negate` variant for evolution requirements has been added, which will negate (invert) the provided inner condition.
|
||||
- Added Molang functions for individual Pokédex ids, wherein a subdex is passed in as a string to `dex_caught_count` etc.
|
||||
|
||||
### Particles
|
||||
|
||||
#### New Move Animations
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
- Brazilian Portuguese
|
||||
- Simplified Chinese
|
||||
- Japanese
|
||||
@@ -1,9 +0,0 @@
|
||||
- Some kind of particle effect for them? they're meant to have glowing eyes, so maybe some red particles on the eye locator
|
||||
--- global posers? stacked posers?
|
||||
--- red texture seems to be what some people wanted.
|
||||
|
||||
/- shoulder mounts part of it
|
||||
- omni boost
|
||||
- roar when player comes within reach, adjust levels
|
||||
|
||||
- Riding settings to control what the min scale is for a pokemon to be ridable.
|
||||
+5
-2
@@ -1,3 +1,6 @@
|
||||
import utilities.gitBranch
|
||||
import utilities.gitShortCommit
|
||||
|
||||
plugins {
|
||||
id("cobblemon.root-conventions")
|
||||
}
|
||||
@@ -6,6 +9,6 @@ version = "${project.property("mod_version")}+${project.property("mc_version")}"
|
||||
|
||||
val isSnapshot = project.property("snapshot")?.equals("true") == true
|
||||
if (isSnapshot) {
|
||||
val fixedBranchName = versioning.info.branch.substringAfter("/")
|
||||
version = "$version-${fixedBranchName}-${versioning.info.build}"
|
||||
val fixedBranchName = gitBranch().substringAfter("/")
|
||||
version = "$version-${fixedBranchName}-${gitShortCommit()}"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Changelog
|
||||
## [1.8.1 (September 13th, 2026)](#1-8-1)
|
||||
|
||||
### Animation updates for the following Pokémon
|
||||
- Flapple, Appletun, Dipplin, Hydrapple (placeholder animations only).
|
||||
|
||||
### Fixes
|
||||
- Fixed a potential world generation stall caused by Type Gem clusters.
|
||||
- Fixed Pokémon on shoulders not animating if they are visibly holding a held item.
|
||||
- Fixed NPCs getting their own TM progress file.
|
||||
- Fixed odd NeoForge crash due to a dependency being outdated. It will now correctly flag the dependency for being outdated.
|
||||
- Fixed the TM Machine refusing move selections that the `unlockAllMoveDexMovesByDefault` config had made visible, leaving the move slot blank and the craft button unresponsive.
|
||||
- Fixed the Beast Ball not applying its 0.1× penalty to Pokémon that are not Ultra Beasts.
|
||||
- Fixed the Moon Ball reading the world's total elapsed time instead of the time of day, so its bonus applied during the day instead.
|
||||
- Fixed the Love Ball never awarding its 2.5× opposite gender bonus, and matching species by display name instead of identifier.
|
||||
- Fixed the Timer Ball being weaker than a regular Poké Ball on early turns and only reaching 4× on turn 14 instead of turn 10.
|
||||
- Fixed the Luxury Ball only doubling Friendship gains of 1, leaving level up and berry gains unaffected.
|
||||
- Fixed Rufflet, Braviary, and Hisuian Braviary flooding logs with errors about missing sound events.
|
||||
- Fixed Pokémon property typos in Pangoro and Dedenne herd spawn files.
|
||||
- Fixed unintentional watermelon cosmetic for Hisuian Electrode.
|
||||
- Fixed Blastoise having an unintentional passenger seat.
|
||||
- Fixed form typo in Rockruff-Dusk spawn, preventing it from being able to spawn.
|
||||
- Fixed the `stat_compare` and `stat_equal` evolution requirements crashing the game when given `special_attack` or `special_defence`, and made them treat any non-permanent stat as a failed requirement instead of crashing.
|
||||
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait.
|
||||
- Fixed Ampharos' eyes not emitting particles.
|
||||
- Fixed Alolan Raticate's cry not playing.
|
||||
- Fixed several incorrect light-based spawning conditions for ghosts and bugs.
|
||||
- Removed unused, placeholder habitat pools.
|
||||
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to having an expression assigned as a recoil animation.
|
||||
- Fixed Lucario's aura particles not emitting.
|
||||
- Fixed Lycanroc's head movement.
|
||||
- Added head movement to Archaludon.
|
||||
- Fixed Duraludon line not having their sleep animations.
|
||||
- Fixed Diglett line not having their faint animations.
|
||||
- Fixed Kingambit moving its legs while walking.
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
- Brazilian Portuguese
|
||||
- Simplified Chinese
|
||||
- Japanese
|
||||
+15
-6
@@ -1,9 +1,8 @@
|
||||
|
||||
import utilities.isSnapshot
|
||||
import utilities.version
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import utilities.gitBranch
|
||||
import utilities.gitCommit
|
||||
|
||||
/*
|
||||
*
|
||||
@@ -72,6 +71,14 @@ tasks.withType<Test> {
|
||||
}
|
||||
}
|
||||
|
||||
// Git info lives in its own output dir (not processResources) so a new commit only rewrites this one file.
|
||||
val writeGitInfo by tasks.registering(WriteProperties::class) {
|
||||
destinationFile = layout.buildDirectory.file("generated/gitInfo/cobblemon-git.properties")
|
||||
property("commit", gitCommit())
|
||||
property("branch", gitBranch())
|
||||
}
|
||||
sourceSets.main { output.dir(mapOf("builtBy" to writeGitInfo), layout.buildDirectory.dir("generated/gitInfo")) }
|
||||
|
||||
tasks.processResources {
|
||||
exclude("data/**/README.md")
|
||||
}
|
||||
@@ -98,11 +105,13 @@ sourceSets {
|
||||
property("modid", "cobblemon")
|
||||
property("version", project.version())
|
||||
property("isSnapshot", if(rootProject.isSnapshot()) "true" else "false")
|
||||
property("gitCommit", versioning.info.commit)
|
||||
property("branch", versioning.info.branch)
|
||||
System.getProperty("buildNumber")?.let { property("buildNumber", it) }
|
||||
property("timestamp", OffsetDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm:ss")) + " UTC")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The platform jars are built from :common:jar, so the remapped common jars are only needed by `publish`
|
||||
tasks.assemble {
|
||||
setDependsOn(dependsOn.filterNot { (it as? TaskProvider<*>)?.name in setOf("remapJar", "remapSourcesJar") })
|
||||
}
|
||||
|
||||
@@ -10,11 +10,13 @@ package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.OrientationControllable;
|
||||
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
|
||||
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker;
|
||||
import com.cobblemon.mod.common.duck.RidePassenger;
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
|
||||
import net.minecraft.client.model.HumanoidModel;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
@@ -96,4 +98,14 @@ public class HumanoidModelMixin {
|
||||
MountedPlayerRenderer.INSTANCE.animate((HumanoidModel<AbstractClientPlayer>)(Object)this, pokemonEntity, player, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "setupAnim*", at = @At("TAIL"))
|
||||
public void cobblemon$poseRecallArm(LivingEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch, CallbackInfo ci) {
|
||||
if (!(entity instanceof AbstractClientPlayer) || entity.getVehicle() instanceof PokemonEntity) return;
|
||||
float raise = RecallArmAnimationTracker.INSTANCE.raiseFactor(entity.getUUID());
|
||||
if (raise <= 0f) return;
|
||||
this.rightArm.xRot = Mth.lerp(raise, this.rightArm.xRot, (float) (-Math.PI / 2.0));
|
||||
this.rightArm.yRot = Mth.lerp(raise, this.rightArm.yRot, 0f);
|
||||
this.rightArm.zRot = Mth.lerp(raise, this.rightArm.zRot, 0f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.entity.layers.ItemInHandLayer;
|
||||
import net.minecraft.world.entity.HumanoidArm;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(ItemInHandLayer.class)
|
||||
public class ItemInHandLayerMixin {
|
||||
@Inject(method = "renderArmWithItem", at = @At("HEAD"), cancellable = true)
|
||||
private void cobblemon$hideRecallHandItem(LivingEntity entity, ItemStack itemStack, ItemDisplayContext itemDisplayContext, HumanoidArm humanoidArm, PoseStack poseStack, MultiBufferSource multiBufferSource, int light, CallbackInfo ci) {
|
||||
if (humanoidArm == HumanoidArm.RIGHT && RecallArmAnimationTracker.INSTANCE.isRecalling(entity.getUUID())) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.OrientationControllable;
|
||||
import com.cobblemon.mod.common.client.render.player.FirstPersonRecallBallRenderer;
|
||||
import com.cobblemon.mod.common.item.PokedexItem;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -31,6 +32,9 @@ public class ItemInHandRendererMixin {
|
||||
if (abstractClientPlayer.isUsingItem() && abstractClientPlayer.getUseItem().getItem() instanceof PokedexItem) {
|
||||
ci.cancel();
|
||||
}
|
||||
else if (interactionHand == InteractionHand.MAIN_HAND && FirstPersonRecallBallRenderer.INSTANCE.render(poseStack, multiBufferSource, j, f)) {
|
||||
ci.cancel();
|
||||
}
|
||||
else if (abstractClientPlayer instanceof OrientationControllable controllable && controllable.getOrientationController().isActive()) {
|
||||
|
||||
}
|
||||
|
||||
@@ -8,8 +8,12 @@ object CobblemonBuildDetails {
|
||||
const val VERSION = "{{ version }}"
|
||||
const val SNAPSHOT = {{ isSnapshot }}
|
||||
|
||||
const val GIT_COMMIT = "{{ gitCommit | default("Not Specified") }}"
|
||||
const val BRANCH = "{{ branch | default("Not Specified") }}"
|
||||
// Read at runtime rather than templated in, so a new commit doesn't invalidate compiled code.
|
||||
private val git = java.util.Properties().apply {
|
||||
CobblemonBuildDetails::class.java.getResourceAsStream("/cobblemon-git.properties")?.use(::load)
|
||||
}
|
||||
@JvmField val GIT_COMMIT: String = git.getProperty("commit", "Not Specified")
|
||||
@JvmField val BRANCH: String = git.getProperty("branch", "Not Specified")
|
||||
|
||||
fun smallCommitHash(): String {
|
||||
return this.GIT_COMMIT.substring(0, 7)
|
||||
|
||||
@@ -109,4 +109,21 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
|
||||
"poke_snack",
|
||||
BlockEntityType.Builder.of(::PokeSnackBlockEntity, CobblemonBlocks.POKE_SNACK, CobblemonBlocks.POKE_CAKE).build(null)
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val TERRAIN_PLANT: BlockEntityType<TerrainPlantBlockEntity> = this.create(
|
||||
"terrain_plant",
|
||||
BlockEntityType.Builder.of(::TerrainPlantBlockEntity,
|
||||
CobblemonBlocks.VOLTACONIA,
|
||||
CobblemonBlocks.LUCIBELL,
|
||||
CobblemonBlocks.VERDROOT
|
||||
).build(null)
|
||||
)
|
||||
@JvmField
|
||||
val MISTLEAF_FLOWER: BlockEntityType<MistleafFlowerBlockEntity> = this.create(
|
||||
"mistleaf",
|
||||
BlockEntityType.Builder.of(::MistleafFlowerBlockEntity,
|
||||
CobblemonBlocks.MISTLEAF
|
||||
).build(null)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.block.MintBlock.MintType
|
||||
import com.cobblemon.mod.common.block.campfirepot.CampfireBlock
|
||||
import com.cobblemon.mod.common.block.campfirepot.CampfirePotBlock
|
||||
import com.cobblemon.mod.common.block.chest.GildedChestBlock
|
||||
import com.cobblemon.mod.common.block.decor.*
|
||||
import com.cobblemon.mod.common.block.general.BaleBlock
|
||||
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
|
||||
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
|
||||
@@ -26,6 +27,7 @@ import com.cobblemon.mod.common.block.sign.CobblemonSignBlock
|
||||
import com.cobblemon.mod.common.block.sign.CobblemonWallHangingSignBlock
|
||||
import com.cobblemon.mod.common.block.sign.CobblemonWallSignBlock
|
||||
import com.cobblemon.mod.common.block.tmmachine.TMMachineBlock
|
||||
import com.cobblemon.mod.common.block.general.MintLitterBlock
|
||||
import com.cobblemon.mod.common.mixin.invoker.BlocksInvoker
|
||||
import com.cobblemon.mod.common.mixin.invoker.DoorBlockInvoker
|
||||
import com.cobblemon.mod.common.mixin.invoker.FireBlockInvoker
|
||||
@@ -621,37 +623,131 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val WEAKNESS_POLICY = create("weakness_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_PAPER_LARGE_SOUNDS).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
|
||||
|
||||
@JvmField
|
||||
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
val RING_TARGET = create("ring_target", RingTargetBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).mapColor(MapColor.NONE).noOcclusion().strength(0.125f)))
|
||||
@JvmField
|
||||
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
val MAGNET = create("magnet", MagnetBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion().strength(0.075f)))
|
||||
@JvmField
|
||||
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
val GALARICA_WREATH = create("galarica_wreath", GalaricaWreathBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noOcclusion().noCollission()))
|
||||
|
||||
@JvmField
|
||||
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
val FLAME_ORB = create("flame_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{15}))
|
||||
@JvmField
|
||||
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
val TOXIC_ORB = create("toxic_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
|
||||
@JvmField
|
||||
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
val LIFE_ORB = create("life_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{7}))
|
||||
@JvmField
|
||||
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
val LIGHT_BALL = create("light_ball", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{13}))
|
||||
@JvmField
|
||||
val SMOKE_BALL = create("smoke_ball", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
|
||||
|
||||
@JvmField
|
||||
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
val KINGS_ROCK = create("kings_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.CENTERED_6x))
|
||||
|
||||
@JvmField
|
||||
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
val GALARICA_CUFF = create("galarica_cuff", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noCollission().noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_10x4))
|
||||
@JvmField
|
||||
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
val UNREMARKABLE_TEACUP = create("unremarkable_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
|
||||
@JvmField
|
||||
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
|
||||
val MASTERPIECE_TEACUP = create("masterpiece_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
|
||||
@JvmField
|
||||
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
|
||||
val CHIPPED_POT = create("chipped_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
|
||||
@JvmField
|
||||
val CRACKED_POT = create("cracked_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
|
||||
@JvmField
|
||||
val ELECTIRIZER = create("electirizer", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.IZER))
|
||||
@JvmField
|
||||
val MAGMARIZER = create("magmarizer", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.IZER))
|
||||
|
||||
@JvmField
|
||||
val SACHET = create("sachet", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
@JvmField
|
||||
val METAL_POWDER = create("metal_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
@JvmField
|
||||
val QUICK_POWDER = create("quick_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
@JvmField
|
||||
val BRIGHT_POWDER = create("bright_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion().lightLevel{3}, DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
@JvmField
|
||||
val SOFT_SAND = create("soft_sand", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
|
||||
@JvmField
|
||||
val NEVER_MELT_ICE = create("never_melt_ice", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().friction(0.989F), DecorativeItemBlock.DecorativeItemBlockType.NEVERMELT))
|
||||
@JvmField
|
||||
val SCOPE_LENS = create("scope_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.SCOPE))
|
||||
@JvmField
|
||||
val WIDE_LENS = create("wide_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.WIDE))
|
||||
@JvmField
|
||||
val ZOOM_LENS = create("zoom_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.ZOOM))
|
||||
|
||||
@JvmField
|
||||
val ICY_ROCK = create("icy_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
|
||||
@JvmField
|
||||
val HEAT_ROCK = create("heat_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
|
||||
@JvmField
|
||||
val SMOOTH_ROCK = create("smooth_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
|
||||
@JvmField
|
||||
val DAMP_ROCK = create("damp_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x6))
|
||||
@JvmField
|
||||
val DESTINY_KNOT = create("destiny_knot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.KNOT))
|
||||
@JvmField
|
||||
val LUMINOUS_MOSS = create("luminous_moss", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.MOSS).mapColor(MapColor.NONE).noOcclusion().lightLevel{5}, DecorativeItemBlock.DecorativeItemBlockType.CENTERED_8x))
|
||||
|
||||
@JvmField
|
||||
val METRONOME = create("metronome", ActivatableDecorationBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion(), ActivatableDecorationBlock.ActivatableDecorationBlockType.METRONOME))
|
||||
@JvmField
|
||||
val EJECT_BUTTON = create("eject_button", EjectButtonBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().strength(0.125f), 40))
|
||||
|
||||
@JvmField
|
||||
val LOADED_DICE = create("loaded_dice", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_DICE_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.DICE))
|
||||
@JvmField
|
||||
val IRON_BALL = create("iron_ball", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.IRON_BALL))
|
||||
@JvmField
|
||||
val METAL_COAT = create("metal_coat", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.METAL_COAT))
|
||||
@JvmField
|
||||
val UPGRADE = create("upgrade", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.UPGRADE))
|
||||
@JvmField
|
||||
val DUBIOUS_DISC = create("dubious_disc", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.UPGRADE))
|
||||
|
||||
@JvmField
|
||||
val THROAT_SPRAY = create("throat_spray", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.THROAT_SPRAY))
|
||||
@JvmField
|
||||
val TERRAIN_EXTENDER = create("terrain_extender", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.EXTENDER))
|
||||
@JvmField
|
||||
val MOOMOO_MILK = create("moomoo_milk", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.GLASS_BOTTLE))
|
||||
@JvmField
|
||||
val MEDICINAL_BREW = create("medicinal_brew", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.GLASS_BOTTLE))
|
||||
|
||||
@JvmField
|
||||
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
|
||||
@JvmField
|
||||
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
@JvmField
|
||||
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
@JvmField
|
||||
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
@JvmField
|
||||
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
|
||||
@JvmField
|
||||
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
@JvmField
|
||||
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
@JvmField
|
||||
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
@JvmField
|
||||
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
|
||||
@JvmField
|
||||
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
|
||||
|
||||
@JvmField
|
||||
val DIRE_HIT = create("dire_hit", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
|
||||
@@ -670,6 +766,23 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val X_SPEED = create("x_${Stats.SPEED.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
|
||||
|
||||
@JvmField
|
||||
val HP_UP = create("hp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val PROTEIN = create("protein", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val IRON = create("iron", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val CARBOS = create("carbos", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val CALCIUM = create("calcium", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val ZINC = create("zinc", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val PP_UP = create("pp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val PP_MAX = create("pp_max", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
|
||||
@JvmField
|
||||
val MONITOR = create("monitor", MonitorBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).lightLevel { if (it.getValue(MonitorBlock.SCREEN) != MonitorBlock.MonitorScreen.OFF) 13 else 0 }))
|
||||
@JvmField
|
||||
@@ -685,6 +798,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val PC = create("pc", PCBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F).noOcclusion().lightLevel { if ((it.getValue(PCBlock.ON) as Boolean) && (it.getValue(PCBlock.PART) == PCBlock.PCPart.TOP)) 13 else 0 }))
|
||||
|
||||
@JvmField
|
||||
val TM_MACHINE = create(
|
||||
"tm_machine",
|
||||
TMMachineBlock(
|
||||
@@ -713,7 +827,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
|
||||
@JvmField
|
||||
val PASTURE = create("pasture", PastureBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BROWN).sound(SoundType.WOOD).strength(2F).noOcclusion().pushReaction(PushReaction.BLOCK).lightLevel { if ((it.getValue(PastureBlock.ON) as Boolean) && (it.getValue(PastureBlock.PART) == PastureBlock.PasturePart.TOP)) 13 else 0 }))
|
||||
|
||||
@JvmField
|
||||
val RED_MINT = create("red_mint", MintBlock(MintType.RED, BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_RED).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MINT_SOUNDS).pushReaction(PushReaction.DESTROY)))
|
||||
@JvmField
|
||||
val BLUE_MINT = create("blue_mint", MintBlock(MintType.BLUE, BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BLUE).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MINT_SOUNDS).pushReaction(PushReaction.DESTROY)))
|
||||
@@ -755,6 +869,64 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val POTTED_SACCHARINE_SAPLING = this.create("potted_saccharine_sapling", BlocksInvoker.createFlowerPotBlock(SACCHARINE_SAPLING))
|
||||
|
||||
@JvmField
|
||||
val RED_MINT_LEAF_LITTER = this.mintLitter("red_mint_leaf_litter", MapColor.COLOR_RED)
|
||||
@JvmField
|
||||
val POTTED_RED_MINT = this.pottedPlant("potted_red_mint", RED_MINT_LEAF_LITTER)
|
||||
@JvmField
|
||||
val BLUE_MINT_LEAF_LITTER = this.mintLitter("blue_mint_leaf_litter", MapColor.COLOR_BLUE)
|
||||
@JvmField
|
||||
val POTTED_BLUE_MINT = this.pottedPlant("potted_blue_mint", BLUE_MINT_LEAF_LITTER)
|
||||
@JvmField
|
||||
val CYAN_MINT_LEAF_LITTER = this.mintLitter("cyan_mint_leaf_litter", MapColor.COLOR_CYAN)
|
||||
@JvmField
|
||||
val POTTED_CYAN_MINT = this.pottedPlant("potted_cyan_mint", CYAN_MINT_LEAF_LITTER)
|
||||
@JvmField
|
||||
val PINK_MINT_LEAF_LITTER = this.mintLitter("pink_mint_leaf_litter", MapColor.COLOR_PINK)
|
||||
@JvmField
|
||||
val POTTED_PINK_MINT = this.pottedPlant("potted_pink_mint", PINK_MINT_LEAF_LITTER)
|
||||
@JvmField
|
||||
val GREEN_MINT_LEAF_LITTER = this.mintLitter("green_mint_leaf_litter", MapColor.COLOR_GREEN)
|
||||
@JvmField
|
||||
val POTTED_GREEN_MINT = this.pottedPlant("potted_green_mint", GREEN_MINT_LEAF_LITTER)
|
||||
@JvmField
|
||||
val WHITE_MINT_LEAF_LITTER = this.mintLitter("white_mint_leaf_litter", MapColor.SNOW)
|
||||
@JvmField
|
||||
val POTTED_WHITE_MINT = this.pottedPlant("potted_white_mint", WHITE_MINT_LEAF_LITTER)
|
||||
|
||||
@JvmField
|
||||
val LOBAILA = this.create("lobaila", LobailaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().instabreak().sound(SoundType.GRASS).offsetType(BlockBehaviour.OffsetType.XZ).ignitedByLava().pushReaction(PushReaction.DESTROY)))
|
||||
@JvmField
|
||||
val POMOHANA = this.create("pomohana", PomohanaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().instabreak().sound(SoundType.GRASS).ignitedByLava().pushReaction(PushReaction.DESTROY)))
|
||||
@JvmField
|
||||
val PAUVILLEA = this.create("pauvillea",PauvilleaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY)))
|
||||
@JvmField
|
||||
val WISTENSU = this.create("wistensu", WistensuFlowerHeadBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY).lightLevel{11}))
|
||||
@JvmField
|
||||
val WISTENSU_VINE = this.create("wistensu_vine", WistensuFlowerVineBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY)))
|
||||
|
||||
@JvmField
|
||||
val ELECTRIC_SEED = this.terrainSeed("electric_seed", TerrainSeedBlock.TerrainSeedType.ELECTRIC)
|
||||
@JvmField
|
||||
val PSYCHIC_SEED = this.terrainSeed("psychic_seed", TerrainSeedBlock.TerrainSeedType.PSYCHIC)
|
||||
@JvmField
|
||||
val MISTY_SEED = this.terrainSeed("misty_seed", TerrainSeedBlock.TerrainSeedType.MISTY)
|
||||
@JvmField
|
||||
val GRASSY_SEED = this.terrainSeed("grassy_seed", TerrainSeedBlock.TerrainSeedType.GRASSY)
|
||||
@JvmField
|
||||
val VOLTACONIA = this.terrainPlant("voltaconia") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 8 else 0 }
|
||||
@JvmField
|
||||
val VERDROOT = this.terrainPlant("verdroot") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 5 else 0 }
|
||||
@JvmField
|
||||
val LUCIBELL = this.terrainPlant("lucibell") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 8 else 0 }
|
||||
@JvmField
|
||||
val MISTLEAF = this.create(
|
||||
"mistleaf",
|
||||
MistleafFlowerBlock {
|
||||
if ((it.getValue(MistleafFlowerBlock.GLOWING) >= 1)) it.getValue(MistleafFlowerBlock.GLOWING) + 2 else 0
|
||||
}
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val HEARTY_GRAIN_BALE = this.create("hearty_grain_bale", BaleBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).instrument(NoteBlockInstrument.BANJO).strength(0.5F).sound(CobblemonSounds.HEARTY_GRAIN_BALE_SOUNDS)))
|
||||
@JvmField
|
||||
@@ -931,6 +1103,147 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val YACHE_BERRY = this.berryBlock("yache")
|
||||
//val BINDING_SOIL = this.create("binding_soil", BindingSoilBlock(BlockBehaviour.Properties.of()))
|
||||
|
||||
@JvmField
|
||||
val POTTED_AGUAV_BERRY = this.create("potted_aguav_berry", BlocksInvoker.createFlowerPotBlock(AGUAV_BERRY))
|
||||
@JvmField
|
||||
val POTTED_APICOT_BERRY = this.create("potted_apicot_berry", BlocksInvoker.createFlowerPotBlock(APICOT_BERRY))
|
||||
@JvmField
|
||||
val POTTED_ASPEAR_BERRY = this.create("potted_aspear_berry", BlocksInvoker.createFlowerPotBlock(ASPEAR_BERRY))
|
||||
@JvmField
|
||||
val POTTED_BABIRI_BERRY = this.create("potted_babiri_berry", BlocksInvoker.createFlowerPotBlock(BABIRI_BERRY))
|
||||
@JvmField
|
||||
val POTTED_BELUE_BERRY = this.create("potted_belue_berry", BlocksInvoker.createFlowerPotBlock(BELUE_BERRY))
|
||||
@JvmField
|
||||
val POTTED_BLUK_BERRY = this.create("potted_bluk_berry", BlocksInvoker.createFlowerPotBlock(BLUK_BERRY))
|
||||
@JvmField
|
||||
val POTTED_CHARTI_BERRY = this.create("potted_charti_berry", BlocksInvoker.createFlowerPotBlock(CHARTI_BERRY))
|
||||
@JvmField
|
||||
val POTTED_CHERI_BERRY = this.create("potted_cheri_berry", BlocksInvoker.createFlowerPotBlock(CHERI_BERRY))
|
||||
@JvmField
|
||||
val POTTED_CHESTO_BERRY = this.create("potted_chesto_berry", BlocksInvoker.createFlowerPotBlock(CHESTO_BERRY))
|
||||
@JvmField
|
||||
val POTTED_CHILAN_BERRY = this.create("potted_chilan_berry", BlocksInvoker.createFlowerPotBlock(CHILAN_BERRY))
|
||||
@JvmField
|
||||
val POTTED_CHOPLE_BERRY = this.create("potted_chople_berry", BlocksInvoker.createFlowerPotBlock(CHOPLE_BERRY))
|
||||
@JvmField
|
||||
val POTTED_COBA_BERRY = this.create("potted_coba_berry", BlocksInvoker.createFlowerPotBlock(COBA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_COLBUR_BERRY = this.create("potted_colbur_berry", BlocksInvoker.createFlowerPotBlock(COLBUR_BERRY))
|
||||
@JvmField
|
||||
val POTTED_CORNN_BERRY = this.create("potted_cornn_berry", BlocksInvoker.createFlowerPotBlock(CORNN_BERRY))
|
||||
@JvmField
|
||||
val POTTED_CUSTAP_BERRY = this.create("potted_custap_berry", BlocksInvoker.createFlowerPotBlock(CUSTAP_BERRY))
|
||||
@JvmField
|
||||
val POTTED_DURIN_BERRY = this.create("potted_durin_berry", BlocksInvoker.createFlowerPotBlock(DURIN_BERRY))
|
||||
@JvmField
|
||||
val POTTED_EGGANT_BERRY = this.create("potted_eggant_berry", BlocksInvoker.createFlowerPotBlock(EGGANT_BERRY))
|
||||
@JvmField
|
||||
val POTTED_ENIGMA_BERRY = this.create("potted_enigma_berry", BlocksInvoker.createFlowerPotBlock(ENIGMA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_FIGY_BERRY = this.create("potted_figy_berry", BlocksInvoker.createFlowerPotBlock(FIGY_BERRY))
|
||||
@JvmField
|
||||
val POTTED_GANLON_BERRY = this.create("potted_ganlon_berry", BlocksInvoker.createFlowerPotBlock(GANLON_BERRY))
|
||||
@JvmField
|
||||
val POTTED_GREPA_BERRY = this.create("potted_grepa_berry", BlocksInvoker.createFlowerPotBlock(GREPA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_HABAN_BERRY = this.create("potted_haban_berry", BlocksInvoker.createFlowerPotBlock(HABAN_BERRY))
|
||||
@JvmField
|
||||
val POTTED_HONDEW_BERRY = this.create("potted_hondew_berry", BlocksInvoker.createFlowerPotBlock(HONDEW_BERRY))
|
||||
@JvmField
|
||||
val POTTED_HOPO_BERRY = this.create("potted_hopo_berry", BlocksInvoker.createFlowerPotBlock(HOPO_BERRY))
|
||||
@JvmField
|
||||
val POTTED_IAPAPA_BERRY = this.create("potted_iapapa_berry", BlocksInvoker.createFlowerPotBlock(IAPAPA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_JABOCA_BERRY = this.create("potted_jaboca_berry", BlocksInvoker.createFlowerPotBlock(JABOCA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_KASIB_BERRY = this.create("potted_kasib_berry", BlocksInvoker.createFlowerPotBlock(KASIB_BERRY))
|
||||
@JvmField
|
||||
val POTTED_KEBIA_BERRY = this.create("potted_kebia_berry", BlocksInvoker.createFlowerPotBlock(KEBIA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_KEE_BERRY = this.create("potted_kee_berry", BlocksInvoker.createFlowerPotBlock(KEE_BERRY))
|
||||
@JvmField
|
||||
val POTTED_KELPSY_BERRY = this.create("potted_kelpsy_berry", BlocksInvoker.createFlowerPotBlock(KELPSY_BERRY))
|
||||
@JvmField
|
||||
val POTTED_LANSAT_BERRY = this.create("potted_lansat_berry", BlocksInvoker.createFlowerPotBlock(LANSAT_BERRY))
|
||||
@JvmField
|
||||
val POTTED_LEPPA_BERRY = this.create("potted_leppa_berry", BlocksInvoker.createFlowerPotBlock(LEPPA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_LIECHI_BERRY = this.create("potted_liechi_berry", BlocksInvoker.createFlowerPotBlock(LIECHI_BERRY))
|
||||
@JvmField
|
||||
val POTTED_LUM_BERRY = this.create("potted_lum_berry", BlocksInvoker.createFlowerPotBlock(LUM_BERRY))
|
||||
@JvmField
|
||||
val POTTED_MAGO_BERRY = this.create("potted_mago_berry", BlocksInvoker.createFlowerPotBlock(MAGO_BERRY))
|
||||
@JvmField
|
||||
val POTTED_MAGOST_BERRY = this.create("potted_magost_berry", BlocksInvoker.createFlowerPotBlock(MAGOST_BERRY))
|
||||
@JvmField
|
||||
val POTTED_MARANGA_BERRY = this.create("potted_maranga_berry", BlocksInvoker.createFlowerPotBlock(MARANGA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_MICLE_BERRY = this.create("potted_micle_berry", BlocksInvoker.createFlowerPotBlock(MICLE_BERRY))
|
||||
@JvmField
|
||||
val POTTED_NANAB_BERRY = this.create("potted_nanab_berry", BlocksInvoker.createFlowerPotBlock(NANAB_BERRY))
|
||||
@JvmField
|
||||
val POTTED_NOMEL_BERRY = this.create("potted_nomel_berry", BlocksInvoker.createFlowerPotBlock(NOMEL_BERRY))
|
||||
@JvmField
|
||||
val POTTED_OCCA_BERRY = this.create("potted_occa_berry", BlocksInvoker.createFlowerPotBlock(OCCA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_ORAN_BERRY = this.create("potted_oran_berry", BlocksInvoker.createFlowerPotBlock(ORAN_BERRY))
|
||||
@JvmField
|
||||
val POTTED_PAMTRE_BERRY = this.create("potted_pamtre_berry", BlocksInvoker.createFlowerPotBlock(PAMTRE_BERRY))
|
||||
@JvmField
|
||||
val POTTED_PASSHO_BERRY = this.create("potted_passho_berry", BlocksInvoker.createFlowerPotBlock(PASSHO_BERRY))
|
||||
@JvmField
|
||||
val POTTED_PAYAPA_BERRY = this.create("potted_payapa_berry", BlocksInvoker.createFlowerPotBlock(PAYAPA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_PECHA_BERRY = this.create("potted_pecha_berry", BlocksInvoker.createFlowerPotBlock(PECHA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_PERSIM_BERRY = this.create("potted_persim_berry", BlocksInvoker.createFlowerPotBlock(PERSIM_BERRY))
|
||||
@JvmField
|
||||
val POTTED_PETAYA_BERRY = this.create("potted_petaya_berry", BlocksInvoker.createFlowerPotBlock(PETAYA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_PINAP_BERRY = this.create("potted_pinap_berry", BlocksInvoker.createFlowerPotBlock(PINAP_BERRY))
|
||||
@JvmField
|
||||
val POTTED_POMEG_BERRY = this.create("potted_pomeg_berry", BlocksInvoker.createFlowerPotBlock(POMEG_BERRY))
|
||||
@JvmField
|
||||
val POTTED_QUALOT_BERRY = this.create("potted_qualot_berry", BlocksInvoker.createFlowerPotBlock(QUALOT_BERRY))
|
||||
@JvmField
|
||||
val POTTED_RABUTA_BERRY = this.create("potted_rabuta_berry", BlocksInvoker.createFlowerPotBlock(RABUTA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_RAWST_BERRY = this.create("potted_rawst_berry", BlocksInvoker.createFlowerPotBlock(RAWST_BERRY))
|
||||
@JvmField
|
||||
val POTTED_RAZZ_BERRY = this.create("potted_razz_berry", BlocksInvoker.createFlowerPotBlock(RAZZ_BERRY))
|
||||
@JvmField
|
||||
val POTTED_RINDO_BERRY = this.create("potted_rindo_berry", BlocksInvoker.createFlowerPotBlock(RINDO_BERRY))
|
||||
@JvmField
|
||||
val POTTED_ROSELI_BERRY = this.create("potted_roseli_berry", BlocksInvoker.createFlowerPotBlock(ROSELI_BERRY))
|
||||
@JvmField
|
||||
val POTTED_ROWAP_BERRY = this.create("potted_rowap_berry", BlocksInvoker.createFlowerPotBlock(ROWAP_BERRY))
|
||||
@JvmField
|
||||
val POTTED_SALAC_BERRY = this.create("potted_salac_berry", BlocksInvoker.createFlowerPotBlock(SALAC_BERRY))
|
||||
@JvmField
|
||||
val POTTED_SHUCA_BERRY = this.create("potted_shuca_berry", BlocksInvoker.createFlowerPotBlock(SHUCA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_SITRUS_BERRY = this.create("potted_sitrus_berry", BlocksInvoker.createFlowerPotBlock(SITRUS_BERRY))
|
||||
@JvmField
|
||||
val POTTED_SPELON_BERRY = this.create("potted_spelon_berry", BlocksInvoker.createFlowerPotBlock(SPELON_BERRY))
|
||||
@JvmField
|
||||
val POTTED_STARF_BERRY = this.create("potted_starf_berry", BlocksInvoker.createFlowerPotBlock(STARF_BERRY))
|
||||
@JvmField
|
||||
val POTTED_TAMATO_BERRY = this.create("potted_tamato_berry", BlocksInvoker.createFlowerPotBlock(TAMATO_BERRY))
|
||||
@JvmField
|
||||
val POTTED_TANGA_BERRY = this.create("potted_tanga_berry", BlocksInvoker.createFlowerPotBlock(TANGA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_TOUGA_BERRY = this.create("potted_touga_berry", BlocksInvoker.createFlowerPotBlock(TOUGA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_WACAN_BERRY = this.create("potted_wacan_berry", BlocksInvoker.createFlowerPotBlock(WACAN_BERRY))
|
||||
@JvmField
|
||||
val POTTED_WATMEL_BERRY = this.create("potted_watmel_berry", BlocksInvoker.createFlowerPotBlock(WATMEL_BERRY))
|
||||
@JvmField
|
||||
val POTTED_WEPEAR_BERRY = this.create("potted_wepear_berry", BlocksInvoker.createFlowerPotBlock(WEPEAR_BERRY))
|
||||
@JvmField
|
||||
val POTTED_WIKI_BERRY = this.create("potted_wiki_berry", BlocksInvoker.createFlowerPotBlock(WIKI_BERRY))
|
||||
@JvmField
|
||||
val POTTED_YACHE_BERRY = this.create("potted_yache_berry", BlocksInvoker.createFlowerPotBlock(YACHE_BERRY))
|
||||
|
||||
init {
|
||||
/**
|
||||
* Makes all blocks in array flammable by adding them to FireBlock's flammableRegistry.
|
||||
@@ -1032,4 +1345,48 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
}
|
||||
|
||||
fun typeGemClusters() = typeGemClusters.toMap()
|
||||
|
||||
private fun pottedPlant(name: String, flower: Block) = this.create(name, BlocksInvoker.createFlowerPotBlock(flower))
|
||||
|
||||
private fun mintLitter(name: String, color: MapColor) = this.create(
|
||||
name,
|
||||
MintLitterBlock(
|
||||
BlockBehaviour.Properties.of()
|
||||
.sound(CobblemonSounds.MINT_SOUNDS)
|
||||
.mapColor(color)
|
||||
.noOcclusion()
|
||||
.noCollission()
|
||||
.pushReaction(PushReaction.DESTROY)
|
||||
)
|
||||
)
|
||||
|
||||
private fun terrainSeed(name: String, type: TerrainSeedBlock.TerrainSeedType) = this.create(
|
||||
name,
|
||||
TerrainSeedBlock(
|
||||
type,
|
||||
BlockBehaviour.Properties.of()
|
||||
.pushReaction(PushReaction.DESTROY)
|
||||
.ignitedByLava()
|
||||
.mapColor(MapColor.PLANT)
|
||||
.offsetType(BlockBehaviour.OffsetType.XZ)
|
||||
.noCollission()
|
||||
.randomTicks()
|
||||
.instabreak()
|
||||
)
|
||||
)
|
||||
|
||||
private fun terrainPlant(name: String, lightLevel: (BlockState) -> Int) = this.create(
|
||||
name,
|
||||
TerrainPlantBlock(
|
||||
BlockBehaviour.Properties.of()
|
||||
.mapColor(MapColor.PLANT)
|
||||
.noCollission()
|
||||
.instabreak()
|
||||
.sound(SoundType.GRASS)
|
||||
.offsetType(BlockBehaviour.OffsetType.XZ)
|
||||
.ignitedByLava()
|
||||
.pushReaction(PushReaction.DESTROY)
|
||||
.lightLevel(lightLevel)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.mojang.brigadier.arguments.ArgumentType
|
||||
import kotlin.reflect.KClass
|
||||
import net.minecraft.commands.synchronization.ArgumentTypeInfo
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.resources.ResourceKey
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.MinecraftServer
|
||||
@@ -168,12 +169,16 @@ interface CobblemonImplementation {
|
||||
* TODO
|
||||
*
|
||||
* @param identifier
|
||||
* @param reloader
|
||||
* @param reloaderFactory
|
||||
* @param type
|
||||
* @param dependencies
|
||||
*/
|
||||
fun registerResourceReloader(identifier: ResourceLocation, reloader: PreparableReloadListener, type: PackType, dependencies: Collection<ResourceLocation>)
|
||||
|
||||
fun registerResourceReloader(
|
||||
identifier: ResourceLocation,
|
||||
type: PackType,
|
||||
dependencies: Collection<ResourceLocation>,
|
||||
reloaderFactory: (HolderLookup.Provider) -> PreparableReloadListener
|
||||
)
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
|
||||
@@ -472,18 +472,18 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
// Evolution items
|
||||
@JvmField val LINK_CABLE = create("link_cable", LinkCableItem())
|
||||
@JvmField val DRAGON_SCALE = noSettingsItem("dragon_scale")
|
||||
@JvmField val METAL_COAT = noSettingsItem("metal_coat")
|
||||
@JvmField val UPGRADE = noSettingsItem("upgrade")
|
||||
@JvmField val DUBIOUS_DISC = noSettingsItem("dubious_disc")
|
||||
@JvmField val METAL_COAT = itemNameBlockItem("metal_coat", CobblemonBlocks.METAL_COAT)
|
||||
@JvmField val UPGRADE = itemNameBlockItem("upgrade", CobblemonBlocks.UPGRADE)
|
||||
@JvmField val DUBIOUS_DISC = itemNameBlockItem("dubious_disc", CobblemonBlocks.DUBIOUS_DISC)
|
||||
@JvmField val DEEP_SEA_SCALE = noSettingsItem("deep_sea_scale")
|
||||
@JvmField val DEEP_SEA_TOOTH = noSettingsItem("deep_sea_tooth")
|
||||
@JvmField val ELECTIRIZER = noSettingsItem("electirizer")
|
||||
@JvmField val MAGMARIZER = noSettingsItem("magmarizer")
|
||||
@JvmField val ELECTIRIZER = itemNameBlockItem("electirizer", CobblemonBlocks.ELECTIRIZER)
|
||||
@JvmField val MAGMARIZER = itemNameBlockItem("magmarizer", CobblemonBlocks.MAGMARIZER)
|
||||
@JvmField val OVAL_STONE = noSettingsItem("oval_stone")
|
||||
@JvmField val PROTECTOR = noSettingsItem("protector")
|
||||
@JvmField val REAPER_CLOTH = noSettingsItem("reaper_cloth")
|
||||
@JvmField val PRISM_SCALE = noSettingsItem("prism_scale")
|
||||
@JvmField val SACHET = noSettingsItem("sachet")
|
||||
@JvmField val SACHET = itemNameBlockItem("sachet", CobblemonBlocks.SACHET)
|
||||
@JvmField val WHIPPED_DREAM = create("whipped_dream", foodItem(8, 0.3f))
|
||||
@JvmField val STRAWBERRY_SWEET = create("strawberry_sweet", foodItem(6, 0.125f))
|
||||
@JvmField val LOVE_SWEET = create("love_sweet", foodItem(6, 0.125f))
|
||||
@@ -492,22 +492,20 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField val FLOWER_SWEET = create("flower_sweet", foodItem(6, 0.125f))
|
||||
@JvmField val STAR_SWEET = create("star_sweet", foodItem(6, 0.125f))
|
||||
@JvmField val RIBBON_SWEET = create("ribbon_sweet", foodItem(6, 0.125f))
|
||||
@JvmField val CHIPPED_POT = noSettingsItem("chipped_pot")
|
||||
@JvmField val CRACKED_POT = noSettingsItem("cracked_pot")
|
||||
@JvmField val MASTERPIECE_TEACUP = noSettingsItem("masterpiece_teacup")
|
||||
@JvmField val UNREMARKABLE_TEACUP = noSettingsItem("unremarkable_teacup")
|
||||
@JvmField val CHIPPED_POT = itemNameBlockItem("chipped_pot", CobblemonBlocks.CHIPPED_POT)
|
||||
@JvmField val CRACKED_POT = itemNameBlockItem("cracked_pot", CobblemonBlocks.CRACKED_POT)
|
||||
@JvmField val MASTERPIECE_TEACUP = itemNameBlockItem("masterpiece_teacup", CobblemonBlocks.MASTERPIECE_TEACUP)
|
||||
@JvmField val UNREMARKABLE_TEACUP = itemNameBlockItem("unremarkable_teacup", CobblemonBlocks.UNREMARKABLE_TEACUP)
|
||||
@JvmField val SWEET_APPLE = compostableItem("sweet_apple", foodItem(4, 0.3f), 0.65f)
|
||||
@JvmField val TART_APPLE = compostableItem("tart_apple", foodItem(4, 0.3f), 0.65f)
|
||||
@JvmField val SYRUPY_APPLE = compostableItem("syrupy_apple", foodItem(4, 0.3f), 0.65f)
|
||||
@JvmField val GALARICA_CUFF = noSettingsItem("galarica_cuff")
|
||||
@JvmField val GALARICA_WREATH = noSettingsItem("galarica_wreath")
|
||||
@JvmField val GALARICA_CUFF = itemNameBlockItem("galarica_cuff", CobblemonBlocks.GALARICA_CUFF)
|
||||
@JvmField val BLACK_AUGURITE = noSettingsItem("black_augurite")
|
||||
@JvmField val PEAT_BLOCK = noSettingsItem("peat_block")
|
||||
@JvmField val RAZOR_CLAW = noSettingsItem("razor_claw")
|
||||
@JvmField val RAZOR_FANG = noSettingsItem("razor_fang")
|
||||
@JvmField val AUSPICIOUS_ARMOR = heldItem("auspicious_armor")
|
||||
@JvmField val MALICIOUS_ARMOR = heldItem("malicious_armor")
|
||||
@JvmField val SHELL_HELMET = heldItem("shell_helmet")
|
||||
@JvmField val METAL_ALLOY = noSettingsItem("metal_alloy")
|
||||
@JvmField val SCROLL_OF_DARKNESS = noSettingsItem("scroll_of_darkness")
|
||||
@JvmField val SCROLL_OF_WATERS = noSettingsItem("scroll_of_waters")
|
||||
@@ -651,17 +649,17 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val EXPERIENCE_CANDY_XL = candyItem("exp_candy_xl") { _, _ -> CandyItem.DEFAULT_XL_CANDY_YIELD }
|
||||
@JvmField
|
||||
val CALCIUM = create("calcium", VitaminItem(Stats.SPECIAL_ATTACK))
|
||||
val CALCIUM = create("calcium", VitaminItem(Stats.SPECIAL_ATTACK, CobblemonBlocks.CALCIUM))
|
||||
@JvmField
|
||||
val CARBOS = create("carbos", VitaminItem(Stats.SPEED))
|
||||
val CARBOS = create("carbos", VitaminItem(Stats.SPEED, CobblemonBlocks.CARBOS))
|
||||
@JvmField
|
||||
val HP_UP = create("hp_up", VitaminItem(Stats.HP))
|
||||
val HP_UP = create("hp_up", VitaminItem(Stats.HP, CobblemonBlocks.HP_UP))
|
||||
@JvmField
|
||||
val IRON = create("iron", VitaminItem(Stats.DEFENCE))
|
||||
val IRON = create("iron", VitaminItem(Stats.DEFENCE, CobblemonBlocks.IRON))
|
||||
@JvmField
|
||||
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK))
|
||||
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK, CobblemonBlocks.PROTEIN))
|
||||
@JvmField
|
||||
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE))
|
||||
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE, CobblemonBlocks.ZINC))
|
||||
@JvmField
|
||||
val HEALTH_MOCHI = create("health_mochi", MochiItem(Stats.HP))
|
||||
@JvmField
|
||||
@@ -713,7 +711,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val PEP_UP_FLOWER = compostableBlockItem("pep_up_flower", CobblemonBlocks.PEP_UP_FLOWER)
|
||||
@JvmField
|
||||
val MEDICINAL_BREW = noSettingsItem("medicinal_brew")
|
||||
val MEDICINAL_BREW = itemNameBlockItem("medicinal_brew", CobblemonBlocks.MEDICINAL_BREW)
|
||||
@JvmField
|
||||
val REMEDY = compostableItem("remedy", RemedyItem(RemedyItem.NORMAL), 0.65f)
|
||||
@JvmField
|
||||
@@ -721,7 +719,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val SUPERB_REMEDY = compostableItem("superb_remedy", RemedyItem(RemedyItem.SUPERB), 1f)
|
||||
@JvmField
|
||||
val MOOMOO_MILK = create("moomoo_milk", MoomooMilk(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16))))
|
||||
val MOOMOO_MILK = create("moomoo_milk", MoomooMilk(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16)), CobblemonBlocks.MOOMOO_MILK))
|
||||
|
||||
@JvmField
|
||||
val POTION = create("potion", PotionItem(PotionType.POTION, CobblemonBlocks.POTION))
|
||||
@@ -743,34 +741,34 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val MAX_REVIVE = create("max_revive", ReviveItem(max = true))
|
||||
@JvmField
|
||||
val PP_UP = create("pp_up", PPUpItem(1))
|
||||
val PP_UP = create("pp_up", PPUpItem(1, CobblemonBlocks.PP_UP))
|
||||
@JvmField
|
||||
val PP_MAX = create("pp_max", PPUpItem(3))
|
||||
val PP_MAX = create("pp_max", PPUpItem(3, CobblemonBlocks.PP_MAX))
|
||||
|
||||
@JvmField
|
||||
val RED_MINT_SEEDS = mintSeed("red", MintType.RED.getCropBlock())
|
||||
@JvmField
|
||||
val RED_MINT_LEAF = mintLeaf("red", MintLeafItem(MintType.RED))
|
||||
val RED_MINT_LEAF = compostableItemNameBlockItem("red_mint_leaf", CobblemonBlocks.RED_MINT_LEAF_LITTER, 0.50f)
|
||||
@JvmField
|
||||
val BLUE_MINT_SEEDS = mintSeed("blue", MintType.BLUE.getCropBlock())
|
||||
@JvmField
|
||||
val BLUE_MINT_LEAF = mintLeaf("blue", MintLeafItem(MintType.BLUE))
|
||||
val BLUE_MINT_LEAF = compostableItemNameBlockItem("blue_mint_leaf", CobblemonBlocks.BLUE_MINT_LEAF_LITTER, 0.50f)
|
||||
@JvmField
|
||||
val CYAN_MINT_SEEDS = mintSeed("cyan", MintType.CYAN.getCropBlock())
|
||||
@JvmField
|
||||
val CYAN_MINT_LEAF = mintLeaf("cyan", MintLeafItem(MintType.CYAN))
|
||||
val CYAN_MINT_LEAF = compostableItemNameBlockItem("cyan_mint_leaf", CobblemonBlocks.CYAN_MINT_LEAF_LITTER, 0.50f)
|
||||
@JvmField
|
||||
val PINK_MINT_SEEDS = mintSeed("pink", MintType.PINK.getCropBlock())
|
||||
@JvmField
|
||||
val PINK_MINT_LEAF = mintLeaf("pink", MintLeafItem(MintType.PINK))
|
||||
val PINK_MINT_LEAF = compostableItemNameBlockItem("pink_mint_leaf", CobblemonBlocks.PINK_MINT_LEAF_LITTER, 0.50f)
|
||||
@JvmField
|
||||
val GREEN_MINT_SEEDS = mintSeed("green", MintType.GREEN.getCropBlock())
|
||||
@JvmField
|
||||
val GREEN_MINT_LEAF = mintLeaf("green", MintLeafItem(MintType.GREEN))
|
||||
val GREEN_MINT_LEAF = compostableItemNameBlockItem("green_mint_leaf", CobblemonBlocks.GREEN_MINT_LEAF_LITTER, 0.50f)
|
||||
@JvmField
|
||||
val WHITE_MINT_SEEDS = mintSeed("white", MintType.WHITE.getCropBlock())
|
||||
@JvmField
|
||||
val WHITE_MINT_LEAF = mintLeaf("white", MintLeafItem(MintType.WHITE))
|
||||
val WHITE_MINT_LEAF = compostableItemNameBlockItem("white_mint_leaf", CobblemonBlocks.WHITE_MINT_LEAF_LITTER, 0.50f)
|
||||
|
||||
val mints = mutableMapOf<String, MintItem>()
|
||||
|
||||
@@ -922,11 +920,18 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField val CHOICE_SPECS = wearableItem("choice_specs")
|
||||
@JvmField val EXP_SHARE = wearableItem("exp_share")
|
||||
@JvmField val FOCUS_BAND = wearableItem("focus_band")
|
||||
@JvmField val KINGS_ROCK = wearableItem("kings_rock")
|
||||
@JvmField val KINGS_ROCK = wearableBlockItem("kings_rock", CobblemonBlocks.KINGS_ROCK)
|
||||
@JvmField val NEVER_MELT_ICE = wearableBlockItem("never_melt_ice", CobblemonBlocks.NEVER_MELT_ICE)
|
||||
@JvmField val MUSCLE_BAND = wearableItem("muscle_band")
|
||||
@JvmField val ROCKY_HELMET = wearableItem("rocky_helmet")
|
||||
@JvmField val SAFETY_GOGGLES = wearableItem("safety_goggles")
|
||||
@JvmField val WISE_GLASSES = wearableItem("wise_glasses")
|
||||
@JvmField val SHARP_BEAK = wearableItem("sharp_beak")
|
||||
@JvmField val COVERT_CLOAK = wearableItem("covert_cloak")
|
||||
@JvmField val SHELL_HELMET = wearableItem("shell_helmet")
|
||||
@JvmField val GALARICA_WREATH = wearableBlockItem("galarica_wreath", CobblemonBlocks.GALARICA_WREATH)
|
||||
@JvmField val CLEANSE_TAG = wearableBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
|
||||
@JvmField val SPELL_TAG = wearableBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
|
||||
|
||||
// Held Items
|
||||
@JvmField
|
||||
@@ -954,21 +959,17 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val CHOICE_SCARF = heldItem("choice_scarf")
|
||||
@JvmField
|
||||
val CLEANSE_TAG = itemNameBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
|
||||
@JvmField
|
||||
val CLEAR_AMULET = heldItem("clear_amulet")
|
||||
@JvmField
|
||||
val COVERT_CLOAK = heldItem("covert_cloak")
|
||||
@JvmField
|
||||
val DESTINY_KNOT = heldItem("destiny_knot")
|
||||
val DESTINY_KNOT = itemNameBlockItem("destiny_knot", CobblemonBlocks.DESTINY_KNOT)
|
||||
@JvmField
|
||||
val DRAGON_FANG = heldItem("dragon_fang")
|
||||
@JvmField
|
||||
val EJECT_BUTTON = heldItem("eject_button")
|
||||
val EJECT_BUTTON = itemNameBlockItem("eject_button", CobblemonBlocks.EJECT_BUTTON)
|
||||
@JvmField
|
||||
val EJECT_PACK = heldItem("eject_pack")
|
||||
@JvmField
|
||||
val ELECTRIC_SEED = heldItem("electric_seed")
|
||||
val ELECTRIC_SEED = itemNameBlockItem("electric_seed", CobblemonBlocks.ELECTRIC_SEED)
|
||||
@JvmField
|
||||
val EVERSTONE = heldItem("everstone")
|
||||
@JvmField
|
||||
@@ -978,13 +979,13 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val FAIRY_FEATHER = heldItem("fairy_feather")
|
||||
@JvmField
|
||||
val FLAME_ORB = heldItem("flame_orb")
|
||||
val FLAME_ORB = itemNameBlockItem("flame_orb", CobblemonBlocks.FLAME_ORB)
|
||||
@JvmField
|
||||
val FLOAT_STONE = heldItem("float_stone")
|
||||
@JvmField
|
||||
val FOCUS_SASH = heldItem("focus_sash")
|
||||
@JvmField
|
||||
val GRASSY_SEED = heldItem("grassy_seed")
|
||||
val GRASSY_SEED = itemNameBlockItem("grassy_seed", CobblemonBlocks.GRASSY_SEED)
|
||||
@JvmField
|
||||
val GRIP_CLAW = heldItem("grip_claw")
|
||||
@JvmField
|
||||
@@ -992,34 +993,32 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val HEAVY_DUTY_BOOTS = heldItem("heavy_duty_boots")
|
||||
@JvmField
|
||||
val IRON_BALL = heldItem("iron_ball")
|
||||
val IRON_BALL = itemNameBlockItem("iron_ball", CobblemonBlocks.IRON_BALL)
|
||||
@JvmField
|
||||
val LAGGING_TAIL = heldItem("lagging_tail")
|
||||
@JvmField
|
||||
val LEFTOVERS = compostableHeldItem("leftovers", null, 0.50f)
|
||||
@JvmField
|
||||
val LIFE_ORB = heldItem("life_orb")
|
||||
val LIFE_ORB = itemNameBlockItem("life_orb", CobblemonBlocks.LIFE_ORB)
|
||||
@JvmField
|
||||
val LIGHT_BALL = heldItem("light_ball")
|
||||
val LIGHT_BALL = itemNameBlockItem("light_ball", CobblemonBlocks.LIGHT_BALL)
|
||||
@JvmField
|
||||
val LIGHT_CLAY = heldItem("light_clay")
|
||||
@JvmField
|
||||
val LOADED_DICE = heldItem("loaded_dice")
|
||||
val LOADED_DICE = itemNameBlockItem("loaded_dice", CobblemonBlocks.LOADED_DICE)
|
||||
@JvmField
|
||||
val LUCKY_EGG = heldItem("lucky_egg")
|
||||
@JvmField
|
||||
val LUMINOUS_MOSS = heldItem("luminous_moss")
|
||||
val LUMINOUS_MOSS = itemNameBlockItem("luminous_moss", CobblemonBlocks.LUMINOUS_MOSS)
|
||||
@JvmField
|
||||
val MAGNET = heldItem("magnet")
|
||||
val MAGNET = itemNameBlockItem("magnet", CobblemonBlocks.MAGNET)
|
||||
@JvmField
|
||||
val METRONOME = heldItem("metronome")
|
||||
val METRONOME = itemNameBlockItem("metronome", CobblemonBlocks.METRONOME)
|
||||
@JvmField
|
||||
val MIRACLE_SEED = heldItem("miracle_seed")
|
||||
@JvmField
|
||||
val MYSTIC_WATER = heldItem("mystic_water")
|
||||
@JvmField
|
||||
val NEVER_MELT_ICE = heldItem("never_melt_ice")
|
||||
@JvmField
|
||||
val POISON_BARB = heldItem("poison_barb")
|
||||
@JvmField
|
||||
val POWER_ANKLET = heldItem("power_anklet")
|
||||
@@ -1034,7 +1033,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val POWER_WEIGHT = heldItem("power_weight")
|
||||
@JvmField
|
||||
val PSYCHIC_SEED = heldItem("psychic_seed")
|
||||
val PSYCHIC_SEED = itemNameBlockItem("psychic_seed", CobblemonBlocks.PSYCHIC_SEED)
|
||||
@JvmField
|
||||
val PROTECTIVE_PADS = heldItem("protective_pads")
|
||||
@JvmField
|
||||
@@ -1044,13 +1043,11 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val RED_CARD = heldItem("red_card")
|
||||
@JvmField
|
||||
val RING_TARGET = heldItem("ring_target")
|
||||
val RING_TARGET = itemNameBlockItem("ring_target", CobblemonBlocks.RING_TARGET)
|
||||
@JvmField
|
||||
val ROOM_SERVICE = heldItem("room_service")
|
||||
@JvmField
|
||||
val SCOPE_LENS = heldItem("scope_lens")
|
||||
@JvmField
|
||||
val SHARP_BEAK = heldItem("sharp_beak")
|
||||
val SCOPE_LENS = itemNameBlockItem("scope_lens", CobblemonBlocks.SCOPE_LENS)
|
||||
@JvmField
|
||||
val SHED_SHELL = heldItem("shed_shell")
|
||||
@JvmField
|
||||
@@ -1060,21 +1057,19 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val SILVER_POWDER = heldItem("silver_powder")
|
||||
@JvmField
|
||||
val SOFT_SAND = heldItem("soft_sand")
|
||||
val SOFT_SAND = itemNameBlockItem("soft_sand", CobblemonBlocks.SOFT_SAND)
|
||||
@JvmField
|
||||
val SPELL_TAG = itemNameBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
|
||||
@JvmField
|
||||
val SMOKE_BALL = heldItem("smoke_ball")
|
||||
val SMOKE_BALL = itemNameBlockItem("smoke_ball", CobblemonBlocks.SMOKE_BALL)
|
||||
@JvmField
|
||||
val SOOTHE_BELL = heldItem("soothe_bell")
|
||||
@JvmField
|
||||
val STICKY_BARB = heldItem("sticky_barb")
|
||||
@JvmField
|
||||
val TERRAIN_EXTENDER = heldItem("terrain_extender")
|
||||
val TERRAIN_EXTENDER = itemNameBlockItem("terrain_extender", CobblemonBlocks.TERRAIN_EXTENDER)
|
||||
@JvmField
|
||||
val THROAT_SPRAY = heldItem("throat_spray")
|
||||
val THROAT_SPRAY = itemNameBlockItem("throat_spray", CobblemonBlocks.THROAT_SPRAY)
|
||||
@JvmField
|
||||
val TOXIC_ORB = heldItem("toxic_orb")
|
||||
val TOXIC_ORB = itemNameBlockItem("toxic_orb", CobblemonBlocks.TOXIC_ORB)
|
||||
@JvmField
|
||||
val TWISTED_SPOON = heldItem("twisted_spoon")
|
||||
@JvmField
|
||||
@@ -1082,31 +1077,31 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val WEAKNESS_POLICY = itemNameBlockItem("weakness_policy", CobblemonBlocks.WEAKNESS_POLICY)
|
||||
@JvmField
|
||||
val WIDE_LENS = heldItem("wide_lens")
|
||||
val WIDE_LENS = itemNameBlockItem("wide_lens", CobblemonBlocks.WIDE_LENS)
|
||||
@JvmField
|
||||
val ZOOM_LENS = heldItem("zoom_lens")
|
||||
val ZOOM_LENS = itemNameBlockItem("zoom_lens", CobblemonBlocks.ZOOM_LENS)
|
||||
@JvmField
|
||||
val MENTAL_HERB = compostableHeldItem("mental_herb", null, 0.85F)
|
||||
@JvmField
|
||||
val MIRROR_HERB = compostableHeldItem("mirror_herb", null, 0.85F)
|
||||
@JvmField
|
||||
val MISTY_SEED = heldItem("misty_seed")
|
||||
val MISTY_SEED = itemNameBlockItem("misty_seed", CobblemonBlocks.MISTY_SEED)
|
||||
@JvmField
|
||||
val POWER_HERB = compostableHeldItem("power_herb", null, 0.85F)
|
||||
@JvmField
|
||||
val WHITE_HERB = compostableHeldItem("white_herb", null, 0.85F)
|
||||
@JvmField
|
||||
val BRIGHT_POWDER = heldItem("bright_powder")
|
||||
val BRIGHT_POWDER = glowItem("bright_powder", CobblemonBlocks.BRIGHT_POWDER)
|
||||
@JvmField
|
||||
val METAL_POWDER = heldItem("metal_powder")
|
||||
val METAL_POWDER = itemNameBlockItem("metal_powder", CobblemonBlocks.METAL_POWDER)
|
||||
@JvmField
|
||||
val QUICK_POWDER = heldItem("quick_powder")
|
||||
val QUICK_POWDER = itemNameBlockItem("quick_powder", CobblemonBlocks.QUICK_POWDER)
|
||||
@JvmField
|
||||
val DAMP_ROCK = heldItem("damp_rock")
|
||||
val DAMP_ROCK = itemNameBlockItem("damp_rock", CobblemonBlocks.DAMP_ROCK)
|
||||
@JvmField
|
||||
val HEAT_ROCK = heldItem("heat_rock")
|
||||
val HEAT_ROCK = itemNameBlockItem("heat_rock", CobblemonBlocks.HEAT_ROCK)
|
||||
@JvmField
|
||||
val SMOOTH_ROCK = heldItem("smooth_rock")
|
||||
val SMOOTH_ROCK = itemNameBlockItem("smooth_rock", CobblemonBlocks.SMOOTH_ROCK)
|
||||
@JvmField
|
||||
val ICY_ROCK = heldItem("icy_rock")
|
||||
|
||||
@@ -1563,6 +1558,25 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
//@JvmField
|
||||
//val BINDING_SOIL = blockItem("binding_soil", CobblemonBlocks.BINDING_SOIL)
|
||||
|
||||
// Plants, Flora, and Flowers
|
||||
@JvmField
|
||||
val LOBAILA = compostableBlockItem("lobaila", CobblemonBlocks.LOBAILA, 0.65f)
|
||||
@JvmField
|
||||
val POMOHANA = compostableBlockItem("pomohana", CobblemonBlocks.POMOHANA, 0.3f)
|
||||
@JvmField
|
||||
val PAUVILLEA = compostableBlockItem("pauvillea", CobblemonBlocks.PAUVILLEA, 0.65f)
|
||||
@JvmField
|
||||
val WISTENSU = compostableBlockItem("wistensu", CobblemonBlocks.WISTENSU, 0.5f)
|
||||
|
||||
@JvmField
|
||||
val VOLTACONIA = compostableBlockItem("voltaconia", CobblemonBlocks.VOLTACONIA, 0.85f)
|
||||
@JvmField
|
||||
val VERDROOT = compostableBlockItem("verdroot", CobblemonBlocks.VERDROOT, 0.85f)
|
||||
@JvmField
|
||||
val MISTLEAF = compostableBlockItem("mistleaf", CobblemonBlocks.MISTLEAF, 0.85f)
|
||||
@JvmField
|
||||
val LUCIBELL = compostableBlockItem("lucibell", CobblemonBlocks.LUCIBELL, 0.85f)
|
||||
|
||||
private fun blockItem(name: String, block: Block, rarity: Rarity = Rarity.COMMON): BlockItem = this.create(name, BlockItem(block, Item.Properties().rarity(rarity)))
|
||||
|
||||
private fun itemNameBlockItem(name: String, block: Block, rarity: Rarity = Rarity.COMMON): ItemNameBlockItem = this.create(name, ItemNameBlockItem(block, Item.Properties().rarity(rarity)))
|
||||
@@ -1599,7 +1613,16 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
|
||||
private fun wearableItem(name: String, heldItemRemappedName: String? = null): CobblemonItem = create(
|
||||
name,
|
||||
WearableItem(name).also {
|
||||
WearableHatItem(name).also {
|
||||
wearables.add(it)
|
||||
if (heldItemRemappedName != null) {
|
||||
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
|
||||
}
|
||||
}
|
||||
)
|
||||
private fun wearableBlockItem(name: String, block: Block, heldItemRemappedName: String? = null): WearableBlockItem = create(
|
||||
name,
|
||||
WearableBlockItem(name, block).also {
|
||||
wearables.add(it)
|
||||
if (heldItemRemappedName != null) {
|
||||
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
|
||||
@@ -1756,4 +1779,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
private fun foodItem(nutrition: Int, saturationModifier: Float): Item {
|
||||
return CobblemonItem(Item.Properties().food(FoodProperties.Builder().nutrition(nutrition).saturationModifier(saturationModifier).build()))
|
||||
}
|
||||
|
||||
private fun glowItem(name: String, block: Block): GlowItem = this.create(name, GlowItem(block, Item.Properties()))
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ 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.OpenSpeedTunerHandler
|
||||
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
|
||||
@@ -83,6 +84,7 @@ 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.OpenSpeedTunerPacket
|
||||
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
|
||||
@@ -173,6 +175,9 @@ import com.cobblemon.mod.common.net.messages.server.callback.party.PartySelectCa
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.partymove.PartyMoveSelectCancelledPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.partymove.PartyPokemonMoveSelectedPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.OpenRidingStatsDebugGUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.RequestOpenSpeedTunerPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.SetSpeedTunerSprintSpeedPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.SetSpeedTunerWalkSpeedPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidingSettingsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidingStatRangePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidingStatsPacket
|
||||
@@ -231,6 +236,9 @@ import com.cobblemon.mod.common.net.serverhandling.callback.party.PartySelectCan
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.partymove.PartyMoveSelectCancelledHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.partymove.PartyPokemonMoveSelectedHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.debug.RequestOpenRidingStatsDebugGUIHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.debug.RequestOpenSpeedTunerHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.debug.SetSpeedTunerSprintSpeedHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.debug.SetSpeedTunerWalkSpeedHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.debug.ServerboundUpdateRidingSettingsHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.debug.ServerboundUpdateRidingStatRangeHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.debug.ServerboundUpdateRidingStatsHandler
|
||||
@@ -425,6 +433,7 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(MovesRegistrySyncPacket.ID, MovesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(BerryRegistrySyncPacket.ID, BerryRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(SpeciesRegistrySyncPacket.ID, SpeciesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(SpeciesEvolutionSyncPacket.ID, SpeciesEvolutionSyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(PropertiesCompletionRegistrySyncPacket.ID, PropertiesCompletionRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(StandardSpeciesFeatureSyncPacket.ID, StandardSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(GlobalSpeciesFeatureSyncPacket.ID, GlobalSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
@@ -516,6 +525,7 @@ object CobblemonNetwork {
|
||||
|
||||
// Debug
|
||||
list.add(PacketRegisterInfo(OpenRidingStatsDebugGUIPacket.ID, OpenRidingStatsDebugGUIPacket::decode, OpenRidingStatsDebugGUIHandler))
|
||||
list.add(PacketRegisterInfo(OpenSpeedTunerPacket.ID, OpenSpeedTunerPacket::decode, OpenSpeedTunerHandler))
|
||||
|
||||
|
||||
return list
|
||||
@@ -648,6 +658,9 @@ object CobblemonNetwork {
|
||||
|
||||
// Debug / cheats
|
||||
list.add(PacketRegisterInfo(RequestOpenRidingStatsDebugGUIPacket.ID, RequestOpenRidingStatsDebugGUIPacket::decode, RequestOpenRidingStatsDebugGUIHandler))
|
||||
list.add(PacketRegisterInfo(RequestOpenSpeedTunerPacket.ID, RequestOpenSpeedTunerPacket::decode, RequestOpenSpeedTunerHandler))
|
||||
list.add(PacketRegisterInfo(SetSpeedTunerSprintSpeedPacket.ID, SetSpeedTunerSprintSpeedPacket::decode, SetSpeedTunerSprintSpeedHandler))
|
||||
list.add(PacketRegisterInfo(SetSpeedTunerWalkSpeedPacket.ID, SetSpeedTunerWalkSpeedPacket::decode, SetSpeedTunerWalkSpeedHandler))
|
||||
|
||||
|
||||
return list
|
||||
|
||||
@@ -148,6 +148,9 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
@JvmField
|
||||
val MONITOR_GLITCHING = this.create("block.monitor.glitching")
|
||||
|
||||
@JvmField
|
||||
val BRIGHT_POWDER_USE = this.create("block.bright_powder.use")
|
||||
|
||||
@JvmField
|
||||
val FOSSIL_MACHINE_ACTIVATE = this.create("block.fossil_machine.activate")
|
||||
@JvmField
|
||||
@@ -657,5 +660,65 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
SoundEvents.WOOL_FALL
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_POWDER_PLACE = this.create("block.item_block.powder.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_POWDER_BREAK = this.create("block.item_block.powder.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_POWDER_STEP = this.create("block.item_block.powder.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_POWDER_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_POWDER_BREAK,
|
||||
ITEM_BLOCK_POWDER_STEP,
|
||||
ITEM_BLOCK_POWDER_PLACE,
|
||||
ITEM_BLOCK_POWDER_STEP,
|
||||
ITEM_BLOCK_POWDER_STEP
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_LIQUID_PLACE = this.create("block.item_block.bottle_liquid.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_LIQUID_BREAK = this.create("block.item_block.bottle_liquid.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_LIQUID_STEP = this.create("block.item_block.bottle_liquid.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_BREAK,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_STEP,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_PLACE,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_PLACE,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_PLACE
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_PILLS_PLACE = this.create("block.item_block.bottle_pills.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_PILLS_BREAK = this.create("block.item_block.bottle_pills.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_PILLS_STEP = this.create("block.item_block.bottle_pills.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_PILLS_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_BREAK,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_STEP,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_PLACE,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_PLACE,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_PLACE
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_DICE_PLACE = this.create("block.item_block.dice.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_DICE_BREAK = this.create("block.item_block.dice.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_DICE_STEP = this.create("block.item_block.dice.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_DICE_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_DICE_BREAK,
|
||||
ITEM_BLOCK_DICE_STEP,
|
||||
ITEM_BLOCK_DICE_PLACE,
|
||||
ITEM_BLOCK_DICE_STEP,
|
||||
ITEM_BLOCK_DICE_STEP
|
||||
)
|
||||
|
||||
private fun create(name: String): SoundEvent = this.create(name, SoundEvent.createVariableRangeEvent(cobblemonResource(name)))
|
||||
}
|
||||
|
||||
+9
-3
@@ -22,17 +22,22 @@ import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
class MoveToOwnerTaskConfig : SingleTaskConfig {
|
||||
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
var completionRange: ExpressionOrEntityVariable = Either.left("4.0".asExpression())
|
||||
// Gap between the pokemon's hitbox and the owner's where it stops
|
||||
var completionRange: ExpressionOrEntityVariable = Either.left("2.0".asExpression())
|
||||
var speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
|
||||
var teleportDistance: ExpressionOrEntityVariable = Either.left("24.0".asExpression())
|
||||
var maxDistance: ExpressionOrEntityVariable = Either.left("14.0".asExpression())
|
||||
// Gap between the pokemon's hitbox and the owner's before it sprints to catch up. Past maxDistance so short
|
||||
// catch ups are walked
|
||||
var sprintDistance: ExpressionOrEntityVariable = Either.left("14.0".asExpression())
|
||||
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = listOf(
|
||||
condition,
|
||||
completionRange,
|
||||
speedMultiplier,
|
||||
teleportDistance,
|
||||
maxDistance
|
||||
maxDistance,
|
||||
sprintDistance
|
||||
).asVariables()
|
||||
|
||||
override fun createTask(
|
||||
@@ -49,7 +54,8 @@ class MoveToOwnerTaskConfig : SingleTaskConfig {
|
||||
completionRange = completionRange.asExpression(),
|
||||
speedMultiplier = speedMultiplier.asExpression(),
|
||||
teleportDistance = teleportDistance.asExpression(),
|
||||
maxDistance = maxDistance.asExpression()
|
||||
maxDistance = maxDistance.asExpression(),
|
||||
sprintDistance = sprintDistance.asExpression()
|
||||
),
|
||||
PokemonEntity::class.java
|
||||
)
|
||||
|
||||
+12
-1
@@ -11,6 +11,9 @@ package com.cobblemon.mod.common.api.conditional
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.JsonSerializer
|
||||
import java.lang.reflect.Type
|
||||
|
||||
/**
|
||||
@@ -21,11 +24,19 @@ import java.lang.reflect.Type
|
||||
* @author Hiroku
|
||||
* @since July 17th, 2022
|
||||
*/
|
||||
interface RegistryLikeAdapter<B> : JsonDeserializer<RegistryLikeCondition<B>> {
|
||||
interface RegistryLikeAdapter<B> : JsonDeserializer<RegistryLikeCondition<B>>, JsonSerializer<RegistryLikeCondition<B>> {
|
||||
val registryLikeConditions: MutableList<(JsonElement) -> RegistryLikeCondition<B>?>
|
||||
|
||||
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): RegistryLikeCondition<B> {
|
||||
return registryLikeConditions.firstNotNullOfOrNull { it(json) }
|
||||
?: throw IllegalArgumentException("Unable to deserialize $json")
|
||||
}
|
||||
|
||||
override fun serialize(src: RegistryLikeCondition<B>, typeOfSrc: Type, ctx: JsonSerializationContext): JsonElement {
|
||||
return when (src) {
|
||||
is RegistryLikeTagCondition<B> -> JsonPrimitive(RegistryLikeTagCondition.PREFIX + src.tag.location())
|
||||
is RegistryLikeIdentifierCondition<B> -> JsonPrimitive(src.identifier.toString())
|
||||
else -> throw IllegalArgumentException("Unable to serialize $src")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.data
|
||||
|
||||
import com.cobblemon.mod.common.util.endsWith
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.core.HolderLookup
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.util.concurrent.ExecutionException
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.packs.PackType
|
||||
import net.minecraft.server.packs.resources.ResourceManager
|
||||
|
||||
/**
|
||||
* A [DataRegistry] that consumes JSON files.
|
||||
* Every deserialized instance is attached to an [Identifier].
|
||||
* For example a file under data/mymod/[resourcePath]/entry.json would be backed by the identifier modid:entry.
|
||||
*
|
||||
* @param T The type of the data consumed by this registry.
|
||||
*
|
||||
* @author Licious
|
||||
* @since August 5th, 2022
|
||||
*/
|
||||
interface ServerJsonDataRegistry<T> : DataRegistry {
|
||||
|
||||
/**
|
||||
* The [TypeToken] of type [T].
|
||||
*/
|
||||
val typeToken: TypeToken<T>
|
||||
|
||||
/**
|
||||
* The folder location for the data this registry will consume.
|
||||
*/
|
||||
val resourcePath: String
|
||||
|
||||
override val type: PackType
|
||||
get() = PackType.SERVER_DATA
|
||||
|
||||
override fun reload(manager: ResourceManager) {
|
||||
//NO-OP, handled by reload(manager, registry)
|
||||
}
|
||||
|
||||
fun reload(manager: ResourceManager, registry: HolderLookup.Provider) {
|
||||
val gson = setupParser(registry)
|
||||
val data = hashMapOf<ResourceLocation, T>()
|
||||
manager.listResources(resourcePath) { path -> path.endsWith(JSON_EXTENSION) }.forEach { (identifier, resource) ->
|
||||
if (identifier.namespace == "pixelmon") {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
resource.open().use { stream ->
|
||||
stream.bufferedReader().use { reader ->
|
||||
val resolvedIdentifier = ResourceLocation.fromNamespaceAndPath(identifier.namespace, File(identifier.path).nameWithoutExtension)
|
||||
data[resolvedIdentifier] = parse(reader, resolvedIdentifier, gson)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reload(data, registry)
|
||||
}
|
||||
|
||||
fun parse(reader: BufferedReader, identifier: ResourceLocation, gson: Gson): T {
|
||||
return try {
|
||||
gson.fromJson(reader, typeToken.type)
|
||||
} catch (exception: Exception) {
|
||||
throw ExecutionException("Error loading JSON for data: $identifier", exception)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the creation of [Gson] instances which have adapters that are registry-aware, e.g. [com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter]
|
||||
*/
|
||||
fun setupParser(registry: HolderLookup.Provider): Gson
|
||||
|
||||
/**
|
||||
* Reloads this registry from the deserialized data.
|
||||
*
|
||||
* @param data A map of the data associating an instance to the respective identifier from the [ResourceManager].
|
||||
*/
|
||||
fun reload(data: Map<ResourceLocation, T>, registry: HolderLookup.Provider)
|
||||
|
||||
companion object {
|
||||
const val JSON_EXTENSION = ".json"
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ object Fossils: JsonDataRegistry<Fossil> {
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter())
|
||||
.create()
|
||||
|
||||
override val typeToken: TypeToken<Fossil> = TypeToken.get(Fossil::class.java)
|
||||
|
||||
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.gui
|
||||
|
||||
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
|
||||
import net.minecraft.client.gui.components.AbstractWidget
|
||||
import net.minecraft.client.gui.components.Renderable
|
||||
import net.minecraft.client.gui.components.events.GuiEventListener
|
||||
import net.minecraft.client.gui.narration.NarrationElementOutput
|
||||
import net.minecraft.network.chat.Component
|
||||
@@ -55,6 +54,8 @@ abstract class ParentWidget(
|
||||
}
|
||||
|
||||
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
|
||||
if (!active) return false
|
||||
|
||||
return children.any {
|
||||
it.mouseClicked(pMouseX, pMouseY, pButton)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.gui
|
||||
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
interface TreeNode<T> {
|
||||
val children: List<TreeNode<T>>
|
||||
}
|
||||
|
||||
class TreeDrawingNode<T>(
|
||||
val value: T,
|
||||
val children: List<TreeDrawingNode<T>>,
|
||||
) {
|
||||
var x = 0
|
||||
var y = 0
|
||||
var offset = 0
|
||||
}
|
||||
|
||||
fun <T> createDrawingTree(treeNode: TreeNode<T>): TreeDrawingNode<T> {
|
||||
val root = setup(treeNode, 0, mutableListOf<Int>(), mutableListOf<Int>())
|
||||
addMods(root, 0)
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <T> setup(
|
||||
treeNode: TreeNode<T>,
|
||||
depth: Int,
|
||||
nexts: MutableList<Int>,
|
||||
offset: MutableList<Int>,
|
||||
): TreeDrawingNode<T> {
|
||||
val children = treeNode.children.map { child ->
|
||||
setup(child, depth + 1, nexts, offset)
|
||||
}
|
||||
|
||||
val treeDrawingNode = TreeDrawingNode<T>(treeNode as T, children)
|
||||
treeDrawingNode.y = depth
|
||||
|
||||
while (nexts.size <= depth) {
|
||||
nexts.add(0)
|
||||
}
|
||||
|
||||
while (offset.size <= depth) {
|
||||
offset.add(0)
|
||||
}
|
||||
|
||||
var place =
|
||||
if (children.isEmpty()) {
|
||||
nexts[depth]
|
||||
} else if (children.size == 1) {
|
||||
// children.first().x - 1
|
||||
children.first().x
|
||||
} else {
|
||||
// (children[0].x + children[1].x) / 2
|
||||
(children.first().x + children.last().x) / 2
|
||||
}
|
||||
|
||||
offset[depth] = max(offset[depth], nexts[depth] - place)
|
||||
|
||||
if (children.isEmpty()) {
|
||||
treeDrawingNode.x = place
|
||||
} else {
|
||||
treeDrawingNode.x = place + offset[depth]
|
||||
}
|
||||
|
||||
nexts[depth] += 2
|
||||
treeDrawingNode.offset = offset[depth]
|
||||
|
||||
return treeDrawingNode
|
||||
}
|
||||
|
||||
private fun <T> addMods(
|
||||
treeNode: TreeDrawingNode<T>,
|
||||
modSum: Int,
|
||||
) {
|
||||
treeNode.x += modSum
|
||||
val newModSum = modSum + treeNode.offset
|
||||
|
||||
treeNode.children.forEach { child ->
|
||||
addMods(child, newModSum)
|
||||
}
|
||||
}
|
||||
|
||||
data class TreeDrawingBounds(val minX: Int, val maxX: Int, val minY: Int, val maxY: Int) {
|
||||
val width = maxX - minX
|
||||
val height = maxY - minY
|
||||
val xCenter = (maxX + minX) / 2.0
|
||||
val yCenter = (maxY + minY) / 2.0
|
||||
}
|
||||
|
||||
fun <T> calculateBounds(treeDrawingNode: TreeDrawingNode<T>): TreeDrawingBounds {
|
||||
var minX = treeDrawingNode.x
|
||||
var maxX = treeDrawingNode.x
|
||||
var minY = treeDrawingNode.y
|
||||
var maxY = treeDrawingNode.y
|
||||
|
||||
for (child in treeDrawingNode.children) {
|
||||
val childBounds = calculateBounds(child)
|
||||
|
||||
minX = min(minX, childBounds.minX)
|
||||
maxX = max(maxX, childBounds.maxX)
|
||||
minY = min(minY, childBounds.minY)
|
||||
maxY = max(maxY, childBounds.maxY)
|
||||
}
|
||||
|
||||
return TreeDrawingBounds(minX, maxX, minY, maxY)
|
||||
}
|
||||
+24
-20
@@ -10,7 +10,7 @@ package com.cobblemon.mod.common.api.interaction
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.data.ServerJsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
|
||||
@@ -26,6 +26,7 @@ import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.advancements.critereon.ItemPredicate
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
@@ -36,28 +37,31 @@ import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.levelgen.structure.Structure
|
||||
import net.minecraft.world.level.material.Fluid
|
||||
|
||||
object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
|
||||
object PokemonInteractions : ServerJsonDataRegistry<PokemonInteractionSet> {
|
||||
override val id = cobblemonResource("pokemon_interactions")
|
||||
override val type = PackType.SERVER_DATA
|
||||
override fun setupParser(registry: HolderLookup.Provider): Gson {
|
||||
return GsonBuilder()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter(registry))
|
||||
.registerTypeAdapter(Requirement::class.java, CobblemonRequirementAdapter)
|
||||
.registerTypeAdapter(InteractionEffect::class.java, InteractionEffectAdapter)
|
||||
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Structure::class.java).type, StructureLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
|
||||
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
|
||||
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory())
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
}
|
||||
|
||||
override val observable = SimpleObservable<PokemonInteractions>()
|
||||
override val typeToken: TypeToken<PokemonInteractionSet> = TypeToken.get(PokemonInteractionSet::class.java)
|
||||
override val resourcePath = "pokemon_interactions"
|
||||
override val gson: Gson = GsonBuilder()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
|
||||
.registerTypeAdapter(Requirement::class.java, CobblemonRequirementAdapter)
|
||||
.registerTypeAdapter(InteractionEffect::class.java, InteractionEffectAdapter)
|
||||
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Structure::class.java).type, StructureLikeConditionAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
|
||||
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
|
||||
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory())
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
|
||||
val interactions = mutableListOf<PokemonInteractionSet>()
|
||||
|
||||
@@ -65,7 +69,7 @@ object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
|
||||
PokemonInteractionsSyncPacket(interactions).sendToPlayer(player)
|
||||
}
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, PokemonInteractionSet>) {
|
||||
override fun reload(data: Map<ResourceLocation, PokemonInteractionSet>, registry: HolderLookup.Provider) {
|
||||
interactions.clear()
|
||||
val split = data.entries
|
||||
.partition {
|
||||
@@ -87,4 +91,4 @@ object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
|
||||
.flatMap { it.interactions }
|
||||
.firstOrNull(interactionCheck)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -55,6 +55,11 @@ object GeneralMoLangFunctions {
|
||||
struct.map.remove(variable)
|
||||
DoubleValue.ONE
|
||||
}
|
||||
functions["delete_variables"] = { params ->
|
||||
val struct = params.get<VariableStruct>(0)
|
||||
struct.map.clear()
|
||||
DoubleValue.ONE
|
||||
}
|
||||
functions["get_variable"] = { params ->
|
||||
val struct = params.get<VariableStruct>(0)
|
||||
val variable = params.getString(1)
|
||||
|
||||
+22
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
|
||||
import com.cobblemon.mod.common.api.pokedex.CaughtCount
|
||||
import com.cobblemon.mod.common.api.pokedex.CaughtPercent
|
||||
import com.cobblemon.mod.common.api.pokedex.Dexes
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexManager
|
||||
import com.cobblemon.mod.common.api.pokedex.SeenCount
|
||||
@@ -74,6 +75,27 @@ object PokedexMoLangFunctions : AbstractMoLangFunctionHolder<AbstractPokedexMana
|
||||
functions["caught_percent"] = { DoubleValue(pokedex.getGlobalCalculatedValue(CaughtPercent)) }
|
||||
functions["seen_percent"] = { DoubleValue(pokedex.getGlobalCalculatedValue(SeenPercent)) }
|
||||
|
||||
functions["dex_caught_count"] = { params ->
|
||||
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, CaughtCount))
|
||||
else DoubleValue.ZERO
|
||||
}
|
||||
functions["dex_seen_count"] = { params ->
|
||||
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, SeenCount))
|
||||
else DoubleValue.ZERO
|
||||
}
|
||||
functions["dex_caught_percent"] = { params ->
|
||||
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, CaughtPercent).toDouble())
|
||||
else DoubleValue.ZERO
|
||||
}
|
||||
functions["dex_seen_percent"] = { params ->
|
||||
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, SeenPercent).toDouble())
|
||||
else DoubleValue.ZERO
|
||||
}
|
||||
|
||||
when (pokedex) {
|
||||
is PokedexManager -> {
|
||||
functions["player_id"] = { StringValue(pokedex.uuid.toString()) }
|
||||
|
||||
+9
-4
@@ -54,10 +54,15 @@ object PokemonEntityMoLangFunctions : AbstractMoLangFunctionHolder<PokemonEntity
|
||||
}
|
||||
}
|
||||
functions["is_sprinting"] = {
|
||||
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
|
||||
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
|
||||
return@ifRidingAvailableSupply moValue.functions["sprinting"]?.apply(MoParams.EMPTY) as? MoValue
|
||||
?: DoubleValue.ZERO
|
||||
// Ridden pokemon sprint from the ride state, otherwise from the AI
|
||||
if (!pokemonEntity.hasControllingPassenger()) {
|
||||
DoubleValue(pokemonEntity.isPokemonSprinting)
|
||||
} else {
|
||||
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
|
||||
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
|
||||
return@ifRidingAvailableSupply moValue.functions["sprinting"]?.apply(MoParams.EMPTY) as? MoValue
|
||||
?: DoubleValue.ZERO
|
||||
}
|
||||
}
|
||||
}
|
||||
functions["is_drifting"] = {
|
||||
|
||||
+23
-7
@@ -8,8 +8,10 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.orientation
|
||||
|
||||
import com.cobblemon.mod.common.util.math.QuatSpring
|
||||
import com.cobblemon.mod.common.util.math.geometry.toDegrees
|
||||
import com.cobblemon.mod.common.util.math.geometry.toRadians
|
||||
import com.cobblemon.mod.common.util.math.simpleSpringDamperQuat
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
@@ -26,6 +28,8 @@ open class OrientationController(val entity: LivingEntity) {
|
||||
val FORWARDS: Vector3f = Vector3f(0.0f, 0.0f, -1.0f)
|
||||
val UP: Vector3f = Vector3f(0.0f, 1.0f, 0.0f)
|
||||
val LEFT: Vector3f = Vector3f(-1.0f, 0.0f, 0.0f)
|
||||
private const val TICK_DT = 1f / 20f // Minecraft server tick rate
|
||||
private const val RENDER_SMOOTH_HALFLIFE = 0.1f // seconds to close ~half the gap
|
||||
}
|
||||
|
||||
open var active: Boolean = false
|
||||
@@ -33,6 +37,8 @@ open class OrientationController(val entity: LivingEntity) {
|
||||
var orientation: Matrix3f? = null
|
||||
private set
|
||||
|
||||
private val renderSpring = QuatSpring()
|
||||
|
||||
private var renderOrientationO: Matrix3f? = null
|
||||
private var renderOrientation: Matrix3f? = null
|
||||
|
||||
@@ -54,6 +60,8 @@ open class OrientationController(val entity: LivingEntity) {
|
||||
orientation = null
|
||||
renderOrientationO = null
|
||||
renderOrientation = null
|
||||
renderSpring.quat.identity()
|
||||
renderSpring.angVel.set(0f, 0f, 0f)
|
||||
active = false
|
||||
}
|
||||
|
||||
@@ -77,18 +85,26 @@ open class OrientationController(val entity: LivingEntity) {
|
||||
return oldQuat
|
||||
}
|
||||
|
||||
/** Every tick move the current render orientation towards the last known true orientation **/
|
||||
fun tick() {
|
||||
if (!active || orientation == null) return
|
||||
renderOrientationO = renderOrientation
|
||||
val current = orientation
|
||||
val renderMatrix = this.renderOrientation ?: current
|
||||
val renderQuat = Quaternionf().setFromUnnormalized(renderMatrix)
|
||||
val targetQuat = Quaternionf().setFromUnnormalized(current)
|
||||
val dampingFactor = 0.66f // Smooth interpolation to reduce jitter of orientations recieved from the server.
|
||||
renderQuat.slerp(targetQuat, dampingFactor)
|
||||
val current = orientation!!
|
||||
val targetQuat = Quaternionf().setFromUnnormalized(current)
|
||||
|
||||
if (renderOrientation == null) {
|
||||
// First tick: snap the spring to the target instead of easing in from identity
|
||||
renderSpring.quat.set(targetQuat)
|
||||
renderSpring.angVel.set(0f, 0f, 0f)
|
||||
}
|
||||
|
||||
// Use a "quat spring" to smoothly move the render orientation towards the goal orientation.
|
||||
// It takes into account the orientations angular velocities and should be much more resilient
|
||||
// to sharp changes in goal orientation or late/missed packets.
|
||||
simpleSpringDamperQuat(renderSpring, targetQuat, 0.15f, TICK_DT)
|
||||
|
||||
val newRenderOrientation = Matrix3f()
|
||||
renderQuat.get(newRenderOrientation)
|
||||
renderSpring.quat.get(newRenderOrientation)
|
||||
renderOrientation = newRenderOrientation
|
||||
}
|
||||
|
||||
|
||||
@@ -151,6 +151,7 @@ object CobblemonPermissions {
|
||||
|
||||
@JvmStatic
|
||||
val USE_RIDING_STATS_DEBUG = this.create("useridingstatsdebug", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
|
||||
val USE_SPEED_TUNER = this.create("usespeedtuner", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
|
||||
|
||||
@JvmStatic
|
||||
fun all(): Iterable<Permission> = this.permissions
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ object CatchRateModifiers {
|
||||
* Used by [PokeBalls.SAFARI_BALL].
|
||||
* Checks if the target is not battling, if true boost by *1.5
|
||||
*/
|
||||
val SAFARI = WorldStateModifier { _, entity -> if (!entity.isBattling) 1.5F else 1F }
|
||||
val SAFARI = MultiplierModifier(1.5f) { _, poke -> !poke.entity?.isBattling!! }
|
||||
|
||||
/**
|
||||
* Used by [PokeBalls.PARK_BALL].
|
||||
|
||||
@@ -13,6 +13,10 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.ChoiceSpeciesFeatureProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.WeightedChoiceSpeciesFeatureProvider
|
||||
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData
|
||||
import com.cobblemon.mod.common.pokemon.Gender
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
@@ -72,6 +76,57 @@ abstract class AbstractPokedexManager {
|
||||
} ?: PokedexEntryProgress.UNREGISTERED
|
||||
}
|
||||
|
||||
fun getHighestKnowledgeFor(entry: PokedexEntry, form: PokedexForm?): PokedexEntryProgress {
|
||||
if (form == null) return getHighestKnowledgeFor(entry)
|
||||
|
||||
val speciesRecord = getSpeciesRecord(entry.speciesId) ?: return PokedexEntryProgress.UNREGISTERED
|
||||
val hasAllAspects = entry.conditionAspects.all(speciesRecord::hasAspect)
|
||||
if (!hasAllAspects) {
|
||||
return PokedexEntryProgress.UNREGISTERED
|
||||
}
|
||||
|
||||
return speciesRecord.getFormRecord(form.displayForm)?.knowledge ?: PokedexEntryProgress.UNREGISTERED
|
||||
}
|
||||
|
||||
fun getHighestKnowledgeFor(entry: PokedexEntry, form: PokedexForm?, aspects: Iterable<String>): PokedexEntryProgress {
|
||||
val formKnowledge = getHighestKnowledgeFor(entry, form)
|
||||
|
||||
if (formKnowledge == PokedexEntryProgress.UNREGISTERED) return PokedexEntryProgress.UNREGISTERED
|
||||
|
||||
val seenAspects = getSeenAspects(entry)
|
||||
|
||||
// Aspects that select one of the species' own forms are already covered by the form knowledge check above,
|
||||
// so exempt them (and every other value of the same feature, since defaults like "phony" never appear on a form directly).
|
||||
val species = PokemonSpecies.getByIdentifier(entry.speciesId)
|
||||
val formAspects = species?.forms?.flatMapTo(hashSetOf()) { it.aspects } ?: emptySet()
|
||||
val formSelectingFeatureAspects = species?.let(SpeciesFeatures::getFeaturesFor).orEmpty()
|
||||
.mapNotNull { provider ->
|
||||
val allAspects: Collection<String> = when (provider) {
|
||||
is ChoiceSpeciesFeatureProvider -> provider.getAllAspects()
|
||||
is WeightedChoiceSpeciesFeatureProvider -> provider.getAllAspects().keys
|
||||
else -> return@mapNotNull null
|
||||
}
|
||||
allAspects.takeIf { it.any { aspect -> aspect in formAspects } }
|
||||
}
|
||||
.flatten()
|
||||
.toHashSet()
|
||||
|
||||
val allSeenAspects = aspects
|
||||
.filter { aspect ->
|
||||
if (Dexes.equivalentFormsLookup[aspect] != null) return@filter false
|
||||
if (aspect in formAspects || aspect in formSelectingFeatureAspects) return@filter false
|
||||
if (aspect.uppercase() in setOf(Gender.MALE.name, Gender.FEMALE.name, Gender.GENDERLESS.name)) return@filter false
|
||||
if ("shiny".equals(aspect, ignoreCase = true)) return@filter false
|
||||
|
||||
return@filter true
|
||||
}
|
||||
.all { aspect -> seenAspects.contains(aspect) }
|
||||
|
||||
if (!allSeenAspects) return PokedexEntryProgress.UNREGISTERED
|
||||
|
||||
return formKnowledge
|
||||
}
|
||||
|
||||
fun getEncounteredForms(entry: PokedexEntry) : List<PokedexForm> {
|
||||
return getFormsWithKnowledge(entry, PokedexEntryProgress.SEEN)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ package com.cobblemon.mod.common.api.pokedex
|
||||
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.pokedex.def.PokedexDef
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.net.messages.client.data.PokedexDexSyncPacket
|
||||
import com.cobblemon.mod.common.util.adapters.CodecBackedAdapter
|
||||
@@ -40,6 +43,17 @@ object Dexes : JsonDataRegistry<PokedexDef> {
|
||||
//Maps a dex id to its PokedexDef
|
||||
val dexEntryMap = linkedMapOf<ResourceLocation, PokedexDef>()
|
||||
|
||||
val equivalentForms = mapOf(
|
||||
"galar" to listOf("region-bias-galar", "bias-galar", "galar-bias", "galarian", "galar"),
|
||||
"alola" to listOf("region-bias-alola", "bias-alola", "alola-bias", "alolan", "alola"),
|
||||
"hisui" to listOf("region-bias-hisui", "bias-hisui", "hisui-bias", "hisuian", "hisui"),
|
||||
"paldea" to listOf("region-bias-paldea", "bias-paldea", "paldea-bias", "paldean", "paldea"),
|
||||
)
|
||||
|
||||
val equivalentFormsLookup = equivalentForms.flatMap { (key, values) ->
|
||||
values.map { it to key }
|
||||
}.toMap()
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, PokedexDef>) {
|
||||
dexEntryMap.clear()
|
||||
data.entries.sortedBy { it.value.sortOrder }.forEach { (id, def) -> dexEntryMap[id] = def }
|
||||
@@ -49,4 +63,41 @@ object Dexes : JsonDataRegistry<PokedexDef> {
|
||||
override fun sync(player: ServerPlayer) {
|
||||
PokedexDexSyncPacket(dexEntryMap.values).sendToPlayer(player)
|
||||
}
|
||||
|
||||
fun findDexEntryBySpeciesId(regionId: ResourceLocation, speciesId: ResourceLocation): PokedexEntry? {
|
||||
return dexEntryMap[regionId]?.getEntries()?.firstOrNull { it -> it.speciesId == speciesId }
|
||||
}
|
||||
|
||||
fun findDexEntryBySpeciesId(speciesId: ResourceLocation): PokedexEntry? {
|
||||
return findDexEntryBySpeciesId(cobblemonResource("national"), speciesId)
|
||||
}
|
||||
|
||||
fun findRootDexEntryBySpeciesId(speciesId: ResourceLocation, formName: String): Pair<PokedexEntry, PokedexForm> {
|
||||
var rootSpecies = PokemonSpecies.getByIdentifier(speciesId)!!
|
||||
val initialFormEquivalents = getFormEquivalents(formName)
|
||||
var rootForm = rootSpecies.forms.firstOrNull { initialFormEquivalents.contains(it.name.lowercase()) }
|
||||
?: rootSpecies.standardForm
|
||||
|
||||
var preEvolution = rootForm.preEvolution
|
||||
while (preEvolution != null) {
|
||||
rootSpecies = preEvolution.species
|
||||
rootForm = preEvolution.resolveForm(rootForm.aspects.toSet())
|
||||
preEvolution = rootForm.preEvolution
|
||||
}
|
||||
|
||||
val entry = findDexEntryBySpeciesId(rootSpecies.resourceIdentifier)
|
||||
?: throw RuntimeException("Root dex entry not found")
|
||||
|
||||
val formEquivalents = getFormEquivalents(rootForm.name)
|
||||
val form =
|
||||
entry.forms.firstOrNull { formEquivalents.contains(it.displayForm.lowercase()) } ?:
|
||||
entry.forms.first { it.displayForm.equals("Normal", ignoreCase = true) }
|
||||
|
||||
return entry to form
|
||||
}
|
||||
|
||||
fun getFormEquivalents(formName: String): List<String> {
|
||||
val key = equivalentFormsLookup[formName.lowercase()]
|
||||
return equivalentForms[key] ?: listOf(formName.lowercase())
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,7 @@ class SpeciesDexRecord {
|
||||
|
||||
fun addAspects(addedAspects: Set<String>) {
|
||||
aspects.addAll(addedAspects)
|
||||
pokedexManager.onSpeciesRecordUpdated(this)
|
||||
}
|
||||
|
||||
/** Returns true if the given Pokémon contains new information. Internal because it's only to be called from [FormDexRecord.wouldBeDifferent]. */
|
||||
|
||||
@@ -44,6 +44,9 @@ import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
|
||||
import com.cobblemon.mod.common.api.spawning.TimeRange
|
||||
import com.cobblemon.mod.common.api.types.ElementalType
|
||||
import com.cobblemon.mod.common.api.types.adapters.ElementalTypeAdapter
|
||||
import com.cobblemon.mod.common.net.messages.client.data.FormEvolutionOverride
|
||||
import com.cobblemon.mod.common.net.messages.client.data.SpeciesEvolutionEntry
|
||||
import com.cobblemon.mod.common.net.messages.client.data.SpeciesEvolutionSyncPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.data.SpeciesRegistrySyncPacket
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.pokemon.Species
|
||||
@@ -128,7 +131,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
|
||||
.registerTypeAdapter(EggGroup::class.java, EggGroupAdapter)
|
||||
.registerTypeAdapter(MobEffect::class.java, RegistryElementAdapter<MobEffect>(BuiltInRegistries::MOB_EFFECT))
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
|
||||
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter())
|
||||
.registerTypeAdapter(RidingBehaviourSettings::class.java, RidingBehaviourSettingsAdapter)
|
||||
.registerTypeAdapter(RideSoundSettingsList::class.java, RideSoundSettingsListAdapter)
|
||||
.registerTypeAdapter(ObtainableItemCondition::class.java, ObtainableItemConditionAdapter)
|
||||
@@ -243,6 +246,18 @@ object PokemonSpecies : JsonDataRegistry<Species> {
|
||||
|
||||
override fun sync(player: ServerPlayer) {
|
||||
SpeciesRegistrySyncPacket(species.toList()).sendToPlayer(player)
|
||||
// Sent after the species themselves so every species is already registered by the time this is decoded.
|
||||
val evolutionEntries = species.map { singleSpecies ->
|
||||
val formOverrides = singleSpecies.forms.mapNotNull { form ->
|
||||
if (form.ownEvolutions == null && form.ownPreEvolution == null) {
|
||||
null
|
||||
} else {
|
||||
FormEvolutionOverride(form.name, form.ownEvolutions, form.ownPreEvolution)
|
||||
}
|
||||
}
|
||||
SpeciesEvolutionEntry(singleSpecies.resourceIdentifier, singleSpecies.evolutions, singleSpecies.preEvolution, formOverrides)
|
||||
}
|
||||
SpeciesEvolutionSyncPacket(evolutionEntries).sendToPlayer(player)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,8 @@ interface PreEvolution {
|
||||
|
||||
val form: FormData
|
||||
|
||||
fun resolveForm(extraAspects: Set<String> = emptySet()): FormData = form
|
||||
|
||||
companion object {
|
||||
|
||||
fun of(species: Species, form: FormData = species.standardForm): PreEvolution = StandardPreEvolution(species, form)
|
||||
|
||||
@@ -11,6 +11,8 @@ package com.cobblemon.mod.common.api.pokemon.requirement
|
||||
import com.cobblemon.mod.common.api.pokemon.evolution.ContextEvolution
|
||||
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import java.util.WeakHashMap
|
||||
|
||||
/**
|
||||
* Represents a universal requirement suitable for various checks on pokemon..
|
||||
@@ -32,4 +34,11 @@ fun interface Requirement {
|
||||
*/
|
||||
fun check(pokemon: Pokemon): Boolean
|
||||
|
||||
val pokedexIcon: ResourceLocation? get() = pokedexIconOverrides[this]
|
||||
val pokedexTooltip: String? get() = pokedexTooltipOverrides[this]
|
||||
|
||||
companion object {
|
||||
val pokedexIconOverrides = WeakHashMap<Requirement, ResourceLocation>()
|
||||
val pokedexTooltipOverrides = WeakHashMap<Requirement, String>()
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.riding.util
|
||||
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.phys.AABB
|
||||
import kotlin.math.floor
|
||||
|
||||
/**
|
||||
* Walks every block position inside [searchBox] and turns each solid block into one
|
||||
* full-cube [FieldPrimitive].
|
||||
*
|
||||
* @author Jackson
|
||||
* @since July 27th, 2026
|
||||
*/
|
||||
|
||||
fun collectPrimitivesFromBlocks(
|
||||
level: BlockGetter,
|
||||
searchBox: AABB,
|
||||
shape: PrimitiveShape,
|
||||
includeFluids: Boolean = false
|
||||
): List<FieldPrimitive> {
|
||||
val primitives = mutableListOf<FieldPrimitive>()
|
||||
|
||||
val minX = floor(searchBox.minX).toInt()
|
||||
val maxX = floor(searchBox.maxX).toInt()
|
||||
val minY = floor(searchBox.minY).toInt()
|
||||
val maxY = floor(searchBox.maxY).toInt()
|
||||
val minZ = floor(searchBox.minZ).toInt()
|
||||
val maxZ = floor(searchBox.maxZ).toInt()
|
||||
|
||||
val pos = BlockPos.MutableBlockPos()
|
||||
for (x in minX..maxX) {
|
||||
for (y in minY..maxY) {
|
||||
for (z in minZ..maxZ) {
|
||||
pos.set(x, y, z)
|
||||
val state = level.getBlockState(pos)
|
||||
|
||||
val isSolid = !state.getCollisionShape(level, pos).isEmpty
|
||||
val isFluid = includeFluids && !state.fluidState.isEmpty
|
||||
if (!isSolid && !isFluid) continue
|
||||
|
||||
primitives.add(
|
||||
FieldPrimitive(
|
||||
shape = shape,
|
||||
centerX = x + 0.5f, centerY = y + 0.5f, centerZ = z + 0.5f,
|
||||
halfWidth = 0.5f, halfHeight = 0.5f, halfDepth = 0.5f
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return primitives
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.riding.util
|
||||
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sqrt
|
||||
|
||||
/**
|
||||
* Helpful Functions when working with signed distance fields/functions.
|
||||
* https://iquilezles.org/articles/distfunctions/
|
||||
*
|
||||
* @author Jackson
|
||||
* @since July 27th, 2026
|
||||
*/
|
||||
|
||||
/** Which distance formula a primitive uses. */
|
||||
enum class PrimitiveShape {
|
||||
BOX,
|
||||
ROUNDED_BOX,
|
||||
ELLIPSOID
|
||||
}
|
||||
|
||||
/** Tunable constants for the field. */
|
||||
data class DistanceFieldTuning(
|
||||
val smoothingRange: Float = 0.5f,
|
||||
val roundingRadius: Float = 0.08f
|
||||
)
|
||||
|
||||
/** A single shape in the field - a center position, half-extents, and which formula to use. */
|
||||
data class FieldPrimitive(
|
||||
val shape: PrimitiveShape,
|
||||
val centerX: Float, val centerY: Float, val centerZ: Float,
|
||||
val halfWidth: Float, val halfHeight: Float, val halfDepth: Float
|
||||
)
|
||||
|
||||
/** Fast approximate 1/sqrt(x) - the Quake III trick, cheaper than a real sqrt + divide. */
|
||||
fun fastInverseSqrt(value: Float): Float {
|
||||
var x = value
|
||||
val halfX = 0.5f * x
|
||||
var bits = java.lang.Float.floatToIntBits(x)
|
||||
bits = 0x5f3759df - (bits shr 1)
|
||||
x = java.lang.Float.intBitsToFloat(bits)
|
||||
x *= (1.5f - halfX * x * x)
|
||||
return x
|
||||
}
|
||||
|
||||
/** Exact distance from a point to an axis-aligned box. Inputs are relative to the box's own center. */
|
||||
fun boxDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float): Float {
|
||||
val qx = abs(relX) - halfWidth
|
||||
val qy = abs(relY) - halfHeight
|
||||
val qz = abs(relZ) - halfDepth
|
||||
val ox = max(qx, 0f)
|
||||
val oy = max(qy, 0f)
|
||||
val oz = max(qz, 0f)
|
||||
val outside = sqrt(ox * ox + oy * oy + oz * oz)
|
||||
val inside = min(max(qx, max(qy, qz)), 0f)
|
||||
return outside + inside
|
||||
}
|
||||
|
||||
/** Same as boxDistance but with rounded corners/edges https://iquilezles.org/articles/distfunctions/ */
|
||||
fun roundedBoxDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float, roundingRadius: Float): Float =
|
||||
boxDistance(relX, relY, relZ, halfWidth - roundingRadius, halfHeight - roundingRadius, halfDepth - roundingRadius) - roundingRadius
|
||||
|
||||
/** Approximate (lower-bound) distance to an ellipsoid https://iquilezles.org/articles/ellipsoids/. */
|
||||
fun ellipsoidDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float): Float {
|
||||
val px = relX / halfWidth
|
||||
val py = relY / halfHeight
|
||||
val pz = relZ / halfDepth
|
||||
val inverseMagnitude = fastInverseSqrt(px * px + py * py + pz * pz)
|
||||
val trueDistance = sqrt(relX * relX + relY * relY + relZ * relZ)
|
||||
return trueDistance * (1.0f - inverseMagnitude)
|
||||
}
|
||||
|
||||
/** Distance from a point to one primitive, dispatched by its shape. */
|
||||
fun distanceToPrimitive(primitive: FieldPrimitive, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
|
||||
val relX = sampleX - primitive.centerX
|
||||
val relY = sampleY - primitive.centerY
|
||||
val relZ = sampleZ - primitive.centerZ
|
||||
return when (primitive.shape) {
|
||||
PrimitiveShape.BOX -> boxDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth)
|
||||
PrimitiveShape.ROUNDED_BOX -> roundedBoxDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth, tuning.roundingRadius)
|
||||
PrimitiveShape.ELLIPSOID -> ellipsoidDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth)
|
||||
}
|
||||
}
|
||||
|
||||
///** Normalized quadratic smooth-min https://iquilezles.org/articles/smin/ */
|
||||
//fun smoothMinimum(a: Float, b: Float, smoothingRange: Float): Float {
|
||||
// if (smoothingRange <= 0.001f) return min(a, b)
|
||||
// val k = smoothingRange * 4.0f
|
||||
// val h = (0.5f + 0.5f * (b - a) / k).coerceIn(0f, 1f)
|
||||
// return b + (a - b) * h - k * h * (1f - h)
|
||||
//}
|
||||
//
|
||||
///** Folds every primitive's distance together into one field value at a point. */
|
||||
//fun sampleField(primitives: List<FieldPrimitive>, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
|
||||
// var result = Float.POSITIVE_INFINITY
|
||||
// for (primitive in primitives) {
|
||||
// val distance = distanceToPrimitive(primitive, sampleX, sampleY, sampleZ, tuning)
|
||||
// result = if (result == Float.POSITIVE_INFINITY) distance else smoothMinimum(result, distance, tuning.smoothingRange)
|
||||
// }
|
||||
// return result
|
||||
//}
|
||||
|
||||
/** Normalized quadratic smooth-min https://iquilezles.org/articles/smin/ */
|
||||
fun smoothMinimum(a: Float, b: Float, smoothingRange: Float): Float {
|
||||
if (smoothingRange <= 0.001f) return min(a, b)
|
||||
val k = smoothingRange * 4.0f
|
||||
val h = (0.5f + 0.5f * (b - a) / k).coerceIn(0f, 1f)
|
||||
return b + (a - b) * h - k * h * (1f - h)
|
||||
}
|
||||
|
||||
/** Folds nearby primitive distances together into one field value at a point. */
|
||||
fun sampleField(primitives: List<FieldPrimitive>, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
|
||||
if (primitives.isEmpty()) return Float.POSITIVE_INFINITY
|
||||
|
||||
val distances = FloatArray(primitives.size) { distanceToPrimitive(primitives[it], sampleX, sampleY, sampleZ, tuning) }
|
||||
val minDistance = distances.min()
|
||||
|
||||
if (tuning.smoothingRange <= 0.001f) return minDistance
|
||||
|
||||
// Anything more than a few blend-widths past the closest primitive is cut - its
|
||||
// contribution would be negligible in a correct blend anyway, but left in, it still
|
||||
// drags the chained result down the more of them there are.
|
||||
val cutoff = tuning.smoothingRange * 3f
|
||||
|
||||
var result = Float.POSITIVE_INFINITY
|
||||
for (distance in distances) {
|
||||
if (distance - minDistance > cutoff) continue
|
||||
result = if (result == Float.POSITIVE_INFINITY) distance else smoothMinimum(result, distance, tuning.smoothingRange)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** Finite-difference gradient of the field at a point - points away from the surface, magnitude ~1 for a clean field. */
|
||||
fun estimateGradient(primitives: List<FieldPrimitive>, point: Vec3, tuning: DistanceFieldTuning, epsilon: Float = 0.05f): Vec3 {
|
||||
val px = point.x.toFloat()
|
||||
val py = point.y.toFloat()
|
||||
val pz = point.z.toFloat()
|
||||
|
||||
val dx = sampleField(primitives, px + epsilon, py, pz, tuning) - sampleField(primitives, px - epsilon, py, pz, tuning)
|
||||
val dy = sampleField(primitives, px, py + epsilon, pz, tuning) - sampleField(primitives, px, py - epsilon, pz, tuning)
|
||||
val dz = sampleField(primitives, px, py, pz + epsilon, tuning) - sampleField(primitives, px, py, pz - epsilon, tuning)
|
||||
|
||||
val gradient = Vec3(dx.toDouble(), dy.toDouble(), dz.toDouble())
|
||||
return if (gradient.lengthSqr() > 1e-8) gradient.normalize() else Vec3(0.0, 1.0, 0.0)
|
||||
}
|
||||
+47
-31
@@ -18,50 +18,49 @@ import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.sign
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
* Class to handle the updating and containment of ride data used in animation.
|
||||
* Class to handle the updating and holding of motion data used in animation. Motion values accumulate for every
|
||||
* pokemon on the client, some only while ridden.
|
||||
*
|
||||
* @author Jackowes
|
||||
* @since April 7th, 2025
|
||||
*/
|
||||
|
||||
|
||||
class RidingAnimationData(val ride: PokemonEntity)
|
||||
class DynamicAnimationData(val ride: PokemonEntity)
|
||||
{
|
||||
companion object {
|
||||
// Blend time for motion values, they catch up within 1.5x this. Longer is smoother but lags more
|
||||
const val MOTION_BLEND_SECONDS = 0.8F
|
||||
// Rider input comes straight from key presses so it catches up quicker
|
||||
const val INPUT_BLEND_SECONDS = 0.3F
|
||||
}
|
||||
|
||||
var prevOrientation: Matrix3f = Matrix3f()
|
||||
var prevRot: Vec3 = Vec3.ZERO
|
||||
var velocitySpring: Vec3Spring = Vec3Spring()
|
||||
var rotSpring: Vec3Spring = Vec3Spring()
|
||||
var rotDeltaSpring: Vec3Spring = Vec3Spring()
|
||||
var localVelocitySpring: Vec3Spring = Vec3Spring()
|
||||
var driverInputSpring: Vec3Spring = Vec3Spring()
|
||||
var diveSpring: Vec3Spring = Vec3Spring()
|
||||
var velocitySpring = Vec3Ease(MOTION_BLEND_SECONDS)
|
||||
var rotSpring = Vec3Ease(MOTION_BLEND_SECONDS, isAngle = true)
|
||||
var rotDeltaSpring = Vec3Ease(MOTION_BLEND_SECONDS)
|
||||
var localVelocitySpring = Vec3Ease(MOTION_BLEND_SECONDS)
|
||||
var driverInputSpring = Vec3Ease(INPUT_BLEND_SECONDS)
|
||||
var diveSpring = Vec3Ease(MOTION_BLEND_SECONDS)
|
||||
private var initialized = false
|
||||
private var dismounted = false
|
||||
|
||||
/** Called on dismount. Motion keeps easing on its own, only the ride orientation needs to start fresh. */
|
||||
fun clear() {
|
||||
prevOrientation = Matrix3f()
|
||||
prevRot = Vec3.ZERO
|
||||
velocitySpring = Vec3Spring()
|
||||
rotSpring = Vec3Spring()
|
||||
rotDeltaSpring = Vec3Spring()
|
||||
localVelocitySpring = Vec3Spring()
|
||||
driverInputSpring = Vec3Spring()
|
||||
diveSpring = Vec3Spring()
|
||||
dismounted = true
|
||||
}
|
||||
|
||||
fun update() {
|
||||
val activeRide = ride.hasControllingPassenger()
|
||||
|
||||
if (!activeRide) return
|
||||
|
||||
var currRot = Vec3.ZERO
|
||||
var angDelta = Vec3.ZERO
|
||||
|
||||
val stiffness = 90.0
|
||||
val damping = 18.0
|
||||
|
||||
// Update velocity first
|
||||
val currentVelocity = Vec3(
|
||||
ride.x - ride.xOld,
|
||||
@@ -69,7 +68,7 @@ class RidingAnimationData(val ride: PokemonEntity)
|
||||
ride.z - ride.zOld
|
||||
)
|
||||
|
||||
val r = ride.ifRidingAvailableSupply(false) { behaviour, settings, state ->
|
||||
val r = activeRide && ride.ifRidingAvailableSupply(false) { behaviour, settings, state ->
|
||||
behaviour.shouldRoll(settings, state, ride);
|
||||
}
|
||||
if( r ) {
|
||||
@@ -118,22 +117,39 @@ class RidingAnimationData(val ride: PokemonEntity)
|
||||
val localVelocityF = Matrix3f(orientation).transpose().transform(velocityWorldF)
|
||||
val localVelocity = Vec3(localVelocityF.x.toDouble(), localVelocityF.y.toDouble(), localVelocityF.z.toDouble())
|
||||
|
||||
localVelocitySpring.update(localVelocity, stiffness, damping)
|
||||
localVelocitySpring.update(localVelocity)
|
||||
|
||||
// Update previous values
|
||||
prevOrientation.set(currOrientation)
|
||||
} else {
|
||||
val currPitch = ride.xRot
|
||||
// Pitch only means something while ridden
|
||||
val currPitch = if (activeRide) ride.xRot else 0.0f
|
||||
val currYaw = ride.yRot
|
||||
val currRoll = 0.0f
|
||||
currRot = Vec3(currPitch.toDouble(), currYaw.toDouble(), currRoll.toDouble())
|
||||
|
||||
// Start from where the pokemon already is instead of turning in from zero
|
||||
if (!initialized) {
|
||||
prevRot = currRot
|
||||
rotSpring.snap(currRot)
|
||||
initialized = true
|
||||
}
|
||||
// measure turning from here rather than as one big turn
|
||||
if (dismounted) {
|
||||
prevRot = currRot
|
||||
dismounted = false
|
||||
}
|
||||
|
||||
//Scale rotation delta by 20 to represent degree per second instead of tick
|
||||
angDelta = Vec3(
|
||||
(currRot.x - prevRot.x),
|
||||
Mth.degreesDifference(currRot.y.toFloat(), prevRot.y.toFloat()).toDouble(),
|
||||
Mth.degreesDifference(currRot.z.toFloat(), prevRot.z.toFloat()).toDouble()
|
||||
).scale(20.0)
|
||||
// Unridden turning comes straight from the heading ease, it's already smooth
|
||||
if (!activeRide) {
|
||||
angDelta = Vec3(angDelta.x, ride.heading.yaw.velocity().toDouble() * -0.5, angDelta.z)
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
* Calculate ride local velocity
|
||||
@@ -147,7 +163,7 @@ class RidingAnimationData(val ride: PokemonEntity)
|
||||
val localVelocityF = Matrix3f(orientationNonRollable).transpose().transform(velocityWorldF)
|
||||
val localVelocity = Vec3(localVelocityF.x.toDouble(), localVelocityF.y.toDouble(), localVelocityF.z.toDouble())
|
||||
|
||||
localVelocitySpring.update(localVelocity, stiffness, damping)
|
||||
localVelocitySpring.update(localVelocity)
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
@@ -159,14 +175,14 @@ class RidingAnimationData(val ride: PokemonEntity)
|
||||
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)
|
||||
val input = if(driver != null && activeRide) Vec3(driver.xxa.toDouble() * -1, vertInput, driver.zza.toDouble()) else Vec3.ZERO
|
||||
driverInputSpring.update(input)
|
||||
if (activeRide) rotDeltaSpring.update(angDelta) else rotDeltaSpring.follow(angDelta)
|
||||
velocitySpring.update(currentVelocity)
|
||||
rotSpring.update(currRot)
|
||||
|
||||
val diving = abs(max(sin(currRot.x.toRadians()),0f))
|
||||
diveSpring.update(Vec3(0.0,diving.toDouble(),0.0), stiffness, damping)
|
||||
diveSpring.update(Vec3(0.0,diving.toDouble(),0.0))
|
||||
prevRot = currRot
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.riding.util
|
||||
|
||||
import net.minecraft.core.particles.DustParticleOptions
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.phys.AABB
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.joml.Vector3f
|
||||
|
||||
/** One shell to draw - a target field value, a color, a particle size. */
|
||||
data class DistanceFieldLayer(
|
||||
val isoValue: Float,
|
||||
val color: Vector3f,
|
||||
val particleSize: Float = 1.0f
|
||||
)
|
||||
|
||||
/**
|
||||
* Marches a grid of vertical columns through the field around [center] and spawns a
|
||||
* particle everywhere it crosses one of [layers]' target values. Server-side only.
|
||||
*/
|
||||
fun visualizeDistanceField(
|
||||
referenceEntity: Entity,
|
||||
center: Vec3,
|
||||
layers: List<DistanceFieldLayer>,
|
||||
shape: PrimitiveShape = PrimitiveShape.BOX,
|
||||
horizontalRadius: Double = 4.0,
|
||||
verticalRange: Double = 4.0,
|
||||
sampleSpacing: Double = 0.5,
|
||||
marchStep: Double = 0.1,
|
||||
tuning: DistanceFieldTuning = DistanceFieldTuning()
|
||||
) {
|
||||
val level = referenceEntity.level()
|
||||
if (level !is ServerLevel) return
|
||||
|
||||
val searchBox = AABB(
|
||||
center.x - horizontalRadius, center.y - verticalRange, center.z - horizontalRadius,
|
||||
center.x + horizontalRadius, center.y, center.z + horizontalRadius
|
||||
)
|
||||
|
||||
val primitives = collectPrimitivesFromBlocks(level, searchBox, shape, includeFluids = true)
|
||||
if (primitives.isEmpty()) return
|
||||
|
||||
var x = -horizontalRadius
|
||||
while (x <= horizontalRadius) {
|
||||
var z = -horizontalRadius
|
||||
while (z <= horizontalRadius) {
|
||||
marchColumn(level, primitives, center, x, z, verticalRange, marchStep, layers, tuning)
|
||||
z += sampleSpacing
|
||||
}
|
||||
x += sampleSpacing
|
||||
}
|
||||
}
|
||||
|
||||
private fun marchColumn(
|
||||
level: ServerLevel,
|
||||
primitives: List<FieldPrimitive>,
|
||||
center: Vec3,
|
||||
offsetX: Double,
|
||||
offsetZ: Double,
|
||||
verticalRange: Double,
|
||||
marchStep: Double,
|
||||
layers: List<DistanceFieldLayer>,
|
||||
tuning: DistanceFieldTuning
|
||||
) {
|
||||
val previousValue = FloatArray(layers.size) { Float.NaN }
|
||||
|
||||
var y = verticalRange
|
||||
while (y >= -verticalRange) {
|
||||
val worldX = (center.x + offsetX).toFloat()
|
||||
val worldY = (center.y + y).toFloat()
|
||||
val worldZ = (center.z + offsetZ).toFloat()
|
||||
val fieldValue = sampleField(primitives, worldX, worldY, worldZ, tuning)
|
||||
|
||||
for (layerIndex in layers.indices) {
|
||||
val layer = layers[layerIndex]
|
||||
val previous = previousValue[layerIndex]
|
||||
|
||||
if (!previous.isNaN() && crossedTarget(previous, fieldValue, layer.isoValue)) {
|
||||
val t = (layer.isoValue - previous) / (fieldValue - previous)
|
||||
val crossingY = (y + marchStep) - marchStep * t
|
||||
spawnLayerParticle(level, center.add(offsetX, crossingY, offsetZ), layer)
|
||||
}
|
||||
|
||||
previousValue[layerIndex] = fieldValue
|
||||
}
|
||||
|
||||
y -= marchStep
|
||||
}
|
||||
}
|
||||
|
||||
private fun crossedTarget(previous: Float, current: Float, target: Float): Boolean =
|
||||
(previous - target) * (current - target) < 0f
|
||||
|
||||
private fun spawnLayerParticle(level: ServerLevel, pos: Vec3, layer: DistanceFieldLayer) {
|
||||
val particle = DustParticleOptions(layer.color, layer.particleSize)
|
||||
level.sendParticles(particle, pos.x, pos.y, pos.z, 1, 0.0, 0.0, 0.0, 0.0)
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.riding.util
|
||||
|
||||
import com.cobblemon.mod.common.util.math.InertialVals
|
||||
import com.cobblemon.mod.common.util.math.smoothstep
|
||||
import com.cobblemon.mod.common.util.math.smoothstepDt
|
||||
import com.cobblemon.mod.common.util.math.smoothstepSolve
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
/**
|
||||
* Eases a Vec3 towards a target with inertial easing, see SpringUtils and https://theorangeduck.com/page/inertial-easing
|
||||
* When the target changes each component keeps its current velocity
|
||||
*
|
||||
* With [isAngle] the components are treated as degrees and always ease the short way round.
|
||||
*/
|
||||
class Vec3Ease(
|
||||
private val blendTime: Float,
|
||||
private val isAngle: Boolean = false
|
||||
) {
|
||||
companion object {
|
||||
const val SECONDS_PER_TICK = 0.05F
|
||||
}
|
||||
|
||||
var value: Vec3 = Vec3.ZERO
|
||||
private set
|
||||
|
||||
private val axes = Array(3) { InertialVals() }
|
||||
// Last tick's value while following a value that's already smooth, null while easing
|
||||
private var followedFrom: Vec3? = null
|
||||
// How far along the curve one tick is
|
||||
private val step = SECONDS_PER_TICK / blendTime
|
||||
private var target = Vec3.ZERO
|
||||
|
||||
/** Moves one tick towards [newTarget] */
|
||||
fun update(newTarget: Vec3) {
|
||||
followedFrom = null
|
||||
val current = doubleArrayOf(value.x, value.y, value.z)
|
||||
val oldGoal = doubleArrayOf(target.x, target.y, target.z)
|
||||
val goal = doubleArrayOf(newTarget.x, newTarget.y, newTarget.z)
|
||||
for (i in 0..2) {
|
||||
val vals = axes[i]
|
||||
if (goal[i] != oldGoal[i]) {
|
||||
val difference = if (isAngle) Mth.wrapDegrees(goal[i] - current[i]) else goal[i] - current[i]
|
||||
// Velocity along the current curve, carried into the new one so there's no sudden change
|
||||
smoothstepSolve(vals, difference.toFloat(), smoothstepDt(vals.t, vals.s))
|
||||
vals.offset = current[i].toFloat() - smoothstep(vals.t, vals.s)
|
||||
}
|
||||
vals.t += step
|
||||
current[i] = (vals.offset + smoothstep(vals.t, vals.s)).toDouble()
|
||||
if (isAngle) {
|
||||
// Keep turning angles in range so they don't grow forever
|
||||
val turns = current[i] - Mth.wrapDegrees(current[i])
|
||||
if (turns != 0.0) {
|
||||
vals.offset -= turns.toFloat()
|
||||
current[i] -= turns
|
||||
}
|
||||
}
|
||||
}
|
||||
target = newTarget
|
||||
value = Vec3(current[0], current[1], current[2])
|
||||
}
|
||||
|
||||
/** Takes [to] as is for something already smooth. Still blends from last tick's value between ticks */
|
||||
fun follow(to: Vec3) {
|
||||
val from = value
|
||||
snap(to)
|
||||
followedFrom = from
|
||||
}
|
||||
|
||||
/** Jumps straight to [to] with no easing */
|
||||
fun snap(to: Vec3) {
|
||||
followedFrom = null
|
||||
val components = floatArrayOf(to.x.toFloat(), to.y.toFloat(), to.z.toFloat())
|
||||
for (i in 0..2) {
|
||||
axes[i].t = 1F
|
||||
axes[i].s = 0F
|
||||
axes[i].offset = components[i]
|
||||
}
|
||||
target = to
|
||||
value = to
|
||||
}
|
||||
|
||||
/** Value between last tick and this one read straight off the curve. [lookBackTick] isn't used anymore */
|
||||
fun getInterpolated(partialTick: Double, @Suppress("UNUSED_PARAMETER") lookBackTick: Int = 0): Vec3 {
|
||||
followedFrom?.let { from ->
|
||||
if (!isAngle) return from.add(value.subtract(from).scale(partialTick))
|
||||
// Short way around for angles
|
||||
return Vec3(
|
||||
Mth.wrapDegrees(from.x + Mth.wrapDegrees(value.x - from.x) * partialTick),
|
||||
Mth.wrapDegrees(from.y + Mth.wrapDegrees(value.y - from.y) * partialTick),
|
||||
Mth.wrapDegrees(from.z + Mth.wrapDegrees(value.z - from.z) * partialTick)
|
||||
)
|
||||
}
|
||||
val back = step * (1 - partialTick.toFloat())
|
||||
val x = axes[0].let { it.offset + smoothstep(it.t - back, it.s) }.toDouble()
|
||||
val y = axes[1].let { it.offset + smoothstep(it.t - back, it.s) }.toDouble()
|
||||
val z = axes[2].let { it.offset + smoothstep(it.t - back, it.s) }.toDouble()
|
||||
// Can land just past ±180 partway through a tick
|
||||
return if (isAngle) Vec3(Mth.wrapDegrees(x), Mth.wrapDegrees(y), Mth.wrapDegrees(z)) else Vec3(x, y, z)
|
||||
}
|
||||
}
|
||||
@@ -1,68 +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.riding.util
|
||||
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* A spring-based smoother for Vec3 values, designed for applying damped spring motion to riding data.
|
||||
* Stores the previous and current values, along with a history buffer for lookback-based interpolation.
|
||||
*
|
||||
* @author Jackowes
|
||||
* @since March 27th, 2025
|
||||
*/
|
||||
|
||||
|
||||
class Vec3Spring(
|
||||
var value: Vec3 = Vec3.ZERO,
|
||||
var velocity: Vec3 = Vec3.ZERO
|
||||
) {
|
||||
val history = ArrayDeque<Vec3>()
|
||||
//Hold up to a seconds worth of values
|
||||
val lookBackSize = 20
|
||||
|
||||
fun update(target: Vec3, stiffness: Double, damping: Double) {
|
||||
|
||||
//Hooke's Law f = kx where:
|
||||
//f is force
|
||||
//k is the spring constant or stiffness of this spring
|
||||
//x is the distance from rest and in this case the difference between the most recent
|
||||
//rotation delta ( the target ) and the springs current value for delta rot
|
||||
val force = target.subtract(value).scale(stiffness)
|
||||
|
||||
//Now calculate acceleration and apply damping
|
||||
val accel = force.subtract(velocity.scale(damping))
|
||||
|
||||
velocity = velocity.add(accel.scale(1.0/20.0))
|
||||
value = value.add(velocity.scale(1.0/20.0))
|
||||
|
||||
history.addFirst(value)
|
||||
//Adding one since if we want to check max look back point it needs something to interp with.
|
||||
if( history.size > lookBackSize + 1) {
|
||||
history.removeLast()
|
||||
}
|
||||
}
|
||||
|
||||
fun getInterpolated(partialTick: Double, lookBackTick: Int): Vec3 {
|
||||
if (history.size < 2) {
|
||||
return value // or Vec3.ZERO if you prefer
|
||||
}
|
||||
|
||||
val tick = min(lookBackTick, history.size - 2)
|
||||
var ret = Vec3.ZERO
|
||||
for( i in 0..tick) {
|
||||
val older = history.get(i + 1).scale(1.0)
|
||||
val newer = history.get(i).scale(1.0)
|
||||
ret = ret.add(older.add(newer.subtract(older).scale(partialTick)))
|
||||
}
|
||||
|
||||
return ret.scale(1.0/(tick+1))
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.api.spawning
|
||||
|
||||
import com.cobblemon.mod.common.api.spawning.TimeRange.Companion.timeRanges
|
||||
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import com.cobblemon.mod.common.util.adapters.IntRangesAdapter
|
||||
|
||||
/**
|
||||
@@ -49,4 +51,25 @@ class TimeRange : IntRanges {
|
||||
|
||||
constructor() : super()
|
||||
constructor(vararg ranges: IntRange) : super(*ranges)
|
||||
|
||||
fun displayName(): MutableComponent? {
|
||||
val key = timeRanges.entries.firstOrNull { entry -> this == entry.value }?.key ?: return null
|
||||
return lang("ui.time_range.$key")
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is TimeRange) return false
|
||||
|
||||
if (other.ranges.size != this.ranges.size) return false
|
||||
other.ranges.forEachIndexed { index, otherRange ->
|
||||
if (this.ranges[index] != otherRange) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return ranges.hashCode()
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.spawning.condition
|
||||
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.world.level.Level
|
||||
|
||||
/**
|
||||
@@ -42,4 +44,7 @@ enum class MoonPhase {
|
||||
fun ofWorld(world: Level): MoonPhase = VALUES[world.moonPhase]
|
||||
}
|
||||
|
||||
fun displayName(): MutableComponent {
|
||||
return lang("ui.moon_phase.${this.name.lowercase()}")
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ class ElementalType(
|
||||
val resourceLocation: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, "ui/types.png"),
|
||||
val showdownId: String = ShowdownIdentifiable.REGEX.replace(name.lowercase(), ""),
|
||||
val typeGem: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, showdownId + "_gem"),
|
||||
val order: Int = 1000
|
||||
) : ShowdownIdentifiable {
|
||||
|
||||
override fun showdownId(): String {
|
||||
|
||||
@@ -25,7 +25,8 @@ object ElementalTypes {
|
||||
hue = 0xE8E8DA,
|
||||
textureXMultiplier = 0,
|
||||
primaryColor = 0x8FA689,
|
||||
secondaryColor = 0xFFFBE2
|
||||
secondaryColor = 0xFFFBE2,
|
||||
order = 0
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -35,7 +36,8 @@ object ElementalTypes {
|
||||
hue = 0xFF6E21,
|
||||
textureXMultiplier = 1,
|
||||
primaryColor = 0xF92D2D,
|
||||
secondaryColor = 0xF99B1D
|
||||
secondaryColor = 0xF99B1D,
|
||||
order = 1
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -45,7 +47,8 @@ object ElementalTypes {
|
||||
hue = 0x3FA5FF,
|
||||
textureXMultiplier = 2,
|
||||
primaryColor = 0x377AF6,
|
||||
secondaryColor = 0x50DDF9
|
||||
secondaryColor = 0x50DDF9,
|
||||
order = 2
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -55,7 +58,8 @@ object ElementalTypes {
|
||||
hue = 0x62D14F,
|
||||
textureXMultiplier = 3,
|
||||
primaryColor = 0x0EAE95,
|
||||
secondaryColor = 0xC6F554
|
||||
secondaryColor = 0xC6F554,
|
||||
order = 3
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -65,7 +69,8 @@ object ElementalTypes {
|
||||
hue = 0xFFD314,
|
||||
textureXMultiplier = 4,
|
||||
primaryColor = 0xF7AB19,
|
||||
secondaryColor = 0xFFF257
|
||||
secondaryColor = 0xFFF257,
|
||||
order = 4
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -75,7 +80,8 @@ object ElementalTypes {
|
||||
hue = 0x54F2F2,
|
||||
textureXMultiplier = 5,
|
||||
primaryColor = 0x41BAED,
|
||||
secondaryColor = 0x91F4F9
|
||||
secondaryColor = 0x91F4F9,
|
||||
order = 5
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -85,7 +91,8 @@ object ElementalTypes {
|
||||
hue = 0xEF565D,
|
||||
textureXMultiplier = 6,
|
||||
primaryColor = 0xC8356B,
|
||||
secondaryColor = 0xF36A3F
|
||||
secondaryColor = 0xF36A3F,
|
||||
order = 6
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -95,7 +102,8 @@ object ElementalTypes {
|
||||
hue = 0xD651FF,
|
||||
textureXMultiplier = 7,
|
||||
primaryColor = 0x7B41DD,
|
||||
secondaryColor = 0xE568F4
|
||||
secondaryColor = 0xE568F4,
|
||||
order = 7
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -105,7 +113,8 @@ object ElementalTypes {
|
||||
hue = 0xF4A453,
|
||||
textureXMultiplier = 8,
|
||||
primaryColor = 0xE08835,
|
||||
secondaryColor = 0xFFD057
|
||||
secondaryColor = 0xFFD057,
|
||||
order = 8
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -115,7 +124,8 @@ object ElementalTypes {
|
||||
hue = 0xB8B2FF,
|
||||
textureXMultiplier = 9,
|
||||
primaryColor = 0x6083F6,
|
||||
secondaryColor = 0xD5CCFA
|
||||
secondaryColor = 0xD5CCFA,
|
||||
order = 9
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -125,7 +135,8 @@ object ElementalTypes {
|
||||
hue = 0xFF5E9E,
|
||||
textureXMultiplier = 10,
|
||||
primaryColor = 0xE553CE,
|
||||
secondaryColor = 0xF69DD5
|
||||
secondaryColor = 0xF69DD5,
|
||||
order = 10
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -135,7 +146,8 @@ object ElementalTypes {
|
||||
hue = 0xD3D319,
|
||||
textureXMultiplier = 11,
|
||||
primaryColor = 0x9FBE1B,
|
||||
secondaryColor = 0xE9FB5B
|
||||
secondaryColor = 0xE9FB5B,
|
||||
order = 11
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -145,7 +157,8 @@ object ElementalTypes {
|
||||
hue = 0xB7A16E,
|
||||
textureXMultiplier = 12,
|
||||
primaryColor = 0x9F814A,
|
||||
secondaryColor = 0xEECB7A
|
||||
secondaryColor = 0xEECB7A,
|
||||
order = 12
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -155,7 +168,8 @@ object ElementalTypes {
|
||||
hue = 0x9C80F7,
|
||||
textureXMultiplier = 13,
|
||||
primaryColor = 0x4F57DD,
|
||||
secondaryColor = 0xC182F1
|
||||
secondaryColor = 0xC182F1,
|
||||
order = 13
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -165,7 +179,8 @@ object ElementalTypes {
|
||||
hue = 0x7580FF,
|
||||
textureXMultiplier = 14,
|
||||
primaryColor = 0x4E68E2,
|
||||
secondaryColor = 0x47C7F7
|
||||
secondaryColor = 0x47C7F7,
|
||||
order = 14
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -175,7 +190,8 @@ object ElementalTypes {
|
||||
hue = 0x587DA0,
|
||||
textureXMultiplier = 15,
|
||||
primaryColor = 0x465B99,
|
||||
secondaryColor = 0x689EBF
|
||||
secondaryColor = 0x689EBF,
|
||||
order = 15
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -185,7 +201,8 @@ object ElementalTypes {
|
||||
hue = 0xABD1F4,
|
||||
textureXMultiplier = 16,
|
||||
primaryColor = 0x628CB9,
|
||||
secondaryColor = 0xD6F7F3
|
||||
secondaryColor = 0xD6F7F3,
|
||||
order = 16
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -195,18 +212,21 @@ object ElementalTypes {
|
||||
hue = 0xFF7FE5,
|
||||
textureXMultiplier = 17,
|
||||
primaryColor = 0xE2678C,
|
||||
secondaryColor = 0xF5A7B7
|
||||
secondaryColor = 0xF5A7B7,
|
||||
order = 17
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
fun register(name: String, displayName: MutableComponent, hue: Int, textureXMultiplier: Int, primaryColor: Int, secondaryColor: Int): ElementalType {
|
||||
@JvmOverloads
|
||||
fun register(name: String, displayName: MutableComponent, hue: Int, textureXMultiplier: Int, primaryColor: Int, secondaryColor: Int, order: Int = 1000): ElementalType {
|
||||
return ElementalType(
|
||||
name = name,
|
||||
displayName = displayName,
|
||||
hue = hue,
|
||||
textureXMultiplier = textureXMultiplier,
|
||||
primaryColor = primaryColor,
|
||||
secondaryColor = secondaryColor
|
||||
secondaryColor = secondaryColor,
|
||||
order = order
|
||||
).also {
|
||||
allTypes.add(it)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block.decor
|
||||
|
||||
import com.cobblemon.mod.common.util.rotateShape
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.util.StringRepresentable
|
||||
import net.minecraft.world.InteractionResult
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.block.*
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.level.block.state.properties.BooleanProperty
|
||||
import net.minecraft.world.level.gameevent.GameEvent
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import net.minecraft.world.level.material.Fluids
|
||||
import net.minecraft.world.phys.BlockHitResult
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class ActivatableDecorationBlock(settings: Properties, val type: ActivatableDecorationBlockType) : HorizontalDirectionalBlock(settings), SimpleWaterloggedBlock {
|
||||
enum class ActivatableDecorationBlockType: StringRepresentable {
|
||||
//For basic use, also ignore camelcase errors, it's just dumb
|
||||
CENTERED_8x,
|
||||
//For specific items
|
||||
METRONOME;
|
||||
|
||||
override fun getSerializedName(): String = this.name.lowercase()
|
||||
|
||||
companion object {
|
||||
val CODEC = StringRepresentable.fromEnum(::values)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC: MapCodec<ActivatableDecorationBlock> = RecordCodecBuilder.mapCodec { it.group(
|
||||
propertiesCodec(),
|
||||
ActivatableDecorationBlockType.CODEC.fieldOf("type").forGetter(ActivatableDecorationBlock::type)
|
||||
).apply(it, ::ActivatableDecorationBlock) }
|
||||
val ACTIVE = BooleanProperty.create("active")
|
||||
|
||||
val CENTERED_8x = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.5, 0.75)
|
||||
val METRONOME_HITBOX = Shapes.box(0.25, 0.0, 0.375, 0.75, 0.8125, 0.75)
|
||||
|
||||
fun getShape(type: ActivatableDecorationBlockType): VoxelShape {
|
||||
return when (type) {
|
||||
ActivatableDecorationBlockType.METRONOME -> METRONOME_HITBOX
|
||||
else -> CENTERED_8x
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(HORIZONTAL_FACING, Direction.NORTH)
|
||||
.setValue(ACTIVE, false)
|
||||
.setValue(WATERLOGGED, false)
|
||||
)
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<ActivatableDecorationBlock> = CODEC
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(HORIZONTAL_FACING, ACTIVE, WATERLOGGED)
|
||||
}
|
||||
|
||||
override fun getFluidState(blockState: BlockState): FluidState? = if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState)
|
||||
|
||||
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
|
||||
if (state.getValue(WATERLOGGED)) {
|
||||
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
|
||||
}
|
||||
return if (state.getValue(HORIZONTAL_FACING).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
|
||||
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
|
||||
}
|
||||
|
||||
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
|
||||
val direction = state.getValue(HORIZONTAL_FACING)
|
||||
return rotateShape(Direction.NORTH, direction, getShape(type))
|
||||
}
|
||||
|
||||
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
|
||||
return getCollisionShape(state, level, pos, context).singleEncompassing()
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
|
||||
val fluidState = context.level.getFluidState(context.clickedPos)
|
||||
return super.getStateForPlacement(context)?.setValue(HORIZONTAL_FACING, context.horizontalDirection.opposite)?.setValue(WATERLOGGED, fluidState.type === Fluids.WATER)
|
||||
}
|
||||
|
||||
override fun useWithoutItem(
|
||||
state: BlockState,
|
||||
level: Level,
|
||||
pos: BlockPos,
|
||||
player: Player,
|
||||
hitResult: BlockHitResult
|
||||
): InteractionResult {
|
||||
if (level.isClientSide) {
|
||||
level.setBlockAndUpdate(pos, state.cycle(ACTIVE))
|
||||
return InteractionResult.SUCCESS
|
||||
} else {
|
||||
this.push(state, level, pos, null as Player?)
|
||||
return InteractionResult.CONSUME
|
||||
}
|
||||
}
|
||||
|
||||
fun push(state: BlockState, level: Level, pos: BlockPos?, player: Player?) {
|
||||
var state = state
|
||||
state = state.cycle(ACTIVE)
|
||||
level.setBlock(pos!!, state, 3)
|
||||
level.gameEvent(player, if (state.getValue(ACTIVE)) GameEvent.BLOCK_ACTIVATE else GameEvent.BLOCK_DEACTIVATE, pos)
|
||||
}
|
||||
|
||||
override fun hasAnalogOutputSignal(state: BlockState): Boolean {
|
||||
return true
|
||||
}
|
||||
//TODO: Replace this forced 4 with a value that can be set by the block, only relevant when more blocks of this class are added.
|
||||
override fun getAnalogOutputSignal(state: BlockState, level: Level, pos: BlockPos): Int {
|
||||
return if (level.getBlockState(pos).getValue(ACTIVE) as Boolean) 4 else 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block
|
||||
|
||||
import com.cobblemon.mod.common.util.rotateShape
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.util.StringRepresentable
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.HorizontalDirectionalBlock
|
||||
import net.minecraft.world.level.block.SimpleWaterloggedBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import net.minecraft.world.level.material.Fluids
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class DecorativeItemBlock(settings: Properties, val type: DecorativeItemBlockType): HorizontalDirectionalBlock(settings), SimpleWaterloggedBlock {
|
||||
|
||||
enum class DecorativeItemBlockType: StringRepresentable {
|
||||
//For basic use, also ignore camelcase errors, it's just dumb
|
||||
CENTERED_8x,
|
||||
CENTERED_6x,
|
||||
FLATTENED_8x6,
|
||||
FLATTENED_8x4,
|
||||
FLATTENED_10x4,
|
||||
//For specific items
|
||||
TEACUP,
|
||||
TEAPOT,
|
||||
KNOT,
|
||||
POUCH,
|
||||
NEVERMELT,
|
||||
SCOPE,
|
||||
WIDE,
|
||||
ZOOM,
|
||||
IZER;
|
||||
|
||||
override fun getSerializedName(): String = this.name.lowercase()
|
||||
|
||||
companion object {
|
||||
val CODEC = StringRepresentable.fromEnum(::values)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC: MapCodec<DecorativeItemBlock> = RecordCodecBuilder.mapCodec { it.group(
|
||||
propertiesCodec(),
|
||||
DecorativeItemBlockType.CODEC.fieldOf("type").forGetter(DecorativeItemBlock::type)
|
||||
).apply(it, ::DecorativeItemBlock) }
|
||||
|
||||
val CENTERED_8x = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.5, 0.75)
|
||||
val CENTERED_6x = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875)
|
||||
val FLAT6_HITBOX = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.375, 0.75)
|
||||
val FLAT4_HITBOX = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.25, 0.75)
|
||||
val SMOOSHED4_HITBOX = Shapes.box(0.1875, 0.0, 0.1875, 0.8125, 0.25, 0.8125)
|
||||
val TEACUP_HITBOX = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.25, 0.6875)
|
||||
val TEAPOT_HITBOX = Shapes.box(0.1875, 0.0, 0.1875, 0.8125, 0.625, 0.8125)
|
||||
val KNOT_HITBOX = Shapes.box(0.375, 0.0, 0.375, 0.75, 0.375, 0.75)
|
||||
val POUCH_HITBOX = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.3125, 0.6875)
|
||||
val NEVERMELT_HITBOX = Shapes.box(0.25, 0.0, 0.3125, 0.6875, 0.25, 0.75)
|
||||
val SCOPE_HITBOX = Shapes.box(0.625, 0.0, 0.4375, 0.9375, 0.125, 0.75)
|
||||
val WIDE_HITBOX = Shapes.box(0.625, 0.0, 0.125, 0.9375, 0.0625, 0.625)
|
||||
val ZOOM_HITBOX = Shapes.box(0.125, 0.0, 0.1875, 0.375, 0.1875, 0.5)
|
||||
val IZER_HITBOX = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.375, 0.75)
|
||||
|
||||
fun getShape(type: DecorativeItemBlockType): VoxelShape {
|
||||
return when (type) {
|
||||
DecorativeItemBlockType.TEAPOT -> TEAPOT_HITBOX
|
||||
DecorativeItemBlockType.TEACUP -> TEACUP_HITBOX
|
||||
DecorativeItemBlockType.KNOT -> KNOT_HITBOX
|
||||
DecorativeItemBlockType.POUCH -> POUCH_HITBOX
|
||||
DecorativeItemBlockType.NEVERMELT -> NEVERMELT_HITBOX
|
||||
DecorativeItemBlockType.SCOPE -> SCOPE_HITBOX
|
||||
DecorativeItemBlockType.WIDE -> WIDE_HITBOX
|
||||
DecorativeItemBlockType.ZOOM -> ZOOM_HITBOX
|
||||
DecorativeItemBlockType.IZER -> IZER_HITBOX
|
||||
DecorativeItemBlockType.FLATTENED_10x4 -> SMOOSHED4_HITBOX
|
||||
DecorativeItemBlockType.FLATTENED_8x4 -> FLAT4_HITBOX
|
||||
DecorativeItemBlockType.FLATTENED_8x6 -> FLAT6_HITBOX
|
||||
DecorativeItemBlockType.CENTERED_6x -> CENTERED_6x
|
||||
else -> CENTERED_8x
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(HORIZONTAL_FACING, Direction.NORTH)
|
||||
.setValue(WATERLOGGED, false))
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<DecorativeItemBlock> = CODEC
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(HORIZONTAL_FACING, WATERLOGGED)
|
||||
}
|
||||
|
||||
override fun getFluidState(blockState: BlockState): FluidState? = if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState)
|
||||
|
||||
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
|
||||
if (state.getValue(WATERLOGGED)) {
|
||||
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
|
||||
}
|
||||
return if (state.getValue(HORIZONTAL_FACING).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
|
||||
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
|
||||
}
|
||||
|
||||
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
|
||||
val direction = state.getValue(HORIZONTAL_FACING)
|
||||
return rotateShape(Direction.NORTH, direction, getShape(type))
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
|
||||
val fluidState = context.level.getFluidState(context.clickedPos)
|
||||
return super.getStateForPlacement(context)?.setValue(HORIZONTAL_FACING, context.horizontalDirection.opposite)?.setValue(WATERLOGGED, fluidState.type === Fluids.WATER)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block
|
||||
|
||||
import com.cobblemon.mod.common.util.rotateShape
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.ButtonBlock
|
||||
import net.minecraft.world.level.block.SimpleWaterloggedBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.AttachFace
|
||||
import net.minecraft.world.level.block.state.properties.BlockSetType
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import net.minecraft.world.level.material.Fluids
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class EjectButtonBlock(properties: Properties, ticksToStayPressed: Int) : ButtonBlock(BlockSetType.IRON, ticksToStayPressed, properties), SimpleWaterloggedBlock {
|
||||
companion object {
|
||||
val FLOOR_UNPOWERED = Shapes.or(
|
||||
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.1875, 0.875),
|
||||
Shapes.box(0.375, 0.1875, 0.375, 0.625, 0.3125, 0.625) )
|
||||
val FLOOR_POWERED = Shapes.or(
|
||||
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.1875, 0.875),
|
||||
Shapes.box(0.375, 0.1875, 0.375, 0.625, 0.25, 0.625) )
|
||||
val FLOOR_PANEL = Shapes.box(0.125, 0.0, 0.125, 0.875, 0.1875, 0.875)
|
||||
val CEILING_UNPOWERED = Shapes.or(
|
||||
Shapes.box(0.125, 0.8125, 0.125, 0.875, 1.0, 0.875),
|
||||
Shapes.box(0.375, 0.6875, 0.375, 0.625, 0.8125, 0.625) )
|
||||
val CEILING_POWERED = Shapes.or(
|
||||
Shapes.box(0.125, 0.8125, 0.125, 0.875, 1.0, 0.875),
|
||||
Shapes.box(0.375, 0.75, 0.375, 0.625, 0.8125, 0.625) )
|
||||
val CEILING_PANEL = Shapes.box(0.125, 0.8125, 0.125, 0.875, 1.0, 0.875)
|
||||
val WALL_UNPOWERED = Shapes.or(
|
||||
Shapes.box(0.125, 0.125, 0.8125, 0.875, 0.875, 1.0),
|
||||
Shapes.box(0.375, 0.375, 0.6875, 0.625, 0.625, 0.8125) )
|
||||
val WALL_POWERED = Shapes.or(
|
||||
Shapes.box(0.125, 0.125, 0.8125, 0.875, 0.875, 1.0),
|
||||
Shapes.box(0.375, 0.375, 0.75, 0.625, 0.625, 0.8125) )
|
||||
val WALL_PANEL = Shapes.box(0.125, 0.125, 0.8125, 0.875, 0.875, 1.0)
|
||||
}
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(FACING, Direction.NORTH)
|
||||
.setValue(FACE, AttachFace.WALL)
|
||||
.setValue(POWERED, false)
|
||||
.setValue(WATERLOGGED, false)
|
||||
)
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(FACING, FACE, POWERED, WATERLOGGED)
|
||||
}
|
||||
|
||||
override fun getFluidState(blockState: BlockState): FluidState? {
|
||||
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState);
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
|
||||
val fluidState = context.level.getFluidState(context.clickedPos)
|
||||
for (direction in context.nearestLookingDirections) {
|
||||
val blockState: BlockState
|
||||
if (direction.getAxis() === Direction.Axis.Y) {
|
||||
blockState = (defaultBlockState()
|
||||
.setValue(FACE, if (direction == Direction.UP) AttachFace.CEILING else AttachFace.FLOOR) as BlockState)
|
||||
.setValue(FACING, context.horizontalDirection.opposite)
|
||||
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
|
||||
) as BlockState
|
||||
} else {
|
||||
blockState = (defaultBlockState()
|
||||
.setValue(FACE, AttachFace.WALL) as BlockState)
|
||||
.setValue(FACING, direction.opposite)
|
||||
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
|
||||
) as BlockState
|
||||
}
|
||||
if (blockState.canSurvive(context.getLevel(), context.getClickedPos())) {
|
||||
return blockState
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
|
||||
val direction = state.getValue(FACING)
|
||||
return when (state.getValue(FACE)) {
|
||||
AttachFace.FLOOR -> if (state.getValue(POWERED)) {FLOOR_POWERED} else {FLOOR_UNPOWERED}
|
||||
AttachFace.WALL ->
|
||||
when (direction) {
|
||||
Direction.NORTH, Direction.UP, Direction.DOWN -> if (state.getValue(POWERED)) {WALL_POWERED} else {WALL_UNPOWERED}
|
||||
else -> rotateShape(Direction.NORTH, direction, if (state.getValue(POWERED)) {WALL_POWERED} else {WALL_UNPOWERED})
|
||||
}
|
||||
AttachFace.CEILING -> if (state.getValue(POWERED)) {CEILING_POWERED} else {CEILING_UNPOWERED}
|
||||
}
|
||||
}
|
||||
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
|
||||
val direction = state.getValue(FACING)
|
||||
return when (state.getValue(FACE)) {
|
||||
AttachFace.FLOOR -> FLOOR_PANEL
|
||||
AttachFace.WALL -> when (direction) {
|
||||
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_PANEL
|
||||
else -> rotateShape(Direction.NORTH, direction, WALL_PANEL)
|
||||
}
|
||||
AttachFace.CEILING -> CEILING_PANEL
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
|
||||
if (state.getValue(WATERLOGGED)) {
|
||||
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
|
||||
}
|
||||
return if (getConnectedDirection(state).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
|
||||
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
|
||||
}
|
||||
|
||||
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
|
||||
return if (state.getValue(FACE) != AttachFace.WALL) {
|
||||
val direction = getConnectedDirection(state)
|
||||
canSupportCenter(level, pos.relative(direction.opposite), direction)
|
||||
} else {
|
||||
level.getBlockState(pos.relative(state.getValue(FACING).opposite)).isSolid
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block
|
||||
|
||||
import com.cobblemon.mod.common.util.rotateShape
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.properties.AttachFace
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class GalaricaWreathBlock(properties: Properties) : SixFaceRotationalBlock(properties) {
|
||||
companion object {
|
||||
val CODEC: MapCodec<GalaricaWreathBlock> = RecordCodecBuilder.mapCodec {
|
||||
it.group(propertiesCodec()).apply(it, ::GalaricaWreathBlock)
|
||||
}
|
||||
|
||||
val FLOOR_NORTH = Shapes.box(0.125, 0.0, 0.125, 0.875, 0.25, 0.875)
|
||||
val WALL_NORTH = Shapes.box(0.125, 0.125, 0.75, 0.875, 0.875, 1.0)
|
||||
val CEILING_NORTH = Shapes.box(0.125, 0.125, 0.375, 0.875, 0.625, 0.5625)
|
||||
}
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(FACING, Direction.NORTH)
|
||||
.setValue(FACE, AttachFace.WALL)
|
||||
.setValue(WATERLOGGED, false)
|
||||
)
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<GalaricaWreathBlock> = CODEC
|
||||
|
||||
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
|
||||
val direction = state.getValue(FACING)
|
||||
return when (state.getValue(FACE)) {
|
||||
AttachFace.FLOOR ->
|
||||
if (direction.axis === Direction.Axis.X) {
|
||||
rotateShape(Direction.NORTH, Direction.WEST, FLOOR_NORTH)
|
||||
} else FLOOR_NORTH
|
||||
AttachFace.WALL ->
|
||||
when (direction) {
|
||||
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_NORTH
|
||||
else -> rotateShape(Direction.NORTH, direction, WALL_NORTH)
|
||||
}
|
||||
AttachFace.CEILING ->
|
||||
if (direction.axis === Direction.Axis.X) {
|
||||
rotateShape(Direction.NORTH, Direction.WEST, CEILING_NORTH)
|
||||
} else CEILING_NORTH
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block
|
||||
|
||||
import com.cobblemon.mod.common.util.rotateShape
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.properties.AttachFace
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class MagnetBlock(properties: Properties) : SixFaceRotationalBlock(properties) {
|
||||
companion object {
|
||||
val CODEC: MapCodec<MagnetBlock> = RecordCodecBuilder.mapCodec {
|
||||
it.group(propertiesCodec()).apply(it, ::MagnetBlock)
|
||||
}
|
||||
|
||||
val FLOOR_NORTH = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.1875, 0.75)
|
||||
val WALL_NORTH = Shapes.box(0.25, 0.25, 0.8125, 0.75, 0.75, 1.0)
|
||||
val CEILING_NORTH = Shapes.box(0.25, 0.8125, 0.25, 0.75, 1.0, 0.75)
|
||||
}
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(FACING, Direction.NORTH)
|
||||
.setValue(FACE, AttachFace.WALL)
|
||||
.setValue(WATERLOGGED, false)
|
||||
)
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<MagnetBlock> = CODEC
|
||||
|
||||
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
|
||||
val direction = state.getValue(FACING)
|
||||
return when (state.getValue(FACE)) {
|
||||
AttachFace.FLOOR ->
|
||||
if (direction.axis === Direction.Axis.X) {
|
||||
rotateShape(Direction.NORTH, Direction.WEST, FLOOR_NORTH)
|
||||
} else FLOOR_NORTH
|
||||
AttachFace.WALL ->
|
||||
when (direction) {
|
||||
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_NORTH
|
||||
else -> rotateShape(Direction.NORTH, direction, WALL_NORTH)
|
||||
}
|
||||
AttachFace.CEILING ->
|
||||
if (direction.axis === Direction.Axis.X) {
|
||||
rotateShape(Direction.NORTH, Direction.WEST, CEILING_NORTH)
|
||||
} else CEILING_NORTH
|
||||
}
|
||||
}
|
||||
|
||||
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -103,15 +103,15 @@ class MintBlock(private val mintType: MintType, settings: Properties) : CropBloc
|
||||
GREEN -> CobblemonItems.GREEN_MINT_SEEDS
|
||||
WHITE -> CobblemonItems.WHITE_MINT_SEEDS
|
||||
}
|
||||
|
||||
fun getLeaf(): MintLeafItem = when (this) {
|
||||
RED -> CobblemonItems.RED_MINT_LEAF
|
||||
BLUE -> CobblemonItems.BLUE_MINT_LEAF
|
||||
CYAN -> CobblemonItems.CYAN_MINT_LEAF
|
||||
PINK -> CobblemonItems.PINK_MINT_LEAF
|
||||
GREEN -> CobblemonItems.GREEN_MINT_LEAF
|
||||
WHITE -> CobblemonItems.WHITE_MINT_LEAF
|
||||
}
|
||||
// These don't seem to get called to anywhere besides the direct item, and since I'm trying to do funny stuff there, just commenting this out
|
||||
// fun getLeaf(): MintLeafItem = when (this) {
|
||||
// RED -> CobblemonItems.RED_MINT_LEAF
|
||||
// BLUE -> CobblemonItems.BLUE_MINT_LEAF
|
||||
// CYAN -> CobblemonItems.CYAN_MINT_LEAF
|
||||
// PINK -> CobblemonItems.PINK_MINT_LEAF
|
||||
// GREEN -> CobblemonItems.GREEN_MINT_LEAF
|
||||
// WHITE -> CobblemonItems.WHITE_MINT_LEAF
|
||||
// }
|
||||
|
||||
fun getCropBlock(): MintBlock = when (this) {
|
||||
RED -> CobblemonBlocks.RED_MINT
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block
|
||||
|
||||
import com.cobblemon.mod.common.util.rotateShape
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.properties.AttachFace
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class OrbBlock(properties: Properties) : SixFaceRotationalBlock(properties) {
|
||||
companion object {
|
||||
val CODEC: MapCodec<OrbBlock> = RecordCodecBuilder.mapCodec {
|
||||
it.group(propertiesCodec()).apply(it, ::OrbBlock)
|
||||
}
|
||||
|
||||
val FLOOR_NORTH = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875)
|
||||
val WALL_NORTH = Shapes.box(0.3125, 0.3125, 0.625, 0.6875, 0.6875, 1.0)
|
||||
val CEILING_NORTH = Shapes.box(0.3125, 0.625, 0.375, 0.6875, 1.0, 0.6875)
|
||||
}
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(FACING, Direction.NORTH)
|
||||
.setValue(FACE, AttachFace.WALL)
|
||||
.setValue(WATERLOGGED, false)
|
||||
)
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<OrbBlock> = CODEC
|
||||
|
||||
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
|
||||
val direction = state.getValue(FACING)
|
||||
return when (state.getValue(FACE)) {
|
||||
AttachFace.FLOOR ->
|
||||
if (direction.axis === Direction.Axis.X) {
|
||||
rotateShape(Direction.NORTH, Direction.WEST, FLOOR_NORTH)
|
||||
} else FLOOR_NORTH
|
||||
AttachFace.WALL ->
|
||||
when (direction) {
|
||||
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_NORTH
|
||||
else -> rotateShape(Direction.NORTH, direction, WALL_NORTH)
|
||||
}
|
||||
AttachFace.CEILING ->
|
||||
if (direction.axis === Direction.Axis.X) {
|
||||
rotateShape(Direction.NORTH, Direction.WEST, CEILING_NORTH)
|
||||
} else CEILING_NORTH
|
||||
}
|
||||
}
|
||||
|
||||
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -13,25 +13,15 @@ import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.FaceAttachedHorizontalDirectionalBlock
|
||||
import net.minecraft.world.level.block.SimpleWaterloggedBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.AttachFace
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import net.minecraft.world.level.material.Fluids
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class PlaqueBlock(properties : Properties) : FaceAttachedHorizontalDirectionalBlock(properties), SimpleWaterloggedBlock {
|
||||
class PlaqueBlock(properties : Properties) : SixFaceRotationalBlock(properties) {
|
||||
companion object {
|
||||
val CODEC: MapCodec<PlaqueBlock> = RecordCodecBuilder.mapCodec {
|
||||
it.group(propertiesCodec()).apply(it, ::PlaqueBlock)
|
||||
@@ -54,40 +44,6 @@ class PlaqueBlock(properties : Properties) : FaceAttachedHorizontalDirectionalBl
|
||||
)
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(FACING, FACE, WATERLOGGED)
|
||||
}
|
||||
|
||||
override fun getFluidState(blockState: BlockState): FluidState? {
|
||||
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState);
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
|
||||
val fluidState = context.level.getFluidState(context.clickedPos)
|
||||
for (direction in context.nearestLookingDirections) {
|
||||
val blockState: BlockState
|
||||
if (direction.getAxis() === Direction.Axis.Y) {
|
||||
blockState = (defaultBlockState()
|
||||
.setValue(FACE, if (direction == Direction.UP) AttachFace.CEILING else AttachFace.FLOOR) as BlockState)
|
||||
.setValue(FACING, context.horizontalDirection.opposite)
|
||||
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
|
||||
) as BlockState
|
||||
} else {
|
||||
blockState = (defaultBlockState()
|
||||
.setValue(FACE, AttachFace.WALL) as BlockState)
|
||||
.setValue(FACING, direction.opposite)
|
||||
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
|
||||
) as BlockState
|
||||
|
||||
}
|
||||
|
||||
if (blockState.canSurvive(context.getLevel(), context.getClickedPos())) {
|
||||
return blockState
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<PlaqueBlock> = CODEC
|
||||
|
||||
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
|
||||
@@ -108,21 +64,4 @@ class PlaqueBlock(properties : Properties) : FaceAttachedHorizontalDirectionalBl
|
||||
} else CEILING_NORTH
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
|
||||
if (state.getValue(WATERLOGGED)) {
|
||||
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
|
||||
}
|
||||
return if (getConnectedDirection(state).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
|
||||
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
|
||||
}
|
||||
|
||||
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
|
||||
return if (state.getValue(FACE) == AttachFace.CEILING) {
|
||||
val direction = getConnectedDirection(state)
|
||||
canSupportCenter(level, pos.relative(direction.opposite), direction)
|
||||
} else {
|
||||
super.canSurvive(state, level, pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block.decor
|
||||
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.*
|
||||
import net.minecraft.world.level.block.HorizontalDirectionalBlock.FACING
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.level.block.state.properties.Property
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import net.minecraft.world.level.material.Fluids
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class RingTargetBlock(settings: Properties) : TargetBlock(settings), SimpleWaterloggedBlock {
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(FACING, Direction.NORTH)
|
||||
.setValue(WATERLOGGED, false)
|
||||
.setValue(OUTPUT_POWER, 0))
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(ctx: BlockPlaceContext): BlockState? {
|
||||
var blockState = defaultBlockState()
|
||||
.setValue(WATERLOGGED, ctx.level.getFluidState(ctx.clickedPos).type == Fluids.WATER)
|
||||
val worldView = ctx.level
|
||||
val blockPos = ctx.clickedPos
|
||||
ctx.nearestLookingDirections.forEach { direction ->
|
||||
if (direction.axis.isHorizontal) {
|
||||
blockState = blockState.setValue(FACING, direction.opposite) as BlockState
|
||||
if (blockState.canSurvive(worldView, blockPos)) {
|
||||
return blockState
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getFluidState(blockState: BlockState): FluidState? {
|
||||
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState)
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(FACING)
|
||||
builder.add(WATERLOGGED)
|
||||
builder.add(*arrayOf<Property<*>>(OUTPUT_POWER))
|
||||
}
|
||||
|
||||
override fun getShape(
|
||||
state: BlockState,
|
||||
blockGetter: BlockGetter,
|
||||
pos: BlockPos,
|
||||
collisionContext: CollisionContext
|
||||
): VoxelShape {
|
||||
return when (state.getValue(FACING)) {
|
||||
Direction.SOUTH -> FULL_PLANE_SOUTH
|
||||
Direction.WEST -> FULL_PLANE_WEST
|
||||
Direction.EAST -> FULL_PLANE_EAST
|
||||
else -> FULL_PLANE_NORTH
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateShape(
|
||||
state: BlockState,
|
||||
direction: Direction,
|
||||
neighborState: BlockState,
|
||||
world: LevelAccessor,
|
||||
pos: BlockPos,
|
||||
neighborPos: BlockPos
|
||||
): BlockState {
|
||||
if (state.getValue(WATERLOGGED)) {
|
||||
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
|
||||
}
|
||||
return if (direction.opposite == state.getValue(FACING) && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
|
||||
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
|
||||
}
|
||||
|
||||
override fun rotate(state: BlockState, rotation: Rotation): BlockState {
|
||||
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)))
|
||||
}
|
||||
|
||||
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
|
||||
return level.getBlockState(pos.relative(state.getValue(FACING).opposite)).isSolid
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC: MapCodec<RingTargetBlock> = RecordCodecBuilder.mapCodec {
|
||||
it.group(
|
||||
propertiesCodec()
|
||||
).apply(it, ::RingTargetBlock) }
|
||||
|
||||
val OUTPUT_POWER = BlockStateProperties.POWER;
|
||||
|
||||
val FULL_PLANE_NORTH = Shapes.box(0.0, 0.0, 0.925, 1.0, 1.0, 1.0)
|
||||
val FULL_PLANE_WEST = Shapes.box(0.925, 0.0, 0.0, 1.0, 1.0, 1.0)
|
||||
val FULL_PLANE_SOUTH = Shapes.box(0.0, 0.0, 0.0, 1.0, 1.0, 0.075)
|
||||
val FULL_PLANE_EAST = Shapes.box(0.0, 0.0, 0.0, 0.075, 1.0, 1.0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block
|
||||
|
||||
import com.mojang.serialization.MapCodec
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.FaceAttachedHorizontalDirectionalBlock
|
||||
import net.minecraft.world.level.block.SimpleWaterloggedBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.AttachFace
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import net.minecraft.world.level.material.Fluids
|
||||
|
||||
open class SixFaceRotationalBlock(properties : Properties) : FaceAttachedHorizontalDirectionalBlock(properties), SimpleWaterloggedBlock {
|
||||
companion object {
|
||||
val CODEC = simpleCodec(::SixFaceRotationalBlock)
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<out SixFaceRotationalBlock> = CODEC
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(FACING, FACE, WATERLOGGED)
|
||||
}
|
||||
|
||||
override fun getFluidState(blockState: BlockState): FluidState? {
|
||||
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState);
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
|
||||
val fluidState = context.level.getFluidState(context.clickedPos)
|
||||
for (direction in context.nearestLookingDirections) {
|
||||
val blockState: BlockState
|
||||
if (direction.getAxis() === Direction.Axis.Y) {
|
||||
blockState = (defaultBlockState()
|
||||
.setValue(FACE, if (direction == Direction.UP) AttachFace.CEILING else AttachFace.FLOOR) as BlockState)
|
||||
.setValue(FACING, context.horizontalDirection.opposite)
|
||||
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
|
||||
) as BlockState
|
||||
} else {
|
||||
blockState = (defaultBlockState()
|
||||
.setValue(FACE, AttachFace.WALL) as BlockState)
|
||||
.setValue(FACING, direction.opposite)
|
||||
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
|
||||
) as BlockState
|
||||
|
||||
}
|
||||
|
||||
if (blockState.canSurvive(context.getLevel(), context.getClickedPos())) {
|
||||
return blockState
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
|
||||
if (state.getValue(WATERLOGGED)) {
|
||||
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
|
||||
}
|
||||
return if (getConnectedDirection(state).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
|
||||
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
|
||||
}
|
||||
|
||||
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
|
||||
return if (state.getValue(FACE) == AttachFace.CEILING) {
|
||||
val direction = getConnectedDirection(state)
|
||||
canSupportCenter(level, pos.relative(direction.opposite), direction)
|
||||
} else if (state.getValue(FACE) == AttachFace.WALL) {
|
||||
level.getBlockState(pos.relative(state.getValue(FACING).opposite)).isSolid
|
||||
} else {
|
||||
super.canSurvive(state, level, pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,15 @@ class StackableItemBlock(settings: Properties, val type: StackableItemBlockType)
|
||||
ETHER,
|
||||
MAX_POTION,
|
||||
POTION,
|
||||
STATUS_RESTORE;
|
||||
STATUS_RESTORE,
|
||||
VITAMIN,
|
||||
METAL_COAT,
|
||||
IRON_BALL,
|
||||
THROAT_SPRAY,
|
||||
UPGRADE,
|
||||
DICE,
|
||||
GLASS_BOTTLE,
|
||||
EXTENDER;
|
||||
|
||||
override fun getSerializedName(): String = this.name.lowercase()
|
||||
|
||||
@@ -57,98 +65,90 @@ class StackableItemBlock(settings: Properties, val type: StackableItemBlockType)
|
||||
StackableItemBlockType.CODEC.fieldOf("type").forGetter(StackableItemBlock::type)
|
||||
).apply(it, ::StackableItemBlock) }
|
||||
|
||||
val northShapeMap: Map<StackableItemBlockType, List<VoxelShape>> = mapOf(
|
||||
StackableItemBlockType.BATTLE_ITEM to listOf(
|
||||
Shapes.box(0.34375, 0.0, 0.34375, 0.65625, 0.4375, 0.65625),
|
||||
Shapes.or(
|
||||
Shapes.box(0.09375, 0.0, 0.34375, 0.40625, 0.4375, 0.65625),
|
||||
Shapes.box(0.59375, 0.0, 0.40625, 0.90625, 0.4375, 0.71875)
|
||||
val northShapeMap: Map<StackableItemBlockType, List<VoxelShape>>
|
||||
get() = mapOf(
|
||||
StackableItemBlockType.BATTLE_ITEM to listOf(
|
||||
Shapes.box(0.34375, 0.0, 0.34375, 0.65625, 0.4375, 0.65625),
|
||||
Shapes.box(0.09375, 0.0, 0.34375, 0.90625, 0.4375, 0.71875),
|
||||
Shapes.box(0.09375, 0.0, 0.09375, 0.90625, 0.4375, 0.90625),
|
||||
Shapes.box(0.09375, 0.0, 0.09375, 0.96875, 0.4375, 0.90625)
|
||||
),
|
||||
Shapes.or(
|
||||
Shapes.box(0.59375, 0.0, 0.59375, 0.90625, 0.4375, 0.90625),
|
||||
Shapes.box(0.09375, 0.0, 0.53125, 0.40625, 0.4375, 0.84375),
|
||||
Shapes.box(0.34375, 0.0, 0.09375, 0.65625, 0.4375, 0.40625)
|
||||
StackableItemBlockType.ETHER to listOf(
|
||||
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5, 0.6875),
|
||||
Shapes.box(0.125, 0.0, 0.3125, 0.875, 0.5, 0.75),
|
||||
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.5, 1.0),
|
||||
Shapes.box(0.125, 0.0, 0.0625, 0.9375, 0.5, 1.0)
|
||||
),
|
||||
Shapes.or(
|
||||
Shapes.box(0.09375, 0.0, 0.09375, 0.40625, 0.4375, 0.40625),
|
||||
Shapes.box(0.59375, 0.0, 0.15625, 0.90625, 0.4375, 0.46875),
|
||||
Shapes.box(0.65625, 0.0, 0.59375, 0.96875, 0.4375, 0.90625),
|
||||
Shapes.box(0.15625, 0.0, 0.53125, 0.46875, 0.4375, 0.84375)
|
||||
)
|
||||
),
|
||||
StackableItemBlockType.ETHER to listOf(
|
||||
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5, 0.6875),
|
||||
Shapes.or(
|
||||
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.5, 0.75),
|
||||
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.5, 0.6875)
|
||||
StackableItemBlockType.MAX_POTION to listOf(
|
||||
Shapes.box(0.34375, 0.0, 0.3125, 0.65625, 0.5, 0.6875),
|
||||
Shapes.box(0.09375, 0.0, 0.3125, 0.90625, 0.5, 0.75),
|
||||
Shapes.box(0.09375, 0.0, 0.0625, 0.90625, 0.5, 1.0),
|
||||
Shapes.box(0.09375, 0.0, 0.0625, 0.96875, 0.5, 1.0)
|
||||
),
|
||||
Shapes.or(
|
||||
Shapes.box(0.375, 0.0, 0.125, 0.625, 0.5, 0.5),
|
||||
Shapes.box(0.125, 0.0, 0.5625, 0.375, 0.5, 0.9375),
|
||||
Shapes.box(0.625, 0.0, 0.625, 0.875, 0.5, 1.0)
|
||||
StackableItemBlockType.POTION to listOf(
|
||||
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5625, 0.75),
|
||||
Shapes.box(0.125, 0.0, 0.3125, 0.875, 0.5625, 0.8125),
|
||||
Shapes.box(0.125, 0.0, 0.0625, 0.875, 0.5625, 1.0),
|
||||
Shapes.box(0.125, 0.0, 0.0, 0.9375, 0.5625, 1.0)
|
||||
),
|
||||
Shapes.or(
|
||||
Shapes.box(0.125, 0.0, 0.0625, 0.375, 0.5, 0.4375),
|
||||
Shapes.box(0.625, 0.0, 0.125, 0.875, 0.5, 0.5),
|
||||
Shapes.box(0.6875, 0.0, 0.625, 0.9375, 0.5, 1.0),
|
||||
Shapes.box(0.1875, 0.0, 0.5625, 0.4375, 0.5, 0.9375)
|
||||
)
|
||||
),
|
||||
StackableItemBlockType.MAX_POTION to listOf(
|
||||
Shapes.box(0.34375, 0.0, 0.3125, 0.65625, 0.5, 0.6875),
|
||||
Shapes.or(
|
||||
Shapes.box(0.09375, 0.0, 0.3125, 0.40625, 0.5, 0.6875),
|
||||
Shapes.box(0.59375, 0.0, 0.375, 0.90625, 0.5, 0.75)
|
||||
StackableItemBlockType.STATUS_RESTORE to listOf(
|
||||
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.4375, 0.6875),
|
||||
Shapes.box(0.125, 0.0, 0.3125, 0.875, 0.4375, 0.75),
|
||||
Shapes.box(0.125, 0.0, 0.0625, 0.875, 0.4375, 0.9375),
|
||||
Shapes.box(0.125, 0.0, 0.0, 0.9375, 0.4375, 0.9375)
|
||||
),
|
||||
Shapes.or(
|
||||
Shapes.box(0.34375, 0.0, 0.0625, 0.65625, 0.5, 0.4375),
|
||||
Shapes.box(0.09375, 0.0, 0.5625, 0.40625, 0.5, 0.9375),
|
||||
Shapes.box(0.59375, 0.0, 0.625, 0.90625, 0.5, 1.0)
|
||||
StackableItemBlockType.METAL_COAT to listOf(
|
||||
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875),
|
||||
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.375, 0.875),
|
||||
Shapes.box(0.125, 0.0, 0.0625, 0.9375, 0.375, 0.875),
|
||||
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375)
|
||||
),
|
||||
Shapes.or(
|
||||
Shapes.box(0.09375, 0.0, 0.0625, 0.40625, 0.5, 0.4375),
|
||||
Shapes.box(0.15625, 0.0, 0.5625, 0.46875, 0.5, 0.9375),
|
||||
Shapes.box(0.65625, 0.0, 0.625, 0.96875, 0.5, 1.0),
|
||||
Shapes.box(0.59375, 0.0, 0.125, 0.90625, 0.5, 0.5)
|
||||
).singleEncompassing()
|
||||
),
|
||||
StackableItemBlockType.POTION to listOf(
|
||||
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5625, 0.75),
|
||||
Shapes.or(
|
||||
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.5625, 0.75),
|
||||
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.5625, 0.8125)
|
||||
StackableItemBlockType.VITAMIN to listOf(
|
||||
Shapes.box(0.375, 0.0, 0.375, 0.625, 0.4375, 0.625),
|
||||
Shapes.box(0.1875, 0.0, 0.3125, 0.8125, 0.4375, 0.6875),
|
||||
Shapes.box(0.1875, 0.0, 0.1875, 0.8125, 0.4375, 0.8125),
|
||||
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.4375, 0.875)
|
||||
),
|
||||
Shapes.or(
|
||||
Shapes.box(0.375, 0.0, 0.0625, 0.625, 0.5625, 0.5),
|
||||
Shapes.box(0.625, 0.0, 0.5625, 0.875, 0.5625, 1.0),
|
||||
Shapes.box(0.125, 0.0, 0.5, 0.375, 0.5625, 0.9375)
|
||||
StackableItemBlockType.IRON_BALL to listOf(
|
||||
Shapes.box(0.25, 0.0, 0.25, 0.75, 0.4375, 0.75),
|
||||
Shapes.box(0.0, 0.0, 0.1875, 1.0, 0.4375, 0.8125),
|
||||
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.4375, 1.0),
|
||||
Shapes.or(
|
||||
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.4375, 1.0),
|
||||
Shapes.box(0.25, 0.4375, 0.3125, 0.6875, 0.875, 0.75)
|
||||
)
|
||||
),
|
||||
Shapes.or(
|
||||
Shapes.box(0.125, 0.0, 0.0, 0.375, 0.5625, 0.4375),
|
||||
Shapes.box(0.625, 0.0, 0.0625, 0.875, 0.5625, 0.5),
|
||||
Shapes.box(0.6875, 0.0, 0.5625, 0.9375, 0.5625, 1.0),
|
||||
Shapes.box(0.1875, 0.0, 0.5, 0.4375, 0.5625, 0.9375)
|
||||
)
|
||||
),
|
||||
StackableItemBlockType.STATUS_RESTORE to listOf(
|
||||
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.4375, 0.6875),
|
||||
Shapes.or(
|
||||
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.4375, 0.75),
|
||||
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.4375, 0.75)
|
||||
StackableItemBlockType.THROAT_SPRAY to listOf(
|
||||
Shapes.box(0.375, 0.0, 0.1875, 0.625, 0.5625, 0.75),
|
||||
Shapes.box(0.125, 0.0, 0.1875, 0.875, 0.5625, 0.8125),
|
||||
Shapes.box(0.125, 0.0, 0.0, 0.875, 0.5625, 1.0),
|
||||
Shapes.box(0.075, 0.0, 0.0, 0.9375, 0.5625, 1.0)
|
||||
),
|
||||
Shapes.or(
|
||||
Shapes.box(0.125, 0.0, 0.5, 0.375, 0.4375, 0.875),
|
||||
Shapes.box(0.625, 0.0, 0.5625, 0.875, 0.4375, 0.9375),
|
||||
Shapes.box(0.375, 0.0, 0.0625, 0.625, 0.4375, 0.4375)
|
||||
StackableItemBlockType.UPGRADE to listOf(
|
||||
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.125, 0.6875),
|
||||
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.25, 0.6875),
|
||||
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875),
|
||||
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.5, 0.6875)
|
||||
),
|
||||
Shapes.or(
|
||||
Shapes.box(0.1875, 0.0, 0.5, 0.4375, 0.4375, 0.875),
|
||||
Shapes.box(0.125, 0.0, 0.0, 0.375, 0.4375, 0.375),
|
||||
Shapes.box(0.625, 0.0, 0.0625, 0.875, 0.4375, 0.4375),
|
||||
Shapes.box(0.6875, 0.0, 0.5625, 0.9375, 0.4375, 0.9375)
|
||||
StackableItemBlockType.DICE to listOf(
|
||||
Shapes.box(0.125, 0.0, 0.6875, 0.3125, 0.1875, 0.875),
|
||||
Shapes.box(0.125, 0.0, 0.625, 0.5625, 0.1875, 0.875),
|
||||
Shapes.box(0.125, 0.0, 0.4375, 0.5625, 0.1875, 0.875),
|
||||
Shapes.box(0.125, 0.0, 0.4375, 0.5625, 0.375, 0.875)
|
||||
),
|
||||
StackableItemBlockType.GLASS_BOTTLE to listOf(
|
||||
Shapes.box(0.375, 0.0, 0.375, 0.625, 0.375, 0.625),
|
||||
Shapes.box(0.1875, 0.0, 0.1875, 0.8125, 0.375, 0.6875),
|
||||
Shapes.box(0.125, 0.0, 0.125, 0.8125, 0.375, 0.875),
|
||||
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.375, 0.875)
|
||||
),
|
||||
StackableItemBlockType.EXTENDER to listOf(
|
||||
Shapes.box(0.25, 0.0, 0.375, 0.625, 0.5625, 0.625),
|
||||
Shapes.box(0.125, 0.0, 0.25, 0.875, 0.5625, 0.75),
|
||||
Shapes.box(0.0625, 0.0, 0.0625, 0.875, 0.5625, 0.875),
|
||||
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.5625, 0.9375)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
fun getShape(type: StackableItemBlockType, count: Int): VoxelShape = northShapeMap[type]?.let { list -> list[(count - 1).coerceIn(0, list.lastIndex)] } ?: Shapes.block()
|
||||
fun getMaxAmount(type: StackableItemBlockType): Int = min(northShapeMap[type]?.size ?: 1 , MAX_AMOUNT)
|
||||
@@ -184,7 +184,7 @@ class StackableItemBlock(settings: Properties, val type: StackableItemBlockType)
|
||||
}
|
||||
|
||||
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
|
||||
return getCollisionShape(state, level, pos, context).singleEncompassing()
|
||||
return getCollisionShape(state, level, pos, context)
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block.decor
|
||||
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.SimpleWaterloggedBlock
|
||||
import net.minecraft.world.level.block.TallFlowerBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
|
||||
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import net.minecraft.world.level.material.Fluids
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
|
||||
class LobailaFlowerBlock(properties: Properties?) : TallFlowerBlock(properties), SimpleWaterloggedBlock {
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(HALF, DoubleBlockHalf.LOWER)
|
||||
.setValue(WATERLOGGED, false))
|
||||
}
|
||||
|
||||
override fun getFluidState(blockState: BlockState): FluidState? {
|
||||
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState);
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(HALF, WATERLOGGED)
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
|
||||
val fluidState = context.level.getFluidState(context.clickedPos)
|
||||
return this.defaultBlockState()
|
||||
.setValue(
|
||||
WATERLOGGED,
|
||||
fluidState.type === Fluids.WATER
|
||||
)
|
||||
}
|
||||
|
||||
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape = AABB
|
||||
|
||||
companion object {
|
||||
private val AABB = Shapes.box(0.125, 0.0, 0.125, 0.875, 1.0, 0.875)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block.decor
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBlockEntities
|
||||
import com.cobblemon.mod.common.block.entity.MistleafFlowerBlockEntity
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.BaseEntityBlock
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.EntityBlock
|
||||
import net.minecraft.world.level.block.SoundType
|
||||
import net.minecraft.world.level.block.TallFlowerBlock
|
||||
import net.minecraft.world.level.block.entity.BlockEntity
|
||||
import net.minecraft.world.level.block.entity.BlockEntityTicker
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf
|
||||
import net.minecraft.world.level.block.state.properties.IntegerProperty
|
||||
import net.minecraft.world.level.material.MapColor
|
||||
import net.minecraft.world.level.material.PushReaction
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class MistleafFlowerBlock(lightLevel: (BlockState) -> Int) :
|
||||
EntityBlock,
|
||||
TallFlowerBlock(
|
||||
Properties.of()
|
||||
.mapColor(MapColor.PLANT)
|
||||
.noCollission()
|
||||
.instabreak()
|
||||
.sound(SoundType.GRASS)
|
||||
.offsetType(OffsetType.XZ)
|
||||
.ignitedByLava()
|
||||
.pushReaction(PushReaction.DESTROY)
|
||||
.lightLevel(lightLevel)
|
||||
) {
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(HALF, DoubleBlockHalf.LOWER)
|
||||
.setValue(GLOWING, 0))
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(HALF, GLOWING)
|
||||
}
|
||||
override fun isValidBonemealTarget(level: LevelReader, pos: BlockPos, state: BlockState): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape = AABB
|
||||
|
||||
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity? {
|
||||
return MistleafFlowerBlockEntity(pos, state)
|
||||
}
|
||||
|
||||
override fun <T : BlockEntity?> getTicker(
|
||||
level: Level,
|
||||
state: BlockState,
|
||||
blockEntityType: BlockEntityType<T?>
|
||||
): BlockEntityTicker<T?>? {
|
||||
return if (level.isClientSide) null else BaseEntityBlock.createTickerHelper(blockEntityType as BlockEntityType<*>,
|
||||
CobblemonBlockEntities.MISTLEAF_FLOWER, MistleafFlowerBlockEntity::serverTick) as BlockEntityTicker<T?>?
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val AABB = Shapes.box(0.125, 0.0, 0.125, 0.875, 1.0, 0.875)
|
||||
val GLOWING = IntegerProperty.create("glowing", 0, 8)
|
||||
}
|
||||
|
||||
override fun hasAnalogOutputSignal(state: BlockState): Boolean { return true }
|
||||
override fun getAnalogOutputSignal(state: BlockState, level: Level, pos: BlockPos): Int {
|
||||
return state.getValue(GLOWING) as Int
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block.decor
|
||||
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.util.RandomSource
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.BonemealableBlock
|
||||
import net.minecraft.world.level.block.DirectionalBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class PauvilleaFlowerBlock(properties: Properties?) : DirectionalBlock(properties), BonemealableBlock {
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(FACING, Direction.DOWN))
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(FACING)
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(ctx: BlockPlaceContext): BlockState? {
|
||||
var blockState = defaultBlockState()
|
||||
val worldView = ctx.level
|
||||
val blockPos = ctx.clickedPos
|
||||
blockState = blockState.setValue(FACING, ctx.clickedFace) as BlockState
|
||||
if (blockState.canSurvive(worldView, blockPos)) {
|
||||
return blockState
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, level: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
|
||||
val facing = state.getValue(FACING)
|
||||
if (direction == facing.opposite && !canSurvive(state, level, pos)) {
|
||||
return Blocks.AIR.defaultBlockState()
|
||||
}
|
||||
return super.updateShape(state, direction, neighborState, level, pos, neighborPos)
|
||||
}
|
||||
|
||||
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
|
||||
val direction = state.getValue(FACING) as Direction
|
||||
val blockState = level.getBlockState(pos.relative(direction.opposite))
|
||||
return blockState.isCollisionShapeFullBlock(level, pos) or blockState.canOcclude()
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<PauvilleaFlowerBlock> = CODEC
|
||||
|
||||
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
|
||||
return when (state.getValue(FACING)) {
|
||||
Direction.DOWN -> CEILING
|
||||
Direction.NORTH -> WALL_NORTH
|
||||
Direction.EAST -> WALL_EAST
|
||||
Direction.SOUTH -> WALL_SOUTH
|
||||
Direction.WEST -> WALL_WEST
|
||||
Direction.UP -> FLOOR
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC: MapCodec<PauvilleaFlowerBlock> = RecordCodecBuilder.mapCodec {
|
||||
it.group(propertiesCodec()).apply(it, ::PauvilleaFlowerBlock)
|
||||
}
|
||||
val FLOOR = Shapes.box(0.075, 0.0, 0.075, 0.925, 0.4375, 0.925)
|
||||
val WALL_NORTH = Shapes.box(0.0, 0.0, 0.8125, 1.0, 1.0, 1.0)
|
||||
val WALL_EAST = Shapes.box(0.0, 0.0, 0.0, 0.1875, 1.0, 1.0)
|
||||
val WALL_SOUTH = Shapes.box(0.0, 0.0, 0.0, 1.0, 1.0, 0.1875)
|
||||
val WALL_WEST = Shapes.box(0.8125, 0.0, 0.0, 1.0, 1.0, 1.0)
|
||||
val CEILING = Shapes.box(0.075, 0.0, 0.075, 0.925, 1.0, 0.925)
|
||||
}
|
||||
|
||||
override fun isValidBonemealTarget(level: LevelReader?, pos: BlockPos?, state: BlockState?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun isBonemealSuccess(level: Level?, random: RandomSource?, pos: BlockPos?, state: BlockState?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun performBonemeal(level: ServerLevel, random: RandomSource?, pos: BlockPos, state: BlockState?) {
|
||||
popResource(level, pos, ItemStack(this))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block.decor
|
||||
|
||||
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.block.PinkPetalsBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
|
||||
class PomohanaFlowerBlock(properties: Properties?) : PinkPetalsBlock(properties) {
|
||||
|
||||
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape = AABB
|
||||
|
||||
companion object {
|
||||
private val AABB = Shapes.box(0.125, 0.0, 0.125, 0.875, 1.0, 0.875)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block.decor
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBlockEntities
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.cobblemon.mod.common.block.entity.TerrainPlantBlockEntity
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.util.RandomSource
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.BaseEntityBlock
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.EntityBlock
|
||||
import net.minecraft.world.level.block.TallFlowerBlock
|
||||
import net.minecraft.world.level.block.entity.BlockEntity
|
||||
import net.minecraft.world.level.block.entity.BlockEntityTicker
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.BooleanProperty
|
||||
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class TerrainPlantBlock(properties: Properties) : TallFlowerBlock(properties), EntityBlock {
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(HALF, DoubleBlockHalf.LOWER)
|
||||
.setValue(GLOWING, false))
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(HALF, GLOWING)
|
||||
}
|
||||
override fun isValidBonemealTarget(level: LevelReader, pos: BlockPos, state: BlockState): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun tick(state: BlockState, level: ServerLevel, pos: BlockPos, random: RandomSource) {
|
||||
if (state.`is`(CobblemonBlocks.LUCIBELL)) { this.checkInside(level, pos, state) }
|
||||
}
|
||||
|
||||
private fun checkInside(level: Level, pos: BlockPos, state: BlockState) {
|
||||
// Only lower will handle this
|
||||
val upperState = level.getBlockState(pos.above()).takeIf { it.`is`(CobblemonBlocks.LUCIBELL) && it.getValue(HALF) == DoubleBlockHalf.UPPER }
|
||||
|
||||
val entityInside = level.getEntities(null, state.getShape(level, pos).bounds().move(pos)).isNotEmpty()
|
||||
|| upperState?.let { level.getEntities(null, it.getShape(level, pos.above()).bounds().move(pos.above())).isNotEmpty() } ?: false
|
||||
|
||||
val glowingState = state.getValue(GLOWING)
|
||||
if (glowingState != entityInside) {
|
||||
level.setBlock(pos, state.setValue(GLOWING, entityInside), UPDATE_CLIENTS)
|
||||
upperState?.let { level.setBlock(pos.above(), it.setValue(GLOWING, entityInside), UPDATE_CLIENTS) }
|
||||
}
|
||||
if (entityInside) level.scheduleTick(pos, this, 30) // tick occasionally to check if player inside
|
||||
}
|
||||
|
||||
override fun entityInside(state: BlockState, level: Level, pos: BlockPos, entity: Entity) {
|
||||
// Lucibell lower block will be our 'driver'
|
||||
if (!level.isClientSide && !state.getValue(GLOWING) && state.`is`(CobblemonBlocks.LUCIBELL)) {
|
||||
val halfState = state.getValue(HALF)
|
||||
if (halfState == DoubleBlockHalf.UPPER) {
|
||||
val lowerState = level.getBlockState(pos.below())
|
||||
.takeIf { it.`is`(CobblemonBlocks.LUCIBELL) && it.getValue(HALF) == DoubleBlockHalf.LOWER }
|
||||
?: return
|
||||
(lowerState.block as TerrainPlantBlock).checkInside(level, pos.below(), lowerState)
|
||||
} else {
|
||||
checkInside(level, pos, state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape = AABB
|
||||
|
||||
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
|
||||
return TerrainPlantBlockEntity(pos, state)
|
||||
}
|
||||
|
||||
override fun <T : BlockEntity> getTicker(
|
||||
level: Level,
|
||||
state: BlockState,
|
||||
blockEntityType: BlockEntityType<T?>
|
||||
): BlockEntityTicker<T?>? {
|
||||
return if (level.isClientSide) null else BaseEntityBlock.createTickerHelper(blockEntityType as BlockEntityType<*>,
|
||||
CobblemonBlockEntities.TERRAIN_PLANT, TerrainPlantBlockEntity::serverTick) as BlockEntityTicker<T?>?
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val AABB = Shapes.box(0.125, 0.0, 0.125, 0.875, 1.0, 0.875)
|
||||
val GLOWING = BooleanProperty.create("glowing")
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block.decor
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.util.RandomSource
|
||||
import net.minecraft.util.StringRepresentable
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.level.*
|
||||
import net.minecraft.world.level.block.*
|
||||
import net.minecraft.world.level.block.entity.BlockEntity
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties
|
||||
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf
|
||||
import net.minecraft.world.level.block.state.properties.EnumProperty
|
||||
import net.minecraft.world.level.block.state.properties.IntegerProperty
|
||||
import net.minecraft.world.level.material.Fluids
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
@Suppress("OVERRIDE_DEPRECATION")
|
||||
class TerrainSeedBlock(private val terrainSeedType: TerrainSeedType, settings: Properties) : CropBlock(settings) {
|
||||
companion object {
|
||||
val CODEC: MapCodec<TerrainSeedBlock> = RecordCodecBuilder.mapCodec { it.group(
|
||||
TerrainSeedType.CODEC.fieldOf("terrainSeedType").forGetter(TerrainSeedBlock::terrainSeedType),
|
||||
propertiesCodec()
|
||||
).apply(it, ::TerrainSeedBlock) }
|
||||
const val MATURE_AGE = 4
|
||||
val AGE: IntegerProperty = IntegerProperty.create("age", 0, MATURE_AGE)
|
||||
val HALF: EnumProperty<DoubleBlockHalf> = BlockStateProperties.DOUBLE_BLOCK_HALF
|
||||
|
||||
val AGE_TO_SHAPE = arrayOf(
|
||||
box(2.0, 0.0, 2.0, 14.0, 6.0, 14.0), // Stage 0
|
||||
box(2.0, 0.0, 2.0, 14.0, 16.0, 14.0), // Stage 1
|
||||
box(2.0, 0.0, 2.0, 14.0, 16.0, 14.0), // Stage 2
|
||||
box(2.0, 0.0, 2.0, 14.0, 16.0, 14.0), // Stage 3
|
||||
box(2.0, 0.0, 2.0, 14.0, 16.0, 14.0), // Stage 4
|
||||
)
|
||||
|
||||
val AGE_TO_SHAPE_TOP = arrayOf(
|
||||
box(2.0, 0.0, 2.0, 14.0, 1.0, 14.0), // Stage 0
|
||||
box(2.0, 0.0, 2.0, 14.0, 1.0, 14.0), // Stage 1
|
||||
box(2.0, 0.0, 2.0, 14.0, 8.0, 14.0), // Stage 2
|
||||
box(2.0, 0.0, 2.0, 14.0, 16.0, 14.0), // Stage 3
|
||||
box(2.0, 0.0, 2.0, 14.0, 16.0, 14.0), // Stage 4
|
||||
)
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<out CropBlock> {
|
||||
return CODEC
|
||||
}
|
||||
|
||||
init {
|
||||
this.registerDefaultState(
|
||||
this.stateDefinition.any()
|
||||
.setValue(AGE, 0)
|
||||
.setValue(HALF, DoubleBlockHalf.LOWER)
|
||||
)
|
||||
}
|
||||
|
||||
override fun getAgeProperty(): IntegerProperty = AGE
|
||||
|
||||
override fun getMaxAge(): Int = MATURE_AGE
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(AGE, HALF)
|
||||
}
|
||||
|
||||
override fun getBaseSeedId(): ItemLike = this.terrainSeedType.getSeed()
|
||||
|
||||
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape =
|
||||
if (state.getValue(HALF) == DoubleBlockHalf.UPPER) AGE_TO_SHAPE_TOP[getAge(state)] else AGE_TO_SHAPE[this.getAge(state)]
|
||||
|
||||
override fun randomTick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
|
||||
if (state.getValue(HALF) == DoubleBlockHalf.UPPER || !canGrow(world, pos, state)) return
|
||||
|
||||
if (hasSufficientLight(world, pos) && random.nextInt(32) == 0) {
|
||||
grow(world, pos, state, 1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun canGrow(world: LevelReader, pos: BlockPos, state: BlockState): Boolean {
|
||||
return !this.isMaxAge(state) && canSurvive(state, world, pos) && !(!canGrowInto(world, pos.above()) && state.getValue(AGE) >= 4)
|
||||
}
|
||||
|
||||
private fun canGrowInto(world: LevelReader, pos: BlockPos): Boolean {
|
||||
val blockState = world.getBlockState(pos)
|
||||
return blockState.isAir || blockState.`is`(this.terrainSeedType.getCropBlock())
|
||||
}
|
||||
|
||||
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
|
||||
return if (state.getValue(AGE) >= 2) {
|
||||
super.updateShape(state, direction, neighborState, world, pos, neighborPos)
|
||||
} else {
|
||||
if (state.canSurvive(world, pos)) state else Blocks.AIR.defaultBlockState()
|
||||
}
|
||||
}
|
||||
|
||||
override fun isValidBonemealTarget(level: LevelReader, pos: BlockPos, state: BlockState): Boolean {
|
||||
val posAndState = getLowerHalf(level, pos, state)
|
||||
return if (posAndState == null) false else this.canGrow(
|
||||
level,
|
||||
posAndState.first,
|
||||
posAndState.second
|
||||
)
|
||||
}
|
||||
|
||||
override fun performBonemeal(level: ServerLevel, random: RandomSource, pos: BlockPos, state: BlockState) {
|
||||
val posAndState = getLowerHalf(level, pos, state)
|
||||
if (posAndState != null) {
|
||||
grow(level, posAndState.first, posAndState.second, getBonemealAgeIncrease(level))
|
||||
}
|
||||
}
|
||||
|
||||
fun grow(world: Level, pos: BlockPos, state: BlockState, increment: Int) {
|
||||
if (!canGrow(world, pos, state)) return
|
||||
|
||||
val newAge = (this.getAge(state) + increment).coerceAtMost(this.maxAge)
|
||||
world.setBlock(pos, state.setValue(this.ageProperty, newAge), UPDATE_CLIENTS)
|
||||
|
||||
val abovePos = pos.above()
|
||||
val stateAbove = world.getBlockState(abovePos)
|
||||
// Set top half depending on age
|
||||
if (newAge >= (2)) {
|
||||
if (stateAbove.isAir) {
|
||||
world.setBlock(
|
||||
abovePos,
|
||||
this.defaultBlockState().setValue(AGE, newAge).setValue(HALF, DoubleBlockHalf.UPPER),
|
||||
UPDATE_CLIENTS
|
||||
)
|
||||
}
|
||||
else if (stateAbove.hasProperty(HALF) && stateAbove.getValue(HALF) == DoubleBlockHalf.UPPER) {
|
||||
world.setBlock(
|
||||
abovePos,
|
||||
stateAbove.setValue(AGE, newAge),
|
||||
UPDATE_CLIENTS
|
||||
)
|
||||
}
|
||||
}
|
||||
// Swaps blocks to the actual Flower when fully grown
|
||||
if (newAge == this.maxAge) {
|
||||
val nextStateLower = this.terrainSeedType.getFlowerBlock().defaultBlockState().setValue(HALF, DoubleBlockHalf.LOWER)
|
||||
val nextStateUpper = this.terrainSeedType.getFlowerBlock().defaultBlockState().setValue(HALF, DoubleBlockHalf.UPPER)
|
||||
world.setBlock(abovePos, nextStateUpper, UPDATE_CLIENTS)
|
||||
world.setBlock(pos, nextStateLower, UPDATE_CLIENTS)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBonemealAgeIncrease(level: Level): Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
override fun canSurvive(state: BlockState, world: LevelReader, pos: BlockPos): Boolean {
|
||||
return when (state.getValue(HALF)) {
|
||||
DoubleBlockHalf.LOWER -> {
|
||||
val floorBlock = pos.below()
|
||||
val floor = world.getBlockState(floorBlock)
|
||||
|
||||
floor.`is`(Blocks.FARMLAND)
|
||||
}
|
||||
DoubleBlockHalf.UPPER -> {
|
||||
getLowerHalf(world, pos, state) != null
|
||||
// Upper part survives only if the lower part is valid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLowerHalf(level: LevelReader, pos: BlockPos, state: BlockState): Pair<BlockPos, BlockState>? {
|
||||
if (isLower(state)) {
|
||||
return Pair(pos, state)
|
||||
} else {
|
||||
val blockPos = pos.below()
|
||||
val blockState = level.getBlockState(blockPos)
|
||||
return if (isLower(blockState)) Pair(
|
||||
blockPos,
|
||||
blockState
|
||||
) else null
|
||||
}
|
||||
}
|
||||
|
||||
private fun isLower(state: BlockState): Boolean =
|
||||
state.`is`(this.terrainSeedType.getCropBlock()) && state.getValue(HALF) == DoubleBlockHalf.LOWER
|
||||
|
||||
override fun getSoundType(state: BlockState): SoundType {
|
||||
return CobblemonSounds.HEARTY_GRAINS_SOUNDS
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
enum class TerrainSeedType : StringRepresentable {
|
||||
ELECTRIC,
|
||||
PSYCHIC,
|
||||
MISTY,
|
||||
GRASSY;
|
||||
fun getSeed(): Item = when (this) {
|
||||
ELECTRIC -> CobblemonItems.ELECTRIC_SEED
|
||||
PSYCHIC -> CobblemonItems.PSYCHIC_SEED
|
||||
MISTY -> CobblemonItems.MISTY_SEED
|
||||
GRASSY -> CobblemonItems.GRASSY_SEED
|
||||
}
|
||||
fun getCropBlock(): TerrainSeedBlock = when (this) {
|
||||
ELECTRIC -> CobblemonBlocks.ELECTRIC_SEED
|
||||
PSYCHIC -> CobblemonBlocks.PSYCHIC_SEED
|
||||
MISTY -> CobblemonBlocks.MISTY_SEED
|
||||
GRASSY -> CobblemonBlocks.GRASSY_SEED
|
||||
}
|
||||
fun getFlowerBlock(): Block = when (this) {
|
||||
ELECTRIC -> CobblemonBlocks.VOLTACONIA
|
||||
PSYCHIC -> CobblemonBlocks.LUCIBELL
|
||||
MISTY -> CobblemonBlocks.MISTLEAF
|
||||
GRASSY -> CobblemonBlocks.VERDROOT
|
||||
}
|
||||
|
||||
override fun getSerializedName(): String = this.name.lowercase()
|
||||
|
||||
companion object {
|
||||
val CODEC = StringRepresentable.fromEnum(::values)
|
||||
}
|
||||
}
|
||||
|
||||
override fun playerWillDestroy(level: Level, pos: BlockPos, state: BlockState, player: Player): BlockState {
|
||||
if (!level.isClientSide) {
|
||||
if (player.isCreative()) {
|
||||
preventDropFromBottomPart(level, pos, state, player)
|
||||
} else {
|
||||
dropResources(state, level, pos, null as BlockEntity?, player, player.getMainHandItem())
|
||||
}
|
||||
}
|
||||
|
||||
return super.playerWillDestroy(level, pos, state, player)
|
||||
}
|
||||
|
||||
override fun playerDestroy(
|
||||
level: Level,
|
||||
player: Player,
|
||||
pos: BlockPos,
|
||||
state: BlockState,
|
||||
blockEntity: BlockEntity?,
|
||||
tool: ItemStack
|
||||
) {
|
||||
super.playerDestroy(level, player, pos, Blocks.AIR.defaultBlockState(), blockEntity, tool)
|
||||
}
|
||||
|
||||
fun preventDropFromBottomPart(level: Level, pos: BlockPos, state: BlockState, player: Player?) {
|
||||
val doubleblockhalf = state.getValue<DoubleBlockHalf?>(DoublePlantBlock.HALF) as DoubleBlockHalf
|
||||
if (doubleblockhalf == DoubleBlockHalf.UPPER) {
|
||||
val blockpos = pos.below()
|
||||
val blockstate = level.getBlockState(blockpos)
|
||||
if (blockstate.`is`(state.getBlock()) && blockstate.getValue<DoubleBlockHalf?>(DoublePlantBlock.HALF) == DoubleBlockHalf.LOWER) {
|
||||
val blockstate1 = if (blockstate.getFluidState()
|
||||
.`is`(Fluids.WATER)
|
||||
) Blocks.WATER.defaultBlockState() else Blocks.AIR.defaultBlockState()
|
||||
level.setBlock(blockpos, blockstate1, 35)
|
||||
level.levelEvent(player, 2001, blockpos, getId(blockstate))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block.decor
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties
|
||||
import net.minecraft.world.level.block.state.properties.BooleanProperty
|
||||
|
||||
interface WistensuFlowerBlock {
|
||||
fun additionalBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(BlockStateProperties.FACING)
|
||||
builder.add(ROOT)
|
||||
}
|
||||
|
||||
fun adjustStateForPlacement(context: BlockPlaceContext, defaultState: BlockState): BlockState {
|
||||
val upperBlock = context.level.getBlockState(context.clickedPos.relative(Direction.UP))
|
||||
if (upperBlock.`is`(CobblemonBlocks.WISTENSU) || upperBlock.`is`(CobblemonBlocks.WISTENSU_VINE)) {
|
||||
return defaultState
|
||||
.setValue(BlockStateProperties.FACING, Direction.DOWN)
|
||||
.setValue(ROOT, false)
|
||||
} else {
|
||||
var placeDirection = context.clickedFace
|
||||
if (placeDirection == Direction.UP) placeDirection = Direction.DOWN
|
||||
return defaultState
|
||||
.setValue(BlockStateProperties.FACING, placeDirection)
|
||||
.setValue(ROOT, true)
|
||||
}
|
||||
}
|
||||
|
||||
fun copyStateValues(target: BlockState, source: BlockState): BlockState {
|
||||
return target
|
||||
.setValue(ROOT, source.getValue(ROOT))
|
||||
.setValue(BlockStateProperties.FACING, source.getValue(BlockStateProperties.FACING))
|
||||
}
|
||||
|
||||
fun wisternCanSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
|
||||
if (state.getValue(ROOT)) {
|
||||
val facing = state.getValue(BlockStateProperties.FACING)
|
||||
val targetPos = pos.relative(facing.opposite)
|
||||
val directionState = level.getBlockState(targetPos)
|
||||
return directionState.isFaceSturdy(level, targetPos, facing)
|
||||
} else {
|
||||
val topState = level.getBlockState(pos.relative(Direction.UP))
|
||||
return topState.`is`(CobblemonBlocks.WISTENSU) || topState.`is`(CobblemonBlocks.WISTENSU_VINE)
|
||||
}
|
||||
}
|
||||
|
||||
fun wisternUpdateShape(state: BlockState, direction: Direction, neighborState: BlockState, level: LevelAccessor, pos: BlockPos, neighborPos: BlockPos, block: Block): BlockState? {
|
||||
if (!wisternCanSurvive(state, level, pos)) {
|
||||
level.scheduleTick(pos, block, 1)
|
||||
}
|
||||
|
||||
if (direction == Direction.UP &&
|
||||
(neighborState.`is`(CobblemonBlocks.WISTENSU) || neighborState.`is`(CobblemonBlocks.WISTENSU_VINE))) {
|
||||
return state
|
||||
.setValue(ROOT, false)
|
||||
.setValue(BlockStateProperties.FACING, Direction.DOWN)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ROOT = BooleanProperty.create("root")
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block.decor
|
||||
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.util.RandomSource
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.GrowingPlantHeadBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
import kotlin.math.min
|
||||
|
||||
//TODO: Root/connection shit
|
||||
class WistensuFlowerHeadBlock(properties: Properties?) : GrowingPlantHeadBlock(properties, Direction.DOWN, SHAPE, false, 0.1), WistensuFlowerBlock {
|
||||
companion object {
|
||||
val CODEC: MapCodec<WistensuFlowerHeadBlock> = RecordCodecBuilder.mapCodec {
|
||||
it.group(propertiesCodec()).apply(it, ::WistensuFlowerHeadBlock)
|
||||
}
|
||||
val SHAPE: VoxelShape = box(1.0, 2.0, 1.0, 15.0, 16.0, 15.0)
|
||||
}
|
||||
override fun codec(): MapCodec<WistensuFlowerHeadBlock> = CODEC
|
||||
|
||||
override fun getBlocksToGrowWhenBonemealed(random: RandomSource): Int {
|
||||
var d = 1.0
|
||||
var i = 0
|
||||
while (random.nextDouble() < d) {
|
||||
d *= 0.826
|
||||
++i
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
override fun canGrowInto(state: BlockState): Boolean {
|
||||
return state.isAir
|
||||
}
|
||||
|
||||
override fun getBodyBlock(): Block {
|
||||
return CobblemonBlocks.WISTENSU_VINE
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
super.createBlockStateDefinition(builder)
|
||||
additionalBlockStateDefinition(builder)
|
||||
}
|
||||
|
||||
override fun updateShape(
|
||||
state: BlockState,
|
||||
direction: Direction,
|
||||
neighborState: BlockState,
|
||||
level: LevelAccessor,
|
||||
pos: BlockPos,
|
||||
neighborPos: BlockPos
|
||||
): BlockState {
|
||||
val updateState = wisternUpdateShape(state, direction, neighborState, level, pos, neighborPos, this)
|
||||
if (updateState != null) return updateState
|
||||
|
||||
if (direction == Direction.DOWN &&
|
||||
(neighborState.`is`(CobblemonBlocks.WISTENSU) || neighborState.`is`(CobblemonBlocks.WISTENSU_VINE))) {
|
||||
return updateBodyAfterConvertedFromHead(state, bodyBlock.defaultBlockState())
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState {
|
||||
val defaultState = super.getStateForPlacement(context)!!
|
||||
return adjustStateForPlacement(context, defaultState)
|
||||
}
|
||||
|
||||
override fun getGrowIntoState(state: BlockState, random: RandomSource): BlockState {
|
||||
return super.getGrowIntoState(state, random)
|
||||
.setValue(WistensuFlowerBlock.ROOT, false)
|
||||
.setValue(BlockStateProperties.FACING, Direction.DOWN)
|
||||
}
|
||||
|
||||
override fun updateBodyAfterConvertedFromHead(head: BlockState, body: BlockState): BlockState {
|
||||
return copyStateValues(body, head)
|
||||
}
|
||||
|
||||
override fun performBonemeal(level: ServerLevel, random: RandomSource, pos: BlockPos, state: BlockState) {
|
||||
var blockPos = pos.relative(growthDirection)
|
||||
var i = min(state.getValue(AGE), MAX_AGE)
|
||||
val j = getBlocksToGrowWhenBonemealed(random)
|
||||
var currentState = state
|
||||
|
||||
for (k in 0..<j) {
|
||||
if (!canGrowInto(level.getBlockState(blockPos))) break
|
||||
level.setBlockAndUpdate(blockPos, getGrowIntoState(currentState, random))
|
||||
currentState = level.getBlockState(blockPos)
|
||||
blockPos = blockPos.relative(growthDirection)
|
||||
i = min(i + 1, MAX_AGE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
|
||||
return wisternCanSurvive(state, level, pos)
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block.decor
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.GrowingPlantBodyBlock
|
||||
import net.minecraft.world.level.block.GrowingPlantHeadBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
//TODO: Root/connection shit
|
||||
class WistensuFlowerVineBlock(properties: Properties?) : GrowingPlantBodyBlock(properties, Direction.DOWN, SHAPE, false), WistensuFlowerBlock
|
||||
{
|
||||
companion object {
|
||||
val CODEC: MapCodec<WistensuFlowerVineBlock> = RecordCodecBuilder.mapCodec {
|
||||
it.group(propertiesCodec()).apply(it, ::WistensuFlowerVineBlock)
|
||||
}
|
||||
val SHAPE: VoxelShape = box(1.0, 0.0, 1.0, 15.0, 16.0, 15.0)
|
||||
}
|
||||
override fun codec(): MapCodec<WistensuFlowerVineBlock> = CODEC
|
||||
|
||||
override fun getHeadBlock(): GrowingPlantHeadBlock {
|
||||
return CobblemonBlocks.WISTENSU
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
super.createBlockStateDefinition(builder)
|
||||
additionalBlockStateDefinition(builder)
|
||||
}
|
||||
|
||||
override fun updateShape(
|
||||
state: BlockState,
|
||||
direction: Direction,
|
||||
neighborState: BlockState,
|
||||
level: LevelAccessor,
|
||||
pos: BlockPos,
|
||||
neighborPos: BlockPos
|
||||
): BlockState {
|
||||
val updateState = wisternUpdateShape(state, direction, neighborState, level, pos, neighborPos, this)
|
||||
if (updateState != null) return updateState
|
||||
|
||||
if (direction == Direction.DOWN &&
|
||||
!neighborState.`is`(CobblemonBlocks.WISTENSU) && !neighborState.`is`(CobblemonBlocks.WISTENSU_VINE)) {
|
||||
return updateHeadAfterConvertedFromBody(state, headBlock.getStateForPlacement(level))
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState {
|
||||
val defaultState = super.getStateForPlacement(context)!!
|
||||
return adjustStateForPlacement(context, defaultState)
|
||||
}
|
||||
|
||||
override fun updateHeadAfterConvertedFromBody(head: BlockState, body: BlockState): BlockState {
|
||||
return copyStateValues(body, head)
|
||||
}
|
||||
|
||||
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
|
||||
return wisternCanSurvive(state, level, pos)
|
||||
}
|
||||
}
|
||||
@@ -128,6 +128,7 @@ class DiscShelfBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobbl
|
||||
|
||||
fun isValidItem(stack: ItemStack): Boolean {
|
||||
return stack.item is TechnicalMachineItem
|
||||
|| stack.item == CobblemonItems.BLANK_TM
|
||||
|| stack.item == CobblemonItems.UPGRADE
|
||||
|| stack.item == CobblemonItems.DUBIOUS_DISC
|
||||
|| stack.has(DataComponents.JUKEBOX_PLAYABLE)
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block.entity
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBlockEntities
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.cobblemon.mod.common.block.decor.MistleafFlowerBlock
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.entity.BlockEntity
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
|
||||
class MistleafFlowerBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(CobblemonBlockEntities.MISTLEAF_FLOWER, pos, state) {
|
||||
companion object {
|
||||
fun serverTick(level: Level, pos: BlockPos, state: BlockState, mistleafFlowerBlockEntity: MistleafFlowerBlockEntity) {
|
||||
if (level.isClientSide) return
|
||||
if (state.block !is MistleafFlowerBlock) return
|
||||
|
||||
val glowing = state.getValue(MistleafFlowerBlock.GLOWING)
|
||||
|
||||
val wantedState = when (state.`is`(CobblemonBlocks.MISTLEAF)) {
|
||||
//The light level and comparator output increases on odds from New to Full, and decreases on the evens from Full to New
|
||||
((level.dayTime().toInt().mod(24000) in 12542..23459) && level.moonPhase == 4) -> 1 //New Moon
|
||||
((level.dayTime().toInt().mod(24000) in 12542..23459) && level.moonPhase == 5) -> 3 //Waxing Crescent
|
||||
((level.dayTime().toInt().mod(24000) in 12542..23459) && level.moonPhase == 6) -> 5 //First Quarter
|
||||
((level.dayTime().toInt().mod(24000) in 12542..23459) && level.moonPhase == 7) -> 7 //Waxing Gibbous
|
||||
((level.dayTime().toInt().mod(24000) in 12542..23459) && level.moonPhase == 0) -> 8 //Full Moon
|
||||
((level.dayTime().toInt().mod(24000) in 12542..23459) && level.moonPhase == 1) -> 6 //Waning Gibbous
|
||||
((level.dayTime().toInt().mod(24000) in 12542..23459) && level.moonPhase == 2) -> 4 // Third Quarter
|
||||
((level.dayTime().toInt().mod(24000) in 12542..23459) && level.moonPhase == 3) -> 2 //Waning Crescent
|
||||
else -> 0 //Daytime, and likely dimensions with no concept of day/night cycle or moon phase
|
||||
}
|
||||
|
||||
if (glowing != wantedState) {
|
||||
level.setBlock(pos, state.setValue(MistleafFlowerBlock.GLOWING, wantedState), Block.UPDATE_ALL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block.entity
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBlockEntities
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.cobblemon.mod.common.block.decor.TerrainPlantBlock
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.entity.BlockEntity
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
|
||||
class TerrainPlantBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(CobblemonBlockEntities.TERRAIN_PLANT, pos, state) {
|
||||
companion object {
|
||||
fun serverTick(level: Level, pos: BlockPos, state: BlockState, terrainPlantBlockEntity: TerrainPlantBlockEntity) {
|
||||
if (level.isClientSide) return
|
||||
if (state.block !is TerrainPlantBlock) return
|
||||
|
||||
val glowing = state.getValue(TerrainPlantBlock.GLOWING)
|
||||
|
||||
val wantedState = if (state.`is`(CobblemonBlocks.VOLTACONIA)) level.isThundering
|
||||
else if (state.`is`(CobblemonBlocks.VERDROOT)) level.isRaining && !level.isThundering
|
||||
else null // We dont control glowing here
|
||||
|
||||
if (wantedState != null && glowing != wantedState) {
|
||||
level.setBlock(pos, state.setValue(TerrainPlantBlock.GLOWING, wantedState), Block.UPDATE_CLIENTS)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.block.general
|
||||
|
||||
import com.cobblemon.mod.common.util.rotateShape
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.*
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.HorizontalDirectionalBlock.FACING
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
|
||||
import net.minecraft.world.level.block.state.properties.IntegerProperty
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
import kotlin.math.min
|
||||
|
||||
class MintLitterBlock(properties: Properties?) : Block(properties) {
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(FACING, Direction.NORTH)
|
||||
.setValue(AMOUNT, 1))
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
|
||||
val blockState = context.getLevel().getBlockState(context.getClickedPos())
|
||||
return if (blockState.`is`(this)) blockState.setValue<Int?, Int?>(
|
||||
AMOUNT,
|
||||
min(4, blockState.getValue<Int?>(AMOUNT) as Int + 1)
|
||||
) as BlockState else this.defaultBlockState().setValue<Direction?, Direction?>(
|
||||
FACING,
|
||||
context.getHorizontalDirection().getOpposite()
|
||||
) as BlockState
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(FACING)
|
||||
builder.add(AMOUNT)
|
||||
}
|
||||
|
||||
override fun updateShape(
|
||||
state: BlockState,
|
||||
direction: Direction?,
|
||||
neighborState: BlockState?,
|
||||
level: LevelAccessor?,
|
||||
pos: BlockPos?,
|
||||
neighborPos: BlockPos?
|
||||
): BlockState {
|
||||
return if (!state.canSurvive(level, pos)) Blocks.AIR.defaultBlockState() else super.updateShape(
|
||||
state,
|
||||
direction,
|
||||
neighborState,
|
||||
level,
|
||||
pos,
|
||||
neighborPos
|
||||
)
|
||||
}
|
||||
|
||||
override fun canSurvive(state: BlockState?, level: LevelReader, pos: BlockPos): Boolean {
|
||||
val posBelow = pos.below()
|
||||
return level.getBlockState(posBelow).isFaceSturdy(level, posBelow, Direction.UP)
|
||||
}
|
||||
|
||||
override fun propagatesSkylightDown(state: BlockState, level: BlockGetter?, pos: BlockPos?): Boolean {
|
||||
return state.getFluidState().isEmpty()
|
||||
}
|
||||
|
||||
public override fun canBeReplaced(state: BlockState, useContext: BlockPlaceContext): Boolean {
|
||||
return if (!useContext.isSecondaryUseActive && useContext.itemInHand.`is`(this.asItem()) && state.getValue(
|
||||
AMOUNT
|
||||
) <= MAX_AMOUNT
|
||||
) true else super.canBeReplaced(state, useContext)
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun rotate(state: BlockState, rotation: Rotation): BlockState {
|
||||
return state.setValue(HORIZONTAL_FACING, rotation.rotate(state.getValue(HORIZONTAL_FACING)))
|
||||
}
|
||||
|
||||
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape? {
|
||||
val litterBoxes = when (state.getValue(FACING)) {
|
||||
Direction.SOUTH -> SOUTH_SHAPES
|
||||
Direction.WEST -> WEST_SHAPES
|
||||
Direction.EAST -> EAST_SHAPES
|
||||
else -> NORTH_SHAPES
|
||||
}
|
||||
|
||||
var litterBox = litterBoxes[state.getValue(AMOUNT)]
|
||||
return litterBox
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC: MapCodec<MintLitterBlock> = RecordCodecBuilder.mapCodec {
|
||||
it.group(
|
||||
propertiesCodec()
|
||||
).apply(it, ::MintLitterBlock) }
|
||||
|
||||
val AMOUNT = IntegerProperty.create("amount", 1, 4)
|
||||
const val MAX_AMOUNT: Int = 4
|
||||
|
||||
private val NORTH_SHAPES = listOf(
|
||||
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.125, 1.0),
|
||||
Shapes.box(0.0, 0.0, 0.0, 0.5, 0.0625, 0.5),
|
||||
Shapes.box(0.0, 0.0, 0.0, 0.5, 0.0625, 1.0),
|
||||
Shapes.or(
|
||||
Shapes.box(0.0, 0.0, 0.0, 0.5, 0.0625, 1.0),
|
||||
Shapes.box(0.5, 0.0, 0.5, 1.0, 0.0625, 1.0)
|
||||
),
|
||||
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.0625, 1.0)
|
||||
)
|
||||
|
||||
private val SOUTH_SHAPES = createShapes(Direction.SOUTH)
|
||||
private val WEST_SHAPES = createShapes(Direction.WEST)
|
||||
private val EAST_SHAPES = createShapes(Direction.EAST)
|
||||
|
||||
private fun createShapes(to: Direction): List<VoxelShape> {
|
||||
return NORTH_SHAPES.map { rotateShape(Direction.NORTH, to, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -31,6 +31,7 @@ import com.cobblemon.mod.common.client.sound.instances.CancellableSoundInstance
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.item.PokeBallItem
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.pokemon.activestate.SentOutState
|
||||
import com.cobblemon.mod.common.util.DataKeys
|
||||
import com.cobblemon.mod.common.util.giveOrDropItemStack
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
@@ -235,6 +236,7 @@ class FossilMultiblockStructure (
|
||||
entity.setPos(fixedPosition.center.subtract(0.0, 0.5, 0.0))
|
||||
// TODO: Find a correct way to set the new entity's Yaw rotation. (Face away from the machine)
|
||||
if (world.addFreshEntity(entity)) {
|
||||
pokemon.state = SentOutState(entity)
|
||||
CobblemonEvents.FOSSIL_REVIVED.post(FossilRevivedEvent(pokemon, null))
|
||||
return true
|
||||
} else {
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
|
||||
import com.cobblemon.mod.common.api.storage.player.client.ClientTMMoveManager
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
|
||||
import com.cobblemon.mod.common.block.entity.TintBlockEntity
|
||||
import com.cobblemon.mod.common.block.sign.CobblemonSignBlock
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattle
|
||||
import com.cobblemon.mod.common.client.gui.PartyOverlay
|
||||
import com.cobblemon.mod.common.client.gui.PartyOverlayDataControl
|
||||
@@ -44,6 +45,7 @@ import com.cobblemon.mod.common.client.render.entity.PokeBobberEntityRenderer
|
||||
import com.cobblemon.mod.common.client.render.generic.GenericBedrockRenderer
|
||||
import com.cobblemon.mod.common.client.render.item.CobblemonBuiltinItemRendererRegistry
|
||||
import com.cobblemon.mod.common.client.render.item.PokemonItemRenderer
|
||||
import com.cobblemon.mod.common.client.render.layer.PlayerRecallBallRenderer
|
||||
import com.cobblemon.mod.common.client.render.layer.PokemonOnShoulderRenderer
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockAnimationRepository
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.BerryModelRepository
|
||||
@@ -238,6 +240,12 @@ object CobblemonClient {
|
||||
CobblemonBlocks.POKE_CAKE
|
||||
)
|
||||
|
||||
this.implementation.registerBlockRenderType(
|
||||
RenderType.translucent(),
|
||||
CobblemonBlocks.UPGRADE,
|
||||
CobblemonBlocks.DUBIOUS_DISC
|
||||
)
|
||||
|
||||
this.implementation.registerBlockRenderType(
|
||||
RenderType.cutout(),
|
||||
CobblemonBlocks.GILDED_CHEST,
|
||||
@@ -383,7 +391,132 @@ object CobblemonClient {
|
||||
CobblemonBlocks.X_SP_ATK,
|
||||
CobblemonBlocks.X_SP_DEF,
|
||||
CobblemonBlocks.X_SPEED,
|
||||
CobblemonBlocks.TM_MACHINE
|
||||
CobblemonBlocks.TM_MACHINE,
|
||||
|
||||
CobblemonBlocks.GALARICA_CUFF,
|
||||
CobblemonBlocks.GALARICA_WREATH,
|
||||
CobblemonBlocks.CHIPPED_POT,
|
||||
CobblemonBlocks.CRACKED_POT,
|
||||
CobblemonBlocks.UNREMARKABLE_TEACUP,
|
||||
CobblemonBlocks.MASTERPIECE_TEACUP,
|
||||
CobblemonBlocks.LUMINOUS_MOSS,
|
||||
CobblemonBlocks.ICY_ROCK,
|
||||
CobblemonBlocks.HEAT_ROCK,
|
||||
CobblemonBlocks.SMOOTH_ROCK,
|
||||
CobblemonBlocks.DAMP_ROCK,
|
||||
CobblemonBlocks.DESTINY_KNOT,
|
||||
CobblemonBlocks.METRONOME,
|
||||
CobblemonBlocks.KINGS_ROCK,
|
||||
CobblemonBlocks.FLAME_ORB,
|
||||
CobblemonBlocks.LIFE_ORB,
|
||||
CobblemonBlocks.TOXIC_ORB,
|
||||
CobblemonBlocks.LIGHT_BALL,
|
||||
CobblemonBlocks.SMOKE_BALL,
|
||||
CobblemonBlocks.NEVER_MELT_ICE,
|
||||
CobblemonBlocks.WIDE_LENS,
|
||||
CobblemonBlocks.ZOOM_LENS,
|
||||
CobblemonBlocks.SCOPE_LENS,
|
||||
CobblemonBlocks.MOOMOO_MILK,
|
||||
CobblemonBlocks.MEDICINAL_BREW,
|
||||
CobblemonBlocks.TERRAIN_EXTENDER,
|
||||
|
||||
CobblemonBlocks.RED_MINT_LEAF_LITTER,
|
||||
CobblemonBlocks.POTTED_RED_MINT,
|
||||
CobblemonBlocks.BLUE_MINT_LEAF_LITTER,
|
||||
CobblemonBlocks.POTTED_BLUE_MINT,
|
||||
CobblemonBlocks.CYAN_MINT_LEAF_LITTER,
|
||||
CobblemonBlocks.POTTED_CYAN_MINT,
|
||||
CobblemonBlocks.PINK_MINT_LEAF_LITTER,
|
||||
CobblemonBlocks.POTTED_PINK_MINT,
|
||||
CobblemonBlocks.GREEN_MINT_LEAF_LITTER,
|
||||
CobblemonBlocks.POTTED_GREEN_MINT,
|
||||
CobblemonBlocks.WHITE_MINT_LEAF_LITTER,
|
||||
CobblemonBlocks.POTTED_WHITE_MINT,
|
||||
|
||||
CobblemonBlocks.LOBAILA,
|
||||
CobblemonBlocks.POMOHANA,
|
||||
CobblemonBlocks.PAUVILLEA,
|
||||
CobblemonBlocks.WISTENSU,
|
||||
CobblemonBlocks.WISTENSU_VINE,
|
||||
CobblemonBlocks.ELECTRIC_SEED,
|
||||
CobblemonBlocks.VOLTACONIA,
|
||||
CobblemonBlocks.GRASSY_SEED,
|
||||
CobblemonBlocks.VERDROOT,
|
||||
CobblemonBlocks.MISTY_SEED,
|
||||
CobblemonBlocks.MISTLEAF,
|
||||
CobblemonBlocks.PSYCHIC_SEED,
|
||||
CobblemonBlocks.LUCIBELL,
|
||||
|
||||
CobblemonBlocks.POTTED_AGUAV_BERRY,
|
||||
CobblemonBlocks.POTTED_APICOT_BERRY,
|
||||
CobblemonBlocks.POTTED_ASPEAR_BERRY,
|
||||
CobblemonBlocks.POTTED_BABIRI_BERRY,
|
||||
CobblemonBlocks.POTTED_BELUE_BERRY,
|
||||
CobblemonBlocks.POTTED_BLUK_BERRY,
|
||||
CobblemonBlocks.POTTED_CHARTI_BERRY,
|
||||
CobblemonBlocks.POTTED_CHERI_BERRY,
|
||||
CobblemonBlocks.POTTED_CHESTO_BERRY,
|
||||
CobblemonBlocks.POTTED_CHILAN_BERRY,
|
||||
CobblemonBlocks.POTTED_CHOPLE_BERRY,
|
||||
CobblemonBlocks.POTTED_COBA_BERRY,
|
||||
CobblemonBlocks.POTTED_COLBUR_BERRY,
|
||||
CobblemonBlocks.POTTED_CORNN_BERRY,
|
||||
CobblemonBlocks.POTTED_CUSTAP_BERRY,
|
||||
CobblemonBlocks.POTTED_DURIN_BERRY,
|
||||
CobblemonBlocks.POTTED_EGGANT_BERRY,
|
||||
CobblemonBlocks.POTTED_ENIGMA_BERRY,
|
||||
CobblemonBlocks.POTTED_FIGY_BERRY,
|
||||
CobblemonBlocks.POTTED_GANLON_BERRY,
|
||||
CobblemonBlocks.POTTED_GREPA_BERRY,
|
||||
CobblemonBlocks.POTTED_HABAN_BERRY,
|
||||
CobblemonBlocks.POTTED_HONDEW_BERRY,
|
||||
CobblemonBlocks.POTTED_HOPO_BERRY,
|
||||
CobblemonBlocks.POTTED_IAPAPA_BERRY,
|
||||
CobblemonBlocks.POTTED_JABOCA_BERRY,
|
||||
CobblemonBlocks.POTTED_KASIB_BERRY,
|
||||
CobblemonBlocks.POTTED_KEBIA_BERRY,
|
||||
CobblemonBlocks.POTTED_KEE_BERRY,
|
||||
CobblemonBlocks.POTTED_KELPSY_BERRY,
|
||||
CobblemonBlocks.POTTED_LANSAT_BERRY,
|
||||
CobblemonBlocks.POTTED_LEPPA_BERRY,
|
||||
CobblemonBlocks.POTTED_LIECHI_BERRY,
|
||||
CobblemonBlocks.POTTED_LUM_BERRY,
|
||||
CobblemonBlocks.POTTED_MAGOST_BERRY,
|
||||
CobblemonBlocks.POTTED_MAGO_BERRY,
|
||||
CobblemonBlocks.POTTED_MARANGA_BERRY,
|
||||
CobblemonBlocks.POTTED_MICLE_BERRY,
|
||||
CobblemonBlocks.POTTED_NANAB_BERRY,
|
||||
CobblemonBlocks.POTTED_NOMEL_BERRY,
|
||||
CobblemonBlocks.POTTED_OCCA_BERRY,
|
||||
CobblemonBlocks.POTTED_ORAN_BERRY,
|
||||
CobblemonBlocks.POTTED_PAMTRE_BERRY,
|
||||
CobblemonBlocks.POTTED_PASSHO_BERRY,
|
||||
CobblemonBlocks.POTTED_PAYAPA_BERRY,
|
||||
CobblemonBlocks.POTTED_PECHA_BERRY,
|
||||
CobblemonBlocks.POTTED_PERSIM_BERRY,
|
||||
CobblemonBlocks.POTTED_PETAYA_BERRY,
|
||||
CobblemonBlocks.POTTED_PINAP_BERRY,
|
||||
CobblemonBlocks.POTTED_POMEG_BERRY,
|
||||
CobblemonBlocks.POTTED_QUALOT_BERRY,
|
||||
CobblemonBlocks.POTTED_RABUTA_BERRY,
|
||||
CobblemonBlocks.POTTED_RAWST_BERRY,
|
||||
CobblemonBlocks.POTTED_RAZZ_BERRY,
|
||||
CobblemonBlocks.POTTED_RINDO_BERRY,
|
||||
CobblemonBlocks.POTTED_ROSELI_BERRY,
|
||||
CobblemonBlocks.POTTED_ROWAP_BERRY,
|
||||
CobblemonBlocks.POTTED_SALAC_BERRY,
|
||||
CobblemonBlocks.POTTED_SHUCA_BERRY,
|
||||
CobblemonBlocks.POTTED_SITRUS_BERRY,
|
||||
CobblemonBlocks.POTTED_SPELON_BERRY,
|
||||
CobblemonBlocks.POTTED_STARF_BERRY,
|
||||
CobblemonBlocks.POTTED_TAMATO_BERRY,
|
||||
CobblemonBlocks.POTTED_TANGA_BERRY,
|
||||
CobblemonBlocks.POTTED_TOUGA_BERRY,
|
||||
CobblemonBlocks.POTTED_WACAN_BERRY,
|
||||
CobblemonBlocks.POTTED_WATMEL_BERRY,
|
||||
CobblemonBlocks.POTTED_WEPEAR_BERRY,
|
||||
CobblemonBlocks.POTTED_WIKI_BERRY,
|
||||
CobblemonBlocks.POTTED_YACHE_BERRY
|
||||
)
|
||||
|
||||
this.createBoatModelLayers()
|
||||
@@ -405,8 +538,10 @@ object CobblemonClient {
|
||||
var renderer: LivingEntityRenderer<Player, PlayerModel<Player>>? =
|
||||
skinMap?.get(PlayerSkin.Model.WIDE) as LivingEntityRenderer<Player, PlayerModel<Player>>
|
||||
renderer?.addLayer(PokemonOnShoulderRenderer(renderer))
|
||||
renderer?.addLayer(PlayerRecallBallRenderer(renderer))
|
||||
renderer = skinMap[PlayerSkin.Model.SLIM] as LivingEntityRenderer<Player, PlayerModel<Player>>?
|
||||
renderer?.addLayer(PokemonOnShoulderRenderer(renderer))
|
||||
renderer?.addLayer(PlayerRecallBallRenderer(renderer))
|
||||
}
|
||||
|
||||
private fun registerMenuScreens() {
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PrimaryAnimation
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
|
||||
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker
|
||||
import com.cobblemon.mod.common.client.render.pokemon.PokemonRenderer.Companion.ease
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
|
||||
@@ -320,6 +321,7 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
|
||||
ballOffset = 0f
|
||||
ballRotOffset = 0f
|
||||
sendOutPosition = null
|
||||
currentEntity.ownerUUID?.let { RecallArmAnimationTracker.start(it, currentEntity.exposedBall) }
|
||||
afterOnClient(seconds = BEAM_EXTEND_TIME) {
|
||||
activeSendoutScale = 1F
|
||||
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
|
||||
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) {
|
||||
if (stackHeight < height && stackHeight + entry.height > 0) {
|
||||
entry.render(guiGraphics, index, x, y + stackHeight, mouseX, mouseY, partialTick)
|
||||
}
|
||||
stackHeight += entry.height
|
||||
|
||||
@@ -0,0 +1,466 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.debug.speed
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.SpeedTunerEntry
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.SetSpeedTunerSprintSpeedPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.SetSpeedTunerWalkSpeedPacket
|
||||
import com.cobblemon.mod.common.platform.events.PlatformEvents
|
||||
import com.cobblemon.mod.common.platform.events.RenderEvent
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.multiplayer.ClientLevel
|
||||
import net.minecraft.client.renderer.LevelRenderer
|
||||
import net.minecraft.core.particles.ParticleTypes
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.network.chat.ClickEvent
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.HoverEvent
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
/**
|
||||
* Client side session for tuning species walk and sprint speeds. A display only pokemon runs a circle or a line at
|
||||
* exactly the speed being tuned, and edits are kept in blocks per second and saved to a file for applying to the
|
||||
* species data later.
|
||||
*/
|
||||
object SpeedTuner {
|
||||
enum class Gait(val jsonKey: String) { WALK("walkSpeed"), SPRINT("sprintSpeed") }
|
||||
enum class Track { CIRCLE, LINE }
|
||||
|
||||
// Vanilla player top speeds in blocks per second, shown and run alongside for comparison
|
||||
const val PLAYER_WALK_SPEED = 4.317
|
||||
const val PLAYER_SPRINT_SPEED = 5.612
|
||||
// Sprint speed shown for species without one yet, matches the data script default
|
||||
const val DEFAULT_SPRINT_SPEED = 5.6
|
||||
// Walk speed values are multiplied by this in PokemonMoveControl
|
||||
const val WALK_SPEED_SCALE = 2.5
|
||||
// Friction of ordinary blocks, walk estimates assume flat normal ground
|
||||
const val VANILLA_BLOCK_FRICTION = 0.6
|
||||
const val TICKS_PER_SECOND = 20.0
|
||||
|
||||
const val DEFAULT_CIRCLE_RADIUS = 6.0
|
||||
const val DEFAULT_LINE_LENGTH = 16.0
|
||||
// How often the track gets outlined, and the gap between outline markers in blocks
|
||||
const val TRACK_MARKER_INTERVAL_TICKS = 10
|
||||
const val TRACK_MARKER_SPACING = 1.0
|
||||
// Markers sit this far above the start height so they aren't inside the ground
|
||||
const val MARKER_HEIGHT = 0.1
|
||||
// Edits made while dragging a slider are written out at most this often
|
||||
const val AUTOSAVE_INTERVAL_TICKS = 40
|
||||
|
||||
private const val UNITS = "blocks per second"
|
||||
private val gson = GsonBuilder().setPrettyPrinting().create()
|
||||
private val sessionFile: Path
|
||||
get() = Minecraft.getInstance().gameDirectory.toPath().resolve("cobblemon-dev").resolve("speed_tuning_session.json")
|
||||
|
||||
var active = false
|
||||
private set
|
||||
var entries: List<SpeedTunerEntry> = emptyList()
|
||||
private set
|
||||
var selected: SpeedTunerEntry? = null
|
||||
private set
|
||||
var gait = Gait.SPRINT
|
||||
set(value) {
|
||||
field = value
|
||||
ensureSelectionVisible()
|
||||
restartRun()
|
||||
}
|
||||
var track = Track.CIRCLE
|
||||
set(value) {
|
||||
field = value
|
||||
restartRun()
|
||||
}
|
||||
var radius = DEFAULT_CIRCLE_RADIUS
|
||||
var lineLength = DEFAULT_LINE_LENGTH
|
||||
var paused = false
|
||||
// How fast the display pokemon plays, movement and animation together. 1 is real time
|
||||
var playbackSpeed = 1.0
|
||||
|
||||
// Species id to edited speeds by gait json key, all in blocks per second
|
||||
private val edits = mutableMapOf<ResourceLocation, MutableMap<String, Double>>()
|
||||
private val reviewed = mutableSetOf<ResourceLocation>()
|
||||
private var dirty = false
|
||||
private var subscribed = false
|
||||
|
||||
private var origin = Vec3.ZERO
|
||||
private var lineDirection = Vec3(0.0, 0.0, 1.0)
|
||||
private var distance = 0.0
|
||||
private var referenceDistance = 0.0
|
||||
private var ticks = 0L
|
||||
// The display pokemon's own clock in ticks, so it can run slower than the game or stand still
|
||||
private var displayTime = 0.0
|
||||
// Game ticks since the display pokemon last stepped, for how far between its steps it is
|
||||
private var gameTicksSinceStep = 0
|
||||
// Partial tick it was drawn at last, held while paused so it freezes exactly where it was
|
||||
private var lastPartialTick = 0F
|
||||
private var puppet: PokemonEntity? = null
|
||||
|
||||
fun open(entries: List<SpeedTunerEntry>) {
|
||||
val player = Minecraft.getInstance().player ?: return
|
||||
this.entries = entries.sortedBy { it.dex }
|
||||
if (!active) {
|
||||
origin = player.position()
|
||||
val look = Vec3(player.lookAngle.x, 0.0, player.lookAngle.z)
|
||||
lineDirection = if (look.lengthSqr() > 0) look.normalize() else Vec3(0.0, 0.0, 1.0)
|
||||
load()
|
||||
active = true
|
||||
// The server starts from the species data, so bring it up to date with the loaded session
|
||||
edits.forEach { (id, speeds) ->
|
||||
speeds[Gait.SPRINT.jsonKey]?.let { sendSprintSpeed(id, it.toFloat()) }
|
||||
speeds[Gait.WALK.jsonKey]?.let { sendWalkSpeed(id, walkSpeedFor(it)) }
|
||||
}
|
||||
}
|
||||
subscribe()
|
||||
ensureSelectionVisible()
|
||||
Minecraft.getInstance().setScreen(SpeedTunerScreen())
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
save()
|
||||
active = false
|
||||
puppet = null
|
||||
selected = null
|
||||
}
|
||||
|
||||
/** Species that make sense for the current gait. Only species with a sprint speed can sprint. */
|
||||
fun visibleEntries(): List<SpeedTunerEntry> {
|
||||
return if (gait == Gait.SPRINT) entries.filter { it.sprintSpeed != null } else entries
|
||||
}
|
||||
|
||||
fun select(entry: SpeedTunerEntry) {
|
||||
selected = entry
|
||||
puppet = null
|
||||
restartRun()
|
||||
}
|
||||
|
||||
fun step(offset: Int) {
|
||||
val visible = visibleEntries().ifEmpty { return }
|
||||
val index = visible.indexOf(selected).coerceAtLeast(0)
|
||||
select(visible[Math.floorMod(index + offset, visible.size)])
|
||||
}
|
||||
|
||||
/** Finds a species by dex number or by name, preferring names that start with the search. */
|
||||
fun search(text: String): Boolean {
|
||||
val query = text.trim().lowercase().ifEmpty { return false }
|
||||
val visible = visibleEntries()
|
||||
val match = query.toIntOrNull()?.let { dex -> visible.firstOrNull { it.dex == dex } }
|
||||
?: visible.firstOrNull { it.speciesId.path.startsWith(query) }
|
||||
?: visible.firstOrNull { it.speciesId.path.contains(query) }
|
||||
?: return false
|
||||
select(match)
|
||||
return true
|
||||
}
|
||||
|
||||
fun speed(entry: SpeedTunerEntry, gait: Gait = this.gait): Double {
|
||||
return edits[entry.speciesId]?.get(gait.jsonKey) ?: originalSpeed(entry, gait)
|
||||
}
|
||||
|
||||
fun originalSpeed(entry: SpeedTunerEntry, gait: Gait = this.gait): Double {
|
||||
return when (gait) {
|
||||
Gait.SPRINT -> entry.sprintSpeed?.toDouble() ?: DEFAULT_SPRINT_SPEED
|
||||
Gait.WALK -> estimatedWalkSpeed(entry)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk speed is still relative in the species data, so this works out what it comes to in blocks per second on
|
||||
* normal ground. Walkers always use the same speed modifier so this is exact.
|
||||
*/
|
||||
fun estimatedWalkSpeed(entry: SpeedTunerEntry): Double {
|
||||
val input = walkInputPerWalkSpeed() * entry.walkSpeed
|
||||
return PokemonMoveControl.vanillaGroundTopSpeed(input, VANILLA_BLOCK_FRICTION) * TICKS_PER_SECOND
|
||||
}
|
||||
|
||||
/** The other way round, walkSpeed that comes out at [blocksPerSecond]. Top speed grows with the square of the input */
|
||||
fun walkSpeedFor(blocksPerSecond: Double): Float {
|
||||
val topSpeedPerInputSquared = PokemonMoveControl.vanillaGroundTopSpeed(1.0, VANILLA_BLOCK_FRICTION)
|
||||
val input = sqrt(blocksPerSecond / TICKS_PER_SECOND / topSpeedPerInputSquared)
|
||||
return (input / walkInputPerWalkSpeed()).toFloat()
|
||||
}
|
||||
|
||||
// Move control input for each unit of walkSpeed
|
||||
private fun walkInputPerWalkSpeed(): Double {
|
||||
return Attributes.MOVEMENT_SPEED.value().defaultValue * PokemonMoveControl.GROUND_SPEED_MODIFIER * WALK_SPEED_SCALE
|
||||
}
|
||||
|
||||
fun referenceSpeed() = if (gait == Gait.SPRINT) PLAYER_SPRINT_SPEED else PLAYER_WALK_SPEED
|
||||
|
||||
fun setSpeed(value: Double) {
|
||||
val entry = selected ?: return
|
||||
edits.getOrPut(entry.speciesId) { mutableMapOf() }[gait.jsonKey] = value
|
||||
dirty = true
|
||||
when (gait) {
|
||||
Gait.SPRINT -> sendSprintSpeed(entry.speciesId, value.toFloat())
|
||||
Gait.WALK -> sendWalkSpeed(entry.speciesId, walkSpeedFor(value))
|
||||
}
|
||||
}
|
||||
|
||||
fun resetSpeed() {
|
||||
val entry = selected ?: return
|
||||
edits[entry.speciesId]?.let {
|
||||
it.remove(gait.jsonKey)
|
||||
if (it.isEmpty()) edits.remove(entry.speciesId)
|
||||
}
|
||||
dirty = true
|
||||
when (gait) {
|
||||
Gait.SPRINT -> sendSprintSpeed(entry.speciesId, entry.sprintSpeed)
|
||||
Gait.WALK -> sendWalkSpeed(entry.speciesId, entry.walkSpeed)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendSprintSpeed(speciesId: ResourceLocation, sprintSpeed: Float?) {
|
||||
SetSpeedTunerSprintSpeedPacket(speciesId, sprintSpeed).sendToServer()
|
||||
}
|
||||
|
||||
/** Walk speeds go over in the species data's own units, converted from the tuner's blocks per second */
|
||||
private fun sendWalkSpeed(speciesId: ResourceLocation, walkSpeed: Float) {
|
||||
SetSpeedTunerWalkSpeedPacket(speciesId, walkSpeed).sendToServer()
|
||||
}
|
||||
|
||||
fun isEdited(entry: SpeedTunerEntry) = edits[entry.speciesId]?.containsKey(gait.jsonKey) == true
|
||||
|
||||
fun isReviewed(entry: SpeedTunerEntry) = entry.speciesId in reviewed
|
||||
|
||||
fun toggleReviewed() {
|
||||
val entry = selected ?: return
|
||||
if (!reviewed.remove(entry.speciesId)) reviewed.add(entry.speciesId)
|
||||
dirty = true
|
||||
}
|
||||
|
||||
fun editedCount() = edits.size
|
||||
|
||||
fun reviewedCount() = reviewed.size
|
||||
|
||||
fun restartRun() {
|
||||
distance = 0.0
|
||||
referenceDistance = 0.0
|
||||
}
|
||||
|
||||
private fun ensureSelectionVisible() {
|
||||
val visible = visibleEntries()
|
||||
if (selected !in visible) {
|
||||
visible.firstOrNull()?.let { select(it) } ?: run { selected = null }
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribe() {
|
||||
if (subscribed) return
|
||||
subscribed = true
|
||||
PlatformEvents.CLIENT_TICK_POST.subscribe { tick() }
|
||||
PlatformEvents.RENDER.subscribe { render(it) }
|
||||
PlatformEvents.CLIENT_PLAYER_LOGOUT.subscribe { if (active) stop() }
|
||||
}
|
||||
|
||||
private fun tick() {
|
||||
if (!active) return
|
||||
ticks++
|
||||
if (dirty && ticks % AUTOSAVE_INTERVAL_TICKS == 0L) save()
|
||||
val level = Minecraft.getInstance().level ?: return
|
||||
val entry = selected ?: return
|
||||
val display = puppet?.takeIf { it.level() === level } ?: createPuppet(level, entry)?.also { puppet = it } ?: return
|
||||
|
||||
val rate = if (paused) 0.0 else playbackSpeed
|
||||
val before = displayTime
|
||||
displayTime += rate
|
||||
// One display tick each time its clock passes a whole tick, so slow motion moves and animates less often
|
||||
val steps = (floor(displayTime) - floor(before)).toInt()
|
||||
repeat(steps) { stepDisplay(display, entry) }
|
||||
// Hold while paused so it picks up from the same spot when resumed
|
||||
if (steps > 0) gameTicksSinceStep = 0 else if (!paused) gameTicksSinceStep++
|
||||
display.isPokemonSprinting = gait == Gait.SPRINT
|
||||
display.entityData.set(PokemonEntity.POSE_TYPE, PoseType.WALK)
|
||||
display.entityData.set(PokemonEntity.MOVING, !paused)
|
||||
|
||||
val reference = trackPosition(referenceDistance)
|
||||
level.addParticle(ParticleTypes.ELECTRIC_SPARK, reference.x, reference.y + MARKER_HEIGHT, reference.z, 0.0, 0.0, 0.0)
|
||||
if (ticks % TRACK_MARKER_INTERVAL_TICKS == 0L) outlineTrack(level)
|
||||
}
|
||||
|
||||
/** One tick of the display pokemon's own clock, moving it along the track and ticking its animations */
|
||||
private fun stepDisplay(display: PokemonEntity, entry: SpeedTunerEntry) {
|
||||
distance += speed(entry) / TICKS_PER_SECOND
|
||||
referenceDistance += referenceSpeed() / TICKS_PER_SECOND
|
||||
display.tickCount++
|
||||
val lapLength = lapLength()
|
||||
// Line runs start over from the beginning, so jump without smoothing between the two ends
|
||||
val wrapped = distance >= lapLength
|
||||
distance %= lapLength
|
||||
referenceDistance %= lapLength
|
||||
|
||||
display.setOldPosAndRot()
|
||||
display.yBodyRotO = display.yBodyRot
|
||||
display.yHeadRotO = display.yHeadRot
|
||||
val position = trackPosition(distance)
|
||||
// Keep yaw continuous with last tick so nothing lerping it spins round at the wrap
|
||||
val yaw = display.yBodyRot + Mth.wrapDegrees(trackYaw(distance) - display.yBodyRot)
|
||||
display.setPos(position.x, position.y, position.z)
|
||||
display.yRot = yaw
|
||||
display.yBodyRot = yaw
|
||||
display.yHeadRot = yaw
|
||||
if (wrapped && track == Track.LINE) {
|
||||
display.setOldPosAndRot()
|
||||
display.yBodyRotO = yaw
|
||||
display.yHeadRotO = yaw
|
||||
}
|
||||
// Animation age and pose changes advance in the client delegate's tick, which normally runs from the
|
||||
// entity's own tick. The display pokemon is never ticked so do it here
|
||||
display.delegate.tick(display)
|
||||
}
|
||||
|
||||
private fun render(event: RenderEvent) {
|
||||
if (!active) return
|
||||
val puppet = puppet ?: return
|
||||
val level = Minecraft.getInstance().level ?: return
|
||||
if (puppet.level() !== level) return
|
||||
|
||||
val camera = event.camera.position
|
||||
// How far between its own steps the display pokemon is, so slow motion stays smooth and paused really stops
|
||||
val partialTick = if (paused) {
|
||||
lastPartialTick
|
||||
} else {
|
||||
val gamePartialTick = event.tickCounter.getGameTimeDeltaPartialTick(false)
|
||||
((gameTicksSinceStep + gamePartialTick) * playbackSpeed).coerceAtMost(1.0).toFloat()
|
||||
}
|
||||
lastPartialTick = partialTick
|
||||
val x = Mth.lerp(partialTick.toDouble(), puppet.xo, puppet.x) - camera.x
|
||||
val y = Mth.lerp(partialTick.toDouble(), puppet.yo, puppet.y) - camera.y
|
||||
val z = Mth.lerp(partialTick.toDouble(), puppet.zo, puppet.z) - camera.z
|
||||
val light = LevelRenderer.getLightColor(level, puppet.blockPosition())
|
||||
val buffers = Minecraft.getInstance().renderBuffers().bufferSource()
|
||||
Minecraft.getInstance().entityRenderDispatcher.render(puppet, x, y, z, puppet.yRot, partialTick, event.poseStack, buffers, light)
|
||||
buffers.endBatch()
|
||||
}
|
||||
|
||||
/** Same setup as HabitatBlockRenderer, a pokemon that only exists to be drawn. */
|
||||
private fun createPuppet(level: ClientLevel, entry: SpeedTunerEntry): PokemonEntity? {
|
||||
val species = PokemonSpecies.getByIdentifier(entry.speciesId) ?: return null
|
||||
val pokemon = Pokemon().apply {
|
||||
isClient = true
|
||||
this.species = species
|
||||
initialize()
|
||||
}
|
||||
return PokemonEntity(level, pokemon).apply {
|
||||
noPhysics = true
|
||||
setNoGravity(true)
|
||||
val start = trackPosition(distance)
|
||||
setPos(start.x, start.y, start.z)
|
||||
setOldPosAndRot()
|
||||
}
|
||||
}
|
||||
|
||||
private fun lapLength() = if (track == Track.CIRCLE) 2 * PI * radius else lineLength
|
||||
|
||||
private fun trackPosition(along: Double): Vec3 {
|
||||
return when (track) {
|
||||
Track.CIRCLE -> {
|
||||
val angle = along / radius
|
||||
origin.add(cos(angle) * radius, 0.0, sin(angle) * radius)
|
||||
}
|
||||
Track.LINE -> origin.add(lineDirection.scale(along))
|
||||
}
|
||||
}
|
||||
|
||||
private fun trackYaw(along: Double): Float {
|
||||
val direction = when (track) {
|
||||
// Tangent of the circle in the direction of travel
|
||||
Track.CIRCLE -> (along / radius).let { Vec3(-sin(it), 0.0, cos(it)) }
|
||||
Track.LINE -> lineDirection
|
||||
}
|
||||
return (Mth.atan2(direction.z, direction.x) * Mth.RAD_TO_DEG).toFloat() - 90F
|
||||
}
|
||||
|
||||
private fun outlineTrack(level: ClientLevel) {
|
||||
val markers = (lapLength() / TRACK_MARKER_SPACING).toInt()
|
||||
for (i in 0..markers) {
|
||||
val marker = trackPosition(i * TRACK_MARKER_SPACING)
|
||||
level.addParticle(ParticleTypes.COMPOSTER, marker.x, marker.y + MARKER_HEIGHT, marker.z, 0.0, 0.0, 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toJson(): JsonObject {
|
||||
val species = JsonObject()
|
||||
edits.toSortedMap(compareBy { it.toString() }).forEach { (id, speeds) ->
|
||||
species.add(id.toString(), JsonObject().also { speedsJson -> speeds.forEach { (key, value) -> speedsJson.addProperty(key, value) } })
|
||||
}
|
||||
return JsonObject().apply {
|
||||
addProperty("units", UNITS)
|
||||
add("species", species)
|
||||
add("reviewed", JsonArray().also { array -> reviewed.map { it.toString() }.sorted().forEach { array.add(it) } })
|
||||
}
|
||||
}
|
||||
|
||||
fun save() {
|
||||
runCatching {
|
||||
Files.createDirectories(sessionFile.parent)
|
||||
Files.writeString(sessionFile, gson.toJson(toJson()))
|
||||
dirty = false
|
||||
}.onFailure { Cobblemon.LOGGER.error("Couldn't save the speed tuning session", it) }
|
||||
}
|
||||
|
||||
/** Writes a timestamped copy of the session next to it and posts links to it in chat. */
|
||||
fun export() {
|
||||
save()
|
||||
val stamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"))
|
||||
val file = sessionFile.resolveSibling("speed_tuning_$stamp.json").toAbsolutePath()
|
||||
runCatching {
|
||||
Files.writeString(file, gson.toJson(toJson()))
|
||||
val message = Component.literal("Speed tuning exported: ")
|
||||
.append(chatLink("[open file]", ClickEvent.Action.OPEN_FILE, file.toString()))
|
||||
.append(" ")
|
||||
.append(chatLink("[open folder]", ClickEvent.Action.OPEN_FILE, file.parent.toString()))
|
||||
.append(" ")
|
||||
.append(chatLink("[copy path]", ClickEvent.Action.COPY_TO_CLIPBOARD, file.toString()))
|
||||
Minecraft.getInstance().player?.displayClientMessage(message, false)
|
||||
}.onFailure { Cobblemon.LOGGER.error("Couldn't export the speed tuning session", it) }
|
||||
}
|
||||
|
||||
private fun chatLink(label: String, action: ClickEvent.Action, value: String): Component {
|
||||
return Component.literal(label).withStyle { style ->
|
||||
style.withColor(ChatFormatting.AQUA)
|
||||
.withUnderlined(true)
|
||||
.withClickEvent(ClickEvent(action, value))
|
||||
.withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal(value)))
|
||||
}
|
||||
}
|
||||
|
||||
private fun load() {
|
||||
edits.clear()
|
||||
reviewed.clear()
|
||||
if (!Files.exists(sessionFile)) return
|
||||
runCatching {
|
||||
val json = JsonParser.parseString(Files.readString(sessionFile)).asJsonObject
|
||||
json.getAsJsonObject("species")?.entrySet()?.forEach { (id, speeds) ->
|
||||
val map = edits.getOrPut(ResourceLocation.parse(id)) { mutableMapOf() }
|
||||
speeds.asJsonObject.entrySet().forEach { (key, value) -> map[key] = value.asDouble }
|
||||
}
|
||||
json.getAsJsonArray("reviewed")?.forEach { reviewed.add(ResourceLocation.parse(it.asString)) }
|
||||
}.onFailure { Cobblemon.LOGGER.error("Couldn't load the speed tuning session", it) }
|
||||
}
|
||||
}
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* 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.debug.speed
|
||||
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import java.awt.Color
|
||||
import java.util.Locale
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.components.AbstractSliderButton
|
||||
import net.minecraft.client.gui.components.Button
|
||||
import net.minecraft.client.gui.components.EditBox
|
||||
import net.minecraft.client.gui.screens.Screen
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.util.Mth
|
||||
import org.lwjgl.glfw.GLFW
|
||||
|
||||
/**
|
||||
* Panel for [SpeedTuner] down the left side of the screen. Doesn't pause and leaves the world visible so the display
|
||||
* pokemon can be watched from anywhere. Closing it leaves the session running, the debug keybind brings it back.
|
||||
*/
|
||||
class SpeedTunerScreen : Screen("Speed Tuner".text()) {
|
||||
companion object {
|
||||
const val PANEL_WIDTH = 180
|
||||
const val MARGIN = 6
|
||||
const val ROW_HEIGHT = 20
|
||||
const val ROW_GAP = 4
|
||||
const val TEXT_LINE_HEIGHT = 11
|
||||
// Typed speeds can go up to this, the slider only covers the usual range
|
||||
const val MAX_SPEED = 20.0
|
||||
const val SLIDER_MAX_SPEED = 8.0
|
||||
// Blocks per second the - and + buttons change the speed by
|
||||
const val SPEED_STEP = 0.05
|
||||
val PLAYBACK_SPEEDS = listOf(1.0, 0.5, 0.25, 0.1)
|
||||
const val MIN_RADIUS = 2.0
|
||||
const val MAX_RADIUS = 24.0
|
||||
const val MIN_LINE_LENGTH = 4.0
|
||||
const val MAX_LINE_LENGTH = 48.0
|
||||
val PANEL_COLOUR = Color(0, 0, 0, 150).rgb
|
||||
val EDITED_COLOUR = Color(255, 200, 80).rgb
|
||||
val TEXT_COLOUR = Color(255, 255, 255).rgb
|
||||
}
|
||||
|
||||
private lateinit var searchBox: EditBox
|
||||
private lateinit var speedBox: EditBox
|
||||
private lateinit var speedSlider: DecimalSlider
|
||||
private var infoTop = 0
|
||||
|
||||
override fun isPauseScreen() = false
|
||||
|
||||
override fun init() {
|
||||
super.init()
|
||||
val fullWidth = PANEL_WIDTH - MARGIN * 2
|
||||
val halfWidth = (fullWidth - ROW_GAP) / 2
|
||||
var y = MARGIN
|
||||
fun nextRow(): Int = y.also { y += ROW_HEIGHT + ROW_GAP }
|
||||
|
||||
nextRow().let { row ->
|
||||
addRenderableWidget(Button.builder("<".text()) { step(-1) }.bounds(MARGIN, row, ROW_HEIGHT, ROW_HEIGHT).build())
|
||||
addRenderableWidget(Button.builder(">".text()) { step(1) }.bounds(PANEL_WIDTH - MARGIN - ROW_HEIGHT, row, ROW_HEIGHT, ROW_HEIGHT).build())
|
||||
}
|
||||
searchBox = addRenderableWidget(EditBox(font, MARGIN, nextRow(), fullWidth, ROW_HEIGHT, "Search".text())).apply {
|
||||
setHint("Dex number or name, Enter".text())
|
||||
}
|
||||
|
||||
nextRow().let { row ->
|
||||
addRenderableWidget(Button.builder("Gait: ${SpeedTuner.gait.name.lowercase()}".text()) {
|
||||
SpeedTuner.gait = if (SpeedTuner.gait == SpeedTuner.Gait.SPRINT) SpeedTuner.Gait.WALK else SpeedTuner.Gait.SPRINT
|
||||
rebuildWidgets()
|
||||
}.bounds(MARGIN, row, halfWidth, ROW_HEIGHT).build())
|
||||
addRenderableWidget(Button.builder("Track: ${SpeedTuner.track.name.lowercase()}".text()) {
|
||||
SpeedTuner.track = if (SpeedTuner.track == SpeedTuner.Track.CIRCLE) SpeedTuner.Track.LINE else SpeedTuner.Track.CIRCLE
|
||||
rebuildWidgets()
|
||||
}.bounds(MARGIN + halfWidth + ROW_GAP, row, halfWidth, ROW_HEIGHT).build())
|
||||
}
|
||||
|
||||
val entry = SpeedTuner.selected
|
||||
nextRow().let { row ->
|
||||
addRenderableWidget(Button.builder("-".text()) { nudgeSpeed(-SPEED_STEP) }.bounds(MARGIN, row, ROW_HEIGHT, ROW_HEIGHT).build())
|
||||
speedSlider = addRenderableWidget(
|
||||
DecimalSlider(MARGIN + ROW_HEIGHT + ROW_GAP, row, fullWidth - 2 * (ROW_HEIGHT + ROW_GAP), "Speed", 0.0, SLIDER_MAX_SPEED, entry?.let { SpeedTuner.speed(it) } ?: 0.0) {
|
||||
SpeedTuner.setSpeed(it)
|
||||
speedBox.value = format(it)
|
||||
}
|
||||
)
|
||||
addRenderableWidget(Button.builder("+".text()) { nudgeSpeed(SPEED_STEP) }.bounds(PANEL_WIDTH - MARGIN - ROW_HEIGHT, row, ROW_HEIGHT, ROW_HEIGHT).build())
|
||||
}
|
||||
nextRow().let { row ->
|
||||
speedBox = addRenderableWidget(EditBox(font, MARGIN, row, halfWidth, ROW_HEIGHT, "Speed".text())).apply {
|
||||
value = entry?.let { format(SpeedTuner.speed(it)) } ?: ""
|
||||
}
|
||||
addRenderableWidget(Button.builder("Set".text()) { applyTypedSpeed() }.bounds(MARGIN + halfWidth + ROW_GAP, row, halfWidth, ROW_HEIGHT).build())
|
||||
}
|
||||
|
||||
if (SpeedTuner.track == SpeedTuner.Track.CIRCLE) {
|
||||
addRenderableWidget(DecimalSlider(MARGIN, nextRow(), fullWidth, "Radius", MIN_RADIUS, MAX_RADIUS, SpeedTuner.radius) {
|
||||
SpeedTuner.radius = it
|
||||
})
|
||||
} else {
|
||||
addRenderableWidget(DecimalSlider(MARGIN, nextRow(), fullWidth, "Length", MIN_LINE_LENGTH, MAX_LINE_LENGTH, SpeedTuner.lineLength) {
|
||||
SpeedTuner.lineLength = it
|
||||
})
|
||||
}
|
||||
|
||||
nextRow().let { row ->
|
||||
addRenderableWidget(Button.builder((if (SpeedTuner.paused) "Resume" else "Pause").text()) {
|
||||
SpeedTuner.paused = !SpeedTuner.paused
|
||||
rebuildWidgets()
|
||||
}.bounds(MARGIN, row, halfWidth, ROW_HEIGHT).build())
|
||||
addRenderableWidget(Button.builder("Reset speed".text()) {
|
||||
SpeedTuner.resetSpeed()
|
||||
rebuildWidgets()
|
||||
}.bounds(MARGIN + halfWidth + ROW_GAP, row, halfWidth, ROW_HEIGHT).build())
|
||||
}
|
||||
addRenderableWidget(Button.builder("Playback: ${format(SpeedTuner.playbackSpeed)}x".text()) {
|
||||
val next = (PLAYBACK_SPEEDS.indexOf(SpeedTuner.playbackSpeed) + 1) % PLAYBACK_SPEEDS.size
|
||||
SpeedTuner.playbackSpeed = PLAYBACK_SPEEDS[next]
|
||||
rebuildWidgets()
|
||||
}.bounds(MARGIN, nextRow(), fullWidth, ROW_HEIGHT).build())
|
||||
nextRow().let { row ->
|
||||
val reviewed = entry?.let { SpeedTuner.isReviewed(it) } == true
|
||||
addRenderableWidget(Button.builder((if (reviewed) "Reviewed ✔" else "Mark reviewed").text()) {
|
||||
SpeedTuner.toggleReviewed()
|
||||
rebuildWidgets()
|
||||
}.bounds(MARGIN, row, halfWidth, ROW_HEIGHT).build())
|
||||
addRenderableWidget(Button.builder("Export".text()) { SpeedTuner.export() }
|
||||
.bounds(MARGIN + halfWidth + ROW_GAP, row, halfWidth, ROW_HEIGHT).build())
|
||||
}
|
||||
addRenderableWidget(Button.builder("End session".text()) {
|
||||
SpeedTuner.stop()
|
||||
onClose()
|
||||
}.bounds(MARGIN, nextRow(), fullWidth, ROW_HEIGHT).build())
|
||||
|
||||
infoTop = y
|
||||
}
|
||||
|
||||
private fun step(offset: Int) {
|
||||
SpeedTuner.step(offset)
|
||||
rebuildWidgets()
|
||||
}
|
||||
|
||||
private fun nudgeSpeed(change: Double) {
|
||||
val entry = SpeedTuner.selected ?: return
|
||||
SpeedTuner.setSpeed((SpeedTuner.speed(entry) + change).coerceIn(0.0, MAX_SPEED))
|
||||
rebuildWidgets()
|
||||
}
|
||||
|
||||
private fun applyTypedSpeed() {
|
||||
val typed = speedBox.value.toDoubleOrNull() ?: return
|
||||
SpeedTuner.setSpeed(typed.coerceIn(0.0, MAX_SPEED))
|
||||
rebuildWidgets()
|
||||
}
|
||||
|
||||
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
|
||||
if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER) {
|
||||
if (searchBox.isFocused) {
|
||||
if (SpeedTuner.search(searchBox.value)) rebuildWidgets()
|
||||
return true
|
||||
}
|
||||
if (speedBox.isFocused) {
|
||||
applyTypedSpeed()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.keyPressed(keyCode, scanCode, modifiers)
|
||||
}
|
||||
|
||||
override fun onClose() {
|
||||
SpeedTuner.save()
|
||||
super.onClose()
|
||||
}
|
||||
|
||||
// Only darken the panel so the world stays visible
|
||||
override fun renderBackground(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
|
||||
guiGraphics.fill(0, 0, PANEL_WIDTH, height, PANEL_COLOUR)
|
||||
}
|
||||
|
||||
override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
|
||||
super.render(guiGraphics, mouseX, mouseY, partialTick)
|
||||
val entry = SpeedTuner.selected
|
||||
val title = entry?.let { selected ->
|
||||
val name = PokemonSpecies.getByIdentifier(selected.speciesId)?.translatedName ?: selected.speciesId.path.text()
|
||||
Component.literal("#${selected.dex} ").append(name)
|
||||
} ?: "No species".text()
|
||||
guiGraphics.drawCenteredString(font, title, PANEL_WIDTH / 2, MARGIN + (ROW_HEIGHT - font.lineHeight) / 2, TEXT_COLOUR)
|
||||
|
||||
var y = infoTop
|
||||
fun line(text: String, colour: Int = TEXT_COLOUR) {
|
||||
guiGraphics.drawString(font, text, MARGIN, y, colour)
|
||||
y += TEXT_LINE_HEIGHT
|
||||
}
|
||||
if (entry != null) {
|
||||
val speed = SpeedTuner.speed(entry)
|
||||
line("Speed: ${format(speed)} blocks/s", if (SpeedTuner.isEdited(entry)) EDITED_COLOUR else TEXT_COLOUR)
|
||||
line("Original: ${format(SpeedTuner.originalSpeed(entry))} blocks/s")
|
||||
if (SpeedTuner.gait == SpeedTuner.Gait.WALK) {
|
||||
line("(estimated, raw walkSpeed ${format(entry.walkSpeed.toDouble())})")
|
||||
}
|
||||
}
|
||||
line("Sparks: player ${SpeedTuner.gait.name.lowercase()} ${format(SpeedTuner.referenceSpeed())} blocks/s")
|
||||
line("Edited: ${SpeedTuner.editedCount()} Reviewed: ${SpeedTuner.reviewedCount()}")
|
||||
line("Species: ${SpeedTuner.visibleEntries().size}")
|
||||
}
|
||||
|
||||
private fun format(value: Double) = String.format(Locale.ROOT, "%.2f", value)
|
||||
|
||||
/** Slider over a decimal range that reports every change. */
|
||||
class DecimalSlider(
|
||||
x: Int,
|
||||
y: Int,
|
||||
width: Int,
|
||||
private val label: String,
|
||||
private val min: Double,
|
||||
private val max: Double,
|
||||
initial: Double,
|
||||
private val onChange: (Double) -> Unit
|
||||
) : AbstractSliderButton(x, y, width, ROW_HEIGHT, Component.empty(), 0.0) {
|
||||
private val current: Double
|
||||
get() = Mth.lerp(value, min, max)
|
||||
|
||||
init {
|
||||
value = ((initial - min) / (max - min)).coerceIn(0.0, 1.0)
|
||||
updateMessage()
|
||||
}
|
||||
|
||||
override fun updateMessage() {
|
||||
message = "$label: ${String.format(Locale.ROOT, "%.2f", current)}".text()
|
||||
}
|
||||
|
||||
override fun applyValue() {
|
||||
onChange(current)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,32 +36,33 @@ import com.cobblemon.mod.common.client.gui.handleProfileDebugKeys
|
||||
import com.cobblemon.mod.common.client.gui.logProfileTranslations
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_HEIGHT
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_WIDTH
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_HEIGHT
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HEADER_BAR_HEIGHT
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.ICONS_ARROW_PADDING_AROUND
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.ICONS_PADDING_AROUND
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCALE
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_ABILITIES
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_DESCRIPTION
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_DROPS
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_EVOLUTION_TREE
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_ICON_SIZE
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_MOVES
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_SIZE
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_STATS
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.AbilitiesWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.DescriptionWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.DropsScrollingWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.EntriesScrollingWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.MovesLearnsetWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.PokemonInfoWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.SearchWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.SizeWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.StatsWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.*
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree.EvolutionTreeWidget
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree.debugEvolutionRequirementRendering
|
||||
import com.cobblemon.mod.common.client.pokedex.PokedexType
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
|
||||
import com.cobblemon.mod.common.pokemon.Gender
|
||||
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.isInventoryKeyPressed
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.mojang.blaze3d.platform.InputConstants
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.components.EditBox
|
||||
@@ -94,6 +95,10 @@ class PokedexGUI private constructor(
|
||||
|
||||
private val categoryBarOverlay = cobblemonResource("textures/gui/pokedex/pokedex_screen_bar_category.png")
|
||||
|
||||
// TODO: separate into smaller components / widgets?
|
||||
private val backgroundOverlay = cobblemonResource("textures/gui/pokedex/pokedex_screen_info_overlay.png")
|
||||
private val expandedBackgroundOverlay = cobblemonResource("textures/gui/pokedex/pokedex_screen_info_overlay_expanded.png")
|
||||
|
||||
private val tabSelectArrow = cobblemonResource("textures/gui/pokedex/select_arrow.png")
|
||||
private val tabIcons = arrayOf(
|
||||
cobblemonResource("textures/gui/pokedex/tab_info.png"),
|
||||
@@ -101,7 +106,8 @@ class PokedexGUI private constructor(
|
||||
cobblemonResource("textures/gui/pokedex/tab_size.png"),
|
||||
cobblemonResource("textures/gui/pokedex/tab_stats.png"),
|
||||
cobblemonResource("textures/gui/pokedex/tab_drops.png"),
|
||||
cobblemonResource("textures/gui/pokedex/tab_moves.png")
|
||||
cobblemonResource("textures/gui/pokedex/tab_moves.png"),
|
||||
cobblemonResource("textures/gui/pokedex/tab_evolution_tree.png"),
|
||||
)
|
||||
|
||||
val arrowUpIcon = cobblemonResource("textures/gui/pokedex/arrow_up.png")
|
||||
@@ -114,6 +120,8 @@ class PokedexGUI private constructor(
|
||||
TAB_MOVES
|
||||
)
|
||||
|
||||
private val ICONS_PADDING_BETWEEN = (BASE_WIDTH / 2 - ICONS_PADDING_AROUND * 2) / tabIcons.size
|
||||
|
||||
/**
|
||||
* Attempts to open this screen for a client.
|
||||
*/
|
||||
@@ -132,6 +140,7 @@ class PokedexGUI private constructor(
|
||||
|
||||
var oldDragPosX = 0.0
|
||||
var canDragRender = false
|
||||
private var activeDragableElement: GuiEventListener? = null
|
||||
|
||||
private var filteredPokedex: Collection<PokedexDef> = mutableListOf()
|
||||
private var seenCount = "0000"
|
||||
@@ -155,13 +164,29 @@ class PokedexGUI private constructor(
|
||||
private lateinit var scrollScreen: EntriesScrollingWidget
|
||||
private lateinit var pokemonInfoWidget: PokemonInfoWidget
|
||||
private lateinit var searchWidget: SearchWidget
|
||||
private lateinit var pokemonNameWidget: PokemonNameWidget
|
||||
|
||||
private var selectedSearchByType: SearchByType = SearchByType.SPECIES
|
||||
private var selectedCategoryFilter: PokedexCategoryFilterType = PokedexCategoryFilterType.ALL
|
||||
private val tabButtons: MutableList<ScaledButton> = mutableListOf()
|
||||
|
||||
var seenShinyStates = setOf<String>()
|
||||
var shiny = false
|
||||
var maleRatio = -1F
|
||||
var gender = Gender.GENDERLESS
|
||||
var aspects = mutableSetOf<String>()
|
||||
|
||||
lateinit var tabInfoElement: GuiEventListener
|
||||
var tabInfoIndex = TAB_DESCRIPTION
|
||||
var evolutionTreeExpanded = false
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
if (::pokemonInfoWidget.isInitialized) {
|
||||
pokemonInfoWidget.active = !value
|
||||
pokemonInfoWidget.visible = !value
|
||||
}
|
||||
}
|
||||
|
||||
override fun renderBlurredBackground(delta: Float) {}
|
||||
override fun renderMenuBackground(context: GuiGraphics) {}
|
||||
@@ -180,7 +205,7 @@ class PokedexGUI private constructor(
|
||||
|
||||
//Info Widget
|
||||
if (::pokemonInfoWidget.isInitialized) removeWidget(pokemonInfoWidget)
|
||||
pokemonInfoWidget = PokemonInfoWidget(x + 180, y + 28) { newForm -> updateSelectedForm(newForm) }
|
||||
pokemonInfoWidget = PokemonInfoWidget(this, x + 180, y + 28) { newForm -> updateSelectedForm(newForm) }
|
||||
addRenderableWidget(pokemonInfoWidget)
|
||||
|
||||
setUpTabs()
|
||||
@@ -192,6 +217,10 @@ class PokedexGUI private constructor(
|
||||
searchWidget = SearchWidget(x + 26, y + 28, 120, HEADER_BAR_HEIGHT, update = ::updateFilters)
|
||||
addRenderableWidget(searchWidget)
|
||||
|
||||
if (::pokemonNameWidget.isInitialized) removeWidget(pokemonNameWidget)
|
||||
pokemonNameWidget = PokemonNameWidget(this, x + 180, y + 28)
|
||||
addRenderableWidget(pokemonNameWidget)
|
||||
|
||||
if (::regionSelectWidgetUp.isInitialized) removeWidget(regionSelectWidgetUp)
|
||||
regionSelectWidgetUp = ScaledButton(
|
||||
buttonX = (x + 95).toFloat(),
|
||||
@@ -367,6 +396,14 @@ class PokedexGUI private constructor(
|
||||
shadow = true
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = if (evolutionTreeExpanded) expandedBackgroundOverlay else backgroundOverlay,
|
||||
x = pokemonInfoWidget.x, y = pokemonInfoWidget.y,
|
||||
width = HALF_OVERLAY_WIDTH,
|
||||
height = HALF_OVERLAY_HEIGHT
|
||||
)
|
||||
|
||||
// Category filter
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
@@ -402,7 +439,7 @@ class PokedexGUI private constructor(
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = tabSelectArrow,
|
||||
x = (x + 191.5F + (22F * tabInfoIndex)) / SCALE,
|
||||
x = (x + BASE_WIDTH / 2 + ICONS_ARROW_PADDING_AROUND + tabInfoIndex * ICONS_PADDING_BETWEEN) / SCALE,
|
||||
y = (y + 177) / SCALE,
|
||||
width = 12,
|
||||
height = 6,
|
||||
@@ -441,6 +478,7 @@ class PokedexGUI private constructor(
|
||||
val canDisplayEntry = true //selectedForm?.unlockForms
|
||||
|
||||
if (::pokemonInfoWidget.isInitialized
|
||||
&& pokemonInfoWidget.active
|
||||
&& !pokemonInfoWidget.suppressViewport
|
||||
&& pokemonInfoWidget.isWithinPortraitSpace(mouseX, mouseY)
|
||||
&& canDisplayEntry == true
|
||||
@@ -449,6 +487,12 @@ class PokedexGUI private constructor(
|
||||
isDragging = true
|
||||
oldDragPosX = mouseX
|
||||
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
|
||||
activeDragableElement = pokemonInfoWidget
|
||||
} else if (tabInfoIndex == TAB_EVOLUTION_TREE &&
|
||||
::tabInfoElement.isInitialized &&
|
||||
(tabInfoElement as EvolutionTreeWidget).isHovered(mouseX, mouseY)
|
||||
) {
|
||||
activeDragableElement = tabInfoElement
|
||||
}
|
||||
return try {
|
||||
super.mouseClicked(mouseX, mouseY, button)
|
||||
@@ -460,14 +504,18 @@ class PokedexGUI private constructor(
|
||||
override fun mouseReleased(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
|
||||
if (canDragRender) canDragRender = false
|
||||
if (isDragging) isDragging = false
|
||||
activeDragableElement = null
|
||||
return super.mouseReleased(pMouseX, pMouseY, pButton)
|
||||
}
|
||||
|
||||
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
|
||||
if (isDragging && canDragRender) {
|
||||
if (isDragging && canDragRender && activeDragableElement == pokemonInfoWidget) {
|
||||
val dragOffsetY = (oldDragPosX - mouseX).toFloat()
|
||||
pokemonInfoWidget.rotationY = (((pokemonInfoWidget.rotationY + dragOffsetY) % 360 + 360) % 360)
|
||||
} else if (activeDragableElement == tabInfoElement) {
|
||||
tabInfoElement.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||
}
|
||||
|
||||
oldDragPosX = mouseX
|
||||
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||
}
|
||||
@@ -558,6 +606,8 @@ class PokedexGUI private constructor(
|
||||
selectedForm = CobblemonClient.clientPokedexData.getEncounteredForms(newSelectedEntry).firstOrNull()
|
||||
|
||||
pokemonInfoWidget.setDexEntry(selectedEntry!!)
|
||||
pokemonNameWidget.update(selectedEntry!!)
|
||||
|
||||
displaytabInfoElement(tabInfoIndex)
|
||||
}
|
||||
|
||||
@@ -569,7 +619,7 @@ class PokedexGUI private constructor(
|
||||
|
||||
for (i in tabIcons.indices) {
|
||||
tabButtons.add(ScaledButton(
|
||||
x + 190.5F + (i * 22F),
|
||||
x + BASE_WIDTH / 2 + ICONS_PADDING_AROUND + i * ICONS_PADDING_BETWEEN,
|
||||
y + 181.5F,
|
||||
TAB_ICON_SIZE,
|
||||
TAB_ICON_SIZE,
|
||||
@@ -582,6 +632,8 @@ class PokedexGUI private constructor(
|
||||
}
|
||||
|
||||
fun displaytabInfoElement(tabIndex: Int, update: Boolean = true) {
|
||||
if (tabIndex != TAB_EVOLUTION_TREE) evolutionTreeExpanded = false
|
||||
|
||||
val showActiveTab = selectedEntry?.let { selectedForm in getSufficientlyKnownForms(it, tabIndex) } == true
|
||||
var statSubIndex = 0
|
||||
|
||||
@@ -633,6 +685,9 @@ class PokedexGUI private constructor(
|
||||
TAB_MOVES -> {
|
||||
tabInfoElement = MovesLearnsetWidget(x + 180, y + 28)
|
||||
}
|
||||
TAB_EVOLUTION_TREE -> {
|
||||
tabInfoElement = EvolutionTreeWidget(this, x, y)
|
||||
}
|
||||
}
|
||||
val element = tabInfoElement
|
||||
if (element is Renderable && element is NarratableEntry) {
|
||||
@@ -707,6 +762,9 @@ class PokedexGUI private constructor(
|
||||
selectedForm = fallbackForm
|
||||
) { next -> cycleSelectedForm(next) }
|
||||
}
|
||||
TAB_EVOLUTION_TREE -> {
|
||||
(tabInfoElement as EvolutionTreeWidget).setup(selectedEntry, selectedForm)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (tabInfoIndex != TAB_DESCRIPTION) displaytabInfoElement(TAB_DESCRIPTION)
|
||||
@@ -770,6 +828,10 @@ class PokedexGUI private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
if (tabInfoIndex == TAB_EVOLUTION_TREE && keyCode == InputConstants.KEY_SEMICOLON) {
|
||||
debugEvolutionRequirementRendering()
|
||||
}
|
||||
|
||||
return super.keyPressed(keyCode, scanCode, modifiers)
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -16,6 +16,8 @@ object PokedexGUIConstants {
|
||||
const val HEADER_BAR_HEIGHT = 11
|
||||
const val POKEMON_DESCRIPTION_HEIGHT = 42
|
||||
const val POKEMON_DESCRIPTION_PADDING = 9
|
||||
const val ICONS_PADDING_AROUND = 18.5f
|
||||
const val ICONS_ARROW_PADDING_AROUND = ICONS_PADDING_AROUND + 1
|
||||
|
||||
const val TAB_ICON_SIZE = 16
|
||||
|
||||
@@ -36,6 +38,7 @@ object PokedexGUIConstants {
|
||||
const val TAB_STATS = 3
|
||||
const val TAB_DROPS = 4
|
||||
const val TAB_MOVES = 5
|
||||
const val TAB_EVOLUTION_TREE = 6
|
||||
|
||||
// RowScrollingWidget
|
||||
const val SCROLL_BASE_HEIGHT = 141
|
||||
|
||||
+72
-9
@@ -13,38 +13,101 @@ import com.cobblemon.mod.common.api.text.font
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
private val tooltipEdge = cobblemonResource("textures/gui/pokedex/tooltip_edge.png")
|
||||
private val tooltipBackground = cobblemonResource("textures/gui/pokedex/tooltip_background.png")
|
||||
|
||||
fun renderTooltip(context: GuiGraphics, text: MutableComponent, posX: Int, posY: Int, delta: Float, offsetY: Int = 0) {
|
||||
private const val TOOLTIP_SLICE_SIZE = 11
|
||||
|
||||
fun renderTooltip(
|
||||
context: GuiGraphics,
|
||||
text: MutableComponent,
|
||||
posX: Int,
|
||||
posY: Int,
|
||||
delta: Float,
|
||||
offsetY: Int = 0,
|
||||
) {
|
||||
renderTooltip(
|
||||
context,
|
||||
listOf(text),
|
||||
posX,
|
||||
posY,
|
||||
delta,
|
||||
offsetY,
|
||||
)
|
||||
}
|
||||
|
||||
fun renderTooltip(
|
||||
context: GuiGraphics,
|
||||
text: List<MutableComponent>,
|
||||
posX: Int,
|
||||
posY: Int,
|
||||
delta: Float,
|
||||
offsetY: Int = 0,
|
||||
) {
|
||||
val poseStack = context.pose()
|
||||
val textWidth = Minecraft.getInstance().font.width(text.font(CobblemonResources.DEFAULT_LARGE))
|
||||
val tooltipWidth = textWidth + 6
|
||||
val tooltipHeight = 11
|
||||
val textWidth = text.maxOf { Minecraft.getInstance().font.width(it.font(CobblemonResources.DEFAULT_LARGE)) }
|
||||
val tooltipPadding = 6
|
||||
val tooltipWidth = textWidth + tooltipPadding
|
||||
val tooltipTop = posY + offsetY
|
||||
|
||||
poseStack.pushPose()
|
||||
poseStack.translate(0.0, 0.0, 1000.0)
|
||||
|
||||
Minecraft.getInstance().let {
|
||||
it.mainRenderTarget.bindWrite(false)
|
||||
context.enableScissor(
|
||||
posX - (tooltipWidth / 2),
|
||||
tooltipTop + 1,
|
||||
(posX - (tooltipWidth / 2)) + tooltipWidth,
|
||||
tooltipTop + 10
|
||||
tooltipTop + TOOLTIP_SLICE_SIZE * text.size - 1
|
||||
)
|
||||
it.gameRenderer.processBlurEffect(delta)
|
||||
context.disableScissor()
|
||||
it.mainRenderTarget.bindWrite(true)
|
||||
}
|
||||
|
||||
blitk(matrixStack = poseStack, texture = tooltipEdge, x = posX - (tooltipWidth / 2) - 1, y = tooltipTop, width = 1, height = tooltipHeight)
|
||||
blitk(matrixStack = poseStack, texture = tooltipBackground, x = posX - (tooltipWidth / 2), y = tooltipTop, width = tooltipWidth, height = tooltipHeight)
|
||||
blitk(matrixStack = poseStack, texture = tooltipEdge, x = posX + (tooltipWidth / 2), y = tooltipTop, width = 1, height = tooltipHeight)
|
||||
drawScaledText(context = context, font = CobblemonResources.DEFAULT_LARGE, text = text, x = posX, y = tooltipTop + 1, shadow = true, centered = true)
|
||||
val totalHeight = TOOLTIP_SLICE_SIZE * text.size
|
||||
|
||||
blitSlicedVertical(poseStack, tooltipEdge, posX - (tooltipWidth / 2) - 1, tooltipTop, 1, totalHeight)
|
||||
blitSlicedVertical(poseStack, tooltipBackground, posX - (tooltipWidth / 2), tooltipTop, tooltipWidth, totalHeight)
|
||||
blitSlicedVertical(poseStack, tooltipEdge, posX + (tooltipWidth / 2), tooltipTop, 1, totalHeight)
|
||||
|
||||
val centered = text.size == 1
|
||||
|
||||
text.forEachIndexed { index, it ->
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = it,
|
||||
x = if (centered) posX else posX - tooltipWidth / 2 + tooltipPadding / 2,
|
||||
y = tooltipTop + 1 + TOOLTIP_SLICE_SIZE * index,
|
||||
shadow = true,
|
||||
centered = centered
|
||||
)
|
||||
}
|
||||
|
||||
poseStack.popPose()
|
||||
}
|
||||
|
||||
// Stretching the whole 11px image over an arbitrary height thickens the caps as more lines are added, so
|
||||
// instead the caps are drawn 1:1 and only the flat fill row is stretched to cover the remaining height
|
||||
private fun blitSlicedVertical(
|
||||
poseStack: PoseStack,
|
||||
texture: ResourceLocation,
|
||||
x: Number,
|
||||
y: Int,
|
||||
width: Number,
|
||||
totalHeight: Int,
|
||||
) {
|
||||
val middleHeight = totalHeight - 2
|
||||
|
||||
blitk(matrixStack = poseStack, texture = texture, x = x, y = y, width = width, height = 1, vOffset = 0, textureHeight = TOOLTIP_SLICE_SIZE)
|
||||
blitk(matrixStack = poseStack, texture = texture, x = x, y = y + 1, width = width, height = middleHeight, vOffset = middleHeight, textureHeight = TOOLTIP_SLICE_SIZE * middleHeight)
|
||||
blitk(matrixStack = poseStack, texture = texture, x = x, y = y + 1 + middleHeight, width = width, height = 1, vOffset = TOOLTIP_SLICE_SIZE - 1, textureHeight = TOOLTIP_SLICE_SIZE)
|
||||
}
|
||||
@@ -20,8 +20,8 @@ import net.minecraft.client.sounds.SoundManager
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
class ScaledButton(
|
||||
var buttonX: Float,
|
||||
var buttonY: Float,
|
||||
buttonX: Float,
|
||||
buttonY: Float,
|
||||
val buttonWidth: Number,
|
||||
val buttonHeight: Number,
|
||||
var resource: ResourceLocation? = null,
|
||||
@@ -30,6 +30,18 @@ class ScaledButton(
|
||||
val clickAction: Button.OnPress
|
||||
): Button(buttonX.toInt(), buttonY.toInt(), buttonWidth.toInt(), buttonHeight.toInt(), "".text(), clickAction, DEFAULT_NARRATION), CobblemonRenderable {
|
||||
|
||||
var buttonX: Float = buttonX
|
||||
set(value) {
|
||||
field = value
|
||||
this.x = value.toInt()
|
||||
}
|
||||
|
||||
var buttonY: Float = buttonY
|
||||
set(value) {
|
||||
field = value
|
||||
this.y = value.toInt()
|
||||
}
|
||||
|
||||
var isWidgetActive = false
|
||||
var isVisible = true
|
||||
|
||||
|
||||
+27
-126
@@ -12,14 +12,13 @@ import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.gui.ScrollingWidget
|
||||
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCALE
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCROLL_BAR_WIDTH
|
||||
@@ -27,19 +26,14 @@ import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCROLL_BA
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCROLL_SLOT_SIZE
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCROLL_SLOT_SPACING
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.EntriesScrollingWidget.PokemonScrollSlotRow
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
|
||||
import com.cobblemon.mod.common.pokemon.Gender
|
||||
import com.cobblemon.mod.common.pokemon.RenderablePokemon
|
||||
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.resources.sounds.SimpleSoundInstance
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.util.Mth
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
|
||||
class EntriesScrollingWidget(val pX: Int, val pY: Int, val setPokedexEntry: (PokedexEntry) -> (Unit)): ScrollingWidget<PokemonScrollSlotRow>(
|
||||
width = PokedexGUIConstants.HALF_OVERLAY_WIDTH,
|
||||
@@ -135,13 +129,6 @@ class EntriesScrollingWidget(val pX: Int, val pY: Int, val setPokedexEntry: (Pok
|
||||
val discoveryLevelList: MutableList<PokedexEntryProgress>,
|
||||
val setPokedexEntry : (PokedexEntry) -> (Unit)
|
||||
): Slot<PokemonScrollSlotRow>() {
|
||||
companion object {
|
||||
private val slotResource = cobblemonResource("textures/gui/pokedex/pokedex_slot.png")
|
||||
private val slotHighlight = cobblemonResource("textures/gui/pokedex/slot_select.png")
|
||||
private val caughtIcon = cobblemonResource("textures/gui/pokedex/caught_icon_small.png")
|
||||
private val unknownIcon = cobblemonResource("textures/gui/pokedex/pokedex_slot_unknown.png")
|
||||
private val unimplementedIcon = cobblemonResource("textures/gui/pokedex/pokedex_slot_unimplemented.png")
|
||||
}
|
||||
val runtime = MoLangRuntime().setupClient().setup().also {
|
||||
it.environment.query.addFunction("get_pokedex") { CobblemonClient.clientPokedexData.struct }
|
||||
}
|
||||
@@ -161,128 +148,42 @@ class EntriesScrollingWidget(val pX: Int, val pY: Int, val setPokedexEntry: (Pok
|
||||
hovered: Boolean,
|
||||
tickDelta: Float
|
||||
) {
|
||||
dexDataList.forEachIndexed { index, dexData ->
|
||||
val state = FloatingState()
|
||||
val species = PokemonSpecies.getByIdentifier(dexData.speciesId)
|
||||
//FIXME: This may not work properly when accounting for custom pokemon with the same dex number
|
||||
val pokemonNumber = (species?.nationalPokedexNumber?.toString() ?: "0").padStart(4, '0')
|
||||
|
||||
val speciesNumber = pokemonNumber.text()
|
||||
dexDataList.forEachIndexed { index, pokedexEntry ->
|
||||
val species = PokemonSpecies.getByIdentifier(pokedexEntry.speciesId)
|
||||
val discoveryLevel = discoveryLevelList[index]
|
||||
val firstVisibleForm = CobblemonClient.clientPokedexData.getEncounteredForms(dexData).firstOrNull()
|
||||
val shouldDrawMon = firstVisibleForm != null
|
||||
val firstVisibleForm = CobblemonClient.clientPokedexData.getEncounteredForms(pokedexEntry).firstOrNull()
|
||||
|
||||
if (species == null) {
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
val matrices = context.pose()
|
||||
val renderablePokemon =
|
||||
if (firstVisibleForm != null) {
|
||||
val firstVisibleGender = CobblemonClient.clientPokedexData.getSeenGenders(pokedexEntry, firstVisibleForm).firstOrNull()
|
||||
val shinyStates = CobblemonClient.clientPokedexData.getSeenShinyStates(pokedexEntry, firstVisibleForm)
|
||||
val firstVisibleShiny = shinyStates.count() == 1 && shinyStates.first() == "shiny"
|
||||
val formAspects = species.forms.firstOrNull { it.name.equals(firstVisibleForm.displayForm, ignoreCase = true) }?.aspects ?: species.standardForm.aspects
|
||||
val seenAspects = CobblemonClient.clientPokedexData.getSeenAspects(pokedexEntry)
|
||||
val variationAspects = pokedexEntry.variations.mapNotNull { it.aspects.firstOrNull { it in seenAspects } }
|
||||
val aspectsToDraw = (pokedexEntry.displayAspects + variationAspects + formAspects + (firstVisibleGender ?: Gender.GENDERLESS).name.lowercase()).toMutableSet()
|
||||
if (firstVisibleShiny) {
|
||||
aspectsToDraw.add("shiny")
|
||||
}
|
||||
|
||||
val startPosX = x + ((SCROLL_SLOT_SPACING + SCROLL_SLOT_SIZE) * index)
|
||||
val startPosY = y + SCROLL_SLOT_SPACING + 1
|
||||
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = slotResource,
|
||||
x = startPosX,
|
||||
y = startPosY,
|
||||
width = SCROLL_SLOT_SIZE,
|
||||
height = SCROLL_SLOT_SIZE
|
||||
)
|
||||
|
||||
if (getHoveredSlotIndex(mouseX, mouseY) == index) {
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = slotHighlight,
|
||||
x = startPosX,
|
||||
y = startPosY,
|
||||
width = SCROLL_SLOT_SIZE,
|
||||
height = SCROLL_SLOT_SIZE,
|
||||
vOffset = SCROLL_SLOT_SIZE,
|
||||
textureHeight = SCROLL_SLOT_SIZE * 2
|
||||
)
|
||||
}
|
||||
|
||||
if (shouldDrawMon) {
|
||||
val firstVisibleGender = CobblemonClient.clientPokedexData.getSeenGenders(dexData, firstVisibleForm).firstOrNull()
|
||||
val shinyStates = CobblemonClient.clientPokedexData.getSeenShinyStates(dexData, firstVisibleForm)
|
||||
val firstVisibleShiny = shinyStates.count() == 1 && shinyStates.first() == "shiny"
|
||||
val formAspects = species.forms.firstOrNull { it.name.equals(firstVisibleForm.displayForm, ignoreCase = true) }?.aspects ?: species.standardForm.aspects
|
||||
val seenAspects = CobblemonClient.clientPokedexData.getSeenAspects(dexData)
|
||||
val variationAspects = dexData.variations.mapNotNull { it.aspects.firstOrNull { it in seenAspects } }
|
||||
context.enableScissor(
|
||||
startPosX + 1,
|
||||
startPosY + 1,
|
||||
startPosX + SCROLL_SLOT_SIZE - 1,
|
||||
startPosY + SCROLL_SLOT_SIZE - 2
|
||||
)
|
||||
val aspectsToDraw = (dexData.displayAspects + variationAspects + formAspects + (firstVisibleGender ?: Gender.GENDERLESS).name.lowercase()).toMutableSet()
|
||||
if (firstVisibleShiny) {
|
||||
aspectsToDraw.add("shiny")
|
||||
RenderablePokemon(species, aspectsToDraw)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
state.currentAspects = aspectsToDraw
|
||||
matrices.pushPose()
|
||||
matrices.translate(startPosX + (SCROLL_SLOT_SIZE / 2.0), startPosY + 1.0, 0.0)
|
||||
matrices.scale(2.5F, 2.5F, 1F)
|
||||
drawProfilePokemon(
|
||||
renderablePokemon = RenderablePokemon(species, aspectsToDraw),
|
||||
matrixStack = matrices,
|
||||
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
|
||||
state = state,
|
||||
partialTicks = 0F,
|
||||
scale = 4.5F
|
||||
)
|
||||
matrices.popPose()
|
||||
context.disableScissor()
|
||||
} else {
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = unknownIcon,
|
||||
x = startPosX + 8.5,
|
||||
y = startPosY + 9,
|
||||
width = 8,
|
||||
height = 10
|
||||
)
|
||||
|
||||
if (!species.implemented) {
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = unimplementedIcon,
|
||||
x = (startPosX + 14) / SCALE,
|
||||
y = (startPosY + 15.5) / SCALE,
|
||||
width = 7,
|
||||
height = 7,
|
||||
scale = SCALE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure elements are not hidden behind Pokémon render
|
||||
matrices.pushPose()
|
||||
matrices.translate(0.0, 0.0, 100.0)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = speciesNumber,
|
||||
x = startPosX + 1.5,//2,
|
||||
y = startPosY + 2.5,//2
|
||||
shadow = true,
|
||||
scale = SCALE
|
||||
PokemonSlotWidget.render(
|
||||
context,
|
||||
x = x + (SCROLL_SLOT_SPACING + SCROLL_SLOT_SIZE) * index,
|
||||
y = y + SCROLL_SLOT_SPACING + 1,
|
||||
getHoveredSlotIndex(mouseX, mouseY) == index,
|
||||
species,
|
||||
renderablePokemon,
|
||||
discoveryLevel,
|
||||
)
|
||||
|
||||
if (discoveryLevel == PokedexEntryProgress.OWNED) {
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = caughtIcon,
|
||||
x = (startPosX + 18) / SCALE,
|
||||
y = (startPosY + 1.5) / SCALE,
|
||||
width = 11,
|
||||
height = 11,
|
||||
scale = SCALE
|
||||
)
|
||||
}
|
||||
|
||||
matrices.popPose()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
-65
@@ -10,10 +10,10 @@ package com.cobblemon.mod.common.client.gui.pokedex.widgets
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexCosmeticVariation
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
@@ -25,8 +25,9 @@ import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.gui.ProfileTransformType
|
||||
import com.cobblemon.mod.common.client.gui.TypeIcon
|
||||
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_HEIGHT
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUI
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_HEIGHT
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.POKEMON_PORTRAIT_HEIGHT
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.POKEMON_PORTRAIT_WIDTH
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.PORTRAIT_POKE_BALL_HEIGHT
|
||||
@@ -52,15 +53,13 @@ import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.sounds.SoundEvent
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm) -> (Unit)) : SoundlessWidget(
|
||||
class PokemonInfoWidget(val parent: PokedexGUI, val pX: Int, val pY: Int, val updateForm: (PokedexForm) -> (Unit)) : SoundlessWidget(
|
||||
pX,
|
||||
pY,
|
||||
POKEMON_PORTRAIT_WIDTH,
|
||||
@@ -82,7 +81,6 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
|
||||
private val arrowFormLeft = cobblemonResource("textures/gui/pokedex/forms_arrow_left.png")
|
||||
private val arrowFormRight = cobblemonResource("textures/gui/pokedex/forms_arrow_right.png")
|
||||
|
||||
private val caughtIcon = cobblemonResource("textures/gui/pokedex/caught_icon.png")
|
||||
private val typeBar = cobblemonResource("textures/gui/pokedex/type_bar.png")
|
||||
private val typeBarDouble = cobblemonResource("textures/gui/pokedex/type_bar_double.png")
|
||||
|
||||
@@ -100,9 +98,6 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
|
||||
|
||||
var currentEntry : PokedexEntry? = null
|
||||
|
||||
var speciesName: MutableComponent = Component.translatable("")
|
||||
var speciesNumber: MutableComponent = "0000".text()
|
||||
|
||||
val runtime = MoLangRuntime().setupClient().setup().also {
|
||||
it.environment.query.addFunction("get_pokedex") { CobblemonClient.clientPokedexData.struct }
|
||||
}
|
||||
@@ -111,10 +106,6 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
|
||||
var selectedFormIndex: Int = 0
|
||||
|
||||
var type: Array<ElementalType?> = arrayOf(null, null)
|
||||
var seenShinyStates: Set<String> = setOf()
|
||||
var shiny = false
|
||||
var maleRatio = -1F
|
||||
var gender: Gender = Gender.GENDERLESS
|
||||
|
||||
var renderablePokemon : RenderablePokemon? = null
|
||||
|
||||
@@ -195,7 +186,10 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
|
||||
20,
|
||||
resource = buttonGenderMale,
|
||||
clickAction = {
|
||||
if (maleRatio > 0 && maleRatio < 1) gender = if (gender == Gender.MALE) Gender.FEMALE else Gender.MALE
|
||||
val maleRatio = parent.maleRatio
|
||||
if (maleRatio > 0 && maleRatio < 1) {
|
||||
parent.gender = if (parent.gender == Gender.MALE) Gender.FEMALE else Gender.MALE
|
||||
}
|
||||
updateAspects()
|
||||
}
|
||||
).apply { addWidget(this) }
|
||||
@@ -207,8 +201,8 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
|
||||
20,
|
||||
buttonNone,
|
||||
clickAction = {
|
||||
if (seenShinyStates.size > 1) {
|
||||
shiny = !shiny
|
||||
if (parent.seenShinyStates.size > 1) {
|
||||
parent.shiny = !parent.shiny
|
||||
updateAspects()
|
||||
}
|
||||
}
|
||||
@@ -263,8 +257,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
|
||||
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
val currentEntry = this.currentEntry ?: return
|
||||
|
||||
val hasKnowledge =
|
||||
CobblemonClient.clientPokedexData.getKnowledgeForSpecies(currentEntry.speciesId) != PokedexEntryProgress.UNREGISTERED
|
||||
val hasKnowledge = CobblemonClient.clientPokedexData.getKnowledgeForSpecies(currentEntry.speciesId) != PokedexEntryProgress.UNREGISTERED
|
||||
val species = currentEntry.speciesId.let { PokemonSpecies.getByIdentifier(it) } ?: return
|
||||
|
||||
val matrices = context.pose()
|
||||
@@ -277,37 +270,6 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
|
||||
height = HALF_OVERLAY_HEIGHT
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = speciesNumber.bold(),
|
||||
x = pX + 3,
|
||||
y = pY + 1,
|
||||
shadow = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = (if (hasKnowledge) speciesName else lang("ui.generic.question_marks")).bold(),
|
||||
x = pX + 26,
|
||||
y = pY + 1,
|
||||
colour = 0x606B6E
|
||||
)
|
||||
|
||||
// Caught icon
|
||||
if (isSelectedPokemonOwned()) {
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = caughtIcon,
|
||||
x = (pX + 129) / SCALE,
|
||||
y = (pY + 2) / SCALE,
|
||||
width = 14,
|
||||
height = 14,
|
||||
scale = SCALE
|
||||
)
|
||||
}
|
||||
|
||||
if (!suppressViewport) {
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
@@ -571,8 +533,6 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
|
||||
|
||||
if (species != null) {
|
||||
this.visibleForms = CobblemonClient.clientPokedexData.getEncounteredForms(pokedexEntry).toMutableList()
|
||||
this.speciesNumber = species.nationalPokedexNumber.toString().padStart(4, '0').text()
|
||||
this.speciesName = species.translatedName
|
||||
|
||||
if (forms.isNotEmpty()) {
|
||||
formLeftButton.active = true
|
||||
@@ -598,23 +558,23 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
|
||||
variationButtons.forEach { removeWidget(it.getWidget()) }
|
||||
variationButtons.clear()
|
||||
|
||||
seenShinyStates = CobblemonClient.clientPokedexData.getSeenShinyStates(pokedexEntry, pokedexForm)
|
||||
shiny = seenShinyStates.count() == 1 && seenShinyStates.first() == "shiny"
|
||||
shinyButton.resource = if (shiny) buttonShiny else buttonNone
|
||||
shinyButton.visible = shiny || seenShinyStates.size > 1
|
||||
shinyButton.active = seenShinyStates.size > 1
|
||||
parent.seenShinyStates = CobblemonClient.clientPokedexData.getSeenShinyStates(pokedexEntry, pokedexForm)
|
||||
parent.shiny = parent.seenShinyStates.count() == 1 && parent.seenShinyStates.first() == "shiny"
|
||||
shinyButton.resource = if (parent.shiny) buttonShiny else buttonNone
|
||||
shinyButton.visible = parent.shiny || parent.seenShinyStates.size > 1
|
||||
shinyButton.active = parent.seenShinyStates.size > 1
|
||||
|
||||
val form = species.forms.find { it.name.equals(pokedexForm.displayForm, ignoreCase = true) } ?: species.standardForm
|
||||
maleRatio = form.maleRatio
|
||||
parent.maleRatio = form.maleRatio
|
||||
|
||||
val seenGenders = CobblemonClient.clientPokedexData.getSeenGenders(pokedexEntry, pokedexForm)
|
||||
val visibleGenders = seenGenders.filter { it != Gender.GENDERLESS }
|
||||
if (visibleGenders.isEmpty()) {
|
||||
gender = Gender.GENDERLESS
|
||||
parent.gender = Gender.GENDERLESS
|
||||
genderButton.visible = false
|
||||
genderButton.active = false
|
||||
} else {
|
||||
gender = visibleGenders.first()
|
||||
parent.gender = visibleGenders.first()
|
||||
genderButton.visible = true
|
||||
genderButton.active = visibleGenders.size > 1
|
||||
}
|
||||
@@ -693,8 +653,8 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
|
||||
return
|
||||
}
|
||||
|
||||
genderButton.resource = if (gender == Gender.FEMALE) buttonGenderFemale else buttonGenderMale
|
||||
shinyButton.resource = if (shiny) buttonShiny else buttonNone
|
||||
genderButton.resource = if (parent.gender == Gender.FEMALE) buttonGenderFemale else buttonGenderMale
|
||||
shinyButton.resource = if (parent.shiny) buttonShiny else buttonNone
|
||||
|
||||
val species = currentEntry?.speciesId?.let { PokemonSpecies.getByIdentifier(it) }
|
||||
if (species != null) {
|
||||
@@ -703,12 +663,14 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
|
||||
|
||||
updateType(species, form)
|
||||
|
||||
val aspects = mutableSetOf<String>()
|
||||
if (shiny) aspects.add(SHINY_ASPECT.aspect)
|
||||
val aspects = parent.aspects
|
||||
aspects.clear()
|
||||
|
||||
if (gender == Gender.FEMALE) {
|
||||
if (parent.shiny) aspects.add(SHINY_ASPECT.aspect)
|
||||
|
||||
if (parent.gender == Gender.FEMALE) {
|
||||
aspects.add("female")
|
||||
} else if (gender == Gender.MALE) {
|
||||
} else if (parent.gender == Gender.MALE) {
|
||||
aspects.add("male")
|
||||
}
|
||||
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.pokedex.widgets
|
||||
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUI
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.POKEMON_PORTRAIT_HEIGHT
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.POKEMON_PORTRAIT_WIDTH
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCALE
|
||||
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
|
||||
class PokemonNameWidget(
|
||||
val parent: PokedexGUI,
|
||||
val pX: Int,
|
||||
val pY: Int,
|
||||
) : SoundlessWidget(
|
||||
pX,
|
||||
pY,
|
||||
POKEMON_PORTRAIT_WIDTH,
|
||||
POKEMON_PORTRAIT_HEIGHT,
|
||||
lang("ui.pokedex.pokemon_name"),
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private val caughtIcon = cobblemonResource("textures/gui/pokedex/caught_icon.png")
|
||||
}
|
||||
|
||||
var hasKnowledge: Boolean = false
|
||||
var isOwned: Boolean = false
|
||||
var speciesName: MutableComponent = Component.translatable("")
|
||||
var speciesNumber: MutableComponent = "0000".text()
|
||||
|
||||
override fun renderWidget(
|
||||
context: GuiGraphics,
|
||||
mouseX: Int,
|
||||
mouseY: Int,
|
||||
partialTick: Float
|
||||
) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = speciesNumber.bold(),
|
||||
x = pX + 3,
|
||||
y = pY + 1,
|
||||
shadow = true
|
||||
)
|
||||
|
||||
if (hasKnowledge) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = speciesName.bold(),
|
||||
x = pX + 26,
|
||||
y = pY + 1,
|
||||
colour = 0x606B6E
|
||||
)
|
||||
}
|
||||
|
||||
if (isOwned) {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = caughtIcon,
|
||||
x = (pX + 129) / SCALE,
|
||||
y = (pY + 2) / SCALE,
|
||||
width = 14,
|
||||
height = 14,
|
||||
scale = SCALE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun update(pokedexEntry: PokedexEntry) {
|
||||
val species = PokemonSpecies.getByIdentifier(pokedexEntry.speciesId) ?: return
|
||||
|
||||
val progress = CobblemonClient.clientPokedexData.getKnowledgeForSpecies(pokedexEntry.speciesId)
|
||||
this.hasKnowledge = progress != PokedexEntryProgress.UNREGISTERED
|
||||
this.isOwned = progress == PokedexEntryProgress.OWNED
|
||||
this.speciesNumber = species.nationalPokedexNumber.toString().padStart(4, '0').text()
|
||||
this.speciesName = species.translatedName
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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.pokedex.widgets
|
||||
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCALE
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCROLL_SLOT_SIZE
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
|
||||
import com.cobblemon.mod.common.pokemon.RenderablePokemon
|
||||
import com.cobblemon.mod.common.pokemon.Species
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
|
||||
abstract class PokemonSlotWidget {
|
||||
companion object {
|
||||
private val slotResource = cobblemonResource("textures/gui/pokedex/pokedex_slot.png")
|
||||
private val slotHighlight = cobblemonResource("textures/gui/pokedex/slot_select.png")
|
||||
private val caughtIcon = cobblemonResource("textures/gui/pokedex/caught_icon_small.png")
|
||||
private val unknownIcon = cobblemonResource("textures/gui/pokedex/pokedex_slot_unknown.png")
|
||||
private val unimplementedIcon = cobblemonResource("textures/gui/pokedex/pokedex_slot_unimplemented.png")
|
||||
|
||||
fun render(
|
||||
context: GuiGraphics,
|
||||
x: Int,
|
||||
y: Int,
|
||||
isHovered: Boolean,
|
||||
species: Species,
|
||||
renderablePokemon: RenderablePokemon?,
|
||||
discoveryLevel: PokedexEntryProgress,
|
||||
) {
|
||||
val shouldDrawMon = renderablePokemon != null
|
||||
|
||||
// FIXME: This may not work properly when accounting for custom pokemon with the same dex number
|
||||
val pokemonNumber = species.nationalPokedexNumber.toString().padStart(4, '0')
|
||||
val speciesNumber = pokemonNumber.text()
|
||||
|
||||
val matrices = context.pose()
|
||||
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = slotResource,
|
||||
x = x,
|
||||
y = y,
|
||||
width = SCROLL_SLOT_SIZE,
|
||||
height = SCROLL_SLOT_SIZE,
|
||||
)
|
||||
|
||||
if (isHovered) {
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = slotHighlight,
|
||||
x = x,
|
||||
y = y,
|
||||
width = SCROLL_SLOT_SIZE,
|
||||
height = SCROLL_SLOT_SIZE,
|
||||
vOffset = SCROLL_SLOT_SIZE,
|
||||
textureHeight = SCROLL_SLOT_SIZE * 2,
|
||||
)
|
||||
}
|
||||
|
||||
if (shouldDrawMon && discoveryLevel != PokedexEntryProgress.UNREGISTERED) {
|
||||
context.enableScissor(
|
||||
x + 1,
|
||||
y + 1,
|
||||
x + SCROLL_SLOT_SIZE - 1,
|
||||
y + SCROLL_SLOT_SIZE - 2,
|
||||
)
|
||||
|
||||
matrices.pushPose()
|
||||
matrices.translate(x + SCROLL_SLOT_SIZE / 2.0, y + 1.0, 0.0)
|
||||
matrices.scale(2.5F, 2.5F, 1F)
|
||||
|
||||
val state = FloatingState()
|
||||
state.currentAspects = renderablePokemon.aspects
|
||||
|
||||
drawProfilePokemon(
|
||||
renderablePokemon = renderablePokemon,
|
||||
matrixStack = matrices,
|
||||
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
|
||||
state = state,
|
||||
partialTicks = 0F,
|
||||
scale = 4.5F
|
||||
)
|
||||
|
||||
matrices.popPose()
|
||||
context.disableScissor()
|
||||
} else {
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = unknownIcon,
|
||||
x = x + 8.5,
|
||||
y = y + 9,
|
||||
width = 8,
|
||||
height = 10
|
||||
)
|
||||
|
||||
if (!species.implemented) {
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = unimplementedIcon,
|
||||
x = (x + 14) / SCALE,
|
||||
y = (y + 15.5) / SCALE,
|
||||
width = 7,
|
||||
height = 7,
|
||||
scale = SCALE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure elements are not hidden behind Pokémon render
|
||||
matrices.pushPose()
|
||||
matrices.translate(0.0, 0.0, 100.0)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = speciesNumber,
|
||||
x = x + 1.5,
|
||||
y = y + 2.5,
|
||||
shadow = true,
|
||||
scale = SCALE
|
||||
)
|
||||
|
||||
if (discoveryLevel == PokedexEntryProgress.OWNED) {
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = caughtIcon,
|
||||
x = (x + 18) / SCALE,
|
||||
y = (y + 1.5) / SCALE,
|
||||
width = 11,
|
||||
height = 11,
|
||||
scale = SCALE
|
||||
)
|
||||
}
|
||||
|
||||
matrices.popPose()
|
||||
}
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.pokedex.widgets.evolutiontree
|
||||
|
||||
import com.cobblemon.mod.common.api.pokemon.Natures
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeature
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeatureProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.StringSpeciesFeature
|
||||
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
|
||||
import com.cobblemon.mod.common.api.text.add
|
||||
import com.cobblemon.mod.common.api.text.green
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.locale.Language
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.world.item.Items
|
||||
|
||||
internal fun describePokemonProperties(properties: PokemonProperties): MutableComponent? {
|
||||
if (hasUndescribedProperty(properties)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val descriptors = mutableListOf<MutableComponent>()
|
||||
|
||||
properties.species?.let { PokemonSpecies.getByIdentifier(cobblemonResource(it)) }?.let {
|
||||
descriptors.add(it.translatedName.plainCopy().green())
|
||||
}
|
||||
properties.gender?.let {
|
||||
descriptors.add(lang("gender.${it.name.lowercase()}").green())
|
||||
}
|
||||
properties.nickname?.let {
|
||||
descriptors.add(lang("ui.pokedex.requirement.properties.nickname", it.plainCopy().green()))
|
||||
}
|
||||
properties.type?.let {
|
||||
val typeName = ElementalTypes.get(it)?.displayName?.plainCopy() ?: Component.literal(it)
|
||||
descriptors.add(lang("ui.pokedex.requirement.properties.type", typeName.green()))
|
||||
}
|
||||
properties.nature?.let {
|
||||
val natureName = Natures.getNature(it.asIdentifierDefaultingNamespace())?.displayName?.asTranslated() ?: Component.literal(it)
|
||||
descriptors.add(lang("ui.pokedex.requirement.properties.nature", natureName.green()))
|
||||
}
|
||||
properties.customProperties.forEach { customProperty ->
|
||||
describeCustomProperty(customProperty)?.let { descriptors.add(it) }
|
||||
}
|
||||
|
||||
val heldItemDescriptor = properties.heldItem?.let { heldItem ->
|
||||
val itemId = heldItem.substringBefore('[').trim().asIdentifierDefaultingNamespace()
|
||||
val item = BuiltInRegistries.ITEM.get(itemId)
|
||||
if (item == Items.AIR) null else lang("ui.pokedex.requirement.properties.held_item", item.description.plainCopy().green())
|
||||
}
|
||||
|
||||
if (descriptors.isEmpty() && heldItemDescriptor == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val mainDescriptor = when {
|
||||
descriptors.isEmpty() -> null
|
||||
descriptors.size == 1 -> descriptors[0]
|
||||
else -> {
|
||||
val allButLast = descriptors.dropLast(1).reduce { acc, next -> acc.add(", ").add(next) }
|
||||
lang("ui.pokedex.requirement.properties.and", allButLast, descriptors.last())
|
||||
}
|
||||
}
|
||||
|
||||
return when {
|
||||
mainDescriptor != null && heldItemDescriptor != null -> mainDescriptor.add(" ").add(heldItemDescriptor)
|
||||
mainDescriptor != null -> mainDescriptor
|
||||
else -> heldItemDescriptor
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasUndescribedProperty(properties: PokemonProperties): Boolean {
|
||||
return properties.form != null ||
|
||||
properties.shiny != null ||
|
||||
properties.level != null ||
|
||||
properties.friendship != null ||
|
||||
properties.fullness != null ||
|
||||
properties.pokeball != null ||
|
||||
properties.ability != null ||
|
||||
properties.status != null ||
|
||||
properties.teraType != null ||
|
||||
properties.dmaxLevel != null ||
|
||||
properties.gmaxFactor != null ||
|
||||
properties.tradeable != null ||
|
||||
properties.originalTrainerType != null ||
|
||||
properties.originalTrainer != null ||
|
||||
properties.moves != null ||
|
||||
properties.movesetBuilders != null ||
|
||||
properties.cosmeticItem != null ||
|
||||
properties.scaleModifier != null ||
|
||||
properties.isAlpha != null ||
|
||||
properties.minPerfectIVs != null ||
|
||||
properties.customProperties.any { describeCustomProperty(it) == null }
|
||||
}
|
||||
|
||||
private fun describeCustomProperty(property: CustomPokemonProperty): MutableComponent? {
|
||||
return when (property) {
|
||||
is IntSpeciesFeature -> describeIntSpeciesFeature(property)
|
||||
is StringSpeciesFeature -> describeStringSpeciesFeature(property)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun describeIntSpeciesFeature(property: IntSpeciesFeature): MutableComponent? {
|
||||
val value = Component.literal(property.value.toString())
|
||||
|
||||
val overrideKey = "ui.pokedex.requirement.properties.custom.${property.name}"
|
||||
if (Language.getInstance().has("cobblemon.$overrideKey")) {
|
||||
return lang(overrideKey, value.green())
|
||||
}
|
||||
|
||||
val displayName = (SpeciesFeatures.getFeature(property.name) as? IntSpeciesFeatureProvider)
|
||||
?.display?.name?.takeIf { it.isNotBlank() }
|
||||
?: return null
|
||||
|
||||
return lang("ui.pokedex.requirement.properties.int_feature", value.green(), displayName.asTranslated().green())
|
||||
}
|
||||
|
||||
private fun describeStringSpeciesFeature(property: StringSpeciesFeature): MutableComponent? {
|
||||
val key = "ui.pokedex.requirement.properties.custom.${property.name}"
|
||||
if (!Language.getInstance().has("cobblemon.$key")) {
|
||||
return null
|
||||
}
|
||||
|
||||
val value = PokemonSpecies.getByIdentifier(cobblemonResource(property.value))?.translatedName?.plainCopy()
|
||||
?: Component.literal(property.value)
|
||||
|
||||
return lang(key, value.green())
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.pokedex.widgets.evolutiontree
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.network.chat.Component
|
||||
|
||||
fun debugEvolutionRequirementRendering() {
|
||||
if (!Cobblemon.config.enableDebugKeys) {
|
||||
return
|
||||
}
|
||||
|
||||
val gaps = mutableListOf<Pair<String, String>>()
|
||||
|
||||
for (species in PokemonSpecies.implemented) {
|
||||
val evolutions = species.evolutions + species.forms.flatMap { it.evolutions }
|
||||
for (evolution in evolutions) {
|
||||
if (renderEvolutionData(evolution) == null) {
|
||||
gaps.add("Evolution: ${evolution::class.simpleName}" to species.showdownId())
|
||||
}
|
||||
for (requirement in evolution.requirements) {
|
||||
if (resolveRequirementData(requirement) == null) {
|
||||
gaps.add("Requirement: ${requirement::class.simpleName}" to species.showdownId())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val player = Minecraft.getInstance().player
|
||||
if (gaps.isEmpty()) {
|
||||
player?.sendSystemMessage(Component.literal("Evolution render debug: no gaps found."))
|
||||
return
|
||||
}
|
||||
|
||||
val byType = gaps.groupBy({ it.first }, { it.second })
|
||||
println("Evolution render debug found ${gaps.size} gap(s) across ${byType.size} type(s):")
|
||||
byType.forEach { (type, species) -> println(" $type: ${species.distinct().joinToString(", ")}") }
|
||||
|
||||
player?.sendSystemMessage(Component.literal("Evolution render debug: ${gaps.size} gap(s) across ${byType.size} type(s), see log for details."))
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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.pokedex.widgets.evolutiontree
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
|
||||
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
|
||||
import com.cobblemon.mod.common.api.text.green
|
||||
import com.cobblemon.mod.common.pokemon.evolution.variants.BlockClickEvolution
|
||||
import com.cobblemon.mod.common.pokemon.evolution.variants.DummyEvolution
|
||||
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
|
||||
import com.cobblemon.mod.common.pokemon.evolution.variants.LevelUpEvolution
|
||||
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.itemRegistry
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.world.item.Item
|
||||
|
||||
data class RenderEvolutionData(
|
||||
val icons: List<Item>,
|
||||
val tooltips: List<MutableComponent>,
|
||||
)
|
||||
|
||||
internal fun resolveRequirementData(requirement: Requirement): RenderRequirementData? {
|
||||
val icon = requirement.pokedexIcon?.let {
|
||||
Minecraft.getInstance()?.player?.level()?.itemRegistry?.get(it)
|
||||
}
|
||||
|
||||
val tooltip = requirement.pokedexTooltip?.let {
|
||||
Component.translatable(it)
|
||||
}
|
||||
|
||||
if (icon != null && tooltip != null) {
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
return renderRequirementData(requirement)
|
||||
}
|
||||
|
||||
fun renderEvolutionData(evolution: Evolution): RenderEvolutionData? {
|
||||
when (evolution) {
|
||||
is BlockClickEvolution -> return renderBlockClickEvolution(evolution)
|
||||
is DummyEvolution -> return renderDummyEvolution(evolution)
|
||||
is ItemInteractionEvolution -> return renderItemInteractionEvolution(evolution)
|
||||
is LevelUpEvolution -> return renderLevelUpEvolution(evolution)
|
||||
is TradeEvolution -> return renderTradeEvolution(evolution)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun renderBlockClickEvolution(blockClickEvolution: BlockClickEvolution): RenderEvolutionData? {
|
||||
return null
|
||||
}
|
||||
|
||||
// TODO: Use dummyEvolution?
|
||||
fun renderDummyEvolution(evolution: Evolution): RenderEvolutionData? {
|
||||
return null
|
||||
}
|
||||
|
||||
fun renderItemInteractionEvolution(itemInteractionEvolution: ItemInteractionEvolution): RenderEvolutionData? {
|
||||
val item = itemInteractionEvolution.requiredContext.items.get().first().value()
|
||||
val icons = mutableListOf(item)
|
||||
val tooltips =
|
||||
mutableListOf(lang("ui.pokedex.evolution.item_interact.tooltip", item.description.plainCopy().green()))
|
||||
|
||||
val requirementsDataList = itemInteractionEvolution.requirements.mapNotNull { requirement ->
|
||||
resolveRequirementData(requirement)
|
||||
}
|
||||
|
||||
requirementsDataList.forEach { requirement ->
|
||||
icons.add(requirement.icon)
|
||||
tooltips.add(requirement.tooltip)
|
||||
}
|
||||
|
||||
return RenderEvolutionData(icons, tooltips)
|
||||
}
|
||||
|
||||
fun renderLevelUpEvolution(levelUpEvolution: LevelUpEvolution): RenderEvolutionData? {
|
||||
val requirementsDataList = levelUpEvolution.requirements.map { requirement ->
|
||||
resolveRequirementData(requirement)
|
||||
}
|
||||
|
||||
val icons = requirementsDataList.mapNotNull { requirementData -> requirementData?.icon }
|
||||
val tooltips = requirementsDataList.mapNotNull { requirementData -> requirementData?.tooltip }
|
||||
|
||||
return RenderEvolutionData(icons, tooltips)
|
||||
}
|
||||
|
||||
fun renderTradeEvolution(tradeEvolution: TradeEvolution): RenderEvolutionData? {
|
||||
val icons = mutableListOf<Item>(CobblemonItems.LINK_CABLE)
|
||||
val tooltips = mutableListOf<MutableComponent>()
|
||||
|
||||
val species = tradeEvolution.requiredContext.species
|
||||
if (species == null) {
|
||||
tooltips.add(lang("ui.pokedex.evolution.trade.tooltip.with_any_species"))
|
||||
} else {
|
||||
PokemonSpecies.getByIdentifier(cobblemonResource(species))?.let {
|
||||
tooltips.add(lang("ui.pokedex.evolution.trade.tooltip.with_species", it.translatedName.green()))
|
||||
}
|
||||
}
|
||||
|
||||
val requirementsDataList = tradeEvolution.requirements.mapNotNull { requirement ->
|
||||
resolveRequirementData(requirement)
|
||||
}
|
||||
|
||||
requirementsDataList.forEach { requirement ->
|
||||
icons.add(requirement.icon)
|
||||
tooltips.add(requirement.tooltip)
|
||||
}
|
||||
|
||||
return RenderEvolutionData(icons, tooltips)
|
||||
}
|
||||
+379
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
* 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.pokedex.widgets.evolutiontree
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
|
||||
import com.cobblemon.mod.common.api.conditional.RegistryLikeIdentifierCondition
|
||||
import com.cobblemon.mod.common.api.conditional.RegistryLikeTagCondition
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeatureProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
|
||||
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.text.add
|
||||
import com.cobblemon.mod.common.api.text.green
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.pokemon.requirements.*
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.level.biome.Biome
|
||||
import net.minecraft.world.level.levelgen.structure.Structure
|
||||
|
||||
data class RenderRequirementData(
|
||||
val icon: Item,
|
||||
val tooltip: MutableComponent,
|
||||
)
|
||||
|
||||
fun renderRequirementData(requirement: Requirement): RenderRequirementData? {
|
||||
when (requirement) {
|
||||
is AdvancementRequirement -> return renderAdvancementRequirement(requirement)
|
||||
is AnyRequirement -> return renderAnyRequirement(requirement)
|
||||
is AreaRequirement -> return renderAreaRequirement(requirement)
|
||||
is AttackDefenceRatioRequirement -> return renderAttackDefenceRatioRequirement(requirement)
|
||||
is BattleCriticalHitsRequirement -> return renderBattleCriticalHitsRequirement(requirement)
|
||||
is BiomeRequirement -> return renderBiomeRequirement(requirement)
|
||||
is BlocksTraveledRequirement -> return renderBlocksTraveledRequirement(requirement)
|
||||
is DamageTakenRequirement -> return renderDamageTakenRequirement(requirement)
|
||||
is DefeatRequirement -> return renderDefeatRequirement(requirement)
|
||||
is FriendshipRequirement -> return renderFriendshipRequirement(requirement)
|
||||
is HeldItemRequirement -> return renderHeldItemRequirement(requirement)
|
||||
is LevelRequirement -> return renderLevelRequirement(requirement)
|
||||
is MoonPhaseRequirement -> return renderMoonPhaseRequirement(requirement)
|
||||
is MoveSetRequirement -> return renderMoveSetRequirement(requirement)
|
||||
is MoveTypeRequirement -> return renderMoveTypeRequirement(requirement)
|
||||
is OwnerHoldsItemRequirement -> return renderOwnerHoldsItemRequirement(requirement)
|
||||
is PartyMemberRequirement -> return renderPartyMemberRequirement(requirement)
|
||||
is PokemonPropertiesRequirement -> return renderPokemonPropertiesRequirement(requirement)
|
||||
is PropertyRangeRequirement -> return renderPropertyRangeRequirement(requirement)
|
||||
is RecoilRequirement -> return renderRecoilRequirement(requirement)
|
||||
is StatCompareRequirement -> return renderStatCompareRequirement(requirement)
|
||||
is StatEqualRequirement -> return renderStatEqualRequirement(requirement)
|
||||
is StructureRequirement -> return renderStructureRequirement(requirement)
|
||||
is TimeRangeRequirement -> return renderTimeRangeRequirement(requirement)
|
||||
is UseMoveRequirement -> return renderUseMoveRequirement(requirement)
|
||||
is WeatherRequirement -> return renderWeatherRequirement(requirement)
|
||||
is WorldRequirement -> return renderWorldRequirement(requirement)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun renderAdvancementRequirement(advancementRequirement: AdvancementRequirement): RenderRequirementData? {
|
||||
val icon = Items.BOOK
|
||||
val identifier = advancementRequirement.requiredAdvancement
|
||||
val advancementLanguageKey = "${identifier.namespace}.${identifier.path.substringAfterLast('/')}"
|
||||
val advancementComponent = Component.translatable("advancements.$advancementLanguageKey").green()
|
||||
val tooltip = lang("ui.pokedex.requirement.advancement.tooltip", advancementComponent)
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderAnyRequirement(anyRequirement: AnyRequirement): RenderRequirementData? {
|
||||
return null
|
||||
}
|
||||
|
||||
fun renderAreaRequirement(areaRequirement: AreaRequirement): RenderRequirementData? {
|
||||
return null
|
||||
}
|
||||
|
||||
fun renderAttackDefenceRatioRequirement(attackDefenceRatioRequirement: AttackDefenceRatioRequirement): RenderRequirementData? {
|
||||
return null
|
||||
}
|
||||
|
||||
fun renderBattleCriticalHitsRequirement(battleCriticalHitsRequirement: BattleCriticalHitsRequirement): RenderRequirementData? {
|
||||
val icon = CobblemonItems.MEDICINAL_LEEK
|
||||
val criticalsComponent = Component.literal(battleCriticalHitsRequirement.amount.toString()).green()
|
||||
val tooltip = lang("ui.pokedex.requirement.battle_critical_hits.tooltip", criticalsComponent)
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderBiomeRequirement(biomeRequirement: BiomeRequirement): RenderRequirementData? {
|
||||
val condition = biomeRequirement.biomeCondition
|
||||
val anticondition = biomeRequirement.biomeAnticondition
|
||||
if (condition == null && anticondition == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val icon = Items.COMPASS
|
||||
val displayName: (RegistryLikeCondition<Biome>) -> MutableComponent = {
|
||||
when (it) {
|
||||
is RegistryLikeTagCondition -> Component.literal(it.tag.location().path.substringAfterLast('/').split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) })
|
||||
is RegistryLikeIdentifierCondition -> Component.translatable("biome.${it.identifier.namespace}.${it.identifier.path}")
|
||||
else -> Component.literal(it.toString())
|
||||
}
|
||||
}
|
||||
val tooltip = if (condition != null && anticondition != null) {
|
||||
lang("ui.pokedex.requirement.biome.both.tooltip", displayName(condition).green(), displayName(anticondition).green())
|
||||
} else if (condition != null) {
|
||||
lang("ui.pokedex.requirement.biome.tooltip", displayName(condition).green())
|
||||
} else {
|
||||
lang("ui.pokedex.requirement.biome.anticondition.tooltip", displayName(anticondition!!).green())
|
||||
}
|
||||
|
||||
val playerBiome = Minecraft.getInstance()?.player?.let { it.level().getBiome(it.blockPosition()) }
|
||||
if (playerBiome != null) {
|
||||
val isMet = (condition == null || condition.fits(playerBiome)) && (anticondition == null || !anticondition.fits(playerBiome))
|
||||
tooltip.add(if (isMet) Component.literal(" ✔").green() else Component.literal(" ✘").red())
|
||||
}
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderBlocksTraveledRequirement(blocksTraveledRequirement: BlocksTraveledRequirement): RenderRequirementData? {
|
||||
val icon = Items.LEATHER_BOOTS
|
||||
val blocksComponent = Component.literal(blocksTraveledRequirement.amount.toString()).green()
|
||||
val tooltip = lang("ui.pokedex.requirement.blocks_traveled.tooltip", blocksComponent)
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderDamageTakenRequirement(damageTakenRequirement: DamageTakenRequirement): RenderRequirementData? {
|
||||
val icon = CobblemonItems.PUNCHING_GLOVE
|
||||
val blocksComponent = Component.literal(damageTakenRequirement.amount.toString()).green()
|
||||
val tooltip = lang("ui.pokedex.requirement.damage_taken.tooltip", blocksComponent)
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderDefeatRequirement(defeatRequirement: DefeatRequirement): RenderRequirementData? {
|
||||
val description = describePokemonProperties(defeatRequirement.target) ?: return null
|
||||
val icon = CobblemonItems.PUNCHING_GLOVE
|
||||
val defeatedAmount = defeatRequirement.amount
|
||||
|
||||
val descriptionComponent = Component.literal(defeatedAmount.toString()).green().add(" ").add(description)
|
||||
val tooltip = lang("ui.pokedex.requirement.defeat.tooltip", descriptionComponent)
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderFriendshipRequirement(friendshipRequirement: FriendshipRequirement): RenderRequirementData? {
|
||||
val icon = CobblemonItems.SOOTHE_BELL
|
||||
val friendshipAmountComponent = Component.literal(friendshipRequirement.amount.toString()).green()
|
||||
val tooltip = lang("ui.pokedex.requirement.friendship.tooltip", friendshipAmountComponent)
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderHeldItemRequirement(heldItemRequirement: HeldItemRequirement): RenderRequirementData? {
|
||||
val item = heldItemRequirement.itemCondition.items.get().first().value()
|
||||
val tooltip = lang("ui.pokedex.requirement.held_item.tooltip", item.description.plainCopy().green())
|
||||
|
||||
return RenderRequirementData(item, tooltip)
|
||||
}
|
||||
|
||||
fun renderLevelRequirement(levelRequirement: LevelRequirement): RenderRequirementData? {
|
||||
val minLevel = levelRequirement.minLevel
|
||||
val maxLevel = levelRequirement.maxLevel
|
||||
|
||||
val icon = CobblemonItems.RARE_CANDY
|
||||
val tooltip =
|
||||
if (minLevel == maxLevel || maxLevel == Int.MAX_VALUE)
|
||||
lang("ui.pokedex.requirement.level.tooltip.min", Component.literal(minLevel.toString()).green())
|
||||
else
|
||||
lang("ui.pokedex.requirement.level.tooltip.between", Component.literal(minLevel.toString()).green(), Component.literal(maxLevel.toString()).green())
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderMoonPhaseRequirement(moonPhaseRequirement: MoonPhaseRequirement): RenderRequirementData? {
|
||||
val icon = Items.CLOCK
|
||||
val moonPhaseComponent = moonPhaseRequirement.moonPhase.displayName().green()
|
||||
val tooltip = lang("ui.pokedex.requirement.moon_phase.tooltip", moonPhaseComponent)
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderMoveSetRequirement(moveSetRequirement: MoveSetRequirement): RenderRequirementData? {
|
||||
val icon = CobblemonItems.TECHNICAL_MACHINE
|
||||
val tooltip = lang("ui.pokedex.requirement.has_move.tooltip", moveSetRequirement.move.displayName.green())
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderMoveTypeRequirement(moveTypeRequirement: MoveTypeRequirement): RenderRequirementData? {
|
||||
val type = ElementalTypes.get(moveTypeRequirement.type.name)
|
||||
val icon = when (type) {
|
||||
ElementalTypes.NORMAL -> CobblemonItems.NORMAL_GEM
|
||||
ElementalTypes.FIRE -> CobblemonItems.FIRE_GEM
|
||||
ElementalTypes.WATER -> CobblemonItems.WATER_GEM
|
||||
ElementalTypes.GRASS -> CobblemonItems.GRASS_GEM
|
||||
ElementalTypes.ELECTRIC -> CobblemonItems.ELECTRIC_GEM
|
||||
ElementalTypes.ICE -> CobblemonItems.ICE_GEM
|
||||
ElementalTypes.FIGHTING -> CobblemonItems.FIGHTING_GEM
|
||||
ElementalTypes.POISON -> CobblemonItems.POISON_GEM
|
||||
ElementalTypes.GROUND -> CobblemonItems.GROUND_GEM
|
||||
ElementalTypes.FLYING -> CobblemonItems.FLYING_GEM
|
||||
ElementalTypes.PSYCHIC -> CobblemonItems.PSYCHIC_GEM
|
||||
ElementalTypes.BUG -> CobblemonItems.BUG_GEM
|
||||
ElementalTypes.ROCK -> CobblemonItems.ROCK_GEM
|
||||
ElementalTypes.GHOST -> CobblemonItems.GHOST_GEM
|
||||
ElementalTypes.DRAGON -> CobblemonItems.DRAGON_GEM
|
||||
ElementalTypes.DARK -> CobblemonItems.DARK_GEM
|
||||
ElementalTypes.STEEL -> CobblemonItems.STEEL_GEM
|
||||
ElementalTypes.FAIRY -> CobblemonItems.FAIRY_GEM
|
||||
else -> CobblemonItems.NORMAL_GEM
|
||||
}
|
||||
|
||||
val tooltip = lang("ui.pokedex.requirement.has_move_type.tooltip", moveTypeRequirement.type.displayName.green())
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderOwnerHoldsItemRequirement(ownerHoldsItemRequirement: OwnerHoldsItemRequirement): RenderRequirementData? {
|
||||
val item = ownerHoldsItemRequirement.itemCondition.items.get().first().value()
|
||||
val tooltip = lang("ui.pokedex.requirement.owner_held_item.tooltip", item.description.plainCopy().green())
|
||||
|
||||
return RenderRequirementData(item, tooltip)
|
||||
}
|
||||
|
||||
fun renderPartyMemberRequirement(partyMemberRequirement: PartyMemberRequirement): RenderRequirementData? {
|
||||
val description = describePokemonProperties(partyMemberRequirement.target) ?: return null
|
||||
val icon = CobblemonItems.POKE_BALL
|
||||
val tooltip = if (partyMemberRequirement.contains) {
|
||||
lang("ui.pokedex.requirement.party_member.tooltip", description)
|
||||
} else {
|
||||
lang("ui.pokedex.requirement.party_member.anticondition.tooltip", description)
|
||||
}
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderPokemonPropertiesRequirement(pokemonPropertiesRequirement: PokemonPropertiesRequirement): RenderRequirementData? {
|
||||
val description = describePokemonProperties(pokemonPropertiesRequirement.target) ?: return null
|
||||
val icon = Items.NAME_TAG
|
||||
val tooltip = lang("ui.pokedex.requirement.pokemon_properties.tooltip", description)
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderPropertyRangeRequirement(propertyRangeRequirement: PropertyRangeRequirement): RenderRequirementData? {
|
||||
val provider = SpeciesFeatures.getFeature(propertyRangeRequirement.feature) as? IntSpeciesFeatureProvider ?: return null
|
||||
val displayName = provider.display?.name?.takeIf { it.isNotBlank() }?.asTranslated()?.green() ?: return null
|
||||
val icon = provider.itemPoints.keys.firstOrNull()?.let { BuiltInRegistries.ITEM.get(it) } ?: Items.NAME_TAG
|
||||
|
||||
val range = propertyRangeRequirement.range
|
||||
val tooltip = lang(
|
||||
"ui.pokedex.requirement.property_range.tooltip",
|
||||
displayName,
|
||||
Component.literal(range.first.toString()).green(),
|
||||
Component.literal(range.last.toString()).green()
|
||||
)
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderRecoilRequirement(recoilRequirement: RecoilRequirement): RenderRequirementData? {
|
||||
val icon = CobblemonItems.LIFE_ORB
|
||||
val recoilComponent = Component.literal(recoilRequirement.amount.toString()).green()
|
||||
val tooltip = lang("ui.pokedex.requirement.recoil.tooltip", recoilComponent)
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderStatCompareRequirement(statCompareRequirement: StatCompareRequirement): RenderRequirementData? {
|
||||
val highStat = Stats.getStat(statCompareRequirement.highStat)
|
||||
val lowStat = Stats.getStat(statCompareRequirement.lowStat)
|
||||
|
||||
val icon = when (highStat) {
|
||||
Stats.HP, Stats.EVASION, Stats.ACCURACY -> CobblemonItems.HEALTH_MOCHI
|
||||
Stats.ATTACK -> CobblemonItems.MUSCLE_MOCHI
|
||||
Stats.DEFENCE -> CobblemonItems.RESIST_MOCHI
|
||||
Stats.SPECIAL_ATTACK -> CobblemonItems.GENIUS_MOCHI
|
||||
Stats.SPECIAL_DEFENCE -> CobblemonItems.CLEVER_MOCHI
|
||||
Stats.SPEED -> CobblemonItems.SWIFT_MOCHI
|
||||
}
|
||||
|
||||
val highStatComponent = highStat.displayName.plainCopy().green()
|
||||
val lowStatComponent = lowStat.displayName.plainCopy().green()
|
||||
val tooltip = lang("ui.pokedex.requirement.stat_compare.tooltip", highStatComponent, lowStatComponent)
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderStatEqualRequirement(statEqualRequirement: StatEqualRequirement): RenderRequirementData? {
|
||||
val statOne = Stats.getStat(statEqualRequirement.statOne)
|
||||
val statTwo = Stats.getStat(statEqualRequirement.statTwo)
|
||||
|
||||
val statOneComponent = statOne.displayName.plainCopy().green()
|
||||
val statTwoComponent = statTwo.displayName.plainCopy().green()
|
||||
|
||||
val icon = CobblemonItems.FRESH_START_MOCHI
|
||||
val tooltip = lang("ui.pokedex.requirement.stat_equal.tooltip", statOneComponent, statTwoComponent)
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderStructureRequirement(structureRequirement: StructureRequirement): RenderRequirementData? {
|
||||
val condition = structureRequirement.structureCondition
|
||||
val anticondition = structureRequirement.structureAnticondition
|
||||
if (condition == null && anticondition == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val icon = Items.STRUCTURE_BLOCK
|
||||
val displayName: (RegistryLikeCondition<Structure>) -> MutableComponent = {
|
||||
val path = when (it) {
|
||||
is RegistryLikeTagCondition -> it.tag.location().path
|
||||
is RegistryLikeIdentifierCondition -> it.identifier.path
|
||||
else -> it.toString()
|
||||
}
|
||||
Component.literal(path.substringAfterLast('/').split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) })
|
||||
}
|
||||
val tooltip = if (condition != null && anticondition != null) {
|
||||
lang("ui.pokedex.requirement.structure.both.tooltip", displayName(condition).green(), displayName(anticondition).green())
|
||||
} else if (condition != null) {
|
||||
lang("ui.pokedex.requirement.structure.tooltip", displayName(condition).green())
|
||||
} else {
|
||||
lang("ui.pokedex.requirement.structure.anticondition.tooltip", displayName(anticondition!!).green())
|
||||
}
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderTimeRangeRequirement(timeRangeRequirement: TimeRangeRequirement): RenderRequirementData? {
|
||||
val icon = Items.CLOCK
|
||||
val timeRangeComponent = timeRangeRequirement.range.displayName() ?: return null
|
||||
val tooltip = lang("ui.pokedex.requirement.time_range.tooltip", timeRangeComponent.green())
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderUseMoveRequirement(useMoveRequirement: UseMoveRequirement): RenderRequirementData? {
|
||||
val icon = CobblemonItems.CHOICE_BAND
|
||||
val tooltip = lang("ui.pokedex.requirement.use_move.tooltip", useMoveRequirement.move.displayName.green())
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderWeatherRequirement(weatherRequirement: WeatherRequirement): RenderRequirementData? {
|
||||
val icon: Item
|
||||
val tooltip: MutableComponent
|
||||
|
||||
if (weatherRequirement.isThundering == true) {
|
||||
icon = Items.LIGHTNING_ROD
|
||||
tooltip = lang("ui.pokedex.requirement.weather.tooltip.thundering")
|
||||
} else {
|
||||
icon = Items.WATER_BUCKET
|
||||
tooltip = lang("ui.pokedex.requirement.weather.tooltip.raining")
|
||||
}
|
||||
|
||||
return RenderRequirementData(icon, tooltip)
|
||||
}
|
||||
|
||||
fun renderWorldRequirement(worldRequirement: WorldRequirement): RenderRequirementData? {
|
||||
return null
|
||||
}
|
||||
+571
@@ -0,0 +1,571 @@
|
||||
/*
|
||||
* 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.pokedex.widgets.evolutiontree
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.api.gui.*
|
||||
import com.cobblemon.mod.common.api.pokedex.Dexes
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
|
||||
import com.cobblemon.mod.common.api.text.add
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUI
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.POKEMON_DESCRIPTION_HEIGHT
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCROLL_SLOT_SIZE
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.ScaledButton
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.renderTooltip
|
||||
import com.cobblemon.mod.common.client.gui.pokedex.widgets.PokemonSlotWidget
|
||||
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
|
||||
import com.cobblemon.mod.common.pokemon.Gender
|
||||
import com.cobblemon.mod.common.pokemon.RenderablePokemon
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
|
||||
class EvolutionTreeWidget(
|
||||
val parent: PokedexGUI,
|
||||
val pX: Int,
|
||||
val pY: Int,
|
||||
) : SoundlessWidget(
|
||||
pX, pY,
|
||||
HALF_OVERLAY_WIDTH,
|
||||
POKEMON_DESCRIPTION_HEIGHT,
|
||||
lang("ui.pokedex.pokemon_info")
|
||||
) {
|
||||
companion object {
|
||||
private val slotResource = cobblemonResource("textures/gui/pokedex/pokedex_slot.png")
|
||||
private val expandResource = cobblemonResource("textures/gui/pokedex/expand.png")
|
||||
private val minimizeResource = cobblemonResource("textures/gui/pokedex/minimize.png")
|
||||
|
||||
const val X_MARGIN = 180
|
||||
const val Y_MARGIN_MINIMIZED = 135
|
||||
const val Y_MARGIN_EXPANDED = 53
|
||||
const val EXPANDED_HEIGHT = 125
|
||||
|
||||
const val HORIZONTAL_PADDING = 38
|
||||
const val VERTICAL_PADDING = 2
|
||||
const val TREE_FORK_LENGTH = 4
|
||||
|
||||
const val ICON_X_PADDING = -4.0
|
||||
const val ICON_Y_PADDING = -4.0
|
||||
const val MILLIS_BETWEEN_ICONS_CYCLE = 1000
|
||||
|
||||
const val ICON_SLOT_BACKGROUND_SIZE = 8
|
||||
const val ICON_SLOT_PADDING = 4
|
||||
const val ICON_SLOT_HALF_PADDING = ICON_SLOT_PADDING / 2
|
||||
|
||||
const val LINE_COLOR = 0xff1d6b85.toInt()
|
||||
|
||||
const val EXPAND_BUTTON_X_MARGIN = 127f
|
||||
const val EXPAND_BUTTON_Y_MARGIN = -10.5f
|
||||
const val EXPAND_BUTTON_SIZE = 20
|
||||
|
||||
private var previousRootPokedexEntry: PokedexEntry? = null
|
||||
private var previousRootPokedexForm: PokedexForm? = null
|
||||
}
|
||||
|
||||
private var rootTreeDrawingNode: TreeDrawingNode<EvolutionNode>? = null
|
||||
private var treeDrawingBounds: TreeDrawingBounds? = null
|
||||
private var zoomLevel: Double = 0.0
|
||||
private var panningX: Double = 0.0
|
||||
private var panningY: Double = 0.0
|
||||
|
||||
private var hoveredTooltips: List<MutableComponent>? = null
|
||||
|
||||
private val expandButton: ScaledButton = ScaledButton(
|
||||
0f,
|
||||
0f,
|
||||
EXPAND_BUTTON_SIZE,
|
||||
EXPAND_BUTTON_SIZE,
|
||||
resource = expandResource,
|
||||
clickAction = {
|
||||
parent.evolutionTreeExpanded = !parent.evolutionTreeExpanded
|
||||
expandButton.resource = if (parent.evolutionTreeExpanded) minimizeResource else expandResource
|
||||
updatePositioning()
|
||||
}
|
||||
).apply { addWidget(this) }
|
||||
|
||||
override fun renderWidget(
|
||||
guiGraphics: GuiGraphics,
|
||||
mouseX: Int,
|
||||
mouseY: Int,
|
||||
partialTick: Float,
|
||||
) {
|
||||
drawScaledText(
|
||||
context = guiGraphics,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = Component.translatable("cobblemon.ui.pokedex.info.evolution_tree").bold(),
|
||||
x = x + 9,
|
||||
y = y - 10,
|
||||
shadow = true
|
||||
)
|
||||
|
||||
expandButton.render(guiGraphics, mouseX, mouseY, partialTick)
|
||||
|
||||
rootTreeDrawingNode?.let {
|
||||
enableScissors(guiGraphics)
|
||||
renderTreeDrawingNode(guiGraphics, it, mouseX, mouseY, partialTick)
|
||||
guiGraphics.disableScissor()
|
||||
|
||||
hoveredTooltips?.let { tooltips ->
|
||||
renderTooltip(
|
||||
guiGraphics,
|
||||
tooltips,
|
||||
mouseX,
|
||||
mouseY,
|
||||
partialTick,
|
||||
4,
|
||||
)
|
||||
}
|
||||
|
||||
hoveredTooltips = null
|
||||
}
|
||||
}
|
||||
|
||||
fun setup(pokedexEntry: PokedexEntry?, pokedexForm: PokedexForm?) {
|
||||
if (pokedexEntry == null || pokedexForm == null) return
|
||||
|
||||
updatePositioning()
|
||||
|
||||
val (rootPokedexEntry, rootPokedexForm) = Dexes.findRootDexEntryBySpeciesId(pokedexEntry.speciesId, pokedexForm.displayForm)
|
||||
val rootEvolutionNode = buildEvolutionTree(rootPokedexEntry, rootPokedexForm, null)
|
||||
|
||||
// val isSameEntry = previousRootPokedexEntry != null && pokedexEntry?.id == previousRootPokedexEntry?.id
|
||||
// val isSameForm =
|
||||
// previousRootPokedexForm != null && pokedexForm?.displayForm == previousRootPokedexForm?.displayForm
|
||||
|
||||
// TODO: figure out how to prevent pokedex from resetting all the widgets state
|
||||
// if (isSameEntry && isSameForm) {
|
||||
// return
|
||||
// }
|
||||
|
||||
previousRootPokedexEntry = pokedexEntry
|
||||
previousRootPokedexForm = pokedexForm
|
||||
|
||||
createDrawingTree(rootEvolutionNode).also {
|
||||
rootTreeDrawingNode = it
|
||||
treeDrawingBounds = calculateBounds<EvolutionNode>(it)
|
||||
}
|
||||
|
||||
zoomLevel = 0.0
|
||||
panningX = 0.0
|
||||
panningY = 0.0
|
||||
}
|
||||
|
||||
fun updatePositioning() {
|
||||
x = pX + X_MARGIN
|
||||
y = pY + if (parent.evolutionTreeExpanded) Y_MARGIN_EXPANDED else Y_MARGIN_MINIMIZED
|
||||
height = if (parent.evolutionTreeExpanded) EXPANDED_HEIGHT else POKEMON_DESCRIPTION_HEIGHT
|
||||
|
||||
panningX = 0.0
|
||||
panningY = 0.0
|
||||
|
||||
expandButton.buttonX = x + EXPAND_BUTTON_X_MARGIN
|
||||
expandButton.buttonY = y + EXPAND_BUTTON_Y_MARGIN
|
||||
}
|
||||
|
||||
private fun buildEvolutionTree(
|
||||
pokedexEntry: PokedexEntry,
|
||||
pokedexForm: PokedexForm,
|
||||
evolution: Evolution? = null,
|
||||
resultProperties: PokemonProperties? = evolution?.result,
|
||||
renderData: RenderEvolutionData? = evolution?.let { renderEvolutionData(it) }
|
||||
): EvolutionNode {
|
||||
val species = PokemonSpecies.getByIdentifier(pokedexEntry.speciesId)!!
|
||||
|
||||
val formEquivalents = Dexes.getFormEquivalents(pokedexForm.displayForm)
|
||||
val form = species.forms.firstOrNull { formEquivalents.contains(it.name.lowercase()) }
|
||||
val parentSpecies = Dexes.findDexEntryBySpeciesId(species.resourceIdentifier)!!
|
||||
val parentPokedexForm = parentSpecies.forms.first { formEquivalents.contains(it.displayForm.lowercase()) }
|
||||
|
||||
val aspects = resultProperties?.aspects?.toMutableSet() ?: mutableSetOf()
|
||||
form?.aspects?.let { aspects.addAll(it) }
|
||||
|
||||
val discoveryLevel = CobblemonClient.clientPokedexData.getHighestKnowledgeFor(pokedexEntry, pokedexForm, aspects)
|
||||
|
||||
val evolutions = form?.evolutions ?: species.evolutions
|
||||
|
||||
// Avoid evolution.result.create() here: it builds a full battle-ready Pokemon (initializeMovesetFromDefault
|
||||
// etc.) which relies on server-only data like CobblemonMovesetBuilders and crashes on the client
|
||||
val children = evolutions.flatMap { evolution ->
|
||||
val baseRenderData = renderEvolutionData(evolution)
|
||||
|
||||
listOfNotNull(evolution.result, evolution.shedder).mapNotNull { resultProperties ->
|
||||
val resultSpecies = resultProperties.species
|
||||
?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
|
||||
?: return@mapNotNull null
|
||||
|
||||
val resultForm = resultSpecies.getForm(aspects + resultProperties.aspects)
|
||||
|
||||
val speciesId = resultSpecies.resourceIdentifier
|
||||
val childPokedexEntry = Dexes.findDexEntryBySpeciesId(speciesId)
|
||||
|
||||
if (childPokedexEntry == null) {
|
||||
Cobblemon.LOGGER.warn("Dex entry not found for species: $speciesId")
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
val formEquivalents = Dexes.getFormEquivalents(resultForm.name.lowercase())
|
||||
val childPokedexForm = childPokedexEntry.forms.first { formEquivalents.contains(it.displayForm.lowercase()) }
|
||||
|
||||
val childRenderData = if (resultProperties === evolution.shedder) {
|
||||
RenderEvolutionData(
|
||||
icons = (baseRenderData?.icons ?: emptyList()) + CobblemonItems.POKE_BALL,
|
||||
tooltips = (baseRenderData?.tooltips ?: emptyList()) + lang("ui.pokedex.evolution.shedder.tooltip"),
|
||||
)
|
||||
} else {
|
||||
baseRenderData
|
||||
}
|
||||
|
||||
return@mapNotNull buildEvolutionTree(childPokedexEntry, childPokedexForm, evolution, resultProperties, childRenderData)
|
||||
}
|
||||
}
|
||||
|
||||
return EvolutionNode(pokedexEntry, parentPokedexForm, aspects, discoveryLevel, children, renderData)
|
||||
}
|
||||
|
||||
private fun renderTreeDrawingNode(
|
||||
guiGraphics: GuiGraphics,
|
||||
treeDrawingNode: TreeDrawingNode<EvolutionNode>,
|
||||
mouseX: Int,
|
||||
mouseY: Int,
|
||||
delta: Float,
|
||||
) {
|
||||
val (treeX, treeY) = getDrawingCoordinates(treeDrawingNode)
|
||||
|
||||
if (treeDrawingNode.children.size == 1) {
|
||||
val child = treeDrawingNode.children.first()
|
||||
val (childX, _) = getDrawingCoordinates(child)
|
||||
|
||||
val lineStartX = treeX + SCROLL_SLOT_SIZE
|
||||
val lineEndX = childX
|
||||
val lineY = treeY + SCROLL_SLOT_SIZE / 2
|
||||
|
||||
child.value.renderData?.let { renderData ->
|
||||
drawEvolutionInfo(
|
||||
guiGraphics,
|
||||
renderData,
|
||||
lineStartX,
|
||||
lineEndX,
|
||||
lineY,
|
||||
mouseX,
|
||||
mouseY,
|
||||
delta,
|
||||
)
|
||||
}
|
||||
} else if (treeDrawingNode.children.size > 1) {
|
||||
val treeForkStart = treeX + SCROLL_SLOT_SIZE
|
||||
val treeForkEnd = treeX + SCROLL_SLOT_SIZE + TREE_FORK_LENGTH
|
||||
val middleStart = treeY + SCROLL_SLOT_SIZE / 2
|
||||
|
||||
val (_, firstChildY) = getDrawingCoordinates(treeDrawingNode.children.first())
|
||||
val (_, lastChildY) = getDrawingCoordinates(treeDrawingNode.children.last())
|
||||
|
||||
guiGraphics.hLine(
|
||||
treeForkStart,
|
||||
treeForkEnd,
|
||||
middleStart,
|
||||
LINE_COLOR,
|
||||
)
|
||||
|
||||
guiGraphics.vLine(
|
||||
treeForkEnd,
|
||||
firstChildY + SCROLL_SLOT_SIZE / 2,
|
||||
lastChildY + SCROLL_SLOT_SIZE / 2,
|
||||
LINE_COLOR,
|
||||
)
|
||||
|
||||
treeDrawingNode.children.forEach { child ->
|
||||
val (childX, childY) = getDrawingCoordinates(child)
|
||||
|
||||
val lineStartX = treeForkEnd
|
||||
val lineEndX = childX
|
||||
val lineY = childY + SCROLL_SLOT_SIZE / 2
|
||||
|
||||
child.value.renderData?.let { renderData ->
|
||||
drawEvolutionInfo(
|
||||
guiGraphics,
|
||||
renderData,
|
||||
lineStartX,
|
||||
lineEndX,
|
||||
lineY,
|
||||
mouseX,
|
||||
mouseY,
|
||||
delta,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
treeDrawingNode.children.forEach { child ->
|
||||
renderTreeDrawingNode(guiGraphics, child, mouseX, mouseY, delta)
|
||||
}
|
||||
|
||||
val isVisible =
|
||||
treeX >= x - SCROLL_SLOT_SIZE &&
|
||||
treeX <= x + width &&
|
||||
treeY >= y - SCROLL_SLOT_SIZE &&
|
||||
treeY <= y + height
|
||||
|
||||
if (!isVisible) return
|
||||
|
||||
val evolutionNode = treeDrawingNode.value
|
||||
val isSlotHovered =
|
||||
isElementHovered(mouseX, mouseY, treeX, treeX + SCROLL_SLOT_SIZE, treeY, treeY + SCROLL_SLOT_SIZE)
|
||||
|
||||
val discoveryLevel = evolutionNode.pokedexEntryProgress
|
||||
if (isSlotHovered) {
|
||||
val text =
|
||||
if (discoveryLevel == PokedexEntryProgress.UNREGISTERED) {
|
||||
lang("ui.pokedex.unknown")
|
||||
} else {
|
||||
val species = PokemonSpecies.getByIdentifier(evolutionNode.pokedexEntry.speciesId)!!
|
||||
val speciesComponent = species.translatedName
|
||||
val formName = evolutionNode.pokedexForm.displayForm.lowercase()
|
||||
val formComponent =
|
||||
if (formName == "normal") null
|
||||
else lang("ui.pokedex.info.form.${species}-${formName.replace("-", "")}")
|
||||
|
||||
formComponent?.add(" ")?.add(speciesComponent) ?: speciesComponent
|
||||
}
|
||||
|
||||
guiGraphics.disableScissor()
|
||||
renderTooltip(
|
||||
context = guiGraphics,
|
||||
text = text,
|
||||
posX = mouseX,
|
||||
posY = mouseY,
|
||||
delta,
|
||||
4,
|
||||
)
|
||||
enableScissors(guiGraphics)
|
||||
}
|
||||
|
||||
|
||||
val pokedexEntry = evolutionNode.pokedexEntry
|
||||
val pokedexForm = evolutionNode.pokedexForm
|
||||
val species = PokemonSpecies.getByIdentifier(evolutionNode.pokedexEntry.speciesId)!!
|
||||
val encounteredForms = CobblemonClient.clientPokedexData.getEncounteredForms(pokedexEntry)
|
||||
val formToRender = encounteredForms.firstOrNull { it.displayForm.equals(pokedexForm.displayForm, ignoreCase = true) }
|
||||
|
||||
val renderablePokemon =
|
||||
if (formToRender != null) {
|
||||
val seenGenders = CobblemonClient.clientPokedexData.getSeenGenders(pokedexEntry, formToRender)
|
||||
val seenShinyStates = CobblemonClient.clientPokedexData.getSeenShinyStates(pokedexEntry, formToRender)
|
||||
|
||||
val aspectsToDraw = (pokedexEntry.displayAspects + evolutionNode.aspects).toMutableSet()
|
||||
|
||||
val formAspects = species.forms.firstOrNull { it.name.equals(formToRender.displayForm, ignoreCase = true) }?.aspects
|
||||
if (formAspects != null) {
|
||||
aspectsToDraw.addAll(formAspects)
|
||||
}
|
||||
|
||||
if (seenGenders.contains(parent.gender)) {
|
||||
aspectsToDraw.add(parent.gender.name.lowercase())
|
||||
} else {
|
||||
aspectsToDraw.add((seenGenders.firstOrNull() ?: Gender.GENDERLESS).name.lowercase())
|
||||
}
|
||||
|
||||
if (parent.shiny && seenShinyStates.contains("shiny")) {
|
||||
aspectsToDraw.add("shiny")
|
||||
}
|
||||
|
||||
RenderablePokemon(species, aspectsToDraw)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
PokemonSlotWidget.render(
|
||||
context = guiGraphics,
|
||||
x = treeX,
|
||||
y = treeY,
|
||||
isHovered = isSlotHovered,
|
||||
species = species,
|
||||
renderablePokemon = renderablePokemon,
|
||||
discoveryLevel = discoveryLevel,
|
||||
)
|
||||
}
|
||||
|
||||
private fun drawEvolutionInfo(
|
||||
guiGraphics: GuiGraphics,
|
||||
renderData: RenderEvolutionData,
|
||||
lineStartX: Int,
|
||||
lineEndX: Int,
|
||||
lineY: Int,
|
||||
mouseX: Int,
|
||||
mouseY: Int,
|
||||
delta: Float,
|
||||
) {
|
||||
val lineXMiddle = (lineStartX + lineEndX) / 2
|
||||
val iconX = (lineXMiddle + ICON_X_PADDING - ICON_SLOT_HALF_PADDING).toInt()
|
||||
val iconY = (lineY + ICON_Y_PADDING - ICON_SLOT_HALF_PADDING).toInt()
|
||||
val iconSize = ICON_SLOT_BACKGROUND_SIZE + ICON_SLOT_PADDING
|
||||
|
||||
val isVisible =
|
||||
iconX >= x - iconSize &&
|
||||
iconX <= x + width &&
|
||||
iconY >= y - iconSize &&
|
||||
iconY <= y + height
|
||||
|
||||
if (!isVisible) {
|
||||
return
|
||||
}
|
||||
|
||||
if (renderData.icons.isEmpty()) {
|
||||
guiGraphics.hLine(
|
||||
lineStartX,
|
||||
lineEndX,
|
||||
lineY,
|
||||
LINE_COLOR,
|
||||
)
|
||||
} else {
|
||||
guiGraphics.hLine(
|
||||
lineStartX,
|
||||
iconX - 1,
|
||||
lineY,
|
||||
LINE_COLOR,
|
||||
)
|
||||
|
||||
guiGraphics.hLine(
|
||||
iconX + iconSize,
|
||||
lineEndX,
|
||||
lineY,
|
||||
LINE_COLOR,
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = guiGraphics.pose(),
|
||||
texture = slotResource,
|
||||
x = iconX,
|
||||
y = iconY,
|
||||
width = iconSize,
|
||||
height = iconSize,
|
||||
)
|
||||
|
||||
val index = (System.currentTimeMillis() / MILLIS_BETWEEN_ICONS_CYCLE) % renderData.icons.size
|
||||
val item = renderData.icons[index.toInt()]
|
||||
|
||||
renderScaledGuiItemIcon(
|
||||
itemStack = item.defaultInstance,
|
||||
x = lineXMiddle + ICON_X_PADDING,
|
||||
y = lineY + ICON_Y_PADDING,
|
||||
matrixStack = guiGraphics.pose(),
|
||||
scale = PokedexGUIConstants.SCALE.toDouble(),
|
||||
)
|
||||
}
|
||||
|
||||
val isLineHovered = isElementHovered(mouseX, mouseY, lineStartX, lineEndX, lineY - 1, lineY + 1)
|
||||
val isIconHovered = isElementHovered(
|
||||
mouseX,
|
||||
mouseY,
|
||||
iconX.toInt(),
|
||||
iconX.toInt() + iconSize,
|
||||
iconY.toInt(),
|
||||
iconY.toInt() + iconSize,
|
||||
)
|
||||
|
||||
if (renderData.tooltips.isNotEmpty() &&
|
||||
(isLineHovered || isIconHovered) &&
|
||||
this.isHovered(mouseX.toDouble(), mouseY.toDouble())
|
||||
) {
|
||||
hoveredTooltips = renderData.tooltips
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDrawingCoordinates(node: TreeDrawingNode<EvolutionNode>): Pair<Int, Int> {
|
||||
val halfSlot = SCROLL_SLOT_SIZE / 2f
|
||||
|
||||
val xStep = halfSlot + HORIZONTAL_PADDING
|
||||
val yStep = halfSlot + VERTICAL_PADDING
|
||||
|
||||
// Swaps the x and y to draw the tree horizontally
|
||||
val xDislocation = (node.y - treeDrawingBounds!!.yCenter) * xStep
|
||||
val yDislocation = (node.x - treeDrawingBounds!!.xCenter) * yStep
|
||||
|
||||
val xCenter = width / 2f - halfSlot
|
||||
val yCenter = height / 2f - halfSlot
|
||||
|
||||
val screenX = (xCenter.toInt() + xDislocation.toInt() + panningX.toInt() + x)
|
||||
val screenY = (yCenter.toInt() + yDislocation.toInt() + panningY.toInt() + y)
|
||||
|
||||
return screenX to screenY
|
||||
}
|
||||
|
||||
private fun isElementHovered(
|
||||
mouseX: Int,
|
||||
mouseY: Int,
|
||||
startX: Int,
|
||||
endX: Int,
|
||||
startY: Int,
|
||||
endY: Int,
|
||||
): Boolean {
|
||||
return mouseX >= startX && mouseX <= endX && mouseY >= startY && mouseY <= endY
|
||||
}
|
||||
|
||||
override fun mouseDragged(
|
||||
mouseX: Double,
|
||||
mouseY: Double,
|
||||
button: Int,
|
||||
deltaX: Double,
|
||||
deltaY: Double
|
||||
): Boolean {
|
||||
panningX += deltaX
|
||||
panningY += deltaY
|
||||
|
||||
val bounds = treeDrawingBounds ?: return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||
val treeHeight = bounds.height.coerceAtLeast(1)
|
||||
val treeWidth = bounds.width.coerceAtLeast(1)
|
||||
|
||||
val xStep = HORIZONTAL_PADDING + SCROLL_SLOT_SIZE / 2.0
|
||||
val yStep = VERTICAL_PADDING + SCROLL_SLOT_SIZE / 2.0
|
||||
|
||||
val minPanningX = (treeHeight - 1) * -xStep / 2.0 - (width + SCROLL_SLOT_SIZE) / 2.0
|
||||
val maxPanningX = (treeHeight - 1) * xStep / 2.0 + (width + SCROLL_SLOT_SIZE) / 2.0
|
||||
|
||||
val divideBy = if (treeWidth % 2 == 0) 4.0 else 2.0
|
||||
val minPanningY = (treeWidth - 1) * -yStep / 2.0 - height / 2.0 + SCROLL_SLOT_SIZE / divideBy
|
||||
val maxPanningY = (treeWidth - 1) * yStep / 2.0 + height / 2.0 - SCROLL_SLOT_SIZE / divideBy
|
||||
|
||||
panningX = panningX.coerceIn(minPanningX, maxPanningX)
|
||||
panningY = panningY.coerceIn(minPanningY, maxPanningY)
|
||||
|
||||
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||
}
|
||||
|
||||
fun isHovered(mouseX: Double, mouseY: Double): Boolean {
|
||||
return mouseX.toInt() in x..(x + width) && mouseY.toInt() in y..(y + height)
|
||||
}
|
||||
|
||||
fun enableScissors(guiGraphics: GuiGraphics) {
|
||||
guiGraphics.enableScissor(x + 1, y, x + width - 1, y + height)
|
||||
}
|
||||
|
||||
private class EvolutionNode(
|
||||
val pokedexEntry: PokedexEntry,
|
||||
val pokedexForm: PokedexForm,
|
||||
val aspects: Set<String>,
|
||||
val pokedexEntryProgress: PokedexEntryProgress,
|
||||
override val children: List<EvolutionNode>,
|
||||
val renderData: RenderEvolutionData?,
|
||||
) : TreeNode<EvolutionNode>
|
||||
}
|
||||
+4
-6
@@ -8,12 +8,10 @@
|
||||
|
||||
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
|
||||
@@ -29,12 +27,11 @@ import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
|
||||
class CategoryList(
|
||||
private val paneWidth: Int,
|
||||
private val paneHeight: Int,
|
||||
paneWidth: Int,
|
||||
paneHeight: Int,
|
||||
private val categories: List<RenderableStarterCategory>,
|
||||
val listX: Int,
|
||||
val listY: Int,
|
||||
private val minecraft: Minecraft = Minecraft.getInstance(),
|
||||
private val starterSelectionScreen: StarterSelectionScreen
|
||||
) : VariableObjectSelectionList(
|
||||
listX,
|
||||
@@ -70,7 +67,8 @@ class CategoryList(
|
||||
}
|
||||
|
||||
private fun createEntries() = categories.map { category ->
|
||||
val rows = (category.pokemon.size - 1) / 3 + 1
|
||||
val pokemonCount = category.pokemon.size - if (category.randomStarter) 0 else 1
|
||||
val rows = pokemonCount / 3 + 1
|
||||
val height = CONTAINER_BUTTON_SIZE * rows + BUTTON_SEPERATION * (rows + 1) + HEADER_HEIGHT + 3
|
||||
Category(category, height)
|
||||
}
|
||||
|
||||
+20
@@ -11,11 +11,14 @@ package com.cobblemon.mod.common.client.keybind.keybinds
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.keybind.CobblemonKeyBinding
|
||||
import com.cobblemon.mod.common.client.gui.debug.speed.SpeedTuner
|
||||
import com.cobblemon.mod.common.client.gui.debug.speed.SpeedTunerScreen
|
||||
import com.cobblemon.mod.common.client.keybind.KeybindCategories
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.RequestOpenRidingStatsDebugGUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.RequestOpenSpeedTunerPacket
|
||||
import com.mojang.blaze3d.platform.InputConstants
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.network.chat.Component
|
||||
@@ -30,6 +33,7 @@ object DebugKeybindings {
|
||||
TranslateDownKeybinding(),
|
||||
PrintModelSettingsKeybinding(),
|
||||
ToggleRidingStatsDebugGUIKeybinding(),
|
||||
ToggleSpeedTunerKeybinding(),
|
||||
)
|
||||
|
||||
class ScaleUpKeybinding: CobblemonKeyBinding(
|
||||
@@ -163,4 +167,20 @@ object DebugKeybindings {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleSpeedTunerKeybinding : CobblemonKeyBinding(
|
||||
"key.cobblemon.togglespeedtuner",
|
||||
InputConstants.Type.KEYSYM,
|
||||
InputConstants.KEY_APOSTROPHE,
|
||||
KeybindCategories.COBBLEMON_DEBUG_CATEGORY
|
||||
) {
|
||||
override fun onPress() {
|
||||
// A running session just needs its panel back, otherwise the server checks permission and sends speeds
|
||||
if (SpeedTuner.active) {
|
||||
Minecraft.getInstance().setScreen(SpeedTunerScreen())
|
||||
} else {
|
||||
RequestOpenSpeedTunerPacket().sendToServer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.gui.debug.speed.SpeedTuner
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.OpenSpeedTunerPacket
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
object OpenSpeedTunerHandler : ClientNetworkPacketHandler<OpenSpeedTunerPacket> {
|
||||
override fun handle(packet: OpenSpeedTunerPacket, client: Minecraft) {
|
||||
SpeedTuner.open(packet.entries)
|
||||
}
|
||||
}
|
||||
+14
-28
@@ -8,9 +8,9 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.render.block
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonItemComponents
|
||||
import com.cobblemon.mod.common.block.entity.DiscShelfBlockEntity
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.client.render.color.TechnicalMachineItemColorProvider
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer
|
||||
@@ -37,15 +37,15 @@ class DiscShelfBlockEntityRenderer(ctx: BlockEntityRendererProvider.Context) :
|
||||
private val slotHeightPixels = 2
|
||||
private val textureSize = 16f
|
||||
|
||||
private val slotUMax = slotWidthPixels / textureSize
|
||||
private val slotVMax = slotHeightPixels / textureSize
|
||||
|
||||
private val leftMargin = 1f
|
||||
private val rightMargin = 1f
|
||||
private val topMargin = 1f
|
||||
private val interRowSpacing = 0f
|
||||
private val tmOverlayDepthOffset = -0.0002f
|
||||
private val slotBrightnessMultiplier = 0.65f
|
||||
|
||||
private val tmBaseTexture = cobblemonResource("textures/block/disc_shelf/technical_machine_base.png")
|
||||
private val tmOverlayTexture = cobblemonResource("textures/block/disc_shelf/technical_machine_overlay.png")
|
||||
private val musicDiscUnknownTexture = cobblemonResource("textures/block/disc_shelf/music_disc_unknown.png")
|
||||
|
||||
override fun render(
|
||||
@@ -98,35 +98,19 @@ class DiscShelfBlockEntityRenderer(ctx: BlockEntityRendererProvider.Context) :
|
||||
poseStack.translate(posX, posY, -0.001f)
|
||||
|
||||
if (item.item == CobblemonItems.TECHNICAL_MACHINE) {
|
||||
val baseColor = TechnicalMachineItemColorProvider.getColor(item, 0)
|
||||
val overlayColor = TechnicalMachineItemColorProvider.getColor(item, 1)
|
||||
|
||||
val elementId = item.components.get(CobblemonItemComponents.TM_MOVE)!!.move.elementalType.showdownId
|
||||
val texture = cobblemonResource("textures/block/disc_shelf/${elementId}_tm.png")
|
||||
renderSlotQuad(
|
||||
poseStack,
|
||||
buffer,
|
||||
tmBaseTexture,
|
||||
baseColor,
|
||||
texture,
|
||||
0xFFFFFF,
|
||||
packedLight,
|
||||
slotWidth,
|
||||
slotHeight,
|
||||
tint = true,
|
||||
useTextRenderType = true
|
||||
)
|
||||
|
||||
poseStack.pushPose()
|
||||
poseStack.translate(0.0, 0.0, tmOverlayDepthOffset.toDouble())
|
||||
renderSlotQuad(
|
||||
poseStack,
|
||||
buffer,
|
||||
tmOverlayTexture,
|
||||
overlayColor,
|
||||
packedLight,
|
||||
slotWidth,
|
||||
slotHeight,
|
||||
tint = true,
|
||||
useTextRenderType = true
|
||||
)
|
||||
poseStack.popPose()
|
||||
} else {
|
||||
val tex = getItemTexture(item)
|
||||
renderSlotQuad(
|
||||
@@ -176,13 +160,15 @@ class DiscShelfBlockEntityRenderer(ctx: BlockEntityRendererProvider.Context) :
|
||||
val g = (gBase * slotBrightnessMultiplier).coerceIn(0f, 1f)
|
||||
val b = (bBase * slotBrightnessMultiplier).coerceIn(0f, 1f)
|
||||
|
||||
consumer.addVertex(matrix, 0f, 0f, 0f).setColor(r, g, b, a).setUv(0f, 1f)
|
||||
// The quad faces -Z, so a viewer looking at it sees +X on their left.
|
||||
// U runs from slotUMax at x = 0 to 0 at x = width so the art is not mirrored.
|
||||
consumer.addVertex(matrix, 0f, 0f, 0f).setColor(r, g, b, a).setUv(slotUMax, slotVMax)
|
||||
.setUv1(0, 0).setUv2(light and 0xFFFF, light shr 16).setNormal(0f, 0f, -1f)
|
||||
consumer.addVertex(matrix, 0f, height, 0f).setColor(r, g, b, a).setUv(0f, 0f)
|
||||
consumer.addVertex(matrix, 0f, height, 0f).setColor(r, g, b, a).setUv(slotUMax, 0f)
|
||||
.setUv1(0, 0).setUv2(light and 0xFFFF, light shr 16).setNormal(0f, 0f, -1f)
|
||||
consumer.addVertex(matrix, width, height, 0f).setColor(r, g, b, a).setUv(1f, 0f)
|
||||
consumer.addVertex(matrix, width, height, 0f).setColor(r, g, b, a).setUv(0f, 0f)
|
||||
.setUv1(0, 0).setUv2(light and 0xFFFF, light shr 16).setNormal(0f, 0f, -1f)
|
||||
consumer.addVertex(matrix, width, 0f, 0f).setColor(r, g, b, a).setUv(1f, 1f)
|
||||
consumer.addVertex(matrix, width, 0f, 0f).setColor(r, g, b, a).setUv(0f, slotVMax)
|
||||
.setUv1(0, 0).setUv2(light and 0xFFFF, light shr 16).setNormal(0f, 0f, -1f)
|
||||
}
|
||||
|
||||
|
||||
-27
@@ -1,27 +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.color
|
||||
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.item.components.TMMoveComponent.Companion.getTMMove
|
||||
import net.minecraft.client.color.item.ItemColor
|
||||
import net.minecraft.util.FastColor
|
||||
import net.minecraft.world.item.ItemStack
|
||||
|
||||
object TechnicalMachineItemColorProvider : ItemColor {
|
||||
override fun getColor(itemStack: ItemStack, layer: Int): Int {
|
||||
val moveType = getTMMove(itemStack)?.elementalType ?: ElementalTypes.NORMAL
|
||||
val color = when (layer) {
|
||||
0 -> moveType.primaryColor
|
||||
1 -> moveType.secondaryColor
|
||||
else -> 0xFFFFFF
|
||||
}
|
||||
return FastColor.ARGB32.opaque(color)
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.layer
|
||||
|
||||
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import com.mojang.math.Axis
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.model.PlayerModel
|
||||
import net.minecraft.client.renderer.MultiBufferSource
|
||||
import net.minecraft.client.renderer.entity.RenderLayerParent
|
||||
import net.minecraft.client.renderer.entity.layers.RenderLayer
|
||||
import net.minecraft.world.entity.HumanoidArm
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.joml.Vector3f
|
||||
|
||||
class PlayerRecallBallRenderer<T : Player>(
|
||||
renderLayerParent: RenderLayerParent<T, PlayerModel<T>>
|
||||
) : RenderLayer<T, PlayerModel<T>>(renderLayerParent) {
|
||||
|
||||
override fun render(
|
||||
matrixStack: PoseStack,
|
||||
buffer: MultiBufferSource,
|
||||
packedLight: Int,
|
||||
player: T,
|
||||
limbSwing: Float,
|
||||
limbSwingAmount: Float,
|
||||
partialTicks: Float,
|
||||
ageInTicks: Float,
|
||||
netHeadYaw: Float,
|
||||
headPitch: Float
|
||||
) {
|
||||
val anim = RecallArmAnimationTracker.get(player.uuid) ?: return
|
||||
val mc = Minecraft.getInstance()
|
||||
if (player === mc.player && mc.options.cameraType.isFirstPerson) return
|
||||
|
||||
matrixStack.pushPose()
|
||||
getParentModel().translateToHand(HumanoidArm.RIGHT, matrixStack)
|
||||
matrixStack.mulPose(Axis.XP.rotationDegrees(-90F))
|
||||
matrixStack.mulPose(Axis.YP.rotationDegrees(180F))
|
||||
matrixStack.translate(0.0625, 0.125, -0.625)
|
||||
matrixStack.scale(0.54F, -0.54F, -0.54F)
|
||||
matrixStack.translate(0.0, 0.5, 0.0)
|
||||
matrixStack.mulPose(Axis.YP.rotationDegrees(180F))
|
||||
|
||||
RecallArmAnimationTracker.renderBall(anim, matrixStack, buffer, packedLight, partialTicks)
|
||||
|
||||
anim.ballState.locatorStates["beam"]?.let { beam ->
|
||||
matrixStack.pushPose()
|
||||
matrixStack.mulPose(beam.matrix)
|
||||
val offset = matrixStack.last().pose().transformPosition(Vector3f(0F, 0.18F, -0.3F))
|
||||
matrixStack.popPose()
|
||||
anim.beamOrigin = mc.gameRenderer.mainCamera.position.add(Vec3(offset))
|
||||
}
|
||||
matrixStack.popPose()
|
||||
}
|
||||
}
|
||||
+17
-8
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common.client.render.layer
|
||||
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
|
||||
import com.cobblemon.mod.common.client.render.item.HeldItemRenderer
|
||||
@@ -23,7 +24,6 @@ import com.cobblemon.mod.common.pokemon.Species
|
||||
import com.cobblemon.mod.common.util.DataKeys
|
||||
import com.cobblemon.mod.common.util.isPokemonEntity
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import com.mojang.math.Axis
|
||||
import java.util.UUID
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.model.PlayerModel
|
||||
@@ -33,11 +33,14 @@ import net.minecraft.client.renderer.entity.LivingEntityRenderer
|
||||
import net.minecraft.client.renderer.entity.RenderLayerParent
|
||||
import net.minecraft.client.renderer.entity.layers.RenderLayer
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.nbt.NbtOps
|
||||
import net.minecraft.nbt.Tag
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.entity.EquipmentSlot
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ArmorItem
|
||||
import net.minecraft.world.item.ItemStack
|
||||
|
||||
class PokemonOnShoulderRenderer<T : Player>(renderLayerParent: RenderLayerParent<T, PlayerModel<T>>) :
|
||||
@@ -165,16 +168,16 @@ class PokemonOnShoulderRenderer<T : Player>(renderLayerParent: RenderLayerParent
|
||||
val width = shoulderData.form.hitbox.width
|
||||
val heightOffset = -1.5 * scale
|
||||
val widthOffset = width / 2 - 0.7
|
||||
// If they're sneaking, the pokemon needs to rotate a little bit and push forward
|
||||
// Shoulders move a bit when sneaking which is why the translation is necessary.
|
||||
// Shoulder exact rotation according to testing (miasmus) is 0.4 radians, the -0.15 is eyeballed though.
|
||||
if (livingEntity.isCrouching) {
|
||||
matrixStack.mulPose(Axis.XP.rotation(0.4F))
|
||||
matrixStack.translate(0F, 0F, -0.15F)
|
||||
val chestItem = livingEntity.getItemBySlot(EquipmentSlot.CHEST)
|
||||
val armorHeightOffset = if (chestItem.item is ArmorItem && !isArmorOffsetBlacklisted(chestItem)) {
|
||||
ARMOR_HEIGHT_OFFSET
|
||||
} else {
|
||||
0F
|
||||
}
|
||||
parentModel.body.translateAndRotate(matrixStack)
|
||||
matrixStack.translate(
|
||||
if (pLeftShoulder) -widthOffset else widthOffset,
|
||||
(if (livingEntity.isCrouching) heightOffset + 0.2 else heightOffset),
|
||||
heightOffset - armorHeightOffset,
|
||||
0.0
|
||||
)
|
||||
|
||||
@@ -263,8 +266,14 @@ class PokemonOnShoulderRenderer<T : Player>(renderLayerParent: RenderLayerParent
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARMOR_HEIGHT_OFFSET = 1F / 16F
|
||||
private val playerCache = hashMapOf<UUID, ShoulderCache>()
|
||||
|
||||
private fun isArmorOffsetBlacklisted(itemStack: ItemStack): Boolean {
|
||||
val itemIdentifier = BuiltInRegistries.ITEM.getKey(itemStack.item)
|
||||
return itemIdentifier in Cobblemon.config.shoulderMountArmorOffsetBlacklist
|
||||
}
|
||||
|
||||
private fun extractUuid(shoulderNbt: CompoundTag): UUID {
|
||||
if (!shoulderNbt.contains(DataKeys.SHOULDER_UUID)) {
|
||||
return shoulderNbt.getCompound(DataKeys.POKEMON).getUUID(DataKeys.POKEMON_UUID)
|
||||
|
||||
+9
-1
@@ -48,6 +48,7 @@ import com.mojang.blaze3d.vertex.DefaultVertexFormat
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer
|
||||
import com.mojang.blaze3d.vertex.VertexFormat
|
||||
import com.cobblemon.mod.common.client.render.pokemon.ModelPitch
|
||||
import com.mojang.math.Axis
|
||||
import net.minecraft.Util
|
||||
import net.minecraft.client.model.geom.ModelPart
|
||||
@@ -776,7 +777,14 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
|
||||
matrixStack.mulPose(transformationMatrix)
|
||||
} else {
|
||||
yRot = Mth.lerp(state.getPartialTicks(), entity.yBodyRotO, entity.yBodyRot)
|
||||
yRot = 180f - Mth.wrapDegrees(yRot)
|
||||
val tilt = ModelPitch.tiltDegrees(entity, state.getPartialTicks())
|
||||
if (tilt != 0F) {
|
||||
// Same as the model tilt in PokemonRenderer
|
||||
matrixStack.mulPose(ModelPitch.aroundCentre(entity, ModelPitch.orientation(yRot, tilt)))
|
||||
yRot = 0F
|
||||
} else {
|
||||
yRot = 180f - Mth.wrapDegrees(yRot)
|
||||
}
|
||||
}
|
||||
} else if (entity is EmptyPokeBallEntity) {
|
||||
scale = 0.7F
|
||||
|
||||
+2
-2
@@ -19,7 +19,7 @@ import com.cobblemon.mod.common.Cobblemon.LOGGER
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.riding.util.RidingAnimationData
|
||||
import com.cobblemon.mod.common.api.riding.util.DynamicAnimationData
|
||||
import com.cobblemon.mod.common.api.scheduling.Schedulable
|
||||
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
|
||||
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
|
||||
@@ -141,7 +141,7 @@ abstract class PosableState : Schedulable {
|
||||
*/
|
||||
val renderMarkers = mutableSetOf<String>()
|
||||
|
||||
/** The MoLang functions that expose current [RidingAnimationData] data.
|
||||
/** The MoLang functions that expose current [DynamicAnimationData] data.
|
||||
* They are all divided by a reference value and coerece into a float range useful in animation
|
||||
*/
|
||||
val ridingFunctions = QueryStruct(hashMapOf())
|
||||
|
||||
+6
-36
@@ -10,23 +10,20 @@ package com.cobblemon.mod.common.client.render.models.blockbench.animation
|
||||
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.addRotation
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
|
||||
import com.cobblemon.mod.common.util.math.geometry.toDegrees
|
||||
import com.cobblemon.mod.common.util.math.geometry.toRadians
|
||||
import net.minecraft.util.Mth
|
||||
|
||||
/**
|
||||
* Animation used to tilt the targeted bone of the entity by a pitch that is proportionate
|
||||
* to the direction the entity is moving. This is used to make birds and fish tilt upwards
|
||||
* or downwards based on their motion.
|
||||
* or downwards based on their motion. The pitch comes from the eased heading locomotion travels on.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since August 9th, 2025
|
||||
*/
|
||||
class PitchTiltAnimation(
|
||||
val bone: Bone,
|
||||
// Not used anymore since the heading is already eased, kept so existing posers still load
|
||||
val maxChangePerTick: Float = 1F,
|
||||
val minPitch: Float = -45F,
|
||||
val maxPitch: Float = 45F,
|
||||
@@ -35,6 +32,8 @@ class PitchTiltAnimation(
|
||||
const val PREVIOUS_ANGLE = "previous_pitch_tilt_angle"
|
||||
const val CORRECTED_ANGLE = "corrected_pitch_tilt_angle"
|
||||
const val PITCHED_TILT = "has_pitched_tilt"
|
||||
// How much of the tilt has been applied this render, so poses blending together add up to one full tilt
|
||||
const val APPLIED_WEIGHT = "applied_pitch_tilt_weight"
|
||||
}
|
||||
|
||||
override fun setupAnim(
|
||||
@@ -48,36 +47,7 @@ class PitchTiltAnimation(
|
||||
headPitch: Float,
|
||||
intensity: Float
|
||||
) {
|
||||
val entity = context.entity ?: return
|
||||
if (entity.passengers.isNotEmpty()) {
|
||||
return
|
||||
}
|
||||
val delta = entity.deltaMovement
|
||||
val vertical = delta.y
|
||||
val horizontal = delta.horizontalDistance()
|
||||
// It should be the angle in degrees that becomes higher as the entity's motion becomes more upwards, and negative as the entity's motion becomes more downwards
|
||||
val angleOfMotion = if (horizontal == 0.0) {
|
||||
when {
|
||||
vertical > 0 -> maxPitch
|
||||
vertical < 0 -> minPitch
|
||||
else -> 0F
|
||||
}
|
||||
} else {
|
||||
Mth.atan2(vertical, horizontal).toDegrees()
|
||||
}.coerceIn(minPitch, maxPitch) * -1
|
||||
|
||||
val lastAngle = state.numbers[PREVIOUS_ANGLE] ?: 0F
|
||||
val change = (angleOfMotion - lastAngle).coerceIn(-maxChangePerTick, maxChangePerTick)
|
||||
if (PITCHED_TILT !in state.renderMarkers) {
|
||||
state.numbers.remove(CORRECTED_ANGLE)
|
||||
val correctedAngle = (lastAngle + change) * intensity
|
||||
bone.addRotation(0, correctedAngle.toRadians())
|
||||
state.numbers[PREVIOUS_ANGLE] = correctedAngle
|
||||
} else {
|
||||
// It was already pitched, which means the body bone already has a rotation, so only rotate it a pinch more.
|
||||
bone.addRotation(0, change.toRadians() * intensity)
|
||||
state.numbers[PREVIOUS_ANGLE] = lastAngle + change * intensity
|
||||
}
|
||||
state.renderMarkers += PITCHED_TILT
|
||||
// The whole model is tilted by ModelPitch now so this does nothing\
|
||||
//TODO: remove posers with this anim if we like ModelPitch
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -262,9 +262,9 @@ data class BedrockAnimation(
|
||||
fun applyEffects(entity: Entity?, state: PosableState, previousSeconds: Float, newSeconds: Float) {
|
||||
val effectCondition: (effectKeyframe: BedrockEffectKeyframe) -> Boolean =
|
||||
if (previousSeconds > newSeconds) {
|
||||
{ it.seconds >= previousSeconds || it.seconds <= newSeconds }
|
||||
{ it.seconds >= previousSeconds || it.seconds < newSeconds }
|
||||
} else {
|
||||
{ it.seconds in previousSeconds..newSeconds }
|
||||
{ it.seconds >= previousSeconds && it.seconds < newSeconds }
|
||||
}
|
||||
|
||||
effects.filter(effectCondition).forEach { it.run(entity, state) }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user