Compare commits

..
Author SHA1 Message Date
saultsc 2e88d971f7 docs: detail multi-Pokémon trading and trade UI changes in the changelog 2026-10-04 14:34:16 -04:00
saultsc bfc1c66097 fix: log trade start at info level instead of error 2026-10-04 14:34:16 -04:00
saultscandClaude Opus 5.5 13d9f84d8d Merge branch 'main' into feature/more-trades
Resolve conflicts with the trade dupe fix and per-OT trade friendship
caching by applying both to every Pokémon in a multi-Pokémon trade.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:55:28 -04:00
saultsc 148eb130d9 ci: install findutils in pipeline jobs and sync CI config with main 2026-10-04 10:11:00 -04:00
saultsc eb07b0e03f docs: document trade evolutions checking every Pokémon received in the changelog 2026-10-04 10:05:51 -04:00
saultsc 1b8d416962 fix: evaluate trade evolutions against every Pokémon received in a multi-Pokémon trade 2026-10-04 10:05:27 -04:00
saultsc 3030b544b5 refactor: migrate trade advancement and stat handlers to the list-based TradeEvent API 2026-10-04 10:05:21 -04:00
saultsc b890d662f6 feature: include every traded Pokémon in TradeEvent.Pre and TradeEvent.Post 2026-10-04 10:05:20 -04:00
saultsc 2a9bbc1d68 fix: log a warning when a trade is performed with an empty offer 2026-10-04 10:05:13 -04:00
saultsc c10c91d86a changes add to changelog 2026-04-23 11:21:30 -04:00
saultsc d5fd2e9a07 fix to reorder now is good :) 2026-04-22 22:37:24 -04:00
saultsc 45cc0ba579 trader hover select poing more tall z 2026-04-22 22:12:38 -04:00
saultsc 1880a0a24e reorder the party if you have a blank space in the middle 2026-04-22 19:59:55 -04:00
saultsc 5f3a0b96cc summary show is changed for a hover 2026-04-22 19:46:11 -04:00
saultsc 7ab1610700 remove the hover to the opposing party 2026-04-20 23:06:50 -04:00
saultsc daad9f6c00 add trade party selected assets and apply in trade ui 2026-04-20 22:52:08 -04:00
saultsc 2b234b740e trade animation directed towards the pokeball 2026-04-20 21:49:14 -04:00
saultsc 39e3983020 Prevent exiting summary view when clicking the active Pokémon 2026-04-20 21:17:20 -04:00
saultsc 9d58cdb21c deleting a suspicius comment 2026-04-20 03:07:18 -04:00
saultsc f05e35e792 Improve trade GUI interaction, modify summary interaction style 2026-04-20 02:59:10 -04:00
saultsc a333e68620 Update trade network packets and handlers 2026-04-20 02:58:15 -04:00
saultsc 2c2791ec69 Refactor trade list<Pokemon> flow on server 2026-04-20 02:57:13 -04:00
729 changed files with 29471 additions and 164799 deletions
+140 -16
View File
@@ -1,47 +1,171 @@
# Changelog
## [1.8.3 (MONTH xth, 2026)](#1-8-3)
## [1.8.2 (MONTH xth, 2026)](#1-8-2)
### Additions
- You can now drag to select multiple Pokémon inside the PC menu.
- The following items can now be placed on the ground as blocks:
- King's Rock, Galarica Cuff, Unremarkable Teacup, Masterpiece Teacup, Chipped Pot, Cracked Pot, Sachet, Metal Powder, Quick Powder, Bright Powder, Soft Sand, Electirizer, Magmarizer, Icy Rock, Heat Rock, Smooth Rock, Damp Rock, Destiny Knot, Luminous Moss, Never-Melt Ice, Wide Lens, Scope Lens, and Zoom Lens.
- Iron Ball, Metal Coat, Throat Spray, Terrain Extender, Medicinal Brew, Moomoo Milk, HP Up, Protein, Iron, Carbos, Calcium, Zinc, PP Up, PP Max, Upgrade, Dubious Disc, and Loaded Dice.
- The following items can now be placed on various surfaces:
- Galarica Wreath, Magnet, Flame Orb, Life Orb, Toxic Orb, Light Ball, and Smoke Ball.
- The following items can now be placed as blocks and have additional functionality:
- Ring Target, Metronome, and Eject Button.
- The following items can now be worn by Players and Pokémon:
- Galarica Wreath, Cleanse Tag, Spell Tag, Sharp Beak, Covert Cloak, Shell Helmet, and Never-Melt Ice.
- Wearable items can now be equipped by a player by holding it and right-clicking (or rebound use/interact)
- Wearable items that can be placed down are exempt from this behaviour.
- Mint Leaves can now be placed on the ground as Leaf Litter, or in Flower Pots
- Added Oricorio Flowers
- Added Terrain Plants
- Berries can now be placed in Flower Pots
- The evolution tree can now be viewed on the Pokédex, showing a Pokémon's full family line and what's needed to evolve into each stage.
- Added custom sounds to a bunch of items that can be placed as blocks: liquid bottles, pill bottles, powders and dice.
### Pokémon Added
#### Generation 4
- Snover
- Abomasnow
- Burmy
- Wormadam
- Mothim
#### Generation 5
- Tympole
- Palpitoad
- Seismitoad
#### Generation 6
- Helioptile
- Heliolisk
#### Generation 7
- Grubbin
- Charjabug
- Vikavolt
- Minior
#### Generation 8
- Eternatus
#### Generation 9
- Greavard
- Houndstone
### Model updates for the following Pokémon
- Sudowoodo
- Spheal
- Cacnea
- Cacturne
- Binacle
- Barbaracle
- Glimmet
- Glimmora
- Deerling
- Sawsbuck (All forms)
- Absol
- Poliwag
- Politoed
- Ekans
- Oddish
- Gloom
- Vileplume
- Bellossom
- Shroomish
### Animation updates for the following Pokémon
- Treecko (New Shoulder Mount!)
- Bonsly (New Shoulder Mount!)
- Sudowoodo
- Cacnea
- Cacturne
- Ekans
- Arbok
- Oddish
- Gloom
- Vileplume
- Bellossom
### Changes
- Restructured Botany Pots integration folder structure
- Decrease default pastured mon wander radius to 32.
- Factor in the weight of pokemon when calculating pushing forces between hitboxes.
- Remade the Starter Selection Screen with new assets
- Refactored berry trees to be less impactful on TPS (more performant)
- Trading has been overhauled to allow offering multiple Pokémon, up to the entire party, in a single trade
- Selected Pokémon are now highlighted in both parties with new party slot assets
- Hovering an offered Pokémon now shows its summary, and clicking the active Pokémon no longer exits the summary view
- The trade animation now moves the offered Pokémon towards the Poké Ball
- Parties are compacted after a trade so no empty slots are left between Pokémon
- Terrain Seeds can now be planted
- Terrain Seeds are now consumed during PvP and NPC battles
- Glow Item Frames can be crafted using Bright Powder as an alternative to Glow Ink Sacs
- Bright Powder can be used on Signs and Hanging Signs to make text glow, as an alternative to Glow Ink Sacs
- Updated various recipes to display in Recipe Groups within the Recipe Book
- Updated most recipes to display more logically/consistently within the Recipe Book's categories
- Updated the following recipes
- Never-Melt Ice, Focus Sash, Blunder Policy, Weakness Policy, Blank TM
- Make ride rotations less jittery in multiplayer.
- Changes Disc Shelf TMs to use textures instead of tinting and overlays to line up with item display
- Blank TM can now be inserted into the Disc Shelf
- Added TM creative tab
- Recall animation now renders ball in hand with the player arm raised.
- Made regional and Vivillon split-evo locations now mutually exclusive
- Item evolutions now unlock **all** locked evolutions whose requirements are met instead of stopping after the first.
- Exceptions being "forced" evolutions which will continue to stop after the first one
### Fixes
- Fixed datapacks with item interaction evolutions that don't specify an item crashing the world on load.
- Fixed the Pokédex evolution tree crashing when a datapack's dex entry is missing an evolution's form.
- Fixed the Pokédex evolution tree crashing with a stack overflow on evolutions that loop back on themselves.
- Fixed the Pokédex evolution tree crashing when looking at a dex that is not part of the "National Dex" aggregate.
- Fixed evolution trees not building properly when a pokemon has several pokedex entries for different regions.
- Fixed form-changing evolutions sometimes resolving to the source form in the Pokédex evolution tree.
- Broken datapack evolutions are now logged and skipped in the Pokédex evolution tree instead of crashing.
- Fixed friendship booster items not being recognised as such
- Fixed conflicts with other mods that would make use of recipe books in certain ways (Farmer's Delight Cooking Pot)
- Fixed Pokémon battles stalling when a sidemod cancels the `THROWN_POKEBALL_HIT` hit event
- Fixed choice feature tab completion in `spawnpokemon/pokegive/pokeedit` commands
- Correctly grants "They're Minerals!" advancement when planting a type gem
- Shoulder mounted Pokémon now follow player body animations, improving their positioning with custom player animations and default actions.
- Shoulder mounted Pokémon now sit slightly higher with chest armor equipped for better alignment, with a configurable blacklist.
- Type Gem Blocks now have advancements to give you the recipe in the recipe book (the data for the advancements were inside the recipes somehow??)
- Some other recipes without advancements have also been corrected
- Corrected the TM Recipes for Charge Beam and Payday
- Type Gem blocks now properly generate clusters off of the block.
- Starter Screen Categories will no longer stop rendering if they are bigger than the viewport, and you are not viewing top or bottom edges
- Random Starter option now properly handled in height calculation, so now it won't run off into other categories or be un-accessible due to scroll limits if it causes a new row
- Mochi and Feathers no longer drop glass bottles on use
- Fixed a rare set of concurrency bugs related to reloading packs on NeoForge; fixes a ModernFix issue
- Removed unnecessary conditions for Mimikyu's standing pose. Hopefully fixes the rare instance of it using its shoulder animations during battle in multiplayer scenarios.
- Fixed Rockruff line not blinking
- Fixed Flygon sometimes using its running animation instead of walking
- Fixed Bramblin always sleeping outside of battle
- Fixed Dragonair using its battle idle as a normal idle
- Fixed Lycanroc's head movement
- Added head movement to Archaludon
- Fixed Duraludon line not having any sleep animations
- Fixed Diglett line not having their faint animations
- Fixed Kingambit moving its legs while walking
- Fixed Alolan Diglett's cry not playing
- Fixed shiny alpha Snorlax using the non-shiny texture
- Various Structure fixes
- Fixed shiny Pokémon under certain circumstances forgetting taught TMs
- Fixed certain Pokémon sounds sometimes being doubled when played at specific times in their animations
- Fixed ball capture beam origin coming from behind the ball.
- Fixed Safari Ball catch rate outside of battle
### Developer
- Bumped NeoForge to `21.1.195` to get access to MixinExtras `0.5` and the expressions annotation
- 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
- Trade network packets (`TradeUpdatedPacket`, `UpdateTradeOfferPacket`, and `TradeCompletedPacket`) now receive an array of Pokémon instead of a single Pokémon to execute trades
- `TradeEvent.Pre` and `TradeEvent.Post` now expose every Pokémon each participant receives (`tradeParticipant1Pokemon` and `tradeParticipant2Pokemon` are now lists), and their Molang context includes `trade_participant_1_pokemon_list` and `trade_participant_2_pokemon_list`
- Evolution requirements can now set `pokedexIcon` and `pokedexTooltip` directly in their JSON to control how they're shown on the Pokédex's evolution tree, for example:
```json
{
"variant": "attack_defence_ratio",
"pokedexIcon": "minecraft:diamond_sword",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.attack_defence_ratio.tooltip"
}
```
- Fixed wrong order of operations in the code flow prior to the `FOSSIL_REVIVED` event causing pokemon to appear without entity link
- Fixed TestPartySlot command not correctly comparing the provided spec with the target Pokémon
### Molang & Datapacks
- Fixed `q.pokemon.nature` returning class instance instead of proper string value (`cobblemon:adamant`)
- Fixed `q.entity.is_looking_at` result being always `1` for whatever entity passed as target
- A new `negate` variant for evolution requirements has been added, which will negate (invert) the provided inner condition.
- Added Molang functions for individual Pokédex ids, wherein a subdex is passed in as a string to `dex_caught_count` etc.
### Particles
#### New Move Animations
### Localization
- Updated translations for:
+1 -4
View File
@@ -36,7 +36,6 @@
- Added `cobblemon:catch_alpha_pokemon` advancement trigger and `totalAlphaCaptureCount` stat for captured Alpha Pokémon.
### New rideable Pokémon
- Pidgeot
- Crobat
- Skarmory
- Milotic
@@ -107,9 +106,7 @@
- Avalugg (Hisuian Form)
### Model updates for the following Pokémon
- Pidgey
- Pidgeotto
- Pidgeot
- Sandshrew
- Sandslash
- Nidoking
-211
View File
@@ -1,211 +0,0 @@
# Changelog
## [1.8.2 (October 11th, 2026)](#1-8-2)
### Additions
- The following items can now be placed on the ground as blocks:
- King's Rock, Galarica Cuff, Unremarkable Teacup, Masterpiece Teacup, Chipped Pot, Cracked Pot, Sachet, Metal Powder, Quick Powder, Bright Powder, Soft Sand, Electirizer, Magmarizer, Icy Rock, Heat Rock, Smooth Rock, Damp Rock, Destiny Knot, Luminous Moss, Never-Melt Ice, Wide Lens, Scope Lens, and Zoom Lens.
- I wasn't finished. Iron Ball, Metal Coat, Throat Spray, Terrain Extender, Medicinal Brew, Moomoo Milk, HP Up, Protein, Iron, Carbos, Calcium, Zinc, PP Up, PP Max, Upgrade, Dubious Disc, and Loaded Dice.
- The following items can now be placed on various surfaces:
- Galarica Wreath, Magnet, Flame Orb, Life Orb, Toxic Orb, Light Ball, and Smoke Ball.
- The following items can now be placed as blocks and have additional functionality:
- Ring Target, Metronome, and Eject Button.
- The following items can now be worn by Players and Pokémon:
- Galarica Wreath, Cleanse Tag, Spell Tag, Sharp Beak, Covert Cloak, Shell Helmet, and Never-Melt Ice.
- Wearable items can now be equipped by a player by holding it and right-clicking (or whatever you've rebound use/interact to).
- Wearable items that can be placed down are exempt from this behaviour.
- Mint Leaves can now be placed on the ground as Leaf Litter, or in Flower Pots.
- The recall animation now renders the ball in hand with the player's arm raised.
- Added Oricorio Flowers.
- Added Terrain Plants, four plants grown from Electric, Grassy, Misty, and Psychic Seeds, which glow under specific environmental conditions and can be farmed for more of their seeds.
- Berries can now be placed in Flower Pots.
- The evolution tree for a Pokémon can now be viewed in the Pokédex, showing the Pokémon's full family line and what's needed to evolve into each stage.
- Added custom sounds to a bunch of items that can be placed as blocks: liquid bottles, pill bottles, powders, and dice.
- Added paper as a new cosmetic item for Pikachu.
- Burmy changes its cloak after a battle it took part in, based on the biome.
- Trash Cloak in the Deep Dark, Nether and End.
- Plant Cloak in lush biomes such as forests, jungles, plains and swamps.
- Sandy Cloak in sandy biomes, beaches and caves.
### Pokémon Added
#### Generation 4
- Snover
- Abomasnow
- Burmy
- Wormadam
- Mothim
#### Generation 5
- Tympole
- Palpitoad
- Seismitoad
#### Generation 6
- Helioptile
- Heliolisk
#### Generation 7
- Grubbin
- Charjabug
- Vikavolt
- Minior
#### Generation 8
- Eternatus
#### Generation 9
- Greavard
- Houndstone
### Model updates for the following Pokémon
- Sudowoodo
- Spheal
- Cacnea
- Cacturne
- Binacle
- Barbaracle
- Glimmet
- Glimmora
- Deerling
- Sawsbuck (All forms)
- Absol
- Poliwag
- Politoed
- Ekans
- Oddish
- Gloom
- Vileplume
- Bellossom
- Shroomish
- Chespin
- Quilladin
- Chesnaught
- Fennekin
- Braixen
- Delphox
- Froakie
- Frogadier
- Greninja
- Shinx
- Luxio
- Luxray
- Slakoth
- Riolu
- Lucario
- Corsola
- Mime Jr.
### Animation updates for the following Pokémon
- Treecko (New Shoulder Mount!)
- Bonsly (New Shoulder Mount!)
- Sudowoodo
- Cacnea
- Cacturne
- Ekans
- Arbok
- Oddish
- Gloom
- Vileplume
- Bellossom
- Pichu
- Pikachu
- Raichu (+ Alolan)
- Greninja
- Slakoth
### Changes
- Slowed the speed of Type Gem growth, and revamped their generation to grow in a more crystal-like shape.
- Terrain Seeds can now be planted.
- Terrain Seeds are now consumed during PvP and NPC battles.
- Glow Item Frames can be crafted using Bright Powder as an alternative to Glow Ink Sacs.
- Bright Powder can be used on Signs and Hanging Signs to make text glow, as an alternative to Glow Ink Sacs.
- Updated various recipes to display in Recipe Groups within the Recipe Book.
- Updated most recipes to display more logically and consistently within the Recipe Book's categories.
- Updated the following recipes
- Never-Melt Ice, Focus Sash, Blunder Policy, Weakness Policy, Blank TM.
- Made ride rotations less jittery in multiplayer.
- Changes Disc Shelf TMs to use textures instead of tinting and overlays to line up with item display.
- Blank TM can now be inserted into the Disc Shelf.
- Added a TM creative tab.
- Made regional and Vivillon split-evo locations mutually exclusive.
- Vivillon's patterns are now viewable as forms with form names and unique Pokédex entries displayed.
- Item evolutions now unlock **all** locked evolutions whose requirements are met instead of stopping after the first.
- Exceptions being "forced" evolutions which stop after the first one.
- Removed Mega Drain as a TM.
- PokeSnacks (and other area spawns) will now start the vertical offset checking from the center rather than base coordinate
- This will help PokeSnacks to work inside caves or tighter spaces better.
### Fixes
- Type Gem blocks now properly generate clusters off of the block.
- Correctly grant "They're Minerals!" advancement when planting a Type Gem.
- Shoulder mounted Pokémon now follow player body animations, improving their positioning with custom player animations and default actions.
- Shoulder mounted Pokémon now sit slightly higher with chest armor equipped for better alignment, with a configurable blacklist.
- Type Gem Blocks now have advancements to give you the recipe in the recipe book (the data for the advancements were inside the recipes somehow??).
- Some other recipes without advancements have also been corrected.
- Corrected the TM Recipes for Charge Beam and Payday.
- Starter Screen Categories will no longer stop rendering if they are bigger than the viewport and you are not viewing the top or bottom edges.
- The Random Starter option no longer runs off into other categories or becomes unaccessible due to scroll limits if it causes a new row.
- Mochi and Feathers no longer drop glass bottles on use.
- Fixed a rare set of concurrency bugs related to reloading packs on NeoForge; fixes a ModernFix compatibility issue.
- Removed unnecessary conditions for Mimikyu's standing pose. Hopefully fixes the rare instance of it using its shoulder animations during battle in multiplayer scenarios.
- Fixed Rockruff line not blinking.
- Fixed Flygon sometimes using its running animation instead of walking.
- Fixed Bramblin always sleeping outside of battle.
- Fixed Dragonair using its battle idle as a normal idle.
- Fixed Lycanroc's head movement.
- Added head movement to Archaludon.
- Fixed Duraludon line not having any sleep animations.
- Fixed Diglett line not having their faint animations.
- Fixed Kingambit moving its legs while walking.
- Fixed Alolan Diglett's cry not playing.
- Fixed shiny alpha Snorlax using the non-shiny texture.
- Fixed Crabominable's head breaking when looking around.
- Fixed Shiny Dipplin and Hydrapple's textures.
- Various structure fixes.
- Fixed shiny Pokémon under certain circumstances forgetting taught TMs.
- Fixed certain Pokémon sounds sometimes being doubled when played at specific times in their animations.
- Fixed ball capture beam origin coming from behind the ball.
- Fixed Safari Ball catch rate outside of battle.
- Fixed alpha eye (and other) particles not respecting the client particle density setting in video options.
- Fixed the TM Machine UI not closing when player is moving away from it.
- Moved the config category for alpha/shiny chance config fields for fossil machine and honey log slathering to Spawning.
- Fixed crashes for addon species with non-Cobblemon namespaces in evolution, fishing bait, and Pokédex lookups.
- Bees will now grow Apricorns and the Galarica Nut Bush, which were in the bee growables tag but never handled, and correctly spend their nectar when they do.
- Saccharine Leaves can now be bone mealed.
- Fixed equipping a stack of wearable items instead of one item of a stack.
- Fixed TM stack consumption in TM Machine screen when a player teaches a move to a Pokémon.
- Fixed every Miltank giving mushroom stew, not only mushroom variants.
- Medicinal Leeks can now be eaten when they aren't being planted, matching their food properties.
- Sweeping attacks no longer set owned Pokémon on fire (e.g. with Fire Aspect) or knock them back. Friendly Fire is now off.
- Fixed Pokémon not losing their previous form's signature moves when changing forms (e.g. Rotom keeping Overheat after leaving its Heat form).
- Form-specific moves gained from a form change are now only granted if the Pokémon is high enough level to learn them, and no longer create duplicates in benched moves.
- Spinda now learns Teeter Dance at level 32 instead of via TM.
- Fixed Oricorio Pa'u form description in the Pokédex.
- Fixed PC playing sound on close when opened via command.
### Developer
- Evolution requirements can now set `pokedexIcon` and `pokedexTooltip` directly in their JSON to control how they're shown on the Pokédex's evolution tree, for example:
```json
{
"variant": "attack_defence_ratio",
"pokedexIcon": "minecraft:diamond_sword",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.attack_defence_ratio.tooltip"
}
```
- Fixed the wrong order of operations in the code flow prior to the `FOSSIL_REVIVED` event causing Pokémon to appear without the entity link.
- Fixed TestPartySlot command not correctly comparing the provided spec with the target Pokémon.
### Molang & Datapacks
- A new `negate` variant for evolution requirements has been added, which will negate (invert) the provided inner condition.
- Added Molang functions for individual Pokédex ids, wherein a subdex is passed in as a string to `dex_caught_count` etc.
- Added a `battle_ended` callback, backed by the new `BATTLE_ENDED` event, which fires whenever a battle ends for any reason, alongside a multitude of flags not previously covered by `BATTLE_VICTORY`.
- Added `battle_pokemon` to battle actor structs and `has_faced_opponents` to battle Pokémon structs.
- Added the `cobblemon:form_change/bagworm_cloak/plant_cloak`, `sandy_cloak` and `trash_cloak` biome tags, which control Burmy's cloak after battle.
### Localization
- Updated translations for:
- Brazilian Portuguese
- Canadian French
- Japanese
- Korean
- Russian
- Ukrainian
@@ -8,42 +8,54 @@
package com.cobblemon.mod.common.mixin;
import com.llamalad7.mixinextras.sugar.Local;
import com.cobblemon.mod.common.block.SaccharineLeafBlock;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.animal.Bee;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Final;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(targets = "net.minecraft.world.entity.animal.Bee$BeeGrowCropGoal")
public abstract class BeeEntityMixin {
@Shadow(aliases = {"this$0", "field_20373"}) @Final private Bee bee;
@Unique
private BlockState cobblemon$result = null;
@Inject(method = "tick()V", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/block/BonemealableBlock;performBonemeal(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/util/RandomSource;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)V",
shift = At.Shift.BY,
by = 2
),
locals = LocalCapture.CAPTURE_FAILHARD
)
private void injectCustomGrowth(CallbackInfo ci, int i, BlockPos blockPos, BlockState blockState, Block block) {
if (block instanceof SaccharineLeafBlock) {
int age = blockState.getValue(SaccharineLeafBlock.Companion.getAGE());
boolean waterlogged = blockState.getValue(BlockStateProperties.WATERLOGGED);
if (age < 2 && !waterlogged) {
this.cobblemon$result = blockState.setValue(SaccharineLeafBlock.Companion.getAGE(), age + 1);
}
}
}
// Grows bonemealable blocks vanilla's hardcoded checks miss, returning the state so it handles the rest.
@ModifyVariable(method = "tick", at = @At(value = "LOAD", ordinal = 0), index = 5)
private BlockState cobblemon$growBonemealableBlocks(BlockState grownState, @Local(ordinal = 0) BlockState blockState, @Local BlockPos pos) {
if (grownState != null || blockState.is(Blocks.CAVE_VINES) || blockState.is(Blocks.CAVE_VINES_PLANT)) {
return grownState;
private BlockState applyCustomBlockState(BlockState before) {
if(this.cobblemon$result != null && before == null) {
BlockState result = this.cobblemon$result;
this.cobblemon$result = null;
return result;
}
if (!(blockState.getBlock() instanceof BonemealableBlock bonemealable)) {
return null;
}
ServerLevel level = (ServerLevel) this.bee.level();
if (!bonemealable.isValidBonemealTarget(level, pos, blockState)) {
return null;
}
bonemealable.performBonemeal(level, this.bee.getRandom(), pos, blockState);
return level.getBlockState(pos);
return before;
}
}
@@ -15,7 +15,6 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.mojang.serialization.MapCodec;
import java.util.Optional;
import java.util.function.Supplier;
import net.minecraft.SharedConstants;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.IntTag;
import net.minecraft.resources.ResourceKey;
@@ -46,24 +45,4 @@ public class ChunkStorageMixin {
int curVersion = vanillaFixed.contains(VERSION_KEY) ? vanillaFixed.getInt(VERSION_KEY) : 0;
return DataFixTypes.CHUNK.update(CobblemonSchemas.getDATA_FIXER(), vanillaFixed, curVersion, CobblemonSchemas.DATA_VERSION);
}
// Our update on the same MC version still needs its own fixes so run them here in that case.
@Inject(
method = "upgradeChunkTag(Lnet/minecraft/resources/ResourceKey;Ljava/util/function/Supplier;Lnet/minecraft/nbt/CompoundTag;Ljava/util/Optional;)Lnet/minecraft/nbt/CompoundTag;",
at = @At("HEAD"),
cancellable = true
)
private void cobblemon$doChunkFixOnCurrentVersion(
ResourceKey<Level> levelKey,
Supplier<DimensionDataStorage> storage,
CompoundTag chunkData,
Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey,
CallbackInfoReturnable<CompoundTag> cir
) {
if (ChunkStorage.getVersion(chunkData) != SharedConstants.getCurrentVersion().getDataVersion().getVersion()) {
return;
}
int curVersion = chunkData.contains(VERSION_KEY) ? chunkData.getInt(VERSION_KEY) : 0;
cir.setReturnValue(DataFixTypes.CHUNK.update(CobblemonSchemas.getDATA_FIXER(), chunkData, curVersion, CobblemonSchemas.DATA_VERSION));
}
}
@@ -35,16 +35,13 @@ import com.cobblemon.mod.common.util.DataKeys;
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
@@ -53,7 +50,6 @@ import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
@@ -375,33 +371,4 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
this.cobblemon$rideEyePos = rideEyePos;
}
/**
* Sweeping attacks apply post-attack enchantment effects (e.g. Fire Aspect) to every entity caught
* in the sweep, even if the damage itself was blocked. Skip them for Pokémon immune to the attack.
*/
@WrapOperation(
method = "attack",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/enchantment/EnchantmentHelper;doPostAttackEffects(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)V")
)
private void cobblemon$skipPostAttackEffectsOnImmunePokemon(ServerLevel level, Entity target, DamageSource source, Operation<Void> original) {
if (target instanceof PokemonEntity pokemonEntity && pokemonEntity.isInvulnerableTo(source)) {
return;
}
original.call(level, target, source);
}
/**
* Sweeping attacks also knock back every entity caught in the sweep regardless of whether it took damage.
*/
@WrapOperation(
method = "attack",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;knockback(DDD)V")
)
private void cobblemon$skipKnockbackOnImmunePokemon(LivingEntity target, double strength, double x, double z, Operation<Void> original, @Local DamageSource source) {
if (target instanceof PokemonEntity pokemonEntity && pokemonEntity.isInvulnerableTo(source)) {
return;
}
original.call(target, strength, x, z);
}
}
@@ -16,7 +16,6 @@ import com.cobblemon.mod.common.item.crafting.CookingPotShapelessRecipe;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.ClientRecipeBook;
import net.minecraft.client.RecipeBookCategories;
import net.minecraft.client.gui.screens.recipebook.RecipeCollection;
@@ -64,12 +63,23 @@ public abstract class ClientRecipeBookMixin {
}
}
@Inject(method = "setupCollections",
at = @At(
value = "INVOKE",
target = "Lcom/google/common/collect/ImmutableMap;copyOf(Ljava/util/Map;)Lcom/google/common/collect/ImmutableMap;"
))
private void addCustomAggregateCategories(Iterable<RecipeHolder<?>> recipes, RegistryAccess registryAccess, CallbackInfo ci, @Local(name = "map2") Map<RecipeBookCategories, List<RecipeCollection>> map2) {
@Inject(method = "setupCollections", at = @At("RETURN"))
private void addCustomAggregateCategories(Iterable<RecipeHolder<?>> recipes, RegistryAccess registryAccess, CallbackInfo ci) {
Map<RecipeBookCategories, List<List<RecipeHolder<?>>>> map = categorizeAndGroupRecipes(recipes);
Map<RecipeBookCategories, List<RecipeCollection>> map2 = Maps.newHashMap();
ImmutableList.Builder<RecipeCollection> builder = ImmutableList.builder();
map.forEach((recipeBookCategories, list) -> {
List<RecipeCollection> collections = list.stream()
.map(listx -> new RecipeCollection(registryAccess, listx))
.peek(builder::add)
.collect(ImmutableList.toImmutableList());
map2.put(recipeBookCategories, collections);
});
RecipeBookCategories.AGGREGATE_CATEGORIES.forEach((recipeBookCategories, list) -> map2.put(recipeBookCategories, (List) list.stream().flatMap((recipeBookCategoriesx) -> ((List) map2.getOrDefault(recipeBookCategoriesx, ImmutableList.of())).stream()).collect(ImmutableList.toImmutableList())));
CobblemonRecipeCategories.Companion.getCustomAggregateCategories().forEach((recipeBookCategories, list) -> map2.put(recipeBookCategories, (List) list.stream().flatMap((recipeBookCategoriesx) -> ((List) map2.getOrDefault(recipeBookCategoriesx, ImmutableList.of())).stream()).collect(ImmutableList.toImmutableList())));
this.collectionsByTab = ImmutableMap.copyOf(map2);
this.allCollections = builder.build();
}
}
@@ -9,25 +9,55 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.CobblemonRecipeCategories;
import com.llamalad7.mixinextras.expression.Definition;
import com.llamalad7.mixinextras.expression.Expression;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.ClientRecipeBook;
import net.minecraft.client.Minecraft;
import net.minecraft.client.RecipeBookCategories;
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
import net.minecraft.client.gui.screens.recipebook.RecipeBookPage;
import net.minecraft.client.gui.screens.recipebook.RecipeBookTabButton;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(RecipeBookComponent.class)
public class RecipeBookComponentMixin {
@Definition(id = "recipeBookCategories", local = @Local(name = "recipeBookCategories"))
@Definition(id = "FURNACE_SEARCH", field = "Lnet/minecraft/client/RecipeBookCategories;FURNACE_SEARCH:Lnet/minecraft/client/RecipeBookCategories;")
@Expression("recipeBookCategories != FURNACE_SEARCH")
@ModifyExpressionValue(
method = "updateTabs",
at = @At(value = "MIXINEXTRAS:EXPRESSION")
)
private boolean notCookingPotSearch(boolean original, @Local(name = "recipeBookCategories") RecipeBookCategories recipeBookCategories) {
return original && recipeBookCategories != CobblemonRecipeCategories.COOKING_POT_SEARCH.toVanillaCategory();
@Shadow private ClientRecipeBook book;
@Shadow protected Minecraft minecraft;
@Shadow private int width;
@Shadow private int xOffset;
@Shadow private int height;
@Shadow @Final private RecipeBookPage recipeBookPage;
@Inject(method = "updateTabs", at = @At("HEAD"), cancellable = true)
private void customUpdateTabs(CallbackInfo ci) {
RecipeBookComponent instance = (RecipeBookComponent) (Object) this;
int i = (width - 147) / 2 - xOffset - 30;
int j = (height - 166) / 2 + 3;
int k = 27;
int l = 0;
for (RecipeBookTabButton recipeBookTabButton : instance.tabButtons) {
RecipeBookCategories recipeBookCategories = recipeBookTabButton.getCategory();
if (recipeBookCategories != RecipeBookCategories.CRAFTING_SEARCH && recipeBookCategories != RecipeBookCategories.FURNACE_SEARCH && recipeBookCategories != CobblemonRecipeCategories.COOKING_POT_SEARCH.toVanillaCategory()) {
if (recipeBookTabButton.updateVisibility(book)) {
recipeBookTabButton.setPosition(i, j + 27 * l++);
recipeBookTabButton.startAnimation(minecraft);
}
} else {
recipeBookTabButton.visible = true;
recipeBookTabButton.setPosition(i, j + k * l++);
}
}
ci.cancel();
}
}
@@ -710,11 +710,11 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val THROAT_SPRAY = create("throat_spray", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.THROAT_SPRAY))
@JvmField
val TERRAIN_EXTENDER = create("terrain_extender", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_METAL_CAN_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.EXTENDER))
val TERRAIN_EXTENDER = create("terrain_extender", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.EXTENDER))
@JvmField
val MOOMOO_MILK = create("moomoo_milk", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_JAR_GLASS_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.GLASS_BOTTLE))
val MOOMOO_MILK = create("moomoo_milk", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.GLASS_BOTTLE))
@JvmField
val MEDICINAL_BREW = create("medicinal_brew", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_JAR_GLASS_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.GLASS_BOTTLE))
val MEDICINAL_BREW = create("medicinal_brew", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.GLASS_BOTTLE))
@JvmField
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@@ -1103,7 +1103,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SMOOTH_ROCK = itemNameBlockItem("smooth_rock", CobblemonBlocks.SMOOTH_ROCK)
@JvmField
val ICY_ROCK = itemNameBlockItem("icy_rock", CobblemonBlocks.ICY_ROCK)
val ICY_ROCK = heldItem("icy_rock")
// Mulch
@JvmField
@@ -425,7 +425,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(MovesRegistrySyncPacket.ID, MovesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BerryRegistrySyncPacket.ID, BerryRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SpeciesRegistrySyncPacket.ID, SpeciesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(DexEvolutionDisplaySyncPacket.ID, DexEvolutionDisplaySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SpeciesEvolutionSyncPacket.ID, SpeciesEvolutionSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(PropertiesCompletionRegistrySyncPacket.ID, PropertiesCompletionRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(StandardSpeciesFeatureSyncPacket.ID, StandardSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(GlobalSpeciesFeatureSyncPacket.ID, GlobalSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
@@ -566,7 +566,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(SwapPartyPokemonPacket.ID, SwapPartyPokemonPacket::decode, SwapPartyPokemonHandler))
list.add(PacketRegisterInfo(MovePCPokemonPacket.ID, MovePCPokemonPacket::decode, MovePCPokemonHandler))
list.add(PacketRegisterInfo(MovePCPokemonBatchPacket.ID, MovePCPokemonBatchPacket::decode, MovePCPokemonBatchHandler))
list.add(PacketRegisterInfo(MovePartyPokemonPacket.ID, MovePartyPokemonPacket::decode, MovePartyPokemonHandler))
list.add(PacketRegisterInfo(SwapPCPartyPokemonPacket.ID, SwapPCPartyPokemonPacket::decode, SwapPCPartyPokemonHandler))
@@ -720,35 +720,5 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
ITEM_BLOCK_DICE_STEP
)
@JvmField
val ITEM_BLOCK_METAL_CAN_PLACE = this.create("block.item_block.metal_can.place")
@JvmField
val ITEM_BLOCK_METAL_CAN_BREAK = this.create("block.item_block.metal_can.break")
@JvmField
val ITEM_BLOCK_METAL_CAN_STEP = this.create("block.item_block.metal_can.step")
@JvmField
val ITEM_BLOCK_METAL_CAN_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_METAL_CAN_BREAK,
ITEM_BLOCK_METAL_CAN_STEP,
ITEM_BLOCK_METAL_CAN_PLACE,
ITEM_BLOCK_METAL_CAN_STEP,
ITEM_BLOCK_METAL_CAN_STEP
)
@JvmField
val ITEM_BLOCK_JAR_GLASS_LIQUID_PLACE = this.create("block.item_block.jar_glass_liquid.place")
@JvmField
val ITEM_BLOCK_JAR_GLASS_LIQUID_BREAK = this.create("block.item_block.jar_glass_liquid.break")
@JvmField
val ITEM_BLOCK_JAR_GLASS_LIQUID_STEP = this.create("block.item_block.jar_glass_liquid.step")
@JvmField
val ITEM_BLOCK_JAR_GLASS_LIQUID_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_JAR_GLASS_LIQUID_BREAK,
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP,
ITEM_BLOCK_JAR_GLASS_LIQUID_PLACE,
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP,
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP
)
private fun create(name: String): SoundEvent = this.create(name, SoundEvent.createVariableRangeEvent(cobblemonResource(name)))
}
@@ -1,25 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.battles.model
/**
* How a [PokemonBattle] came to an end.
*/
enum class BattleEndResult {
/** One side defeated the other. */
VICTORY,
/** A wild Pokémon was captured, ending the battle. */
CAPTURE,
/** An actor forfeited, giving the victory to the other side. */
FORFEIT,
/** A player fled from a wild Pokémon. */
FLED,
/** The battle was stopped without a winner, such as by a command, a player disconnecting or an error. */
STOPPED
}
@@ -21,7 +21,6 @@ import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
import com.cobblemon.mod.common.api.battles.model.actor.FleeableBattleActor
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.BattleEndedEvent
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
import com.cobblemon.mod.common.api.events.battles.BattleFleeAttemptEvent
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
@@ -125,12 +124,6 @@ open class PokemonBattle(
var winners = listOf<BattleActor>()
var losers = listOf<BattleActor>()
var ended = false
/** How the battle ended, or null if it hasn't ended yet. */
var endResult: BattleEndResult? = null
/** The actor that forfeited the battle, if any. */
var forfeitedBy: BattleActor? = null
/** Whether a player fled from the battle. */
var fled = false
// TEMP battle showcase stuff
var announcingRules = false
var turn: Int = 0
@@ -258,14 +251,6 @@ open class PokemonBattle(
fun end() {
ended = true
val result = when {
winners.isNotEmpty() && showdownMessages.any { "capture" in it } -> BattleEndResult.CAPTURE
winners.isNotEmpty() && forfeitedBy != null -> BattleEndResult.FORFEIT
winners.isNotEmpty() -> BattleEndResult.VICTORY
fled -> BattleEndResult.FLED
else -> BattleEndResult.STOPPED
}
endResult = result
val awardToFainted = Cobblemon.config.awardExperienceToFaintedPokemon
val awardOnLoss = Cobblemon.config.awardExperienceOnBattleLoss
this.actors.forEach { actor ->
@@ -347,7 +332,6 @@ open class PokemonBattle(
}
}
}
CobblemonEvents.BATTLE_ENDED.post(BattleEndedEvent(this, result, forfeitedBy.takeIf { result == BattleEndResult.FORFEIT }))
sendUpdate(BattleEndPacket())
BattleRegistry.closeBattle(this)
}
@@ -518,7 +502,6 @@ open class PokemonBattle(
.forEach{it.pokemon.heal()}
CobblemonEvents.BATTLE_FLED.post(BattleFledEvent(this, actors.asSequence().filterIsInstance<PlayerBattleActor>().iterator().next()))
actors.filterIsInstance<EntityBackedBattleActor<*>>().mapNotNull { it.entity }.forEach { it.sendSystemMessage(battleLang("flee").yellow()) }
fled = true
stop()
}
}
@@ -556,7 +539,6 @@ open class PokemonBattle(
private fun checkForfeit(): Boolean {
val forfeit = actors.find { it.responses.any { it is ForfeitActionResponse } }
return forfeit?.let {
forfeitedBy = it
this.dispatchWaiting { this.broadcastChatMessage(battleLang("forfeit", it.getName()).red()) }
writeShowdownAction(">forcelose ${it.showdownId}")
true
@@ -11,6 +11,9 @@ package com.cobblemon.mod.common.api.conditional
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
/**
@@ -21,11 +24,19 @@ import java.lang.reflect.Type
* @author Hiroku
* @since July 17th, 2022
*/
interface RegistryLikeAdapter<B> : JsonDeserializer<RegistryLikeCondition<B>> {
interface RegistryLikeAdapter<B> : JsonDeserializer<RegistryLikeCondition<B>>, JsonSerializer<RegistryLikeCondition<B>> {
val registryLikeConditions: MutableList<(JsonElement) -> RegistryLikeCondition<B>?>
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): RegistryLikeCondition<B> {
return registryLikeConditions.firstNotNullOfOrNull { it(json) }
?: throw IllegalArgumentException("Unable to deserialize $json")
}
override fun serialize(src: RegistryLikeCondition<B>, typeOfSrc: Type, ctx: JsonSerializationContext): JsonElement {
return when (src) {
is RegistryLikeTagCondition<B> -> JsonPrimitive(RegistryLikeTagCondition.PREFIX + src.tag.location())
is RegistryLikeIdentifierCondition<B> -> JsonPrimitive(src.identifier.toString())
else -> throw IllegalArgumentException("Unable to serialize $src")
}
}
}
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.api.events.battles.BattleFaintedEvent
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
import com.cobblemon.mod.common.api.events.battles.BattleFleeAttemptEvent
import com.cobblemon.mod.common.api.events.battles.BattleStartedEvent
import com.cobblemon.mod.common.api.events.battles.BattleEndedEvent
import com.cobblemon.mod.common.api.events.battles.BattleVictoryEvent
import com.cobblemon.mod.common.api.events.battles.instruction.FormeChangeEvent
import com.cobblemon.mod.common.api.events.battles.instruction.MegaEvolutionEvent
@@ -140,9 +139,6 @@ object CobblemonEvents {
@JvmField
val BATTLE_FAINTED = EventObservable<BattleFaintedEvent>()
@JvmField
val BATTLE_ENDED = EventObservable<BattleEndedEvent>()
// instructions
@JvmField
val MEGA_EVOLUTION = EventObservable<MegaEvolutionEvent>()
@@ -1,69 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.battles
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.battles.model.BattleEndResult
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.util.asArrayValue
/**
* Event fired whenever a [PokemonBattle] ends, regardless of how it ended (victory, fleeing, being stopped, etc).
*
* [PokemonBattle.winners] and [PokemonBattle.losers] are only populated if the [result] has a winner,
* that is [BattleEndResult.VICTORY], [BattleEndResult.CAPTURE] or [BattleEndResult.FORFEIT].
*
* @param result How the battle ended.
* @param forfeitedBy The actor that forfeited, if the [result] is [BattleEndResult.FORFEIT].
*/
data class BattleEndedEvent(
override val battle: PokemonBattle,
val result: BattleEndResult,
val forfeitedBy: BattleActor?
) : BattleEvent {
/** Whether one side won the battle, which includes captures and forfeits. */
val hasWinner = battle.winners.isNotEmpty()
val wasVictory = result == BattleEndResult.VICTORY
val wasCapture = result == BattleEndResult.CAPTURE
val wasForfeit = result == BattleEndResult.FORFEIT
val wasFled = result == BattleEndResult.FLED
val wasStopped = result == BattleEndResult.STOPPED
/** Whether the battle was between players and wild Pokémon. */
val isPvW = battle.isPvW
/** Whether the battle was between players and NPCs. */
val isPvN = battle.isPvN
/** Whether the battle was between players. */
val isPvP = battle.isPvP
val context = mutableMapOf<String, MoValue>(
"battle" to battle.struct,
"result" to StringValue(result.name.lowercase()),
"has_winner" to DoubleValue(hasWinner),
"was_victory" to DoubleValue(wasVictory),
"was_capture" to DoubleValue(wasCapture),
"was_forfeit" to DoubleValue(wasForfeit),
"was_fled" to DoubleValue(wasFled),
"was_stopped" to DoubleValue(wasStopped),
"is_pvw" to DoubleValue(isPvW),
"is_pvn" to DoubleValue(isPvN),
"is_pvp" to DoubleValue(isPvP),
"winners" to battle.winners.asArrayValue { it.struct },
"losers" to battle.losers.asArrayValue { it.struct },
"players" to battle.actors.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
"npcs" to battle.actors.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
"wild_pokemon" to battle.actors.filter { it.type == ActorType.WILD }.asArrayValue { it.struct }
).apply {
forfeitedBy?.let { put("forfeited_by", it.struct) }
}
}
@@ -9,12 +9,14 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.trade.TradeParticipant
import com.cobblemon.mod.common.util.asArrayValue
import com.cobblemon.mod.common.util.getPlayer
/**
@@ -31,9 +33,9 @@ interface TradeEvent {
val tradeParticipant1: TradeParticipant
/**
* The [Pokemon] being traded by the first participant.
* All the [Pokemon] the first participant receives in the trade.
*/
val tradeParticipant1Pokemon: Pokemon
val tradeParticipant1Pokemon: List<Pokemon>
/**
* The second [TradeParticipant].
@@ -41,9 +43,19 @@ interface TradeEvent {
val tradeParticipant2: TradeParticipant
/**
* The [Pokemon] being traded by the second participant.
* All the [Pokemon] the second participant receives in the trade.
*/
val tradeParticipant2Pokemon: Pokemon
val tradeParticipant2Pokemon: List<Pokemon>
fun createContext(): MutableMap<String, MoValue> = mutableMapOf(
"trade_participant_1" to (tradeParticipant1.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO),
"trade_participant_2" to (tradeParticipant2.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO),
// Kept for backwards compatibility, this is the first Pokémon of each side.
"trade_participant_1_pokemon" to tradeParticipant1Pokemon.first().struct,
"trade_participant_2_pokemon" to tradeParticipant2Pokemon.first().struct,
"trade_participant_1_pokemon_list" to tradeParticipant1Pokemon.asArrayValue { it.struct },
"trade_participant_2_pokemon_list" to tradeParticipant2Pokemon.asArrayValue { it.struct }
)
/**
* Event fired when a trade is about to happen. Cancelling this event prevents the trade from occurring.
@@ -53,16 +65,11 @@ interface TradeEvent {
*/
data class Pre(
override val tradeParticipant1: TradeParticipant,
override val tradeParticipant1Pokemon: Pokemon,
override val tradeParticipant1Pokemon: List<Pokemon>,
override val tradeParticipant2: TradeParticipant,
override val tradeParticipant2Pokemon: Pokemon
override val tradeParticipant2Pokemon: List<Pokemon>
) : TradeEvent, Cancelable() {
val context = mutableMapOf(
"trade_participant_1" to (tradeParticipant1.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO),
"trade_participant_2" to (tradeParticipant2.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO),
"trade_participant_1_pokemon" to tradeParticipant1Pokemon.struct,
"trade_participant_2_pokemon" to tradeParticipant2Pokemon.struct
)
val context = createContext()
val functions = moLangFunctionMap(
cancelFunc
@@ -77,15 +84,10 @@ interface TradeEvent {
*/
data class Post(
override val tradeParticipant1: TradeParticipant,
override val tradeParticipant1Pokemon: Pokemon,
override val tradeParticipant1Pokemon: List<Pokemon>,
override val tradeParticipant2: TradeParticipant,
override val tradeParticipant2Pokemon: Pokemon
override val tradeParticipant2Pokemon: List<Pokemon>
) : TradeEvent {
val context = mutableMapOf(
"trade_participant_1" to (tradeParticipant1.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO),
"trade_participant_2" to (tradeParticipant2.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO),
"trade_participant_1_pokemon" to tradeParticipant1Pokemon.struct,
"trade_participant_2_pokemon" to tradeParticipant2Pokemon.struct
)
val context = createContext()
}
}
@@ -16,7 +16,6 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.util.asArrayValue
object BattleActorMoLangFunctions : AbstractMoLangFunctionHolder<BattleActor>() {
override fun BattleActor.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
@@ -26,7 +25,6 @@ object BattleActorMoLangFunctions : AbstractMoLangFunctionHolder<BattleActor>()
functions["is_npc"] = { DoubleValue(actor.type == ActorType.NPC) }
functions["is_player"] = { DoubleValue(actor.type == ActorType.PLAYER) }
functions["is_wild"] = { DoubleValue(actor.type == ActorType.WILD) }
functions["battle_pokemon"] = { actor.pokemonList.asArrayValue { it.struct } }
when (actor) {
is NPCBattleActor -> {
@@ -98,14 +98,14 @@ object LivingEntityMoLangFunctions : AbstractMoLangFunctionHolder<LivingEntity>(
val targetEntity = params.get<MoValue>(0)
val maxDistance = params.getDoubleOrNull(1)?.toFloat() ?: 2.0F
val target =
val entity =
if (targetEntity is ObjectValue<*> && targetEntity.obj is Entity) {
targetEntity.obj as Entity
} else {
return@isLookingAt DoubleValue.ZERO
}
DoubleValue(entity.isLookingAt(target, maxDistance))
DoubleValue(entity.isLookingAt(entity, maxDistance))
}
functions["is_living_entity"] = { DoubleValue.ONE }
functions["is_flying"] = { DoubleValue(entity.isFallFlying) }
@@ -212,7 +212,7 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
// Lol. Lmao, even.
// TO-DO: Subscribe to [PokemonSpecies.observable].
functions["pre_evolution"] = { pokemon.species.preEvolution ?: DoubleValue.ZERO }
functions["nature"] = { StringValue(pokemon.nature.name.toString()) }
functions["nature"] = { StringValue(pokemon.nature.toString()) }
functions["is_wild"] = { DoubleValue(pokemon.entity?.let { it.ownerUUID == null } == true) }
functions["is_shiny"] = { DoubleValue(pokemon.shiny) }
functions["is_in_party"] = { DoubleValue(pokemon.storeCoordinates.get()?.store is PartyStore) }
@@ -10,6 +10,9 @@ package com.cobblemon.mod.common.api.pokedex
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.pokedex.def.PokedexDef
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.PokedexDexSyncPacket
import com.cobblemon.mod.common.util.adapters.CodecBackedAdapter
@@ -61,6 +64,38 @@ object Dexes : JsonDataRegistry<PokedexDef> {
PokedexDexSyncPacket(dexEntryMap.values).sendToPlayer(player)
}
fun findDexEntryBySpeciesId(regionId: ResourceLocation, speciesId: ResourceLocation): PokedexEntry? {
return dexEntryMap[regionId]?.getEntries()?.firstOrNull { it -> it.speciesId == speciesId }
}
fun findDexEntryBySpeciesId(speciesId: ResourceLocation): PokedexEntry? {
return findDexEntryBySpeciesId(cobblemonResource("national"), speciesId)
}
fun findRootDexEntryBySpeciesId(speciesId: ResourceLocation, formName: String): Pair<PokedexEntry, PokedexForm> {
var rootSpecies = PokemonSpecies.getByIdentifier(speciesId)!!
val initialFormEquivalents = getFormEquivalents(formName)
var rootForm = rootSpecies.forms.firstOrNull { initialFormEquivalents.contains(it.name.lowercase()) }
?: rootSpecies.standardForm
var preEvolution = rootForm.preEvolution
while (preEvolution != null) {
rootSpecies = preEvolution.species
rootForm = preEvolution.resolveForm(rootForm.aspects.toSet())
preEvolution = rootForm.preEvolution
}
val entry = findDexEntryBySpeciesId(rootSpecies.resourceIdentifier)
?: throw RuntimeException("Root dex entry not found")
val formEquivalents = getFormEquivalents(rootForm.name)
val form =
entry.forms.firstOrNull { formEquivalents.contains(it.displayForm.lowercase()) } ?:
entry.forms.first { it.displayForm.equals("Normal", ignoreCase = true) }
return entry to form
}
fun getFormEquivalents(formName: String): List<String> {
val key = equivalentFormsLookup[formName.lowercase()]
return equivalentForms[key] ?: listOf(formName.lowercase())
@@ -8,20 +8,13 @@
package com.cobblemon.mod.common.api.pokedex.entry
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.DexEntrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.DexEvolutionDisplaySyncPacket
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
@@ -45,8 +38,6 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
val entries = mutableMapOf<ResourceLocation, PokedexEntry>()
val evolutionDisplays = mutableListOf<DexEvolutionDisplay>()
override fun reload(data: Map<ResourceLocation, PokedexEntry>) {
data.forEach { _, entry ->
entries[entry.id] = entry
@@ -59,7 +50,6 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
}
}
}
computeEvolutionDisplays()
observable.emit(this)
}
@@ -67,58 +57,5 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
override fun sync(player: ServerPlayer) {
DexEntrySyncPacket(entries.values).sendToPlayer(player)
DexEvolutionDisplaySyncPacket(evolutionDisplays).sendToPlayer(player)
}
fun computeEvolutionDisplays() {
evolutionDisplays.clear()
PokemonSpecies.species.forEach {
try {
evolutionDisplays.addAll(buildDisplays(it.standardForm) + it.forms.flatMap { form -> buildDisplays(form) })
} catch (e: Exception) {
Cobblemon.LOGGER.error("Failed to build dex evolution displays for ${it.resourceIdentifier}", e)
}
}
}
fun buildDisplays(form: FormData): List<DexEvolutionDisplay> {
val result = mutableListOf<DexEvolutionDisplay>()
(form.ownEvolutions ?: form.evolutions).forEach { evolution ->
val requirements = mutableListOf<DexEvolutionRequirementDisplay>()
getToolTipHeaderIcon(evolution)?.let { header ->
requirements.add(header)
}
evolution.requirements.mapNotNull { resolveRequirementData(it) }.forEach { requirement ->
requirements.add(requirement)
}
listOfNotNull(evolution.result, evolution.shedder).forEach resultLoop@{ resultProperties ->
val resultSpecies = resultProperties.species
?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
?: return@resultLoop
// The result's own aspects win, otherwise carry the source form's aspects over
// like a real evolution does (Alola-Bias Pichu -> Alola-Bias Pikachu)
val resultForm = resultSpecies.getForm(resultProperties.aspects).takeIf { it != resultSpecies.standardForm }
?: resultSpecies.getForm(form.aspects.toSet() + resultProperties.aspects)
val toAspects = resultProperties.aspects + resultForm.aspects
val resultRequirements = if (resultProperties === evolution.shedder) {
requirements + CobblemonDexEvolutionRequirementDisplay(CobblemonItems.POKE_BALL, lang("ui.pokedex.evolution.shedder.tooltip"))
} else {
requirements
}
result.add(DexEvolutionDisplay(
fromSpecies = form.species.resourceIdentifier,
fromForm = form.name,
fromAspects = form.aspects.toSet(),
toSpecies = resultSpecies.resourceIdentifier,
toForm = resultForm.name,
toAspects = toAspects,
requirements = resultRequirements,
))
}
}
return result
}
}
}
@@ -1,466 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokedex.entry
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeIdentifierCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeTagCondition
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.text.add
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
import com.cobblemon.mod.common.pokemon.requirements.*
import com.cobblemon.mod.common.util.adapters.RegistryAccessResolver
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.lang
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.ExtraCodecs
import net.minecraft.world.item.Item
import net.minecraft.world.item.Items
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.levelgen.structure.Structure
import kotlin.jvm.optionals.getOrNull
data class DexEvolutionDisplay(
val fromSpecies: ResourceLocation,
val fromForm: String,
val fromAspects: Set<String>,
val toSpecies: ResourceLocation,
val toForm: String,
val toAspects: Set<String>,
val requirements: List<DexEvolutionRequirementDisplay>,
)
interface DexEvolutionRequirementDisplay {
val icon: Item
val tooltip: MutableComponent
}
data class CobblemonDexEvolutionRequirementDisplay(
override val icon: Item,
override val tooltip: MutableComponent,
) : DexEvolutionRequirementDisplay
data class CobblemonBiomeDexEvolutionRequirementDisplay(
override val icon: Item,
override val tooltip: MutableComponent,
val condition: ExtraCodecs.TagOrElementLocation? = null,
val antiCondition: ExtraCodecs.TagOrElementLocation? = null,
) : DexEvolutionRequirementDisplay
fun getToolTipHeaderIcon(evolution: Evolution): DexEvolutionRequirementDisplay? {
when (evolution) {
is ItemInteractionEvolution -> return getToolTipHeaderIcon(evolution)
is TradeEvolution -> return getToolTipHeaderIcon(evolution)
}
return null
}
fun getToolTipHeaderIcon(itemInteractionEvolution: ItemInteractionEvolution): CobblemonDexEvolutionRequirementDisplay? {
val item = itemInteractionEvolution.requiredContext.items.orElse(null)?.firstOrNull()?.value() ?: return null
return CobblemonDexEvolutionRequirementDisplay(item, lang("ui.pokedex.evolution.item_interact.tooltip", item.description.plainCopy().green()))
}
fun getToolTipHeaderIcon(tradeEvolution: TradeEvolution): CobblemonDexEvolutionRequirementDisplay {
val species = tradeEvolution.requiredContext.species
val tooltip = if (species == null) {
lang("ui.pokedex.evolution.trade.tooltip.with_any_species")
} else {
PokemonSpecies.getByIdentifier(species.asIdentifierDefaultingNamespace())
?.let { lang("ui.pokedex.evolution.trade.tooltip.with_species", it.translatedName.green()) }
?: lang("ui.pokedex.evolution.trade.tooltip.with_any_species")
}
return CobblemonDexEvolutionRequirementDisplay(CobblemonItems.LINK_CABLE, tooltip)
}
fun resolveRequirementData(requirement: Requirement): DexEvolutionRequirementDisplay? {
val icon = requirement.pokedexIcon?.let {
RegistryAccessResolver.getAccess().registryOrThrow(Registries.ITEM).get(it)
}
val tooltip = requirement.pokedexTooltip?.let {
Component.translatable(it)
}
if (icon != null && tooltip != null) {
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
return buildRequirementData(requirement)
}
fun buildRequirementData(requirement: Requirement): DexEvolutionRequirementDisplay? {
when (requirement) {
is AdvancementRequirement -> return buildAdvancementRequirement(requirement)
is BattleCriticalHitsRequirement -> return buildBattleCriticalHitsRequirement(requirement)
is BiomeRequirement -> return buildBiomeRequirement(requirement)
is BlocksTraveledRequirement -> return buildBlocksTraveledRequirement(requirement)
is DamageTakenRequirement -> return buildDamageTakenRequirement(requirement)
is DefeatRequirement -> return buildDefeatRequirement(requirement)
is FriendshipRequirement -> return buildFriendshipRequirement(requirement)
is HeldItemRequirement -> return buildHeldItemRequirement(requirement)
is LevelRequirement -> return buildLevelRequirement(requirement)
is MoonPhaseRequirement -> return buildMoonPhaseRequirement(requirement)
is MoveSetRequirement -> return buildMoveSetRequirement(requirement)
is MoveTypeRequirement -> return buildMoveTypeRequirement(requirement)
is NegateRequirement -> return buildNegateRequirement(requirement)
is OwnerHoldsItemRequirement -> return buildOwnerHoldsItemRequirement(requirement)
is PartyMemberRequirement -> return buildPartyMemberRequirement(requirement)
is PokemonPropertiesRequirement -> return buildPokemonPropertiesRequirement(requirement)
is PropertyRangeRequirement -> return buildPropertyRangeRequirement(requirement)
is RecoilRequirement -> return buildRecoilRequirement(requirement)
is StatCompareRequirement -> return buildStatCompareRequirement(requirement)
is StatEqualRequirement -> return buildStatEqualRequirement(requirement)
is StructureRequirement -> return buildStructureRequirement(requirement)
is TimeRangeRequirement -> return buildTimeRangeRequirement(requirement)
is UseMoveRequirement -> return buildUseMoveRequirement(requirement)
is WeatherRequirement -> return buildWeatherRequirement(requirement)
}
return null
}
fun buildAdvancementRequirement(advancementRequirement: AdvancementRequirement): DexEvolutionRequirementDisplay {
val icon = Items.BOOK
val identifier = advancementRequirement.requiredAdvancement
val advancementLanguageKey = "${identifier.namespace}.${identifier.path.substringAfterLast('/')}"
val advancementComponent = Component.translatable("advancements.$advancementLanguageKey").green()
val tooltip = lang("ui.pokedex.requirement.advancement.tooltip", advancementComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildBattleCriticalHitsRequirement(battleCriticalHitsRequirement: BattleCriticalHitsRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.MEDICINAL_LEEK
val criticalsComponent = Component.literal(battleCriticalHitsRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.battle_critical_hits.tooltip", criticalsComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
private fun tagDisplayName(registry: String, id: ResourceLocation): MutableComponent {
return Component.translatableWithFallback("tag.$registry.${id.namespace}.${id.path.replace('/', '.')}", prettifyPath(id.path))
}
private fun prettifyPath(path: String): String {
return path.substringAfterLast('/').removePrefix("is_").split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) }
}
fun buildNegateRequirement(negateRequirement: NegateRequirement): DexEvolutionRequirementDisplay? {
val inner = negateRequirement.inner
when (inner) {
is BiomeRequirement -> buildBiomeRequirement(inner, negated = true)?.let { return it }
is StructureRequirement -> buildStructureRequirement(inner, negated = true)?.let { return it }
}
val innerDisplay = resolveRequirementData(inner) ?: return null
return CobblemonDexEvolutionRequirementDisplay(innerDisplay.icon, lang("ui.pokedex.requirement.negate.tooltip", innerDisplay.tooltip))
}
fun buildBiomeRequirement(biomeRequirement: BiomeRequirement, negated: Boolean = false): DexEvolutionRequirementDisplay? {
// Negating "in X" is "not in X"; negating both a condition and an anticondition isn't expressible as one line
if (negated && biomeRequirement.biomeCondition != null && biomeRequirement.biomeAnticondition != null) return null
val condition = if (negated) biomeRequirement.biomeAnticondition else biomeRequirement.biomeCondition
val antiCondition = if (negated) biomeRequirement.biomeCondition else biomeRequirement.biomeAnticondition
if (condition == null && antiCondition == null) {
return null
}
val icon = Items.COMPASS
val displayName: (RegistryLikeCondition<Biome>) -> MutableComponent = {
when (it) {
is RegistryLikeTagCondition -> tagDisplayName("worldgen.biome", it.tag.location())
is RegistryLikeIdentifierCondition -> Component.translatable("biome.${it.identifier.namespace}.${it.identifier.path}")
else -> Component.literal(it.toString())
}
}
val tooltip = if (condition != null && antiCondition != null) {
lang("ui.pokedex.requirement.biome.both.tooltip", displayName(condition).green(), displayName(antiCondition).green())
} else if (condition != null) {
lang("ui.pokedex.requirement.biome.tooltip", displayName(condition).green())
} else {
lang("ui.pokedex.requirement.biome.anticondition.tooltip", displayName(antiCondition!!).green())
}
var conditionConverted: ExtraCodecs.TagOrElementLocation? = null
if (condition != null) {
val conditionIdentifier = if (condition is RegistryLikeIdentifierCondition) condition.identifier else (condition as RegistryLikeTagCondition).tag.location
conditionConverted = ExtraCodecs.TagOrElementLocation(conditionIdentifier, condition is RegistryLikeTagCondition)
}
var antiConditionConverted: ExtraCodecs.TagOrElementLocation? = null
if (antiCondition != null) {
val conditionIdentifier = if (antiCondition is RegistryLikeIdentifierCondition) antiCondition.identifier else (antiCondition as RegistryLikeTagCondition).tag.location
antiConditionConverted = ExtraCodecs.TagOrElementLocation(conditionIdentifier, antiCondition is RegistryLikeTagCondition)
}
return CobblemonBiomeDexEvolutionRequirementDisplay(icon, tooltip, conditionConverted, antiConditionConverted)
}
fun buildBlocksTraveledRequirement(blocksTraveledRequirement: BlocksTraveledRequirement): DexEvolutionRequirementDisplay {
val icon = Items.LEATHER_BOOTS
val blocksComponent = Component.literal(blocksTraveledRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.blocks_traveled.tooltip", blocksComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildDamageTakenRequirement(damageTakenRequirement: DamageTakenRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.PUNCHING_GLOVE
val blocksComponent = Component.literal(damageTakenRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.damage_taken.tooltip", blocksComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildDefeatRequirement(defeatRequirement: DefeatRequirement): DexEvolutionRequirementDisplay? {
val description = describePokemonProperties(defeatRequirement.target) ?: return null
val icon = CobblemonItems.PUNCHING_GLOVE
val defeatedAmount = defeatRequirement.amount
val descriptionComponent = Component.literal(defeatedAmount.toString()).green().add(" ").add(description)
val tooltip = lang("ui.pokedex.requirement.defeat.tooltip", descriptionComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildFriendshipRequirement(friendshipRequirement: FriendshipRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.SOOTHE_BELL
val friendshipAmountComponent = Component.literal(friendshipRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.friendship.tooltip", friendshipAmountComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildHeldItemRequirement(heldItemRequirement: HeldItemRequirement): DexEvolutionRequirementDisplay? {
val items = heldItemRequirement.itemCondition.items.getOrNull() ?: return null
val item = items.firstOrNull()?.value() ?: return null
val itemComponent = items.unwrapKey().map { tag ->
val id = tag.location()
val fallback = id.path.substringAfterLast('/').split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) }
Component.translatableWithFallback("tag.item.${id.namespace}.${id.path.replace('/', '.')}", fallback)
}.orElseGet { item.description.plainCopy() }
val tooltip = lang("ui.pokedex.requirement.held_item.tooltip", itemComponent.green())
return CobblemonDexEvolutionRequirementDisplay(item, tooltip)
}
fun buildLevelRequirement(levelRequirement: LevelRequirement): DexEvolutionRequirementDisplay {
val minLevel = levelRequirement.minLevel
val maxLevel = levelRequirement.maxLevel
val icon = CobblemonItems.RARE_CANDY
val tooltip =
if (minLevel == maxLevel || maxLevel == Int.MAX_VALUE)
lang("ui.pokedex.requirement.level.tooltip.min", Component.literal(minLevel.toString()).green())
else
lang("ui.pokedex.requirement.level.tooltip.between", Component.literal(minLevel.toString()).green(), Component.literal(maxLevel.toString()).green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildMoonPhaseRequirement(moonPhaseRequirement: MoonPhaseRequirement): DexEvolutionRequirementDisplay {
val icon = Items.CLOCK
val moonPhaseComponent = moonPhaseRequirement.moonPhase.displayName().green()
val tooltip = lang("ui.pokedex.requirement.moon_phase.tooltip", moonPhaseComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildMoveSetRequirement(moveSetRequirement: MoveSetRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.TECHNICAL_MACHINE
val tooltip = lang("ui.pokedex.requirement.has_move.tooltip", moveSetRequirement.move.displayName.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildMoveTypeRequirement(moveTypeRequirement: MoveTypeRequirement): DexEvolutionRequirementDisplay {
val type = ElementalTypes.get(moveTypeRequirement.type.name)
val icon = when (type) {
ElementalTypes.NORMAL -> CobblemonItems.NORMAL_GEM
ElementalTypes.FIRE -> CobblemonItems.FIRE_GEM
ElementalTypes.WATER -> CobblemonItems.WATER_GEM
ElementalTypes.GRASS -> CobblemonItems.GRASS_GEM
ElementalTypes.ELECTRIC -> CobblemonItems.ELECTRIC_GEM
ElementalTypes.ICE -> CobblemonItems.ICE_GEM
ElementalTypes.FIGHTING -> CobblemonItems.FIGHTING_GEM
ElementalTypes.POISON -> CobblemonItems.POISON_GEM
ElementalTypes.GROUND -> CobblemonItems.GROUND_GEM
ElementalTypes.FLYING -> CobblemonItems.FLYING_GEM
ElementalTypes.PSYCHIC -> CobblemonItems.PSYCHIC_GEM
ElementalTypes.BUG -> CobblemonItems.BUG_GEM
ElementalTypes.ROCK -> CobblemonItems.ROCK_GEM
ElementalTypes.GHOST -> CobblemonItems.GHOST_GEM
ElementalTypes.DRAGON -> CobblemonItems.DRAGON_GEM
ElementalTypes.DARK -> CobblemonItems.DARK_GEM
ElementalTypes.STEEL -> CobblemonItems.STEEL_GEM
ElementalTypes.FAIRY -> CobblemonItems.FAIRY_GEM
else -> CobblemonItems.NORMAL_GEM
}
val tooltip = lang("ui.pokedex.requirement.has_move_type.tooltip", moveTypeRequirement.type.displayName.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildOwnerHoldsItemRequirement(ownerHoldsItemRequirement: OwnerHoldsItemRequirement): DexEvolutionRequirementDisplay? {
val item = ownerHoldsItemRequirement.itemCondition.items.orElse(null)?.firstOrNull()?.value() ?: return null
val tooltip = lang("ui.pokedex.requirement.owner_held_item.tooltip", item.description.plainCopy().green())
return CobblemonDexEvolutionRequirementDisplay(item, tooltip)
}
fun buildPartyMemberRequirement(partyMemberRequirement: PartyMemberRequirement): DexEvolutionRequirementDisplay? {
val description = describePokemonProperties(partyMemberRequirement.target) ?: return null
val icon = CobblemonItems.POKE_BALL
val tooltip = if (partyMemberRequirement.contains) {
lang("ui.pokedex.requirement.party_member.tooltip", description)
} else {
lang("ui.pokedex.requirement.party_member.anticondition.tooltip", description)
}
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildPokemonPropertiesRequirement(pokemonPropertiesRequirement: PokemonPropertiesRequirement): DexEvolutionRequirementDisplay? {
val description = describePokemonProperties(pokemonPropertiesRequirement.target) ?: return null
val icon = Items.NAME_TAG
val tooltip = lang("ui.pokedex.requirement.pokemon_properties.tooltip", description)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildPropertyRangeRequirement(propertyRangeRequirement: PropertyRangeRequirement): DexEvolutionRequirementDisplay? {
val provider = SpeciesFeatures.getFeature(propertyRangeRequirement.feature) as? IntSpeciesFeatureProvider ?: return null
val displayName = provider.display?.name?.takeIf { it.isNotBlank() }?.asTranslated()?.green() ?: return null
val icon = provider.itemPoints.keys.firstOrNull()?.let { BuiltInRegistries.ITEM.get(it) } ?: Items.NAME_TAG
val range = propertyRangeRequirement.range
val tooltip = lang(
"ui.pokedex.requirement.property_range.tooltip",
displayName,
Component.literal(range.first.toString()).green(),
Component.literal(range.last.toString()).green()
)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildRecoilRequirement(recoilRequirement: RecoilRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.LIFE_ORB
val recoilComponent = Component.literal(recoilRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.recoil.tooltip", recoilComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildStatCompareRequirement(statCompareRequirement: StatCompareRequirement): DexEvolutionRequirementDisplay {
val highStat = Stats.getStat(statCompareRequirement.highStat)
val lowStat = Stats.getStat(statCompareRequirement.lowStat)
val icon = when (highStat) {
Stats.HP, Stats.EVASION, Stats.ACCURACY -> CobblemonItems.HEALTH_MOCHI
Stats.ATTACK -> CobblemonItems.MUSCLE_MOCHI
Stats.DEFENCE -> CobblemonItems.RESIST_MOCHI
Stats.SPECIAL_ATTACK -> CobblemonItems.GENIUS_MOCHI
Stats.SPECIAL_DEFENCE -> CobblemonItems.CLEVER_MOCHI
Stats.SPEED -> CobblemonItems.SWIFT_MOCHI
}
val highStatComponent = highStat.displayName.plainCopy().green()
val lowStatComponent = lowStat.displayName.plainCopy().green()
val tooltip = lang("ui.pokedex.requirement.stat_compare.tooltip", highStatComponent, lowStatComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildStatEqualRequirement(statEqualRequirement: StatEqualRequirement): DexEvolutionRequirementDisplay {
val statOne = Stats.getStat(statEqualRequirement.statOne)
val statTwo = Stats.getStat(statEqualRequirement.statTwo)
val statOneComponent = statOne.displayName.plainCopy().green()
val statTwoComponent = statTwo.displayName.plainCopy().green()
val icon = CobblemonItems.FRESH_START_MOCHI
val tooltip = lang("ui.pokedex.requirement.stat_equal.tooltip", statOneComponent, statTwoComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildStructureRequirement(structureRequirement: StructureRequirement, negated: Boolean = false): DexEvolutionRequirementDisplay? {
if (negated && structureRequirement.structureCondition != null && structureRequirement.structureAnticondition != null) return null
val condition = if (negated) structureRequirement.structureAnticondition else structureRequirement.structureCondition
val anticondition = if (negated) structureRequirement.structureCondition else structureRequirement.structureAnticondition
if (condition == null && anticondition == null) {
return null
}
val icon = Items.FILLED_MAP
val displayName: (RegistryLikeCondition<Structure>) -> MutableComponent = {
when (it) {
is RegistryLikeTagCondition -> tagDisplayName("worldgen.structure", it.tag.location())
is RegistryLikeIdentifierCondition -> Component.literal(prettifyPath(it.identifier.path))
else -> Component.literal(it.toString())
}
}
val tooltip = if (condition != null && anticondition != null) {
lang("ui.pokedex.requirement.structure.both.tooltip", displayName(condition).green(), displayName(anticondition).green())
} else if (condition != null) {
lang("ui.pokedex.requirement.structure.tooltip", displayName(condition).green())
} else {
lang("ui.pokedex.requirement.structure.anticondition.tooltip", displayName(anticondition!!).green())
}
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildTimeRangeRequirement(timeRangeRequirement: TimeRangeRequirement): DexEvolutionRequirementDisplay? {
val icon = Items.CLOCK
val timeRangeComponent = timeRangeRequirement.range.displayName() ?: return null
val tooltip = lang("ui.pokedex.requirement.time_range.tooltip", timeRangeComponent.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildUseMoveRequirement(useMoveRequirement: UseMoveRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.CHOICE_BAND
val tooltip = lang("ui.pokedex.requirement.use_move.tooltip", useMoveRequirement.move.displayName.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildWeatherRequirement(weatherRequirement: WeatherRequirement): DexEvolutionRequirementDisplay {
val icon: Item
val tooltip: MutableComponent
if (weatherRequirement.isThundering == true) {
icon = Items.LIGHTNING_ROD
tooltip = lang("ui.pokedex.requirement.weather.tooltip.thundering")
} else {
icon = Items.WATER_BUCKET
tooltip = lang("ui.pokedex.requirement.weather.tooltip.raining")
}
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
@@ -26,6 +26,7 @@ import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.MovesetBuilder
import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies.getByIdentifier
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffect
import com.cobblemon.mod.common.api.pokemon.effect.adapter.ShoulderEffectAdapter
import com.cobblemon.mod.common.api.pokemon.egg.EggGroup
@@ -43,6 +44,9 @@ import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.adapters.ElementalTypeAdapter
import com.cobblemon.mod.common.net.messages.client.data.FormEvolutionOverride
import com.cobblemon.mod.common.net.messages.client.data.SpeciesEvolutionEntry
import com.cobblemon.mod.common.net.messages.client.data.SpeciesEvolutionSyncPacket
import com.cobblemon.mod.common.net.messages.client.data.SpeciesRegistrySyncPacket
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
@@ -242,6 +246,18 @@ object PokemonSpecies : JsonDataRegistry<Species> {
override fun sync(player: ServerPlayer) {
SpeciesRegistrySyncPacket(species.toList()).sendToPlayer(player)
// Sent after the species themselves so every species is already registered by the time this is decoded.
val evolutionEntries = species.map { singleSpecies ->
val formOverrides = singleSpecies.forms.mapNotNull { form ->
if (form.ownEvolutions == null && form.ownPreEvolution == null) {
null
} else {
FormEvolutionOverride(form.name, form.ownEvolutions, form.ownPreEvolution)
}
}
SpeciesEvolutionEntry(singleSpecies.resourceIdentifier, singleSpecies.evolutions, singleSpecies.preEvolution, formOverrides)
}
SpeciesEvolutionSyncPacket(evolutionEntries).sendToPlayer(player)
}
/**
@@ -15,8 +15,6 @@ import com.cobblemon.mod.common.block.entity.PokeSnackBlockEntity
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
import net.minecraft.client.ParticleStatus
import net.minecraft.core.particles.BlockParticleOption
import net.minecraft.core.particles.ParticleOptions
import net.minecraft.core.particles.ParticleTypes
@@ -42,25 +40,13 @@ sealed class ParticleData {
val chance: Double,
val amount: Int,
val locatorResolver: (Map<String, MatrixWrapper>) -> List<String>
): ParticleData() {
fun amount(): Int = when(Minecraft.getInstance().options.particles().get()) {
ParticleStatus.ALL -> amount
ParticleStatus.DECREASED -> amount / 2
ParticleStatus.MINIMAL -> amount / 4
}
}
): ParticleData()
data class MinecraftParticle(
val particle: ParticleOptions,
val chance: Double,
val amount: Int
): ParticleData() {
fun amount(): Int = when(Minecraft.getInstance().options.particles().get()) {
ParticleStatus.ALL -> amount
ParticleStatus.DECREASED -> amount / 2
ParticleStatus.MINIMAL -> amount / 4
}
}
): ParticleData()
}
/**
@@ -25,6 +25,8 @@ interface PreEvolution {
val form: FormData
fun resolveForm(extraAspects: Set<String> = emptySet()): FormData = form
companion object {
fun of(species: Species, form: FormData = species.standardForm): PreEvolution = StandardPreEvolution(species, form)
@@ -22,7 +22,6 @@ import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.AlphaLevelMatchingSensor
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.Entity
@@ -77,7 +76,7 @@ open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (
?: return super.affectWeight(detail, spawnablePosition, weight)
val detailSpecies = pokemonDetail.pokemon.species
?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
?.let { PokemonSpecies.getByName(it) }
?: return super.affectWeight(detail, spawnablePosition, weight)
val formData = detailSpecies.getForm(pokemonDetail.pokemon.aspects)
@@ -27,7 +27,6 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.isBoxLoaded
import com.cobblemon.mod.common.util.removeIf
import com.cobblemon.mod.common.util.squeezeWithinBounds
import com.cobblemon.mod.common.util.toBlockPos
import com.cobblemon.mod.common.util.toVec3f
import com.cobblemon.mod.common.util.weightedSelection
import kotlin.math.max
@@ -191,60 +190,73 @@ interface Spawner {
}
fun isValidStartPoint(world: Level, chunk: ChunkAccess, startPos: BlockPos.MutableBlockPos): Boolean {
val abovePos = startPos.above()
if (!world.isLoaded(startPos) || !world.isLoaded(abovePos)) {
val y = startPos.y
if (!world.isLoaded(startPos) || !world.isLoaded(startPos.setY(y + 1))) {
return false
}
val mid = chunk.getBlockState(startPos)
val above = chunk.getBlockState(abovePos)
return above.isPathfindable(PathComputationType.AIR) && mid.isAir
val mid = chunk.getBlockState(startPos.setY(y))
val above = chunk.getBlockState(startPos.setY(y + 1))
// Above must be non-solid
if (!above.isPathfindable(PathComputationType.AIR)) {
return false
}
// Position must be non-air
if (mid.isAir) {
return false
}
return true
}
fun constrainArea(area: SpawningZoneInput): SpawningZoneInput? {
val world = area.world
val centerPos = area.getCenter().toBlockPos()
val basePos = BlockPos.MutableBlockPos(area.baseX, area.baseY, area.baseZ)
val originalY = area.baseY
val (chunkX, chunkZ) = Pair(SectionPos.blockToSectionCoord(area.baseX), SectionPos.blockToSectionCoord(area.baseZ))
val (centerChunkX, centerChunkZ) = Pair(SectionPos.blockToSectionCoord(centerPos.x), SectionPos.blockToSectionCoord(centerPos.z))
// if the chunk isn't loaded, we don't want to go further & we don't want the getChunk function below to load/create the chunk.
if (!world.areEntitiesLoaded(ChunkPos.asLong(centerChunkX, centerChunkZ))) return null
if (!area.world.areEntitiesLoaded(ChunkPos.asLong(chunkX, chunkZ))) return null
val chunk = world.getChunk(centerChunkX, centerChunkZ, ChunkStatus.FULL) ?: return null
val chunk = area.world.getChunk(chunkX, chunkZ, ChunkStatus.FULL) ?: return null
val probePos = centerPos.mutable()
var validY = if (isValidStartPoint(world, chunk, probePos)) centerPos.y else null
var valid = isValidStartPoint(area.world, chunk, basePos)
if (validY == null) {
for (offset in 1..Cobblemon.config.maxVerticalCorrectionBlocks) {
if (isValidStartPoint(world, chunk, probePos.set(centerPos.x, centerPos.y + offset, centerPos.z))) {
validY = centerPos.y + offset
if (!valid) {
var offset = 1
do {
if (isValidStartPoint(area.world, chunk, basePos.setY(originalY + offset))) {
valid = true
basePos.y = originalY + offset
break
} else if (isValidStartPoint(world, chunk, probePos.set(centerPos.x, centerPos.y - offset, centerPos.z))) {
validY = centerPos.y - offset
} else if (isValidStartPoint(area.world, chunk, basePos.setY(originalY - offset))) {
valid = true
basePos.y = originalY - offset
break
}
}
offset++
} while (offset <= Cobblemon.config.maxVerticalCorrectionBlocks)
}
val targetY = validY ?: return null
val newBaseY = (targetY - (area.height / 2)).coerceIn(world.minBuildHeight, world.maxBuildHeight)
val minPos = world.squeezeWithinBounds(BlockPos(area.baseX, newBaseY, area.baseZ))
val maxPos = world.squeezeWithinBounds(BlockPos(area.baseX + area.length, newBaseY + area.height, area.baseZ + area.width))
val finalBox = AABB(
minPos.x.toDouble(), minPos.y.toDouble(), minPos.z.toDouble(),
maxPos.x.toDouble(), maxPos.y.toDouble(), maxPos.z.toDouble()
)
if (minPos.x < maxPos.x && minPos.y < maxPos.y && minPos.z < maxPos.z && world.isBoxLoaded(finalBox)) {
return SpawningZoneInput(
cause = area.cause,
world = world,
baseX = minPos.x,
baseY = minPos.y,
baseZ = minPos.z,
length = maxPos.x - minPos.x,
height = maxPos.y - minPos.y,
width = maxPos.z - minPos.z
)
if (valid) {
val min = area.world.squeezeWithinBounds(basePos)
val max = area.world.squeezeWithinBounds(basePos.move(area.length, area.height, area.width))
if (area.world.isLoaded(min) && area.world.isLoaded(max) &&
min.x < max.x && min.y < max.y && min.z < max.z
) {
return SpawningZoneInput(
cause = area.cause,
world = area.world,
baseX = min.x,
baseY = min.y,
baseZ = min.z,
length = max.x - min.x,
height = max.y - min.y,
width = max.z - min.z
)
}
}
return null
@@ -29,7 +29,6 @@ import com.cobblemon.mod.common.net.messages.client.storage.pc.InitializePCPacke
import com.cobblemon.mod.common.net.messages.client.storage.pc.MoveClientPCPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.UnlockPCBoxWallpaperPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonBatchPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.asTranslated
@@ -271,34 +270,6 @@ open class PCStore(
}
}
fun moveBatch(moves: List<Triple<UUID, PCPosition, PCPosition>>): Boolean {
if (moves.isEmpty() || moves.size > MovePCPokemonBatchPacket.MAX_MOVES) return false
if (moves.map { it.second }.toSet().size != moves.size ||
moves.map { it.third }.toSet().size != moves.size ||
moves.map { it.first }.toSet().size != moves.size
) return false
if (moves.any { (_, source, target) -> !isValidPosition(source) || !isValidPosition(target) }) return false
val sourcePositions = moves.mapTo(mutableSetOf()) { it.second }
val pokemonMoves = moves.map { (pokemonID, source, target) ->
val pokemon = get(source)?.takeIf { it.uuid == pokemonID } ?: return false
Triple(source, target, pokemon)
}
if (pokemonMoves.any { (_, target, _) -> get(target) != null && target !in sourcePositions }) return false
pokemonMoves.forEach { (source, _, _) -> setAtPosition(source, null) }
pokemonMoves.forEach { (_, target, pokemon) -> setAtPosition(target, pokemon) }
pokemonMoves.forEach { (_, _, pokemon) ->
sendPacketToObservers(RemoveClientPokemonPacket(this, pokemon.uuid))
}
pokemonMoves.forEach { (_, target, pokemon) ->
sendPacketToObservers(SetPCPokemonPacket(uuid, target) { pokemon })
}
pcChangeObservable.emit(Unit)
return true
}
override fun loadPositionFromNBT(nbt: CompoundTag): StoreCoordinates<PCPosition> {
return StoreCoordinates(this, PCPosition(nbt.getShort(DataKeys.STORE_BOX).toInt(), nbt.getByte(DataKeys.STORE_SLOT).toInt()))
}
@@ -120,7 +120,7 @@ object CobblemonItemTags {
@JvmField val DESTINY_KNOT = create("held/destiny_knot")
@JvmField val EVERSTONE = create("held/everstone")
@JvmField val EXPERIENCE_SHARE = create("held/experience_share")
@JvmField val IS_FRIENDSHIP_BOOSTER = create("held/is_friendship_booster")
@JvmField val IS_FRIENDSHIP_BOOSTER = create("is_friendship_booster")
@JvmField val ANY_HELD_ITEM = create("held/is_held_item")
@JvmField val BLACKLISTED_ITEMS_TO_HOLD = create("held/blacklisted_items_to_hold")
@JvmField val WHITELISTED_ITEMS_TO_HOLD = create("held/whitelisted_items_to_hold")
@@ -83,8 +83,7 @@ open class BattlePokemon(
"max_health" to Function { DoubleValue(maxHealth.toDouble()) },
"ivs" to Function { effectedPokemon.ivs.struct },
"nature" to Function { StringValue(effectedPokemon.nature.name.toString()) },
"moveset" to Function { effectedPokemon.moveSet.toStruct() },
"has_faced_opponents" to Function { DoubleValue(facedOpponents.isNotEmpty()) }
"moveset" to Function { effectedPokemon.moveSet.toStruct() }
))
val uuid: UUID
@@ -34,9 +34,7 @@ import net.minecraft.world.item.alchemy.Potions
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.BonemealableBlock
import net.minecraft.world.level.block.DispenserBlock
import net.minecraft.world.level.block.LeavesBlock
import net.minecraft.world.level.block.state.BlockState
@@ -54,7 +52,7 @@ import net.minecraft.world.phys.shapes.EntityCollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings), BonemealableBlock {
class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
companion object {
val AGE: IntegerProperty = BlockStateProperties.AGE_2
val DISTANCE: IntegerProperty = BlockStateProperties.DISTANCE
@@ -183,15 +181,6 @@ class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings), Bonemea
super.randomTick(state, world, pos, random)
}
override fun isValidBonemealTarget(level: LevelReader, pos: BlockPos, state: BlockState): Boolean =
state.getValue(AGE) < MAX_AGE && !state.getValue(BlockStateProperties.WATERLOGGED)
override fun isBonemealSuccess(level: Level, random: RandomSource, pos: BlockPos, state: BlockState) = true
override fun performBonemeal(level: ServerLevel, random: RandomSource, pos: BlockPos, state: BlockState) {
level.setBlock(pos, changeAge(state, 1), UPDATE_CLIENTS)
}
override fun placeLiquid(level: LevelAccessor, pos: BlockPos, state: BlockState, fluidState: FluidState): Boolean {
if (!state.getValue(BlockStateProperties.WATERLOGGED) && fluidState.type === Fluids.WATER) {
val hasHoney = state.getValue(AGE) > 0
@@ -19,7 +19,6 @@ import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.util.StringRepresentable
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.component.BlockItemStateProperties
@@ -34,8 +33,8 @@ import net.minecraft.world.level.block.Mirror
import net.minecraft.world.level.block.Rotation
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.block.state.properties.DirectionProperty
import net.minecraft.world.level.block.state.properties.EnumProperty
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.level.material.PushReaction
import net.minecraft.world.level.storage.loot.LootParams
@@ -62,27 +61,22 @@ class TypeGemClusterBlock(
}
val gemToClusterMap: MutableMap<Block, TypeGemClusterBlock> = mutableMapOf()
val SHOULD_GROW: BooleanProperty = BooleanProperty.create("should_grow")
val FACING: DirectionProperty = DirectionalBlock.FACING
val STAGE: IntegerProperty = IntegerProperty.create("stage", 0, 3)
val GROWTH_TYPE: EnumProperty<GrowthType> = EnumProperty.create("growth_type", GrowthType::class.java)
val STUNTED: BooleanProperty = BooleanProperty.create("stunted")
fun clusterFromGemBlock(gemBlock: Block): TypeGemClusterBlock? {
return gemToClusterMap[gemBlock]
}
}
enum class GrowthType(private val label: String, val chance: Float): StringRepresentable {
DECORATION("none", 0.25f),
FULL("full", 0.5f),
STUNTED("stunted", 0.25f);
override fun getSerializedName(): String = label
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.UP)
.setValue(SHOULD_GROW, true)
.setValue(STAGE, 0)
.setValue(GROWTH_TYPE, GrowthType.FULL)
.setValue(STUNTED, false)
)
}
@@ -95,19 +89,15 @@ class TypeGemClusterBlock(
return defaultBlockState()
.setValue(FACING, facing)
.setValue(SHOULD_GROW, shouldGrow)
.setValue(STAGE, 0)
.setValue(GROWTH_TYPE, if(shouldGrow) GrowthType.FULL else GrowthType.DECORATION)
.setValue(STUNTED, false)
}
public override fun isRandomlyTicking(state: BlockState): Boolean {
// if growth type is either stunted or full, we need to tick
return state.getValue(GROWTH_TYPE) != GrowthType.DECORATION
}
public override fun isRandomlyTicking(state: BlockState): Boolean = state.getValue(SHOULD_GROW)
override fun randomTick(state: BlockState, level: ServerLevel, pos: BlockPos, random: RandomSource) {
/*
advanceGrowth(state, level, pos, random)
*/
}
fun advanceGrowth(state: BlockState, level: LevelAccessor, pos: BlockPos, random: RandomSource) {
@@ -121,8 +111,15 @@ class TypeGemClusterBlock(
return
}
// At STAGE_3
val isStunted = state.getValue(STUNTED)
val facing = state.getValue(FACING)
if (isStunted) {
level.setBlock(pos, state.setValue(SHOULD_GROW, false), UPDATE_ALL)
return
}
// Check neighboring gem blocks
val hasConflict = Direction.entries
.filter { it != facing.opposite }
@@ -135,36 +132,61 @@ class TypeGemClusterBlock(
if (hasConflict) {
level.setBlock(
pos,
state.setValue(GROWTH_TYPE, GrowthType.STUNTED),
state.setValue(STUNTED, true).setValue(SHOULD_GROW, false),
UPDATE_ALL
)
}
} else {
var nextState = nextStage.defaultBlockState()
if (nextState.hasProperty(FACING)) {
nextState = nextState.setValue(FACING, facing)
}
level.setBlock(pos, nextState, UPDATE_ALL)
if (isGem(nextState)) {
TypeGemCoreBlock.addClustersToBlock(state, pos, level, random)
}
attachDecorativeClusters(level, pos, random)
}
}
private fun isGem(state: BlockState): Boolean {
return state.`is`(CobblemonBlockTags.TYPE_GEM_BLOCKS)
}
private fun attachDecorativeClusters(level: LevelAccessor, gemPos: BlockPos, random: RandomSource) {
val openDirections = Direction.entries.filter { direction ->
level.getBlockState(gemPos.relative(direction)).isAir
}.toMutableList()
if (openDirections.isEmpty()) {
return
}
val minClusters = maxOf(1, (openDirections.size * 0.75f).toInt())
val clusterCount = random.nextInt(minClusters, openDirections.size + 1)
repeat(clusterCount) {
if (openDirections.isEmpty()) return@repeat
val index = random.nextInt(openDirections.size)
val direction = openDirections.removeAt(index)
val clusterPos = gemPos.relative(direction)
val stage = random.nextInt(0, 4)
val clusterState = defaultBlockState()
.setValue(FACING, direction)
.setValue(STAGE, stage)
.setValue(SHOULD_GROW, false) // so we can reduce lag... hopefully
.setValue(STUNTED, false)
level.setBlock(clusterPos, clusterState, UPDATE_ALL)
}
}
fun gemClusterCanGrow(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
if (state.getValue(GROWTH_TYPE) == GrowthType.DECORATION) return false
if (state.getValue(GROWTH_TYPE) == GrowthType.STUNTED
&& state.getValue(STAGE) == 3) return false
if (!state.getValue(SHOULD_GROW)) return false
val supportPos = pos.relative(state.getValue(FACING).opposite)
val supportBlock = level.getBlockState(supportPos).block
return CobblemonBlocks.typeGemBlocks().containsValue(supportBlock) || (supportBlock == CobblemonBlocks.TYPE_GEM_CORE) // Cannot grow if not on any gem blocks
}
override fun getDrops(state: BlockState, params: LootParams.Builder): List<ItemStack> {
val drops = super.getDrops(state, params)
@@ -178,7 +200,8 @@ class TypeGemClusterBlock(
DataComponents.BLOCK_STATE,
BlockItemStateProperties.EMPTY
.with(STAGE, state.getValue(STAGE))
.with(GROWTH_TYPE, GrowthType.DECORATION)
.with(SHOULD_GROW, false)
.with(STUNTED, false)
)
stack.set(
DataComponents.CUSTOM_MODEL_DATA,
@@ -249,7 +272,7 @@ class TypeGemClusterBlock(
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
super.createBlockStateDefinition(builder)
builder.add(FACING, STAGE, GROWTH_TYPE)
builder.add(FACING, SHOULD_GROW, STAGE, STUNTED)
}
override fun getCloneItemStack(level: LevelReader, pos: BlockPos, state: BlockState): ItemStack {
@@ -260,7 +283,8 @@ class TypeGemClusterBlock(
DataComponents.BLOCK_STATE,
BlockItemStateProperties.EMPTY
.with(STAGE, state.getValue(STAGE))
.with(GROWTH_TYPE, GrowthType.DECORATION)
.with(SHOULD_GROW, false)
.with(STUNTED, false)
)
stack.set(
DataComponents.CUSTOM_MODEL_DATA,
@@ -11,10 +11,8 @@ package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import com.cobblemon.mod.common.block.TypeGemClusterBlock.Companion.FACING
import com.cobblemon.mod.common.block.TypeGemClusterBlock.Companion.GROWTH_TYPE
import com.cobblemon.mod.common.block.TypeGemClusterBlock.Companion.STAGE
import com.cobblemon.mod.common.block.TypeGemClusterBlock.GrowthType
import com.cobblemon.mod.common.block.TypeGemClusterBlock.Companion.SHOULD_GROW
import com.cobblemon.mod.common.block.TypeGemClusterBlock.Companion.STUNTED
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.registries.BuiltInRegistries
@@ -23,8 +21,8 @@ import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.WorldGenLevel
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.DirectionalBlock
import net.minecraft.world.level.block.state.BlockState
import java.util.*
@@ -32,6 +30,13 @@ import java.util.*
class TypeGemCoreBlock(properties: Properties) : Block(properties) {
companion object {
const val MAX_CONNECTED_GEMS = 7
const val MIN_DISTANCE_BETWEEN_GEMS = 1
const val CONTINUATION_CHANCE = 0.8f
// Upper safety check to prevent infinite loops
const val MAX_FORCE_GROW_ATTEMPTS = 64
val BLOCK_TO_CLUSTER: Map<ResourceLocation, Block> by lazy {
mapOf(
ResourceLocation.parse("cobblemon:normal_gem_block") to CobblemonBlocks.TYPE_GEM_CLUSTER_NORMAL,
@@ -55,6 +60,28 @@ class TypeGemCoreBlock(properties: Properties) : Block(properties) {
)
}
fun setStuntState(level: LevelAccessor, gemBlocks: List<Pair<BlockState, BlockPos>>, stunted: Boolean) {
for ((_, gemPos) in gemBlocks) {
for (dir in Direction.entries) {
val neighborPos = gemPos.relative(dir)
val neighborState = level.getBlockState(neighborPos)
if (neighborState.block is TypeGemClusterBlock) {
if (neighborState.hasProperty(STUNTED) && neighborState.getValue(STUNTED) != stunted) {
var updatedState = neighborState.setValue(STUNTED, stunted)
if (updatedState.hasProperty(SHOULD_GROW)) {
updatedState = updatedState.setValue(SHOULD_GROW, !stunted)
}
level.setBlock(neighborPos, updatedState, 3)
}
}
}
}
}
fun getConnectedGemBlocks(level: BlockGetter, pos: BlockPos): List<Pair<BlockState, BlockPos>> {
val connectedGems = mutableListOf<Pair<BlockState, BlockPos>>()
val visited = mutableSetOf<BlockPos>()
@@ -80,159 +107,119 @@ class TypeGemCoreBlock(properties: Properties) : Block(properties) {
}
return connectedGems
}
fun addClustersToBlock(state: BlockState, pos: BlockPos, level: LevelAccessor, random: RandomSource) {
Direction.entries.forEach {
if (random.nextFloat() < Cobblemon.config.typeGemGrowthChance) {
val faceState = level.getBlockState(pos.relative(it))
val randomGrowthType = decideClusterGrowthType(level, pos)
val newState = BLOCK_TO_CLUSTER[BuiltInRegistries.BLOCK.getKey(state.block)]?.defaultBlockState()
?.setValue(DirectionalBlock.FACING, it)
?.setValue(GROWTH_TYPE, randomGrowthType)
?: return
if (faceState.`is`(Blocks.AIR)) {
level.setBlock(pos.relative(it), newState, 3)
}
}
}
}
private fun decideClusterGrowthType(level: LevelAccessor, pos: BlockPos, random: RandomSource = RandomSource.create()): GrowthType {
val growthTypes = if (getConnectedGemBlocks(level, pos).size > Cobblemon.config.typeGemMaxConnected) {
listOf(GrowthType.DECORATION, GrowthType.STUNTED)
} else {
GrowthType.entries
}
val totalChance = growthTypes.sumOf { it.chance.toDouble() }
var roll = random.nextDouble() * totalChance
for (type in growthTypes) {
roll -= type.chance.toDouble()
if (roll <= 0.0) {
return type
}
}
return GrowthType.DECORATION
}
}
private fun getConnectedClusters(level: LevelAccessor, pos: BlockPos): List<Pair<BlockState, BlockPos>> {
val gemBlocks = getConnectedGemBlocks(level, pos)
val clusters = mutableListOf<Pair<BlockState, BlockPos>>()
val visited = mutableSetOf<BlockPos>()
for ((gemState, gemPos) in gemBlocks) {
val expectedCluster = BLOCK_TO_CLUSTER[BuiltInRegistries.BLOCK.getKey(gemState.block)] ?: continue
for (direction in Direction.entries) {
val clusterPos = gemPos.relative(direction)
if (!visited.add(clusterPos)) continue
val clusterState = level.getBlockState(clusterPos)
if (clusterState.block == expectedCluster) {
clusters += clusterState to clusterPos
}
}
}
return clusters
}
override fun randomTick(state: BlockState, level: ServerLevel, pos: BlockPos, random: RandomSource) {
// should we tick at all?
if (random.nextFloat() >= Cobblemon.config.typeGemTickChance) {
return
}
val gemCount = getConnectedGemBlocks(level, pos).size
grow(level, pos, random)
}
// if growth has stopped fully but min growth hasn't been reached, randomly set a stopped bud to GROWTH_TYPE full
val clusters = getConnectedClusters(level, pos)
val fullClusterCount = clusters.count { (clusterState, _) ->
clusterState.getValue(GROWTH_TYPE) == GrowthType.FULL
fun forceGrow(level: WorldGenLevel, pos: BlockPos, random: RandomSource, percentage: Float) {
val desiredLength = MAX_CONNECTED_GEMS * percentage
repeat(MAX_FORCE_GROW_ATTEMPTS) {
val (grown, clusterSize) = grow(level, pos, random, true)
if (!grown || clusterSize >= desiredLength) return
}
if (fullClusterCount == 0) {
if (gemCount < Cobblemon.config.typeGemMinGrowth && clusters.isNotEmpty()) {
println("Force set a cluster to FULL at size $gemCount")
val randomCluster = clusters.random()
level.setBlockAndUpdate(randomCluster.second, randomCluster.first.setValue(GROWTH_TYPE, GrowthType.FULL))
Cobblemon.LOGGER.warn(
"Forced type gem growth at {} stopped after {} attempts without reaching its target size; keeping the partial formation.",
pos,
MAX_FORCE_GROW_ATTEMPTS
)
}
private fun grow(
level: WorldGenLevel,
pos: BlockPos,
random: RandomSource,
forced: Boolean = false
): Pair<Boolean, Int> {
val connectedGems = getConnectedGemBlocks(level, pos) // maybe we can store this and access it for better performance? check for changes in list rather than checking each time grow is called? idk
val gemCount = connectedGems.size
val overLimit = gemCount >= MAX_CONNECTED_GEMS
if (overLimit) {
// if there is no open air pockets to grow any new clusters then exit early
if (!hasBreathingRoom(level, connectedGems)) {
return Pair(false, gemCount)
}
// max type gem block count met, tell clusters to grow but not turn into blocks
setStuntState(level, connectedGems, true)
//return Pair(false, gemCount)
} else {
// make sure connected clusters know they can grow and turn into blocks
setStuntState(level, connectedGems, false)
}
val javaRandom = java.util.Random(random.nextLong())
for ((gemState, gemPos) in connectedGems.shuffled(javaRandom)) {
val registryKey = BuiltInRegistries.BLOCK.getKey(gemState.block)
val clusterBlock = BLOCK_TO_CLUSTER[registryKey] ?: continue // If there is no block to grow, skip the block
for (dir in Direction.entries.shuffled(javaRandom)) {
val targetPos = gemPos.relative(dir)
val targetState = level.getBlockState(targetPos)
if (!targetState.isAir) continue
if (!isPositionValidForGrowth(level, targetPos)) continue
// Place gem cluster
val placeState = clusterBlock.defaultBlockState()
.setValue(DirectionalBlock.FACING, dir)
.setValue(TypeGemClusterBlock.STAGE, 0)
.setValue(SHOULD_GROW, true)
level.setBlock(targetPos, placeState, UPDATE_ALL)
// During world gen, the region silently discards writes into neighboring chunks that are already fully generated.
// Through ImposterProtoChunk with writes set to disabled. Checks if the block is still air after the write and skips if so as it can't be filled.
// Preventing infinite loops.
if (!level.getBlockState(targetPos).`is`(clusterBlock)) continue
if (forced) {
forceAdvanceClusterGrowth(level, targetPos, random)
}
val updatedGemCount = if (forced) getConnectedGemBlocks(level, pos).size else connectedGems.size
return Pair(true, updatedGemCount)
}
}
expand(level, pos, random)
// println("[TypeGemCoreBlock] No valid spot found for gem placement.")
return Pair(false, connectedGems.size)
}
fun expand(level: LevelAccessor, pos: BlockPos, random: RandomSource, forceGrow: Boolean = false) {
getConnectedGemBlocks(level, pos).forEach {
val state = it.first
val pos = it.second
addClustersToBlock(state, pos, level, random)
growClusters(pos, level, random, forceGrow)
private fun forceAdvanceClusterGrowth(level: WorldGenLevel, clusterPos: BlockPos, random: RandomSource) {
repeat(5) {
val clusterState = level.getBlockState(clusterPos)
val clusterBlock = clusterState.block as? TypeGemClusterBlock ?: return
clusterBlock.advanceGrowth(clusterState, level, clusterPos, random)
}
}
private fun growClusters(pos: BlockPos, level: LevelAccessor, random: RandomSource, forceGrow: Boolean) {
Direction.entries.forEach { direction ->
val relativePos = pos.relative(direction)
val relativeState = level.getBlockState(relativePos)
val clusterBlock = relativeState.block as? TypeGemClusterBlock ?: return@forEach
if ((relativeState.getValue(DirectionalBlock.FACING) == direction &&
random.nextFloat() < Cobblemon.config.typeGemGrowthChance) || forceGrow
) {
clusterBlock.advanceGrowth(relativeState, level, relativePos, random)
private fun hasBreathingRoom(level: WorldGenLevel, gems: List<Pair<BlockState, BlockPos>>): Boolean {
for ((_, gemPos) in gems) {
for (dir in Direction.entries) {
if (level.getBlockState(gemPos.relative(dir)).isAir) {
return true
}
}
}
return false
}
private fun addInitialClusters(pos: BlockPos, level: ServerLevel, clusterType: TypeGemClusterBlock) {
Direction.entries.forEach {
val faceState = level.getBlockState(pos.relative(it))
if (faceState.`is`(Blocks.AIR)) {
level.setBlockAndUpdate(pos.relative(it), clusterType.defaultBlockState().setValue(
DirectionalBlock.FACING, it)
.setValue(GROWTH_TYPE, GrowthType.FULL)
)
}
private fun isPositionValidForGrowth(level: WorldGenLevel, pos: BlockPos): Boolean {
return Direction.entries.none { dir ->
val nearby = pos.relative(dir)
isGem(level.getBlockState(nearby)) &&
pos.distManhattan(nearby) < MIN_DISTANCE_BETWEEN_GEMS
}
}
private fun isGem(state: BlockState): Boolean {
return state.`is`(CobblemonBlockTags.TYPE_GEM_BLOCKS)
}
override fun isRandomlyTicking(state: BlockState): Boolean = true
private fun attachDecorativeClusters(level: LevelAccessor, gemPos: BlockPos, random: RandomSource) {
val openDirections = Direction.entries.filter { direction ->
level.getBlockState(gemPos.relative(direction)).isAir
}.toMutableList()
if (openDirections.isEmpty()) {
return
}
val gemState = level.getBlockState(gemPos)
val clusterBlock = BLOCK_TO_CLUSTER[BuiltInRegistries.BLOCK.getKey(gemState.block)] ?: return
val minClusters = maxOf(1, (openDirections.size * 0.75f).toInt())
val clusterCount = random.nextInt(minClusters, openDirections.size + 1)
repeat(clusterCount) {
if (openDirections.isEmpty()) return@repeat
val index = random.nextInt(openDirections.size)
val direction = openDirections.removeAt(index)
val clusterPos = gemPos.relative(direction)
val stage = random.nextInt(0, 4)
val clusterState = clusterBlock.defaultBlockState()
.setValue(FACING, direction)
.setValue(STAGE, stage)
.setValue(GROWTH_TYPE, GrowthType.DECORATION)
level.setBlock(clusterPos, clusterState, UPDATE_ALL)
}
}
}
}
@@ -171,7 +171,11 @@ open class PokeSnackBlockEntity(pos: BlockPos, state: BlockState) :
fun attemptSpawn(player: Player) {
val cause = SpawnCause(spawner = spawner, entity = player)
val zoneInput = spawner.getZoneInput(cause)
val spawnAction = spawner.calculateSpawnActionsForArea(zoneInput, 1).firstOrNull() ?: return
val spawnAction = spawner.calculateSpawnActionsForArea(zoneInput, 1).firstOrNull()
if (spawnAction == null) {
return
}
CobblemonEvents.POKE_SNACK_SPAWN_POKEMON_PRE.postThen(
PokeSnackSpawnPokemonEvent.Pre(this, spawnAction),
@@ -334,6 +334,8 @@ class TMMachineBlockEntity(pos: BlockPos, state: BlockState) :
level?.updateNeighborsAt(blockPos, currentState.block)
}
override fun stillValid(player: Player): Boolean = tmMachineInventory.stillValid(player)
override fun canPlaceItem(slot: Int, stack: ItemStack): Boolean = tmMachineInventory.canPlaceItem(slot, stack)
override fun canTakeItem(target: Container, slot: Int, stack: ItemStack): Boolean =
@@ -475,7 +475,7 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
val locators = particleData.locatorResolver(locatorStates).filter(::isLocatorVisible)
if (particleData.chance > random.nextDouble()) {
locators.forEach { locator ->
repeat(particleData.amount()) {
repeat(particleData.amount) {
runtime.resolve("q.particle('${particleData.particle}', '$locator')".asExpressionLike())
}
}
@@ -485,7 +485,7 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
is ParticleData.MinecraftParticle -> {
if (particleData.chance > random.nextDouble()) {
val box = currentEntity.boundingBox
repeat(particleData.amount()) {
repeat(particleData.amount) {
val x = box.minX + (box.maxX - box.minX) * random.nextDouble()
val y = box.minY + (box.maxY - box.minY) * random.nextDouble()
val z = box.minZ + (box.maxZ - box.minZ) * random.nextDouble()
@@ -17,7 +17,6 @@ import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.adapters.IntRangesAdapter
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.lang
import java.awt.Color
import net.minecraft.client.Minecraft
@@ -329,7 +328,7 @@ class HabitatSpawnEditorGUI(
private fun parseSpecies(value: String): Species? {
val lowered = value.lowercase()
pokemonSpeciesByTranslatedName[lowered]?.let { return it }
return runCatching { PokemonSpecies.getByIdentifier(lowered.asIdentifierDefaultingNamespace()) }.getOrNull()
return runCatching { PokemonSpecies.getByName(lowered) }.getOrNull()
}
private fun parsePhases(raw: String): IntRanges? {
@@ -29,8 +29,7 @@ class BoxStorageSlot(
val grabbedSlot = parent.grabbedSlot
return if (grabbedSlot == null) {
val pokemon = getPokemon()
pokemon == null || !parent.isInMultiSelection(pokemon, position)
true
} else {
grabbedSlot.getPokemon() != getPokemon()
}
@@ -50,15 +50,13 @@ import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvent
import kotlin.isInitialized
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.FastColor
class PCGUI(
val pc: ClientPC,
val party: ClientParty,
val configuration: PCGUIConfiguration,
val openOnBox: Int = CobblemonClient.lastPcBoxViewed,
val unseenWallpapers: MutableSet<ResourceLocation> = mutableSetOf(),
val silent: Boolean = false
val unseenWallpapers: MutableSet<ResourceLocation> = mutableSetOf()
) : Screen(Component.translatable("cobblemon.ui.pc.title")), CobblemonRenderable {
companion object {
@@ -130,55 +128,6 @@ class PCGUI(
var selectPointerOffsetIncrement = false
var displayOptions = false
private var dragStartX: Int? = null
private var dragStartY: Int? = null
private var dragEndX: Int? = null
private var dragEndY: Int? = null
private var selectionDragActive = false
data class SelectionArea(val left: Int, val top: Int, val right: Int, val bottom: Int)
private data class BoxGridBounds(val left: Int, val top: Int, val right: Int, val bottom: Int) {
fun contains(x: Int, y: Int) = x in left..< right && y >= top && y < bottom
}
private fun getBoxGridBounds(): BoxGridBounds {
val left = storageWidget.x
val top = storageWidget.y
return BoxGridBounds(
left,
top,
left + StorageWidget.SCREEN_WIDTH,
top + StorageWidget.SCREEN_HEIGHT
)
}
fun getSelectionArea(): SelectionArea? {
if (!selectionDragActive) return null
val startX = dragStartX ?: return null
val startY = dragStartY ?: return null
val endX = dragEndX ?: return null
val endY = dragEndY ?: return null
val bounds = getBoxGridBounds()
if (!bounds.contains(startX, startY)) return null
val columns = 6
val rows = 5
val slotPitch = StorageSlot.SIZE + StorageWidget.BOX_SLOT_PADDING
val gridLeft = bounds.left + StorageWidget.BOX_SLOT_START_OFFSET_X
val gridTop = bounds.top + StorageWidget.BOX_SLOT_START_OFFSET_Y + if (displayOptions) 5 else 0
val startColumn = ((startX - gridLeft) / slotPitch).coerceIn(0, columns - 1)
val endColumn = (((endX.coerceIn(bounds.left, bounds.right - 1)) - gridLeft) / slotPitch).coerceIn(0, columns - 1)
val startRow = ((startY - gridTop) / slotPitch).coerceIn(0, rows - 1)
val endRow = (((endY.coerceIn(bounds.top, bounds.bottom - 1)) - gridTop) / slotPitch).coerceIn(0, rows - 1)
return SelectionArea(
gridLeft + minOf(startColumn, endColumn) * slotPitch, // left
gridTop + minOf(startRow, endRow) * slotPitch, // top
gridLeft + (maxOf(startColumn, endColumn) * slotPitch) + StorageSlot.SIZE, // right
gridTop + (maxOf(startRow, endRow) * slotPitch) + StorageSlot.SIZE // bottom
)
}
override fun renderBlurredBackground(delta: Float) {}
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -300,7 +249,7 @@ class PCGUI(
configuration.showParty = isVisible
wallpaperWidget.visible = !isVisible
(it as IconButton).highlighted = !isVisible
if (unseenWallpapers.isNotEmpty()) MarkPCBoxWallpapersSeenPacket(unseenWallpapers).sendToServer()
if (!unseenWallpapers.isEmpty()) MarkPCBoxWallpapersSeenPacket(unseenWallpapers).sendToServer()
}.also {
it.visible = displayOptions
optionButtons.add(it)
@@ -365,20 +314,6 @@ class PCGUI(
super.init()
}
private fun renderSelectionBox(context: GuiGraphics, area: SelectionArea) {
context.pose().pushPose()
context.pose().translate(0.0, 0.0, 1500.0)
val borderColor = FastColor.ARGB32.color(255, 205, 212, 255)
val bgColor = FastColor.ARGB32.color(75, 133, 183, 230)
val thickness = 1
context.fill(area.left, area.top, area.right, area.bottom, bgColor)
context.fill(area.left, area.top, area.right, area.top + thickness, borderColor)
context.fill(area.left, area.bottom - thickness, area.right, area.bottom, borderColor)
context.fill(area.left, area.top, area.left + thickness, area.bottom, borderColor)
context.fill(area.right - thickness, area.top, area.right, area.bottom, borderColor)
context.pose().popPose()
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val matrices = context.pose()
renderBackground(context, mouseX, mouseY, delta)
@@ -749,14 +684,12 @@ class PCGUI(
scale = SCALE
)
if (optionButtons.isNotEmpty() && displayOptions) {
if (!optionButtons.isEmpty() && displayOptions) {
for (button in optionButtons) button.showAlt = hasShiftDown()
}
super.render(context, mouseX, mouseY, delta)
getSelectionArea()?.let { renderSelectionBox(context, it) }
// Item Tooltip
if (pokemon != null) {
val displayedItem = if (showCosmeticItem) pokemon.cosmeticItem else pokemon.heldItemNoCopy()
@@ -770,7 +703,7 @@ class PCGUI(
}
fun closeNormally(unlink: Boolean = true) {
if (!silent) playSound(CobblemonSounds.PC_OFF)
playSound(CobblemonSounds.PC_OFF)
Minecraft.getInstance().setScreen(null)
if (unlink) {
UnlinkPlayerFromPCPacket().sendToServer()
@@ -778,20 +711,6 @@ class PCGUI(
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
storageWidget.clearPendingStorageSlotClick()
selectionDragActive = false
val clickX = mouseX.toInt()
val clickY = mouseY.toInt()
if (storageWidget.multiSelection == null && button == 0 &&
getBoxGridBounds().contains(clickX, clickY) &&
storageWidget.pastureWidget == null) {
dragStartX = clickX
dragStartY = clickY
} else {
dragStartX = null
dragStartY = null
}
// Trigger child component function as they will need to check the entire screen click area
if (::boxNameWidget.isInitialized) boxNameWidget.mouseClicked(mouseX, mouseY, button)
return super.mouseClicked(mouseX, mouseY, button)
@@ -820,50 +739,16 @@ class PCGUI(
storageWidget.pastureWidget?.let { pasture ->
if (pasture.pastureScrollList.isHovered) pasture.pastureScrollList.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
if (button == 0 && storageWidget.pastureWidget == null) {
val startX = dragStartX
val startY = dragStartY
if (startX != null && startY != null) {
val offsetX = mouseX - startX
val offsetY = mouseY - startY
if (offsetX * offsetX + offsetY * offsetY >= 25.0) {
selectionDragActive = true
storageWidget.clearPendingStorageSlotClick()
dragEndX = mouseX.toInt()
dragEndY = mouseY.toInt()
}
}
}
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
val handled = super.mouseReleased(mouseX, mouseY, button)
if (button == 0) {
if (selectionDragActive) {
dragEndX = mouseX.toInt()
dragEndY = mouseY.toInt()
storageWidget.updateSelection()
storageWidget.clearPendingStorageSlotClick()
} else {
storageWidget.completePendingStorageSlotClick(mouseX, mouseY)
}
}
dragStartX = null
dragStartY = null
dragEndX = null
dragEndY = null
selectionDragActive = false
return handled
}
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
val boxNameFocused = this::boxNameWidget.isInitialized && boxNameWidget.isFocused
val filterFocused = this::filterWidget.isInitialized && filterWidget.isFocused
if (isInventoryKeyPressed(minecraft, keyCode, scanCode) && !boxNameFocused && !filterFocused) {
saveMarkings(isPreviewInParty ?: false)
if (!silent) playSound(CobblemonSounds.PC_OFF)
playSound(CobblemonSounds.PC_OFF)
UnlinkPlayerFromPCPacket().sendToServer()
Minecraft.getInstance().setScreen(null)
return true
@@ -874,7 +759,7 @@ class PCGUI(
if (!filterFocused && !boxNameFocused) {
when (keyCode) {
InputConstants.KEY_ESCAPE -> {
if (!silent) playSound(CobblemonSounds.PC_OFF)
playSound(CobblemonSounds.PC_OFF)
UnlinkPlayerFromPCPacket().sendToServer()
onClose()
return true
@@ -946,4 +831,4 @@ class PCGUI(
markingsWidget.setActivePokemon(null)
}
}
}
}
@@ -1,37 +0,0 @@
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.pokemon.Pokemon
data class PCMultiSelection(
private val grid: MutableList<MutableList<Pokemon?>> = mutableListOf(),
val sourceBox: Int = 0,
val sourceColumn: Int = 0,
val sourceRow: Int = 0
) {
fun getHorizontalSize(): Int = grid.firstOrNull()?.size ?: 0
fun getVerticalSize(): Int = grid.size
constructor(horizontalSize: Int, verticalSize: Int, sourceBox: Int, sourceColumn: Int, sourceRow: Int) : this(
MutableList(verticalSize) { MutableList<Pokemon?>(horizontalSize) { null } },
sourceBox,
sourceColumn,
sourceRow
)
fun getPokemon(x: Int, y: Int): Pokemon? {
return grid[y][x]
}
fun setPokemon(x: Int, y: Int, pokemon: Pokemon?) {
grid[y][x] = pokemon
}
fun pokemonEntries(): List<Triple<Int, Int, Pokemon>> {
return grid.flatMapIndexed { y, row ->
row.mapIndexedNotNull { x, pokemon -> pokemon?.let { Triple(x, y, it) } }
}
}
fun getPokemonCount(): Int = pokemonEntries().size
}
@@ -45,13 +45,11 @@ class ReleaseButton(
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
val pokemonCount = parent.multiSelection?.getPokemonCount() ?: 0
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = if (pokemonCount > 1)
{ lang("ui.pc.release_all").bold() } else { lang("ui.pc.release").bold() },
text = lang("ui.pc.release").bold(),
x = x + (WIDTH / 2),
y = y + 3.5,
centered = true,
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.gui.ColourLibrary
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
@@ -55,17 +54,9 @@ open class StorageSlot(
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val selectionArea = parent.pcGui.getSelectionArea()
if (selectionArea != null && parent.getSelectedPokemon(selectionArea).contains(this)) {
val highlightColor = 0x40FFFFFF
context.fill(x + 1, y, x + (SIZE - 1), y + SIZE, highlightColor)
context.fill(x, y + 1, x + 1, y + (SIZE - 1), highlightColor)
context.fill(x + SIZE - 1, y + 1, x + SIZE, y + (SIZE - 1), highlightColor)
}
if (shouldRender()) {
renderSlot(context, x, y, delta)
}
}
fun renderSlot(context: GuiGraphics, posX: Int, posY: Int, partialTicks: Float) {
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.gui.pc
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.storage.StorePosition
import com.cobblemon.mod.common.api.storage.party.PartyPosition
import com.cobblemon.mod.common.api.storage.pc.PCPosition
import com.cobblemon.mod.common.api.text.bold
@@ -38,9 +39,8 @@ import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import java.util.UUID
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonBatchPacket.PCPokemonMove
class StorageWidget(
pX: Int, pY: Int,
@@ -77,13 +77,9 @@ class StorageWidget(
var displayConfirmRelease = false
var screenLoaded = false
var selectedPosition: PartyPosition? = null
var selectedPosition: StorePosition? = null
var grabbedSlot: GrabbedStorageSlot? = null
var multiSelection: PCMultiSelection? = null
private val multiGrabbedSlots = mutableMapOf<UUID, GrabbedStorageSlot>()
private var pendingStorageSlotClick: Button? = null
var box = 0
set(value) {
field =
@@ -117,22 +113,16 @@ class StorageWidget(
subKey = "ui.generic.yes",
onPress = {
if (canDeleteSelected() && displayConfirmRelease) {
val selection = multiSelection
if (selection != null) {
selection.pokemonEntries().forEach { (column, row, pokemon) ->
val position = PCPosition(
selection.sourceBox,
(selection.sourceRow + row) * 6 + selection.sourceColumn + column
)
CobblemonNetwork.sendToServer(ReleasePCPokemonPacket(pokemon.uuid, position))
}
} else {
val position = selectedPosition ?: return@ReleaseConfirmButton
val pokemon = getSelectedPokemon() ?: return@ReleaseConfirmButton
val position = selectedPosition ?: return@ReleaseConfirmButton
val pokemon = getSelectedPokemon() ?: return@ReleaseConfirmButton
CobblemonNetwork.sendToServer(ReleasePartyPokemonPacket(pokemon.uuid, position))
val packet = when (position) {
is PartyPosition -> ReleasePartyPokemonPacket(pokemon.uuid, position)
is PCPosition -> ReleasePCPokemonPacket(pokemon.uuid, position)
else -> return@ReleaseConfirmButton
}
CobblemonNetwork.sendToServer(packet)
playSound(CobblemonSounds.PC_RELEASE)
resetSelected()
displayConfirmRelease = false
@@ -159,8 +149,9 @@ class StorageWidget(
}
fun canDeleteSelected(): Boolean {
multiSelection?.let { return it.pokemonEntries().isNotEmpty() }
return party.filterNotNull().size > 1 && selectedPosition != null && grabbedSlot != null
return !(selectedPosition is PartyPosition && party.filterNotNull().size <= 1)
&& selectedPosition != null
&& grabbedSlot != null
}
fun setupStorageSlots() {
@@ -179,7 +170,7 @@ class StorageWidget(
parent = this,
pc = pc,
position = PCPosition(box, index),
onPress = { pendingStorageSlotClick = it }
onPress = { this.onStorageSlotClicked(it) }
).also { widget ->
this.addWidget(widget)
this.boxSlots.add(widget)
@@ -209,7 +200,7 @@ class StorageWidget(
parent = this,
party = party,
position = PartyPosition(partyIndex),
onPress = { pendingStorageSlotClick = it }
onPress = { this.onStorageSlotClicked(it) }
).also { widget ->
this.addWidget(widget)
this.partySlots.add(widget)
@@ -219,115 +210,13 @@ class StorageWidget(
private fun getSelectedPokemon(): Pokemon? {
val selectedPosition = this.selectedPosition ?: return null
return party.get(selectedPosition)
}
fun getSelectedPokemon(area: PCGUI.SelectionArea): List<BoxStorageSlot> {
return boxSlots.filter { slot ->
slot.x < area.right && slot.x + StorageSlot.SIZE > area.left &&
slot.y < area.bottom && slot.y + StorageSlot.SIZE > area.top &&
slot.getPokemon()?.let(pcGui.search::passes) == true
return when (selectedPosition) {
is PCPosition -> pc.get(selectedPosition)
is PartyPosition -> party.get(selectedPosition)
else -> null
}
}
fun clearPendingStorageSlotClick() {
pendingStorageSlotClick = null
}
fun completePendingStorageSlotClick(mouseX: Double, mouseY: Double) {
val button = pendingStorageSlotClick ?: return
pendingStorageSlotClick = null
if (button.isMouseOver(mouseX, mouseY)) {
onStorageSlotClicked(button)
}
}
fun updateSelection() {
val area = pcGui.getSelectionArea()
if (area == null) {
multiSelection = null
return
}
selectedPosition = null
grabbedSlot = null
pcGui.setPreviewPokemon(null)
val selectedSlots = getSelectedPokemon(area)
if (selectedSlots.isEmpty()) {
multiSelection = null
return
}
val minColumn = selectedSlots.minOf { it.position.slot % 6 }
val maxColumn = selectedSlots.maxOf { it.position.slot % 6 }
val minRow = selectedSlots.minOf { it.position.slot / 6 }
val maxRow = selectedSlots.maxOf { it.position.slot / 6 }
val selection = PCMultiSelection(maxColumn - minColumn + 1, maxRow - minRow + 1, box, minColumn, minRow)
selectedSlots.forEach { slot ->
val pokemon = slot.getPokemon() ?: return@forEach
val slotIndex = slot.position.slot
val column = slotIndex % 6
val row = slotIndex / 6
if (column in minColumn..maxColumn && row in minRow..maxRow) {
selection.setPokemon(column - minColumn, row - minRow, pokemon)
}
}
playSound(CobblemonSounds.PC_GRAB)
multiSelection = selection
}
private fun moveMultiSelectionTo(destination: PCPosition): Boolean {
val selection = multiSelection ?: return false
val width = selection.getHorizontalSize()
val height = selection.getVerticalSize()
val destinationColumn = destination.slot % 6
val destinationRow = destination.slot / 6
if (destinationColumn + width > 6 || destinationRow + height > 5) return false
val moves = mutableListOf<PCPokemonMove>()
val selectedSources = mutableSetOf<PCPosition>()
for (row in 0 until height) {
for (column in 0 until width) {
val pokemon = selection.getPokemon(column, row) ?: continue
val source = PCPosition(selection.sourceBox, (selection.sourceRow + row) * 6 + selection.sourceColumn + column)
val target = PCPosition(destination.box, (destinationRow + row) * 6 + destinationColumn + column)
selectedSources.add(source)
moves.add(PCPokemonMove(pokemon.uuid, source, target))
}
}
if (moves.isEmpty()) return false
for (row in 0 until height) {
for (column in 0 until width) {
if (selection.getPokemon(column, row) == null) continue
val target = PCPosition(destination.box, (destinationRow + row) * 6 + destinationColumn + column)
val displacedPokemon = pc.get(target) ?: continue
if (target in selectedSources) continue
val displacedSource = PCPosition(
selection.sourceBox,
(selection.sourceRow + row) * 6 + selection.sourceColumn + column
)
if (pc.get(displacedSource) != null && displacedSource !in selectedSources) return false
moves.add(PCPokemonMove(displacedPokemon.uuid, target, displacedSource))
}
}
if (moves.map { it.newPosition }.toSet().size != moves.size) return false
MovePCPokemonBatchPacket(moves).sendToServer()
return true
}
fun isInMultiSelection(pokemon: Pokemon, position: PCPosition): Boolean {
val selection = multiSelection ?: return false
return selection.sourceBox == position.box && selection.pokemonEntries().any { it.third == pokemon }
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val matrices = context.pose()
// Party Label
@@ -351,14 +240,11 @@ class StorageWidget(
shadow = true
)
val pokemonCount = multiSelection?.getPokemonCount() ?: 0
if (canDeleteSelected() && displayConfirmRelease) {
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = if (pokemonCount > 1)
{ lang("ui.pc.release_all").bold() } else { lang("ui.pc.release").bold() },
text = lang("ui.pc.release").bold(),
x = x + 223,
y = y + 119,
centered = true
@@ -422,7 +308,6 @@ class StorageWidget(
slot.render(context, mouseX, mouseY, delta)
val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
&& multiSelection == null
&& pokemon != null && pokemon != pcGui.previewPokemon
) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: false)
@@ -439,7 +324,6 @@ class StorageWidget(
slot.render(context, mouseX, mouseY, delta)
val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
&& multiSelection == null
&& pokemon != null && pokemon != pcGui.previewPokemon
) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: true)
@@ -451,31 +335,10 @@ class StorageWidget(
pastureWidget?.render(context, mouseX, mouseY, delta)
matrices.pushPose()
matrices.translate(0.0, 0.0, 1000.0)
renderSelectedPokemon(context, mouseX, mouseY, delta)
grabbedSlot?.render(context, mouseX, mouseY, delta)
matrices.popPose()
}
private fun renderSelectedPokemon(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val selection = multiSelection
if (selection == null) {
grabbedSlot?.render(context, mouseX, mouseY, delta)
return
}
val slotOffset = StorageSlot.SIZE + BOX_SLOT_PADDING
selection.pokemonEntries().forEach { (column, row, pokemon) ->
val grabbed = multiGrabbedSlots.getOrPut(pokemon.uuid) {
GrabbedStorageSlot(0, 0, this, pokemon)
}
grabbed.render(
context,
mouseX + column * slotOffset,
mouseY + row * slotOffset,
delta
)
}
}
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (displayConfirmRelease) {
if (releaseYesButton.isHovered(pMouseX, pMouseY)) releaseYesButton.mouseClicked(pMouseX, pMouseY, pButton)
@@ -508,55 +371,23 @@ class StorageWidget(
else -> return
}
val clickedPokemon = when (clickedPosition) {
is PCPosition -> pc.get(clickedPosition)
is PartyPosition -> party.get(clickedPosition)
else -> null
}
multiSelection?.let { selection ->
when (clickedPosition) {
is PCPosition -> {
if (clickedPosition.box == selection.sourceBox &&
clickedPosition.slot == selection.sourceRow * 6 + selection.sourceColumn
) {
resetSelected()
} else if (moveMultiSelectionTo(clickedPosition)) {
playSound(CobblemonSounds.PC_DROP)
resetSelected()
}
}
is PartyPosition -> {
val selectedPokemon = selection.pokemonEntries().singleOrNull() ?: return
val (column, row, pokemon) = selectedPokemon
val source = PCPosition(
selection.sourceBox,
(selection.sourceRow + row) * 6 + selection.sourceColumn + column
)
if (pc.get(source) !== pokemon) return
val packet = clickedPokemon?.let {
SwapPCPartyPokemonPacket(it.uuid, clickedPosition, pokemon.uuid, source)
} ?: MovePCPokemonToPartyPacket(pokemon.uuid, source, clickedPosition)
packet.sendToServer()
playSound(CobblemonSounds.PC_DROP)
resetSelected()
}
else -> Unit
}
return
}
// Reset release confirmation
displayConfirmRelease = false
// Clicking on itself, so unselect position
if (selectedPosition == clickedPosition) {
if (this.selectedPosition != null && this.selectedPosition == clickedPosition) {
if (grabbedSlot != null) playSound(CobblemonSounds.PC_DROP)
resetSelected()
return
}
// Check for selecting a Pokémon
val clickedPokemon = when(button) {
is BoxStorageSlot -> pc.get(clickedPosition as PCPosition)
is PartyStorageSlot -> party.get(clickedPosition as PartyPosition)
else -> null
}
val selectOverride = pcGui.configuration.selectOverride
if (selectOverride != null) {
selectOverride(pcGui, clickedPosition, clickedPokemon)
@@ -589,44 +420,56 @@ class StorageWidget(
}
}
if (clickedPosition is PCPosition) {
val column = clickedPosition.slot % 6
val row = clickedPosition.slot / 6
multiSelection = PCMultiSelection(1, 1, clickedPosition.box, column, row).also {
it.setPokemon(0, 0, clickedPokemon)
}
playSound(CobblemonSounds.PC_GRAB)
return
} else if (clickedPosition is PartyPosition) {
this.selectedPosition = clickedPosition
this.pcGui.setPreviewPokemon(clickedPokemon, pcGui.isPreviewInParty ?: false)
grabbedSlot = GrabbedStorageSlot(
x = button.x,
y = button.y,
parent = this,
pokemon = clickedPokemon
)
playSound(CobblemonSounds.PC_GRAB)
}
this.selectedPosition = clickedPosition
this.pcGui.setPreviewPokemon(clickedPokemon, pcGui.isPreviewInParty ?: false)
grabbedSlot = GrabbedStorageSlot(
x = button.x,
y = button.y,
parent = this,
pokemon = clickedPokemon
)
playSound(CobblemonSounds.PC_GRAB)
}
} else {
val sourcePosition = selectedPosition ?: return
val selectedPokemon = party.get(sourcePosition) ?: return
// Handle movement within the Box
val selectedPokemon = when(this.selectedPosition) {
is PCPosition -> pc.get(this.selectedPosition as PCPosition)
is PartyPosition -> party.get(this.selectedPosition as PartyPosition)
else -> null
} ?: return
// Box to Box
if (this.selectedPosition is PCPosition && clickedPosition is PCPosition) {
val packet = clickedPokemon?.let { SwapPCPokemonPacket(it.uuid, clickedPosition, selectedPokemon.uuid, this.selectedPosition as PCPosition) }
?: MovePCPokemonPacket(selectedPokemon.uuid, selectedPosition as PCPosition, clickedPosition)
packet.sendToServer()
playSound(CobblemonSounds.PC_DROP)
resetSelected()
}
// Box to Party
else if (this.selectedPosition is PCPosition && clickedPosition is PartyPosition) {
val packet = clickedPokemon?.let { SwapPCPartyPokemonPacket(clickedPokemon.uuid, clickedPosition, selectedPokemon.uuid, this.selectedPosition as PCPosition) }
?: MovePCPokemonToPartyPacket(selectedPokemon.uuid, this.selectedPosition as PCPosition, clickedPosition)
packet.sendToServer()
playSound(CobblemonSounds.PC_DROP)
resetSelected()
}
// Party to Box
if (clickedPosition is PCPosition) {
else if (this.selectedPosition is PartyPosition && clickedPosition is PCPosition) {
if (ServerSettings.preventCompletePartyDeposit && this.party.filterNotNull().size == 1 && clickedPokemon == null) {
return
}
val packet = clickedPokemon?.let { SwapPCPartyPokemonPacket(selectedPokemon.uuid, sourcePosition, clickedPokemon.uuid, clickedPosition) }
?: MovePartyPokemonToPCPacket(selectedPokemon.uuid, sourcePosition, clickedPosition)
val packet = clickedPokemon?.let { SwapPCPartyPokemonPacket(selectedPokemon.uuid, this.selectedPosition as PartyPosition, clickedPokemon.uuid, clickedPosition) }
?: MovePartyPokemonToPCPacket(selectedPokemon.uuid, this.selectedPosition as PartyPosition, clickedPosition)
packet.sendToServer()
playSound(CobblemonSounds.PC_DROP)
resetSelected()
}
// Party to Party
else if (clickedPosition is PartyPosition) {
val packet = clickedPokemon?.let { SwapPartyPokemonPacket(it.uuid, clickedPosition, selectedPokemon.uuid, sourcePosition) }
?: MovePartyPokemonPacket(selectedPokemon.uuid, sourcePosition, clickedPosition)
else if (this.selectedPosition is PartyPosition && clickedPosition is PartyPosition) {
val packet = clickedPokemon?.let { SwapPartyPokemonPacket(it.uuid, clickedPosition, selectedPokemon.uuid, this.selectedPosition as PartyPosition) }
?: MovePartyPokemonPacket(selectedPokemon.uuid, selectedPosition as PartyPosition, clickedPosition)
packet.sendToServer()
playSound(CobblemonSounds.PC_DROP)
resetSelected()
@@ -637,8 +480,6 @@ class StorageWidget(
fun resetSelected() {
selectedPosition = null
grabbedSlot = null
multiSelection = null
multiGrabbedSlots.clear()
pcGui.setPreviewPokemon(null)
}
}
@@ -38,6 +38,7 @@ import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_HEIG
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HEADER_BAR_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.ICONS_ARROW_PADDING_AROUND
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.ICONS_PADDING_AROUND
@@ -51,7 +52,8 @@ 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.gui.pokedex.widgets.EvolutionTreeWidget
import com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree.EvolutionTreeWidget
import com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree.debugEvolutionRequirementRendering
import com.cobblemon.mod.common.client.pokedex.PokedexType
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
@@ -192,7 +194,6 @@ class PokedexGUI private constructor(
public override fun init() {
super.init()
clearWidgets()
evolutionTreeExpanded = false
availableRegions = Dexes.dexEntryMap.keys.toList()
selectedRegionIndex = 0
@@ -827,6 +828,10 @@ class PokedexGUI private constructor(
}
}
if (tabInfoIndex == TAB_EVOLUTION_TREE && keyCode == InputConstants.KEY_SEMICOLON) {
debugEvolutionRequirementRendering()
}
return super.keyPressed(keyCode, scanCode, modifiers)
}
@@ -16,17 +16,13 @@ import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.chat.Style
import net.minecraft.resources.ResourceLocation
import java.util.Optional
private val tooltipEdge = cobblemonResource("textures/gui/pokedex/tooltip_edge.png")
private val tooltipBackground = cobblemonResource("textures/gui/pokedex/tooltip_background.png")
private const val TOOLTIP_SLICE_SIZE = 11
private const val TOOLTIP_MAX_TEXT_WIDTH = 160
fun renderTooltip(
context: GuiGraphics,
@@ -53,12 +49,7 @@ fun renderTooltip(
posY: Int,
delta: Float,
offsetY: Int = 0,
maxTextWidth: Int = TOOLTIP_MAX_TEXT_WIDTH,
) {
val text = text.flatMap { wrapTooltipLine(it, maxTextWidth) }
// Only a tooltip that fits on a single line is centered; wrapped text is left-aligned
val centered = text.size == 1
val poseStack = context.pose()
val textWidth = text.maxOf { Minecraft.getInstance().font.width(it.font(CobblemonResources.DEFAULT_LARGE)) }
val tooltipPadding = 6
@@ -87,6 +78,8 @@ fun renderTooltip(
blitSlicedVertical(poseStack, tooltipBackground, posX - (tooltipWidth / 2), tooltipTop, tooltipWidth, totalHeight)
blitSlicedVertical(poseStack, tooltipEdge, posX + (tooltipWidth / 2), tooltipTop, 1, totalHeight)
val centered = text.size == 1
text.forEachIndexed { index, it ->
drawScaledText(
context = context,
@@ -102,20 +95,6 @@ fun renderTooltip(
poseStack.popPose()
}
private fun wrapTooltipLine(line: MutableComponent, maxWidth: Int): List<MutableComponent> {
val styled = line.copy().font(CobblemonResources.DEFAULT_LARGE)
if (Minecraft.getInstance().font.width(styled) <= maxWidth) return listOf(line)
return Minecraft.getInstance().font.splitter.splitLines(styled, maxWidth, Style.EMPTY).map { wrapped ->
val component = Component.empty()
wrapped.visit({ style, string ->
component.append(Component.literal(string).withStyle(style))
Optional.empty<Unit>()
}, Style.EMPTY)
component
}
}
// Stretching the whole 11px image over an arbitrary height thickens the caps as more lines are added, so
// instead the caps are drawn 1:1 and only the flat fill row is stretched to cover the remaining height
private fun blitSlicedVertical(
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokedex.entry
package com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree
import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.locale.Language
@@ -28,14 +29,14 @@ import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.item.Items
fun describePokemonProperties(properties: PokemonProperties): MutableComponent? {
internal fun describePokemonProperties(properties: PokemonProperties): MutableComponent? {
if (hasUndescribedProperty(properties)) {
return null
}
val descriptors = mutableListOf<MutableComponent>()
properties.species?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }?.let {
properties.species?.let { PokemonSpecies.getByIdentifier(cobblemonResource(it)) }?.let {
descriptors.add(it.translatedName.plainCopy().green())
}
properties.gender?.let {
@@ -135,7 +136,7 @@ private fun describeStringSpeciesFeature(property: StringSpeciesFeature): Mutabl
return null
}
val value = PokemonSpecies.getByIdentifier(property.value.asIdentifierDefaultingNamespace())?.translatedName?.plainCopy()
val value = PokemonSpecies.getByIdentifier(cobblemonResource(property.value))?.translatedName?.plainCopy()
?: Component.literal(property.value)
return lang(key, value.green())
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import net.minecraft.client.Minecraft
import net.minecraft.network.chat.Component
fun debugEvolutionRequirementRendering() {
if (!Cobblemon.config.enableDebugKeys) {
return
}
val gaps = mutableListOf<Pair<String, String>>()
for (species in PokemonSpecies.implemented) {
val evolutions = species.evolutions + species.forms.flatMap { it.evolutions }
for (evolution in evolutions) {
if (renderEvolutionData(evolution) == null) {
gaps.add("Evolution: ${evolution::class.simpleName}" to species.showdownId())
}
for (requirement in evolution.requirements) {
if (resolveRequirementData(requirement) == null) {
gaps.add("Requirement: ${requirement::class.simpleName}" to species.showdownId())
}
}
}
}
val player = Minecraft.getInstance().player
if (gaps.isEmpty()) {
player?.sendSystemMessage(Component.literal("Evolution render debug: no gaps found."))
return
}
val byType = gaps.groupBy({ it.first }, { it.second })
println("Evolution render debug found ${gaps.size} gap(s) across ${byType.size} type(s):")
byType.forEach { (type, species) -> println(" $type: ${species.distinct().joinToString(", ")}") }
player?.sendSystemMessage(Component.literal("Evolution render debug: ${gaps.size} gap(s) across ${byType.size} type(s), see log for details."))
}
@@ -0,0 +1,123 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.pokemon.evolution.variants.BlockClickEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.DummyEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.LevelUpEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.itemRegistry
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.item.Item
data class RenderEvolutionData(
val icons: List<Item>,
val tooltips: List<MutableComponent>,
)
internal fun resolveRequirementData(requirement: Requirement): RenderRequirementData? {
val icon = requirement.pokedexIcon?.let {
Minecraft.getInstance()?.player?.level()?.itemRegistry?.get(it)
}
val tooltip = requirement.pokedexTooltip?.let {
Component.translatable(it)
}
if (icon != null && tooltip != null) {
return RenderRequirementData(icon, tooltip)
}
return renderRequirementData(requirement)
}
fun renderEvolutionData(evolution: Evolution): RenderEvolutionData? {
when (evolution) {
is BlockClickEvolution -> return renderBlockClickEvolution(evolution)
is DummyEvolution -> return renderDummyEvolution(evolution)
is ItemInteractionEvolution -> return renderItemInteractionEvolution(evolution)
is LevelUpEvolution -> return renderLevelUpEvolution(evolution)
is TradeEvolution -> return renderTradeEvolution(evolution)
}
return null
}
fun renderBlockClickEvolution(blockClickEvolution: BlockClickEvolution): RenderEvolutionData? {
return null
}
// TODO: Use dummyEvolution?
fun renderDummyEvolution(evolution: Evolution): RenderEvolutionData? {
return null
}
fun renderItemInteractionEvolution(itemInteractionEvolution: ItemInteractionEvolution): RenderEvolutionData? {
val item = itemInteractionEvolution.requiredContext.items.get().first().value()
val icons = mutableListOf(item)
val tooltips =
mutableListOf(lang("ui.pokedex.evolution.item_interact.tooltip", item.description.plainCopy().green()))
val requirementsDataList = itemInteractionEvolution.requirements.mapNotNull { requirement ->
resolveRequirementData(requirement)
}
requirementsDataList.forEach { requirement ->
icons.add(requirement.icon)
tooltips.add(requirement.tooltip)
}
return RenderEvolutionData(icons, tooltips)
}
fun renderLevelUpEvolution(levelUpEvolution: LevelUpEvolution): RenderEvolutionData? {
val requirementsDataList = levelUpEvolution.requirements.map { requirement ->
resolveRequirementData(requirement)
}
val icons = requirementsDataList.mapNotNull { requirementData -> requirementData?.icon }
val tooltips = requirementsDataList.mapNotNull { requirementData -> requirementData?.tooltip }
return RenderEvolutionData(icons, tooltips)
}
fun renderTradeEvolution(tradeEvolution: TradeEvolution): RenderEvolutionData? {
val icons = mutableListOf<Item>(CobblemonItems.LINK_CABLE)
val tooltips = mutableListOf<MutableComponent>()
val species = tradeEvolution.requiredContext.species
if (species == null) {
tooltips.add(lang("ui.pokedex.evolution.trade.tooltip.with_any_species"))
} else {
PokemonSpecies.getByIdentifier(cobblemonResource(species))?.let {
tooltips.add(lang("ui.pokedex.evolution.trade.tooltip.with_species", it.translatedName.green()))
}
}
val requirementsDataList = tradeEvolution.requirements.mapNotNull { requirement ->
resolveRequirementData(requirement)
}
requirementsDataList.forEach { requirement ->
icons.add(requirement.icon)
tooltips.add(requirement.tooltip)
}
return RenderEvolutionData(icons, tooltips)
}
@@ -0,0 +1,379 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeIdentifierCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeTagCondition
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.text.add
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.pokemon.requirements.*
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.item.Item
import net.minecraft.world.item.Items
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.levelgen.structure.Structure
data class RenderRequirementData(
val icon: Item,
val tooltip: MutableComponent,
)
fun renderRequirementData(requirement: Requirement): RenderRequirementData? {
when (requirement) {
is AdvancementRequirement -> return renderAdvancementRequirement(requirement)
is AnyRequirement -> return renderAnyRequirement(requirement)
is AreaRequirement -> return renderAreaRequirement(requirement)
is AttackDefenceRatioRequirement -> return renderAttackDefenceRatioRequirement(requirement)
is BattleCriticalHitsRequirement -> return renderBattleCriticalHitsRequirement(requirement)
is BiomeRequirement -> return renderBiomeRequirement(requirement)
is BlocksTraveledRequirement -> return renderBlocksTraveledRequirement(requirement)
is DamageTakenRequirement -> return renderDamageTakenRequirement(requirement)
is DefeatRequirement -> return renderDefeatRequirement(requirement)
is FriendshipRequirement -> return renderFriendshipRequirement(requirement)
is HeldItemRequirement -> return renderHeldItemRequirement(requirement)
is LevelRequirement -> return renderLevelRequirement(requirement)
is MoonPhaseRequirement -> return renderMoonPhaseRequirement(requirement)
is MoveSetRequirement -> return renderMoveSetRequirement(requirement)
is MoveTypeRequirement -> return renderMoveTypeRequirement(requirement)
is OwnerHoldsItemRequirement -> return renderOwnerHoldsItemRequirement(requirement)
is PartyMemberRequirement -> return renderPartyMemberRequirement(requirement)
is PokemonPropertiesRequirement -> return renderPokemonPropertiesRequirement(requirement)
is PropertyRangeRequirement -> return renderPropertyRangeRequirement(requirement)
is RecoilRequirement -> return renderRecoilRequirement(requirement)
is StatCompareRequirement -> return renderStatCompareRequirement(requirement)
is StatEqualRequirement -> return renderStatEqualRequirement(requirement)
is StructureRequirement -> return renderStructureRequirement(requirement)
is TimeRangeRequirement -> return renderTimeRangeRequirement(requirement)
is UseMoveRequirement -> return renderUseMoveRequirement(requirement)
is WeatherRequirement -> return renderWeatherRequirement(requirement)
is WorldRequirement -> return renderWorldRequirement(requirement)
}
return null
}
fun renderAdvancementRequirement(advancementRequirement: AdvancementRequirement): RenderRequirementData? {
val icon = Items.BOOK
val identifier = advancementRequirement.requiredAdvancement
val advancementLanguageKey = "${identifier.namespace}.${identifier.path.substringAfterLast('/')}"
val advancementComponent = Component.translatable("advancements.$advancementLanguageKey").green()
val tooltip = lang("ui.pokedex.requirement.advancement.tooltip", advancementComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderAnyRequirement(anyRequirement: AnyRequirement): RenderRequirementData? {
return null
}
fun renderAreaRequirement(areaRequirement: AreaRequirement): RenderRequirementData? {
return null
}
fun renderAttackDefenceRatioRequirement(attackDefenceRatioRequirement: AttackDefenceRatioRequirement): RenderRequirementData? {
return null
}
fun renderBattleCriticalHitsRequirement(battleCriticalHitsRequirement: BattleCriticalHitsRequirement): RenderRequirementData? {
val icon = CobblemonItems.MEDICINAL_LEEK
val criticalsComponent = Component.literal(battleCriticalHitsRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.battle_critical_hits.tooltip", criticalsComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderBiomeRequirement(biomeRequirement: BiomeRequirement): RenderRequirementData? {
val condition = biomeRequirement.biomeCondition
val anticondition = biomeRequirement.biomeAnticondition
if (condition == null && anticondition == null) {
return null
}
val icon = Items.COMPASS
val displayName: (RegistryLikeCondition<Biome>) -> MutableComponent = {
when (it) {
is RegistryLikeTagCondition -> Component.literal(it.tag.location().path.substringAfterLast('/').split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) })
is RegistryLikeIdentifierCondition -> Component.translatable("biome.${it.identifier.namespace}.${it.identifier.path}")
else -> Component.literal(it.toString())
}
}
val tooltip = if (condition != null && anticondition != null) {
lang("ui.pokedex.requirement.biome.both.tooltip", displayName(condition).green(), displayName(anticondition).green())
} else if (condition != null) {
lang("ui.pokedex.requirement.biome.tooltip", displayName(condition).green())
} else {
lang("ui.pokedex.requirement.biome.anticondition.tooltip", displayName(anticondition!!).green())
}
val playerBiome = Minecraft.getInstance()?.player?.let { it.level().getBiome(it.blockPosition()) }
if (playerBiome != null) {
val isMet = (condition == null || condition.fits(playerBiome)) && (anticondition == null || !anticondition.fits(playerBiome))
tooltip.add(if (isMet) Component.literal(" ✔").green() else Component.literal(" ✘").red())
}
return RenderRequirementData(icon, tooltip)
}
fun renderBlocksTraveledRequirement(blocksTraveledRequirement: BlocksTraveledRequirement): RenderRequirementData? {
val icon = Items.LEATHER_BOOTS
val blocksComponent = Component.literal(blocksTraveledRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.blocks_traveled.tooltip", blocksComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderDamageTakenRequirement(damageTakenRequirement: DamageTakenRequirement): RenderRequirementData? {
val icon = CobblemonItems.PUNCHING_GLOVE
val blocksComponent = Component.literal(damageTakenRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.damage_taken.tooltip", blocksComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderDefeatRequirement(defeatRequirement: DefeatRequirement): RenderRequirementData? {
val description = describePokemonProperties(defeatRequirement.target) ?: return null
val icon = CobblemonItems.PUNCHING_GLOVE
val defeatedAmount = defeatRequirement.amount
val descriptionComponent = Component.literal(defeatedAmount.toString()).green().add(" ").add(description)
val tooltip = lang("ui.pokedex.requirement.defeat.tooltip", descriptionComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderFriendshipRequirement(friendshipRequirement: FriendshipRequirement): RenderRequirementData? {
val icon = CobblemonItems.SOOTHE_BELL
val friendshipAmountComponent = Component.literal(friendshipRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.friendship.tooltip", friendshipAmountComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderHeldItemRequirement(heldItemRequirement: HeldItemRequirement): RenderRequirementData? {
val item = heldItemRequirement.itemCondition.items.get().first().value()
val tooltip = lang("ui.pokedex.requirement.held_item.tooltip", item.description.plainCopy().green())
return RenderRequirementData(item, tooltip)
}
fun renderLevelRequirement(levelRequirement: LevelRequirement): RenderRequirementData? {
val minLevel = levelRequirement.minLevel
val maxLevel = levelRequirement.maxLevel
val icon = CobblemonItems.RARE_CANDY
val tooltip =
if (minLevel == maxLevel || maxLevel == Int.MAX_VALUE)
lang("ui.pokedex.requirement.level.tooltip.min", Component.literal(minLevel.toString()).green())
else
lang("ui.pokedex.requirement.level.tooltip.between", Component.literal(minLevel.toString()).green(), Component.literal(maxLevel.toString()).green())
return RenderRequirementData(icon, tooltip)
}
fun renderMoonPhaseRequirement(moonPhaseRequirement: MoonPhaseRequirement): RenderRequirementData? {
val icon = Items.CLOCK
val moonPhaseComponent = moonPhaseRequirement.moonPhase.displayName().green()
val tooltip = lang("ui.pokedex.requirement.moon_phase.tooltip", moonPhaseComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderMoveSetRequirement(moveSetRequirement: MoveSetRequirement): RenderRequirementData? {
val icon = CobblemonItems.TECHNICAL_MACHINE
val tooltip = lang("ui.pokedex.requirement.has_move.tooltip", moveSetRequirement.move.displayName.green())
return RenderRequirementData(icon, tooltip)
}
fun renderMoveTypeRequirement(moveTypeRequirement: MoveTypeRequirement): RenderRequirementData? {
val type = ElementalTypes.get(moveTypeRequirement.type.name)
val icon = when (type) {
ElementalTypes.NORMAL -> CobblemonItems.NORMAL_GEM
ElementalTypes.FIRE -> CobblemonItems.FIRE_GEM
ElementalTypes.WATER -> CobblemonItems.WATER_GEM
ElementalTypes.GRASS -> CobblemonItems.GRASS_GEM
ElementalTypes.ELECTRIC -> CobblemonItems.ELECTRIC_GEM
ElementalTypes.ICE -> CobblemonItems.ICE_GEM
ElementalTypes.FIGHTING -> CobblemonItems.FIGHTING_GEM
ElementalTypes.POISON -> CobblemonItems.POISON_GEM
ElementalTypes.GROUND -> CobblemonItems.GROUND_GEM
ElementalTypes.FLYING -> CobblemonItems.FLYING_GEM
ElementalTypes.PSYCHIC -> CobblemonItems.PSYCHIC_GEM
ElementalTypes.BUG -> CobblemonItems.BUG_GEM
ElementalTypes.ROCK -> CobblemonItems.ROCK_GEM
ElementalTypes.GHOST -> CobblemonItems.GHOST_GEM
ElementalTypes.DRAGON -> CobblemonItems.DRAGON_GEM
ElementalTypes.DARK -> CobblemonItems.DARK_GEM
ElementalTypes.STEEL -> CobblemonItems.STEEL_GEM
ElementalTypes.FAIRY -> CobblemonItems.FAIRY_GEM
else -> CobblemonItems.NORMAL_GEM
}
val tooltip = lang("ui.pokedex.requirement.has_move_type.tooltip", moveTypeRequirement.type.displayName.green())
return RenderRequirementData(icon, tooltip)
}
fun renderOwnerHoldsItemRequirement(ownerHoldsItemRequirement: OwnerHoldsItemRequirement): RenderRequirementData? {
val item = ownerHoldsItemRequirement.itemCondition.items.get().first().value()
val tooltip = lang("ui.pokedex.requirement.owner_held_item.tooltip", item.description.plainCopy().green())
return RenderRequirementData(item, tooltip)
}
fun renderPartyMemberRequirement(partyMemberRequirement: PartyMemberRequirement): RenderRequirementData? {
val description = describePokemonProperties(partyMemberRequirement.target) ?: return null
val icon = CobblemonItems.POKE_BALL
val tooltip = if (partyMemberRequirement.contains) {
lang("ui.pokedex.requirement.party_member.tooltip", description)
} else {
lang("ui.pokedex.requirement.party_member.anticondition.tooltip", description)
}
return RenderRequirementData(icon, tooltip)
}
fun renderPokemonPropertiesRequirement(pokemonPropertiesRequirement: PokemonPropertiesRequirement): RenderRequirementData? {
val description = describePokemonProperties(pokemonPropertiesRequirement.target) ?: return null
val icon = Items.NAME_TAG
val tooltip = lang("ui.pokedex.requirement.pokemon_properties.tooltip", description)
return RenderRequirementData(icon, tooltip)
}
fun renderPropertyRangeRequirement(propertyRangeRequirement: PropertyRangeRequirement): RenderRequirementData? {
val provider = SpeciesFeatures.getFeature(propertyRangeRequirement.feature) as? IntSpeciesFeatureProvider ?: return null
val displayName = provider.display?.name?.takeIf { it.isNotBlank() }?.asTranslated()?.green() ?: return null
val icon = provider.itemPoints.keys.firstOrNull()?.let { BuiltInRegistries.ITEM.get(it) } ?: Items.NAME_TAG
val range = propertyRangeRequirement.range
val tooltip = lang(
"ui.pokedex.requirement.property_range.tooltip",
displayName,
Component.literal(range.first.toString()).green(),
Component.literal(range.last.toString()).green()
)
return RenderRequirementData(icon, tooltip)
}
fun renderRecoilRequirement(recoilRequirement: RecoilRequirement): RenderRequirementData? {
val icon = CobblemonItems.LIFE_ORB
val recoilComponent = Component.literal(recoilRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.recoil.tooltip", recoilComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderStatCompareRequirement(statCompareRequirement: StatCompareRequirement): RenderRequirementData? {
val highStat = Stats.getStat(statCompareRequirement.highStat)
val lowStat = Stats.getStat(statCompareRequirement.lowStat)
val icon = when (highStat) {
Stats.HP, Stats.EVASION, Stats.ACCURACY -> CobblemonItems.HEALTH_MOCHI
Stats.ATTACK -> CobblemonItems.MUSCLE_MOCHI
Stats.DEFENCE -> CobblemonItems.RESIST_MOCHI
Stats.SPECIAL_ATTACK -> CobblemonItems.GENIUS_MOCHI
Stats.SPECIAL_DEFENCE -> CobblemonItems.CLEVER_MOCHI
Stats.SPEED -> CobblemonItems.SWIFT_MOCHI
}
val highStatComponent = highStat.displayName.plainCopy().green()
val lowStatComponent = lowStat.displayName.plainCopy().green()
val tooltip = lang("ui.pokedex.requirement.stat_compare.tooltip", highStatComponent, lowStatComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderStatEqualRequirement(statEqualRequirement: StatEqualRequirement): RenderRequirementData? {
val statOne = Stats.getStat(statEqualRequirement.statOne)
val statTwo = Stats.getStat(statEqualRequirement.statTwo)
val statOneComponent = statOne.displayName.plainCopy().green()
val statTwoComponent = statTwo.displayName.plainCopy().green()
val icon = CobblemonItems.FRESH_START_MOCHI
val tooltip = lang("ui.pokedex.requirement.stat_equal.tooltip", statOneComponent, statTwoComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderStructureRequirement(structureRequirement: StructureRequirement): RenderRequirementData? {
val condition = structureRequirement.structureCondition
val anticondition = structureRequirement.structureAnticondition
if (condition == null && anticondition == null) {
return null
}
val icon = Items.STRUCTURE_BLOCK
val displayName: (RegistryLikeCondition<Structure>) -> MutableComponent = {
val path = when (it) {
is RegistryLikeTagCondition -> it.tag.location().path
is RegistryLikeIdentifierCondition -> it.identifier.path
else -> it.toString()
}
Component.literal(path.substringAfterLast('/').split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) })
}
val tooltip = if (condition != null && anticondition != null) {
lang("ui.pokedex.requirement.structure.both.tooltip", displayName(condition).green(), displayName(anticondition).green())
} else if (condition != null) {
lang("ui.pokedex.requirement.structure.tooltip", displayName(condition).green())
} else {
lang("ui.pokedex.requirement.structure.anticondition.tooltip", displayName(anticondition!!).green())
}
return RenderRequirementData(icon, tooltip)
}
fun renderTimeRangeRequirement(timeRangeRequirement: TimeRangeRequirement): RenderRequirementData? {
val icon = Items.CLOCK
val timeRangeComponent = timeRangeRequirement.range.displayName() ?: return null
val tooltip = lang("ui.pokedex.requirement.time_range.tooltip", timeRangeComponent.green())
return RenderRequirementData(icon, tooltip)
}
fun renderUseMoveRequirement(useMoveRequirement: UseMoveRequirement): RenderRequirementData? {
val icon = CobblemonItems.CHOICE_BAND
val tooltip = lang("ui.pokedex.requirement.use_move.tooltip", useMoveRequirement.move.displayName.green())
return RenderRequirementData(icon, tooltip)
}
fun renderWeatherRequirement(weatherRequirement: WeatherRequirement): RenderRequirementData? {
val icon: Item
val tooltip: MutableComponent
if (weatherRequirement.isThundering == true) {
icon = Items.LIGHTNING_ROD
tooltip = lang("ui.pokedex.requirement.weather.tooltip.thundering")
} else {
icon = Items.WATER_BUCKET
tooltip = lang("ui.pokedex.requirement.weather.tooltip.raining")
}
return RenderRequirementData(icon, tooltip)
}
fun renderWorldRequirement(worldRequirement: WorldRequirement): RenderRequirementData? {
return null
}
@@ -6,18 +6,20 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pokedex.widgets
package com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.gui.*
import com.cobblemon.mod.common.api.pokedex.Dexes
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.entry.*
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.text.add
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUI
@@ -27,22 +29,18 @@ import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.POKEMON_D
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCROLL_SLOT_SIZE
import com.cobblemon.mod.common.client.gui.pokedex.ScaledButton
import com.cobblemon.mod.common.client.gui.pokedex.renderTooltip
import com.cobblemon.mod.common.client.gui.pokedex.widgets.PokemonSlotWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.core.registries.Registries
import net.minecraft.locale.Language
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.tags.TagKey
class EvolutionTreeWidget(
val parent: PokedexGUI,
@@ -94,8 +92,6 @@ class EvolutionTreeWidget(
private var hoveredTooltips: List<MutableComponent>? = null
private var viewedForm: FormData? = null
private val expandButton: ScaledButton = ScaledButton(
0f,
0f,
@@ -104,14 +100,11 @@ class EvolutionTreeWidget(
resource = expandResource,
clickAction = {
parent.evolutionTreeExpanded = !parent.evolutionTreeExpanded
expandButton.resource = if (parent.evolutionTreeExpanded) minimizeResource else expandResource
updatePositioning()
}
).apply { addWidget(this) }
init {
updatePositioning()
}
override fun renderWidget(
guiGraphics: GuiGraphics,
mouseX: Int,
@@ -153,31 +146,9 @@ class EvolutionTreeWidget(
if (pokedexEntry == null || pokedexForm == null) return
updatePositioning()
var rootSpecies = pokedexEntry
var rootForm = pokedexForm
var preEvolution = findPreEvolution(rootSpecies, rootForm)
// Stop at whatever the dex can't resolve, and at cycles (datapacks)
val visited = mutableSetOf(rootSpecies.speciesId to rootForm.displayForm)
while (preEvolution != null) {
val preEvolutionSpecies = DexEntries.entries.values.firstOrNull { it.speciesId == preEvolution.fromSpecies &&
it.forms.any {form -> form.displayForm.equals(preEvolution.fromForm, ignoreCase = true)}} ?: break
val preEvolutionForm = preEvolutionSpecies.forms.firstOrNull { it.displayForm.equals(preEvolution.fromForm, ignoreCase = true) } ?: break
if (!visited.add(preEvolutionSpecies.speciesId to preEvolutionForm.displayForm)) break
rootSpecies = preEvolutionSpecies
rootForm = preEvolutionForm
preEvolution = findPreEvolution(rootSpecies, rootForm)
}
val viewedFormEquivalents = Dexes.getFormEquivalents(pokedexForm.displayForm)
viewedForm = PokemonSpecies.getByIdentifier(pokedexEntry.speciesId)
?.forms?.firstOrNull { viewedFormEquivalents.contains(it.name.lowercase()) }
val formEquivalents = Dexes.getFormEquivalents(rootForm.displayForm)
val form =
rootSpecies.forms.firstOrNull { formEquivalents.contains(it.displayForm.lowercase()) } ?:
rootSpecies.forms.first { it.displayForm.equals("Normal", ignoreCase = true) }
val rootEvolutionNode = buildEvolutionTree(rootSpecies, form)
val (rootPokedexEntry, rootPokedexForm) = Dexes.findRootDexEntryBySpeciesId(pokedexEntry.speciesId, pokedexForm.displayForm)
val rootEvolutionNode = buildEvolutionTree(rootPokedexEntry, rootPokedexForm, null)
// val isSameEntry = previousRootPokedexEntry != null && pokedexEntry?.id == previousRootPokedexEntry?.id
// val isSameForm =
@@ -201,13 +172,6 @@ class EvolutionTreeWidget(
panningY = 0.0
}
/** The evolution leading to [form]; the standard form falls back to any form of the species (no evolution leads to regular Vivillon). */
private fun findPreEvolution(entry: PokedexEntry, form: PokedexForm): DexEvolutionDisplay? {
return DexEntries.evolutionDisplays.firstOrNull { it.toSpecies == entry.speciesId && form.displayForm.equals(it.toForm, ignoreCase = true) }
?: DexEntries.evolutionDisplays.takeIf { form.displayForm.equals("Normal", ignoreCase = true) }
?.firstOrNull { it.toSpecies == entry.speciesId }
}
fun updatePositioning() {
x = pX + X_MARGIN
y = pY + if (parent.evolutionTreeExpanded) Y_MARGIN_EXPANDED else Y_MARGIN_MINIMIZED
@@ -216,7 +180,6 @@ class EvolutionTreeWidget(
panningX = 0.0
panningY = 0.0
expandButton.resource = if (parent.evolutionTreeExpanded) minimizeResource else expandResource
expandButton.buttonX = x + EXPAND_BUTTON_X_MARGIN
expandButton.buttonY = y + EXPAND_BUTTON_Y_MARGIN
}
@@ -224,62 +187,61 @@ class EvolutionTreeWidget(
private fun buildEvolutionTree(
pokedexEntry: PokedexEntry,
pokedexForm: PokedexForm,
aspects: Set<String> = emptySet(),
ancestors: Set<Pair<ResourceLocation, String>> = emptySet(),
evolution: Evolution? = null,
resultProperties: PokemonProperties? = evolution?.result,
renderData: RenderEvolutionData? = evolution?.let { renderEvolutionData(it) }
): EvolutionNode {
// Datapack evolutions can loop back (A -> B -> A), so stop at anything already on this branch
val path = ancestors + (pokedexEntry.speciesId to pokedexForm.displayForm.lowercase())
val discoveryLevel = CobblemonClient.clientPokedexData.getHighestKnowledgeFor(pokedexEntry, pokedexForm, aspects.toMutableSet())
val species = PokemonSpecies.getByIdentifier(pokedexEntry.speciesId)!!
val formEquivalents = Dexes.getFormEquivalents(pokedexForm.displayForm)
val evolutions = DexEntries.evolutionDisplays.filter { it.fromSpecies == pokedexEntry.speciesId && formEquivalents.contains(it.fromForm.lowercase()) }
val form = species.forms.firstOrNull { formEquivalents.contains(it.name.lowercase()) }
val parentSpecies = Dexes.findDexEntryBySpeciesId(species.resourceIdentifier)!!
val parentPokedexForm = parentSpecies.forms.first { formEquivalents.contains(it.displayForm.lowercase()) }
val children = evolutions.mapNotNull {
val childPokedexEntry = DexEntries.entries.values.firstOrNull { entry -> entry.speciesId == it.toSpecies &&
entry.forms.any {form -> form.displayForm.equals(it.toForm, ignoreCase = true)}}
if (childPokedexEntry == null) {
Cobblemon.LOGGER.warn("Dex entry not found for species: ${it.toSpecies}")
return@mapNotNull null
val aspects = resultProperties?.aspects?.toMutableSet() ?: mutableSetOf()
form?.aspects?.let { aspects.addAll(it) }
val discoveryLevel = CobblemonClient.clientPokedexData.getHighestKnowledgeFor(pokedexEntry, pokedexForm, aspects)
val evolutions = form?.evolutions ?: species.evolutions
// Avoid evolution.result.create() here: it builds a full battle-ready Pokemon (initializeMovesetFromDefault
// etc.) which relies on server-only data like CobblemonMovesetBuilders and crashes on the client
val children = evolutions.flatMap { evolution ->
val baseRenderData = renderEvolutionData(evolution)
listOfNotNull(evolution.result, evolution.shedder).mapNotNull { resultProperties ->
val resultSpecies = resultProperties.species
?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
?: return@mapNotNull null
val resultForm = resultSpecies.getForm(aspects + resultProperties.aspects)
val speciesId = resultSpecies.resourceIdentifier
val childPokedexEntry = Dexes.findDexEntryBySpeciesId(speciesId)
if (childPokedexEntry == null) {
Cobblemon.LOGGER.warn("Dex entry not found for species: $speciesId")
return@mapNotNull null
}
val formEquivalents = Dexes.getFormEquivalents(resultForm.name.lowercase())
val childPokedexForm = childPokedexEntry.forms.first { formEquivalents.contains(it.displayForm.lowercase()) }
val childRenderData = if (resultProperties === evolution.shedder) {
RenderEvolutionData(
icons = (baseRenderData?.icons ?: emptyList()) + CobblemonItems.POKE_BALL,
tooltips = (baseRenderData?.tooltips ?: emptyList()) + lang("ui.pokedex.evolution.shedder.tooltip"),
)
} else {
baseRenderData
}
return@mapNotNull buildEvolutionTree(childPokedexEntry, childPokedexForm, evolution, resultProperties, childRenderData)
}
val formEquivalents = Dexes.getFormEquivalents(it.toForm.lowercase())
val childPokedexForm = childPokedexEntry.forms.firstOrNull { formEquivalents.contains(it.displayForm.lowercase()) }
if (childPokedexForm == null) {
Cobblemon.LOGGER.warn("Dex form ${it.toForm} not found for species: ${it.toSpecies}")
return@mapNotNull null
}
if ((childPokedexEntry.speciesId to childPokedexForm.displayForm.lowercase()) in path) {
Cobblemon.LOGGER.warn("Evolution loop found at species: ${it.toSpecies} form: ${it.toForm}")
return@mapNotNull null
}
buildEvolutionTree(childPokedexEntry, childPokedexForm, it.toAspects, path)
}
return EvolutionNode(
pokedexEntry = pokedexEntry,
pokedexForm = pokedexForm,
aspects = aspects,
pokedexEntryProgress = discoveryLevel,
children = mergeEquivalentChildren(children),
renderDataAlternatives = evolutions,
)
}
private fun mergeEquivalentChildren(children: List<EvolutionNode>): List<EvolutionNode> {
return children
.groupBy { Triple(it.pokedexEntry.speciesId, it.pokedexForm.displayForm.lowercase(), it.aspects) }
.values
.map { group ->
if (group.size == 1) return@map group.first()
val first = group.first()
EvolutionNode(
first.pokedexEntry,
first.pokedexForm,
first.aspects,
first.pokedexEntryProgress,
first.children,
group.flatMap { it.renderDataAlternatives }
)
}
return EvolutionNode(pokedexEntry, parentPokedexForm, aspects, discoveryLevel, children, renderData)
}
private fun renderTreeDrawingNode(
@@ -296,19 +258,15 @@ class EvolutionTreeWidget(
val (childX, _) = getDrawingCoordinates(child)
val lineStartX = treeX + SCROLL_SLOT_SIZE
val lineEndX = childX
val lineY = treeY + SCROLL_SLOT_SIZE / 2
val formEquivalents = Dexes.getFormEquivalents(child.value.pokedexForm.displayForm)
val alternatives = treeDrawingNode.value.renderDataAlternatives.filter {
it.toSpecies == child.value.pokedexEntry.speciesId && formEquivalents.contains(it.toForm.lowercase()) && it.toAspects == child.value.aspects
}
alternatives.takeIf { it.isNotEmpty() }?.let {
child.value.renderData?.let { renderData ->
drawEvolutionInfo(
guiGraphics,
it,
renderData,
lineStartX,
childX,
lineEndX,
lineY,
mouseX,
mouseY,
@@ -340,19 +298,16 @@ class EvolutionTreeWidget(
treeDrawingNode.children.forEach { child ->
val (childX, childY) = getDrawingCoordinates(child)
val lineStartX = treeForkEnd
val lineEndX = childX
val lineY = childY + SCROLL_SLOT_SIZE / 2
val formEquivalents = Dexes.getFormEquivalents(child.value.pokedexForm.displayForm)
val alternatives = treeDrawingNode.value.renderDataAlternatives.filter {
it.toSpecies == child.value.pokedexEntry.speciesId && formEquivalents.contains(it.toForm.lowercase()) && it.toAspects == child.value.aspects
}
alternatives.takeIf { it.isNotEmpty() }?.let {
child.value.renderData?.let { renderData ->
drawEvolutionInfo(
guiGraphics,
it,
treeForkEnd,
childX,
renderData,
lineStartX,
lineEndX,
lineY,
mouseX,
mouseY,
@@ -391,7 +346,7 @@ class EvolutionTreeWidget(
if (formName == "normal") null
else lang("ui.pokedex.info.form.${species}-${formName.replace("-", "")}")
formComponent?.let { speciesComponent.copy().add(" (").add(it).add(")") } ?: speciesComponent
formComponent?.add(" ")?.add(speciesComponent) ?: speciesComponent
}
guiGraphics.disableScissor()
@@ -453,7 +408,7 @@ class EvolutionTreeWidget(
private fun drawEvolutionInfo(
guiGraphics: GuiGraphics,
renderDataAlternatives: List<DexEvolutionDisplay>,
renderData: RenderEvolutionData,
lineStartX: Int,
lineEndX: Int,
lineY: Int,
@@ -461,26 +416,6 @@ class EvolutionTreeWidget(
mouseY: Int,
delta: Float,
) {
val icons = renderDataAlternatives.flatMap { it.requirements.filter(::isDisplayable).map { req -> req.icon } }.distinct()
val playerBiome = Minecraft.getInstance().player?.let { it.level().getBiome(it.blockPosition()) }
val alternativeTooltips = renderDataAlternatives.map { alternative ->
alternative.requirements.filter(::isDisplayable).map { req ->
if (req is CobblemonBiomeDexEvolutionRequirementDisplay) {
val tooltip = req.tooltip.copy()
if (playerBiome != null) {
val conditionFits = req.condition?.let { if (it.tag) playerBiome.`is`(TagKey.create(Registries.BIOME, it.id)) else playerBiome.`is`(it.id) } ?: true
val antiConditionFits = req.antiCondition?.let { if (it.tag) playerBiome.`is`(TagKey.create(Registries.BIOME, it.id)) else playerBiome.`is`(it.id) } ?: false
val isMet = conditionFits && !antiConditionFits
tooltip.add(if (isMet) Component.literal(" ✔").green() else Component.literal(" ✘").red())
}
tooltip
} else {
req.tooltip
}
}
}
val tooltips = combineAlternativeTooltips(alternativeTooltips)
val lineXMiddle = (lineStartX + lineEndX) / 2
val iconX = (lineXMiddle + ICON_X_PADDING - ICON_SLOT_HALF_PADDING).toInt()
val iconY = (lineY + ICON_Y_PADDING - ICON_SLOT_HALF_PADDING).toInt()
@@ -496,7 +431,7 @@ class EvolutionTreeWidget(
return
}
if (icons.isEmpty()) {
if (renderData.icons.isEmpty()) {
guiGraphics.hLine(
lineStartX,
lineEndX,
@@ -527,8 +462,8 @@ class EvolutionTreeWidget(
height = iconSize,
)
val index = (System.currentTimeMillis() / MILLIS_BETWEEN_ICONS_CYCLE) % icons.size
val item = icons[index.toInt()]
val index = (System.currentTimeMillis() / MILLIS_BETWEEN_ICONS_CYCLE) % renderData.icons.size
val item = renderData.icons[index.toInt()]
renderScaledGuiItemIcon(
itemStack = item.defaultInstance,
@@ -543,49 +478,17 @@ class EvolutionTreeWidget(
val isIconHovered = isElementHovered(
mouseX,
mouseY,
iconX,
iconX + iconSize,
iconY,
iconY + iconSize,
iconX.toInt(),
iconX.toInt() + iconSize,
iconY.toInt(),
iconY.toInt() + iconSize,
)
if (tooltips.isNotEmpty() &&
if (renderData.tooltips.isNotEmpty() &&
(isLineHovered || isIconHovered) &&
this.isHovered(mouseX.toDouble(), mouseY.toDouble())
) {
hoveredTooltips = tooltips.map { it.copy() }
}
}
/** Hides biome requirements naming untranslated biomes, e.g. from mods that aren't installed (aether:skyroot_forest) */
private fun isDisplayable(requirement: DexEvolutionRequirementDisplay): Boolean {
if (requirement !is CobblemonBiomeDexEvolutionRequirementDisplay) return true
return listOfNotNull(requirement.condition, requirement.antiCondition).all {
it.tag || Language.getInstance().has("biome.${it.id.namespace}.${it.id.path}")
}
}
private fun combineAlternativeTooltips(alternatives: List<List<Component>>): List<Component> {
val groups = alternatives.filter { it.isNotEmpty() }
if (groups.size <= 1) return groups.firstOrNull() ?: emptyList()
val sharedStrings = groups.map { group -> group.map { it.string }.toSet() }.reduce { acc, strings -> acc intersect strings }
val shared = groups.first().filter { it.string in sharedStrings }.distinctBy { it.string }
val remainders = groups
.map { group -> group.filter { it.string !in sharedStrings } }
.distinctBy { group -> group.map { it.string } }
// An alternative needing nothing beyond the shared lines makes the others irrelevant
if (remainders.any { it.isEmpty() }) return shared
if (remainders.size == 1) return shared + remainders.first()
return if (remainders.size >= 3 && remainders.all { it.size == 1 }) {
shared + lang("ui.pokedex.evolution.one_of").bold() + remainders.map { it.first() }
} else {
// Show each alternative in full, otherwise it's unclear where the first one starts
groups.distinctBy { group -> group.map { it.string } }.flatMapIndexed { index, group ->
if (index == 0) group else listOf(lang("ui.pokedex.evolution.or").bold()) + group
}
hoveredTooltips = renderData.tooltips
}
}
@@ -663,6 +566,6 @@ class EvolutionTreeWidget(
val aspects: Set<String>,
val pokedexEntryProgress: PokedexEntryProgress,
override val children: List<EvolutionNode>,
val renderDataAlternatives: List<DexEvolutionDisplay>,
val renderData: RenderEvolutionData?,
) : TreeNode<EvolutionNode>
}
@@ -1041,10 +1041,7 @@ class TMMachineScreen(containerMenu: TMMachineMenu, val inventory: Inventory, ti
// Scroll to move if moves list contains held TM move
if (tmList.get().contains(heldStackTm)) {
val moveListIndex = tmList.get().indexOf(heldStackTm)
movesScrollingList.scrollAmount = movesScrollingList.maxScroll * when (moveListIndex) {
tmList.get().lastIndex -> 1.0
else -> (moveListIndex / tmList.get().size.toDouble())
}
movesScrollingList.scrollAmount = (if (moveListIndex == tmList.get().lastIndex) 1.0 else (moveListIndex / tmList.get().size.toDouble()))* movesScrollingList.maxScroll
}
}
}
@@ -28,9 +28,9 @@ import org.joml.Vector3f
open class PartySlot(
x: Int, y: Int,
private val pokemon: TradeStartedPacket.TradeablePokemon?,
val pokemon: TradeStartedPacket.TradeablePokemon?,
private val parent: TradeGUI,
private val isOpposing: Boolean = false,
val isOpposing: Boolean = false,
onPress: OnPress
) : Button(x, y, SIZE, SIZE, Component.literal("PartySlot"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
val state = FloatingState()
@@ -42,6 +42,8 @@ open class PartySlot(
private val genderIconFemale = cobblemonResource("textures/gui/pc/gender_icon_female.png")
private val selectPointerResource = cobblemonResource("textures/gui/pc/pc_pointer.png")
private val untradeableResource = cobblemonResource("textures/gui/trade/trade_slot_icon_locked.png")
private val tradePartySlotSelectedResource = cobblemonResource("textures/gui/trade/trade_party_slot_selected.png")
private val tradePartySlotSelectedOpposingResource = cobblemonResource("textures/gui/trade/trade_party_slot_selected_opposing.png")
}
override fun playDownSound(soundManager: SoundManager) {
@@ -60,6 +62,17 @@ open class PartySlot(
)
}
if (hasSelected()) {
blitk(
matrixStack = matrices,
texture = if (isOpposing) tradePartySlotSelectedOpposingResource else tradePartySlotSelectedResource,
x = x - 2,
y = y - 2,
width = 29,
height = 29
)
}
if (pokemon != null) {
// context.enableScissor(x - 2, y + 2, x + SIZE + 4, y + SIZE + 4)
// Render Pokémon
@@ -118,7 +131,16 @@ open class PartySlot(
}
matrices.popPose()
if (hasSelected()) {
val isViewed = if (isOpposing) {
parent.opposingOfferedPokemon?.uuid == pokemon.pokemonId
} else {
parent.offeredPokemon?.uuid == pokemon.pokemonId
}
if (isViewed && pokemon.tradeable) {
matrices.pushPose()
matrices.translate(0.0, 0.0, 200.0)
blitk(
matrixStack = matrices,
texture = selectPointerResource,
@@ -128,6 +150,7 @@ open class PartySlot(
height = 8,
scale = TradeGUI.SCALE
)
matrices.popPose()
}
// Held Item
@@ -145,8 +168,9 @@ open class PartySlot(
}
open fun hasSelected(): Boolean {
val offeredPokemon = if (isOpposing) parent.trade.oppositeOffer.get() else parent.trade.myOffer.get()
return pokemon?.pokemonId == offeredPokemon?.uuid && pokemon != null
if (pokemon == null) return false
val offeredPokemons = if (isOpposing) parent.trade.oppositeOffer.get() else parent.trade.myOffer.get()
return offeredPokemons.any { it.uuid == pokemon.pokemonId }
}
fun isHovered(mouseX: Int, mouseY: Int) = pokemon?.tradeable != false && mouseX.toFloat() in (x.toFloat()..(x.toFloat() + SIZE)) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + SIZE))
@@ -25,6 +25,7 @@ import com.cobblemon.mod.common.client.gui.summary.widgets.common.reformatNature
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
import com.cobblemon.mod.common.client.trade.ClientTrade
import com.cobblemon.mod.common.net.messages.client.trade.TradeStartedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeStartedPacket.TradeablePokemon
import com.cobblemon.mod.common.net.messages.server.trade.*
import com.cobblemon.mod.common.pokemon.Gender
@@ -98,15 +99,23 @@ class TradeGUI(
private val tradeAnimationArrowsResource = cobblemonResource("textures/gui/trade/trade_animation_arrows.png")
}
private var offeredPokemonModel: ModelWidget? = null
private var opposingOfferedPokemonModel: ModelWidget? = null
private var offeredPokemonModels: MutableList<ModelWidget> = mutableListOf()
private var opposingOfferedPokemonModels: MutableList<ModelWidget> = mutableListOf()
private lateinit var offeredPokemonMarkings: MarkingsWidget
private lateinit var opposingOfferedPokemonMarkings: MarkingsWidget
var selectedPartyIndices = mutableListOf<Int>()
var offeredPokemons: MutableList<Pokemon> = mutableListOf()
var opposingPokemons: MutableList<Pokemon> = mutableListOf()
// For backwards compatibility where single is referenced in rendering text info
var offeredPokemon: Pokemon? = null
var opposingOfferedPokemon: Pokemon? = null
var myGridMode = true
var opposingGridMode = true
var ticksElapsed = 0
var protectiveTicks = 0
var selectPointerOffsetY = 0
@@ -125,30 +134,67 @@ class TradeGUI(
}
trade.completedEmitter.subscribe {
val (pokemonId1, pokemonId2) = it
val myTradedPokemon = party.find { it?.pokemonId == pokemonId1 }
val theirTradedPokemon = traderParty.find { it?.pokemonId == pokemonId2 }
if (myTradedPokemon == null || theirTradedPokemon == null) {
val (pokemonIds1, pokemonIds2) = it
val myTradedPokemons = pokemonIds1.mapNotNull { id -> party.find { it?.pokemonId == id } }
val theirTradedPokemons = pokemonIds2.mapNotNull { id -> traderParty.find { it?.pokemonId == id } }
if (myTradedPokemons.isEmpty() || theirTradedPokemons.isEmpty()) {
CobblemonNetwork.sendToServer(CancelTradePacket())
return@subscribe onClose()
}
val tradedSlot = party.indexOf(myTradedPokemon)
val opposingTradedSlot = traderParty.indexOf(theirTradedPokemon)
party[tradedSlot] = theirTradedPokemon
traderParty[opposingTradedSlot] = myTradedPokemon
myTradedPokemons.forEach { pk ->
val idx = party.indexOf(pk)
if (idx != -1) party[idx] = null
}
theirTradedPokemons.forEach { pk ->
val idx = traderParty.indexOf(pk)
if (idx != -1) traderParty[idx] = null
}
val newSelectedIndices = mutableListOf<Int>()
theirTradedPokemons.forEach { pk ->
val emptyIdx = party.indexOf(null)
if (emptyIdx != -1) {
party[emptyIdx] = pk
}
}
myTradedPokemons.forEach { pk ->
val emptyIdx = traderParty.indexOf(null)
if (emptyIdx != -1) traderParty[emptyIdx] = pk
}
val compactUI = { list: MutableList<TradeStartedPacket.TradeablePokemon?> ->
val nonNulls = list.filterNotNull()
list.clear()
list.addAll(nonNulls)
while (list.size < 6) list.add(null)
}
compactUI(party)
compactUI(traderParty)
theirTradedPokemons.forEach { pk ->
val idx = party.indexOf(pk)
if (idx != -1) newSelectedIndices.add(idx)
}
trade.oppositeAcceptedMyOffer.set(false)
CobblemonNetwork.sendToServer(UpdateTradeOfferPacket(Pair(theirTradedPokemon.pokemonId, PartyPosition(tradedSlot))))
selectedPartyIndices.clear()
selectedPartyIndices.addAll(newSelectedIndices)
val newOffers = selectedPartyIndices.mapNotNull { idx ->
party[idx]?.let { it.pokemonId to PartyPosition(idx) }
}
CobblemonNetwork.sendToServer(UpdateTradeOfferPacket(newOffers))
rebuildWidgets()
}
trade.oppositeOffer.subscribe { newOffer: Pokemon? ->
setOfferedPokemon(pokemon = newOffer, isOpposing = true)
trade.oppositeOffer.subscribe { newOffer: List<Pokemon> ->
setOfferedPokemon(pokemonList = newOffer, isOpposing = true)
}
trade.myOffer.subscribe { myOffer: Pokemon? ->
setOfferedPokemon(pokemon = myOffer)
trade.myOffer.subscribe { myOffer: List<Pokemon> ->
setOfferedPokemon(pokemonList = myOffer)
}
trade.oppositeAcceptedMyOffer.subscribe {
@@ -216,9 +262,16 @@ class TradeGUI(
pokemon = pokemon,
parent = this,
onPress = {
if (!trade.acceptedOppositeOffer && !tradeProcessing) {
val pk = if (offeredPokemon?.uuid == pokemon?.pokemonId) null else pokemon
CobblemonNetwork.sendToServer(UpdateTradeOfferPacket(pk?.let { it.pokemonId to PartyPosition(partyIndex) }))
if (!trade.acceptedOppositeOffer && !tradeProcessing && pokemon != null) {
if (selectedPartyIndices.contains(partyIndex)) {
selectedPartyIndices.remove(partyIndex)
} else if (selectedPartyIndices.size < 6) {
selectedPartyIndices.add(partyIndex)
}
val newOffers = selectedPartyIndices.mapNotNull { idx ->
party[idx]?.let { it.pokemonId to PartyPosition(idx) }
}
CobblemonNetwork.sendToServer(UpdateTradeOfferPacket(newOffers))
playSound(CobblemonSounds.GUI_CLICK)
}
}
@@ -250,8 +303,8 @@ class TradeGUI(
).also { widget -> addRenderableWidget(widget) }
}
setOfferedPokemon(pokemon = offeredPokemon, isOpposing = false)
setOfferedPokemon(pokemon = opposingOfferedPokemon, isOpposing = true)
setOfferedPokemon(pokemonList = offeredPokemons.toList(), isOpposing = false)
setOfferedPokemon(pokemonList = opposingPokemons.toList(), isOpposing = true)
offeredPokemonMarkings = MarkingsWidget(x + 74.5, y + 21, offeredPokemon, false)
opposingOfferedPokemonMarkings = MarkingsWidget(x + 177.5, y + 21, opposingOfferedPokemon, false)
@@ -284,7 +337,7 @@ class TradeGUI(
if (tradeAnimationProgress > REVERSE_ANIMATION_TICK)
(REVERSE_ANIMATION_TICK + TRADE_ANIMATION_FRAMES - tradeAnimationProgress)
else (tradeAnimationProgress - MODEL_SCALE_BUFFER_FRAMES)
) * TRADE_ANIMATION_HEIGHT
) * TRADE_ANIMATION_HEIGHT
blitk(
matrixStack = matrices,
texture = tradeAnimationResource,
@@ -313,20 +366,56 @@ class TradeGUI(
// Render Model Portraits
var scale = PORTRAIT_SCALE
var scaleOffsetY = PORTRAIT_MODEL_OFFSET_Y
var isShrinking = false
if (tradeProcessing) {
val scaleInterval = if (tradeAnimationProgress > REVERSE_ANIMATION_TICK) (MAX_TRADE_PROGRESS - tradeAnimationProgress) else tradeAnimationProgress
scale = (PORTRAIT_SCALE - (scaleInterval * PORTRAIT_SCALE_MULTIPLIER)).coerceIn(0F, PORTRAIT_SCALE)
scaleOffsetY = (45.0 - (45.0 / PORTRAIT_SCALE) * scale) + PORTRAIT_MODEL_OFFSET_Y
isShrinking = true
}
val shrinkPercentage = if (isShrinking) 1.0F - (scale / PORTRAIT_SCALE) else 0.0F
val myCenterXDest = x + 70 + (PORTRAIT_SIZE / 2.0)
val myCenterYDest = y + 30 + (PORTRAIT_SIZE / 2.0)
val opCenterXDest = x + 145 + (PORTRAIT_SIZE / 2.0)
val opCenterYDest = y + 30 + (PORTRAIT_SIZE / 2.0)
if (scale > 0F) {
context.enableScissor(backgroundX, backgroundY, backgroundX + BASE_BACKGROUND_WIDTH, backgroundY + BASE_BACKGROUND_HEIGHT)
offeredPokemonModel?.baseScale = scale
offeredPokemonModel?.offsetY = scaleOffsetY
offeredPokemonModel?.render(context, mouseX, mouseY, delta)
opposingOfferedPokemonModel?.baseScale = scale
opposingOfferedPokemonModel?.offsetY = scaleOffsetY
opposingOfferedPokemonModel?.render(context, mouseX, mouseY, delta)
offeredPokemonModels.forEachIndexed { i, model ->
val posAndScale = getModelPositionAndScale(i, offeredPokemons.size, x + 70, y + 30)
val originalScale = posAndScale.second
model.baseScale = if (isShrinking) (originalScale / PORTRAIT_SCALE) * scale else originalScale
model.offsetY = if (isShrinking) scaleOffsetY else PORTRAIT_MODEL_OFFSET_Y
val startCenterX = posAndScale.first.first + (PORTRAIT_SIZE / 2.0)
val startCenterY = posAndScale.first.second + (PORTRAIT_SIZE / 2.0)
context.pose().pushPose()
context.pose().translate((myCenterXDest - startCenterX) * shrinkPercentage, (myCenterYDest - startCenterY) * shrinkPercentage, 0.0)
model.render(context, mouseX, mouseY, delta)
context.pose().popPose()
}
opposingOfferedPokemonModels.forEachIndexed { i, model ->
val posAndScale = getModelPositionAndScale(i, opposingPokemons.size, x + 145, y + 30)
val originalScale = posAndScale.second
model.baseScale = if (isShrinking) (originalScale / PORTRAIT_SCALE) * scale else originalScale
model.offsetY = if (isShrinking) scaleOffsetY else PORTRAIT_MODEL_OFFSET_Y
val startCenterX = posAndScale.first.first + (PORTRAIT_SIZE / 2.0)
val startCenterY = posAndScale.first.second + (PORTRAIT_SIZE / 2.0)
context.pose().pushPose()
context.pose().translate((opCenterXDest - startCenterX) * shrinkPercentage, (opCenterYDest - startCenterY) * shrinkPercentage, 0.0)
model.render(context, mouseX, mouseY, delta)
context.pose().popPose()
}
context.disableScissor()
}
@@ -341,8 +430,46 @@ class TradeGUI(
renderInfoLabels(context, x, y)
renderPokemonInfo(offeredPokemon, false, context, x, y, mouseX, mouseY)
renderPokemonInfo(opposingOfferedPokemon, true, context, x, y, mouseX, mouseY)
children().filterIsInstance<PartySlot>().forEach { slot ->
if (slot.isHovered(mouseX, mouseY)) {
val hoveredId = slot.pokemon?.pokemonId
if (slot.isOpposing && slot.hasSelected()) {
opposingPokemons.find { it.uuid == hoveredId }?.let { opposingOfferedPokemon = it }
} else if (!slot.isOpposing) {
val matchingModel = offeredPokemons.find { it.uuid == hoveredId }
if (matchingModel != null) {
offeredPokemon = matchingModel
} else {
val realPokemon = com.cobblemon.mod.common.client.CobblemonClient.storage.party.find { it?.uuid == hoveredId }
if (realPokemon != null) {
offeredPokemon = realPokemon
}
}
}
}
}
val currentOffered = offeredPokemon
if (currentOffered != null) {
renderPokemonInfo(currentOffered, false, context, x, y, mouseX, mouseY)
if (::offeredPokemonMarkings.isInitialized) {
offeredPokemonMarkings.visible = true
if (offeredPokemonMarkings.pokemon != currentOffered) offeredPokemonMarkings.setActivePokemon(currentOffered)
}
} else {
if (::offeredPokemonMarkings.isInitialized) offeredPokemonMarkings.visible = false
}
val currentOpposing = opposingOfferedPokemon
if (currentOpposing != null) {
renderPokemonInfo(currentOpposing, true, context, x, y, mouseX, mouseY)
if (::opposingOfferedPokemonMarkings.isInitialized) {
opposingOfferedPokemonMarkings.visible = true
if (opposingOfferedPokemonMarkings.pokemon != currentOpposing) opposingOfferedPokemonMarkings.setActivePokemon(currentOpposing)
}
} else {
if (::opposingOfferedPokemonMarkings.isInitialized) opposingOfferedPokemonMarkings.visible = false
}
if (trade.acceptedOppositeOffer) {
blitk(
@@ -431,19 +558,25 @@ class TradeGUI(
super.render(context, mouseX, mouseY, delta)
// Item Tooltip
if (offeredPokemon != null && !offeredPokemon!!.heldItemNoCopy().isEmpty) {
val itemX = x + 50
val itemY = y + 125
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, offeredPokemon!!.heldItemNoCopy(), mouseX, mouseY)
val ttOffered = offeredPokemon
if (ttOffered != null) {
// Item Tooltip
if (!ttOffered.heldItemNoCopy().isEmpty) {
val itemX = x + 50
val itemY = y + 125
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, ttOffered.heldItemNoCopy(), mouseX, mouseY)
}
}
if (opposingOfferedPokemon != null && !opposingOfferedPokemon!!.heldItemNoCopy().isEmpty) {
val itemX = x + 227
val itemY = y + 125
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)
val ttOpposing = opposingOfferedPokemon
if (ttOpposing != null) {
if (!ttOpposing.heldItemNoCopy().isEmpty) {
val itemX = x + 227
val itemY = y + 125
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, ttOpposing.heldItemNoCopy(), mouseX, mouseY)
}
}
}
@@ -480,11 +613,13 @@ class TradeGUI(
}
if (tradeAnimationProgress == (MAX_TRADE_PROGRESS + CRY_BUFFER)) {
if (offeredPokemonModel !== null && opposingOfferedPokemonModel !== null) {
offeredPokemonModel!!.state.activeAnimations.clear()
offeredPokemonModel!!.state.addFirstAnimation(setOf("cry"))
opposingOfferedPokemonModel!!.state.activeAnimations.clear()
opposingOfferedPokemonModel!!.state.addFirstAnimation(setOf("cry"))
offeredPokemonModels.forEach {
it.state.activeAnimations.clear()
it.state.addFirstAnimation(setOf("cry"))
}
opposingOfferedPokemonModels.forEach {
it.state.activeAnimations.clear()
it.state.addFirstAnimation(setOf("cry"))
}
}
@@ -515,41 +650,140 @@ class TradeGUI(
override fun renderMenuBackground(context: GuiGraphics) {}
private fun setOfferedPokemon(pokemon: Pokemon?, isOpposing: Boolean = false) {
private fun setOfferedPokemon(pokemonList: List<Pokemon>, isOpposing: Boolean = false) {
protectiveTicks = 20
if (isOpposing) {
val wasEmpty = opposingPokemons.isEmpty()
val currentViewedUuid = if (opposingGridMode) null else opposingOfferedPokemon?.uuid
opposingPokemons.clear()
opposingPokemons.addAll(pokemonList)
val matchingPokemon = opposingPokemons.find { it.uuid == currentViewedUuid }
if (matchingPokemon != null) {
opposingOfferedPokemon = matchingPokemon
if (::opposingOfferedPokemonMarkings.isInitialized) opposingOfferedPokemonMarkings.setActivePokemon(opposingOfferedPokemon)
} else {
opposingOfferedPokemon = opposingPokemons.lastOrNull()
if (::opposingOfferedPokemonMarkings.isInitialized) opposingOfferedPokemonMarkings.setActivePokemon(opposingOfferedPokemon)
if (wasEmpty && opposingOfferedPokemon != null) {
opposingGridMode = false
} else if (!opposingGridMode) {
opposingGridMode = true
}
}
trade.acceptedOppositeOffer = false
} else {
val wasEmpty = offeredPokemons.isEmpty()
val currentViewedUuid = if (myGridMode) null else offeredPokemon?.uuid
offeredPokemons.clear()
offeredPokemons.addAll(pokemonList)
val matchingPokemon = offeredPokemons.find { it.uuid == currentViewedUuid }
if (matchingPokemon != null) {
offeredPokemon = matchingPokemon
if (::offeredPokemonMarkings.isInitialized) offeredPokemonMarkings.setActivePokemon(offeredPokemon)
} else {
offeredPokemon = offeredPokemons.lastOrNull()
if (::offeredPokemonMarkings.isInitialized) offeredPokemonMarkings.setActivePokemon(offeredPokemon)
if (wasEmpty && offeredPokemon != null) {
myGridMode = false
} else if (!myGridMode) {
myGridMode = true
}
}
}
rebuildSelectedModels(isOpposing)
}
private fun rebuildSelectedModels(isOpposing: Boolean) {
val x = (width - BASE_WIDTH) / 2
val y = (height - BASE_HEIGHT) / 2
if (isOpposing) {
opposingOfferedPokemon = pokemon
opposingOfferedPokemonModel = if (pokemon != null) ModelWidget(
pX = x + 145,
pY = y + 30,
pWidth = PORTRAIT_SIZE,
pHeight = PORTRAIT_SIZE,
pokemon = pokemon.asRenderablePokemon(),
baseScale = PORTRAIT_SCALE,
rotationY = 35F,
offsetY = PORTRAIT_MODEL_OFFSET_Y
) else null
if (::opposingOfferedPokemonMarkings.isInitialized) opposingOfferedPokemonMarkings.setActivePokemon(opposingOfferedPokemon)
trade.acceptedOppositeOffer = false
opposingOfferedPokemonModels.clear()
val total = opposingPokemons.size
opposingPokemons.forEachIndexed { i, pk ->
val (pos, currentScale) = getModelPositionAndScale(i, total, x + 145, y + 30)
opposingOfferedPokemonModels.add(ModelWidget(
pX = pos.first, pY = pos.second,
pWidth = PORTRAIT_SIZE, pHeight = PORTRAIT_SIZE,
pokemon = pk.asRenderablePokemon(),
baseScale = currentScale,
rotationY = 35F,
offsetY = PORTRAIT_MODEL_OFFSET_Y
))
}
} else {
offeredPokemon = pokemon
offeredPokemonModel = if (pokemon != null) ModelWidget(
pX = x + 70,
pY = y + 30,
pWidth = PORTRAIT_SIZE,
pHeight = PORTRAIT_SIZE,
pokemon = pokemon.asRenderablePokemon(),
baseScale = PORTRAIT_SCALE,
rotationY = 325F,
offsetY = PORTRAIT_MODEL_OFFSET_Y
) else null
if (::offeredPokemonMarkings.isInitialized) offeredPokemonMarkings.setActivePokemon(offeredPokemon)
offeredPokemonModels.clear()
val total = offeredPokemons.size
offeredPokemons.forEachIndexed { i, pk ->
val (pos, currentScale) = getModelPositionAndScale(i, total, x + 70, y + 30)
offeredPokemonModels.add(ModelWidget(
pX = pos.first, pY = pos.second,
pWidth = PORTRAIT_SIZE, pHeight = PORTRAIT_SIZE,
pokemon = pk.asRenderablePokemon(),
baseScale = currentScale,
rotationY = 325F,
offsetY = PORTRAIT_MODEL_OFFSET_Y
))
}
}
}
private fun getModelPositionAndScale(index: Int, total: Int, baseX: Int, baseY: Int): Pair<Pair<Int, Int>, Float> {
val scale = when (total) {
1 -> PORTRAIT_SCALE
2 -> PORTRAIT_SCALE * 0.75f
3 -> PORTRAIT_SCALE * 0.65f
4 -> PORTRAIT_SCALE * 0.55f
else -> PORTRAIT_SCALE * 0.5f
}
if (total <= 1) {
return Pair(Pair(baseX, baseY), scale)
}
val rowLength1 = when (total) {
2, 3, 4 -> 2
else -> 3
}
val rowLength2 = total - rowLength1
val isBottomRow = index >= rowLength1
val row = if (isBottomRow) 1 else 0
val col = if (isBottomRow) index - rowLength1 else index
val currentRowLength = if (isBottomRow) rowLength2 else rowLength1
val colSpacing = when (total) {
2 -> 35
3 -> 30
4 -> 25
else -> 23
}
val rowSpacing = when (total) {
2, 3 -> 30
4 -> 28
else -> 25
}
val totalWidth = (currentRowLength - 1) * colSpacing
val startX = baseX - (totalWidth / 2)
val totalHeight = if (rowLength2 > 0) rowSpacing else 0
var startY = baseY + 12 - (totalHeight / 2)
if (total >= 4) {
startY += 8
}
val posX = startX + (col * colSpacing)
val posY = startY + (row * rowSpacing)
return Pair(Pair(posX, posY), scale)
}
private fun playSound(soundEvent: SoundEvent): SoundInstance {
val soundInstance = SimpleSoundInstance.forUI(soundEvent, 1.0F)
Minecraft.getInstance().soundManager.play(soundInstance)
@@ -557,10 +791,10 @@ class TradeGUI(
}
private fun cancelTradeSound() {
if (tradeSoundInstance !== null) Minecraft.getInstance().soundManager.stop(tradeSoundInstance)
if (tradeSoundInstance != null) Minecraft.getInstance().soundManager.stop(tradeSoundInstance!!)
}
private fun renderPokemonInfo(pokemon: Pokemon?, isOpposing: Boolean, context: GuiGraphics, x: Int, y: Int, mouseX: Int, mouseY: Int) {
public fun renderPokemonInfo(pokemon: Pokemon?, isOpposing: Boolean, context: GuiGraphics, x: Int, y: Int, mouseX: Int, mouseY: Int) {
if (pokemon != null) {
val matrices = context.pose()
// Level
@@ -1047,4 +1281,4 @@ class TradeGUI(
scale = SCALE
)
}
}
}
@@ -24,9 +24,8 @@ object OpenPCHandler : ClientNetworkPacketHandler<OpenPCPacket> {
party = CobblemonClient.storage.party,
configuration = PCGUIConfiguration(),
openOnBox = packet.box ?: CobblemonClient.lastPcBoxViewed,
unseenWallpapers = packet.unseenWallpapers.toMutableSet(),
silent = packet.silent
unseenWallpapers = packet.unseenWallpapers.toMutableSet()
)
)
}
}
}
@@ -15,9 +15,9 @@ import net.minecraft.client.Minecraft
object TradeAcceptanceChangedHandler : ClientNetworkPacketHandler<TradeAcceptanceChangedPacket> {
override fun handle(packet: TradeAcceptanceChangedPacket, client: Minecraft) {
if (CobblemonClient.trade?.myOffer?.get()?.uuid == packet.pokemonId) {
if (CobblemonClient.trade?.myOffer?.get()?.any { it.uuid == packet.pokemonId } == true) {
CobblemonClient.trade!!.oppositeAcceptedMyOffer.set(packet.accepted)
} else if (CobblemonClient.trade?.oppositeOffer?.get()?.uuid == packet.pokemonId) {
} else if (CobblemonClient.trade?.oppositeOffer?.get()?.any { it.uuid == packet.pokemonId } == true) {
CobblemonClient.trade!!.acceptedOppositeOffer = packet.accepted
}
}
@@ -16,6 +16,6 @@ import net.minecraft.client.Minecraft
object TradeCompletedHandler : ClientNetworkPacketHandler<TradeCompletedPacket> {
override fun handle(packet: TradeCompletedPacket, client: Minecraft) {
val trade = CobblemonClient.trade ?: return
trade.completedEmitter.emit(packet.pokemonId1 to packet.pokemonId2)
trade.completedEmitter.emit(packet.pokemonIds1 to packet.pokemonIds2)
}
}
@@ -20,7 +20,7 @@ import net.minecraft.client.Minecraft
object TradeStartedHandler : ClientNetworkPacketHandler<TradeStartedPacket> {
override fun handle(packet: TradeStartedPacket, client: Minecraft) {
Cobblemon.LOGGER.error("handle trade start from: " + packet.traderId)
Cobblemon.LOGGER.info("Trade started with {}", packet.traderId)
val trade = ClientTrade()
CobblemonClient.trade = trade
Minecraft.getInstance().setScreen(
@@ -18,9 +18,9 @@ object TradeUpdatedHandler : ClientNetworkPacketHandler<TradeUpdatedPacket> {
val trade = CobblemonClient.trade ?: return
if (packet.playerId == Minecraft.getInstance().player?.uuid) {
trade.myOffer.set(packet.pokemon)
trade.myOffer.set(packet.pokemons)
} else {
trade.oppositeOffer.set(packet.pokemon)
trade.oppositeOffer.set(packet.pokemons)
}
trade.oppositeAcceptedMyOffer.set(false)
trade.acceptedOppositeOffer = false
@@ -0,0 +1,98 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.createTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.CobblemonPose
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class CaterpieModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("caterpie")
override val head = getPart("head")
override var portraitScale = 2.1F
override var portraitTranslation = Vec3(-0.55, -1.55, 0.0)
override var profileScale = 0.8F
override var profileTranslation = Vec3(0.0, 0.55, 0.0)
lateinit var sleep: Pose
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var shoulderLeft: CobblemonPose
lateinit var shoulderRight: CobblemonPose
val shoulderOffset = 3
override val cryAnimation = CryProvider { bedrockStateful("caterpie", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("caterpie", "blink")}
sleep = registerPose(
poseType = PoseType.SLEEP,
transformTicks = 10,
animations = arrayOf(bedrock("caterpie", "sleep"))
)
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("caterpie", "ground_idle")
)
)
walk = registerPose(
poseName = "walking",
poseTypes = MOVING_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("caterpie", "ground_walk")
)
)
shoulderLeft = registerPose(
poseType = PoseType.SHOULDER_LEFT,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("caterpie", "ground_idle")
),
transformedParts = arrayOf(
rootPart.createTransformation().addPosition(ModelPartTransformation.X_AXIS, shoulderOffset)
)
)
shoulderRight = registerPose(
poseType = PoseType.SHOULDER_RIGHT,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("caterpie", "ground_idle")
),
transformedParts = arrayOf(
rootPart.createTransformation().addPosition(ModelPartTransformation.X_AXIS, -shoulderOffset)
)
)
}
override fun getFaintAnimation(state: PosableState) = bedrockStateful("caterpie", "faint")
}
@@ -0,0 +1,98 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.createTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class WeedleModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("weedle")
override val head = getPart("head")
override var portraitScale = 2.2F
override var portraitTranslation = Vec3(-0.31, -1.83, 0.0)
override var profileScale = 0.8F
override var profileTranslation = Vec3(0.1, 0.6, 0.0)
lateinit var sleep: Pose
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var shoulderLeft: Pose
lateinit var shoulderRight: Pose
val shoulderOffset = 3.8
val shoulderZOffset = -3
override val cryAnimation = CryProvider { bedrockStateful("weedle", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("weedle", "blink")}
sleep = registerPose(
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("weedle", "sleep"))
)
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("weedle", "ground_idle2")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("weedle", "ground_walk")
)
)
shoulderLeft = registerPose(
poseType = PoseType.SHOULDER_LEFT,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("weedle", "ground_idle")
),
transformedParts = arrayOf(
rootPart.createTransformation().addPosition(ModelPartTransformation.X_AXIS, shoulderOffset),
rootPart.createTransformation().addPosition(ModelPartTransformation.Z_AXIS, shoulderZOffset)
)
)
shoulderRight = registerPose(
poseType = PoseType.SHOULDER_RIGHT,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("weedle", "ground_idle")
),
transformedParts = arrayOf(
rootPart.createTransformation().addPosition(ModelPartTransformation.X_AXIS, -shoulderOffset),
rootPart.createTransformation().addPosition(ModelPartTransformation.Z_AXIS, shoulderZOffset)
)
)
}
override fun getFaintAnimation(state: PosableState) = bedrockStateful("weedle", "faint")
}
@@ -0,0 +1,72 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen4
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.animation.QuadrupedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.QuadrupedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class LuxioModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame, QuadrupedFrame {
override val rootPart = root.registerChildWithAllChildren("luxio")
override val head = getPart("head")
override val foreLeftLeg = getPart("front_leg_left")
override val foreRightLeg = getPart("front_leg_right")
override val hindLeftLeg = getPart("back_leg_left")
override val hindRightLeg = getPart("back_leg_right")
override var portraitScale = 1.8F
override var portraitTranslation = Vec3(-0.6, -0.25, 0.0)
override var profileScale = 0.7F
override var profileTranslation = Vec3(0.0, 0.7, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var sleep: Pose
override fun registerPoses() {
// val blink = quirk { bedrockStateful("luxio", "blink") }
sleep = registerPose(
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("luxio", "sleep"))
)
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.UI_POSES + PoseType.STATIONARY_POSES,
// quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("luxio", "idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
// quirks = arrayOf(blink),
animations = arrayOf(
QuadrupedWalkAnimation(this, periodMultiplier = 1.1F),
singleBoneLook(),
bedrock("luxio", "idle")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("luxio", "faint") else null
}
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen4
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.animation.QuadrupedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.QuadrupedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class LuxrayModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame, QuadrupedFrame {
override val rootPart = root.registerChildWithAllChildren("luxray")
override val head = getPart("head")
override val foreLeftLeg = getPart("front_leg_left")
override val foreRightLeg = getPart("front_leg_right")
override val hindLeftLeg = getPart("back_leg_left")
override val hindRightLeg = getPart("back_leg_right")
override var portraitScale = 1.8F
override var portraitTranslation = Vec3(-0.9, 0.6, 0.0)
override var profileScale = 0.66F
override var profileTranslation = Vec3(0.0, 0.7, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
override fun registerPoses() {
// val blink = quirk { bedrockStateful("luxray", "blink") }
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.UI_POSES + PoseType.STATIONARY_POSES,
// quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("luxray", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
// quirks = arrayOf(blink),
animations = arrayOf(
QuadrupedWalkAnimation(this, periodMultiplier = 1.1F),
bedrock("luxray", "ground_idle"),
singleBoneLook()
// bedrock("luxray", "idle")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("luxray", "faint") else null
}
@@ -0,0 +1,66 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen4
import com.cobblemon.mod.common.client.render.models.blockbench.animation.QuadrupedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.QuadrupedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class ShinxModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame, QuadrupedFrame {
override val rootPart = root.registerChildWithAllChildren("shinx")
override val head = getPart("head")
override val foreLeftLeg = getPart("leg_left")
override val foreRightLeg = getPart("leg_right")
override val hindLeftLeg = getPart("back_leg_left")
override val hindRightLeg = getPart("back_leg_right")
override var portraitScale = 1.8F
override var portraitTranslation = Vec3(-0.6, -0.25, 0.0)
override var profileScale = 0.66F
override var profileTranslation = Vec3(0.0, 0.7, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
override fun registerPoses() {
// val blink = quirk { bedrockStateful("shinx", "blink") }
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.UI_POSES + PoseType.STATIONARY_POSES,
// quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("shinx", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
// quirks = arrayOf(blink),
animations = arrayOf(
QuadrupedWalkAnimation(this, periodMultiplier = 1.1F),
singleBoneLook(),
bedrock("shinx", "ground_idle")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("shinx", "faint") else null
}
@@ -0,0 +1,59 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen5
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.CobblemonPose
import com.cobblemon.mod.common.entity.PoseType
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class JellicentModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("jellicent")
override val head = getPart("head")
override var portraitScale = 1.13F
override var portraitTranslation = Vec3(-0.56, 1.64, 0.0)
override var profileScale = 0.51F
override var profileTranslation = Vec3(0.0, 1.05, 0.0)
lateinit var standing: CobblemonPose
lateinit var walk: CobblemonPose
override fun registerPoses() {
val blink = quirk { bedrockStateful("jellicent", "blink") }
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("jellicent", "render")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("jellicent", "render")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("jellicent", "faint") else null
}
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen6
import com.cobblemon.mod.common.client.render.models.blockbench.animation.BimanualSwingAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.BipedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class ChesnaughtModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame, BimanualFrame {
override val rootPart = root.registerChildWithAllChildren("chesnaught")
override val head = getPart("head")
override val rightArm = getPart("arm_right")
override val leftArm = getPart("arm_left")
override val leftLeg = getPart("leg_left")
override val rightLeg = getPart("leg_right")
override var portraitScale = 2.2F
override var portraitTranslation = Vec3(-1.0, 1.65, 0.0)
override var profileScale = 0.6F
override var profileTranslation = Vec3(0.0, 0.85, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
override val cryAnimation = CryProvider { bedrockStateful("chesnaught", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("chesnaught", "blink")}
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("chesnaught", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
BimanualSwingAnimation(this),
BipedWalkAnimation(this),
bedrock("chesnaught", "ground_idle")
)
)
}
}
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen6
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class ChespinModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame, BimanualFrame {
override val rootPart = root.registerChildWithAllChildren("chespin")
override val head = getPart("head")
override val rightArm = getPart("arm_right")
override val leftArm = getPart("arm_left")
override val leftLeg = getPart("leg_left")
override val rightLeg = getPart("leg_right")
override var portraitScale = 2.03F
override var portraitTranslation = Vec3(-0.12, -0.44, 0.0)
override var profileScale = 0.77F
override var profileTranslation = Vec3(0.0, 0.6, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var sleep: Pose
override val cryAnimation = CryProvider { bedrockStateful("chespin", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("chespin", "blink")}
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("chespin", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("chespin", "ground_walk")
)
)
sleep = registerPose(
poseType = PoseType.SLEEP,
transformTicks = 10,
animations = arrayOf(bedrock("chespin", "sleep"))
)
}
}
@@ -0,0 +1,118 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen6
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.isBattling
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class FroakieModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame, BimanualFrame {
override val rootPart = root.registerChildWithAllChildren("froakie")
override val head = getPart("head")
override val rightArm = getPart("arm_right")
override val leftArm = getPart("arm_left")
override val rightLeg = getPart("leg_right")
override val leftLeg = getPart("leg_left")
override var portraitScale = 1.86F
override var portraitTranslation = Vec3(-0.25, -0.51, 0.0)
override var profileScale = 0.75F
override var profileTranslation = Vec3(0.0, 0.56, 0.0)
lateinit var sleep: Pose
lateinit var standing: Pose
lateinit var float: Pose
lateinit var swim: Pose
lateinit var walk: Pose
lateinit var battleidle: Pose
override val cryAnimation = CryProvider { bedrockStateful("froakie", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("froakie", "blink")}
sleep = registerPose(
poseType = PoseType.SLEEP,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(bedrock("froakie", "sleep"))
)
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.UI_POSES + PoseType.STATIONARY_POSES - PoseType.FLOAT,
transformTicks = 10,
quirks = arrayOf(blink),
condition = { !it.isBattling },
animations = arrayOf(
singleBoneLook(),
bedrock("froakie", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseType = PoseType.WALK,
transformTicks = 10,
quirks = arrayOf(blink),
condition = { !it.isBattling },
animations = arrayOf(
singleBoneLook(),
bedrock("froakie", "ground_walk")
)
)
float = registerPose(
poseName = "swim_idle",
poseTypes = setOf(PoseType.FLOAT, PoseType.HOVER),
transformTicks = 10,
quirks = arrayOf(blink),
condition = { !it.isBattling },
animations = arrayOf(
singleBoneLook(),
bedrock("froakie", "water_idle")
)
)
swim = registerPose(
poseName = "swim",
poseTypes = setOf(PoseType.SWIM, PoseType.FLY),
transformTicks = 10,
quirks = arrayOf(blink),
condition = { !it.isBattling },
animations = arrayOf(
singleBoneLook(),
bedrock("froakie", "water_swim")
)
)
battleidle = registerPose(
poseName = "battle_idle",
poseTypes = PoseType.STATIONARY_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
condition = { it.isBattling },
animations = arrayOf(
singleBoneLook(),
bedrock("froakie", "battle_idle")
)
)
}
override fun getFaintAnimation(state: PosableState) = if (state.isPosedIn(standing, walk, battleidle, swim, float, sleep)) bedrockStateful("froakie", "faint") else null
}
@@ -0,0 +1,118 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen6
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.isBattling
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class FrogadierModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame, BimanualFrame {
override val rootPart = root.registerChildWithAllChildren("frogadier")
override val head = getPart("head")
override val rightArm = getPart("arm_right")
override val leftArm = getPart("arm_left")
override val rightLeg = getPart("leg_right")
override val leftLeg = getPart("leg_left")
override var portraitScale = 1.9F
override var portraitTranslation = Vec3(-0.1, 1.4, 0.0)
override var profileScale = 0.7F
override var profileTranslation = Vec3(0.0, 0.7, 0.0)
lateinit var sleep: Pose
lateinit var standing: Pose
lateinit var float: Pose
lateinit var swim: Pose
lateinit var walk: Pose
lateinit var battleidle: Pose
override val cryAnimation = CryProvider { bedrockStateful("frogadier", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("frogadier", "blink")}
sleep = registerPose(
poseType = PoseType.SLEEP,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(bedrock("frogadier", "sleep"))
)
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.UI_POSES + PoseType.STATIONARY_POSES - PoseType.FLOAT,
transformTicks = 10,
quirks = arrayOf(blink),
condition = { !it.isBattling },
animations = arrayOf(
singleBoneLook(),
bedrock("frogadier", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseType = PoseType.WALK,
transformTicks = 10,
quirks = arrayOf(blink),
condition = { !it.isBattling },
animations = arrayOf(
singleBoneLook(),
bedrock("frogadier", "ground_walk")
)
)
float = registerPose(
poseName = "swim_idle",
poseTypes = setOf(PoseType.FLOAT, PoseType.HOVER),
transformTicks = 10,
quirks = arrayOf(blink),
condition = { !it.isBattling },
animations = arrayOf(
singleBoneLook(),
bedrock("frogadier", "water_idle")
)
)
swim = registerPose(
poseName = "swim",
poseTypes = setOf(PoseType.SWIM, PoseType.FLY),
transformTicks = 10,
quirks = arrayOf(blink),
condition = { !it.isBattling },
animations = arrayOf(
singleBoneLook(),
bedrock("frogadier", "water_swim")
)
)
battleidle = registerPose(
poseName = "battle_idle",
poseTypes = PoseType.STATIONARY_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
condition = { it.isBattling },
animations = arrayOf(
singleBoneLook(),
bedrock("frogadier", "battle_idle")
)
)
}
override fun getFaintAnimation(state: PosableState) = if (state.isPosedIn(standing, walk, battleidle, swim, float, sleep)) bedrockStateful("frogadier", "faint") else null
}
@@ -0,0 +1,142 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen6
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.createTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.isBattling
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class GreninjaModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame, BimanualFrame {
override val rootPart = root.registerChildWithAllChildren("greninja")
override val head = getPart("head")
override val rightArm = getPart("arm_right")
override val leftArm = getPart("arm_left")
override val rightLeg = getPart("leg_right")
override val leftLeg = getPart("leg_left")
val shurikenright = getPart("hand_shuriken_right")
val shurikenleft = getPart("hand_shuriken_left")
override var portraitScale = 2.2F
override var portraitTranslation = Vec3(-0.1, 1.34, 0.0)
override var profileScale = 0.7F
override var profileTranslation = Vec3(0.0, 0.7, 0.0)
lateinit var sleep: Pose
lateinit var standing: Pose
lateinit var float: Pose
lateinit var swim: Pose
lateinit var walk: Pose
lateinit var battleidle: Pose
override val cryAnimation = CryProvider { bedrockStateful("greninja", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("greninja", "blink")}
sleep = registerPose(
poseType = PoseType.SLEEP,
transformTicks = 10,
transformedParts = arrayOf(
shurikenleft.createTransformation().withVisibility(visibility = false),
shurikenright.createTransformation().withVisibility(visibility = false)
),
animations = arrayOf(bedrock("greninja", "sleep"))
)
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.UI_POSES + PoseType.STATIONARY_POSES - PoseType.FLOAT,
transformTicks = 10,
quirks = arrayOf(blink),
condition = { !it.isBattling },
transformedParts = arrayOf(
shurikenleft.createTransformation().withVisibility(visibility = false),
shurikenright.createTransformation().withVisibility(visibility = false)
),
animations = arrayOf(
singleBoneLook(),
bedrock("greninja", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseType = PoseType.WALK,
transformTicks = 10,
quirks = arrayOf(blink),
transformedParts = arrayOf(
shurikenleft.createTransformation().withVisibility(visibility = false),
shurikenright.createTransformation().withVisibility(visibility = false)
),
animations = arrayOf(
singleBoneLook(),
bedrock("greninja", "ground_walk")
)
)
float = registerPose(
poseName = "swim_idle",
poseTypes = setOf(PoseType.FLOAT, PoseType.HOVER),
transformTicks = 10,
quirks = arrayOf(blink),
condition = { !it.isBattling },
transformedParts = arrayOf(
shurikenleft.createTransformation().withVisibility(visibility = false),
shurikenright.createTransformation().withVisibility(visibility = false)
),
animations = arrayOf(
singleBoneLook(),
bedrock("greninja", "water_idle")
)
)
swim = registerPose(
poseName = "swim",
poseTypes = setOf(PoseType.SWIM, PoseType.FLY),
transformTicks = 10,
quirks = arrayOf(blink),
condition = { !it.isBattling },
transformedParts = arrayOf(
shurikenleft.createTransformation().withVisibility(visibility = false),
shurikenright.createTransformation().withVisibility(visibility = false)
),
animations = arrayOf(
singleBoneLook(),
bedrock("greninja", "water_swim")
)
)
battleidle = registerPose(
poseName = "battle_idle",
poseTypes = PoseType.STATIONARY_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
condition = { it.isBattling },
transformedParts = arrayOf(
shurikenleft.createTransformation().withVisibility(visibility = false),
shurikenright.createTransformation().withVisibility(visibility = false)
),
animations = arrayOf(
singleBoneLook(),
bedrock("greninja", "battle_idle")
)
)
}
override fun getFaintAnimation(state: PosableState) = if (state.isPosedIn(standing, walk, battleidle, swim, float, sleep)) bedrockStateful("greninja", "faint") else null
}
@@ -0,0 +1,73 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen6
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class QuilladinModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame, BimanualFrame {
override val rootPart = root.registerChildWithAllChildren("quilladin")
override val head = getPart("torso")
override val rightArm = getPart("arm_right")
override val leftArm = getPart("arm_left")
override val leftLeg = getPart("leg_left")
override val rightLeg = getPart("leg_right")
override var portraitScale = 2F
override var portraitTranslation = Vec3(-0.3, 0.1, 0.0)
override var profileScale = 0.7F
override var profileTranslation = Vec3(0.0, 0.7, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var sleep: Pose
override val cryAnimation = CryProvider { bedrockStateful("quilladin", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("quilladin", "blink")}
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("quilladin", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("quilladin", "ground_walk")
)
)
sleep = registerPose(
poseType = PoseType.SLEEP,
transformTicks = 10,
animations = arrayOf(bedrock("quilladin", "sleep"))
)
}
}
@@ -29,7 +29,7 @@ class VivillonModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, B
override var portraitTranslation = Vec3(-0.3, 0.2, 0.0)
override var profileScale = 0.7F
override var profileTranslation = Vec3(0.0, 0.8, 0.0)
override var profileTranslation = Vec3(0.1, 0.8, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen7
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class CrabominableModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("crabominable")
override val head = getPart("head")
override var portraitScale = 1.0F
override var portraitTranslation = Vec3(-0.55, 1.65, 0.0)
override var profileScale = 0.3F
override var profileTranslation = Vec3(0.0, 1.2, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
override fun registerPoses() {
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + UI_POSES,
animations = arrayOf(
singleBoneLook(),
bedrock("crabominable", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
animations = arrayOf(
singleBoneLook(),
bedrock("crabominable", "ground_idle"),
bedrock("crabominable", "ground_walk")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("crabominable", "faint") else null
}
@@ -220,8 +220,10 @@ object VaryingModelRepository {
inbuilt("squirtle", ::SquirtleModel)
inbuilt("wartortle", ::WartortleModel)
inbuilt("caterpie", ::CaterpieModel)
inbuilt("metapod", ::MetapodModel)
inbuilt("butterfree", ::ButterfreeModel)
inbuilt("weedle", ::WeedleModel)
inbuilt("beedrill", ::BeedrillModel)
inbuilt("raticate", ::RaticateModel)
inbuilt("raticate_alolan", ::RaticateAlolanModel)
@@ -309,6 +311,9 @@ object VaryingModelRepository {
inbuilt("morelull", :: MorelullModel)
inbuilt("shiinotic", :: ShiinoticModel)
inbuilt("spiritomb", :: SpiritombModel)
inbuilt("chespin", :: ChespinModel)
inbuilt("quilladin", :: QuilladinModel)
inbuilt("chesnaught", :: ChesnaughtModel)
inbuilt("pineco", :: PinecoModel)
inbuilt("lotad", :: LotadModel)
inbuilt("lombre", :: LombreModel)
@@ -336,6 +341,9 @@ object VaryingModelRepository {
inbuilt("walkingwake", ::WalkingwakeModel)
inbuilt("ironleaves", ::IronleavesModel)
inbuilt("miltank", ::MiltankModel)
inbuilt("froakie", ::FroakieModel)
inbuilt("frogadier", ::FrogadierModel)
inbuilt("greninja", ::GreninjaModel)
inbuilt("grookey", ::GrookeyModel)
inbuilt("thwackey", ::ThwackeyModel)
inbuilt("rillaboom", ::RillaboomModel)
@@ -388,6 +396,9 @@ object VaryingModelRepository {
inbuilt("hoothoot", ::HoothootModel)
inbuilt("wingull", ::WingullModel)
inbuilt("pelipper", ::PelipperModel)
inbuilt("shinx", ::ShinxModel)
inbuilt("luxio", ::LuxioModel)
inbuilt("luxray", ::LuxrayModel)
inbuilt("numel", ::NumelModel)
inbuilt("roggenrola", ::RoggenrolaModel)
inbuilt("boldore", ::BoldoreModel)
@@ -444,6 +455,7 @@ object VaryingModelRepository {
inbuilt("bayleef", ::BayleefModel)
inbuilt("meganium", ::MeganiumModel)
inbuilt("crabrawler", ::CrabrawlerModel)
inbuilt("crabominable", ::CrabominableModel)
inbuilt("wimpod", ::WimpodModel)
inbuilt("golisopod", ::GolisopodModel)
inbuilt("ralts", ::RaltsModel)
@@ -499,6 +511,7 @@ object VaryingModelRepository {
inbuilt("woobat", ::WoobatModel)
inbuilt("swoobat", ::SwoobatModel)
inbuilt("frillish", ::FrillishModel)
inbuilt("jellicent", ::JellicentModel)
inbuilt("cubchoo", ::CubchooModel)
inbuilt("beartic", ::BearticModel)
inbuilt("dreepy", ::DreepyModel)
@@ -319,37 +319,32 @@ class PokemonRenderer(
val clientDelegate = entity.delegate as PokemonClientDelegate
val pokemonPosition = entity.position().add(0.0, entity.bbHeight / 2.0 * clientDelegate.activeSendoutScale.toDouble(), 0.0)
val recallBallOrigin = (beamTarget as? Player)?.let { RecallArmAnimationTracker.get(it.uuid)?.beamOrigin }
val beamSourcePosition = when {
// cover the recall ball origin presence
recallBallOrigin != null -> recallBallOrigin
// cover the pokeball catching a specimen
beamTarget is EmptyPokeBallEntity -> (beamTarget.delegate as PokeBallPosableState)
.locatorStates["beam"]
?.transformPosition(Vec3(0.0, 0.2, 0.45)) ?: beamTarget.position()
// player recall
beamTarget.uuid == Minecraft.getInstance().player?.uuid -> {
var beamSourcePosition = if (recallBallOrigin != null) {
recallBallOrigin
} else if (beamTarget is EmptyPokeBallEntity) {
(beamTarget.delegate as PokeBallPosableState).locatorStates["beam"]?.transformPosition(Vec3(0.0, 0.2, 0.45)) ?: beamTarget.position()
} else {
if (beamTarget.uuid == Minecraft.getInstance().player?.uuid) {
val lookVec = beamTarget.lookAngle.yRot((PI / 2).toFloat()).multiply(1.0, 0.0, 1.0).normalize()
beamTarget.getEyePosition(partialTicks).subtract(0.0, 0.4, 0.0).subtract(lookVec.scale(0.3))
}
// npc recall
beamTarget is NPCEntity -> {
} else if (beamTarget is NPCEntity) {
val npcDelegate = beamTarget.delegate as NPCClientDelegate
val baseScale = beamTarget.renderScale * beamTarget.hitboxScale
(npcDelegate.locatorStates["beam"]?.getOrigin()?.scale(baseScale.toDouble()))
?: beamTarget.position().add(0.0, (beamTarget.bbHeight / 2.0) * baseScale, 0.0)
}
clientDelegate.sendOutPosition != null -> clientDelegate.sendOutPosition!!
// ???
else -> {
} else {
val lookVec = beamTarget.lookAngle.yRot((PI / 2 - (beamTarget.visualRotationYInDegrees - beamTarget.xRot).toRadians()).toFloat()).multiply(1.0, 0.0, 1.0).normalize()
beamTarget.getEyePosition(partialTicks).subtract(0.0, 0.7, 0.0).subtract(lookVec.scale(0.4))
}
}
if (clientDelegate.sendOutPosition != null) {
beamSourcePosition = clientDelegate.sendOutPosition!!
}
if (beamSourcePosition.distanceTo(pokemonPosition) > 20) return
if (beamSourcePosition.distanceTo(pokemonPosition) > 20) {
return
}
var newOffset = offset.scale(2.0)
// the further away the source position is, the smaller the newOffset should be. Max distance is 20 blocks
val distance = beamSourcePosition.distanceTo(entity.position())
@@ -14,12 +14,12 @@ import com.cobblemon.mod.common.pokemon.Pokemon
import java.util.UUID
class ClientTrade {
var myOffer = SettableObservable<Pokemon?>(null)
var oppositeOffer = SettableObservable<Pokemon?>(null)
var myOffer = SettableObservable<List<Pokemon>>(emptyList())
var oppositeOffer = SettableObservable<List<Pokemon>>(emptyList())
var tradeProcessing = SettableObservable(Pair(false, false))
var oppositeAcceptedMyOffer = SettableObservable(false)
var acceptedOppositeOffer = false
var completedEmitter = SimpleObservable<Pair<UUID, UUID>>()
var completedEmitter = SimpleObservable<Pair<List<UUID>, List<UUID>>>()
var cancelEmitter = SimpleObservable<Unit>()
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.command
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
import com.cobblemon.mod.common.api.storage.pc.link.PCLinkManager
import com.cobblemon.mod.common.api.storage.pc.link.PermissiblePcLink
@@ -53,8 +54,8 @@ object PcCommand {
throw SimpleCommandExceptionType(lang("command.pc.invalid-box", box, pc.boxes.size).red()).create()
}
PCLinkManager.addLink(PermissiblePcLink(pc, player, CobblemonPermissions.PC))
OpenPCPacket(pc, box = if (box != null) (box - 1) else null, silent = true).sendToPlayer(player)
OpenPCPacket(pc, box = if (box != null) (box - 1) else null).sendToPlayer(player)
return Command.SINGLE_SUCCESS
}
}
}
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.command.argument
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.google.common.base.Splitter
import com.mojang.brigadier.StringReader
import com.mojang.brigadier.arguments.ArgumentType
@@ -39,7 +38,7 @@ class FormArgumentType : ArgumentType<FormData> {
}
speciesBeginning++
val speciesToken = reader.string.substring(speciesBeginning, formTokenBeginning)
val species = PokemonSpecies.getByIdentifier(speciesToken.asIdentifierDefaultingNamespace()) ?: PokemonSpecies.species.first()
val species = PokemonSpecies.getByName(speciesToken) ?: PokemonSpecies.species.first()
val result = species.forms.union(listOf(species.standardForm)).first { it.formOnlyShowdownId().lowercase() == formToken }
return result
}
@@ -58,7 +57,7 @@ class FormArgumentType : ArgumentType<FormData> {
curIdx++
val speciesToken = builder.input.substring(curIdx, builder.start - 1)
val species = PokemonSpecies.getByIdentifier(speciesToken.asIdentifierDefaultingNamespace()) ?: PokemonSpecies.species.first()
val species = PokemonSpecies.getByName(speciesToken) ?: PokemonSpecies.species.first()
val forms = species.forms.union(listOf(species.standardForm)).filter { it.formOnlyShowdownId().lowercase().startsWith(formToken) }
forms.forEach { str ->
builder.suggest(str.formOnlyShowdownId())
@@ -222,16 +222,16 @@ class CobblemonConfig {
@CobblemonConfigField(Category.Spawning, lang = "shiny_rate", SERVER)
var shinyRate = 8192F
@CobblemonConfigField(Category.Spawning, lang = "fossil_machine_alpha_chance", SERVER)
@CobblemonConfigField(Category.Pokemon, lang = "fossil_machine_alpha_chance", SERVER)
var fossilMachineAlphaChance = 20
@CobblemonConfigField(Category.Spawning, lang = "fossil_machine_shiny_chance", SERVER)
@CobblemonConfigField(Category.Pokemon, lang = "fossil_machine_shiny_chance", SERVER)
var fossilMachineShinyChance = 100
@CobblemonConfigField(Category.Spawning, lang = "honey_slather_alpha_chance", SERVER)
@CobblemonConfigField(Category.Pokemon, lang = "honey_slather_alpha_chance", SERVER)
var honeySlatherAlphaChance = 100
@CobblemonConfigField(Category.Spawning, lang = "honey_slather_shiny_chance", SERVER)
@CobblemonConfigField(Category.Pokemon, lang = "honey_slather_shiny_chance", SERVER)
var honeySlatherShinyChance = 4000
@CobblemonConfigField(Category.Pokemon, lang = "shiny_notice_particles_distance", CLIENT)
@@ -392,21 +392,6 @@ class CobblemonConfig {
@CobblemonConfigField(Category.Spawning, lang = "data_monitor_shiny_rate", SERVER)
var monitorShinyRate = 200
@CobblemonConfigField(Category.World, lang = "type_gem_max_connected", SERVER)
var typeGemMaxConnected = 16
@CobblemonConfigField(Category.World, lang = "type_gem_min_growth", SERVER)
var typeGemMinGrowth = 8
@CobblemonConfigField(Category.World, lang = "type_gem_growth_chance", SERVER)
var typeGemGrowthChance = 0.1f
@CobblemonConfigField(Category.World, lang = "type_gem_initial_expansion_cycles", SERVER)
var typeGemInitialExpansionCycles = 100
@CobblemonConfigField(Category.World, lang = "type_gem_tick_chance", SERVER)
var typeGemTickChance = 1.0f
fun clone(): CobblemonConfig {
val newConfig = CobblemonConfig()
CobblemonConfig::class.memberProperties.forEach { property ->
@@ -30,7 +30,7 @@ object CobblemonSchemas {
private val SAME: (Int, Schema) -> Schema = ::Schema
private val RESULT: DataFixerBuilder.Result = this.create()
const val DATA_VERSION = 3
const val DATA_VERSION = 2
/**
* The Cobblemon [DataFixer].
@@ -85,8 +85,6 @@ object CobblemonSchemas {
builder.addFixer(RaisedPPStagesFix(schema1))
val schema2 = builder.addSchema(2, SAME)
builder.addFixer(HypertrainIVFix(schema2))
val schema3 = builder.addSchema(3, SAME)
builder.addFixer(TypeGemGrowthTypeFix(schema3))
}
private class CobblemonDataFixerCodec<R>(private val baseCodec: Codec<R>, private val typeReference: TypeReference) : Codec<R> {
@@ -1,101 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.datafixer.fix
import com.mojang.datafixers.DSL
import com.mojang.datafixers.DataFix
import com.mojang.datafixers.TypeRewriteRule
import com.mojang.datafixers.schemas.Schema
import com.mojang.serialization.Dynamic
import net.minecraft.util.datafix.fixes.References
/**
* Merges the old `should_grow` and `stunted` type gem cluster properties into `growth_type`.
*/
class TypeGemGrowthTypeFix(outputSchema: Schema) : DataFix(outputSchema, false) {
override fun makeRule(): TypeRewriteRule {
return fixTypeEverywhereTyped("TypeGemGrowthTypeFix", inputSchema.getType(References.CHUNK)) { chunk ->
chunk.update(DSL.remainderFinder(), ::fixChunk)
}
}
private fun fixChunk(chunk: Dynamic<*>): Dynamic<*> {
return chunk.update("sections") { sections ->
sections.createList(sections.asStream().map(::fixSection))
}
}
private fun fixSection(section: Dynamic<*>): Dynamic<*> {
return section.update("block_states") { blockStates ->
blockStates.update("palette") { palette ->
palette.createList(palette.asStream().map(::fixPaletteEntry))
}
}
}
private fun fixPaletteEntry(entry: Dynamic<*>): Dynamic<*> {
if (entry.get("Name").asString("") !in CLUSTER_IDS) return entry
return entry.update("Properties") { properties ->
if (properties.get(GROWTH_TYPE).result().isPresent) return@update properties
val shouldGrow = properties.get(SHOULD_GROW).asString(OLD_SHOULD_GROW_DEFAULT) == "true"
val stunted = properties.get(STUNTED).asString(OLD_STUNTED_DEFAULT) == "true"
properties
.remove(SHOULD_GROW)
.remove(STUNTED)
.set(GROWTH_TYPE, properties.createString(growthTypeFor(shouldGrow, stunted)))
}
}
private fun growthTypeFor(shouldGrow: Boolean, stunted: Boolean): String {
return when {
stunted -> GROWTH_TYPE_STUNTED
shouldGrow -> GROWTH_TYPE_FULL
else -> GROWTH_TYPE_NONE
}
}
companion object {
private const val SHOULD_GROW = "should_grow"
private const val STUNTED = "stunted"
private const val GROWTH_TYPE = "growth_type"
private const val OLD_SHOULD_GROW_DEFAULT = "true"
private const val OLD_STUNTED_DEFAULT = "false"
private const val GROWTH_TYPE_FULL = "full"
private const val GROWTH_TYPE_STUNTED = "stunted"
private const val GROWTH_TYPE_NONE = "none"
// ID's as they were when this fix was made.
private val CLUSTER_IDS = setOf(
"cobblemon:normal_gem_cluster",
"cobblemon:fire_gem_cluster",
"cobblemon:water_gem_cluster",
"cobblemon:electric_gem_cluster",
"cobblemon:grass_gem_cluster",
"cobblemon:ice_gem_cluster",
"cobblemon:fighting_gem_cluster",
"cobblemon:poison_gem_cluster",
"cobblemon:ground_gem_cluster",
"cobblemon:flying_gem_cluster",
"cobblemon:psychic_gem_cluster",
"cobblemon:bug_gem_cluster",
"cobblemon:rock_gem_cluster",
"cobblemon:ghost_gem_cluster",
"cobblemon:dragon_gem_cluster",
"cobblemon:dark_gem_cluster",
"cobblemon:steel_gem_cluster",
"cobblemon:fairy_gem_cluster"
)
}
}
@@ -269,7 +269,6 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
attemptCatch(pokemonEntity)
},
ifCanceled = {
captureFuture.complete(false)
drop()
return
}
@@ -1192,7 +1192,7 @@ open class PokemonEntity(
itemStack.hurtAndBreak(1, player, EquipmentSlot.MAINHAND)
return InteractionResult.SUCCESS
} else if (itemStack.`is`(Items.BOWL)) {
if (pokemon.aspects.any { it.contains("mooshtank") && !it.contains("mooshtank-false") }) {
if (pokemon.aspects.any { it.contains("mooshtank") }) {
player.playSound(SoundEvents.MOOSHROOM_MILK, 1.0f, 1.0f)
// if the Mooshtank ate a Flower beforehand
if (pokemon.lastFlowerFed != ItemStack.EMPTY && pokemon.aspects.any { it.contains("mooshtank-brown") }) {
@@ -1234,8 +1234,7 @@ open class PokemonEntity(
) {
if (pokemon.aspects.any { it.contains("mooshtank-brown") }) {
player.playSound(SoundEvents.MOOSHROOM_EAT, 1.0f, 1.0f)
pokemon.lastFlowerFed = itemStack.copyWithCount(1)
if (!player.isCreative && !level().isClientSide) itemStack.shrink(1)
pokemon.lastFlowerFed = itemStack
return InteractionResult.sidedSuccess(level().isClientSide)
}
} else if (!player.isShiftKeyDown && StashHandler.interactMob(player, pokemon, itemStack)) {
@@ -2742,4 +2741,4 @@ open class PokemonEntity(
}
fun hasRider() = this.passengers.any { it is LivingEntity }
}
}
@@ -31,6 +31,7 @@ import net.minecraft.world.item.BlockItem
import com.cobblemon.mod.common.platform.events.ServerPlayerEvent
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.item.GemItem
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.util.effectiveName
import com.cobblemon.mod.common.util.getPlayer
@@ -164,26 +165,21 @@ object AdvancementHandler : EventHandler {
}
fun onTradeCompleted(event : TradeEvent.Post) {
val player1 = event.tradeParticipant1Pokemon.getOwnerPlayer()
val player2 = event.tradeParticipant2Pokemon.getOwnerPlayer()
if (player1 != null) {
CobblemonCriteria.TRADE_POKEMON.trigger(player1, TradePokemonContext(event.tradeParticipant1Pokemon, event.tradeParticipant2Pokemon))
val playerData = Cobblemon.playerDataManager.getGenericData(player1)
val advancementData = playerData.advancementData
advancementData.updateTotalTradedCount()
advancementData.updateAspectsCollected(player1, event.tradeParticipant2Pokemon)
CobblemonCriteria.COLLECT_ASPECT.trigger(player1, advancementData.aspectsCollected)
Cobblemon.playerDataManager.saveSingle(playerData, PlayerInstancedDataStoreTypes.GENERAL)
}
if (player2 != null) {
CobblemonCriteria.TRADE_POKEMON.trigger(player2, TradePokemonContext(event.tradeParticipant2Pokemon, event.tradeParticipant1Pokemon))
val playerData = Cobblemon.playerDataManager.getGenericData(player2)
val advancementData = playerData.advancementData
advancementData.updateTotalTradedCount()
advancementData.updateAspectsCollected(player2, event.tradeParticipant1Pokemon)
CobblemonCriteria.COLLECT_ASPECT.trigger(player2, advancementData.aspectsCollected)
Cobblemon.playerDataManager.saveSingle(playerData, PlayerInstancedDataStoreTypes.GENERAL)
onTradeCompleted(event.tradeParticipant1Pokemon, event.tradeParticipant2Pokemon)
onTradeCompleted(event.tradeParticipant2Pokemon, event.tradeParticipant1Pokemon)
}
private fun onTradeCompleted(received: List<Pokemon>, traded: List<Pokemon>) {
val player = received.firstOrNull()?.getOwnerPlayer() ?: return
received.forEach { receivedPokemon ->
traded.forEach { tradedPokemon -> CobblemonCriteria.TRADE_POKEMON.trigger(player, TradePokemonContext(tradedPokemon, receivedPokemon)) }
}
val playerData = Cobblemon.playerDataManager.getGenericData(player)
val advancementData = playerData.advancementData
advancementData.updateTotalTradedCount()
received.forEach { advancementData.updateAspectsCollected(player, it) }
CobblemonCriteria.COLLECT_ASPECT.trigger(player, advancementData.aspectsCollected)
Cobblemon.playerDataManager.saveSingle(playerData, PlayerInstancedDataStoreTypes.GENERAL)
}
/**
@@ -26,7 +26,6 @@ object CallbackHandler {
CobblemonEvents.POKEMON_CAPTURED.subscribe { CobblemonCallbacks.run(cobblemonResource("pokemon_captured"), it.context) }
CobblemonEvents.POKEMON_ENTITY_SPAWN.subscribe { entity -> CobblemonCallbacks.run(cobblemonResource("pokemon_entity_spawn"), mutableMapOf<String, MoValue>("pokemon_entity" to entity.entity.asMoLangValue()), functions = moLangFunctionMap("pokemon" to { entity.entity.pokemon.struct })) }
CobblemonEvents.BATTLE_VICTORY.subscribe { CobblemonCallbacks.run(cobblemonResource("battle_victory"), it.context) }
CobblemonEvents.BATTLE_ENDED.subscribe { CobblemonCallbacks.run(cobblemonResource("battle_ended"), it.context) }
CobblemonEvents.POKEDEX_DATA_CHANGED_PRE.subscribe { CobblemonCallbacks.run(cobblemonResource("pokedex_data_changed_pre"), it.getContext(), it.functions) }
CobblemonEvents.POKEDEX_DATA_CHANGED_POST.subscribe { CobblemonCallbacks.run(cobblemonResource("pokedex_data_changed_post"), it.getContext()) }
CobblemonEvents.FORME_CHANGE.subscribe { CobblemonCallbacks.run(cobblemonResource("forme_change"), it.context) }
@@ -110,8 +110,8 @@ object StatHandler : EventHandler {
}
fun onTradeCompleted(event : TradeEvent.Post) {
event.tradeParticipant1Pokemon.getOwnerPlayer()?.awardStat(getStat(Cobblemon.statistics.TRADED))
event.tradeParticipant2Pokemon.getOwnerPlayer()?.awardStat(getStat(Cobblemon.statistics.TRADED))
event.tradeParticipant1Pokemon.firstOrNull()?.getOwnerPlayer()?.awardStat(getStat(Cobblemon.statistics.TRADED))
event.tradeParticipant2Pokemon.firstOrNull()?.getOwnerPlayer()?.awardStat(getStat(Cobblemon.statistics.TRADED))
}
fun onFossilRevived(event : FossilRevivedEvent) {
@@ -35,10 +35,14 @@ class MedicinalLeekItem(block: MedicinalLeekBlock, settings: Properties) : ItemN
val blockHitResult2 = blockHitResult.withPosition(blockHitResult.blockPos.above())
val placeResult = this.place(BlockPlaceContext(UseOnContext(user, hand, blockHitResult2)))
val stack = user.getItemInHand(hand)
// If the leek couldn't be planted, fall back to the default item behaviour so its food component lets it be eaten
if (!placeResult.consumesAction()) {
return super.use(world, user, hand)
// This will always be true but just in case we change it down the line.
/*
if (!placeResult.isAccepted && this.isFood && user.canConsume(this.foodComponent?.isAlwaysEdible == true)) {
user.setCurrentHand(hand)
return TypedInteractionResult.consume(stack)
}
*/
return InteractionResultHolder(placeResult, stack)
}
@@ -19,13 +19,14 @@ import net.minecraft.world.level.Level
class WearableHatItem(override val name: String): CobblemonItem(Properties()), WearableItem {
override fun use(level: Level, player: Player, usedHand: InteractionHand): InteractionResultHolder<ItemStack> {
val itemStack = player.getItemInHand(usedHand)
val hitPokemon = player.traceFirstEntityCollision(
entityClass = PokemonEntity::class.java,
maxDistance = player.entityInteractionRange().toFloat()
)
// If the player is looking at a Pokémon within range, block the ability to swap the item onto their head
if (player.isCrouching && hitPokemon != null && hitPokemon.isOwnedBy(player)) return InteractionResultHolder.fail(player.getItemInHand(usedHand))
return this.swapStackWithEquipmentSlot(level, player, usedHand)
return if (player.isCrouching && hitPokemon != null && hitPokemon.isOwnedBy(player)) {
InteractionResultHolder.fail(itemStack)
} else this.swapWithEquipmentSlot(this, level, player, usedHand)
}
}
}
@@ -10,18 +10,10 @@ package com.cobblemon.mod.common.item
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.resources.ResourceLocation
import net.minecraft.stats.Stats
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResultHolder
import net.minecraft.world.entity.EquipmentSlot
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Equipable
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.enchantment.EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE
import net.minecraft.world.item.enchantment.EnchantmentHelper
import net.minecraft.world.level.Level
interface WearableItem : Equipable {
interface WearableItem: Equipable {
val name: String
companion object {
@@ -31,43 +23,5 @@ interface WearableItem : Equipable {
fun getModel3d(): ResourceLocation = cobblemonResource("${MODEL_PATH}/${this.name}")
fun getModel2d(): ResourceLocation = cobblemonResource(this.name)
override fun getEquipmentSlot(): EquipmentSlot {
return EquipmentSlot.HEAD
}
/**
* Swap stack with equipment slot
* Copied from vanilla, modified to account for stackable items
* @see Equipable.swapWithEquipmentSlot
*
* @param item
* @param level
* @param player
* @param hand
* @return the interaction result holder
*/
fun swapStackWithEquipmentSlot(
level: Level,
player: Player,
hand: InteractionHand
): InteractionResultHolder<ItemStack> {
var itemStack = player.getItemInHand(hand)
val equipmentSlot = player.getEquipmentSlotForItem(itemStack)
if (!player.canUseSlot(equipmentSlot)) return InteractionResultHolder.pass(itemStack)
val slotStack = player.getItemBySlot(equipmentSlot)
if (!(!EnchantmentHelper.has(slotStack, PREVENT_ARMOR_CHANGE) || player.isCreative) || ItemStack.matches(itemStack, slotStack)) return InteractionResultHolder.fail(itemStack)
if (!level.isClientSide()) player.awardStat(Stats.ITEM_USED.get(itemStack.item))
val equipStack = itemStack.split(1)
when {
itemStack.isEmpty -> itemStack = slotStack.copyAndClear()
else -> player.addItem(slotStack.copyAndClear())
}
player.setItemSlot(equipmentSlot, equipStack)
return InteractionResultHolder.sidedSuccess(itemStack, level.isClientSide())
}
}
override fun getEquipmentSlot(): EquipmentSlot { return EquipmentSlot.HEAD }
}
@@ -379,7 +379,8 @@ object CobblemonItemGroups {
DataComponents.BLOCK_STATE,
BlockItemStateProperties.EMPTY
.with(TypeGemClusterBlock.STAGE, i)
.with(TypeGemClusterBlock.GROWTH_TYPE, TypeGemClusterBlock.GrowthType.DECORATION)
.with(TypeGemClusterBlock.SHOULD_GROW, false)
.with(TypeGemClusterBlock.STUNTED, false)
)
stack.set(
DataComponents.CUSTOM_MODEL_DATA,
@@ -1,107 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.client.data
import com.cobblemon.mod.common.api.pokedex.entry.CobblemonBiomeDexEvolutionRequirementDisplay
import com.cobblemon.mod.common.api.pokedex.entry.CobblemonDexEvolutionRequirementDisplay
import com.cobblemon.mod.common.api.pokedex.entry.DexEntries
import com.cobblemon.mod.common.api.pokedex.entry.DexEvolutionDisplay
import com.cobblemon.mod.common.api.pokedex.entry.DexEvolutionRequirementDisplay
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readIdentifier
import com.cobblemon.mod.common.util.readNullable
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.readText
import com.cobblemon.mod.common.util.writeIdentifier
import com.cobblemon.mod.common.util.writeNullable
import com.cobblemon.mod.common.util.writeString
import com.cobblemon.mod.common.util.writeText
import com.google.common.collect.Lists
import com.google.common.collect.Sets
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.ExtraCodecs
class DexEvolutionDisplaySyncPacket(entries: MutableList<DexEvolutionDisplay>)
: DataRegistrySyncPacket<DexEvolutionDisplay, DexEvolutionDisplaySyncPacket>(entries) {
override val id = ID
override fun encodeEntry(buffer: RegistryFriendlyByteBuf, entry: DexEvolutionDisplay) {
buffer.writeIdentifier(entry.fromSpecies)
buffer.writeString(entry.fromForm)
buffer.writeCollection(entry.fromAspects) { _, aspect -> buffer.writeString(aspect) }
buffer.writeIdentifier(entry.toSpecies)
buffer.writeString(entry.toForm)
buffer.writeCollection(entry.toAspects) { _, aspect -> buffer.writeString(aspect) }
buffer.writeCollection(entry.requirements) { _, req ->
when (req) {
is CobblemonBiomeDexEvolutionRequirementDisplay -> {
buffer.writeByte(1)
buffer.writeIdentifier(BuiltInRegistries.ITEM.getKey(req.icon))
buffer.writeText(req.tooltip)
buffer.writeNullable(req.condition) { b, cond ->
b.writeIdentifier(cond.id)
b.writeBoolean(cond.tag)
}
buffer.writeNullable(req.antiCondition) { b, cond ->
b.writeIdentifier(cond.id)
b.writeBoolean(cond.tag)
}
}
else -> {
buffer.writeByte(0)
buffer.writeIdentifier(BuiltInRegistries.ITEM.getKey(req.icon))
buffer.writeText(req.tooltip)
}
}
}
}
override fun decodeEntry(buffer: RegistryFriendlyByteBuf): DexEvolutionDisplay? {
val fromSpecies = buffer.readIdentifier()
val fromForm = buffer.readString()
val fromAspects = buffer.readCollection(Sets::newHashSetWithExpectedSize) { buffer.readString() }
val toSpecies = buffer.readIdentifier()
val toForm = buffer.readString()
val toAspects = buffer.readCollection(Sets::newHashSetWithExpectedSize) { buffer.readString() }
val requirements = buffer.readCollection(Lists::newArrayListWithExpectedSize) {
val type = buffer.readByte().toInt()
val icon = BuiltInRegistries.ITEM.get(buffer.readIdentifier())
val tooltip = buffer.readText().copy()
if (type == 1) {
val condition = buffer.readNullable { b ->
val id = b.readIdentifier()
val tag = b.readBoolean()
ExtraCodecs.TagOrElementLocation(id, tag)
}
val antiCondition = buffer.readNullable { b ->
val id = b.readIdentifier()
val tag = b.readBoolean()
ExtraCodecs.TagOrElementLocation(id, tag)
}
CobblemonBiomeDexEvolutionRequirementDisplay(icon, tooltip, condition, antiCondition)
} else {
CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
}.toList()
return DexEvolutionDisplay(fromSpecies, fromForm, fromAspects, toSpecies, toForm, toAspects, requirements)
}
override fun synchronizeDecoded(entries: Collection<DexEvolutionDisplay>) {
DexEntries.evolutionDisplays.clear()
DexEntries.evolutionDisplays.addAll(entries)
}
companion object {
val ID = cobblemonResource("dex_evolution_display_sync")
fun decode(buffer: RegistryFriendlyByteBuf): DexEvolutionDisplaySyncPacket =
DexEvolutionDisplaySyncPacket(mutableListOf()).apply { decodeBuffer(buffer) }
}
}
@@ -0,0 +1,100 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.client.data
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.evolution.PreEvolution
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readIdentifier
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeIdentifier
import com.cobblemon.mod.common.util.writeString
import com.google.gson.reflect.TypeToken
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
/**
* Carries the evolutions/preEvolution of every [Species] (and any [FormData] overrides), sent right after
* [SpeciesRegistrySyncPacket] so that every species is already registered by the time this is decoded. This avoids
* the cross-species lookup ordering hazards that come with resolving evolutions during the species sync itself.
*/
class SpeciesEvolutionSyncPacket(entries: Collection<SpeciesEvolutionEntry>) : DataRegistrySyncPacket<SpeciesEvolutionEntry, SpeciesEvolutionSyncPacket>(entries) {
override val id = ID
override fun encodeEntry(buffer: RegistryFriendlyByteBuf, entry: SpeciesEvolutionEntry) {
try {
buffer.writeIdentifier(entry.speciesIdentifier)
buffer.writeString(PokemonSpecies.gson.toJson(entry.evolutions, EVOLUTION_SET_TYPE))
buffer.writeNullable(entry.preEvolution) { _, preEvolution -> buffer.writeString(PokemonSpecies.gson.toJson(preEvolution, PreEvolution::class.java)) }
buffer.writeCollection(entry.formOverrides) { _, override ->
buffer.writeString(override.formName)
buffer.writeNullable(override.evolutions) { _, evolutions -> buffer.writeString(PokemonSpecies.gson.toJson(evolutions, EVOLUTION_SET_TYPE)) }
buffer.writeNullable(override.preEvolution) { _, preEvolution -> buffer.writeString(PokemonSpecies.gson.toJson(preEvolution, PreEvolution::class.java)) }
}
} catch (e: Exception) {
Cobblemon.LOGGER.error("Caught exception encoding evolution data for species {}", entry.speciesIdentifier, e)
}
}
override fun decodeEntry(buffer: RegistryFriendlyByteBuf): SpeciesEvolutionEntry? {
val identifier = buffer.readIdentifier()
return try {
val evolutions: MutableSet<Evolution> = PokemonSpecies.gson.fromJson(buffer.readString(), EVOLUTION_SET_TYPE)
val preEvolution = buffer.readNullable { _ -> PokemonSpecies.gson.fromJson(buffer.readString(), PreEvolution::class.java) }
val formOverrides = buffer.readList { _ ->
val formName = buffer.readString()
val formEvolutions = buffer.readNullable { _ -> PokemonSpecies.gson.fromJson<MutableSet<Evolution>>(buffer.readString(), EVOLUTION_SET_TYPE) }
val formPreEvolution = buffer.readNullable { _ -> PokemonSpecies.gson.fromJson(buffer.readString(), PreEvolution::class.java) }
FormEvolutionOverride(formName, formEvolutions, formPreEvolution)
}
SpeciesEvolutionEntry(identifier, evolutions, preEvolution, formOverrides)
} catch (e: Exception) {
Cobblemon.LOGGER.error("Caught exception decoding evolution data for species {}", identifier, e)
null
}
}
override fun synchronizeDecoded(entries: Collection<SpeciesEvolutionEntry>) {
entries.forEach { entry ->
val species = PokemonSpecies.getByIdentifier(entry.speciesIdentifier)
if (species == null) {
Cobblemon.LOGGER.error("Received evolution data for unknown species {}", entry.speciesIdentifier)
return@forEach
}
species.applyEvolutionData(entry.evolutions, entry.preEvolution)
entry.formOverrides.forEach { override ->
species.getFormByName(override.formName).applyEvolutionData(override.evolutions, override.preEvolution)
}
}
}
companion object {
val ID = cobblemonResource("species_evolution_sync")
private val EVOLUTION_SET_TYPE = TypeToken.getParameterized(MutableSet::class.java, Evolution::class.java).type
fun decode(buffer: RegistryFriendlyByteBuf): SpeciesEvolutionSyncPacket = SpeciesEvolutionSyncPacket(emptyList()).apply { decodeBuffer(buffer) }
}
}
class SpeciesEvolutionEntry(
val speciesIdentifier: ResourceLocation,
val evolutions: MutableSet<Evolution>,
val preEvolution: PreEvolution?,
val formOverrides: List<FormEvolutionOverride>
)
class FormEvolutionOverride(
val formName: String,
val evolutions: MutableSet<Evolution>?,
val preEvolution: PreEvolution?
)
@@ -34,19 +34,17 @@ class OpenPCPacket : NetworkPacket<OpenPCPacket> {
val storeID: UUID
val box: Int?
val unseenWallpapers: Set<ResourceLocation>
val silent: Boolean
@JvmOverloads
@Deprecated("Use the constructor with the PCStore object instead, this will become private in a future title update", level = DeprecationLevel.WARNING)
constructor(storeID: UUID, box: Int? = null, unseenWallpapers: Set<ResourceLocation> = emptySet(), silent: Boolean? = false) {
constructor(storeID: UUID, box: Int? = null, unseenWallpapers: Set<ResourceLocation> = emptySet()) {
this.storeID = storeID
this.box = box
this.unseenWallpapers = unseenWallpapers
this.silent = silent ?: false
}
@JvmOverloads
constructor(pc: PCStore, box: Int? = null, silent: Boolean? = false): this(pc.uuid, box, pc.unseenWallpapers, silent ?: false)
constructor(pc: PCStore, box: Int? = null): this(pc.uuid, box, pc.unseenWallpapers)
override val id = ID
@@ -54,7 +52,6 @@ class OpenPCPacket : NetworkPacket<OpenPCPacket> {
buffer.writeUUID(storeID)
buffer.writeNullable(box) { buf, value -> buf.writeInt(value)}
buffer.writeCollection(unseenWallpapers) { _, it -> buffer.writeResourceLocation(it) }
buffer.writeBoolean(silent)
}
companion object {
@@ -62,8 +59,7 @@ class OpenPCPacket : NetworkPacket<OpenPCPacket> {
fun decode(buffer: RegistryFriendlyByteBuf) = OpenPCPacket(
buffer.readUUID(),
buffer.readNullable { it.readInt() },
buffer.readList { buffer.readResourceLocation() }.toSet(),
buffer.readBoolean()
buffer.readList { buffer.readResourceLocation() }.toSet()
)
}
}
}
@@ -23,16 +23,19 @@ import java.util.UUID
* @author Hiroku
* @since March 5th, 2023
*/
class TradeCompletedPacket(val pokemonId1: UUID, val pokemonId2: UUID) : NetworkPacket<TradeCompletedPacket> {
class TradeCompletedPacket(val pokemonIds1: List<UUID>, val pokemonIds2: List<UUID>) : NetworkPacket<TradeCompletedPacket> {
companion object {
val ID = cobblemonResource("trade_completed")
fun decode(buffer: RegistryFriendlyByteBuf) = TradeCompletedPacket(buffer.readUUID(), buffer.readUUID())
fun decode(buffer: RegistryFriendlyByteBuf) = TradeCompletedPacket(
buffer.readList { it.readUUID() },
buffer.readList { it.readUUID() }
)
}
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeUUID(pokemonId1)
buffer.writeUUID(pokemonId2)
buffer.writeCollection(pokemonIds1) { buf, id -> buf.writeUUID(id) }
buffer.writeCollection(pokemonIds2) { buf, id -> buf.writeUUID(id) }
}
}
@@ -12,10 +12,6 @@ import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.net.UnsplittablePacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readNullable
import com.cobblemon.mod.common.util.readUUID
import com.cobblemon.mod.common.util.writeNullable
import com.cobblemon.mod.common.util.writeUUID
import net.minecraft.network.RegistryFriendlyByteBuf
import java.util.UUID
@@ -27,15 +23,18 @@ import java.util.UUID
* @author Hiroku
* @since March 5th, 2023
*/
class TradeUpdatedPacket(val playerId: UUID, val pokemon: Pokemon?) : NetworkPacket<TradeUpdatedPacket>, UnsplittablePacket {
class TradeUpdatedPacket(val playerId: UUID, val pokemons: List<Pokemon>) : NetworkPacket<TradeUpdatedPacket>, UnsplittablePacket {
companion object {
val ID = cobblemonResource("trade_updated")
fun decode(buffer: RegistryFriendlyByteBuf) = TradeUpdatedPacket(buffer.readUUID(), buffer.readNullable { Pokemon.S2C_CODEC.decode(buffer) })
fun decode(buffer: RegistryFriendlyByteBuf) = TradeUpdatedPacket(
buffer.readUUID(),
buffer.readList { Pokemon.S2C_CODEC.decode(it as RegistryFriendlyByteBuf) }
)
}
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeUUID(playerId)
buffer.writeNullable(pokemon) { _, pokemon -> Pokemon.S2C_CODEC.encode(buffer, pokemon) }
buffer.writeCollection(pokemons) { buf, pokemon -> Pokemon.S2C_CODEC.encode(buf as RegistryFriendlyByteBuf, pokemon) }
}
}
@@ -1,55 +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://www.mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.server.storage.pc
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.storage.pc.PCPosition
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readPCPosition
import com.cobblemon.mod.common.util.writePCPosition
import java.util.UUID
import net.minecraft.network.RegistryFriendlyByteBuf
class MovePCPokemonBatchPacket(val moves: List<PCPokemonMove>) : NetworkPacket<MovePCPokemonBatchPacket> {
override val id = ID
data class PCPokemonMove(
val pokemonID: UUID,
val oldPosition: PCPosition,
val newPosition: PCPosition
)
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeVarInt(moves.size)
moves.forEach { move ->
buffer.writeUUID(move.pokemonID)
buffer.writePCPosition(move.oldPosition)
buffer.writePCPosition(move.newPosition)
}
}
companion object {
val ID = cobblemonResource("move_pc_pokemon_batch")
const val MAX_MOVES = 60
fun decode(buffer: RegistryFriendlyByteBuf): MovePCPokemonBatchPacket {
val size = buffer.readVarInt()
require(size in 1..MAX_MOVES) { "Invalid batch PC move size: $size" }
return MovePCPokemonBatchPacket(
List(size) {
PCPokemonMove(
pokemonID = buffer.readUUID(),
oldPosition = buffer.readPCPosition(),
newPosition = buffer.readPCPosition()
)
}
)
}
}
}
@@ -11,26 +11,30 @@ package com.cobblemon.mod.common.net.messages.server.trade
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.storage.party.PartyPosition
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readList
import com.cobblemon.mod.common.util.readNullable
import com.cobblemon.mod.common.util.readPartyPosition
import com.cobblemon.mod.common.util.readUUID
import com.cobblemon.mod.common.util.writeCollection
import com.cobblemon.mod.common.util.writeNullable
import com.cobblemon.mod.common.util.writePartyPosition
import com.cobblemon.mod.common.util.writeUUID
import net.minecraft.network.RegistryFriendlyByteBuf
import java.util.UUID
class UpdateTradeOfferPacket(val newOffer: Pair<UUID, PartyPosition>?): NetworkPacket<UpdateTradeOfferPacket> {
class UpdateTradeOfferPacket(val newOffers: List<Pair<UUID, PartyPosition>>): NetworkPacket<UpdateTradeOfferPacket> {
companion object {
val ID = cobblemonResource("update_trade_offer")
fun decode(buffer: RegistryFriendlyByteBuf) = UpdateTradeOfferPacket(buffer.readNullable { buffer.readUUID() to buffer.readPartyPosition() })
fun decode(buffer: RegistryFriendlyByteBuf) = UpdateTradeOfferPacket(
buffer.readList { it.readUUID() to it.readPartyPosition() }
)
}
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeNullable(newOffer) { buffer, (pokemonId, partyPosition) ->
buffer.writeUUID(pokemonId)
buffer.writePartyPosition(partyPosition)
buffer.writeCollection(newOffers) { buf, (pokemonId, partyPosition) ->
buf.writeUUID(pokemonId)
buf.writePartyPosition(partyPosition)
}
}
}
@@ -43,8 +43,6 @@ object TMMachineTeachMoveHandler : ServerNetworkPacketHandler<TMMachineTeachMove
!pokemon.moveSet.getMoveTemplates().contains(moveTemplate) &&
!pokemon.allAccessibleMoves.contains(moveTemplate)
) {
menu.incrementStateId()
if (!player.isCreative && !Cobblemon.config.infiniteTmUses) menu.carried.shrink(1)
if (pokemon.moveSet.hasSpace()) {
@@ -60,4 +58,4 @@ object TMMachineTeachMoveHandler : ServerNetworkPacketHandler<TMMachineTeachMove
player.playNotifySound(CobblemonSounds.MOVE_LEARN, SoundSource.MASTER, 1.0F, 1.0F)
}
}
}
}
@@ -1,23 +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://www.mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.serverhandling.storage.pc
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.api.storage.pc.link.PCLinkManager
import com.cobblemon.mod.common.net.messages.client.storage.pc.ClosePCPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonBatchPacket
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
object MovePCPokemonBatchHandler : ServerNetworkPacketHandler<MovePCPokemonBatchPacket> {
override fun handle(packet: MovePCPokemonBatchPacket, server: MinecraftServer, player: ServerPlayer) {
val pc = PCLinkManager.getPC(player) ?: return run { ClosePCPacket(null).sendToPlayer(player) }
pc.moveBatch(packet.moves.map { Triple(it.pokemonID, it.oldPosition, it.newPosition) })
}
}

Some files were not shown because too many files have changed in this diff Show More