Compare commits

..
Author SHA1 Message Date
saultsc 508d11f3a4 battle switch with animation 2026-05-04 23:57:48 -04:00
saultsc 8590999785 preview buffs correctly showing 2026-05-04 23:34:50 -04:00
saultsc 68569b790b PreviewWidget boost information 2026-05-04 23:15:28 -04:00
saultsc b2302c8c80 more packts, im lost 2026-05-04 18:32:35 -04:00
saultsc 0594525f59 add timer, preview, and turn packets 2026-05-04 17:38:07 -04:00
saultsc 54802450df lang en_us battle ui info 2026-05-04 16:58:38 -04:00
saultsc c90bbac694 landon new battle ui frist commit 2026-05-04 16:34:17 -04:00
2969 changed files with 31939 additions and 152737 deletions

No files matched your search

-1
View File
@@ -24,7 +24,6 @@ eclipse
run
runClient
runServer
utilityscripts/implementation/*.txt
# Files from Forge MDK
forge*changelog.txt
+2 -2
View File
@@ -71,8 +71,8 @@ setup_packsquash:
- echo "Installing wget & unzip"
- yum install -y wget unzip
- echo "Downloading PackSquash"
- wget https://github.com/ComunidadAylas/PackSquash/releases/download/v0.4.1/packsquash-x86_64-unknown-linux-musl.zip
- unzip packsquash-x86_64-unknown-linux-musl.zip -d ./packsquash_bin
- wget https://github.com/ComunidadAylas/PackSquash/releases/download/v0.4.0/PackSquash.CLI.executable.x86_64-unknown-linux-musl.zip
- unzip PackSquash.CLI.executable.x86_64-unknown-linux-musl.zip -d ./packsquash_bin
- echo "Creating PackSquash options.toml"
- |
cat <<EOF > ./options.toml
+32 -250
View File
@@ -53,161 +53,80 @@
- All shipwreck coves' spawners have been adjusted to give compatible mobs enchanted iron or diamond armour and weapons while in its ominous state. The armour given to the mobs will also use the automaton trim. We upped the difficulty because it was too easy now that your Pokémon can fight for you.
- One TM of the elemental hyper beams has been added as guaranteed loot to the main treasure chest in every shipwreck cove. (Magma cove gets blast burn, lush cove gets frenzy plant, and submerged cove gets hydro cannon)
- Added 7 new advancements
- Pressing the inventory key now closes the Pokémon interact wheel GUI
- Pokémon cries now play when clicking Pokémon in the summary screen.
- Pressing the Summary key while either the starter selection or summary screen are open now closes those screens
- Ice type Pokémon are now immune to Packed Snow
### New rideable Pokémon
- Drampa
- Archeops
- Skarmory
- Crobat
- Goodra
- Hisuian Goodra
- Milotic
### Pokémon Added
#### Generation 3
- Pancham (+ Cosmetic with Black Glasses)
- Pangoro (+ Cosmetic with Black Glasses)
- Skitty
- Delcatty
- Wurmple
- Silcoon
- Beautifly
- Cascoon
- Dustox
- Skitty
- Delcatty
- Seviper
- Zangoose
#### Generation 4
- Cherubi
- Cherrim
- Stunky
- Skuntank
#### Generation 5
- Audino
- Pawniard
- Bisharp
#### Generation 6
- Pancham (+ Cosmetic with Black Glasses)
- Pangoro (+ Cosmetic with Black Glasses)
#### Generation 7
- Rockruff
- Lycanroc
#### Generation 8
- Rolycoly
- Carkol
- Coalossal
- Runerigus
- Snom
- Frosmoth
- Indeedee
- Duraludon
- Applin
- Flapple
- Appletun
#### Generation 9
- Nymble
- Lokix
- Kingambit
- Dipplin
- Archaludon
- Hydrapple
#### Regional Forms
- Sandshrew (Alolan Form)
- Sandslash (Alolan Form)
- Yamask (Galarian Form)
- Avalugg (Hisuian Form)
- Sandshrew (Alolan)
- Sandslash (Alolan)
- Seviper
- Zangoose
- Stunky
- Skuntank
- Pawniard
- Bisharp
- Kingambit
### Model updates for the following Pokémon
- Sandshrew
- Sandslash
- Nidoking
- Zubat
- Golbat
- Crobat
- Ditto
- Goomy
- Sliggoo
- Goodra
- Porygon
- Porygon2
- Porygon-Z
- Skarmory
- Smoochum
- Jynx
- Popplio
- Brionne
- Primarina
- Fletchling
- Fletchinder
- Talonflame
- Zubat
- Golbat
- Crobat
- Pidgey
- Pidgeotto
- Pidgeot
- Sandslash
- Nidoking
- Sableye
- Mawile
- Goomy
- Sliggoo
- Goodra
- Nacli
- Naclstack
- Naclestack
- Garganacl
- Bidoof
- Cranidos
- Chatot
- Yamask
- Cofagrigus
- Tyranitar
- Turtonator
- Arbok (New Patterns: Angry, Cartoon, Clown, Comedic, Crow, Grin, Ink, Modern, Rain, Scribble, Silence, Sloth, Suspicious, Will)
#### Model changes to the regional bias resource pack:
- Added Mime Jr. (Galar Bias)
- Added Bergmite (Hisui Bias)
- Changed models for every existing regional bias
- Skarmory
### Animation updates for the following Pokémon
- Drampa
- Archeops
- Skarmory
- Skitty
- Flygon
- Shellos
- Riolu
- Lucario
- Shellos
- Skitty
- Joltik
- Purrloin
- Liepard
- Joltik
- Galvantula
- Nidoking
- Fletchling
- Fletchinder
- Talonflame
- Archeops
- Drampa
- Sewaddle
- Swadloon
- Leavanny
- Goomy
- Sliggoo
- Goodra
- Shedinja
- Skwovet
- Greedent
- Bidoof
- Cranidos
- Chatot
- Yamask
- Cofagrigus
- Luvdisc
- Flygon
#### Added sounds to the following moves
- Absorb
- Aurora Beam
- Double Team
- Draco Meteor
- Fire Punch
- Fire Spin
- Flame Charge
@@ -227,19 +146,10 @@
- Tail Whip
### Changes
- Stat increase and decrease particles have been changed, with the addition of alpha boost particles.
- Dragonite's mailbag cosmetic can now also be obtained with a chest in the cosmetic slot
### Fixes
- Adjusted Croagunk's walkspeed
- Fixed Blitzle's base scale being `1` rather than the intended `0.8`
- Fixed Magnezone's emissive layer for the shiny having the wrong name, thus displaying wrongly
- Fixed Litleo not blinking
- Fixed Garchomp T-posing when sleeping while out of battle.
- Sharp Beak can now be crafted
- Metal Alloy can now be crafted
- Ability Capsules can now be brewed
- Using an Ability Capsule will now return an Empty Bottle
- The following items can now be obtained by brushing the following Pokémon:
- Pink Wool - Wigglytuff
- White Wool - Swablu, Altaria
@@ -263,43 +173,6 @@
- Cleaned up seat data. Removed all now unused fields from seats in all species files. Also made locator seat names explicit in the species files (however, this doesn't break the legacy format)
- Marks can now provide aspects.
- Added new shiny and Alpha chance configs
- Apricorns now make a new sound when harvested
- Lowered the volume of harvesting berries
- Added alternate versions of the basic PC Wallpapers based on the original design of Cobblemon's PC, before public release
- Improved compatibility with various mods
- Farmer's Delight's cutting board can now be used to strip Apricorn and Saccharine's logs, or salvage their planks from various blocks. It can also separate their boats from chests
- Farmer's Delight's cutting board can be used to get two white dye from Pep-Up Flowers and two magenta dye from Medicinal Leeks
- Farmer's Delight's cutting board can separate Tumblestone Blocks back into Tumblestones
- Farmer's Delight's cutting board can salvage Type Gems from their Gem Blocks
- Farmer's Delight's Onions, Tomatoes, and Cabbages can be used to cook Open-Faced Sandwiches. Nature's Spirit's Olives also can be used
- Farmer's Delight's cutting board can be used on Hearty Grains to obtain rice and straw, similarly to Farmer's Delight's Rice Panicles
- Farmer's Delight's Apple Pies and Apple Ciders can now be made using Applin Apples, alongside the Vanilla counterpart
- Amendments allows for cooking Leek and Potato Stew in a Cauldron
- Any mods that utilise Moonlight's Soft Fluid system can store Leek and Potato Stew, Medicinal Brew, Antidote, Awakening, Burn Heal, Ice Heal, Paralyze Heal, Full Heal, Potion, Super Potion, Hyper Potion, Max Potion, Full Restore, Ability Capsules, Moomoo Milk, and Berry Juice in liquid form (such as Suplementaries' Jar or Goblet, or Cauldrons while using Amendments)
- Supplementaries' Hourglass can now use Bright Powder, Quick Powder, Metal Powder, Heal Powder, Silver Powder, and Remedy
- Berry Trees are now compatible with the Planter properly
- Saccharine Way Signs (added by Supplementaries automagically) now use a premade texture to prevent odd visual issues
- Supplementaries' Jar can now store various Cobblemon cookies, candies, and similar items
- Some of Supplementaries' items have been added to the `#cobblemon:held/container_held_items` item tag, including sacks, safes, and lunch baskets
- Experience Nuggets can be used in place of Sculk for making the EXP Candies if using Create
- Rage Candy Bar has a Create Mixing recipe
- All Cobblemon ores can now be crushed in Create's Crushing Wheels
- All Gem and Tumblestone Clusters can be crushed for automatic, low return, gems
- Hearty Grains can be milled like Farmer's Delight Rice Panicles if both Farmer's Delight and Create are installed
- Big Roots and Revival Herbs can be milled to their respective powders
- Pep-Up Flowers, Medicinal Leeks, and all 70 berries can be milled in Create's Millstone for additional dye drops
- Auspicious Armor and Upgrades can be Haunted using Create's crafting systems
- Applin Apples can be used to make Honeyed Apples in Create
- Golden Apples can now be crafted using the Applin Apples
- Prismarine Crystals and Amethyst Shards can now be used to craft Pokedexes
- Updated Pokedex Screens item tag to support tagged variants of Redstone and Glowstone Dust
- Sounds for interacting with various Cobblemon blocks now play from the center of the block, rather than the corner
- The `/pc` command no longer plays PC activation sounds
- Lowered the volume of Blitzle and Zebstrika's cry
- Fixed Raichu (male) and Alolan Raichu (both genders) not displaying the correct Partner Cap when Pewter Crunchies are in the cosmetic slot.
- Regional Foods will now prioritize being eaten instead of used for healing Pokémon, shift clicking will use it as healing
- Resolved Z-Fighting on Pokédex Models
- Restored Ninjask's ability to do barrel rolls
### Fixes
- Fixed Croagunk's walkspeed
@@ -311,7 +184,6 @@
- Fixed a compatibility issue with World Game Rules which caused some log spam in specific cases.
- Fixed improper validation of Pokémon when performing a trade.
- Fixed an issue where particle like beam can disappear when you don't see the beginning of the beam, or you look away
- Fixed Bedrock particle materials (alpha/opaque/blend/add) sharing one render batch, which caused incorrect culling on mixed batches; each material now batches and draws with its own blend and cull state, and the alpha material renders as a true cutout
- Fixed an occasional crash on retrieving revived fossils
- Fixed brushing not working on Neoforge
- Fixed braking not working for submarines (whoops).
@@ -339,17 +211,6 @@
- Fixed various log warnings caused by redundant files and sound events.
- Traded Pokémon will now retain the original friendship level when traded back to its OT
- Clusters now display correctly on maps
- Fixed removing mulch from berry trees with a shovel not working.
- Fixed collecting honey from Saccharine Leaf blocks deleting Honey Bottles when the inventory was full
- Fixed Stripped Saccharine Wood recipe being missing
- Fixed CancelableSoundInstance causing lag by attempting to create sounds every tick when out of range of hearing
- Fixed Stat Reducing Aprijuice not being possible to apply on a max out Pokémon, and the possibility of going into negatives
- Fixed Exploit where you could take out the bait or drop your Pokérod with bait to avoid consuming bait, now the bobber will just disappear if you do so
- Fixed Apricorn and Saccharine boats incorrectly using chest boat models
- Fixed wild and trainer battle AI refusing to select moves with the `adjacentAlly` target type, even when it's their only option
- Fixed Guilded chests being more accessible than intended
- Fixed fished up Pokémon not being added as "seen" to the Pokedex
- Fixed surprised Gimmighoul encounters not being added as "seen" to the Pokedex
### Developer
- Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters
@@ -358,8 +219,6 @@
- Tweaked the spawning selector interface to take a bucket function rather than a bucket directly.
- Added a new `cobblemon:height_range` processor type to allow developers to restrict structure-spawning ranges.
- Separated all the functions of MoLangFunctions.kt into their own files.
- Spawn buckets and their base weights are now provided by the spawner, with different base weights for world, fishing, snack, and habitat spawn buckets.
- Removed "SpawnBucket" class in favour of just storing maps of bucket names to weights.
### Molang & Datapacks
- Added `moveset_builders` datapack folder for different styles of movesets.
@@ -382,85 +241,8 @@
- Ride sounds can now be set as exclusive to passengers
- Ride sounds no longer play when submerged unless specified with a new setting
- NPC Trainers can now call a `command` animation when issuing a command in battle.
- Added a q.failed function to move action effects.
- Herd spawned Pokémon have their levels controlled differently. The `levelRange` property now acts as a clamp instead of a filter, with a new `herdLevelRange` property to do the filter behaviour.
- `q.sound` now accepts three parameters (sound event, volume and pitch) instead of just the sound event and pitch.
### Particles
#### New Move Animations
- Blizzard
- Disarming Voice
- Draco Meteor
- Dragon Breath
- Dragon Rage
- Drain Punch
- Fairy Wind
- Fire Fang
- Gust
- Horn Drill
- Hydro Pump
- Ice Fang
- Leafage
- Leaf Storm
- Light Screen
- Night Shade
- Play Rough
- Pyro Ball
- Reflect
- Rock Slide
- Sonic Boom
- Sucker Punch
- Supersonic
- Thunder Fang
#### Tweaks and Fixes (Moves)
- Confusion
- Dragon Claw
- Eruption
- Fire Punch
- Flame Burst
- Flame Charge
- Flamethrower
- Frost Breath
- Hypnosis
- Ice Punch
- Icy Wind
- Mud Sport
- Poison Fang
- Protect
- Psychic
- Quick Attack
- Rage Powder
- Razor Leaf
- Sand Attack
- Scratch
- Shadow Claw
- Sing
- Spore
- String Shot
- Tackle
- Tail Whip
- Thunder
- Thunder Punch
- Thunder Wave
- Water Gun
- Water Pulse
- Water Sport
#### Miscellaneous Particles
+ Added Wild Might particles for Alpha Pokémon
+ Updated stat increase and decrease particles
### Localization
- Updated translations for:
- Japanese
- German
- Spanish
- Ecuadorian Spanish
- Brazilian Portuguese
- Simplified Chinese
- Traditional Chinese
- Korean
-4
View File
@@ -72,10 +72,6 @@ tasks.withType<Test> {
}
}
tasks.processResources {
exclude("data/**/README.md")
}
sourceSets {
main {
blossom {
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.client;
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.duck.ChannelDuck;
import com.mojang.blaze3d.audio.Channel;
@@ -1,111 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.mixin.accessor.SinglePoolElementAccessor;
import com.cobblemon.mod.common.world.structureprocessors.HeightRangeStructureProcessor;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.pools.*;
import net.minecraft.world.level.levelgen.structure.pools.alias.PoolAliasLookup;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Optional;
@Mixin(JigsawPlacement.class)
public class JigsawPlacementMixin {
@Inject(
method = "addPieces(Lnet/minecraft/world/level/levelgen/structure/Structure$GenerationContext;Lnet/minecraft/core/Holder;Ljava/util/Optional;ILnet/minecraft/core/BlockPos;ZLjava/util/Optional;ILnet/minecraft/world/level/levelgen/structure/pools/alias/PoolAliasLookup;Lnet/minecraft/world/level/levelgen/structure/pools/DimensionPadding;Lnet/minecraft/world/level/levelgen/structure/templatesystem/LiquidSettings;)Ljava/util/Optional;",
at = @At("RETURN"),
cancellable = true
)
private static void onAddPieces(
Structure.GenerationContext context,
Holder<StructureTemplatePool> startPool,
Optional<ResourceLocation> startJigsawName,
int maxDepth,
BlockPos pos,
boolean useExpansionHack,
Optional<Heightmap.Types> projectStartToHeightmap,
int maxDistanceFromCenter,
PoolAliasLookup aliasLookup,
DimensionPadding dimensionPadding,
LiquidSettings liquidSettings,
CallbackInfoReturnable<Optional<Structure.GenerationStub>> cir
) {
Optional<Structure.GenerationStub> result = cir.getReturnValue();
if (result.isEmpty()) {
return;
}
BlockPos spawnPos = result.get().position();
boolean canPlace = cobblemon$canAnyStartElementPlace(context, startPool, spawnPos);
if (canPlace) {
return;
}
cir.setReturnValue(Optional.empty());
}
@Unique
private static boolean cobblemon$canAnyStartElementPlace(
Structure.GenerationContext generationContext,
Holder<StructureTemplatePool> startPool,
BlockPos startPos
) {
for (StructurePoolElement element : startPool.value().templates) {
Optional<HeightRangeStructureProcessor> heightRangeProcessor = cobblemon$getHeightRangeProcessor(element);
if (heightRangeProcessor.isEmpty()) {
return true;
}
HeightRangeStructureProcessor processor = heightRangeProcessor.get();
boolean valid = processor.isValidPlacement(
generationContext.chunkGenerator(),
generationContext.heightAccessor(),
generationContext.randomState(),
startPos
);
if (valid) {
return true;
}
}
return false;
}
@Unique
private static Optional<HeightRangeStructureProcessor> cobblemon$getHeightRangeProcessor(StructurePoolElement element) {
if (!(element instanceof SinglePoolElement)) {
return Optional.empty();
}
StructureProcessorList processorList = ((SinglePoolElementAccessor) element).getProcessors().value();
for (StructureProcessor processor : processorList.list()) {
if (processor instanceof HeightRangeStructureProcessor heightRangeStructureProcessor) {
return Optional.of(heightRangeStructureProcessor);
}
}
return Optional.empty();
}
}
@@ -1,22 +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.accessor;
import net.minecraft.core.Holder;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(SinglePoolElement.class)
public interface SinglePoolElementAccessor {
@Accessor("processors")
Holder<StructureProcessorList> getProcessors();
}
@@ -308,9 +308,7 @@ object Cobblemon {
this.config.maxPokemonLevel,
this.config.maxPokemonFriendship,
this.config.maxDynamaxLevel,
this.config.unlockAllMoveDexMovesByDefault,
this.config.pokemonIntrinsicSizeMin,
this.config.pokemonIntrinsicSizeMax
this.config.unlockAllMoveDexMovesByDefault
).sendToPlayer(player)
}
@@ -131,7 +131,6 @@ import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.ChangeP
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.RequestPCBoxWallpapersPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.SetPCBoxWallpapersPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.UnlockPCBoxWallpaperPacket
import com.cobblemon.mod.common.net.messages.client.tms.TechnicalMachineRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.toast.ToastPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeAcceptanceChangedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeCancelledPacket
@@ -160,6 +159,9 @@ import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamRequestPack
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamResponsePacket
import com.cobblemon.mod.common.net.messages.server.battle.RemoveSpectatorPacket
import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
import com.cobblemon.mod.common.net.messages.server.battle.SpectateNearestBattlePacket
import com.cobblemon.mod.common.net.messages.server.battle.TeamPreviewSelectPokemonPacket
import com.cobblemon.mod.common.net.messages.server.battle.TeamPreviewSelectPokemonPacket.Companion.decode
import com.cobblemon.mod.common.net.messages.server.behaviour.DamageOnCollisionPacket
import com.cobblemon.mod.common.net.messages.server.behaviour.SetEntityBehaviourPacket
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
@@ -215,7 +217,9 @@ import com.cobblemon.mod.common.net.serverhandling.RequestInteractionsHandler
import com.cobblemon.mod.common.net.serverhandling.battle.BattleSelectActionsHandler
import com.cobblemon.mod.common.net.serverhandling.battle.RemoveSpectatorHandler
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateBattleHandler
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateNearestBattleHandler
import com.cobblemon.mod.common.net.serverhandling.battle.TeamLeaveHandler
import com.cobblemon.mod.common.net.serverhandling.battle.TeamPreviewSelectPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestHandler
import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestResponseHandler
import com.cobblemon.mod.common.net.serverhandling.behaviour.DamageOnCollisionPacketHandler
@@ -403,6 +407,12 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(BattleChallengeExpiredPacket.ID, BattleChallengeExpiredPacket::decode, BattleChallengeExpiredHandler))
list.add(PacketRegisterInfo(BattleReplacePokemonPacket.ID, BattleReplacePokemonPacket::decode, BattleReplacePokemonHandler))
list.add(PacketRegisterInfo(BattleTransformPokemonPacket.ID, BattleTransformPokemonPacket::decode, BattleTransformPokemonHandler))
list.add(PacketRegisterInfo(BattleActorInformationPacket.ID, BattleActorInformationPacket::decode, BattleActorInformationHandler))
list.add(PacketRegisterInfo(BattleActorTeamPacket.ID, BattleActorTeamPacket::decode, BattleActorTeamHandler))
list.add(PacketRegisterInfo(BattleInformationPacket.ID, BattleInformationPacket::decode, BattleInformationHandler))
list.add(PacketRegisterInfo(BattleTimerPacket.ID, BattleTimerPacket::decode, BattleTimerHandler))
list.add(PacketRegisterInfo(TeamPreviewPacket.ID, TeamPreviewPacket::decode, TeamPreviewHandler))
list.add(PacketRegisterInfo(CloseTeamPreviewPacket.ID, CloseTeamPreviewPacket::decode, CloseTeamPreviewHandler))
// MultiBattleTeam Packets
list.add(PacketRegisterInfo(TeamRequestNotificationPacket.ID, TeamRequestNotificationPacket::decode, TeamRequestNotificationHandler))
@@ -440,7 +450,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(BehaviourSyncPacket.ID, BehaviourSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(MarkRegistrySyncPacket.ID, MarkRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(RideSettingsSyncPacket.ID, RideSettingsSyncPacket::decode, RideSettingsSyncHandler))
list.add(PacketRegisterInfo(TechnicalMachineRegistrySyncPacket.ID, TechnicalMachineRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
// Effects
list.add(PacketRegisterInfo(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler))
@@ -572,6 +581,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(BattleSelectActionsPacket.ID, BattleSelectActionsPacket::decode, BattleSelectActionsHandler))
list.add(PacketRegisterInfo(SpectateBattlePacket.ID, SpectateBattlePacket::decode, SpectateBattleHandler))
list.add(PacketRegisterInfo(RemoveSpectatorPacket.ID, RemoveSpectatorPacket::decode, RemoveSpectatorHandler))
list.add(PacketRegisterInfo(TeamPreviewSelectPokemonPacket.ID, TeamPreviewSelectPokemonPacket::decode, TeamPreviewSelectPokemonHandler))
// Trade
list.add(PacketRegisterInfo(AcceptTradeRequestPacket.ID, AcceptTradeRequestPacket::decode, AcceptTradeRequestHandler))
@@ -646,6 +656,9 @@ object CobblemonNetwork {
// Debug / cheats
list.add(PacketRegisterInfo(RequestOpenRidingStatsDebugGUIPacket.ID, RequestOpenRidingStatsDebugGUIPacket::decode, RequestOpenRidingStatsDebugGUIHandler))
list.add(PacketRegisterInfo(SpectateNearestBattlePacket.ID, SpectateNearestBattlePacket
::decode, SpectateNearestBattleHandler))
return list
}
@@ -108,9 +108,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val LEVELUP = this.create("gui.levelup")
@JvmField
val MOVE_LEARN = this.create("gui.move_learn")
@JvmField
val PVN_BATTLE = this.create("battle.pvn.default")
@JvmField
@@ -215,23 +212,17 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val TM_USE = this.create("item.tm.use")
@JvmField
val TM_MACHINE_BURN_LOOP = this.create("block.tm_machine.burn_loop")
@JvmField
val TM_MACHINE_CRAFT = this.create("block.tm_machine.craft")
@JvmField
val TM_MACHINE_START = this.create("block.tm_machine.start")
val TM_MACHINE_OFF = this.create("block.tm_machine.off")
@JvmField
val TM_MACHINE_ON = this.create("block.tm_machine.on")
@JvmField
val TM_MACHINE_OPEN = this.create("block.tm_machine.open")
@JvmField
val TM_MACHINE_CLOSE = this.create("block.tm_machine.close")
@JvmField
val TM_MACHINE_TM_INSERT = this.create("block.tm_machine.tm_insert")
@JvmField
val TM_MACHINE_TM_RETRIEVE = this.create("block.tm_machine.tm_retrieve")
@JvmField
val TM_MACHINE_ITEM_INSERT = this.create("block.tm_machine.item_insert")
@JvmField
val TM_MACHINE_ITEM_RETRIEVE = this.create("block.tm_machine.item_retrieve")
val TM_MACHINE_PLACE_DISC = this.create("block.tm_machine.place_disc")
@JvmField
@@ -272,9 +263,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val BERRY_EAT_FULL = this.create("item.berry.eat.full")
@JvmField
val APRICORN_HARVEST = this.create("block.apricorn.harvest")
@JvmField
val BIG_ROOT_BREAK = this.create("block.big_root.break")
@JvmField
@@ -422,9 +410,9 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
)
@JvmField
val TYPE_GEM_BLOCK_BREAK = this.create("block.type_gem_block.break")
val TYPE_GEM_BLOCK_BREAK = this.create("block.type_gem_cluster.break")
@JvmField
val TYPE_GEM_CLUSTER_BREAK = this.create("block.type_gem_cluster.break")
val TYPE_GEM_CLUSTER_BREAK = this.create("block.type_gem_block.break")
@JvmField
val TYPE_GEM_BLOCK_HIT = this.create("block.type_gem_block.hit")
@JvmField
@@ -433,7 +421,7 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
val TYPE_GEM_BLOCK_STEP = this.create("block.type_gem_block.step")
@JvmField
val TYPE_GEM_CLUSTER_SOUNDS = SoundType(1f, 1.1f,
val TYPE_GEM_CLUSTER_SOUNDS = SoundType(1f, 1.2f,
TYPE_GEM_CLUSTER_BREAK,
TYPE_GEM_BLOCK_STEP,
TYPE_GEM_BLOCK_PLACE,
@@ -48,7 +48,7 @@ class RidingStatBoostCriterion(
val max = context.pokemon.pokemon.getMaxRideBoost(rideStat).toDouble()
val min = context.pokemon.pokemon.getBaseRideStat(rideStat).toDouble()
val value = context.pokemon.getRideStat(rideStat, ridingStyle, min, max)
val isSufficient = (value >= this.statValue && this.statValue > 0.0) || !context.pokemon.pokemon.canApplyRideBoost(rideStat)
val isSufficient = (value >= this.statValue && this.statValue > 0.0) || !context.pokemon.pokemon.canAddRideBoost(rideStat)
if (isSufficient && this.rideStat == "any") {
return true
} else if (!isSufficient && this.rideStat == "all") {
@@ -15,6 +15,8 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.CobblemonBuildDetails
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
@@ -46,14 +48,22 @@ import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.battles.interpreter.ContextManager
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.battles.timers.PlayerBattleTimer
import com.cobblemon.mod.common.battles.timers.ShowdownTimer
import com.cobblemon.mod.common.entity.PlatformType
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorTeamPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleInitializePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleInformationDTO
import com.cobblemon.mod.common.net.messages.client.battle.BattleInformationPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleQueueRequestPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleTimerPacket
import com.cobblemon.mod.common.net.messages.client.battle.FieldEffect
import com.cobblemon.mod.common.pokemon.evolution.progress.DefeatEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
import com.cobblemon.mod.common.pokemon.requirements.DefeatRequirement
@@ -69,6 +79,7 @@ import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.item.ItemStack
import java.time.Instant
/**
* Individual battle instance
@@ -80,7 +91,8 @@ import net.minecraft.world.item.ItemStack
open class PokemonBattle(
val format: BattleFormat,
val side1: BattleSide,
val side2: BattleSide
val side2: BattleSide,
val playerTimer: (PokemonBattle, PlayerBattleActor) -> PlayerBattleTimer? = { battle, actor -> ShowdownTimer(battle, actor) }
) {
/** Whether logging will be silenced for this battle. */
var mute = !CobblemonBuildDetails.SNAPSHOT
@@ -95,6 +107,9 @@ open class PokemonBattle(
side1.battle = this
side2.battle = this
this.actors.forEach { actor ->
if (actor is PlayerBattleActor) {
actor.timer = playerTimer(this, actor)
}
actor.battle = this
actor.pokemonList.forEach { battlePokemon ->
battlePokemon.effectedPokemon.evolutionProxy.current().progress()
@@ -128,6 +143,8 @@ open class PokemonBattle(
var announcingRules = false
var turn: Int = 0
private set
var turnStartTime: Instant = Instant.now()
private set
private var ticks: Int = 0
set(value) { field = value.coerceAtMost(Int.MAX_VALUE) } // go outside
@@ -137,13 +154,11 @@ open class PokemonBattle(
var dispatchResult = GO
val dispatches = ConcurrentLinkedDeque<BattleDispatch>()
val afterDispatches = mutableListOf<() -> Unit>()
val captureActions = mutableListOf<BattleCaptureAction>()
val majorBattleActions = hashMapOf<UUID, BattleMessage>()
/** Monotonically increasing counter to track faint ordering within this battle. */
var faintCounter = 0
val minorBattleActions = hashMapOf<UUID, BattleMessage>()
val contextManager = ContextManager()
@@ -213,6 +228,23 @@ open class PokemonBattle(
return actor to pokemon
}
fun startSpectating(player: ServerPlayer) {
if (!Cobblemon.config.allowSpectating) return
spectators.add(player.uuid)
player.sendPacket(BattleInitializePacket(this, null))
player.sendPacket(BattleMessagePacket(chatLog))
notifyOfUpdates(listOf(player.uuid))
actors.forEach { actor ->
val team = actor.pokemonList.map { it.toBattleDTO(false) }
player.sendPacket(BattleActorTeamPacket(actor.uuid, team))
}
}
fun stopSpectating(player: ServerPlayer) {
player.sendPacket(BattleEndPacket())
spectators.remove(player.uuid)
}
/**
* Gets a [BattlePokemon] from a pnx key and uuid.
*
@@ -238,6 +270,11 @@ open class PokemonBattle(
fun turn(newTurnNumber: Int) {
actors.forEach { it.turn() }
actors.filterIsInstance<PlayerBattleActor>().forEach { actor ->
actor.timer?.mustChooseBy()?.let {
actor.uuid.getPlayer()?.sendPacket(BattleTimerPacket(it))
}
}
// TODO: If a pokemon switches in the same turn another pokemon is KO'd it will not receive exp for the KO
for (side in sides) {
val opposite = side.getOppositeSide()
@@ -247,27 +284,49 @@ open class PokemonBattle(
}
}
this.turn = newTurnNumber
this.turnStartTime = Instant.now()
notifyAllOfUpdates()
}
fun notifyAllOfUpdates() {
val uuids = this.actors.map { it.uuid } + this.spectators
notifyOfUpdates(uuids)
}
fun notifyOfUpdates(uuids: List<UUID>) {
val weather = this.contextManager.get(BattleContext.Type.WEATHER)?.firstOrNull()
val terrain = this.contextManager.get(BattleContext.Type.TERRAIN)?.firstOrNull()
val room = this.contextManager.get(BattleContext.Type.ROOM)?.firstOrNull()
val side1SidedEffects = getSidedFieldEffects(side1)
val side2SidedEffects = getSidedFieldEffects(side2)
val updatePacket = BattleInformationPacket(this.battleId, BattleInformationDTO(
turn = turn,
weather = weather?.let { FieldEffect(it.id, it.turn, it.origin?.entity?.id) },
terrain = terrain?.let { FieldEffect(it.id, it.turn, it.origin?.entity?.id) },
room = room?.let { FieldEffect(it.id, it.turn, it.origin?.entity?.id) },
side1SidedEffects = side1SidedEffects,
side2SidedEffects = side2SidedEffects,
))
uuids.mapNotNull { it.getPlayer() }.forEach {
it.sendPacket(updatePacket)
}
}
private fun getSidedFieldEffects(side: BattleSide): List<FieldEffect> {
val hazards = side.contextManager.get(BattleContext.Type.HAZARD)?.map { FieldEffect(it.id, it.turn, it.origin?.entity?.id) } ?: emptyList()
val screens = side.contextManager.get(BattleContext.Type.SCREEN)?.map { FieldEffect(it.id, it.turn, it.origin?.entity?.id) } ?: emptyList()
val tailwind = side.contextManager.get(BattleContext.Type.TAILWIND)?.firstOrNull()?.let { FieldEffect(it.id, it.turn, it.origin?.entity?.id) }
return hazards + screens + listOfNotNull(tailwind)
}
fun end() {
ended = true
val awardToFainted = Cobblemon.config.awardExperienceToFaintedPokemon
val awardOnLoss = Cobblemon.config.awardExperienceOnBattleLoss
this.actors.forEach { actor ->
val faintedPokemons = actor.pokemonList.filter { it.health <= 0 }
actor.getSide().getOppositeSide().actors.forEach { opponent ->
val opponentPokemons = if (awardToFainted) opponent.pokemonList else opponent.pokemonList.filter { it.health > 0 }
val opponentNonFaintedPokemons = opponent.pokemonList.filter { it.health > 0 }
faintedPokemons.forEach { faintedPokemon ->
for (opponentPokemon in opponentPokemons) {
// Faint-order check: if awarding XP to fainted pokemon, only award for enemies that fainted before this pokemon did
if (awardToFainted) {
val enemyFaintedAt = faintedPokemon.faintedAt ?: continue
val opponentFaintedAt = opponentPokemon.faintedAt
if (opponentFaintedAt != null && opponentFaintedAt <= enemyFaintedAt) {
continue
}
}
for (opponentPokemon in opponentNonFaintedPokemons) {
val facedFainted = opponentPokemon.facedOpponents.contains(faintedPokemon)
val pokemon = opponentPokemon.effectedPokemon
if (facedFainted) {
@@ -288,8 +347,7 @@ open class PokemonBattle(
else -> continue
}
val experience = Cobblemon.experienceCalculator.calculate(opponentPokemon, faintedPokemon, multiplier)
val enemyTeamWiped = actor.pokemonList.all { it.health <= 0 }
if (experience > 0 && (enemyTeamWiped || awardOnLoss)) {
if (experience > 0 && actor.pokemonList.all { it.health <= 0 }) {
opponent.awardExperience(opponentPokemon, experience)
}
Cobblemon.evYieldCalculator.calculate(opponentPokemon, faintedPokemon).forEach { (stat, amount) ->
@@ -303,7 +361,7 @@ open class PokemonBattle(
val player = actor.getPlayerUUIDs().first().getPlayer()
player?.level()?.itemRegistry.let { registry ->
actor.itemsUsed.mapNotNull { registry?.get(ResourceLocation.tryBySeparator(it.itemName.substringAfter('.'), '.')) }
.forEach { player?.giveOrDropItemStack(ItemStack(it))}
.forEach { player?.giveOrDropItemStack(ItemStack(it))}
}
}
@@ -324,10 +382,16 @@ open class PokemonBattle(
actors.forEach { actor ->
actor.pokemonList.forEach { battlePokemon ->
battlePokemon.postBattlePokemonOperations.forEach { it(battlePokemon) }
battlePokemon.entity?.let { entity -> battlePokemon.postBattleEntityOperations.forEach { it(entity) } }
battlePokemon.clearBattleFeatures()
battlePokemon.entity?.let { entity ->
entity.terastallize(null)
battlePokemon.postBattleEntityOperations.forEach { it(entity) }
}
if (battlePokemon.effectedPokemon.entity != null
&& battlePokemon.effectedPokemon.entity?.beamMode == 0
&& battlePokemon.effectedPokemon.entity?.platform != PlatformType.NONE) {
&& battlePokemon.effectedPokemon.entity?.beamMode == 0
&& battlePokemon.effectedPokemon.entity?.platform != PlatformType.NONE) {
battlePokemon.effectedPokemon.tryRecallWithAnimation()
}
}
@@ -399,10 +463,12 @@ open class PokemonBattle(
fun dispatch(dispatcher: () -> DispatchResult) {
dispatches.add(BattleDispatch { dispatcher() })
}
fun dispatchToFront(dispatcher: () -> DispatchResult) {
dispatches.addFirst(BattleDispatch { dispatcher() })
}
fun dispatchWaitingToFront(delaySeconds: Float = 1F, dispatcher: () -> Unit) {
@@ -453,12 +519,24 @@ open class PokemonBattle(
dispatches.addFirst(dispatcher)
}
fun doWhenClear(action: () -> Unit) {
afterDispatches.add(action)
}
fun tick() {
if (turn >= 1) {
this.actors.filterIsInstance<PlayerBattleActor>().forEach { it.timer?.tick() }
}
try {
while (dispatchResult.canProceed()) {
val dispatch = dispatches.poll() ?: break
dispatchResult = dispatch(this)
}
if (dispatches.isEmpty()) {
afterDispatches.toList().forEach { it() }
afterDispatches.clear()
}
} catch (e: Exception) {
LOGGER.error("Exception while ticking a battle. Saving battle log.", e)
val message = battleLang("crash").red()
@@ -514,7 +592,10 @@ open class PokemonBattle(
fun checkForInputDispatch() {
if (checkForfeit()) return // ignore actors that are still choosing, their choices don't matter anymore
if (checkFleeAttempt()) return
val readyToInput = (actors.any { !it.mustChoose && it.responses.isNotEmpty() } && actors.none { it.mustChoose })
val readyToInput = (actors.any { !it.mustChoose && it.responses.isNotEmpty() } && actors.none {
LOGGER.info("${it.getName().string} (Must Choose: ${it.mustChoose}): ${it.responses}")
it.mustChoose
})
if (readyToInput && captureActions.isEmpty()) {
actors.filter { it.responses.isNotEmpty() }.forEach { it.writeShowdownResponse() }
actors.forEach { it.responses.clear() ; it.request = null }
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.data
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
// TODO this is implemented in multiple places but unused. Can we scratch this?
/**
* Represents an object that will possibly be synchronized to the client during datapack synchronization.
*
* @param T The type of the object being synchronized.
*
* @author Licious
* @since October 14th, 2022
*/
interface ClientDataSynchronizer<T> : Decodable, Encodable {
/**
* Compares an object of type [T] with this instance to see if synchronization is necessary.
*
* @param other The other instance of type [T].
* @return If synchronization is necessary.
*/
fun shouldSynchronize(other: T): Boolean
}
@@ -8,10 +8,17 @@
package com.cobblemon.mod.common.api.events
import com.cobblemon.mod.common.api.events.battles.BattleChoiceMadeEvent
import com.cobblemon.mod.common.api.events.battles.BattleChoiceRequestedEvent
import com.cobblemon.mod.common.api.events.battles.BattleFaintedEvent
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
import com.cobblemon.mod.common.api.events.battles.BattleFleeAttemptEvent
import com.cobblemon.mod.common.api.events.battles.BattleHPEvent
import com.cobblemon.mod.common.api.events.battles.BattlePickMoveEvent
import com.cobblemon.mod.common.api.events.battles.BattleStartedEvent
import com.cobblemon.mod.common.api.events.battles.BattleSwitchEvent
import com.cobblemon.mod.common.api.events.battles.BattleTerastallizeEvent
import com.cobblemon.mod.common.api.events.battles.BattleUseMoveEvent
import com.cobblemon.mod.common.api.events.battles.BattleVictoryEvent
import com.cobblemon.mod.common.api.events.battles.instruction.FormeChangeEvent
import com.cobblemon.mod.common.api.events.battles.instruction.MegaEvolutionEvent
@@ -118,6 +125,21 @@ object CobblemonEvents {
@JvmField
val POKEMON_CAPTURED = EventObservable<PokemonCapturedEvent>()
@JvmField
val BATTLE_USE_MOVE = EventObservable<BattleUseMoveEvent>()
@JvmField
val BATTLE_PICK_MOVE = EventObservable<BattlePickMoveEvent>()
@JvmField
val BATTLE_SWITCH = EventObservable<BattleSwitchEvent>()
@JvmField
val BATTLE_TERA = EventObservable<BattleTerastallizeEvent>()
@JvmField
val BATTLE_HP = EventObservable<BattleHPEvent>()
@JvmField
val FOSSIL_REVIVED = EventObservable<FossilRevivedEvent>()
@@ -157,7 +179,7 @@ object CobblemonEvents {
@JvmField
val POKEMON_SENT_POST = EventObservable<PokemonSentEvent.Post>()
@JvmField
val POKEMON_RECALL_PRE = CancelableObservable<PokemonRecallEvent.Pre>()
@@ -186,7 +208,7 @@ object CobblemonEvents {
val POKEMON_HEALED = CancelableObservable<PokemonHealedEvent>()
@JvmField
/** CLIENT ONLY! */
/** CLIENT ONLY! */
val POKEMON_INTERACTION_GUI_CREATION = EventObservable<PokemonInteractionGUICreationEvent>()
@JvmField
@@ -382,6 +404,12 @@ object CobblemonEvents {
@JvmField
val SELECT_DRIVER = EventObservable<SelectDriverEvent>()
@JvmField
val BATTLE_CHOICE_REQUESTED = EventObservable<BattleChoiceRequestedEvent>()
@JvmField
val BATTLE_CHOICE_MADE = EventObservable<BattleChoiceMadeEvent>()
// Habitats
@JvmField
val HABITAT_SPAWN_ACTIVATED = CancelableObservable<HabitatSpawnActivatedEvent>()
@@ -0,0 +1,15 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.battles
import net.minecraft.server.level.ServerPlayer
data class BattleChoiceMadeEvent (
val player: ServerPlayer
)
@@ -0,0 +1,17 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.battles
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
import net.minecraft.server.level.ServerPlayer
data class BattleChoiceRequestedEvent (
val player: ServerPlayer,
val packet: BattleMakeChoicePacket
)
@@ -0,0 +1,17 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.battles
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
data class BattleHPEvent(
val battle: PokemonBattle,
val pokemon: BattlePokemon,
val newHealth: String
)
@@ -0,0 +1,15 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.battles
import java.util.*
data class BattlePickMoveEvent(
val player: UUID
)
@@ -0,0 +1,18 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.battles
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
data class BattleStartEvent (
val playerActor: PlayerBattleActor,
val wildActor: PokemonBattleActor
): Cancelable()
@@ -0,0 +1,17 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.battles
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.pokemon.Pokemon
data class BattleSwitchEvent(
val battle: PokemonBattle,
val pokemon: Pokemon
)
@@ -0,0 +1,19 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.battles
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import net.minecraft.server.level.ServerPlayer
data class BattleTerastallizeEvent(
val battle: PokemonBattle,
val playerEntity: ServerPlayer,
val pokemon: BattlePokemon
)
@@ -0,0 +1,21 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.battles
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import net.minecraft.server.level.ServerPlayer
data class BattleUseMoveEvent(
val battle: PokemonBattle,
val players: List<ServerPlayer>,
val userPokemon: BattlePokemon,
val move: MoveTemplate
)
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.battles
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import net.minecraft.server.level.ServerPlayer
/**
* Event fired when a [BattlePokemon] faints. Exposes the [BattlePokemon] that fainted and the [BattleContext]
* of how it fainted.
*
* @author Segfault Guy
* @since April 6th, 2023
*/
data class GimmighoulEvent (
val playerEntity: ServerPlayer
)
@@ -0,0 +1,19 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.battles
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.entity.npc.NPCEntity
import net.minecraft.server.level.ServerPlayer
data class NPCDefineBattleActorEvent(
val player: ServerPlayer,
val npcEntity: NPCEntity,
var battleActor: NPCBattleActor
)
@@ -0,0 +1,19 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.battles
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.server.level.ServerPlayer
import java.util.*
data class NPCEvent(
val player: ServerPlayer,
val entity: PokemonEntity,
val leadingPokemon: UUID
)
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.battles
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.pokeball.PokeBall
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.server.level.ServerPlayer
data class PokemonCapturePreEvent (
val player: ServerPlayer,
val pokemon: Pokemon,
val pokeBall: PokeBall
): Cancelable()
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.events.entity
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
@@ -20,7 +21,7 @@ class SpawnBucketChosenEvent(
/** The cause of the spawn. */
val spawnCause: SpawnCause,
/** The bucket that was chosen. You can alter this. */
var bucket: String,
var bucket: SpawnBucket,
/** The final weights that were used to make the choice. */
val bucketWeights: Map<String, Float>
val bucketWeights: Map<SpawnBucket, Float>
)
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.events.fishing
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.item.ItemStack
@@ -26,7 +27,7 @@ interface BaitSpawnPokemonEvent {
*/
data class Pre(
val bobber: PokeRodFishingBobberEntity, // todo replace this with the bait entity
val chosenBucket: String,
val chosenBucket: SpawnBucket,
val rod: ItemStack
) : Cancelable(), BobberSpawnPokemonEvent
@@ -37,7 +38,7 @@ interface BaitSpawnPokemonEvent {
* @param pokemon The Pokemon that is modified.
*/
data class Modify(
val chosenBucket: String,
val chosenBucket: SpawnBucket,
val rod: ItemStack,
val pokemon: PokemonEntity
) : BobberSpawnPokemonEvent
@@ -51,7 +52,7 @@ interface BaitSpawnPokemonEvent {
*/
data class Post(
val bobber: PokeRodFishingBobberEntity,
val chosenBucket: String,
val chosenBucket: SpawnBucket,
val bait: ItemStack,
val pokemon: PokemonEntity
) : BobberSpawnPokemonEvent
@@ -8,10 +8,12 @@
package com.cobblemon.mod.common.api.events.fishing
import com.cobblemon.mod.common.api.spawning.SpawnBucket
/**
* Event that is fired when a bucket is chosen by the bobber.
* @param chosenBucket The bucket that is chosen.
* @param buckets The list of buckets that the bobber can choose from.
* @param luckOfTheSeaLevel The level of the Luck of the Sea enchantment on the rod.
*/
class BobberBucketChosenEvent(var chosenBucket: String, val buckets: MutableList<String>, val luckOfTheSeaLevel: Int)
class BobberBucketChosenEvent(var chosenBucket: SpawnBucket, val buckets: MutableList<SpawnBucket>, val luckOfTheSeaLevel: Int)
@@ -12,7 +12,10 @@ import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -70,8 +73,8 @@ interface BobberSpawnPokemonEvent {
val bait: ItemStack,
val pokemon: PokemonEntity
) : BobberSpawnPokemonEvent {
val context = mutableMapOf(
"chosen_bucket" to StringValue(spawnAction.bucket),
val context = mutableMapOf<String, MoValue>(
"chosen_bucket" to ObjectValue(spawnAction.bucket, { it.name }),
"bait" to pokemon.level().itemRegistry.wrapAsHolder(bait.item).asMoLangValue(Registries.ITEM),
"pokemon_entity" to pokemon.asMoLangValue()
)
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.gui
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import net.minecraft.client.gui.components.AbstractWidget
import net.minecraft.client.gui.components.Renderable
import net.minecraft.client.gui.components.events.GuiEventListener
import net.minecraft.client.gui.narration.NarrationElementOutput
import net.minecraft.network.chat.Component
@@ -54,8 +55,6 @@ abstract class ParentWidget(
}
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (!active) return false
return children.any {
it.mouseClicked(pMouseX, pMouseY, pButton)
}
@@ -1,116 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.gui
import kotlin.math.max
import kotlin.math.min
interface TreeNode<T> {
val children: List<TreeNode<T>>
}
class TreeDrawingNode<T>(
val value: T,
val children: List<TreeDrawingNode<T>>,
) {
var x = 0
var y = 0
var offset = 0
}
fun <T> createDrawingTree(treeNode: TreeNode<T>): TreeDrawingNode<T> {
val root = setup(treeNode, 0, mutableListOf<Int>(), mutableListOf<Int>())
addMods(root, 0)
return root
}
@Suppress("UNCHECKED_CAST")
private fun <T> setup(
treeNode: TreeNode<T>,
depth: Int,
nexts: MutableList<Int>,
offset: MutableList<Int>,
): TreeDrawingNode<T> {
val children = treeNode.children.map { child ->
setup(child, depth + 1, nexts, offset)
}
val treeDrawingNode = TreeDrawingNode<T>(treeNode as T, children)
treeDrawingNode.y = depth
while (nexts.size <= depth) {
nexts.add(0)
}
while (offset.size <= depth) {
offset.add(0)
}
var place =
if (children.isEmpty()) {
nexts[depth]
} else if (children.size == 1) {
// children.first().x - 1
children.first().x
} else {
// (children[0].x + children[1].x) / 2
(children.first().x + children.last().x) / 2
}
offset[depth] = max(offset[depth], nexts[depth] - place)
if (children.isEmpty()) {
treeDrawingNode.x = place
} else {
treeDrawingNode.x = place + offset[depth]
}
nexts[depth] += 2
treeDrawingNode.offset = offset[depth]
return treeDrawingNode
}
private fun <T> addMods(
treeNode: TreeDrawingNode<T>,
modSum: Int,
) {
treeNode.x += modSum
val newModSum = modSum + treeNode.offset
treeNode.children.forEach { child ->
addMods(child, newModSum)
}
}
data class TreeDrawingBounds(val minX: Int, val maxX: Int, val minY: Int, val maxY: Int) {
val width = maxX - minX
val height = maxY - minY
val xCenter = (maxX + minX) / 2.0
val yCenter = (maxY + minY) / 2.0
}
fun <T> calculateBounds(treeDrawingNode: TreeDrawingNode<T>): TreeDrawingBounds {
var minX = treeDrawingNode.x
var maxX = treeDrawingNode.x
var minY = treeDrawingNode.y
var maxY = treeDrawingNode.y
for (child in treeDrawingNode.children) {
val childBounds = calculateBounds(child)
minX = min(minX, childBounds.minX)
maxX = max(maxX, childBounds.maxX)
minY = min(minY, childBounds.minY)
maxY = max(maxY, childBounds.maxY)
}
return TreeDrawingBounds(minX, maxX, minY, maxY)
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.habitats
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
/**
* A [HabitatPool] of [ActivatedHabitatSpawn]s. Mainly just to help with deserialization and ensuring
* a single type for everything that's applied to a habitat block.
*
* @author Hiroku
* @since February 13th, 2026
*/
class ActivatedHabitatPool : HabitatPool<ActivatedHabitatSpawn>() {
override val type: ResourceLocation = ActivatedHabitatSpawn.TYPE
override var spawns: List<ActivatedHabitatSpawn> = listOf()
companion object {
fun default() = ActivatedHabitatPool().apply {
id = ResourceLocation("cobblemon", "custom__default_activated_pool")
name = "Custom"
}
}
override fun decode(buffer: RegistryFriendlyByteBuf, buckets: List<SpawnBucket>) {
super.decode(buffer, buckets)
spawns = buffer.readList { ActivatedHabitatSpawn().apply { decode(buffer, buckets) } }
}
}
@@ -0,0 +1,23 @@
/*
* 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.habitats
import com.cobblemon.mod.common.util.cobblemonResource
/**
* A basic [HabitatSpawn] that will be triggered via activated spawning in a habitat. Nothing to it, really.
*
* @author Hiroku
* @since February 13th, 2026
*/
class ActivatedHabitatSpawn : HabitatSpawn() {
companion object {
val TYPE = cobblemonResource("activated")
}
}
@@ -8,81 +8,39 @@
package com.cobblemon.mod.common.api.habitats
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.Tag
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
/**
* A pool of [HabitatSpawn]s. This is filled with [HabitatSpawn]s. The pool is responsible for converting
* A pool of [HabitatSpawn]s. This is either filled with [NaturalHabitatSpawn]s or [ActivatedHabitatSpawn]s depending
* on whether the pool is a [NaturalHabitatPool] or an [ActivatedHabitatPool]. The pool is responsible for converting
* its spawns into [PokemonSpawnDetail]s.
*
* @author Hiroku
* @since February 13th, 2026
*/
class HabitatPool {
abstract class HabitatPool<T : HabitatSpawn> {
companion object {
fun default() = HabitatPool().apply {
id = ResourceLocation("cobblemon", "custom__default_pool")
name = "Custom"
}
fun writePoolToNBT(pool: HabitatPool, nbt: CompoundTag) {
nbt.putString(DataKeys.HABITAT_POOL_ID, pool.id.toString())
if (pool.id.path.startsWith("custom__")) {
nbt.putString(DataKeys.HABITAT_POOL_NAME, pool.name)
val spawnsListNBT = ListTag()
for (spawn in pool.spawns) {
val spawnTag = CompoundTag()
spawn.writeToNBT(spawnTag)
spawnsListNBT.add(spawnTag)
}
nbt.put(DataKeys.HABITAT_POOL_SPAWNS, spawnsListNBT)
}
}
fun readPoolFromNBT(nbt: CompoundTag): HabitatPool {
val poolId = nbt.getString(DataKeys.HABITAT_POOL_ID).asIdentifierDefaultingNamespace()
if (nbt.contains(DataKeys.HABITAT_POOL_NAME)) {
// It's one that was stored on the block
val pool = HabitatPool()
pool.id = poolId
pool.name = nbt.getString(DataKeys.HABITAT_POOL_NAME)
val spawnsListNBT = nbt.getList(DataKeys.HABITAT_POOL_SPAWNS, Tag.TAG_COMPOUND.toInt())
val spawns = mutableListOf<HabitatSpawn>()
spawnsListNBT.forEach { spawnTag ->
try {
val spawn = HabitatSpawn().apply { readFromNBT(spawnTag as CompoundTag) }
spawns.add(spawn)
} catch (_: Exception) {
// If there's an error reading a spawn, skip it and log the error.
Cobblemon.LOGGER.error("Error reading activated habitat spawn from NBT for habitat block, skipping.")
}
}
pool.spawns = spawns
return pool
} else {
return HabitatPools.habitatPoolsById[poolId] ?: HabitatPool.default()
}
}
val types = mutableMapOf(
ActivatedHabitatSpawn.TYPE to ActivatedHabitatPool::class.java,
NaturalHabitatSpawn.TYPE to NaturalHabitatPool::class.java
)
}
lateinit var id: ResourceLocation
var name: String = ""
var spawns: List<HabitatSpawn> = listOf()
abstract val type: ResourceLocation
abstract var spawns: List<T>
/** Whether a pool is editable is just down to whether it's not from a datapack. */
@Transient
var editable: Boolean = false
fun validate() {
open fun validate() {
for (spawn in spawns) {
if (SpawnablePosition.getByName(spawn.spawnablePositionType) == null) {
throw IllegalArgumentException("$id mentions non-existent spawnable position type ${spawn.spawnablePositionType} on spawn for ${spawn.species}")
@@ -100,9 +58,8 @@ class HabitatPool {
buffer.writeCollection(spawns) { _, it -> it.encode(buffer) }
}
fun decode(buffer: RegistryFriendlyByteBuf) {
open fun decode(buffer: RegistryFriendlyByteBuf, buckets: List<SpawnBucket>) {
id = buffer.readResourceLocation()
name = buffer.readUtf()
spawns = buffer.readList { HabitatSpawn().apply { decode(buffer) } }
}
}
@@ -12,13 +12,16 @@ import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.IntRanges
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.spawning.position.SpawnablePositionType
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.adapters.HabitatPoolAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.IntRangeAdapter
import com.cobblemon.mod.common.util.adapters.IntRangesAdapter
import com.cobblemon.mod.common.util.adapters.RegisteredSpawnablePositionAdapter
import com.cobblemon.mod.common.util.adapters.SpawnBucketAdapter
import com.cobblemon.mod.common.util.adapters.SpeciesAdapter
import com.cobblemon.mod.common.util.adapters.pokemonPropertiesShortAdapter
import com.cobblemon.mod.common.util.cobblemonResource
@@ -33,16 +36,18 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.world.level.storage.LevelResource
object HabitatPools : JsonDataRegistry<HabitatPool> {
object HabitatPools : JsonDataRegistry<HabitatPool<*>> {
override val id = cobblemonResource("habitat_pools")
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<HabitatPools>()
override val typeToken: TypeToken<HabitatPool> = TypeToken.get(HabitatPool::class.java)
override val typeToken: TypeToken<HabitatPool<*>> = TypeToken.get(HabitatPool::class.java)
override val resourcePath = "habitat_pools"
override val gson: Gson = GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(HabitatPool::class.java, HabitatPoolAdapter)
.registerTypeAdapter(SpawnablePositionType::class.java, RegisteredSpawnablePositionAdapter)
.registerTypeAdapter(SpawnBucket::class.java, SpawnBucketAdapter)
.registerTypeAdapter(Species::class.java, SpeciesAdapter)
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapter(TimeRange::class.java, TimeRange.adapter)
@@ -51,10 +56,10 @@ object HabitatPools : JsonDataRegistry<HabitatPool> {
.setPrettyPrinting()
.create()
private val datapacked = mutableMapOf<ResourceLocation, HabitatPool>()
private val worldSaved = mutableMapOf<ResourceLocation, HabitatPool>()
private val datapacked = mutableMapOf<ResourceLocation, HabitatPool<*>>()
private val worldSaved = mutableMapOf<ResourceLocation, HabitatPool<*>>()
val habitatPoolsById = mutableMapOf<ResourceLocation, HabitatPool>()
val habitatPoolsById = mutableMapOf<ResourceLocation, HabitatPool<*>>()
private lateinit var worldSavedPoolsFolder: File
@@ -68,7 +73,7 @@ object HabitatPools : JsonDataRegistry<HabitatPool> {
worldSavedPoolsFolder.listFiles { it.endsWith(".json") }.forEach {
val identifier = ResourceLocation.parse(it.nameWithoutExtension)
val habitat = gson.fromJson<HabitatPool>(it.reader())
val habitat = gson.fromJson<HabitatPool<*>>(it.reader())
habitat.id = identifier
habitat.validate()
habitat.editable = true
@@ -77,7 +82,7 @@ object HabitatPools : JsonDataRegistry<HabitatPool> {
setupHabitatPoolsById()
}
override fun reload(data: Map<ResourceLocation, HabitatPool>) {
override fun reload(data: Map<ResourceLocation, HabitatPool<*>>) {
datapacked.clear()
datapacked.putAll(data)
data.entries.forEach { (identifier, habitat) ->
@@ -10,7 +10,9 @@ package com.cobblemon.mod.common.api.habitats
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.IntRanges
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.spawning.condition.AppendageCondition
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
@@ -28,7 +30,9 @@ import net.minecraft.nbt.CompoundTag
import net.minecraft.network.RegistryFriendlyByteBuf
/**
* A kind of simplified spawn definition for the purposes of spawning from [HabitatBlockEntity] instances.
* A kind of simplified spawn definition for the purposes of spawning from [HabitatBlockEntity] instances. This is a
* very shallow class tree, with [ActivatedHabitatSpawn] being a trivial subclass but the other subclass being
* [NaturalHabitatSpawn] which introduces the need to supply a spawn bucket.
*
* These have only a small subset of the properties that a regular [PokemonSpawnDetail] would have to align with the
* fact that these are configured from a simple GUI. There are limits to how many properties and conditions could be
@@ -42,13 +46,11 @@ import net.minecraft.network.RegistryFriendlyByteBuf
* @author Hiroku
* @since February 13th, 2026
*/
class HabitatSpawn {
sealed class HabitatSpawn {
/** The species that will be spawned. */
lateinit var species: Species
/** The kind of position the Pokémon will spawn in. Things like "grounded", "seafloor", "fishing". */
lateinit var spawnablePositionType: String
/** The bucket to spawn from. */
var bucket: String = "common"
/** Selection weight. */
var weight: Float = 1F
@@ -75,7 +77,6 @@ class HabitatSpawn {
timeRange?.let { nbt.putString(DataKeys.HABITAT_POOL_SPAWN_TIMES, TimeRange.adapter.serialize(it)) }
minLight?.let { nbt.putInt(DataKeys.HABITAT_POOL_SPAWN_MIN_LIGHT, it) }
maxLight?.let { nbt.putInt(DataKeys.HABITAT_POOL_SPAWN_MAX_LIGHT, it) }
nbt.putString(DataKeys.HABITAT_POOL_SPAWN_BUCKET, bucket)
}
open fun readFromNBT(nbt: CompoundTag) {
@@ -88,7 +89,6 @@ class HabitatSpawn {
timeRange = if (nbt.contains(DataKeys.HABITAT_POOL_SPAWN_TIMES)) TimeRange.adapter.deserialize(nbt.getString(DataKeys.HABITAT_POOL_SPAWN_TIMES)) else null
minLight = if (nbt.contains(DataKeys.HABITAT_POOL_SPAWN_MIN_LIGHT)) nbt.getInt(DataKeys.HABITAT_POOL_SPAWN_MIN_LIGHT) else null
maxLight = if (nbt.contains(DataKeys.HABITAT_POOL_SPAWN_MAX_LIGHT)) nbt.getInt(DataKeys.HABITAT_POOL_SPAWN_MAX_LIGHT) else null
bucket = if (nbt.contains(DataKeys.HABITAT_POOL_SPAWN_BUCKET)) nbt.getString(DataKeys.HABITAT_POOL_SPAWN_BUCKET) else "common"
}
open fun encode(buffer: RegistryFriendlyByteBuf) {
@@ -101,10 +101,12 @@ class HabitatSpawn {
buffer.writeNullable(timeRange) { _, value -> buffer.writeUtf(TimeRange.adapter.serialize(value)) }
buffer.writeNullable(minLight) { _, value -> buffer.writeInt(value) }
buffer.writeNullable(maxLight) { _, value -> buffer.writeInt(value) }
buffer.writeUtf(bucket)
}
open fun decode(buffer: RegistryFriendlyByteBuf) {
open fun decode(
buffer: RegistryFriendlyByteBuf,
buckets: List<SpawnBucket>
) {
species = PokemonSpecies.getByIdentifier(buffer.readResourceLocation())!!
spawnablePositionType = buffer.readUtf()
weight = buffer.readFloat()
@@ -114,7 +116,6 @@ class HabitatSpawn {
timeRange = buffer.readNullable { _ -> TimeRange.adapter.deserialize(buffer.readUtf()) }
minLight = buffer.readNullable { _ -> buffer.readInt() }
maxLight = buffer.readNullable { _ -> buffer.readInt() }
bucket = buffer.readUtf()
}
/** Creates the [PokemonSpawnDetail] that will be used in the spawner. This is done per habitat block. */
@@ -137,7 +138,7 @@ class HabitatSpawn {
spawnDetail.apply {
this.id = "habitat_${species.resourceIdentifier}"
this.pokemon = properties
this.bucket = this@HabitatSpawn.bucket
this.bucket = BestSpawner.config.buckets.first { it.weight > 0 }
this.weight = this@HabitatSpawn.weight
this.levelRange = levelRange
this.conditions = mutableListOf(condition)
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.habitats
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
/**
* A [HabitatPool] of [NaturalHabitatSpawn]s. Mainly just to help with deserialization and ensuring
* a single type for everything that's applied to a habitat block.
*
* @author Hiroku
* @since February 13th, 2026
*/
class NaturalHabitatPool : HabitatPool<NaturalHabitatSpawn>() {
override val type: ResourceLocation = NaturalHabitatSpawn.TYPE
override var spawns: List<NaturalHabitatSpawn> = listOf()
companion object {
fun default() = NaturalHabitatPool().apply {
id = ResourceLocation("cobblemon", "custom__default_natural_pool")
name = "Custom"
}
}
override fun decode(buffer: RegistryFriendlyByteBuf, buckets: List<SpawnBucket>) {
super.decode(buffer, buckets)
spawns = buffer.readList { NaturalHabitatSpawn().apply { decode(buffer, buckets) } }
}
}
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.habitats
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.RegistryFriendlyByteBuf
/**
* A type of [HabitatSpawn] that spawns Pokémon as part of natural world spawning, and therefore
* needs to indicate the [SpawnBucket] it will be part of.
*
* @author Hiroku
* @since February 13th, 2026
*/
class NaturalHabitatSpawn : HabitatSpawn() {
companion object {
val TYPE = cobblemonResource("natural")
}
lateinit var bucket: SpawnBucket
override fun createSpawnDetail(habitatBlockEntity: HabitatBlockEntity): PokemonSpawnDetail? {
val spawnDetail = super.createSpawnDetail(habitatBlockEntity)
spawnDetail?.bucket = bucket
return spawnDetail
}
override fun writeToNBT(nbt: CompoundTag) {
super.writeToNBT(nbt)
nbt.putString(DataKeys.HABITAT_POOL_SPAWN_BUCKET, bucket.name)
}
override fun readFromNBT(nbt: CompoundTag) {
super.readFromNBT(nbt)
bucket = Cobblemon.bestSpawner.config.buckets.first { it.name == nbt.getString(DataKeys.HABITAT_POOL_SPAWN_BUCKET) }
}
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeUtf(bucket.name)
}
override fun decode(
buffer: RegistryFriendlyByteBuf,
buckets: List<SpawnBucket>
) {
super.decode(buffer, buckets)
val bucketName = buffer.readUtf()
bucket = buckets.first { it.name == bucketName }
}
}
@@ -8,7 +8,9 @@
package com.cobblemon.mod.common.api.habitats.dto
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatPool
import com.cobblemon.mod.common.api.habitats.spawningstyle.ActivatedHabitatSpawning
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import net.minecraft.network.RegistryFriendlyByteBuf
class ActivatedHabitatSettingsDTO() {
@@ -18,6 +20,7 @@ class ActivatedHabitatSettingsDTO() {
var spawnRange = 16
var maxSpawns = -1
var maxSpawnsPerActivation = 1
var habitatPool: HabitatPoolSettingsDTO = HabitatPoolSettingsDTO(ActivatedHabitatPool.default())
constructor(activatedHabitatSpawning: ActivatedHabitatSpawning): this() {
this.chance = activatedHabitatSpawning.chance
@@ -26,6 +29,7 @@ class ActivatedHabitatSettingsDTO() {
this.spawnRange = activatedHabitatSpawning.spawnRange
this.maxSpawns = activatedHabitatSpawning.maxSpawns
this.maxSpawnsPerActivation = activatedHabitatSpawning.maxSpawnsPerActivation
this.habitatPool = HabitatPoolSettingsDTO(activatedHabitatSpawning.pool)
}
fun encode(buffer: RegistryFriendlyByteBuf) {
@@ -35,14 +39,20 @@ class ActivatedHabitatSettingsDTO() {
buffer.writeInt(spawnRange)
buffer.writeInt(maxSpawns)
buffer.writeInt(maxSpawnsPerActivation)
habitatPool.encode(buffer)
}
fun decode(buffer: RegistryFriendlyByteBuf) {
fun decode(
buffer: RegistryFriendlyByteBuf,
buckets: List<SpawnBucket>
) {
this.chance = buffer.readFloat()
this.trigger = buffer.readEnum(ActivatedHabitatSpawning.Trigger::class.java)
this.cancelledNaturalSpawningRange = buffer.readInt()
this.spawnRange = buffer.readInt()
this.maxSpawns = buffer.readInt()
this.maxSpawnsPerActivation = buffer.readInt()
this.habitatPool = HabitatPoolSettingsDTO()
habitatPool.decode(buffer, buckets)
}
}
@@ -9,23 +9,30 @@
package com.cobblemon.mod.common.api.habitats.dto
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatPool
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatSpawn
import com.cobblemon.mod.common.api.habitats.HabitatPool
import com.cobblemon.mod.common.api.habitats.HabitatPools
import com.cobblemon.mod.common.api.habitats.HabitatSpawn
import com.cobblemon.mod.common.api.habitats.NaturalHabitatPool
import com.cobblemon.mod.common.api.habitats.NaturalHabitatSpawn
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
class HabitatPoolSettingsDTO() {
lateinit var id: ResourceLocation
var isReference = false
var isActivated = false
// Below properties are only there when it's not a reference, otherwise the handler will have to look them up from the id
var name: String? = null
var spawns: MutableList<HabitatSpawn>? = null
constructor(habitatPool: HabitatPool): this() {
constructor(habitatPool: HabitatPool<*>): this() {
this.id = habitatPool.id
this.isReference = !id.path.startsWith("custom__")
this.isActivated = habitatPool is ActivatedHabitatPool
if (!this.isReference) {
this.name = habitatPool.name
this.spawns = habitatPool.spawns.toMutableList()
@@ -35,6 +42,7 @@ class HabitatPoolSettingsDTO() {
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(id)
buffer.writeBoolean(isReference)
buffer.writeBoolean(isActivated)
if (!isReference) {
buffer.writeUtf(name!!)
buffer.writeCollection(spawns!!) { _, spawn ->
@@ -44,32 +52,50 @@ class HabitatPoolSettingsDTO() {
}
fun decode(
buffer: RegistryFriendlyByteBuf
buffer: RegistryFriendlyByteBuf,
buckets: List<SpawnBucket>
) {
this.id = buffer.readResourceLocation()
this.isReference = buffer.readBoolean()
this.isActivated = buffer.readBoolean()
if (!isReference) {
this.name = buffer.readUtf()
this.spawns = buffer.readList {
val spawn = HabitatSpawn()
spawn.decode(buffer)
val spawn = if (isActivated) {
ActivatedHabitatSpawn()
} else {
NaturalHabitatSpawn()
}
spawn.decode(buffer, buckets)
spawn
}.toMutableList()
}
}
fun toHabitatPool(): HabitatPool? {
fun toHabitatPool(): HabitatPool<*>? {
if (isReference) {
val pool = HabitatPools.habitatPoolsById[id]
if (pool == null) {
Cobblemon.LOGGER.error("Could not find habitat pool with id $id")
} else if (pool is ActivatedHabitatPool && !isActivated) {
Cobblemon.LOGGER.error("Pool with id $id is an activated habitat pool but was deserialized as a natural pool")
} else if (pool !is ActivatedHabitatPool && isActivated) {
Cobblemon.LOGGER.error("Pool with id $id is a natural habitat pool but was deserialized as an activated pool")
}
return pool
}
val pool = HabitatPool()
pool.id = id
pool.name = name!!
pool.spawns = spawns!!
return pool
return if (isActivated) {
val pool = ActivatedHabitatPool()
pool.id = id
pool.name = name!!
pool.spawns = spawns!!.map { it as ActivatedHabitatSpawn }
pool
} else {
val pool = NaturalHabitatPool()
pool.id = id
pool.name = name!!
pool.spawns = spawns!!.map { it as NaturalHabitatSpawn }
pool
}
}
}
@@ -9,9 +9,9 @@
package com.cobblemon.mod.common.api.habitats.dto
import com.cobblemon.mod.common.api.habitats.HabitatPhaseOrder
import com.cobblemon.mod.common.api.habitats.HabitatPool
import com.cobblemon.mod.common.api.habitats.spawningstyle.ActivatedHabitatSpawning
import com.cobblemon.mod.common.api.habitats.spawningstyle.NaturalHabitatSpawning
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
@@ -23,7 +23,6 @@ class HabitatSettingsDTO() {
var modifiers: String = ""
lateinit var mimicId: ResourceLocation
var isActivatedSpawning = false
var pool: HabitatPoolSettingsDTO = HabitatPoolSettingsDTO(HabitatPool.default())
var activatedSettings: ActivatedHabitatSettingsDTO? = null
var naturalSettings: NaturalHabitatSettingsDTO? = null
@@ -33,7 +32,6 @@ class HabitatSettingsDTO() {
this.levelRange = habitatBlockEntity.levelRange
this.modifiers = habitatBlockEntity.modifiers.asString()
this.mimicId = habitatBlockEntity.mimicId
this.pool = HabitatPoolSettingsDTO(habitatBlockEntity.pool)
val style = habitatBlockEntity.spawningStyle
if (style is ActivatedHabitatSpawning) {
this.isActivatedSpawning = true
@@ -50,7 +48,6 @@ class HabitatSettingsDTO() {
buffer.writeInt(levelRange.last)
buffer.writeUtf(modifiers)
buffer.writeResourceLocation(mimicId)
pool.encode(buffer)
buffer.writeBoolean(isActivatedSpawning)
if (isActivatedSpawning) {
activatedSettings?.encode(buffer)
@@ -59,7 +56,10 @@ class HabitatSettingsDTO() {
}
}
fun decode(buffer: RegistryFriendlyByteBuf) {
fun decode(
buffer: RegistryFriendlyByteBuf,
buckets: List<SpawnBucket>
) {
this.numberOfPhases = buffer.readInt()
this.phaseOrder = buffer.readEnum(HabitatPhaseOrder::class.java)
val levelRangeStart = buffer.readInt()
@@ -67,18 +67,16 @@ class HabitatSettingsDTO() {
this.levelRange = levelRangeStart..levelRangeEnd
this.modifiers = buffer.readUtf()
this.mimicId = buffer.readResourceLocation()
this.pool = HabitatPoolSettingsDTO()
this.pool.decode(buffer)
if (buffer.readBoolean()) {
this.isActivatedSpawning = true
this.naturalSettings = null
val activatedSettings = ActivatedHabitatSettingsDTO()
activatedSettings.decode(buffer)
activatedSettings.decode(buffer, buckets)
this.activatedSettings = activatedSettings
} else {
this.activatedSettings = null
val naturalSettings = NaturalHabitatSettingsDTO()
naturalSettings.decode(buffer)
naturalSettings.decode(buffer, buckets)
this.naturalSettings = naturalSettings
}
}
@@ -8,25 +8,35 @@
package com.cobblemon.mod.common.api.habitats.dto
import com.cobblemon.mod.common.api.habitats.NaturalHabitatPool
import com.cobblemon.mod.common.api.habitats.spawningstyle.NaturalHabitatSpawning
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import net.minecraft.network.RegistryFriendlyByteBuf
class NaturalHabitatSettingsDTO() {
var replaceSpawns = false
var rangeOfInfluence = 16
var habitatPool: HabitatPoolSettingsDTO = HabitatPoolSettingsDTO(NaturalHabitatPool.default())
constructor(naturalHabitatSpawning: NaturalHabitatSpawning): this() {
this.replaceSpawns = naturalHabitatSpawning.replaceSpawns
this.rangeOfInfluence = naturalHabitatSpawning.rangeOfInfluence
this.habitatPool = HabitatPoolSettingsDTO(naturalHabitatSpawning.pool)
}
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeBoolean(replaceSpawns)
buffer.writeInt(rangeOfInfluence)
habitatPool.encode(buffer)
}
fun decode(buffer: RegistryFriendlyByteBuf, ) {
fun decode(
buffer: RegistryFriendlyByteBuf,
buckets: List<SpawnBucket>
) {
this.replaceSpawns = buffer.readBoolean()
this.rangeOfInfluence = buffer.readInt()
this.habitatPool = HabitatPoolSettingsDTO()
habitatPool.decode(buffer, buckets)
}
}
@@ -8,16 +8,17 @@
package com.cobblemon.mod.common.api.habitats.spawningstyle
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.habitats.HabitatSpawnActivatedEvent
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatPool
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatSpawn
import com.cobblemon.mod.common.api.habitats.ActivatedHabitatSpawningInfluence
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.api.spawning.spawner.FixedAreaSpawner
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag
import net.minecraft.server.level.ServerLevel
@@ -26,7 +27,7 @@ import net.minecraft.server.level.ServerLevel
* A habitat spawning style defined by being triggered by something. The activation trigger, [trigger], is either
* a redstone pulse, every tick, or every random tick.
*
* The spawn details are generated from the pool every time there is some change to the habitat block's settings,
* The spawn details are generated from the [pool] every time there is some change to the habitat block's settings,
* then those spawn details are used for spawning Pokémon when the habitat block is triggered.
*
* The [chance] property is applied after the trigger logic so that not every activation necessarily results in a spawn
@@ -36,10 +37,6 @@ import net.minecraft.server.level.ServerLevel
* @since February 14th, 2026
*/
class ActivatedHabitatSpawning(val habitatBlockEntity: HabitatBlockEntity) : HabitatSpawningStyle {
companion object {
val TYPE = cobblemonResource("activated")
}
enum class Trigger {
/** Requires a redstone pulse. */
REDSTONE,
@@ -49,8 +46,7 @@ class ActivatedHabitatSpawning(val habitatBlockEntity: HabitatBlockEntity) : Hab
RANDOM_TICK
}
@Transient
override val type = TYPE
override val type = ActivatedHabitatSpawn.TYPE
/** The chance that an activation by the [trigger] will result in a spawn attempt. The spawn attempt may still fail. */
var chance = 1F
@@ -64,7 +60,11 @@ class ActivatedHabitatSpawning(val habitatBlockEntity: HabitatBlockEntity) : Hab
var maxSpawns = -1
/** The maximum number of Pokémon that can spawn in a single activation. This will still be capped by [maxSpawns]. */
var maxSpawnsPerActivation = 1
/** The pool of spawns to choose from. */
var pool: ActivatedHabitatPool = ActivatedHabitatPool.default()
// This is cached here so that it doesn't have to be regenerated every time the block is triggered, updated when settings change.
var spawnDetails: List<SpawnDetail> = listOf()
lateinit var spawner: FixedAreaSpawner
override fun writeToNBT(nbt: CompoundTag) {
@@ -75,6 +75,10 @@ class ActivatedHabitatSpawning(val habitatBlockEntity: HabitatBlockEntity) : Hab
nbt.putInt(DataKeys.HABITAT_ACTIVATED_SPAWN_RANGE, spawnRange)
nbt.putInt(DataKeys.HABITAT_ACTIVATED_MAX_SPAWNS, maxSpawns)
nbt.putInt(DataKeys.HABITAT_ACTIVATED_MAX_SPAWNS_PER_ACTIVATION, maxSpawnsPerActivation)
val poolNBT = CompoundTag()
writePoolToNBT(pool, poolNBT)
nbt.put(DataKeys.HABITAT_POOL, poolNBT)
}
override fun readFromNBT(nbt: CompoundTag) {
@@ -84,18 +88,25 @@ class ActivatedHabitatSpawning(val habitatBlockEntity: HabitatBlockEntity) : Hab
spawnRange = nbt.getInt(DataKeys.HABITAT_ACTIVATED_SPAWN_RANGE)
maxSpawns = nbt.getInt(DataKeys.HABITAT_ACTIVATED_MAX_SPAWNS)
maxSpawnsPerActivation = nbt.getInt(DataKeys.HABITAT_ACTIVATED_MAX_SPAWNS_PER_ACTIVATION)
pool = HabitatSpawningStyle.readPoolFromNBT(
nbt = nbt.getCompound(DataKeys.HABITAT_POOL),
poolInitializer = ::ActivatedHabitatPool,
spawnInitializer = ::ActivatedHabitatSpawn,
defaultPool = ActivatedHabitatPool::default
)
}
fun generateSpawnDetails(world: ServerLevel, pos: BlockPos) {
spawnDetails = pool.createSpawnDetails(habitatBlockEntity)
spawner = FixedAreaSpawner(
name = "habitat_spawner_$pos",
world = world,
position = pos,
spawnPool = SpawnPool("habitat_block").also {
it.details.addAll(habitatBlockEntity.spawnDetails)
it.details.addAll(spawnDetails)
it.precalculate()
},
buckets = Cobblemon.bestSpawner.config.activatedHabitatBuckets,
maxPokemonPerChunk = 16F,
verticalRadius = spawnRange,
horizontalRadius = spawnRange
@@ -107,7 +118,7 @@ class ActivatedHabitatSpawning(val habitatBlockEntity: HabitatBlockEntity) : Hab
// Check if the spawn attempt should even happen based on chance.
if (chance < 1F && world.random.nextFloat() >= chance) {
return
} else if (habitatBlockEntity.spawnDetails.isEmpty()) {
} else if (spawnDetails.isEmpty()) {
// If there are no spawn details, then there's no point in trying to spawn anything.
return
}
@@ -8,7 +8,15 @@
package com.cobblemon.mod.common.api.habitats.spawningstyle
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.habitats.HabitatPool
import com.cobblemon.mod.common.api.habitats.HabitatPools
import com.cobblemon.mod.common.api.habitats.HabitatSpawn
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceLocation
/**
@@ -25,4 +33,47 @@ sealed interface HabitatSpawningStyle {
fun writeToNBT(nbt: CompoundTag)
fun readFromNBT(nbt: CompoundTag)
fun writePoolToNBT(pool: HabitatPool<*>, nbt: CompoundTag) {
nbt.putString(DataKeys.HABITAT_POOL_ID, pool.id.toString())
if (pool.id.path.startsWith("custom__")) {
nbt.putString(DataKeys.HABITAT_POOL_NAME, pool.name)
val spawnsListNBT = ListTag()
for (spawn in pool.spawns) {
val spawnTag = CompoundTag()
spawn.writeToNBT(spawnTag)
spawnsListNBT.add(spawnTag)
}
nbt.put(DataKeys.HABITAT_POOL_SPAWNS, spawnsListNBT)
}
}
companion object {
fun <T : HabitatSpawn, P : HabitatPool<T>> readPoolFromNBT(nbt: CompoundTag, poolInitializer: () -> P, spawnInitializer: () -> T, defaultPool: () -> P): P {
var pool: P
val poolId = nbt.getString(DataKeys.HABITAT_POOL_ID).asIdentifierDefaultingNamespace()
if (nbt.contains(DataKeys.HABITAT_POOL_NAME)) {
// It's one that was stored on the block
pool = poolInitializer()
pool.id = poolId
pool.name = nbt.getString(DataKeys.HABITAT_POOL_NAME)
val spawnsListNBT = nbt.getList(DataKeys.HABITAT_POOL_SPAWNS, Tag.TAG_COMPOUND.toInt())
val spawns = mutableListOf<T>()
spawnsListNBT.forEach { spawnTag ->
try {
val spawn = spawnInitializer().apply { readFromNBT(spawnTag as CompoundTag) }
spawns.add(spawn)
} catch (_: Exception) {
// If there's an error reading a spawn, skip it and log the error.
Cobblemon.LOGGER.error("Error reading activated habitat spawn from NBT for habitat block, skipping.")
}
}
pool.spawns = spawns
} else {
pool = HabitatPools.habitatPoolsById[poolId] as? P ?: defaultPool()
}
return pool
}
}
}
@@ -8,18 +8,21 @@
package com.cobblemon.mod.common.api.habitats.spawningstyle
import com.cobblemon.mod.common.api.habitats.NaturalHabitatPool
import com.cobblemon.mod.common.api.habitats.NaturalHabitatSpawn
import com.cobblemon.mod.common.api.habitats.spawningstyle.HabitatSpawningStyle.Companion.readPoolFromNBT
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.nbt.CompoundTag
import net.minecraft.resources.ResourceLocation
/**
* A habitat spawning style defined by tapping into regular spawning methods. This works by adding to the spawn list
* for spawning that's occurring nearby. When [replaceSpawns] is set to true, the original spawns will be
* removed completely in favour of the spawns provided in this habitat's pool.
* removed completely in favour of the spawns provided in this habitat's [pool].
*
* The spawn details are generated from the pool every time there is some change to the habitat block's settings,
* The spawn details are generated from the [pool] every time there is some change to the habitat block's settings,
* then those spawn details are used for spawning Pokémon when world spawning is occurring nearby.
*
* Whether or not this interactions with overworld or fishing spawns is determined by whether the habitat pool has
@@ -29,13 +32,10 @@ import net.minecraft.resources.ResourceLocation
* @since February 22nd, 2026
*/
class NaturalHabitatSpawning(val habitatBlockEntity: HabitatBlockEntity) : HabitatSpawningStyle {
companion object {
val TYPE = cobblemonResource("natural")
}
@Transient
override val type: ResourceLocation = TYPE
override val type: ResourceLocation = NaturalHabitatSpawn.TYPE
var pool: NaturalHabitatPool = NaturalHabitatPool.default()
var replaceSpawns = false
var rangeOfInfluence = 16
@@ -43,19 +43,33 @@ class NaturalHabitatSpawning(val habitatBlockEntity: HabitatBlockEntity) : Habit
var affectsFishing = false
@Transient
var affectsOverworld = false
@Transient
var spawnDetails: List<SpawnDetail> = listOf()
fun generateSpawnDetails() {
affectsFishing = habitatBlockEntity.spawnDetails.any { it.spawnablePositionType.name == "fishing" }
affectsOverworld = habitatBlockEntity.spawnDetails.any { it.spawnablePositionType.name != "fishing" }
spawnDetails = pool.createSpawnDetails(habitatBlockEntity = habitatBlockEntity)
affectsFishing = spawnDetails.any { it.spawnablePositionType.name == "fishing" }
affectsOverworld = spawnDetails.any { it.spawnablePositionType.name != "fishing" }
}
override fun writeToNBT(nbt: CompoundTag) {
nbt.putBoolean(DataKeys.HABITAT_NATURAL_REPLACE_SPAWNS, replaceSpawns)
nbt.putInt(DataKeys.HABITAT_NATURAL_RANGE_OF_INFLUENCE, rangeOfInfluence)
val poolNBT = CompoundTag()
writePoolToNBT(pool, poolNBT)
nbt.put(DataKeys.HABITAT_POOL, poolNBT)
}
override fun readFromNBT(nbt: CompoundTag) {
replaceSpawns = nbt.getBoolean(DataKeys.HABITAT_NATURAL_REPLACE_SPAWNS)
rangeOfInfluence = nbt.getInt(DataKeys.HABITAT_NATURAL_RANGE_OF_INFLUENCE)
pool = readPoolFromNBT(
nbt = nbt.getCompound(DataKeys.HABITAT_POOL),
poolInitializer = ::NaturalHabitatPool,
spawnInitializer = ::NaturalHabitatSpawn,
defaultPool = NaturalHabitatPool::default
)
}
}
@@ -51,15 +51,11 @@ data class PokemonInteractionSet(
}
data class PokemonInteraction(
val grouping: ResourceLocation,
val requirements: List<Requirement> = listOf(),
val effects: List<InteractionEffect> = listOf(),
val cooldown: ExpressionLike = "0".asExpressionLike()
) {
// Not put as a necessary constructor parameter because then there isn't a default constructor
// and that means Gson just gives null for everything in the constructor and falls back to the JSON
// properties - completely ignoring the default values above.
lateinit var grouping: ResourceLocation
fun encode(buffer: FriendlyByteBuf) {
buffer.writeResourceLocation(grouping)
buffer.writeCollection(requirements) { it, requirement ->
@@ -80,12 +76,11 @@ data class PokemonInteraction(
val cooldown = buffer.readExpressionLike()
return PokemonInteraction(
grouping,
requirements,
effects,
cooldown,
).also {
it.grouping = grouping
}
)
}
}
}
@@ -38,7 +38,7 @@ import net.minecraft.world.phys.AABB
* @since July 29th, 2023
*/
interface PokemonSelectingItem {
fun use(player: ServerPlayer, stack: ItemStack, ignoreShift: Boolean = false): InteractionResultHolder<ItemStack> {
fun use(player: ServerPlayer, stack: ItemStack): InteractionResultHolder<ItemStack> {
val range = player.entityInteractionRange()
val entity = player.level()
.getEntities(player, AABB.ofSize(player.position(), range, range, range))
@@ -60,7 +60,7 @@ interface PokemonSelectingItem {
return interactWithSpecificBattle(player, stack, battlePokemon)
}
} ?: run {
if (ignoreShift || !player.isShiftKeyDown) {
if (!player.isShiftKeyDown) {
return if (entity != null) {
val pokemon = entity.pokemon
if (entity.ownerUUID == player.uuid) {
@@ -109,7 +109,7 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
DoubleValue(pokemon.species.nationalPokedexNumber.toDouble())
}
map["types"] = {
pokemon.form.types.map { it.showdownId }.asArrayValue(::StringValue)
pokemon.form.types.map { it.toString() }.asArrayValue(::StringValue)
}
map["gender_ratio"] = {
DoubleValue(pokemon.form.maleRatio.toDouble())
@@ -12,6 +12,7 @@ import com.bedrockk.molang.runtime.struct.MoStruct
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
import com.cobblemon.mod.common.api.moves.categories.DamageCategory
import com.cobblemon.mod.common.api.types.ElementalType
@@ -73,6 +74,7 @@ open class MoveTemplate(
.addFunction("weight") { DoubleValue(weight.toDouble()) }
}
val displayName: MutableComponent
get() = lang("move.$name")
val description: MutableComponent
@@ -72,40 +72,6 @@ abstract class AbstractPokedexManager {
} ?: PokedexEntryProgress.UNREGISTERED
}
fun getHighestKnowledgeFor(entry: PokedexEntry, form: PokedexForm?): PokedexEntryProgress {
if (form == null) return getHighestKnowledgeFor(entry)
val speciesRecord = getSpeciesRecord(entry.speciesId) ?: return PokedexEntryProgress.UNREGISTERED
val hasAllAspects = entry.conditionAspects.all(speciesRecord::hasAspect)
if (!hasAllAspects) {
return PokedexEntryProgress.UNREGISTERED
}
return speciesRecord.getFormRecord(form.displayForm)?.knowledge ?: PokedexEntryProgress.UNREGISTERED
}
fun getHighestKnowledgeFor(entry: PokedexEntry, form: PokedexForm?, aspects: Iterable<String>): PokedexEntryProgress {
val formKnowledge = getHighestKnowledgeFor(entry, form)
if (formKnowledge == PokedexEntryProgress.UNREGISTERED) return PokedexEntryProgress.UNREGISTERED
val seenAspects = getSeenAspects(entry)
val allSeenAspects = aspects
// TODO this is ugly
.filter { it ->
if (Dexes.equivalentFormsLookup[it] != null) return@filter false
if (it.uppercase() in setOf(Gender.MALE.name, Gender.FEMALE.name, Gender.GENDERLESS.name)) return@filter false
if ("shiny".equals(it, ignoreCase = true)) return@filter false
return@filter true
}
.all { it -> seenAspects.contains(it) }
if (!allSeenAspects) return PokedexEntryProgress.UNREGISTERED
return formKnowledge
}
fun getEncounteredForms(entry: PokedexEntry) : List<PokedexForm> {
return getFormsWithKnowledge(entry, PokedexEntryProgress.SEEN)
}
@@ -10,9 +10,6 @@ package com.cobblemon.mod.common.api.pokedex
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.pokedex.def.PokedexDef
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.PokedexDexSyncPacket
import com.cobblemon.mod.common.util.adapters.CodecBackedAdapter
@@ -43,17 +40,6 @@ object Dexes : JsonDataRegistry<PokedexDef> {
//Maps a dex id to its PokedexDef
val dexEntryMap = linkedMapOf<ResourceLocation, PokedexDef>()
val equivalentForms = mapOf(
"galar" to listOf("region-bias-galar", "bias-galar", "galar-bias", "galarian", "galar"),
"alola" to listOf("region-bias-alola", "bias-alola", "alola-bias", "alolan", "alola"),
"hisui" to listOf("region-bias-hisui", "bias-hisui", "hisui-bias", "hisuian", "hisui"),
"paldea" to listOf("region-bias-paldea", "bias-paldea", "paldea-bias", "paldean", "paldea"),
)
val equivalentFormsLookup = equivalentForms.flatMap { (key, values) ->
values.map { it to key }
}.toMap()
override fun reload(data: Map<ResourceLocation, PokedexDef>) {
dexEntryMap.clear()
data.entries.sortedBy { it.value.sortOrder }.forEach { (id, def) -> dexEntryMap[id] = def }
@@ -63,34 +49,4 @@ object Dexes : JsonDataRegistry<PokedexDef> {
override fun sync(player: ServerPlayer) {
PokedexDexSyncPacket(dexEntryMap.values).sendToPlayer(player)
}
fun findDexEntryBySpeciesId(regionId: ResourceLocation, speciesId: ResourceLocation): PokedexEntry? {
return dexEntryMap[regionId]?.getEntries()?.firstOrNull { it -> it.speciesId == speciesId }
}
fun findDexEntryBySpeciesId(speciesId: ResourceLocation): PokedexEntry? {
return findDexEntryBySpeciesId(cobblemonResource("national"), speciesId)
}
fun findRootDexEntryBySpeciesId(speciesId: ResourceLocation, formName: String): Pair<PokedexEntry, PokedexForm> {
var rootSpecies = speciesId.let { PokemonSpecies.getByIdentifier(it) }!!
while (rootSpecies.preEvolution != null) {
rootSpecies = rootSpecies.preEvolution!!.species
}
val entry = findDexEntryBySpeciesId(rootSpecies.resourceIdentifier)
?: throw RuntimeException("Root dex entry not found")
val formEquivalents = getFormEquivalents(formName)
val form =
entry.forms.firstOrNull { formEquivalents.contains(it.displayForm.lowercase()) } ?:
entry.forms.first { it.displayForm.equals("Normal", ignoreCase = true) }
return entry to form
}
fun getFormEquivalents(formName: String): List<String> {
val key = equivalentFormsLookup[formName.lowercase()]
return equivalentForms[key] ?: listOf(formName.lowercase())
}
}
@@ -22,7 +22,10 @@ import net.minecraft.util.StringRepresentable
enum class PokedexEntryProgress : StringRepresentable, Comparable<PokedexEntryProgress> {
UNREGISTERED,
SEEN,
OWNED;
OWNED,
NONE,
CAUGHT;
override fun getSerializedName() = this.name
}
@@ -56,8 +56,10 @@ class AggregatePokedexDef(
return speciesToEntry.values.toList()
}
}
fun decode(buffer: RegistryFriendlyByteBuf) {
override fun shouldSynchronize(other: PokedexDef) = true
override fun decode(buffer: RegistryFriendlyByteBuf) {
sortOrder = buffer.readSizedInt(IntSize.U_BYTE)
val size = buffer.readInt()
for (i in 0 until size) {
@@ -65,7 +67,7 @@ class AggregatePokedexDef(
}
}
fun encode(buffer: RegistryFriendlyByteBuf) {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeSizedInt(IntSize.U_BYTE, sortOrder)
buffer.writeInt(subDexIds.size)
subDexIds.forEach {
@@ -8,6 +8,8 @@
package com.cobblemon.mod.common.api.pokedex.def
import com.cobblemon.mod.common.api.data.ClientDataSynchronizer
import com.cobblemon.mod.common.api.pokedex.entry.DexEntries
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
@@ -21,7 +23,7 @@ import net.minecraft.resources.ResourceLocation
* @since August 24, 2024
* @author Apion
*/
abstract class PokedexDef {
abstract class PokedexDef: ClientDataSynchronizer<PokedexDef> {
//The ID used to find the codecs for this "type" of PokedexDef
abstract val typeId: ResourceLocation
//The ID of this dex in the Dexes registry
@@ -45,7 +45,9 @@ class SimplePokedexDef(
override fun getEntries() = entries.mapNotNull { DexEntries.entries[it] }
fun decode(buffer: RegistryFriendlyByteBuf) {
override fun shouldSynchronize(other: PokedexDef) = true
override fun decode(buffer: RegistryFriendlyByteBuf) {
sortOrder = buffer.readSizedInt(IntSize.U_BYTE)
val size = buffer.readInt()
for (i in 0 until size) {
@@ -53,7 +55,7 @@ class SimplePokedexDef(
}
}
fun encode(buffer: RegistryFriendlyByteBuf) {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeSizedInt(IntSize.U_BYTE, sortOrder)
buffer.writeInt(entries.size)
entries.forEach {
@@ -41,18 +41,16 @@ class PokedexCategoryFilter(
PokedexCategoryFilterType.UNDISCOVERED_TM_MOVE -> hasUndiscoveredLevelUpTM(entry)
PokedexCategoryFilterType.RIDEABLE -> {
val species = PokemonSpecies.getByIdentifier(entry.speciesId)
// we were silly as standard forms also can be rideable and we were ingoring those
(species?.forms.orEmpty() + listOfNotNull(species?.standardForm)).any { form -> !form.riding.behaviours.isNullOrEmpty() }
&& (pokedexManager.getHighestKnowledgeFor(entry) >= PokedexEntryProgress.OWNED ||
pokedexManager.getHighestKnowledgeFor(entry) >= PokedexEntryProgress.SEEN)
species?.forms?.any { form -> !form.riding.behaviours.isNullOrEmpty() } == true
&& pokedexManager.getHighestKnowledgeFor(entry) >= PokedexEntryProgress.OWNED
}
}
}
private fun hasUndiscoveredLevelUpTM(entry: PokedexEntry): Boolean {
if (pokedexManager.getHighestKnowledgeFor(entry) == PokedexEntryProgress.UNREGISTERED) return false
if (pokedexManager.getHighestKnowledgeFor(entry) != PokedexEntryProgress.OWNED) return false
val species = PokemonSpecies.getByIdentifier(entry.speciesId) ?: return false
val forms = pokedexManager.getEncounteredForms(entry)
val forms = pokedexManager.getCaughtForms(entry)
val formData = if (forms.isEmpty()) {
listOf(species.standardForm)
} else {
@@ -72,7 +72,7 @@ class SearchFilter(val pokedexManager: AbstractPokedexManager, val searchString:
}
}
SearchByType.DROPS -> {
if (pokedexManager.getHighestKnowledgeFor(entry) == PokedexEntryProgress.UNREGISTERED) return false
if (pokedexManager.getHighestKnowledgeFor(entry) !== PokedexEntryProgress.OWNED) return false
val dropsList = mutableListOf<String>()
val formsList = if (species.forms.isEmpty()) mutableListOf(species.standardForm) else species.forms
formsList.forEach {
@@ -131,7 +131,9 @@ class SearchFilter(val pokedexManager: AbstractPokedexManager, val searchString:
form.moves.tutorMoves,
form.moves.eggMoves,
form.moves.evolutionMoves,
form.moves.formChangeMoves
form.moves.formChangeMoves,
form.moves.specialMoves,
form.moves.legacyMoves
)
for (moves in alwaysDiscovered) {
@@ -674,6 +674,9 @@ open class PokemonProperties {
@JvmOverloads
fun createEntity(world: Level, player: ServerPlayer? = null): PokemonEntity {
val pokemon = create(player)
if (pokemon.isAlpha) {
pokemon.initializeMovesetWithRandomTm()
}
return PokemonEntity(world, pokemon).also { applyCustomProperties(it) }
}
@@ -1,30 +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.pokemon.evolution
import com.cobblemon.mod.common.pokemon.evolution.variants.*
interface EvolutionVisitor<R> {
fun visit(evolution: Evolution): R? {
return when (evolution) {
is BlockClickEvolution -> visitBlockClickEvolution(evolution)
is DummyEvolution -> visitDummyEvolution(evolution)
is ItemInteractionEvolution -> visitItemInteractionEvolution(evolution)
is LevelUpEvolution -> visitLevelUpEvolution(evolution)
is TradeEvolution -> visitTradeEvolution(evolution)
else -> null
}
}
fun visitBlockClickEvolution(blockClickEvolution: BlockClickEvolution): R
fun visitDummyEvolution(evolution: Evolution): R // TODO: Use dummyEvolution?
fun visitItemInteractionEvolution(itemInteractionEvolution: ItemInteractionEvolution): R
fun visitLevelUpEvolution(levelUpEvolution: LevelUpEvolution): R
fun visitTradeEvolution(tradeEvolution: TradeEvolution): R
}
@@ -0,0 +1,132 @@
/*
* 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.pokemon.feature
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
import com.cobblemon.mod.common.client.gui.summary.featurerenderers.SummarySpeciesFeatureRenderer
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.readString
import com.google.gson.JsonObject
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.RegistryFriendlyByteBuf
class BattleFormFeature(formName: String, enabled: Boolean): FlagSpeciesFeature(formName, enabled) {
override fun saveToJSON(pokemonJSON: JsonObject): JsonObject {
return pokemonJSON
}
override fun saveToNBT(pokemonNBT: CompoundTag): CompoundTag {
return pokemonNBT
}
}
class BattleFlagSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<BattleFormFeature>,
CustomPokemonPropertyType<BattleFormFeature>, AspectProvider {
override var keys: List<String>
override val needsKey get() = true
var default: String? = null
var isAspect = true
override var visible: Boolean = false
override fun invoke(buffer: RegistryFriendlyByteBuf, name: String): BattleFormFeature? {
return if (name in keys) {
BattleFormFeature(name, false).also { it.loadFromBuffer(buffer) }
} else {
null
}
}
override fun examples() = setOf("true", "false")
internal constructor() {
this.keys = emptyList()
}
constructor(keys: List<String>) {
this.keys = keys
}
constructor(keys: List<String>, default: Boolean) {
this.keys = keys
this.default = default.toString()
}
constructor(vararg keys: String) : this(keys.toList())
override fun invoke(pokemon: Pokemon): BattleFormFeature? {
return pokemon.getFeature(keys.first())
?: when (default) {
in setOf("true", "false") -> BattleFormFeature(keys.first(), default.toBoolean())
else -> null
}
}
override fun saveToBuffer(buffer: RegistryFriendlyByteBuf, toClient: Boolean) {
buffer.writeCollection(keys) { _, value -> buffer.writeUtf(value) }
buffer.writeNullable(default) { _, value -> buffer.writeUtf(value) }
buffer.writeBoolean(isAspect)
}
override fun loadFromBuffer(buffer: RegistryFriendlyByteBuf) {
keys = buffer.readList { it.readString() }
default = buffer.readNullable { it.readString() }
isAspect = buffer.readBoolean()
}
override fun invoke(nbt: CompoundTag): BattleFormFeature? {
return if (nbt.contains(keys.first())) {
BattleFormFeature(keys.first(), false).also { it.loadFromNBT(nbt) }
} else null
}
override fun invoke(json: JsonObject): BattleFormFeature? {
return if (json.has(keys.first())) {
BattleFormFeature(keys.first(), false).also { it.loadFromJSON(json) }
} else null
}
override fun get(pokemon: Pokemon) = pokemon.getFeature<BattleFormFeature>(keys.first())
override fun getRenderer(pokemon: Pokemon): SummarySpeciesFeatureRenderer<BattleFormFeature>? {
return null
}
override fun fromString(value: String?): BattleFormFeature? {
val isWeirdValue = value != null && value !in examples()
if (isWeirdValue) {
return null
}
return if (value == null) {
BattleFormFeature(keys.first(), true)
} else {
BattleFormFeature(keys.first(), value.toBoolean())
}
}
override fun provide(pokemon: Pokemon): Set<String> {
return if (isAspect && pokemon.getFeature<FlagSpeciesFeature>(keys.first())?.enabled == true) {
setOf(keys.first())
} else {
emptySet()
}
}
override fun provide(properties: PokemonProperties): Set<String> {
return if (isAspect && properties.customProperties.filterIsInstance<FlagSpeciesFeature>().find { it.name == keys.first() }?.enabled == true) {
setOf(keys.first())
} else {
emptySet()
}
}
}
@@ -8,10 +8,16 @@
package com.cobblemon.mod.common.api.pokemon.feature
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
import com.cobblemon.mod.common.client.gui.summary.featurerenderers.SummarySpeciesFeatureRenderer
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.substitute
import com.cobblemon.mod.common.util.writeString
import com.google.gson.JsonObject
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.RegistryFriendlyByteBuf
/**
@@ -21,11 +27,16 @@ import net.minecraft.network.RegistryFriendlyByteBuf
* @author Hiroku
* @since November 30th, 2022
*/
class ChoiceSpeciesFeatureProvider : AbstractChoiceSpeciesFeatureProvider<List<String>>(emptyList(), emptyList()) {
open class ChoiceSpeciesFeatureProvider(
override var keys: List<String>,
var default: String? = null,
var choices: List<String> = listOf(),
var isAspect: Boolean = true,
var aspectFormat: String = "{{choice}}"
) : SynchronizedSpeciesFeatureProvider<StringSpeciesFeature>, CustomPokemonPropertyType<StringSpeciesFeature>, AspectProvider {
override var needsKey = true
override var visible = false
override fun examples() = choices
fun getAspect(feature: StringSpeciesFeature) = aspectFormat.substitute("choice", feature.value)
override fun saveToBuffer(buffer: RegistryFriendlyByteBuf, toClient: Boolean) {
buffer.writeCollection(keys) { _, value -> buffer.writeString(value) }
@@ -45,13 +56,32 @@ class ChoiceSpeciesFeatureProvider : AbstractChoiceSpeciesFeatureProvider<List<S
needsKey = buffer.readBoolean()
}
override fun fromString(value: String?): StringSpeciesFeature? {
val lower = value?.lowercase()
if (lower == null || lower !in choices) {
return null
}
override fun getRenderer(pokemon: Pokemon): SummarySpeciesFeatureRenderer<StringSpeciesFeature>? {
return null
}
return StringSpeciesFeature(keys.first(), lower)
override fun invoke(buffer: RegistryFriendlyByteBuf, name: String): StringSpeciesFeature? {
return if (name in keys) {
StringSpeciesFeature(name, "").also { it.loadFromBuffer(buffer) }
} else {
null
}
}
fun getAllAspects(): MutableList<String> {
val aspects = choices.toMutableList()
choices.forEach {
aspects[choices.indexOf(it)] = (aspectFormat.substitute("choice", it))
}
return aspects
}
override fun examples() = choices
internal constructor(): this(emptyList())
override fun get(pokemon: Pokemon): StringSpeciesFeature? {
return pokemon.features.filterIsInstance<StringSpeciesFeature>().find { it.name in keys }
}
override fun invoke(pokemon: Pokemon): StringSpeciesFeature? {
@@ -63,8 +93,7 @@ class ChoiceSpeciesFeatureProvider : AbstractChoiceSpeciesFeatureProvider<List<S
default!!
} else if (default == "random") {
// If it's mandatory, but they provided no value and no default, give it a random value.
choices.randomOrNull()
?: throw IllegalStateException("The 'choices' list is empty for species feature provider: ${keys.joinToString()}")
choices.randomOrNull() ?: throw IllegalStateException("The 'choices' list is empty for species feature provider: ${keys.joinToString()}")
} else {
return null
}
@@ -73,11 +102,45 @@ class ChoiceSpeciesFeatureProvider : AbstractChoiceSpeciesFeatureProvider<List<S
}
}
override fun getAllAspects(): MutableList<String> {
val aspects = choices.toMutableList()
choices.forEach {
aspects[choices.indexOf(it)] = (aspectFormat.substitute("choice", it))
}
return aspects
override fun invoke(nbt: CompoundTag): StringSpeciesFeature? {
val key = keys.find { nbt.contains(it) }
if (key == null) return null
return StringSpeciesFeature(key, "").also { it.loadFromNBT(nbt) }
}
}
override fun invoke(json: JsonObject): StringSpeciesFeature? {
val key = keys.find { json.has(it) }
if (key == null) return null
return StringSpeciesFeature(key, "").also { it.loadFromJSON(json) }
}
override fun fromString(value: String?): StringSpeciesFeature? {
val lower = value?.lowercase()
if (lower == null || lower !in choices) {
return null
}
return StringSpeciesFeature(keys.first(), lower)
}
override fun provide(pokemon: Pokemon): Set<String> {
return if (isAspect) {
get(pokemon)?.let { setOf(getAspect(it)) } ?: emptySet()
} else {
emptySet()
}
}
override fun provide(properties: PokemonProperties): Set<String> {
return if (isAspect) {
val feature = properties.customProperties.filterIsInstance<StringSpeciesFeature>().find { it.name in keys }
if (feature != null) {
setOf(getAspect(feature))
} else {
emptySet()
}
} else {
emptySet()
}
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.pokemon.moves
import com.cobblemon.mod.common.api.data.ClientDataSynchronizer
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.net.IntSize
@@ -17,7 +18,7 @@ import com.cobblemon.mod.common.util.writeSizedInt
import com.google.gson.JsonElement
import net.minecraft.network.RegistryFriendlyByteBuf
open class Learnset {
open class Learnset : ClientDataSynchronizer<Learnset> {
class Interpreter(val loadMove: (JsonElement, Learnset) -> Boolean) {
companion object {
fun parseFromPrefixIntoList(prefix: String, list: (Learnset) -> MutableList<MoveTemplate>): Interpreter {
@@ -110,9 +111,9 @@ open class Learnset {
}
// We only sync level up moves atm
fun shouldSynchronize(other: Learnset) = other.levelUpMoves != this.levelUpMoves
override fun shouldSynchronize(other: Learnset) = other.levelUpMoves != this.levelUpMoves
fun decode(buffer: RegistryFriendlyByteBuf) {
override fun decode(buffer: RegistryFriendlyByteBuf) {
this.levelUpMoves.clear()
repeat(times = buffer.readSizedInt(IntSize.U_BYTE)) {
val level = buffer.readSizedInt(IntSize.U_SHORT)
@@ -124,7 +125,7 @@ open class Learnset {
}
}
fun encode(buffer: RegistryFriendlyByteBuf) {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeSizedInt(IntSize.U_BYTE, levelUpMoves.size)
for ((level, moves) in levelUpMoves) {
buffer.writeSizedInt(IntSize.U_SHORT, level)
@@ -21,7 +21,7 @@ import net.minecraft.world.entity.LivingEntity
* @author Licious
* @since March 21st, 2022
*/
abstract class EntityQueryRequirement : Requirement() {
interface EntityQueryRequirement : Requirement {
override fun check(pokemon: Pokemon): Boolean {
val queriedEntity = pokemon.entity ?: pokemon.getOwnerPlayer() ?: return false
return this.check(pokemon, queriedEntity)
@@ -34,5 +34,5 @@ abstract class EntityQueryRequirement : Requirement() {
* @param queriedEntity The [LivingEntity] that can be compared in context of a [Level].
* @return If the requirement was satisfied.
*/
abstract fun check(pokemon: Pokemon, queriedEntity: LivingEntity): Boolean
fun check(pokemon: Pokemon, queriedEntity: LivingEntity): Boolean
}
@@ -23,7 +23,7 @@ import net.minecraft.world.entity.player.Player
* @author Polymeta
* @since May 6th, 2025
*/
abstract class OwnerQueryRequirement : Requirement() {
interface OwnerQueryRequirement : Requirement {
override fun check(pokemon: Pokemon): Boolean {
val owner = pokemon.getOwnerEntity() ?: return false
return when (owner) {
@@ -44,7 +44,7 @@ abstract class OwnerQueryRequirement : Requirement() {
* @param owner The [Player] that owns this [Pokemon].
* @return If the requirement was satisfied.
*/
abstract fun checkPlayer(pokemon: Pokemon, owner: Player): Boolean
fun checkPlayer(pokemon: Pokemon, owner: Player): Boolean
/**
* Checks if the given [Pokemon] & [NPCEntity] satisfies the requirement.
@@ -55,5 +55,5 @@ abstract class OwnerQueryRequirement : Requirement() {
* @param owner The [NPCEntity] that owns this [Pokemon].
* @return If the requirement was satisfied.
*/
abstract fun checkNPC(pokemon: Pokemon, owner: NPCEntity): Boolean
fun checkNPC(pokemon: Pokemon, owner: NPCEntity): Boolean
}
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.pokemon.requirement
import com.cobblemon.mod.common.api.pokemon.evolution.ContextEvolution
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.resources.ResourceLocation
/**
* Represents a universal requirement suitable for various checks on pokemon..
@@ -23,7 +22,7 @@ import net.minecraft.resources.ResourceLocation
* @author Licious
* @since March 19th, 2022
*/
abstract class Requirement {
fun interface Requirement {
/**
* Checks if the given [Pokemon] satisfies the requirement.
@@ -31,8 +30,6 @@ abstract class Requirement {
* @param pokemon The [Pokemon] being queried.
* @return If the requirement was satisfied.
*/
abstract fun check(pokemon: Pokemon): Boolean
fun check(pokemon: Pokemon): Boolean
val pokedexIcon: ResourceLocation? = null
val pokedexTooltip: String? = null
}
@@ -1,74 +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.pokemon.requirement
import com.cobblemon.mod.common.pokemon.requirements.*
interface RequirementVisitor<R> {
fun visit(requirement: Requirement): R? {
return when (requirement) {
is AdvancementRequirement -> visitAdvancementRequirement(requirement)
is AnyRequirement -> visitAnyRequirement(requirement)
is AreaRequirement -> visitAreaRequirement(requirement)
is AttackDefenceRatioRequirement -> visitAttackDefenceRatioRequirement(requirement)
is BattleCriticalHitsRequirement -> visitBattleCriticalHitsRequirement(requirement)
is BiomeRequirement -> visitBiomeRequirement(requirement)
is BlocksTraveledRequirement -> visitBlocksTraveledRequirement(requirement)
is DamageTakenRequirement -> visitDamageTakenRequirement(requirement)
is DefeatRequirement -> visitDefeatRequirement(requirement)
is FriendshipRequirement -> visitFriendshipRequirement(requirement)
is HeldItemRequirement -> visitHeldItemRequirement(requirement)
is LevelRequirement -> visitLevelRequirement(requirement)
is MoonPhaseRequirement -> visitMoonPhaseRequirement(requirement)
is MoveSetRequirement -> visitMoveSetRequirement(requirement)
is MoveTypeRequirement -> visitMoveTypeRequirement(requirement)
is OwnerHoldsItemRequirement -> visitOwnerHoldsItemRequirement(requirement)
is PartyMemberRequirement -> visitPartyMemberRequirement(requirement)
is PokemonPropertiesRequirement -> visitPokemonPropertiesRequirement(requirement)
is PropertyRangeRequirement -> visitPropertyRangeRequirement(requirement)
is RecoilRequirement -> visitRecoilRequirement(requirement)
is StatCompareRequirement -> visitStatCompareRequirement(requirement)
is StatEqualRequirement -> visitStatEqualRequirement(requirement)
is StructureRequirement -> visitStructureRequirement(requirement)
is TimeRangeRequirement -> visitTimeRangeRequirement(requirement)
is UseMoveRequirement -> visitUseMoveRequirement(requirement)
is WeatherRequirement -> visitWeatherRequirement(requirement)
is WorldRequirement -> visitWorldRequirement(requirement)
else -> null
}
}
fun visitAdvancementRequirement(advancementRequirement: AdvancementRequirement): R
fun visitAnyRequirement(anyRequirement: AnyRequirement): R
fun visitAreaRequirement(areaRequirement: AreaRequirement): R
fun visitAttackDefenceRatioRequirement(attackDefenceRatioRequirement: AttackDefenceRatioRequirement): R
fun visitBattleCriticalHitsRequirement(battleCriticalHitsRequirement: BattleCriticalHitsRequirement): R
fun visitBiomeRequirement(biomeRequirement: BiomeRequirement): R
fun visitBlocksTraveledRequirement(blocksTraveledRequirement: BlocksTraveledRequirement): R
fun visitDamageTakenRequirement(damageTakenRequirement: DamageTakenRequirement): R
fun visitDefeatRequirement(defeatRequirement: DefeatRequirement): R
fun visitFriendshipRequirement(friendshipRequirement: FriendshipRequirement): R
fun visitHeldItemRequirement(heldItemRequirement: HeldItemRequirement): R
fun visitLevelRequirement(levelRequirement: LevelRequirement): R
fun visitMoonPhaseRequirement(moonPhaseRequirement: MoonPhaseRequirement): R
fun visitMoveSetRequirement(moveSetRequirement: MoveSetRequirement): R
fun visitMoveTypeRequirement(moveTypeRequirement: MoveTypeRequirement): R
fun visitOwnerHoldsItemRequirement(ownerHoldsItemRequirement: OwnerHoldsItemRequirement): R
fun visitPartyMemberRequirement(partyMemberRequirement: PartyMemberRequirement): R
fun visitPokemonPropertiesRequirement(pokemonPropertiesRequirement: PokemonPropertiesRequirement): R
fun visitPropertyRangeRequirement(propertyRangeRequirement: PropertyRangeRequirement): R
fun visitRecoilRequirement(recoilRequirement: RecoilRequirement): R
fun visitStatCompareRequirement(statCompareRequirement: StatCompareRequirement): R
fun visitStatEqualRequirement(statEqualRequirement: StatEqualRequirement): R
fun visitStructureRequirement(structureRequirement: StructureRequirement): R
fun visitTimeRangeRequirement(timeRangeRequirement: TimeRangeRequirement): R
fun visitUseMoveRequirement(useMoveRequirement: UseMoveRequirement): R
fun visitWeatherRequirement(weatherRequirement: WeatherRequirement): R
fun visitWorldRequirement(worldRequirement: WorldRequirement): R
}
@@ -22,8 +22,8 @@ import net.minecraft.network.RegistryFriendlyByteBuf
*/
data class Seat(
val locator: String = "",
val condition: Expression? = null,
val poseAnimations: MutableList<SeatPoseAnimations>? = null,
val condition: Expression?,
val poseAnimations: MutableList<SeatPoseAnimations>?,
val handsBusy: Boolean = false,
) : Encodable {
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.snowstorm
import com.cobblemon.mod.common.client.render.shader.CobblemonShaders
import com.mojang.blaze3d.platform.GlStateManager
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.BufferBuilder
@@ -15,7 +16,6 @@ import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.Tesselator
import com.mojang.blaze3d.vertex.VertexFormat
import net.minecraft.client.particle.ParticleRenderType
import net.minecraft.client.renderer.GameRenderer
import net.minecraft.client.renderer.texture.TextureAtlas
import net.minecraft.client.renderer.texture.TextureManager
@@ -23,9 +23,8 @@ object ParticleMaterials {
val ALPHA = object : ParticleRenderType {
override fun begin(tessellator: Tesselator, textureManager: TextureManager): BufferBuilder {
RenderSystem.enableBlend()
RenderSystem.enableCull()
RenderSystem.depthMask(true)
RenderSystem.setShader { GameRenderer.getParticleShader() }
RenderSystem.setShader { CobblemonShaders.PARTICLE_CUTOUT }
RenderSystem.setShaderTexture(0, TextureAtlas.LOCATION_PARTICLES)
RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA)
return tessellator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.PARTICLE)
@@ -37,11 +36,10 @@ object ParticleMaterials {
val ADD = object : ParticleRenderType {
override fun begin(tessellator: Tesselator, textureManager: TextureManager): BufferBuilder {
RenderSystem.enableBlend()
RenderSystem.disableCull()
RenderSystem.depthMask(true)
RenderSystem.setShader { GameRenderer.getParticleShader() }
RenderSystem.setShader { CobblemonShaders.PARTICLE_BLEND }
RenderSystem.setShaderTexture(0, TextureAtlas.LOCATION_PARTICLES)
RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE)
RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA)
return tessellator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.PARTICLE)
}
@@ -51,11 +49,10 @@ object ParticleMaterials {
val BLEND = object : ParticleRenderType {
override fun begin(tessellator: Tesselator, textureManager: TextureManager): BufferBuilder {
RenderSystem.enableBlend()
RenderSystem.disableCull()
RenderSystem.depthMask(true)
RenderSystem.setShader { GameRenderer.getParticleShader() }
RenderSystem.setShader { CobblemonShaders.PARTICLE_BLEND }
RenderSystem.setShaderTexture(0, TextureAtlas.LOCATION_PARTICLES)
RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA)
RenderSystem.blendFunc(GlStateManager.SourceFactor.DST_COLOR, GlStateManager.DestFactor.ZERO)
return tessellator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.PARTICLE)
}
@@ -65,11 +62,10 @@ object ParticleMaterials {
val OPAQUE = object : ParticleRenderType {
override fun begin(tessellator: Tesselator, textureManager: TextureManager): BufferBuilder {
RenderSystem.enableBlend()
RenderSystem.enableCull()
RenderSystem.depthMask(true)
RenderSystem.setShader { GameRenderer.getParticleShader() }
RenderSystem.setShader { CobblemonShaders.PARTICLE_BLEND }
RenderSystem.setShaderTexture(0, TextureAtlas.LOCATION_PARTICLES)
RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_COLOR, GlStateManager.DestFactor.ZERO)
RenderSystem.blendFunc(GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ONE)
return tessellator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.PARTICLE)
}
@@ -18,12 +18,12 @@ import com.cobblemon.mod.common.api.spawning.condition.SeafloorSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SubmergedSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SurfaceSpawningCondition
import com.cobblemon.mod.common.api.spawning.detail.EmptySpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.NPCSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.PokemonHerdSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.influence.BucketMultiplyingInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePositionResolver
import com.cobblemon.mod.common.api.spawning.position.FishingSpawnablePosition
@@ -121,7 +121,6 @@ object BestSpawner {
SpawnDetail.registerSpawnType(name = PokemonSpawnDetail.TYPE, PokemonSpawnDetail::class.java)
SpawnDetail.registerSpawnType(name = NPCSpawnDetail.TYPE, NPCSpawnDetail::class.java)
SpawnDetail.registerSpawnType(name = PokemonHerdSpawnDetail.TYPE, PokemonHerdSpawnDetail::class.java)
SpawnDetail.registerSpawnType(name = EmptySpawnDetail.TYPE, EmptySpawnDetail::class.java)
LOGGER.info("Loaded ${SpawnDetail.spawnDetailTypes.size} spawn detail types.")
loadConfig()
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning
/**
* A spawn bucket is a simple referenceable object by spawn files. The bucket is used at the beginning of
* the selection process to decide which set of spawns will be used for spawning on that attempt. Weights are used to
* make entire buckets more or less likely, and then weighted selections will occur within whichever bucket
* was chosen.
*
* Spawn buckets are configured in the main config.
*
* @author Hiroku
* @since June 20th, 2022
*/
class SpawnBucket() {
lateinit var name: String
var weight = 0F
constructor(name: String, weight: Float): this() {
this.name = name
this.weight = weight
}
override fun hashCode() = name.hashCode()
override fun equals(other: Any?) = other is SpawnBucket && other.name == name
}
@@ -49,6 +49,7 @@ object SpawnDetailPresets : JsonDataRegistry<SpawnDetailPreset> {
.setPrettyPrinting()
.setLenient()
.disableHtmlEscaping()
.registerTypeAdapter(SpawnBucket::class.java, SpawnBucketAdapter)
.registerTypeAdapter(SpawnablePositionType::class.java, RegisteredSpawnablePositionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
@@ -67,6 +67,7 @@ object SpawnLoader {
.registerTypeAdapter(MoonPhaseRange::class.java, IntRangesAdapter(MoonPhaseRange.moonPhaseRanges) { MoonPhaseRange(*it) })
.registerTypeAdapter(ItemDropMethod::class.java, ItemDropMethod.adapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(SpawnBucket::class.java, SpawnBucketAdapter)
.registerTypeAdapter(CompoundTag::class.java, NbtCompoundAdapter)
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapter(PossibleHeldItem::class.java, PossibleHeldItemAdapter)
@@ -10,8 +10,6 @@ package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.api.spawning.TimeRange.Companion.timeRanges
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.util.lang
import net.minecraft.network.chat.MutableComponent
import com.cobblemon.mod.common.util.adapters.IntRangesAdapter
/**
@@ -51,25 +49,4 @@ class TimeRange : IntRanges {
constructor() : super()
constructor(vararg ranges: IntRange) : super(*ranges)
fun displayName(): MutableComponent? {
val key = timeRanges.entries.firstOrNull { entry -> this == entry.value }?.key ?: return null
return lang("ui.time_range.$key")
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is TimeRange) return false
if (other.ranges.size != this.ranges.size) return false
other.ranges.forEachIndexed { index, otherRange ->
if (this.ranges[index] != otherRange) return false
}
return true
}
override fun hashCode(): Int {
return ranges.hashCode()
}
}
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.util.lang
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.level.Level
/**
@@ -44,7 +42,4 @@ enum class MoonPhase {
fun ofWorld(world: Level): MoonPhase = VALUES[world.moonPhase]
}
fun displayName(): MutableComponent {
return lang("ui.moon_phase.${this.name.lowercase()}")
}
}
@@ -8,9 +8,10 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import net.minecraft.resources.ResourceLocation
/**
@@ -21,8 +22,8 @@ import net.minecraft.resources.ResourceLocation
* time as sharing that value. The requirement of implementations is only to get that
* key from both a [SpawnDetail] and a [SpawnablePosition].
*
* When a precalculation has been registered with a [SpawnPool] by adding to [SpawnPool.precalculators],
* the precalculation result will need to be updated by running [SpawnPool.precalculate]. That
* When a precalculation has been registered with a [Spawner] by adding to [Spawner.precalculators],
* the precalculation result will need to be updated by running [Spawner.precalculate]. That
* might take it a hot minute, but the result will be that there is an extra layer of
* shortcutting to the matching algorithm.
*
@@ -33,7 +34,7 @@ interface SpawningPrecalculation<T : Any> {
/** Retrieves the precalculation keys for the given spawn detail. */
fun select(detail: SpawnDetail): Collection<T>
/** Retrieves the precalculation key for the given spawnable position, if that key exists for this position. */
fun select(bucket: String, spawnablePosition: SpawnablePosition): T?
fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): T?
/**
* Generates a precalculation result for the list of [SpawnDetail]s and the subsequent precalculations.
@@ -73,7 +74,7 @@ interface SpawningPrecalculation<T : Any> {
object RootPrecalculation : SpawningPrecalculation<Any> {
val list = listOf(Unit)
override fun select(detail: SpawnDetail): List<Any> = list
override fun select(bucket: String, spawnablePosition: SpawnablePosition): Any = Unit
override fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): Any = Unit
}
/**
@@ -84,12 +85,12 @@ object RootPrecalculation : SpawningPrecalculation<Any> {
*/
object SpawnablePositionTypePrecalculation : SpawningPrecalculation<Class<out SpawnablePosition>> {
override fun select(detail: SpawnDetail) = listOf(detail.spawnablePositionType.clazz)
override fun select(bucket: String, spawnablePosition: SpawnablePosition) = spawnablePosition::class.java
override fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = spawnablePosition::class.java
}
object BucketPrecalculation : SpawningPrecalculation<String> {
object BucketPrecalculation : SpawningPrecalculation<SpawnBucket> {
override fun select(detail: SpawnDetail) = listOf(detail.bucket)
override fun select(bucket: String, spawnablePosition: SpawnablePosition) = bucket
override fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = bucket
}
/**
@@ -98,7 +99,7 @@ object BucketPrecalculation : SpawningPrecalculation<String> {
*/
object BiomePrecalculation : SpawningPrecalculation<ResourceLocation> {
override fun select(detail: SpawnDetail) = detail.validBiomes
override fun select(bucket: String, spawnablePosition: SpawnablePosition) = spawnablePosition.biomeHolder.unwrapKey().map { it.location() }.orElse(null)
override fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = spawnablePosition.biomeHolder.unwrapKey().map { it.location() }.orElse(null)
}
/**
@@ -113,7 +114,7 @@ sealed class PrecalculationResult<T : Any>(
val calculation: SpawningPrecalculation<*>
) {
/** Gets the list of spawn details that fit this spawnable position. */
abstract fun retrieve(bucket: String, spawnablePosition: SpawnablePosition): List<SpawnDetail>
abstract fun retrieve(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): List<SpawnDetail>
}
/**
@@ -126,7 +127,7 @@ class NestedPrecalculationResult<T : Any>(
calculation: SpawningPrecalculation<T>,
val mapping: Map<T, PrecalculationResult<*>> = mutableMapOf()
) : PrecalculationResult<T>(calculation) {
override fun retrieve(bucket: String, spawnablePosition: SpawnablePosition) = mapping[calculation.select(bucket, spawnablePosition)]?.retrieve(bucket, spawnablePosition) ?: emptyList()
override fun retrieve(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = mapping[calculation.select(bucket, spawnablePosition)]?.retrieve(bucket, spawnablePosition) ?: emptyList()
}
/**
@@ -140,5 +141,5 @@ class FinalPrecalculationResult<T : Any>(
calculation: SpawningPrecalculation<*>,
val mapping: Map<T, List<SpawnDetail>>
) : PrecalculationResult<T>(calculation) {
override fun retrieve(bucket: String, spawnablePosition: SpawnablePosition) = mapping[calculation.select(bucket, spawnablePosition)] ?: emptyList()
override fun retrieve(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = mapping[calculation.select(bucket, spawnablePosition)] ?: emptyList()
}
@@ -1,25 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
/**
* Does a whole lot of nothing. Spawns nothing.
*
* @author Hiroku
* @since May 16th, 2026
*/
class EmptySpawnAction(
spawnablePosition: SpawnablePosition,
bucket: String,
detail: EmptySpawnDetail
) : SpawnAction<Unit>(spawnablePosition, bucket, detail) {
override fun run() {}
}
@@ -1,48 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.selection.SpawnSelectionData
/**
* A spawn detail for nothing. It is used to take a slot, like to weigh down a spawn attempt,
* with something that ultimately doesn't spawn anything.
*
* The use case for this is very niche. With 1.8, the buckets that can be selected by a spawner
* during a spawn attempt will be narrowed down to the buckets that are used by the spawn
* pool. That means that if a bucket is never used by the pool, it will never be selected. That
* way, a bucket won't be selected when it never had any chance of being selected. This is what
* you would want in almost every case.
*
* However, in some cases you may want spawning to not spawn anything or you may want to preserve
* the relative rarity of the other buckets, and in that case you'd put something like this in a
* bucket so that it technically exists in the spawn pool without actually resulting in a spawned
* entity.
*
* @author Hiroku
* @since May 16th, 2026
*/
class EmptySpawnDetail : SpawnDetail() {
companion object {
val TYPE = "empty"
}
override val type: String = TYPE
override fun createSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: String,
selectionData: SpawnSelectionData
) = EmptySpawnAction(
spawnablePosition = spawnablePosition,
bucket = bucket,
detail = this
)
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.resolveInt
@@ -19,7 +20,7 @@ import com.cobblemon.mod.common.util.resolveInt
* @author Hiroku
* @since October 8th, 2023
*/
class NPCSpawnAction(spawnablePosition: SpawnablePosition, bucket: String, override val detail: NPCSpawnDetail) : SingleEntitySpawnAction<NPCEntity>(spawnablePosition, bucket, detail) {
class NPCSpawnAction(spawnablePosition: SpawnablePosition, bucket: SpawnBucket, override val detail: NPCSpawnDetail) : SingleEntitySpawnAction<NPCEntity>(spawnablePosition, bucket, detail) {
override fun createEntity(): NPCEntity {
val npc = NPCEntity(spawnablePosition.world)
npc.npc = detail.npcClass
@@ -11,11 +11,13 @@ package com.cobblemon.mod.common.api.spawning.detail
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.npc.NPCClass
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.selection.SpawnSelectionData
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asExpression
import com.google.gson.annotations.SerializedName
import kotlin.math.ceil
/**
* A [SpawnDetail] describing an [NPCEntity] spawn.
@@ -49,7 +51,7 @@ class NPCSpawnDetail : SpawnDetail() {
override fun createSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: String,
bucket: SpawnBucket,
selectionData: SpawnSelectionData
) = NPCSpawnAction(spawnablePosition, bucket, this)
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.selection.SpawnSelectionData
import com.cobblemon.mod.common.util.weightedSelection
@@ -53,10 +54,8 @@ class PokemonHerdSpawnDetail : SpawnDetail() {
var pokemon: PokemonProperties = PokemonProperties()
val levelRange: IntRange? = null
var levelRangeOffset: IntRange? = null
val herdLevelRange: IntRange? = null
var dropTable: DropTable? = null
var isLeader: Boolean = false
var isFollower: Boolean = true
var isLeader: Boolean? = null
var weight: Float = 1F
var maxTimes = 10
var heldItem: ResourceLocation? = null
@@ -84,7 +83,7 @@ class PokemonHerdSpawnDetail : SpawnDetail() {
val herdable = getValidHerdMembers(selectionData)
val herdCounts = getHerdMemberCounts(selectionData)
val leaderIsPossible = herdable.any { it.herdLevelRange?.contains(level) != false && it.isLeader }
val leaderIsPossible = herdable.any { it.levelRange?.contains(level) != false && it.isLeader == true }
val leaderIsSelected = herdCounts.any { it.key?.isLeader == true && it.value > 0 }
return leaderIsPossible && !leaderIsSelected
}
@@ -99,16 +98,14 @@ class PokemonHerdSpawnDetail : SpawnDetail() {
override fun createSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: String,
bucket: SpawnBucket,
selectionData: SpawnSelectionData
): PokemonSpawnAction {
val validHerdMembers = getValidHerdMembers(selectionData)
var searchedForLeader = false
val herdable = if (lacksPossibleLeader(selectionData)) {
searchedForLeader = true
validHerdMembers.filter { it.isLeader }
validHerdMembers.filter { it.isLeader == true }
} else {
validHerdMembers.filter { it.isFollower }
validHerdMembers
}.weightedSelection { it.weight } ?: herdablePokemon[0]
val level = selectionData.context[herdLevelKey] as? Int ?: 1
@@ -137,7 +134,7 @@ class PokemonHerdSpawnDetail : SpawnDetail() {
heldItem = heldItem,
levelRange = levelRange
).also {
if (herdable.isLeader && searchedForLeader) {
if (herdable.isLeader == true) {
it.labels += HERD_LEADER
}
}
@@ -156,7 +153,7 @@ class PokemonHerdSpawnDetail : SpawnDetail() {
val count = counts[herdable] ?: 0
return@filter when {
count >= herdable.maxTimes -> false
herdable.herdLevelRange != null && level !in herdable.herdLevelRange -> false
herdable.levelRange != null && level !in herdable.levelRange -> false
else -> true
}
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.feature.SeasonFeatureHandler
@@ -25,7 +26,7 @@ import net.minecraft.world.item.ItemStack
*/
class PokemonSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: String,
bucket: SpawnBucket,
override val detail: SpawnDetail,
/**
* The [PokemonProperties] that are about to be used. When using it, a copy will be taken.
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.selection.SpawnSelectionData
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -103,8 +104,7 @@ class PokemonSpawnDetail : SpawnDetail() {
}
override fun createSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: String,
spawnablePosition: SpawnablePosition, bucket: SpawnBucket,
selectionData: SpawnSelectionData
): SingleEntitySpawnAction<PokemonEntity> {
val levelRange = pokemon.deriveLevelRange(levelRange)
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.entity.SpawnEvent
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.util.toVec3d
import net.minecraft.world.entity.Entity
@@ -25,7 +26,7 @@ import net.minecraft.world.entity.MobSpawnType
*/
abstract class SingleEntitySpawnAction<T : Entity>(
spawnablePosition: SpawnablePosition,
bucket: String,
bucket: SpawnBucket,
detail: SpawnDetail
) : SpawnAction<EntitySpawnResult>(spawnablePosition, bucket, detail) {
abstract fun createEntity(): T?
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import java.util.concurrent.CompletableFuture
@@ -20,7 +21,7 @@ import java.util.concurrent.CompletableFuture
*/
abstract class SpawnAction<R>(
val spawnablePosition: SpawnablePosition,
val bucket: String,
val bucket: SpawnBucket,
open val detail: SpawnDetail
) {
val labels = mutableListOf<String>()
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.ModDependant
import com.cobblemon.mod.common.api.molang.MoLangFunctions.queryStructOf
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.condition.CompositeSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.multiplier.WeightMultiplier
@@ -53,7 +54,7 @@ abstract class SpawnDetail : ModDependant {
var displayName: String? = null
@SerializedName("spawnablePositionType", alternate = ["context"])
lateinit var spawnablePositionType: SpawnablePositionType<*>
var bucket = ""
var bucket = SpawnBucket("", 0F)
var conditions = mutableListOf<SpawningCondition<*>>()
var anticonditions = mutableListOf<SpawningCondition<*>>()
var compositeCondition: CompositeSpawningCondition? = null
@@ -78,7 +79,7 @@ abstract class SpawnDetail : ModDependant {
"weight" to { DoubleValue(weight) },
"percentage" to { DoubleValue(percentage) },
"id" to { StringValue(id) },
"bucket" to { StringValue(bucket) },
"bucket" to { StringValue(bucket.name) },
"width" to { DoubleValue(width.toDouble()) },
"height" to { DoubleValue(height.toDouble()) },
"spawnable_position_type" to { StringValue(spawnablePositionType.name) },
@@ -135,10 +136,6 @@ abstract class SpawnDetail : ModDependant {
containsNullValues = true
LOGGER.error("Spawn Detail with id $id is invalid as it contains invalid values in its anticonditions (commonly caused by trailing comma in biomes or other arrays)")
}
if (bucket.isBlank()) {
containsNullValues = true
LOGGER.error("Spawn Detail with id $id is invalid as it does not have a bucket assigned")
}
return super.isModDependencySatisfied() && !containsNullValues
}
@@ -166,7 +163,7 @@ abstract class SpawnDetail : ModDependant {
*/
fun choose(
spawnablePosition: SpawnablePosition,
bucket: String,
bucket: SpawnBucket,
selectionData: SpawnSelectionData
): SpawnAction<*> {
val action = createSpawnAction(spawnablePosition, bucket, selectionData)
@@ -178,10 +175,10 @@ abstract class SpawnDetail : ModDependant {
return action
}
/** Instantiates an action at the given [SpawnablePosition] for the given spawn bucket. */
/** Instantiates an action at the given [SpawnablePosition] for the given [SpawnBucket]. */
protected abstract fun createSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: String,
bucket: SpawnBucket,
selectionData: SpawnSelectionData
): SpawnAction<*>
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnLoader
import com.cobblemon.mod.common.api.spawning.SpawnSet
import com.cobblemon.mod.common.api.spawning.condition.PrecalculationResult
@@ -60,13 +61,12 @@ class SpawnPool(val name: String) : JsonDataRegistry<SpawnSet>, Iterable<SpawnDe
}
val details = mutableListOf<SpawnDetail>()
/** Tracks which buckets are in use by the pool, to prevent spawners for rolling buckets that don't exist. */
val buckets = mutableListOf<String>()
var precalculation: PrecalculationResult<*> = RootPrecalculation.generate(details, emptyList())
val precalculators = mutableListOf<SpawningPrecalculation<*>>()
override fun iterator() = details.iterator()
fun addPrecalculators(vararg precalculators: SpawningPrecalculation<*>): SpawnPool {
this.precalculators.addAll(precalculators)
precalculate()
@@ -88,9 +88,6 @@ class SpawnPool(val name: String) : JsonDataRegistry<SpawnSet>, Iterable<SpawnDe
* be slow, especially if there are many precalculators and spawns.
*/
fun precalculate() {
buckets.clear()
buckets.addAll(details.asSequence().map(SpawnDetail::bucket).distinct())
if (precalculators.isEmpty()) {
precalculation = RootPrecalculation.generate(details, emptyList())
} else {
@@ -106,7 +103,7 @@ class SpawnPool(val name: String) : JsonDataRegistry<SpawnSet>, Iterable<SpawnDe
* to leverage the precalculation to get a smaller list of spawns as quickly
* as possible.
*/
fun retrieve(bucket: String, spawnablePosition: SpawnablePosition): List<SpawnDetail> {
fun retrieve(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): List<SpawnDetail> {
return precalculation.retrieve(bucket, spawnablePosition)
}
@@ -145,7 +145,7 @@ class FishingSpawnCause(
return
}
pokemonEntity.pokemon.isAlpha = true
pokemonEntity.pokemon.initializeMovesetFromDefault()
pokemonEntity.pokemon.initializeMovesetWithRandomTm(2)
if (!pokemonEntity.level().isClientSide) {
// Alpha status is applied after entity creation in bait flows, so rebuild the brain
// to ensure alpha-only sensors/memories are actually attached.
@@ -8,19 +8,19 @@
package com.cobblemon.mod.common.api.spawning.influence
import com.cobblemon.mod.common.api.spawning.SpawnBucket
/**
* A [SpawningInfluence] that multiplies bucket weights by the provided values
* and then normalizes the resulting weights so that their total adds up to 100.
*
* No longer used by the mod, but kept for external suers.
*/
class BucketMultiplyingInfluence(
val multipliers: Map<String, Float>,
) : SpawningInfluence {
override fun affectBucketWeights(bucketWeights: MutableMap<String, Float>) {
val updates = mutableMapOf<String, Float>()
override fun affectBucketWeights(bucketWeights: MutableMap<SpawnBucket, Float>) {
val updates = mutableMapOf<SpawnBucket, Float>()
for ((bucket, weight) in bucketWeights) {
val multiplier = multipliers[bucket]
val multiplier = multipliers[bucket.name]
if (multiplier != null) {
updates[bucket] = weight * multiplier
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.spawning.influence
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import kotlin.math.pow
/**
@@ -32,7 +33,7 @@ open class BucketNormalizingInfluence(
val gradient: Float = 0.2F,
val firstTier: Float = 1.29F
) : SpawningInfluence {
override fun affectBucketWeights(bucketWeights: MutableMap<String, Float>) {
override fun affectBucketWeights(bucketWeights: MutableMap<SpawnBucket, Float>) {
if (tier == 0) {
return
}
@@ -150,7 +150,7 @@ class SaccharineLogSlatheredInfluence(val pos: BlockPos? = null) : SpawningInflu
val chance = Cobblemon.config.honeySlatherAlphaChance
if (chance > 0 && Random.nextInt(chance) == 0) {
pokemon.isAlpha = true
pokemon.initializeMovesetFromDefault()
pokemon.initializeMovesetWithRandomTm(2)
}
}
@@ -21,8 +21,6 @@ import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.AlphaLevelMatchingSensor
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.Entity
/**
@@ -49,13 +47,6 @@ open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (
Effects.getEffectFunction(effect.type)?.invoke(entity, effect)
}
}
if (entity.pokemon.isAlpha) {
val spawningPlayer = action.spawnablePosition.cause.entity as? ServerPlayer
if (spawningPlayer != null) {
AlphaLevelMatchingSensor.applyLevelMatching(entity, spawningPlayer)
}
}
}
}
@@ -139,4 +130,4 @@ open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (
if (used) markUsed()
return super.affectWeight(detail, spawnablePosition, newWeight)
}
}
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.spawning.influence
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
@@ -38,9 +39,9 @@ interface SpawningInfluence {
/** Applies some influence over the entity that's been spawned. */
fun affectSpawn(action: SpawnAction<*>, entity: Entity) {}
/** Applies some influence over the weight of spawn buckets. */
fun affectBucketWeights(bucketWeights: MutableMap<String, Float>) {}
fun affectBucketWeights(bucketWeights: MutableMap<SpawnBucket, Float>) {}
/** Normalizes all bucket weights to add up to 100 */
fun normalizeBucketWeights(bucketWeights: MutableMap<String, Float>) {
fun normalizeBucketWeights(bucketWeights: MutableMap<SpawnBucket, Float>) {
val sum = bucketWeights.values.sum()
val to100 = 100F / sum
bucketWeights.keys.toList().forEach {
@@ -55,5 +56,5 @@ interface SpawningInfluence {
*
* You should make sure the spawn position satisfies any spawn details that you inject.
*/
fun injectSpawns(bucket: String, spawnablePosition: SpawnablePosition): List<SpawnDetail>? = null
fun injectSpawns(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): List<SpawnDetail>? = null
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.spawning.preset
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePositionType
import com.cobblemon.mod.common.util.adapters.RegisteredSpawnablePositionAdapter
import com.google.gson.GsonBuilder
@@ -38,34 +39,11 @@ class BestSpawnerConfig {
"surface" to 0.01F,
"seafloor" to 1F
)
val worldBuckets = mutableMapOf(
"common" to 94F,
"uncommon" to 5F,
"rare" to 0.5F,
"ultra-rare" to 0.2F,
"boss" to 0.3F,
)
val fishingBuckets = mutableMapOf(
"common" to 83.25F,
"uncommon" to 11.25F,
"rare" to 4.125F,
"ultra-rare" to 1.375F
)
val activatedHabitatBuckets: MutableMap<String, Float> = mutableMapOf(
"common" to 83.25F,
"uncommon" to 11.25F,
"rare" to 4.125F,
"ultra-rare" to 1.375F
)
val pokeSnackBuckets = mutableMapOf(
"common" to 83.25F,
"uncommon" to 11.25F,
"rare" to 4.125F,
"ultra-rare" to 1.375F
val buckets = mutableListOf(
SpawnBucket("common", 94.3F),
SpawnBucket("uncommon", 5F),
SpawnBucket("rare", 0.5F),
SpawnBucket("ultra-rare", 0.2F)
)
companion object {
@@ -8,12 +8,13 @@
package com.cobblemon.mod.common.api.spawning.preset
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnDetailPresets
import com.cobblemon.mod.common.api.spawning.SpawnLoader
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.position.SpawnablePositionType
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.multiplier.WeightMultiplier
import com.cobblemon.mod.common.api.spawning.position.SpawnablePositionType
import com.cobblemon.mod.common.util.MergeMode
import com.cobblemon.mod.common.util.adapters.SpawnDetailAdapter
import com.google.gson.JsonObject
@@ -40,7 +41,7 @@ import com.google.gson.annotations.SerializedName
* @since July 8th, 2022
*/
abstract class SpawnDetailPreset {
var bucket: String? = null
var bucket: SpawnBucket? = null
var spawnDetailType: String? = null
@SerializedName("spawnablePositionType", alternate = ["context"])
var spawnablePositionType: SpawnablePositionType<*>? = null
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.spawning.selection
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
@@ -87,12 +88,12 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
class SpawnablePositionSelectionData(
val spawner: Spawner,
val spawnablePositions: List<SpawnablePosition>,
val selectingSpawnInformation: MutableMap<String, SpawnablePositionBucketSelectionData>,
val selectingSpawnInformation: MutableMap<SpawnBucket, SpawnablePositionBucketSelectionData>,
): SpawnSelectionData {
override val spawnActions = mutableListOf<SpawnAction<*>>()
override val context = mutableMapOf<String, Any>()
fun getDataForBucket(bucket: String): SpawnablePositionBucketSelectionData {
fun getDataForBucket(bucket: SpawnBucket): SpawnablePositionBucketSelectionData {
val existing = selectingSpawnInformation[bucket]
if (existing != null) {
return existing
@@ -129,14 +130,14 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
spawner: Spawner,
spawnablePositions: List<SpawnablePosition>
): SpawnablePositionSelectionData {
val selectingSpawnInformation: MutableMap<String, SpawnablePositionBucketSelectionData> = mutableMapOf()
val selectingSpawnInformation: MutableMap<SpawnBucket, SpawnablePositionBucketSelectionData> = mutableMapOf()
return SpawnablePositionSelectionData(spawner, spawnablePositions, selectingSpawnInformation)
}
override fun selectSpawnAction(
spawner: Spawner,
bucket: String,
bucket: SpawnBucket,
selectionData: SpawnablePositionSelectionData,
): SpawnAction<*>? {
val selectingSpawnInformation = selectionData.getDataForBucket(bucket) ?: return null
@@ -148,7 +149,7 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
if (percentSum > 100) {
Cobblemon.LOGGER.warn(
"""
A spawn list for ${spawner.name} exceeded 100% on percentage sums in bucket $bucket...
A spawn list for ${spawner.name} exceeded 100% on percentage sums in bucket ${bucket.name}...
This means you don't understand how this option works.
""".trimIndent()
)
@@ -185,7 +186,7 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
override fun getTotalWeights(
spawner: Spawner,
bucket: String,
bucket: SpawnBucket,
spawnablePositions: List<SpawnablePosition>
): Map<SpawnDetail, Float> {
val selectionData = getSelectionData(spawner, spawnablePositions).getDataForBucket(bucket)
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.spawning.selection
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
@@ -104,7 +105,7 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
override val spawnActions = mutableListOf<SpawnAction<*>>()
override val context = mutableMapOf<String, Any>()
val bucketData = mutableMapOf<String, MutableMap<SpawnablePositionType<*>, SpawnablePositionSelectionData>>()
val bucketData = mutableMapOf<SpawnBucket, MutableMap<SpawnablePositionType<*>, SpawnablePositionSelectionData>>()
override fun removeSpawnDetails(shouldRemove: (SpawnDetail) -> Boolean) {
bucketData.values.forEach {
@@ -120,7 +121,7 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
}
}
fun getDataForBucket(bucket: String): MutableMap<SpawnablePositionType<*>, SpawnablePositionSelectionData> {
fun getDataForBucket(bucket: SpawnBucket): MutableMap<SpawnablePositionType<*>, SpawnablePositionSelectionData> {
bucketData[bucket]?.let { return it }
val spawnablePositionTypeToSpawns = mutableMapOf<SpawnablePositionType<*>, SpawnablePositionSelectionData>()
@@ -148,7 +149,7 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
override fun selectSpawnAction(
spawner: Spawner,
bucket: String,
bucket: SpawnBucket,
selectionData: SeparatedSelectionData
): SpawnAction<*>? {
val bucketData = selectionData.getDataForBucket(bucket)
@@ -171,7 +172,7 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
if (percentSum > 100) {
LOGGER.warn(
"""
A spawn list for ${spawner.name} exceeded 100% on percentage sums in bucket $bucket...
A spawn list for ${spawner.name} exceeded 100% on percentage sums in bucket ${bucket.name}...
This means you don't understand how this option works.
""".trimIndent()
)
@@ -243,7 +244,7 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
override fun getTotalWeights(
spawner: Spawner,
bucket: String,
bucket: SpawnBucket,
spawnablePositions: List<SpawnablePosition>
): Map<SpawnDetail, Float> {
val selectionData = getSelectionData(spawner, spawnablePositions).getDataForBucket(bucket)
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.spawning.selection
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
@@ -32,13 +33,13 @@ interface SpawningSelector<T : SpawnSelectionData> {
fun selectSpawnAction(
spawner: Spawner,
bucket: String,
bucket: SpawnBucket,
selectionData: T
): SpawnAction<*>?
fun select(
spawner: Spawner,
bucketSelector: () -> String,
bucketSelector: () -> SpawnBucket,
spawnablePositions: List<SpawnablePosition>,
maxSpawns: Int
): List<SpawnAction<*>> {
@@ -64,7 +65,7 @@ interface SpawningSelector<T : SpawnSelectionData> {
return spawnActions
}
fun getProbabilities(spawner: Spawner, bucket: String, spawnablePositions: List<SpawnablePosition>): Map<SpawnDetail, Float> {
fun getProbabilities(spawner: Spawner, bucket: SpawnBucket, spawnablePositions: List<SpawnablePosition>): Map<SpawnDetail, Float> {
val weights = getTotalWeights(spawner, bucket, spawnablePositions)
val totalWeight = weights.values.sum()
val percentages = mutableMapOf<SpawnDetail, Float>()
@@ -72,5 +73,5 @@ interface SpawningSelector<T : SpawnSelectionData> {
return percentages
}
fun getTotalWeights(spawner: Spawner, bucket: String, spawnablePositions: List<SpawnablePosition>): Map<SpawnDetail, Float>
fun getTotalWeights(spawner: Spawner, bucket: SpawnBucket, spawnablePositions: List<SpawnablePosition>): Map<SpawnDetail, Float>
}
@@ -25,8 +25,7 @@ import com.cobblemon.mod.common.api.spawning.selection.SpawningSelector
open class BasicSpawner(
override val name: String,
override var spawnPool: SpawnPool,
override var maxPokemonPerChunk: Float = Cobblemon.config.pokemonPerChunk,
override val buckets: Map<String, Float>
override var maxPokemonPerChunk: Float = Cobblemon.config.pokemonPerChunk
) : Spawner {
override var selector: SpawningSelector<*> = SpawningSelector.DEFAULT
override var generator: SpawningZoneGenerator = Cobblemon.spawningZoneGenerator
@@ -8,11 +8,11 @@
package com.cobblemon.mod.common.api.spawning.spawner
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonPoiTypes
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.api.spawning.influence.BucketMultiplyingInfluence
import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluence
import com.cobblemon.mod.common.api.spawning.influence.PlayerLevelRangeInfluence
import com.cobblemon.mod.common.api.spawning.influence.PlayerLevelRangeInfluence.Companion.TYPICAL_VARIATION
@@ -72,9 +72,16 @@ object FishingSpawnerFactory {
fun createSharedSpawner(): BasicSpawner {
return BasicSpawner(
name = "fishing",
spawnPool = spawnPool,
buckets = Cobblemon.bestSpawner.config.fishingBuckets,
)
spawnPool = spawnPool
).also { spawner ->
spawner.influences += BucketMultiplyingInfluence(
multipliers = mapOf(
"uncommon" to 2.25f,
"rare" to 5.5f,
"ultra-rare" to 5.5f,
)
)
}
}
fun findHabitatInfluences(world: ServerLevel, position: BlockPos, spawner: Spawner): Array<HabitatBlockEntity> {
Loaded 100 of 2969 files, more files were not shown because too many files have changed in this diff. Show more