Compare commits

..
712 changed files with 4763 additions and 32164 deletions

No files matched your search

+44 -4
View File
@@ -1,15 +1,55 @@
# Changelog
## [1.8.x (MONTH xth, 2026)](#1-8-x)
## [1.8.3 (MONTH xth, 2026)](#1-8-3)
### Additions
### Pokémon Added
#### Generation 4
#### Generation 5
#### Generation 6
#### Generation 7
#### Generation 8
#### Generation 9
### Model updates for the following Pokémon
### Animation updates for the following Pokémon
### Changes
- Changed the Misc icon in the Campfire Pot Recipe Book to Health Candy
- Moved EXP Candies from the Medicine recipe book category to the Misc category
### Fixes
- Fixed datapacks with item interaction evolutions that don't specify an item crashing the world on load.
- Fixed the Pokédex evolution tree crashing when a datapack's dex entry is missing an evolution's form.
- Fixed the Pokédex evolution tree crashing with a stack overflow on evolutions that loop back on themselves.
- Fixed the Pokédex evolution tree crashing when looking at a dex that is not part of the "National Dex" aggregate.
- Fixed evolution trees not building properly when a pokemon has several pokedex entries for different regions.
- Fixed form-changing evolutions sometimes resolving to the source form in the Pokédex evolution tree.
- Broken datapack evolutions are now logged and skipped in the Pokédex evolution tree instead of crashing.
- Fixed friendship booster items not being recognised as such
- Fixed conflicts with other mods that would make use of recipe books in certain ways (Farmer's Delight Cooking Pot)
- Fixed Pokémon battles stalling when a sidemod cancels the `THROWN_POKEBALL_HIT` hit event
- Fixed choice feature tab completion in `spawnpokemon/pokegive/pokeedit` commands
- Fixed MoLang that's targeting players intermittedly failing when they have died, needing them to relog
- Fixed all pots of mod on NeoForge
- Fixed party overlay not hiding when player opens the pokedex scanner
- Added compat for AppleSkin
### Developer
- Bumped NeoForge to `21.1.195` to get access to MixinExtras `0.5` and the expressions annotation
- New Platform Event `PLAYER_RESPAWN` which is fired when a player respawns (either through death or returning from the end)
- Platform Events `SERVER_PLAYER_TICK_PRE` and `SERVER_PLAYER_TICK_POST` no longer fire when the player is dead
- it is now tied much closer to the actual entity rather than the player (or their connection)
### Molang & Datapacks
### Particles
- Fixed `q.pokemon.nature` returning class instance instead of proper string value (`cobblemon:adamant`)
- Fixed `q.entity.is_looking_at` result being always `1` for whatever entity passed as target
### Localization
- Updated translations for:
- Updated translations for:
-107
View File
@@ -1,107 +0,0 @@
# Changelog
## [1.8.3 (October 25th, 2026)](#1-8-3)
### Additions
- Added compatability with AppleSkin.
### New Rideable Pokémon
- Vikavolt
- Fearow
### Particles
## New Move Animations
- Water Shuriken
### Cries added/updated for the following Pokémon
- Hoppip
- Skiploom
- Jumpluff
- Houndour
- Houndoom
- Wurmple
- Silcoon
- Beautifly
- Cascoon
- Dustox
- Surskit
- Masquerain
- Torkoal
- Starly
- Staravia
- Staraptor
- Combee
- Vespiquen
- Dusknoir
- Helioptile
- Heliolisk
- Grubbin
- Charjabug
- Vikavolt
- Applin (updated)
- Flapple (updated)
- Appletun
- Runerigus
- Dipplin
### Animation updates for the following Pokémon
- Vikavolt
- Magby
- Magmar
- Ninetales (+ Alolan)
- Spearow
- Fearow
- Squawkabilly
- Mudkip
- Marshtomp
- Swampert
### Changes
- Changed the Misc icon in the Campfire Pot Recipe Book to Health Candy.
- Moved EXP Candies from the Medicine recipe book category to the Misc category.
- Removed Dynamic Lights data from the Magmarizer item for consistency with the block.
- Added `doPokemonPredation` game rule, which controls aggressiveness of Pokémon with the `hate_entity` brain task defined.
- Added `predation_chance` server config value, which controls the possibility of attacking the hated entity found in `hate_entity` brain tasks.
- Prehistoric Eroded Pillar generates slightly more frequently.
- Togekiss was changed from a jet-style mount to bird-style.
### Fixes
- Fixed datapacks with item interaction evolutions that don't specify an item crashing the world on load.
- Fixed the Pokédex evolution tree crashing when a datapack's dex entry is missing an evolution's form.
- Fixed the Pokédex evolution tree crashing with a stack overflow on evolutions that loop back on themselves.
- Fixed the Pokédex evolution tree crashing when looking at a dex that is not part of the "National Dex" aggregate.
- Fixed some display names in the Pokédex evolution tree having unintended font colours.
- Fixed evolution trees not building properly when a Pokémon has several Pokédex entries for different regions.
- Fixed form-changing evolutions sometimes resolving to the source form in the Pokédex evolution tree.
- Broken datapack evolutions are now logged and skipped in the Pokédex evolution tree instead of crashing.
- Fixed the `alpha` property having no tab completion in commands such as `/pokespawn`, `/pokegive` and `/pokeedit`, and added the missing `dmax_level`, `gmax_factor` and `tradable` aliases to tab completion.
- Fixed friendship booster items not being recognised as such.
- Fixed conflicts with other mods that would make use of recipe books in certain ways (Farmer's Delight Cooking Pot).
- Fixed Pokémon battles stalling when a sidemod cancels the `THROWN_POKEBALL_HIT` hit event.
- Fixed choice feature tab completion in `spawnpokemon/pokegive/pokeedit` commands.
- Fixed MoLang that's targeting players intermittedly failing when they have died, requiring them to relog.
- Fixed a NeoForge bug where none of the Cobblemon plants/berries could be placed into pots.
- Fixed party overlay not hiding when the player opens the Pokédex scanner.
- Fixed `hate_entity` task focusing on its own entity, therefore enabling deadly warfare between Zangoose and Seviper.
- Prehistoric Frozen Pond, Prehistoric Frozen Spike, and Prehistoric Lush Den now actually generate (oops).
- Fixed Frogadier floating in its `battle_idle` animation.
- Fixed absence or incorrect mergers in spawning conditions, causing the max height/depth of conditions to use the min value instead.
- Added stricter riding checks on server side to prevent possible packet forgery when a player tries to ride Pokémon.
- Fixed Pokémon cries and animation sounds played from GUIs, such as the starter selection screen and the Pokédex, being tied to the Master volume slider instead of Friendly Creatures.
- Fixed Archeops T-posing during its `air_cry` animation.
### Developer
- Bumped NeoForge to `21.1.195` to get access to MixinExtras `0.5` and the expressions annotation.
- New Platform Event `PLAYER_RESPAWN` which is fired when a player respawns (either through death or returning from the end).
- Platform Events `SERVER_PLAYER_TICK_PRE` and `SERVER_PLAYER_TICK_POST` no longer fire when the player is dead.
- It is now tied much closer to the actual entity rather than the player or their connection.
### Molang & Datapacks
- Fixed `q.pokemon.nature` returning class instance instead of proper string value (`cobblemon:adamant`).
- Fixed `q.entity.is_looking_at` result being always `1` for whatever entity passed as the target parameter.
- Added hate chance optional parameter to `hate_entity` task. `1.0` will make an entity always attack the target.
### Localization
- Updated translations for:
- Russian
- Japanese
- Brazilian Portuguese
-25
View File
@@ -1,25 +0,0 @@
# Changelog
## [1.8.4 (October 27th, 2026)](#1-8-4)
### Changes
- The Froakie line now blinks. The world will never be the same.
### Fixes
- Fixed heaps of spawns not working. This was caused by us missing something before release. RIP.
- Fixed a crash caused by Fearow blinking while you're riding one. Makes sense to me.
- Fixed Minior's "Shields Down" ability being very broken.
- Fixed Dusclops using Squirtle's cry sound (lol).
- Fixed blank Cooking Pot recipe book entries and missing tabs on Fabric.
- Fixed a hitbox issue with placeable Orb held items attached to a ceiling.
- Fixed something sneaky. I'll tell you about it later.
### Molang & Datapacks
- Added `q.target_fainted` function to move action effects that checks if any opponents fainted from the move.
### Particles
- Updated Water Shuriken effect to be timed better when the opponent faints.
### Localization
- Updated translations for:
- Korean
- Simplified Chinese
@@ -69,7 +69,7 @@ public abstract class ClientRecipeBookMixin {
value = "INVOKE",
target = "Lcom/google/common/collect/ImmutableMap;copyOf(Ljava/util/Map;)Lcom/google/common/collect/ImmutableMap;"
))
private void addCustomAggregateCategories(Iterable<RecipeHolder<?>> recipes, RegistryAccess registryAccess, CallbackInfo ci, @Local(ordinal = 1) Map<RecipeBookCategories, List<RecipeCollection>> map2) {
private void addCustomAggregateCategories(Iterable<RecipeHolder<?>> recipes, RegistryAccess registryAccess, CallbackInfo ci, @Local(name = "map2") Map<RecipeBookCategories, List<RecipeCollection>> map2) {
CobblemonRecipeCategories.Companion.getCustomAggregateCategories().forEach((recipeBookCategories, list) -> map2.put(recipeBookCategories, (List) list.stream().flatMap((recipeBookCategoriesx) -> ((List) map2.getOrDefault(recipeBookCategoriesx, ImmutableList.of())).stream()).collect(ImmutableList.toImmutableList())));
}
}
@@ -20,14 +20,14 @@ import org.spongepowered.asm.mixin.injection.At;
@Mixin(RecipeBookComponent.class)
public class RecipeBookComponentMixin {
@Definition(id = "recipeBookCategories", local = @Local(type = RecipeBookCategories.class))
@Definition(id = "recipeBookCategories", local = @Local(name = "recipeBookCategories"))
@Definition(id = "FURNACE_SEARCH", field = "Lnet/minecraft/client/RecipeBookCategories;FURNACE_SEARCH:Lnet/minecraft/client/RecipeBookCategories;")
@Expression("recipeBookCategories != FURNACE_SEARCH")
@ModifyExpressionValue(
method = "updateTabs",
at = @At(value = "MIXINEXTRAS:EXPRESSION")
)
private boolean notCookingPotSearch(boolean original, @Local RecipeBookCategories recipeBookCategories) {
private boolean notCookingPotSearch(boolean original, @Local(name = "recipeBookCategories") RecipeBookCategories recipeBookCategories) {
return original && recipeBookCategories != CobblemonRecipeCategories.COOKING_POT_SEARCH.toVanillaCategory();
}
}
@@ -60,6 +60,8 @@ import com.cobblemon.mod.common.api.storage.pc.PCStore
import com.cobblemon.mod.common.api.storage.pc.link.PCLinkManager
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreManager
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.api.storage.player.adapter.BadgeMongoBackend
import com.cobblemon.mod.common.api.storage.player.adapter.BadgeNbtBackend
import com.cobblemon.mod.common.api.storage.player.adapter.DexDataMongoBackend
import com.cobblemon.mod.common.api.storage.player.adapter.DexDataNbtBackend
import com.cobblemon.mod.common.api.storage.player.adapter.PlayerDataJsonBackend
@@ -391,10 +393,13 @@ object Cobblemon {
val tmMoveFactory = CachedPlayerDataStoreFactory(TMMoveNbtBackend())
tmMoveFactory.setup(server)
val badgeFactory = CachedPlayerDataStoreFactory(BadgeNbtBackend())
badgeFactory.setup(server)
playerDataManager.setFactory(generalJsonFactory, PlayerInstancedDataStoreTypes.GENERAL)
playerDataManager.setFactory(pokedexNbtFactory, PlayerInstancedDataStoreTypes.POKEDEX)
playerDataManager.setFactory(tmMoveFactory, PlayerInstancedDataStoreTypes.TM_MOVES)
playerDataManager.setFactory(badgeFactory, PlayerInstancedDataStoreTypes.BADGES)
if (config.storageFormat == "nbt") {
NBTStoreAdapter(pokemonStoreRoot.absolutePath, useNestedFolders = true, folderPerClass = true)
@@ -425,10 +430,15 @@ object Cobblemon {
TMMoveMongoBackend(mongoClient, config.mongoDBDatabaseName, "TMMovesCollection")
)
tmMovesMongoFactory.setup(server)
val badgesMongoFactory = CachedPlayerDataStoreFactory(
BadgeMongoBackend(mongoClient, config.mongoDBDatabaseName, "BadgesCollection")
)
badgesMongoFactory.setup(server)
playerDataManager.setFactory(generalMongoFactory, PlayerInstancedDataStoreTypes.GENERAL)
playerDataManager.setFactory(pokedexMongoFactory, PlayerInstancedDataStoreTypes.POKEDEX)
playerDataManager.setFactory(tmMovesMongoFactory, PlayerInstancedDataStoreTypes.TM_MOVES)
playerDataManager.setFactory(badgesMongoFactory, PlayerInstancedDataStoreTypes.BADGES)
MongoDBStoreAdapter(mongoClient, config.mongoDBDatabaseName)
} catch (e: ClassNotFoundException) {
LOGGER.error("MongoDB driver not found.")
@@ -613,6 +623,7 @@ object Cobblemon {
this.implementation.registerCommandArgument(cobblemonResource("transform_type"), TransformTypeArgumentType::class, SingletonArgumentInfo.contextFree(TransformTypeArgumentType::transformType))
this.implementation.registerCommandArgument(cobblemonResource("model_part"), ModelPartArgumentType::class, SingletonArgumentInfo.contextFree(ModelPartArgumentType::modelPart))
this.implementation.registerCommandArgument(cobblemonResource("tm"), TmArgumentType::class, SingletonArgumentInfo.contextFree(TmArgumentType::tm))
this.implementation.registerCommandArgument(cobblemonResource("badge"), BadgeArgumentType::class, SingletonArgumentInfo.contextFree(BadgeArgumentType::badge))
}
}
@@ -73,6 +73,7 @@ object CobblemonCommands {
PcTakeCommand.register(dispatcher)
GiveTmCommand.register(dispatcher)
TmCommand.register(dispatcher)
BadgeCommand.register(dispatcher)
// Possibly lock down registration if and only if under dev environment or running in an environment
// with a certain system environment variable set
@@ -42,14 +42,9 @@ import com.cobblemon.mod.common.client.net.npc.OpenNPCEditorHandler
import com.cobblemon.mod.common.client.net.orientation.ClientboundUpdateOrientationHandler
import com.cobblemon.mod.common.client.net.pasture.ClosePastureHandler
import com.cobblemon.mod.common.client.net.pasture.OpenPastureHandler
import com.cobblemon.mod.common.client.net.pasture.UpdatePastureRangeHandler
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureRangePacket
import com.cobblemon.mod.common.net.messages.server.pasture.AdjustPastureRangePacket
import com.cobblemon.mod.common.net.serverhandling.pasture.AdjustPastureRangeHandler
import com.cobblemon.mod.common.client.net.pasture.PokemonPasturedHandler
import com.cobblemon.mod.common.client.net.pasture.PokemonUnpasturedHandler
import com.cobblemon.mod.common.client.net.pasture.UpdatePastureConflictFlagHandler
import com.cobblemon.mod.common.client.net.pasture.UpdatePastureRecoveryHandler
import com.cobblemon.mod.common.client.net.pokedex.ServerConfirmedRegisterHandler
import com.cobblemon.mod.common.client.net.pokemon.update.ClientboundSeatAssignmentPacketHandler
import com.cobblemon.mod.common.client.net.pokemon.update.ClientboundUpdateRidingStateHandler
@@ -106,7 +101,6 @@ import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonPasturedPacket
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonUnpasturedPacket
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureConflictFlagPacket
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureRecoveryPacket
import com.cobblemon.mod.common.net.messages.client.pokedex.ServerConfirmedRegisterPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.*
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.AddEvolutionPacket
@@ -438,6 +432,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(SpeciesFeatureAssignmentSyncPacket.ID, SpeciesFeatureAssignmentSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(NaturalMaterialRegistrySyncPacket.ID, NaturalMaterialRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(FossilRegistrySyncPacket.ID, FossilRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BadgeRegistrySyncPacket.ID, BadgeRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(NPCRegistrySyncPacket.ID, NPCRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(PokeRodRegistrySyncPacket.ID, PokeRodRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(ScriptRegistrySyncPacket.ID, ScriptRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
@@ -479,12 +474,10 @@ object CobblemonNetwork {
// Pasture
list.add(PacketRegisterInfo(OpenPasturePacket.ID, OpenPasturePacket::decode, OpenPastureHandler))
list.add(PacketRegisterInfo(UpdatePastureRangePacket.ID, UpdatePastureRangePacket::decode, UpdatePastureRangeHandler))
list.add(PacketRegisterInfo(ClosePasturePacket.ID, ClosePasturePacket::decode, ClosePastureHandler))
list.add(PacketRegisterInfo(PokemonPasturedPacket.ID, PokemonPasturedPacket::decode, PokemonPasturedHandler))
list.add(PacketRegisterInfo(PokemonUnpasturedPacket.ID, PokemonUnpasturedPacket::decode, PokemonUnpasturedHandler))
list.add(PacketRegisterInfo(UpdatePastureConflictFlagPacket.ID, UpdatePastureConflictFlagPacket::decode, UpdatePastureConflictFlagHandler))
list.add(PacketRegisterInfo(UpdatePastureRecoveryPacket.ID, UpdatePastureRecoveryPacket::decode, UpdatePastureRecoveryHandler))
// Orientation
list.add(PacketRegisterInfo(ClientboundUpdateOrientationPacket.ID, ClientboundUpdateOrientationPacket::decode, ClientboundUpdateOrientationHandler))
@@ -599,7 +592,6 @@ object CobblemonNetwork {
// Pasture
list.add(PacketRegisterInfo(PasturePokemonPacket.ID, PasturePokemonPacket::decode, PasturePokemonHandler))
list.add(PacketRegisterInfo(AdjustPastureRangePacket.ID, AdjustPastureRangePacket::decode, AdjustPastureRangeHandler))
list.add(PacketRegisterInfo(UnpasturePokemonPacket.ID, UnpasturePokemonPacket::decode, UnpasturePokemonHandler))
list.add(PacketRegisterInfo(UnpastureAllPokemonPacket.ID, UnpastureAllPokemonPacket::decode, UnpastureAllPokemonHandler))
list.add(PacketRegisterInfo(SetPastureConflictPacket.ID, SetPastureConflictPacket::decode, SetPastureConflictHandler))
@@ -666,4 +658,4 @@ object CobblemonNetwork {
fun sendPacketToPlayer(player: ServerPlayer, packet: NetworkPacket<*>) {
Cobblemon.implementation.networkManager.sendPacketToPlayer(player, packet)
}
}
}
@@ -15,13 +15,11 @@ import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.config.task.WanderTaskConfig.Companion.WANDER
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.util.Either
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
@@ -71,14 +69,7 @@ class AirWanderTaskConfig : SingleTaskConfig {
val rotVec = entity.getViewVector(0F)
pathCooldown.setWithExpiry(true, 60L)
val tethering = (entity as? PokemonEntity)?.tethering
val horizontal = horizontalRange.resolveInt(runtime)
val vertical = verticalRange.resolveInt(runtime)
val minUpwards = minUpwardsMovement.resolveInt(runtime)
val minDownwards = minDownwardsMovement.resolveInt(runtime)
val target = (0 until 10).asSequence()
.mapNotNull { HoverRandomPos.getPos(entity, horizontal, vertical, rotVec.x, rotVec.y, 1.5707964f, minUpwards, minDownwards) }
.firstOrNull { tethering?.canRoamTo(BlockPos.containing(it)) != false }
val target = HoverRandomPos.getPos(entity, horizontalRange.resolveInt(runtime), verticalRange.resolveInt(runtime), rotVec.x, rotVec.y, 1.5707964f, minUpwardsMovement.resolveInt(runtime), minDownwardsMovement.resolveInt(runtime))
?: return@Trigger false
walkTarget.set(WalkTarget(target, speedMultiplier.resolveFloat(runtime), 3))
lookTarget.set(BlockPosTracker(target))
@@ -87,4 +78,4 @@ class AirWanderTaskConfig : SingleTaskConfig {
}
}
}
}
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
@@ -18,7 +17,6 @@ import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.withQueryValue
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
@@ -36,8 +34,6 @@ import net.minecraft.world.entity.ai.sensing.SensorType
class HateEntityTaskConfig : SingleTaskConfig {
val entityCondition: Expression = "true".asExpression()
val range: ExpressionOrEntityVariable = Either.left("24".asExpression())
val chance: Float = Cobblemon.config.predationChance
private val coercedChance = chance.coerceIn(0f, 1f)
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = emptyList<MoLangConfigVariable>()
override fun createTask(
@@ -47,44 +43,24 @@ class HateEntityTaskConfig : SingleTaskConfig {
val range = range.resolveFloat(behaviourConfigurationContext.runtime)
behaviourConfigurationContext.addMemories(
MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES,
MemoryModuleType.ANGRY_AT,
MemoryModuleType.ATTACK_TARGET
MemoryModuleType.ANGRY_AT
)
behaviourConfigurationContext.addSensors(SensorType.NEAREST_LIVING_ENTITIES)
return BehaviorBuilder.create { instance ->
instance.group(
instance.present(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES),
instance.registered(MemoryModuleType.ANGRY_AT),
instance.registered(MemoryModuleType.ATTACK_TARGET),
).apply(instance) { entities, angryAt, attackTarget ->
instance.registered(MemoryModuleType.ANGRY_AT)
).apply(instance) { entities, angryAt ->
Trigger { world, entity, _ ->
// bail if gamerule is disabled
if (!world.gameRules.getBoolean(CobblemonGameRules.DO_POKEMON_PREDATION)) return@Trigger false
val target = instance.get(entities).findClosest { hated ->
mainThreadRuntime.withQueryValue("entity", hated.asMostSpecificMoLangValue())
val target = instance.get(entities).findClosest {
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
return@findClosest mainThreadRuntime.resolveBoolean(entityCondition)
}.orElse(null)
var shouldTrigger = false
if (target != null && target.distanceTo(entity) <= range) {
shouldTrigger = when {
// set always attacking if activity chance is equal to 1.0
coercedChance == 1f -> {
angryAt.set(target.uuid)
attackTarget.set(target)
true
}
// roll the dice in other way
coercedChance != 1f && world.random.nextFloat() < coercedChance -> {
angryAt.set(target.uuid)
attackTarget.set(target)
true
}
// fallback to peaceful behavior in unattended cases
else -> false
}
angryAt.set(target.uuid)
}
return@Trigger shouldTrigger
return@Trigger true
}
}
}
@@ -17,7 +17,6 @@ import com.cobblemon.mod.common.api.ai.config.task.WanderTaskConfig.Companion.WA
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveFloat
@@ -96,7 +95,6 @@ class WaterWanderTaskConfig : SingleTaskConfig {
attempts++
target = BehaviorUtils.getRandomSwimmablePos(entity, horizontalRange.resolveInt(mainThreadRuntime), verticalRange.resolveInt(mainThreadRuntime))
?: continue
if ((entity as? PokemonEntity)?.tethering?.canRoamTo(BlockPos.containing(target)) == false) continue
pos = BlockPos.containing(target).takeIf(wanderControl::isSuitable)
}
@@ -145,4 +143,4 @@ class WaterWanderTaskConfig : SingleTaskConfig {
}
}
}
}
}
@@ -0,0 +1,20 @@
package com.cobblemon.mod.common.api.badges
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.StringRepresentable
class Badge(
identifier: ResourceLocation,
val name: String,
val texture: ResourceLocation,
val order: Int
): StringRepresentable {
@Transient
var identifier: ResourceLocation = identifier
internal set
override fun getSerializedName(): String {
return identifier.toString()
}
}
@@ -0,0 +1,54 @@
package com.cobblemon.mod.common.api.badges
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.storage.player.InstancedPlayerData
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.api.storage.player.client.ClientBadgeManager
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
import com.cobblemon.mod.common.util.getPlayer
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.resources.ResourceLocation
import java.util.UUID
class BadgeManager(
override val uuid: UUID,
val obtainedBadges: MutableSet<ResourceLocation> = mutableSetOf()
) : InstancedPlayerData {
val storeType = PlayerInstancedDataStoreTypes.BADGES
override fun toClientData() = ClientBadgeManager(obtainedBadges.toMutableSet())
fun give(badge: Badge): Boolean {
if (!obtainedBadges.add(badge.identifier)) return false
syncAndSave()
return true
}
fun remove(badge: Badge): Boolean {
if (!obtainedBadges.remove(badge.identifier)) return false
syncAndSave()
return true
}
private fun syncAndSave() {
uuid.getPlayer()?.sendPacket(SetClientPlayerDataPacket(storeType, toClientData()))
Cobblemon.playerDataManager.saveSingle(this, storeType)
}
companion object {
val CODEC: Codec<BadgeManager> = RecordCodecBuilder.create { instance ->
instance.group(
Codec.STRING.xmap(UUID::fromString, UUID::toString)
.fieldOf("uuid")
.forGetter { it.uuid },
Codec.list(ResourceLocation.CODEC)
.xmap({ it.toMutableSet() }, { it.toList() })
.fieldOf("obtainedBadges")
.forGetter { it.obtainedBadges }
).apply(instance, ::BadgeManager)
}
}
}
@@ -0,0 +1,64 @@
package com.cobblemon.mod.common.api.badges
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.BadgeRegistrySyncPacket
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object Badges : JsonDataRegistry<Badge> {
override val id: ResourceLocation = cobblemonResource("badges")
override val type: PackType = PackType.SERVER_DATA
override val observable = SimpleObservable<Badges>()
override val gson: Gson = GsonBuilder()
.disableHtmlEscaping()
.setPrettyPrinting()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.create()
override val typeToken: TypeToken<Badge> = TypeToken.get(Badge::class.java)
override val resourcePath: String = "badges"
private val badges = hashMapOf<ResourceLocation, Badge>()
override fun reload(data: Map<ResourceLocation, Badge>) {
this.badges.clear()
data.forEach { (id, badge) ->
try {
badge.identifier = id
this.badges[id] = badge
} catch (e: Exception) {
Cobblemon.LOGGER.error("Skipped loading the {} badge", id, e)
}
}
Cobblemon.LOGGER.info("Loaded {} badges", this.badges.size)
this.observable.emit(this)
}
override fun sync(player: ServerPlayer) {
BadgeRegistrySyncPacket(this.all()).sendToPlayer(player)
}
/**
* Get all loaded [Badge]s.
*/
@JvmStatic
fun all() = this.badges.values.toList()
/**
* Gets a [Badge] by its [ResourceLocation].
* @param identifier The identifier of the badge.
* @return The [Badge], if loaded, otherwise null.
*/
@JvmStatic
fun getByIdentifier(identifier: ResourceLocation): Badge? = this.badges[identifier]
}
@@ -239,10 +239,6 @@ open class PokemonBattle(
}
fun writeShowdownAction(vararg messages: String) {
if (messages.any { it.contains("\n") || it.contains("\r") }) {
LOGGER.warn("Refusing to forward Showdown action with an embedded line break for battle {}: {}", battleId, messages.joinToString(" | "))
return
}
log(messages.joinToString("\n"))
ShowdownService.service.send(battleId, messages.toList().toTypedArray())
}
@@ -34,7 +34,7 @@ object PastureLinkManager {
fun getLinkByPlayer(player: ServerPlayer): PastureLink? {
val link = getLinkByPlayerId(player.uuid)
if (link != null) {
if (player.level().dimension().location() != link.dimension || !link.pos.closerToCenterThan(player.position(), 10.0)) {
if (!player.level().dimensionTypeRegistration().`is`(link.dimension) || !link.pos.closerToCenterThan(player.position(), 10.0)) {
links.remove(player.uuid)
return null
}
@@ -46,8 +46,8 @@ object PastureLinkManager {
@JvmStatic
fun removeAt(world: ServerLevel, pos: BlockPos) {
links.removeIf { (uuid, pastureLink) ->
val shouldRemove = world.dimension().location() == pastureLink.dimension && pastureLink.pos == pos
if (shouldRemove) uuid.getPlayer()?.sendPacket(ClosePasturePacket())
val shouldRemove = world.dimensionTypeRegistration().`is`(pastureLink.dimension) && pastureLink.pos == pos
uuid.getPlayer()?.sendPacket(ClosePasturePacket())
return@removeIf shouldRemove
}
}
@@ -1,82 +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.pasture
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.network.RegistryFriendlyByteBuf
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.floor
data class PastureRange(val north: Int, val east: Int, val south: Int, val west: Int) {
fun isValid(maximum: Int): Boolean =
listOf(north, east, south, west).all { it in 0..maximum } &&
north.toLong() + south >= 8 && east.toLong() + west >= 8
operator fun get(direction: Direction): Int = when (direction) {
Direction.NORTH -> north
Direction.EAST -> east
Direction.SOUTH -> south
Direction.WEST -> west
else -> error("Pasture range directions must be horizontal")
}
fun adjusted(direction: Direction, amount: Int): PastureRange = when (direction) {
Direction.NORTH -> copy(north = north + amount)
Direction.EAST -> copy(east = east + amount)
Direction.SOUTH -> copy(south = south + amount)
Direction.WEST -> copy(west = west + amount)
else -> this
}
fun minPos(origin: BlockPos) = origin.offset(-west, 0, -north)
fun maxPos(origin: BlockPos) = origin.offset(east, 0, south)
fun nearbyBlocks(origin: BlockPos, preferred: BlockPos, width: Double): Sequence<BlockPos> = sequence {
val halfWidth = width / 2.0
val minX = ceil(origin.x - west + halfWidth - 0.5).toInt()
val maxX = floor(origin.x + east - halfWidth - 0.5).toInt()
val minZ = ceil(origin.z - north + halfWidth - 0.5).toInt()
val maxZ = floor(origin.z + south - halfWidth - 0.5).toInt()
if (minX > maxX || minZ > maxZ) return@sequence
val centerX = preferred.x.coerceIn(minX, maxX)
val centerZ = preferred.z.coerceIn(minZ, maxZ)
for (distance in 0..16) {
for (dx in -distance..distance) {
val x = centerX + dx
if (x !in minX..maxX) continue
val dz = distance - abs(dx)
val z = centerZ + dz
if (z in minZ..maxZ) yield(BlockPos(x, origin.y, z))
if (dz != 0 && centerZ - dz in minZ..maxZ) yield(BlockPos(x, origin.y, centerZ - dz))
}
}
}
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeVarInt(north)
buffer.writeVarInt(east)
buffer.writeVarInt(south)
buffer.writeVarInt(west)
}
companion object {
const val MAX_DISTANCE = 64
fun fromBounds(origin: BlockPos, min: BlockPos, max: BlockPos) = PastureRange(
origin.z - min.z, max.x - origin.x, max.z - origin.z, origin.x - min.x
)
fun decode(buffer: RegistryFriendlyByteBuf) = PastureRange(
buffer.readVarInt(), buffer.readVarInt(), buffer.readVarInt(), buffer.readVarInt()
)
}
}
@@ -145,6 +145,8 @@ object CobblemonPermissions {
val SPECTATE_BATTLE = this.create("${COMMAND_PREFIX}spectatebattle", PermissionLevel.ALL_COMMANDS)
@JvmStatic
val TECHNICAL_MACHINE = this.create("${COMMAND_PREFIX}technicalmachine", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val BADGE = this.create("${COMMAND_PREFIX}badge", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val SEE_HIDDEN_NPCS = this.create("seehiddennpcs", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@@ -88,7 +88,7 @@ fun getToolTipHeaderIcon(tradeEvolution: TradeEvolution): CobblemonDexEvolutionR
lang("ui.pokedex.evolution.trade.tooltip.with_any_species")
} else {
PokemonSpecies.getByIdentifier(species.asIdentifierDefaultingNamespace())
?.let { lang("ui.pokedex.evolution.trade.tooltip.with_species", it.translatedName.plainCopy().green()) }
?.let { lang("ui.pokedex.evolution.trade.tooltip.with_species", it.translatedName.green()) }
?: lang("ui.pokedex.evolution.trade.tooltip.with_any_species")
}
return CobblemonDexEvolutionRequirementDisplay(CobblemonItems.LINK_CABLE, tooltip)
@@ -282,7 +282,7 @@ fun buildLevelRequirement(levelRequirement: LevelRequirement): DexEvolutionRequi
fun buildMoonPhaseRequirement(moonPhaseRequirement: MoonPhaseRequirement): DexEvolutionRequirementDisplay {
val icon = Items.CLOCK
val moonPhaseComponent = moonPhaseRequirement.moonPhase.displayName().plainCopy().green()
val moonPhaseComponent = moonPhaseRequirement.moonPhase.displayName().green()
val tooltip = lang("ui.pokedex.requirement.moon_phase.tooltip", moonPhaseComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
@@ -290,7 +290,7 @@ fun buildMoonPhaseRequirement(moonPhaseRequirement: MoonPhaseRequirement): DexEv
fun buildMoveSetRequirement(moveSetRequirement: MoveSetRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.TECHNICAL_MACHINE
val tooltip = lang("ui.pokedex.requirement.has_move.tooltip", moveSetRequirement.move.displayName.plainCopy().green())
val tooltip = lang("ui.pokedex.requirement.has_move.tooltip", moveSetRequirement.move.displayName.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
@@ -319,7 +319,7 @@ fun buildMoveTypeRequirement(moveTypeRequirement: MoveTypeRequirement): DexEvolu
else -> CobblemonItems.NORMAL_GEM
}
val tooltip = lang("ui.pokedex.requirement.has_move_type.tooltip", moveTypeRequirement.type.displayName.plainCopy().green())
val tooltip = lang("ui.pokedex.requirement.has_move_type.tooltip", moveTypeRequirement.type.displayName.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
@@ -437,7 +437,7 @@ fun buildStructureRequirement(structureRequirement: StructureRequirement, negate
fun buildTimeRangeRequirement(timeRangeRequirement: TimeRangeRequirement): DexEvolutionRequirementDisplay? {
val icon = Items.CLOCK
val timeRangeComponent = timeRangeRequirement.range.displayName()?.plainCopy() ?: return null
val timeRangeComponent = timeRangeRequirement.range.displayName() ?: return null
val tooltip = lang("ui.pokedex.requirement.time_range.tooltip", timeRangeComponent.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
@@ -445,7 +445,7 @@ fun buildTimeRangeRequirement(timeRangeRequirement: TimeRangeRequirement): DexEv
fun buildUseMoveRequirement(useMoveRequirement: UseMoveRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.CHOICE_BAND
val tooltip = lang("ui.pokedex.requirement.use_move.tooltip", useMoveRequirement.move.displayName.plainCopy().green())
val tooltip = lang("ui.pokedex.requirement.use_move.tooltip", useMoveRequirement.move.displayName.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
@@ -42,8 +42,8 @@ abstract class AreaTypeSpawningCondition<T : AreaSpawnablePosition> : SpawningCo
override fun copyFrom(other: SpawningCondition<*>, merger: Merger) {
super.copyFrom(other, merger)
if (other is AreaTypeSpawningCondition) {
minHeight = merger.mergeSingle(minHeight, other.minHeight)
maxHeight = merger.mergeSingle(maxHeight, other.maxHeight)
merger.mergeSingle(minHeight, other.minHeight)
merger.mergeSingle(maxHeight, other.maxHeight)
neededNearbyBlocks = merger.merge(neededNearbyBlocks, other.neededNearbyBlocks)?.toMutableList()
}
}
@@ -46,7 +46,7 @@ abstract class SubmergedTypeSpawningCondition<T : SubmergedSpawnablePosition> :
super.copyFrom(other, merger)
if (other is SubmergedTypeSpawningCondition) {
minDepth = merger.mergeSingle(minDepth, other.minDepth)
maxDepth = merger.mergeSingle(maxDepth, other.maxDepth)
maxDepth = merger.mergeSingle(minDepth, other.minDepth)
fluidIsSource = merger.mergeSingle(fluidIsSource, other.fluidIsSource)
fluid = merger.mergeSingle(fluid, other.fluid)
}
@@ -39,7 +39,7 @@ abstract class SurfaceTypeSpawningCondition<T : SurfaceSpawnablePosition> : Area
super.copyFrom(other, merger)
if (other is SurfaceTypeSpawningCondition) {
minDepth = merger.mergeSingle(minDepth, other.minDepth)
maxDepth = merger.mergeSingle(maxDepth, other.maxDepth)
maxDepth = merger.mergeSingle(minDepth, other.minDepth)
fluid = merger.mergeSingle(fluid, other.fluid)
}
}
@@ -198,17 +198,7 @@ interface Spawner {
val mid = chunk.getBlockState(startPos)
val above = chunk.getBlockState(abovePos)
// Above must be non-solid
if (!above.isPathfindable(PathComputationType.AIR)) {
return false
}
// Position must be non-air
if (mid.isAir) {
return false
}
return true
return above.isPathfindable(PathComputationType.AIR) && mid.isAir
}
fun constrainArea(area: SpawningZoneInput): SpawningZoneInput? {
@@ -29,8 +29,12 @@ object CobblemonStats {
@JvmField
val BATTLES_WON: CobblemonStat = CobblemonStat("battles_won")
@JvmField
val PVP_BATTLES_WON: CobblemonStat = CobblemonStat("pvp_battles_won")
@JvmField
val BATTLES_LOST: CobblemonStat = CobblemonStat("battles_lost")
@JvmField
val PVP_BATTLES_LOST: CobblemonStat = CobblemonStat("pvp_battles_lost")
@JvmField
val BATTLES_FLED: CobblemonStat = CobblemonStat("battles_fled")
@JvmField
val BATTLES_TOTAL: CobblemonStat = CobblemonStat("battles_total")
@@ -68,7 +72,9 @@ object CobblemonStats {
stats["evolved"] = EVOLVED
stats["level_up"] = LEVEL_UP
stats["battles_won"] = BATTLES_WON
stats["pvp_battles_won"] = PVP_BATTLES_WON
stats["battles_lost"] = BATTLES_LOST
stats["pvp_battles_lost"] = PVP_BATTLES_LOST
stats["battles_fled"] = BATTLES_FLED
stats["battles_total"] = BATTLES_TOTAL
stats["dex_entries"] = DEX_ENTRIES
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.storage.player
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.MODID
import com.cobblemon.mod.common.api.badges.BadgeManager
import com.cobblemon.mod.common.api.pokedex.PokedexManager
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
@@ -76,6 +77,14 @@ open class PlayerInstancedDataStoreManager {
.infiniteIterations()
.tracker(ServerTaskTracker)
.build()
saveTasks[PlayerInstancedDataStoreTypes.BADGES] = ScheduledTask.Builder()
.execute { saveAllOfOneType(PlayerInstancedDataStoreTypes.BADGES) }
.delay(30F)
.interval(120F)
.infiniteIterations()
.tracker(ServerTaskTracker)
.build()
}
open fun get(playerId: UUID, dataType: PlayerInstancedDataStoreType): InstancedPlayerData {
@@ -148,4 +157,9 @@ open class PlayerInstancedDataStoreManager {
}
return data as TMMoveManager
}
fun getBadgeData(player: ServerPlayer): BadgeManager = getBadgeData(player.uuid)
fun getBadgeData(playerId: UUID): BadgeManager =
get(playerId, PlayerInstancedDataStoreTypes.BADGES) as BadgeManager
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.storage.player
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.storage.player.client.ClientBadgeManager
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
import com.cobblemon.mod.common.api.storage.player.client.ClientGeneralPlayerData
import com.cobblemon.mod.common.api.storage.player.client.ClientTMMoveManager
@@ -35,6 +36,12 @@ object PlayerInstancedDataStoreTypes {
ClientTMMoveManager::runAction,
ClientTMMoveManager::runIncremental
))
val BADGES = register(PlayerInstancedDataStoreType(
cobblemonResource("badge_collection"),
ClientBadgeManager::decode,
ClientBadgeManager::runAction,
ClientBadgeManager::runIncremental
))
fun register(type: PlayerInstancedDataStoreType): PlayerInstancedDataStoreType {
Cobblemon.LOGGER.debug("Registering PlayerInstancedDataStoreType: {}", type.id)
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.storage.player.adapter
import com.cobblemon.mod.common.api.badges.BadgeManager
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.util.adapters.CodecBackedAdapter
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import com.mongodb.client.MongoClient
import java.util.UUID
class BadgeMongoBackend(
mongoClient: MongoClient,
databaseName: String,
collectionName: String
) : MongoBackedPlayerDataStoreBackend<BadgeManager>(
mongoClient,
databaseName,
collectionName,
PlayerInstancedDataStoreTypes.BADGES
) {
override val gson = GsonBuilder()
.registerTypeAdapter(BadgeManager::class.java, CodecBackedAdapter(BadgeManager.CODEC))
.create()
override val classToken = TypeToken.get(BadgeManager::class.java)
override val defaultData = defaultDataFunc
companion object {
val defaultDataFunc = { uuid: UUID -> BadgeManager(uuid) }
}
}
@@ -0,0 +1,22 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.storage.player.adapter
import com.cobblemon.mod.common.api.badges.BadgeManager
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import java.util.UUID
class BadgeNbtBackend : NbtBackedPlayerData<BadgeManager>("badges", PlayerInstancedDataStoreTypes.BADGES) {
override val codec = BadgeManager.CODEC
override val defaultData = defaultDataFunc
companion object {
val defaultDataFunc = { uuid: UUID -> BadgeManager(uuid) }
}
}
@@ -52,7 +52,7 @@ abstract class FileBasedPlayerDataStoreBackend<T : InstancedPlayerData>(
val playerFile = filePath(uuid)
val playerFileOld = filePath(uuid, OLD_FILE_EXTENSION)
playerFile.parentFile.mkdirs()
val loadFallback: () -> T = {
val loadFallback: (Boolean) -> T = { primaryFileCorrupt ->
playerFile.delete()
if (playerFileOld.exists() && playerFileOld.length() > 0L) {
try {
@@ -68,7 +68,9 @@ abstract class FileBasedPlayerDataStoreBackend<T : InstancedPlayerData>(
}
}
else {
Cobblemon.LOGGER.error(".old File $subfolder for $uuid is corrupt or missing. Data is lost")
if (primaryFileCorrupt) {
Cobblemon.LOGGER.error(".old File $subfolder for $uuid is corrupt or missing. Data is lost")
}
playerFileOld.delete()
defaultData.invoke(uuid).also(::save)
}
@@ -80,10 +82,10 @@ abstract class FileBasedPlayerDataStoreBackend<T : InstancedPlayerData>(
catch (e: Exception) {
Cobblemon.LOGGER.warn("Failed to load $subfolder for $uuid due to ${e.message}")
Cobblemon.LOGGER.warn(e)
loadFallback()
loadFallback(true)
}
} else {
loadFallback()
loadFallback(false)
}.also { it.initialize() }
}
@@ -0,0 +1,42 @@
package com.cobblemon.mod.common.api.storage.player.client
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeString
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
class ClientBadgeManager(
val obtainedBadges: MutableSet<ResourceLocation>
) : ClientInstancedPlayerData {
override fun encode(buf: RegistryFriendlyByteBuf) {
buf.writeCollection(obtainedBadges) { _, id, -> buf.writeString(id.toString()) }
}
companion object {
fun decode(buf: RegistryFriendlyByteBuf): SetClientPlayerDataPacket {
val badges = buf.readCollection(
{ mutableSetOf<ResourceLocation>() },
{ buf.readString().let(ResourceLocation::tryParse) ?: ResourceLocation("minecraft", "empty") }
)
return SetClientPlayerDataPacket(
PlayerInstancedDataStoreTypes.BADGES,
ClientBadgeManager(badges)
)
}
fun runAction(data: ClientInstancedPlayerData) {
if (data !is ClientBadgeManager) return
CobblemonClient.clientBadgeData = data
}
fun runIncremental(data: ClientInstancedPlayerData) {
if (data !is ClientBadgeManager) return
CobblemonClient.clientBadgeData.obtainedBadges.addAll(data.obtainedBadges)
}
}
}
@@ -117,9 +117,6 @@ abstract class ShowdownActionResponse(val type: ShowdownActionResponseType) {
data class MoveActionResponse(var moveName: String, var targetPnx: String? = null, var gimmickID: String? = null): ShowdownActionResponse(ShowdownActionResponseType.MOVE) {
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean): Boolean {
// Ensure that the gimmick ID the player is sending is valid
gimmickID?.let { if (it !in VALID_GIMMICK_IDS) return false }
if (forceSwitch || showdownMoveSet == null) {
return false
}
@@ -171,10 +168,6 @@ data class MoveActionResponse(var moveName: String, var targetPnx: String? = nul
gimmickID = buffer.readNullable { buffer.readString() }
return this
}
companion object {
private val VALID_GIMMICK_IDS = ShowdownMoveset.Gimmick.entries.map { it.id }.toSet()
}
}
data class HealItemActionResponse(var item: String) : ShowdownActionResponse(ShowdownActionResponseType.FORCE_PASS) {
@@ -190,8 +183,7 @@ data class HealItemActionResponse(var item: String) : ShowdownActionResponse(Sho
}
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean): Boolean {
// Previously checked for !forceSwitch, excepts in the case of a forceSwitch, this couldn't legally be sent anyway.
return false
return !forceSwitch
}
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?): String {
@@ -37,6 +37,7 @@ import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import java.util.concurrent.CompletableFuture
import net.minecraft.world.entity.LivingEntity
/**
* Format: |move|POKEMON|MOVE|TARGET
@@ -76,7 +77,7 @@ class MoveInstruction(
val pokemonName = userPokemon.getName()
ShowdownInterpreter.lastCauser[battle.battleId] = message
// For Spread targets the message data only gives the pnx strings. So we can't know what pokemon are actually targeted until the previous messages have been interpreted
val spreadTargetPokemon = spreadTargets.map { battle.activePokemon.firstOrNull { poke -> poke.getPNX() == it }?.battlePokemon }
val spreadTargetPokemon = spreadTargets.map { battle.activePokemon.firstOrNull() { poke -> poke.getPNX() == it }?.battlePokemon }
val targetPokemonEntity = targetPokemon?.entity
if (targetPokemonEntity != null) {
@@ -136,20 +137,10 @@ class MoveInstruction(
val subsequentInstructions = instructionSet.findInstructionsCausedBy(this)
val missedTargets = subsequentInstructions.filterIsInstance<MissInstruction>().mapNotNull { it.target }
val faintedTargets = subsequentInstructions.filterIsInstance<FaintInstruction>().map { it.faintingPokemon }
val failed = subsequentInstructions.any { it is FailInstruction }
val failedTargets = subsequentInstructions.filterIsInstance<FailInstruction>().mapNotNull { it.target }
val hitCountInstruction = subsequentInstructions.filterIsInstance<HitCountInstruction>().firstOrNull()
runtime.environment.query.addFunction("target_fainted") { params ->
if (params.params.size == 0) {
return@addFunction DoubleValue(faintedTargets.isNotEmpty())
} else {
val entityUUID = params.getString(0)
return@addFunction DoubleValue(faintedTargets.any { it.entity?.stringUUID == entityUUID })
}
}
runtime.environment.query.addFunction("missed") { params ->
if (params.params.size == 0) {
return@addFunction DoubleValue(missedTargets.isNotEmpty())
@@ -30,7 +30,7 @@ class OrbBlock(properties: Properties) : SixFaceRotationalBlock(properties) {
val FLOOR_NORTH = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875)
val WALL_NORTH = Shapes.box(0.3125, 0.3125, 0.625, 0.6875, 0.6875, 1.0)
val CEILING_NORTH = Shapes.box(0.3125, 0.625, 0.3125, 0.6875, 1.0, 0.6875)
val CEILING_NORTH = Shapes.box(0.3125, 0.625, 0.375, 0.6875, 1.0, 0.6875)
}
init {
@@ -25,6 +25,7 @@ import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.StringRepresentable
@@ -282,13 +283,11 @@ class PastureBlock(settings: Properties): BaseEntityBlock(settings), SimpleWater
pastureId = linkId,
permissions = perms,
limit = baseEntity.getMaxTethered(),
tetheredPokemon = baseEntity.tetheredPokemon.mapNotNull { it.toDTO(player) },
range = baseEntity.getRange(),
facing = state.getValue(HorizontalDirectionalBlock.FACING)
tetheredPokemon = baseEntity.tetheredPokemon.mapNotNull { it.toDTO(player) }
)
)
PastureLinkManager.createLink(player.uuid, PastureLink(linkId, pcId, world.dimension().location(), getBasePosition(state, pos), perms))
PastureLinkManager.createLink(player.uuid, PastureLink(linkId, pcId, ResourceLocation.tryParse(world.dimensionTypeRegistration().registeredName)!!, getBasePosition(state, pos), perms))
world.playSoundServer(
position = pos.bottomCenter,
@@ -367,4 +366,4 @@ class PastureBlock(settings: Properties): BaseEntityBlock(settings), SimpleWater
return state
}
}
}
@@ -14,19 +14,15 @@ import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.advancement.CobblemonCriteria
import com.cobblemon.mod.common.api.pasture.PastureLinkManager
import com.cobblemon.mod.common.api.pasture.PastureRange
import com.cobblemon.mod.common.api.scheduling.afterOnServer
import com.cobblemon.mod.common.api.text.italicise
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.block.PastureBlock
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
import com.cobblemon.mod.common.net.messages.client.pasture.ClosePasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonPasturedPacket
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureRecoveryPacket
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureConflictFlagPacket
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
@@ -43,6 +39,7 @@ import net.minecraft.core.Vec3i
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.NbtUtils
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.tags.FluidTags
@@ -51,61 +48,38 @@ import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.entity.EntityTypeTest
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
BlockEntity(CobblemonBlockEntities.PASTURE, pos, state) {
open class Tethering(
var minRoamPos: BlockPos,
var maxRoamPos: BlockPos,
val minRoamPos: BlockPos,
val maxRoamPos: BlockPos,
val playerId: UUID,
val playerName: String,
val tetheringId: UUID,
val pokemonId: UUID,
val pcId: UUID,
var entityId: Int,
val pasturePos: BlockPos,
var entityUuid: UUID? = null,
var lastEntityPos: BlockPos? = null,
var respawnAt: Long = 0L,
var healingAfterFaint: Boolean = false,
var conflictEnabled: Boolean? = null
val entityId: Int,
val pasturePos: BlockPos
) {
fun getPokemon(): Pokemon? {
val srv = server() ?: return null
val pc = Cobblemon.storage.getPC(pcId, srv.registryAccess())
return pc[pokemonId]
}
val box get() = AABB(minRoamPos.toVec3d(), maxRoamPos.toVec3d())
fun canRoamTo(x: Double, z: Double) = x >= minRoamPos.x && x < maxRoamPos.x && z >= minRoamPos.z && z < maxRoamPos.z
open fun canRoamTo(pos: BlockPos) = canRoamTo(pos.x + 0.5, pos.z + 0.5)
internal fun beginRecovery(now: Long, fainted: Boolean) {
if (fainted && !healingAfterFaint) {
healingAfterFaint = true
respawnAt = now + FAINT_COOLDOWN
} else if (respawnAt == 0L) {
respawnAt = now + MISSING_ENTITY_COOLDOWN
}
}
val box = AABB(minRoamPos.toVec3d(), maxRoamPos.toVec3d())
open fun canRoamTo(pos: BlockPos) = box.contains(pos.center)
fun toDTO(player: ServerPlayer): OpenPasturePacket.PasturePokemonDataDTO? {
val pokemon = getPokemon() ?: return null
val entity = pokemon.entity
val behaviourFlags = (entity?.getActiveBehaviourFlags() ?: emptySet()).toMutableSet()
when (conflictEnabled) {
true -> behaviourFlags.add(PokemonBehaviourFlag.PASTURE_CONFLICT)
false -> behaviourFlags.remove(PokemonBehaviourFlag.PASTURE_CONFLICT)
null -> Unit
}
val behaviourFlags = entity?.getActiveBehaviourFlags() ?: emptySet()
return OpenPasturePacket.PasturePokemonDataDTO(
pokemonId = pokemonId,
@@ -117,16 +91,12 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
heldItem = pokemon.heldItem(),
level = pokemon.level,
entityKnown = (entity?.tethering?.tetheringId == tetheringId),
behaviourFlags = behaviourFlags,
healingAfterFaint = healingAfterFaint,
respawnAt = respawnAt
behaviourFlags = behaviourFlags
)
}
}
companion object {
private const val FAINT_COOLDOWN = 24000L
private const val MISSING_ENTITY_COOLDOWN = 200L
internal val TICKER = BlockEntityTicker<PokemonPastureBlockEntity> { world, _, _, blockEntity ->
if (world.isClientSide) return@BlockEntityTicker
blockEntity.ticksUntilCheck--
@@ -164,26 +134,6 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
fun getMaxTethered() = Cobblemon.config.defaultPasturedPokemonLimit
fun getRange() = PastureRange.fromBounds(blockPos, minRoamPos, maxRoamPos)
fun setRange(range: PastureRange) {
minRoamPos = range.minPos(blockPos).atY(minRoamPos.y)
maxRoamPos = range.maxPos(blockPos).atY(maxRoamPos.y)
tetheredPokemon.forEach { tethering ->
tethering.minRoamPos = minRoamPos
tethering.maxRoamPos = maxRoamPos
tethering.getPokemon()?.entity?.let { entity ->
if (entity.tethering?.tetheringId == tethering.tetheringId) {
entity.tethering = tethering
entity.navigation.stop()
entity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
entity.checkPastureTether()
}
}
}
setChanged()
}
fun canAddPokemon(player: ServerPlayer, pokemon: Pokemon, maxPerPlayer: Int): Boolean {
val forThisPlayer = tetheredPokemon.count { it.playerId == player.uuid }
// Shouldn't be possible, client should've prevented it
@@ -208,64 +158,57 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
fun tether(player: ServerPlayer, pokemon: Pokemon, directionToBehind: Direction): Boolean {
val tethering = Tethering(
minRoamPos, maxRoamPos, player.uuid, player.gameProfile.name, UUID.randomUUID(),
pokemon.uuid, Cobblemon.storage.getPC(player).uuid, -1, blockPos
)
if (!spawnPokemon(pokemon, tethering, directionToBehind)) return false
pokemon.tetheringId = tethering.tetheringId
pokemon.onChange()
tetheredPokemon.add(tethering)
tethering.toDTO(player)?.let { player.sendPacket(PokemonPasturedPacket(it)) }
setChanged()
CobblemonCriteria.PASTURE_USE.trigger(player, pokemon)
return true
}
private fun spawnPokemon(pokemon: Pokemon, tethering: Tethering, directionToBehind: Direction): Boolean {
val world = level ?: return false
val entity = PokemonEntity(world, pokemon = pokemon)
entity.refreshDimensions()
val position = findSpawnPosition(entity, tethering.lastEntityPos, directionToBehind) ?: return false
entity.setPos(position)
entity.beamMode = 2
entity.tethering = tethering
entity.setBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT, tethering.conflictEnabled == true)
if (!world.addFreshEntity(entity)) return false
if (tethering.conflictEnabled == true) entity.remakeBrain()
tethering.entityId = entity.id
tethering.entityUuid = entity.uuid
tethering.lastEntityPos = entity.blockPosition()
tethering.respawnAt = 0L
syncRecovery(tethering)
afterOnServer(seconds = 0.5F) {
if (!entity.isRemoved && entity.pokemon.shiny) {
SpawnSnowstormEntityParticlePacket(cobblemonResource("shiny_ring"), entity.id, listOf("shiny_particles", "middle"))
.sendToPlayersAround(entity.x, entity.y, entity.z, 64.0, entity.level().dimension())
}
entity.after(seconds = SendOutPokemonHandler.SEND_OUT_DURATION) {
entity.beamMode = 0
}
}
return true
}
val width = entity.boundingBox.xsize
fun findSpawnPosition(entity: PokemonEntity, preferred: BlockPos? = null, direction: Direction = blockState.getValue(HorizontalDirectionalBlock.FACING)): Vec3? {
val world = level ?: return null
val dimensions = entity.getDimensions(Pose.STANDING)
val idealPlace = preferred ?: blockPos.offset(direction.normal.multiply(ceil(dimensions.width / 2.0).toInt() + 1))
for (candidate in getRange().nearbyBlocks(blockPos, idealPlace, dimensions.width.toDouble())) {
if (!world.hasChunkAt(candidate)) continue
val box = dimensions.makeBoundingBox(candidate.center.subtract(0.0, 0.5, 0.0))
val fixedPosition = makeSuitableY(world, candidate, entity, box) ?: continue
val position = fixedPosition.center.subtract(0.0, 0.5, 0.0)
val spawnBox = dimensions.makeBoundingBox(position)
if (world.noCollision(entity, spawnBox) &&
world.getEntitiesOfClass(PokemonEntity::class.java, spawnBox) { it != entity }.isEmpty()) {
return position
val idealPlace = blockPos.offset(directionToBehind.normal.multiply(ceil(width).toInt() + 1))
var box = entity.getDimensions(Pose.STANDING).makeBoundingBox(idealPlace.center.subtract(0.0, 0.5, 0.0))
for (i in 0..5) {
box = box.move(directionToBehind.normal.x.toDouble(), 0.0, directionToBehind.normal.z.toDouble())
val fixedPosition = makeSuitableY(world, idealPlace.offset(directionToBehind.normal.multiply(i + 1)), entity, box)
if (fixedPosition != null) {
entity.setPos(fixedPosition.center.subtract(0.0, 0.5, 0.0))
val pc = Cobblemon.storage.getPC(player)
entity.beamMode = 2
afterOnServer(seconds = 0.5F) {
if(entity.pokemon.shiny) {
SpawnSnowstormEntityParticlePacket(cobblemonResource("shiny_ring"), entity.id, listOf("shiny_particles", "middle"))
.sendToPlayersAround(entity.x, entity.y, entity.z, 64.0, entity.level().dimension())
}
entity.after(seconds = SendOutPokemonHandler.SEND_OUT_DURATION) {
entity.beamMode = 0
}
}
if (world.addFreshEntity(entity)) {
val tethering = Tethering(
minRoamPos = minRoamPos,
maxRoamPos = maxRoamPos,
playerId = player.uuid,
playerName = player.gameProfile.name,
tetheringId = UUID.randomUUID(),
pokemonId = pokemon.uuid,
pcId = pc.uuid,
entityId = entity.id,
pasturePos = blockPos
)
pokemon.tetheringId = tethering.tetheringId
tetheredPokemon.add(tethering)
entity.tethering = tethering
tethering.toDTO(player)?.let { player.sendPacket(PokemonPasturedPacket(it)) }
setChanged()
CobblemonCriteria.PASTURE_USE.trigger(player, pokemon)
return true
} else {
Cobblemon.LOGGER.warn("Couldn't spawn pastured Pokémon for some reason")
}
break
}
}
return null
return false
}
private fun togglePastureOn(on: Boolean) {
@@ -346,7 +289,6 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
}
fun checkPokemon() {
val world = level as? ServerLevel ?: return
val deadLinks = mutableListOf<UUID>()
tetheredPokemon.forEach {
val pokemon = it.getPokemon()
@@ -354,8 +296,6 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
deadLinks.add(it.pokemonId)
} else if (pokemon.tetheringId == null || pokemon.tetheringId != it.tetheringId) {
deadLinks.add(it.pokemonId)
} else {
checkDeployment(world, it, pokemon)
}
}
deadLinks.forEach(::releasePokemon)
@@ -363,85 +303,6 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
setChanged()
}
private fun checkDeployment(world: ServerLevel, tethering: Tethering, pokemon: Pokemon) {
val now = world.gameTime
if (pokemon.isFainted() && !tethering.healingAfterFaint) {
tethering.beginRecovery(now, fainted = true)
syncRecovery(tethering)
}
val entity = (pokemon.entity ?: tethering.entityUuid?.let(world::getEntity) as? PokemonEntity)
?.takeIf { !it.isRemoved && it.tethering?.tetheringId == tethering.tetheringId }
if (tethering.conflictEnabled == null && entity != null) {
tethering.conflictEnabled = entity.getBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT)
}
if (entity != null && pokemon.isFainted()) entity.discard()
if (entity != null && !entity.isRemoved) {
if (tethering.conflictEnabled != null && entity.getBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT) != tethering.conflictEnabled) {
entity.setBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT, tethering.conflictEnabled == true)
entity.remakeBrain()
}
tethering.entityUuid = entity.uuid
tethering.lastEntityPos = entity.blockPosition()
entity.tethering = tethering
if (tethering.respawnAt != 0L || tethering.healingAfterFaint) {
tethering.respawnAt = 0L
tethering.healingAfterFaint = false
syncRecovery(tethering)
}
return
}
// A Pokémon in an unloaded chunk is still deployed. Never create its replacement.
val lastPosition = tethering.lastEntityPos
if (lastPosition != null) {
if (!world.hasChunkAt(lastPosition)) return
}
// The last saved position may predate a chunk crossing. Require the entire range
// to be entity-ticking before deciding that its missing Pokémon has disappeared.
for (chunkX in (minRoamPos.x shr 4)..(maxRoamPos.x shr 4)) {
for (chunkZ in (minRoamPos.z shr 4)..(maxRoamPos.z shr 4)) {
if (!world.isPositionEntityTicking(BlockPos(chunkX shl 4, blockPos.y, chunkZ shl 4))) return
}
}
if (tethering.respawnAt == 0L) {
tethering.beginRecovery(now, fainted = false)
syncRecovery(tethering)
return
}
if (now < tethering.respawnAt) return
if (tethering.healingAfterFaint) {
pokemon.heal()
if (pokemon.isFainted()) return
tethering.healingAfterFaint = false
syncRecovery(tethering)
}
val radius = Cobblemon.config.pastureMaxWanderDistance.toDouble()
val nearby = world.getEntitiesOfClass(PokemonEntity::class.java, AABB.ofSize(blockPos.toVec3d(), radius * 2, world.height.toDouble(), radius * 2)).size
val chunkDiameter = (radius / 16) * 2
if (nearby >= Cobblemon.config.pastureMaxPerChunk * chunkDiameter * chunkDiameter ||
!spawnPokemon(pokemon, tethering, blockState.getValue(HorizontalDirectionalBlock.FACING))) {
tethering.respawnAt = now + MISSING_ENTITY_COOLDOWN
syncRecovery(tethering)
}
}
private fun syncRecovery(tethering: Tethering) {
val world = level as? ServerLevel ?: return
val packet = UpdatePastureRecoveryPacket(tethering.pokemonId, tethering.healingAfterFaint, tethering.respawnAt)
world.server.playerList.players.filter(::isPlayerViewing).forEach { it.sendPacket(packet) }
}
fun setConflict(tethering: Tethering, enabled: Boolean) {
tethering.conflictEnabled = enabled
tethering.getPokemon()?.entity?.takeIf { it.tethering?.tetheringId == tethering.tetheringId }?.let {
it.setBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT, enabled)
it.remakeBrain()
}
setChanged()
val world = level as? ServerLevel ?: return
val packet = UpdatePastureConflictFlagPacket(tethering.pokemonId, enabled)
world.server.playerList.players.filter(::isPlayerViewing).forEach { it.sendPacket(packet) }
}
fun onBroken() {
if (level is ServerLevel) {
tetheredPokemon.toList().forEach { releasePokemon(it.pokemonId) }
@@ -451,10 +312,7 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
fun releasePokemon(pokemonId: UUID) {
val tethering = tetheredPokemon.find { it.pokemonId == pokemonId } ?: return
tethering.getPokemon()?.let { pokemon ->
pokemon.tetheringId = null
pokemon.onChange()
}
tethering.getPokemon()?.tetheringId = null
tetheredPokemon.remove(tethering)
setChanged()
}
@@ -462,10 +320,7 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
fun releaseAllPokemon(playerId: UUID): List<UUID> {
val unpastured = mutableListOf<UUID>()
tetheredPokemon.filter { it.playerId == playerId }.forEach {
it.getPokemon()?.let { pokemon ->
pokemon.tetheringId = null
pokemon.onChange()
}
it.getPokemon()?.tetheringId = null
tetheredPokemon.remove(it)
unpastured.add(it.pokemonId)
}
@@ -488,7 +343,9 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
private fun isPlayerViewing(player: ServerPlayer): Boolean {
val pastureLink = PastureLinkManager.getLinkByPlayer(player)
return pastureLink != null && pastureLink.pos == blockPos && pastureLink.dimension == player.level().dimension().location()
return pastureLink != null && pastureLink.pos == blockPos && pastureLink.dimension == ResourceLocation.tryParse(
player.level().dimensionTypeRegistration().registeredName // todo (techdaan): confirm this is good
)
}
override fun loadAdditional(nbt: CompoundTag, registryLookup: HolderLookup.Provider) {
@@ -496,9 +353,6 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
val list = nbt.getList(DataKeys.TETHER_POKEMON, CompoundTag.TAG_COMPOUND.toInt())
this.ownerId = if (nbt.hasUUID(DataKeys.TETHER_OWNER_ID)) nbt.getUUID(DataKeys.TETHER_OWNER_ID) else null
this.ownerName = nbt.getString(DataKeys.TETHER_OWNER_NAME).takeIf { it.isNotEmpty() } ?: ""
this.minRoamPos = NbtUtils.readBlockPos(nbt, DataKeys.TETHER_MIN_ROAM_POS).orElse(minRoamPos)
this.maxRoamPos = NbtUtils.readBlockPos(nbt, DataKeys.TETHER_MAX_ROAM_POS).orElse(maxRoamPos)
tetheredPokemon.clear()
for (tetheringNBT in list) {
tetheringNBT as CompoundTag
val tetheringId = tetheringNBT.getUUID(DataKeys.TETHERING_ID)
@@ -511,20 +365,17 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
minRoamPos = minRoamPos,
maxRoamPos = maxRoamPos,
playerId = playerId,
playerName = tetheringNBT.getString("PlayerName").ifEmpty { ownerName },
playerName = ownerName,
tetheringId = tetheringId,
pokemonId = pokemonId,
pcId = pcId,
entityId = entityId,
pasturePos = blockPos,
entityUuid = if (tetheringNBT.hasUUID("EntityUUID")) tetheringNBT.getUUID("EntityUUID") else null,
lastEntityPos = NbtUtils.readBlockPos(tetheringNBT, "LastEntityPos").orElse(null),
respawnAt = tetheringNBT.getLong("RespawnAt"),
healingAfterFaint = tetheringNBT.getBoolean("HealingAfterFaint"),
conflictEnabled = if (tetheringNBT.contains("PastureConflict")) tetheringNBT.getBoolean("PastureConflict") else null
pasturePos = blockPos
)
)
}
this.minRoamPos = NbtUtils.readBlockPos(nbt, DataKeys.TETHER_MIN_ROAM_POS).get()
this.maxRoamPos = NbtUtils.readBlockPos(nbt, DataKeys.TETHER_MAX_ROAM_POS).get()
}
override fun saveAdditional(nbt: CompoundTag, registryLookup: HolderLookup.Provider) {
@@ -537,12 +388,6 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
tetheringNBT.putUUID(DataKeys.POKEMON_UUID, tethering.pokemonId)
tetheringNBT.putUUID(DataKeys.PC_ID, tethering.pcId)
tetheringNBT.putInt(DataKeys.TETHERING_ENTITY_ID, tethering.entityId)
tetheringNBT.putString("PlayerName", tethering.playerName)
tethering.entityUuid?.let { tetheringNBT.putUUID("EntityUUID", it) }
tethering.lastEntityPos?.let { tetheringNBT.put("LastEntityPos", NbtUtils.writeBlockPos(it)) }
tetheringNBT.putLong("RespawnAt", tethering.respawnAt)
tetheringNBT.putBoolean("HealingAfterFaint", tethering.healingAfterFaint)
tethering.conflictEnabled?.let { tetheringNBT.putBoolean("PastureConflict", it) }
list.add(tetheringNBT)
}
nbt.put(DataKeys.TETHER_POKEMON, list)
@@ -551,4 +396,4 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
ownerId?.let { nbt.putUUID(DataKeys.TETHER_OWNER_ID, it) }
nbt.putString(DataKeys.TETHER_OWNER_NAME, this.ownerName)
}
}
}
@@ -17,12 +17,12 @@ import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonMenuType
import com.cobblemon.mod.common.api.berry.Berries
import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker
import com.cobblemon.mod.common.api.storage.player.client.ClientBadgeManager
import com.cobblemon.mod.common.api.storage.player.client.ClientGeneralPlayerData
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
import com.cobblemon.mod.common.api.storage.player.client.ClientTMMoveManager
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.block.entity.TintBlockEntity
import com.cobblemon.mod.common.block.sign.CobblemonSignBlock
import com.cobblemon.mod.common.client.battle.ClientBattle
import com.cobblemon.mod.common.client.gui.PartyOverlay
import com.cobblemon.mod.common.client.gui.PartyOverlayDataControl
@@ -101,6 +101,7 @@ object CobblemonClient {
var clientPlayerData = ClientGeneralPlayerData()
var clientPokedexData = ClientPokedexManager(mutableMapOf())
var clientTMMoveData = ClientTMMoveManager(mutableSetOf())
var clientBadgeData = ClientBadgeManager(mutableSetOf())
/** If true then we won't bother them anymore about choosing a starter even if it's a thing they can do. */
var checkedStarterScreen = false
@@ -118,6 +119,7 @@ object CobblemonClient {
teamData = ClientPlayerTeamData()
clientPokedexData = ClientPokedexManager(mutableMapOf())
clientTMMoveData = ClientTMMoveManager(mutableSetOf())
clientBadgeData = ClientBadgeManager(mutableSetOf())
storage.onLogin()
// CobblemonDataProvider.canReload = false
}
@@ -11,8 +11,6 @@ package com.cobblemon.mod.common.client.gui.pasture
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.pasture.PasturePermissions
import com.cobblemon.mod.common.api.pasture.PastureRange
import net.minecraft.core.Direction
import com.cobblemon.mod.common.api.reactive.SettableObservable
import com.cobblemon.mod.common.client.gui.pc.PCGUIConfiguration
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
@@ -23,9 +21,7 @@ class PasturePCGUIConfiguration(
val pastureId: UUID,
val limit: Int,
val pasturedPokemon: SettableObservable<List<OpenPasturePacket.PasturePokemonDataDTO>>,
var permissions: PasturePermissions,
var range: PastureRange,
val facing: Direction
var permissions: PasturePermissions
) : PCGUIConfiguration(
exitFunction = { it.closeNormally(unlink = true) },
showParty = false,
@@ -36,6 +32,4 @@ class PasturePCGUIConfiguration(
}
},
canSelect = { pokemon -> !pokemon.isFainted() && pokemon.tetheringId == null }
) {
var editingRange = false
}
)
@@ -58,7 +58,6 @@ class PasturePokemonScrollList(
private val scrollOverlayResource = cobblemonResource("textures/gui/pasture/pasture_scroll_overlay.png")
private val slotResource = cobblemonResource("textures/gui/pasture/pasture_slot.png")
private val slotOwnerResource = cobblemonResource("textures/gui/pasture/pasture_slot_owner.png")
private val slotFaintedResource = cobblemonResource("textures/gui/pasture/pasture_slot_fainted.png")
}
private var scrolling = false
@@ -195,7 +194,7 @@ class PasturePokemonScrollList(
}
)
override fun getNarration() = if (pokemon.healingAfterFaint) lang("ui.pasture.fainted").append(" ").append(pokemon.displayName) else pokemon.displayName
override fun getNarration() = pokemon.displayName
override fun render(
context: GuiGraphics,
@@ -215,7 +214,7 @@ class PasturePokemonScrollList(
val matrixStack = context.pose()
blitk(
matrixStack = matrixStack,
texture = if (pokemon.healingAfterFaint) slotFaintedResource else if (isOwned()) slotOwnerResource else slotResource,
texture = if (isOwned()) slotOwnerResource else slotResource,
x = x,
y = y,
height = SLOT_HEIGHT,
@@ -235,8 +234,7 @@ class PasturePokemonScrollList(
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = state,
partialTicks = partialTicks,
scale = 4.5F,
a = if (pokemon.healingAfterFaint) 0.7F else 1F
scale = 4.5F
)
matrixStack.popPose()
@@ -251,21 +249,14 @@ class PasturePokemonScrollList(
)
}
val recovering = pokemon.healingAfterFaint || pokemon.respawnAt > 0L
if (recovering) {
if (!pokemon.healingAfterFaint) {
drawScaledText(context, text = lang("ui.pasture.returning"),
x = x + 18, y = y + 7, scale = SCALE, colour = 0xFFFF8585.toInt(), shadow = true)
}
val remainingTicks = (pokemon.respawnAt - (client.level?.gameTime ?: 0L)).coerceAtLeast(0L)
val seconds = (remainingTicks + 19L) / 20L
val timer = if (remainingTicks == 0L) lang("ui.pasture.ready") else "%d:%02d".format(seconds / 60L, seconds % 60L).text()
drawScaledTextJustifiedRight(context = context, text = timer, x = x + 59, y = y + 17,
shadow = true, scale = SCALE)
} else {
drawScaledTextJustifiedRight(context = context, text = lang("ui.lv.number", pokemon.level),
x = x + 59, y = y + 17, shadow = true, scale = SCALE)
}
drawScaledTextJustifiedRight(
context = context,
text = lang("ui.lv.number", pokemon.level),
x = x + 59,
y = y + 17,
shadow = true,
scale = SCALE
)
drawScaledText(
context = context,
@@ -288,7 +279,7 @@ class PasturePokemonScrollList(
}
if (canUnpasture()) {
if (isOwned() && canDefend() == true) {
if (canDefend() == true) {
conflictButton.setPos(x + 44, y + 3)
conflictButton.render(context, mouseX, mouseY, partialTicks)
}
@@ -300,7 +291,7 @@ class PasturePokemonScrollList(
override fun mouseClicked(mouseX: Double, mouseY: Double, delta: Int): Boolean {
if (canUnpasture()) {
if (isOwned() && canDefend() == true && conflictButton.isHovered(mouseX, mouseY)) {
if (canDefend() == true && conflictButton.isHovered(mouseX, mouseY)) {
conflictButton.onPress()
return true
}
@@ -312,4 +303,4 @@ class PasturePokemonScrollList(
return false
}
}
}
}
@@ -1,225 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pasture
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.font
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.pc.StorageWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.gui.PCBoxWallpaperRepository
import com.cobblemon.mod.common.net.messages.server.pasture.AdjustPastureRangePacket
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.core.Direction
import net.minecraft.network.chat.Component
class PastureRangeWidget(x: Int, y: Int, private val configuration: PasturePCGUIConfiguration) :
SoundlessWidget(x, y, StorageWidget.SCREEN_WIDTH, StorageWidget.SCREEN_HEIGHT, lang("ui.pasture.range")) {
private val rangeResource = cobblemonResource("textures/gui/pasture/pasture_range.png")
private val glowResource = cobblemonResource("textures/gui/pc/wallpaper/basic/glow/wallpaper_basic_05.png")
private val overlayResource = cobblemonResource("textures/gui/pc/pc_screen_overlay.png")
private val infoResource = cobblemonResource("textures/gui/pasture/pasture_range_help.png")
private val infoButtonResource = cobblemonResource("textures/gui/pasture/pasture_button.png")
private val infoButtonSize = 17
private val directions = listOf(configuration.facing, configuration.facing.clockWise, configuration.facing.opposite, configuration.facing.counterClockWise)
private val arrowBounds = listOf(
intArrayOf(75, 17, 103, 46), intArrayOf(116, 64, 152, 96),
intArrayOf(75, 109, 103, 143), intArrayOf(26, 64, 62, 96)
)
// Centers of the numeral shadows in pasture_range.png, relative to its (85, 27) panel origin.
private val numberPositions = listOf(89 to 37, 125 to 77, 89 to 114, 53 to 77)
private var hoveredDirection: Direction? = null
private var showInfo = false
val isInfoOpen get() = showInfo
fun closeInfo() { showInfo = false }
private val infoButtonX get() = x + 5
private val infoButtonY get() = y + 5
private val infoPanelX get() = x + 7
private val infoPanelY get() = y + 40
private val infoPanelWidth = 160
private val infoPanelHeight = 74
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
blitk(
matrixStack = context.pose(), texture = PCBoxWallpaperRepository.defaultWallpaper,
x = x, y = y, width = width, height = height
)
blitk(
matrixStack = context.pose(), texture = rangeResource,
x = x + 3, y = y + 3, width = width - 6, height = height - 6,
uOffset = 88, vOffset = 30, textureWidth = 349, textureHeight = 205
)
blitk(
matrixStack = context.pose(), texture = glowResource,
x = x - 17, y = y - 17, width = 208, height = 189
)
blitk(matrixStack = context.pose(), texture = overlayResource, x = x, y = y, width = width, height = height)
hoveredDirection = if (showInfo) null else directionAt(mouseX.toDouble(), mouseY.toDouble())
directions.indexOf(hoveredDirection).takeIf { it >= 0 }?.let { index ->
renderArrowHighlight(context, index)
}
context.pose().pushPose()
context.pose().translate(-0.5F, 0F, 0F)
directions.forEachIndexed { index, direction ->
val (numberX, numberY) = numberPositions[index]
drawScaledText(context, text = Component.literal(configuration.range[direction].toString()),
x = x + numberX + 1, y = y + numberY - 7, centered = true,
colour = 0xFFFFFFFF.toInt())
}
context.pose().popPose()
if (showInfo) context.fill(x + 3, y + 3, x + width - 3, y + height - 3, 0xAA002730.toInt())
val infoHovered = isOverInfoButton(mouseX.toDouble(), mouseY.toDouble())
// Use the same button face and persistent open-state highlight as the gear.
blitk(matrixStack = context.pose(), texture = infoButtonResource,
x = infoButtonX, y = infoButtonY, width = 13, height = infoButtonSize,
textureWidth = 70, textureHeight = 34)
blitk(matrixStack = context.pose(), texture = infoButtonResource,
x = infoButtonX + 13, y = infoButtonY, width = 4, height = infoButtonSize,
uOffset = 66, textureWidth = 70, textureHeight = 34)
if (showInfo || infoHovered) context.fill(infoButtonX + 2, infoButtonY + 2,
infoButtonX + 16, infoButtonY + 15, 0x88FFFFFF.toInt())
blitk(matrixStack = context.pose(), texture = infoResource,
x = infoButtonX + 4.5F, y = infoButtonY + 3.5F, width = 8, height = 10)
if (showInfo) {
val options = Minecraft.getInstance().options
val boxX = infoPanelX
val boxY = infoPanelY
val boxWidth = infoPanelWidth
val boxHeight = infoPanelHeight
context.fill(boxX + 1, boxY, boxX + boxWidth - 1, boxY + boxHeight, 0x96FFFFFF.toInt())
context.fill(boxX, boxY + 1, boxX + boxWidth, boxY + boxHeight - 1, 0x96FFFFFF.toInt())
blitk(matrixStack = context.pose(), texture = PCBoxWallpaperRepository.defaultWallpaper,
x = boxX + 1, y = boxY + 1, width = boxWidth - 2, height = boxHeight - 2,
textureWidth = StorageWidget.SCREEN_WIDTH, textureHeight = StorageWidget.SCREEN_HEIGHT)
val increaseBinding = if (options.keyAttack.matchesMouse(0)) lang("ui.pasture.range.left_click")
else options.keyAttack.translatedKeyMessage.copy()
val decreaseBinding = if (options.keyUse.matchesMouse(1)) lang("ui.pasture.range.right_click")
else options.keyUse.translatedKeyMessage.copy()
val columns = listOf(
Triple(boxX + 43, lang("ui.pasture.range.increase").bold(), listOf(increaseBinding, lang("ui.pasture.range.scroll_up"))),
Triple(boxX + 117, lang("ui.pasture.range.decrease").bold(), listOf(decreaseBinding, lang("ui.pasture.range.scroll_down")))
)
val detailWidth = columns.flatMap { it.third }.maxOf {
Minecraft.getInstance().font.width(it.copy().font(CobblemonResources.DEFAULT_LARGE))
}
val detailScale = minOf(1F, 72F / detailWidth)
drawScaledText(context, font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.pasture.range.help_title").bold(), x = boxX + 80, y = boxY + 8,
centered = true, shadow = true)
context.fill(boxX + 8, boxY + 22, boxX + boxWidth - 8, boxY + 23, 0x96FFFFFF.toInt())
context.fill(boxX + 80, boxY + 27, boxX + 81, boxY + 66, 0x96FFFFFF.toInt())
columns.forEach { (centerX, title, details) ->
drawScaledText(context, font = CobblemonResources.DEFAULT_LARGE,
text = title, x = centerX, y = boxY + 29, centered = true, shadow = true)
details.forEachIndexed { index, line ->
drawScaledText(context, font = CobblemonResources.DEFAULT_LARGE,
text = line, x = centerX, y = boxY + 44 + index * 14,
scale = detailScale, centered = true, shadow = true)
}
}
}
}
private fun renderArrowHighlight(context: GuiGraphics, index: Int) {
fun fill(left: Int, top: Int, right: Int, bottom: Int) {
context.fill(x + left, y + top, x + right, y + bottom, 0x66FFFFFF)
}
// Trace the arrow faces in pasture_range.png; horizontal arrows have longer stems.
// Keep their offset drop shadows visible instead of brightening the surrounding background.
when (index) {
0 -> {
for (row in 0..14) {
val halfWidth = 3 + minOf(row, 11)
fill(89 - halfWidth, 17 + row, 89 + halfWidth, 18 + row)
}
fill(81, 32, 97, 41)
}
1, 3 -> {
for (row in 0..26) {
val tip = 141 + minOf(row, 11, 26 - row)
if (index == 1) fill(137, 64 + row, tip, 65 + row)
else fill(178 - tip, 64 + row, 41, 65 + row)
}
if (index == 1) fill(116, 72, 137, 83)
else fill(41, 72, 62, 83)
}
2 -> {
fill(81, 109, 97, 123)
for (row in 0..14) {
val halfWidth = 14 - maxOf(0, row - 3)
fill(89 - halfWidth, 123 + row, 89 + halfWidth, 124 + row)
}
}
}
}
private fun directionAt(mouseX: Double, mouseY: Double): Direction? {
val index = arrowBounds.indexOfFirst { (left, top, right, bottom) ->
mouseX >= x + left && mouseX < x + right &&
mouseY >= y + top && mouseY < y + bottom
}
return directions.getOrNull(index)
}
private fun adjust(direction: Direction, increase: Boolean): Boolean {
AdjustPastureRangePacket(configuration.pastureId, direction, increase).sendToServer()
return true
}
private fun isOverInfoButton(mouseX: Double, mouseY: Double) =
mouseX >= infoButtonX && mouseX < infoButtonX + infoButtonSize &&
mouseY >= infoButtonY && mouseY < infoButtonY + infoButtonSize
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (pButton == 0 && isOverInfoButton(pMouseX, pMouseY)) {
showInfo = !showInfo
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.PC_CLICK, 1F))
return true
}
if (showInfo) {
if (pMouseX < infoPanelX || pMouseX >= infoPanelX + infoPanelWidth ||
pMouseY < infoPanelY || pMouseY >= infoPanelY + infoPanelHeight) closeInfo()
return true
}
val direction = directionAt(pMouseX, pMouseY) ?: return false
val options = Minecraft.getInstance().options
return when {
options.keyAttack.matchesMouse(pButton) -> adjust(direction, true)
options.keyUse.matchesMouse(pButton) -> adjust(direction, false)
else -> false
}
}
override fun mouseScrolled(mouseX: Double, mouseY: Double, scrollX: Double, scrollY: Double): Boolean {
if (showInfo) return true
val direction = directionAt(mouseX, mouseY) ?: return false
if (scrollY == 0.0) return false
return adjust(direction, scrollY > 0)
}
override fun keyPressed(pKeyCode: Int, pScanCode: Int, pModifiers: Int): Boolean {
if (showInfo) return !Minecraft.getInstance().options.keyInventory.matches(pKeyCode, pScanCode)
val direction = hoveredDirection ?: return false
val options = Minecraft.getInstance().options
return when {
options.keyAttack.matches(pKeyCode, pScanCode) -> adjust(direction, true)
options.keyUse.matches(pKeyCode, pScanCode) -> adjust(direction, false)
else -> false
}
}
}
@@ -20,9 +20,7 @@ import com.cobblemon.mod.common.net.messages.server.pasture.UnpastureAllPokemonP
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.Component
import net.minecraft.client.gui.components.Button
class PastureWidget(
val storageWidget: StorageWidget,
@@ -34,8 +32,6 @@ class PastureWidget(
companion object {
private val baseResource = cobblemonResource("textures/gui/pasture/pasture_panel.png")
private val buttonResource = cobblemonResource("textures/gui/pasture/pasture_button.png")
private val gearResource = cobblemonResource("textures/gui/pasture/pasture_range_gear.png")
}
private val recallButton = RecallButton(
@@ -46,14 +42,6 @@ class PastureWidget(
UnpastureAllPokemonPacket(pasturePCGUIConfiguration.pastureId).sendToServer()
}
private val rangeButton = object : Button(x + 6, y + 174, 27, 17, lang("ui.pasture.range"), {
storageWidget.pcGui.setPastureRangeEditing(!pasturePCGUIConfiguration.editingRange)
}, DEFAULT_NARRATION) {
override fun playDownSound(soundManager: SoundManager) {
storageWidget.pcGui.playSound(CobblemonSounds.PC_CLICK)
}
}
val pastureScrollList = PasturePokemonScrollList(
listX = x + 6,
listY = y + 31,
@@ -82,29 +70,11 @@ class PastureWidget(
pastureScrollList.render(context, mouseX, mouseY, delta)
recallButton.render(context, mouseX, mouseY, delta)
rangeButton.message = lang(if (pasturePCGUIConfiguration.editingRange) "ui.pasture.storage" else "ui.pasture.range")
val buttonX = rangeButton.x
val buttonY = rangeButton.y
val highlighted = pasturePCGUIConfiguration.editingRange || rangeButton.isMouseOver(mouseX.toDouble(), mouseY.toDouble())
blitk(matrixStack = context.pose(), texture = buttonResource, x = buttonX, y = buttonY,
width = 23, height = 17, textureWidth = 70, textureHeight = 34)
blitk(matrixStack = context.pose(), texture = buttonResource, x = buttonX + 23, y = buttonY,
width = 4, height = 17, uOffset = 66, textureWidth = 70, textureHeight = 34)
if (highlighted) context.fill(buttonX + 2, buttonY + 2, buttonX + 26, buttonY + 15, 0x88FFFFFF.toInt())
val gearSize = 11
val gearX = buttonX + (rangeButton.width - gearSize) / 2
val gearY = buttonY + (rangeButton.height - gearSize) / 2
if (highlighted) blitk(matrixStack = context.pose(), texture = gearResource,
x = gearX + 1, y = gearY, width = gearSize, height = gearSize,
red = 0.2F, green = 0.2F, blue = 0.2F)
blitk(matrixStack = context.pose(), texture = gearResource,
x = gearX, y = gearY, width = gearSize, height = gearSize)
}
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (rangeButton.mouseClicked(pMouseX, pMouseY, pButton)) return true
if (recallButton.isHovered(pMouseX, pMouseY)) recallButton.mouseClicked(pMouseX, pMouseY, pButton)
if (pastureScrollList.isHovered(pMouseX, pMouseY)) pastureScrollList.mouseClicked(pMouseX, pMouseY, pButton)
return super.mouseClicked(pMouseX, pMouseY, pButton)
}
}
}
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
@@ -64,6 +65,9 @@ class FilterWidget(
renderCursor(context, input)
if (iconHovered(mouseX, mouseY)) {
context.renderTooltip(Minecraft.getInstance().font, lang("ui.pc.filter.tooltip"), mouseX, mouseY)
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
@@ -77,4 +81,4 @@ class FilterWidget(
fun iconHovered(mouseX: Int, mouseY: Int): Boolean = mouseX >= (x - 9) && mouseY >= (y + 3)
&& mouseX <= ((x - 9) + (ICON_SIZE * SCALE)) && mouseY <= ((y + 3) + (ICON_SIZE * SCALE))
}
}
@@ -20,7 +20,6 @@ import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.pasture.PasturePCGUIConfiguration
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.Summary.Companion.iconCosmeticItemResource
@@ -141,10 +140,7 @@ class PCGUI(
// Add Exit Button
this.addRenderableWidget(
ExitButton(pX = x + 320, pY = y + 186) {
if (storageWidget.editingPastureRange) {
setPastureRangeEditing(false)
playSound(CobblemonSounds.PC_CLICK)
} else if (::wallpaperWidget.isInitialized && wallpaperWidget.visible) {
if (::wallpaperWidget.isInitialized && wallpaperWidget.visible) {
wallpaperWidget.visible = false
configuration.showParty = true
for (button in optionButtons) button.highlighted = false
@@ -162,7 +158,7 @@ class PCGUI(
pX = x + 220,
pY = y + 16,
forward = true
) { if (!storageWidget.editingPastureRange) storageWidget.box += 1 }
) { storageWidget.box += 1 }
)
// Add Backwards Button
@@ -171,7 +167,7 @@ class PCGUI(
pX = x + 117,
pY = y + 16,
forward = false
) { if (!storageWidget.editingPastureRange) storageWidget.box -= 1 }
) { storageWidget.box -= 1 }
)
// Add Filter Widget
@@ -201,8 +197,6 @@ class PCGUI(
storageWidget = storageWidget
)
this.addRenderableWidget(boxNameWidget)
filterWidget.active = !storageWidget.editingPastureRange
boxNameWidget.active = !storageWidget.editingPastureRange
// Initialise box options if not pasture
if (storageWidget.pastureWidget == null) {
@@ -697,10 +691,6 @@ class PCGUI(
super.render(context, mouseX, mouseY, delta)
if (!storageWidget.editingPastureRange && filterWidget.iconHovered(mouseX, mouseY)) {
context.renderTooltip(Minecraft.getInstance().font, lang("ui.pc.filter.tooltip"), mouseX, mouseY)
}
// Item Tooltip
if (pokemon != null) {
val displayedItem = if (showCosmeticItem) pokemon.cosmeticItem else pokemon.heldItemNoCopy()
@@ -721,34 +711,17 @@ class PCGUI(
}
}
fun setPastureRangeEditing(editing: Boolean) {
val pasture = configuration as? PasturePCGUIConfiguration ?: return
pasture.editingRange = editing
if (!editing) storageWidget.pastureRangeWidget?.closeInfo()
filterWidget.active = !editing
boxNameWidget.active = !editing
focused = null
storageWidget.resetSelected()
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
val rangeWidget = storageWidget.pastureRangeWidget
if (storageWidget.editingPastureRange && rangeWidget?.isInfoOpen == true &&
children().none { it is ExitButton && it.isHovered(mouseX, mouseY) }) {
return rangeWidget.mouseClicked(mouseX, mouseY, button)
}
// Trigger child component function as they will need to check the entire screen click area
if (::boxNameWidget.isInitialized && !storageWidget.editingPastureRange) boxNameWidget.mouseClicked(mouseX, mouseY, button)
if (::boxNameWidget.isInitialized) boxNameWidget.mouseClicked(mouseX, mouseY, button)
return super.mouseClicked(mouseX, mouseY, button)
}
override fun mouseScrolled(mouseX: Double, mouseY: Double, amount: Double, verticalAmount: Double): Boolean {
if (storageWidget.editingPastureRange &&
storageWidget.pastureRangeWidget?.mouseScrolled(mouseX, mouseY, amount, verticalAmount) == true) return true
storageWidget.pastureWidget?.let { pasture ->
if (pasture.pastureScrollList.isHovered) pasture.pastureScrollList.mouseScrolled(mouseX, mouseY, amount, verticalAmount)
}
if (!storageWidget.editingPastureRange && storageWidget.isHovered && mouseX < (storageWidget.x + StorageWidget.SCREEN_WIDTH)) this.storageWidget.box -= verticalAmount.toInt()
if (storageWidget.isHovered && mouseX < (storageWidget.x + StorageWidget.SCREEN_WIDTH)) this.storageWidget.box -= verticalAmount.toInt()
if (::wallpaperWidget.isInitialized && wallpaperWidget.isHovered) wallpaperWidget.mouseScrolled(mouseX, mouseY, amount, verticalAmount)
val infoBoxX = ((width - BASE_WIDTH) / 2) + 9
@@ -771,11 +744,6 @@ class PCGUI(
}
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
if (storageWidget.editingPastureRange) {
if (keyCode != InputConstants.KEY_ESCAPE &&
storageWidget.pastureRangeWidget?.keyPressed(keyCode, scanCode, modifiers) == true) return true
if (keyCode == InputConstants.KEY_RIGHT || keyCode == InputConstants.KEY_LEFT) return true
}
val boxNameFocused = this::boxNameWidget.isInitialized && boxNameWidget.isFocused
val filterFocused = this::filterWidget.isInitialized && filterWidget.isFocused
@@ -19,7 +19,6 @@ import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.pasture.PasturePCGUIConfiguration
import com.cobblemon.mod.common.client.gui.pasture.PastureWidget
import com.cobblemon.mod.common.client.gui.pasture.PastureRangeWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.gui.PCBoxWallpaperRepository
@@ -75,9 +74,6 @@ class StorageWidget(
private val releaseNoButton: ReleaseConfirmButton
var pastureWidget: PastureWidget? = null
val pastureRangeWidget = (pcGui.configuration as? PasturePCGUIConfiguration)?.let { PastureRangeWidget(x, y, it) }
val editingPastureRange: Boolean
get() = (pcGui.configuration as? PasturePCGUIConfiguration)?.editingRange == true
var displayConfirmRelease = false
var screenLoaded = false
@@ -222,11 +218,6 @@ class StorageWidget(
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
if (editingPastureRange) {
pastureRangeWidget?.render(context, mouseX, mouseY, delta)
pastureWidget?.render(context, mouseX, mouseY, delta)
return
}
val matrices = context.pose()
// Party Label
if (pcGui.configuration.showParty) {
@@ -349,10 +340,6 @@ class StorageWidget(
}
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (editingPastureRange) {
return pastureRangeWidget?.mouseClicked(pMouseX, pMouseY, pButton) == true ||
pastureWidget?.mouseClicked(pMouseX, pMouseY, pButton) == true
}
if (displayConfirmRelease) {
if (releaseYesButton.isHovered(pMouseX, pMouseY)) releaseYesButton.mouseClicked(pMouseX, pMouseY, pButton)
if (releaseNoButton.isHovered(pMouseX, pMouseY)) releaseNoButton.mouseClicked(pMouseX, pMouseY, pButton)
@@ -0,0 +1,167 @@
package com.cobblemon.mod.common.client.gui.trainercard
import com.cobblemon.mod.common.api.badges.Badge
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.gui.ScrollingWidget
import com.cobblemon.mod.common.client.gui.pokedex.renderTooltip
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.util.FastColor
import net.minecraft.util.Mth
class BadgesScrollingWidget(
x: Int,
y: Int,
width: Int,
height: Int,
private val columns: Int
) : ScrollingWidget<BadgesScrollingWidget.BadgeRow>(
width = width,
height = height,
left = x,
top = y - height,
slotHeight = SLOT_SIZE + SLOT_SPACING
) {
private var tooltip: MutableComponent? = null
private var tooltipX = 0
private var tooltipY = 0
companion object {
const val SLOT_SIZE = 16
const val SLOT_SPACING = 3
const val MAX_VISIBLE_ROWS = 2
}
fun createEntries(badges: List<Badge>) {
val obtainedBadgeList = badges.filter { CobblemonClient.clientBadgeData.obtainedBadges.contains(it.identifier) }
.sortedBy { it.order }
val unobtainedBadgesList = badges.filter { !obtainedBadgeList.contains(it) }
.sortedBy { it.order }
val sortedBadgeList = obtainedBadgeList.plus(unobtainedBadgesList)
sortedBadgeList.chunked(columns).forEach { listChunk ->
addEntry(BadgeRow(this, listChunk))
}
}
private val visibleRows: Int
get() = (height / itemHeight).coerceAtLeast(1)
private fun hasScrollbar(): Boolean = children().size > visibleRows
override fun getRowLeft(): Int = if (hasScrollbar()) left + 1 else left
override fun getScrollbarPosition(): Int = rowLeft + width - 3
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
tooltip = null
super.renderWidget(context, mouseX, mouseY, delta)
tooltip?.let { renderTooltip(context, it, tooltipX, tooltipY, delta) }
}
fun setTooltip(text: MutableComponent, mouseX: Int, mouseY: Int) {
if (mouseX in left until left + width && mouseY in this.y until bottom) {
tooltip = text
tooltipX = mouseX + 20
tooltipY = mouseY - 10
}
}
override fun renderScrollbar(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
if (hasScrollbar()) {
val xLeft = this.scrollbarPosition
val xRight = xLeft + 3
val barHeight = this.bottom - this.y
var yBottom = ((barHeight * barHeight).toFloat() / this.maxPosition.toFloat()).toInt()
yBottom = Mth.clamp(yBottom, minOf(32, barHeight), maxOf(minOf(32, barHeight), barHeight - 8))
var yTop = scrollAmount.toInt() * (barHeight - yBottom) / this.maxScroll + this.y
if (yTop < this.y) {
yTop = this.y
}
context.fill(xLeft, this.y, xRight, this.bottom, FastColor.ARGB32.color(255, 75, 75, 75))
context.fill(xLeft, yTop, xRight, yTop + yBottom, FastColor.ARGB32.color(255, 141, 141, 141))
}
}
private fun renderEntry(context: GuiGraphics, mouseX: Int, mouseY: Int, index: Int, x: Int, y: Int) {
getEntry(index).renderRow(context, y, x, mouseX, mouseY)
}
override fun renderListItems(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val rowX = rowLeft
for (index in 0 until this.itemCount) {
val rowY = this.getRowTop(index)
val o = this.getRowBottom(index)
if (o >= this.y && rowY <= this.bottom) {
this.renderEntry(context, mouseX, mouseY, index, rowX, rowY)
}
}
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, dragX: Double, dragY: Double): Boolean =
if (hasScrollbar()) super.mouseDragged(mouseX, mouseY, button, dragX, dragY) else true
override fun mouseScrolled(mouseX: Double, mouseY: Double, scrollX: Double, scrollY: Double): Boolean =
if (hasScrollbar()) super.mouseScrolled(mouseX, mouseY, scrollX, scrollY) else true
class BadgeRow(
val parent: BadgesScrollingWidget,
val badgeList: List<Badge?>
) : Slot<BadgeRow>() {
fun renderRow(context: GuiGraphics, y: Int, x: Int, mouseX: Int, mouseY: Int) {
badgeList.forEachIndexed { index, badge ->
val startPosX = x + ((SLOT_SPACING + SLOT_SIZE) * index)
val startPosY = y + SLOT_SPACING
if (badge != null) {
val obtained = CobblemonClient.clientBadgeData.obtainedBadges.contains(badge.identifier)
blitk(
matrixStack = context.pose(),
texture = badge.texture,
x = startPosX,
y = startPosY,
width = SLOT_SIZE,
height = SLOT_SIZE,
red = if (obtained) 1 else 0,
green = if (obtained) 1 else 0,
blue = if (obtained) 1 else 0
)
if (mouseX in startPosX until startPosX + SLOT_SIZE &&
mouseY in startPosY until startPosY + SLOT_SIZE
) {
parent.setTooltip(
if (obtained) Component.translatable(badge.name) else lang("badge.unobtained"),
mouseX, mouseY
)
}
}
}
}
override fun getNarration(): Component? {
return badgeList.firstOrNull()?.let { Component.translatable(it.name) }
}
override fun render(
guiGraphics: GuiGraphics,
index: Int,
top: Int,
left: Int,
width: Int,
height: Int,
mouseX: Int,
mouseY: Int,
hovering: Boolean,
partialTick: Float
) {}
}
}
@@ -0,0 +1,221 @@
package com.cobblemon.mod.common.client.gui.trainercard
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.badges.Badges
import com.cobblemon.mod.common.api.pokedex.CaughtCount
import com.cobblemon.mod.common.api.stats.CobblemonStats.getStat
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.gui.screens.inventory.InventoryScreen
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.Component
import net.minecraft.network.protocol.game.ServerboundClientCommandPacket
import net.minecraft.stats.Stats
import net.minecraft.util.CommonColors
import kotlin.math.ceil
class TrainerCardMenu : Screen(
Component.translatable("cobblemon.ui.trainercard.title")
), CobblemonRenderable {
private var elapsedTicks = 0
private var lastSyncedTimePlayed = 0
private var panelLeft = 0
private var panelTop = 0
private lateinit var badgeScrollList: BadgesScrollingWidget
private lateinit var leftPageArrow: TrainerCardPageArrow
private lateinit var rightPageArrow: TrainerCardPageArrow
private var currentPage = 0
private val maxPages = 3
override fun isPauseScreen(): Boolean = false
override fun init() {
super.init()
elapsedTicks = 0
lastSyncedTimePlayed = getTimePlayed()
// stats need to update when we open the menu, otherwise time played only updates when stats menu is opened
Minecraft.getInstance().connection?.send(
ServerboundClientCommandPacket(ServerboundClientCommandPacket.Action.REQUEST_STATS)
)
val badges = Badges.all().sortedBy { it.order }
val panelWidth = minOf(220, (width - 16).coerceAtLeast(1))
panelLeft = (width - panelWidth) / 2
val leftColumnWidth = panelWidth / 2
val badgeAreaWidth = panelWidth - leftColumnWidth - 8
val columns = ((badgeAreaWidth + BadgesScrollingWidget.SLOT_SPACING) /
(BadgesScrollingWidget.SLOT_SIZE + BadgesScrollingWidget.SLOT_SPACING)).coerceIn(1, 4)
val badgeWidth = columns * BadgesScrollingWidget.SLOT_SIZE +
(columns - 1) * BadgesScrollingWidget.SLOT_SPACING + 4
val rowCount = ceil(badges.size.toDouble() / columns).toInt().coerceAtLeast(1)
val rowHeight = BadgesScrollingWidget.SLOT_SIZE + BadgesScrollingWidget.SLOT_SPACING
val badgeHeight = minOf(rowCount, BadgesScrollingWidget.MAX_VISIBLE_ROWS) * rowHeight
val panelHeight = maxOf(92, badgeHeight + 20)
panelTop = ((height - panelHeight) / 2).coerceAtLeast(8)
badgeScrollList = BadgesScrollingWidget(
x = panelLeft + panelWidth - badgeWidth,
y = panelTop + 10,
width = badgeWidth,
height = badgeHeight,
columns = columns
)
badgeScrollList.createEntries(badges)
addRenderableWidget(badgeScrollList)
leftPageArrow = TrainerCardPageArrow(
buttonX = panelLeft - 10,
buttonY = panelTop,
width = 16,
height = 16,
onPress = {},
parent = this,
isRight = false
)
rightPageArrow = TrainerCardPageArrow(
buttonX = panelLeft + panelWidth + 10,
buttonY = panelTop,
width = 16,
height = 16,
onPress = {},
parent = this,
isRight = true
)
addRenderableWidget(leftPageArrow)
addRenderableWidget(rightPageArrow)
}
override fun tick() {
super.tick()
// make our own live playtime update, otherwise we have to send a packet a second to retrieve the playtime stat
val timePlayed = getTimePlayed()
if (timePlayed != lastSyncedTimePlayed) {
lastSyncedTimePlayed = timePlayed
elapsedTicks = 0
} else { elapsedTicks++ }
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
when(currentPage) {
0 -> {
val portraitX = panelLeft
val portraitY = minOf(panelTop + 48, (height - 32).coerceAtLeast(0))
val portraitSize = 30
val statsTextWidth = (badgeScrollList.x - panelLeft - 8).coerceAtLeast(1)
val player = Minecraft.getInstance().player ?: return // unsure of why this would ever not return a player but if you're here maybe it's because it did
val entity = Minecraft.getInstance().level?.getPlayerByUUID(player.uuid) ?: return
context.enableScissor(portraitX, portraitY, portraitX + portraitSize, portraitY + portraitSize)
InventoryScreen.renderEntityInInventoryFollowsMouse(context,
portraitX, portraitY, portraitX + portraitSize, portraitY + portraitSize, 28, 0.5f, mouseX.toFloat(), mouseY.toFloat(),
entity
)
context.disableScissor()
val totalSeconds = (getTimePlayed() + elapsedTicks) / 20
val timePlayed = "%d:%02d:%02d:%02d".format(
totalSeconds / 86_400, // days
totalSeconds % 86_400 / 3_600, // hours
totalSeconds % 3_600 / 60, // minutes
totalSeconds % 60 // seconds
)
drawScaledText(
context = context,
text = lang("ui.trainermenu.timeplayed", timePlayed),
x = panelLeft,
y = panelTop + 4,
maxCharacterWidth = statsTextWidth
)
drawScaledText(
context = context,
text = lang("ui.trainermenu.pokemon_registered",
CobblemonClient.clientPokedexData.getDexCalculatedValue(cobblemonResource("national"), CaughtCount)),
x = panelLeft,
y = panelTop + 14,
maxCharacterWidth = statsTextWidth
)
val playerBattlesWon = player.stats.getValue(Stats.CUSTOM.get(getStat(Cobblemon.statistics.PVP_BATTLES_WON)))
val playerBattlesLost = player.stats.getValue(Stats.CUSTOM.get(getStat(Cobblemon.statistics.PVP_BATTLES_LOST)))
if (playerBattlesWon > 0 || playerBattlesLost > 0) {
drawScaledText(
context = context,
text = lang("ui.trainermenu.battle_stats",
playerBattlesWon,
playerBattlesLost
),
x = panelLeft,
y = panelTop + 24,
maxCharacterWidth = statsTextWidth
)
}
badgeScrollList.render(context, mouseX, mouseY, partialTick)
}
}
drawScaledText(
context = context,
text = Component.literal("$currentPage"),
x = panelLeft - 30,
y = panelTop
)
if (leftPageArrow.shouldExist()) {
leftPageArrow.render(context, mouseX, mouseY, partialTick)
}
if (rightPageArrow.shouldExist()) {
rightPageArrow.render(context, mouseX, mouseY, partialTick)
}
}
private class TrainerCardPageArrow(
buttonX: Int,
buttonY: Int, width: Int, height: Int, onPress: OnPress,
val parent: TrainerCardMenu,
val isRight: Boolean
): Button(
buttonX, buttonY, width, height, "".text(), onPress, DEFAULT_NARRATION
) {
fun shouldExist(): Boolean {
if (isRight && parent.currentPage == parent.maxPages - 1) return false
if (!isRight && parent.currentPage == 0) return false
return true
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
context.fill(x, y, x + width, y + height, CommonColors.WHITE)
}
override fun playDownSound(handler: SoundManager) {
if (shouldExist()) super.playDownSound(handler)
}
override fun onClick(mouseX: Double, mouseY: Double) {
if (!shouldExist()) return
parent.currentPage += if (isRight) 1 else -1
}
}
private fun getTimePlayed(): Int =
Minecraft.getInstance().player?.stats?.getValue(Stats.CUSTOM.get(Stats.PLAY_TIME)) ?: 0
}
@@ -39,6 +39,7 @@ object CobblemonKeyBinds {
val PARTY_OVERLAY_UP = this.queue(UpShiftPartyBinding)
val SEND_OUT_POKEMON = this.queue(PartySendBinding)
val RIDING_FREELOOK = this.queue(RidingFreelookBinding)
val TRAINER_MENU = this.queue(TrainerMenuBinding)
fun register(registrar: (KeyMapping) -> Unit) {
this.keyBinds.forEach(registrar::invoke)
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.keybind.keybinds
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.trainercard.TrainerCardMenu
import com.cobblemon.mod.common.client.keybind.CobblemonPartyLockedKeyBinding
import com.cobblemon.mod.common.client.keybind.KeybindCategories
import com.mojang.blaze3d.platform.InputConstants
import net.minecraft.client.Minecraft
object TrainerMenuBinding : CobblemonPartyLockedKeyBinding(
"key.cobblemon.trainermenu",
InputConstants.Type.KEYSYM,
InputConstants.KEY_N,
KeybindCategories.COBBLEMON_CATEGORY
) {
override fun onPress() {
Minecraft.getInstance().setScreen(TrainerCardMenu())
}
}
@@ -23,8 +23,6 @@ object OpenPastureHandler : ClientNetworkPacketHandler<OpenPasturePacket> {
pastureId = packet.pastureId,
limit = packet.limit,
permissions = packet.permissions,
range = packet.range,
facing = packet.facing,
pasturedPokemon = SettableObservable(packet.tetheredPokemon)
)
@@ -1,26 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.pasture
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.client.gui.pasture.PasturePCGUIConfiguration
import com.cobblemon.mod.common.client.gui.pc.PCGUI
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureRangePacket
import net.minecraft.client.Minecraft
import net.minecraft.client.resources.sounds.SimpleSoundInstance
object UpdatePastureRangeHandler : ClientNetworkPacketHandler<UpdatePastureRangePacket> {
override fun handle(packet: UpdatePastureRangePacket, client: Minecraft) {
val configuration = (client.screen as? PCGUI)?.configuration as? PasturePCGUIConfiguration ?: return
if (configuration.pastureId != packet.pastureId) return
configuration.range = packet.range
client.soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.PC_CLICK, 1F))
}
}
@@ -1,24 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.pasture
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.client.gui.pc.PCGUI
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureRecoveryPacket
import net.minecraft.client.Minecraft
object UpdatePastureRecoveryHandler : ClientNetworkPacketHandler<UpdatePastureRecoveryPacket> {
override fun handle(packet: UpdatePastureRecoveryPacket, client: Minecraft) {
val slots = (client.screen as? PCGUI)?.storage?.pastureWidget?.pastureScrollList?.children() ?: return
slots.firstOrNull { it.pokemon.pokemonId == packet.pokemonId }?.pokemon?.apply {
healingAfterFaint = packet.fainted
respawnAt = packet.respawnAt
}
}
}
@@ -34,7 +34,6 @@ import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animatio
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.client.render.models.blockbench.quirk.ModelQuirk
import com.cobblemon.mod.common.client.render.models.blockbench.quirk.QuirkData
import com.cobblemon.mod.common.client.sound.instances.UIEntitySoundInstance
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -45,6 +44,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException
import java.util.concurrent.ConcurrentLinkedQueue
import net.minecraft.client.Minecraft
import net.minecraft.client.multiplayer.ClientLevel
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.commands.arguments.item.ItemParser
import net.minecraft.sounds.SoundEvent
import net.minecraft.world.entity.Entity
@@ -289,7 +289,7 @@ abstract class PosableState : Schedulable {
entity.level().playLocalSound(entity, soundEvent, entity.soundSource, volume, pitch)
}
} else {
Minecraft.getInstance().soundManager.play(UIEntitySoundInstance(soundEvent, volume, pitch))
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, volume, pitch))
}
}
}
@@ -19,7 +19,6 @@ import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.sound.instances.AlphaCrySoundInstance
import com.cobblemon.mod.common.client.sound.instances.UIEntitySoundInstance
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.effectiveName
import com.cobblemon.mod.common.util.genericRuntime
@@ -32,6 +31,7 @@ import net.minecraft.client.Minecraft
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.client.multiplayer.ClientLevel
import net.minecraft.client.resources.sounds.EntityBoundSoundInstance
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.world.entity.Entity
@@ -121,7 +121,7 @@ class BedrockSoundKeyframe(
}
}
} else {
Minecraft.getInstance().soundManager.play(UIEntitySoundInstance(soundEvent))
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1F, 1F))
}
}
}
@@ -29,8 +29,8 @@ class FarfetchdGalarianModel(root: ModelPart) : PokemonPosableModel(root), Heade
override var portraitScale = 2.6F
override var portraitTranslation = Vec3(-0.2, -1.0, 0.0)
override var profileScale = 0.93F
override var profileTranslation = Vec3(-0.1, 0.36, 0.0)
override var profileScale = 1.1F
override var profileTranslation = Vec3(-0.1, 0.1, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
@@ -28,8 +28,8 @@ class FarfetchdModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame,
override var portraitScale = 2.6F
override var portraitTranslation = Vec3(-0.2, -1.0, 0.0)
override var profileScale = 0.93F
override var profileTranslation = Vec3(-0.1, 0.36, 0.0)
override var profileScale = 1.1F
override var profileTranslation = Vec3(-0.1, 0.1, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
@@ -23,8 +23,8 @@ class KabutoModel(root: ModelPart) : PokemonPosableModel(root) {
override var portraitScale = 3.3F
override var portraitTranslation = Vec3(0.0, -3.3, 0.0)
override var profileScale = 1.25F
override var profileTranslation = Vec3(0.0, -0.17, 0.0)
override var profileScale = 1.6F
override var profileTranslation = Vec3(0.0, -0.6, 0.0)
lateinit var standing: CobblemonPose
lateinit var walk: CobblemonPose
@@ -21,8 +21,8 @@ class HoothootModel (root: ModelPart) : PokemonPosableModel(root) {
override var portraitScale = 2.2F
override var portraitTranslation = Vec3(-0.25, -0.9, 0.0)
override var profileScale = 0.82F
override var profileTranslation = Vec3(0.0, 0.53, 0.0)
override var profileScale = 1.0F
override var profileTranslation = Vec3(0.0, 0.3, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
@@ -29,8 +29,8 @@ class WooperModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override var portraitScale = 2.0F
override var portraitTranslation = Vec3(-0.15, -0.65, 0.0)
override var profileScale = 0.75F
override var profileTranslation = Vec3(0.0, 0.6, 0.0)
override var profileScale = 0.9F
override var profileTranslation = Vec3(0.0, 0.4, 0.0)
lateinit var shoulderLeft: Pose
lateinit var shoulderRight: Pose
@@ -29,8 +29,8 @@ class WooperPaldeanModel(root: ModelPart) : PokemonPosableModel(root), HeadedFra
override var portraitScale = 2.0F
override var portraitTranslation = Vec3(-0.15, -0.65, 0.0)
override var profileScale = 0.75F
override var profileTranslation = Vec3(0.0, 0.6, 0.0)
override var profileScale = 0.9F
override var profileTranslation = Vec3(0.0, 0.4, 0.0)
lateinit var shoulderLeft: Pose
lateinit var shoulderRight: Pose
@@ -34,7 +34,7 @@ class DusclopsModel (root: ModelPart) : PokemonPosableModel(root) {
lateinit var battleidle: Pose
override fun registerPoses() {
animations["cry"] = "q.bedrock_stateful('dusclops', 'cry')".asExpressionLike()
animations["cry"] = "q.bedrock_stateful('squirtle', 'cry')".asExpressionLike()
val blink = quirk { bedrockStateful("dusclops", "blink") }
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonP
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.asExpressionLike
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
@@ -29,8 +28,6 @@ class MasquerainModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame
lateinit var walk: Pose
lateinit var standing: Pose
override fun registerPoses() {
animations["cry"] = "q.bedrock_stateful('masquerain', 'cry')".asExpressionLike()
val blink = quirk { bedrockStateful("masquerain", "blink") }
standing = registerPose(
poseName = "standing",
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonP
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.isInWater
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
@@ -37,8 +36,6 @@ class SurskitModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
val wateroffset = -8
override fun registerPoses() {
animations["cry"] = "q.bedrock_stateful('surskit', 'cry')".asExpressionLike()
val blink = quirk { bedrockStateful("surskit", "blink") }
standing = registerPose(
poseName = "standing",
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonP
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.asExpressionLike
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
@@ -37,8 +36,6 @@ class CombeeModel (root: ModelPart) : PokemonPosableModel(root) {
val shoulderZOffset = 8
override fun registerPoses() {
animations["cry"] = "q.bedrock_stateful('combee', 'cry')".asExpressionLike()
val blink1 = quirk { bedrockStateful("combee", "blink_right") }
val blink2 = quirk { bedrockStateful("combee", "blink_gender") }
val blink3 = quirk { bedrockStateful("combee", "blink_left") }
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonP
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.asExpressionLike
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
@@ -31,8 +30,6 @@ class VespiquenModel (root: ModelPart) : PokemonPosableModel(root) {
lateinit var standing: Pose
override fun registerPoses() {
animations["cry"] = "q.bedrock_stateful('vespiquen', 'cry')".asExpressionLike()
val blink1 = quirk { bedrockStateful("vespiquen", "blink") }
val wingsleep = quirk { bedrockStateful("vespiquen", "sleep_flap") }
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen5
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class SerperiorModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("serperior")
override val head = getPart("head_ai")
override var portraitScale = 2.5F
override var portraitTranslation = Vec3(-0.3, 1.25, 0.0)
override var profileScale = 0.6F
override var profileTranslation = Vec3(0.0, 0.8, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var sleep: Pose
override val cryAnimation = CryProvider { bedrockStateful("serperior", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("serperior", "blink") }
sleep = registerPose(
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("serperior", "sleep"))
)
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("serperior", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("serperior", "ground_walk")
)
)
}
}
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen5
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class ServineModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame, BimanualFrame {
override val rootPart = root.registerChildWithAllChildren("servine")
override val head = getPart("head_ai")
override val rightArm = getPart("arm_right")
override val leftArm = getPart("arm_left")
override val rightLeg = getPart("leg_right")
override val leftLeg = getPart("leg_left")
override var portraitScale = 2.6F
override var portraitTranslation = Vec3(-0.15, 0.75, 0.0)
override var profileScale = 0.8F
override var profileTranslation = Vec3(0.0, 0.55, 0.0)
lateinit var sleep: Pose
lateinit var standing: Pose
lateinit var walk: Pose
override val cryAnimation = CryProvider { bedrockStateful("servine", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("servine", "blink") }
sleep = registerPose(
poseType = PoseType.SLEEP,
transformTicks = 10,
animations = arrayOf(bedrock("servine", "sleep"))
)
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("servine", "ground_idle")
)
)
walk = registerPose(
poseName = "walking",
poseTypes = PoseType.MOVING_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("servine", "ground_walk")
)
)
}
}
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen5
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class SnivyModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame, BimanualFrame {
override val rootPart = root.registerChildWithAllChildren("snivy")
override val head = getPart("head_ai")
override val rightArm = getPart("arm_right")
override val leftArm = getPart("arm_left")
override val rightLeg = getPart("leg_right")
override val leftLeg = getPart("leg_left")
override var portraitScale = 2.0F
override var portraitTranslation = Vec3(-0.15, 0.32, 0.0)
override var profileScale = 0.8F
override var profileTranslation = Vec3(0.0, 0.55, 0.0)
lateinit var sleep: Pose
lateinit var standing: Pose
lateinit var walk: Pose
override val cryAnimation = CryProvider { bedrockStateful("snivy", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("snivy", "blink") }
sleep = registerPose(
poseType = PoseType.SLEEP,
transformTicks = 10,
animations = arrayOf(bedrock("snivy", "sleep"))
)
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("snivy", "ground_idle")
)
)
walk = registerPose(
poseName = "walking",
poseTypes = PoseType.MOVING_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("snivy", "ground_walk")
)
)
}
}
@@ -23,7 +23,7 @@ class CarbinkModel (root: ModelPart) : PokemonPosableModel(root) {
override var portraitScale = 1.8F
override var portraitTranslation = Vec3(0.0, 0.2, 0.0)
override var profileScale = 0.68F
override var profileScale = 0.85F
override var profileTranslation = Vec3(0.0, 0.7, 0.0)
lateinit var standing: CobblemonPose
@@ -0,0 +1,85 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen6
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class GourgeistModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("gourgeist")
override val head = getPart("head")
override var portraitScale = 4.0F
override var portraitTranslation = Vec3(-0.1, 0.6, 0.0)
override var profileScale = 0.8F
override var profileTranslation = Vec3(0.0, 0.55, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var hovering: Pose
lateinit var fly: Pose
lateinit var sleep: Pose
override val cryAnimation = CryProvider { bedrockStateful("gourgeist", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("gourgeist", "blink") }
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.UI_POSES + PoseType.STAND,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("gourgeist", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseType = PoseType.WALK,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("gourgeist", "ground_walk")
)
)
hovering = registerPose(
poseName = "hovering",
poseType = PoseType.HOVER,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("gourgeist", "air_idle")
)
)
fly = registerPose(
poseName = "fly",
poseType = PoseType.FLY,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("gourgeist", "air_fly")
)
)
sleep = registerPose(
poseName = "sleep",
poseType = PoseType.SLEEP,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("gourgeist", "sleep")
)
)
}
}
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen6
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.CobblemonPose
import com.cobblemon.mod.common.entity.PoseType
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class PumpkabooModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("pumpkaboo")
override val head = getPart("head")
override var portraitScale = 3.5F
override var portraitTranslation = Vec3(0.0, -1.5, 0.0)
override var profileScale = 1.0F
override var profileTranslation = Vec3(0.0, 0.25, 0.0)
lateinit var standing: CobblemonPose
lateinit var walk: CobblemonPose
lateinit var battleidle: CobblemonPose
lateinit var sleep: CobblemonPose
override val cryAnimation = CryProvider { bedrockStateful("pumpkaboo", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("pumpkaboo", "blink") }
val quirk = quirk { bedrockStateful("pumpkaboo", "quirk_idle") }
sleep = registerPose(
poseName = "sleep",
poseType = PoseType.SLEEP,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("pumpkaboo", "sleep")
)
)
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES,
quirks = arrayOf(blink, quirk),
animations = arrayOf(
singleBoneLook(),
bedrock("pumpkaboo", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("pumpkaboo", "ground_walk")
)
)
battleidle = registerPose(
poseName = "battleidle",
poseTypes = PoseType.STATIONARY_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("pumpkaboo", "battle_idle")
)
)
}
}
@@ -26,8 +26,8 @@ class ScatterbugModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
override var portraitScale = 2.6F
override var portraitTranslation = Vec3(-0.2, -1.9, 0.0)
override var profileScale = 0.98F
override var profileTranslation = Vec3(0.0, 0.28, 0.0)
override var profileScale = 1.2F
override var profileTranslation = Vec3(0.0, 0.05, 0.0)
lateinit var standing: CobblemonPose
lateinit var walk: CobblemonPose
@@ -36,8 +36,8 @@ class CharcadetModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame,
override var portraitScale = 1.7F
override var portraitTranslation = Vec3(-0.15, 0.08, 0.0)
override var profileScale = 0.58F
override var profileTranslation = Vec3(0.0, 0.91, 0.0)
override var profileScale = 0.8F
override var profileTranslation = Vec3(0.0, 0.6, 0.0)
lateinit var standing: CobblemonPose
lateinit var walking: CobblemonPose
@@ -35,8 +35,8 @@ class QuaquavalModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame,
override var portraitScale = 2.3F
override var portraitTranslation = Vec3(-0.42, 3.7, 0.0)
override var profileScale = 0.36F
override var profileTranslation = Vec3(0.0, 1.19, 0.0)
override var profileScale = 0.32F
override var profileTranslation = Vec3(0.0, 1.24, 0.0)
lateinit var standing: CobblemonPose
lateinit var standing2: CobblemonPose
@@ -0,0 +1,208 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen9
import com.cobblemon.mod.common.client.render.models.blockbench.animation.WingFlapIdleAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.createTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BiWingedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.parabolaFunction
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.sineFunction
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class SquawkabillyModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame, BiWingedFrame {
override val rootPart = root.registerChildWithAllChildren("squawkabilly")
override val head = getPart("head")
override val leftWing = getPart("wing_left")
override val rightWing = getPart("wing_right")
override val leftLeg = getPart("leg_left")
override val rightLeg = getPart("leg_right")
private val tail = getPart("tail")
override var portraitScale = 2.0F
override var portraitTranslation = Vec3(-0.2, -0.2, 0.0)
override var profileScale = 0.85F
override var profileTranslation = Vec3(0.0, 0.51, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var hovering: Pose
lateinit var flying: Pose
lateinit var shoulderLeft: Pose
lateinit var shoulderRight: Pose
override val cryAnimation = CryProvider { bedrockStateful("squawkabilly", "cry") }
val shoulderOffsetX = -1.0
val shoulderOffsetY = 0
override fun registerPoses() {
val blink = quirk { bedrockStateful("squawkabilly", "blink") }
standing = registerPose(
poseName = "standing",
quirks = arrayOf(blink),
poseTypes = PoseType.UI_POSES + PoseType.STAND,
transformTicks = 10,
animations = arrayOf(
singleBoneLook(),
bedrock("squawkabilly", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
quirks = arrayOf(blink),
poseType = PoseType.WALK,
transformTicks = 10,
animations = arrayOf(
singleBoneLook(),
bedrock("squawkabilly", "ground_idle"),
rootPart.translation(
function = parabolaFunction(
peak = -4F,
period = 0.4F
),
timeVariable = { state, _, _ -> state.animationSeconds },
axis = ModelPartTransformation.Y_AXIS
),
head.translation(
function = sineFunction(
amplitude = (-20F).toRadians(),
period = 1F,
verticalShift = (-10F).toRadians()
),
axis = ModelPartTransformation.X_AXIS,
timeVariable = { state, _, _ -> state.animationSeconds }
),
leftLeg.rotation(
function = parabolaFunction(
tightness = -20F,
phaseShift = 0F,
verticalShift = (30F).toRadians()
),
axis = ModelPartTransformation.X_AXIS,
timeVariable = { _, _, ageInTicks -> ageInTicks / 20 },
),
rightLeg.rotation(
function = parabolaFunction(
tightness = -20F,
phaseShift = 0F,
verticalShift = (30F).toRadians()
),
axis = ModelPartTransformation.X_AXIS,
timeVariable = { _, _, ageInTicks -> ageInTicks / 20 },
),
tail.rotation(
function = sineFunction(
amplitude = (-5F).toRadians(),
period = 1F
),
axis = ModelPartTransformation.X_AXIS,
timeVariable = { _, _, ageInTicks -> ageInTicks / 20 },
),
wingFlap(
flapFunction = sineFunction(
amplitude = (-5F).toRadians(),
period = 0.4F,
phaseShift = 0.00F,
verticalShift = (-20F).toRadians()
),
timeVariable = { state, _, _ -> state.animationSeconds },
axis = ModelPartTransformation.Z_AXIS
),
rightWing.translation(
function = parabolaFunction(
tightness = -10F,
phaseShift = 30F,
verticalShift = (25F).toRadians()
),
axis = ModelPartTransformation.Y_AXIS,
timeVariable = { _, _, ageInTicks -> ageInTicks / 20 },
),
leftWing.translation(
function = parabolaFunction(
tightness = -10F,
phaseShift = 30F,
verticalShift = (25F).toRadians()
),
axis = ModelPartTransformation.Y_AXIS,
timeVariable = { _, _, ageInTicks -> ageInTicks / 20 },
),
)
)
hovering = registerPose(
poseName = "hovering",
quirks = arrayOf(blink),
poseTypes = setOf(PoseType.FLOAT, PoseType.HOVER),
transformTicks = 10,
animations = arrayOf(
singleBoneLook(),
WingFlapIdleAnimation(this,
flapFunction = sineFunction(verticalShift = -10F.toRadians(), period = 0.9F, amplitude = 0.9F),
timeVariable = { state, _, _ -> state.animationSeconds },
axis = ModelPartTransformation.Z_AXIS
)
)
)
flying = registerPose(
poseName = "flying",
quirks = arrayOf(blink),
poseTypes = setOf(PoseType.FLY, PoseType.SWIM),
transformTicks = 10,
animations = arrayOf(
singleBoneLook(),
WingFlapIdleAnimation(this,
flapFunction = sineFunction(verticalShift = -14F.toRadians(), period = 0.9F, amplitude = 0.9F),
timeVariable = { state, _, _ -> state.animationSeconds },
axis = ModelPartTransformation.Z_AXIS
)
)
)
shoulderLeft = registerPose(
poseType = PoseType.SHOULDER_LEFT,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("squawkabilly", "ground_idle")
),
transformedParts = arrayOf(
rootPart.createTransformation().addPosition(shoulderOffsetX, shoulderOffsetY, 0.0)
)
)
shoulderRight = registerPose(
poseType = PoseType.SHOULDER_RIGHT,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("squawkabilly", "ground_idle")
),
transformedParts = arrayOf(
rootPart.createTransformation().addPosition(-shoulderOffsetX, shoulderOffsetY, 0.0)
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isNotPosedIn(sleep)) bedrockStateful("squawkabilly", "faint") else null
}
@@ -322,6 +322,8 @@ object VaryingModelRepository {
inbuilt("loudred", :: LoudredModel)
inbuilt("exploud", :: ExploudModel)
inbuilt("cryogonal", :: CryogonalModel)
inbuilt("pumpkaboo", :: PumpkabooModel)
inbuilt("gourgeist", :: GourgeistModel)
inbuilt("eiscue", :: EiscueModel)
inbuilt("wooloo", :: WoolooModel)
inbuilt("dubwool", :: DubwoolModel)
@@ -338,6 +340,9 @@ object VaryingModelRepository {
inbuilt("thwackey", ::ThwackeyModel)
inbuilt("rillaboom", ::RillaboomModel)
inbuilt("samurott", ::SamurottModel)
inbuilt("snivy", ::SnivyModel)
inbuilt("servine", ::ServineModel)
inbuilt("serperior", ::SerperiorModel)
inbuilt("slugma", ::SlugmaModel)
inbuilt("magcargo", ::MagcargoModel)
inbuilt("slugma_shiny", ::SlugmaShinyModel)
@@ -397,6 +402,7 @@ object VaryingModelRepository {
inbuilt("bunnelby", ::BunnelbyModel)
inbuilt("arrokuda", ::ArrokudaModel)
inbuilt("barraskewda", ::BarraskewdaModel)
inbuilt("squawkabilly", ::SquawkabillyModel)
inbuilt("ponyta_galarian", ::PonytaGalarianModel)
inbuilt("rapidash_galarian", ::RapidashGalarianModel)
inbuilt("volbeat", ::VolbeatModel)
@@ -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.client.sound.instances
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.resources.sounds.SoundInstance
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
/**
* Plays a sound for a Pokémon rendered in a GUI, where there is no entity to take the sound source from.
* Unlike [SimpleSoundInstance.forUI] this uses [SoundSource.NEUTRAL] so the Friendly Creatures slider applies.
*/
class UIEntitySoundInstance(sound: SoundEvent, volume: Float = 1F, pitch: Float = 1F) :
SimpleSoundInstance(sound.location, SoundSource.NEUTRAL, volume, pitch, SoundInstance.createUnseededRandom(),
false, 0, SoundInstance.Attenuation.NONE, 0.0, 0.0, 0.0, true)
@@ -0,0 +1,69 @@
package com.cobblemon.mod.common.command
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
import com.cobblemon.mod.common.command.argument.BadgeArgumentType
import com.cobblemon.mod.common.util.permission
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.context.CommandContext
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands
import net.minecraft.commands.arguments.EntityArgument
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
object BadgeCommand {
private const val NAME = "badge"
private const val GIVE = "give"
private const val REMOVE = "remove"
private const val PLAYER = "player"
fun register(dispatcher: CommandDispatcher<CommandSourceStack>) {
val command =
Commands.literal(NAME)
.permission(CobblemonPermissions.BADGE)
.then(Commands.literal(GIVE)
.then(Commands.argument(PLAYER, EntityArgument.player())
.then(Commands.argument(BADGE, BadgeArgumentType.badge())
.executes { executeGive(it, EntityArgument.getPlayer(it, PLAYER)) }
)
)
)
.then(Commands.literal(REMOVE)
.then(Commands.argument(PLAYER, EntityArgument.player())
.then(Commands.argument(BADGE, BadgeArgumentType.badge())
.executes { executeRemove(it, EntityArgument.getPlayer(it, PLAYER)) }
)
)
)
dispatcher.register(command)
}
private fun executeGive(context: CommandContext<CommandSourceStack>, player: ServerPlayer): Int {
val badge = BadgeArgumentType.getBadge(context, BADGE)
val changed = Cobblemon.playerDataManager.getBadgeData(player).give(badge)
val message = if (changed) {
"Gave ${badge.name} to ${player.name.string}."
} else {
"${player.name.string} already has ${badge.name}."
}
context.source.sendSuccess({ Component.literal(message) }, true)
return Command.SINGLE_SUCCESS
}
private fun executeRemove(context: CommandContext<CommandSourceStack>, player: ServerPlayer): Int {
val badge = BadgeArgumentType.getBadge(context, BADGE)
val changed = Cobblemon.playerDataManager.getBadgeData(player).remove(badge)
val message = if (changed) {
"Removed ${badge} from ${player.name.string}." // TODO move this to lang
} else {
"${player.name.string} does not have ${badge.name}."
}
context.source.sendSuccess({ Component.literal(message) }, true)
return Command.SINGLE_SUCCESS
}
private const val BADGE = "badge"
}
@@ -0,0 +1,51 @@
/*
* 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.command.argument
import com.cobblemon.mod.common.api.badges.Badge
import com.cobblemon.mod.common.api.badges.Badges
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.mojang.brigadier.StringReader
import com.mojang.brigadier.arguments.ArgumentType
import com.mojang.brigadier.context.CommandContext
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType
import com.mojang.brigadier.suggestion.Suggestions
import com.mojang.brigadier.suggestion.SuggestionsBuilder
import net.minecraft.commands.SharedSuggestionProvider
import net.minecraft.network.chat.Component
import java.util.concurrent.CompletableFuture
class BadgeArgumentType : ArgumentType<Badge> {
override fun parse(reader: StringReader): Badge {
val identifier = try {
reader.asIdentifierDefaultingNamespace()
} catch (exception: Exception) {
throw SimpleCommandExceptionType(INVALID_BADGE).createWithContext(reader)
}
return Badges.getByIdentifier(identifier)
?: throw SimpleCommandExceptionType(INVALID_BADGE).createWithContext(reader)
}
override fun <S : Any> listSuggestions(
context: CommandContext<S>,
builder: SuggestionsBuilder
): CompletableFuture<Suggestions> {
return SharedSuggestionProvider.suggest(Badges.all().map { it.identifier.toString() }, builder)
}
companion object {
private val INVALID_BADGE = Component.translatable("cobblemon.command.badge.invalid")
fun badge() = BadgeArgumentType()
fun <S> getBadge(context: CommandContext<S>, name: String): Badge =
context.getArgument(name, Badge::class.java)
}
}
@@ -164,9 +164,6 @@ class CobblemonConfig {
@CobblemonConfigField(Category.Pokemon, lang = "experience_multiplier", SERVER)
var experienceMultiplier = 2F
@CobblemonConfigField(Category.Pokemon, lang = "predation_chance", SERVER)
var predationChance = 0.05F
@CobblemonConfigField(Category.Spawning, lang = "pokemon_per_chunk", SERVER)
var pokemonPerChunk = 1F
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.data
import com.cobblemon.mod.common.*
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.badges.Badges
import com.cobblemon.mod.common.api.berry.Berries
import com.cobblemon.mod.common.api.cooking.Seasonings
import com.cobblemon.mod.common.api.data.DataProvider
@@ -120,6 +121,7 @@ object CobblemonDataProvider : DataProvider {
this.register(SpawnBaitEffects, reloadable = false)
this.register(CobblemonRideSettings, reloadable = true)
this.register(TechnicalMachines, reloadable = false)
this.register(Badges, reloadable = true)
SpawnBait.Effects.setupEffects()
PlatformEvents.SERVER_PLAYER_LOGOUT.subscribe {
@@ -166,6 +166,7 @@ import net.minecraft.world.item.Items
import net.minecraft.world.level.Level
import net.minecraft.world.level.LightLayer
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.SuspiciousEffectHolder
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.gameevent.GameEvent
@@ -303,7 +304,6 @@ open class PokemonEntity(
var drops: DropTable? = null
var tethering: PokemonPastureBlockEntity.Tethering? = null
private var nextPastureReturnAttempt = 0
// TODO review if we still want this
var queuedToDespawn = false
@@ -738,29 +738,34 @@ open class PokemonEntity(
// Check to see if the entity needs recalled or repositioned if pastured.
fun checkPastureTether() {
val currentTethering = tethering ?: return
val world = level() as? ServerLevel ?: return
if (ticksLived == 1 && world.hasChunkAt(currentTethering.pasturePos)) {
val pasture = world.getBlockEntity(currentTethering.pasturePos) as? PokemonPastureBlockEntity
pasture?.tetheredPokemon?.find { it.tetheringId == currentTethering.tetheringId }?.let { tethering = it }
}
if (tethering!!.canRoamTo(x, z) || !world.hasChunkAt(currentTethering.pasturePos)) return
navigation.stop()
(moveControl as? PokemonMoveControl)?.apply { stop(); stopBanking() }
brain.eraseMemory(MemoryModuleType.WALK_TARGET)
deltaMovement = Vec3.ZERO
if (ticksLived < nextPastureReturnAttempt) return
val pasture = world.getBlockEntity(currentTethering.pasturePos) as? PokemonPastureBlockEntity
val position = pasture?.findSpawnPosition(this, blockPosition())
if (position != null) {
teleportTo(position.x, position.y, position.z)
nextPastureReturnAttempt = 0
} else if (pasture == null) {
tethering = null
pokemon.recall()
return
} else {
nextPastureReturnAttempt = ticksLived + 20
// Teleport a pastured mon back to the pasture block if they are out of range
if (this.tethering != null && !this.tethering!!.box.contains(this.x, this.y, this.z)) {
// Gather the ideal position (behind the pastureBlock)
val pastureBlockPos = this.tethering!!.pasturePos
val state = this.level().getBlockState(pastureBlockPos)
// Check to ensure we are working with a pastureblock here and it hasn't been broken
if (state.block == CobblemonBlocks.PASTURE) {
val direction = state.getValue(HorizontalDirectionalBlock.FACING)
val width = this.boundingBox.xsize
val tpPos = pastureBlockPos.offset(direction.normal.multiply(ceil(width).toInt() + 1)).center
val heightAllowance = 4.0 // Allow spawning up to x blocks above the pastureBlock y level
// Attempt tp and recall if it fails
if (!this.randomTeleport(tpPos.x, tpPos.y + heightAllowance, tpPos.z, false)) {
// If the teleport was not successful then recall the mon
this.tethering = null
this.pokemon.recall()
} else {
// Stop any now invalid navigation post teleport
this.navigation.stop()
}
} else {
// If the block is destroyed or something else went wrong.
this.tethering = null
this.pokemon.recall()
}
}
}
@@ -1289,7 +1294,16 @@ open class PokemonEntity(
}
private fun showInteractionWheel(player: ServerPlayer, itemStack: ItemStack) {
val canRide = isRideableFor(player)
val canRide = ifRidingAvailableSupply(false) { behaviour, settings, state ->
if (platform != PlatformType.NONE) return@ifRidingAvailableSupply false
if (tethering != null) return@ifRidingAvailableSupply false;
if (seats.isEmpty()) return@ifRidingAvailableSupply false;
if ((owner as? ServerPlayer)?.isInBattle() == true) return@ifRidingAvailableSupply false;
if (this.owner != player && this.passengers.isEmpty()) return@ifRidingAvailableSupply false;
if(pokemon.effectiveScale < Cobblemon.config.minimumRidingScale) return@ifRidingAvailableSupply false;
if(beamMode == 3) return@ifRidingAvailableSupply false;
return@ifRidingAvailableSupply behaviour.isActive(settings, state, this);
}
if (pokemon.getOwnerPlayer() == player) {
val cosmeticItemDefinition = CobblemonCosmeticItems.findValidCosmeticForPokemonAndItem(
player.level().registryAccess(),
@@ -1311,18 +1325,6 @@ open class PokemonEntity(
}
}
fun isRideableFor(player: ServerPlayer): Boolean = ifRidingAvailableSupply(false) canRide@{ behaviour, settings, state ->
if (!player.canInteractWithEntity(this, player.entityInteractionRange())) return@canRide false
if (platform != PlatformType.NONE) return@canRide false
if (tethering != null) return@canRide false
if (seats.isEmpty()) return@canRide false
if ((owner as? ServerPlayer)?.isInBattle() == true) return@canRide false
if (this.owner != player && this.passengers.isEmpty()) return@canRide false
if (pokemon.effectiveScale < Cobblemon.config.minimumRidingScale) return@canRide false
if (beamMode == 3) return@canRide false
return@canRide behaviour.isActive(settings, state, this)
}
override fun getDimensions(pose: Pose): EntityDimensions {
val scale = effects.mockEffect?.scale ?: (form.baseScale * pokemon.effectiveScale)
var result = this.exposedForm.hitbox.scale(scale)
@@ -1604,8 +1606,9 @@ open class PokemonEntity(
|| stack.`is`(CobblemonItemTags.WHITELISTED_ITEMS_TO_HOLD)
fun tryRidingPokemon(player: ServerPlayer): Boolean {
if (!isRideableFor(player)) return false
if (pokemon.effectiveScale < Cobblemon.config.minimumRidingScale) {
return false
}
val event = RidePokemonEvent.Pre(player, this)
CobblemonEvents.RIDE_EVENT_PRE.post(event)
if (!event.isCanceled) {
@@ -1795,7 +1798,7 @@ open class PokemonEntity(
removalObservable.emit(reason)
this.brain.clearMemories()
if (reason.shouldDestroy() && pokemon.tetheringId != null && tethering == null) {
if (reason.shouldDestroy() && pokemon.tetheringId != null) {
pokemon.tetheringId = null
}
if (evolutionEntity != null) {
@@ -29,7 +29,6 @@ import net.minecraft.tags.FluidTags
import net.minecraft.util.Mth
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.entity.ai.control.MoveControl
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.phys.AABB
@@ -107,14 +106,7 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
if (banking) {
pokemonEntity.yRot += bankRightDegreesPerTick
val forward = bankForwardBlocksPerTick * mediumSpeed
val tethering = pokemonEntity.tethering
if (tethering?.canRoamTo(
pokemonEntity.x - Mth.sin(pokemonEntity.yRot.toRadians()) * forward,
pokemonEntity.z + Mth.cos(pokemonEntity.yRot.toRadians()) * forward
) != false) {
pokemonEntity.travel(Vec3(0.0, bankUpwardsBlocksPerTick.toDouble(), bankForwardBlocksPerTick.toDouble()).scale(mediumSpeed.toDouble()))
}
pokemonEntity.travel(Vec3(0.0, bankUpwardsBlocksPerTick.toDouble(), bankForwardBlocksPerTick.toDouble()).scale(mediumSpeed.toDouble()))
if (bankDurationTicks > 0) {
bankDurationTicks--
}
@@ -288,16 +280,6 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
}
}
}
val tethering = pokemonEntity.tethering
val motion = mob.deltaMovement
if (tethering?.canRoamTo(mob.x, mob.z) == true &&
!tethering.canRoamTo(mob.x + motion.x, mob.z + motion.z)) {
mob.navigation.stop()
pokemonEntity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
stop()
stopBanking()
mob.deltaMovement = Vec3(0.0, motion.y, 0.0)
}
}
/*
@@ -387,4 +369,4 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
strafeForwards = 0.0f
strafeRight = 0.0f
}
}
}
@@ -78,10 +78,21 @@ object StatHandler : EventHandler {
fun onWinBattle(event : BattleVictoryEvent) {
if (!event.wasWildCapture) {
if(event.battle.isPvP) {
event.winners
.flatMap { it.getPlayerUUIDs().mapNotNull(UUID::getPlayer) }
.forEach { player -> player.awardStat(getStat(Cobblemon.statistics.PVP_BATTLES_WON)) }
event.losers
.flatMap { it.getPlayerUUIDs().mapNotNull(UUID::getPlayer) }
.forEach { player -> player.awardStat(getStat(Cobblemon.statistics.PVP_BATTLES_LOST)) }
}
if (event.battle.isPvW) {
event.winners
.flatMap { it.getPlayerUUIDs().mapNotNull(UUID::getPlayer) }
.forEach { player -> player.awardStat(getStat(Cobblemon.statistics.BATTLES_WON)) }
event.losers
.flatMap { it.getPlayerUUIDs().mapNotNull(UUID::getPlayer) }
.forEach { player -> player.awardStat(getStat(Cobblemon.statistics.BATTLES_LOST)) }
}
}
}
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.client.data
import com.cobblemon.mod.common.api.badges.Badge
import com.cobblemon.mod.common.api.badges.Badges
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readIdentifier
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeIdentifier
import com.cobblemon.mod.common.util.writeString
import net.minecraft.network.RegistryFriendlyByteBuf
class BadgeRegistrySyncPacket(badges: List<Badge>) : DataRegistrySyncPacket<Badge, BadgeRegistrySyncPacket>(badges) {
companion object {
val ID = cobblemonResource("badges")
fun decode(buffer: RegistryFriendlyByteBuf) = BadgeRegistrySyncPacket(emptyList()).apply { decodeBuffer(buffer) }
}
override val id = ID
override fun encodeEntry(buffer: RegistryFriendlyByteBuf, entry: Badge) {
buffer.writeIdentifier(entry.identifier)
buffer.writeString(entry.name)
buffer.writeResourceLocation(entry.texture)
buffer.writeInt(entry.order)
}
override fun decodeEntry(buffer: RegistryFriendlyByteBuf): Badge {
return Badge(
order = buffer.readInt(),
texture = buffer.readResourceLocation(),
name = buffer.readString(),
identifier = buffer.readIdentifier()
)
}
override fun synchronizeDecoded(entries: Collection<Badge>) {
Badges.reload(entries.associateBy { it.identifier })
}
}
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.net.messages.client.pasture
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.pasture.PasturePermissions
import com.cobblemon.mod.common.api.pasture.PastureRange
import com.cobblemon.mod.common.client.net.pasture.OpenPastureHandler
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.net.IntSize
@@ -20,7 +19,6 @@ import net.minecraft.world.item.ItemStack
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.core.Direction
/**
* Opens a pasture GUI using the provided data.
@@ -30,7 +28,7 @@ import net.minecraft.core.Direction
* @author Hiroku
* @since April 9th, 2023
*/
class OpenPasturePacket(val pcId: UUID, val pastureId: UUID, val limit: Int, val tetheredPokemon: List<PasturePokemonDataDTO>, val permissions: PasturePermissions, val range: PastureRange, val facing: Direction) : NetworkPacket<OpenPasturePacket> {
class OpenPasturePacket(val pcId: UUID, val pastureId: UUID, val limit: Int, val tetheredPokemon: List<PasturePokemonDataDTO>, val permissions: PasturePermissions) : NetworkPacket<OpenPasturePacket> {
class PasturePokemonDataDTO(
val pokemonId: UUID,
val playerId: UUID,
@@ -41,9 +39,7 @@ class OpenPasturePacket(val pcId: UUID, val pastureId: UUID, val limit: Int, val
val heldItem: ItemStack,
val level: Int,
val entityKnown: Boolean,
var behaviourFlags: Set<PokemonBehaviourFlag> = emptySet(),
var healingAfterFaint: Boolean = false,
var respawnAt: Long = 0L
var behaviourFlags: Set<PokemonBehaviourFlag> = emptySet()
) {
companion object {
fun decode(buffer: RegistryFriendlyByteBuf): PasturePokemonDataDTO {
@@ -57,8 +53,6 @@ class OpenPasturePacket(val pcId: UUID, val pastureId: UUID, val limit: Int, val
val level = buffer.readSizedInt(IntSize.U_SHORT)
val entityKnown = buffer.readBoolean()
val behaviourFlags = buffer.readList { it.readEnumConstant(PokemonBehaviourFlag::class.java) }.toSet()
val healingAfterFaint = buffer.readBoolean()
val respawnAt = buffer.readLong()
return PasturePokemonDataDTO(
pokemonId = pokemonId,
@@ -70,9 +64,7 @@ class OpenPasturePacket(val pcId: UUID, val pastureId: UUID, val limit: Int, val
heldItem = heldItem,
level = level,
entityKnown = entityKnown,
behaviourFlags = behaviourFlags,
healingAfterFaint = healingAfterFaint,
respawnAt = respawnAt
behaviourFlags = behaviourFlags
)
}
}
@@ -88,8 +80,6 @@ class OpenPasturePacket(val pcId: UUID, val pastureId: UUID, val limit: Int, val
buffer.writeSizedInt(IntSize.U_SHORT, level)
buffer.writeBoolean(entityKnown)
buffer.writeCollection(behaviourFlags) { _, flag -> buffer.writeEnumConstant(flag) }
buffer.writeBoolean(healingAfterFaint)
buffer.writeLong(respawnAt)
}
}
@@ -105,7 +95,7 @@ class OpenPasturePacket(val pcId: UUID, val pastureId: UUID, val limit: Int, val
dtos.add(PasturePokemonDataDTO.decode(buffer))
}
val permissions = PasturePermissions.decode(buffer)
return OpenPasturePacket(pcId, pastureId, limit, dtos, permissions, PastureRange.decode(buffer), buffer.readEnum(Direction::class.java))
return OpenPasturePacket(pcId, pastureId, limit, dtos, permissions)
}
}
@@ -120,7 +110,5 @@ class OpenPasturePacket(val pcId: UUID, val pastureId: UUID, val limit: Int, val
tethered.encode(buffer)
}
permissions.encode(buffer)
range.encode(buffer)
buffer.writeEnum(facing)
}
}
@@ -1,31 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.client.pasture
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.pasture.PastureRange
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
import java.util.UUID
class UpdatePastureRangePacket(val pastureId: UUID, val range: PastureRange) : NetworkPacket<UpdatePastureRangePacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeUUID(pastureId)
range.encode(buffer)
}
companion object {
val ID = cobblemonResource("update_pasture_range")
fun decode(buffer: RegistryFriendlyByteBuf) = UpdatePastureRangePacket(
buffer.readUUID(), PastureRange.decode(buffer)
)
}
}
@@ -1,29 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.client.pasture
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import java.util.UUID
import net.minecraft.network.RegistryFriendlyByteBuf
data class UpdatePastureRecoveryPacket(val pokemonId: UUID, val fainted: Boolean, val respawnAt: Long) : NetworkPacket<UpdatePastureRecoveryPacket> {
override val id = ID
companion object {
val ID = cobblemonResource("update_pasture_recovery")
fun decode(buffer: RegistryFriendlyByteBuf) = UpdatePastureRecoveryPacket(buffer.readUUID(), buffer.readBoolean(), buffer.readLong())
}
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeUUID(pokemonId)
buffer.writeBoolean(fainted)
buffer.writeLong(respawnAt)
}
}
@@ -1,32 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.server.pasture
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.core.Direction
import net.minecraft.network.RegistryFriendlyByteBuf
import java.util.UUID
class AdjustPastureRangePacket(val pastureId: UUID, val direction: Direction, val increase: Boolean) : NetworkPacket<AdjustPastureRangePacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeUUID(pastureId)
buffer.writeEnum(direction)
buffer.writeBoolean(increase)
}
companion object {
val ID = cobblemonResource("adjust_pasture_range")
fun decode(buffer: RegistryFriendlyByteBuf) = AdjustPastureRangePacket(
buffer.readUUID(), buffer.readEnum(Direction::class.java), buffer.readBoolean()
)
}
}
@@ -8,12 +8,9 @@
package com.cobblemon.mod.common.net.serverhandling.battle
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.HealItemActionResponse
import com.cobblemon.mod.common.battles.MoveActionResponse
import com.cobblemon.mod.common.exception.IllegalActionChoiceException
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleQueueRequestPacket
@@ -28,27 +25,6 @@ object BattleSelectActionsHandler : ServerNetworkPacketHandler<BattleSelectActio
if (!actor.mustChoose) {
return
}
// Battle command injection attempt detection
packet.showdownActionResponses.asSequence()
.flatMap {
when (it) {
is MoveActionResponse -> sequenceOf(it.moveName, it.targetPnx, it.gimmickID)
is HealItemActionResponse -> sequenceOf(it.item)
else -> emptySequence()
}
}
.filterNotNull()
.firstOrNull { field -> field.any(Char::isISOControl) }
?.let {
// Escapes control chars as \uXXXX to prevent forging logs.
val printable = it.replace(Regex("\\p{Cc}")) { "\\u%04x".format(it.value[0].code) }
Cobblemon.LOGGER.warn("Blocked battle command injection from ${player.name.string}: $printable")
actor.sendUpdate(BattleQueueRequestPacket(actor.request!!))
actor.sendUpdate(BattleMakeChoicePacket())
return
}
try {
actor.setActionResponses(packet.showdownActionResponses)
} catch (e: IllegalActionChoiceException) {
@@ -1,39 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.serverhandling.pasture
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.api.pasture.PastureLinkManager
import com.cobblemon.mod.common.api.pasture.PastureRange
import com.cobblemon.mod.common.block.entity.PokemonPastureBlockEntity
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureRangePacket
import com.cobblemon.mod.common.net.messages.server.pasture.AdjustPastureRangePacket
import net.minecraft.core.Direction
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
object AdjustPastureRangeHandler : ServerNetworkPacketHandler<AdjustPastureRangePacket> {
override fun handle(packet: AdjustPastureRangePacket, server: MinecraftServer, player: ServerPlayer) {
val link = PastureLinkManager.getLinkByPlayer(player) ?: return
if (link.linkId != packet.pastureId || packet.direction.axis == Direction.Axis.Y) return
val pasture = player.serverLevel().getBlockEntity(link.pos) as? PokemonPastureBlockEntity ?: return
val range = pasture.getRange().adjusted(packet.direction, if (packet.increase) 1 else -1)
if (!range.isValid(PastureRange.MAX_DISTANCE)) {
UpdatePastureRangePacket(link.linkId, pasture.getRange()).sendToPlayer(player)
return
}
pasture.setRange(range)
for (viewer in player.serverLevel().players()) {
val viewerLink = PastureLinkManager.getLinkByPlayer(viewer) ?: continue
if (viewerLink.pos == link.pos && viewerLink.dimension == link.dimension) {
UpdatePastureRangePacket(viewerLink.linkId, range).sendToPlayer(viewer)
}
}
}
}
@@ -9,18 +9,24 @@
package com.cobblemon.mod.common.net.serverhandling.pasture
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.api.pasture.PastureLinkManager
import com.cobblemon.mod.common.block.entity.PokemonPastureBlockEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.server.pasture.SetPastureConflictPacket
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureConflictFlagPacket
object SetPastureConflictHandler : ServerNetworkPacketHandler<SetPastureConflictPacket> {
override fun handle(packet: SetPastureConflictPacket, server: MinecraftServer, player: ServerPlayer) {
val link = PastureLinkManager.getLinkByPlayer(player) ?: return
val pasture = player.level().getBlockEntity(link.pos) as? PokemonPastureBlockEntity ?: return
val tethering = pasture.tetheredPokemon.firstOrNull { it.pokemonId == packet.pokemonId && it.playerId == player.uuid } ?: return
pasture.setConflict(tethering, packet.enabled)
val world = player.level()
val entity = world.getEntitiesOfClass(PokemonEntity::class.java, player.boundingBox.inflate(64.0))
.firstOrNull { it.pokemon.uuid == packet.pokemonId }
?: return
entity.setBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT, packet.enabled)
entity.remakeBrain()
// Send packet from the server to client GUI to update the toggle button
UpdatePastureConflictFlagPacket(packet.pokemonId, packet.enabled).sendToPlayer(player)
}
}
}
@@ -135,18 +135,17 @@ internal object PropertiesCompletionProvider : DataRegistry {
private fun addDefaults() {
this.inject(setOf("level", "lvl", "l"), setOf("1", "${Cobblemon.config.maxPokemonLevel}") )
this.inject(setOf("shiny", "s"), setOf("yes", "no"))
this.inject(setOf("alpha", "is_alpha"), setOf("yes", "no"))
this.inject(setOf("gender"), Gender.entries.map { it.name.lowercase() })
this.inject(setOf("friendship"), setOf("0", Cobblemon.config.maxPokemonFriendship.toString()))
this.inject(setOf("fullness"), setOf("0", "255"))
this.inject(setOf("pokeball"), PokeBalls.all().map { it.name.simplify() })
this.inject(setOf("nature"), Natures.all().map { it.name.simplify() })
this.inject(setOf("ability"), Abilities.all().map { if (it.name.asIdentifierDefaultingNamespace().namespace == Cobblemon.MODID) it.name.asIdentifierDefaultingNamespace().path else it.name })
this.inject(setOf("dmax_level", "dmax"), setOf("0", Cobblemon.config.maxDynamaxLevel.toString()))
this.inject(setOf("gmax_factor", "gmax"), setOf("yes", "no"))
this.inject(setOf("dmax"), setOf("0", Cobblemon.config.maxDynamaxLevel.toString()))
this.inject(setOf("gmax"), setOf("yes", "no"))
this.inject(setOf("type", "elemental_type"), ElementalTypes.all().map { it.name })
this.inject(setOf("tera_type", "tera"), TeraTypes.map { it.id.simplify() })
this.inject(setOf("tradeable", "tradable"), setOf("yes", "no"))
this.inject(setOf("tradeable"), setOf("yes", "no"))
this.inject(setOf("originaltrainer", "ot"), setOf(""))
this.inject(setOf("originaltrainertype", "ottype"), setOf("None", "Player", "NPC"))
this.inject(setOf("nickname", "nick"), setOf(""))
@@ -26,6 +26,5 @@ object CobblemonGameRules {
val SHINY_STARTERS: GameRules.Key<GameRules.BooleanValue> = Cobblemon.implementation.registerGameRule("doShinyStarters", GameRules.Category.MISC, BooleanRuleInvoker.`cobblemon$create`(false))
@JvmField
val HEALERS_HEAL_PC: GameRules.Key<GameRules.BooleanValue> = Cobblemon.implementation.registerGameRule("healersHealPC", GameRules.Category.MISC, BooleanRuleInvoker.`cobblemon$create`(false))
@JvmField
val DO_POKEMON_PREDATION: GameRules.Key<GameRules.BooleanValue> = Cobblemon.implementation.registerGameRule("doPokemonPredation", GameRules.Category.MOBS, BooleanRuleInvoker.`cobblemon$create`(true))
}
@@ -1,90 +0,0 @@
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "cobblemon:watershuriken_actor",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/moves/watershuriken"
}
},
"curves": {
"variable.rsize": {
"type": "linear",
"input": "v.particle_age",
"horizontal_range": "v.particle_lifetime",
"nodes": [0, 1, 1, 1, 1, 1, 1, 1]
}
},
"events": {
"trail": {
"particle_effect": {
"effect": "cobblemon:watershuriken_actortrail",
"type": "particle"
}
},
"hit": {
"particle_effect": {
"effect": "cobblemon:watershuriken_target",
"type": "particle"
}
}
},
"components": {
"minecraft:emitter_initialization": {
"creation_expression": "v.target_deltaz=5;"
},
"minecraft:emitter_rate_instant": {
"num_particles": 1
},
"minecraft:emitter_lifetime_once": {
"active_time": 0.25
},
"minecraft:emitter_shape_disc": {
"offset": [0, 0.02, 0.1],
"radius": 0.5,
"direction": "outwards"
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 0.35
},
"minecraft:particle_lifetime_events": {
"timeline": {
"0.00": "trail",
"0.06": "trail",
"0.12": "trail",
"0.18": "trail",
"0.24": "trail",
"0.30": "trail",
"0.34": "hit"
}
},
"minecraft:particle_initial_spin": {
"rotation": "math.random_integer(0,3)*90"
},
"minecraft:particle_motion_parametric": {
"relative_position": ["-v.target_deltax*(v.particle_age / v.particle_lifetime)", "(v.target_deltay*(v.particle_age / v.particle_lifetime))", "-v.target_deltaz*(v.particle_age / v.particle_lifetime)"]
},
"minecraft:particle_appearance_billboard": {
"size": ["0.4*v.rsize", "0.4*v.rsize"],
"facing_camera_mode": "lookat_direction",
"direction": {
"mode": "custom",
"custom_direction": [0, 0, -1]
},
"uv": {
"texture_width": 60,
"texture_height": 15,
"flipbook": {
"base_UV": [0, 0],
"size_UV": [15, 15],
"step_UV": [15, 0],
"frames_per_second": 24,
"max_frame": 4,
"loop": true
}
}
}
}
}
}
@@ -1,91 +0,0 @@
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "cobblemon:watershuriken_actoralt",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/moves/watershuriken"
}
},
"curves": {
"variable.rsize": {
"type": "linear",
"input": "v.particle_age",
"horizontal_range": "v.particle_lifetime",
"nodes": [0, 1, 1, 1, 1, 1, 1, 1]
}
},
"events": {
"trail": {
"particle_effect": {
"effect": "cobblemon:watershuriken_actortrail",
"type": "particle"
}
},
"hit": {
"particle_effect": {
"effect": "cobblemon:watershuriken_target",
"type": "particle"
}
}
},
"components": {
"minecraft:emitter_initialization": {
"creation_expression": "v.target_deltaz=5;"
},
"minecraft:emitter_rate_steady": {
"spawn_rate": 7,
"max_particles": 3
},
"minecraft:emitter_lifetime_once": {
"active_time": 0.4
},
"minecraft:emitter_shape_disc": {
"offset": [0, 0.02, 0.1],
"radius": 0.5,
"direction": "outwards"
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 0.35
},
"minecraft:particle_lifetime_events": {
"timeline": {
"0.00": "trail",
"0.06": "trail",
"0.12": "trail",
"0.18": "trail",
"0.24": "trail",
"0.30": "trail",
"0.34": "hit"
}
},
"minecraft:particle_initial_spin": {
"rotation": "math.random_integer(0,3)*90"
},
"minecraft:particle_motion_parametric": {
"relative_position": ["-(v.target_deltax+((v.particle_random_1-0.5)*1.33))*(v.particle_age / v.particle_lifetime)", "(v.target_deltay+((v.particle_random_2-0.5)*1.33))*(v.particle_age / v.particle_lifetime)", "-v.target_deltaz*(v.particle_age / v.particle_lifetime)"]
},
"minecraft:particle_appearance_billboard": {
"size": ["0.4*v.rsize", "0.4*v.rsize"],
"facing_camera_mode": "lookat_direction",
"direction": {
"mode": "custom",
"custom_direction": [0, 0, -1]
},
"uv": {
"texture_width": 60,
"texture_height": 15,
"flipbook": {
"base_UV": [0, 0],
"size_UV": [15, 15],
"step_UV": [15, 0],
"frames_per_second": 24,
"max_frame": 4,
"loop": true
}
}
}
}
}
}
@@ -1,86 +0,0 @@
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "cobblemon:watershuriken_actorhor",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/moves/watershuriken"
}
},
"curves": {
"variable.rsize": {
"type": "linear",
"input": "v.particle_age",
"horizontal_range": "v.particle_lifetime",
"nodes": [0, 1, 1, 1, 1, 1, 1, 1]
}
},
"events": {
"trail": {
"particle_effect": {
"effect": "cobblemon:watershuriken_actortrail",
"type": "particle"
}
},
"hit": {
"particle_effect": {
"effect": "cobblemon:watershuriken_target",
"type": "particle"
}
}
},
"components": {
"minecraft:emitter_initialization": {
"creation_expression": "v.target_deltaz=5;"
},
"minecraft:emitter_rate_instant": {
"num_particles": 1
},
"minecraft:emitter_lifetime_once": {
"active_time": 0.25
},
"minecraft:emitter_shape_disc": {
"offset": [0, 0.02, 0.1],
"radius": 0.5,
"direction": "outwards"
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 0.35
},
"minecraft:particle_lifetime_events": {
"timeline": {
"0.00": "trail",
"0.06": "trail",
"0.12": "trail",
"0.18": "trail",
"0.24": "trail",
"0.30": "trail",
"0.34": "hit"
}
},
"minecraft:particle_initial_spin": {
"rotation": "math.random_integer(0,3)*90"
},
"minecraft:particle_motion_parametric": {
"relative_position": ["-v.target_deltax*(v.particle_age / v.particle_lifetime)", "(v.target_deltay*(v.particle_age / v.particle_lifetime))", "-v.target_deltaz*(v.particle_age / v.particle_lifetime)"]
},
"minecraft:particle_appearance_billboard": {
"size": ["0.4*v.rsize", "0.4*v.rsize"],
"facing_camera_mode": "emitter_transform_xz",
"uv": {
"texture_width": 60,
"texture_height": 15,
"flipbook": {
"base_UV": [0, 0],
"size_UV": [15, 15],
"step_UV": [15, 0],
"frames_per_second": 24,
"max_frame": 4,
"loop": true
}
}
}
}
}
}
@@ -1,63 +0,0 @@
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "cobblemon:watershuriken_actortrail",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/generic/impact/impact_water"
}
},
"curves": {
"variable.scale": {
"type": "linear",
"input": "v.particle_age",
"horizontal_range": "v.particle_lifetime",
"nodes": [1, 0]
}
},
"components": {
"minecraft:emitter_rate_instant": {
"num_particles": 1
},
"minecraft:emitter_lifetime_once": {
"active_time": 1
},
"minecraft:emitter_shape_point": {
"direction": ["v.particle_random_1-0.5", "v.particle_random_2-0.25", -3]
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": "math.random(0.3, 0.6)"
},
"minecraft:particle_initial_spin": {
"rotation": "math.round(v.particle_random_4*3)*90"
},
"minecraft:particle_initial_speed": 8,
"minecraft:particle_motion_dynamic": {
"linear_acceleration": [0, -5, 0],
"linear_drag_coefficient": 8
},
"minecraft:particle_appearance_billboard": {
"size": ["0.22 * variable.scale", "0.22 * variable.scale"],
"facing_camera_mode": "rotate_xyz",
"uv": {
"texture_width": 8,
"texture_height": 56,
"flipbook": {
"base_UV": [0, 24],
"size_UV": [8, 8],
"step_UV": [0, 8],
"frames_per_second": 16,
"max_frame": 4
}
}
},
"minecraft:particle_motion_collision": {
"collision_radius": 0.02
},
"minecraft:particle_appearance_tinting": {
"color": [0.61176, 0.87451, 1, 1]
}
}
}
}
@@ -1,86 +0,0 @@
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "cobblemon:watershuriken_actorvert",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/moves/watershuriken"
}
},
"curves": {
"variable.rsize": {
"type": "linear",
"input": "v.particle_age",
"horizontal_range": "v.particle_lifetime",
"nodes": [0, 1, 1, 1, 1, 1, 1, 1]
}
},
"events": {
"trail": {
"particle_effect": {
"effect": "cobblemon:watershuriken_actortrail",
"type": "particle"
}
},
"hit": {
"particle_effect": {
"effect": "cobblemon:watershuriken_target",
"type": "particle"
}
}
},
"components": {
"minecraft:emitter_initialization": {
"creation_expression": "v.target_deltaz=5;"
},
"minecraft:emitter_rate_instant": {
"num_particles": 1
},
"minecraft:emitter_lifetime_once": {
"active_time": 0.25
},
"minecraft:emitter_shape_disc": {
"offset": [0, 0.02, 0.1],
"radius": 0.5,
"direction": "outwards"
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 0.35
},
"minecraft:particle_lifetime_events": {
"timeline": {
"0.00": "trail",
"0.06": "trail",
"0.12": "trail",
"0.18": "trail",
"0.24": "trail",
"0.30": "trail",
"0.34": "hit"
}
},
"minecraft:particle_initial_spin": {
"rotation": "math.random_integer(0,3)*90"
},
"minecraft:particle_motion_parametric": {
"relative_position": ["-v.target_deltax*(v.particle_age / v.particle_lifetime)", "(v.target_deltay*(v.particle_age / v.particle_lifetime))", "-v.target_deltaz*(v.particle_age / v.particle_lifetime)"]
},
"minecraft:particle_appearance_billboard": {
"size": ["0.4*v.rsize", "0.4*v.rsize"],
"facing_camera_mode": "emitter_transform_yz",
"uv": {
"texture_width": 60,
"texture_height": 15,
"flipbook": {
"base_UV": [0, 0],
"size_UV": [15, 15],
"step_UV": [15, 0],
"frames_per_second": 24,
"max_frame": 4,
"loop": true
}
}
}
}
}
}
@@ -1,69 +0,0 @@
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "cobblemon:watershuriken_target",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/generic/impact/impact_water"
}
},
"curves": {
"variable.scale": {
"type": "linear",
"input": "v.particle_age",
"horizontal_range": "v.particle_lifetime",
"nodes": [1, 0]
}
},
"events": {
"event": {
"particle_effect": {
"effect": "cobblemon:watershuriken_targetspark",
"type": "particle"
}
}
},
"components": {
"minecraft:emitter_rate_steady": {
"spawn_rate": 240,
"max_particles": 14
},
"minecraft:emitter_lifetime_once": {
"active_time": 0.1
},
"minecraft:emitter_lifetime_events": {
"creation_event": "event"
},
"minecraft:emitter_shape_point": {},
"minecraft:particle_lifetime_expression": {
"max_lifetime": "math.random(0.3, 0.6)"
},
"minecraft:particle_initial_spin": {
"rotation": "math.round(v.particle_random_4*3)*90"
},
"minecraft:particle_initial_speed": "math.random(9,17)",
"minecraft:particle_motion_dynamic": {
"linear_drag_coefficient": 5
},
"minecraft:particle_appearance_billboard": {
"size": ["0.3 * variable.scale", "0.3 * variable.scale"],
"facing_camera_mode": "rotate_xyz",
"uv": {
"texture_width": 8,
"texture_height": 56,
"flipbook": {
"base_UV": [0, 16],
"size_UV": [8, 8],
"step_UV": [0, 8],
"frames_per_second": 16,
"max_frame": 5
}
}
},
"minecraft:particle_appearance_tinting": {
"color": [0.61176, 0.87451, 1, 1]
}
}
}
}
@@ -1,62 +0,0 @@
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "cobblemon:watershuriken_targetspark",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/vanilla/critical_hit"
}
},
"curves": {
"variable.bitespark": {
"type": "linear",
"input": "v.particle_age",
"horizontal_range": "v.particle_lifetime",
"nodes": [0, 1, 0.88, 0.66, 0.35, -0.05]
}
},
"components": {
"minecraft:emitter_rate_instant": {
"num_particles": 7
},
"minecraft:emitter_lifetime_once": {
"active_time": 1
},
"minecraft:emitter_shape_point": {
"offset": [0, 0, -0.25],
"direction": ["v.particle_random_1-0.5", "v.particle_random_2-0.25", "v.particle_random_3-0.5"]
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": "0.5-(v.particle_random_4*0.1)"
},
"minecraft:particle_initial_speed": "math.random(1.75,3.55)*3",
"minecraft:particle_motion_dynamic": {
"linear_acceleration": [0, -5, 0],
"linear_drag_coefficient": 4
},
"minecraft:particle_appearance_billboard": {
"size": ["0.125*v.bitespark", "0.125*v.bitespark"],
"facing_camera_mode": "rotate_xyz",
"uv": {
"texture_width": 8,
"texture_height": 8,
"uv": [0, 0],
"uv_size": [7, 7]
}
},
"minecraft:particle_motion_collision": {
"collision_radius": 0.01
},
"minecraft:particle_appearance_tinting": {
"color": {
"interpolant": "v.particle_age / v.particle_lifetime",
"gradient": {
"0.0": "#FF51D8FF",
"1.0": "#FF0076FF"
}
}
}
}
}
}
@@ -37,10 +37,10 @@
"rotation": [0, -7.5, 0]
},
"tail2": {
"rotation": ["30-math.sin(q.anim_time*90*2-60)*-3", 0, 0]
"rotation": [30, 0, 0]
},
"tail3": {
"rotation": ["12.5-math.sin(q.anim_time*90*2-120)*-3", 0, 0]
"rotation": [12.5, 0, 0]
},
"leg_right": {
"rotation": [0, 5, 0]
@@ -53,22 +53,6 @@
},
"wings_open": {
"scale": 0
},
"torso": {
"rotation": ["-math.sin(q.anim_time*90*2-90)*1", 0, "math.sin(q.anim_time*90*1-90)*1"],
"position": [0, "math.sin(q.anim_time*90*2)*0.1", 0]
},
"wing_closed_left": {
"rotation": ["-0-math.sin(q.anim_time*90*2-150)*1", 0, 0]
},
"wing_closed_right": {
"rotation": ["-0-math.sin(q.anim_time*90*2-150)*1", 0, 0]
},
"neck": {
"rotation": ["-math.sin(q.anim_time*90*2-180)*1", 0, 0]
},
"head": {
"rotation": ["-math.sin(q.anim_time*90*2-90)*-2", 0, "math.sin(q.anim_time*90*1-60)*-1"]
}
}
},
@@ -127,30 +111,6 @@
}
}
},
"torso": {
"rotation": {
"0.0": {
"post": [0, 0, 0],
"lerp_mode": "catmullrom"
},
"0.3333": {
"post": [5, 0, 0],
"lerp_mode": "catmullrom"
},
"0.5833": {
"post": [2.5, 0, 0],
"lerp_mode": "catmullrom"
},
"0.8333": {
"post": [-5, 0, 0],
"lerp_mode": "catmullrom"
},
"1.1667": {
"post": [0, 0, 0],
"lerp_mode": "catmullrom"
}
}
},
"jaw": {
"rotation": {
"0.0": {
@@ -158,15 +118,19 @@
"lerp_mode": "catmullrom"
},
"0.1667": {
"post": [72.5, 0, 0],
"post": [85, 0, 0],
"lerp_mode": "catmullrom"
},
"0.4167": {
"post": [85, 0, 0],
"lerp_mode": "catmullrom"
},
"0.5833": {
"post": [0, 0, 0],
"lerp_mode": "catmullrom"
},
"0.8333": {
"post": [52.5, 0, 0],
"0.75": {
"post": [85, 0, 0],
"lerp_mode": "catmullrom"
},
"1.1667": {
@@ -174,32 +138,6 @@
"lerp_mode": "catmullrom"
}
}
},
"eyelid_left": {
"position": {
"0.0": [0, 0, 0],
"0.5833": {
"pre": [0, 0, 0],
"post": [0.1, 0, -0.1]
},
"0.75": {
"pre": [0.1, 0, -0.1],
"post": [0, 0, 0]
}
}
},
"eyelid_right": {
"position": {
"0.0": [0, 0, 0],
"0.5833": {
"pre": [0, 0, 0],
"post": [-0.1, 0, -0.1]
},
"0.75": {
"pre": [-0.1, 0, -0.1],
"post": [0, 0, 0]
}
}
}
},
"sound_effects": {
@@ -265,546 +203,6 @@
"position": [-0.1, 0, -0.1]
}
}
},
"animation.spearow.hold_item": {
"loop": true,
"bones": {
"jaw": {
"rotation": [30, 0, 0]
}
}
},
"animation.spearow.ground_walk": {
"loop": true,
"animation_length": 0.75,
"bones": {
"torso": {
"rotation": ["1-math.cos(q.anim_time*90*16/3*2)*-2", "-math.sin(q.anim_time*90*16/3+60)*3", "-1+math.sin(q.anim_time*90*16/3+30)*-4"],
"position": [0, "math.sin(q.anim_time*90*16/3*2)*0.2", 0]
},
"wings_open": {
"scale": 0
},
"wing_closed_left": {
"rotation": [3, 0, 0]
},
"wing_closed_right": {
"rotation": [3, 0, 0]
},
"tail2": {
"rotation": ["22-math.cos(q.anim_time*90*16/3*2)*5", 0, 0]
},
"tail3": {
"rotation": ["5-math.cos(q.anim_time*90*16/3*2)*5", 0, 0]
},
"neck": {
"rotation": {
"0.0": ["-math.cos(q.anim_time*90*16/3*2)*-12.5", "-math.sin(q.anim_time*90*16/3+60)*-3", "math.sin(q.anim_time*90*16/3+30)*3"],
"0.125": ["-math.cos(q.anim_time*90*16/3*2)*-9.5", "-math.sin(q.anim_time*90*16/3+60)*-3", "math.sin(q.anim_time*90*16/3+30)*3"],
"0.1667": ["-math.cos(q.anim_time*90*16/3*2)*-12.5", "-math.sin(q.anim_time*90*16/3+60)*-3", "math.sin(q.anim_time*90*16/3+30)*3"],
"0.375": ["-math.cos(q.anim_time*90*16/3*2)*-12.5", "-math.sin(q.anim_time*90*16/3+60)*-3", "math.sin(q.anim_time*90*16/3+30)*3"],
"0.5": ["-math.cos(q.anim_time*90*16/3*2)*-9.5", "-math.sin(q.anim_time*90*16/3+60)*-3", "math.sin(q.anim_time*90*16/3+30)*3"],
"0.5417": ["-math.cos(q.anim_time*90*16/3*2)*-12.5", "-math.sin(q.anim_time*90*16/3+60)*-3", "math.sin(q.anim_time*90*16/3+30)*3"]
},
"position": {
"0.0": [0, "math.cos(q.anim_time*90*16/3*2)*0.2", 0],
"0.0417": [0, -0.075, 0.025],
"0.0833": [0, "-0.2+math.cos(q.anim_time*90*16/3*2)*0.2", -0.2],
"0.125": [0, -0.22, -0.17],
"0.1667": [0, -0.21, -0.185],
"0.375": [0, "math.cos(q.anim_time*90*16/3*2)*0.2", 0],
"0.4167": [0, -0.075, 0.025],
"0.4583": [0, "-0.2+math.cos(q.anim_time*90*16/3*2)*0.2", -0.2],
"0.5": [0, -0.22, -0.17],
"0.5417": [0, -0.21, -0.185],
"0.75": [0, "math.cos(q.anim_time*90*16/3*2)*0.2", 0]
}
},
"head": {
"rotation": ["5-math.cos(q.anim_time*90*16/3*2)*18", 0, 0],
"position": [0, 0, "-0.2+math.cos(q.anim_time*90*16/3*2)*0.3"]
},
"scruff_left": {
"rotation": [0, "-0-math.cos(q.anim_time*90*16/3*2-30)*7", 0]
},
"scruff_right": {
"rotation": [0, "0+math.cos(q.anim_time*90*16/3*2-30)*7", 0]
},
"scruff_top_right": {
"rotation": ["-0-math.cos(q.anim_time*90*16/3*2-30)*5", 0, 0]
},
"scruff_bottom": {
"rotation": ["-0-math.cos(q.anim_time*90*16/3*2)*-12", 0, 0]
},
"leg_left": {
"rotation": {
"0.0": [25, -17.5, 0],
"0.125": {
"pre": [21.25, -12.5, 0],
"post": [21.25, -12.5, 0],
"lerp_mode": "catmullrom"
},
"0.375": [-40, -5, 0],
"0.75": [25, -17.5, 0]
},
"position": {
"0.0": [0.3, 0.1, 0.6],
"0.0417": [0.23, 0.25, 0.71],
"0.125": {
"pre": [0, 0.68, -0.08],
"post": [0, 0.68, -0.08],
"lerp_mode": "catmullrom"
},
"0.25": {
"post": [0, 0.97, -1.41],
"lerp_mode": "catmullrom"
},
"0.375": [0, -0.5, -2.15],
"0.5": [0, -0.19, -1.02],
"0.5833": [0, -0.07, -0.3],
"0.625": [0, -0.02, 0.21],
"0.7083": [0.1, 0.215, 0.43],
"0.75": [0.3, 0.1, 0.6]
}
},
"leg_left2": {
"rotation": {
"0.0": [0, 0, 0],
"0.125": {
"pre": [-30.83, 0, 0],
"post": [-30.83, 0, 0],
"lerp_mode": "catmullrom"
},
"0.25": {
"post": [-2.65, 0, 0],
"lerp_mode": "catmullrom"
},
"0.375": [12.5, 0, 0],
"0.75": [0, 0, 0]
}
},
"foot_left": {
"rotation": {
"0.0": [27.5, 0, -5],
"0.125": {
"pre": [20.17, 0, 0],
"post": [20.17, 0, 0],
"lerp_mode": "catmullrom"
},
"0.25": {
"post": [12.596, 0.6637, -1.3486],
"lerp_mode": "catmullrom"
},
"0.375": [28, 0, 0],
"0.625": [-7.33, 0, 0],
"0.75": [27.5, 0, -5]
},
"position": {
"0.0": [0, 0.25, 0],
"0.125": [0, 0, 0],
"0.375": [0, 0, 0],
"0.625": [0, 0.17, 0],
"0.75": [0, 0.25, 0]
}
},
"toe_back_left": {
"rotation": {
"0.0": [-20, 0, 0],
"0.2083": {
"pre": [-8.33, 0, 0],
"post": [-8.33, 0, 0],
"lerp_mode": "catmullrom"
},
"0.375": [0, 0, 0],
"0.625": [0, 0, 0],
"0.75": [-20, 0, 0]
}
},
"toe_inner_left": {
"rotation": {
"0.0": [-55, 10, -7.5],
"0.125": {
"pre": [7.5, 15, 0],
"post": [7.5, 15, 0],
"lerp_mode": "catmullrom"
},
"0.25": {
"post": [5, 15, 0],
"lerp_mode": "catmullrom"
},
"0.375": [0, 15, 0],
"0.625": [0, 15, 0],
"0.75": [-55, 10, -7.5]
},
"position": {
"0.0": [0, -0.75, 1.35],
"0.125": [0, 0, 0],
"0.625": [0, 0, 0],
"0.75": [0, -0.75, 1.35]
}
},
"toe_outer_left": {
"rotation": {
"0.0": [-51.5, -2.5, 10],
"0.125": {
"pre": [5, -15, 0],
"post": [5, -15, 0],
"lerp_mode": "catmullrom"
},
"0.25": {
"post": [5, -15, 0],
"lerp_mode": "catmullrom"
},
"0.375": [0, -15, 0],
"0.625": [0, -15, 0],
"0.75": [-51.5, -2.5, 10]
},
"position": {
"0.0": [0, -0.75, 1.35],
"0.125": [0, 0, 0],
"0.625": [0, 0, 0],
"0.75": [0, -0.75, 1.35]
}
},
"leg_right": {
"rotation": {
"0.0": [-40, 5, 0],
"0.375": [25, 17.5, 0],
"0.5": {
"pre": [21.25, 12.5, 0],
"post": [21.25, 12.5, 0],
"lerp_mode": "catmullrom"
},
"0.75": [-40, 5, 0]
},
"position": {
"0.0": [0, -0.5, -2.15],
"0.125": [0, -0.19, -1.02],
"0.2083": [0, -0.07, -0.3],
"0.25": [0, -0.02, 0.21],
"0.3333": [-0.1, 0.215, 0.43],
"0.375": [-0.3, 0.1, 0.6],
"0.4167": [-0.23, 0.25, 0.71],
"0.5": {
"pre": [0, 0.68, -0.08],
"post": [0, 0.68, -0.08],
"lerp_mode": "catmullrom"
},
"0.625": {
"post": [0, 0.97, -1.41],
"lerp_mode": "catmullrom"
},
"0.75": [0, -0.5, -2.15]
}
},
"leg_right2": {
"rotation": {
"0.0": [12.5, 0, 0],
"0.375": [0, 0, 0],
"0.5": {
"pre": [-30.83, 0, 0],
"post": [-30.83, 0, 0],
"lerp_mode": "catmullrom"
},
"0.625": {
"post": [-2.65, 0, 0],
"lerp_mode": "catmullrom"
},
"0.75": [12.5, 0, 0]
}
},
"foot_right": {
"rotation": {
"0.0": [28, 0, 0],
"0.25": [-7.33, 0, 0],
"0.375": [27.5, 0, 5],
"0.5": {
"pre": [20.17, 0, 0],
"post": [20.17, 0, 0],
"lerp_mode": "catmullrom"
},
"0.625": {
"post": [12.596, -0.6637, 1.3486],
"lerp_mode": "catmullrom"
},
"0.75": [28, 0, 0]
},
"position": {
"0.0": [0, 0, 0],
"0.25": [0, 0.17, 0],
"0.375": [0, 0.25, 0],
"0.5": [0, 0, 0],
"0.75": [0, 0, 0]
}
},
"toe_back_right": {
"rotation": {
"0.0": [0, 0, 0],
"0.25": [0, 0, 0],
"0.375": [-20, 0, 0],
"0.5833": {
"pre": [-8.33, 0, 0],
"post": [-8.33, 0, 0],
"lerp_mode": "catmullrom"
},
"0.75": [0, 0, 0]
}
},
"toe_inner_right": {
"rotation": {
"0.0": [0, -15, 0],
"0.25": [0, -15, 0],
"0.375": [-55, -10, 7.5],
"0.5": {
"pre": [7.5, -15, 0],
"post": [7.5, -15, 0],
"lerp_mode": "catmullrom"
},
"0.625": {
"post": [5, -15, 0],
"lerp_mode": "catmullrom"
},
"0.75": [0, -15, 0]
},
"position": {
"0.0": [0, 0, 0],
"0.25": [0, 0, 0],
"0.375": [0, -0.75, 1.35],
"0.5": [0, 0, 0],
"0.75": [0, 0, 0]
}
},
"toe_outer_right": {
"rotation": {
"0.0": [0, 15, 0],
"0.25": [0, 15, 0],
"0.375": [-51.5, 2.5, -10],
"0.5": {
"pre": [5, 15, 0],
"post": [5, 15, 0],
"lerp_mode": "catmullrom"
},
"0.625": {
"post": [5, 15, 0],
"lerp_mode": "catmullrom"
},
"0.75": [0, 15, 0]
},
"position": {
"0.0": [0, 0, 0],
"0.25": [0, 0, 0],
"0.375": [0, -0.75, 1.35],
"0.5": [0, 0, 0],
"0.75": [0, 0, 0]
}
}
}
},
"animation.spearow.air_idle": {
"loop": true,
"animation_length": 4,
"bones": {
"body": {
"position": ["-math.sin(q.anim_time*90*3-45)*0.5", "math.sin(q.anim_time*90*6-45)*1.5", "math.sin(q.anim_time*90*6-45)*1/2"]
},
"torso": {
"rotation": ["-math.sin(q.anim_time*90*6-120)*-4", 0, "math.sin(q.anim_time*90*3-90)*4"]
},
"wing_open_left": {
"rotation": ["-math.sin(q.anim_time*90*6-90)*6", 0, "math.sin(q.anim_time*90*6)*65"],
"position": [0, "math.sin(q.anim_time*90*6)*-1", 0]
},
"wing_open_left2": {
"rotation": [0, 0, "20+math.sin(q.anim_time*90*6-45)*30"]
},
"wing_open_left3": {
"rotation": [0, 0, "0+math.sin(q.anim_time*90*6-75)*30"]
},
"wing_open_right": {
"rotation": ["-math.sin(q.anim_time*90*6-90)*6", 0, "-math.sin(q.anim_time*90*6)*65"],
"position": [0, "math.sin(q.anim_time*90*6)*-1", 0]
},
"wing_open_right2": {
"rotation": [0, 0, "-20-math.sin(q.anim_time*90*6-45)*30"]
},
"wing_open_right3": {
"rotation": [0, 0, "-0-math.sin(q.anim_time*90*6-75)*30"]
},
"tail2": {
"rotation": ["-math.sin(q.anim_time*90*6-30)*5", 0, 0]
},
"tail3": {
"rotation": ["-math.sin(q.anim_time*90*6-90)*5", 0, 0]
},
"neck": {
"rotation": ["5-math.sin(q.anim_time*90*6-140)*-1", 0, "math.sin(q.anim_time*90*3-00)*-1"]
},
"head": {
"rotation": ["-math.sin(q.anim_time*90*6-110)*3", 0, "math.sin(q.anim_time*90*3-120)*-2"]
},
"scruff_left": {
"rotation": [0, "-0-math.sin(q.anim_time*90*6-170)*4", 0]
},
"scruff_right": {
"rotation": [0, "0+math.sin(q.anim_time*90*6-170)*4", 0]
},
"scruff_top_right": {
"rotation": ["-0-math.sin(q.anim_time*90*6-210)*6", 0, 0]
},
"scruff_bottom": {
"rotation": ["-0-math.sin(q.anim_time*90*6-170)*3", 0, 0]
},
"leg_left": {
"rotation": [50, 0, 0]
},
"leg_left2": {
"rotation": [-100, 0, 0]
},
"foot_left": {
"rotation": ["40-math.sin(q.anim_time*90*6-30)*-5", -10, "math.sin(q.anim_time*90*3-150)*6"]
},
"toe_back_left": {
"rotation": [-27.5, 0, 0]
},
"toe_inner_left": {
"rotation": [37.5, 0, 0]
},
"toe_outer_left": {
"rotation": [37.5, 0, 0]
},
"leg_right": {
"rotation": [50, 0, 0]
},
"leg_right2": {
"rotation": [-100, 0, 0]
},
"foot_right": {
"rotation": ["40-math.sin(q.anim_time*90*6-30)*-5", 10, "-math.sin(q.anim_time*90*3-150)*6"]
},
"toe_back_right": {
"rotation": [-27.5, 0, 0]
},
"toe_inner_right": {
"rotation": [37.5, 0, 0]
},
"toe_outer_right": {
"rotation": [37.5, 0, 0]
}
},
"timeline": {
"0.6667": "q.has_entity?{q.sound('animation.plumage.wing_flap.small');};",
"1.3333": "q.has_entity?{q.sound('animation.plumage.wing_flap.small');};",
"2.0": "q.has_entity?{q.sound('animation.plumage.wing_flap.small');};",
"2.6667": "q.has_entity?{q.sound('animation.plumage.wing_flap.small');};",
"3.3333": "q.has_entity?{q.sound('animation.plumage.wing_flap.small');};",
"4.0": "q.has_entity?{q.sound('animation.plumage.wing_flap.small');};"
}
},
"animation.spearow.air_fly": {
"loop": true,
"animation_length": 4,
"bones": {
"body": {
"position": ["-math.sin(q.anim_time*90*3-45)*0.5", "math.sin(q.anim_time*90*6-45)*1.5", "math.sin(q.anim_time*90*6-45)*1/2"]
},
"torso": {
"rotation": ["15-math.sin(q.anim_time*90*6-120)*-1.5", 0, "math.sin(q.anim_time*90*3-90)*4"]
},
"wing_open_left": {
"rotation": ["-math.sin(q.anim_time*90*6-90)*6", 0, "math.sin(q.anim_time*90*6)*65"],
"position": [0, "math.sin(q.anim_time*90*6)*-1", 0]
},
"wing_open_left2": {
"rotation": [0, 0, "20+math.sin(q.anim_time*90*6-45)*30"]
},
"wing_open_left3": {
"rotation": [0, 0, "0+math.sin(q.anim_time*90*6-75)*30"]
},
"wing_open_right": {
"rotation": ["-math.sin(q.anim_time*90*6-90)*6", 0, "-math.sin(q.anim_time*90*6)*65"],
"position": [0, "math.sin(q.anim_time*90*6)*-1", 0]
},
"wing_open_right2": {
"rotation": [0, 0, "-20-math.sin(q.anim_time*90*6-45)*30"]
},
"wing_open_right3": {
"rotation": [0, 0, "-0-math.sin(q.anim_time*90*6-75)*30"]
},
"tail": {
"rotation": [-10, 0, 0]
},
"tail2": {
"rotation": ["-math.sin(q.anim_time*90*6-30)*5", 0, 0]
},
"tail3": {
"rotation": ["-math.sin(q.anim_time*90*6-90)*5", 0, 0]
},
"neck": {
"rotation": ["5-math.sin(q.anim_time*90*6-140)*-1", 0, "math.sin(q.anim_time*90*3-00)*-1"]
},
"head_ai": {
"rotation": [5, 0, 0]
},
"head": {
"rotation": ["-7.5-math.sin(q.anim_time*90*6-110)*3", 0, "math.sin(q.anim_time*90*3-120)*-2"]
},
"scruff_left": {
"rotation": [0, "-0-math.sin(q.anim_time*90*6-170)*4", 0]
},
"scruff_right": {
"rotation": [0, "0+math.sin(q.anim_time*90*6-170)*4", 0]
},
"scruff_top_right": {
"rotation": ["-0-math.sin(q.anim_time*90*6-210)*6", 0, 0]
},
"scruff_bottom": {
"rotation": ["-0-math.sin(q.anim_time*90*6-170)*3", 0, 0]
},
"leg_left": {
"rotation": [50, 0, 0]
},
"leg_left2": {
"rotation": [-100, 0, 0]
},
"foot_left": {
"rotation": ["40-math.sin(q.anim_time*90*6-30)*-5", -10, "math.sin(q.anim_time*90*3-150)*6"]
},
"toe_back_left": {
"rotation": [-27.5, 0, 0]
},
"toe_inner_left": {
"rotation": [37.5, 0, 0]
},
"toe_outer_left": {
"rotation": [37.5, 0, 0]
},
"leg_right": {
"rotation": [50, 0, 0]
},
"leg_right2": {
"rotation": [-100, 0, 0]
},
"foot_right": {
"rotation": ["40-math.sin(q.anim_time*90*6-30)*-5", 10, "-math.sin(q.anim_time*90*3-150)*6"]
},
"toe_back_right": {
"rotation": [-27.5, 0, 0]
},
"toe_inner_right": {
"rotation": [37.5, 0, 0]
},
"toe_outer_right": {
"rotation": [37.5, 0, 0]
}
},
"timeline": {
"0.6667": "q.has_entity?{q.sound('animation.plumage.wing_flap.small');};",
"1.3333": "q.has_entity?{q.sound('animation.plumage.wing_flap.small');};",
"2.0": "q.has_entity?{q.sound('animation.plumage.wing_flap.small');};",
"2.6667": "q.has_entity?{q.sound('animation.plumage.wing_flap.small');};",
"3.3333": "q.has_entity?{q.sound('animation.plumage.wing_flap.small');};",
"4.0": "q.has_entity?{q.sound('animation.plumage.wing_flap.small');};"
}
}
}
}
Loaded 100 of 712 files, more files were not shown because too many files have changed in this diff. Show more