Compare commits

..
Author SHA1 Message Date
Lily 2f82f381d4 - added custom equip sounds
- tweaked armor material values
- made fireproof
- made trimmable
2023-09-08 15:25:00 -04:00
Lily 4d75103a68 WIP implementations of auspicious and malicious armor, to be completed for 1.5 2023-09-04 16:12:46 -04:00
5606 changed files with 34782 additions and 349517 deletions

No files matched your search

+79 -268
View File
@@ -1,56 +1,41 @@
# Changelog
## [1.4.0 - The Friends and Farms Update (October 13th, 2023)](#1-4-0)
## [1.4.0](#1-4-0)
### Additions
- Added pasture blocks, used to let your PC Pokémon roam around an area.
- Added nicknaming from the summary menu of a Pokémon (click their name).
- Added trading between players. Press R while looking at another player and you'll figure the rest out.
- Added mints for changing Pokémon stats. These are most commonly found at high altitudes.
- Added [Revival Herbs](https://wiki.cobblemon.com/index.php/Revival_Herb), with pep-up flowers when fully grown, growing in lush caves.
- Added [Medicinal Leeks](https://wiki.cobblemon.com/index.php/Medicinal_Leek), growing on the surface of rivers and ponds. It is a potion ingredient and can be cooked as food!.
- Added [Big Roots](https://wiki.cobblemon.com/index.php/Big_Root), generating from cave ceiling dirt which sometimes spread as energy roots.
- Added [69 Berry Trees](https://wiki.cobblemon.com/index.php/Berry_Tree) and [Berries](https://wiki.cobblemon.com/index.php/Berry). Some are found in village farms, some from planting different berries close to each other.
- Added mulches: Mulch Base, Growth Mulch, Surprise Mulch, Coarse Mulch, Humid Mulch, Rich Mulch, Loamy Mulch, Peat Mulch, and Sandy Mulch
- Added [Vivichokes](https://wiki.cobblemon.com/index.php/Vivichoke), obtainable from wandering villager trades and some loot chests.
- Added nicknaming.
- Added mints, found on mountains.
- Added revival herbs, with pep-up flowers if fully grown, growing in lush caves.
- Added medicinal leeks, growing on the surface of rivers and ponds. It is a potion ingredient!
- Added big roots, generating from cave ceiling dirt and sometimes spreading into energy roots.
- Added trading between players. Press R while looking at another player, you'll figure it out.
- Added pasture blocks, used to let your PC Pokémon roam around your base.
- Added 69 berry trees. Some are found in village farms, some from planting different berries close to each other.
- Added medicine brewing using medicinal leeks and berries in brewing stands.
- Added Pokémon cries when in battles and being sent out.
- Added medicinal items: Berry Juice, Heal Powder, Remedy, Fine Remedy, Superb Remedy, Revive, Max Revive, Potion, Super Potion, Hyper Potion, Max Potion, Full Restore, Full Heal, Antidote, Awakening, Burn Heal, Ice Heal, and Paralyze Heal.
- Added battle items: X Attack, X Defence, X Sp.Atk, X Sp.Def, X Speed, Dire Hit, and Guard Spec.
- Added EV items: Power Anklet, Power Band, Power Belt, Power Bracelet, Power Lens, Power Weight.
- Added food items: Roasted Leek, Leek and Potato Stew, Braised Vivichoke, and Vivichoke Dip
- Added evolution items: Auspicious Armor and Malicious Armor, which can be used to evolve Charcadet into Armarouge or Ceruledge respectively.
- Added (mostly brewing) recipes for HP Up, Protein, Iron, Calcium, Zinc, Carbos, PP Up, PP Max, and Medicinal Leek to Magenta Dye.
- Added held items: Bright Powder, Destiny Knot
- Added AI for Nosepass to point towards world spawn when idle. We just think it's neat.
- Added shoulder mounting for Mimikyu.
- Added support for scale in animations.
- Added support for jump keyframes (i.e. pre and post keyframes)
- Added AI for Nosepass to point towards world spawn when idle.
- Added structure spawn conditions
- Added cries for Gen 1-9 starters and their evolutions.
- Added cries for Chatot, Darmanitan, Darumaka, Lucario, Mimikyu, Quagsire, Riolu, Wooper, Caterpie, Metapod, Butterfree, Weedle, Kakuna, Beedrill, Pidgey, Pidgeotto, Pidgeot, Rattata, Raticate, Cleffa, Clefairy, Clefable, Tauros, Miltank, Buneary, Lopunny, Wooloo, Dubwool.
- Added recipes for Berry Juice, Heal Powder, Remedy, Fine Remedy, Superb Remedy, Revive, Max Revive, HP Up, Protein, Iron, Calcium, Zinc, Carbos, PP Up, PP Max, Pasture, Medicinal Leek to Magenta Dye, Roasted Leek, Leek and Potato Stew, Braised Vivichoke, Vivichoke Dip, Mulch Base, Growth Mulch, Surprise Mulch, Coarse Mulch, Humid Mulch, Rich Mulch, Loamy Mulch, Peat Mulch, Sandy Mulch, Health Feather, Muscle Feather, Resist Feather, Genius Feather, Clever Feather, Genius Feather
- Added a shoulder mount for Mimikyu
- Added Advancement trigger for defeating Pokémon and collecting varieties of Pokémon.
- Added sleep animations to Arcanine, Jigglypuff, Wigglytuff, Vulpix and Ninetales.
- Added flying placeholder animations to Pidgey, Pidgeotto, Pidgeot, Golbat, Crobat, Scyther, Scizor, Zapdos, Moltres, Articuno, Dragonite, Rowlet, Dartrix, and Decidueye.
- Added loot to various vanilla chest loot tables (Link Cable in Ancient Cities, Woodland Mansions, End Cities, and Igloos, Vivichoke Seeds in Jungle Temples, Dungeons, and Plains, Savanna, Snowy, and Taiga Villages, and all 7 Apricorn Sprouts in Desert, Plains, Savanna, Snowy, and Taiga Villages, as well as the Bonus Chest, which can also have 5 of the basic Poké Ball types)
- Added a `doShinyStarters` gamerule to make it quick and easy to be offered shiny starters.
- Added a `doPokemonLoot` gamerule to toggle Pokémon dropping items/exp on death.
- Added ability activation announcement when in battle.
- Added animations for Wailord and made it BIGGER.
- Added Cherry Torterra variant.
- Added 2 new face spots for Spinda. The number of unique Spindas increases...
- Added Forretress Shulker variant.
- Added loot to various vanilla chest loot tables (Link Cable in Ancient Cities, Woodland Mansions, End Cities, and Igloos, Vivichoke Seeds in Jungle Temples, Dungeons, and Plains, Savanna, Snowy, and Taiga Villages, and all 7 Apricorn Sprouts in Desert, Plains, Savanna, Snowy, and Taiga Villages, as well as the Bonus Chest)
- Added the `/teststore <player> <store> <properties>` command allowing command block/mcfunction users to query a party, PC or both for Pokémon matching specific properties and returning the match count, this will be a cheat command in the Minecraft permission system or use the permission `cobblemon.command.teststore` if a permission mod is present.
- Added the `/querylearnset <player> <slot> <move>` command allowing command block/mcfunction users to query a party slot and check if the Pokémon can learn a specific move returning a 1 if yes otherwise 0, this will be a cheat command in the Minecraft permission system or use the permission `cobblemon.command.querylearnset` if a permission mod is present.
- Added the `/testpcslot <player> <slot> <properties>` command allowing command block/mcfunction users to query a pc slot and check if the Pokémon matches specific properties returning a 1 if yes otherwise 0, this will be a cheat command in the Minecraft permission system or use the permission `cobblemon.command.testpcslot` if a permission mod is present.
- Added the `/testpartyslot <player> <slot> <properties>` command allowing command block/mcfunction users to query a party slot and check if the Pokémon matches a specific properties returning a 1 if yes otherwise 0, this will be a cheat command in the Minecraft permission system or use the permission `cobblemon.command.testpartyslot` if a permission mod is present.
- Added the `/clearparty <player>` command for emptying a player's party.
- Added the `/clearpc <player>` command for emptying a player's PC.
- Added the `/pokemonrestart <reset_starters>` and the `/pokemonrestartother <player> <reset_starters>` command allowing command block/mcfunction users to reset a players Pokémon data.
- Added support for "isBattle" and "isTouchingWater" properties on resource pack Pokémon poses. This allows your custom Pokémon to be posed differently when in battle.
- Added support for "isVisible" on a transformed part on resource pack Pokémon poses. This allows your custom Pokémon to have bones disappear in specific poses, such as hiding Greninja's throwing star when not in a battle pose.
- Added the `doPokemonLoot` gamerule to toggle Pokémon dropping items/exp on death.
### Pokémon Added
#### Gen 2
- Chikorita
- Bayleef
- Meganium
- Totodile
- Croconaw
- Feraligatr
- Cyndaquil
- Quilava
- Typhlosion
- Spinarak
- Ariados
- Shuckle
@@ -64,12 +49,6 @@
- Flaaffy
- Ampharos
- Sudowoodo
- Snubbull
- Granbull
- Phanpy
- Donphan
- Teddiursa
- Ursaring
#### Gen 3
@@ -83,7 +62,7 @@
- Cacnea
- Cacturne
- Poochyena
- Mightyena
- Mightyenna
- Wingull
- Pelipper
- Numel
@@ -96,22 +75,11 @@
- Chimecho
- Barboach
- Whiscash
- Volbeat
- Illumise
- Zigzagoon
- Linoone
- Ralts
- Kirlia
- Gardevoir
- Nincada
- Ninjask
- Shedinja
- Beldum
- Metang
- Metagross
#### Gen 4
- Cherubi (Planned)
- Cherrim (Planned)
- Carnivine
- Shinx
- Luxio
@@ -124,77 +92,48 @@
- Chatot
- Combee
- Vespiquen
- Buizel
- Floatzel
- Starly
- Staravia
- Staraptor
- Gallade
#### Gen 5
- Bouffalant
- Bouffolant
- Roggenrola
- Boldore
- Gigalith
- Venipede
- Whirlipede
- Venepede
- Whirlpede
- Scolipede
- Yamask
- Cofagrigus
- Patrat
- Watchog
- Yamask (Planned)
- Cofagrigus (Planned)
- Patrat (Planned)
- Watchog (Planned)
- Lillipup
- Herdier
- Stoutland
- Cottonee
- Whimsicott
- Pidove
- Tranquill
- Unfezant
- Timburr
- Gurdurr
- Conkeldurr
#### Gen 6
- Scatterbug
- Spewpa
- Vivillon
- Skrelp
- Dragalge
- Bunnelby
- Diggersby
- Phantump
- Trevenant
- Fletchling
- Fletchinder
- Talonflame
- Vivillion
- Skrelp (Planned)
- Dragalge (Planned)
- Bunnelby (Planned)
- Diggersby (Planned)
#### Gen 7
- Wishiwashi
- Wishiwashi (Planned)
- Cutiefly
- Ribombee
- Stufful
- Bewear
- Comfey
- Alolan Exeggutor
- Alolan Raichu
- Alolan Meowth
- Alolan Persian
- Komala
- Wimpod
- Golisopod
- Crabrawler
- Crabominable
- Mudbray
- Mudsdale
#### Gen 8
- Arrokuda
- Barraskewda
- Arrokuda (Planned)
- Barraskewda (Planned)
- Nickit
- Thievul
- Falinks
@@ -203,28 +142,12 @@
- Rookidee
- Corvisquire
- Corviknight
- Galarian Ponyta
- Galarian Rapidash
- Yamper
- Boltund
- Galarian Zigzagoon
- Galarian Linoone
- Obstagoon
- Galarian Meowth
- Perrserker
- Ursaluna
#### Gen 9
- Sprigatito
- Sprigatitto
- Floragato
- Meowscarada
- Fuecoco
- Crocalor
- Skeledirge
- Quaxly
- Quaxwell
- Quaquaval
- Flittle
- Espathra
- Garganacl
@@ -234,186 +157,91 @@
- Ceruledge
- Cetoddle
- Cetitan
- Shroodle
- Grafaiai
- Shroodle (Planned)
- Grafaiai (Planned)
- Tandemaus
- Maushold
- Varoom
- Revavroom
- Squawkabilly
- Squawkabilly (Planned)
- Glimmet
- Glimmora
- Annihilape
- Tinkatink
- Tinkatuff
- Tinkaton
- Maschiff
- Mabosstiff
- Lechonk
- Oinkologne
- Paldean Wooper
- Clodsire
### Changes
- Removed the existing shoulder effects from Pokémon until we have more balanced versions of them (they're too powerful!)
- Updated models and textures of Weedle, Dwebble and Crustle, Spiritomb, Koffing and Weezing, Kadabra and Alakazam, Emolga, Oshawott, Doduo and Dodrio, Dratini and Dragonair and Dragonite, Sneasel and Weavile, Gyarados, Hitmonlee and Hitmonchan, Chesnaught, Spinda, Mamoswine, Steelix, Misdreavus and Mismagius, Buneary and Lopunny, Golduck, Meowth and Persian, Fennekin and Braixen and Delphox, Snivy and Servine and Serperior, Ratatta and Raticate, Nidorina and Nidoqueen, Nidoran Male and Nidoking, Riolu and Lucario, Haunter and Gengar, Mankey and Primeape, Mew and Mewtwo, Arcanine, Magnemite and Magneton and Magnezone, Exeggcute and Exeggutor, Elekid and Electabuzz and Electivire, Pichu and Pikachu and Raichu, Wooper, Drowzee and Hypno, Aerodactyl, Spearow and Fearow, Lickitung and Lickilicky, Pidgey and Pidgeotto and Pidgeot, Scyther and Scizor and Kleavor, Popplio and Brionne and Primarina, Torchic and Combusken and Blaziken, Happiny and Chansey and Blissey.
- Updated animations for Steelix, Turtwig and Grotle and Torterra, Ponyta and Rapidash, Piplup and Prinplup and Empoleon, Drowzee and Hypno, Farfetch'd, Exeggcute and Exeggutor, Bidoof, Chimecho, Lickitung and Lickilicky, Popplio and Brionne, Luvdisc, Chimchar and Monferno and Infernape, Sobble and Drizzile and Inteleon, Greninja, Heatmor, Aerodactyl, Ditto, Lotad and Lombre and Ludicolo, Pumpkaboo and Gourgeist.
- Updated model and textures of Arcanine, Magnemite, Magneton, Magnezone, Exeggcute, Exeggutor, Farfetchd, Elekid, Electabuzz, Electivire, Pichu, Pikachu, Raichu, Gengar, Wooper Drowzee, Hypno, Aerodactyl, Spearow, Fearow, Lickitung, Pidgeotto, Pidgeot, Scyther, Scizor, Kleavor, Popplio, Brionne, Primarina, Torchic, Combusken, Blaziken, Aerodacyl, Scyther, Scizor, Kleavor, Lickitung, Lickilicky, Happiny, Chansey, Pidgey, Pidgeotto, Pidgeot, Spearow, Fearow, Drowzee, Hypno, Arcanine, Magnemite, Magneton, Magnezone, Exeggcute, Exeggutor, Farfetchd, Elekid, Electabuzz, Electivire, Pichu, Pikachu, Raichu, and Gengar.
- Updated models of Sceptile and Shuckle.
- Updated sprites for EV medicines, the rare candy, and the apricorn door item.
- Updated textures for apricorn doors and all the evolution stone ores.
- Ponyta and Rapidash now have animated textures; they look insane.
- Updated Apricorn Leaves color.
- Wild Pokémon now heal if you are defeated by them or flee from them.
- Doubled the default time between ambient Pokémon cries (they have cries if you're using a resource pack to add them)
- Moved spawn attempts per tick to a config option (ticksBetweenSpawnAttempts)
- PCs can now be waterlogged
- Starter selection prompt now appears as a tutorial-esque toast instead of plain text
- Cobblemon items can now all have their own tooltips via resourcepacks, to add a tooltip simply add a lang entry like "item.cobblemon.{item_id}.tooltip", if you want to add multiple tooltip lines you can do so with "item.cobblemon.{item_id}.tooltip_1" and upwards.
- Updated texture of Weedle.
- Updated shiny texture of Dwebble and Crustle.
- Updated Animations for Piplup, Prinplup, Empoleon, Drowzee, Hypno, Farfetch'd, Exeggcute, Exeggutor, Bidoof, Chimecho, Lickytung, Lickilicky, Popplio, Brionne, Luvdisc, Chimchar, Monferno, Infernape, Sobble, Drizzile, Inteleon, Greninja, Heatmor, Aerodactyl, Ditto, Lotad, Lombre, Ludicolo, Pumpkaboo, Gourgeist.
- Reorganised the advancements recipes folder
- Pokéedit command now supports IVs and EVs.
- Pokeedit command now supports IVs and EVs.
- Reorganised creative categories
- Pokémon can now wander into non-solid blocks such as foliage
- Thrown Poké Balls now despawn after 30 seconds so that they don't fly forever.
- Pokemon can now wander into non-solid blocks such as foliage
- Pokeballs now despawn after 30 seconds
- Dive Balls will now have the same motion speed underwater as if they were thrown in the air.
- Hardcoded potion shoulder effects have been removed. You can now use any potion vanilla or otherwise with the parameters you'd like, for more information see the [Datapack & Resourcepack Creators](#datapack-&-resourcepack-creators) section.
- Clicking categories of the Stat subsection or the party reorder button in the Summary screen will now produce a click sound.
- Hardcoded potion shoulder effects have been removed, you can now use any potion vanilla or otherwise with the parameters you'd like, for more information see the [Datapack & Resourcepack Creators](#datapack-&-resourcepack-creators) section.
- Clicking categories of the Stat sub-section or the party reorder button in the Summary screen will now produce a click sound.
- Updated PC Recipe.
- Improved Pokémon AI and movement.
- Friendship will slowly increase when Pokémon are shoulder-mounted.
- Master Balls are now unable to be burned when dropped into fire/lava. They're made from stronger stuff.
- Master balls are now unable to be burned when dropped into fire/lava.
- Pokémon will appear red when hurt, like regular entities, except when they're fainting.
- Pokémon's air meter no longer depletes while battling underwater.
- Sleeping partially restores PP of Pokémon
- Shoulder mounts now match the shoulder position a bit more accurately when sneaking.
- Poison Heal will now cause poisoned Pokémon to heal outside of battle.
- Updated Poké Ball, PC, UI, evolution and Healing Machine sounds.
### Added cries to the following Pokémon:
- All starters and their evolutions
- Caterpie, Metapod, Butterfree
- Weedle, Kakuna, Beedrill
- Pidgey, Pidgeotto, Pidgeot
- Rattata, Raticate
- Spearow, Fearow
- Ekans, Arbok
- Pichu, Pikachu, Raichu, Alolan Raichu
- Cleffa, Clefairy, Clefable
- Mankey, Primeape, Annihilape
- Ponyta, Rapidash, Galarian Ponyta, Galarian Rapidash
- Farfetch'd, Galarian Farfetch'd, Sirfetch'd
- Onix, Steelix
- Tauros
- Ditto
- Eevee, Vaporeon, Jolteon, Flareon, Espeon, Umbreon, Leafeon, Glaceon, Sylveon
- Hoothoot, Noctowl
- Mareep, Flaaffy, Ampharos
- Aipom, Ambipom
- Wooper, Quagsire, Clodsire
- Snubbull, Granbull
- Miltank
- Poochyena, Mightyena
- Taillow, Swellow
- Ralts, Kirlia, Gardevoir, Gallade
- Shroomish, Breloom
- Nincada, Ninjask, Shedinja
- Buneary, Lopunny
- Chingling
- Chatot
- Riolu, Lucario
- Pidove, Tranquill, Unfezant
- Roggenrola, Boldore, Gigalith
- Venipede, Whirlipede, Scolipede
- Maractus
- Dwebble, Crustle
- Yamask, Cofagrigus
- Bunnelby, Diggersby
- Fletchling, Fletchinder, Talonflame
- Scatterbug, Spewpa, Vivillon
- Honedge, Doublade, Aegislash
- Skrelp, Dragalge
- Phantump, Trevenant
- Pumpkaboo, Gourgeist
- Bergmite, Avalugg
- Mudbray, Mudsdale
- Stufful, Bewear
- Mimikyu
- Rookidee, Corvisquire, Corviknight
- Nickit, Thievul
- Wooloo, Dubwool
- Yamper, Boltund
- Tandemaus
- Fidough, Dachsbun
- Squawkabilly
- Nacli, Naclstack, Garganacl
- Charcadet, Armarouge, Ceruledge
- Maschiff, Mabosstiff
- Shroodle, Grafaiai
- Flittle, Espathra
- Tinkatink, Tinkatuff, Tinkaton
- Varoom, Revavroom
- Glimmet, Glimmora
- Cetoddle, Cetitan
- Tatsugiri
### Fixes
- Fixed spawning moon phase dependent Pokémon only when the moon phase is wrong (that's a funny woopsy)
- Fixed spawning moon phase dependent Pokémon only when the moon phase is wrong
- Fixed large Pokémon spawning partially inside walls where they suffocate.
- Fixed custom Pokémon in your party or PC not being removed when the addon is removed, causing major issues.
- Fixed messages for entry hazards, screens, weather, damage, healing, Tailwind, Perish Song, Destiny Bond, Shed Skin, Uproar, Forewarn, Disguise, Arena Trap, Yawn, Curse, Clamp, Whirlpool, Liquid Ooze, Miracle Eye, Safeguard, Magic Bounce, Lock On, Focus Energy, Confusion, and more.
- Fixed messages for entry hazards, screens, Tailwind, Perish Song, Destiny Bond, Shed Skin, Uproar, Forewarn, Disguise, Arena Trap, Yawn, Curse, Clamp, Whirlpool, Liquid Ooze, Miracle Eye and Safeguard.
- Fixed Porygon not evolving with an Upgrade.
- Fixed super sized Pumpkaboo not having any moves.
- Fixed Infernape look animation.
- Fixed Garchomp T-posing while swimming which was very funny.
- Fixed a bug that caused sleeping Pokémon to stay asleep. Forever. The years passing them by as they dream of a world without hate...
- Fixed a bug that would freeze a battle when a Pokémon gets trapped by an ability, making the trap abilities even scarier and trap-like than they were before.
- Fixed Garchomp t-posing while swimming.
- Fixed a bug that caused sleeping Pokémon to stay asleep.
- Fixed a bug that would freeze a battle when a Pokémon gets trapped due to an ability.
- Fixed the Poké Ball close animation canceling whenever colliding with a block.
- Fixed faint animations not working properly in add-ons.
- Fixed lighting and Pokémon label issues when a Pokémon item frame is nearby.
- Fixed Pokémon being able to spawn outside the world border as a tease.
- Fixed deepslate water stone ore items looking like deepslate fire stone ores. Huh?
- Fixed a bunch of client-side logging errors when Pokémon are shoulder mounted. You didn't notice? Good.
- Fixed deepslate water stone ore items looking like deepslate fire stone ores.
- Fixed a bunch of client-side logging errors when Pokémon are shoulder mounted.
- Fixed a crash when wild Pokémon have to struggle under specific circumstances.
- Fixed uncolored pixels on Yanma's shiny texture.
- Fixed apricorn tree leaves looking gross on the Fast graphics mode.
- Fixed apricorn tree leaves looking gross on Fast graphics mode.
- Fixed hoes not breaking apricorn tree leaves any faster.
- Fixed Shiftry's PC model position.
- Fixed the /pc command not playing the opening sound.
- Fixed Shedinja not being able to recover naturally.
- Fixed different forms of Pokémon not being able to appear as different sizes.
- Fixed the Healing Machine soft locking you from using others when removed by non-players.
- Fixed animations being sped up when using the Replay Mod.
- Fixed particle animations not running when a Pokémon is off-screen.
- Fixed Pokémon variants and layers not rendering correctly when shouldered and playing on a dedicated server, existing shoulders affected will need to be retrieved and shouldered again.
- Fixed shoulder effects not staying applied through situations that remove potion effects such as drinking milk.
- Fixed Shedinja not being able to recover naturally.
- Fixed Shedinja evolving to use the consumed Poké Ball and removed the held item to prevent dupes.
- Fixed Shedinja healing above 1 HP.
- Fixed Shedinja, basically.
- Shoulder effects will now stay applied through situations that remove potion effects such as drinking milk.
- Fixed Shedinja evolving to use the consumed Pokeball and removed the held item.
- Fixed non-existent species in spawn pool file causing random species to spawn.
- Fixed shearing Pokémon dropping 0-2 wool instead of 1-3.
- Fixed some alignment issues in the stat hexagon of the summary menu. OCD people rejoice.
- Fixed some alignment issues in the stat hexagon of the summary menu.
- Fixed capture calculations not applying ball bonuses entirely correctly.
- Fixed battles soft-locking when consecutive Pokémon faint on switch-in.
- Fixed timing and color of battle window messages.
- Fixed players being able to trade, battle and let out their Pokémon while in spectator mode.
- Fixed Galarian Yamask not being able to evolve and by proxy the ``damage_taken`` evolution requirement.
- Fixed Bisharp not being able to evolve and by proxy the ``defeat`` evolution requirement.
- Fixed White-Striped Basculin not being able to evolve because of a broken ``recoil`` evolution requirement.
- Fixed Primeape, Qwilfish and Stantler not being able to evolve because of a broken ``use_move`` evolution requirement.
- Fixed Bramblin, Pawmo, and Rellor not being able to evolve because of a broken ``blocks_traveled`` evolution requirement.
- Fixed displayName property in spawn files not doing what it's meant to do.
- Fixed Pokémon not sleeping in the wild like we wanted them to.
### Developer
- Added SpawnEvent, ThrownPokeballHitEvent, PokemonSentEvent, PokemonRecalledEvent.
- Added BattleFledEvent, BattleStartedEvent, BattleFaintedEvent.
- Added persistent NBT property inside Pokémon to store quick and simple data.
- Added SpawnEvent
- Added persistent NBT property inside Pokemon to store quick and simple data.
- Species and FormData have had their evolutions, pre-evolution and labels properties exposed. It is still recommended to work using a Pokémon instance when possible.
- Added capture check to BattleVictoryEvent.
- Added capture check to BattleVictoryEvent
- Added ThownPokeballHitEvent
- The various hardcoded potion shoulder effects have been removed, make use of PotionBaseEffect.
- Added ContextManager for tracking causes and contexts of conditions created during a battle. See BattleContext for types of conditions that are tracked.
- Added MongoDB support for storing Pokémon and Player data. Must be enabled in config, requires MongoDB core and sync drivers (4.10.0+).
- CobblemonShowdown updated to version 10.
- Generation of a battle can be set in BattleFormat.
- Pokémon now have ``teraType``, ``dmaxLevel``, and ``gmaxFactor`` properties. Gimmicks can be used during battle by adding the respective identifiers to ``keyItems`` in PlayerData: ``key_stone``, ``z_ring``, ``dynamax_band``, and ``tera_orb``. Dynamax is only supported in Gen 8 battles. Mega Evolution and Z-Power require custom held items to be added (e.g. an item with the path ``gengarite`` will allow Gengar to Mega Evolve). Currently custom Z-Crystals and Mega Stones are not supported.
### Datapack & Resourcepack Creators
- All potion related shoulder effects have had their IDs changed. They now all share the same type being `potion_effect` and use the vanilla Potion data [parameters](https://minecraft.fandom.com/wiki/Potion#Item_data). For example, here is the converted Pidgey asset:
- All potion related shoulder effects have had their IDs changed, they now all share the same type being `potion_effect` and use the vanilla Potion data [parameters](https://minecraft.fandom.com/wiki/Potion#Item_data), take for example the default Pidgey asset:
- ```json
{
"type": "potion_effect",
@@ -424,23 +252,6 @@
"showIcon": false
}
```
- Renamed the ``walked_steps`` evolution requirement to ``blocks_traveled``.
- Added support for scale in animations.
- Added support for jump keyframes (i.e. pre and post keyframes)
- Added structure spawning conditions
- Added Advancement trigger for defeating Pokémon and collecting varieties of Pokémon.
- Added support for "isBattle" and "isTouchingWater" properties on resource pack Pokémon poses. This allows your custom Pokémon to be posed differently when in battle.
- Added support for "isVisible" on a transformed part on resource pack Pokémon poses. This allows your custom Pokémon to have bones disappear in specific poses, such as hiding Greninja's throwing star when not in a battle pose.
- Added support for battle music. Sounds can be added to the ``battle.pvp.default`` and ``battle.pvw.default`` sound events.
- Added 'enabled' optional property on model layers, allowing later variations to disable previously-defined layers. See [this issue](https://gitlab.com/cable-mc/cobblemon/-/issues/335) for how this looks.
- Cobblemon items can now all have their own tooltips via resourcepacks. To add a tooltip, add a lang entry like "item.cobblemon.{item_id}.tooltip". If you want to add multiple tooltip lines you can do so with "item.cobblemon.{item_id}.tooltip_1" and upwards.
- Item interaction evolutions and held item requirements now support NBT by creating an object JSON containing the key ``item`` for what used to be the existing condition support and a ``nbt`` key for the NBT format, this is the string [format](https://minecraft.fandom.com/wiki/NBT_format) expected in commands. Existing data does not need to be updated.
- Fixed faint animations not working properly in add-ons.
- Fixed non-existent species in spawn pool files causing random species to spawn.
### Localization
- Added partial translations for Dutch, Polish, Swedish, Hungarian, Czech, Cyprus Greek, and even Esperanto.
- Updated translation for French and Canadian French, Simplified Chinese, Japanese, Korean, Spanish and Mexican Spanish, Pirate English, German, Thai, Turkish, Portuguese and Brazilian Portuguese, Ukrainian, and Russian.
## [1.3.1 (March 31st, 2023)](#1-3-1)
@@ -454,7 +265,7 @@
- Add lava surface spawn preset.
- Added an ``any`` evolution requirement allowing you to define ``possibilities`` of other evolution requirements, for example, this allows you to create an evolution that requires the Pokémon to be shiny or a female.
- Added the `/spawnpokemonfrompool [amount]` or `/forcespawn [amount]` command to spawn Pokémon(s) in the surrounding area using the natural spawn rates/pool of that area, this will be a cheat command in the Minecraft permission system or use the permission `cobblemon.command.spawnpokemon` if a permission mod is present. On a successful execution of the command, the amount of Pokémon spawned will be the output.
- Added the `/pokebox` and `/pokeboxall` commands to move Pokémon(s) to the PC from a Player's party, this will be a cheat command in the Minecraft permission system or use the permission `cobblemon.command.pokebox` if a permission mod is present. On a successful execution of the command the output will be the number of pokemon moved to the Player's PC.
- Added the `/pokebox` and `/pokeboxall` commands to move Pokemon(s) to the PC from a Player's party, this will be a cheat command in the Minecraft permission system or use the permission `cobblemon.command.pokebox` if a permission mod is present. On a successful execution of the command the output will be the number of pokemon moved to the Player's PC.
- Added the `/pc` command which opens up the PC UI the same way interacting with the block would, this will be a cheat command in the Minecraft permission system or use the permission `cobblemon.command.pc` if a permission mod is present.
### Changes
@@ -477,6 +288,7 @@
- Changed the recipes for Mystic Water, Miracle Seed, and Charcoal Stick to utilise the evolution stones, as well as Never-Melt Ice having an alternate recipe using the Ice Stone.
- Replaced the `Failed to handle` battle messages to `Missing interpretation` to make it more clear that mechanics do work just still pending dedicated messages.
- Healing Machine and PC are now mine-able with pickaxes and Apricorn leaves are mine-able using hoes.
- Updated Pokéball, UI and evolution sounds.
### Fixes
- Fixed killing a Dodrio killing your game. Dodrio will never look the same to us.
@@ -797,7 +609,6 @@
- Fixed Magnitude battle messages.
- Moves that are disabled or out of PP now show transparently in the battle GUI instead of being hidden completely.
- Statuses like sleep and frozen no longer last forever if it was carried over from a previous battle.
- Fixed a formatting error that affected Pokemon nicknames when the storage type is JSON.
### Localization
- Added species, ability, and move translations for `ko_ko`, `jp_jp`, `fr_fr`, `es_es`, `it_it`, and `zh_cn`.
@@ -25,7 +25,6 @@ repositories {
mavenCentral()
//JEI
maven("https://maven.blamejared.com/")
maven("https://maven.tterrag.com/")
}
license {
-5
View File
@@ -27,8 +27,6 @@ dependencies {
implementation(libs.reflect)
modImplementation(libs.fabricLoader)
//Flywheel has no common dep so just pick one and don't use any platform specific code in common
// modCompileOnly(libs.flywheelFabric)
modApi(libs.molang)
compileOnlyApi(libs.jeiApi)
@@ -37,9 +35,6 @@ dependencies {
//shadowCommon group: 'commons-io', name: 'commons-io', version: '2.6'
// For datastore
modCompileOnly(libs.mongoDriverCore)
modCompileOnly(libs.mongoDriverSync)
testRuntimeOnly(libs.junitEngine)
testImplementation(libs.junitApi)
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.client.render.models.blockbench.pose;
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel;
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.Entity;
@@ -40,7 +38,7 @@ public interface Bone {
/**
* Renders a particular bone in relation to a particular entity.
*
* @param context The render context this bone is being drawn for.
* @param entity The entity this bone is being drawn for. This parameter need not be available and can be null
* @param stack The current [MatrixStack] for the renderer
* @param buffer The vertex consumer deployed by the renderer
* @param packedLight The light level to be applied to the bone
@@ -52,8 +50,8 @@ public interface Bone {
*
* @since 1.4.0
*/
void render(
RenderContext context,
<T extends Entity> void render(
@Nullable T entity,
MatrixStack stack,
VertexConsumer buffer,
int packedLight,
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.cobblemonstructures;
public class CobblemonStructureIDs {
//Location of the nbt
//Not sure if this is a good way to store them but doing this meant I wouldnt have to keep typing this out during development
public static final String PLAINS_POKECENTER = "cobblemon:village_plains/village_plains_pokecenter";
public static final String DESERT_POKECENTER = "cobblemon:village_desert/village_desert_pokecenter";
public static final String SAVANNA_POKECENTER = "cobblemon:village_savanna/village_savanna_pokecenter";
public static final String SNOWY_POKECENTER = "cobblemon:village_snowy/village_snowy_pokecenter";
public static final String TAIGA_POKECENTER = "cobblemon:village_taiga/village_taiga_pokecenter";
public static final String PLAINS_LONG_PATH = "cobblemon:village_plains/village_plains_long_path";
public static final String DESERT_LONG_PATH = "cobblemon:village_desert/village_desert_long_path";
public static final String SAVANNA_LONG_PATH = "cobblemon:village_savanna/village_savanna_long_path";
public static final String SNOWY_LONG_PATH = "cobblemon:village_snowy/village_snowy_long_path";
public static final String TAIGA_LONG_PATH = "cobblemon:village_taiga/village_taiga_long_path";
}
@@ -0,0 +1,101 @@
/*
* 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.cobblemonstructures;
import com.cobblemon.mod.common.mixin.StructurePoolAccessor;
import com.mojang.datafixers.util.Pair;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.server.MinecraftServer;
import net.minecraft.structure.pool.LegacySinglePoolElement;
import net.minecraft.structure.pool.SinglePoolElement;
import net.minecraft.structure.pool.StructurePool;
import net.minecraft.structure.pool.StructurePoolElement;
import net.minecraft.structure.processor.StructureProcessorList;
import net.minecraft.structure.processor.StructureProcessorLists;
import net.minecraft.util.Identifier;
import java.util.ArrayList;
import java.util.List;
public class CobblemonStructures {
private static final RegistryKey<StructureProcessorList> EMPTY_PROCESSOR_LIST_KEY = RegistryKey.of(RegistryKeys.PROCESSOR_LIST, new Identifier("minecraft", "empty"));
private static final Integer pokecenterWeight = 35;
private static final Integer longPathWeight = 10;
public static void registerJigsaws(MinecraftServer server) {
Registry<StructurePool> templatePoolRegistry = server.getRegistryManager().get(RegistryKeys.TEMPLATE_POOL);
Registry<StructureProcessorList> processorListRegistry = server.getRegistryManager().get(RegistryKeys.PROCESSOR_LIST);
//No pokecenters yet
//addPokecenters(templatePoolRegistry, processorListRegistry);
//addLongPaths(templatePoolRegistry, processorListRegistry);
}
private static void addPokecenters(Registry<StructurePool> templatePoolRegistry, Registry<StructureProcessorList> processorListRegistry) {
Identifier plainsHousesPoolLocation = new Identifier("minecraft:village/plains/houses");
Identifier desertHousesPoolLocation = new Identifier("minecraft:village/desert/houses");
Identifier savannaHousesPoolLocation = new Identifier("minecraft:village/savanna/houses");
Identifier snowyHousesPoolLocation = new Identifier("minecraft:village/snowy/houses");
Identifier taigaHousesPoolLocation = new Identifier("minecraft:village/taiga/houses");
addBuildingToPool(templatePoolRegistry, processorListRegistry, plainsHousesPoolLocation, CobblemonStructureIDs.PLAINS_POKECENTER, pokecenterWeight, StructurePool.Projection.RIGID, EMPTY_PROCESSOR_LIST_KEY);
addBuildingToPool(templatePoolRegistry, processorListRegistry, desertHousesPoolLocation, CobblemonStructureIDs.DESERT_POKECENTER, pokecenterWeight, StructurePool.Projection.RIGID, EMPTY_PROCESSOR_LIST_KEY);
addBuildingToPool(templatePoolRegistry, processorListRegistry, savannaHousesPoolLocation, CobblemonStructureIDs.SAVANNA_POKECENTER, pokecenterWeight, StructurePool.Projection.RIGID, EMPTY_PROCESSOR_LIST_KEY);
addBuildingToPool(templatePoolRegistry, processorListRegistry, snowyHousesPoolLocation, CobblemonStructureIDs.SNOWY_POKECENTER, pokecenterWeight, StructurePool.Projection.RIGID, EMPTY_PROCESSOR_LIST_KEY);
addBuildingToPool(templatePoolRegistry, processorListRegistry, taigaHousesPoolLocation, CobblemonStructureIDs.TAIGA_POKECENTER, pokecenterWeight, StructurePool.Projection.RIGID, StructureProcessorLists.MOSSIFY_10_PERCENT);
}
private static void addLongPaths(Registry<StructurePool> templatePoolRegistry, Registry<StructureProcessorList> processorListRegistry) {
Identifier plainsStreetsPoolLocation = new Identifier("minecraft:village/plains/streets");
Identifier desertStreetsPoolLocation = new Identifier("minecraft:village/desert/streets");
Identifier savannaStreetsPoolLocation = new Identifier("minecraft:village/savanna/streets");
Identifier snowyStreetsPoolLocation = new Identifier("minecraft:village/snowy/streets");
Identifier taigaStreetsPoolLocation = new Identifier("minecraft:village/taiga/streets");
addLegacyBuildingToPool(templatePoolRegistry, processorListRegistry, plainsStreetsPoolLocation, CobblemonStructureIDs.PLAINS_LONG_PATH, longPathWeight, StructurePool.Projection.TERRAIN_MATCHING, StructureProcessorLists.STREET_PLAINS);
addLegacyBuildingToPool(templatePoolRegistry, processorListRegistry, desertStreetsPoolLocation, CobblemonStructureIDs.DESERT_LONG_PATH, longPathWeight, StructurePool.Projection.TERRAIN_MATCHING, EMPTY_PROCESSOR_LIST_KEY);
addLegacyBuildingToPool(templatePoolRegistry, processorListRegistry, savannaStreetsPoolLocation, CobblemonStructureIDs.SAVANNA_LONG_PATH, longPathWeight, StructurePool.Projection.TERRAIN_MATCHING, StructureProcessorLists.STREET_SAVANNA);
addLegacyBuildingToPool(templatePoolRegistry, processorListRegistry, snowyStreetsPoolLocation, CobblemonStructureIDs.SNOWY_LONG_PATH, longPathWeight, StructurePool.Projection.TERRAIN_MATCHING, StructureProcessorLists.STREET_SNOWY_OR_TAIGA);
addLegacyBuildingToPool(templatePoolRegistry, processorListRegistry, taigaStreetsPoolLocation, CobblemonStructureIDs.TAIGA_LONG_PATH, longPathWeight, StructurePool.Projection.TERRAIN_MATCHING, StructureProcessorLists.STREET_SNOWY_OR_TAIGA);
}
public static void addLegacyBuildingToPool(Registry<StructurePool> templatePoolRegistry, Registry<StructureProcessorList> processorListRegistry, Identifier poolRL, String nbtPieceRL, int weight, StructurePool.Projection projection, RegistryKey<StructureProcessorList> processorListKey) {
addBuildingToPool(templatePoolRegistry, processorListRegistry, poolRL, nbtPieceRL, weight, projection, processorListKey, true);
}
public static void addBuildingToPool(Registry<StructurePool> templatePoolRegistry, Registry<StructureProcessorList> processorListRegistry, Identifier poolRL, String nbtPieceRL, int weight, StructurePool.Projection projection, RegistryKey<StructureProcessorList> processorListKey) {
addBuildingToPool(templatePoolRegistry, processorListRegistry, poolRL, nbtPieceRL, weight, projection, processorListKey, false);
}
public static void addBuildingToPool(Registry<StructurePool> templatePoolRegistry, Registry<StructureProcessorList> processorListRegistry, Identifier poolRL, String nbtPieceRL, int weight, StructurePool.Projection projection, RegistryKey<StructureProcessorList> processorListKey, boolean shouldUseLegacySingePoolElement) {
if (processorListRegistry.getEntry(processorListKey).isEmpty()) return;
RegistryEntry.Reference<StructureProcessorList> processorList = processorListRegistry.getEntry(processorListKey).get();
StructurePool pool = templatePoolRegistry.get(poolRL);
if (pool == null) return;
SinglePoolElement piece;
if (shouldUseLegacySingePoolElement) {
piece = LegacySinglePoolElement.ofProcessedLegacySingle(nbtPieceRL, processorList).apply(projection);
} else {
piece = SinglePoolElement.ofProcessedSingle(nbtPieceRL, processorList).apply(projection);
}
for (int i = 0; i < weight; i++) {
((StructurePoolAccessor) pool).getElements().add(piece);
}
List<Pair<StructurePoolElement, Integer>> listOfPieceEntries = new ArrayList<>(((StructurePoolAccessor) pool).getElementCounts());
listOfPieceEntries.add(new Pair<>(piece, weight));
((StructurePoolAccessor) pool).setElementCounts(listOfPieceEntries);
}
}
@@ -1,38 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.block.PreEmptsExplosion;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.explosion.Explosion;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(value = Explosion.class)
public class ExplosionMixin {
@Redirect(
method = "affectWorld",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/World;getBlockState(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/BlockState;",
ordinal = 0
)
)
public BlockState cobblemon$whenExploded(World world, BlockPos pos) {
BlockState blockState = world.getBlockState(pos);
if(blockState.getBlock() instanceof PreEmptsExplosion preExplosionBlock) {
preExplosionBlock.whenExploded(world, blockState, pos);
}
return blockState;
}
}
@@ -8,9 +8,7 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityModel;
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone;
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext;
import net.minecraft.client.model.ModelPart;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.util.math.MatrixStack;
@@ -38,7 +36,7 @@ public abstract class ModelPartMixin implements Bone {
}
@Override
public void render(RenderContext context, MatrixStack stack, VertexConsumer buffer, int packedLight, int packedOverlay, float r, float g, float b, float a) {
public <T extends Entity> void render(T entity, MatrixStack stack, VertexConsumer buffer, int packedLight, int packedOverlay, float r, float g, float b, float a) {
this.render(stack, buffer, packedLight, packedOverlay, r, g, b, a);
}
@@ -8,12 +8,11 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.world.CobblemonStructureIDs;
import com.cobblemon.mod.common.cobblemonstructures.CobblemonStructureIDs;
import net.minecraft.registry.Registry;
import net.minecraft.structure.PoolStructurePiece;
import net.minecraft.structure.StructureTemplateManager;
import net.minecraft.structure.pool.*;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.random.Random;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.HeightLimitView;
@@ -30,6 +29,7 @@ import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.*;
import java.util.stream.Collectors;
@Mixin(StructurePoolBasedGenerator.StructurePoolGenerator.class)
public abstract class StructurePoolGeneratorMixin {
@@ -37,35 +37,17 @@ public abstract class StructurePoolGeneratorMixin {
@Mutable
@Shadow Deque<StructurePoolBasedGenerator.ShapedPoolStructurePiece> structurePieces;
Map<String, Integer> generatedStructureGroupCounts;
Map<String, Integer> generatedStructureCounts;
private static final Map<String, Integer> structureMaxes;
private static final Map<Identifier, Set<String>> structureGroups = new HashMap<>();
static {
String pokecenter = "pokecenter";
String berry = "berry_farm";
structureGroups.put(CobblemonStructureIDs.PLAINS_POKECENTER, Set.of(pokecenter));
structureGroups.put(CobblemonStructureIDs.DESERT_POKECENTER, Set.of(pokecenter));
structureGroups.put(CobblemonStructureIDs.SAVANNA_POKECENTER, Set.of(pokecenter));
structureGroups.put(CobblemonStructureIDs.SNOWY_POKECENTER, Set.of(pokecenter));
structureGroups.put(CobblemonStructureIDs.TAIGA_POKECENTER, Set.of(pokecenter));
structureGroups.put(CobblemonStructureIDs.SAVANNA_BERRY_SMALL, Set.of(berry));
structureGroups.put(CobblemonStructureIDs.SAVANNA_BERRY_LARGE, Set.of(berry));
structureGroups.put(CobblemonStructureIDs.PLAINS_BERRY_SMALL, Set.of(berry));
structureGroups.put(CobblemonStructureIDs.PLAINS_BERRY_LARGE, Set.of(berry));
structureGroups.put(CobblemonStructureIDs.SNOWY_BERRY_SMALL, Set.of(berry));
structureGroups.put(CobblemonStructureIDs.SNOWY_BERRY_LARGE, Set.of(berry));
structureGroups.put(CobblemonStructureIDs.DESERT_BERRY_SMALL, Set.of(berry));
structureGroups.put(CobblemonStructureIDs.DESERT_BERRY_LARGE, Set.of(berry));
structureGroups.put(CobblemonStructureIDs.TAIGA_BERRY_SMALL, Set.of(berry));
structureGroups.put(CobblemonStructureIDs.TAIGA_BERRY_LARGE, Set.of(berry));
//Mapped using location string as key
Map<String, Integer> aMap = new HashMap<>();
aMap.put(pokecenter, 1);
aMap.put(berry, 2);
aMap.put(CobblemonStructureIDs.PLAINS_POKECENTER, 1);
aMap.put(CobblemonStructureIDs.DESERT_POKECENTER, 1);
aMap.put(CobblemonStructureIDs.SAVANNA_POKECENTER, 1);
aMap.put(CobblemonStructureIDs.SNOWY_POKECENTER, 1);
aMap.put(CobblemonStructureIDs.TAIGA_POKECENTER, 1);
// aMap.put(CobblemonStructureIDs.PLAINS_LONG_PATH, 3);
// aMap.put(CobblemonStructureIDs.DESERT_LONG_PATH, 3);
@@ -75,143 +57,143 @@ public abstract class StructurePoolGeneratorMixin {
structureMaxes = Collections.unmodifiableMap(aMap);
}
public Set<String> getGroups(Identifier structureIdentifier) {
return structureGroups.getOrDefault(structureIdentifier, Set.of());
}
public boolean hasReachedMaximum(Identifier structureIdentifier) {
Set<String> groups = getGroups(structureIdentifier);
for (String group : groups) {
int count = generatedStructureGroupCounts.getOrDefault(group, 0);
if (count >= structureMaxes.getOrDefault(group, Integer.MAX_VALUE)) {
return true;
}
}
return false;
}
public void incrementStructureCount(Identifier structureIdentifier) {
Set<String> groups = getGroups(structureIdentifier);
for (String group : groups) {
generatedStructureGroupCounts.put(group, generatedStructureGroupCounts.getOrDefault(group, 0) + 1);
}
}
@Inject(method = "<init>", at = @At("RETURN"))
private void onStructurePoolGeneratorCreation(Registry<StructurePool> registry, int maxSize, ChunkGenerator chunkGenerator, StructureTemplateManager structureTemplateManager, List<? super PoolStructurePiece> children, Random random, CallbackInfo ci) {
generatedStructureGroupCounts = new HashMap<>();
generatedStructureCounts = new HashMap<>();
}
@ModifyVariable(method = "generatePiece", at = @At("STORE"), ordinal = 1)
private Iterator<StructurePoolElement> reduceStructurePoolElementIterator(Iterator<StructurePoolElement> iterator) {
private Iterator<StructurePoolElement> reduceStructurePoolELementIterator(Iterator<StructurePoolElement> iterator) {
List<StructurePoolElement> reducedList = new ArrayList<>();
while (iterator.hasNext()) {
StructurePoolElement structure = iterator.next();
Identifier structurePieceLocationKey = getCobblemonOnlyLocation(structure);
String structurePieceLocationKey = getCobblemonOnlyLocation(structure);
if (structurePieceLocationKey == null) {
reducedList.add(structure);
continue;
}
if (!hasReachedMaximum(structurePieceLocationKey)) {
Integer currentlyPlacedCount = generatedStructureCounts.get(structurePieceLocationKey);
if (currentlyPlacedCount == null) currentlyPlacedCount = 0;
Integer maxAllowed = structureMaxes.get(structurePieceLocationKey);
if (maxAllowed == null) {
reducedList.add(structure);
continue;
}
if (currentlyPlacedCount < maxAllowed) {
reducedList.add(structure);
}
}
for (String maxStructureLocationKey : structureMaxes.keySet()) {
Integer maxAllowed = structureMaxes.get(maxStructureLocationKey);
Integer currentlyGenerated = generatedStructureCounts.get(maxStructureLocationKey);
if (currentlyGenerated == null) currentlyGenerated = 0;
Integer allowedInstances = maxAllowed - currentlyGenerated;
reducedList = removeInstanceOfLocationKeyFrom(reducedList, allowedInstances, maxStructureLocationKey);
}
return reducedList.iterator();
}
@ModifyVariable(method = "generatePiece", at = @At("STORE"), ordinal = 1)
private PoolStructurePiece injected(PoolStructurePiece poolStructurePiece) {
Identifier structureLocationKey = getCobblemonOnlyLocation(poolStructurePiece.getPoolElement());
String structureLocationKey = getCobblemonOnlyLocation(poolStructurePiece.getPoolElement());
if (structureLocationKey != null) {
incrementStructureCount(structureLocationKey);
Integer currentlyGenerated = generatedStructureCounts.get(structureLocationKey);
if (currentlyGenerated == null) currentlyGenerated = 0;
generatedStructureCounts.put(structureLocationKey, currentlyGenerated + 1);
}
return poolStructurePiece;
}
// This doesn't SEEM necessary? It appears to work without.
@Inject(method = "generatePiece", at = @At("HEAD"))
private void beforeGeneratePiece(PoolStructurePiece piece, MutableObject<VoxelShape> pieceShape, int minY, boolean modifyBoundingBox, HeightLimitView world, NoiseConfig noiseConfig, CallbackInfo ci) {
// Identifier structureLocationKey = getCobblemonOnlyLocation(piece.getPoolElement());
//
// if (structureLocationKey != null) {
// Integer currentlyGenerated = generatedStructureCounts.get(structureLocationKey);
// if (currentlyGenerated == null) currentlyGenerated = 0;
// generatedStructureCounts.put(structureLocationKey, currentlyGenerated + 1);
// }
//
// List<StructurePoolBasedGenerator.ShapedPoolStructurePiece> reducedStructurePiecesList = structurePieces.stream().toList();
//
// for (Identifier maxStructureLocationKey : structureMaxes.keySet()) {
// Integer maxAllowed = structureMaxes.get(maxStructureLocationKey);
//
// Integer currentlyGenerated = generatedStructureCounts.get(maxStructureLocationKey);
// if (currentlyGenerated == null) currentlyGenerated = 0;
// if (currentlyGenerated < maxAllowed) {
// continue;
// }
//
// //Already have max so need to remove
// reducedStructurePiecesList = reducedStructurePiecesList.stream()
// .filter(shapedStructurePiece -> {
// Identifier locationKey = getCobblemonOnlyLocation(shapedStructurePiece.piece.getPoolElement());
// if (locationKey == null) {
// return true;
// }
//
// return !locationKey.equals(maxStructureLocationKey);
// })
// .collect(Collectors.toList());
// }
//
// structurePieces = new ArrayDeque<>(reducedStructurePiecesList);
String structureLocationKey = getCobblemonOnlyLocation(piece.getPoolElement());
if (structureLocationKey != null && structureLocationKey.equals("cobblemon:village_snowy/village_snowy_pokecenter")) System.out.println("Before Counts" + generatedStructureCounts);
if (structureLocationKey != null) {
Integer currentlyGenerated = generatedStructureCounts.get(structureLocationKey);
if (currentlyGenerated == null) currentlyGenerated = 0;
generatedStructureCounts.put(structureLocationKey, currentlyGenerated + 1);
}
List<StructurePoolBasedGenerator.ShapedPoolStructurePiece> reducedStructurePiecesList = structurePieces.stream().toList();
for (String maxStructureLocationKey : structureMaxes.keySet()) {
Integer maxAllowed = structureMaxes.get(maxStructureLocationKey);
Integer currentlyGenerated = generatedStructureCounts.get(maxStructureLocationKey);
if (currentlyGenerated == null) currentlyGenerated = 0;
if (currentlyGenerated < maxAllowed) {
continue;
}
//Already have max so need to remove
reducedStructurePiecesList = reducedStructurePiecesList.stream()
.filter(shapedStructurePiece -> {
String locationKey = getCobblemonOnlyLocation(shapedStructurePiece.piece.getPoolElement());
if (locationKey == null) {
return true;
}
return !locationKey.equals(maxStructureLocationKey);
})
.collect(Collectors.toList());
}
structurePieces = new ArrayDeque<>(reducedStructurePiecesList);
}
private static Identifier getCobblemonOnlyLocation(StructurePoolElement structurePoolElement) {
Identifier location = getLocationIfAvailable(structurePoolElement);
private static String getCobblemonOnlyLocation(StructurePoolElement structurePoolElement) {
String location = getLocationIfAvailable(structurePoolElement);
if (location == null) return null;
if (!location.getNamespace().equals("cobblemon")) return null;
if (!location.contains("cobblemon")) return null;
return location;
}
private static Identifier getLocationIfAvailable(StructurePoolElement structurePoolElement) {
private static String getLocationIfAvailable(StructurePoolElement structurePoolElement) {
if (structurePoolElement instanceof LegacySinglePoolElement legacySinglePoolElement) {
if (legacySinglePoolElement.location.left().isEmpty()) return null;
return legacySinglePoolElement.location.left().get();
return legacySinglePoolElement.location.left().get().toString();
} else if (structurePoolElement instanceof SinglePoolElement singlePoolElement) {
if (singlePoolElement.location.left().isEmpty()) return null;
return singlePoolElement.location.left().get();
return singlePoolElement.location.left().get().toString();
} else {
return null;
}
}
// private List<StructurePoolElement> removeInstanceOfLocationKeyFrom(List<StructurePoolElement> structureList, Integer allowedNumberOfInstances, Identifier locationKey) {
// List<StructurePoolElement> reducedList = new ArrayList<>();
// int instancesFound = 0;
//
// for (StructurePoolElement structurePoolElement: structureList) {
// Identifier structureKey = getCobblemonOnlyLocation(structurePoolElement);
// if (structureKey == null || !structureKey.equals(locationKey)) {
// reducedList.add(structurePoolElement);
// continue;
// }
//
// if (instancesFound >= allowedNumberOfInstances) {
// continue;
// }
//
// reducedList.add(structurePoolElement);
// instancesFound++;
// }
//
// return reducedList;
// }
private List<StructurePoolElement> removeInstanceOfLocationKeyFrom(List<StructurePoolElement> structureList, Integer allowedNumberOfInstances, String locationKey) {
List<StructurePoolElement> reducedList = new ArrayList<>();
int instancesFound = 0;
for (StructurePoolElement structurePoolElement: structureList) {
String structureKey = getCobblemonOnlyLocation(structurePoolElement);
if (structureKey == null || !structureKey.equals(locationKey)) {
reducedList.add(structurePoolElement);
continue;
}
if (instancesFound >= allowedNumberOfInstances) {
continue;
}
reducedList.add(structurePoolElement);
instancesFound++;
}
return reducedList;
}
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.advancement.CobblemonCriteria
import com.cobblemon.mod.common.advancement.criterion.EvolvePokemonContext
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.SeasonResolver
import com.cobblemon.mod.common.api.data.DataProvider
@@ -19,9 +18,7 @@ import com.cobblemon.mod.common.api.drop.ItemDropEntry
import com.cobblemon.mod.common.api.events.CobblemonEvents.BATTLE_VICTORY
import com.cobblemon.mod.common.api.events.CobblemonEvents.DATA_SYNCHRONIZED
import com.cobblemon.mod.common.api.events.CobblemonEvents.EVOLUTION_COMPLETE
import com.cobblemon.mod.common.api.events.CobblemonEvents.LEVEL_UP_EVENT
import com.cobblemon.mod.common.api.events.CobblemonEvents.POKEMON_CAPTURED
import com.cobblemon.mod.common.api.events.CobblemonEvents.TRADE_COMPLETED
import com.cobblemon.mod.common.api.net.serializers.PoseTypeDataSerializer
import com.cobblemon.mod.common.api.net.serializers.StringSetDataSerializer
import com.cobblemon.mod.common.api.net.serializers.Vec3DataSerializer
@@ -49,16 +46,13 @@ import com.cobblemon.mod.common.api.spawning.prospecting.SpawningProspector
import com.cobblemon.mod.common.api.starter.StarterHandler
import com.cobblemon.mod.common.api.storage.PokemonStoreManager
import com.cobblemon.mod.common.api.storage.adapter.conversions.ReforgedConversion
import com.cobblemon.mod.common.api.storage.adapter.database.MongoDBStoreAdapter
import com.cobblemon.mod.common.api.storage.adapter.flatfile.FileStoreAdapter
import com.cobblemon.mod.common.api.storage.adapter.flatfile.JSONStoreAdapter
import com.cobblemon.mod.common.api.storage.adapter.flatfile.NBTStoreAdapter
import com.cobblemon.mod.common.api.storage.adapter.flatifle.FileStoreAdapter
import com.cobblemon.mod.common.api.storage.adapter.flatifle.JSONStoreAdapter
import com.cobblemon.mod.common.api.storage.adapter.flatifle.NBTStoreAdapter
import com.cobblemon.mod.common.api.storage.factory.FileBackedPokemonStoreFactory
import com.cobblemon.mod.common.api.storage.pc.PCStore
import com.cobblemon.mod.common.api.storage.pc.link.PCLinkManager
import com.cobblemon.mod.common.api.storage.player.PlayerDataStoreManager
import com.cobblemon.mod.common.api.storage.player.factory.JsonPlayerDataStoreFactory
import com.cobblemon.mod.common.api.storage.player.factory.MongoPlayerDataStoreFactory
import com.cobblemon.mod.common.api.tags.CobblemonEntityTypeTags
import com.cobblemon.mod.common.battles.BagItems
import com.cobblemon.mod.common.battles.BattleFormat
@@ -92,6 +86,7 @@ import com.cobblemon.mod.common.pokemon.feature.TagSeasonResolver
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.pokemon.properties.HiddenAbilityPropertyType
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.pokemon.properties.UntradeableProperty
import com.cobblemon.mod.common.pokemon.properties.tags.PokemonFlagProperty
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
@@ -106,10 +101,6 @@ import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.world.feature.CobblemonPlacedFeatures
import com.cobblemon.mod.common.world.feature.ore.CobblemonOrePlacedFeatures
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import com.mongodb.ConnectionString
import com.mongodb.MongoClientSettings
import com.mongodb.client.MongoClient
import com.mongodb.client.MongoClients
import java.io.File
import java.io.FileReader
import java.io.FileWriter
@@ -254,6 +245,7 @@ object Cobblemon {
DataKeys.HAS_BEEN_SHEARED,
FlagSpeciesFeatureProvider(keys = listOf(DataKeys.HAS_BEEN_SHEARED), default = false))
CustomPokemonProperty.register(UntradeableProperty)
CustomPokemonProperty.register(UncatchableProperty)
CustomPokemonProperty.register(PokemonFlagProperty)
CustomPokemonProperty.register(HiddenAbilityPropertyType)
@@ -266,52 +258,15 @@ object Cobblemon {
PlatformEvents.SERVER_STARTING.subscribe { event ->
val server = event.server
playerData = PlayerDataStoreManager().also { it.setup(server) }
val mongoClient: MongoClient?
val pokemonStoreRoot = server.getSavePath(WorldSavePath.ROOT).resolve("pokemon").toFile()
val storeAdapter = when (config.storageFormat) {
"nbt", "json" -> {
val jsonFactory = JsonPlayerDataStoreFactory()
jsonFactory.setup(server)
playerData.setFactory(jsonFactory)
if (config.storageFormat == "nbt") {
NBTStoreAdapter(pokemonStoreRoot.absolutePath, useNestedFolders = true, folderPerClass = true)
} else {
JSONStoreAdapter(
pokemonStoreRoot.absolutePath,
useNestedFolders = true,
folderPerClass = true
)
}
}
"mongodb" -> {
try {
Class.forName("com.mongodb.client.MongoClient")
val mongoClientSettings = MongoClientSettings.builder()
.applyConnectionString(ConnectionString(config.mongoDBConnectionString))
.build()
mongoClient = MongoClients.create(mongoClientSettings)
val mongoFactory = MongoPlayerDataStoreFactory(mongoClient, config.mongoDBDatabaseName)
playerData.setFactory(mongoFactory)
MongoDBStoreAdapter(mongoClient, config.mongoDBDatabaseName)
} catch (e: ClassNotFoundException) {
LOGGER.error("MongoDB driver not found.")
throw e
}
}
else -> throw IllegalArgumentException("Unsupported storageFormat: ${config.storageFormat}")
}
.with(ReforgedConversion(server.getSavePath(WorldSavePath.ROOT))) as FileStoreAdapter<*>
storage.registerFactory(
priority = Priority.LOWEST,
factory = FileBackedPokemonStoreFactory(
adapter = storeAdapter,
adapter = if (config.storageFormat == "nbt") {
NBTStoreAdapter(pokemonStoreRoot.absolutePath, useNestedFolders = true, folderPerClass = true)
} else {
JSONStoreAdapter(pokemonStoreRoot.absolutePath, useNestedFolders = true, folderPerClass = true)
}.with(ReforgedConversion(server.getSavePath(WorldSavePath.ROOT))) as FileStoreAdapter<*>,
createIfMissing = true,
pcConstructor = { uuid -> PCStore(uuid).also { it.resize(config.defaultBoxCount) } }
)
@@ -337,7 +292,7 @@ object Cobblemon {
// Ensure the config option is enabled and that the result was a ninjask and that shedinja exists
if (this.config.ninjaskCreatesShedinja && pokemon.species.resourceIdentifier == ninjaskIdentifier && PokemonSpecies.getByIdentifier(Pokemon.SHEDINJA) != null) {
val player = pokemon.getOwnerPlayer() ?: return@subscribe
if (player.isCreative || player.inventory.containsAny { it.item is PokeBallItem }) {
if (player.inventory.containsAny { it.item is PokeBallItem }) {
var pokeball = Items.AIR
player.inventory.combinedInventory.forEach {
it.forEach {
@@ -346,12 +301,7 @@ object Cobblemon {
}
}
}
if (!player.isCreative) {
player.inventory.removeAmountIf(1) { it.item is PokeBallItem }
}
if (pokeball == Items.AIR) {
pokeball = CobblemonItems.POKE_BALL
}
player.inventory.removeAmountIf(1) { it.item is PokeBallItem }
val properties = event.evolution.result.copy()
properties.species = Pokemon.SHEDINJA.toString()
val product = pokemon.clone()
@@ -359,12 +309,9 @@ object Cobblemon {
properties.apply(product)
product.caughtBall = (pokeball as PokeBallItem).pokeBall
pokemon.storeCoordinates.get()?.store?.add(product)
CobblemonCriteria.EVOLVE_POKEMON.trigger(player, EvolvePokemonContext(event.pokemon.preEvolution!!.species.resourceIdentifier, product.species.resourceIdentifier, playerData.get(player).advancementData.totalEvolvedCount))
}
}
}
LEVEL_UP_EVENT.subscribe { AdvancementHandler.onLevelUp(it) }
TRADE_COMPLETED.subscribe { AdvancementHandler.onTradeCompleted(it) }
BagItems.observable.subscribe {
LOGGER.info("Starting dummy Showdown battle to force it to pre-load data.")
@@ -306,7 +306,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, RegistryKey<Registry<
private fun berryBlock(name: String): BerryBlock {
val identifier = cobblemonResource("${name}_berry")
val block = this.create(identifier.path, BerryBlock(identifier, AbstractBlock.Settings.copy(Blocks.WHEAT).dynamicBounds().sounds(BlockSoundGroup.CROP).strength(0.2F)))
val block = this.create(identifier.path, BerryBlock(identifier, AbstractBlock.Settings.copy(Blocks.WHEAT).dynamicBounds().sounds(BlockSoundGroup.CROP)))
this.berries[identifier] = block
return block
}
@@ -44,7 +44,6 @@ object CobblemonCommands {
TestPcSlotCommand.register(dispatcher)
TestPartySlotCommand.register(dispatcher)
ClearPartyCommand.register(dispatcher)
ClearPCCommand.register(dispatcher)
PokemonRestartCommand.register(dispatcher)
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
@@ -21,7 +20,6 @@ import net.minecraft.network.listener.ClientPlayPacketListener
import net.minecraft.network.packet.Packet
import net.minecraft.registry.RegistryKey
import net.minecraft.registry.tag.TagKey
import net.minecraft.resource.ResourceManager
import net.minecraft.resource.ResourceReloader
import net.minecraft.resource.ResourceType
import net.minecraft.server.MinecraftServer
@@ -162,13 +160,6 @@ interface CobblemonImplementation {
*/
fun server(): MinecraftServer?
/**
* Handles platform independent reloading of a [JsonDataRegistry].
*
* @param registry The [JsonDataRegistry] to reload.
* @param manager The [ResourceManager] to reload from.
*/
fun <T> reloadJsonRegistry(registry: JsonDataRegistry<T>, manager: ResourceManager): HashMap<Identifier, T>
}
enum class ModAPI {
@@ -231,8 +231,10 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
@JvmField val PEAT_BLOCK = noSettingsItem("peat_block")
@JvmField val RAZOR_CLAW = noSettingsItem("razor_claw")
@JvmField val RAZOR_FANG = noSettingsItem("razor_fang")
@JvmField val AUSPICIOUS_ARMOR = heldItem("auspicious_armor")
@JvmField val MALICIOUS_ARMOR = heldItem("malicious_armor")
// Armor
@JvmField val AUSPICIOUS_ARMOR = armorItem("auspicious_armor", AuspiciousArmorMaterial(), ArmorItem.Type.CHESTPLATE, Item.Settings().fireproof())
@JvmField val MALICIOUS_ARMOR = armorItem("malicious_armor", MaliciousArmorMaterial(), ArmorItem.Type.CHESTPLATE, Item.Settings().fireproof())
private val berries = mutableMapOf<Identifier, BerryItem>()
// Plants
@@ -338,7 +340,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
@JvmField
val ROASTED_LEEK = create("roasted_leek", Item(Item.Settings().food(FoodComponent.Builder().snack().hunger(3).saturationModifier(0.3f).build())))
@JvmField
val BRAISED_VIVICHOKE = create("braised_vivichoke", Item(Item.Settings().food(FoodComponent.Builder().hunger(6).saturationModifier(0.6f).build())))
val BRAISED_VIVICHOKE = create("braised_vivichoke", Item(Item.Settings().maxCount(1).food(FoodComponent.Builder().hunger(6).saturationModifier(0.6f).build())))
@JvmField
val VIVICHOKE_DIP = create("vivichoke_dip", object : StewItem(Settings().maxCount(1)
.food(FoodComponent.Builder()
@@ -696,6 +698,12 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
}
)
private fun armorItem(name: String, type: ArmorMaterial, slot: ArmorItem.Type, settings: Item.Settings): ArmorItem {
val armor = ArmorItem(type, slot, settings)
val item = create(name, armor)
return item
}
private fun berryItem(name: String, berryBlock: BerryBlock): BerryItem {
val finalName = "${name}_berry"
val item = this.create(finalName, BerryItem(berryBlock))
@@ -211,7 +211,6 @@ object CobblemonNetwork : NetworkManager {
this.createClientBound(HeldItemUpdatePacket.ID, HeldItemUpdatePacket::decode, PokemonUpdatePacketHandler())
this.createClientBound(PokemonStateUpdatePacket.ID, PokemonStateUpdatePacket::decode, PokemonUpdatePacketHandler())
this.createClientBound(TetheringUpdatePacket.ID, TetheringUpdatePacket::decode, PokemonUpdatePacketHandler())
this.createClientBound(TradeableUpdatePacket.ID, TradeableUpdatePacket::decode, PokemonUpdatePacketHandler())
// Evolution start
this.createClientBound(AddEvolutionPacket.ID, AddEvolutionPacket::decode, PokemonUpdatePacketHandler())
@@ -78,8 +78,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, RegistryKey<Regi
@JvmField
val MEDICINE_SPRAY_USE = this.create("medicine_spray.use")
@JvmField
val BERRY_HARVEST = this.create("berry.harvest")
@JvmField
val BERRY_EAT = this.create("berry.eat")
@@ -88,6 +86,11 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, RegistryKey<Regi
@JvmField
val MULCH_REMOVE = this.create("mulch.remove")
@JvmField
val AUSPICIOUS_ARMOR_EQUIP = this.create("auspicious_armor.equip")
@JvmField
val MALICIOUS_ARMOR_EQUIP = this.create("malicious_armor.equip")
private fun create(name: String): SoundEvent = this.create(name, SoundEvent.of(cobblemonResource(name)))
}
@@ -32,7 +32,7 @@ object CobblemonTradeOffers {
* @return The resulting list.
*/
fun tradeOffersFor(profession: VillagerProfession): List<VillagerTradeOffer> = when (profession) {
VillagerProfession.FARMER -> listOf(VillagerTradeOffer(VillagerProfession.FARMER, 3, listOf(TradeOffers.SellItemFactory(CobblemonItems.VIVICHOKE_SEEDS, 24, 1, 1, 6))))
VillagerProfession.FARMER -> listOf(VillagerTradeOffer(VillagerProfession.FARMER, 3, listOf(TradeOffers.SellItemFactory(CobblemonItems.VIVICHOKE_SEEDS, 32, 1, 1, 6))))
else -> emptyList()
}
@@ -42,7 +42,7 @@ object CobblemonTradeOffers {
* @return The resulting list.
*/
fun resolveWanderingTradeOffers(): List<WandererTradeOffer> = listOf(
WandererTradeOffer(false, listOf(TradeOffers.SellItemFactory(CobblemonItems.VIVICHOKE_SEEDS, 24, 1, 1, 6)))
WandererTradeOffer(false, listOf(TradeOffers.SellItemFactory(CobblemonItems.VIVICHOKE_SEEDS, 32, 1, 1, 6)))
)
/**
@@ -47,13 +47,13 @@ object CobblemonCriteria {
val EVOLVE_POKEMON = this.create(
SimpleCriterionTrigger(
cobblemonResource("pokemon_evolved"),
EvolvePokemonCriterionCondition::class.java
SimpleCountableCriterionCondition::class.java
)
)
val WIN_BATTLE = this.create(
BattleCountableCriterionTrigger(
SimpleCriterionTrigger(
cobblemonResource("battles_won"),
BattleCountableCriterionCondition::class.java
SimpleCountableCriterionCondition::class.java
)
)
@@ -71,41 +71,6 @@ object CobblemonCriteria {
)
)
val POKEMON_INTERACT = this.create(
SimpleCriterionTrigger(
cobblemonResource("pokemon_interact"),
PokemonInteractCriterion::class.java
)
)
val PARTY_CHECK = this.create(
SimpleCriterionTrigger(
cobblemonResource("party"),
PartyCheckCriterion::class.java
)
)
val LEVEL_UP = this.create(
SimpleCriterionTrigger(
cobblemonResource("level_up"),
LevelUpCriterionCondition::class.java
)
)
val PASTURE_USE = this.create(
SimpleCriterionTrigger(
cobblemonResource("pasture_use"),
PickStarterCriterionCondition::class.java
)
)
val TRADE_POKEMON = this.create(
SimpleCriterionTrigger(
cobblemonResource("trade_pokemon"),
TradePokemonCriterionCondition::class.java
)
)
private fun <T : Criterion<*>> create(criteria: T): T = Cobblemon.implementation.registerCriteria(criteria)
}
@@ -1,67 +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.advancement.criterion
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.util.toJsonArray
import com.google.gson.JsonObject
import net.minecraft.predicate.entity.LootContextPredicate
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
open class BattleCountableCriterionTrigger(identifier: Identifier, criterionClass: Class<BattleCountableCriterionCondition>) : SimpleCriterionTrigger<BattleCountableContext, BattleCountableCriterionCondition>(identifier, criterionClass) {
}
class BattleCountableCriterionCondition(id: Identifier, predicate: LootContextPredicate) : CountableCriterionCondition<BattleCountableContext>(id, predicate) {
private var battleTypes = mutableListOf("any")
override fun fromJson(json: JsonObject) {
super.fromJson(json)
if(!json.get("battle_types").isJsonNull) {
battleTypes.clear()
json.get("battle_types").asJsonArray.asList().forEach() {
battleTypes.add(it.asString)
}
}
}
override fun toJson(json: JsonObject) {
super.toJson(json)
json.add("battle_types", battleTypes.toJsonArray())
}
override fun matches(player: ServerPlayerEntity, context: BattleCountableContext): Boolean {
var typeCheck = false
val advancementData = Cobblemon.playerData.get(player).advancementData
if (battleTypes.isEmpty() || battleTypes.contains("any")) {
typeCheck = true
}
if (battleTypes.contains("pvp")) {
typeCheck = context.battle.isPvP
context.times = advancementData.totalPvPBattleVictoryCount
}
if (battleTypes.contains("pvw")) {
typeCheck = context.battle.isPvW
context.times = advancementData.totalPvWBattleVictoryCount
}
if (battleTypes.contains("pvn")) {
typeCheck = context.battle.isPvN
context.times = advancementData.totalPvWBattleVictoryCount
}
if (battleTypes.size > 1) {
context.times = advancementData.totalBattleVictoryCount
}
return context.times >= count && typeCheck
}
}
open class BattleCountableContext(times : Int, var battle : PokemonBattle) : CountableContext(times)
@@ -34,6 +34,6 @@ class CaughtPokemonCriterionCondition(id: Identifier, predicate: LootContextPred
}
override fun matches(player: ServerPlayerEntity, context: CountablePokemonTypeContext): Boolean {
return super.matches(player, context) && (context.type == type || type == "any")
return super.matches(player, context) && context.type == type
}
}
@@ -1,38 +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.advancement.criterion
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.google.gson.JsonObject
import net.minecraft.predicate.entity.LootContextPredicate
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
class EvolvePokemonCriterionCondition(id: Identifier, entity: LootContextPredicate) : CountableCriterionCondition<EvolvePokemonContext>(id, entity) {
var species = "any"
var evolution = "any"
override fun toJson(json: JsonObject) {
super.toJson(json)
json.addProperty("species", species)
json.addProperty("evolution", evolution)
}
override fun fromJson(json: JsonObject) {
super.fromJson(json)
species = json.get("species")?.asString ?: "any"
evolution = json.get("evolution")?.asString ?: "any"
}
override fun matches(player: ServerPlayerEntity, context: EvolvePokemonContext): Boolean {
return context.times >= count && (context.species == species.asIdentifierDefaultingNamespace() || species == "any") &&
(context.evolution == evolution.asIdentifierDefaultingNamespace() || evolution == "any")
}
}
open class EvolvePokemonContext(val species : Identifier, val evolution : Identifier, times: Int) : CountableContext(times)
@@ -1,41 +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.advancement.criterion
import com.cobblemon.mod.common.pokemon.Pokemon
import com.google.gson.JsonObject
import net.minecraft.predicate.entity.LootContextPredicate
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
class LevelUpCriterionCondition(id: Identifier, entity: LootContextPredicate) : SimpleCriterionCondition<LevelUpContext>(id, entity) {
var level = 0
var evolved = true
override fun toJson(json: JsonObject) {
json.addProperty("level", level)
json.addProperty("has_evolved", evolved)
}
override fun fromJson(json: JsonObject) {
level = json.get("level")?.asInt ?: 0
evolved = json.get("has_evolved")?.asBoolean ?: true
}
override fun matches(player: ServerPlayerEntity, context: LevelUpContext): Boolean {
val preEvo = context.pokemon.preEvolution == null
val hasEvolution = !context.pokemon.evolutions.none()
var evolutionCheck = true
if (preEvo || hasEvolution) {
evolutionCheck = !(preEvo == hasEvolution)
}
return level == context.level && evolutionCheck == evolved
}
}
open class LevelUpContext(var level : Int, var pokemon : Pokemon)
@@ -1,54 +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.advancement.criterion
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.party
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import net.minecraft.predicate.entity.LootContextPredicate
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
class PartyCheckCriterion(id: Identifier, entity: LootContextPredicate) : SimpleCriterionCondition<PartyCheckContext>(id, entity){
val party = mutableListOf<Identifier>()
override fun toJson(json: JsonObject) {
json.add("party", JsonArray(party.size).also {
party.forEach { pokemon -> it.add(pokemon.toString()) }
})
}
override fun fromJson(json: JsonObject) {
party.clear()
json.getAsJsonArray("party").forEach { element ->
party.add(element.asString.asIdentifierDefaultingNamespace())
}
}
override fun matches(player: ServerPlayerEntity, context: PartyCheckContext): Boolean {
val playerParty = player.party()
val matches = mutableListOf<Identifier>()
party.forEach {
if (it == "any".asIdentifierDefaultingNamespace()) {
matches.add(it)
}
}
val partyCount = playerParty.count()
if (matches.containsAll(party) && party.size == partyCount && matches.size == partyCount) return true
playerParty.iterator().forEach {
if (party.contains(it.species.resourceIdentifier)) {
matches.add(it.species.resourceIdentifier)
}
}
return matches.containsAll(party) && matches.size == partyCount
}
}
open class PartyCheckContext(val party : PlayerPartyStore)
@@ -1,34 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.advancement.criterion
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.google.gson.JsonObject
import net.minecraft.predicate.entity.LootContextPredicate
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
open class PokemonInteractContext(var type: Identifier, var item : Identifier)
class PokemonInteractCriterion(id: Identifier, entity: LootContextPredicate) : SimpleCriterionCondition<PokemonInteractContext>(id, entity) {
var type = "any"
var item = "any"
override fun toJson(json: JsonObject) {
json.addProperty("type", type)
json.addProperty("item", item)
}
override fun fromJson(json: JsonObject) {
type = json.get("type")?.asString ?: "any"
item = json.get("item")?.asString ?: "any"
}
override fun matches(player: ServerPlayerEntity, context: PokemonInteractContext): Boolean {
return (context.type == type.asIdentifierDefaultingNamespace() || type == "any") && (context.item == item.asIdentifierDefaultingNamespace() || item == "any")
}
}
@@ -1,47 +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.advancement.criterion
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.google.gson.JsonObject
import net.minecraft.predicate.entity.LootContextPredicate
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
class TradePokemonCriterionCondition(id: Identifier, entity: LootContextPredicate) : SimpleCriterionCondition<TradePokemonContext>(id, entity) {
var traded = "any"
var received = "any"
var tradedHeldItem = "any"
var receivedHeldItem = "any"
override fun toJson(json: JsonObject) {
json.addProperty("traded", traded)
json.addProperty("received", received)
json.addProperty("traded_held_item", tradedHeldItem)
json.addProperty("received_held_item", receivedHeldItem)
}
override fun fromJson(json: JsonObject) {
traded = json.get("traded")?.asString ?: "any"
received = json.get("received")?.asString ?: "any"
tradedHeldItem = json.get("traded_held_item")?.asString ?: "minecraft:air"
receivedHeldItem = json.get("received_held_item")?.asString ?: "minecraft:air"
}
override fun matches(player: ServerPlayerEntity, context: TradePokemonContext): Boolean {
val heldItem1 = context.traded.heldItem().item.registryEntry.registryKey().value
val heldItem2 = context.received.heldItem().item.registryEntry.registryKey().value
return (context.traded.species.resourceIdentifier == traded.asIdentifierDefaultingNamespace() || traded == "any") &&
(context.received.species.resourceIdentifier == received.asIdentifierDefaultingNamespace() || received == "any") &&
(heldItem1 == tradedHeldItem.asIdentifierDefaultingNamespace() || heldItem1 == "minecraft:air".asIdentifierDefaultingNamespace()) &&
(heldItem2 == receivedHeldItem.asIdentifierDefaultingNamespace() || heldItem2 == "minecraft:air".asIdentifierDefaultingNamespace())
}
}
open class TradePokemonContext(val traded: Pokemon, val received: Pokemon)
@@ -35,7 +35,7 @@ interface SleepDepth {
override fun shouldWake(pokemonEntity: PokemonEntity): Boolean {
val nearbyPlayers = pokemonEntity.world.getPlayers(TargetPredicate.createNonAttackable(), pokemonEntity, Box.of(pokemonEntity.pos, 16.0, 16.0, 16.0))
return nearbyPlayers.any { !it.isSneaking }
return nearbyPlayers.none { !it.isSneaking }
}
}
@@ -10,8 +10,6 @@ package com.cobblemon.mod.common.api.battles.interpreter
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.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import java.util.UUID
@@ -196,11 +194,6 @@ class BattleMessage(rawMessage: String) {
return Effect.parse(data)
}
fun moveAt(index: Int): MoveTemplate? {
val argument = argumentAt(index)?.lowercase()?.replace("[^a-z0-9]".toRegex(), "") ?: return null
return Moves.getByName(argument)
}
/**
* Queries an optional argument with given [argumentName] for a 'pnx' that will be parsed into a [BattleActor] and [ActiveBattlePokemon].
*
@@ -40,7 +40,6 @@ import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMusicPacket
import com.cobblemon.mod.common.pokemon.evolution.progress.DefeatEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.requirements.DefeatRequirement
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getPlayer
import java.io.File
@@ -209,16 +208,11 @@ open class PokemonBattle(
faintedPokemons.forEach { faintedPokemon ->
for (opponentPokemon in opponentNonFaintedPokemons) {
val facedFainted = opponentPokemon.facedOpponents.contains(faintedPokemon)
val defeatProgress = DefeatEvolutionProgress()
val pokemon = opponentPokemon.effectedPokemon
if (facedFainted) {
pokemon.evolutions.forEach { evolution ->
evolution.requirements.filterIsInstance<DefeatRequirement>().forEach { defeatRequirement ->
if (defeatRequirement.target.matches(faintedPokemon.effectedPokemon)) {
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is DefeatEvolutionProgress && it.currentProgress().target == defeatRequirement.target }) { DefeatEvolutionProgress() }
progress.updateProgress(DefeatEvolutionProgress.Progress(defeatRequirement.target, progress.currentProgress().amount + 1))
}
}
}
if (facedFainted && defeatProgress.shouldKeep(pokemon)) {
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is DefeatEvolutionProgress && it.currentProgress().target.matches(faintedPokemon.effectedPokemon) }) { defeatProgress }
progress.updateProgress(DefeatEvolutionProgress.Progress(progress.currentProgress().target, progress.currentProgress().amount + 1))
}
val multiplier = when {
// ToDo when Exp. All is implement if enabled && !facedFainted return 2.0, probably should be a configurable value too, this will have priority over the Exp. Share
@@ -50,7 +50,6 @@ object Berries : JsonDataRegistry<Berry> {
.registerTypeAdapter(MulchVariant::class.java, MulchVariantAdapter)
.registerTypeAdapter(NumberRange.FloatRange::class.java, FloatNumberRangeAdapter)
.registerTypeAdapter(Status::class.java, StatusAdapter)
.registerTypeAdapter(TypeToken.getParameterized(Collection::class.java, Box::class.java).type, BoxCollectionAdapter)
.registerTypeAdapter(Box::class.java, BoxAdapter)
.registerTypeAdapter(Vec3d::class.java, VerboseVec3dAdapter)
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
@@ -66,6 +65,9 @@ object Berries : JsonDataRegistry<Berry> {
private val berries = hashMapOf<Identifier, Berry>()
val PECHA
get() = this.getByName("")
override fun reload(data: Map<Identifier, Berry>) {
this.berries.clear()
data.forEach { (identifier, berry) ->
@@ -23,7 +23,6 @@ import com.cobblemon.mod.common.util.writeBox
import com.google.gson.annotations.SerializedName
import java.awt.Color
import java.util.EnumSet
import kotlin.math.min
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.client.model.ModelPart
@@ -160,14 +159,16 @@ class Berry(
val base = this.baseYield.random()
val bonus = this.bonusYield(world, state, pos)
var yield = base + bonus.first
val treeEntity = world.getBlockEntity(pos) as BerryBlockEntity
if (BerryBlock.getMulch(state) == MulchVariant.RICH) {
yield = min(yield + 1, maxYield())
treeEntity.decrementMulchDuration(world, pos, state)
}
val event = BerryYieldCalculationEvent(this, world, state, pos, placer, yield, bonus.second)
val treeEntity = world.getBlockEntity(pos) as BerryBlockEntity
if (treeEntity.mulchVariant == MulchVariant.RICH) {
yield = yield.times(1.2).coerceAtMost(maxYield().toDouble()).toInt()
treeEntity.mulchDuration--
}
CobblemonEvents.BERRY_YIELD.post(event) { yield = it.yield }
return yield
//Just for testing the renderer
//return growthPoints.size
}
/**
@@ -292,13 +293,14 @@ class Berry(
private fun bonusYield(world: World, state: BlockState, pos: BlockPos): Pair<Int, Collection<GrowthFactor>> {
var bonus = 0
val passed = arrayListOf<GrowthFactor>()
val mulchVariant = BerryBlock.getMulch(state)
val hasBiomeMulch = mulchVariant in favoriteMulches
val entity = world.getBlockEntity(pos) as BerryBlockEntity
val hasBiomeMulch = favoriteMulches.contains(entity.mulchVariant)
this.growthFactors.forEach { factor ->
if (factor.isValid(world, state, pos)) {
bonus += factor.yield()
passed += factor
} else if (hasBiomeMulch) {
}
else if (hasBiomeMulch) {
bonus += factor.yield()
}
}
@@ -121,7 +121,6 @@ class PartySelectCallback(
open class PartySelectPokemonDTO(
val pokemonProperties: PokemonProperties,
val aspects: Set<String>,
val heldItem: ItemStack = ItemStack.EMPTY,
var currentHealth: Int,
var maxHealth: Int,
@@ -130,13 +129,13 @@ open class PartySelectPokemonDTO(
@JvmOverloads
constructor(pokemon: Pokemon, enabled: Boolean = true): this(
pokemonProperties = pokemon.createPokemonProperties(
PokemonPropertyExtractor.ASPECTS,
PokemonPropertyExtractor.SPECIES,
PokemonPropertyExtractor.LEVEL,
PokemonPropertyExtractor.NICKNAME,
PokemonPropertyExtractor.POKEBALL,
PokemonPropertyExtractor.STATUS,
PokemonPropertyExtractor.STATUS
),
aspects = pokemon.aspects,
heldItem = pokemon.heldItemNoCopy(),
currentHealth = pokemon.currentHealth,
maxHealth = pokemon.hp,
@@ -145,7 +144,6 @@ open class PartySelectPokemonDTO(
constructor(buffer: PacketByteBuf): this(
pokemonProperties = PokemonProperties().loadFromNBT(buffer.readNbt() as NbtCompound),
aspects = buffer.readList { it.readString() }.toSet(),
heldItem = buffer.readItemStack(),
currentHealth = buffer.readInt(),
maxHealth = buffer.readInt(),
@@ -154,7 +152,6 @@ open class PartySelectPokemonDTO(
fun writeToBuffer(buffer: PacketByteBuf) {
buffer.writeNbt(pokemonProperties.saveToNBT())
buffer.writeCollection(aspects) { _, aspect -> buffer.writeString(aspect) }
buffer.writeItemStack(heldItem)
buffer.writeInt(currentHealth)
buffer.writeInt(maxHealth)
@@ -8,9 +8,11 @@
package com.cobblemon.mod.common.api.data
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.util.endsWith
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.io.File
import java.util.concurrent.ExecutionException
import net.minecraft.resource.ResourceManager
import net.minecraft.util.Identifier
@@ -42,7 +44,20 @@ interface JsonDataRegistry<T> : DataRegistry {
val resourcePath: String
override fun reload(manager: ResourceManager) {
this.reload(Cobblemon.implementation.reloadJsonRegistry(this, manager))
val data = hashMapOf<Identifier, T>()
manager.findResources(this.resourcePath) { path -> path.endsWith(JSON_EXTENSION) }.forEach { (identifier, resource) ->
resource.inputStream.use { stream ->
stream.bufferedReader().use { reader ->
val resolvedIdentifier = Identifier(identifier.namespace, File(identifier.path).nameWithoutExtension)
try {
data[resolvedIdentifier] = this.gson.fromJson(reader, this.typeToken.type)
} catch (exception: Exception) {
throw ExecutionException("Error loading JSON for data: $identifier", exception)
}
}
}
}
this.reload(data)
}
/**
@@ -53,6 +68,6 @@ interface JsonDataRegistry<T> : DataRegistry {
fun reload(data: Map<Identifier, T>)
companion object {
const val JSON_EXTENSION = ".json"
private const val JSON_EXTENSION = ".json"
}
}
@@ -10,11 +10,11 @@ package com.cobblemon.mod.common.api.drop
import com.cobblemon.mod.common.api.events.CobblemonEvents.LOOT_DROPPED
import com.cobblemon.mod.common.api.events.drops.LootDroppedEvent
import kotlin.random.Random
import net.minecraft.entity.LivingEntity
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.math.Vec3d
import kotlin.random.Random
/**
* A table of drops that can produce a list of [DropEntry]. You can produce a drop list from [getDrops] to
@@ -13,10 +13,8 @@ import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
import com.cobblemon.mod.common.api.events.battles.BattleStartedPostEvent
import com.cobblemon.mod.common.api.events.battles.BattleStartedPreEvent
import com.cobblemon.mod.common.api.events.battles.BattleVictoryEvent
import com.cobblemon.mod.common.api.events.berry.BerryHarvestEvent
import com.cobblemon.mod.common.api.events.berry.BerryMutationOfferEvent
import com.cobblemon.mod.common.api.events.berry.BerryMutationResultEvent
import com.cobblemon.mod.common.api.events.berry.BerryYieldCalculationEvent
import com.cobblemon.mod.common.api.events.berry.*
import com.cobblemon.mod.common.api.events.battles.*
import com.cobblemon.mod.common.api.events.drops.LootDroppedEvent
import com.cobblemon.mod.common.api.events.entity.PokemonEntityLoadEvent
import com.cobblemon.mod.common.api.events.entity.PokemonEntitySaveEvent
@@ -90,9 +88,6 @@ object CobblemonEvents {
@JvmField
val POKEMON_RECALLED = EventObservable<PokemonRecalledEvent>()
@JvmField
val TRADE_COMPLETED = EventObservable<TradeCompletedEvent>()
@JvmField
val LEVEL_UP_EVENT = EventObservable<LevelUpEvent>()
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.events.farming
import com.cobblemon.mod.common.api.apricorn.Apricorn
import net.minecraft.block.BlockState
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.server.world.ServerWorld
@@ -19,7 +18,6 @@ import net.minecraft.util.math.BlockPos
*/
class ApricornHarvestEvent(
val player: ServerPlayerEntity,
val apricorn: Apricorn,
val world: ServerWorld,
val pos: BlockPos
) {
@@ -1,14 +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.pokemon
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.trade.TradeParticipant
class TradeCompletedEvent(val tradeParticipant1 : TradeParticipant, val tradeParticipant1Pokemon : Pokemon, val tradeParticipant2 : TradeParticipant, val tradeParticipant2Pokemon : Pokemon)
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.api.text.font
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay.Companion.PORTRAIT_DIAMETER
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.PokemonModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Species
@@ -180,11 +179,7 @@ fun drawPortraitPokemon(
partialTicks: Float
) {
val model = PokemonModelRepository.getPoser(species.resourceIdentifier, aspects)
val texture = PokemonModelRepository.getTexture(species.resourceIdentifier, aspects, state?.animationSeconds ?: 0F)
val context = RenderContext()
PokemonModelRepository.getTextureNoSubstitute(species.resourceIdentifier, aspects, 0f).let { it -> context.put(RenderContext.TEXTURE, it) }
context.put(RenderContext.SCALE, species.getForm(aspects).baseScale)
val texture = PokemonModelRepository.getTexture(species.resourceIdentifier, aspects, state)
val renderType = model.getLayer(texture)
@@ -220,12 +215,11 @@ fun drawPortraitPokemon(
val packedLight = LightmapTextureManager.pack(11, 7)
model.withLayerContext(immediate, state, PokemonModelRepository.getLayers(species.resourceIdentifier, aspects)) {
model.render(context, matrixStack, buffer, packedLight, OverlayTexture.DEFAULT_UV, 1F, 1F, 1F, 1F)
model.render(matrixStack, buffer, packedLight, OverlayTexture.DEFAULT_UV, 1F, 1F, 1F, 1F)
immediate.draw()
}
matrixStack.pop()
model.setDefault()
DiffuseLighting.enableGuiDepthLighting()
}
@@ -98,11 +98,6 @@ interface PokemonAndMoveSelectingItem {
fun canUseOnMove(move: Move): Boolean
fun interactWithSpecific(player: ServerPlayerEntity, stack: ItemStack, pokemon: Pokemon): TypedActionResult<ItemStack>? {
if (player.isSneaking) {
return TypedActionResult.pass(stack)
}
MoveSelectCallbacks.create(
player = player,
moves = pokemon.moveSet.toList(),
@@ -151,4 +146,4 @@ interface PokemonAndMoveSelectingItem {
return TypedActionResult.success(stack)
}
}
}
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.api.item
import com.cobblemon.mod.common.advancement.CobblemonCriteria
import com.cobblemon.mod.common.advancement.criterion.PokemonInteractContext
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.callback.PartySelectCallbacks
import com.cobblemon.mod.common.api.text.red
@@ -24,7 +22,6 @@ import com.cobblemon.mod.common.util.isHeld
import com.cobblemon.mod.common.util.isLookingAt
import com.cobblemon.mod.common.util.party
import net.minecraft.item.ItemStack
import net.minecraft.registry.Registries
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.TypedActionResult
import net.minecraft.util.math.Box
@@ -62,17 +59,15 @@ interface PokemonSelectingItem {
return interactWithSpecificBattle(player, stack, battlePokemon)
}
} ?: run {
if (!player.isSneaking) {
return if (entity != null) {
val pokemon = entity.pokemon
if (entity.ownerUuid == player.uuid) {
applyToPokemon(player, stack, pokemon) ?: TypedActionResult.pass(stack)
} else {
TypedActionResult.fail(stack)
}
if (entity != null) {
val pokemon = entity.pokemon
if (entity.ownerUuid == player.uuid) {
return applyToPokemon(player, stack, pokemon) ?: TypedActionResult.pass(stack)
} else {
interactGeneral(player, stack)
return TypedActionResult.fail(stack)
}
} else {
return interactGeneral(player, stack)
}
}
@@ -94,7 +89,6 @@ interface PokemonSelectingItem {
if (!player.isCreative) {
stack.decrement(1)
}
CobblemonCriteria.POKEMON_INTERACT.trigger(player, PokemonInteractContext(battlePokemon.entity!!.pokemon.species.resourceIdentifier, Registries.ITEM.getId(stack.item)))
}
}
@@ -124,7 +118,9 @@ interface PokemonSelectingItem {
handler = { pk ->
if (stack.isHeld(player)) {
applyToPokemon(player, stack, pk)
CobblemonCriteria.POKEMON_INTERACT.trigger(player, PokemonInteractContext(pk.species.resourceIdentifier, Registries.ITEM.getId(stack.item)))
if (!player.isCreative) {
stack.decrement(1)
}
}
}
)
@@ -18,8 +18,6 @@ import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtElement
import net.minecraft.nbt.NbtList
import net.minecraft.network.PacketByteBuf
import kotlin.math.min
class MoveSet : Iterable<Move> {
val observable = SimpleObservable<MoveSet>()
private var emit = true
@@ -65,11 +63,6 @@ class MoveSet : Iterable<Move> {
update()
}
fun partialHeal() {
getMoves().forEach { it.currentPp = min((it.currentPp + (it.maxPp / 2)), it.maxPp)}
update()
}
fun clear() {
doWithoutEmitting {
for (i in 0 until MOVE_COUNT){
@@ -8,22 +8,40 @@
package com.cobblemon.mod.common.api.mulch
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.item.MulchItem
import net.minecraft.util.StringIdentifiable
import java.time.Duration
/**
* Represents the different types of Mulch implemented in the mod.
*
*/
enum class MulchVariant(val duration: Int = -1) : StringIdentifiable {
enum class MulchVariant(val isBiomeMulch: Boolean = true, val duration: Int = 0) : StringIdentifiable {
COARSE,
GROWTH(3),
GROWTH(false, 3),
HUMID,
LOAMY,
PEAT,
RICH(3),
RICH(false, 3),
SANDY,
SURPRISE(3),
NONE;
SURPRISE(false, 3);
/**
* Resolves the item version of the Mulch.
*
* @return The [MulchItem] equivalent.
*/
fun item(): MulchItem = when (this) {
COARSE -> CobblemonItems.COARSE_MULCH
GROWTH -> CobblemonItems.GROWTH_MULCH
HUMID -> CobblemonItems.HUMID_MULCH
LOAMY -> CobblemonItems.LOAMY_MULCH
PEAT -> CobblemonItems.PEAT_MULCH
RICH -> CobblemonItems.RICH_MULCH
SANDY -> CobblemonItems.SANDY_MULCH
SURPRISE -> CobblemonItems.SURPRISE_MULCH
}
override fun asString(): String {
return name.lowercase()
@@ -65,7 +65,6 @@ object CobblemonPermissions {
val TEST_PARTY_SLOT = this.create("${COMMAND_PREFIX}testpartyslot", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
val CLEAR_PARTY = this.create("${COMMAND_PREFIX}clearparty", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
val CLEAR_PC = this.create("${COMMAND_PREFIX}clearpc", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
fun all(): Iterable<Permission> = this.permissions
@@ -166,16 +166,6 @@ object Natures {
return allNatures.find { nature -> nature.name == name }
}
/**
* Utility method to get a nature by string
* @return a nature type or null
*/
fun getNature(identifier: String): Nature? {
val nature = getNature(cobblemonResource(identifier))
if(nature != null) return nature
return getNature(Identifier(identifier))
}
/**
* Helper function for a random Nature
* @return a random nature type
@@ -15,21 +15,16 @@ import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.EVs
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.IVs
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.pokemon.*
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.isInt
import com.cobblemon.mod.common.util.splitMap
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import kotlin.random.Random
import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtElement
import net.minecraft.nbt.NbtList
@@ -91,10 +86,6 @@ open class PokemonProperties {
props.ability = parseStringOfRegistry(keyPairs, listOf("ability")) { Abilities.get(it)?.name }
props.status = parseStringOfRegistry(keyPairs, listOf("status")) { (Statuses.getStatus(it) ?: Statuses.getStatus(it.asIdentifierDefaultingNamespace()))?.showdownName }
props.nickname = parseText(keyPairs, listOf("nickname", "nick"))
props.teraType = parseStringOfRegistry(keyPairs, listOf("tera_type", "tera")) { ElementalTypes.get(it)?.name }
props.dmaxLevel = parseIntProperty(keyPairs, listOf("dmax_level", "dmax"))?.coerceIn(0, Cobblemon.config.maxDynamaxLevel)
props.gmaxFactor = parseBooleanProperty(keyPairs, listOf("gmax_factor", "gmax"))
props.tradeable = parseBooleanProperty(keyPairs, listOf("tradeable", "tradable"))
val maybeIVs = IVs()
val maybeEVs = EVs()
@@ -156,10 +147,9 @@ open class PokemonProperties {
}
private fun parseSpeciesIdentifier(keyPairs: MutableList<Pair<String, String?>>): String? {
fun cleanSpeciesName(string: String) = string.lowercase().replace("[^a-z0-9_:]".toRegex(), "")
val matched = getMatchedKeyPair(keyPairs, listOf("species"))
if (matched != null) {
val value = matched.second?.let(::cleanSpeciesName) ?: return null
val value = matched.second?.lowercase() ?: return null
return if (value.lowercase() == "random") {
"random"
} else {
@@ -178,7 +168,7 @@ open class PokemonProperties {
"random"
} else {
try {
val identifier = cleanSpeciesName(pair.first).asIdentifierDefaultingNamespace()
val identifier = pair.first.asIdentifierDefaultingNamespace()
val found = PokemonSpecies.getByIdentifier(identifier) ?: return@find false
if (found.resourceIdentifier.namespace == Cobblemon.MODID) found.resourceIdentifier.path else found.resourceIdentifier.toString()
} catch (e: InvalidIdentifierException) {
@@ -256,10 +246,6 @@ open class PokemonProperties {
var ability: String? = null
var aspects: Set<String> = emptySet()
var status: String? = null
var teraType: String? = null
var dmaxLevel: Int? = null
var gmaxFactor: Boolean? = null
var tradeable: Boolean? = null
var ivs: IVs? = null
var evs: EVs? = null
@@ -309,11 +295,6 @@ open class PokemonProperties {
pokemon.setEV(stat.key, stat.value)
}
}
teraType?.let { ElementalTypes.get(it)?.let { type -> pokemon.teraType = type } }
dmaxLevel?.let { pokemon.dmaxLevel = it }
gmaxFactor?.let { pokemon.gmaxFactor = it }
tradeable?.let { pokemon.tradeable = it }
pokemon.updateAspects()
}
fun apply(pokemonEntity: PokemonEntity) {
@@ -349,11 +330,6 @@ open class PokemonProperties {
pokemonEntity.pokemon.setEV(stat.key, stat.value)
}
}
teraType?.let { ElementalTypes.get(it)?.let { type -> pokemonEntity.pokemon.teraType = type } }
dmaxLevel?.let { pokemonEntity.pokemon.dmaxLevel = it }
gmaxFactor?.let { pokemonEntity.pokemon.gmaxFactor = it }
tradeable?.let { pokemonEntity.pokemon.tradeable = it }
pokemonEntity.pokemon.updateAspects()
}
fun matches(pokemon: Pokemon): Boolean {
@@ -387,10 +363,6 @@ open class PokemonProperties {
evs?.forEach{ stat ->
if (stat.value != pokemon.evs[stat.key]) { return false }
}
teraType?.takeIf { it != pokemon.teraType.name }?.let { return false }
dmaxLevel?.takeIf { it != pokemon.dmaxLevel }?.let { return false }
gmaxFactor?.takeIf { it != pokemon.gmaxFactor }?.let { return false }
tradeable?.takeIf { it != pokemon.tradeable }?.let { return false }
return customProperties.none { !it.matches(pokemon) }
}
@@ -423,10 +395,6 @@ open class PokemonProperties {
evs?.forEach{ stat ->
if (stat.value != pokemonEntity.pokemon.evs[stat.key]) { return false }
}
teraType?.takeIf { it != pokemonEntity.pokemon.teraType.name }?.let { return false }
dmaxLevel?.takeIf { it != pokemonEntity.pokemon.dmaxLevel }?.let { return false }
gmaxFactor?.takeIf { it != pokemonEntity.pokemon.gmaxFactor }?.let { return false }
tradeable?.takeIf { it != pokemonEntity.pokemon.tradeable }?.let { return false }
return customProperties.none { !it.matches(pokemonEntity) }
}
@@ -467,33 +435,24 @@ open class PokemonProperties {
if (stat.value != propertiesEVs[stat.key]) { return false }
}
}
teraType?.takeIf { it != properties.teraType }?.let { return false }
dmaxLevel?.takeIf { it != properties.dmaxLevel }?.let { return false }
gmaxFactor?.takeIf { it != properties.gmaxFactor }?.let { return false }
tradeable?.takeIf { it != properties.tradeable }?.let { return false }
return true
}
fun create(): Pokemon {
val pokemon = Pokemon()
apply(pokemon)
pokemon.initialize()
roll(pokemon)
// Force the setter to initialize it
pokemon.species = pokemon.species
// This should only be a thing once we have moveset control in properties until then a creation should require a moveset init.
/*
if (pokemon.moveSet.none { it != null }) {
pokemon.initializeMoveset()
}
*/
pokemon.initializeMoveset()
return pokemon
}
// TEST YOUR LUCK!
fun roll(pokemon: Pokemon) {
val baseTypes = pokemon.species.types.toList()
if (this.shiny == null) pokemon.shiny = Cobblemon.config.shinyRate.checkRate()
if (this.teraType == null) pokemon.teraType =
if (Cobblemon.config.teraTypeRate.checkRate()) ElementalTypes.all().filter { !baseTypes.contains(it) }.random()
else baseTypes.random()
}
// If the config value is at least 1, then do 1/x and use that as the property chance
private fun Float.checkRate(): Boolean = this >= 1 && (Random.Default.nextFloat() < 1 / this)
fun createEntity(world: World): PokemonEntity {
return PokemonEntity(world, create())
}
@@ -514,10 +473,6 @@ open class PokemonProperties {
status?.let { nbt.putString(DataKeys.POKEMON_STATUS_NAME, it) }
ivs?.let { nbt.put(DataKeys.POKEMON_IVS, it.saveToNBT(NbtCompound())) }
evs?.let { nbt.put(DataKeys.POKEMON_EVS, it.saveToNBT(NbtCompound())) }
teraType?.let { nbt.putString(DataKeys.POKEMON_TERA_TYPE, it) }
dmaxLevel?.let { nbt.putInt(DataKeys.POKEMON_DMAX_LEVEL, it) }
gmaxFactor?.let { nbt.putBoolean(DataKeys.POKEMON_GMAX_FACTOR, it) }
tradeable?.let { nbt.putBoolean(DataKeys.POKEMON_TRADEABLE, it) }
val custom = NbtList()
customProperties.map { NbtString.of(it.asString()) }.forEach { custom.add(it) }
nbt.put(DataKeys.POKEMON_PROPERTIES_CUSTOM, custom)
@@ -539,10 +494,6 @@ open class PokemonProperties {
status = if (tag.contains(DataKeys.POKEMON_STATUS_NAME)) tag.getString(DataKeys.POKEMON_STATUS_NAME) else null
ivs = if (tag.contains(DataKeys.POKEMON_IVS)) ivs?.loadFromNBT(tag.getCompound(DataKeys.POKEMON_IVS)) as IVs? else null
evs = if (tag.contains(DataKeys.POKEMON_EVS)) evs?.loadFromNBT(tag.getCompound(DataKeys.POKEMON_EVS)) as EVs? else null
teraType = if (tag.contains(DataKeys.POKEMON_TERA_TYPE)) tag.getString(DataKeys.POKEMON_TERA_TYPE) else null
dmaxLevel = if (tag.contains(DataKeys.POKEMON_DMAX_LEVEL)) tag.getInt(DataKeys.POKEMON_DMAX_LEVEL) else null
gmaxFactor = if (tag.contains(DataKeys.POKEMON_GMAX_FACTOR)) tag.getBoolean(DataKeys.POKEMON_GMAX_FACTOR) else null
tradeable = if (tag.contains(DataKeys.POKEMON_TRADEABLE)) tag.getBoolean(DataKeys.POKEMON_TRADEABLE) else null
val custom = tag.getList(DataKeys.POKEMON_PROPERTIES_CUSTOM, NbtElement.STRING_TYPE.toInt())
// This is kinda gross
custom.forEach { customProperties.addAll(parse(it.asString()).customProperties) }
@@ -566,10 +517,6 @@ open class PokemonProperties {
status?.let { json.addProperty(DataKeys.POKEMON_STATUS_NAME, it) }
ivs?.let { json.add(DataKeys.POKEMON_IVS, it.saveToJSON(JsonObject())) }
evs?.let { json.add(DataKeys.POKEMON_EVS, it.saveToJSON(JsonObject())) }
teraType?.let { json.addProperty(DataKeys.POKEMON_TERA_TYPE, it) }
dmaxLevel?.let { json.addProperty(DataKeys.POKEMON_DMAX_LEVEL, it) }
gmaxFactor?.let { json.addProperty(DataKeys.POKEMON_GMAX_FACTOR, it) }
tradeable?.let { json.addProperty(DataKeys.POKEMON_TRADEABLE, it) }
val custom = JsonArray()
customProperties.map { it.asString() }.forEach { custom.add(it) }
json.add(DataKeys.POKEMON_PROPERTIES_CUSTOM, custom)
@@ -592,10 +539,6 @@ open class PokemonProperties {
status = json.get(DataKeys.POKEMON_STATUS_NAME)?.asString
ivs?.loadFromJSON(json.getAsJsonObject(DataKeys.POKEMON_IVS))
evs?.loadFromJSON(json.getAsJsonObject(DataKeys.POKEMON_EVS))
teraType = json.get(DataKeys.POKEMON_TERA_TYPE)?.asString
dmaxLevel = json.get(DataKeys.POKEMON_DMAX_LEVEL)?.asInt
gmaxFactor = json.get(DataKeys.POKEMON_GMAX_FACTOR)?.asBoolean
tradeable = json.get(DataKeys.POKEMON_TRADEABLE)?.asBoolean
val custom = json.get(DataKeys.POKEMON_PROPERTIES_CUSTOM)?.asJsonArray
// This is still kinda gross
custom?.forEach { customProperties.addAll(parse(it.asString).customProperties) }
@@ -622,10 +565,6 @@ open class PokemonProperties {
evs?.forEach{ stat ->
pieces.add("${stat.key}_ev=${stat.value}")
}
teraType?.let { pieces.add("tera_type=$it") }
dmaxLevel?.let { pieces.add("dmax_level=$it") }
gmaxFactor?.let { pieces.add("gmax_factor=$it") }
tradeable?.let { pieces.add("tradeable=$it") }
customProperties.forEach { pieces.add(it.asString()) }
return pieces.joinToString(separator)
}
@@ -34,6 +34,7 @@ import com.cobblemon.mod.common.api.reactive.SimpleObservable
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.battles.runner.ShowdownService
import com.cobblemon.mod.common.net.messages.client.data.SpeciesRegistrySyncPacket
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
@@ -41,8 +42,7 @@ import com.cobblemon.mod.common.pokemon.SpeciesAdditions
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonEvolutionAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonPreEvolutionAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonRequirementAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.NbtItemPredicateAdapter
import com.cobblemon.mod.common.pokemon.evolution.predicate.NbtItemPredicate
import com.cobblemon.mod.common.pokemon.helditem.CobblemonEmptyHeldItemManager
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.util.adapters.*
import com.cobblemon.mod.common.util.cobblemonResource
@@ -95,7 +95,6 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
.registerTypeAdapter(EggGroup::class.java, EggGroupAdapter)
.registerTypeAdapter(StatusEffect::class.java, RegistryElementAdapter<StatusEffect> { Registries.STATUS_EFFECT })
.registerTypeAdapter(NbtItemPredicate::class.java, NbtItemPredicateAdapter)
.disableHtmlEscaping()
.enableComplexMapKeySerialization()
.create()
@@ -244,9 +243,6 @@ object PokemonSpecies : JsonDataRegistry<Species> {
val cannotDynamax = form?.dynamaxBlocked ?: species.dynamaxBlocked
// ToDo battleOnly
// ToDo changesFrom
val requiredMove = form?.requiredMove
val requiredItem = form?.requiredItem
val requiredItems = form?.requiredItems
}
private fun createShowdownName(species: Species): String {
@@ -10,14 +10,11 @@ package com.cobblemon.mod.common.api.pokemon.effect.adapter
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffect
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffectRegistry
import com.cobblemon.mod.common.pokemon.effects.PotionBaseEffect
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import java.lang.reflect.Type
import net.minecraft.registry.Registries
import net.minecraft.util.Identifier
object ShoulderEffectAdapter: JsonDeserializer<ShoulderEffect> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ShoulderEffect {
@@ -26,18 +23,7 @@ object ShoulderEffectAdapter: JsonDeserializer<ShoulderEffect> {
} else {
json.asJsonObject.get("type").asString to json.asJsonObject
}
val effect = ShoulderEffectRegistry.get(typeId) ?: run {
try {
val effectId = Identifier(typeId.replace("-", "_").replace("slow_fall", "slow_falling"))
val registry = Registries.STATUS_EFFECT
val effect = registry.get(effectId)
if (effect != null) {
return PotionBaseEffect(effect, 0, true, false, false)
}
} catch (_: Exception) {}
throw IllegalArgumentException("Cannot find shoulder effect with type '$typeId'")
}
val effect = ShoulderEffectRegistry.get(typeId) ?: throw IllegalArgumentException("Cannot find shoulder effect with type '$typeId'")
return context.deserialize(obj, effect)
}
}
@@ -90,7 +90,7 @@ object CobblemonSpawningProspector : SpawningProspector {
val state = query.getBlockState(pos.set(x, y, z))
blocks[x - area.baseX][y - baseY][z - area.baseZ] = WorldSlice.BlockData(
state = state,
light = world.getLightLevel(pos)
light = state.getOpacity(world, pos)
)
if (canSeeSky) {
@@ -14,7 +14,9 @@ import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.feature.SeasonFeatureHandler
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.weightedSelection
import kotlin.random.Random
/**
* A [SpawnAction] that will spawn a single [PokemonEntity].
@@ -33,6 +35,10 @@ class PokemonSpawnAction(
if (props.level == null) {
props.level = detail.getDerivedLevelRange().random()
}
if (props.shiny == null) {
// If the config value is at least 1, then do 1/x and use that as the shiny chance
props.shiny = Cobblemon.config.shinyRate.takeIf { it >= 1 }?.let { Random.Default.nextFloat() < 1 / it }
}
val heldItems = detail.heldItems?.takeIf { it.isNotEmpty() }?.toMutableList() ?: mutableListOf()
val heldItem = if (heldItems.isNotEmpty()) {
val until100 = 1 - heldItems.sumOf { it.percentage / 100 }
@@ -17,7 +17,6 @@ import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.lang
import com.google.gson.annotations.SerializedName
import kotlin.math.ceil
@@ -49,8 +48,6 @@ class PokemonSpawnDetail : SpawnDetail() {
override fun getName(): MutableText {
displayName?.let { return it.asTranslated() }
val speciesString = pokemon.species
if (speciesString != null) {
if (speciesString.lowercase() == "random") {
@@ -32,12 +32,12 @@ open class BottomlessStore(override val uuid: UUID) : PokemonStore<BottomlessPos
override fun iterator() = pokemon.iterator()
override fun get(position: BottomlessPosition) = position.currentIndex
.takeIf { it in pokemon.indices }
.takeIf { it < pokemon.size && it >= 0 }
?.let { pokemon[it] }
override fun getFirstAvailablePosition() = BottomlessPosition(pokemon.size)
override fun isValidPosition(position: BottomlessPosition) = position.currentIndex >= 0
operator fun get(index: Int) = index.takeIf { it in pokemon.indices }?.let { pokemon[it] }
operator fun get(index: Int) = index.takeIf { it >= 0 && it < pokemon.size }?.let { pokemon[it] }
override fun getObservingPlayers() = emptySet<ServerPlayerEntity>()
override fun sendTo(player: ServerPlayerEntity) {}
@@ -58,12 +58,7 @@ open class BottomlessStore(override val uuid: UUID) : PokemonStore<BottomlessPos
override fun loadFromNBT(nbt: NbtCompound): BottomlessStore {
var i = -1
while (nbt.contains(DataKeys.STORE_SLOT + ++i)) {
val pokemonNBT = nbt.getCompound(DataKeys.STORE_SLOT + i)
try {
pokemon.add(Pokemon().loadFromNBT(pokemonNBT))
} catch(_: InvalidSpeciesException) {
handleInvalidSpeciesNBT(pokemonNBT)
}
pokemon.add(Pokemon().loadFromNBT(nbt.getCompound(DataKeys.STORE_SLOT + i)))
}
return this
}
@@ -76,12 +71,7 @@ open class BottomlessStore(override val uuid: UUID) : PokemonStore<BottomlessPos
override fun loadFromJSON(json: JsonObject): BottomlessStore {
var i = -1
while (json.has(DataKeys.STORE_SLOT + ++i)) {
val pokemonJSON = json.getAsJsonObject(DataKeys.STORE_SLOT + i)
try {
pokemon.add(Pokemon().loadFromJSON(pokemonJSON))
} catch (_: InvalidSpeciesException) {
handleInvalidSpeciesJSON(pokemonJSON)
}
pokemon.add(Pokemon().loadFromJSON(json.getAsJsonObject(DataKeys.STORE_SLOT + i)))
}
return this
}
@@ -102,16 +92,11 @@ open class BottomlessStore(override val uuid: UUID) : PokemonStore<BottomlessPos
this.pokemon.add(pokemon)
storeChangeObservable.emit(Unit)
} else if (position.currentIndex in 0 until this.pokemon.size) {
var startIndex = position.currentIndex;
if (pokemon != null) {
this.pokemon.add(position.currentIndex, pokemon)
startIndex += 1;
} else {
this.pokemon.removeAt(position.currentIndex)
}
for(i in startIndex until this.pokemon.size) {
this.pokemon[i].storeCoordinates.set(StoreCoordinates(this, BottomlessPosition(i)))
}
storeChangeObservable.emit(Unit)
}
}
@@ -1,19 +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.storage
import net.minecraft.util.Identifier
/**
* An exception when trying to deserialize a Pokemon instance with an identifier that doesn't seem to be in the registry.
*
* @author Hiroku
* @since October 9th, 2023
*/
class InvalidSpeciesException(val identifier: Identifier) : IllegalStateException("Invalid species: $identifier")
@@ -146,10 +146,8 @@ abstract class PokemonStore<T : StorePosition> : Iterable<Pokemon> {
operator fun get(uuid: UUID) = find { it.uuid == uuid }
open fun handleInvalidSpeciesNBT(nbt: NbtCompound) {}
abstract fun saveToNBT(nbt: NbtCompound): NbtCompound
abstract fun loadFromNBT(nbt: NbtCompound): PokemonStore<T>
open fun handleInvalidSpeciesJSON(json: JsonObject) {}
abstract fun saveToJSON(json: JsonObject): JsonObject
abstract fun loadFromJSON(json: JsonObject): PokemonStore<T>
abstract fun savePositionToNBT(position: T, nbt: NbtCompound)
@@ -1,101 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.storage.adapter.database
import com.cobblemon.mod.common.api.storage.PokemonStore
import com.cobblemon.mod.common.api.storage.StorePosition
import com.cobblemon.mod.common.api.storage.adapter.CobblemonAdapterParent
import com.cobblemon.mod.common.api.storage.adapter.flatfile.FileStoreAdapter
import com.cobblemon.mod.common.api.storage.adapter.flatfile.JSONStoreAdapter
import com.cobblemon.mod.common.api.storage.adapter.flatfile.NBTStoreAdapter
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
import com.cobblemon.mod.common.api.storage.pc.PCStore
import com.cobblemon.mod.common.util.server
import com.google.gson.Gson
import com.google.gson.JsonObject
import com.mongodb.client.MongoClient
import com.mongodb.client.MongoCollection
import com.mongodb.client.model.ReplaceOptions
import net.minecraft.util.WorldSavePath
import org.bson.Document
import java.util.UUID
/**
* A [FileStoreAdapter] for MongoDB. This allows a [PokemonStore] to be serialized to a MongoDB database.
*
* @author Pebbles
* @since August 23rd, 2023
*/
open class MongoDBStoreAdapter(
private val mongoClient: MongoClient,
private val databaseName: String,
) : CobblemonAdapterParent<JsonObject>(), FileStoreAdapter<JsonObject> {
private fun getCollection(storeClass: Class<out PokemonStore<*>>): MongoCollection<Document> {
val collectionName = when (storeClass) {
PlayerPartyStore::class.java -> "PlayerPartyCollection"
PCStore::class.java -> "PCCollection"
else -> "OtherCollection"
}
return mongoClient.getDatabase(databaseName).getCollection(collectionName)
}
private val gson: Gson = Gson()
override fun <E : StorePosition, T : PokemonStore<E>> serialize(store: T): JsonObject {
return store.saveToJSON(JsonObject())
}
override fun save(storeClass: Class<out PokemonStore<*>>, uuid: UUID, serialized: JsonObject) {
val document = Document.parse(gson.toJson(serialized))
document.put("uuid", uuid.toString())
val collection = getCollection(storeClass)
val filter = Document("uuid", uuid.toString())
collection.replaceOne(filter, document, ReplaceOptions().upsert(true))
}
override fun <E : StorePosition, T : PokemonStore<E>> provide(storeClass: Class<T>, uuid: UUID): T? {
val server = server()!!
val pokemonStoreRoot = server.getSavePath(WorldSavePath.ROOT).resolve("pokemon").toFile()
val jsonAdapter = JSONStoreAdapter(
pokemonStoreRoot.absolutePath,
useNestedFolders = true,
folderPerClass = true
)
val nbtAdapter = NBTStoreAdapter(pokemonStoreRoot.absolutePath, useNestedFolders = true, folderPerClass = true)
// 1. Check if data exists in MongoDB
val collection = getCollection(storeClass)
val filter = Document("uuid", uuid.toString())
val document = collection.find(filter).first()
if (document != null) {
val json = gson.fromJson(document.toJson(), JsonObject::class.java)
val store = storeClass.getConstructor(UUID::class.java).newInstance(uuid)
store.loadFromJSON(json)
return store
}
// 2. Fallback to checking JSON and NBT
val nbtStore = nbtAdapter.provide(storeClass, uuid)
if (nbtStore != null) {
save(storeClass, uuid, serialize(nbtStore))
return nbtStore
}
val jsonStore = jsonAdapter.provide(storeClass, uuid)
if (jsonStore != null) {
save(storeClass, uuid, serialize(jsonStore))
return jsonStore
}
return null
}
}
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.storage.adapter.flatfile
package com.cobblemon.mod.common.api.storage.adapter.flatifle
import com.cobblemon.mod.common.api.storage.PokemonStore
import com.cobblemon.mod.common.api.storage.StorePosition
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.storage.adapter.flatfile
package com.cobblemon.mod.common.api.storage.adapter.flatifle
import com.cobblemon.mod.common.api.storage.PokemonStore
import com.cobblemon.mod.common.api.storage.StorePosition
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.storage.adapter.flatfile
package com.cobblemon.mod.common.api.storage.adapter.flatifle
import com.cobblemon.mod.common.api.storage.PokemonStore
import com.cobblemon.mod.common.api.storage.StorePosition
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.storage.adapter.flatfile
package com.cobblemon.mod.common.api.storage.adapter.flatifle
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.storage.PokemonStore
@@ -14,7 +14,7 @@ import com.cobblemon.mod.common.api.reactive.Observable.Companion.emitWhile
import com.cobblemon.mod.common.api.storage.PokemonStore
import com.cobblemon.mod.common.api.storage.StorePosition
import com.cobblemon.mod.common.api.storage.adapter.SerializedStore
import com.cobblemon.mod.common.api.storage.adapter.flatfile.FileStoreAdapter
import com.cobblemon.mod.common.api.storage.adapter.flatifle.FileStoreAdapter
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
import com.cobblemon.mod.common.api.storage.pc.PCStore
import com.cobblemon.mod.common.platform.events.PlatformEvents
@@ -123,7 +123,7 @@ open class FileBackedPokemonStoreFactory<S>(
}
override fun onPlayerDisconnect(playerID: UUID) {
dirtyStores.filter { it.uuid == playerID }.forEach(::save)
dirtyStores.find { it.uuid == playerID }?.let { save(it) }
storeCaches.forEach { (_, cache) -> cache.cacheMap.remove(playerID) }
}
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.Observable.Companion.stopAfter
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.storage.InvalidSpeciesException
import com.cobblemon.mod.common.api.storage.PokemonStore
import com.cobblemon.mod.common.api.storage.StoreCoordinates
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
@@ -169,12 +168,8 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
while (slotCount < slots.size) { slots.add(null) }
for (slot in slots.indices) {
val pokemonNBT = nbt.getCompound(DataKeys.STORE_SLOT + slot)
try {
if (!pokemonNBT.isEmpty) {
slots[slot] = Pokemon().loadFromNBT(pokemonNBT)
}
} catch (_: InvalidSpeciesException) {
handleInvalidSpeciesNBT(pokemonNBT)
if (!pokemonNBT.isEmpty) {
slots[slot] = Pokemon().loadFromNBT(pokemonNBT)
}
}
@@ -201,12 +196,7 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
for (slot in slots.indices) {
val key = DataKeys.STORE_SLOT + slot
if (json.has(key)) {
val pokemonJSON = json.get(key).asJsonObject
try {
slots[slot] = Pokemon().loadFromJSON(pokemonJSON)
} catch (_: InvalidSpeciesException) {
handleInvalidSpeciesJSON(pokemonJSON)
}
slots[slot] = Pokemon().loadFromJSON(json.get(key).asJsonObject)
}
}
@@ -9,8 +9,6 @@
package com.cobblemon.mod.common.api.storage.party
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.advancement.CobblemonCriteria
import com.cobblemon.mod.common.advancement.criterion.PartyCheckContext
import com.cobblemon.mod.common.api.pokemon.evolution.PassiveEvolution
import com.cobblemon.mod.common.api.storage.pc.PCStore
import com.cobblemon.mod.common.battles.BattleRegistry
@@ -20,13 +18,13 @@ import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.isPokemonEntity
import com.cobblemon.mod.common.util.lang
import java.util.UUID
import kotlin.math.round
import kotlin.random.Random
import net.minecraft.nbt.NbtCompound
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.Text
import java.util.*
import kotlin.math.ceil
import kotlin.math.round
import kotlin.random.Random
/**
* A [PartyStore] used for a single player. This uses the player's UUID as the store's UUID, and is declared as its own
@@ -54,7 +52,6 @@ open class PlayerPartyStore(
override fun add(pokemon: Pokemon): Boolean {
return if (super.add(pokemon)) {
pokemon.getOwnerPlayer()?.let { CobblemonCriteria.PARTY_CHECK.trigger(it, PartyCheckContext(this)) }
true
} else {
val player = playerUUID.getPlayer()
@@ -154,31 +151,4 @@ open class PlayerPartyStore(
}
return true
}
override fun swap(position1: PartyPosition, position2: PartyPosition) {
super.swap(position1, position2)
//Make it so we can check what's in the Player's party
val pokemon1 = get(position1)
val pokemon2 = get(position2)
if (pokemon1 != null && pokemon2 != null) {
val player = pokemon1.getOwnerPlayer()
if (player != null) {
CobblemonCriteria.PARTY_CHECK.trigger(player, PartyCheckContext(this))
}
} else if (pokemon1 != null || pokemon2 != null) {
var player = pokemon1?.getOwnerPlayer()
if (player != null) {
CobblemonCriteria.PARTY_CHECK.trigger(player, PartyCheckContext(this))
} else {
player = pokemon2!!.getOwnerPlayer()
CobblemonCriteria.PARTY_CHECK.trigger(player!!, PartyCheckContext(this))
}
}
}
override fun set(position: PartyPosition, pokemon: Pokemon) {
super.set(position, pokemon)
pokemon.getOwnerPlayer()?.let { CobblemonCriteria.PARTY_CHECK.trigger(it, PartyCheckContext(this)) }
}
}
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.storage.pc
import com.cobblemon.mod.common.api.reactive.Observable.Companion.stopAfter
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.storage.InvalidSpeciesException
import com.cobblemon.mod.common.api.storage.StoreCoordinates
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCBoxPokemonPacket
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -121,12 +120,7 @@ open class PCBox(val pc: PCStore) : Iterable<Pokemon> {
open fun loadFromJSON(json: JsonObject): PCBox {
for (slot in 0 until POKEMON_PER_BOX) {
if (json.has(DataKeys.STORE_SLOT + slot)) {
val pokemonJson = json.getAsJsonObject(DataKeys.STORE_SLOT + slot)
try {
pokemon[slot] = Pokemon().loadFromJSON(pokemonJson)
} catch (_: InvalidSpeciesException) {
pc.handleInvalidSpeciesJSON(pokemonJson)
}
pokemon[slot] = Pokemon().loadFromJSON(json.getAsJsonObject(DataKeys.STORE_SLOT + slot))
}
}
return this
@@ -135,12 +129,7 @@ open class PCBox(val pc: PCStore) : Iterable<Pokemon> {
open fun loadFromNBT(nbt: NbtCompound): PCBox {
for (slot in 0 until POKEMON_PER_BOX) {
if (nbt.contains(DataKeys.STORE_SLOT + slot)) {
val pokemonNBT = nbt.getCompound(DataKeys.STORE_SLOT + slot)
try {
pokemon[slot] = Pokemon().loadFromNBT(pokemonNBT)
} catch (_: InvalidSpeciesException) {
pc.handleInvalidSpeciesNBT(pokemonNBT)
}
pokemon[slot] = Pokemon().loadFromNBT(nbt.getCompound(DataKeys.STORE_SLOT + slot))
}
}
return this
@@ -1,35 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.storage.player
import com.google.gson.JsonObject
/**
* A dummy implementation of the [PlayerDataExtension] interface.
* Used for when a previous [PlayerDataExtension] is no longer registered.
*
* @author Deltric
* @since 2023-10-01
*/
class DummyPlayerDataExtension(
val json: JsonObject
): PlayerDataExtension {
override fun name(): String {
return this.json.get(PlayerDataExtension.NAME_KEY).asString
}
override fun serialize(): JsonObject {
return this.json
}
override fun deserialize(json: JsonObject): PlayerDataExtension {
return DummyPlayerDataExtension(json)
}
}
@@ -24,16 +24,8 @@ class PlayerAdvancementData {
private set
var totalBattleVictoryCount: Int = 0
private set
var totalPvPBattleVictoryCount: Int = 0
private set
var totalPvWBattleVictoryCount: Int = 0
private set
var totalPvNBattleVictoryCount: Int = 0
private set
var totalShinyCaptureCount: Int = 0
private set
var totalTradedCount: Int = 0
private set
private var totalTypeCaptureCounts = mutableMapOf<String, Int>()
private var totalDefeatedCounts = mutableMapOf<Identifier, Int>()
@@ -53,29 +45,13 @@ class PlayerAdvancementData {
}
fun updateTotalBattleVictoryCount() {
totalBattleVictoryCount++
}
fun updateTotalPvPBattleVictoryCount() {
totalPvPBattleVictoryCount++
}
fun updateTotalPvWBattleVictoryCount() {
totalPvWBattleVictoryCount++
}
fun updateTotalPvNBattleVictoryCount() {
totalPvNBattleVictoryCount++
totalEvolvedCount++
}
fun updateTotalShinyCaptureCount() {
totalShinyCaptureCount++
}
fun updateTotalTradedCount() {
totalTradedCount++
}
fun getTotalTypeCaptureCount(type: ElementalType): Int {
if (!totalTypeCaptureCounts.containsKey(key = type.name)) {
totalTypeCaptureCounts[type.name] = 0
@@ -11,10 +11,8 @@ package com.cobblemon.mod.common.api.storage.player
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.net.messages.client.starter.SetClientPlayerDataPacket
import com.cobblemon.mod.common.util.cobblemonResource
import java.util.UUID
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
data class PlayerData(
val uuid: UUID,
@@ -22,8 +20,7 @@ data class PlayerData(
var starterLocked: Boolean,
var starterSelected: Boolean,
var starterUUID: UUID?,
var keyItems: MutableSet<Identifier>,
val extraData: MutableMap<String, PlayerDataExtension>,
val extraData: MutableMap<String, PlayerDataExtension>
) {
var advancementData: PlayerAdvancementData = PlayerAdvancementData()
@@ -39,7 +36,6 @@ data class PlayerData(
starterLocked = !Cobblemon.starterConfig.allowStarterOnJoin,
starterSelected = false,
starterUUID = null,
keyItems = mutableSetOf(),
extraData = mutableMapOf()
)
}
@@ -8,10 +8,13 @@
package com.cobblemon.mod.common.api.storage.player.adapter
import com.cobblemon.mod.common.api.storage.player.PlayerAdvancementData
import com.cobblemon.mod.common.api.storage.player.PlayerData
import com.cobblemon.mod.common.api.storage.player.PlayerDataExtension
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.fromJson
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.removeIf
import com.google.gson.GsonBuilder
import java.io.BufferedReader
import java.io.FileReader
@@ -21,8 +24,6 @@ import java.util.UUID
import net.minecraft.server.MinecraftServer
import net.minecraft.util.Identifier
import net.minecraft.util.WorldSavePath
import kotlin.reflect.KMutableProperty
import kotlin.reflect.full.memberProperties
class JsonPlayerData: PlayerDataStoreAdapter {
@@ -56,11 +57,9 @@ class JsonPlayerData: PlayerDataStoreAdapter {
playerFile.parentFile.mkdirs()
return if (playerFile.exists()) {
gson.fromJson<PlayerData>(BufferedReader(FileReader(playerFile))).also {
// Resolves old data that's missing new properties
val newProps = it::class.memberProperties.filterIsInstance<KMutableProperty<*>>().filter { member -> member.getter.call(it) == null }
if (newProps.isNotEmpty()) {
val defaultData = PlayerData.defaultData(uuid)
newProps.forEach { member -> member.setter.call(it, member.getter.call(defaultData)) }
// Resolves old data from pre 1.0, don't even ask man. - Hiroku
if (it.advancementData == null) {
it.advancementData = PlayerAdvancementData()
}
}
} else {
@@ -1,66 +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.storage.player.adapter
import com.cobblemon.mod.common.api.storage.player.PlayerAdvancementData
import com.cobblemon.mod.common.api.storage.player.PlayerData
import com.cobblemon.mod.common.api.storage.player.PlayerDataExtension
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.mongodb.client.MongoClient
import com.mongodb.client.model.ReplaceOptions
import net.minecraft.util.Identifier
import org.bson.Document
import java.util.UUID
import kotlin.reflect.KMutableProperty
import kotlin.reflect.full.memberProperties
class MongoPlayerDataAdapter(
mongoClient: MongoClient,
databaseName: String
) : PlayerDataStoreAdapter {
companion object {
val gson = GsonBuilder()
.disableHtmlEscaping()
.registerTypeAdapter(PlayerDataExtension::class.java, PlayerDataExtensionAdapter)
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
.create()
}
private val collection = mongoClient.getDatabase(databaseName).getCollection("PlayerDataCollection")
override fun load(uuid: UUID): PlayerData {
val filter = Document("uuid", uuid.toString())
val document = collection.find(filter).first()
return if (document != null) {
val jsonStr = document.toJson()
gson.fromJson(jsonStr, PlayerData::class.java).also {
val newProps = it::class.memberProperties.filterIsInstance<KMutableProperty<*>>().filter { member -> member.getter.call(it) == null }
if (newProps.isNotEmpty()) {
val defaultData = PlayerData.defaultData(uuid)
newProps.forEach { member -> member.setter.call(it, member.getter.call(defaultData)) }
}
}
} else {
PlayerData.defaultData(uuid).also(::save)
}
}
override fun save(playerData: PlayerData) {
val jsonStr = gson.toJson(playerData)
val document = Document.parse(jsonStr)
document.put("uuid", playerData.uuid.toString())
val filter = Document("uuid", playerData.uuid.toString())
collection.replaceOne(filter, document, ReplaceOptions().upsert(true))
}
}
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.api.storage.player.adapter
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.storage.player.DummyPlayerDataExtension
import com.cobblemon.mod.common.api.storage.player.PlayerDataExtension
import com.cobblemon.mod.common.api.storage.player.PlayerDataExtensionRegistry
import com.google.gson.JsonDeserializationContext
@@ -36,13 +34,7 @@ object PlayerDataExtensionAdapter: JsonSerializer<PlayerDataExtension>, JsonDese
val jObject = json.asJsonObject
val name = jObject.get(PlayerDataExtension.NAME_KEY)
?: throw IllegalStateException("PlayerDataExtension without name")
val extension = PlayerDataExtensionRegistry.get(name.asString)
return if (extension != null) {
extension.getDeclaredConstructor().newInstance().deserialize(jObject)
} else {
Cobblemon.LOGGER.info("No PlayerDataExtension registered with name ${name.asString}, loading data with a dummy extension.")
DummyPlayerDataExtension(jObject)
}
val extension = PlayerDataExtensionRegistry.getOrException(name.asString)
return extension.getDeclaredConstructor().newInstance().deserialize(jObject)
}
}
@@ -26,7 +26,7 @@ class JsonPlayerDataStoreFactory : PlayerDataStoreFactory {
}
override fun load(uuid: UUID): PlayerData {
return if (cache.contains(uuid))
return if(cache.contains(uuid))
cache[uuid]!!;
else {
val data = adapter.load(uuid);
@@ -40,7 +40,7 @@ class JsonPlayerDataStoreFactory : PlayerDataStoreFactory {
}
override fun saveAll() {
cache.forEach { (_, pd) -> adapter.save(pd) }
cache.forEach { (_, pd) -> adapter.save(pd)}
cache.removeIf { (uuid, _) -> uuid.getPlayer() == null }
}
@@ -1,45 +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.storage.player.factory
import com.cobblemon.mod.common.api.storage.player.PlayerData
import com.cobblemon.mod.common.api.storage.player.adapter.MongoPlayerDataAdapter
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.removeIf
import com.mongodb.client.MongoClient
import java.util.UUID
class MongoPlayerDataStoreFactory(mongoClient: MongoClient, databaseName: String) : PlayerDataStoreFactory {
private val cache = mutableMapOf<UUID, PlayerData>()
private val adapter = MongoPlayerDataAdapter(mongoClient, databaseName)
override fun load(uuid: UUID): PlayerData {
return if (cache.contains(uuid))
cache[uuid]!!;
else {
val data = adapter.load(uuid);
cache[uuid] = data
data
}
}
override fun save(data: PlayerData) {
adapter.save(data)
}
override fun saveAll() {
cache.forEach { (_, pd) -> adapter.save(pd) }
cache.removeIf { (uuid, _) -> uuid.getPlayer() == null }
}
override fun onPlayerDisconnect(uuid: UUID) {
cache.remove(uuid)
}
}
@@ -22,7 +22,7 @@ import net.minecraft.registry.tag.TagKey
object CobblemonBiomeTags {
@JvmField
val IS_DEEP_DARK = create("is_deep_dark")
val IS_ABYSS = create("is_abyss")
@JvmField
val IS_ARID = create("is_arid")
@JvmField
@@ -38,7 +38,7 @@ object CobblemonBiomeTags {
@JvmField
val IS_COLD = create("is_cold")
@JvmField
val IS_DEEP_OCEAN = create("is_deep_ocean")
val IS_DEEP = create("is_deep")
@JvmField
val IS_DESERT = create("is_desert")
@JvmField
@@ -52,9 +52,9 @@ object CobblemonBiomeTags {
@JvmField
val IS_FRESHWATER = create("is_freshwater")
@JvmField
val IS_COLD_OCEAN = create("is_cold_ocean")
val IS_FRIGID = create("is_frigid")
@JvmField
val IS_FROZEN_OCEAN = create("is_frozen_ocean")
val IS_FROZEN = create("is_frozen")
@JvmField
val IS_GLACIAL = create("is_glacial")
@JvmField
@@ -68,7 +68,7 @@ object CobblemonBiomeTags {
@JvmField
val IS_JUNGLE = create("is_jungle")
@JvmField
val IS_LUKEWARM_OCEAN = create("is_lukewarm_ocean")
val IS_LUKEWARM = create("is_lukewarm")
@JvmField
val IS_LUSH = create("is_lush")
@JvmField
@@ -86,7 +86,7 @@ object CobblemonBiomeTags {
@JvmField
val IS_PLATEAU = create("is_plateau")
@JvmField
val IS_WARM_OCEAN = create("is_warm_ocean")
val IS_REEF = create("is_reef")
@JvmField
val IS_RIVER = create("is_river")
@JvmField
@@ -96,7 +96,7 @@ object CobblemonBiomeTags {
@JvmField
val IS_SKY = create("is_sky")
@JvmField
val IS_SNOWY_FOREST = create("is_snowy_forest")
val IS_SNOWY = create("is_snowy")
@JvmField
val IS_SPOOKY = create("is_spooky")
@JvmField
@@ -119,10 +119,6 @@ object CobblemonBiomeTags {
val IS_VOlCANIC = create("is_volcanic")
@JvmField
val IS_WINTER = create("is_winter")
@JvmField
val IS_SPARSE = create("is_sparse")
@JvmField
val IS_DENSE = create("is_dense")
// Has Feature tags
@JvmField
@@ -26,9 +26,7 @@ object CobblemonBlockTags {
@JvmField val APRICORN_LOGS = createTag("apricorn_logs")
@JvmField val APRICORN_SAPLINGS = createTag("apricorn_saplings")
@JvmField val APRICORNS = createTag("apricorns")
@JvmField val BERRY_WILD_SOIL = createTag("berry_wild_soil")
@JvmField val BERRY_SOIL = createTag("berry_soil")
@JvmField val BERRY_REPLACEABLE = createTag("berry_replaceable")
@JvmField val CROPS = createTag("crops")
@JvmField val DRIPSTONE_GROWABLE = createTag("dripstone_growable")
@JvmField val DRIPSTONE_REPLACEABLES = createTag("dripstone_replaceables")
@@ -38,8 +36,7 @@ object CobblemonBlockTags {
@JvmField val ROOTS_SPREADABLE = createTag("roots_spreadable")
@JvmField val SMALL_FLOWERS = createTag("small_flowers")
@JvmField val SEES_SKY = createTag("sees_sky")
@JvmField val SNOW_BLOCK = createTag("snow_block")
@JvmField val ROOTS = createTag("roots")
private fun createTag(name: String) = TagKey.of(RegistryKeys.BLOCK, cobblemonResource(name))
}
@@ -169,6 +169,4 @@ object ElementalTypes {
}
fun count() = allTypes.size
fun all() = this.allTypes.toList()
}
@@ -66,7 +66,7 @@ object BattleBuilder {
CobblemonEvents.BATTLE_STARTED_PRE.postThen(
BattleStartedPreEvent(listOf(player1Actor, player2Actor), battleFormat, true, false, false))
{
return SuccessfulBattleStart(
SuccessfulBattleStart(
BattleRegistry.startBattle(
battleFormat = battleFormat,
side1 = BattleSide(player1Actor),
@@ -136,7 +136,7 @@ object BattleBuilder {
if (!cloneParties) {
pokemonEntity.battleId.set(Optional.of(battle.battleId))
}
return SuccessfulBattleStart(battle)
SuccessfulBattleStart(battle)
}
errors
} else {
@@ -28,7 +28,7 @@ data class BattleFormat(
companion object {
val GEN_9_SINGLES = BattleFormat(
battleType = BattleTypes.SINGLES,
ruleSet = setOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
ruleSet = setOf(BattleRules.OBTAINABLE)
)
val GEN_9_DOUBLES = BattleFormat(
@@ -16,9 +16,9 @@ import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemProvider
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.net.messages.client.battle.BattleChallengeExpiredPacket
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.google.gson.GsonBuilder
import java.time.Instant
import java.util.Optional
@@ -38,10 +38,7 @@ object BattleRegistry {
fun isExpired() = Instant.now().isAfter(challengedTime.plusSeconds(expiryTimeSeconds.toLong()))
}
val gson = GsonBuilder()
.disableHtmlEscaping()
.registerTypeAdapter(ShowdownMoveset::class.java, ShowdownMovesetAdapter)
.create()
val gson = GsonBuilder().disableHtmlEscaping().create()
private val battleMap = ConcurrentHashMap<UUID, PokemonBattle>()
// Challenger to challenge
val pvpChallenges = mutableMapOf<UUID, BattleChallenge>()
@@ -108,7 +105,7 @@ object BattleRegistry {
}|"
)
// Nature
val battleNature = pk.effectiveNature
val battleNature = pk.mintedNature ?: pk.nature
packedTeamBuilder.append("${battleNature.name.path}|")
// EVs
val evsInOrder = Stats.PERMANENT.map { pk.evs.getOrDefault(it) }.joinToString(separator = ",")
@@ -133,12 +130,11 @@ object BattleRegistry {
// Hidden Power Type
packedTeamBuilder.append(",")
// Gigantamax
packedTeamBuilder.append("${if (pk.gmaxFactor) "G" else ""},")
packedTeamBuilder.append(",")
// DynamaxLevel
// 0 - 9, empty == 10
packedTeamBuilder.append("${if (pk.dmaxLevel < 10) pk.dmaxLevel else ""},")
packedTeamBuilder.append(",")
// Teratype
packedTeamBuilder.append("${pokemon.effectedPokemon.teraType.name},")
packedTeamBuilder.append(",")
team.add(packedTeamBuilder.toString())
}
@@ -10,8 +10,6 @@ package com.cobblemon.mod.common.battles
object BattleRules {
const val OBTAINABLE = "Obtainable"
const val PAST = "+Past"
const val UNOBTAINABLE = "+Unobtainable"
const val TEAM_PREVIEW = "Team Preview"
const val ENDLESS_BATTLE_CLAUSE = "Endless Battle Clause"
const val CANCEL_MOD = "Cancel Mod"
@@ -19,7 +19,6 @@ class InBattleMove {
var maxpp: Int = 100
var target: MoveTarget = MoveTarget.self
var disabled: Boolean = false
var gimmickMove: InBattleGimmickMove? = null
companion object {
fun loadFromBuffer(buffer: PacketByteBuf): InBattleMove {
@@ -45,27 +44,4 @@ class InBattleMove {
buffer.writeEnumConstant(target)
buffer.writeBoolean(disabled)
}
}
// Defined in sim/battle-actions.ts canZMove and getMaxMove
class InBattleGimmickMove {
lateinit var move: String
var target: MoveTarget = MoveTarget.self
var disabled: Boolean = false
companion object {
fun loadFromBuffer(buffer: PacketByteBuf): InBattleGimmickMove {
return InBattleGimmickMove().apply {
move = buffer.readString()
target = buffer.readEnumConstant(MoveTarget::class.java)
disabled = buffer.readBoolean()
}
}
}
fun saveToBuffer(buffer: PacketByteBuf) {
buffer.writeString(move)
buffer.writeEnumConstant(target)
buffer.writeBoolean(disabled)
}
}
@@ -8,22 +8,14 @@
package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.item.battle.BagItem
import com.cobblemon.mod.common.net.IntSize
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.hasKeyItem
import com.cobblemon.mod.common.util.readSizedInt
import com.cobblemon.mod.common.util.writeSizedInt
import com.google.gson.*
import com.google.gson.reflect.TypeToken
import java.lang.Integer.max
import java.util.UUID
import net.minecraft.network.PacketByteBuf
import java.lang.reflect.Type
class ShowdownActionRequest(
var wait: Boolean = false,
var active: MutableList<ShowdownMoveset>? = null,
@@ -71,22 +63,6 @@ class ShowdownActionRequest(
}
return this
}
fun sanitize(battle: PokemonBattle, battleActor: BattleActor) {
val player = battle.players.find { it.uuid == battleActor.uuid } ?: return
this.active?.forEach { moveset ->
moveset.getGimmicks().forEach { gimmick ->
// TODO: use the Identifiers of actual items
val triggerItem = when(gimmick) {
ShowdownMoveset.Gimmick.MEGA_EVOLUTION -> cobblemonResource("key_stone")
ShowdownMoveset.Gimmick.DYNAMAX -> cobblemonResource("dynamax_band")
ShowdownMoveset.Gimmick.TERASTALLIZATION -> cobblemonResource("tera_orb")
else -> cobblemonResource("z_ring")
}
if (!player.hasKeyItem(triggerItem)) moveset.blockGimmick(gimmick)
}
}
}
}
enum class ShowdownActionResponseType(val loader: (PacketByteBuf) -> ShowdownActionResponse) {
@@ -115,20 +91,18 @@ abstract class ShowdownActionResponse(val type: ShowdownActionResponseType) {
abstract fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?): String
}
data class MoveActionResponse(var moveName: String, var targetPnx: String? = null, var gimmickID: String? = null): ShowdownActionResponse(ShowdownActionResponseType.MOVE) {
data class MoveActionResponse(var moveName: String, var targetPnx: String? = null): ShowdownActionResponse(ShowdownActionResponseType.MOVE) {
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean): Boolean {
if (forceSwitch || showdownMoveSet == null) {
return false
}
val move = showdownMoveSet.moves.find { it.id == moveName } ?: return false
val gimmickMove = move.gimmickMove
val validGimmickMove = gimmickMove != null && !gimmickMove.disabled
if (!validGimmickMove && !move.canBeUsed()) {
if (!move.canBeUsed()) {
// No PP or disabled or something
return false
}
val availableTargets = (gimmickMove?.target ?: move.target).targetList(activeBattlePokemon)?.takeIf { it.isNotEmpty() } ?: return true
val availableTargets = move.target.targetList(activeBattlePokemon)?.takeIf { it.isNotEmpty() } ?: return true
val pnx = targetPnx ?: return false // If the targets list is non-null then they need to have specified a target
val (_, targetPokemon) = activeBattlePokemon.actor.battle.getActorAndActiveSlotFromPNX(pnx)
@@ -150,21 +124,22 @@ data class MoveActionResponse(var moveName: String, var targetPnx: String? = nul
"move $moveIndex $digit"
} else {
"move $moveIndex"
}.plus(gimmickID?.let { " $gimmickID" } ?: "")
}
}
override fun saveToBuffer(buffer: PacketByteBuf) {
super.saveToBuffer(buffer)
buffer.writeString(moveName)
buffer.writeNullable(targetPnx) { _, targetPnx -> buffer.writeString(targetPnx) }
buffer.writeNullable(gimmickID) { _, gimmickID -> buffer.writeString(gimmickID) }
buffer.writeBoolean(targetPnx != null)
targetPnx?.let(buffer::writeString)
}
override fun loadFromBuffer(buffer: PacketByteBuf): ShowdownActionResponse {
super.loadFromBuffer(buffer)
moveName = buffer.readString()
targetPnx = buffer.readNullable { buffer.readString() }
gimmickID = buffer.readNullable { buffer.readString() }
if (buffer.readBoolean()) {
targetPnx = buffer.readString()
}
return this
}
}
@@ -268,31 +243,11 @@ class BagItemActionResponse(val bagItem: BagItem, val target: BattlePokemon, val
class ShowdownMoveset {
lateinit var moves: List<InBattleMove>
var trapped = false
var canMegaEvo = false
var canUltraBurst = false
var canZMove: List<InBattleGimmickMove?>? = null
var canDynamax = false
var maxMoves: List<InBattleGimmickMove?>? = null
var canTerastallize: String? = null
fun saveToBuffer(buffer: PacketByteBuf) {
buffer.writeSizedInt(IntSize.U_BYTE, moves.size)
moves.forEach { it.saveToBuffer(buffer) }
buffer.writeBoolean(trapped)
buffer.writeBoolean(canMegaEvo)
buffer.writeBoolean(canUltraBurst)
buffer.writeNullable(canZMove) { _, canZMove ->
canZMove.forEach {
buffer.writeNullable(it) { _, zmove -> zmove.saveToBuffer(buffer)}
}
}
buffer.writeBoolean(canDynamax)
buffer.writeNullable(maxMoves) { _, maxMoves ->
maxMoves.forEach {
buffer.writeNullable(it) { _, maxMove -> maxMove.saveToBuffer(buffer)}
}
}
buffer.writeNullable(canTerastallize) { _, teraType -> buffer.writeString(teraType) }
}
fun loadFromBuffer(buffer: PacketByteBuf): ShowdownMoveset {
@@ -302,60 +257,8 @@ class ShowdownMoveset {
}
this.moves = moves
this.trapped = buffer.readBoolean()
this.canMegaEvo = buffer.readBoolean()
this.canUltraBurst = buffer.readBoolean()
this.canZMove = buffer.readNullable {
val zMoves = mutableListOf<InBattleGimmickMove?>()
repeat(moves.size) { zMoves.add(buffer.readNullable { InBattleGimmickMove.loadFromBuffer(buffer) }) }
return@readNullable zMoves
}
this.canDynamax = buffer.readBoolean()
this.maxMoves = buffer.readNullable {
val maxMoves = mutableListOf<InBattleGimmickMove?>()
repeat(moves.size) { maxMoves.add(buffer.readNullable { InBattleGimmickMove.loadFromBuffer(buffer) }) }
return@readNullable maxMoves
}
this.canTerastallize = buffer.readNullable{ buffer.readString() }
this.setGimmickMapping()
return this
}
/** Showdown IDs of battle gimmicks. */
enum class Gimmick(val id: String) {
MEGA_EVOLUTION("mega"),
ULTRA_BURST("ultra"),
Z_POWER("zmove"),
DYNAMAX("max"),
TERASTALLIZATION("terastal")
}
/** Check whether Dynamax/Gigantamax is already active. */
fun hasActiveGimmick() = !this.canDynamax && this.maxMoves != null
/** [Gimmick]s that can be activated with a [MoveActionResponse]. */
fun getGimmicks() = if (!hasActiveGimmick()) buildList{
if (canMegaEvo) add(Gimmick.MEGA_EVOLUTION)
if (canUltraBurst) add(Gimmick.ULTRA_BURST)
if (canZMove != null) add(Gimmick.Z_POWER)
if (canDynamax) add(Gimmick.DYNAMAX)
if (canTerastallize != null) add(Gimmick.TERASTALLIZATION)
}.toList() else listOf() // keep this immutable
/** Map each [InBattleMove] to their respective [InBattleGimmickMove] variant. */
fun setGimmickMapping() = (canZMove ?: maxMoves)?.let { gimmickMoves ->
moves.forEachIndexed { index, move -> move.gimmickMove = gimmickMoves[index] }
}
/** Sanitize Moveset by disabling gimmick. */
fun blockGimmick(gimmick: Gimmick) {
when (gimmick) {
Gimmick.MEGA_EVOLUTION -> this.canMegaEvo = false
Gimmick.DYNAMAX -> this.canDynamax = false
Gimmick.ULTRA_BURST -> this.canUltraBurst = false
Gimmick.Z_POWER -> this.canZMove = null
else -> this.canTerastallize = null
}
}
}
class ShowdownSide {
lateinit var name: UUID
@@ -417,30 +320,4 @@ class ShowdownPokemon {
ability = buffer.readString()
return this
}
}
/** Unwraps useless maxMoves object and initializes [ShowdownMoveset.gimmickMapping] */
object ShowdownMovesetAdapter : JsonDeserializer<ShowdownMoveset> {
val gson = GsonBuilder().addDeserializationExclusionStrategy(MovesetExclusionStrategy).create()
object MovesetExclusionStrategy : ExclusionStrategy {
override fun shouldSkipField(field: FieldAttributes?): Boolean {
return field?.name == "maxMoves"
}
override fun shouldSkipClass(p0: Class<*>?): Boolean {
return false;
}
}
override fun deserialize(jsonElement: JsonElement, type: Type, context: JsonDeserializationContext): ShowdownMoveset {
val json = jsonElement.asJsonObject
val moveset = gson.fromJson(json, ShowdownMoveset::class.java)
json.get("maxMoves")?.asJsonObject?.let { dynamaxOptions ->
val maxMovesToken = object: TypeToken<List<InBattleGimmickMove?>?>() {}.type
moveset.maxMoves = Gson().fromJson(dynamaxOptions.get("maxMoves"), maxMovesToken)
}
moveset.setGimmickMapping()
return moveset
}
}
@@ -23,7 +23,6 @@ import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.scheduling.after
import com.cobblemon.mod.common.api.text.*
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.dispatch.BattleDispatch
import com.cobblemon.mod.common.battles.dispatch.DispatchResult
import com.cobblemon.mod.common.battles.dispatch.GO
@@ -109,12 +108,6 @@ object ShowdownInterpreter {
updateInstructions["|-sidestart|"] = this::handleSideStartInstructions
updateInstructions["|-sideend|"] = this::handleSideEndInstructions
updateInstructions["|-fieldactivate|"] = this::handleFieldActivateInstructions
updateInstructions["|-clearnegativeboost|"] = this::handleClearNegativeBoostInstructions
updateInstructions["|-zpower|"] = this::handleZPowerInstructions
updateInstructions["|-zbroken|"] = this::handleZBrokenInstructions
updateInstructions["|-terastallize|"] = this::handleTerastallizeInstructions
updateInstructions["|detailschange|"] = this::handleDetailsChangeInstructions
updateInstructions["|-mega|"] = this::handleMegaInstructions
sideUpdateInstructions["|request|"] = this::handleRequestInstruction
splitUpdateInstructions["|switch|"] = this::handleSwitchInstruction
@@ -155,12 +148,7 @@ object ShowdownInterpreter {
}
battle.dispatchWaiting(1.5F) {
val lang = when {
message.hasOptionalArgument("zeffect") -> battleLang("$rootKey.$severity.zeffect", pokemon.getName(), stat)
else -> battleLang("$rootKey.$severity", pokemon.getName(), stat)
}
battle.broadcastChatMessage(lang)
battle.broadcastChatMessage(battleLang("$rootKey.$severity", pokemon.getName(), stat))
val boostBucket = if (isBoost) BattleContext.Type.BOOST else BattleContext.Type.UNBOOST
val context = getContextFromAction(message, boostBucket, battle)
// TODO: replace with context that tracks detailed information such as # of stages
@@ -658,9 +646,10 @@ object ShowdownInterpreter {
this.lastCauser[battle.battleId] = message
userPokemon.effectedPokemon.let { pokemon ->
if (UseMoveEvolutionProgress.supports(pokemon, move)) {
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is UseMoveEvolutionProgress && it.currentProgress().move == move }) { UseMoveEvolutionProgress() }
progress.updateProgress(UseMoveEvolutionProgress.Progress(move, progress.currentProgress().amount + 1))
val progress = UseMoveEvolutionProgress()
if (progress.shouldKeep(pokemon)) {
val created = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is UseMoveEvolutionProgress && it.currentProgress().move == move }) { progress }
created.updateProgress(UseMoveEvolutionProgress.Progress(created.currentProgress().move, created.currentProgress().amount + 1))
}
}
@@ -690,7 +679,7 @@ object ShowdownInterpreter {
val effectID = message.effectAt(1)?.id ?: return@dispatchWaiting
val name = pokemon.getName()
// Move may be null as it's not always given
val moveName = message.moveAt(2)?.displayName ?: run { println(message.argumentAt(2)); "(Unrecognized: ${message.argumentAt(2)})".text() }
val moveName = message.argumentAt(2)?.let { Moves.getByName(it)?.displayName } ?: Text.EMPTY
val lang = when (effectID) {
// TODO: in the games they use a generic image because there is a popup of the ability and the sprite of the mon, it may be good to have a similar system here
@@ -887,7 +876,6 @@ object ShowdownInterpreter {
val lang = when (effectID) {
"confusion", "perish3" -> return@dispatch GO // Skip
"perish2", "perish1", "perish0" -> battleLang("start.perish", pokemon.getName(), effectID.last().digitToInt())
"dynamax" -> battleLang("start.${message.effectAt(2)?.id ?: effectID}", pokemon.getName()).yellow()
else -> battleLang("start.$effectID", pokemon.getName())
}
battle.broadcastChatMessage(lang)
@@ -962,11 +950,10 @@ object ShowdownInterpreter {
}
// Includes revealed move
"forewarn" -> {
val moveName = message.moveAt(2)?.displayName ?: run { println(message.argumentAt(2)); "(Unrecognized: ${message.argumentAt(2)})".text() }
val moveName = message.argumentAt(2)?.let { Moves.getByName(it)?.displayName } ?: Text.EMPTY
battleLang("activate.forewarn", sourceName, moveName)
}
"focussash", "focusband" -> battleLang("activate.focusband", pokemonName, message.effectAt(1)!!.typelessData)
"maxguard", "protect" -> battleLang("activate.protect", pokemonName)
else -> battleLang("activate.${effect.id}", pokemonName, sourceName)
}
battle.broadcastChatMessage(lang)
@@ -1236,7 +1223,6 @@ object ShowdownInterpreter {
// Parse Json message and update state info for actor
val request = BattleRegistry.gson.fromJson(message.rawMessage.split("|request|")[1], ShowdownActionRequest::class.java)
request.sanitize(battle, battleActor)
if (battle.started) {
battle.dispatchGo {
// This request won't be acted on until the start of next turn
@@ -1369,12 +1355,14 @@ object ShowdownInterpreter {
val battlePokemon = publicMessage.getBattlePokemon(0, battle) ?: return
if (privateMessage.optionalArgument("from")?.equals("recoil", true) == true) {
battlePokemon.effectedPokemon.let { pokemon ->
if (RecoilEvolutionProgress.supports(pokemon)) {
val recoilProgress = RecoilEvolutionProgress()
// Lazy cheat to see if it's necessary to use this
if (recoilProgress.shouldKeep(pokemon)) {
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is RecoilEvolutionProgress }) { recoilProgress }
val newPercentage = privateMessage.argumentAt(1)?.split("/")?.getOrNull(0)?.toIntOrNull() ?: 0
val newHealth = (pokemon.hp * (newPercentage / 100.0)).roundToInt()
val difference = pokemon.currentHealth - newHealth
if (difference > 0) {
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is RecoilEvolutionProgress }) { RecoilEvolutionProgress() }
progress.updateProgress(RecoilEvolutionProgress.Progress(progress.currentProgress().recoil + difference))
}
}
@@ -1418,8 +1406,10 @@ object ShowdownInterpreter {
battlePokemon.effectedPokemon.currentHealth = remainingHealth
if (difference > 0) {
battlePokemon.effectedPokemon.let { pokemon ->
if (DamageTakenEvolutionProgress.supports(pokemon)) {
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is DamageTakenEvolutionProgress }) { DamageTakenEvolutionProgress() }
val damageProgress = DamageTakenEvolutionProgress()
// Lazy cheat to see if it's necessary to use this
if (damageProgress.shouldKeep(pokemon)) {
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is DamageTakenEvolutionProgress }) { damageProgress }
progress.updateProgress(DamageTakenEvolutionProgress.Progress(progress.currentProgress().amount + difference))
}
}
@@ -1444,8 +1434,8 @@ object ShowdownInterpreter {
* The switched Pokémon has HP HP, and status STATUS.
*/
fun handleDragInstruction(battle: PokemonBattle, actor: BattleActor, publicMessage: BattleMessage, privateMessage: BattleMessage) {
battle.dispatchInsert {
val (pnx, pokemonID) = publicMessage.pnxAndUuid(0)!!
battle.dispatchGo {
val (pnx, pokemonID) = publicMessage.pnxAndUuid(0) ?: return@dispatchGo
val (_, activePokemon) = battle.getActorAndActiveSlotFromPNX(pnx)
val pokemon = battle.getBattlePokemon(pnx, pokemonID)
@@ -1457,15 +1447,13 @@ object ShowdownInterpreter {
battle.majorBattleActions[pokemon.uuid] = publicMessage
val entity = if (actor is EntityBackedBattleActor<*>) actor.entity else null
setOf(
BattleDispatch {
if (entity != null) {
this.createEntitySwitch(battle, actor, entity, pnx, activePokemon, pokemon)
} else {
this.createNonEntitySwitch(battle, actor, pnx, activePokemon, pokemon)
}
battle.dispatch {
if (entity != null) {
this.createEntitySwitch(battle, actor, entity, pnx, activePokemon, pokemon)
} else {
this.createNonEntitySwitch(battle, actor, pnx, activePokemon, pokemon)
}
)
}
}
}
@@ -1544,16 +1532,15 @@ object ShowdownInterpreter {
val battlePokemon = privateMessage.getBattlePokemon(0, battle) ?: return
val rawHpAndStatus = privateMessage.argumentAt(1)?.split(" ") ?: return
val rawHpRatio = rawHpAndStatus.getOrNull(0) ?: return
val newHealth = rawHpRatio.split("/").map { it.toFloatOrNull() ?: return }
val newHealthRatio = rawHpRatio.split("/").map { it.toFloatOrNull()?.div(100) ?: return }
val newHealth = rawHpRatio.split("/").getOrNull(0)?.toIntOrNull() ?: return
val newHealthRatio = publicMessage.argumentAt(1)?.split("/")?.getOrNull(0)?.toFloatOrNull()?.times(0.01F) ?: return
val effect = privateMessage.effect()
val pokemonName = battlePokemon.getName()
broadcastOptionalAbility(battle, effect, pokemonName)
battle.dispatchWaiting {
if (pnx != null) {
// dynamax changes max health
battle.sendSidedUpdate(actor, BattleHealthChangePacket(pnx, newHealth[0], newHealth[1]), BattleHealthChangePacket(pnx, newHealthRatio[0]))
battle.sendSidedUpdate(actor, BattleHealthChangePacket(pnx, newHealth.toFloat()), BattleHealthChangePacket(pnx, newHealthRatio))
}
val silent = privateMessage.hasOptionalArgument("silent")
if (!silent) {
@@ -1588,7 +1575,7 @@ object ShowdownInterpreter {
battle.broadcastChatMessage(lang)
}
battle.minorBattleActions[battlePokemon.uuid] = privateMessage
battlePokemon.effectedPokemon.currentHealth = newHealth[0].toInt()
battlePokemon.effectedPokemon.currentHealth = newHealth
// This part is not always present
val rawStatus = rawHpAndStatus.getOrNull(1) ?: return@dispatchWaiting
@@ -1644,105 +1631,6 @@ object ShowdownInterpreter {
}
}
/**
* Format:
* |-clearnegativeboost|POKEMON
*
* Clear the negative boosts from the target Pokémon POKEMON (usually as the result of a zeffect).
*/
private fun handleClearNegativeBoostInstructions(battle: PokemonBattle, message: BattleMessage, remainingLines: MutableList<String>) {
val battlePokemon = message.getBattlePokemon(0, battle) ?: return
val pokemonName = battlePokemon.getName()
battle.dispatchWaiting(1.5F) {
val lang = when {
message.hasOptionalArgument("zeffect") -> battleLang("clearallnegativeboost.zeffect", pokemonName)
else -> battleLang("clearallnegativeboost", pokemonName)
}
battle.broadcastChatMessage(lang)
battlePokemon.contextManager.clear(BattleContext.Type.UNBOOST)
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
/**
* Format:
* |-zpower|POKEMON
*
* The Pokémon POKEMON has used the z-move version of its move.
*/
private fun handleZPowerInstructions(battle: PokemonBattle, message: BattleMessage, remainingLines: MutableList<String>) {
val battlePokemon = message.getBattlePokemon(0, battle) ?: return
val pokemonName = battlePokemon.getName()
battle.dispatchWaiting {
battle.broadcastChatMessage(battleLang("zpower", pokemonName).yellow())
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
/**
* Format:
* |-zbroken|POKEMON
*
* A z-move has broken through protect and hit the Pokémon POKEMON.
*/
private fun handleZBrokenInstructions(battle: PokemonBattle, message: BattleMessage, remainingLines: MutableList<String>) {
val battlePokemon = message.getBattlePokemon(0, battle) ?: return
val pokemonName = battlePokemon.getName()
battle.dispatchWaiting {
battle.broadcastChatMessage(battleLang("zbroken", pokemonName).red())
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
/**
* Format:
* |-terastallize|POKEMON|TYPE
*
* The Pokémon POKEMON terastallized into type TYPE.
*/
private fun handleTerastallizeInstructions(battle: PokemonBattle, message: BattleMessage, remainingLines: MutableList<String>) {
val battlePokemon = message.getBattlePokemon(0, battle) ?: return
val pokemonName = battlePokemon.getName()
val type = message.effectAt(1)?.let { ElementalTypes.get(it.id) } ?: return
battle.dispatchWaiting {
battle.broadcastChatMessage(battleLang("terastallize", pokemonName, type.displayName).yellow())
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
/**
* Format:
* |detailschange|POKEMON|DETAILS|HP STATUS
*
* The specified Pokémon has changed formes (via Mega Evolution, ability, etc.). If the forme change is permanent,
* then detailschange will appear; otherwise, the client will send -formechange.
*/
private fun handleDetailsChangeInstructions(battle: PokemonBattle, message: BattleMessage, remainingLines: MutableList<String>) {
val battlePokemon = message.getBattlePokemon(0, battle) ?: return
val pokemonName = battlePokemon.getName()
val formName = message.argumentAt(1)?.split(',')?.get(0)?.substringAfter('-')?.lowercase() ?: return
battle.dispatchWaiting {
battle.broadcastChatMessage(battleLang("detailschange.$formName", pokemonName))
battle.majorBattleActions[battlePokemon.uuid] = message
}
}
/**
* Format:
* |-mega|POKEMON|MEGASTONE
*
* The Pokémon POKEMON used MEGASTONE to Mega Evolve.
*/
private fun handleMegaInstructions(battle: PokemonBattle, message: BattleMessage, remainingLines: MutableList<String>) {
val battlePokemon = message.getBattlePokemon(0, battle) ?: return
val pokemonName = battlePokemon.getName()
battle.dispatchWaiting {
battle.broadcastChatMessage(battleLang("mega", pokemonName).yellow())
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
// Used for things that are only meant for visual information we don't have
private fun handleSilently(battle: PokemonBattle, message: BattleMessage, remainingLines: MutableList<String>) {
battle.dispatchGo { }
@@ -137,7 +137,7 @@ class ApricornBlock(settings: Settings, val apricorn: Apricorn) : HorizontalFaci
world.playSoundServer(position = pos.toVec3d(), sound = SoundEvents.ENTITY_ITEM_PICKUP, volume = 0.7F, pitch = 1.4F)
if (world is ServerWorld && player is ServerPlayerEntity) {
CobblemonEvents.APRICORN_HARVESTED.post(ApricornHarvestEvent(player, apricorn, world, pos))
CobblemonEvents.APRICORN_HARVESTED.post(ApricornHarvestEvent(player, world, pos))
}
}
return ActionResult.success(world.isClient)
@@ -8,7 +8,9 @@
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.berry.Berries
import com.cobblemon.mod.common.api.berry.Berry
@@ -19,13 +21,8 @@ import com.cobblemon.mod.common.api.mulch.MulchVariant
import com.cobblemon.mod.common.api.mulch.Mulchable
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import net.minecraft.block.Block
import net.minecraft.block.BlockRenderType
import net.minecraft.block.BlockState
import net.minecraft.block.BlockWithEntity
import net.minecraft.block.Blocks
import net.minecraft.block.Fertilizable
import net.minecraft.block.ShapeContext
import com.cobblemon.mod.common.block.entity.HealingMachineBlockEntity
import net.minecraft.block.*
import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityTicker
import net.minecraft.block.entity.BlockEntityType
@@ -45,7 +42,6 @@ import net.minecraft.util.Hand
import net.minecraft.util.Identifier
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Box
import net.minecraft.util.math.Direction
import net.minecraft.util.math.random.Random
import net.minecraft.util.shape.VoxelShape
@@ -67,14 +63,6 @@ class BerryBlock(private val berryIdentifier: Identifier, settings: Settings) :
*/
fun berry(): Berry? = Berries.getByIdentifier(this.berryIdentifier)
override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity) {
if (!player.isCreative && state.get(AGE) == FRUIT_AGE) {
val treeEntity = world.getBlockEntity(pos) as BerryBlockEntity
treeEntity.harvest(world, state, pos, player).forEach { drop -> Block.dropStack(world, pos, drop) }
}
super.onBreak(world, pos, state, player)
}
override fun createBlockEntity(pos: BlockPos, state: BlockState) = BerryBlockEntity(pos, state, berryIdentifier)
override fun isFertilizable(world: WorldView, pos: BlockPos, state: BlockState, isClient: Boolean) = !this.isMaxAge(state)
@@ -87,20 +75,17 @@ class BerryBlock(private val berryIdentifier: Identifier, settings: Settings) :
defaultState = this.stateManager.defaultState
.with(WAS_GENERATED, false)
.with(AGE, 0)
.with(MULCH, MulchVariant.NONE)
}
override fun grow(world: ServerWorld, random: Random, pos: BlockPos, state: BlockState) {
val curAge = state.get(AGE)
val newAge = curAge + 1
if (newAge > FRUIT_AGE) return
val newState = state.with(AGE, newAge)
var newState = state.with(AGE, newAge)
val treeEntity = world.getBlockEntity(pos) as BerryBlockEntity
if (curAge == MATURE_AGE) {
treeEntity.generateGrowthPoints(world, state, pos, null)
determineMutation(world, random, pos, state)
}
world.setBlockState(pos, newState, Block.NOTIFY_LISTENERS)
treeEntity.goToNextStageTimer(FRUIT_AGE - curAge)
treeEntity.markDirty()
@@ -121,9 +106,9 @@ class BerryBlock(private val berryIdentifier: Identifier, settings: Settings) :
CobblemonEvents.BERRY_MUTATION_OFFER.post(BerryMutationOfferEvent(berry, world, state, pos, mutations)) { berryMutationOffer ->
if (berryMutationOffer.mutations.isNotEmpty()) {
var mutateChance = 125
if (getMulch(state) == MulchVariant.SURPRISE) {
if (treeEntity.mulchVariant == MulchVariant.SURPRISE) {
mutateChance *= 4
treeEntity.decrementMulchDuration(world, pos, state)
treeEntity.mulchDuration--
}
val mutation = if (random.nextInt(1000) < mutateChance) mutations.random() else null
(world.getBlockEntity(pos) as? BerryBlockEntity)?.let { blockEntity ->
@@ -142,7 +127,8 @@ class BerryBlock(private val berryIdentifier: Identifier, settings: Settings) :
state: BlockState,
variant: MulchVariant
): Boolean {
return getMulch(state) == MulchVariant.NONE && state.get(AGE) < FLOWER_AGE && world.getBlockState(pos.down()).isOf(Blocks.FARMLAND)
val treeEntity = world.getBlockEntity(pos) as BerryBlockEntity
return treeEntity.mulchVariant == null
}
override fun applyMulch(
@@ -153,31 +139,29 @@ class BerryBlock(private val berryIdentifier: Identifier, settings: Settings) :
variant: MulchVariant
) {
val treeEntity = world.getBlockEntity(pos) as BerryBlockEntity
setMulch(world, pos, state, variant)
treeEntity.mulchVariant = variant
treeEntity.mulchDuration = variant.duration
world.playSound(null, pos, CobblemonSounds.MULCH_PLACE, SoundCategory.BLOCKS, 0.6F, 1F)
treeEntity.refreshTimers(pos)
world.playSound(null, pos, CobblemonSounds.MULCH_PLACE, SoundCategory.BLOCKS)
}
@Deprecated("Deprecated in Java")
override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hit: BlockHitResult): ActionResult {
val treeEntity = world.getBlockEntity(pos) as BerryBlockEntity
if (player.getStackInHand(hand).item is ShovelItem && getMulch(state) != MulchVariant.NONE) {
setMulch(world, pos, state, MulchVariant.NONE)
if(player.getStackInHand(hand).item is ShovelItem && treeEntity.mulchVariant != null) {
treeEntity.mulchVariant = null
treeEntity.markDirty()
world.playSound(null, pos, CobblemonSounds.MULCH_REMOVE, SoundCategory.BLOCKS, 0.6F, 1F)
this.spawnBreakParticles(world, player, pos, state.with(AGE, 0))
world.playSound(null, pos, CobblemonSounds.MULCH_REMOVE, SoundCategory.BLOCKS)
return ActionResult.SUCCESS
}
if (player.getStackInHand(hand).isOf(Items.BONE_MEAL) && !this.isMaxAge(state)) {
if (player.getStackInHand(hand).isOf(Items.BONE_MEAL)) {
return ActionResult.PASS
} else if (this.isMaxAge(state)) {
}
else if (this.isMaxAge(state)) {
val blockEntity = world.getBlockEntity(pos) as? BerryBlockEntity ?: return ActionResult.PASS
blockEntity.harvest(world, state, pos, player).forEach { drop ->
Block.dropStack(world, pos, drop)
}
world.playSound(null, pos, CobblemonSounds.BERRY_HARVEST, SoundCategory.BLOCKS, 0.4F, 1F)
return ActionResult.success(world.isClient)
}
return super.onUse(state, world, pos, player, hand, hit)
@@ -185,8 +169,7 @@ class BerryBlock(private val berryIdentifier: Identifier, settings: Settings) :
@Deprecated("Deprecated in Java")
override fun canPlaceAt(state: BlockState, world: WorldView, pos: BlockPos): Boolean {
val below = world.getBlockState(pos.down())
return (state.get(WAS_GENERATED) && below.isIn(CobblemonBlockTags.BERRY_WILD_SOIL)) || below.isIn(CobblemonBlockTags.BERRY_SOIL)
return state.get(WAS_GENERATED) || world.getBlockState(pos.down()).isIn(CobblemonBlockTags.BERRY_SOIL)
}
@Deprecated("Deprecated in Java")
@@ -195,16 +178,15 @@ class BerryBlock(private val berryIdentifier: Identifier, settings: Settings) :
}
override fun onPlaced(world: World, pos: BlockPos, state: BlockState, placer: LivingEntity?, itemStack: ItemStack) {
// if (!world.isClient) {
// val blockEntity = world.getBlockEntity(pos) as? BerryBlockEntity ?: return
// blockEntity.generateGrowthPoints(world, state, pos, placer)
// }
if (!world.isClient) {
val blockEntity = world.getBlockEntity(pos) as? BerryBlockEntity ?: return
blockEntity.generateGrowthPoints(world, state, pos, placer)
}
}
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
builder.add(AGE)
builder.add(WAS_GENERATED)
builder.add(MULCH)
}
override fun getPickStack(world: BlockView?, pos: BlockPos?, state: BlockState?): ItemStack {
@@ -215,12 +197,7 @@ class BerryBlock(private val berryIdentifier: Identifier, settings: Settings) :
@Deprecated("Deprecated in Java")
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape {
val berry = this.berry() ?: return VoxelShapes.fullCube()
return when (state.get(AGE)) {
0 -> PLANTED_SHAPE
1 -> PLANTED_SHAPE
2 -> berry.sproutShape
else -> berry.matureShape
}
return if (state.get(AGE) >= MATURE_AGE) berry.matureShape else berry.sproutShape
}
private fun isMaxAge(state: BlockState) = state.get(AGE) == FRUIT_AGE
@@ -232,81 +209,7 @@ class BerryBlock(private val berryIdentifier: Identifier, settings: Settings) :
const val MATURE_AGE = 3
const val FLOWER_AGE = 4
const val FRUIT_AGE = 5
val AGE: IntProperty = IntProperty.of("age", 0, FRUIT_AGE)
val MULCH: EnumProperty<MulchVariant> = EnumProperty.of("mulch", MulchVariant::class.java)
val WAS_GENERATED: BooleanProperty = BooleanProperty.of("generated")
// val PLANTED_SHAPE = VoxelShapes.union(
// VoxelShapes.cuboid(0.3125, -0.0625, 0.3125, 0.6875, 0.0, 0.6875),
// VoxelShapes.cuboid(0.375, 0.0, 0.375, 0.625, 0.0625, 0.625)
// )
val PLANTED_SHAPE = VoxelShapes.cuboid(0.0, -0.1, 0.0, 1.0, 0.25, 1.0)
val STANDARD_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 16.0, 16.0))
val STANDARD_MATURE = listOf(Box(0.0, -1.0, 0.0, 16.0, 24.0, 16.0))
val SHORT_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 12.0, 16.0))
val SHORT_MATURE = listOf(Box(0.0, -1.0, 0.0, 16.0, 16.0, 16.0))
val VOLCANO_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 6.0, 16.0))
val VOLCANO_MATURE = listOf(Box(0.0, -1.0, 0.0, 16.0, 16.0, 16.0))
val NEST_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 6.0, 16.0))
val NEST_MATURE = listOf(Box(0.0, -1.0, 0.0, 16.0, 20.0, 16.0))
val FRILL_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 8.0, 16.0))
val FRILL_MATURE = listOf(Box(0.0, -1.0, 0.0, 16.0, 14.0, 16.0))
val BLOCK_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 17.0, 16.0))
val BLOCK_MATURE = listOf(Box(0.0, -1.0, 0.0, 16.0, 24.0, 16.0))
val PYRAMID_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 17.0, 16.0))
val PYRAMID_MATURE = listOf(Box(0.0, -1.0, 0.0, 16.0, 24.0, 16.0))
val TAIL_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 16.0, 16.0))
val TAIL_MATURE = listOf(Box(0.0, 1.0, 0.0, 16.0, 24.0, 16.0))
val SWORD_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 5.0, 16.0))
val SWORD_MATURE = listOf(Box(0.0, -1.0, 0.0, 16.0, 24.0, 16.0))
val PLATFORM_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 7.0, 16.0))
val PLATFORM_MATURE = listOf(Box(0.0, -1.0, 0.0, 16.0, 22.0, 16.0))
val STAND_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 13.0, 16.0))
val STAND_MATURE = listOf(Box(0.0, -1.0, 0.0, 16.0, 24.0, 16.0))
val CONE_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 16.0, 16.0))
val CONE_MATURE = listOf(Box(0.0, -1.0, 0.0, 16.0, 23.0, 16.0))
val SQUAT_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 12.0, 16.0))
val SQUAT_MATURE = listOf(Box(0.0, -1.0, 0.0, 16.0, 22.0, 16.0))
val LANTERN_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 20.0, 16.0))
val LANTERN_MATURE = listOf(Box(0.0, -1.0, 0.0, 16.0, 24.0, 16.0))
val BOX_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 16.0, 16.0))
val BOX_MATURE = listOf(Box(0.0, -1.0, 0.0, 16.0, 24.0, 16.0))
val BLOSSOM_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 4.0, 16.0))
val BLOSSOM_MATURE = listOf(Box(0.0, -1.0, 0.0, 16.0, 6.0, 16.0))
val LILYPAD_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 11.0, 16.0))
val LILYPAD_MATURE = listOf(Box(0.0, -1.0, 0.0, 16.0, 16.0, 16.0))
val TALL_SPROUT = listOf(Box(0.0, -1.0, 0.0, 16.0, 16.0, 16.0))
val TALL_MATURE = listOf(Box(0.0, -1.0, 0.0, 16.0, 24.0, 16.0))
fun getMulch(state: BlockState): MulchVariant {
if (!state.contains(MULCH)) {
return MulchVariant.NONE
}
return state.get(MULCH)
}
fun setMulch(world: World, pos: BlockPos, state: BlockState, mulch: MulchVariant) {
world.setBlockState(pos, state.with(MULCH, mulch))
}
}
}
@@ -25,6 +25,7 @@ import net.minecraft.world.World
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
class BigRootBlock(settings: Settings) : RootBlock(settings) {
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape = AABB
override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hit: BlockHitResult): ActionResult {
@@ -50,7 +50,7 @@ class MedicinalLeekBlock(settings: Settings) : CropBlock(settings) {
// These 3 are still around for the sake of compatibility, vanilla won't trigger it but some mods might
// We implement applyGrowth & getGrowthAmount for them
override fun isFertilizable(world: WorldView?, pos: BlockPos?, state: BlockState?, isClient: Boolean): Boolean = !this.isMature(state)
override fun isFertilizable(world: WorldView?, pos: BlockPos?, state: BlockState?, isClient: Boolean): Boolean = false
override fun applyGrowth(world: World, pos: BlockPos, state: BlockState) {
world.setBlockState(pos, state.with(this.ageProperty, (this.getAge(state) + 1).coerceAtMost(this.maxAge)), NOTIFY_LISTENERS)
@@ -39,7 +39,7 @@ class MintBlock(private val mintType: MintType, settings: Settings) : CropBlock(
// DO NOT use withAge
// Explanation for these 2 beautiful copy pasta are basically that we need to keep the blockstate and that's not possible with the default impl :(
override fun randomTick(state: BlockState, world: ServerWorld, pos: BlockPos, random: Random) {
if (world.getBaseLightLevel(pos, 0) < 9 || this.isMature(state) || random.nextInt(8) != 0) {
if (world.getBaseLightLevel(pos, 0) < 9 || this.isMature(state) || random.nextInt(50) != 0) {
return
}
this.applyGrowth(world, pos, state, false)
@@ -139,4 +139,4 @@ class MintBlock(private val mintType: MintType, settings: Settings) : CropBlock(
)
}
}
}
@@ -20,14 +20,7 @@ import com.cobblemon.mod.common.util.isInBattle
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.playSoundServer
import com.cobblemon.mod.common.util.toVec3d
import net.minecraft.block.Block
import net.minecraft.block.BlockRenderType
import net.minecraft.block.BlockState
import net.minecraft.block.BlockWithEntity
import net.minecraft.block.Blocks
import net.minecraft.block.HorizontalFacingBlock
import net.minecraft.block.ShapeContext
import net.minecraft.block.Waterloggable
import net.minecraft.block.*
import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityType
import net.minecraft.entity.LivingEntity
@@ -41,11 +34,7 @@ import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.state.StateManager
import net.minecraft.state.property.BooleanProperty
import net.minecraft.state.property.EnumProperty
import net.minecraft.util.ActionResult
import net.minecraft.util.BlockMirror
import net.minecraft.util.BlockRotation
import net.minecraft.util.Hand
import net.minecraft.util.StringIdentifiable
import net.minecraft.util.*
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
@@ -54,7 +43,6 @@ import net.minecraft.util.shape.VoxelShapes
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.minecraft.world.WorldEvents
import net.minecraft.world.WorldView
class PCBlock(properties: Settings): BlockWithEntity(properties), Waterloggable {
@@ -180,6 +168,15 @@ class PCBlock(properties: Settings): BlockWithEntity(properties), Waterloggable
private fun isBase(state: BlockState): Boolean = state.get(PART) == PCPart.BOTTOM
override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity?) {
super.onBreak(world, pos, state, player)
val otherPart = world.getBlockState(getPositionOfOtherPart(state, pos))
if (otherPart.block is PCBlock) {
world.setBlockState(getPositionOfOtherPart(state, pos), Blocks.AIR.defaultState, 35)
world.syncWorldEvent(player, 2001, getPositionOfOtherPart(state, pos), Block.getRawIdFromState(otherPart))
}
}
override fun onPlaced(world: World, pos: BlockPos, state: BlockState, placer: LivingEntity?, itemStack: ItemStack?) {
world.setBlockState(pos.up(), state
.with(PART, PCPart.TOP)
@@ -208,20 +205,6 @@ class PCBlock(properties: Settings): BlockWithEntity(properties), Waterloggable
return if (state.get(PART) == PCPart.BOTTOM) blockState.isSideSolidFullSquare(world, blockPos, Direction.UP) else blockState.isOf(this)
}
override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity?) {
if (!world.isClient && player?.isCreative == true) {
var blockPos: BlockPos = BlockPos.ORIGIN
var blockState: BlockState = state
val part = state.get(PART)
if (part == PCPart.TOP && world.getBlockState(pos.down().also { blockPos = it }).also { blockState = it }.isOf(state.block) && blockState.get(PART) == PCPart.BOTTOM) {
val blockState2 = if (blockState.fluidState.isOf(Fluids.WATER)) Blocks.WATER.defaultState else Blocks.AIR.defaultState
world.setBlockState(blockPos, blockState2, NOTIFY_ALL or SKIP_DROPS)
world.syncWorldEvent(player, WorldEvents.BLOCK_BROKEN, blockPos, getRawIdFromState(blockState))
}
}
super.onBreak(world, pos, state, player)
}
@Deprecated("Deprecated in Java")
override fun canPathfindThrough(blockState: BlockState, blockGetter: BlockView, blockPos: BlockPos, pathComputationType: NavigationType) = false
@@ -298,24 +281,15 @@ class PCBlock(properties: Settings): BlockWithEntity(properties), Waterloggable
override fun getStateForNeighborUpdate(
state: BlockState,
direction: Direction,
neighborState: BlockState,
direction: Direction?,
neighborState: BlockState?,
world: WorldAccess,
pos: BlockPos,
neighborPos: BlockPos
): BlockState {
pos: BlockPos?,
neighborPos: BlockPos?
): BlockState? {
if (state.get(WATERLOGGED)) {
world.scheduleFluidTick(pos, Fluids.WATER, Fluids.WATER.getTickRate(world))
}
val isPC = neighborState.isOf(this)
val part = state.get(PART)
if (!isPC && part == PCPart.TOP && neighborPos == pos.down()) {
return Blocks.AIR.defaultState
} else if (!isPC && part == PCPart.BOTTOM && neighborPos == pos.up()) {
return Blocks.AIR.defaultState
}
return state
return super.getStateForNeighborUpdate(state, direction, neighborState, world, pos, neighborPos)
}
}
@@ -21,48 +21,39 @@ import com.cobblemon.mod.common.util.isInBattle
import com.cobblemon.mod.common.util.playSoundServer
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.voxelShape
import java.util.UUID
import net.minecraft.block.Block
import net.minecraft.block.BlockRenderType
import net.minecraft.block.BlockState
import net.minecraft.block.BlockWithEntity
import net.minecraft.block.Blocks
import net.minecraft.block.HorizontalFacingBlock
import net.minecraft.block.ShapeContext
import net.minecraft.block.Waterloggable
import net.minecraft.block.*
import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityTicker
import net.minecraft.block.entity.BlockEntityType
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.ai.pathing.NavigationType
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.fluid.FluidState
import net.minecraft.fluid.Fluids
import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemStack
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.server.world.ServerWorld
import net.minecraft.state.StateManager
import net.minecraft.state.property.BooleanProperty
import net.minecraft.state.property.EnumProperty
import net.minecraft.util.ActionResult
import net.minecraft.util.BlockMirror
import net.minecraft.util.BlockRotation
import net.minecraft.util.Hand
import net.minecraft.util.StringIdentifiable
import net.minecraft.util.*
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.util.shape.VoxelShape
import net.minecraft.util.shape.VoxelShapes
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.minecraft.world.WorldEvents
import net.minecraft.world.WorldView
import java.util.*
import net.minecraft.entity.LivingEntity
import net.minecraft.fluid.FluidState
import net.minecraft.fluid.Fluids
import net.minecraft.item.ItemStack
import net.minecraft.server.world.ServerWorld
import net.minecraft.state.property.BooleanProperty
import net.minecraft.state.property.EnumProperty
import net.minecraft.util.BlockMirror
import net.minecraft.util.BlockRotation
import net.minecraft.util.StringIdentifiable
import net.minecraft.util.shape.VoxelShape
import net.minecraft.util.shape.VoxelShapes
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
class PastureBlock(properties: Settings): BlockWithEntity(properties), Waterloggable, PreEmptsExplosion {
class PastureBlock(properties: Settings): BlockWithEntity(properties), Waterloggable {
companion object {
val PART: EnumProperty<PasturePart> = EnumProperty.of("part", PasturePart::class.java)
val ON: BooleanProperty = BooleanProperty.of("on")
@@ -154,7 +145,7 @@ class PastureBlock(properties: Settings): BlockWithEntity(properties), Waterlogg
}
fun getPositionOfOtherPart(state: BlockState, pos: BlockPos): BlockPos {
return if (state.contains(PART) && state.get(PART) == PasturePart.BOTTOM) {
return if (state.get(PART) == PasturePart.BOTTOM) {
pos.up()
} else {
pos.down()
@@ -169,7 +160,7 @@ class PastureBlock(properties: Settings): BlockWithEntity(properties), Waterlogg
}
}
private fun isBase(state: BlockState): Boolean = state.contains(PART) && state.get(PART) == PasturePart.BOTTOM
private fun isBase(state: BlockState): Boolean = state.get(PART) == PasturePart.BOTTOM
override fun canPathfindThrough(blockState: BlockState, blockGetter: BlockView, blockPos: BlockPos, pathComputationType: NavigationType) = false
@@ -180,34 +171,18 @@ class PastureBlock(properties: Settings): BlockWithEntity(properties), Waterlogg
builder.add(WATERLOGGED)
}
fun checkBreakEntity(world: WorldAccess, state: BlockState, pos: BlockPos) {
if (state.get(PART) == PasturePart.TOP) {
return
}
val blockEntity = world.getBlockEntity(pos)
if (blockEntity is PokemonPastureBlockEntity) {
blockEntity.onBroken()
}
}
override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity?) {
checkBreakEntity(world, state, pos)
if (!world.isClient && player?.isCreative == true) {
var blockPos: BlockPos = BlockPos.ORIGIN
var blockState: BlockState = state
val part = state.get(PART)
if (part == PasturePart.TOP && world.getBlockState(pos.down().also { blockPos = it }).also { blockState = it }.isOf(state.block) && blockState.get(PART) == PasturePart.BOTTOM) {
checkBreakEntity(world, blockState, blockPos)
val blockState2 = if (blockState.fluidState.isOf(Fluids.WATER)) Blocks.WATER.defaultState else Blocks.AIR.defaultState
world.setBlockState(blockPos, blockState2, NOTIFY_ALL or SKIP_DROPS)
world.syncWorldEvent(player, WorldEvents.BLOCK_BROKEN, blockPos, getRawIdFromState(blockState))
}
}
super.onBreak(world, pos, state, player)
val otherPart = world.getBlockState(getPositionOfOtherPart(state, pos))
if (otherPart.block is PastureBlock) {
world.setBlockState(getPositionOfOtherPart(state, pos), Blocks.AIR.defaultState, 35)
world.syncWorldEvent(player, 2001, getPositionOfOtherPart(state, pos), Block.getRawIdFromState(otherPart))
}
}
override fun whenExploded(world: World, state: BlockState, pos: BlockPos) {
override fun onBroken(world: WorldAccess, pos: BlockPos, state: BlockState) {
val blockEntity = world.getBlockEntity(pos) as? PokemonPastureBlockEntity ?: return
super.onBroken(world, pos, state)
blockEntity.onBroken()
}
@@ -318,25 +293,16 @@ class PastureBlock(properties: Settings): BlockWithEntity(properties), Waterlogg
override fun getStateForNeighborUpdate(
state: BlockState,
direction: Direction,
neighborState: BlockState,
direction: Direction?,
neighborState: BlockState?,
world: WorldAccess,
pos: BlockPos,
neighborPos: BlockPos
): BlockState {
pos: BlockPos?,
neighborPos: BlockPos?
): BlockState? {
if (state.get(WATERLOGGED)) {
world.scheduleFluidTick(pos, Fluids.WATER, Fluids.WATER.getTickRate(world))
}
val isPasture = neighborState.isOf(this)
val part = state.get(PART)
if (!isPasture && part == PasturePart.TOP && neighborPos == pos.down()) {
return Blocks.AIR.defaultState
} else if (!isPasture && part == PasturePart.BOTTOM && neighborPos == pos.up()) {
checkBreakEntity(world, state, pos)
return Blocks.AIR.defaultState
}
return state
return super.getStateForNeighborUpdate(state, direction, neighborState, world, pos, neighborPos)
}
}
@@ -1,17 +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.block
import net.minecraft.block.BlockState
import net.minecraft.util.math.BlockPos
import net.minecraft.world.World
interface PreEmptsExplosion {
fun whenExploded(world: World, state: BlockState, pos: BlockPos)
}
@@ -13,7 +13,6 @@ import com.cobblemon.mod.common.api.mulch.MulchVariant
import com.cobblemon.mod.common.api.mulch.Mulchable
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.block.CropBlock
import net.minecraft.block.ShapeContext
import net.minecraft.item.ItemConvertible
@@ -51,9 +50,8 @@ class RevivalHerbBlock(settings: Settings) : CropBlock(settings), Mulchable {
override fun canPlaceAt(state: BlockState, world: WorldView, pos: BlockPos): Boolean {
val floor = world.getBlockState(pos.down())
val block = world.getBlockState(pos)
// A bit of a copy pasta but we don't have access to the BlockState being attempted to be placed above on the canPlantOnTop
return (block.isIn(BlockTags.REPLACEABLE_BY_TREES) || block.isOf(Blocks.AIR) || block.isOf(this)) && ((state.get(IS_WILD) && floor.isIn(BlockTags.DIRT)) || this.canPlantOnTop(floor, world, pos))
return (world.getBlockState(pos).isIn(BlockTags.REPLACEABLE_BY_TREES)) && ((state.get(IS_WILD) && floor.isIn(BlockTags.DIRT)) || this.canPlantOnTop(floor, world, pos))
}
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape = AGE_SHAPES.getOrNull(this.getAge(state)) ?: VoxelShapes.fullCube()
@@ -65,7 +63,7 @@ class RevivalHerbBlock(settings: Settings) : CropBlock(settings), Mulchable {
override fun applyMulch(world: ServerWorld, random: Random, pos: BlockPos, state: BlockState, variant: MulchVariant) {
val picked = Mutation.values().filterNot { it == Mutation.NONE }.random()
world.setBlockState(pos, state.with(MUTATION, picked))//.with(AGE, MUTABLE_MAX_AGE + 1))
world.setBlockState(pos, state.with(MUTATION, picked).with(AGE, MUTABLE_MAX_AGE + 1))
}
/**
@@ -135,7 +133,6 @@ class RevivalHerbBlock(settings: Settings) : CropBlock(settings), Mulchable {
val AGE = IntProperty.of("age", MIN_AGE, MAX_AGE)
val IS_WILD = BooleanProperty.of("is_wild")
val MUTATION = EnumProperty.of("mutation", Mutation::class.java)
// val MULCH = EnumProperty.of("mulch", MulchVariant::class.java)
/**
* The [VoxelShape] equivalent to a certain age.
@@ -13,11 +13,7 @@ import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.world.BigRootPropagatedEvent
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import net.minecraft.block.Block
import net.minecraft.block.BlockRenderType
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.block.Fertilizable
import net.minecraft.block.*
import net.minecraft.item.ItemStack
import net.minecraft.server.world.ServerWorld
import net.minecraft.sound.SoundCategory
@@ -32,31 +28,18 @@ import net.minecraft.world.event.GameEvent
@Suppress("OVERRIDE_DEPRECATION", "UNUSED_PARAMETER", "MemberVisibilityCanBePrivate", "DEPRECATION")
abstract class RootBlock(settings: Settings) : Block(settings), Fertilizable, ShearableBlock {
private val possibleDirections = setOf(Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST)
override fun hasRandomTicks(state: BlockState) = true
override fun randomTick(state: BlockState, world: ServerWorld, pos: BlockPos, random: Random) {
// Check for propagation
if (random.nextDouble() < Cobblemon.config.bigRootPropagationChance && world.getLightLevel(pos) < MAX_PROPAGATING_LIGHT_LEVEL && !hasReachedSpreadCap(world, pos)) {
if (random.nextDouble() < Cobblemon.config.bigRootPropagationChance && world.getLightLevel(pos) < MAX_PROPAGATING_LIGHT_LEVEL) {
this.spreadFrom(world, pos, random)
}
}
fun hasReachedSpreadCap(world: World, pos: BlockPos): Boolean {
var nearby = 0
val nearbyPositions = BlockPos.iterate(pos.add(-4, -1, -4), pos.add(4, 1, 4)).iterator()
while (nearbyPositions.hasNext()) {
val blockPos = nearbyPositions.next()
if (world.getBlockState(blockPos).isIn(CobblemonBlockTags.ROOTS)) {
nearby++
if (nearby >= Cobblemon.config.maxRootsInArea) {
return true
}
}
}
return false
}
override fun canPlaceAt(state: BlockState, world: WorldView, pos: BlockPos): Boolean = this.canGoOn(state, world, pos) { true }
override fun getStateForNeighborUpdate(
@@ -185,6 +168,9 @@ abstract class RootBlock(settings: Settings) : Block(settings), Fertilizable, Sh
protected abstract fun shearedDrop(): ItemStack
companion object {
const val MAX_PROPAGATING_LIGHT_LEVEL = 11
}
}
}
@@ -16,8 +16,6 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.berry.BerryHarvestEvent
import com.cobblemon.mod.common.api.mulch.MulchVariant
import com.cobblemon.mod.common.block.BerryBlock
import com.cobblemon.mod.common.block.BerryBlock.Companion.getMulch
import com.cobblemon.mod.common.block.BerryBlock.Companion.setMulch
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.entity.BlockEntity
@@ -56,6 +54,9 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
//The time left for the tree to grow to the next stage
var stageTimer: Int = growthTimer / BerryBlock.MATURE_AGE
set(value) {
if (value < 0) {
throw IllegalArgumentException("You cannot set the stage growth time to less than zero")
}
if (field != value) {
this.markDirty()
}
@@ -78,26 +79,16 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
private var growthPointSequence = "0123456789ABCDEF"
// Just a cheat to not invoke markDirty unnecessarily
private var wasLoading = false
var mulchVariant: MulchVariant? = null
var mulchDuration = 0
set(value) {
if (value == 0 && !(mulchVariant?.isBiomeMulch ?: false)) {
mulchVariant = null
}
field = value
markDirty()
this.markDirty()
}
fun decrementMulchDuration(world: World, pos: BlockPos, state: BlockState) {
val currentMulch = getMulch(state)
if (currentMulch == MulchVariant.NONE || currentMulch.duration == -1) {
return
}
val newDuration = mulchDuration - 1
mulchDuration = if (newDuration <= 0) {
setMulch(world, pos, state, MulchVariant.NONE)
0
} else {
newDuration
}
}
constructor(pos: BlockPos, state: BlockState, berryIdentifier: Identifier): this(pos, state) {
this.berryIdentifier = berryIdentifier
resetGrowTimers(pos, state)
@@ -108,18 +99,20 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
fun resetGrowTimers(pos: BlockPos, state: BlockState) {
val curAge = state.get(BerryBlock.AGE)
if (curAge == 5) {
return
if (curAge != 5) {
val berry = Berries.getByIdentifier(berryIdentifier)!!
var multiplier = 10
if (mulchVariant == MulchVariant.GROWTH) {
mulchDuration--
multiplier = 15
}
val upperGrowthLimit = ((if (curAge == 0) berry.growthTime.last else berry.refreshRate.last) * multiplier) / 10
val lowerGrowthLimit = ((if (curAge == 0) berry.growthTime.first else berry.refreshRate.first) * multiplier) / 10
val growthRange = lowerGrowthLimit..upperGrowthLimit
growthTimer = growthRange.random() * ticksPerMinute
goToNextStageTimer(BerryBlock.FRUIT_AGE - curAge)
}
val multiplier = 14
val berry = Berries.getByIdentifier(berryIdentifier)!!
val lowerGrowthLimit = (if (curAge == 0) berry.growthTime.first else berry.refreshRate.first) * multiplier / 10
val upperGrowthLimit = (if (curAge == 0) berry.growthTime.last else berry.refreshRate.last) * multiplier / 10
val growthRange = lowerGrowthLimit..upperGrowthLimit
this.growthTimer = this.applyMulchModifier(pos, growthRange.random() * ticksPerMinute)
this.goToNextStageTimer(BerryBlock.FRUIT_AGE - curAge)
}
fun goToNextStageTimer(stagesLeft: Int) {
@@ -129,37 +122,6 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
growthTimer -= stageTimer
}
/**
* Used to apply an effect to the growth timer based on the mulch type.
* Currently only used for growth mulch.
* @param pos The position of the block
* @param timer Timer to be modified
* @return The modified timer
*/
private fun applyMulchModifier(pos: BlockPos, timer: Int): Int {
val state = world?.getBlockState(pos) ?: return timer
val curAge = state.get(BerryBlock.AGE)
if (curAge == 5) {
return timer
}
if (getMulch(state) != MulchVariant.GROWTH) {
return timer
}
return (timer * 0.66).toInt()
}
/**
* Used to refresh the timers when a new mulch is applied.
* @param pos The position of the block
*/
fun refreshTimers(pos: BlockPos) {
this.growthTimer = this.applyMulchModifier(pos, growthTimer)
this.stageTimer = this.applyMulchModifier(pos, stageTimer)
}
/**
* Returns the [Berry] behind this entity,
* This will be null if it doesn't exist in the [Berries] registry.
@@ -193,7 +155,6 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
repeat(numBerries) {
growthPoints.add(berryIdentifier)
}
this.growthPointSequence = this.growthPointSequence.toCharArray().also { if (berry()?.randomizedGrowthPoints != false) it.shuffle() }.concatToString()
}
/**
@@ -242,6 +203,11 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
this.growthPoints += identifier
} catch (ignored: InvalidIdentifierException) {}
}
this.mulchVariant = if (nbt.contains(MULCH_VARIANT)) {
MulchVariant.valueOf(nbt.getString(MULCH_VARIANT))
} else {
null
}
this.mulchDuration = nbt.getInt(MULCH_DURATION)
this.wasLoading = false
if (nbt.contains(GROWTH_POINTS_SEQUENCE)) {
@@ -256,6 +222,9 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
list += this.growthPoints.map { NbtString.of(it.toString()) }
nbt.put(GROWTH_POINTS, list)
nbt.putString(BERRY, berryIdentifier.toString())
if (mulchVariant != null) {
nbt.putString(MULCH_VARIANT, mulchVariant.toString())
}
nbt.putInt(MULCH_DURATION, mulchDuration)
nbt.putString(GROWTH_POINTS_SEQUENCE, growthPointSequence)
}
@@ -309,6 +278,7 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
val newState = state.with(BerryBlock.AGE, 3)
world.setBlockState(pos, newState, Block.NOTIFY_LISTENERS)
world.emitGameEvent(player, GameEvent.BLOCK_CHANGE, pos)
this.generateGrowthPoints(world, newState, pos, player)
resetGrowTimers(pos, newState)
return
}
@@ -316,7 +286,7 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
companion object {
internal val TICKER = BlockEntityTicker<BerryBlockEntity> { world, pos, state, blockEntity ->
if (world.isClient) return@BlockEntityTicker
if (blockEntity.stageTimer >= 0) {
if (blockEntity.stageTimer > 0) {
blockEntity.stageTimer--
}
if (blockEntity.stageTimer == 0) {
@@ -329,6 +299,7 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
private const val GROWTH_TIMER = "GrowthTimer"
private const val STAGE_TIMER = "StageTimer"
private const val BERRY = "Berry"
private const val MULCH_VARIANT = "MulchVariant"
private const val MULCH_DURATION = "MulchDuration"
}
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.advancement.CobblemonCriteria
import com.cobblemon.mod.common.api.pasture.PastureLinkManager
import com.cobblemon.mod.common.api.scheduling.afterOnMain
import com.cobblemon.mod.common.api.text.red
@@ -26,6 +25,8 @@ import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.toVec3d
import java.util.UUID
import kotlin.math.ceil
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.block.entity.BlockEntity
@@ -45,8 +46,6 @@ import net.minecraft.util.math.Box
import net.minecraft.util.math.Direction
import net.minecraft.util.math.Vec3i
import net.minecraft.world.World
import java.util.*
import kotlin.math.ceil
class PokemonPastureBlockEntity(pos: BlockPos, val state: BlockState) : BlockEntity(CobblemonBlockEntities.PASTURE, pos, state) {
open class Tethering(
@@ -160,7 +159,6 @@ class PokemonPastureBlockEntity(pos: BlockPos, val state: BlockState) : BlockEnt
entity.tethering = tethering
tethering.toDTO(player)?.let { player.sendPacket(PokemonPasturedPacket(it)) }
markDirty()
CobblemonCriteria.PASTURE_USE.trigger(player, pokemon)
return true
} else {
Cobblemon.LOGGER.warn("Couldn't spawn pastured Pokémon for some reason")
@@ -200,6 +198,12 @@ class PokemonPastureBlockEntity(pos: BlockPos, val state: BlockState) : BlockEnt
}
}
override fun markRemoved() {
super.markRemoved()
onBroken()
}
fun isSafeFloor(world: World, pos: BlockPos, entity: PokemonEntity): Boolean {
val state = world.getBlockState(pos)
return if (state.isAir) {
@@ -6,19 +6,15 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render
package com.cobblemon.mod.common.client
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.texture.SpriteAtlasHolder
import net.minecraft.client.texture.TextureManager
import net.minecraft.util.Identifier
class CobblemonAtlas(
textureManager: TextureManager,
atlasId: Identifier,
sourcePath: Identifier
) : SpriteAtlasHolder(
class CobblemonBerryAtlas(textureManager: TextureManager) : SpriteAtlasHolder(
textureManager,
atlasId,
sourcePath
cobblemonResource("textures/atlas/berries.png"),
cobblemonResource("berries")
) {
}
@@ -15,8 +15,8 @@ import com.cobblemon.mod.common.api.text.gray
import com.cobblemon.mod.common.client.battle.ClientBattle
import com.cobblemon.mod.common.client.gui.PartyOverlay
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.particle.BedrockParticleEffectRepository
import com.cobblemon.mod.common.client.render.block.BerryBlockRenderer
import com.cobblemon.mod.common.client.particle.BedrockParticleEffectRepository
import com.cobblemon.mod.common.client.render.block.HealingMachineRenderer
import com.cobblemon.mod.common.client.render.item.CobblemonBuiltinItemRendererRegistry
import com.cobblemon.mod.common.client.render.item.PokemonItemRenderer
@@ -31,10 +31,16 @@ import com.cobblemon.mod.common.client.storage.ClientStorageManager
import com.cobblemon.mod.common.client.trade.ClientTrade
import com.cobblemon.mod.common.data.CobblemonDataProvider
import com.cobblemon.mod.common.item.PokeBallItem
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import com.cobblemon.mod.common.client.render.models.blockbench.repository.BerryModelRepository
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.asTranslated
import java.awt.Color
import net.minecraft.client.color.block.BlockColorProvider
import net.minecraft.client.color.block.BlockColors
import net.minecraft.client.color.item.ItemColorProvider
import net.minecraft.client.color.world.BiomeColors
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.entity.EntityRenderer
@@ -87,8 +93,7 @@ object CobblemonClient {
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.BERRY, ::BerryBlockRenderer)
registerBlockRenderTypes()
//registerColors()
registerFlywheelRenderers()
registerColors()
LOGGER.info("Registering custom BuiltinItemRenderers")
CobblemonBuiltinItemRendererRegistry.register(CobblemonItems.POKEMON_MODEL, PokemonItemRenderer())
@@ -116,15 +121,6 @@ object CobblemonClient {
}
}
fun registerFlywheelRenderers() {
// InstancedRenderRegistry
// .configure(CobblemonBlockEntities.BERRY)
// .alwaysSkipRender()
// .factory(::BerryEntityInstance)
// .apply()
}
/*
fun registerColors() {
this.implementation.registerBlockColors(BlockColorProvider { _, _, _, _ ->
return@BlockColorProvider 0xE0A33A
@@ -133,7 +129,6 @@ object CobblemonClient {
return@ItemColorProvider 0xE0A33A
}, CobblemonItems.APRICORN_LEAVES)
}
*/
private fun registerBlockRenderTypes() {
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
import java.util.UUID
class ClientBattle(
val battleId: UUID,
val battleFormat: BattleFormat
@@ -51,8 +50,4 @@ class ClientBattle(
?: throw IllegalStateException("Invalid pnx: $pnx - unknown pokemon")
return actor to pokemon
}
fun getParticipatingActor(uuid: UUID): ClientBattleActor? {
return sides.flatMap { it.actors }.find { it.uuid == uuid }
}
}
@@ -27,7 +27,6 @@ import net.minecraft.text.MutableText
* @property properties
* @property aspects
* @property hpValue The current value of the HP.
* @property maxHp The maximum value of HP.
* @property isHpFlat If this is a flat value, this will be true if the client is the player controlling the Pokémon or is an ally of the controller.
* @property status
* @property statChanges
@@ -38,7 +37,6 @@ class ClientBattlePokemon(
var properties: PokemonProperties,
var aspects: Set<String>,
var hpValue: Float,
var maxHp: Float,
var isHpFlat: Boolean,
var status: PersistentStatus?,
var statChanges: MutableMap<Stat, Int>
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.api.scheduling.after
import com.cobblemon.mod.common.api.scheduling.lerp
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.additives.EarBounceAdditive
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatefulAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPoseableModel
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -47,9 +46,6 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
private val minimumFallSpeed = -0.1F
private val intensityVelocityCap = -0.5F
private var cryAnimation: StatefulAnimation<PokemonEntity, *>? = null
override fun changePokemon(pokemon: Pokemon) {
pokemon.isClient = true
currentEntity.subscriptions.add(currentEntity.species.subscribeIncludingCurrent {
@@ -152,13 +148,8 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
fun cry() {
val model = currentModel ?: return
if (model is PokemonPoseableModel) {
if (cryAnimation != null && cryAnimation in statefulAnimations) {
return
}
val animation = model.cryAnimation(currentEntity, this) ?: return
statefulAnimations.add(animation)
cryAnimation = animation
}
}
}
@@ -8,10 +8,8 @@
package com.cobblemon.mod.common.client.gui
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.PokemonModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.RenderablePokemon
@@ -52,11 +50,7 @@ fun drawProfilePokemon(
scale: Float = 20F
) {
val model = PokemonModelRepository.getPoser(species, aspects)
val texture = PokemonModelRepository.getTexture(species, aspects, state?.animationSeconds ?: 0F)
val context = RenderContext()
PokemonModelRepository.getTextureNoSubstitute(species, aspects, 0f).let { it -> context.put(RenderContext.TEXTURE, it) }
context.put(RenderContext.SCALE, PokemonSpecies.getByIdentifier(species)!!.getForm(aspects).baseScale)
val texture = PokemonModelRepository.getTexture(species, aspects, state)
val renderType = model.getLayer(texture)
@@ -89,10 +83,10 @@ fun drawProfilePokemon(
val packedLight = LightmapTextureManager.pack(11, 7)
model.withLayerContext(bufferSource, state, PokemonModelRepository.getLayers(species, aspects)) {
model.render(context, matrixStack, buffer, packedLight, OverlayTexture.DEFAULT_UV, 1F, 1F, 1F, 1F)
model.render(matrixStack, buffer, packedLight, OverlayTexture.DEFAULT_UV, 1F, 1F, 1F, 1F)
bufferSource.draw()
}
model.setDefault()
entityRenderDispatcher.setRenderShadows(true)
DiffuseLighting.enableGuiDepthLighting()
}
@@ -173,7 +173,7 @@ class BattleOverlay : InGameHud(MinecraftClient.getInstance(), MinecraftClient.g
colour = Triple(r, g, b),
opacity = opacity.toFloat(),
ballState = activeBattlePokemon.ballCapturing,
maxHealth = battlePokemon.maxHp.toInt(),
maxHealth = truePokemon?.hp ?: 0,
health = battlePokemon.hpValue,
isFlatHealth = battlePokemon.isHpFlat
)
@@ -387,7 +387,7 @@ class BattleOverlay : InGameHud(MinecraftClient.getInstance(), MinecraftClient.g
reversed: Boolean = false
) {
val model = PokeBallModelRepository.getPoser(state.pokeBall.name, state.aspects)
val texture = PokeBallModelRepository.getTexture(state.pokeBall.name, state.aspects, state.animationSeconds)
val texture = PokeBallModelRepository.getTexture(state.pokeBall.name, state.aspects, state)
val renderType = model.getLayer(texture)
RenderSystem.applyModelViewMatrix()
@@ -1,213 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.battle.subscreen
import com.cobblemon.mod.common.api.data.ShowdownIdentifiable
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.battles.*
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.toRGB
import net.minecraft.client.MinecraftClient
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.sound.SoundEvents
/**
* Button for toggling gimmicks during a battle.
*
* @param gimmick The [ShowdownMoveset.Gimmick] tied to this button.
* @property tiles The [GimmickTile]s to render when this button is [toggled].
*
* @author Segfault Guy
* @since July 8th, 2023
*/
abstract class BattleGimmickButton(gimmick: ShowdownMoveset.Gimmick, val x: Float, val y: Float) {
companion object {
const val WIDTH = 36
const val HEIGHT = 34
const val SCALE = 0.5F
const val XOFF = WIDTH * SCALE
const val YOFF = HEIGHT * SCALE
const val SPACING = 26
/** Factory for creating an instance of [BattleGimmickButton] based on [ShowdownMoveset.Gimmick]. */
fun create(gimmick: ShowdownMoveset.Gimmick, moveSelection: BattleMoveSelection, x: Float, y: Float): BattleGimmickButton {
return when(gimmick) {
ShowdownMoveset.Gimmick.Z_POWER, ShowdownMoveset.Gimmick.ULTRA_BURST ->
ZPowerButton(moveSelection, x, y)
ShowdownMoveset.Gimmick.DYNAMAX ->
DynamaxButton(moveSelection, x, y)
else ->
object: BattleGimmickButton(gimmick, x, y) {
override var tiles: List<BattleMoveSelection.MoveTile> = moveSelection.baseTiles.map { tile ->
GimmickTile(gimmick, moveSelection, tile.move, tile.x, tile.y)
}
}
}
}
}
abstract val tiles: List<BattleMoveSelection.MoveTile>
var toggled = false
private val sfx = PositionedSoundInstance.master(SoundEvents.BLOCK_ANVIL_LAND, 1.0F)
private val texture = gimmick.id
fun render(matrices: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
blitk(
matrixStack = matrices,
texture = cobblemonResource("textures/gui/battle/battle_gimmick_${texture}.png"),
x = x * 2,
y = y * 2,
height = HEIGHT,
width = WIDTH,
vOffset = if (toggled || isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
textureHeight = HEIGHT * 2,
scale = SCALE
)
}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX >= x && mouseX <= x + XOFF && mouseY >= y && mouseY <= y + YOFF
fun toggle(): Boolean {
toggled = !toggled
MinecraftClient.getInstance().soundManager.play(sfx)
return toggled
}
/**
* Tile for an [InBattleMove] when a [BattleGimmickButton] is toggled. Triggers a gimmick when executed.
*
* @param moveSelection The [BattleMoveSelection] subscreen this tile is rendered on.
* @param move The [InBattleMove] this tile is rendered for. May or may not have an associated [InBattleGimmickMove].
* @property gimmick The [ShowdownMoveset.Gimmick] that is triggered.
*
* @author Segfault Guy
* @since July 15th, 2023
*/
open class GimmickTile(
private val gimmick: ShowdownMoveset.Gimmick,
moveSelection: BattleMoveSelection,
move: InBattleMove,
x: Float,
y: Float
) : BattleMoveSelection.MoveTile(moveSelection, move, x, y) {
// if there isn't a compatible gimmick for this move, the rendered moveTemplate will default to the base template
init {
gimmickMoveTemplate?.let {
moveTemplate = it
rgb = it.elementalType.hue.toRGB()
}
}
protected val gimmickMove = move.gimmickMove
// showdown already translates the base move id to the gimmick variant
override val response: MoveActionResponse
get() = MoveActionResponse(move.id, targetPnx, gimmick.id)
override val targetList: List<Targetable>?
get() = if (gimmickMove != null) gimmickMove.target.targetList(moveSelection.request.activePokemon) else super.targetList
override val selectable: Boolean
get() = if (gimmickMove != null) !gimmickMove.disabled else super.selectable
/**
* Couple reasons for making a unique template for gimmick moves:
* 1. Z versions of status moves aren't registered as distinct moves, so can't get a template by name or num
* 2. Damaging moves are registered as physical and it's confusing since the actual damageCategory is inherited from the base move
*/
private val gimmickMoveTemplate: MoveTemplate? get() {
val gimmickMoveID = move.gimmickMove?.move?.lowercase()?.replace(ShowdownIdentifiable.REGEX, "") ?: return null
val gimmickTemplate = Moves.getByName(gimmickMoveID)
return MoveTemplate(
name = gimmickMoveID,
num = gimmickTemplate?.num ?: -1,
elementalType = gimmickTemplate?.elementalType ?: moveTemplate.elementalType,
damageCategory = moveTemplate.damageCategory,
power = gimmickTemplate?.power ?: moveTemplate.power,
target = gimmickTemplate?.target ?: moveTemplate.target,
accuracy = gimmickTemplate?.accuracy ?: moveTemplate.accuracy,
pp = gimmickTemplate?.pp ?: moveTemplate.pp,
priority = gimmickTemplate?.priority ?: moveTemplate.priority,
critRatio = gimmickTemplate?.critRatio ?: moveTemplate.critRatio,
effectChances = gimmickTemplate?.effectChances ?: moveTemplate.effectChances
)
}
}
}
/**
* Button for toggling Z-Power variations of compatible moves.
*
* @property tiles The Z-Moves for this moveset.
*
* @author Segfault Guy
* @since July 15th, 2023
*/
class ZPowerButton(moveSelection: BattleMoveSelection, x: Float, y: Float) : BattleGimmickButton(ShowdownMoveset.Gimmick.Z_POWER, x, y) {
override var tiles: List<BattleMoveSelection.MoveTile> = moveSelection.baseTiles.map { tile ->
ZPowerTile(moveSelection, tile.move, tile.x, tile.y)
}
/**
* Tile for Z-Power variation of a move.
*
* @param move The base move.
* @property gimmickMove The respective Z-Move (if it exists).
*
* @author Segfault Guy
* @since July 16th, 2023
*/
class ZPowerTile(
moveSelection: BattleMoveSelection,
move: InBattleMove,
x: Float,
y: Float
) : GimmickTile(ShowdownMoveset.Gimmick.Z_POWER, moveSelection, move, x, y) {
override val selectable: Boolean
get() = gimmickMove != null && !gimmickMove.disabled
}
}
/**
* Button for toggling Max variations of moves.
*
* @property tiles The Max Moves for this moveset.
*
* @author Segfault Guy
* @since July 27th, 2023
*/
class DynamaxButton(moveSelection: BattleMoveSelection, x: Float, y: Float) : BattleGimmickButton(ShowdownMoveset.Gimmick.DYNAMAX, x, y) {
override var tiles: List<BattleMoveSelection.MoveTile> = moveSelection.baseTiles.map { tile ->
DynamaxTile(moveSelection, tile.move, tile.x, tile.y)
}
/**
* Tile for Max variation of a move.
*
* @param move The base move.
* @property gimmickMove The respective Max Move.
*
* @author Segfault Guy
* @since July 27th, 2023
*/
class DynamaxTile(
moveSelection: BattleMoveSelection,
move: InBattleMove,
x: Float,
y: Float
) : GimmickTile(ShowdownMoveset.Gimmick.DYNAMAX, moveSelection, move, x, y) {
override val selectable: Boolean
get() = gimmickMove != null && !gimmickMove.disabled
}
}
@@ -17,7 +17,6 @@ import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.InBattleMove
import com.cobblemon.mod.common.battles.MoveActionResponse
import com.cobblemon.mod.common.battles.Targetable
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
@@ -31,6 +30,7 @@ import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
import net.minecraft.util.math.MathHelper.floor
@@ -57,49 +57,28 @@ class BattleMoveSelection(
}
val moveSet = request.moveSet!!
val baseTiles = moveSet.moves.mapIndexed { index, inBattleMove ->
val moveTiles = moveSet.moves.mapIndexed { index, inBattleMove ->
val isEven = index % 2 == 0
val x = if (isEven) this.x.toFloat() else this.x + MOVE_HORIZONTAL_SPACING + MOVE_WIDTH
val y = if (index > 1) this.y + MOVE_HEIGHT + MOVE_VERTICAL_SPACING else this.y.toFloat()
// if already dynamaxed, base tiles are the gimmick tiles
if (moveSet.hasActiveGimmick())
DynamaxButton.DynamaxTile(this, inBattleMove, x, y)
else
MoveTile(this, inBattleMove, x, y)
MoveTile(this, inBattleMove, x, y)
}
var moveTiles = baseTiles
val backButton = BattleBackButton(x - 3F, MinecraftClient.getInstance().window.scaledHeight - 22F)
val gimmickButtons = moveSet.getGimmicks().mapIndexed { index, gimmick ->
val initOff = BattleBackButton.WIDTH * 0.65F
val xOff = initOff + BattleGimmickButton.SPACING * index
BattleGimmickButton.create(gimmick, this, backButton.x + xOff, backButton.y)
}
open class MoveTile(
class MoveTile(
val moveSelection: BattleMoveSelection,
val move: InBattleMove,
val x: Float,
val y: Float,
val y: Float
) {
var moveTemplate = Moves.getByNameOrDummy(move.id)
var rgb = moveTemplate.elementalType.hue.toRGB()
open val targetList: List<Targetable>? get() = move.target.targetList(moveSelection.request.activePokemon)
open val response: MoveActionResponse get() = MoveActionResponse(move.id, targetPnx)
open val selectable: Boolean get() = !move.disabled
val targetPnx: String? get() = targetList?.let { targets ->
return@let when {
targets.isEmpty() -> null
targets.size == 1 -> targets[0].getPNX()
else -> null // TODO: multi-battles
}
}
val moveTemplate = Moves.getByNameOrDummy(move.id)
val rgb = moveTemplate.elementalType.hue.toRGB()
fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
val selectConditionOpacity = moveSelection.opacity * if (!selectable) 0.5F else 1F
val unselectable = move.disabled
val selectConditionOpacity = moveSelection.opacity * if (unselectable) 0.5F else 1F
blitk(
matrixStack = context.matrices,
@@ -108,7 +87,7 @@ class BattleMoveSelection(
y = y,
width = MOVE_WIDTH,
height = MOVE_HEIGHT,
vOffset = if (selectable && isHovered(mouseX.toDouble(), mouseY.toDouble())) MOVE_HEIGHT else 0,
vOffset = if (!unselectable && isHovered(mouseX.toDouble(), mouseY.toDouble())) MOVE_HEIGHT else 0,
textureHeight = MOVE_HEIGHT * 2,
red = rgb.first,
green = rgb.second,
@@ -176,9 +155,18 @@ class BattleMoveSelection(
fun isHovered(mouseX: Double, mouseY: Double) = mouseX >= x && mouseX <= x + MOVE_WIDTH && mouseY >= y && mouseY <= y + MOVE_HEIGHT
fun onClick() {
if (!selectable) return
if (move.disabled) {
return
}
moveSelection.playDownSound(MinecraftClient.getInstance().soundManager)
moveSelection.battleGUI.selectAction(moveSelection.request, response)
val targets = move.target.targetList(moveSelection.request.activePokemon)
if (targets.isNullOrEmpty()) {
moveSelection.battleGUI.selectAction(moveSelection.request, MoveActionResponse(move.id, null))
} else if (targets.size == 1) {
moveSelection.battleGUI.selectAction(moveSelection.request, MoveActionResponse(move.id, targets[0].getPNX()))
} else {
// Target selection
}
}
}
@@ -186,24 +174,18 @@ class BattleMoveSelection(
moveTiles.forEach {
it.render(context, mouseX, mouseY, delta)
}
backButton.render(context.matrices, mouseX, mouseY, delta)
gimmickButtons.forEach {
it.render(context.matrices, mouseX, mouseY, delta)
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
val move = moveTiles.find { it.isHovered(mouseX, mouseY) }
val gimmick = gimmickButtons.find { it.isHovered(mouseX, mouseY) }
if (move != null) {
move.onClick()
return true
} else if (backButton.isHovered(mouseX, mouseY)) {
playDownSound(MinecraftClient.getInstance().soundManager)
battleGUI.changeActionSelection(null)
} else if (gimmick != null) {
gimmickButtons.filter { it != gimmick }.forEach { it.toggled = false }
moveTiles = if (gimmick.toggle()) gimmick.tiles else baseTiles
}
return false
}
Loaded 100 of 5606 files, more files were not shown because too many files have changed in this diff. Show more