Compare commits

..
Author SHA1 Message Date
Gertimoshka 72999ff600 Fixes to Rendering Order 2026-08-15 14:37:59 +03:00
Gertimoshka ec18bb9154 Starter Screen Implementation and Pokedex Partial Fixes 2026-08-12 23:56:44 +03:00
Gertimoshka 1e1fba5f7f Innitial Merge Fixes 2026-08-12 10:20:33 +03:00
Gertimoshka 69497bc3cc The Painful Merge Complete: The Revival of Controlify Support 2026-08-12 09:49:01 +03:00
Gertimoshka 64f7556bdc Dont forget the licenses 2026-03-10 22:12:16 +02:00
Gertimoshka 9fea42bce0 Controlify Binds English Localization 2026-02-24 12:08:51 +02:00
Gertimoshka 7abe83c413 Controller Hotbar navigation buttons will now zoom with with pokedex and switch party when holding the sendout button 2026-02-23 15:33:53 +02:00
Gertimoshka 2d70f48176 Additional Changes + Fixes 2026-02-13 16:28:53 +02:00
JPAKx4 8f9c1c40d7 Merge tag '1.7.3' into controllify-compat-ui-navigation 2026-02-09 15:22:27 -06:00
Gertimoshka 89c9cd9fb4 Added Missing Button Guides 2026-02-09 19:54:21 +02:00
Gertimoshka 9335eb9650 And... Done, Gameplay Important GUIs complete 2026-02-08 11:31:06 +02:00
Gertimoshka 7a7be6f25a Even more GUIs done for Controlify Support 2026-02-07 23:28:48 +02:00
Gertimoshka 76ed55ebe8 Fixes to when Button Guides are Rendered 2026-01-29 09:52:26 +02:00
Gertimoshka b004b13eda Finally PC Block Controller Support (+Pasture) 2026-01-29 09:30:28 +02:00
Gertimoshka dbe8a1a9f5 A Working Version of Interaction Wheel 2026-01-26 23:03:30 +02:00
JPAKx4 25e8e3c990 Try to begin fixing the interactionWheel 2026-01-26 13:46:41 -06:00
Gertimoshka b84012f98f Rapid Fire 3 Easier Guis for Controlify Support 2026-01-26 21:35:53 +02:00
JPAKx4 a0979b28d3 Merge tag '1.7.2' into controllify-compat-ui-navigation 2026-01-25 09:58:19 -06:00
Gertimoshka f459c64073 Button Guides for our Screens 2026-01-24 15:02:56 +02:00
Gertimoshka 8a7abb9a11 Starter Selection Controlified 2026-01-22 23:56:47 +02:00
Gertimoshka 7be8beb71e Pokedex GUI Controlify Support + Rendering Layering fixes 2026-01-18 20:47:56 +02:00
Gertimoshka 637274c7ee Battle GUI now has working Controller Support 2026-01-12 19:44:51 +02:00
Gertimoshka 63786357b9 Finally working Summary GUI for contrilify with Visuals 2025-12-31 15:40:55 +02:00
Gertimoshka fa3e85b054 Summary GUI Controllify Support, Functional Implementation 2025-12-25 16:54:51 +02:00
Gertimoshka 61c1c6b21f Additional Fixes for Rebase 2025-12-23 09:46:12 +02:00
Gertimoshka a347844986 Summary GUI - navigation for moves tab and party tab 2025-12-23 09:45:14 +02:00
Gertimoshka d78a92f069 Rebasing for New Changes 2025-12-23 09:44:57 +02:00
JPAKx4 ba3207d176 Work on a framework to make UI stuff not suck 2025-12-20 12:23:48 -06:00
JPAKx4 762f9ce5f1 Merge branch 'main' into controllify-compat 2025-12-17 22:24:43 -06:00
JPAKx4 6c12198344 Improve InteractWheelProcessor.kt and use controlify sensitivity 2025-12-17 22:24:29 -06:00
JPAKx4 99a0ec7f0d Merge remote-tracking branch 'origin/controllify-compat' into controllify-compat
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/integration/controlify/CobblemonControlifyEntrypoint.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/integration/controlify/gui/InteractWheelProcessor.kt
2025-12-08 22:38:47 -06:00
JPAKx4 aacebc9726 Add changelog, handle horse sprinting, add all UIs to virtual mouse. 2025-12-08 22:37:43 -06:00
JPAKx4 378c85b7dd More work on keybinds for Controlify compat 2025-12-08 22:20:26 -06:00
JPAKx4 c4614ff418 Add radial menu support for controlify 2025-12-08 22:20:26 -06:00
JPAK 8cbbd2de27 Move compat to common for neoforge and fabric support. 2025-12-08 22:20:26 -06:00
JPAKx4 067c59a636 Continue work on controlify compat 2025-12-08 22:20:26 -06:00
JPAKx4 98a02438da Begin ControlifyCompat 2025-12-08 22:20:25 -06:00
JPAKx4 26b25c8c39 More work on keybinds for Controlify compat 2025-12-07 17:01:04 -06:00
JPAKx4 bfd78c1a85 Merge remote-tracking branch 'origin/controllify-compat' into controllify-compat 2025-12-06 17:10:33 -06:00
JPAKx4 239485ccb5 Add radial menu support for controlify 2025-12-06 17:10:20 -06:00
JPAKx4 ff959daaef Add radial menu support for controlify 2025-12-06 16:57:09 -06:00
JPAK 32bb340260 Move compat to common for neoforge and fabric support. 2025-12-05 21:36:59 -06:00
JPAKx4 b7bfc6d0a1 Continue work on controlify compat 2025-12-03 07:01:01 -06:00
JPAKx4 d86879095f Begin ControlifyCompat 2025-11-29 17:02:35 -06:00
3097 changed files with 20019 additions and 134223 deletions

No files matched your search

+260 -199
View File
@@ -1,39 +1,134 @@
# Changelog
## [1.8.0 - Make Your Move Update (September 6th, 2026)](#1-8-0)
#### I think the red eyes mean they're friendly!
## [1.7.2 (January 25th, 2026)](#1-7-2)
### Additions
- Added Alpha Pokémon
- Alpha Pokémon are bigger, stronger, and more aggressive than regular Pokémon.
- They are also scarier.
- They always know a TM move and will adapt to be stronger than your party.
- Alphas will always lead herds and spawn in herds.
- Added alpha Pokémon attraction bait effect to Hopo Berries.
- Added intrinsic scaling to all Pokémon (anywhere from 5% smaller to 5% bigger than normal). Some cases, such as shoulder mounting, will not scale so that the shoulder animations don't look silly.
- Added scaling to "baby" Pokémon (10% smaller at level 1 → normally sized at level 10).
- Added TMs.
- Added TM Machine. TMs are made from elemental type gems, and their recipes are unlocked by having Pokémon that know the move.
- Added config options for making TMs infinite-use and instant unlocking of all TM recipes.
- Added Type Gem blocks as well as crystal clusters and deepslate core blocks that are used for growing elemental type gems.
- Added a new Starter Selection menu that looks way better.
- Added a "Move Dex" to the Pokédex to help identify what moves a Pokémon can learn and what Pokémon can get a move.
- Added the Habitat Block, a spawner block for servers and adventure maps that controls spawning in an area.
- Added 49 new habitat structures. In these habitats you will find rarer Pokémon and sometimes different Pokémon will appear on different days.
- Added a toast to notify players of how they can scroll through their party. Did you know you can hold R and scroll instead of using arrow keys? It's true!!!
- Added a way to obtain Porygon by inserting an Upgrade or Dubious Disc into a data monitor.
- Added Disk Rack for storing disks and being an audio sequencer for Noteblocks. What does that have to do with Pokémon? How about you mind your own business!
- Added missing crops to Botany Pots integration.
- Added conditional seats for riding. Seats can be gated by Molang such as "is_alpha".
- Added 10 new ruin structures.
- Added a new shipwreck cove structure called the Magma Shipwreck Cove.
- Added 7 new advancements.
- Pokémon cries now play when clicking Pokémon in the summary screen.
- Held item visibility can now be controlled by the `held_item_visible` command argument.
- Added config option to disable Pokémon spawning on a per-world basis.
- Added config options to award experience to fainted Pokémon for opponents they helped defeat (`awardExperienceToFaintedPokemon`) and to still award experience for defeated enemies even after battle loss/forfeit (`awardExperienceOnBattleLoss`).
- Added `/givetm` command.
- Added Saccharine wood furniture for Adorn.
- Added the Partner Mark, which party Pokémon have a very small chance to earn as the player walks, provided they have enough friendship.
- Added trades for Saccharine Saplings, Hearty Grains, Chipped Pot, and Masterpiece Teacup to the Wandering Trader.
- Added brewing recipe for Throat Spray.
- Added official Controlify Support
- Added new effects
- Mental Restoration: Reduces "Time Since Last Rest" statistic, which controls Insomnia, by half a minute per second per effect level
- Negative Effect Cleanse: Removes all negative effects
- Effect Cleanse: Removes all effects
- Added more seasonings for sinister tea
- Mental Herb - Mental Restoration I for 10 seconds
- White Herb - Negative Effect Cleanse
- Milk - Effect Cleanse
- Moomoo Milk - Effect Cleanse
- Added decorative block models for existing items: Potion, Super Potion, Hyper Potion, Max Potion, Full Restore, Antidote, Awakening, Burn Heal, Ice Heal, Paralyze Heal, Full Heal, Ether, Elixir, Max Ether, Max Elixir, X Accuracy, X Attack, X Defence, X Special Attack, X Special Defence, X Speed, Dire Hit, Guard Spec, Weakness Policy, Blunder Policy, Cleanse Tag, and Spell Tag.
- Added decorative sign blocks called Plaques.
- Added ride style labels to ride stats in Pokédex, as well as ride style icons in the summary GUI.
- Added more seasonings for sinister tea:
- Mental Herb - Mental Restoration I for 10 seconds, which staves off Insomnia.
- White Herb - Removes negative effects.
- Milk, Moomoo Milk - Removes effects.
- Added dispenser support for:
- Applying a Honey Bottle to Saccharine Logs.
- Washing honey off of Saccharine Slathered Logs.
- Applying and removing honey on Saccharine Leaves.
- Added riding statistics: `Distance by Pokémon on Land`, `Distance by Pokémon in Air` and `Distance by Pokémon in Liquid`.
### New rideable Pokémon
- Honchkrow
- Mantine
### Pokémon Added
#### Gen 5
- Throh
- Sawk
#### Gen 6
- Litleo
- Pyroar
- Spritzee
- Aromatisse
- Swirlix
- Slurpuff
### Model updates for the following Pokémon
- Falinks
- Cottonee
- Whimsicott
- Ferroseed
- Ferrothorn
- Chingling
- Chimecho
### Animation updates for the following Pokémon
- Mantine (ride animations)
- Flygon (ride jump animation)
- Honchkrow (ride animations)
- Croagunk
- Toxicroak
### Changes
- Added a new config option, `Enable In-Flight Dismounting` (default: off), which lets you dismount while riding a Pokémon in the air. This is the textbook definition of caveat emptor.
- Ride sounds have been separated into stereo for passengers and mono for other players. Riding will now sound more spacious.
- Battle AI now uses a smarter threshold for switching, reducing unnecessary switches.
- AI will always use the most damaging move when at low HP and when it is not switching, improving endgame decision-making.
- Items given from interacting with Saccharine Leaves are placed in the active hotbar slot if possible.
- Added `label` as an alternate key to the `tag` property.
## [1.7.3 (January 31st, 2026)](#1-7-3)
## [1.8.0 (MONTH Xth, 2026)](#1-8-0)
### Additions
- Added config options to award experience to fainted Pokémon for opponents they helped defeat (`awardExperienceToFaintedPokemon`) and to still award experience for defeated enemies even after battle loss/forfeit (`awardExperienceOnBattleLoss`)
- Party Overlay Exp Gained Animation will now display the amount of EXP the Pokémon gained
- Added a toast to notify players of how they can scroll through their party. Did you know you can hold R and scroll instead of using arrow keys??!
- Added a new layer property that makes a layer scroll in a direction. "scrolling": {"speedU": 0.1, "speedV": 0.1}
- Added missing crops to Botany Pots integration
- Added a way to obtain Porygon by inserting the Upgrade or Dubious Disc into a data monitor
- Added a new `cobblemon:structure`, which is a feature without any restrictions allowing the Builders to place certain structures in much more specific manner.
- Added Configs for Infinite Use TMs and Unlocking all TMs
- Added Type Gem blocks
- Added Type Gem Clusters
- Added TMs
- Added TM Machine
- Added Deepslate Core Block
- Added /givetm Command
- Added advancement triggers for learning TMs: `cobblemon:has_learn_specific_tm` (per-TM), `cobblemon:has_learn_all_tm` (all TMs learned), and `cobblemon:tms_learned` (count-based), backed by a new `totalTMLearnedCount` on player advancement data.
- Added `cobblemon:catch_alpha_pokemon` advancement trigger and `totalAlphaCaptureCount` stat for captured Alpha Pokémon.
- Added `cobblemon:catch_alpha_pokemon` advancement trigger and `totalAlphaCaptureCount` tracking for capturing Alpha Pokémon.
- Added Move Dex to Pokédex
- Added the Habitat Block, a spawner block for servers and adventure maps that controls spawning in an area.
- Added Disk Rack for storing disks and being an audio sequencer for Noteblocks
- Added intrinsic scaling to all Pokémon (+-5%). Some rendering cases, such as shoulder mounting, revert to 1.0 scaling to preserve animation accuracy.
- Added scaling to "baby" Pokémon (smaller (-20%) at level 1 → normally sized at level 10)
- Added conditional seats for riding. Seats can be gated by molang such as "is_alpha".
- Added Alpha Pokémon
- Added alpha scaling (alphas are larger than their normal counterparts)
- Alphas will always lead herds and will spawn in herds
- Alphas spawn with two random TM moves from their learnset
- Alpha Pokémon will always defend themselves from players that attack them
- Added alpha level matching and stat boost (based on level)
- Added pitch-down and reverb to alpha cries
- Added alpha Pokémon bait effect
- Added alpha eye rendering including eye overlay, bloom, eye trail, and particles.
- Added cobweb block slowness immunity for spider Pokémon
- Added 49 new habitat structures. 34 as typical Minecraft structures and 15 as structures that get placed as biome features to allow for more natural placement in generation. Structures placed as biome features will not be able to be located with the `/locate structure` command.
- Vanilla village house template pools have been adjusted to account for the new abandoned village structure habitat. Weights have changed within the JSON file for every piece to ensure too many habitats don't generate in a single village.
- Added 10 new ruin structures.
- rooted_arch_ruins & crumbling_arch_ruins have been transferred over to biome features, allowing for better placement in generation.
- All ruin structures that did not generate with a gimmighoul/gilded chest before will now generate with one.
- All ruin structures gilded chest loot tables have been updated to include a guaranteed TM not learned naturally by any Pokémon through their level-up moveset. Each ruin gets its own specific TM, so you can now go hunting ruins to complete your move dex!
- All fossil structures have been transferred over to biome features, allowing for better placement in generation.
- The prehistoric_underwater_fissure & prehistoric_submerged_impact fossil structures now use the biome tag `#cobblemon:is_temperate_ocean`.
- Gimmighoul towers now generate at ground level rather than sinking one block into the ground.
- A new shipwreck cove structure called the "magma_shipwreck_cove" has been added.
- Shipwreck coves now generate slightly more frequently.
- Added equipment loot tables for shipwreck cove enemies.
- Maps from abandoned fishing boats leading to shipwreck coves now will skip explored chunks, allowing the player to find more shipwreck coves easier.
- The lush shipwreck cove's phantom mob spawner has been replaced with breezes.
- The lush shipwreck cove structure itself has been slightly cleaned up to be less noisy with the blocks it is built out of.
- All shipwreck coves' spawners have been adjusted to give compatible mobs enchanted iron or diamond armour and weapons while in its ominous state. The armour given to the mobs will also use the automaton trim. We upped the difficulty because it was too easy now that your Pokémon can fight for you.
- One TM of the elemental hyper beams has been added as guaranteed loot to the main treasure chest in every shipwreck cove. (Magma cove gets blast burn, lush cove gets frenzy plant, and submerged cove gets hydro cannon)
- Added 7 new advancements
- Pressing the inventory key now closes the Pokémon interact wheel GUI
- Pokémon cries now play when clicking Pokémon in the summary screen.
- Pressing the Summary key while either the starter selection or summary screen are open now closes those screens
- Ice type Pokémon are now immune to Packed Snow
- Held item visibility can now be controlled by the held_item_visible command argument.
### New rideable Pokémon
- Crobat
@@ -147,6 +242,7 @@
- ... and Substitute
#### Model changes to the regional bias resource pack:
- Added Mime Jr. (Galar Bias)
- Added Bergmite (Hisui Bias)
- Changed models for every existing regional bias
@@ -239,27 +335,14 @@
- Tail Whip
### Changes
- Removed the penalty for block destruction speed when on a hover mount, making them better for digging.
- Golden Apples can now be crafted using the Applin Apples.
- Prismarine Crystals and Amethyst Shards can now be used to craft Pokedexes.
- Updated Pokédex Screens item tag to support tagged variants of Redstone and Glowstone Dust.
- Sharp Beak can now be crafted using bones and Flying Gems.
- Metal Alloy can now be crafted using iron and Thunder Stones.
- Ability Capsules can now be brewed from Medicinal Brew and Dragon Breath.
- Using an Ability Capsule will now return an Empty Bottle.
- Decreased the default pastured wander radius to 32 blocks.
- Added alternate versions of the basic PC Wallpapers based on the original design of Cobblemon's PC, before public release.
- Rooted Arch Ruins, Crumbling Arch Ruins, and all fossil structures have been improved so that they generate more naturally in terrain.
- Gimmighoul towers now generate at ground-level rather than sinking one block into the ground.
- All ruin structures that did not generate with a gimmighoul/gilded chest before will now generate with one.
- All ruin structures gilded chest loot tables have been updated to include a guaranteed TM not learned naturally by any Pokémon through their level-up moveset. Each ruin gets its own specific TM, so you can now go hunting ruins to complete your move dex!
- The Prehistoric Underwater Fissure and Prehistoric Submerged Impact fossil structures now use the biome tag `#cobblemon:is_temperate_ocean`.
- The lush shipwreck cove's phantom mob spawner has been replaced with breezes.
- Shipwreck coves now generate slightly more frequently.
- Added equipment loot tables for shipwreck cove enemies.
- Maps from abandoned fishing boats leading to shipwreck coves now will skip explored chunks, allowing the player to find more shipwreck coves more easily.
- All shipwreck coves' spawners have been adjusted to give compatible mobs enchanted iron or diamond armour and weapons while in its ominous state. The armour given to the mobs will also use the automaton trim. We upped the difficulty because it was too easy now that your Pokémon can fight for you.
- One TM of the elemental hyper beams has been added as guaranteed loot to the main treasure chest in every shipwreck cove. (Magma Cove gets Blast Burn, Lush Cove gets Frenzy Plant, and Submerged Cove gets Hydro Cannon.)
- Stat increase and decrease particles have been changed, with the addition of alpha boost particles.
- Dragonite's mailbag cosmetic can now also be obtained with a chest in the cosmetic slot
- Adjusted Croagunk's walkspeed
- Adjusted Aerodactyl's walkspeed
- Sharp Beak can now be crafted
- Metal Alloy can now be crafted
- Ability Capsules can now be brewed
- Using an Ability Capsule will now return an Empty Bottle
- The following items can now be obtained by brushing the following Pokémon:
- Pink Wool - Wigglytuff
- White Wool - Swablu, Altaria
@@ -268,147 +351,129 @@
- The following items can now be obtained by evolving the following Pokémon into any of their evolutions:
- Turtle Scute - Squirtle, Wartortle, Turtwig, Grotle, Tirtouga, Chewtle
- Shed Shell - Metapod, Kakuna, Ekans, Paras, Venonat, Krabby, Scyther, Kabuto, Dratini, Dragonair, Ledyba, Spinarak, Yanma, Pineco, Dunsparce, GLigar, Pupitar, Treecko, Grovyle, Silcoon, Cascoon, Surskit, Nincada, Trapinch, Vibrava, Corphish, Anorith, Bagon, Shelgon, Kricketot, Burmy, Combee, Skorupi, Swadloon, Whirlipede, Dwebble, Scraggy, Karrablast, Joltik, Larvesta, Spewpa, Clauncher, Helioptile, Charjabug, Crabrawler, Cutiefly, Dewpider, Salandit, Wimpod, Poipole, Sobble, Drizzile, Dottler, Silicobra, Toxel, Sizzlipede, Snom, Tarountula, Nymble, Rellor
- Saccharine Leaves now drop Tart, Sweet, or Syrupy Apples, depending on their honey coating stage, instead of vanilla Apples.
- Fixed the height of the Tumblestone Bud items when held in hand, placed on head, viewed in a GUI, and placed in an Item Frame.
- Refactored all advancements, removing several deemed unnecessary. Due to this cleanup, all previously obtained advancements will be reset besides the root (for picking a starter Pokémon). This sucks, and we're sorry. We had to do this eventually so better now than later.
- Forces between colliding hitboxes now factors in the weight of Pokémon.
- Improved berry tree performance on singleplayer and servers.
- Updated stat increase and decrease particle effects with the addition of alpha boost particles.
- Improved the performance of pasture blocks on singleplayer and servers.
- Changed the bait effects of the following items: (Vanilla) Sweet & Glow Berries, Golden & Enchanted Golden Apples (Cobblemon) Custap, Eggant, Hopo & Micle Berries, Sweet Hearts.
- Wild Pokémon now reliably have at least one offensive move if they can have offensive moves at that level. No more free lunches with Pokémon that can't even hurt you.
- Data Monitor can accept Disks (TMs, Music Disks, Upgrade, Dubious Disk, etc.) to change what's being displayed on the screen.
- The party overlay's experience-gained animation now displays the amount of EXP the Pokémon gained.
- Pressing the inventory key now closes the Pokémon interact wheel GUI.
- Updated the sound for harvesting Apricorns.
- Lowered the volume of harvesting berries.
- Saccharine Leaves now drop Tart, Sweet, or Syrupy Apples, depending on their honey coating stage, instead of vanilla Apples
- Fixed the height of the Tumblestone Bud items when held in hand, placed on head, viewed in a GUI, and placed in an Item Frame
- Refactored all advancements, removing several deemed unnecessary. Due to this refactor, all previously obtained advancements will be reset besides the root (for picking a starter Pokémon). We are sorry for the inconvience, but it makes the tab cleaner and simplifies future advancements.
- Restructured Botany Pots integration folder structure
- Decrease the default pastured mon wander radius to 32.
- Factor in the weight of Pokémon when calculating pushing forces between hitboxes.
- Remade the Starter Selection Screen with new assets
- Refactored berry trees to be less impactful on TPS (more performant)
- Changed the bait effects of the following items: (Vanilla) Sweet & Glow Berries, Golden & Enchanted Golden Apples (Cobblemon) Custap, Eggant, Hopo & Micle Berries, Sweet Hearts
- Wild Pokémon now reliably have at least one offensive move if they can have offensive moves at that level.
- Data Monitor can accept Disks (TMs, Music Disks, Upgrade, Dubious Disk, etc.) to change what's being displayed on the screen
- Rider positioning is now client-driven (Remove poseOffsets and offset per seat/pose) in favour of sending real-time passenger position.
- Cleaned up seat data. Removed all now unused fields from seats in all species files. Also made locator seat names explicit in the species files (however, this doesn't break the legacy format)
- Marks can now provide aspects.
- Added new shiny and Alpha chance configs
- Apricorns now make a new sound when harvested
- Lowered the volume of harvesting berries
- Added alternate versions of the basic PC Wallpapers based on the original design of Cobblemon's PC, before public release
- Improved compatibility with various mods
- Farmer's Delight's cutting board can now be used to strip Apricorn and Saccharine's logs, or salvage their planks from various blocks. It can also separate their boats from chests.
- Farmer's Delight's cutting board can be used to get two white dye from Pep-Up Flowers and two magenta dye from Medicinal Leeks.
- Farmer's Delight's cutting board can separate Tumblestone Blocks back into Tumblestones.
- Farmer's Delight's cutting board can salvage Type Gems from their Gem Blocks.
- Farmer's Delight's Onions, Tomatoes, and Cabbages can be used to cook Open-Faced Sandwiches. Nature's Spirit's Olives also can be used.
- Farmer's Delight's cutting board can be used on Hearty Grains to obtain rice and straw, similarly to Farmer's Delight's Rice Panicles.
- Farmer's Delight's Apple Pies and Apple Ciders can now be made using Applin Apples, alongside the Vanilla counterpart.
- Amendments allows for cooking Leek and Potato Stew in a Cauldron.
- Any mods that utilise Moonlight's Soft Fluid system can store Leek and Potato Stew, Medicinal Brew, Antidote, Awakening, Burn Heal, Ice Heal, Paralyze Heal, Full Heal, Potion, Super Potion, Hyper Potion, Max Potion, Full Restore, Ability Capsules, Moomoo Milk, and Berry Juice in liquid form (such as Suplementaries' Jar or Goblet, or Cauldrons while using Amendments).
- Supplementaries' Hourglass can now use Bright Powder, Quick Powder, Metal Powder, Heal Powder, Silver Powder, and Remedy.
- Farmer's Delight's cutting board can now be used to strip Apricorn and Saccharine's logs, or salvage their planks from various blocks. It can also separate their boats from chests
- Farmer's Delight's cutting board can be used to get two white dye from Pep-Up Flowers and two magenta dye from Medicinal Leeks
- Farmer's Delight's cutting board can separate Tumblestone Blocks back into Tumblestones
- Farmer's Delight's cutting board can salvage Type Gems from their Gem Blocks
- Farmer's Delight's Onions, Tomatoes, and Cabbages can be used to cook Open-Faced Sandwiches. Nature's Spirit's Olives also can be used
- Farmer's Delight's cutting board can be used on Hearty Grains to obtain rice and straw, similarly to Farmer's Delight's Rice Panicles
- Farmer's Delight's Apple Pies and Apple Ciders can now be made using Applin Apples, alongside the Vanilla counterpart
- Amendments allows for cooking Leek and Potato Stew in a Cauldron
- Any mods that utilise Moonlight's Soft Fluid system can store Leek and Potato Stew, Medicinal Brew, Antidote, Awakening, Burn Heal, Ice Heal, Paralyze Heal, Full Heal, Potion, Super Potion, Hyper Potion, Max Potion, Full Restore, Ability Capsules, Moomoo Milk, and Berry Juice in liquid form (such as Suplementaries' Jar or Goblet, or Cauldrons while using Amendments)
- Supplementaries' Hourglass can now use Bright Powder, Quick Powder, Metal Powder, Heal Powder, Silver Powder, and Remedy
- Berry Trees are now compatible with the Planter properly
- Saccharine Way Signs (added by Supplementaries automagically) now use a premade texture to prevent odd visual issues.
- Supplementaries' Jar can now store various Cobblemon cookies, candies, and similar items.
- Some of Supplementaries' items have been added to the `#cobblemon:held/container_held_items` item tag, including sacks, safes, and lunch baskets.
- Experience Nuggets can be used in place of Sculk for making the EXP Candies if using Create.
- Rage Candy Bar has a Create Mixing recipe.
- All Cobblemon ores can now be crushed in Create's Crushing Wheels. All Gem and Tumblestone Clusters can be crushed for automatic, low return, gems.
- Hearty Grains can be milled like Farmer's Delight Rice Panicles if both Farmer's Delight and Create are installed.
- Big Roots and Revival Herbs can be milled to their respective powders.
- Pep-Up Flowers, Medicinal Leeks, and all 70 berries can be milled in Create's Millstone for additional dye drops.
- Auspicious Armour and Upgrades can be Haunted using Create's crafting systems.
- Applin Apples can be used to make Honeyed Apples in Create.
- Sounds for interacting with various Cobblemon blocks now play from the centre of the block, rather than the corner. You probably didn't notice.
- Dragonite's mailbag cosmetic can now also be obtained with a chest in the cosmetic slot.
- Adjusted the walk speed for Croagunk and Aerodactyl.
- Saccharine Way Signs (added by Supplementaries automagically) now use a premade texture to prevent odd visual issues
- Supplementaries' Jar can now store various Cobblemon cookies, candies, and similar items
- Some of Supplementaries' items have been added to the `#cobblemon:held/container_held_items` item tag, including sacks, safes, and lunch baskets
- Experience Nuggets can be used in place of Sculk for making the EXP Candies if using Create
- Rage Candy Bar has a Create Mixing recipe
- All Cobblemon ores can now be crushed in Create's Crushing Wheels
- All Gem and Tumblestone Clusters can be crushed for automatic, low return, gems
- Hearty Grains can be milled like Farmer's Delight Rice Panicles if both Farmer's Delight and Create are installed
- Big Roots and Revival Herbs can be milled to their respective powders
- Pep-Up Flowers, Medicinal Leeks, and all 70 berries can be milled in Create's Millstone for additional dye drops
- Auspicious Armour and Upgrades can be Haunted using Create's crafting systems
- Applin Apples can be used to make Honeyed Apples in Create
- Golden Apples can now be crafted using the Applin Apples
- Prismarine Crystals and Amethyst Shards can now be used to craft Pokedexes
- Updated Pokédex Screens item tag to support tagged variants of Redstone and Glowstone Dust
- Sounds for interacting with various Cobblemon blocks now play from the centre of the block, rather than the corner
- The `/pc` command no longer plays PC activation sounds
- Pressing the Summary key while either the starter selection or summary screen are open now closes those screens.
- Ice type Pokémon are now immune to Packed Snow.
- Added cobweb block slowness immunity for spider Pokémon
### Fixes
- Fixed rare Pokémon being far too likely to spawn multiple times in a single spawn cycle. The dev told me that the exact bug is too complicated to explain.
- Fixed Blitzle's base scale being `1` rather than the intended `0.8`
- Fixed Magnezone's emissive layer for the shiny having the wrong name, thus displaying wrongly
- Fixed Litleo not blinking
- Fixed Omanyte and Omastar incorrectly using water animations on land.
- Fixed Pokédex layout for genderless variations.
- Fixed odd stacking behaviour with Poké Baits.
- Fixed Pokédex layout for genderless variations
- Fixed odd stacking behaviour with the poke_bait item.
- Fixed a compatibility issue with World Game Rules which caused some log spam in specific cases.
- Fixed improper validation of Pokémon when performing a trade.
- Fixed an issue where particles like beams can disappear when you don't see the beginning of the beam, or you look away.
- Fixed Bedrock particle materials sometimes being incorrectly invisible from some sides.
- Fixed an occasional crash on retrieving revived fossils.
- Fixed brushing not working on Neoforge. Again.
- Fixed braking not working for submarine-style mounts (whoops).
- Fixed an issue where particle like beam can disappear when you don't see the beginning of the beam, or you look away
- Fixed Bedrock particle materials (alpha/opaque/blend/add) sharing one render batch, which caused incorrect culling on mixed batches; each material now batches and draws with its own blend and cull state, and the alpha material renders as a true cutout
- Fixed an occasional crash on retrieving revived fossils
- Fixed brushing not working on Neoforge
- Fixed braking not working for submarines (whoops).
- Fixed strafing not having the same friction as forward movement (funny whoops).
- Fixed submarine-style mounts' surface-water animations not playing when supposed to.
- Fixed submarine-style mounts' camera freaking out when looking up or down on the surface.
- Fixed pastured Pokémon despawning and running away.
- Fixed Apricorn trees crashing when other mods bonemeal them.
- Fixed the issue that caused hover-style mounts without a ground style to be rendered useless upon touching the ground on a server.
- Fixed horizontal and vertical collisions with hover-style mounts so they no longer accumulate or retain momentum.
- Fixed NeoForge brewing stand unexpectedly crashing users when shift clicking items within its menu. Well, I suppose all crashes are unexpected. You know what I mean.
- Fixed NPCs switching Pokémon very often during battles.
- Fixed Campfire Pot causing crashes under certain circumstances.
- Fixed Type Gems not being consumed upon use in battles.
- Fixed several Ancient Pokéball's not using the correct capture multiplier.
- Fixed a server warning that could occur when recalling Pokémon.
- Fixed sliding particles incorrectly handling block collision.
- Fixed Full Heal not curing status conditions in battle. Literally it's one job.
- Fixed submarine's surface-water animations not playing when supposed to.
- Fixed submarine camera freaking out when looking up or down on the surface.
- Fixed pastured mons despawning and running away.
- Fixed Apricorn trees crashing when other mods bonemeal it
- Fixed the issue that caused Hover Behaviour rides without a ground style to be rendered useless upon touching the ground on a server.
- Removed the "inAir" penalty for block destruction speed when on a hover mount.
- Fixed horizontal and vertical collisions with hover mounts so they no longer accumulate or retain momentum
- Fixed NeoForge brewing stand unexpectedly crashing users when shift clicking items within its menu
- Fixed NPCs switching Pokémon very often during battles
- Fixed Campfire pot causing crashes under certain circumstances
- Fixed Type Gems not being consumed upon use
- Fixed Ancient Pokéball's to their respective modifiers
- Fixed a Server warning when recalling Pokémon
- Fixed sliding particles incorrectly handling block collision
- Fixed Full Heal not curing status conditions in battle
- Fixed a rare issue with battles locking up when you are forced to switch, with the switch menu appearing instantly.
- Fixed the combined effects of Pledge moves having broken Showdown message handling and localisation.
- Fixed a very niche issue where structures would spawn in an incorrect location, creating very funky world gen.
- Fixed the combined effects of Pledge moves having broken Showdown message handling and localisation
- Fixed rare Pokémon being far too likely to spawn multiple times in a single spawn cycle. The dev told me that the exact bug is too complicated to explain.
- Resolved the niche issue where Structures would spawn in an incorrect location, creating very funky world gen.
- Fixed Galarian Ponyta missing its cry.
- Fixed various log warnings caused by redundant files and sound events.
- Traded Pokémon will now retain the original friendship level when traded back to its OT.
- Tumblestone Clusters now display correctly on maps.
- Fixed removing mulch from berry trees with a shovel not working. That was meant to be a thing.
- Fixed collecting honey from Saccharine Leaf blocks deleting Honey Bottles when the inventory was full.
- Fixed Stripped Saccharine Wood recipe being... not there.
- Fixed some performance issues with a kind of sound event causing lag by attempting to create sounds every tick when out of range of hearing.
- Fixed stat-reducing Aprijuice not being possible to apply on a maxed out Pokémon. Also fixed the possibility of going into negatives.
- Fixed exploit where you could take out the bait or drop your Pokérod with bait to avoid consuming bait, now the bobber will just disappear if you do so.
- Fixed Apricorn and Saccharine boats incorrectly using chest boat models.
- Fixed wild and trainer battle AI refusing to select moves with the `adjacentAlly` target type, even when it's their only option.
- Fixed gilded chests being more accessible than intended.
- Fixed fished up Pokémon not being added as "seen" to the Pokédex.
- Fixed surprised Gimmighoul encounters not being added as "seen" to the Pokédex.
- Traded Pokémon will now retain the original friendship level when traded back to its OT
- Clusters now display correctly on maps
- Fixed removing mulch from berry trees with a shovel not working.
- Fixed collecting honey from Saccharine Leaf blocks deleting Honey Bottles when the inventory was full
- Fixed Stripped Saccharine Wood recipe being missing
- Fixed CancelableSoundInstance causing lag by attempting to create sounds every tick when out of range of hearing
- Fixed Stat Reducing Aprijuice not being possible to apply on a max out Pokémon, and the possibility of going into negatives
- Fixed Exploit where you could take out the bait or drop your Pokérod with bait to avoid consuming bait, now the bobber will just disappear if you do so
- Fixed Apricorn and Saccharine boats incorrectly using chest boat models
- Fixed wild and trainer battle AI refusing to select moves with the `adjacentAlly` target type, even when it's their only option
- Fixed gilded chests being more accessible than intended
- Fixed fished up Pokémon not being added as "seen" to the Pokedex
- Fixed surprised Gimmighoul encounters not being added as "seen" to the Pokedex
- Fixed a crash involving berry trees that have been world edited.
- Fixed visual desync in multiplayer for players mounting Pokémon outside other players' loaded chunks.
- Fixed visual desync in multiplayer for players mounting Pokémon outside other players' loaded chunks
- Fixed Pokémon entities leveled by rare candy not getting stronger as Minecraft entities unless recalled.
- Fixed a freeze during game load that occurred when multiple mods tried to load Cobblemon API classes at the same time.
- Lowered the volume of Blitzle and Zebstrika's cry (shut up!).
- Regional Foods will now prioritise being eaten instead of used for healing Pokémon - shift clicking will use it as healing.
- Resolved z-fighting (texture flickering) on Pokédex Models.
- Restored Ninjask's ability to do barrel rolls.
- Fixed random tick crashes from berry trees
- Lowered the volume of Blitzle and Zebstrika's cry
- Regional Foods will now prioritise being eaten instead of used for healing Pokémon, shift clicking will use it as healing
- Resolved Z-Fighting on Pokédex Models
- Restored Ninjask's ability to do barrel rolls
- Fixed Raichu (male) and Alolan Raichu (both genders) not displaying the correct Partner Cap when Pewter Crunchies are in the cosmetic slot.
- Fixed a crash caused by adding enchanted items to the Resurrection Machine.
- Fixed a crash caused by adding enchanted items to the Resurrection Machine
- Fixed a crash that could occur when starting a PvN battle with only fainted Pokémon.
- Fixed the Remember Riding Camera config not being respected when dismounting and resolved the incompatibility with the Auto Third Person mod.
- Fixed Full Heal block not dropping Full Heals. Lmao.
- Fixed status cure items not returning an empty bottle when used.
- Fixed Panpour consuming its held item when evolving with a Water Stone.
- Fixed Smoked Tail Curry returning a Wooden Bowl when consumed despite it not being included in the recipe. Free bowls!
- Fixed `/giveallpokemon` not respecting the min and max arguments.
- Fixed the choose starter toast not disappearing when capturing a Pokémon instead of opening the starter selection screen.
- Fixed an issue where certain Sinister Tea seasonings were only applying as instant effects and would not apply over time.
- Long config option labels now scroll instead of overflowing in the configuration menu.
- Fixed some z-fighting and clipping on Dewott and Hisui-Bias Dewott.
- Eggant Berries can now be composted properly on NeoForge.
- Composting chances are now the same on NeoForge as on Fabric.
- Fixed problem where furnaces which had smelted Revival Herbs into Heal Powder in older versions would glitch out on trying to get the experience out of the block.
- Fixed the Battle AI not recognizing Surging Strikes as a multistrike move.
- Fixed Tentacool not using its swim animation when on water surface.
- Fixed Eevee using its left shoulder pose when in battle.
- Fixed Magikarp and Gyarados Jump Pattern resourcepacks replacing shiny textures of addons targeting Magikarp or Gyarados.
- Fixed Combee's pollinated texture overwriting the shiny and female textures.
- Fixed a colour mistake with Dracovish's shiny.
- Fixed Togedemaru's back spikes z-fighting.
- Fixed Sliggoo (+ Hisuian) evolving in the Nether and End where it can't actually rain.
- Corrected the scales, hitbox sizes, and eye heights for Ariados, Cetoddle, Durant, Galvantula, Klink, Lanturn, Lumineon, Mantine, Mantyke, Mudbray, Numel, Staravia, and Torkoal.
- Fixed Torkoal's walk speed not matching its animation. He will no longer move faster than Bunnelby and Buneary. This tortoise would've won.
- Fixed Lanturn T-posing due to missing ground walk and sleep animations.
- Fixed Oshawott and Dewott using their sleep animation in unintended scenarios.
- Fixed Dudunsparce and Maushold always having the same form instead of having the 1 in 100 chance of being different.
- Fixed bird mounts rapidly switching between their flying and gliding animations when flying at low speed.
- Fixed some battles being startable with fainted Pokémon in the initial sendout.
- Fixed random gender selection for forms with strict gender divides (such as Meowstic and Oinkologne) never choosing female (that's sexist).
- Fixed the "Press R to battle" prompt not disappearing after you finish your first battle, at least until you relogged.
- Fixed the composter block being listed as "composer" in the cosmetic items species feature
- Fixed Castform's forms being tagged as introduced in generation 4 instead of 3.
- Fixed Blitzle's base scale being `1` rather than the intended `0.8`.
- Fixed Magnezone's emissive layer for the shiny having the wrong name, thus displaying wrongly.
- Fixed Litleo not blinking.
- Fixed the Remember Riding Camera config not being respected when dismounting and resolved the incompatibility with the Auto Third Person mod
- Fixed Full Heal block not dropping Full Heals
- Fixed status cure items not returning an empty bottle when used
- Fixed Panpour consuming its held item when evolving with a Water Stone
- Fixed Smoked Tail Curry returning a Wooden Bowl when consumed despite it not being included in the recipe
- Fixed `/giveallpokemon` not respecting the min and max parameters
- Fixed the choose starter toast not disappearing when capturing a Pokémon instead of opening the starter selection screen
- Fixed an issue where certain Sinister Tea seasonings were only applying as instant effects and would not apply over time
- Long config option labels now scroll instead of overflowing
### Developer
- Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters.
- Added `FishingSpawnerFactory` which allows influence registration on fishing encounters.
- Added `BattleFleeAttemptEvent` fired from `PokemonBattle.checkFleeAttempt`, allowing developers to intercept and control flee attempts.
- Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters
- Added `FishingSpawnerFactory` which allows influence registration on fishing encounters
- Added `BattleFleeAttemptEvent` fired from `PokemonBattle.checkFleeAttempt`, allowing developers to intercept and control flee attempts
- Tweaked the spawning selector interface to take a bucket function rather than a bucket directly.
- Added a new `cobblemon:height_range` processor type to allow developers to restrict structure-spawning ranges.
- Separated all the functions of MoLangFunctions.kt into their own files.
@@ -416,36 +481,33 @@
- Removed "SpawnBucket" class in favour of just storing maps of bucket names to weights.
- Fixed nicknames not being applied to Pokémon spawned from `spawn_pool_world` files.
- Added support for `profilePokedexScale`, `profilePokedexTranslation`, `profileSummaryScale`, and `profileSummaryTranslation` in poser files for finer control over profile displays.
- Fixed riding camera offsets defined on Pokémon forms not being applied.
- Marks can now provide aspects.
- Fixed riding camera offsets defined on Pokémon forms not being applied
### Molang & Datapacks
- Added `moveset_builders` datapack folder for different styles of movesets.
- Added `party_pools` datapack folder for defining groups of Pokémon for complex NPC party definitions.
- Added `party_compositions` datapack folder for arranging parties on NPCs that rely on party pools.
- Added `composed_pool` NPC party provider type which combines a party pool with a party composition to create more complex NPC parties.
- Added basic chatter NPC Behaviour.
- Added `q.item.is_enchanted()`.
- Added `q.item.has_enchantment(minecraft:sharpness, 3)`.
- Added `has_chosen_starter` and `get_starter_uuid` as available Molang functions.
- Added `has_tm_move_unlocked`, `unlock_tm_move`, and `lock_tm_move` as available Molang functions for Players.
- Added `marks`, `has_mark`, and `remove_marks` as available Molang functions for Pokémon.
- Added basic chatter NPC Behaviour
- Added `q.item.is_enchanted()`
- Added `q.item.has_enchantment(minecraft:sharpness, 3)`
- Added `has_chosen_starter` & `get_starter_uuid` as available Molang functions
- Added `has_tm_move_unlocked`, `unlock_tm_move`, & `lock_tm_move` as available Molang functions for Players
- Added `marks`, `has_mark`, & `remove_marks` as available Molang functions for Pokémon
- Added Data Component support for species drops, eg `"components": {"minecraft:custom_model_data": 123}`
- Added the new `sortOrder` field to mark entries, (defaults to 0 if not specified) which controls the order in which marks are rendered on the HUD, with higher numbers being rendered on top of lower numbers.
- Added optional `order` field to starter categories. It will allow explicitly sorting categories in the starter selection screen.
- SpeciesAdditions append vs override now matches the wiki documentation.
- Added `weighted_choice` as a new feature type. It requires setting weights for each choice and allows developers to simplify the process of assigning random aspect choice based on marble chance.
- Added `q.world.spawn_loot_table_items(loot_table_id, x, y, z)` which spawns the items from the loot table at the position. Use type: `minecraft:chest` for the loot table.
- Added the new `sortOrder` field to mark entries, (defaults to 0 if not specified) which controls the order in which marks are rendered on the HUD, with higher numbers being rendered on top of lower numbers
- Added optional `order` field to starter categories. It will allow explicitly sorting categories in the starter selection screen
- SpeciesAdditions append vs override now matches wiki documentation
- Added `weighted_choice` as a new feature type. It requires setting weights for each choice and allows developers to simplify the process of assigning random aspect choice based on marble chance
- Added `q.world.spawn_loot_table_items(loot_table_id, x, y, z)` which spawns the items from the loot table at the position. (Use type: "minecraft:chest" for the loot table.)
- Added `q.pokemon.entity` to access the entity of Pokémon.
- Flamethrower and Seismic Toss had their action effect JSON cleaned up.
- Ride sounds can now be set as exclusive to passengers.
- Ride sounds no longer play when submerged unless specified with a new setting.
- Flamethrower and Seismic Toss had their action effect JSON cleaned up
- Ride sounds can now be set as exclusive to passengers
- Ride sounds no longer play when submerged unless specified with a new setting
- NPC Trainers can now call a `command` animation when issuing a command in battle.
- Added a q.failed function to move action effects.
- Herd spawned Pokémon have their levels controlled differently. The `levelRange` property now acts as a pre-offset clamp instead of a filter, with a new `herdLevelRange` property to do the filter behaviour.
- `q.sound` now accepts three parameters (sound event, volume, and pitch) instead of just the sound event and pitch.
- Added a new layer property that makes a layer scroll in a direction. "scrolling": {"speedU": 0.1, "speedV": 0.1}
- Cleaned up seat data. Removed all now unused fields from seats in all species files. Also made locator seat names explicit in the species files (however, this doesn't break the legacy format)
- Herd spawned Pokémon have their levels controlled differently. The `levelRange` property now acts as a clamp instead of a filter, with a new `herdLevelRange` property to do the filter behaviour.
- `q.sound` now accepts three parameters (sound event, volume and pitch) instead of just the sound event and pitch.
### Particles
@@ -512,8 +574,8 @@
#### Miscellaneous Particles
- Added Wild Might particles for Alpha Pokémon.
- Updated stat increase and decrease particles.
+ Added Wild Might particles for Alpha Pokémon
+ Updated stat increase and decrease particles
### Localization
- Updated translations for:
@@ -525,5 +587,4 @@
- Brazilian Portuguese
- Simplified Chinese
- Traditional Chinese
- Korean
- Russian
- Korean
+9
View File
@@ -32,6 +32,10 @@ repositories {
maven(url = "https://api.modrinth.com/maven")
maven(url = "https://maven.neoforged.net/releases")
maven(url = "https://maven.gegy.dev")
exclusiveContent {
forRepository { maven(url = "https://maven.isxander.dev/releases") }
filter { includeGroup("dev.isxander") }
}
mavenLocal()
}
@@ -62,6 +66,11 @@ dependencies {
// Unit Testing
testImplementation(libs.bundles.unitTesting)
// Controlify Compat, same as flywheel so we pick one and only use the api
modCompileOnlyApi("dev.isxander:controlify:2.4.3+1.21.1-fabric") {
isTransitive = false
}
}
tasks.withType<Test> {
@@ -1,24 +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.duck;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
import java.util.Optional;
/**
* Duck that facilitates getting a level key for a given level.
*
* @author dualspiral
* @since August 30th, 2026
*/
public interface MinecraftServerDuck {
Optional<ResourceKey<Level>> cobblemon$getLevelKey(Level level);
}
@@ -1,21 +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.duck;
import net.minecraft.resources.ResourceLocation;
/**
* Duck that facilitates getting a level key that we store on the level.
*
* @author dualspiral
* @since August 30th, 2026
*/
public interface ServerLevelDuck {
ResourceLocation cobblemon$getLevelResourceLocation();
}
@@ -0,0 +1,54 @@
/*
* 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;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
@Mixin(ClientPacketListener.class)
public class ClientPacketListenerMixin {
@Shadow private ClientLevel level;
@Inject(method = "handleSetEntityPassengersPacket", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/world/entity/Entity;hasIndirectPassenger(Lnet/minecraft/world/entity/Entity;)Z", shift = At.Shift.BEFORE), cancellable = true)
private void cobblemon$handlePokemonPassengers(ClientboundSetPassengersPacket packet, CallbackInfo ci) {
Entity entity = this.level.getEntity(packet.getVehicle());
if (entity instanceof PokemonEntity pokemon) {
var passengers = new ArrayList<Entity>();
for (int passenger : packet.getPassengers()) {
passengers.add(this.level.getEntity(passenger));
}
pokemon.getOccupiedSeats().values().stream()
.filter(x -> !passengers.contains(x))
.filter(Objects::nonNull)
.forEach(Entity::stopRiding);
// Mount passengers not yet in the map
var currentPassengers = new ArrayList<>(pokemon.getOccupiedSeats().values());
passengers.stream()
.filter(x -> !currentPassengers.contains(x))
.filter(Objects::nonNull)
.forEach(x -> x.startRiding(pokemon, true));
ci.cancel();
}
}
}
@@ -0,0 +1,111 @@
/*
* 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;
import com.cobblemon.mod.common.mixin.accessor.SinglePoolElementAccessor;
import com.cobblemon.mod.common.world.structureprocessors.HeightRangeStructureProcessor;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.pools.*;
import net.minecraft.world.level.levelgen.structure.pools.alias.PoolAliasLookup;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Optional;
@Mixin(JigsawPlacement.class)
public class JigsawPlacementMixin {
@Inject(
method = "addPieces(Lnet/minecraft/world/level/levelgen/structure/Structure$GenerationContext;Lnet/minecraft/core/Holder;Ljava/util/Optional;ILnet/minecraft/core/BlockPos;ZLjava/util/Optional;ILnet/minecraft/world/level/levelgen/structure/pools/alias/PoolAliasLookup;Lnet/minecraft/world/level/levelgen/structure/pools/DimensionPadding;Lnet/minecraft/world/level/levelgen/structure/templatesystem/LiquidSettings;)Ljava/util/Optional;",
at = @At("RETURN"),
cancellable = true
)
private static void onAddPieces(
Structure.GenerationContext context,
Holder<StructureTemplatePool> startPool,
Optional<ResourceLocation> startJigsawName,
int maxDepth,
BlockPos pos,
boolean useExpansionHack,
Optional<Heightmap.Types> projectStartToHeightmap,
int maxDistanceFromCenter,
PoolAliasLookup aliasLookup,
DimensionPadding dimensionPadding,
LiquidSettings liquidSettings,
CallbackInfoReturnable<Optional<Structure.GenerationStub>> cir
) {
Optional<Structure.GenerationStub> result = cir.getReturnValue();
if (result.isEmpty()) {
return;
}
BlockPos spawnPos = result.get().position();
boolean canPlace = cobblemon$canAnyStartElementPlace(context, startPool, spawnPos);
if (canPlace) {
return;
}
cir.setReturnValue(Optional.empty());
}
@Unique
private static boolean cobblemon$canAnyStartElementPlace(
Structure.GenerationContext generationContext,
Holder<StructureTemplatePool> startPool,
BlockPos startPos
) {
for (StructurePoolElement element : startPool.value().templates) {
Optional<HeightRangeStructureProcessor> heightRangeProcessor = cobblemon$getHeightRangeProcessor(element);
if (heightRangeProcessor.isEmpty()) {
return true;
}
HeightRangeStructureProcessor processor = heightRangeProcessor.get();
boolean valid = processor.isValidPlacement(
generationContext.chunkGenerator(),
generationContext.heightAccessor(),
generationContext.randomState(),
startPos
);
if (valid) {
return true;
}
}
return false;
}
@Unique
private static Optional<HeightRangeStructureProcessor> cobblemon$getHeightRangeProcessor(StructurePoolElement element) {
if (!(element instanceof SinglePoolElement)) {
return Optional.empty();
}
StructureProcessorList processorList = ((SinglePoolElementAccessor) element).getProcessors().value();
for (StructureProcessor processor : processorList.list()) {
if (processor instanceof HeightRangeStructureProcessor heightRangeStructureProcessor) {
return Optional.of(heightRangeStructureProcessor);
}
}
return Optional.empty();
}
}
@@ -1,40 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.duck.MinecraftServerDuck;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.Map;
import java.util.Optional;
@Mixin(MinecraftServer.class)
public class MinecraftServerMixin implements MinecraftServerDuck {
@Shadow
@Final
private Map<ResourceKey<Level>, ServerLevel> levels;
@Override
public Optional<ResourceKey<Level>> cobblemon$getLevelKey(final Level level) {
return this.levels.entrySet()
.stream()
// Reference equality is intended here, the level we're handed is the same one as in the
// map so we don't need to check value semantics.
.filter(entry -> entry.getValue() == level)
.map(Map.Entry::getKey)
.findFirst();
}
}
@@ -40,7 +40,6 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
import java.util.LinkedHashMap;
import java.util.Set;
import java.util.function.Consumer;
@@ -115,13 +114,15 @@ public abstract class ServerEntityMixin {
if (currentPassengers.equals(cobblemon$lastSeatPassengers)) return;
cobblemon$lastSeatPassengers = List.copyOf(currentPassengers);
var assignments = new LinkedHashMap<String, Integer>();
pokemon.getOccupiedSeats().forEach((seat, passenger) -> {
if (seat.getLocator() == null) return;
if (passenger == null) return;
assignments.put(seat.getLocator(), passenger.getId());
cobblemon$broadcast(new ClientboundSeatAssignmentPacket(
passenger.getId(),
pokemon.getId(),
seat.getLocator()
));
});
cobblemon$broadcast(new ClientboundSeatAssignmentPacket(pokemon.getId(), assignments));
}
private void cobblemon$broadcast(NetworkPacket<?> packet) {
@@ -8,69 +8,22 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.duck.MinecraftServerDuck;
import com.cobblemon.mod.common.duck.ServerLevelDuck;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.storage.WritableLevelData;
import net.minecraft.world.phys.AABB;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
@Mixin(ServerLevel.class)
public abstract class ServerLevelMixin extends Level implements ServerLevelDuck {
// Ignored by Mixin, fulfils Java contract
protected ServerLevelMixin(
WritableLevelData levelData,
ResourceKey<Level> dimension,
RegistryAccess registryAccess,
Holder<DimensionType> dimensionTypeRegistration,
Supplier<ProfilerFiller> profiler,
boolean isClientSide,
boolean isDebug,
long biomeZoomSeed,
int maxChainedNeighborUpdates
) {
super(levelData, dimension, registryAccess, dimensionTypeRegistration, profiler, isClientSide, isDebug, biomeZoomSeed, maxChainedNeighborUpdates);
}
@Shadow
public abstract MinecraftServer shadow$getServer();
/**
* Cached to speed up level world key checks every tick.
*/
private ResourceLocation cobblemon$levelResourceLocation;
@Override
public ResourceLocation cobblemon$getLevelResourceLocation() {
if (this.cobblemon$levelResourceLocation == null) {
this.cobblemon$levelResourceLocation = ((MinecraftServerDuck) this.shadow$getServer())
.cobblemon$getLevelKey(this)
.map(ResourceKey::location)
.orElse(null);
}
return this.cobblemon$levelResourceLocation;
}
public abstract class ServerLevelMixin {
@Inject(method = "findLightningRod", at = @At(value = "RETURN"), cancellable = true)
private void cobblemon$findLightningRod(BlockPos blockPos, CallbackInfoReturnable<Optional<BlockPos>> ci) {
@@ -15,18 +15,15 @@ import com.cobblemon.mod.common.api.riding.RidingStyle;
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner;
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawnerFactory;
import com.cobblemon.mod.common.api.stats.CobblemonStats;
import com.cobblemon.mod.common.duck.ServerLevelDuck;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@@ -36,13 +33,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ServerPlayer.class)
public abstract class ServerPlayerMixin implements PlayerSpawnerAccessor {
@Shadow
public abstract ServerLevel serverLevel();
@Shadow
@Final
public MinecraftServer server;
@Unique
public PlayerSpawner cobblemon$spawner;
@@ -62,9 +55,12 @@ public abstract class ServerPlayerMixin implements PlayerSpawnerAccessor {
@Inject(method = "tick", at = @At("TAIL"))
private void cobblemon$tickSpawner(CallbackInfo ci) {
if (cobblemon$canSpawn()) {
getPlayerSpawner().tick();
if (!Cobblemon.config.getEnableSpawning()) {
return;
} else if (!serverLevel().getGameRules().getBoolean(CobblemonGameRules.DO_POKEMON_SPAWNING)) {
return;
}
getPlayerSpawner().tick();
}
@Inject(method = "rideTick", at = @At("HEAD"))
@@ -125,21 +121,4 @@ public abstract class ServerPlayerMixin implements PlayerSpawnerAccessor {
return entity;
}
/**
* Gets whether we should spawn Cobblemon entities here.
*
* @return true if we can spawn cobblemon in the current level,
* false otherwise
*/
@SuppressWarnings("resource")
@Unique
private boolean cobblemon$canSpawn() {
return Cobblemon.config.getEnableSpawning() &&
this.serverLevel().getGameRules().getBoolean(CobblemonGameRules.DO_POKEMON_SPAWNING) &&
// intentionally last as this is a more expensive check, even with all the caching
!Cobblemon.config.getWorldSpawningBlocklist().contains(
((ServerLevelDuck) this.serverLevel()).cobblemon$getLevelResourceLocation()
);
}
}
@@ -1,103 +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.mixin;
import com.cobblemon.mod.common.mixin.accessor.SinglePoolElementAccessor;
import com.cobblemon.mod.common.world.structureprocessors.HeightRangeStructureProcessor;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Optional;
import java.util.function.Predicate;
@Mixin(Structure.class)
public abstract class StructureMixin {
/**
* Discards a structure start when any of its pieces carries a {@link HeightRangeStructureProcessor}
* whose range the piece does not fit in.
* Crab Note: We have to make this has to happen here rather than in the processor itself. Terrain adaptation and {@code /locate}
* both read the start, so by the time processors run the terrain has already been generated and the
* structure is already on the map which previously were left behdind when we were trying to make some structures not generate via processor. Now
* Returning {@link StructureStart#INVALID_START} keeps the start from ever existing, which makes sure that weird generation isn't left behind.
*/
@Inject(
method = "generate",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/levelgen/structure/StructureStart;isValid()Z"),
cancellable = true
)
private void cobblemon$rejectStartsOutsideHeightRange(
RegistryAccess registryAccess, ChunkGenerator chunkGenerator, BiomeSource biomeSource,
RandomState randomState, StructureTemplateManager structureTemplateManager, long seed,
ChunkPos chunkPos, int references, LevelHeightAccessor heightAccessor,
Predicate<Holder<Biome>> validBiome,
CallbackInfoReturnable<StructureStart> cir,
@Local StructureStart structureStart
) {
for (StructurePiece piece : structureStart.getPieces()) {
if (!(piece instanceof PoolElementStructurePiece poolPiece)) {
continue;
}
Optional<HeightRangeStructureProcessor> heightRangeProcessor = cobblemon$getHeightRangeProcessor(poolPiece.getElement());
if (heightRangeProcessor.isEmpty()) {
continue;
}
boolean valid = heightRangeProcessor.get().isValidPlacement(
chunkGenerator,
heightAccessor,
randomState,
piece.getBoundingBox()
);
if (!valid) {
cir.setReturnValue(StructureStart.INVALID_START);
return;
}
}
}
@Unique
private static Optional<HeightRangeStructureProcessor> cobblemon$getHeightRangeProcessor(StructurePoolElement element) {
if (!(element instanceof SinglePoolElement)) {
return Optional.empty();
}
StructureProcessorList processorList = ((SinglePoolElementAccessor) element).getProcessors().value();
for (StructureProcessor processor : processorList.list()) {
if (processor instanceof HeightRangeStructureProcessor heightRangeStructureProcessor) {
return Optional.of(heightRangeStructureProcessor);
}
}
return Optional.empty();
}
}
@@ -21,12 +21,14 @@ import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.Blaze3D;
import kotlin.Pair;
import kotlin.jvm.functions.Function4;
import net.minecraft.client.Minecraft;
import net.minecraft.client.MouseHandler;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.util.Mth;
import net.minecraft.util.SmoothDouble;
import net.minecraft.world.phys.Vec3;
import org.joml.Vector2d;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -149,6 +151,12 @@ public abstract class MouseHandlerMixin {
pokeVehicle.clampPassengerRotation(player);
}
for(Function4<LocalPlayer, Double, Double, Double, Vector2d> rideFun : CobblemonClient.INSTANCE.getRidingTurnInputHandler().values()) {
Vector2d vec = rideFun.invoke(player, cursorDeltaX, cursorDeltaY, d);
cursorDeltaX = vec.x;
cursorDeltaY = vec.y;
}
// If the ride is not orientation controllable then return and perform the normal turnPlayer logic
if (!(vehicle instanceof OrientationControllable controllableVehicle) || !controllableVehicle.getOrientationController().isActive()) {
xMouseSmoother.reset();
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.integration.controlify;
import com.cobblemon.mod.common.client.CobblemonClient;
import com.cobblemon.mod.common.client.integration.controlify.CobblemonControlifyEntrypoint;
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding;
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext;
import dev.isxander.controlify.controller.ControllerEntity;
import dev.isxander.controlify.ingame.InGameInputHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.player.Inventory;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(InGameInputHandler.class)
public class InGameInputHandlerMixin {
@Shadow @Final private ControllerEntity controller;
@Redirect(
method = "handleKeybinds()V",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/player/Inventory;swapPaint(D)V"
)
)
public void cobblemon$onHotbarSwitch(Inventory instance, double direction) {
PokedexUsageContext usageContext = CobblemonClient.INSTANCE.getPokedexUsageContext();
if (usageContext.getScanningGuiOpen()) {
usageContext.adjustZoom(direction);
return;
}
if (CobblemonControlifyEntrypoint.Companion.getSEND_OUT_POKEMON().on(controller).digitalNow()) {
CobblemonClient.INSTANCE.getStorage().shiftSelected(direction < 0);
PartySendBinding.INSTANCE.actioned();
return;
}
instance.swapPaint(direction);
}
}
@@ -20,7 +20,7 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
// There appears to be a data driven way to do this now, however I am not sure how it works since there is no wiki or anything so yolo
//This is a yucky hack but better than what we were doing before
@Mixin(BlockVariantSets.class)
public abstract class AdornRegisterInvoker {
@Final
@@ -133,7 +133,7 @@ open class PokemonBattle(
set(value) { field = value.coerceAtMost(Int.MAX_VALUE) } // go outside
/** The current duration of the battle in seconds. */
val time: Int get() = ticks / 20
val time: Int get() = ticks % 20
var dispatchResult = GO
val dispatches = ConcurrentLinkedDeque<BattleDispatch>()
@@ -65,7 +65,7 @@ class HabitatSpawn {
/** The maximum light level that the Pokémon can spawn in. */
var maxLight: Int? = null
fun writeToNBT(nbt: CompoundTag) {
open fun writeToNBT(nbt: CompoundTag) {
nbt.putString(DataKeys.HABITAT_POOL_SPAWN_SPECIES, species.resourceIdentifier.toString())
nbt.putString(DataKeys.HABITAT_POOL_SPAWN_POSITION_TYPE, spawnablePositionType)
nbt.putFloat(DataKeys.HABITAT_POOL_SPAWN_WEIGHT, weight)
@@ -78,7 +78,7 @@ class HabitatSpawn {
nbt.putString(DataKeys.HABITAT_POOL_SPAWN_BUCKET, bucket)
}
fun readFromNBT(nbt: CompoundTag) {
open fun readFromNBT(nbt: CompoundTag) {
species = PokemonSpecies.getByIdentifier(nbt.getString(DataKeys.HABITAT_POOL_SPAWN_SPECIES).asIdentifierDefaultingNamespace())!!
spawnablePositionType = nbt.getString(DataKeys.HABITAT_POOL_SPAWN_POSITION_TYPE)
weight = nbt.getFloat(DataKeys.HABITAT_POOL_SPAWN_WEIGHT)
@@ -91,7 +91,7 @@ class HabitatSpawn {
bucket = if (nbt.contains(DataKeys.HABITAT_POOL_SPAWN_BUCKET)) nbt.getString(DataKeys.HABITAT_POOL_SPAWN_BUCKET) else "common"
}
fun encode(buffer: RegistryFriendlyByteBuf) {
open fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(species.resourceIdentifier)
buffer.writeUtf(spawnablePositionType)
buffer.writeFloat(weight)
@@ -104,7 +104,7 @@ class HabitatSpawn {
buffer.writeUtf(bucket)
}
fun decode(buffer: RegistryFriendlyByteBuf) {
open fun decode(buffer: RegistryFriendlyByteBuf) {
species = PokemonSpecies.getByIdentifier(buffer.readResourceLocation())!!
spawnablePositionType = buffer.readUtf()
weight = buffer.readFloat()
@@ -118,7 +118,7 @@ class HabitatSpawn {
}
/** Creates the [PokemonSpawnDetail] that will be used in the spawner. This is done per habitat block. */
fun createSpawnDetail(habitatBlockEntity: HabitatBlockEntity): PokemonSpawnDetail? {
open fun createSpawnDetail(habitatBlockEntity: HabitatBlockEntity): PokemonSpawnDetail? {
val spawnDetail = PokemonSpawnDetail()
// If the habitat block specifies a level range, it restricts how and if this spawn exists
@@ -109,10 +109,8 @@ open class Learnset {
evolutionMoves
}
// Syncs level up, TM, and egg moves.
// We only sync level up moves atm
fun shouldSynchronize(other: Learnset) = other.levelUpMoves != this.levelUpMoves
|| other.tmMoves != this.tmMoves
|| other.eggMoves != this.eggMoves
fun decode(buffer: RegistryFriendlyByteBuf) {
this.levelUpMoves.clear()
@@ -124,8 +122,6 @@ open class Learnset {
}
levelUpMoves[level] = moves
}
decodeMoveList(buffer, this.tmMoves)
decodeMoveList(buffer, this.eggMoves)
}
fun encode(buffer: RegistryFriendlyByteBuf) {
@@ -137,21 +133,5 @@ open class Learnset {
buffer.writeInt(move.num)
}
}
encodeMoveList(buffer, this.tmMoves)
encodeMoveList(buffer, this.eggMoves)
}
private fun decodeMoveList(buffer: RegistryFriendlyByteBuf, list: MutableList<MoveTemplate>) {
list.clear()
repeat(times = buffer.readSizedInt(IntSize.U_SHORT)) {
Moves.getByNumericalId(buffer.readInt())?.let(list::add)
}
}
private fun encodeMoveList(buffer: RegistryFriendlyByteBuf, list: List<MoveTemplate>) {
buffer.writeSizedInt(IntSize.U_SHORT, list.size)
for (move in list) {
buffer.writeInt(move.num)
}
}
}
@@ -40,10 +40,6 @@ import kotlin.math.*
class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
companion object {
val KEY = cobblemonResource("air/bird")
private const val FLY_POSE_SPEED = 0.1
private const val GLIDE_ENTRY_SPEED = 0.19
private const val GLIDE_EXIT_SPEED = 0.13
}
override val key: ResourceLocation = KEY
@@ -53,7 +49,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
val poseProvider = PoseProvider<BirdSettings, BirdState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > FLY_POSE_SPEED })
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.2 })
val globalBird: BirdSettings
get() = CobblemonRideSettings.bird
@@ -196,23 +192,21 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
// speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0) {
activeInput = true
if (newVelocity.length() < glideTopSpeed) {
//make sure it can't exceed top speed
val forwardInput = when {
state.stamina.get() == 0.0f -> 0.0
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z < (-topSpeed / 3.0) -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
if (driver.zza != 0.0f && state.stamina.get() > 0.0 && newVelocity.length() < glideTopSpeed) {
//make sure it can't exceed top speed
val forwardInput = when {
state.stamina.get() == 0.0f -> 0.0
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z < (-topSpeed / 3.0) -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
activeInput = true
}
val controller = (vehicle as? OrientationControllable)?.orientationController
@@ -270,9 +264,11 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
//Check if the ride should be gliding
if (driver.isLocalPlayer) {
val powered = activeInput && state.stamina.get() > 0.0
val requiredSpeed = if (state.gliding.get()) GLIDE_EXIT_SPEED else GLIDE_ENTRY_SPEED
state.gliding.set(!powered && newVelocity.z >= requiredSpeed)
if ((activeInput && state.stamina.get() > 0.0) || state.rideVelocity.get().z < 0.2) {
state.gliding.set(false)
} else {
state.gliding.set(true)
}
}
//air resistance
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
@@ -144,11 +145,12 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
) {
val sprintTimerTickLength = 7 // Emulate vanillas tick timer length
var hasDoubleTapped = false
val forward = Minecraft.getInstance().player?.input?.up == true
// If you are not sprinting and you just pressed down and theres no
if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && !state.sprinting.get() && state.sprintTickTimer.get() == 0) {
if (!state.forwardHeldLastTick.get() && forward && !state.sprinting.get() && state.sprintTickTimer.get() == 0) {
state.sprintTickTimer.set(sprintTimerTickLength)
} else if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && state.sprintTickTimer.get() != 0) { // If has double tapped
} else if (!state.forwardHeldLastTick.get() && forward && state.sprintTickTimer.get() != 0) { // If has double tapped
hasDoubleTapped = true
}else if(!state.sprinting.get() && state.sprintTickTimer.get() > 0) {
state.sprintTickTimer.set(state.sprintTickTimer.get() - 1)
@@ -156,9 +158,9 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
state.sprintTickTimer.set(0)
}
val tryingToSprint = (Minecraft.getInstance().options.keySprint.isDown() && Minecraft.getInstance().options.keyUp.isDown()) || hasDoubleTapped
val tryingToSprint = hasDoubleTapped || CobblemonClient.ridingSprintHandler.any { it.value.invoke() }
if (state.stamina.get() <= 0.0f || !Minecraft.getInstance().options.keyUp.isDown()) {
if (state.stamina.get() <= 0.0f || !forward) {
// If stamina runs out or the player is not holding forward then stop sprinting
state.sprinting.set(false)
if (state.stamina.get() <= 0.0f) {
@@ -172,8 +174,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
state.sprinting.set(true)
}
// Take not of whether forward was held last tick
state.forwardHeldLastTick.set(Minecraft.getInstance().options.keyUp.isDown())
// Take note of whether forward was held last tick
state.forwardHeldLastTick.set(forward)
}
fun tickStamina(
@@ -193,7 +195,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
val newStam = if (state.sprinting.get()) {
max(0.0f,stam - stamDrainRate)
} else {
min(1.0f,stam + stamDrainRate * 8) // Need to change this to probably be in seconds eventually? idk something that isn't this magic number.
min(1.0f,stam + stamDrainRate * 4)
}
state.stamina.set(newStam.coerceIn(0F, 1F))
@@ -147,7 +147,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
val stamDrainRate = (1.0f / boostTime).toFloat()
val newStam = if (state.boosting.get()) max(0.0f,stam - stamDrainRate)
else (stam + stamDrainRate * 2).coerceIn(0F, 1F)
else (stam + stamDrainRate).coerceIn(0F, 1F)
state.stamina.set(newStam)
}
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.MoonPhaseRange
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.block.entity.PokeSnackBlockEntity
import com.cobblemon.mod.common.util.Merger
import com.cobblemon.mod.common.util.math.orMax
import com.cobblemon.mod.common.util.math.orMin
@@ -60,7 +59,6 @@ abstract class SpawningCondition<T : SpawnablePosition> {
var structures: MutableList<Either<ResourceLocation, TagKey<Structure>>>? = null
var isSlimeChunk: Boolean? = null
var markers: MutableList<String>? = null
var isPokeSnack: Boolean? = null
@Transient
var appendages = mutableListOf<AppendageCondition>()
@@ -77,9 +75,7 @@ abstract class SpawningCondition<T : SpawnablePosition> {
}
protected open fun fits(spawnablePosition: T): Boolean {
if (isPokeSnack != null && (spawnablePosition.spawner is PokeSnackBlockEntity.Spawner != isPokeSnack!!)) {
return false
} else if (spawnablePosition.position.x < minX.orMin() || spawnablePosition.position.x > maxX.orMax()) {
if (spawnablePosition.position.x < minX.orMin() || spawnablePosition.position.x > maxX.orMax()) {
return false
} else if (spawnablePosition.position.y < minY.orMin() || spawnablePosition.position.y > maxY.orMax()) {
return false
@@ -32,7 +32,6 @@ import com.cobblemon.mod.common.api.spawning.selection.SpawnSelectionData
class EmptySpawnDetail : SpawnDetail() {
companion object {
val TYPE = "empty"
val EMPTY = EmptySpawnDetail()
}
override val type: String = TYPE
@@ -11,19 +11,14 @@ package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.selection.SpawnSelectionData
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.weightedSelection
import com.google.gson.annotations.SerializedName
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth.clamp
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import kotlin.math.ceil
/**
* A spawn detail that spawns a herd of Pokémon. This relies on the same [PokemonSpawnAction]s as
@@ -41,6 +36,7 @@ import kotlin.math.ceil
class PokemonHerdSpawnDetail : SpawnDetail() {
companion object {
val TYPE = "pokemon-herd"
const val HERD_LEADER = "leader"
}
override val type: String = TYPE
@@ -54,14 +50,14 @@ class PokemonHerdSpawnDetail : SpawnDetail() {
get() = "${id}__LEVEL"
class Herdable {
var pokemon = PokemonProperties()
var pokemon: PokemonProperties = PokemonProperties()
val levelRange: IntRange? = null
var levelRangeOffset: IntRange? = null
val herdLevelRange: IntRange? = null
var dropTable: DropTable? = null
var isLeader = false
var isFollower = true
var weight = 1F
var isLeader: Boolean = false
var isFollower: Boolean = true
var weight: Float = 1F
var maxTimes = 10
var heldItem: ResourceLocation? = null
}
@@ -73,23 +69,22 @@ class PokemonHerdSpawnDetail : SpawnDetail() {
selectionData: SpawnSelectionData
) {
selectionData.removeSpawnDetails { it != this }
selectionData.guaranteedBucket = bucket
selectionData.removeSpawnablePositions { _, pos -> pos.distanceTo(spawnablePosition) < minDistanceBetweenSpawns }
val herdSpawnCount = selectionData.spawnActions.count { it.detail == this } + 1
val lacksPossibleLeader = lacksPossibleLeader(selectionData)
if (herdSpawnCount >= maxHerdSize || getValidHerdMembersForRole(selectionData, useLeaderRole = lacksPossibleLeader).isEmpty()) {
if (herdSpawnCount >= maxHerdSize || getValidHerdMembers(selectionData).isEmpty()) {
// We can't spawn any more in the herd so remove this spawn from the selection pool.
selectionData.removeSpawnDetails { it == this }
}
}
fun lacksPossibleLeader(selectionData: SpawnSelectionData): Boolean {
val level = getOrSetHerdLevel(selectionData)
val herdable = getValidHerdMembers(selectionData)
val herdCounts = getHerdMemberCounts(selectionData)
val leaderIsPossible = herdable.any { it.isLeader }
val leaderIsPossible = herdable.any { it.herdLevelRange?.contains(level) != false && it.isLeader }
val leaderIsSelected = herdCounts.any { it.key?.isLeader == true && it.value > 0 }
return leaderIsPossible && !leaderIsSelected
}
@@ -98,42 +93,25 @@ class PokemonHerdSpawnDetail : SpawnDetail() {
return (selectionData.context[herdLevelKey] as? Int) ?: run {
val level = levelRange.random()
selectionData.context[herdLevelKey] = level
level
return level
}
}
private fun getValidHerdMembersForRole(selectionData: SpawnSelectionData, useLeaderRole: Boolean): List<Herdable> {
return getValidHerdMembers(selectionData).filter { (useLeaderRole && it.isLeader) || (!useLeaderRole && it.isFollower) }
}
override fun createSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: String,
selectionData: SpawnSelectionData
): SpawnAction<*> {
val herdable = getValidHerdMembersForRole(
selectionData = selectionData,
useLeaderRole = lacksPossibleLeader(selectionData)
).weightedSelection { it.weight } ?: herdablePokemon[0]
var level = selectionData.context[herdLevelKey] as? Int ?: 1
// Attempt to do more detailed anti-wall-spawn logic. Herd members could spawn in walls because
// we've just been assuming they're 1 block wide (or at least, all in the herd have the same size).
// Some pokemon are not that though.
if (spawnablePosition is AreaSpawnablePosition) {
val species = herdable.pokemon.species?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
if (species != null) {
val height = ceil(species.hitbox.height * species.baseScale).toInt()
val width = ceil(species.hitbox.width * species.baseScale).toInt()
if (!spawnablePosition.hasSpace(width, height)) {
return EmptySpawnAction(spawnablePosition, bucket, EmptySpawnDetail.EMPTY)
}
}
}
level = herdable.levelRange?.let { clamp(level, it.first, it.last) } ?: level
): PokemonSpawnAction {
val validHerdMembers = getValidHerdMembers(selectionData)
var searchedForLeader = false
val herdable = if (lacksPossibleLeader(selectionData)) {
searchedForLeader = true
validHerdMembers.filter { it.isLeader }
} else {
validHerdMembers.filter { it.isFollower }
}.weightedSelection { it.weight } ?: herdablePokemon[0]
val level = selectionData.context[herdLevelKey] as? Int ?: 1
val levelRange = herdable.levelRangeOffset?.let { offset ->
val min = level + offset.first
val max = level + offset.last
@@ -158,7 +136,11 @@ class PokemonHerdSpawnDetail : SpawnDetail() {
drops = herdable.dropTable,
heldItem = heldItem,
levelRange = levelRange
)
).also {
if (herdable.isLeader && searchedForLeader) {
it.labels += HERD_LEADER
}
}
}
fun getHerdMemberCounts(selectionData: SpawnSelectionData): Map<Herdable?, Int> {
@@ -53,39 +53,34 @@ open class AreaSpawnablePosition(
*/
open fun isSafeSpace(world: ServerLevel, pos: BlockPos, state: BlockState): Boolean = !state.isSolid
fun hasSpace(width: Int, height: Int): Boolean {
val sizeX = width.takeIf { it > 0 } ?: 1
val sizeY = height.takeIf { it > 0 } ?: 1
val minX = floor(position.x + 0.5 - (sizeX - 1) / 2F).toInt() - 1
val maxX = ceil(position.x + 0.5 + (sizeX + 1) / 2F).toInt() + 1
val maxY = ceil(position.y + (sizeY + 1) / 2F).toInt() + 1
val minZ = floor(position.z + 0.5 - (sizeX - 1) / 2F).toInt() - 1
val maxZ = ceil(position.z + 0.5 + (sizeX + 1) / 2F).toInt() + 1
val mutable = BlockPos.MutableBlockPos()
for (x in minX until maxX) {
for (y in (position.y + 1)..maxY) {
for (z in minZ until maxZ) {
val state = zone.getBlockState(mutable.set(x, y, z))
if (!isSafeSpace(world, mutable, state)) {
return false
}
}
}
}
return true
}
override fun postFilter(detail: SpawnDetail): Boolean {
if (!super.postFilter(detail)) {
return false
}
if (detail.width > 1 || detail.height > 1) {
return hasSpace(detail.width, detail.height)
val sizeX = detail.width.takeIf { it > 0 } ?: 1
val sizeY = detail.height.takeIf { it > 0 } ?: 1
val minX = floor(position.x + 0.5 - (sizeX - 1) / 2F).toInt() - 1
val maxX = ceil(position.x + 0.5 + (sizeX + 1) / 2F).toInt() + 1
val maxY = ceil(position.y + (sizeY + 1) / 2F).toInt() + 1
val minZ = floor(position.z + 0.5 - (sizeX - 1) / 2F).toInt() - 1
val maxZ = ceil(position.z + 0.5 + (sizeX + 1) / 2F).toInt() + 1
val mutable = BlockPos.MutableBlockPos()
for (x in minX until maxX) {
for (y in (position.y + 1)..maxY) {
for (z in minZ until maxZ) {
val state = zone.getBlockState(mutable.set(x, y, z))
if (!isSafeSpace(world, mutable, state)) {
return false
}
}
}
}
}
return true
@@ -28,7 +28,7 @@ import java.io.InputStreamReader
* @since July 8th, 2022
*/
class BestSpawnerConfig {
val version = 2
val version = 0
/** Whether an external config will be replaced by an internal one once [version] is higher on the internal. */
val replaceWithNewVersion = true
@SerializedName("spawnablePositionTypeWeights", alternate = ["contextWeights"])
@@ -40,11 +40,11 @@ class BestSpawnerConfig {
)
val worldBuckets = mutableMapOf(
"common" to 94.05F,
"common" to 94F,
"uncommon" to 5F,
"rare" to 0.5F,
"ultra-rare" to 0.2F,
"boss" to 0.25F,
"boss" to 0.3F,
)
val fishingBuckets = mutableMapOf(
@@ -89,7 +89,6 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
val spawnablePositions: List<SpawnablePosition>,
val selectingSpawnInformation: MutableMap<String, SpawnablePositionBucketSelectionData>,
): SpawnSelectionData {
override var guaranteedBucket: String? = null
override val spawnActions = mutableListOf<SpawnAction<*>>()
override val context = mutableMapOf<String, Any>()
@@ -101,7 +101,6 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
val spawner: Spawner,
val spawnablePositions: List<SpawnablePosition>
) : SpawnSelectionData {
override var guaranteedBucket: String? = null
override val spawnActions = mutableListOf<SpawnAction<*>>()
override val context = mutableMapOf<String, Any>()
@@ -21,7 +21,6 @@ import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
* @since April 21st, 2025
*/
interface SpawnSelectionData {
var guaranteedBucket: String?
val context: MutableMap<String, Any>
val spawnActions: MutableList<SpawnAction<*>>
fun removeSpawnDetails(shouldRemove: (SpawnDetail) -> Boolean)
@@ -29,7 +28,6 @@ interface SpawnSelectionData {
companion object {
val EMPTY = object : SpawnSelectionData {
override var guaranteedBucket: String? = null
override val context: MutableMap<String, Any> = mutableMapOf()
override val spawnActions: MutableList<SpawnAction<*>> = mutableListOf()
override fun removeSpawnDetails(shouldRemove: (SpawnDetail) -> Boolean) {}
@@ -47,7 +47,7 @@ interface SpawningSelector<T : SpawnSelectionData> {
val spawnActions = selectionData.spawnActions
while (spawnActions.size < maxSpawns) {
val bucket = selectionData.guaranteedBucket ?: bucketSelector()
val bucket = bucketSelector()
val spawnAction = selectSpawnAction(
spawner = spawner,
bucket = bucket,
@@ -17,7 +17,6 @@ import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluenc
import com.cobblemon.mod.common.api.spawning.influence.SpawnBaitInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.rules.SpawnRule
import com.cobblemon.mod.common.block.entity.PokeSnackBlockEntity
import com.cobblemon.mod.common.util.mutableLazy
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
@@ -67,10 +66,10 @@ object PokeSnackSpawnerFactory {
},
)
fun create(ctx: Context): PokeSnackBlockEntity.Spawner {
fun create(ctx: Context): FixedAreaSpawner {
val influences = influenceBuilders.mapNotNull { it(ctx) }
return PokeSnackBlockEntity.Spawner(
return FixedAreaSpawner(
name = ctx.name,
spawnPool = spawnPool,
buckets = Cobblemon.bestSpawner.config.pokeSnackBuckets,
@@ -46,7 +46,6 @@ open class BottomlessStore(override val uuid: UUID) : PokemonStore<BottomlessPos
pokemon.forEachIndexed { index, pokemon ->
pokemon.storeCoordinates.set(StoreCoordinates(this, BottomlessPosition(index)))
}
rebuildPokemonByUuid()
}
override fun saveToNBT(nbt: CompoundTag, registryAccess: RegistryAccess): CompoundTag {
@@ -53,7 +53,7 @@ abstract class PokemonStore<T : StorePosition> : Iterable<Pokemon> {
* Runs initialization logic for this store, knowing that it has just been constructed in a [PokemonStoreFactory].
*
* The minimum of what this function should do is iterate over all the Pokémon in this store and set their store
* coordinates. Built-in stores then rebuild the UUID index via [rebuildPokemonByUuid].
* coordinates.
*
* If this does not get called, or it does not do its job properly, serious de-sync issues may follow.
*/
@@ -99,7 +99,6 @@ abstract class PokemonStore<T : StorePosition> : Iterable<Pokemon> {
setAtPosition(position, pokemon)
pokemon.storeCoordinates.set(StoreCoordinates(this, position))
pokemonByUuid[pokemon.uuid] = pokemon
}
/** Swaps the Pokémon at the specified positions. If one of the spaces is empty, it will simply move the not-null one to that space. */
@@ -148,26 +147,10 @@ abstract class PokemonStore<T : StorePosition> : Iterable<Pokemon> {
pokemon.recall()
pokemon.storeCoordinates.set(null)
setAtPosition(currentPosition.position, null)
pokemonByUuid.remove(pokemon.uuid)
return true
}
/** In-memory UUID index. Not serialized; rebuilt by [rebuildPokemonByUuid]. */
private val pokemonByUuid = hashMapOf<UUID, Pokemon>()
/** Gets the [Pokemon] with this UUID, or null if it is not in this store. */
operator fun get(uuid: UUID): Pokemon? = pokemonByUuid[uuid]
/**
* Rebuilds the UUID index from the store's current contents.
*
* Call after load or any structural mutation that does not go through [set] / [remove], such as
* filling slots directly from NBT/JSON or shrinking a PC.
*/
protected fun rebuildPokemonByUuid() {
pokemonByUuid.clear()
forEach { pokemonByUuid[it.uuid] = it }
}
operator fun get(uuid: UUID) = find { it.uuid.equals(uuid) }
open fun handleInvalidSpeciesNBT(nbt: CompoundTag) {
Cobblemon.LOGGER.error("Failed to read unknown species: ${nbt.getString(DataKeys.POKEMON_SPECIES_IDENTIFIER)}")
@@ -138,7 +138,6 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
val pokemon = get(slot) ?: continue
pokemon.storeCoordinates.set(StoreCoordinates(this, PartyPosition(slot)))
}
rebuildPokemonByUuid()
}
override fun onPokemonChanged(pokemon: Pokemon) {
@@ -280,7 +279,7 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
}
}
return result.sortedBy { it.health <= 0 }
return result
}
fun clearParty() {
forEach {
@@ -105,7 +105,6 @@ open class PCStore(
override fun initialize() {
boxes.forEach { it.initialize() }
backupStore.initialize()
rebuildPokemonByUuid()
}
fun relocateEvictedBoxPokemon(pokemon: Pokemon) {
@@ -136,7 +135,6 @@ open class PCStore(
tryRestoreBackedUpPokemon()
}
rebuildPokemonByUuid()
pcChangeObservable.emit(Unit)
}
@@ -144,7 +142,6 @@ open class PCStore(
this.lockedSize = lockNewSize
boxes.removeAll(boxList)
boxList.flatMap { it.asIterable() }.forEach(overflowHandler)
rebuildPokemonByUuid()
pcChangeObservable.emit(Unit)
}
@@ -66,8 +66,6 @@ object CobblemonBiomeTags {
val IS_WARM_OCEAN = create("is_warm_ocean")
@JvmField
val IS_DEEP_DARK = create("is_deep_dark")
@JvmField
val IS_MOUNTAIN = create("is_mountain")
// Has Block tags
@JvmField
@@ -166,7 +166,7 @@ object AIUtility {
"geargrind" to Pair(2, 2),
"twinbeam" to Pair(2, 2),
"twineedle" to Pair(2, 2),
"surgingstrikes" to Pair(3, 3),
"suringstrikes" to Pair(3, 3),
"tripledive" to Pair(3, 3),
"watershuriken" to Pair(3, 3),
@@ -39,6 +39,7 @@ class FaintInstruction(battle: PokemonBattle, val message: BattleMessage) : Inte
battle.dispatchFuture {
val (pnx, _) = message.pnxAndUuid(0) ?: return@dispatchFuture CompletableFuture.completedFuture(Unit)
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchFuture CompletableFuture.completedFuture(Unit)
battle.sendUpdate(BattleFaintPacket(pnx))
val actor = pokemon.actor
pokemon.effectedPokemon.currentHealth = 0
val preamble = if (actor is EntityBackedBattleActor<*>) {
@@ -49,8 +50,6 @@ class FaintInstruction(battle: PokemonBattle, val message: BattleMessage) : Inte
val context = ShowdownInterpreter.getContextFromFaint(faintingPokemon, battle)
preamble.thenAccept {
// Sent after the faint/recall animation so clients don't learn about the KO early
battle.sendUpdate(BattleFaintPacket(pnx))
faintingPokemon.effectedPokemon.currentHealth = 0
faintingPokemon.faintedAt = battle.faintCounter++
faintingPokemon.sendUpdate()
@@ -17,8 +17,15 @@ import com.cobblemon.mod.common.api.events.cooking.PokeSnackSpawnPokemonEvent
import com.cobblemon.mod.common.api.fishing.SpawnBait
import com.cobblemon.mod.common.api.fishing.SpawnBait.Effect
import com.cobblemon.mod.common.api.fishing.SpawnBaitEffects
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.detail.*
import com.cobblemon.mod.common.api.spawning.detail.EntitySpawnResult
import com.cobblemon.mod.common.api.spawning.detail.PokemonHerdSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.influence.BucketMultiplyingInfluence
import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawnBaitInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.spawner.FixedAreaSpawner
@@ -30,6 +37,7 @@ import com.cobblemon.mod.common.item.components.FoodColourComponent
import com.cobblemon.mod.common.item.components.IngredientComponent
import com.cobblemon.mod.common.net.messages.client.effect.PokeSnackBlockParticlesPacket
import com.cobblemon.mod.common.util.DataKeys
import java.util.UUID
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
@@ -43,7 +51,6 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import java.util.*
open class PokeSnackBlockEntity(pos: BlockPos, state: BlockState) :
BlockEntity(CobblemonBlockEntities.POKE_SNACK, pos, state), TintBlockEntity,
@@ -56,29 +63,9 @@ open class PokeSnackBlockEntity(pos: BlockPos, state: BlockState) :
const val POKE_SNACK_CRUMBED_ASPECT = "poke_snack_crumbed"
}
class Spawner(
name: String,
spawnPool: SpawnPool,
buckets: Map<String, Float>,
world: ServerLevel,
position: BlockPos,
horizontalRadius: Int,
verticalRadius: Int,
maxPokemonPerChunk: Float
) : FixedAreaSpawner(
name = name,
spawnPool = spawnPool,
buckets = buckets,
world = world,
position = position,
horizontalRadius = horizontalRadius,
verticalRadius = verticalRadius,
maxPokemonPerChunk = maxPokemonPerChunk
)
override var tint: Int? = null
val spawner: Spawner by lazy {
val spawner: FixedAreaSpawner by lazy {
val server = level as ServerLevel
val baitEffects = getBaitEffects()
@@ -278,7 +278,7 @@ class HabitatBlockEntity(pos: BlockPos, state: BlockState) :
override fun injectSpawns(bucket: String, spawnablePosition: SpawnablePosition): List<SpawnDetail>? {
val spawningStyle = spawningStyle
return if (spawningStyle is NaturalHabitatSpawning) {
spawnDetails.filter { it.bucket == bucket && it.spawnablePositionType.clazz == spawnablePosition::class.java && it.isSatisfiedBy(spawnablePosition) }
spawnDetails.filter { it.spawnablePositionType.clazz == spawnablePosition::class.java && it.isSatisfiedBy(spawnablePosition) }
} else {
null
}
@@ -70,6 +70,7 @@ import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.isLookingAt
import net.minecraft.client.Minecraft
import net.minecraft.client.color.block.BlockColor
@@ -79,16 +80,19 @@ import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.model.BoatModel
import net.minecraft.client.model.ChestBoatModel
import net.minecraft.client.model.PlayerModel
import net.minecraft.client.player.LocalPlayer
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.blockentity.HangingSignRenderer
import net.minecraft.client.renderer.blockentity.SignRenderer
import net.minecraft.client.renderer.entity.EntityRenderer
import net.minecraft.client.renderer.entity.LivingEntityRenderer
import net.minecraft.client.resources.PlayerSkin
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.resources.ResourceManager
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.AABB
import org.joml.Vector2d
object CobblemonClient {
@@ -109,6 +113,8 @@ object CobblemonClient {
val battleOverlay: BattleOverlay by lazy { BattleOverlay() }
val pokedexUsageContext: PokedexUsageContext by lazy { PokedexUsageContext() }
val rideControlsOverlay: RideControlsOverlay by lazy { RideControlsOverlay() }
val ridingTurnInputHandler: MutableMap<ResourceLocation, (LocalPlayer, Double, Double, Double) -> Vector2d> = mutableMapOf()
val ridingSprintHandler: MutableMap<ResourceLocation, () -> Boolean> = mutableMapOf()
fun onLogin() {
clientPlayerData = ClientGeneralPlayerData()
@@ -194,6 +200,8 @@ object CobblemonClient {
ClientPlayerIcon.onTick()
PartyOverlayDataControl.tick(event.client.isPaused)
}
ridingSprintHandler.put(cobblemonResource("normal")) { Minecraft.getInstance().options.keySprint.isDown }
}
private fun registerTooltipManagers() {
@@ -470,7 +470,7 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
val random = currentEntity.level().random
when (particleData) {
is ParticleData.SnowstormParticle -> {
val locators = particleData.locatorResolver(locatorStates).filter(::isLocatorVisible)
val locators = particleData.locatorResolver(locatorStates).ifEmpty { listOf("root") }.filter(::isLocatorVisible)
if (particleData.chance > random.nextDouble()) {
locators.forEach { locator ->
repeat(particleData.amount) {
@@ -518,14 +518,15 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
}
}
fun getSeatLocator(passenger: Entity): String? {
return this.getEntity().occupiedSeats.entries
fun getSeatLocator(passenger: Entity): String {
val seat = this.getEntity().occupiedSeats.entries
.firstOrNull { it.value == passenger }?.key
?.locator
?: throw IllegalArgumentException("Entity is not currently riding a seat")
return seat.locator
}
override fun positionRider(passenger: Entity, positionUpdater: MoveFunction) {
val locatorName = getSeatLocator(passenger) ?: return
val locatorName = getSeatLocator(passenger)
val locator = this.locatorStates[locatorName] ?: return
val locatorOffset = locator.matrix.getTranslation(Vector3f())
@@ -129,6 +129,10 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
val panelX = 0
val party = CobblemonClient.storage.party
val matrices = context.pose()
matrices.pushPose()
matrices.translate(0.0, 0.0, -100.0)
if (party.slots.none { it != null }) {
if (!this.attachedToast &&
CobblemonClient.clientPlayerData.promptStarter &&
@@ -182,10 +186,11 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
)
matrices.pushPose()
matrices.scale(1F, 1F, 0.2F)
matrices.translate(
panelX + portraitFrameOffsetX + selectedOffsetX + PORTRAIT_DIAMETER / 2.0 - 1.0,
y.toDouble() - 12,
0.0
-50.0
)
val shouldAnimate = when (Cobblemon.config.partyPortraitAnimations) {
@@ -557,5 +562,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
}
}
}
matrices.popPose()
}
}
@@ -195,7 +195,7 @@ abstract class ScrollingWidget<T : ObjectSelectionList.Entry<T>>(
return this.width
}
override fun getRowTop(index: Int): Int {
public override fun getRowTop(index: Int): Int {
return this.y - scrollAmount.toInt() + (index * this.itemHeight)
}
@@ -205,6 +205,19 @@ abstract class ScrollingWidget<T : ObjectSelectionList.Entry<T>>(
override fun getScrollbarPosition(): Int = this.left + this.width - this.scrollBarWidth
public override fun ensureVisible(entry: T) {
val i = getRowTop(children().indexOf(entry))
val topDifference = i - y
if (topDifference < 0) {
scroll(topDifference)
}
val bottomDifference = bottom - i - itemHeight - 4
if (bottomDifference < 0) {
scroll(-bottomDifference)
}
}
abstract class Slot<T : Slot<T>>(): Entry<T>() {
// Override render to show each individual element
}
@@ -68,25 +68,23 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
}
override fun renderWidget(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
var stackHeight = -scrollAmount.toInt()
guiGraphics.enableScissor(x, y, x + width, y + height)
children.forEachIndexed { index, entry ->
if (stackHeight in 1..<height || stackHeight + entry.height in 1..<height) {
entry.render(guiGraphics, index, x, y + stackHeight, mouseX, mouseY, partialTick)
val yTop = getRowTop(entry)
if (yTop + entry.height > y || yTop < y + height) {
entry.render(guiGraphics, index, x, yTop, mouseX, mouseY, partialTick)
}
stackHeight += entry.height
}
guiGraphics.disableScissor()
}
fun getEntryAtPosition(mouseX: Int, mouseY: Int): Entry? {
if (mouseX in x..(x + width) && mouseY in y..(y + height)) {
var stackHeight = -scrollAmount
children.forEach {
if (mouseY.toDouble() in (y + stackHeight)..(y + stackHeight + it.height)) {
val yTop = getRowTop(it)
if (mouseY in (yTop)..(yTop + it.height)) {
return it
}
stackHeight += it.height
}
}
return null
@@ -94,10 +92,16 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
fun ensureVisible(index: Int) {
val entry = children[index]
if (entry.scrolledY - scrollAmount < 0) {
ensureVisible(entry)
}
open fun ensureVisible(entry: Entry) {
if (entry !in children) return
val yTop = entry.scrolledY - scrollAmount.toInt()
if (yTop < 0) {
setScroll(entry.scrolledY.toDouble())
} else if (entry.scrolledY + entry.height > height) {
setScroll(entry.scrolledY.toDouble() + entry.height)
} else if (yTop + entry.height > height) {
setScroll((entry.scrolledY + entry.height - height).toDouble())
}
}
@@ -121,14 +125,12 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
}
fun getRowTop(entry: Entry): Int {
var stackHeight = -scrollAmount.toInt()
children.forEach {
if (it == entry) {
return y + stackHeight
}
stackHeight += it.height
}
return y
if (entry !in children) return -1
return y + entry.scrolledY - scrollAmount.toInt()
}
fun getLeft(): Int {
return x
}
override fun defaultButtonNarrationText(pNarrationElementOutput: NarrationElementOutput) {}
@@ -136,6 +138,7 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
abstract inner class Entry(var height: Int): GuiEventListener {
var scrolledY: Int = 0
private var widgetFocused = false
override fun setFocused(focused: Boolean) {
@@ -145,6 +148,14 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
override fun isFocused(): Boolean = widgetFocused
abstract fun render(guiGraphics: GuiGraphics, index: Int, x: Int, y: Int, mouseX: Int, mouseY: Int, partialTick: Float)
fun getRowTop(): Int {
return getRowTop(this)
}
fun getRowLeft(): Int {
return getLeft()
}
}
inner class EmptySeparator(height: Int): Entry(height) {
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.client.gui.battle
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.battles.PassActionResponse
import com.cobblemon.mod.common.battles.ShowdownActionResponse
import com.cobblemon.mod.common.client.CobblemonClient
@@ -17,9 +18,18 @@ import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleActionSelection
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleBackButton
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleGeneralActionSelection
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleMoveSelection
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleSwitchPokemonSelection
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleTargetSelection
import com.cobblemon.mod.common.client.gui.battle.subscreen.ForfeitConfirmationSelection
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleMessagePane
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleOptionTile
import com.cobblemon.mod.common.client.gui.summary.Summary.Companion.PARTY
import com.cobblemon.mod.common.client.gui.summary.widgets.PartyWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.integration.controlify.gui.component.SubManagerComponent
import com.cobblemon.mod.common.client.keybind.boundKey
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding
import com.cobblemon.mod.common.client.render.drawScaledText
@@ -29,8 +39,11 @@ import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.Screen
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import org.joml.Vector4i
class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable, ControllerScreen {
companion object {
const val OPTION_VERTICAL_SPACING = 3
const val OPTION_HORIZONTAL_SPACING = 3
@@ -51,11 +64,49 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
var queuedActions = mutableListOf<() -> Unit>()
val controllerComponentManager = ControllerComponentManager(this)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun init() {
super.init()
messagePane = BattleMessagePane(CobblemonClient.battle!!.messages)
messagePane.opacity = CobblemonClient.battleOverlay.opacityRatio.toFloat().coerceAtLeast(0.3F)
addRenderableWidget(messagePane)
controllerComponentManager.componentList["spectating_quit"] = ControllerComponent().also {
it.onPress = {
quitSpectating()
}
}
controllerComponentManager.componentList["battle_action"] = SubManagerComponent().also {
it.subManager = { getCurrentActionSelection()?.controllerComponentManager }
}
controllerComponentManager.let {
it.focused = it.componentList["battle_action"]
it.onActiveChange = { state ->
if (!state) {
getCurrentActionSelection()?.controllerComponentManager?.active = false
} else {
if (it.currentSelectedComponent == "battle_action") getCurrentActionSelection()?.controllerComponentManager?.active = true
}
}
it.onBack = {
if (it.currentSelectedComponent == "battle_action") {
getCurrentActionSelection()?.controllerComponentManager?.onBack?.invoke()
} else {
minimizeBattle()
}
}
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ (getCurrentActionSelection() as? BattleMoveSelection)?.shiftButtonActive() ?: false },
Component.translatable("cobblemon.controller.battle.swap_position"),
true
))
}
}
fun changeActionSelection(newSelection: BattleActionSelection?) {
@@ -65,6 +116,11 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
if (newSelection != null) {
addRenderableWidget(newSelection)
}
if (controllerComponentManager.currentSelectedComponent == "battle_action" && controllerComponentManager.active) {
focused = null
getCurrentActionSelection()?.controllerComponentManager?.active = true
controllerComponentManager.focused?.onFocus?.invoke()
}
}
}
@@ -85,6 +141,38 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
}
}
override fun getSelectionPosAndSize(): Vector4i? {
val data = super.getSelectionPosAndSize()
if (data != null) {
return data
}
val actionSelection = getCurrentActionSelection()
var newData: Vector4i? = null
when (actionSelection) {
is BattleGeneralActionSelection -> {
val tile = actionSelection.controllerComponentManager.focused?.associatedGUIElement as? BattleOptionTile
if (tile != null) {
newData = Vector4i(tile.x - 2, tile.y - 2, BattleOptionTile.OPTION_WIDTH + 4, BattleOptionTile.OPTION_HEIGHT + 4)
}
}
is BattleMoveSelection -> {
val move = actionSelection.moveTiles[actionSelection.selectedMoveIndex]
newData = Vector4i(move.x.toInt() - 10, move.y.toInt() - 2, BattleMoveSelection.MOVE_WIDTH + 12, BattleMoveSelection.MOVE_HEIGHT + 4)
}
is BattleSwitchPokemonSelection -> {
val tile = actionSelection.tiles[actionSelection.selectedTileIndex]
newData = Vector4i(tile.x.toInt() - 2, tile.y.toInt() - 2, BattleSwitchPokemonSelection.SwitchTile.SELECT_WIDTH + 2, BattleSwitchPokemonSelection.SwitchTile.SELECT_HEIGHT - 1)
}
is BattleTargetSelection -> {
val tile = actionSelection.baseTiles[actionSelection.selectedTileIndex]
newData = Vector4i(tile.x.toInt() - 2, tile.y.toInt() - 2, BattleTargetSelection.TARGET_WIDTH + 4, BattleTargetSelection.TARGET_HEIGHT + 2)
}
}
return newData
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
super.render(context, mouseX, mouseY, delta)
@@ -122,9 +210,10 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
val currentSelection = getCurrentActionSelection()
if (currentSelection == null || currentSelection is BattleGeneralActionSelection ) {
val hideLabelButton = if (controllerComponentManager.active) controllerComponentManager.getVisual(ResourceLocation.fromNamespaceAndPath("controlify", "gui_back")) else PartySendBinding.boundKey().displayName
drawScaledText(
context = context,
text = battleLang("ui.hide_label", PartySendBinding.boundKey().displayName),
text = battleLang("ui.hide_label", hideLabelButton),
x = Minecraft.getInstance().window.guiScaledWidth / 2,
y = (Minecraft.getInstance().window.guiScaledHeight / 5),
opacity = 0.75F * opacity,
@@ -141,6 +230,17 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
)
}
renderSelection(context.pose())
val activeComponent: ControllerComponent?
if (battle.spectating) {
activeComponent = controllerComponentManager.componentList["spectator_quit"]
} else {
activeComponent = controllerComponentManager.componentList["battle_action"]
}
if (controllerComponentManager.focused != activeComponent) {
controllerComponentManager.focused = activeComponent
}
queuedActions.forEach { it() }
queuedActions.clear()
@@ -151,8 +251,6 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
}
fun deriveRootActionSelection(actor: ClientBattleActor, request: SingleActionRequest): BattleActionSelection? {
return if (request.forceSwitch) {
BattleSwitchPokemonSelection(this, request)
} else {
@@ -192,15 +290,21 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
return minimizeBattle()
}
val battle = CobblemonClient.battle
if (battle?.spectating == true && specBackButton.isHovered(mouseX, mouseY)) {
RemoveSpectatorPacket(battle.battleId).sendToServer()
CobblemonClient.endBattle()
if (specBackButton.isHovered(mouseX, mouseY)) {
quitSpectating()
}
return super.mouseClicked(mouseX, mouseY, button)
}
private fun minimizeBattle(): Boolean {
fun quitSpectating() {
val battle = CobblemonClient.battle
if (battle?.spectating == true) {
RemoveSpectatorPacket(battle.battleId).sendToServer()
CobblemonClient.endBattle()
}
}
fun minimizeBattle(): Boolean {
val battle = CobblemonClient.battle ?: return false
battle.minimised = !battle.minimised
PartySendBinding.actioned()
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.ActiveClientBattlePokemon
import com.cobblemon.mod.common.client.battle.ClientBallDisplay
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleMessagePane
import com.cobblemon.mod.common.client.integration.controlify.CobblemonControlifyEntrypoint
import com.cobblemon.mod.common.client.keybind.boundKey
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding
import com.cobblemon.mod.common.client.render.drawScaledText
@@ -114,6 +115,10 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
override val schedulingTracker = SchedulingTracker()
override fun render(context: GuiGraphics, tickCounter: DeltaTracker) {
val matrixStack = context.pose()
matrixStack.pushPose()
matrixStack.translate(0.0, 0.0, -50.0) // Push back so controlify gui renders infront
val tickDelta = tickCounter.realtimeDeltaTicks.takeIf { !Minecraft.getInstance()!!.isPaused } ?: 0F
schedulingTracker.update(tickDelta / 20F)
passedSeconds += tickDelta / 20
@@ -142,9 +147,14 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
if (Minecraft.getInstance().screen !is BattleGUI && battle.mustChoose) {
val textOpacity = PROMPT_TEXT_OPACITY_CURVE(passedSeconds)
val bindingComponent = if (Cobblemon.implementation.isModInstalled("controlify") && CobblemonControlifyEntrypoint.controllerActive())
CobblemonControlifyEntrypoint.SEND_OUT_POKEMON!!.inputGlyph()
else PartySendBinding.boundKey().displayName
drawScaledText(
context = context,
text = battleLang("ui.actions_label", PartySendBinding.boundKey().displayName),
text = battleLang("ui.actions_label", bindingComponent),
x = Minecraft.getInstance().window.guiScaledWidth / 2,
y = Minecraft.getInstance().window.guiScaledHeight / 5,
opacity = textOpacity,
@@ -160,6 +170,8 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
messagePane.opacity = 0.3F
messagePane.render(context, 0, 0, 0F)
}
matrixStack.popPose()
}
fun drawTile(context: GuiGraphics, tickDelta: Float, activeBattlePokemon: ActiveClientBattlePokemon, left: Boolean, rank: Int, dexState: PokedexEntryProgress, hasCommand: Boolean = false, isHovered: Boolean = false, isCompact: Boolean = false) {
@@ -297,10 +309,11 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
(y + portraitDiameter + portraitOffsetY).toInt(),
)
matrixStack.pushPose()
matrixStack.scale(1F, 1F, 0.2F)
matrixStack.translate(
portraitStartX + portraitDiameter / 2.0,
y.toDouble() + portraitOffsetY - if (isCompact) 15.0 else 5.0,
0.0
-50.0
)
if (ballState != null && ballState.currentPose != "shut") {
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.gui.battle.subscreen
import com.cobblemon.mod.common.api.gui.ParentWidget
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.mojang.blaze3d.platform.InputConstants
import net.minecraft.network.chat.MutableComponent
@@ -26,6 +27,8 @@ abstract class BattleActionSelection(
val opacity: Float
get() = battleGUI.opacity
val controllerComponentManager = ControllerComponentManager(battleGUI)
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if(pButton == InputConstants.MOUSE_BUTTON_LEFT){
return mousePrimaryClicked(pMouseX, pMouseY)
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleOptionTile
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.util.battleLang
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
@@ -41,6 +42,7 @@ class BattleGeneralActionSelection(
val lastAnwseredRequest = CobblemonClient.battle?.getLastAnsweredRequest()
val tiles = mutableListOf<BattleOptionTile>()
init {
var rank = 0
@@ -76,22 +78,41 @@ class BattleGeneralActionSelection(
}
}
}
controllerComponentManager.let {
it.focused = it.componentList["tile_0_0"]
it.onBack = {
battleGUI.minimizeBattle()
}
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
}
private fun addOption(rank: Int, text: MutableComponent, texture: ResourceLocation, onClick: () -> Unit) {
val startY = Minecraft.getInstance().window.guiScaledHeight - BattleGUI.OPTION_VERTICAL_OFFSET
val x = if (rank % 2 == 0) BattleGUI.OPTION_ROOT_X else BattleGUI.OPTION_ROOT_X + BattleGUI.OPTION_HORIZONTAL_SPACING + BattleOptionTile.OPTION_WIDTH
val y = if (rank > 1) startY + BattleOptionTile.OPTION_HEIGHT + BattleGUI.OPTION_HORIZONTAL_SPACING else startY
tiles.add(
BattleOptionTile(
battleGUI = battleGUI,
x = x,
y = y,
resource = texture,
text = text,
onClick = onClick
)
val tile = BattleOptionTile(
battleGUI = battleGUI,
x = x,
y = y,
resource = texture,
text = text,
onClick = onClick
)
tiles.add(tile)
val row = rank / 2
val column = rank % 2
controllerComponentManager.componentList["tile_${row}_${column}"] = ControllerComponent().also {
it.associatedGUIElement = tile
if (row == 1) it.up = { controllerComponentManager.componentList["tile_0_${column}"] }
else it.down = { controllerComponentManager.componentList["tile_1_${column}"] }
if (column == 1) it.left = { controllerComponentManager.componentList["tile_${row}_0"] }
else it.right = { controllerComponentManager.componentList["tile_${row}_1"] }
it.onPress = { tile.onClick.invoke() }
}
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -21,10 +21,12 @@ import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.toRGB
import com.cobblemon.mod.common.util.startWith
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
@@ -66,6 +68,10 @@ class BattleMoveSelection(
MoveTile(this, inBattleMove, x, y)
}
var moveTiles = baseTiles
set(value) {
field = value
recalculateMoveGrid()
}
val backButton = BattleBackButton(x - 11F, Minecraft.getInstance().window.guiScaledHeight - 22F)
val gimmickButtons = moveSet.getGimmicks().filter { it !in moveSet.pendingGimmickUsedThisTurn }.mapIndexed { index, gimmick ->
@@ -76,6 +82,53 @@ class BattleMoveSelection(
val shiftButton = BattleShiftButton(x + 22.5F, Minecraft.getInstance().window.guiScaledHeight - 22F )
private fun recalculateMoveGrid() {
controllerComponentManager.componentList.filter { it.key.startsWith("move_") }.forEach { controllerComponentManager.componentList.remove(it.key) }
moveTiles.forEachIndexed { index, tile ->
val column = index % 2
val row = index / 2
controllerComponentManager.componentList["move_${row}_${column}"] = ControllerComponent().also {
if (row == 1) it.up = { controllerComponentManager.componentList["move_${0}_${column}"] }
else it.down = { controllerComponentManager.componentList["move_${1}_${column}"] }
if (column == 1) it.left = { controllerComponentManager.componentList["move_${row}_0"] }
else it.right = { controllerComponentManager.componentList["move_${row}_1"] }
it.onPress = {
if(request.activePokemon.getFormat().battleType.pokemonPerSide == 1) {
tile.onClick()
} else {
battleGUI.changeActionSelection(BattleTargetSelection(battleGUI, request, tile.move, tile.response.gimmickID, tile.move.gimmickMove))
playDownSound(Minecraft.getInstance().soundManager)
}
}
it.onSpecial1 = {
if (shiftButtonActive()) {
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.selectAction(request, ShiftActionResponse())
}
}
it.onFocus = { tile.controllerSelected = true }
it.onUnfocus = { tile.controllerSelected = false }
it.onSelect = { selectedMoveIndex = index }
}
}
}
var selectedMoveIndex = 0
init {
recalculateMoveGrid()
controllerComponentManager.let {
it.focused = it.componentList["move_0_0"]
it.onBack = {
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.changeActionSelection(null)
}
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
}
open class MoveTile(
val moveSelection: BattleMoveSelection,
val move: InBattleMove,
@@ -99,6 +152,8 @@ class BattleMoveSelection(
}
}
var controllerSelected = false
fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val selectConditionOpacity = moveSelection.opacity * if (!selectable) 0.5F else 1F
@@ -110,7 +165,7 @@ class BattleMoveSelection(
y = y,
width = MOVE_WIDTH,
height = MOVE_HEIGHT,
vOffset = if (selectable && isHovered(mouseX.toDouble(), mouseY.toDouble())) MOVE_HEIGHT else 0,
vOffset = if (selectable && (isHovered(mouseX.toDouble(), mouseY.toDouble()) || controllerSelected)) MOVE_HEIGHT else 0,
textureHeight = MOVE_HEIGHT * 2,
red = rgb.first,
green = rgb.second,
@@ -175,7 +230,7 @@ class BattleMoveSelection(
)
}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX >= x && mouseX <= x + MOVE_WIDTH && mouseY >= y && mouseY <= y + MOVE_HEIGHT
fun isHovered(mouseX: Double, mouseY: Double) = (mouseX >= x && mouseX <= x + MOVE_WIDTH && mouseY >= y && mouseY <= y + MOVE_HEIGHT)
fun onClick() {
if (!selectable) return
@@ -184,6 +239,10 @@ class BattleMoveSelection(
}
}
fun shiftButtonActive(): Boolean {
return this.request.activePokemon.getFormat().battleType.slotsPerActor == 3 && (request.activePokemon.getPNX()[2] == 'a' || request.activePokemon.getPNX()[2] == 'c')
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
moveTiles.forEach {
it.render(context, mouseX, mouseY, delta)
@@ -192,7 +251,7 @@ class BattleMoveSelection(
gimmickButtons.forEach {
it.render(context.pose(), mouseX, mouseY, delta)
}
if(this.request.activePokemon.getFormat().battleType.slotsPerActor == 3 && (request.activePokemon.getPNX()[2] == 'a' || request.activePokemon.getPNX()[2] == 'c')) {
if(shiftButtonActive()) {
shiftButton.render(context, mouseX, mouseY, delta)
}
}
@@ -214,7 +273,7 @@ class BattleMoveSelection(
} else if (gimmick != null) {
gimmickButtons.filter { it != gimmick }.forEach { it.toggled = false }
moveTiles = if (gimmick.toggle()) gimmick.tiles else baseTiles
} else if(shiftButton.isHovered(mouseX,mouseY)) {
} else if(shiftButton.isHovered(mouseX,mouseY) && shiftButtonActive()) {
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.selectAction(request, ShiftActionResponse())
}
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.getDepletableRedGreen
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
@@ -68,6 +69,8 @@ class BattleSwitchPokemonSelection(
var isReviving = false
var selectedTileIndex = 0
init {
val pendingActionRequests = CobblemonClient.battle!!.pendingActionRequests
val switchingInPokemon = pendingActionRequests.mapNotNull { it.response }.filterIsInstance<SwitchActionResponse>().map { it.newPokemonId }
@@ -92,6 +95,33 @@ class BattleSwitchPokemonSelection(
tiles.add(SwitchTile(this, slotX, slotY, pokemon, showdownPokemon, isFainted, isCurrentlyInBattle))
}
tiles.forEachIndexed { index, tile ->
val row = index / 2
val column = index % 2
val maxRow = (tiles.size + 1) / 2
controllerComponentManager.componentList["tile_${row}_${column}"] = ControllerComponent().also {
if (row - 1 >= 0) it.up = { controllerComponentManager.componentList["tile_${row-1}_${column}"] }
if (row + 1 < maxRow) it.down = { controllerComponentManager.componentList["tile_${row+1}_${column}"] }
if (column == 1) it.left = { controllerComponentManager.componentList["tile_${row}_0"] }
else if (row != maxRow || tiles.size % 2 == 0) it.right = { controllerComponentManager.componentList["tile_${row}_1"] }
it.onPress = { clickTile(tile) }
it.onFocus = { tile.controllerSelected = true }
it.onUnfocus = { tile.controllerSelected = false }
it.onSelect = { selectedTileIndex = index }
}
}
controllerComponentManager.let {
it.focused = it.componentList["tile_0_0"]
it.onBack = {
battleGUI.changeActionSelection(null)
playDownSound(Minecraft.getInstance().soundManager)
}
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
}
fun getSlotPosition(index: Int): Pair<Float, Float> {
@@ -154,11 +184,15 @@ class BattleSwitchPokemonSelection(
playDownSound(Minecraft.getInstance().soundManager)
return true
}
val clicked = tiles.find { it.isHovered(mouseX, mouseY) && if (isReviving) it.isFainted else (!it.isFainted && !it.isCurrentlyInBattle) } ?: return false
val pokemon = clicked.pokemon
val clicked = tiles.find { it.isHovered(mouseX, mouseY) } ?: return false
return clickTile(clicked)
}
fun clickTile(tile: SwitchTile): Boolean {
if (!(if (isReviving) tile.isFainted else (!tile.isFainted && !tile.isCurrentlyInBattle))) return false
val pokemon = tile.pokemon
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.selectAction(request, SwitchActionResponse(pokemon.uuid))
return true
}
@@ -190,7 +224,9 @@ class BattleSwitchPokemonSelection(
val state = FloatingState()
fun isHovered(mouseX: Double, mouseY: Double) = mouseX in x..(x + SELECT_WIDTH) && mouseY in (y..(y + SELECT_HEIGHT))
var controllerSelected = false
fun isHovered(mouseX: Double, mouseY: Double) = (mouseX in x..(x + SELECT_WIDTH) && mouseY in (y..(y + SELECT_HEIGHT)))
fun render(context: GuiGraphics, mouseX: Double, mouseY: Double, deltaTicks: Float) {
state.currentAspects = pokemon.aspects
@@ -229,7 +265,7 @@ class BattleSwitchPokemonSelection(
y = y,
width = SELECT_WIDTH,
height = SELECT_HEIGHT,
vOffset = if (!isFainted && (isHovered(mouseX, mouseY) || isCurrentlyInBattle)) SELECT_HEIGHT else 0,
vOffset = if (!isFainted && (isHovered(mouseX, mouseY) || isCurrentlyInBattle || controllerSelected)) SELECT_HEIGHT else 0,
textureHeight = SELECT_HEIGHT * 2,
)
@@ -26,6 +26,7 @@ import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleSwitchPokemonSelection.Companion
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
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
@@ -111,10 +112,35 @@ class BattleTargetSelection(
else -> if (isAlly) ArrowDirection.UP else ArrowDirection.DOWN
}
}
TargetTile(this, target, x, y, arrowDirection)
var out = TargetTile(this, target, x, y, arrowDirection)
controllerComponentManager.componentList["tile_${if (isAlly) 0 else 1}_${fieldPos}"] = ControllerComponent().also {
if (isAlly) it.up = { controllerComponentManager.componentList["tile_1_${fieldPos}"] }
else it.down = { controllerComponentManager.componentList["tile_0_${fieldPos}"] }
if (fieldPos - 1 >= 0) it.left = { controllerComponentManager.componentList["tile_${if (isAlly) 0 else 1}_${fieldPos-1}"] }
if (fieldPos + 1 < teamSize) it.right = { controllerComponentManager.componentList["tile_${if (isAlly) 0 else 1}_${fieldPos+1}"] }
it.onPress = { out.onClick() }
it.onFocus = { out.controllerSelected = true }
it.onUnfocus = { out.controllerSelected = false }
it.onSelect = { selectedTileIndex = index }
}
out
}
var targetTiles = baseTiles
var selectedTileIndex = 0
init {
controllerComponentManager.let {
it.focused = it.componentList["tile_0_0"]
it.onBack = {
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.changeActionSelection(BattleMoveSelection(battleGUI, request))
}
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
}
open inner class TargetTile(
val targetSelection: BattleTargetSelection,
@@ -151,6 +177,8 @@ class BattleTargetSelection(
ArrowDirection.DOWN -> Pair(this.x + TARGET_WIDTH / 2 - 4F, this.y - 7)
}
var controllerSelected = false
fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val passedSeconds = CobblemonClient.battleOverlay.passedSeconds // This syncs the role throbbers
val selectConditionOpacity = targetSelection.opacity * if (!selectable) 0.5F else 1F
@@ -304,9 +332,9 @@ class BattleTargetSelection(
fun isHovered(mouseX: Double, mouseY: Double): Boolean {
if(isMultiTarget) {
return targetTiles.firstOrNull { it.selectable && mouseX >= it.x && mouseX <= it.x + TARGET_WIDTH && mouseY >= it.y && mouseY <= it.y + TARGET_HEIGHT } != null
return targetTiles.firstOrNull { it.selectable && ((mouseX >= it.x && mouseX <= it.x + TARGET_WIDTH && mouseY >= it.y && mouseY <= it.y + TARGET_HEIGHT) || it.controllerSelected) } != null
}
return mouseX >= x && mouseX <= x + TARGET_WIDTH && mouseY >= y && mouseY <= y + TARGET_HEIGHT
return mouseX >= x && mouseX <= x + TARGET_WIDTH && mouseY >= y && mouseY <= y + TARGET_HEIGHT || controllerSelected
}
fun onClick() {
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.interact.battleRequest.BattleResponseButton
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
@@ -51,21 +52,52 @@ class ForfeitConfirmationSelection(
acceptButton = BattleResponseButton(xPos + 22, yPos + 18, true) {}
declineButton = BattleResponseButton(xPos + 57, yPos + 18, false) {}
controllerComponentManager.componentList["accept_button"] = ControllerComponent().also {
it.associatedGUIElement = acceptButton
it.onPress = {
onAccept()
playDownSound(Minecraft.getInstance().soundManager)
}
it.right = { controllerComponentManager.componentList["decline_button"] }
}
controllerComponentManager.componentList["decline_button"] = ControllerComponent().also {
it.associatedGUIElement = declineButton
it.onPress = {
onDecline()
playDownSound(Minecraft.getInstance().soundManager)
}
it.left = { controllerComponentManager.componentList["accept_button"] }
}
controllerComponentManager.let {
it.focused = it.componentList["decline_button"]
it.onBack = {
it.componentList["decline_button"]?.onPress?.invoke()
}
}
}
fun onAccept() {
battleGUI.selectAction(request, ForfeitActionResponse())
// Need to fill out any other pending requests
var pendingRequest = CobblemonClient.battle?.getFirstUnansweredRequest()
while (pendingRequest != null) {
battleGUI.selectAction(pendingRequest, PassActionResponse)
pendingRequest = CobblemonClient.battle?.getFirstUnansweredRequest()
}
}
fun onDecline() {
battleGUI.changeActionSelection(null)
}
override fun mousePrimaryClicked(mouseX: Double, mouseY: Double): Boolean {
if (declineButton.isHovered(mouseX, mouseY) || acceptButton.isHovered(mouseX, mouseY)) {
if (declineButton.isHovered(mouseX, mouseY)) {
battleGUI.changeActionSelection(null)
onDecline()
} else {
battleGUI.selectAction(request, ForfeitActionResponse())
// Need to fill out any other pending requests
var pendingRequest = CobblemonClient.battle?.getFirstUnansweredRequest()
while (pendingRequest != null) {
battleGUI.selectAction(pendingRequest, PassActionResponse)
pendingRequest = CobblemonClient.battle?.getFirstUnansweredRequest()
}
onAccept()
}
playDownSound(Minecraft.getInstance().soundManager)
return true
@@ -50,7 +50,7 @@ class BattleOptionTile(
alpha = opacity,
width = OPTION_WIDTH,
height = OPTION_HEIGHT,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) OPTION_HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || focused) OPTION_HEIGHT else 0,
textureHeight = OPTION_HEIGHT * 2
)
@@ -80,7 +80,7 @@ class BattleOptionTile(
override fun isFocused() = focused
fun isHovered(mouseX: Double, mouseY: Double) = mouseX > x && mouseY > y && mouseX < x + OPTION_WIDTH && mouseY < y + OPTION_HEIGHT
fun isHovered(mouseX: Double, mouseY: Double) = (mouseX > x && mouseY > y && mouseX < x + OPTION_WIDTH && mouseY < y + OPTION_HEIGHT)
override fun updateNarration(builder: NarrationElementOutput) {
builder.add(NarratedElementType.TITLE, text)
@@ -10,6 +10,10 @@ package com.cobblemon.mod.common.client.gui.config
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.config.variable.CobblemonConfigVariable
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.config.CobblemonConfig
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.ifServer
@@ -22,11 +26,14 @@ import net.minecraft.client.gui.components.StringWidget
import net.minecraft.client.gui.layouts.HeaderAndFooterLayout
import net.minecraft.client.gui.layouts.LinearLayout
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.CommonComponents
import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvents
class CobblemonConfigScreen(
private val previousScreen: Screen?,
) : Screen("gui.cobblemon_config.title".asTranslated()), CobblemonRenderable {
) : Screen("gui.cobblemon_config.title".asTranslated()), CobblemonRenderable, ControllerScreen {
companion object {
const val HEIGHT = 240
@@ -55,6 +62,13 @@ class CobblemonConfigScreen(
private var searchString: String = ""
val controllerComponentManager = ControllerComponentManager(this)
var lastSelectedEntry = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun init() {
super.init()
@@ -98,6 +112,46 @@ class CobblemonConfigScreen(
addRenderableWidget(widget)
}
arrangeLayoutElements()
controllerComponentManager.componentList["search"] = ControllerComponent().also {
it.associatedGUIElement = searchEdit
it.down = {
val variableEntries = variableList.children().filter { it is CobblemonConfigVariable }
if (variableEntries.isNotEmpty()) {
controllerComponentManager.componentList["entry_${lastSelectedEntry}_input"]
} else {
controllerComponentManager.componentList["done"]
}
}
}
controllerComponentManager.componentList["done"] = ControllerComponent().also {
it.associatedGUIElement = doneButton
it.up = {
val variableEntries = variableList.children().filter { it is CobblemonConfigVariable }
if (variableEntries.isNotEmpty()) {
controllerComponentManager.componentList["entry_${lastSelectedEntry}_input"]
} else {
controllerComponentManager.componentList["search"]
}
}
}
controllerComponentManager.let {
it.focused = it.componentList["search"]
it.onBack = {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 1.0F))
doneButtonClick()
}
it.onStart = {
it.focused = it.componentList["search"]
it.openKeyboard.invoke(it.focused!!.associatedGUIElement as EditBox)
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 1.0F))
}
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.START,
{ true },
Component.translatable("cobblemon.controller.quick_search")
))
}
}
private fun arrangeLayoutElements() {
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.client.gui.config.CobblemonConfigScreen.Companio
import com.cobblemon.mod.common.client.gui.config.CobblemonConfigScreen.Companion.SLOT_WIDTH
import com.cobblemon.mod.common.client.gui.config.CobblemonConfigVariableList.CobblemonConfigVariableListEntry
import com.cobblemon.mod.common.client.gui.config.variable.*
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.config.Category
import com.cobblemon.mod.common.config.CobblemonConfig
@@ -134,6 +135,63 @@ class CobblemonConfigVariableList(
}
scrollAmount = min(maxScroll.toDouble(), scrollAmount)
val variableEntries = children().filter { it is CobblemonConfigVariable }
variableEntries.forEachIndexed { index, entry ->
parent.controllerComponentManager.componentList["entry_${index}_input"] = ControllerComponent().also {
when (entry) {
is CobblemonConfigButtonVariable -> it.associatedGUIElement = entry.button
is CobblemonConfigEditBoxVariable -> it.associatedGUIElement = entry.editBox
}
it.right = {
if ((entry as CobblemonConfigVariable).resetButton.active) {
parent.controllerComponentManager.componentList["entry_${index}_reset"]
}
else null
}
if (index + 1 <= variableEntries.size - 1) it.down = { parent.controllerComponentManager.componentList["entry_${index+1}_input"] }
else it.down = { parent.controllerComponentManager.componentList["done"] }
if (index - 1 >= 0) it.up = { parent.controllerComponentManager.componentList["entry_${index-1}_input"] }
else it.up = { parent.controllerComponentManager.componentList["search"] }
it.onSelect = { parent.lastSelectedEntry = index }
it.onFocus = { ensureVisible(entry) }
}
parent.controllerComponentManager.componentList["entry_${index}_reset"] = ControllerComponent().also {
it.associatedGUIElement = (entry as CobblemonConfigVariable).resetButton
it.left = { parent.controllerComponentManager.componentList["entry_${index}_input"] }
if (index + 1 <= variableEntries.size - 1) it.down = {
if ((variableEntries[index+1] as CobblemonConfigVariable).resetButton.active) {
parent.controllerComponentManager.componentList["entry_${index+1}_reset"]
}
else null
}
else it.down = { parent.controllerComponentManager.componentList["done"] }
if (index - 1 >= 0) it.up = {
if ((variableEntries[index-1] as CobblemonConfigVariable).resetButton.active) {
parent.controllerComponentManager.componentList["entry_${index-1}_reset"]
}
else null
}
else it.up = { parent.controllerComponentManager.componentList["search"] }
it.onPress = {
entry.resetButton.also { button ->
button.onPress()
button.playDownSound(Minecraft.getInstance().soundManager)
parent.controllerComponentManager.also { manager ->
manager.focused = manager.componentList["entry_${index}_input"]
}
}
}
it.onSelect = { parent.lastSelectedEntry = index }
it.onFocus = { ensureVisible(entry) }
}
}
parent.controllerComponentManager.let {
if (it.currentSelectedComponent.startsWith("entry_")) {
it.focused = it.componentList["search"]
}
}
parent.lastSelectedEntry = 0
}
abstract class CobblemonConfigVariableListEntry(
@@ -26,6 +26,9 @@ import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueOptionWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialoguePortraitWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueTextInputWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueTimerWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.net.messages.client.dialogue.dto.DialogueDTO
import com.cobblemon.mod.common.net.messages.client.dialogue.dto.DialogueGibberDTO
@@ -45,9 +48,10 @@ import net.minecraft.client.gui.components.EditBox
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvent
class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTranslated()), CobblemonRenderable {
class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTranslated()), CobblemonRenderable, ControllerScreen {
val speakers = dialogueDTO.speakers?.mapNotNull { (key, value) ->
val name = value.name
when (val face = value.face) {
@@ -141,6 +145,12 @@ class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTra
)
}
var controllerComponentManager = ControllerComponentManager(this)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun renderBackground(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {}
override fun renderBlurredBackground(delta: Float) {}
@@ -242,6 +252,34 @@ class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTra
}
this.dialogueStartTick = minecraft!!.player!!.tickCount
controllerComponentManager = ControllerComponentManager(this)
controllerComponentManager.componentList["none"] = ControllerComponent().also {
it.onPress = { dialogueBox.onClick() }
}
dialogueOptionWidgets.forEachIndexed { index, option ->
controllerComponentManager.componentList["option_${index}"] = ControllerComponent().also {
it.associatedGUIElement = option
if (index + 1 <= dialogueOptionWidgets.size - 1) it.right = { controllerComponentManager.componentList["option_${index+1}"] }
if (index - 1 >= 0) it.left = { controllerComponentManager.componentList["option_${index-1}"] }
it.onPress = { if (!option.onClick()) dialogueBox.onClick() }
}
}
controllerComponentManager.let {
it.focused = if (dialogueOptionWidgets.isNotEmpty()) {
it.componentList["option_0"]
} else {
it.componentList["none"]
}
it.onSpecial1 = { dialogueBox.onClick() }
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ dialogueBox.dialogue.dialogueInput.allowSkip && dialogueBox.dialogue.dialogueInput.inputType in listOf(DialogueInputDTO.InputType.NONE, DialogueInputDTO.InputType.AUTO_CONTINUE)},
Component.translatable("cobblemon.controller.dialogue.skip"),
true
))
}
}
var gibberIndex = 0
@@ -265,6 +303,8 @@ class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTra
super.render(guiGraphics, mouseX, mouseY, delta)
expressionsToRun.forEach { runtime.resolve(it) }
expressionsToRun = emptyList()
renderSelection(guiGraphics.pose())
}
override fun isPauseScreen() = false
@@ -160,10 +160,17 @@ class DialogueBox(
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
// TODO change this coordinate check to just be "anywhere the scroll bar isn't"
if (!dialogueScreen.waitingForServerUpdate &&
mouseX > this.x && mouseX < this.scrollbarPosition &&
if (mouseX > this.x && mouseX < this.scrollbarPosition &&
mouseY > this.y && mouseY < this.bottom
) {
if (onClick()) return true
}
return super.mouseClicked(mouseX, mouseY, button)
}
fun onClick(): Boolean {
if (!dialogueScreen.waitingForServerUpdate) {
if (dialogue.dialogueInput.allowSkip && dialogue.dialogueInput.inputType in listOf(DialogueInputDTO.InputType.NONE, DialogueInputDTO.InputType.AUTO_CONTINUE)) {
val gibber = dialogueScreen.gibber
if (gibber != null && gibber.graduallyShowText && !dialogueScreen.gibberDone) {
@@ -177,8 +184,7 @@ class DialogueBox(
return true
}
}
return super.mouseClicked(mouseX, mouseY, button)
return false
}
class DialogueLine(val line: FormattedCharSequence, val lineColor: Int) : Entry<DialogueLine>() {
@@ -42,7 +42,7 @@ class DialogueOptionWidget(
y = y,
width = width,
height = height,
vOffset = if (selectable) (if (isHovered) height else 0) else (height * 2),
vOffset = if (selectable) (if (isHovered || isFocused) height else 0) else (height * 2),
textureHeight = height * 3,
)
@@ -58,10 +58,14 @@ class DialogueOptionWidget(
}
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (!isHovered) return false
return onClick()
}
fun onClick(): Boolean {
if (!dialogueScreen.renderInput()) {
return false
}
if (!isHovered) return false
if (!selectable || dialogueScreen.waitingForServerUpdate) return true
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.client.gui.interact.battleRequest
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.ColourLibrary
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
@@ -16,6 +17,9 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.ClientBattleChallenge
import com.cobblemon.mod.common.client.battle.ClientTeamRequest
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
import com.cobblemon.mod.common.net.messages.server.*
@@ -27,16 +31,18 @@ import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.Screen
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 java.util.*
class BattleConfigureGUI(
private val packet: PlayerInteractOptionsPacket,
private val activeRequest: ClientBattleChallenge? = null,
private val activeTeamRequest: ClientTeamRequest? = null
) : Screen(lang("ui.challenge.challenge_title")), CobblemonRenderable {
) : Screen(lang("ui.challenge.challenge_title")), CobblemonRenderable, ControllerScreen {
companion object {
const val SIZE = 113
private val backgroundResource = cobblemonResource("textures/gui/interact/request/battle_request.png")
@@ -200,6 +206,12 @@ class BattleConfigureGUI(
private var hasRequest = false
private var ticksPassed = 0F
val controllerComponentManager = ControllerComponentManager(this)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun renderBlurredBackground(delta: Float) { }
override fun renderMenuBackground(context: GuiGraphics) { }
@@ -218,29 +230,29 @@ class BattleConfigureGUI(
}
val (x, y) = getDimensions()
var battleAccept: BattleResponseButton? = null
var battleDecline: BattleResponseButton? = null
if(pendingRequest != null) {
// Draw Accept/Decline buttons
this.addRenderableWidget(
BattleResponseButton(
x + 22,
y + 99,
true
) {
battleRequestMap[options[currentPage]]?.onResponse?.let { it1 -> it1(packet, pendingRequest.requestID, true) }
closeGUI()
}
)
battleAccept = BattleResponseButton(
x + 22,
y + 99,
true
) {
battleRequestMap[options[currentPage]]?.onResponse?.let { it1 -> it1(packet, pendingRequest.requestID, true) }
closeGUI()
}
this.addRenderableWidget(battleAccept)
this.addRenderableWidget(
BattleResponseButton(
x + 56,
y + 99,
false
) {
battleRequestMap[options[currentPage]]?.onResponse?.let { it1 -> it1(packet, pendingRequest.requestID, false) }
closeGUI()
}
)
battleDecline = BattleResponseButton(
x + 56,
y + 99,
false
) {
battleRequestMap[options[currentPage]]?.onResponse?.let { it1 -> it1(packet, pendingRequest.requestID, false) }
closeGUI()
}
this.addRenderableWidget(battleDecline)
}
else {
@@ -306,6 +318,36 @@ class BattleConfigureGUI(
this.addRenderableWidget(autoLevelNavButtonRight)
}
}
controllerComponentManager.componentList["battle_accept"] = ControllerComponent().also {
it.associatedGUIElement = battleAccept
it.right = { controllerComponentManager.componentList["battle_decline"] }
}
controllerComponentManager.componentList["battle_decline"] = ControllerComponent().also {
it.associatedGUIElement = battleDecline
it.left = { controllerComponentManager.componentList["battle_accept"] }
}
controllerComponentManager.componentList["request"] = ControllerComponent().also {
it.associatedGUIElement = requestButton
}
controllerComponentManager.let {
if (pendingRequest != null) {
it.focused = it.componentList["battle_decline"]
} else {
it.focused = it.componentList["request"]
it.onTabNav = { nextTab ->
currentPage = (currentPage + (if (nextTab) 1 else -1)) % options.count()
updateRequestButtonText()
playSound(CobblemonSounds.GUI_CLICK)
}
if (battleRequestMap[options[currentPage]]?.option != PlayerInteractOptionsPacket.Options.TEAM_REQUEST) {
it.onSecondaryTabNav = { nextTab ->
levelRulesetOptionIndex += if (nextTab) 1 else -1
playSound(CobblemonSounds.GUI_CLICK)
}
}
}
}
}
fun updateRequestButtonText() {
@@ -438,6 +480,8 @@ class BattleConfigureGUI(
)
super.render(context, mouseX, mouseY, delta)
renderSelection(context.pose())
}
private fun getDimensions(): Pair<Int, Int> {
@@ -454,4 +498,7 @@ class BattleConfigureGUI(
Minecraft.getInstance().setScreen(null)
}
fun playSound(soundEvent: SoundEvent) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
}
}
@@ -35,7 +35,7 @@ class BattleResponseButton(
}
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
val hovered = (isHovered(pMouseX.toDouble(), pMouseY.toDouble()))
val hovered = (isHovered(pMouseX.toDouble(), pMouseY.toDouble()) || isFocused)
blitk(
matrixStack = context.pose(),
x = x / SCALE,
@@ -14,6 +14,9 @@ import com.cobblemon.mod.common.api.callback.MoveSelectDTO
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
import com.cobblemon.mod.common.util.cobblemonResource
@@ -36,7 +39,7 @@ class MoveSelectConfiguration(
class MoveSelectGUI(
val config: MoveSelectConfiguration
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable {
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable, ControllerScreen {
companion object {
const val WIDTH = 122
const val HEIGHT = 133
@@ -46,6 +49,8 @@ class MoveSelectGUI(
var closed = false
val controllerComponentManager = ControllerComponentManager(this)
constructor(
title: MutableComponent,
moves: List<MoveSelectDTO>,
@@ -63,6 +68,10 @@ class MoveSelectGUI(
)
)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
fun closeProperly() {
closed = true
onClose()
@@ -73,16 +82,21 @@ class MoveSelectGUI(
val y = (height - HEIGHT) / 2
config.moves.forEachIndexed { index, move ->
addRenderableWidget(
MoveSlotButton(
x = x + 7,
y = y + 7 + ((MoveSlotButton.HEIGHT + 3) * index),
move = move.moveTemplate,
pp = move.pp,
ppMax = move.ppMax,
enabled = move.enabled
) { onPress(move) }
)
val moveSlot = MoveSlotButton(
x = x + 7,
y = y + 7 + ((MoveSlotButton.HEIGHT + 3) * index),
move = move.moveTemplate,
pp = move.pp,
ppMax = move.ppMax,
enabled = move.enabled
) { onPress(move) }
addRenderableWidget(moveSlot)
controllerComponentManager.componentList["move_${index}"] = ControllerComponent().also {
it.associatedGUIElement = moveSlot
if (index + 1 <= config.moves.size - 1) it.down = { controllerComponentManager.componentList["move_${index+1}"] }
if (index - 1 >= 0) it.up = { controllerComponentManager.componentList["move_${index-1}"] }
}
}
// Add Exit Button
@@ -97,6 +111,10 @@ class MoveSelectGUI(
)
super.init()
controllerComponentManager.let {
it.focused = it.componentList["move_0"]
}
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
@@ -114,6 +132,8 @@ class MoveSelectGUI(
// Render all added Widgets
super.render(context, mouseX, mouseY, partialTicks)
renderSelection(context.pose())
}
private fun onPress(move: MoveSelectDTO) {
@@ -16,6 +16,9 @@ import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonSelectedPacket
import com.cobblemon.mod.common.net.messages.server.callback.party.PartySelectCancelledPacket
@@ -38,7 +41,7 @@ class PartySelectConfiguration(
)
class PartySelectGUI(
val config: PartySelectConfiguration
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable {
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable, ControllerScreen {
companion object {
const val WIDTH = 163
const val HEIGHT = 132
@@ -50,6 +53,8 @@ class PartySelectGUI(
var closed = false
val controllerComponentManager = ControllerComponentManager(this)
constructor(
title: MutableComponent,
pokemon: List<PartySelectPokemonDTO>,
@@ -67,6 +72,10 @@ class PartySelectGUI(
)
)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
fun closeProperly() {
closed = true
onClose()
@@ -90,20 +99,29 @@ class PartySelectGUI(
slotY += (31 * offsetIndex) + offsetY
}
addRenderableWidget(
PartySlotButton(
parent = this,
x = slotX,
y = slotY,
pokemon = pokemon.pokemonProperties,
aspects = pokemon.aspects,
heldItem = pokemon.heldItem,
currentHealth = pokemon.currentHealth,
maxHealth = pokemon.maxHealth,
enabled = pokemon.enabled,
hoverText = pokemon.hoverText,
) { onPress(pokemon) }
)
val slot = PartySlotButton(
parent = this,
x = slotX,
y = slotY,
pokemon = pokemon.pokemonProperties,
aspects = pokemon.aspects,
heldItem = pokemon.heldItem,
currentHealth = pokemon.currentHealth,
maxHealth = pokemon.maxHealth,
enabled = pokemon.enabled,
hoverText = pokemon.hoverText,
) { onPress(pokemon) }
addRenderableWidget(slot)
val row = index / 2
val column = index % 2
controllerComponentManager.componentList["slot_${row}_${column}"] = ControllerComponent().also {
it.associatedGUIElement = slot
if (column == 0) it.right = { controllerComponentManager.componentList["slot_${row}_${1}"] }
else it.left = { controllerComponentManager.componentList["slot_${row}_${0}"] }
if (row - 1 >= 0) it.up = { controllerComponentManager.componentList["slot_${row-1}_${column}"] }
if (row + 1 <= 2) it.down = { controllerComponentManager.componentList["slot_${row+1}_${column}"] }
}
}
// Add Exit Button
@@ -118,6 +136,10 @@ class PartySelectGUI(
)
super.init()
controllerComponentManager.let {
it.focused = it.componentList["slot_0_0"]
}
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
@@ -155,6 +177,8 @@ class PartySelectGUI(
// Render all added Widgets
super.render(context, mouseX, mouseY, partialTicks)
renderSelection(context.pose())
}
private fun onPress(pokemon: PartySelectPokemonDTO) {
@@ -63,7 +63,7 @@ class PartySlotButton(
private val renderablePokemon = pokemon.asRenderablePokemon().also { it.aspects = aspects }
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
isHovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height && enabled
isHovered = ((pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height) || isFocused) && enabled
val alpha = if (enabled) 1.0 else 0.7
val matrices = context.pose()
@@ -28,7 +28,7 @@ class InteractWheelButton(
private val tooltipText: String?,
x: Int,
y: Int,
private val isEnabled: Boolean,
val isEnabled: Boolean,
private val colour: () -> Vector3f?,
onPress: OnPress,
private val canHover: (Double, Double) -> Boolean
@@ -74,12 +74,12 @@ class InteractWheelButton(
y = y,
width = width,
height = height,
vOffset = if (isHovered(mouseX.toFloat(), mouseY.toFloat()) && isEnabled) height else 0,
vOffset = if ((isHovered(mouseX.toFloat(), mouseY.toFloat()) || isFocused) && isEnabled) height else 0,
textureHeight = height * 2,
alpha = if (isEnabled) 1f else 0.4F
)
if(isEnabled && isHovered(mouseX.toFloat(), mouseY.toFloat())){
if(isEnabled && (isHovered(mouseX.toFloat(), mouseY.toFloat()) || isFocused)){
tooltipText?.let {
context.renderTooltip(Minecraft.getInstance().font, Component.translatable(it), mouseX, mouseY)
}
@@ -162,7 +162,7 @@ class InteractWheelButton(
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
return if (isHovered(mouseX.toFloat(), mouseY.toFloat())) super.mouseClicked(mouseX, mouseY, button) else false
return if (isHovered(mouseX.toFloat(), mouseY.toFloat()) || isFocused) super.mouseClicked(mouseX, mouseY, button) else false
}
override fun getTooltip(): Tooltip? {
@@ -32,9 +32,10 @@ class InteractWheelGUI(private val options: Multimap<Orientation, InteractWheelO
return super.keyPressed(keyCode, scanCode, modifiers)
}
private val buttons = mutableListOf<InteractWheelButton>()
val buttons = mutableListOf<InteractWheelButton>()
private var maxPage = 1
private var currentPage = 0
override fun renderBlurredBackground(delta: Float) { }
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -86,7 +87,7 @@ class InteractWheelGUI(private val options: Multimap<Orientation, InteractWheelO
currentPage = page
buttons.forEach { removeWidget(it) }
buttons.clear()
val orientations = Orientation.values()
val orientations = Orientation.entries.toTypedArray()
orientations.forEach { orientation ->
val option = options[orientation].toList().getOrNull(page)
addButton(orientation, option)
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pc.StorageSlot
import com.cobblemon.mod.common.client.gui.summary.widgets.PartySlotWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
@@ -76,11 +77,58 @@ class PasturePokemonScrollList(
removedEntries.forEach(this::removeEntry)
newEntries.forEach { addEntry(PastureSlot(it, parent)) }
rebuildControllerSelection()
}
}
fun rebuildControllerSelection() {
if (parent.lastSelectedIndex > 0 && parent.lastSelectedIndex >= children().size) parent.lastSelectedIndex = children().size - 1
children().forEachIndexed { index, slot ->
parent.controllerComponentManager.componentList["slot_${index}"] = ControllerComponent().also {
it.associatedGUIElement = slot
if (index - 1 >= 0) it.up = { parent.controllerComponentManager.componentList["slot_${index - 1}"] }
if (index + 1 <= children().size - 1) it.down =
{ parent.controllerComponentManager.componentList["slot_${index + 1}"] }
else it.down = { parent.controllerComponentManager.componentList["recall_all"] }
it.onPress = { slot.moveButton.onPress() }
it.onSpecial1 = { slot.conflictButton.onPress() }
it.onFocus = {
slot.controllerFocused = true
ensureVisible(slot)
}
it.onUnfocus = {
slot.controllerFocused = false
}
}
}
if (parent.controllerComponentManager.currentSelectedComponent.let { it.startsWith("slot_") || it == "empty" }) {
parent.controllerComponentManager.focused =
if (children().isEmpty()) parent.controllerComponentManager.componentList["empty"]
else parent.controllerComponentManager.componentList["slot_${parent.lastSelectedIndex}"]
}
}
override fun getScrollbarPosition() = x + width - 3
public override fun ensureVisible(entry: PastureSlot) {
val i = getRowTop(children().indexOf(entry))
val topDifference = i - y
if (topDifference < 0) {
scroll(topDifference)
}
val bottomDifference = bottom - i - itemHeight - 4
if (bottomDifference < 0) {
scroll(-bottomDifference)
}
}
public override fun getRowTop(index: Int): Int {
return super.getRowTop(index)
}
public override fun addEntry(entry: PastureSlot) = super.addEntry(entry)
public override fun removeEntry(entry: PastureSlot) = super.removeEntry(entry)
override fun renderSelection(context: GuiGraphics, y: Int, entryWidth: Int, entryHeight: Int, borderColor: Int, fillColor: Int) {}
@@ -166,6 +214,8 @@ class PasturePokemonScrollList(
val client: Minecraft = Minecraft.getInstance()
val state = FloatingState()
var controllerFocused = false
fun isOwned() = client.player?.uuid == pokemon.playerId
fun canUnpasture() = isOwned() || parent.pasturePCGUIConfiguration.permissions.canUnpastureOthers
fun canDefend() = PokemonSpecies.getByIdentifier(pokemon.species)?.behaviour?.combat?.willDefendOwner
@@ -182,7 +232,7 @@ class PasturePokemonScrollList(
setEnabled(PokemonBehaviourFlag.PASTURE_CONFLICT in pokemon.behaviourFlags)
}
private val moveButton: PastureSlotIconButton = PastureSlotIconButton(
val moveButton: PastureSlotIconButton = PastureSlotIconButton(
xPos = 0,
yPos = 0,
onPress = {
@@ -191,7 +241,8 @@ class PasturePokemonScrollList(
pastureId = parent.pasturePCGUIConfiguration.pastureId,
pokemonId = pokemon.pokemonId
).sendToServer()
}
},
parent = this@PastureSlot
)
override fun getNarration() = pokemon.displayName
@@ -219,7 +270,7 @@ class PasturePokemonScrollList(
y = y,
height = SLOT_HEIGHT,
width = rowWidth,
vOffset = if (isHovered) SLOT_HEIGHT else 0,
vOffset = if (isHovered || controllerFocused) SLOT_HEIGHT else 0,
textureHeight = SLOT_HEIGHT * 2
)
@@ -18,7 +18,8 @@ import net.minecraft.network.chat.Component
class PastureSlotIconButton(
var xPos: Int, var yPos: Int,
onPress: OnPress
onPress: OnPress,
var parent: PasturePokemonScrollList.PastureSlot
) : Button(xPos, yPos, (SIZE * SCALE).toInt(), (SIZE * SCALE).toInt(), Component.literal("Pasture Move"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
@@ -35,7 +36,7 @@ class PastureSlotIconButton(
y = yPos / SCALE,
width = SIZE,
height = SIZE,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) SIZE else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || parent.controllerFocused) SIZE else 0,
textureHeight = SIZE * 2,
texture = baseResource,
scale = SCALE
@@ -15,6 +15,8 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.pc.PCGUI
import com.cobblemon.mod.common.client.gui.pc.StorageWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.pasture.UnpastureAllPokemonPacket
import com.cobblemon.mod.common.util.cobblemonResource
@@ -34,6 +36,9 @@ class PastureWidget(
private val baseResource = cobblemonResource("textures/gui/pasture/pasture_panel.png")
}
val controllerComponentManager = ControllerComponentManager(storageWidget.pcGui)
var lastSelectedIndex = 0
private val recallButton = RecallButton(
x = x + 6,
y = y + 153
@@ -48,6 +53,23 @@ class PastureWidget(
parent = this
)
init {
controllerComponentManager.componentList["recall_all"] = ControllerComponent().also {
it.associatedGUIElement = recallButton
it.up = {
if (pastureScrollList.children().isEmpty()) controllerComponentManager.componentList["empty"]
else controllerComponentManager.componentList["slot_${pastureScrollList.children().size-1}"]
}
}
controllerComponentManager.componentList["empty"] = ControllerComponent().also {
it.down = { controllerComponentManager.componentList["recall_all"] }
}
controllerComponentManager.let {
it.focused = if (pastureScrollList.children().isEmpty()) it.componentList["empty"]
else it.componentList["slot_0"]
}
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
blitk(
matrixStack = context.pose(),
@@ -40,7 +40,7 @@ class RecallButton(
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || isFocused) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.pasture.PasturePokemonScrollList
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.Summary.Companion.iconCosmeticItemResource
import com.cobblemon.mod.common.client.gui.summary.Summary.Companion.iconHeldItemResource
@@ -28,6 +29,10 @@ import com.cobblemon.mod.common.client.gui.summary.SummaryButton
import com.cobblemon.mod.common.client.gui.summary.widgets.MarkingsWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.common.reformatNatureTextIfMinted
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.integration.controlify.gui.component.SubManagerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.storage.ClientPC
import com.cobblemon.mod.common.client.storage.ClientParty
@@ -44,12 +49,15 @@ import com.cobblemon.mod.common.util.toAssetPath
import com.mojang.blaze3d.platform.InputConstants
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.EditBox
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvent
import kotlin.isInitialized
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvents
import org.joml.Vector4i
class PCGUI(
val pc: ClientPC,
@@ -57,7 +65,7 @@ class PCGUI(
val configuration: PCGUIConfiguration,
val openOnBox: Int = CobblemonClient.lastPcBoxViewed,
val unseenWallpapers: MutableSet<ResourceLocation> = mutableSetOf()
) : Screen(Component.translatable("cobblemon.ui.pc.title")), CobblemonRenderable {
) : Screen(Component.translatable("cobblemon.ui.pc.title")), CobblemonRenderable, ControllerScreen {
companion object {
const val BASE_WIDTH = 349
@@ -128,6 +136,14 @@ class PCGUI(
var selectPointerOffsetIncrement = false
var displayOptions = false
val controllerComponentManager = ControllerComponentManager(this)
var lastSelectedBeforeOptions = ""
var controllerShiftToggle = false
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun renderBlurredBackground(delta: Float) {}
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -198,30 +214,39 @@ class PCGUI(
this.addRenderableWidget(boxNameWidget)
// Initialise box options if not pasture
var optionsButton: IconButton? = null
var wallpaperButton: IconButton? = null
if (storageWidget.pastureWidget == null) {
optionButtons.clear()
addRenderableWidget(
IconButton(
pX = x + 218,
pY = y + 186,
buttonWidth = 16,
buttonHeight = 16,
resource = buttonOptionsResource,
label = "options"
) {
displayOptions = !displayOptions
if (!displayOptions && wallpaperWidget.visible) {
configuration.showParty = true
wallpaperWidget.visible = false
}
(it as IconButton).highlighted = displayOptions
storageWidget.setupStorageSlots()
for (button in optionButtons) {
button.visible = displayOptions
button.highlighted = false
}
}.also { it.highlighted = displayOptions }
)
optionsButton = IconButton(
pX = x + 218,
pY = y + 186,
buttonWidth = 16,
buttonHeight = 16,
resource = buttonOptionsResource,
label = "options"
) {
if (!displayOptions) {
lastSelectedBeforeOptions = controllerComponentManager.currentSelectedComponent
controllerComponentManager.focused = controllerComponentManager.componentList["setting_button_0"]
controllerShiftToggle = false
} else {
controllerComponentManager.focused = controllerComponentManager.componentList[lastSelectedBeforeOptions]
}
displayOptions = !displayOptions
if (!displayOptions && wallpaperWidget.visible) {
configuration.showParty = true
wallpaperWidget.visible = false
}
(it as IconButton).highlighted = displayOptions
storageWidget.setupStorageSlots()
for (button in optionButtons) {
button.visible = displayOptions
button.highlighted = false
}
}.also { it.highlighted = displayOptions }
addRenderableWidget(optionsButton)
// Add Wallpaper Widget
this.wallpaperWidget = WallpapersScrollingWidget(
@@ -237,7 +262,7 @@ class PCGUI(
this.addRenderableWidget(wallpaperWidget)
// Add Wallpaper Settings Button
this.addRenderableWidget(IconButton(
wallpaperButton = IconButton(
pX = x + 242,
pY = y + 31,
buttonWidth = 20,
@@ -250,14 +275,16 @@ class PCGUI(
wallpaperWidget.visible = !isVisible
(it as IconButton).highlighted = !isVisible
if (!unseenWallpapers.isEmpty()) MarkPCBoxWallpapersSeenPacket(unseenWallpapers).sendToServer()
controllerComponentManager.focused = if (wallpaperWidget.visible) controllerComponentManager.componentList["wallpaper_widget"] else controllerComponentManager.componentList["wallpaper_button"]
}.also {
it.visible = displayOptions
optionButtons.add(it)
})
}
this.addRenderableWidget(wallpaperButton)
PokemonSortMode.entries.forEachIndexed { index, sortType ->
val typeName = sortType.name.lowercase()
this.addRenderableWidget(IconButton(
val button = IconButton(
pX = x + 92 + (12 * index),
pY = y + 31,
buttonWidth = 20,
@@ -268,33 +295,58 @@ class PCGUI(
label = "sort_${typeName}"
) {
storageWidget.resetSelected()
SortPCBoxPacket(pc.uuid, storageWidget.box, sortType, hasShiftDown()).sendToServer()
SortPCBoxPacket(pc.uuid, storageWidget.box, sortType, hasShiftDown() || (controllerComponentManager.active && controllerShiftToggle)).sendToServer()
}.also {
it.visible = displayOptions
optionButtons.add(it)
})
}
this.addRenderableWidget(button)
controllerComponentManager.componentList["setting_button_${index}"] = ControllerComponent().also {
it.associatedGUIElement = button
if (index - 1 >= 0) it.left = { controllerComponentManager.componentList["setting_button_${index-1}"] }
if (index + 1 <= PokemonSortMode.entries.size - 1) it.right = { controllerComponentManager.componentList["setting_button_${index+1}"] }
else it.right = { controllerComponentManager.componentList["wallpaper_button"] }
it.onSpecial1 = {
controllerShiftToggle = !controllerShiftToggle
playSound(CobblemonSounds.PC_CLICK)
}
}
}
controllerComponentManager.componentList["wallpaper_button"] = ControllerComponent().also {
it.associatedGUIElement = wallpaperButton
it.left = { controllerComponentManager.componentList["setting_button_${PokemonSortMode.entries.size - 1}"] }
}
controllerComponentManager.componentList["wallpaper_widget"] = SubManagerComponent().also {
it.subManager = { wallpaperWidget.controllerComponentManager }
it.onSpecial1 = {
controllerShiftToggle = !controllerShiftToggle
playSound(CobblemonSounds.PC_CLICK)
}
}
}
this.markingsWidget = MarkingsWidget(x + 29, y + 96.5, null, false)
this.addRenderableWidget(markingsWidget)
// Held/Cosmetic Item Button
addRenderableWidget(
SummaryButton(
buttonX = x + 67F,
buttonY = y + 107F,
buttonWidth = 12,
buttonHeight = 12,
scale = 0.5F,
resource = iconCosmeticItemResource,
activeResource = iconHeldItemResource,
clickAction = {
showCosmeticItem = !showCosmeticItem
(it as SummaryButton).buttonActive = showCosmeticItem
}
)
val switchCosmetic = SummaryButton(
buttonX = x + 67F,
buttonY = y + 107F,
buttonWidth = 12,
buttonHeight = 12,
scale = 0.5F,
resource = iconCosmeticItemResource,
activeResource = iconHeldItemResource,
clickAction = {
showCosmeticItem = !showCosmeticItem
(it as SummaryButton).buttonActive = showCosmeticItem
}
)
// Held/Cosmetic Item Button
addRenderableWidget(switchCosmetic)
addRenderableWidget(
IconButton(
@@ -312,6 +364,149 @@ class PCGUI(
this.setPreviewPokemon(null)
super.init()
controllerComponentManager.componentList["box_name"] = ControllerComponent().also {
it.associatedGUIElement = boxNameWidget
it.down = { controllerComponentManager.componentList["storage"] }
}
controllerComponentManager.componentList["storage"] = SubManagerComponent().also {
it.subManager = { storageWidget.controllerComponentManager }
it.up = {
var returnVal: ControllerComponent? = null
val manager = storageWidget.controllerComponentManager
if (manager.up() && manager.currentSelectedComponent.startsWith("slot") && storageWidget.grabbedSlot == null) {
returnVal = controllerComponentManager.componentList["box_name"]
}
returnVal
}
it.down = {
var returnVal: ControllerComponent? = null
val manager = storageWidget.controllerComponentManager
if (manager.down() && manager.currentSelectedComponent.startsWith("slot") && storageWidget.grabbedSlot == null) {
returnVal = controllerComponentManager.componentList["filter"]
}
returnVal
}
it.onSpecial1 = {
if (storageWidget.controllerComponentManager.currentSelectedComponent == "pasture") {
storageWidget.controllerComponentManager.special1()
} else {
switchCosmetic.onPress()
playSound(CobblemonSounds.PC_CLICK)
}
}
it.onSpecial2 = { if (storageWidget.pastureWidget == null) displayOptions = !displayOptions }
}
controllerComponentManager.componentList["filter"] = ControllerComponent().also {
it.associatedGUIElement = filterWidget
it.up = { controllerComponentManager.componentList["storage"] }
}
controllerComponentManager.let {
it.focused = it.componentList["storage"]
it.onTabNav = { nextTab ->
storageWidget.box += if (nextTab) 1 else -1
playSound(CobblemonSounds.PC_CLICK)
}
it.onSecondaryTabNav = { nextTab ->
var nextValue = currentStatIndex + if (nextTab) 1 else -1
if (nextValue > 2) nextValue = 0
if (nextValue < 0) nextValue = 2
currentStatIndex = nextValue
playSound(CobblemonSounds.PC_CLICK)
}
it.onStart = {
if (it.currentSelectedComponent != "storage" || storageWidget.grabbedSlot == null) {
it.focused = controllerComponentManager.componentList["filter"]
storageWidget.controllerComponentManager.focused =
storageWidget.controllerComponentManager.componentList["slot_4_0"]
it.openKeyboard.invoke(it.focused!!.associatedGUIElement as EditBox)
playSound(SoundEvents.UI_BUTTON_CLICK.value())
}
}
it.onSpecial2 = {
if (storageWidget.pastureWidget == null && storageWidget.grabbedSlot == null) {
optionsButton!!.onPress()
playSound(CobblemonSounds.PC_CLICK)
}
}
it.onBack = {
if (storageWidget.pastureWidget == null && wallpaperWidget.visible) {
wallpaperButton?.onPress()
playSound(CobblemonSounds.PC_CLICK)
} else if (displayOptions) {
optionsButton!!.onPress()
playSound(CobblemonSounds.PC_CLICK)
} else if (storageWidget.grabbedSlot != null) {
storageWidget.resetSelected()
} else {
closeNormally(true)
}
}
it.onActiveChange = { state ->
if (!state) {
storageWidget.controllerComponentManager.active = false
} else {
if (it.currentSelectedComponent == "storage") storageWidget.controllerComponentManager.active = true
}
}
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.START,
{ true },
Component.translatable("cobblemon.controller.quick_search")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.TAB,
{ true },
Component.translatable("cobblemon.controller.pc.switch_box")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SECONDARY_TAB,
{ true },
Component.translatable("cobblemon.controller.pc.switch_stats")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "storage" && storageWidget.controllerComponentManager.currentSelectedComponent != "pasture" },
Component.translatable("cobblemon.controller.toggle_held_item_type"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "storage" && storageWidget.controllerComponentManager.currentSelectedComponent == "pasture" &&
storageWidget.pastureWidget!!.controllerComponentManager.currentSelectedComponent.startsWith("slot_") },
Component.translatable("cobblemon.controller.pc.toggle_defence"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_2,
{ it.currentSelectedComponent == "storage" && storageWidget.controllerComponentManager.currentSelectedComponent != "pasture" },
Component.translatable("cobblemon.controller.pc.box_settings"),
true
))
}
}
override fun getSelectionPosAndSize(): Vector4i? {
val data = super.getSelectionPosAndSize()
if (data == null) {
when (controllerComponentManager.currentSelectedComponent) {
"wallpaper_widget" -> return Vector4i(wallpaperWidget.rowLeft - 2, wallpaperWidget.getRowTop(wallpaperWidget.children().indexOf(focused)) + 2,
WallpapersScrollingWidget.SLOT_WIDTH + 4, WallpapersScrollingWidget.SLOT_HEIGHT + 4)
"storage" -> {
if (storageWidget.pastureWidget != null && storageWidget.controllerComponentManager.currentSelectedComponent == "pasture") {
val scrollWidget = storageWidget.pastureWidget!!.pastureScrollList
if (storageWidget.pastureWidget!!.controllerComponentManager.currentSelectedComponent.startsWith("slot_")) {
return Vector4i(scrollWidget.rowLeft - 6, scrollWidget.getRowTop(scrollWidget.children().indexOf(focused)),
PasturePokemonScrollList.SLOT_WIDTH + 4, PasturePokemonScrollList.SLOT_HEIGHT + 4)
} else if (storageWidget.pastureWidget!!.controllerComponentManager.currentSelectedComponent == "empty") {
return Vector4i(scrollWidget.x - 2, scrollWidget.y - 11, scrollWidget.width + 4, scrollWidget.height + 13)
}
}
}
}
}
return data
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -685,7 +880,7 @@ class PCGUI(
)
if (!optionButtons.isEmpty() && displayOptions) {
for (button in optionButtons) button.showAlt = hasShiftDown()
for (button in optionButtons) button.showAlt = hasShiftDown() || (controllerComponentManager.active && controllerShiftToggle)
}
super.render(context, mouseX, mouseY, delta)
@@ -700,6 +895,8 @@ class PCGUI(
if (itemHovered) context.renderTooltip(Minecraft.getInstance().font, displayedItem, mouseX, mouseY)
}
}
renderSelection(matrices)
}
fun closeNormally(unlink: Boolean = true) {
@@ -27,8 +27,8 @@ class ReleaseButton(
) : Button(x, y, WIDTH, HEIGHT, Component.literal("Release"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
private const val WIDTH = 58
private const val HEIGHT = 16
const val WIDTH = 58
const val HEIGHT = 16
private val buttonResource = cobblemonResource("textures/gui/pc/pc_release_button.png")
}
@@ -42,7 +42,7 @@ class ReleaseButton(
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || isFocused) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
@@ -28,8 +28,8 @@ class ReleaseConfirmButton(
) : Button(x, y, WIDTH, HEIGHT, Component.literal("ReleaseConfirm"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
private const val WIDTH = 30
private const val HEIGHT = 13
const val WIDTH = 30
const val HEIGHT = 13
private val buttonResource = cobblemonResource("textures/gui/pc/pc_release_button_confirm.png")
}
@@ -43,7 +43,7 @@ class ReleaseConfirmButton(
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || isFocused) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
@@ -20,6 +20,9 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.pasture.PasturePCGUIConfiguration
import com.cobblemon.mod.common.client.gui.pasture.PastureWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.SubManagerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.gui.PCBoxWallpaperRepository
import com.cobblemon.mod.common.client.settings.ServerSettings
@@ -41,6 +44,7 @@ import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import org.joml.Vector2i
class StorageWidget(
pX: Int, pY: Int,
@@ -91,6 +95,9 @@ class StorageWidget(
this.setupStorageSlots()
}
val controllerComponentManager = ControllerComponentManager(pcGui)
var grabbedSlotControllerPos = Vector2i()
init {
this.setupStorageSlots()
@@ -102,6 +109,7 @@ class StorageWidget(
if (!displayConfirmRelease && canDeleteSelected()) {
displayConfirmRelease = true
playSound(CobblemonSounds.PC_CLICK)
controllerComponentManager.focused = controllerComponentManager.componentList["release_no"]
}
}
)
@@ -126,6 +134,7 @@ class StorageWidget(
playSound(CobblemonSounds.PC_RELEASE)
resetSelected()
displayConfirmRelease = false
controllerComponentManager.focused = controllerComponentManager.componentList["slot_0_0"]
}
}
)
@@ -139,6 +148,7 @@ class StorageWidget(
if (displayConfirmRelease) {
displayConfirmRelease = false
playSound(CobblemonSounds.PC_CLICK)
controllerComponentManager.focused = controllerComponentManager.componentList["release"]
}
}
)
@@ -146,6 +156,47 @@ class StorageWidget(
if (pcGui.configuration is PasturePCGUIConfiguration) {
this.pastureWidget = PastureWidget(this, pcGui.configuration, x + 182, y - 19)
}
controllerComponentManager.componentList["release"] = ControllerComponent().also {
it.associatedGUIElement = releaseButton
it.up = { controllerComponentManager.componentList["party_slot_2_0"] }
it.left = { controllerComponentManager.componentList["slot_4_5"] }
it.onSelect = {
grabbedSlotControllerPos = Vector2i(releaseButton.x + ReleaseButton.WIDTH / 2 + 4, releaseButton.y + ReleaseButton.HEIGHT / 2 + 4)
}
}
controllerComponentManager.componentList["release_no"] = ControllerComponent().also {
it.associatedGUIElement = releaseNoButton
it.up = { controllerComponentManager.componentList["party_slot_2_1"] }
it.left = { controllerComponentManager.componentList["release_yes"] }
it.onSelect = {
grabbedSlotControllerPos = Vector2i(releaseNoButton.x + ReleaseConfirmButton.WIDTH / 2 + 4, releaseNoButton.y + ReleaseConfirmButton.HEIGHT / 2 + 4)
}
it.onUnselect = {
if (!controllerComponentManager.currentSelectedComponent.startsWith("release")) displayConfirmRelease = false
}
}
controllerComponentManager.componentList["release_yes"] = ControllerComponent().also {
it.associatedGUIElement = releaseYesButton
it.up = { controllerComponentManager.componentList["party_slot_2_0"] }
it.left = { controllerComponentManager.componentList["slot_4_5"] }
it.right = { controllerComponentManager.componentList["release_no"] }
it.onSelect = {
grabbedSlotControllerPos = Vector2i(releaseYesButton.x + ReleaseConfirmButton.WIDTH / 2 + 4, releaseYesButton.y + ReleaseConfirmButton.HEIGHT / 2 + 4)
}
it.onUnselect = {
if (!controllerComponentManager.currentSelectedComponent.startsWith("release")) displayConfirmRelease = false
}
}
if (pastureWidget != null) {
controllerComponentManager.componentList["pasture"] = SubManagerComponent().also {
it.subManager = { pastureWidget!!.controllerComponentManager }
it.left = { controllerComponentManager.componentList["slot_0_5"] }
}
}
controllerComponentManager.let {
it.focused = it.componentList["slot_0_0"]
}
}
fun canDeleteSelected(): Boolean {
@@ -164,7 +215,7 @@ class StorageWidget(
for (row in 1..5) {
for (col in 1..6) {
BoxStorageSlot(
val slot = BoxStorageSlot(
x = boxStartX + ((col - 1) * (StorageSlot.SIZE + BOX_SLOT_PADDING)),
y = boxStartY + ((row - 1) * (StorageSlot.SIZE + BOX_SLOT_PADDING)),
parent = this,
@@ -176,6 +227,27 @@ class StorageWidget(
this.boxSlots.add(widget)
}
index++
val adjCol = col - 1
val adjRow = row - 1
controllerComponentManager.componentList["slot_${adjRow}_${adjCol}"] = ControllerComponent().also {
it.associatedGUIElement = slot
if (adjCol - 1 >= 0) it.left = { controllerComponentManager.componentList["slot_${adjRow}_${adjCol-1}"] }
if (adjCol + 1 <= 5) it.right = { controllerComponentManager.componentList["slot_${adjRow}_${adjCol+1}"] }
else if (pastureWidget == null) {
it.right = when (adjRow) {
4 -> {{ if (canDeleteSelected()) controllerComponentManager.componentList["release"] else null }}
3 -> {{ controllerComponentManager.componentList["party_slot_2_0"] }}
else -> {{ controllerComponentManager.componentList["party_slot_${adjRow}_0"] }}
}
} else {
it.right = { controllerComponentManager.componentList["pasture"] }
}
if (adjRow - 1 >= 0) it.up = { controllerComponentManager.componentList["slot_${adjRow-1}_${adjCol}"] }
if (adjRow + 1 <= 4) it.down = { controllerComponentManager.componentList["slot_${adjRow+1}_${adjCol}"] }
it.onSelect = { grabbedSlotControllerPos = Vector2i(slot.x + slot.width / 2 + 4, slot.y + slot.height / 2 + 4) }
it.onPress = { if (slot.clickable()) slot.onPress() }
}
}
}
@@ -194,7 +266,7 @@ class StorageWidget(
partyY += ((StorageSlot.SIZE + PARTY_SLOT_PADDING) * offsetIndex) + offsetY
}
PartyStorageSlot(
val slot = PartyStorageSlot(
x = partyX,
y = partyY,
parent = this,
@@ -205,6 +277,22 @@ class StorageWidget(
this.addWidget(widget)
this.partySlots.add(widget)
}
val row = partyIndex / 2
val col = partyIndex % 2
controllerComponentManager.componentList["party_slot_${row}_${col}"] = ControllerComponent().also {
it.associatedGUIElement = slot
if (col == 1) it.left = { controllerComponentManager.componentList["party_slot_${row}_0"] }
else {
it.right = { controllerComponentManager.componentList["party_slot_${row}_1"] }
it.left = { controllerComponentManager.componentList["slot_${row}_5"] }
}
if (row - 1 >= 0) it.up = { controllerComponentManager.componentList["party_slot_${row-1}_${col}"] }
if (row + 1 <= 2) it.down = { controllerComponentManager.componentList["party_slot_${row+1}_${col}"] }
else it.down = { if (canDeleteSelected()) controllerComponentManager.componentList["release"] else null }
it.onSelect = { grabbedSlotControllerPos = Vector2i(slot.x + slot.width / 2 + 4, slot.y + slot.height / 2 + 4) }
it.onPress = { if (slot.clickable()) slot.onPress() }
}
}
}
@@ -307,7 +395,7 @@ class StorageWidget(
this.boxSlots.forEach { slot ->
slot.render(context, mouseX, mouseY, delta)
val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
if (grabbedSlot == null && (slot.isHovered(mouseX, mouseY) || slot.isFocused)
&& pokemon != null && pokemon != pcGui.previewPokemon
) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: false)
@@ -323,7 +411,7 @@ class StorageWidget(
this.partySlots.forEach { slot ->
slot.render(context, mouseX, mouseY, delta)
val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
if (grabbedSlot == null && (slot.isHovered(mouseX, mouseY) || slot.isFocused)
&& pokemon != null && pokemon != pcGui.previewPokemon
) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: true)
@@ -335,7 +423,11 @@ class StorageWidget(
pastureWidget?.render(context, mouseX, mouseY, delta)
matrices.pushPose()
matrices.translate(0.0, 0.0, 1000.0)
grabbedSlot?.render(context, mouseX, mouseY, delta)
if (controllerComponentManager.active) {
grabbedSlot?.render(context, grabbedSlotControllerPos.x, grabbedSlotControllerPos.y, delta)
} else {
grabbedSlot?.render(context, mouseX, mouseY, delta)
}
matrices.popPose()
}
@@ -372,6 +464,9 @@ class StorageWidget(
}
// Reset release confirmation
if (controllerComponentManager.currentSelectedComponent.startsWith("release")) {
controllerComponentManager.focused = controllerComponentManager.componentList["slot_0_0"]
}
displayConfirmRelease = false
// Clicking on itself, so unselect position
@@ -15,6 +15,8 @@ import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.ScrollingWidget
import com.cobblemon.mod.common.client.gui.pc.WallpapersScrollingWidget.WallpaperEntry
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.gui.PCBoxWallpaperRepository
import com.cobblemon.mod.common.net.messages.server.storage.pc.RequestChangePCBoxWallpaperPacket
@@ -49,10 +51,14 @@ class WallpapersScrollingWidget(
val backgroundResource = cobblemonResource("textures/gui/pc/wallpaper_scroll_background.png")
}
val controllerComponentManager = ControllerComponentManager(pcGui)
init {
this.visible = false
this.y = pY
createEntries()
}
override fun getScrollbarPosition(): Int = x + width - 3
@@ -90,6 +96,24 @@ class WallpapersScrollingWidget(
}
}
override fun addEntry(entry: WallpaperEntry): Int {
val i = super.addEntry(entry)
controllerComponentManager.componentList["wallpaper_${i}"] = ControllerComponent().also {
it.associatedGUIElement = entry
it.onPress = { entry.selectWallpaper() }
it.onFocus = { ensureVisible(entry) }
}
if (children().size > 1) {
controllerComponentManager.componentList["wallpaper_${i}"]?.let {
it.pairUp(controllerComponentManager.componentList["wallpaper_${i-1}"])
it.pairDown(controllerComponentManager.componentList["wallpaper_${0}"])
}
} else {
controllerComponentManager.focused = controllerComponentManager.componentList["wallpaper_${i}"]
}
return i
}
override fun getRowLeft(): Int {
return this.left + SLOT_PADDING
}
@@ -116,7 +140,7 @@ class WallpapersScrollingWidget(
partialTick: Float
) {
val matrices = guiGraphics.pose()
val resource = if (Screen.hasShiftDown() && altWallpaper !== null) altWallpaper else wallpaper
val resource = if ((Screen.hasShiftDown() || (pcGui.controllerComponentManager.active && pcGui.controllerShiftToggle)) && altWallpaper !== null) altWallpaper else wallpaper
blitk(
matrixStack = matrices,
texture = resource,
@@ -133,24 +157,28 @@ class WallpapersScrollingWidget(
y = top + SLOT_PADDING,
width = width,
height = SLOT_HEIGHT,
vOffset = if (hovering) SLOT_HEIGHT else 0,
vOffset = if (hovering || isFocused) SLOT_HEIGHT else 0,
textureHeight = SLOT_HEIGHT * 2
)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (this@WallpapersScrollingWidget.visible && isMouseOver(mouseX, mouseY)) {
val appliedWallpaper = if (Screen.hasShiftDown()) altWallpaper ?: wallpaper else wallpaper
RequestChangePCBoxWallpaperPacket(pcGui.pc.uuid, storageWidget.box, wallpaper, if (Screen.hasShiftDown()) altWallpaper else null).sendToServer()
pcGui.pc.boxes[storageWidget.box].wallpaper = appliedWallpaper
pcGui.unseenWallpapers.remove(wallpaper)
isNew = false
minecraft.soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.PC_CLICK, 1.0F))
selectWallpaper()
return true
}
return false
}
fun selectWallpaper() {
val appliedWallpaper = if (Screen.hasShiftDown() || (pcGui.controllerComponentManager.active && pcGui.controllerShiftToggle)) altWallpaper ?: wallpaper else wallpaper
RequestChangePCBoxWallpaperPacket(pcGui.pc.uuid, storageWidget.box, wallpaper, if (Screen.hasShiftDown() || (pcGui.controllerComponentManager.active && pcGui.controllerShiftToggle)) altWallpaper else null).sendToServer()
pcGui.pc.boxes[storageWidget.box].wallpaper = appliedWallpaper
pcGui.unseenWallpapers.remove(wallpaper)
isNew = false
minecraft.soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.PC_CLICK, 1.0F))
}
override fun getNarration(): Component? {
return wallpaper.toString().text()
}
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.pokedex.CaughtCount
import com.cobblemon.mod.common.api.pokedex.Dexes
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.SeenCount
import com.cobblemon.mod.common.api.pokedex.def.PokedexDef
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
@@ -46,6 +47,10 @@ import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_ICON_
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.*
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
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
@@ -74,6 +79,8 @@ import net.minecraft.core.BlockPos
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
import org.joml.Vector4i
/**
* Pokedex GUI
@@ -85,7 +92,7 @@ class PokedexGUI private constructor(
val type: PokedexType,
val initSpecies: ResourceLocation?,
val blockPos: BlockPos?
): Screen(Component.translatable("cobblemon.ui.pokedex.title")), CobblemonRenderable {
): Screen(Component.translatable("cobblemon.ui.pokedex.title")), CobblemonRenderable, ControllerScreen {
companion object {
private val screenBackground = cobblemonResource("textures/gui/pokedex/pokedex_screen.png")
@@ -163,6 +170,14 @@ class PokedexGUI private constructor(
lateinit var tabInfoElement: GuiEventListener
var tabInfoIndex = TAB_DESCRIPTION
val controllerComponentManager = ControllerComponentManager(this)
var lastTileComponent: ControllerComponent? = null
var lastSelectedColumn = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun renderBlurredBackground(delta: Float) {}
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -268,6 +283,222 @@ class PokedexGUI private constructor(
)
addRenderableWidget(categoryFilterButtonDown)
updateFilters(true)
fun hasKnowledge(): Boolean {
val currentEntry = pokemonInfoWidget.currentEntry
return currentEntry != null && CobblemonClient.clientPokedexData.getKnowledgeForSpecies(currentEntry.speciesId) != PokedexEntryProgress.UNREGISTERED
}
controllerComponentManager.componentList["search"] = ControllerComponent().also {
it.associatedGUIElement = searchWidget
it.down = {
val returnVal = if (scrollScreen.children().isNotEmpty()) {
controllerComponentManager.componentList["tile_0_0"]
} else {
controllerComponentManager.componentList["empty_entries"]
}
returnVal
}
it.onSpecial1 = {
searchByTypeButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
controllerComponentManager.componentList["empty_entries"] = ControllerComponent().also {
it.up = { controllerComponentManager.componentList["search"] }
it.right = { controllerComponentManager.componentList["pokemon_render"] }
}
controllerComponentManager.componentList["pokemon_render"] = ControllerComponent().also {
it.left = {
val returnVal = if (scrollScreen.children().isNotEmpty()) {
lastTileComponent
} else {
controllerComponentManager.componentList["empty_entries"]
}
returnVal
}
it.down = { controllerComponentManager.componentList["info_widget"] }
it.onPress = {
if (hasKnowledge()) {
pokemonInfoWidget.playCry()
}
}
it.onSpecial1 = {
if (hasKnowledge() && pokemonInfoWidget.genderButton.visible) {
pokemonInfoWidget.genderButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
it.onSpecial2 = {
if (hasKnowledge() && pokemonInfoWidget.shinyButton.visible) {
pokemonInfoWidget.shinyButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
}
controllerComponentManager.componentList["info_widget"] = ControllerComponent().also {
it.left = {
val returnVal = if (scrollScreen.children().isNotEmpty()) {
lastTileComponent
} else {
controllerComponentManager.componentList["empty_entries"]
}
returnVal
}
it.up = { controllerComponentManager.componentList["pokemon_render"] }
}
controllerComponentManager.let {
it.focused = it.componentList["tile_0_0"] ?: it.componentList["empty_entries"]
it.onTabNav = { nextTab ->
if (it.currentSelectedComponent == "empty_entries" || it.currentSelectedComponent.startsWith("tile_")) {
updatePokedexRegion(nextTab)
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
} else if (it.currentSelectedComponent == "pokemon_render") {
val currentEntry = pokemonInfoWidget.currentEntry
if (currentEntry != null) {
val hasKnowledge = CobblemonClient.clientPokedexData.getKnowledgeForSpecies(currentEntry.speciesId) != PokedexEntryProgress.UNREGISTERED
val showableForms = CobblemonClient.clientPokedexData.getEncounteredForms(currentEntry)
if (hasKnowledge && showableForms.size > 1 && showableForms.size > pokemonInfoWidget.selectedFormIndex) {
pokemonInfoWidget.switchForm(nextTab)
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
} else if (it.currentSelectedComponent == "info_widget") {
val trimmedTabs = tabButtons.filterIndexed { index, _ -> tabInfoIndex == index || canSelectTab(index) }.map { tabButtons.indexOf(it) }.toMutableList()
var currentTab = trimmedTabs.indexOf(tabInfoIndex) + if (nextTab) 1 else -1
if (currentTab > trimmedTabs.size - 1) currentTab = 0
else if (currentTab < 0) currentTab = trimmedTabs.size - 1
displaytabInfoElement(trimmedTabs[currentTab])
if (trimmedTabs.size > 1) playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
it.onSecondaryTabNav = { nextTab ->
if (it.currentSelectedComponent == "pokemon_render") {
pokemonInfoWidget.switchPose(nextTab)
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
} else if (it.currentSelectedComponent == "info_widget") {
(tabInfoElement as? AbilitiesWidget)?.let {
it.switchAbility(nextTab)
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
(tabInfoElement as? StatsWidget)?.let { widget ->
if (nextTab) {
if (children().contains(widget.rightButton)) {
widget.rightButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
} else {
if (children().contains(widget.leftButton)) {
widget.leftButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
}
}
}
it.onRHorizontal = { right ->
if (it.currentSelectedComponent == "info_widget") {
(tabInfoElement as? StatsWidget)?.let { widget ->
if (widget.selectedStatTypeIndex == StatsWidget.STAT_TYPE_RIDE) {
if (right) {
if (children().contains(widget.rightSubButton)) {
widget.rightSubButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
} else {
if (children().contains(widget.leftSubButton)) {
widget.leftSubButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
}
}
}
}
it.onStart = {
it.focused = it.componentList["search"]
it.openKeyboard.invoke(it.focused!!.associatedGUIElement as EditBox)
playSound(SoundEvents.UI_BUTTON_CLICK.value())
}
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.START,
{ true },
Component.translatable("cobblemon.controller.quick_search")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.TAB,
{ it.currentSelectedComponent == "empty_entries" || it.currentSelectedComponent.startsWith("tile_") },
Component.translatable("cobblemon.controller.dex.cycle_region")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.TAB,
{ it.currentSelectedComponent == "pokemon_render" && hasKnowledge() && pokemonInfoWidget.visibleForms.size > 1},
Component.translatable("cobblemon.controller.dex.cycle_form"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SECONDARY_TAB,
{ it.currentSelectedComponent == "pokemon_render" && hasKnowledge() },
Component.translatable("cobblemon.controller.dex.cycle_animation")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SECONDARY_TAB,
{ it.currentSelectedComponent == "info_widget" && tabInfoElement is AbilitiesWidget },
Component.translatable("cobblemon.controller.dex.cycle_ability")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SECONDARY_TAB,
{ it.currentSelectedComponent == "info_widget" && (tabInfoElement as? StatsWidget)?.let { children().contains(it.rightButton) } ?: false },
Component.translatable("cobblemon.controller.dex.cycle_stats")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.JOYSTICK_HORIZONTAL,
{ it.currentSelectedComponent == "info_widget" && (tabInfoElement as? StatsWidget)?.let { it.selectedStatTypeIndex == StatsWidget.STAT_TYPE_RIDE && children().contains(it.rightSubButton) } ?: false },
Component.translatable("cobblemon.controller.dex.cycle_ride_style")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "search" },
Component.translatable("cobblemon.controller.dex.cycle_search_by"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "pokemon_render" && hasKnowledge() && pokemonInfoWidget.genderButton.visible },
Component.translatable("cobblemon.controller.dex.toggle_gender"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_2,
{ it.currentSelectedComponent == "pokemon_render" && hasKnowledge() && pokemonInfoWidget.shinyButton.visible },
Component.translatable("cobblemon.controller.dex.toggle_shiny"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.PRESS,
{ it.currentSelectedComponent == "pokemon_render" && hasKnowledge() },
Component.translatable("cobblemon.controller.dex.play_cry"),
true
))
}
}
override fun getSelectionPosAndSize(): Vector4i? {
return when (controllerComponentManager.currentSelectedComponent) {
"search" -> Vector4i(searchWidget.x - 2, searchWidget.y - 2, searchWidget.width + 15, searchWidget.height + 4)
"pokemon_render" -> Vector4i(pokemonInfoWidget.x - 2, pokemonInfoWidget.y + 12, pokemonInfoWidget.width + 6, pokemonInfoWidget.height + 16)
"info_widget" -> Vector4i(pokemonInfoWidget.x - 2, pokemonInfoWidget.y + 94, pokemonInfoWidget.width + 6, pokemonInfoWidget.height + 3)
"empty_entries" -> Vector4i(scrollScreen.x - 2, scrollScreen.y - 2, scrollScreen.width + 4, scrollScreen.height + 4)
else -> {
if (controllerComponentManager.currentSelectedComponent.startsWith("tile_")) {
val scrollWidget = controllerComponentManager.focused?.associatedGUIElement as? EntriesScrollingWidget.PokemonScrollSlotRow
val startX = scrollScreen.x + lastSelectedColumn * (PokedexGUIConstants.SCROLL_SLOT_SIZE + PokedexGUIConstants.SCROLL_SLOT_SPACING)
if (scrollWidget != null) {
Vector4i(startX - 2, scrollScreen.getRowTop(scrollScreen.children().indexOf(scrollWidget)) + 1, PokedexGUIConstants.SCROLL_SLOT_SIZE + 4, PokedexGUIConstants.SCROLL_SLOT_SIZE + 4)
} else null
} else null
}
}
}
fun getSufficientlyKnownForms(selectedEntry: PokedexEntry, tabIndex: Int) = if (tabIndex in looseUnlockTabs) {
@@ -412,6 +643,8 @@ class PokedexGUI private constructor(
super.render(context, mouseX, mouseY, delta)
renderSelection(context.pose())
// Search type tooltip
if (searchByTypeButton.isButtonHovered(mouseX, mouseY)) {
val searchTypeText = lang("ui.pokedex.search.search_by", lang("ui.pokedex.search.type.${selectedSearchByType.name.lowercase()}")).bold()
@@ -467,6 +700,7 @@ class PokedexGUI private constructor(
if (isDragging && canDragRender) {
val dragOffsetY = (oldDragPosX - mouseX).toFloat()
pokemonInfoWidget.rotationY = (((pokemonInfoWidget.rotationY + dragOffsetY) % 360 + 360) % 360)
pokemonInfoWidget.lastRotationY = pokemonInfoWidget.rotationY
}
oldDragPosX = mouseX
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
@@ -482,6 +716,18 @@ class PokedexGUI private constructor(
override fun tick() {
if (::pokemonInfoWidget.isInitialized && !pokemonInfoWidget.suppressViewport) pokemonInfoWidget.tick()
//Controller
if (controllerComponentManager.rHorizontalState != 0) {
if (controllerComponentManager.currentSelectedComponent == "pokemon_render") {
pokemonInfoWidget.rotationY = ((pokemonInfoWidget.rotationY + 5F * controllerComponentManager.rHorizontalState) % 360 + 360) % 360
}
}
if (controllerComponentManager.rVerticalState != 0) {
if (controllerComponentManager.currentSelectedComponent == "info_widget") {
(tabInfoElement as? InfoTextScrollWidget)?.scroll(-3 * controllerComponentManager.rVerticalState)
}
}
}
fun updatePokedexRegion(nextIndex: Boolean) {
@@ -541,6 +787,45 @@ class PokedexGUI private constructor(
} else {
setSelectedEntry(entries.first())
}
val totalRows = scrollScreen.children().size
val lastRowCount = scrollScreen.children()[totalRows-1].dexDataList.size
scrollScreen.children().forEachIndexed { row, scrollRow ->
scrollRow.dexDataList.forEachIndexed { column, slot ->
controllerComponentManager.componentList["tile_${row}_${column}"] = ControllerComponent().also {
it.associatedGUIElement = scrollRow
if (row - 1 >= 0) it.up = { controllerComponentManager.componentList["tile_${row-1}_${column}"] }
else it.up = { controllerComponentManager.componentList["search"] }
if (row + 1 <= scrollScreen.children().size - 1 &&
(row + 1 != totalRows || column <= lastRowCount - 1)) it.down = { controllerComponentManager.componentList["tile_${row+1}_${column}"] }
if (column - 1 >= 0) it.left = { controllerComponentManager.componentList["tile_${row}_${column-1}"] }
if (column + 1 <= scrollRow.dexDataList.size - 1) it.right = { controllerComponentManager.componentList["tile_${row}_${column+1}"] }
else it.right = { controllerComponentManager.componentList["pokemon_render"] }
it.onPress = {
scrollRow.setPokedexEntry.invoke(slot)
playSound(CobblemonSounds.POKEDEX_CLICK)
}
it.onSelect = {
lastTileComponent = it
lastSelectedColumn = column
scrollScreen.ensureVisible(scrollRow)
}
it.onFocus = {
scrollRow.slotControllerSelected = column
}
it.onUnfocus = {
scrollRow.slotControllerSelected = -1
}
}
}
}
}
if (controllerComponentManager.currentSelectedComponent.let { it == "empty_entries" || it.startsWith("tile_") }) {
if (entries.isNotEmpty()) {
controllerComponentManager.focused = controllerComponentManager.componentList["tile_0_0"]
} else {
controllerComponentManager.focused = controllerComponentManager.componentList["empty_entries"]
}
}
}
@@ -47,7 +47,7 @@ class AbilitiesWidget(x: Int, y: Int): InfoTextScrollWidget(pX = x, pY = y) {
init { setAbility() }
private fun switchAbility(nextIndex: Boolean) {
fun switchAbility(nextIndex: Boolean) {
val size = abilitiesList.size
selectedAbilitiesIndex = if (nextIndex) {
(selectedAbilitiesIndex + 1) % size
@@ -149,6 +149,8 @@ class EntriesScrollingWidget(val pX: Int, val pY: Int, val setPokedexEntry: (Pok
var x: Int = 0
var y: Int = 0
var slotControllerSelected = -1
override fun render(
context: GuiGraphics,
index: Int,
@@ -190,7 +192,7 @@ class EntriesScrollingWidget(val pX: Int, val pY: Int, val setPokedexEntry: (Pok
height = SCROLL_SLOT_SIZE
)
if (getHoveredSlotIndex(mouseX, mouseY) == index) {
if (getHoveredSlotIndex(mouseX, mouseY) == index || slotControllerSelected == index) {
blitk(
matrixStack = matrices,
texture = slotHighlight,
@@ -56,6 +56,7 @@ import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.util.Mth
import org.joml.Quaternionf
import org.joml.Vector3f
import java.io.FileNotFoundException
@@ -123,6 +124,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
var state = FloatingState()
var rotationY = 30F
var lastRotationY = rotationY // Used to smooth out rotation for controllers
var ticksElapsed = 0
var pokeBallBackgroundFrame = 0
@@ -188,7 +190,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
val variationButtons = mutableListOf<VariationButtonWrapper>()
private val genderButton: ScaledButton = ScaledButton(
val genderButton: ScaledButton = ScaledButton(
pX + 114F,
pY + 27F,
20,
@@ -200,7 +202,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
}
).apply { addWidget(this) }
private val shinyButton: ScaledButton = ScaledButton(
val shinyButton: ScaledButton = ScaledButton(
(pX + 126F),
(pY + 27F),
20,
@@ -261,6 +263,8 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
).apply { addWidget(this) }
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val realDetla = Minecraft.getInstance().timer.getGameTimeDeltaPartialTick(true)
val currentEntry = this.currentEntry ?: return
val hasKnowledge =
@@ -375,10 +379,10 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
matrices.translate(
pX.toDouble() + (POKEMON_PORTRAIT_WIDTH.toDouble() + 2) / 2,
pY.toDouble() + portraitStartY - 12,
1000.0 // Prevent model from clipping into background
10.0 // Dont use too big numbers otherwise side mod overlays can break (controllify keyboard)
)
matrices.scale(scaleAmount, scaleAmount, scaleAmount)
val rotationVector = Vector3f(13F, rotationY, 0F)
matrices.scale(scaleAmount, scaleAmount, 0.2F)
val rotationVector = Vector3f(13F, Mth.rotLerp(realDetla, lastRotationY, rotationY), 0F)
drawProfilePokemon(
renderablePokemon = renderablePokemon!!,
@@ -407,7 +411,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
// Ensure elements are not hidden behind Pokémon render
matrices.pushPose()
matrices.translate(0.0, 0.0, 2000.0)
matrices.translate(0.0, 0.0, 20.0)
if (isSelectedPokemonOwned()) {
val primaryType = type[0]
@@ -638,12 +642,12 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
type = arrayOf(form.primaryType, form.secondaryType)
}
private fun playCry() {
fun playCry() {
state.activeAnimations.clear()
state.addFirstAnimation(setOf("cry"))
}
private fun switchForm(nextIndex: Boolean) {
fun switchForm(nextIndex: Boolean) {
if (visibleForms.isEmpty() || currentEntry == null) {
return
}
@@ -664,7 +668,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
updateAspects()
}
private fun switchPose(nextIndex: Boolean) {
fun switchPose(nextIndex: Boolean) {
if (nextIndex) {
if (selectedPoseIndex < poseList.lastIndex) selectedPoseIndex++
else selectedPoseIndex = 0
@@ -756,6 +760,8 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
val delay = 3
if (ticksElapsed % delay == 0) pokeBallBackgroundFrame++
if (pokeBallBackgroundFrame == 16) pokeBallBackgroundFrame = 0
lastRotationY = rotationY
}
fun isWithinPortraitSpace(mouseX: Double, mouseY: Double): Boolean =
@@ -9,6 +9,9 @@
package com.cobblemon.mod.common.client.gui.snapshots
import com.cobblemon.mod.common.CobblemonBuildDetails
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import net.minecraft.client.Minecraft
import net.minecraft.client.OptionInstance
@@ -20,10 +23,12 @@ import net.minecraft.client.gui.components.Tooltip
import net.minecraft.client.gui.layouts.FrameLayout
import net.minecraft.client.gui.layouts.GridLayout
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.CommonComponents
import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvents
class SnapshotWarningScreen(val consumer: (Acknowledgement, Boolean) -> Unit) : Screen(TITLE) {
class SnapshotWarningScreen(val consumer: (Acknowledgement, Boolean) -> Unit) : Screen(TITLE), ControllerScreen {
companion object {
@@ -36,6 +41,12 @@ class SnapshotWarningScreen(val consumer: (Acknowledgement, Boolean) -> Unit) :
private val layout = GridLayout().columnSpacing(10).rowSpacing(20)
private val dontShowAgain = OptionInstance.createBoolean("cobblemon\$snapshots\$dont_show_again", false)
val controllerComponentManager = ControllerComponentManager(this)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun init() {
super.init()
@@ -45,17 +56,42 @@ class SnapshotWarningScreen(val consumer: (Acknowledgement, Boolean) -> Unit) :
helper.addChild(StringWidget(this.title, this.font), 2, settings)
helper.addChild(MultiLineTextWidget(DESCRIPTION, this.font).setCentered(true).setMaxWidth(310), 2, settings)
helper.addChild(Checkbox.builder(Component.translatable("cobblemon.snapshots.warning.dont_show_again"), Minecraft.getInstance().font)
val checkbox = Checkbox.builder(Component.translatable("cobblemon.snapshots.warning.dont_show_again"), Minecraft.getInstance().font)
.selected(dontShowAgain)
.tooltip(Tooltip.create(DONT_SHOW_AGAIN_TIP))
.build(), 2, settings)
.build()
helper.addChild(checkbox, 2, settings)
helper.addChild(Button.builder(CommonComponents.GUI_YES) { this.consumer.invoke(Acknowledgement.YES, dontShowAgain.get()) }.build())
helper.addChild(Button.builder(CommonComponents.GUI_NO) { this.consumer.invoke(Acknowledgement.NO, false) }.build())
val yesButton = Button.builder(CommonComponents.GUI_YES) { this.consumer.invoke(Acknowledgement.YES, dontShowAgain.get()) }.build()
helper.addChild(yesButton)
val noButton = Button.builder(CommonComponents.GUI_NO) { this.consumer.invoke(Acknowledgement.NO, false) }.build()
helper.addChild(noButton)
this.layout.visitWidgets { this.addRenderableWidget(it) }
this.layout.arrangeElements()
this.repositionElements()
controllerComponentManager.componentList["checkbox"] = ControllerComponent().also {
it.associatedGUIElement = checkbox
it.down = { controllerComponentManager.componentList["yes_button"] }
it.onPress = {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F))
checkbox.onPress()
}
}
controllerComponentManager.componentList["yes_button"] = ControllerComponent().also {
it.associatedGUIElement = yesButton
it.up = { controllerComponentManager.componentList["checkbox"] }
it.right = { controllerComponentManager.componentList["no_button"] }
}
controllerComponentManager.componentList["no_button"] = ControllerComponent().also {
it.associatedGUIElement = noButton
it.up = { controllerComponentManager.componentList["checkbox"] }
it.left = { controllerComponentManager.componentList["yes_button"] }
}
controllerComponentManager.let {
it.focused = it.componentList["yes_button"]
}
}
override fun repositionElements() {
@@ -25,9 +25,10 @@ import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.VariableObjectSelectionScrollbar
import com.cobblemon.mod.common.client.gui.startselection.widgets.CategoryList
import com.cobblemon.mod.common.client.gui.startselection.widgets.SelectionButton
import com.cobblemon.mod.common.client.gui.startselection.widgets.preview.StarterRoundabout
import com.cobblemon.mod.common.client.gui.summary.Summary.Companion.PARTY
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.keybind.keybinds.SummaryBinding
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.config.starter.RenderableStarterCategory
@@ -41,8 +42,9 @@ import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.toasts.Toast
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import org.joml.Vector4i
import net.minecraft.resources.ResourceLocation
import java.io.FileNotFoundException
/**
@@ -51,7 +53,7 @@ import java.io.FileNotFoundException
* @author Qu
* @since 2022-06-18
*/
class StarterSelectionScreen(private val categories: List<RenderableStarterCategory>): Screen("cobblemon.ui.starter.title".asTranslated()), CobblemonRenderable {
class StarterSelectionScreen(private val categories: List<RenderableStarterCategory>): Screen("cobblemon.ui.starter.title".asTranslated()), CobblemonRenderable, ControllerScreen {
companion object {
// Size of UI at scale 1
private const val BASE_WIDTH = 239
@@ -66,14 +68,20 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
private val platformShadow = cobblemonResource("textures/gui/pokedex/platform_shadow.png")
private val typeSpacerResource = cobblemonResource("textures/gui/starterselection/type_spacer_single.png")
private val ballResource = cobblemonResource("textures/item/poke_balls/poke_ball.png")
private val unknownResource = cobblemonResource("textures/gui/pokedex/platform_unknown.png")
}
var currentSelection = 0
lateinit var currentCategory: RenderableStarterCategory
private lateinit var modelWidget: ModelWidget
private var currentPokemon: RenderablePokemon? = null
private lateinit var currentPokemon: RenderablePokemon
val controllerComponentManager = ControllerComponentManager(this)
var lastSelectedCategory = 0
var lastSelectedButton = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
var ticksElapsed = 0
var currentBallBackgroundFrame = 0
@@ -100,9 +108,9 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
}
currentCategory = categories.first()
currentPokemon = currentCategory.pokemon[0]
currentPokemon = currentCategory.pokemon[currentSelection]
with(currentPokemon!!) {
with(currentPokemon) {
modelWidget = ModelWidget(
pX = x + 6, pY = y + 17,
pWidth = 118, pHeight = 100,
@@ -153,6 +161,91 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
width = 3,
height = 145
))
controllerComponentManager.componentList["select"] = ControllerComponent().also {
it.associatedGUIElement = selectionButton
it.right = { controllerComponentManager.componentList["pokemon_${lastSelectedCategory}_${lastSelectedButton}"] }
}
categoryList.generatedCategoriesWidgets.forEachIndexed { categoryIndex, category ->
for (buttonIndex in 0..<category.category.pokemon.size) {
val column = buttonIndex % 3
controllerComponentManager.componentList["pokemon_${categoryIndex}_${buttonIndex}"] = ControllerComponent().also {
it.associatedGUIElement = category
it.right = {
if (column < 2) controllerComponentManager.componentList["pokemon_${categoryIndex}_${buttonIndex+1}"]
else null
}
it.left = {
if (column > 0) controllerComponentManager.componentList["pokemon_${categoryIndex}_${buttonIndex-1}"]
else controllerComponentManager.componentList["select"]
}
it.up = {
if (buttonIndex-3 < 0) {
var returnVal: ControllerComponent? = null
var index = categoryIndex
while (index-1 >= 0) {
val lastCategory = categoryList.generatedCategoriesWidgets[index-1]
val row = (lastCategory.category.pokemon.size - 1) / 3 + if ((lastCategory.category.pokemon.size - 1) % 3 < column) -1 else 0
if (row >= 0) {
returnVal = controllerComponentManager.componentList["pokemon_${index-1}_${row*3+column}"]
break
}
index -= 1
}
returnVal
}
else controllerComponentManager.componentList["pokemon_${categoryIndex}_${buttonIndex-3}"]
}
it.down = {
if (buttonIndex+3 >= category.category.pokemon.size) {
var returnVal: ControllerComponent? = null
var index = categoryIndex
while (index+1 < categoryList.generatedCategoriesWidgets.size) {
val nextCategory = categoryList.generatedCategoriesWidgets[index+1]
if (nextCategory.category.pokemon.size > column) {
returnVal = controllerComponentManager.componentList["pokemon_${index+1}_${column}"]
break
}
index += 1
}
returnVal
}
else controllerComponentManager.componentList["pokemon_${categoryIndex}_${buttonIndex+3}"]
}
it.onPress = {
selectPokemon(category.category, buttonIndex)
playSound(CobblemonSounds.GUI_CLICK)
}
it.onSelect = {
lastSelectedCategory = categoryIndex
lastSelectedButton = buttonIndex
}
it.onFocus = {
category.controllerButtonSelected = buttonIndex
categoryList.ensureVisible(category)
}
it.onUnfocus = {
category.controllerButtonSelected = -1
}
}
}
}
controllerComponentManager.let {
it.focused = controllerComponentManager.componentList["select"]
}
}
override fun getSelectionPosAndSize(): Vector4i? {
if (controllerComponentManager.currentSelectedComponent.startsWith("pokemon_")) {
val category = controllerComponentManager.focused?.associatedGUIElement as? CategoryList.Category ?: return null
val row = category.controllerButtonSelected / 3
val column = category.controllerButtonSelected % 3
val x = category.getRowLeft() + (CategoryList.CONTAINER_BUTTON_SIZE + 2) * column + CategoryList.MARGIN + 3
val y = category.getRowTop() + (CategoryList.CONTAINER_BUTTON_SIZE + CategoryList.BUTTON_SEPERATION) * row + CategoryList.HEADER_HEIGHT + CategoryList.BUTTON_SEPERATION - 1
return Vector4i(x, y, CategoryList.CONTAINER_BUTTON_SIZE + 4, CategoryList.CONTAINER_BUTTON_SIZE + 4)
}
return super.getSelectionPosAndSize()
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -190,7 +283,7 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
width = 113,
height = 32
)
val typePlatform = currentPokemon?.let { getPlatformResource(it.form.primaryType) }
val typePlatform = getPlatformResource(currentPokemon.form.primaryType)
if (typePlatform != null) {
blitk(
matrixStack = matrices,
@@ -211,18 +304,6 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
scale = 0.5F
)
if (currentPokemon == null) { // Model widget won't render
blitk(
matrixStack = matrices,
texture = unknownResource,
x = (x + 36) / 0.15f,
y = (y + 36) / 0.15f,
width = 390,
height = 444,
scale = 0.15f
)
}
//Base
blitk(
matrixStack = matrices,
@@ -234,25 +315,23 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
)
//Type Icon
if (currentPokemon != null) {
TypeIcon(
x = x + 65,
y = y + 120,
type = currentPokemon!!.form.primaryType,
secondaryType = currentPokemon!!.form.secondaryType,
centeredX = true
).render(context)
TypeIcon(
x = x + 65,
y = y + 120,
type = currentPokemon.form.primaryType,
secondaryType = currentPokemon.form.secondaryType,
centeredX = true
).render(context)
if (currentPokemon!!.form.secondaryType == null) {
blitk(
matrixStack = matrices,
texture = typeSpacerResource,
x = x + 47,
y = y + 123,
width = 36,
height = 12
)
}
if (currentPokemon.form.secondaryType == null) {
blitk(
matrixStack = matrices,
texture = typeSpacerResource,
x = x + 47,
y = y + 123,
width = 36,
height = 12
)
}
//Name
@@ -269,7 +348,7 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = currentPokemon?.species?.translatedName?.bold() ?: lang("ui.starter.random").bold(),
text = currentPokemon.species.translatedName.bold(),
x = x + 14,
y = y + 2,
shadow = true
@@ -277,13 +356,11 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
// Pokédex Number
// Add preceding zeroes if Pokédex number is less than 4 digits
var dexNo = currentPokemon?.species?.nationalPokedexNumber?.toString()
if (dexNo != null) {
while (dexNo!!.length < 4) {
dexNo = "0$dexNo"
}
dexNo = "#$dexNo"
} else dexNo = "#????"
var dexNo = currentPokemon.species.nationalPokedexNumber.toString()
while (dexNo.length < 4) {
dexNo = "0$dexNo"
}
dexNo = "#$dexNo"
drawScaledText(
context = context,
@@ -308,12 +385,10 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
//Description Text
val smallTextScale = 0.5F
val textEntry = currentPokemon?.form?.pokedex?.first()?.asTranslated() ?: lang("ui.starter.random_description")
matrices.pushPose()
matrices.scale(smallTextScale, smallTextScale, 1F)
MultiLineLabelK.create(
component = textEntry,
component = currentPokemon.form.pokedex.first().asTranslated(),
width = 114 / smallTextScale,
maxLines = 5
).renderLeftAligned(
@@ -327,6 +402,8 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
matrices.popPose()
super.render(context, mouseX, mouseY, delta)
renderSelection(matrices)
}
fun getPlatformResource(type: ElementalType): ResourceLocation? {
@@ -340,14 +417,8 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
fun selectPokemon(category: RenderableStarterCategory, slot: Int) {
currentCategory = category
currentSelection = slot
if (currentSelection < currentCategory.pokemon.size) {
currentPokemon = currentCategory.pokemon[currentSelection].also {
modelWidget.pokemon = it
}
modelWidget.shouldRender = true
} else {
currentPokemon = null
modelWidget.shouldRender = false
currentPokemon = currentCategory.pokemon[currentSelection].also {
modelWidget.pokemon = it
}
}
@@ -52,8 +52,6 @@ class CategoryList(
private val buttonResource = cobblemonResource("textures/gui/starterselection/starter_selection.png")
private val buttonActiveResource = cobblemonResource("textures/gui/starterselection/starter_selection_active.png")
private val unknownResource = cobblemonResource("textures/gui/pokedex/platform_unknown.png")
const val MARGIN = 4
const val HEADER_HEIGHT = 7
const val BUTTON_SEPERATION = 3
@@ -61,9 +59,12 @@ class CategoryList(
const val CONTAINER_WIDTH = 89
}
val generatedCategoriesWidgets = mutableListOf<Category>()
init {
addEntry(EmptySeparator(MARGIN))
createEntries().forEach {
generatedCategoriesWidgets.add(it)
addEntry(it)
addEntry(EmptySeparator(MARGIN))
}
@@ -75,7 +76,18 @@ class CategoryList(
Category(category, height)
}
inner class Category(private val category: RenderableStarterCategory, height: Int) : Entry(height) {
override fun ensureVisible(entry: Entry) {
if (entry !in children) return
val yTop = entry.scrolledY - scrollAmount.toInt()
if (yTop - MARGIN < 0) {
setScroll((entry.scrolledY - MARGIN).toDouble())
} else if (yTop + entry.height + MARGIN > height) {
setScroll((entry.scrolledY + entry.height - height + MARGIN).toDouble())
}
}
inner class Category(val category: RenderableStarterCategory, height: Int) : Entry(height) {
var controllerButtonSelected = -1
override fun render(
guiGraphics: GuiGraphics,
@@ -86,8 +98,7 @@ class CategoryList(
mouseY: Int,
partialTick: Float
) {
val itemSize = category.pokemon.size + if (category.randomStarter) 1 else 0
val rows = (itemSize - 1) / 3 + 1
val rows = (category.pokemon.size - 1) / 3 + 1
val matrices = guiGraphics.pose()
//Header
blitk(
@@ -155,7 +166,7 @@ class CategoryList(
)
//Buttons
for (index in 0..<itemSize) {
for (index in 0..<category.pokemon.size) {
val row = index / 3
val column = index % 3
@@ -173,36 +184,24 @@ class CategoryList(
width = CONTAINER_BUTTON_SIZE,
height = CONTAINER_BUTTON_SIZE,
textureHeight = CONTAINER_BUTTON_SIZE * 2,
vOffset = if (hovered) CONTAINER_BUTTON_SIZE else 0
vOffset = if (hovered || controllerButtonSelected == index) CONTAINER_BUTTON_SIZE else 0
)
if (index == category.pokemon.size) {
blitk(
matrixStack = matrices,
texture = unknownResource,
x = (startX + 4.5F) / 0.04f,
y = (startY + 3.5F) / 0.04f,
width = 390,
height = 444,
scale = 0.04f
)
} else {
matrices.pushPose()
matrices.translate(startX + (CONTAINER_BUTTON_SIZE / 2.0), startY.toDouble() - 6, 0.0)
matrices.scale(3.5F, 3.5F, 1F)
matrices.pushPose()
matrices.translate(startX + (CONTAINER_BUTTON_SIZE / 2.0), startY.toDouble() - 6, 0.0)
matrices.scale(3.5F, 3.5F, 1F)
val state = FloatingState()
state.currentAspects = category.pokemon[index].aspects
drawProfilePokemon(
species = category.pokemon[index].species.resourceIdentifier,
matrixStack = matrices,
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = state,
scale = 4.5F,
partialTicks = 0F
)
matrices.popPose()
}
val state = FloatingState()
state.currentAspects = category.pokemon[index].aspects
drawProfilePokemon(
species = category.pokemon[index].species.resourceIdentifier,
matrixStack = matrices,
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = state,
scale = 4.5F,
partialTicks = 0F
)
matrices.popPose()
}
}
@@ -218,8 +217,7 @@ class CategoryList(
}
private fun getButtonHovered(x: Int, y: Int, mouseX: Double, mouseY: Double): Int {
val itemSize = category.pokemon.size + if (category.randomStarter) 1 else 0
for (index in 0..<itemSize) {
for (index in 0..<category.pokemon.size) {
val row = index / 3
val column = index % 3
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.client.gui.startselection.widgets
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.ColourLibrary
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
@@ -17,10 +15,8 @@ import com.cobblemon.mod.common.client.gui.CobblemonRenderable
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.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.Component
@@ -50,7 +46,7 @@ class SelectionButton(
width = BUTTON_WIDTH,
height = BUTTON_HEIGHT,
textureHeight = BUTTON_HEIGHT * 2,
vOffset = if (isHovered) BUTTON_HEIGHT else 0
vOffset = if (isHovered || isFocused) BUTTON_HEIGHT else 0
)
drawScaledText(
@@ -20,14 +20,27 @@ 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.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.widgets.EvolutionSelectScreen
import com.cobblemon.mod.common.client.gui.summary.widgets.MarkingsWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.NicknameEntryWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.PartySlotWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.PartyWidget
import com.cobblemon.mod.common.client.gui.*
import com.cobblemon.mod.common.client.gui.summary.widgets.*
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.SummaryTab
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.info.InfoWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.marks.MarksScrollingWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.marks.MarksWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MoveSwapScreen
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MovesWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.stats.StatWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.integration.controlify.gui.component.SubManagerComponent
import com.cobblemon.mod.common.client.keybind.keybinds.SummaryBinding
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetItemHiddenPacket
@@ -50,6 +63,8 @@ import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvent
import org.joml.Vector2i
import org.joml.Vector4i
/**
* The screen responsible for displaying various information regarding a Pokémon team.
@@ -60,7 +75,7 @@ import net.minecraft.sounds.SoundEvent
* @param selection The index the [party] will have as the base [selectedPokemon].
*/
class Summary private constructor(party: Collection<Pokemon?>, private val editable: Boolean, private val selection: Int): Screen(
Component.translatable("cobblemon.ui.summary.title")), Schedulable, CobblemonRenderable {
Component.translatable("cobblemon.ui.summary.title")), Schedulable, CobblemonRenderable, ControllerScreen {
companion object {
const val BASE_WIDTH = 331
@@ -69,10 +84,10 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
private const val SCALE = 0.5F
// Main Screen Index
private const val INFO = 0
private const val MOVES = 1
private const val STATS = 2
private const val MARKS = 3
const val INFO = 0
const val MOVES = 1
const val STATS = 2
const val MARKS = 3
// Side Screen Index
const val PARTY = 0
@@ -116,19 +131,32 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
override val schedulingTracker = SchedulingTracker()
private val party = ArrayList(party)
val party = ArrayList(party)
internal var selectedPokemon: Pokemon = this.party[selection] ?: this.party.first { it != null }!!
private lateinit var mainScreen: AbstractWidget
lateinit var mainScreen: AbstractWidget
lateinit var sideScreen: GuiEventListener
private lateinit var modelWidget: ModelWidget
private lateinit var nicknameEntryWidget: NicknameEntryWidget
lateinit var nicknameEntryWidget: NicknameEntryWidget
private lateinit var markingsWidget: MarkingsWidget
private val summaryTabs = mutableListOf<SummaryTab>()
val summaryTabs = mutableListOf<SummaryTab>()
private var showCosmeticItem = false
private lateinit var heldItemVisibilityButton: SummaryButton
private var mainScreenIndex = INFO
lateinit var heldItemVisibilityButton: SummaryButton
lateinit var heldItemSwitchTypeButton: SummaryButton
lateinit var evolveButton: SummaryButton
var mainScreenIndex = INFO
var sideScreenIndex = PARTY
val controllerComponentManager = ControllerComponentManager(this)
var mainScreenComponentManager: ControllerComponentManager? = null
var sideScreenComponentManager: ControllerComponentManager? = null
var selectedMoveIndex = 0
var moveSwitchingMode = false
var lastSelectedPartySlot = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun renderBlurredBackground(delta: Float) { }
/**
@@ -158,7 +186,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
addRenderableWidget(markingsWidget)
// Evolve Button
addRenderableWidget(
evolveButton = addRenderableWidget(
SummaryButton(
buttonX = x + 12F,
buttonY = y + 145F,
@@ -204,22 +232,22 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
heldItemVisibilityButton.buttonActive = !selectedPokemon.heldItemVisible
addRenderableWidget(heldItemVisibilityButton)
// Held/Cosmetic Item Button
addRenderableWidget(
SummaryButton(
buttonX = x + 67F,
buttonY = y + 113F,
buttonWidth = 12,
buttonHeight = 12,
scale = 0.5F,
resource = iconCosmeticItemResource,
activeResource = iconHeldItemResource,
clickAction = {
showCosmeticItem = !showCosmeticItem
(it as SummaryButton).buttonActive = showCosmeticItem
}
)
heldItemSwitchTypeButton = SummaryButton(
buttonX = x + 67F,
buttonY = y + 113F,
buttonWidth = 12,
buttonHeight = 12,
scale = 0.5F,
resource = iconCosmeticItemResource,
activeResource = iconHeldItemResource,
clickAction = {
showCosmeticItem = !showCosmeticItem
(it as SummaryButton).buttonActive = showCosmeticItem
}
)
addRenderableWidget(heldItemSwitchTypeButton)
// Init Tabs
summaryTabs.clear()
@@ -253,14 +281,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
// Add Exit Button
addRenderableWidget(
ExitButton(pX = x + 302, pY = y + 145) {
playSound(CobblemonSounds.GUI_CLICK)
if (sideScreenIndex != PARTY) {
displaySideScreen(PARTY)
} else {
saveActiveMark()
saveMarkings()
Minecraft.getInstance().setScreen(null)
}
onBack()
}
)
@@ -294,9 +315,214 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
addRenderableWidget(this.modelWidget)
updatePokemonInfo()
//Controller Support
controllerComponentManager.componentList["nickname"] = ControllerComponent().also {
it.associatedGUIElement = nicknameEntryWidget
it.right = { controllerComponentManager.componentList["center_screen"] }
}
controllerComponentManager.componentList["held_item"] = ControllerComponent().also {
it.associatedGUIElement = heldItemVisibilityButton
it.right = { controllerComponentManager.componentList["center_screen"] }
it.pairUp(controllerComponentManager.componentList["nickname"]!!)
it.down = { if (evolveButton.renderRequirement.invoke(evolveButton)) controllerComponentManager.componentList["evolve"] else null }
it.onSpecial1 = {
playSound(CobblemonSounds.GUI_CLICK)
heldItemSwitchTypeButton.onPress()
}
it.onPress = {
if (heldItemVisibilityButton.clickRequirement.invoke(heldItemVisibilityButton)) heldItemVisibilityButton.onPress()
}
}
controllerComponentManager.componentList["evolve"] = ControllerComponent().also {
it.associatedGUIElement = evolveButton
it.right = { controllerComponentManager.componentList["center_screen"] }
it.up = { controllerComponentManager.componentList["held_item"] }
}
controllerComponentManager.componentList["center_screen"] = SubManagerComponent().also {
it.subManager = { mainScreenComponentManager }
it.left = {
var returnVal: ControllerComponent? = null
if (mainScreenComponentManager?.left() ?: true && !moveSwitchingMode) {
returnVal = controllerComponentManager.componentList["nickname"]
}
returnVal
}
it.right = {
var returnVal: ControllerComponent? = null
if (mainScreenComponentManager?.right() ?: true && !moveSwitchingMode) {
returnVal = controllerComponentManager.componentList["right_screen"]
}
returnVal
}
it.up = {
if (mainScreenIndex == STATS) {
val statWidget = mainScreen as StatWidget
var index = statWidget.statTabIndex + 1
if (index > statWidget.statOptions.size - 1) {
index = 0
}
statWidget.statTabIndex = index
playSound(CobblemonSounds.GUI_CLICK)
} else {
mainScreenComponentManager?.up()
}
null
}
it.down = {
if (mainScreenIndex == STATS) {
val statWidget = mainScreen as StatWidget
var index = statWidget.statTabIndex - 1
if (index < 0) {
index = statWidget.statOptions.size - 1
}
statWidget.statTabIndex = index
playSound(CobblemonSounds.GUI_CLICK)
} else {
mainScreenComponentManager?.down()
}
null
}
it.onPress = {
if (mainScreenIndex == STATS) {
val statWidget = mainScreen as StatWidget
if (statWidget.statOptions.get(statWidget.statTabIndex) == StatWidget.RIDE && (statWidget.pokemon.riding.behaviours?.size ?: 0) > 1) {
statWidget.rideBehaviourIndex = (statWidget.rideBehaviourIndex + 1) % (statWidget.pokemon.riding.behaviours?.size ?: 1)
playSound(CobblemonSounds.GUI_CLICK)
}
} else {
mainScreenComponentManager?.press()
}
}
}
controllerComponentManager.componentList["right_screen"] = SubManagerComponent().also {
it.subManager = { sideScreenComponentManager }
it.left = {
var returnVal: ControllerComponent? = null
if (sideScreenComponentManager?.left() ?: true && sideScreenIndex == PARTY && (sideScreen as? PartyWidget)?.swapSource == null) {
returnVal = controllerComponentManager.componentList["center_screen"]
}
returnVal
}
}
controllerComponentManager.let {
it.focused = it.componentList["center_screen"]
it.onTabNav = { nextTab ->
var index = mainScreenIndex + if (nextTab) 1 else -1
if (index < 0) index = summaryTabs.size - 1
if (index > summaryTabs.size - 1) index = 0
playSound(CobblemonSounds.GUI_CLICK)
displayMainScreen(index)
}
it.onSecondaryTabNav = { nextTab ->
val pokemonIndex = party.indexOf(selectedPokemon)
val indexList = party.filter { it != null }.map { party.indexOf(it) }.toMutableList()
var index = indexList.indexOf(pokemonIndex) + if (nextTab) 1 else -1
if (index < 0) index = indexList.size - 1
if (index > indexList.size - 1) index = 0
playSound(CobblemonSounds.GUI_CLICK)
displaySideScreen(PARTY)
switchSelection(indexList[index])
}
it.onBack = {
if (moveSwitchingMode) {
playSound(CobblemonSounds.GUI_CLICK)
moveSwitchingMode = false
val movesWidget = mainScreen as? MovesWidget
if (movesWidget != null) {
movesWidget.controllerComponentManager.focused?.onUnselect()
}
} else {
val partyWidget = sideScreen as? PartyWidget
if (partyWidget != null && partyWidget.swapSource != null) {
partyWidget.draggedSlot = null
partyWidget.controllerComponentManager.let {
it.focused = it.componentList["slot_${partyWidget.swapSource}"]
}
partyWidget.swapSource = null
playSound(CobblemonSounds.PC_DROP)
} else {
onBack()
}
}
}
it.onActiveChange = { state ->
if (!state) {
moveSwitchingMode = false
}
if (mainScreenComponentManager != null) {
if (!state) {
mainScreenComponentManager?.active = false
} else {
if (it.currentSelectedComponent == "center_screen") mainScreenComponentManager?.active = true
}
}
if (sideScreenComponentManager != null) {
if (!state) {
sideScreenComponentManager?.active = false
} else {
if (it.currentSelectedComponent == "right_screen") sideScreenComponentManager?.active = true
}
}
}
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SECONDARY_TAB,
{ true },
Component.translatable("cobblemon.controller.summary.quick_switch"),
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "center_screen" && mainScreenIndex == MOVES },
Component.translatable("cobblemon.controller.summary.change_move"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "held_item" },
Component.translatable("cobblemon.controller.toggle_held_item_type"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_2,
{ it.currentSelectedComponent == "center_screen" && mainScreenIndex == MOVES },
Component.translatable("cobblemon.controller.summary.adjust_move"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_2,
{ it.currentSelectedComponent == "center_screen" && mainScreenIndex == MARKS },
Component.translatable("cobblemon.controller.summary.clear_mark"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_2,
{ it.currentSelectedComponent == "right_screen" && sideScreenIndex == PARTY },
Component.translatable("cobblemon.controller.summary.swap_position"),
true
))
}
}
fun onBack() {
playSound(CobblemonSounds.GUI_CLICK)
if (sideScreenIndex != PARTY) {
if (sideScreenIndex == EVOLVE) {
controllerComponentManager.focused = controllerComponentManager.componentList["evolve"]
} else {
controllerComponentManager.focused = controllerComponentManager.componentList["center_screen"]
}
displaySideScreen(PARTY)
} else {
saveActiveMark()
saveMarkings()
Minecraft.getInstance().setScreen(null)
}
}
fun swapPartySlot(sourceIndex: Int, targetIndex: Int) {
moveSwitchingMode = false
if (sourceIndex >= this.party.size || targetIndex >= this.party.size) {
return
}
@@ -357,11 +583,11 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
}
}
private fun saveMarkings() {
fun saveMarkings() {
if (::markingsWidget.isInitialized) markingsWidget.saveMarkingsToPokemon()
}
private fun saveActiveMark() {
fun saveActiveMark() {
if (mainScreenIndex == MARKS && mainScreen is MarksWidget) (mainScreen as MarksWidget).saveActiveMarkToPokemon()
}
@@ -373,12 +599,16 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
/**
* Switch center screen
*/
private fun displayMainScreen(screen: Int) {
fun displayMainScreen(screen: Int) {
// Save mark to Pokémon if any changes when switching out of marks screen
saveActiveMark()
// Get stat tab index if currently displaying stat screen
val subIndex = if (mainScreenIndex == STATS && mainScreen is StatWidget) (mainScreen as StatWidget).statTabIndex else 0
val subIndex = if (mainScreenIndex == STATS && mainScreen is StatWidget) (mainScreen as StatWidget).rawStatTabIndex else 0
if (screen != MOVES) {
moveSwitchingMode = false
}
mainScreenIndex = screen
if (::mainScreen.isInitialized) removeWidget(mainScreen)
@@ -398,6 +628,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
pY = y + 12,
pokemon = this.selectedPokemon
)
mainScreenComponentManager = null
}
MOVES -> {
@@ -405,7 +636,13 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
pX = x + 77,
pY = y + 12,
summary = this
)
).also { movesWidget ->
mainScreenComponentManager = movesWidget.controllerComponentManager
movesWidget.controllerComponentManager.let {
it.focused = it.componentList["move_${selectedMoveIndex}"]
}
}
}
STATS -> {
@@ -413,25 +650,38 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
pX = x + 77,
pY = y + 12,
pokemon = this.selectedPokemon,
tabIndex = subIndex
tabIndex = subIndex,
summary = this
)
mainScreenComponentManager = null
}
MARKS -> {
mainScreen = MarksWidget(
pX = x + 77,
pY = y + 12,
pokemon = this.selectedPokemon
)
pokemon = this.selectedPokemon,
summary = this
).also {
mainScreenComponentManager = it.controllerComponentManager
}
}
}
addRenderableWidget(mainScreen)
if (controllerComponentManager.currentSelectedComponent == "center_screen" && controllerComponentManager.active) {
focused = null
mainScreenComponentManager?.active = true
controllerComponentManager.focused?.onFocus?.invoke()
}
}
/**
* Switch right screen
*/
fun displaySideScreen(screen: Int, move: Move? = null) {
moveSwitchingMode = false
sideScreenIndex = screen
if (::sideScreen.isInitialized) removeWidget(sideScreen)
@@ -446,7 +696,9 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
isParty = selectedPokemon in CobblemonClient.storage.party,
summary = this,
partyList = this.party
)
).also {
sideScreenComponentManager = it.controllerComponentManager
}
}
MOVE_SWAP -> {
@@ -468,7 +720,9 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
moveSlotList += MoveSwapScreen.MoveSlot(switchPane, null, 0, pokemon)
}
moveSlotList.forEach { switchPane.addEntry(it) }
sideScreenComponentManager = switchPane.controllerComponentManager
}
controllerComponentManager.focused = controllerComponentManager.componentList["right_screen"]
}
}
@@ -478,14 +732,67 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
sideScreen = EvolutionSelectScreen(
x + 216,
y + 23,
pokemon = selectedPokemon
)
pokemon = selectedPokemon,
summary = this
).also {
sideScreenComponentManager = it.controllerComponentManager
}
controllerComponentManager.focused = controllerComponentManager.componentList["right_screen"]
}
}
val element = sideScreen
if (element is Renderable && element is NarratableEntry) {
addRenderableWidget(element)
}
if (controllerComponentManager.currentSelectedComponent == "right_screen" && controllerComponentManager.active) {
focused = null
sideScreenComponentManager?.active = true
controllerComponentManager.focused?.onFocus?.invoke()
}
}
override fun getSelectionPosAndSize(): Vector4i? {
val data = super.getSelectionPosAndSize()
if (data != null) {
when (focused) {
nicknameEntryWidget -> return Vector4i(nicknameEntryWidget.x - 12, nicknameEntryWidget.y - 4, 78, 17)
heldItemVisibilityButton -> return Vector4i(data.x + 1, data.y + 1, data.z / 2, data.w / 2)
is PartySlotWidget -> return data.add(-2, -2, 0, 2)
else -> return data
}
} else {
when (controllerComponentManager.currentSelectedComponent) {
"center_screen" -> {
when (mainScreenIndex) {
MARKS -> {
val marksScreen = mainScreen as MarksWidget
val horizontalSpacing = if (marksScreen.marksScrollList.children().size > 5) MarksScrollingWidget.SLOT_SPACING else (MarksScrollingWidget.SLOT_SPACING + 1)
val row = marksScreen.lastSelectedMark / 6
val column = marksScreen.lastSelectedMark % 6
val startX = marksScreen.marksScrollList.rowLeft + (horizontalSpacing + MarksScrollingWidget.SLOT_SIZE) * column
val startY = marksScreen.marksScrollList.getRowTop(row) + MarksScrollingWidget.SLOT_SPACING
return Vector4i(startX - 2, startY - 2, MarksScrollingWidget.SLOT_SIZE + 4, MarksScrollingWidget.SLOT_SIZE + 4)
}
else -> return Vector4i(mainScreen.x + 2, mainScreen.y + 2, 130, 144)
}
}
"right_screen" -> {
when (sideScreenIndex) {
EVOLVE -> {
val evolveScreen = sideScreen as EvolutionSelectScreen
return Vector4i(evolveScreen.rowLeft - 5, evolveScreen.getRowTop(evolveScreen.children().indexOf(focused)) - 2, evolveScreen.rowWidth + 4, EvolutionSelectScreen.SLOT_HEIGHT + 4)
}
MOVE_SWAP -> {
val moveSwapScreen = sideScreen as MoveSwapScreen
return Vector4i(moveSwapScreen.rowLeft - 11, moveSwapScreen.getRowTop(moveSwapScreen.children().indexOf(focused)) - 2, moveSwapScreen.rowWidth + 13, MoveSwapScreen.SLOT_HEIGHT + 4)
}
}
}
}
}
return null
}
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -625,8 +932,11 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
val itemX = x + 3
val itemY = y + 104
if (!displayedItem.isEmpty) {
matrices.pushPose()
matrices.translate(0.0,0.0,-200.0)
context.renderItem(displayedItem, itemX, itemY)
context.renderItemDecorations(Minecraft.getInstance().font, displayedItem, itemX, itemY)
matrices.popPose()
}
drawScaledText(
@@ -655,19 +965,25 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
scale = SCALE
)
matrices.pushPose()
// Prevent widgets from being overlapped by other components
matrices.translate(0.0, 0.0, 1000.0)
// Set Look View Pos
val selectionData = getSelectionPosAndSize()
if (controllerComponentManager.active && selectionData != null) {
modelWidget.overrideLookPos = Vector2i(selectionData.x + selectionData.z / 2, selectionData.y + selectionData.w / 2)
} else {
modelWidget.overrideLookPos = null
}
// Render all added Widgets
super.render(context, mouseX, mouseY, delta)
// Selection Rendering
renderSelection(matrices)
// Render Item Tooltip
if (!displayedItem.isEmpty) {
val itemHovered = mouseX.toFloat() in (itemX.toFloat()..(itemX.toFloat() + 16)) && mouseY.toFloat() in (itemY.toFloat()..(itemY.toFloat() + 16))
if (itemHovered) context.renderTooltip(Minecraft.getInstance().font, displayedItem, mouseX, mouseY)
}
matrices.popPose()
}
/**
@@ -32,8 +32,8 @@ class SummaryButton(
private val text: MutableComponent = Component.empty(),
private val resource: ResourceLocation,
private val activeResource: ResourceLocation? = null,
private val renderRequirement: ((button: SummaryButton) -> Boolean) = { true },
private val clickRequirement: ((button: SummaryButton) -> Boolean) = { true },
val renderRequirement: ((button: SummaryButton) -> Boolean) = { true },
val clickRequirement: ((button: SummaryButton) -> Boolean) = { true },
private val hoverTexture: Boolean = true,
private val silent: Boolean = false,
private val boldText: Boolean = true,
@@ -67,7 +67,7 @@ class SummaryButton(
y = buttonY / scale,
width = buttonWidth,
height = buttonHeight,
vOffset = if (hoverTexture && isButtonHovered(pMouseX, pMouseY)) buttonHeight else 0,
vOffset = if (hoverTexture && (isButtonHovered(pMouseX, pMouseY) || isFocused)) buttonHeight else 0,
textureHeight = if (hoverTexture) (buttonHeight.toFloat() * 2) else buttonHeight,
scale = scale
)
@@ -15,8 +15,11 @@ import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.SummaryButton
import com.cobblemon.mod.common.client.gui.summary.widgets.common.SummaryScrollList
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.pokemon.FormData
@@ -34,7 +37,8 @@ import org.joml.Vector3f
class EvolutionSelectScreen(
x: Int,
y: Int,
val pokemon: Pokemon
val pokemon: Pokemon,
val summary: Summary
): SummaryScrollList<EvolutionSelectScreen.EvolveSlot>(
x,
y,
@@ -52,8 +56,36 @@ class EvolutionSelectScreen(
private var entriesCreated = false
var controllerComponentManager = ControllerComponentManager(summary).also {
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
public override fun addEntry(entry: EvolveSlot): Int {
return super.addEntry(entry)
val i = super.addEntry(entry)
controllerComponentManager.componentList["moves_${i}"] = ControllerComponent().also {
it.associatedGUIElement = entry
it.onPress = {
entry.selectButton.onPress()
}
it.onSelect = {
ensureVisible(entry)
entry.selectButton.isFocused = true
}
it.onUnselect = {
entry.selectButton.isFocused = false
}
}
if (children().size > 1) {
controllerComponentManager.componentList["moves_${i}"]?.let {
it.pairUp(controllerComponentManager.componentList["moves_${i-1}"])
it.pairDown(controllerComponentManager.componentList["moves_${0}"])
}
} else {
controllerComponentManager.focused = controllerComponentManager.componentList["moves_${i}"]
}
return i
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import org.joml.Quaternionf
import org.joml.Vector2i
import org.joml.Vector3f
class ModelWidget(
@@ -36,6 +37,7 @@ class ModelWidget(
): SoundlessWidget(pX, pY, pWidth, pHeight, Component.literal("Summary - ModelWidget")) {
companion object {
var render = true
const val MAX_SECONDS_LOOKING = 6L
}
@@ -58,10 +60,10 @@ class ModelWidget(
var currentYawAndPitch: Pair<Float, Float> = Pair(0f, 0f)
val rotationVector = Vector3f(13F, rotationY, 0F)
var shouldRender = true
var overrideLookPos: Vector2i? = null
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, partialTicks: Float) {
if (!shouldRender) {
if (!render) {
return
}
isHovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height
@@ -89,7 +91,7 @@ class ModelWidget(
val timeLooking = lookStartTime?.let { (System.currentTimeMillis() - it) / 1000 } ?: MAX_SECONDS_LOOKING
val lookedTooMuch = timeLooking >= MAX_SECONDS_LOOKING
val resetToCenter = (!isHovered && lookedTooMuch) || !shouldFollowCursor || !Cobblemon.config.summaryPokemonFollowCursor
val resetToCenter = (!isHovered && lookedTooMuch && overrideLookPos == null) || !shouldFollowCursor || !Cobblemon.config.summaryPokemonFollowCursor
if (resetToCenter) {
lookStartTime = null
@@ -98,12 +100,19 @@ class ModelWidget(
val rotation = Quaternionf().fromEulerXYZDegrees(rotationVector)
if (shouldFollowCursor) {
var xPos = mouseX
var yPos = mouseY
if (overrideLookPos != null) {
xPos = overrideLookPos!!.x
yPos = overrideLookPos!!.y
}
currentYawAndPitch = calculateHeadYawAndPitch(
x + width / 2f,
y + height / 2f,
rotation,
mouseX,
mouseY,
xPos,
yPos,
currentYawAndPitch.first,
currentYawAndPitch.second,
resetToCenter
@@ -121,6 +130,7 @@ class ModelWidget(
blockLight = blockLight,
profileTransformType = ProfileTransformType.SUMMARY,
)
context.flush()
heldItemRenderer.renderOnModel(
pokemon.heldItem,
@@ -88,7 +88,7 @@ class PartySlotWidget(
y = y,
width = width,
height = height,
vOffset = getSlotVOffset(slotPokemon, isHovered, isSelected),
vOffset = getSlotVOffset(slotPokemon, isHovered || isFocused, isSelected),
textureHeight = height * 2,
)
@@ -223,16 +223,21 @@ class PartySlotWidget(
if (partyWidget.swapEnabled) {
toggleDrag(true)
} else {
if ((index > -1) && (summary.selectedPokemon.uuid != pokemon?.uuid)) {
summary.switchSelection(index)
if (pokemon != null) partyWidget.playSound(CobblemonSounds.GUI_CLICK)
return true
}
if (onPress()) return true
}
}
return super.mouseClicked(pMouseX, pMouseY, pButton)
}
fun onPress(): Boolean {
if ((index > -1) && (summary.selectedPokemon.uuid != pokemon?.uuid)) {
summary.switchSelection(index)
if (pokemon != null) partyWidget.playSound(CobblemonSounds.GUI_CLICK)
return true
}
return false
}
override fun mouseReleased(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (partyWidget.swapEnabled) toggleDrag(false)
return partyWidget.mouseReleased(pMouseX, pMouseY, pButton)
@@ -14,6 +14,8 @@ import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.SummaryButton
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
@@ -67,6 +69,8 @@ class PartyWidget(
}
)
var controllerComponentManager = ControllerComponentManager(summary)
init {
if (partySize > 6 || partySize < 1)
throw InvalidParameterException("Invalid party size")
@@ -85,7 +89,7 @@ class PartyWidget(
y += (32 * offsetIndex) + offsetY
}
PartySlotWidget(
val slot = PartySlotWidget(
pX = x,
pY = y,
partyWidget = this,
@@ -97,6 +101,86 @@ class PartyWidget(
this.addWidget(widget)
partySlots.add(widget)
}
controllerComponentManager.componentList["slot_${index}"] = ControllerComponent().also {
it.associatedGUIElement = slot
it.onPress = {
if (swapEnabled) {
if (swapSource == null) {
if (partyList[index] != null) {
swapSource = index
draggedSlot = PartySlotWidget(
pX = slot.x + 4,
pY = slot.y + 4,
partyWidget = this,
summary = summary,
pokemon = partyList[index],
index = -1,
isClientPartyMember = isParty
)
draggedSlot?.isFocused = true
playSound(CobblemonSounds.PC_GRAB)
}
} else {
if (swapSource != index) {
summary.swapPartySlot(swapSource!!, index)
}
swapSource = null
draggedSlot = null
playSound(CobblemonSounds.PC_DROP)
}
} else slot.onPress()
}
it.onSpecial1 = {
swapEnabled = !swapEnabled
swapButton.buttonActive = swapEnabled
if (!swapEnabled) {
swapSource = null
draggedSlot = null
}
playSound(CobblemonSounds.GUI_CLICK)
}
fun updateDraggedSlotPos() {
draggedSlot?.let { theSlot ->
theSlot.x = slot.x + 4
theSlot.y = slot.y + 4
}
}
if (index-2 >= 0) {
it.up = {
controllerComponentManager.componentList["slot_${index-2}"]
}
}
if (index+2 <= 5) {
it.down = {
controllerComponentManager.componentList["slot_${index+2}"]
}
}
if (index.mod(2) == 0) {
it.right = {
controllerComponentManager.componentList["slot_${index+1}"]
}
} else {
it.left = {
controllerComponentManager.componentList["slot_${index-1}"]
}
}
it.onSelect = {
updateDraggedSlotPos()
summary.lastSelectedPartySlot = index
}
}
}
controllerComponentManager.let {
it.focused = it.componentList["slot_${summary.lastSelectedPartySlot}"]
it.onActiveChange = { state ->
if (!state) {
swapSource = null
draggedSlot = null
}
}
}
}
@@ -125,6 +125,23 @@ abstract class SummaryScrollList<T : ObjectSelectionList.Entry<T>>(
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
public override fun ensureVisible(entry: T) {
val i = getRowTop(children().indexOf(entry))
val topDifference = i - y - 4
if (topDifference < 0) {
scroll(topDifference)
}
val bottomDifference = bottom - i - itemHeight
if (bottomDifference < 0) {
scroll(-bottomDifference)
}
}
public override fun getRowTop(index: Int): Int {
return super.getRowTop(index)
}
private fun updateScrollingState(mouseX: Double, mouseY: Double) {
scrolling = mouseX >= this.scrollbarPosition.toDouble()
&& mouseX < (this.scrollbarPosition + 3).toDouble()
@@ -135,4 +152,6 @@ abstract class SummaryScrollList<T : ObjectSelectionList.Entry<T>>(
private fun correctSize() {
setRectangle(WIDTH, HEIGHT, listX, listY)
}
open fun onPress(entry: T) {}
}
@@ -106,10 +106,11 @@ class MarksScrollingWidget(val pX: Int, val pY: Int, val setSelectedMark: () ->
companion object {
private val slotResource = cobblemonResource("textures/gui/summary/summary_mark_slot.png")
}
var x: Int = 0
var y: Int = 0
var slotSelected: (Int) -> Boolean = { false }
fun renderRow(context: GuiGraphics, y: Int, x: Int, mouseX: Int, mouseY: Int): Boolean {
var anyHovered = false
val horizontalSpacing = if (isScrollVisible()) SLOT_SPACING else (SLOT_SPACING + 1)
@@ -133,7 +134,7 @@ class MarksScrollingWidget(val pX: Int, val pY: Int, val setSelectedMark: () ->
y = startPosY,
width = SLOT_SIZE,
height = SLOT_SIZE,
vOffset = if (slotHovered) SLOT_SIZE else 0,
vOffset = if (slotHovered || (mark != null && slotSelected.invoke(index))) SLOT_SIZE else 0,
textureHeight = SLOT_SIZE * 2
)
@@ -160,7 +161,7 @@ class MarksScrollingWidget(val pX: Int, val pY: Int, val setSelectedMark: () ->
return true
}
private fun isScrollVisible(): Boolean = parent.children().size > 5
fun isScrollVisible(): Boolean = parent.children().size > 5
private fun getHoveredSlotIndex(mouseX: Int, mouseY: Int): Int {
val horizontalSpacing = if (isScrollVisible()) SLOT_SPACING else (SLOT_SPACING + 1)
@@ -15,7 +15,10 @@ import com.cobblemon.mod.common.api.gui.MultiLineLabelK
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.mark.Mark
import com.cobblemon.mod.common.client.gui.common.MarkIcon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetActiveMarkPacket
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -27,7 +30,8 @@ import net.minecraft.network.chat.Component
class MarksWidget(
pX: Int, pY: Int,
val pokemon: Pokemon
val pokemon: Pokemon,
val summary: Summary
): SoundlessWidget(pX, pY, WIDTH, HEIGHT, Component.literal("MarksWidget")) {
companion object {
private const val WIDTH = 134
@@ -53,6 +57,9 @@ class MarksWidget(
setHoveredMark = { hoveredMark = it }
)
val controllerComponentManager = ControllerComponentManager(summary)
var lastSelectedMark = 0
init {
children.clear()
activeMark = pokemon.activeMark
@@ -64,6 +71,54 @@ class MarksWidget(
while ((marksList.size < 30) || (marksList.size > 30 && marksList.size % 6 != 0)) marksList.add(null)
marksScrollList.createEntries(marksList)
addWidget(marksScrollList)
marksScrollList.children().forEachIndexed { firstIndex, row ->
row.markList.forEachIndexed { secondIndex, mark ->
controllerComponentManager.componentList["mark_${firstIndex}_${secondIndex}"] = ControllerComponent().also {
it.associatedGUIElement = row
it.onPress = {
if (mark != null) {
selectedMark = mark
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
}
it.onSpecial1 = {
if (selectedMark !== null) {
selectedMark = null
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
}
it.onSelect = {
lastSelectedMark = firstIndex * 6 + secondIndex
marksScrollList.ensureVisible(row)
}
if (firstIndex - 1 >= 0) {
it.up = {
controllerComponentManager.componentList["mark_${firstIndex-1}_${secondIndex}"]
}
}
if (firstIndex + 1 <= marksScrollList.children().size - 1) {
it.down = {
controllerComponentManager.componentList["mark_${firstIndex+1}_${secondIndex}"]
}
}
if (secondIndex - 1 >= 0) {
it.left = {
controllerComponentManager.componentList["mark_${firstIndex}_${secondIndex-1}"]
}
}
if (secondIndex + 1 <= row.markList.size - 1) {
it.right = {
controllerComponentManager.componentList["mark_${firstIndex}_${secondIndex+1}"]
}
}
}
}
row.slotSelected = { index ->
controllerComponentManager.active && lastSelectedMark / 6 == firstIndex && lastSelectedMark % 6 == index
}
}
controllerComponentManager.let { it.focused = it.componentList["mark_${lastSelectedMark / 6}_${lastSelectedMark % 6}"] }
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
@@ -45,14 +45,14 @@ class MoveSlotWidget(
const val MOVE_HEIGHT = 22
}
private val moveUpButton = ReorderMoveButton(x, y, true) {
val moveUpButton = ReorderMoveButton(x, y, true) {
movesWidget.selectMove(null)
movesWidget.reorderMove(this, true)
}.apply {
addWidget(this)
}
private val moveDownButton = ReorderMoveButton(x, y, false) {
val moveDownButton = ReorderMoveButton(x, y, false) {
movesWidget.selectMove(null)
movesWidget.reorderMove(this, false)
}.apply {
@@ -20,6 +20,8 @@ import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.widgets.common.SummaryScrollList
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.BenchMovePacket
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -49,13 +51,45 @@ class MoveSwapScreen(
private val forgetMoveIconResource = cobblemonResource("textures/gui/summary/summary_move_condensed_empty_icon.png")
}
var controllerComponentManager = ControllerComponentManager(movesWidget.summary).also {
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
public override fun addEntry(entry: MoveSlot): Int {
return super.addEntry(entry)
val i = super.addEntry(entry)
controllerComponentManager.componentList["moves_${i}"] = ControllerComponent().also {
it.associatedGUIElement = entry
it.onPress = {
entry.onPress()
movesWidget.summary.controllerComponentManager.let { controller -> controller.focused = controller.componentList["center_screen"] }
}
it.onSelect = {
ensureVisible(entry)
entry.slotSelected = true
}
it.onUnselect = {
entry.slotSelected = false
}
}
if (children().size > 1) {
controllerComponentManager.componentList["moves_${i}"]?.let {
it.pairUp(controllerComponentManager.componentList["moves_${i-1}"])
it.pairDown(controllerComponentManager.componentList["moves_${0}"])
}
} else {
controllerComponentManager.focused = controllerComponentManager.componentList["moves_${i}"]
}
return i
}
class MoveSlot(val pane: MoveSwapScreen, val move: MoveTemplate?, val ppRaisedStages: Int, pokemon: Pokemon) : Entry<MoveSlot>() {
override fun getNarration() = move?.displayName ?: lang("ui.moves.forget")
val elementalType = move?.getEffectiveElementalType(pokemon) ?: ElementalTypes.NORMAL
var slotSelected = false
override fun render(
context: GuiGraphics,
index: Int,
@@ -80,7 +114,7 @@ class MoveSwapScreen(
y = tweakedRowTop,
height = SLOT_HEIGHT,
width = rowWidth,
vOffset = if (isHovered) SLOT_HEIGHT else 0,
vOffset = if (isHovered || slotSelected) SLOT_HEIGHT else 0,
textureHeight = SLOT_HEIGHT * 2,
red = rgb.first,
green = rgb.second,
@@ -196,7 +230,7 @@ class MoveSwapScreen(
y = tweakedRowTop,
height = SLOT_HEIGHT,
width = 100,
vOffset = if (isHovered) SLOT_HEIGHT else 0,
vOffset = if (isHovered || slotSelected) SLOT_HEIGHT else 0,
textureHeight = SLOT_HEIGHT * 2,
)
blitk(
@@ -206,7 +240,7 @@ class MoveSwapScreen(
y = tweakedRowTop + SLOT_HEIGHT / 2 - 4,
height = 7,
width = 7,
vOffset = if (isHovered) 7 else 0,
vOffset = if (isHovered || slotSelected) 7 else 0,
textureHeight = 14,
)
}
@@ -215,21 +249,29 @@ class MoveSwapScreen(
override fun mouseClicked(d: Double, e: Double, i: Int): Boolean {
if (isMouseOver(d, e)) {
val pokemon = pane.movesWidget.summary.selectedPokemon
val isParty = pokemon in CobblemonClient.storage.party
CobblemonNetwork.sendToServer(
BenchMovePacket(
isParty = isParty,
uuid = pokemon.uuid,
oldMove = pane.replacedMove?.template,
newMove = move
)
)
pane.movesWidget.summary.playSound(CobblemonSounds.GUI_CLICK)
pane.movesWidget.summary.displaySideScreen(Summary.PARTY)
onPress()
return true
}
return false
}
fun onPress() {
switchMove()
pane.movesWidget.summary.playSound(CobblemonSounds.GUI_CLICK)
pane.movesWidget.summary.displaySideScreen(Summary.PARTY)
}
fun switchMove() {
val pokemon = pane.movesWidget.summary.selectedPokemon
val isParty = pokemon in CobblemonClient.storage.party
CobblemonNetwork.sendToServer(
BenchMovePacket(
isParty = isParty,
uuid = pokemon.uuid,
oldMove = pane.replacedMove?.template,
newMove = move
)
)
}
}
}
@@ -9,12 +9,15 @@
package com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.moves.Move
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.RequestMoveSwapPacket
import com.cobblemon.mod.common.util.cobblemonResource
@@ -52,8 +55,8 @@ class MovesWidget(
var selectedMove: Move? = null
private var index = -1
private val moves = (
var index = -1
val moves = (
if (summary.selectedPokemon.relearnableMoves.count() > 0)
summary.selectedPokemon.moveSet.getMovesWithNulls()
else summary.selectedPokemon.moveSet
@@ -76,6 +79,79 @@ class MovesWidget(
5
)
val controllerComponentManager = ControllerComponentManager(summary).also { controller ->
var i = 0
moves.forEach { move ->
controller.componentList["move_${i}"] = ControllerComponent().also {
it.associatedGUIElement = move
var upLoc = i - 1
if (upLoc < 0) upLoc = moves.size - 1
it.up = {
var returnVal: ControllerComponent?
if (!summary.moveSwitchingMode) {
returnVal = controller.componentList["move_${upLoc}"]
} else {
returnVal = null
summary.selectedMoveIndex = upLoc
reorderMove(move, true)
}
returnVal
}
var downLoc = i + 1
if (downLoc > moves.size - 1) downLoc = 0
it.down = {
var returnVal: ControllerComponent?
if (!summary.moveSwitchingMode) {
returnVal = controller.componentList["move_${downLoc}"]
} else {
returnVal = null
summary.selectedMoveIndex = downLoc
reorderMove(move, false)
}
returnVal
}
it.onSpecial1 = {
if (!summary.moveSwitchingMode) {
summary.playSound(CobblemonSounds.GUI_CLICK)
summary.displaySideScreen(Summary.MOVE_SWAP, move.move)
}
}
it.onSpecial2 = {
summary.playSound(CobblemonSounds.GUI_CLICK)
summary.moveSwitchingMode = !summary.moveSwitchingMode
if (summary.moveSwitchingMode) {
move.moveUpButton.isFocused = true
move.moveDownButton.isFocused = true
} else {
move.moveUpButton.isFocused = false
move.moveDownButton.isFocused = false
}
}
val staticI = i
it.onSelect = {
summary.selectedMoveIndex = staticI
if (selectedMove != move.move) selectMove(move.move)
if (summary.moveSwitchingMode) {
move.moveUpButton.isFocused = true
move.moveDownButton.isFocused = true
}
}
it.onUnselect = {
move.moveUpButton.isFocused = false
move.moveDownButton.isFocused = false
}
}
i++
}
controller.onNoneSelected = {
summary.selectedMoveIndex = summary.selectedMoveIndex.coerceIn(0, moves.size - 1)
controller.focused = controller.componentList["move_${summary.selectedMoveIndex}"]
true
}
controller.onActiveChange = { state ->
if (state) controller.focused?.onSelect?.invoke() else controller.focused?.onUnselect?.invoke()
}
}
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
val matrices = context.pose()
@@ -50,7 +50,7 @@ class ReorderMoveButton(
texture = if (isUp) moveReorderUpResource else moveReorderDownResource,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(pMouseX.toDouble(), pMouseY.toDouble(), offsetY.toFloat())) HEIGHT else 0,
vOffset = if (isHovered(pMouseX.toDouble(), pMouseY.toDouble(), offsetY.toFloat()) || isFocused) HEIGHT else 0,
textureHeight = HEIGHT * 2,
scale = SCALE
)
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.SummaryButton
import com.cobblemon.mod.common.client.gui.summary.featurerenderers.BarSummarySpeciesFeatureRenderer
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
@@ -49,16 +50,17 @@ import org.joml.Vector3f
class StatWidget(
pX: Int, pY: Int,
val pokemon: Pokemon,
val tabIndex: Int = 0
val tabIndex: Int = 0,
val summary: Summary
): SoundlessWidget(pX, pY, WIDTH, HEIGHT, Component.literal("StatWidget")) {
companion object {
// Stat tab options
private const val STATS = "stats"
private const val IV = "ivs"
private const val EV = "evs"
private const val RIDE = "ride"
private const val OTHER = "other"
const val STATS = "stats"
const val IV = "ivs"
const val EV = "evs"
const val RIDE = "ride"
const val OTHER = "other"
private const val OTHER_STAT_BARS_PER_PAGE = 4
private const val STAT_TAB_WIDTH_6 = 22
@@ -113,13 +115,13 @@ class StatWidget(
)
}
private val statOptions = if (pokemon.form.riding.behaviours != null) listOf(STATS, IV, EV, RIDE, OTHER) else listOf(STATS, IV, EV, OTHER)
val statOptions = if (pokemon.form.riding.behaviours != null) listOf(STATS, IV, EV, RIDE, OTHER) else listOf(STATS, IV, EV, OTHER)
private var _statTabIndex: Int = tabIndex
var rawStatTabIndex: Int = tabIndex
var statTabIndex: Int
get() = _statTabIndex.coerceIn(0, statOptions.size - 1)
get() = rawStatTabIndex.coerceIn(0, statOptions.size - 1)
set(value) {
_statTabIndex = value.coerceIn(0, statOptions.size - 1)
rawStatTabIndex = value.coerceIn(0, statOptions.size - 1)
}
private var _rideBehaviourIndex: Int = 0
@@ -302,7 +304,7 @@ class StatWidget(
pokemon.form.riding.behaviours?.let {
val behaviours = it.entries.toList()
val selectedBehaviour = behaviours[rideBehaviourIndex]
val canSwitchStyle = behaviours.size > 1 && centerHovered(pMouseX, pMouseY)
val canSwitchStyle = behaviours.size > 1 && (centerHovered(pMouseX, pMouseY) || (summary.controllerComponentManager.let { it.active && it.currentSelectedComponent == "center_screen" }))
blitk(
matrixStack = matrices,
@@ -1267,19 +1267,6 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
}
override fun hasClickedOutside(
mouseX: Double,
mouseY: Double,
guiLeft: Int,
guiTop: Int,
mouseButton: Int
): Boolean {
partySlotList.forEach {
if (it.pokemon != null && it.isMouseOver(mouseX, mouseY)) return false
}
return super.hasClickedOutside(mouseX, mouseY, guiLeft, guiTop, mouseButton)
}
class MoveDescriptionWidget(pX: Int, pY: Int, width: Int, height: Int) : ScrollingWidget<MoveDescriptionWidget.TextSlot>(
left = pX,
top = pY - height,
@@ -49,7 +49,7 @@ open class PartySlot(
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val matrices = context.pose()
if (!isOpposing && isHovered(mouseX, mouseY)) {
if (!isOpposing && (isHovered(mouseX, mouseY) || isFocused)) {
blitk(
matrixStack = matrices,
texture = hoverBackgroundResource,
@@ -49,7 +49,7 @@ class TradeButton(
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || isFocused) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
@@ -22,6 +22,9 @@ import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.widgets.MarkingsWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.common.reformatNatureTextIfMinted
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
import com.cobblemon.mod.common.client.trade.ClientTrade
@@ -55,7 +58,7 @@ class TradeGUI(
val traderName: MutableComponent,
val traderParty: MutableList<TradeablePokemon?>,
val party: MutableList<TradeablePokemon?>
): Screen(lang("trade.gui.title")), CobblemonRenderable {
): Screen(lang("trade.gui.title")), CobblemonRenderable, ControllerScreen {
companion object {
const val BASE_WIDTH = 293
@@ -118,6 +121,13 @@ class TradeGUI(
var tradeSoundInstance: SoundInstance? = null
val controllerComponentManager = ControllerComponentManager(this)
var lastSelectedRow = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
init {
trade.cancelEmitter.subscribe {
cancelTradeSound()
@@ -178,21 +188,25 @@ class TradeGUI(
)
// Trade Button
this.addRenderableWidget(
TradeButton(
x = x + 120,
y = y + 119,
parent = this,
onPress = {
if (offeredPokemon != null && opposingOfferedPokemon != null && protectiveTicks <= 0 && !tradeProcessing) {
ticksElapsed = 0
readyProgress = 0
CobblemonNetwork.sendToServer(ChangeTradeAcceptancePacket(opposingOfferedPokemon!!.uuid, !trade.acceptedOppositeOffer))
playSound(CobblemonSounds.GUI_CLICK)
}
val tradeButton = TradeButton(
x = x + 120,
y = y + 119,
parent = this,
onPress = {
if (offeredPokemon != null && opposingOfferedPokemon != null && protectiveTicks <= 0 && !tradeProcessing) {
ticksElapsed = 0
readyProgress = 0
CobblemonNetwork.sendToServer(ChangeTradeAcceptancePacket(opposingOfferedPokemon!!.uuid, !trade.acceptedOppositeOffer))
playSound(CobblemonSounds.GUI_CLICK)
}
)
}
)
this.addRenderableWidget(tradeButton)
controllerComponentManager.componentList["trade"] = ControllerComponent().also {
it.associatedGUIElement = tradeButton
it.left = { controllerComponentManager.componentList["slot_${lastSelectedRow}_${1}"] }
}
// Party
for (partyIndex in 0..5) {
@@ -210,7 +224,7 @@ class TradeGUI(
}
val pokemon = party[partyIndex]
PartySlot(
val slot = PartySlot(
x = slotX,
y = slotY,
pokemon = pokemon,
@@ -223,6 +237,22 @@ class TradeGUI(
}
}
).also { widget -> addRenderableWidget(widget) }
val row = partyIndex / 2
val column = partyIndex % 2
controllerComponentManager.componentList["slot_${row}_${column}"] = ControllerComponent().also {
it.associatedGUIElement = slot
if (column == 0) it.right = { controllerComponentManager.componentList["slot_${row}_${1}"] }
else {
it.left = { controllerComponentManager.componentList["slot_${row}_${0}"] }
it.right = { controllerComponentManager.componentList["trade"] }
}
if (row - 1 >= 0) it.up = { controllerComponentManager.componentList["slot_${row-1}_${column}"] }
if (row + 1 <= 2) it.down = { controllerComponentManager.componentList["slot_${row+1}_${column}"] }
it.onSelect = {
lastSelectedRow = row
}
}
}
// Opposing Party
@@ -257,6 +287,10 @@ class TradeGUI(
opposingOfferedPokemonMarkings = MarkingsWidget(x + 177.5, y + 21, opposingOfferedPokemon, false)
addRenderableWidget(offeredPokemonMarkings)
addRenderableWidget(opposingOfferedPokemonMarkings)
controllerComponentManager.let {
it.focused = it.componentList["slot_0_0"]
}
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -445,6 +479,8 @@ class TradeGUI(
val itemHovered = mouseX.toFloat() in (itemX.toFloat()..(itemX.toFloat() + 16)) && mouseY.toFloat() in (itemY.toFloat()..(itemY.toFloat() + 16))
if (itemHovered) context.renderTooltip(Minecraft.getInstance().font, opposingOfferedPokemon!!.heldItemNoCopy(), mouseX, mouseY)
}
renderSelection(matrices)
}
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
Loaded 100 of 3097 files, more files were not shown because too many files have changed in this diff. Show more