mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-21 06:36:25 -04:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fa8484876 | ||
|
|
aafdb7688a | ||
|
|
73b53f9702 | ||
|
|
33317a57c7 | ||
|
|
72d8f26cdb | ||
|
|
aea1afe145 | ||
|
|
0fec45b7ea | ||
|
|
af98b823aa | ||
|
|
07d9da2ca9 | ||
|
|
c2c86b2ef1 | ||
|
|
22733e7117 |
@@ -0,0 +1,16 @@
|
||||
### Additions
|
||||
|
||||
### Changes
|
||||
- Wild Pokémon now reliably have at least one offensive move, if they can have offensive moves at that level.
|
||||
|
||||
### Fixes
|
||||
|
||||
### Developer
|
||||
|
||||
### Molang & Datapacks
|
||||
- Added `moveset_builders` datapack folder for different styles of movesets.
|
||||
- Added `party_pools` datapack folder for defining groups of Pokémon for the purposes of complex NPC party definitions.
|
||||
- Added `party_compositions` datapack folder for arranging parties on NPCs that rely on party pools.
|
||||
- Added `composed_pool` NPC party provider type which combines a party pool with a party composition to create more complex NPC parties.
|
||||
|
||||
### Particles
|
||||
@@ -11,9 +11,6 @@
|
||||
- Fixed honey from Saccharine Leaves being harvestable at less than the max age.
|
||||
- Items given from interacting with Saccharine Leaves are placed in the active hotbar slot if possible.
|
||||
- Fix all Pokémon being saved to chunks and never despawning.
|
||||
- Fixed PokeSnacks crashing if there was no available spawn.
|
||||
- Fixed Pokémon spawn influences not being specific to regional forms
|
||||
- Fixed crashing when viewing another player in spectator mode
|
||||
|
||||
### Developer
|
||||
|
||||
|
||||
@@ -54,8 +54,6 @@ dependencies {
|
||||
// Flywheel has no common dep so just pick one and don't use any platform specific code in common
|
||||
// modCompileOnly(libs.flywheelFabric)
|
||||
|
||||
modCompileOnly(files("temp/vivecraft-1.21.1-1.3.4.jar")) // TODO: Switch this to a proper dependency when Vivecraft gets a maven repo
|
||||
|
||||
// Showdown
|
||||
modCompileOnly(libs.graal)
|
||||
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ public class AbstractClientPlayerMixin {
|
||||
cir.setReturnValue(usageContext.getFovMultiplier());
|
||||
} else {
|
||||
// Only modify fov through riding if the pokedex is not open
|
||||
AbstractClientPlayer player = (AbstractClientPlayer) (Object) this;
|
||||
LocalPlayer player = (LocalPlayer) (Object) this;
|
||||
// Only modify fov for the driver and not the passengers
|
||||
if (player != null && player.isPassenger() && player.getVehicle() instanceof PokemonEntity && player.getVehicle().getControllingPassenger() == player) {
|
||||
PokemonEntity ride = (PokemonEntity) player.getVehicle();
|
||||
|
||||
@@ -42,8 +42,8 @@ import com.cobblemon.mod.common.api.scheduling.ServerRealTimeTaskTracker
|
||||
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
|
||||
import com.cobblemon.mod.common.api.spawning.BestSpawner
|
||||
import com.cobblemon.mod.common.api.spawning.CobblemonSpawningZoneGenerator
|
||||
import com.cobblemon.mod.common.api.spawning.SpawningZoneGenerator
|
||||
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePositionResolver
|
||||
import com.cobblemon.mod.common.api.spawning.SpawningZoneGenerator
|
||||
import com.cobblemon.mod.common.api.starter.StarterHandler
|
||||
import com.cobblemon.mod.common.api.stats.CobblemonStats
|
||||
import com.cobblemon.mod.common.api.storage.PokemonStoreManager
|
||||
@@ -76,8 +76,12 @@ import com.cobblemon.mod.common.config.LastChangedVersion
|
||||
import com.cobblemon.mod.common.config.constraint.IntConstraint
|
||||
import com.cobblemon.mod.common.config.starter.StarterConfig
|
||||
import com.cobblemon.mod.common.data.CobblemonDataProvider
|
||||
import com.cobblemon.mod.common.events.*
|
||||
import com.cobblemon.mod.common.integration.vivecraft.CobblemonVR
|
||||
import com.cobblemon.mod.common.events.AdvancementHandler
|
||||
import com.cobblemon.mod.common.events.EntityCallbackHandler
|
||||
import com.cobblemon.mod.common.events.CallbackHandler
|
||||
import com.cobblemon.mod.common.events.PokedexHandler
|
||||
import com.cobblemon.mod.common.events.ServerTickHandler
|
||||
import com.cobblemon.mod.common.events.StatHandler
|
||||
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
|
||||
import com.cobblemon.mod.common.permission.LaxPermissionValidator
|
||||
import com.cobblemon.mod.common.platform.events.PlatformEvents
|
||||
@@ -131,12 +135,10 @@ object Cobblemon {
|
||||
const val MODID = CobblemonBuildDetails.MOD_ID
|
||||
const val VERSION = CobblemonBuildDetails.VERSION
|
||||
const val CONFIG_PATH = "config/$MODID/main.json"
|
||||
|
||||
@JvmField
|
||||
val LOGGER: Logger = LogManager.getLogger()
|
||||
|
||||
lateinit var implementation: CobblemonImplementation
|
||||
|
||||
@Deprecated("This field is now a config value", ReplaceWith("Cobblemon.config.captureCalculator"))
|
||||
var captureCalculator: CaptureCalculator
|
||||
get() = this.config.captureCalculator
|
||||
@@ -166,49 +168,21 @@ object Cobblemon {
|
||||
val serverPlayerStructs = mutableMapOf<UUID, ObjectValue<Player>>()
|
||||
|
||||
val statistics: CobblemonStats = CobblemonStats
|
||||
var cobblemonVR: CobblemonVR? = null
|
||||
|
||||
@JvmStatic
|
||||
val builtinPacks = listOf<CobblemonPack>(
|
||||
CobblemonPack(
|
||||
id = "adorncompatibility",
|
||||
name = "Adorn Compatibility",
|
||||
packType = PackType.CLIENT_RESOURCES,
|
||||
activationBehaviour = ResourcePackActivationBehaviour.ALWAYS_ENABLED,
|
||||
neededMods = setOf("adorn")
|
||||
),
|
||||
CobblemonPack(
|
||||
id = "gyaradosjump",
|
||||
name = "Gyarados Jump Patterns",
|
||||
packType = PackType.CLIENT_RESOURCES,
|
||||
activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED
|
||||
),
|
||||
CobblemonPack(
|
||||
id = "regionbiasforms",
|
||||
name = "Region Bias Forms",
|
||||
packType = PackType.CLIENT_RESOURCES,
|
||||
activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED
|
||||
),
|
||||
CobblemonPack(
|
||||
id = "uniqueshinyforms",
|
||||
name = "Shinies for Magikarp Jump",
|
||||
packType = PackType.CLIENT_RESOURCES,
|
||||
activationBehaviour = ResourcePackActivationBehaviour.NORMAL
|
||||
),
|
||||
CobblemonPack(id = "adorncompatibility", name = "Adorn Compatibility", packType = PackType.CLIENT_RESOURCES, activationBehaviour = ResourcePackActivationBehaviour.ALWAYS_ENABLED, neededMods = setOf("adorn")),
|
||||
CobblemonPack(id = "gyaradosjump", name = "Gyarados Jump Patterns", packType = PackType.CLIENT_RESOURCES, activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED),
|
||||
CobblemonPack(id = "regionbiasforms", name = "Region Bias Forms", packType = PackType.CLIENT_RESOURCES, activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED),
|
||||
CobblemonPack(id = "uniqueshinyforms", name = "Shinies for Magikarp Jump", packType = PackType.CLIENT_RESOURCES, activationBehaviour = ResourcePackActivationBehaviour.NORMAL),
|
||||
|
||||
CobblemonPack(
|
||||
id = "repurposedstructurescobblemon",
|
||||
name = "Repurposed Structures Cobblemon",
|
||||
packType = PackType.SERVER_DATA,
|
||||
activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED,
|
||||
neededMods = setOf("repurposed_structures")
|
||||
),
|
||||
CobblemonPack(id = "repurposedstructurescobblemon", name = "Repurposed Structures Cobblemon", packType = PackType.SERVER_DATA, activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED, neededMods = setOf("repurposed_structures")),
|
||||
)
|
||||
|
||||
fun preInitialize(implementation: CobblemonImplementation) {
|
||||
this.implementation = implementation
|
||||
|
||||
this.LOGGER.info("Launching Cobblemon ${CobblemonBuildDetails.VERSION}${if (CobblemonBuildDetails.SNAPSHOT) "-SNAPSHOT" else ""} ")
|
||||
this.LOGGER.info("Launching Cobblemon ${CobblemonBuildDetails.VERSION}${if(CobblemonBuildDetails.SNAPSHOT) "-SNAPSHOT" else ""} ")
|
||||
if (CobblemonBuildDetails.SNAPSHOT) {
|
||||
this.LOGGER.info(" - Git Commit: ${smallCommitHash()} (https://gitlab.com/cable-mc/cobblemon/-/commit/${CobblemonBuildDetails.GIT_COMMIT})")
|
||||
this.LOGGER.info(" - Branch: ${CobblemonBuildDetails.BRANCH}")
|
||||
@@ -272,10 +246,7 @@ object Cobblemon {
|
||||
}
|
||||
|
||||
PlatformEvents.RIGHT_CLICK_ENTITY.subscribe { event ->
|
||||
if (event.player.getItemInHand(event.hand).item is NameTagItem && event.entity.type.`is`(
|
||||
CobblemonEntityTypeTags.CANNOT_HAVE_NAME_TAG
|
||||
)
|
||||
) {
|
||||
if (event.player.getItemInHand(event.hand).item is NameTagItem && event.entity.type.`is`(CobblemonEntityTypeTags.CANNOT_HAVE_NAME_TAG)) {
|
||||
event.cancel()
|
||||
}
|
||||
}
|
||||
@@ -286,10 +257,7 @@ object Cobblemon {
|
||||
pokemon.lockedEvolutions
|
||||
.filterIsInstance<BlockClickEvolution>()
|
||||
.forEach { evolution ->
|
||||
evolution.attemptEvolution(
|
||||
pokemon,
|
||||
BlockClickEvolution.BlockInteractionContext(block, player.level())
|
||||
)
|
||||
evolution.attemptEvolution(pokemon, BlockClickEvolution.BlockInteractionContext(block, player.level()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,11 +317,6 @@ object Cobblemon {
|
||||
SlowpokeTailRegrowthSpeciesFeatureProvider
|
||||
)
|
||||
|
||||
if (implementation.isModInstalled("vivecraft")) { // VR integration
|
||||
cobblemonVR = CobblemonVR()
|
||||
cobblemonVR?.init()
|
||||
}
|
||||
|
||||
CustomPokemonProperty.register(UncatchableProperty)
|
||||
CustomPokemonProperty.register(BattleCloneProperty)
|
||||
CustomPokemonProperty.register(PokemonFlagProperty)
|
||||
@@ -373,7 +336,7 @@ object Cobblemon {
|
||||
}
|
||||
|
||||
PlatformEvents.SERVER_TICK_POST.subscribe {
|
||||
ServerTaskTracker.update(1 / 20F)
|
||||
ServerTaskTracker.update(1/20F)
|
||||
ServerRealTimeTaskTracker.update()
|
||||
}
|
||||
PlatformEvents.SERVER_TICK_PRE.subscribe {
|
||||
@@ -417,22 +380,10 @@ object Cobblemon {
|
||||
.applyConnectionString(ConnectionString(config.mongoDBConnectionString))
|
||||
.build()
|
||||
mongoClient = MongoClients.create(mongoClientSettings)
|
||||
val generalMongoFactory = CachedPlayerDataStoreFactory(
|
||||
PlayerDataMongoBackend(
|
||||
mongoClient,
|
||||
config.mongoDBDatabaseName,
|
||||
"PlayerDataCollection"
|
||||
)
|
||||
)
|
||||
val generalMongoFactory = CachedPlayerDataStoreFactory(PlayerDataMongoBackend(mongoClient, config.mongoDBDatabaseName, "PlayerDataCollection"))
|
||||
generalMongoFactory.setup(server)
|
||||
|
||||
val pokedexMongoFactory = CachedPlayerDataStoreFactory(
|
||||
DexDataMongoBackend(
|
||||
mongoClient,
|
||||
config.mongoDBDatabaseName,
|
||||
"PokeDexCollection"
|
||||
)
|
||||
)
|
||||
val pokedexMongoFactory = CachedPlayerDataStoreFactory(DexDataMongoBackend(mongoClient, config.mongoDBDatabaseName, "PokeDexCollection"))
|
||||
pokedexMongoFactory.setup(server)
|
||||
|
||||
playerDataManager.setFactory(generalMongoFactory, PlayerInstancedDataStoreTypes.GENERAL)
|
||||
@@ -554,7 +505,6 @@ object Cobblemon {
|
||||
field.set(config, field.get(defaultConfig))
|
||||
}
|
||||
}
|
||||
|
||||
is IntConstraint -> {
|
||||
var value = field.get(config)
|
||||
if (value is Int) {
|
||||
@@ -607,76 +557,20 @@ object Cobblemon {
|
||||
}
|
||||
|
||||
private fun registerArgumentTypes() {
|
||||
this.implementation.registerCommandArgument(
|
||||
cobblemonResource("pokemon"),
|
||||
SpeciesArgumentType::class,
|
||||
SingletonArgumentInfo.contextFree(SpeciesArgumentType::species)
|
||||
)
|
||||
this.implementation.registerCommandArgument(
|
||||
cobblemonResource("pokemon_properties"),
|
||||
PokemonPropertiesArgumentType::class,
|
||||
SingletonArgumentInfo.contextFree(PokemonPropertiesArgumentType::properties)
|
||||
)
|
||||
this.implementation.registerCommandArgument(
|
||||
cobblemonResource("spawn_bucket"),
|
||||
SpawnBucketArgumentType::class,
|
||||
SingletonArgumentInfo.contextFree(SpawnBucketArgumentType::spawnBucket)
|
||||
)
|
||||
this.implementation.registerCommandArgument(
|
||||
cobblemonResource("move"),
|
||||
MoveArgumentType::class,
|
||||
SingletonArgumentInfo.contextFree(MoveArgumentType::move)
|
||||
)
|
||||
this.implementation.registerCommandArgument(
|
||||
cobblemonResource("party_slot"),
|
||||
PartySlotArgumentType::class,
|
||||
SingletonArgumentInfo.contextFree(PartySlotArgumentType::partySlot)
|
||||
)
|
||||
this.implementation.registerCommandArgument(
|
||||
cobblemonResource("pokemon_store"),
|
||||
PokemonStoreArgumentType::class,
|
||||
SingletonArgumentInfo.contextFree(PokemonStoreArgumentType::pokemonStore)
|
||||
)
|
||||
this.implementation.registerCommandArgument(
|
||||
cobblemonResource("dialogue"),
|
||||
DialogueArgumentType::class,
|
||||
SingletonArgumentInfo.contextFree(DialogueArgumentType::dialogue)
|
||||
)
|
||||
this.implementation.registerCommandArgument(
|
||||
cobblemonResource("form"),
|
||||
FormArgumentType::class,
|
||||
SingletonArgumentInfo.contextFree(FormArgumentType::form)
|
||||
)
|
||||
this.implementation.registerCommandArgument(
|
||||
cobblemonResource("dex"),
|
||||
DexArgumentType::class,
|
||||
SingletonArgumentInfo.contextFree(DexArgumentType::dex)
|
||||
)
|
||||
this.implementation.registerCommandArgument(
|
||||
cobblemonResource("npc_class"),
|
||||
NPCClassArgumentType::class,
|
||||
SingletonArgumentInfo.contextFree(NPCClassArgumentType::npcClass)
|
||||
)
|
||||
this.implementation.registerCommandArgument(
|
||||
cobblemonResource("wallpaper"),
|
||||
UnlockablePCBoxWallpaperArgumentType::class,
|
||||
SingletonArgumentInfo.contextFree(UnlockablePCBoxWallpaperArgumentType::wallpaper)
|
||||
)
|
||||
this.implementation.registerCommandArgument(
|
||||
cobblemonResource("mark"),
|
||||
MarkArgumentType::class,
|
||||
SingletonArgumentInfo.contextFree(MarkArgumentType::mark)
|
||||
)
|
||||
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("pokemon"), SpeciesArgumentType::class, SingletonArgumentInfo.contextFree(SpeciesArgumentType::species))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("pokemon_properties"), PokemonPropertiesArgumentType::class, SingletonArgumentInfo.contextFree(PokemonPropertiesArgumentType::properties))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("spawn_bucket"), SpawnBucketArgumentType::class, SingletonArgumentInfo.contextFree(SpawnBucketArgumentType::spawnBucket))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("move"), MoveArgumentType::class, SingletonArgumentInfo.contextFree(MoveArgumentType::move))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("party_slot"), PartySlotArgumentType::class, SingletonArgumentInfo.contextFree(PartySlotArgumentType::partySlot))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("pokemon_store"), PokemonStoreArgumentType::class, SingletonArgumentInfo.contextFree(PokemonStoreArgumentType::pokemonStore))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("dialogue"), DialogueArgumentType::class, SingletonArgumentInfo.contextFree(DialogueArgumentType::dialogue))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("form"), FormArgumentType::class, SingletonArgumentInfo.contextFree(FormArgumentType::form))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("dex"), DexArgumentType::class, SingletonArgumentInfo.contextFree (DexArgumentType::dex))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("npc_class"), NPCClassArgumentType::class, SingletonArgumentInfo.contextFree(NPCClassArgumentType::npcClass))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("wallpaper"), UnlockablePCBoxWallpaperArgumentType::class, SingletonArgumentInfo.contextFree(UnlockablePCBoxWallpaperArgumentType::wallpaper))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("mark"), MarkArgumentType::class, SingletonArgumentInfo.contextFree(MarkArgumentType::mark))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("transform_type"), TransformTypeArgumentType::class, SingletonArgumentInfo.contextFree(TransformTypeArgumentType::transformType))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("model_part"), ModelPartArgumentType::class, SingletonArgumentInfo.contextFree(ModelPartArgumentType::modelPart))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.moves.MoveSelector
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.MovesetBuilderAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.StringToObjectAdapter
|
||||
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 CobblemonMovesetBuilders : JsonDataRegistry<MovesetBuilder> {
|
||||
override val gson: Gson = GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(MovesetBuilder::class.java, MovesetBuilderAdapter)
|
||||
.registerTypeAdapter(MoveSelector::class.java, StringToObjectAdapter(MoveSelector.selectors))
|
||||
.create()
|
||||
|
||||
override val typeToken = TypeToken.get(MovesetBuilder::class.java)
|
||||
override val resourcePath = "moveset_builders"
|
||||
override val id: ResourceLocation = cobblemonResource("moveset_builders")
|
||||
override val type = PackType.SERVER_DATA
|
||||
override val observable = SimpleObservable<CobblemonMovesetBuilders>()
|
||||
|
||||
val movesetBuilders = mutableMapOf<ResourceLocation, MovesetBuilder>()
|
||||
|
||||
fun getOrThrow(id: ResourceLocation): MovesetBuilder {
|
||||
return movesetBuilders[id]
|
||||
?: throw IllegalArgumentException("Unknown MovesetBuilder id: $id")
|
||||
}
|
||||
|
||||
override fun sync(player: ServerPlayer) { /* These don't sync. But what if they did? Nah probably shouldn't sync. */ }
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, MovesetBuilder>) {
|
||||
movesetBuilders.clear()
|
||||
data.forEach { (id, value) -> value.id = id }
|
||||
movesetBuilders.putAll(data)
|
||||
observable.emit(this)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.npc.PartyComposition
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
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 CobblemonPartyCompositions : JsonDataRegistry<PartyComposition> {
|
||||
override val id: ResourceLocation = cobblemonResource("party_compositions")
|
||||
override val type = PackType.SERVER_DATA
|
||||
override val typeToken: TypeToken<PartyComposition> = TypeToken.get(PartyComposition::class.java)
|
||||
override val resourcePath: String = "party_compositions"
|
||||
override val observable = SimpleObservable<CobblemonPartyCompositions>()
|
||||
override val gson: Gson = GsonBuilder()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
|
||||
val partyCompositions = mutableMapOf<ResourceLocation, PartyComposition>()
|
||||
|
||||
override fun sync(player: ServerPlayer) {
|
||||
// probably worth syncing which exist, yeah. the deeper details probably not. I'll get to this eventually.
|
||||
}
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, PartyComposition>) {
|
||||
data.entries.forEach { it.value.id = it.key }
|
||||
partyCompositions.clear()
|
||||
partyCompositions.putAll(data)
|
||||
observable.emit(this)
|
||||
Cobblemon.LOGGER.info("Loaded ${data.size} party compositions.")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.npc.PartyPool
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.util.ScriptableIntRange
|
||||
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.IntRangeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.MovesetBuilderReferenceAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.PokemonPropertiesAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.ScriptableIntRangeAdapter
|
||||
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 CobblemonPartyPools : JsonDataRegistry<PartyPool> {
|
||||
override val id: ResourceLocation = cobblemonResource("party_pools")
|
||||
override val type = PackType.SERVER_DATA
|
||||
override val typeToken: TypeToken<PartyPool> = TypeToken.get(PartyPool::class.java)
|
||||
override val resourcePath: String = "party_pools"
|
||||
override val observable = SimpleObservable<CobblemonPartyPools>()
|
||||
override val gson: Gson = GsonBuilder()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(PokemonProperties::class.java, PokemonPropertiesAdapter(saveLong = false))
|
||||
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
|
||||
.registerTypeAdapter(ScriptableIntRange::class.java, ScriptableIntRangeAdapter)
|
||||
.registerTypeAdapter(MovesetBuilder::class.java, MovesetBuilderReferenceAdapter)
|
||||
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
|
||||
val partyPools = mutableMapOf<ResourceLocation, PartyPool>()
|
||||
|
||||
override fun sync(player: ServerPlayer) {
|
||||
// probably worth syncing. Knowing the details might help exploit the server? lot of effort for little though.
|
||||
}
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, PartyPool>) {
|
||||
data.entries.forEach { it.value.id = it.key }
|
||||
partyPools.clear()
|
||||
partyPools.putAll(data)
|
||||
observable.emit(this)
|
||||
}
|
||||
}
|
||||
@@ -1824,8 +1824,7 @@ object MoLangFunctions {
|
||||
return@put DoubleValue(if (pokemon.addRideBoost(stat, value)) 1.0 else 0.0)
|
||||
}
|
||||
map.put("initialize_moveset") { params ->
|
||||
val preferLatest = params.getBooleanOrNull(0) ?: true
|
||||
pokemon.initializeMoveset(preferLatest)
|
||||
pokemon.initializeMovesetFromDefault()
|
||||
return@put DoubleValue.ONE
|
||||
}
|
||||
map.put("validate_moveset") { params ->
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.moves
|
||||
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
/**
|
||||
* This is all you need tbh. A moveset builder that picks moves from up to 4 slots of move selectors.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 5th, 2025
|
||||
*/
|
||||
class DefaultMovesetBuilder : MovesetBuilder {
|
||||
companion object {
|
||||
val TYPE = cobblemonResource("default")
|
||||
}
|
||||
|
||||
override val type: ResourceLocation = TYPE
|
||||
override var id: ResourceLocation = cobblemonResource("temp") // replaced on load.
|
||||
|
||||
val slot1: List<MoveSelector> = listOf()
|
||||
val slot2: List<MoveSelector> = listOf()
|
||||
val slot3: List<MoveSelector> = listOf()
|
||||
val slot4: List<MoveSelector> = listOf()
|
||||
|
||||
override fun build(form: FormData, level: Int): MoveSet {
|
||||
val chosenMoves = mutableSetOf<MoveTemplate>()
|
||||
tryAddMoveFromSlot(form, level, slot1, chosenMoves)
|
||||
tryAddMoveFromSlot(form, level, slot2, chosenMoves)
|
||||
tryAddMoveFromSlot(form, level, slot3, chosenMoves)
|
||||
tryAddMoveFromSlot(form, level, slot4, chosenMoves)
|
||||
if (chosenMoves.isEmpty()) {
|
||||
chosenMoves.add(Moves.getExceptional())
|
||||
}
|
||||
val moveset = MoveSet()
|
||||
chosenMoves.map(MoveTemplate::create).forEach(moveset::add)
|
||||
return moveset
|
||||
}
|
||||
|
||||
fun tryAddMoveFromSlot(form: FormData, level: Int, selectors: List<MoveSelector>, chosenMoves: MutableSet<MoveTemplate>) {
|
||||
for (selector in selectors) {
|
||||
val move = selector(form, form.moves, level, chosenMoves)
|
||||
if (move != null) {
|
||||
chosenMoves.add(move)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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.moves
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
|
||||
import com.cobblemon.mod.common.api.pokemon.moves.Learnset
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.util.weightedSelection
|
||||
|
||||
/**
|
||||
* A function that selects a move from a given [Learnset] for a particular [FormData] at a certain level,
|
||||
* taking into account any moves that have already been chosen for the moveset.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 5th, 2025
|
||||
*/
|
||||
fun interface MoveSelector {
|
||||
operator fun invoke(form: FormData, learnset: Learnset, level: Int, chosenMoves: Set<MoveTemplate>): MoveTemplate?
|
||||
|
||||
companion object {
|
||||
val NONE = MoveSelector { _, _, _, _ -> null }
|
||||
val LAST_LEVELUP = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val levels = learnset.levelUpMoves.keys.filter { it <= level }.sortedDescending()
|
||||
levels.forEach {
|
||||
val moves = (learnset.levelUpMoves[it] ?: return@forEach) - chosenMoves
|
||||
if (moves.isNotEmpty()) {
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
}
|
||||
return@MoveSelector null
|
||||
}
|
||||
val LAST_OFFENSIVE = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val levels = learnset.levelUpMoves.keys.filter { it <= level }.sortedDescending()
|
||||
levels.forEach {
|
||||
val moves = (learnset.levelUpMoves[it]?.filter { it.damageCategory != DamageCategories.STATUS } ?: return@forEach) - chosenMoves
|
||||
if (moves.isNotEmpty()) {
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
}
|
||||
return@MoveSelector null
|
||||
}
|
||||
val LAST_SUITABLE_OFFENSIVE = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val levels = learnset.levelUpMoves.keys.filter { it <= level }.sortedDescending()
|
||||
val suitableCategories = if (form.baseStats[Stats.ATTACK]!! > form.baseStats[Stats.SPECIAL_ATTACK]!!) {
|
||||
setOf(DamageCategories.PHYSICAL)
|
||||
} else if (form.baseStats[Stats.SPECIAL_ATTACK]!! > form.baseStats[Stats.ATTACK]!!) {
|
||||
setOf(DamageCategories.SPECIAL)
|
||||
} else {
|
||||
setOf(DamageCategories.PHYSICAL, DamageCategories.SPECIAL)
|
||||
}
|
||||
levels.forEach {
|
||||
val moves = (learnset.levelUpMoves[it]?.filter { it.damageCategory in suitableCategories } ?: return@forEach) - chosenMoves
|
||||
if (moves.isNotEmpty()) {
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
}
|
||||
return@MoveSelector null
|
||||
}
|
||||
val LAST_STATUS = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val levels = learnset.levelUpMoves.keys.filter { it <= level }.sortedDescending()
|
||||
levels.forEach {
|
||||
val moves = (learnset.levelUpMoves[it]?.filter { it.damageCategory == DamageCategories.STATUS } ?: return@forEach) - chosenMoves
|
||||
if (moves.isNotEmpty()) {
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
}
|
||||
return@MoveSelector null
|
||||
}
|
||||
val STAB = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val stabMoves = moves.filter { it.elementalType in form.types && it.damageCategory != DamageCategories.STATUS }
|
||||
return@MoveSelector stabMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val STAB_PHYSICAL = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val stabMoves = moves.filter { it.elementalType in form.types && it.damageCategory == DamageCategories.PHYSICAL }
|
||||
return@MoveSelector stabMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val STAB_SPECIAL = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val stabMoves = moves.filter { it.elementalType in form.types && it.damageCategory == DamageCategories.SPECIAL }
|
||||
return@MoveSelector stabMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val PHYSICAL = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val physicalMoves = moves.filter { it.damageCategory == DamageCategories.PHYSICAL }
|
||||
return@MoveSelector physicalMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val SPECIAL = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val specialMoves = moves.filter { it.damageCategory == DamageCategories.SPECIAL }
|
||||
return@MoveSelector specialMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val OFFENSIVE = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val offensiveMoves = moves.filter { it.damageCategory != DamageCategories.STATUS }
|
||||
return@MoveSelector offensiveMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val RANDOM_LEVELUP = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val STATUS = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val statusMoves = moves.filter { it.damageCategory == DamageCategories.STATUS }
|
||||
return@MoveSelector statusMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val TM = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.tmMoves - chosenMoves - learnset.getLevelUpMovesUpTo(level)
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val STAB_TM = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.tmMoves - chosenMoves - learnset.getLevelUpMovesUpTo(level)
|
||||
val stabMoves = moves.filter { it.elementalType in form.types }
|
||||
return@MoveSelector stabMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val EGG = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.eggMoves - chosenMoves - learnset.getLevelUpMovesUpTo(level)
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
|
||||
val selectors = mutableMapOf(
|
||||
"none" to NONE,
|
||||
"last_levelup" to LAST_LEVELUP,
|
||||
"last_offensive" to LAST_OFFENSIVE,
|
||||
"last_suitable_offensive" to LAST_SUITABLE_OFFENSIVE,
|
||||
"last_status" to LAST_STATUS,
|
||||
"levelup" to RANDOM_LEVELUP,
|
||||
"stab" to STAB,
|
||||
"stab_physical" to STAB_PHYSICAL,
|
||||
"stab_special" to STAB_SPECIAL,
|
||||
"physical" to PHYSICAL,
|
||||
"special" to SPECIAL,
|
||||
"offensive" to OFFENSIVE,
|
||||
"status" to STATUS,
|
||||
"tm" to TM,
|
||||
"stab_tm" to STAB_TM,
|
||||
"egg" to EGG
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,12 @@ import com.bedrockk.molang.runtime.struct.MoStruct
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategory
|
||||
import com.cobblemon.mod.common.api.types.ElementalType
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.battles.MoveTarget
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.util.codec.CodecUtils
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
@@ -53,7 +53,8 @@ open class MoveTemplate(
|
||||
val pp: Int,
|
||||
val priority: Int,
|
||||
val critRatio: Double,
|
||||
val effectChances: Array<Double>
|
||||
val effectChances: Array<Double>,
|
||||
val weight: Float
|
||||
) {
|
||||
val struct: MoStruct by lazy {
|
||||
QueryStruct(hashMapOf())
|
||||
@@ -66,9 +67,9 @@ open class MoveTemplate(
|
||||
.addFunction("pp") { DoubleValue(pp) }
|
||||
.addFunction("priority") { DoubleValue(priority) }
|
||||
.addFunction("crit_ratio") { DoubleValue(critRatio) }
|
||||
.addFunction("weight") { DoubleValue(weight.toDouble()) }
|
||||
}
|
||||
|
||||
|
||||
val displayName: MutableComponent
|
||||
get() = lang("move.$name")
|
||||
val description: MutableComponent
|
||||
@@ -86,7 +87,8 @@ open class MoveTemplate(
|
||||
pp = 5,
|
||||
priority = 0,
|
||||
critRatio = 0.0,
|
||||
effectChances = emptyArray()
|
||||
effectChances = emptyArray(),
|
||||
weight = 0F,
|
||||
)
|
||||
|
||||
companion object {
|
||||
@@ -157,4 +159,12 @@ open class MoveTemplate(
|
||||
}
|
||||
return this.elementalType
|
||||
}
|
||||
|
||||
fun getSelectionWeight(form: FormData): Float {
|
||||
return if (this in form.signatureMoves) {
|
||||
weight * MovesetBuilder.signatureMoveWeightMultiplier
|
||||
} else {
|
||||
weight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,15 @@ package com.cobblemon.mod.common.api.moves
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.data.DataRegistry
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.battles.MoveTarget
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.net.messages.client.data.MovesRegistrySyncPacket
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.fromJson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
@@ -46,6 +46,14 @@ object Moves : DataRegistry {
|
||||
this.idMapping.clear()
|
||||
this.moveScripts.clear()
|
||||
|
||||
val weights = mutableMapOf<String, Number>()
|
||||
manager.getResourceStack(cobblemonResource("move_weights.json")).forEach {
|
||||
it.open().use {
|
||||
val map = GsonBuilder().create().fromJson<Map<String, Number>>(it.reader())
|
||||
weights.putAll(map)
|
||||
}
|
||||
}
|
||||
|
||||
ShowdownService.service.resetRegistryData("move")
|
||||
manager.listResources("moves") { it.path.endsWith(".js") }.forEach { (identifier, resource) ->
|
||||
resource.open().use { stream ->
|
||||
@@ -75,6 +83,7 @@ object Moves : DataRegistry {
|
||||
val priority = jsMove.get("priority").asInt
|
||||
val critRatio = jsMove.get("critRatio")?.asDouble ?: 1.0
|
||||
val effectChances = arrayListOf<Double>()
|
||||
val weight = weights[id]?.toFloat() ?: 50F
|
||||
val secondariesMember = jsMove.get("secondaries")
|
||||
val secondaryMember = jsMove.get("secondary")
|
||||
if (secondariesMember != null && secondariesMember is JsonArray) {
|
||||
@@ -92,7 +101,20 @@ object Moves : DataRegistry {
|
||||
effectChances += secondaryMember.get("chance").asDouble
|
||||
}
|
||||
}
|
||||
val move = MoveTemplate(id, num, elementalType, damageCategory, power, target, accuracy, pp, priority, critRatio, effectChances.toTypedArray())
|
||||
val move = MoveTemplate(
|
||||
name = id,
|
||||
num = num,
|
||||
elementalType = elementalType,
|
||||
damageCategory = damageCategory,
|
||||
power = power,
|
||||
target = target,
|
||||
accuracy = accuracy,
|
||||
pp = pp,
|
||||
priority = priority,
|
||||
critRatio = critRatio,
|
||||
effectChances = effectChances.toTypedArray(),
|
||||
weight = weight
|
||||
)
|
||||
this.register(move)
|
||||
} catch (e: Exception) {
|
||||
Cobblemon.LOGGER.error("Caught exception trying to resolve the move '{}'", id, e)
|
||||
@@ -113,7 +135,7 @@ object Moves : DataRegistry {
|
||||
@JvmStatic
|
||||
fun getByNameOrDummy(name: String) = allMoves[name.lowercase()] ?: MoveTemplate.dummy(name.lowercase())
|
||||
@JvmStatic
|
||||
fun getExceptional() = getByName("tackle") ?: allMoves.values.random()
|
||||
fun getExceptional() = getByName("tackle") ?: allMoves.values.first()
|
||||
@JvmStatic
|
||||
fun count() = allMoves.size
|
||||
@JvmStatic
|
||||
@@ -129,5 +151,4 @@ object Moves : DataRegistry {
|
||||
this.allMoves[move.name] = move
|
||||
this.idMapping[move.num] = move
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.moves
|
||||
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
/**
|
||||
* Builders a moveset for a particular [FormData] and level. Probably only really need [DefaultMovesetBuilder] though.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 5th, 2025
|
||||
*/
|
||||
interface MovesetBuilder {
|
||||
/** The ID of this builder. It's autofilled from the registry loader. */
|
||||
var id: ResourceLocation
|
||||
/** The subtype designation for this moveset builder. Generally unused once read from JSON. */
|
||||
val type: ResourceLocation
|
||||
|
||||
/** Builds a brand new moveset for the [form] and [level]. */
|
||||
fun build(form: FormData, level: Int): MoveSet
|
||||
|
||||
companion object {
|
||||
/** Weight multiplier for moves that are in the form's signatureMoves list. Makes them way more likely to be chosen. */
|
||||
var signatureMoveWeightMultiplier = 5F
|
||||
/** Subtypes of MovesetBuilder. I personally doubt you'll need any other subtypes, but hey. */
|
||||
val movesetBuilderTypes = mutableMapOf<ResourceLocation, Class<out MovesetBuilder>>(
|
||||
cobblemonResource("default") to DefaultMovesetBuilder::class.java
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,14 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.npc
|
||||
|
||||
import com.cobblemon.mod.common.api.npc.partyproviders.ComposedPoolPartyProvider
|
||||
import com.cobblemon.mod.common.api.npc.partyproviders.PoolPartyProvider
|
||||
import com.cobblemon.mod.common.api.npc.partyproviders.ScriptPartyProvider
|
||||
import com.cobblemon.mod.common.api.npc.partyproviders.SimplePartyProvider
|
||||
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.google.gson.JsonElement
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
/**
|
||||
@@ -25,14 +27,15 @@ import net.minecraft.server.level.ServerPlayer
|
||||
*/
|
||||
interface NPCPartyProvider {
|
||||
companion object {
|
||||
val types = mutableMapOf<String, (String) -> NPCPartyProvider>(
|
||||
SimplePartyProvider.TYPE to { SimplePartyProvider() },
|
||||
PoolPartyProvider.TYPE to { PoolPartyProvider() },
|
||||
ScriptPartyProvider.TYPE to { ScriptPartyProvider() }
|
||||
val types = mutableMapOf(
|
||||
SimplePartyProvider.TYPE to ::SimplePartyProvider,
|
||||
PoolPartyProvider.TYPE to ::PoolPartyProvider,
|
||||
ScriptPartyProvider.TYPE to ::ScriptPartyProvider,
|
||||
ComposedPoolPartyProvider.TYPE to ::ComposedPoolPartyProvider
|
||||
)
|
||||
}
|
||||
|
||||
val type: String
|
||||
val type: ResourceLocation
|
||||
val isStatic: Boolean
|
||||
fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer> = emptyList()): NPCPartyStore
|
||||
// Why did I opt for manual JSON loading??? I must have had a reason but I can't remember. Maybe for S2C? Use a codec doofus
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.npc
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* A kind of definition for how to compose a party of Pokémon given a [PartyPool].
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 7th, 2025
|
||||
*/
|
||||
class PartyComposition {
|
||||
var id = cobblemonResource("dummy_composition")
|
||||
// val displayName = "cobblemon.party_composition.$id" // dunno if i will use this yet
|
||||
val scrambleOrder: Boolean = false
|
||||
val slot1 = listOf<String>()
|
||||
val slot2 = listOf<String>()
|
||||
val slot3 = listOf<String>()
|
||||
val slot4 = listOf<String>()
|
||||
val slot5 = listOf<String>()
|
||||
val slot6 = listOf<String>()
|
||||
|
||||
fun compose(pool: PartyPool, level: Int, aspects: Set<String>, desiredPokemonCount: Int, runtime: MoLangRuntime, random: Random = Random.Default): List<Pokemon> {
|
||||
val chosenEntries = mutableListOf<PartyPool.PoolEntry>()
|
||||
val availableEntries = pool.entries.filter { level in it.npcLevels.resolve(runtime) && (it.npcAspects.isEmpty() || it.npcAspects.all(aspects::contains)) }
|
||||
|
||||
for (labelSet in listOf(slot1, slot2, slot3, slot4, slot5, slot6)) {
|
||||
pool.tryChoosingEntry(labelSet, runtime, random, availableEntries, chosenEntries)
|
||||
if (chosenEntries.size >= desiredPokemonCount) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
val finalEntries = if (scrambleOrder) {
|
||||
chosenEntries.shuffled(random)
|
||||
} else {
|
||||
chosenEntries
|
||||
}
|
||||
|
||||
val pokemon = finalEntries.map { entry ->
|
||||
val levelVariation = entry.levelVariation.resolve(runtime)
|
||||
val lvlVariation = if (levelVariation.first == levelVariation.last) {
|
||||
levelVariation.first
|
||||
} else {
|
||||
levelVariation.random(random)
|
||||
}
|
||||
|
||||
val finalLevel = level + lvlVariation
|
||||
val properties = entry.pokemon.copy()
|
||||
properties.level = properties.level ?: finalLevel
|
||||
val pokemon = properties.create()
|
||||
entry.movesetBuilders.randomOrNull()?.let(pokemon::initializeMovesetFrom)
|
||||
pokemon
|
||||
}
|
||||
|
||||
return pokemon
|
||||
}
|
||||
}
|
||||
@@ -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.api.npc
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.util.ScriptableIntRange
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.toProperties
|
||||
import com.cobblemon.mod.common.util.weightedSelection
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* A pool of Pokémon to choose from, typically for generating a party for an NPC. The entries
|
||||
* of the pool work on a weight and label system which is used as part of a [PartyComposition].
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 7th, 2025
|
||||
*/
|
||||
class PartyPool {
|
||||
var id = cobblemonResource("dummy")
|
||||
val displayName = "cobblemon.party_pool.$id"
|
||||
val entries = mutableListOf<PoolEntry>()
|
||||
|
||||
class PoolEntry {
|
||||
val pokemon: PokemonProperties = "pikachu".toProperties()
|
||||
val labels: List<String> = emptyList()
|
||||
val npcAspects: List<String> = emptyList()
|
||||
val required: List<String> = emptyList()
|
||||
val incompatible: List<String> = emptyList()
|
||||
val maxSelectableTimes: ExpressionLike = "6".asExpressionLike()
|
||||
val levelVariation: ScriptableIntRange = ScriptableIntRange(0, 0)
|
||||
val npcLevels: ScriptableIntRange = ScriptableIntRange(1, 100)
|
||||
val movesetBuilders: List<MovesetBuilder> = emptyList()
|
||||
val weight: ExpressionLike = "50".asExpressionLike()
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes through each of the labels in order, trying to find a valid choice. Once it finds one, it skips the later
|
||||
* labels and adds the value to [chosenEntries] and returns. If none of the labels could find a valid entry, nothing is added.
|
||||
*/
|
||||
fun tryChoosingEntry(
|
||||
labels: List<String>,
|
||||
runtime: MoLangRuntime,
|
||||
random: Random,
|
||||
availableEntries: List<PoolEntry>,
|
||||
chosenEntries: MutableList<PoolEntry>,
|
||||
) {
|
||||
for (label in labels) {
|
||||
val entries = availableEntries
|
||||
.filter { label in it.labels || label.lowercase() == "any" }
|
||||
.filter { entry ->
|
||||
// Check required labels
|
||||
entry.required.all { it in labels } &&
|
||||
// Check excluded labels
|
||||
entry.incompatible.none { it in labels } &&
|
||||
// Check max selectable times
|
||||
chosenEntries.count { it == entry } < entry.maxSelectableTimes.resolveFloat(runtime)
|
||||
}
|
||||
|
||||
if (entries.isNotEmpty()) {
|
||||
val selectedEntry = entries.weightedSelection(random) { it.weight.resolveFloat(runtime) } ?: return
|
||||
chosenEntries.add(selectedEntry)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.npc.partyproviders
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.CobblemonPartyCompositions
|
||||
import com.cobblemon.mod.common.CobblemonPartyPools
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.npc.NPCPartyProvider
|
||||
import com.cobblemon.mod.common.api.npc.PartyComposition
|
||||
import com.cobblemon.mod.common.api.npc.PartyPool
|
||||
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.util.asArrayValue
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.resolveInt
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import kotlin.random.Random
|
||||
import kotlin.ranges.random
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
/**
|
||||
* An [NPCPartyProvider] which uses a [PartyComposition] and [PartyPool] to generate a party.
|
||||
*
|
||||
* There are controls for minimum and maximum Pokémon count, as well as whether to use a fixed random seed for the NPC.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 8th, 2025
|
||||
*/
|
||||
class ComposedPoolPartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
val TYPE = cobblemonResource("composed_pool")
|
||||
}
|
||||
|
||||
override val type = TYPE
|
||||
override var isStatic: Boolean = true
|
||||
var useFixedRandom: Boolean = false
|
||||
var minPokemon: Expression = "1".asExpression()
|
||||
var maxPokemon: Expression = "6".asExpression()
|
||||
lateinit var pool: PartyPool
|
||||
lateinit var composition: PartyComposition
|
||||
|
||||
override fun loadFromJSON(json: JsonElement) {
|
||||
json as JsonObject
|
||||
isStatic = json.getAsJsonPrimitive("isStatic")?.asBoolean ?: true
|
||||
useFixedRandom = json.getAsJsonPrimitive("useFixedRandom")?.asBoolean ?: false
|
||||
pool = CobblemonPartyPools.partyPools[json.get("pool").asString.asIdentifierDefaultingNamespace()]
|
||||
?: throw IllegalArgumentException("Unknown PartyPool id: ${json.get("pool").asString}")
|
||||
composition = CobblemonPartyCompositions.partyCompositions[json.get("composition").asString.asIdentifierDefaultingNamespace()]
|
||||
?: throw IllegalArgumentException("Unknown PartyComposition id: ${json.get("composition").asString}")
|
||||
minPokemon = json.getAsJsonPrimitive("minPokemon").asString?.asExpression() ?: "1".asExpression()
|
||||
maxPokemon = json.getAsJsonPrimitive("maxPokemon").asString?.asExpression() ?: "6".asExpression()
|
||||
}
|
||||
|
||||
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
|
||||
val runtime = MoLangRuntime().setup().withQueryValue("npc", npc.struct)
|
||||
val random = if (useFixedRandom) Random(npc.uuid.hashCode()) else Random.Default
|
||||
runtime.withQueryValue("level", DoubleValue(level))
|
||||
runtime.withQueryValue("players", players.asArrayValue { it.asMoLangValue() })
|
||||
if (players.size == 1) {
|
||||
// This is for the convenience, most cases will be pvn one player
|
||||
runtime.withQueryValue("player", players.first().asMoLangValue())
|
||||
}
|
||||
val minPokemon = runtime.resolveInt(this.minPokemon)
|
||||
val maxPokemon = runtime.resolveInt(this.maxPokemon)
|
||||
val desiredPokemonCount = (minPokemon..maxPokemon).random(random)
|
||||
|
||||
val pokemonList = composition.compose(
|
||||
pool = pool,
|
||||
level = level,
|
||||
aspects = npc.aspects,
|
||||
desiredPokemonCount = desiredPokemonCount,
|
||||
runtime = runtime,
|
||||
random = random
|
||||
)
|
||||
|
||||
val partyStore = NPCPartyStore(npc)
|
||||
pokemonList.forEach(partyStore::add)
|
||||
return partyStore
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -48,7 +48,7 @@ import kotlin.random.Random
|
||||
*/
|
||||
class PoolPartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
const val TYPE = "pool"
|
||||
val TYPE = cobblemonResource("pool")
|
||||
}
|
||||
|
||||
@Transient
|
||||
@@ -150,10 +150,10 @@ class PoolPartyProvider : NPCPartyProvider {
|
||||
val instance = selected.pokemon.copy().also { it.level = it.level ?: dictatedLevel ?: randomLevel }.create()
|
||||
party.add(instance)
|
||||
}
|
||||
if (party.none()) {
|
||||
LOGGER.error("${npc} has no Pokemon on Party")
|
||||
}
|
||||
|
||||
if (party.none()) {
|
||||
LOGGER.error("$npc had no Pokémon produced by their party provider")
|
||||
}
|
||||
}
|
||||
|
||||
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
class ScriptPartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
const val TYPE = "script"
|
||||
val TYPE = cobblemonResource("script")
|
||||
}
|
||||
|
||||
override val type = TYPE
|
||||
|
||||
+2
-1
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.util.asArrayValue
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.getBooleanOrNull
|
||||
import com.cobblemon.mod.common.util.toProperties
|
||||
import com.google.gson.JsonElement
|
||||
@@ -29,7 +30,7 @@ import net.minecraft.server.level.ServerPlayer
|
||||
*/
|
||||
class SimplePartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
const val TYPE = "simple"
|
||||
val TYPE = cobblemonResource("simple")
|
||||
}
|
||||
|
||||
@Transient
|
||||
|
||||
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.pokemon
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.Cobblemon.config
|
||||
import com.cobblemon.mod.common.CobblemonMovesetBuilders
|
||||
import com.cobblemon.mod.common.api.abilities.Abilities
|
||||
import com.cobblemon.mod.common.api.abilities.Ability
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
@@ -148,6 +149,13 @@ open class PokemonProperties {
|
||||
props.originalTrainerType = OriginalTrainerType.entries.toList().parsePropertyOfCollection(keyPairs, listOf("originaltrainertype", "ottype"), labelsOptional = true) { it.name.lowercase() }
|
||||
props.originalTrainer = parsePlayerProperty(keyPairs, listOf("originaltrainer", "ot"))
|
||||
props.moves = parseString(keyPairs, listOf("moves"))?.split(",")
|
||||
props.movesetBuilders = parseString(keyPairs, listOf("moveset_builders", "movesetbuilders", "moveset_builder", "movesetbuilder"))?.split(",")?.mapNotNull {
|
||||
try {
|
||||
it.asIdentifierDefaultingNamespace()
|
||||
} catch (_: ResourceLocationException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
props.heldItem = parseString(keyPairs, listOf("helditem", "held_item"))
|
||||
props.minPerfectIVs = parseIntProperty(keyPairs, listOf("min_perfect_ivs"))?.coerceIn(0, Stats.PERMANENT.size)
|
||||
props.scaleModifier = parseFloatProperty(keyPairs, listOf("scale_modifier"))
|
||||
@@ -356,6 +364,7 @@ open class PokemonProperties {
|
||||
var originalTrainerType: OriginalTrainerType? = null
|
||||
var originalTrainer: String? = null // Original Trainer by Username or UUID
|
||||
var moves: List<String>? = null
|
||||
var movesetBuilders: List<ResourceLocation>? = null
|
||||
var heldItem: String? = null
|
||||
var cosmeticItem: String? = null
|
||||
var scaleModifier: Float? = null
|
||||
@@ -462,7 +471,7 @@ open class PokemonProperties {
|
||||
}
|
||||
moves?.let { moves ->
|
||||
if (pokemon.moveSet.getMoves().isEmpty()) {
|
||||
pokemon.initializeMoveset(preferLatest = true)
|
||||
pokemon.initializeMovesetFromDefault()
|
||||
}
|
||||
val moveTemplates = moves.mapNotNull(Moves::getByName).shuffled()
|
||||
val replaceableIndices = (0..3).filterNot { pokemon.moveSet[it]?.template?.let(moveTemplates::contains) == true }.shuffled()
|
||||
@@ -475,6 +484,13 @@ open class PokemonProperties {
|
||||
pokemon.moveSet.update()
|
||||
}
|
||||
}
|
||||
movesetBuilders?.let { movesetBuilders ->
|
||||
val builders = movesetBuilders.mapNotNull { CobblemonMovesetBuilders.movesetBuilders[it] }
|
||||
if (builders.isNotEmpty()) {
|
||||
val movesetBuilder = builders.random()
|
||||
pokemon.initializeMovesetFrom(movesetBuilder)
|
||||
}
|
||||
}
|
||||
heldItem?.let { itemKey ->
|
||||
val server = server() ?: return@let
|
||||
val parser = ItemParser(server.registryAccess())
|
||||
@@ -678,6 +694,7 @@ open class PokemonProperties {
|
||||
originalTrainerType?.let { nbt.putInt(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE, it.ordinal) }
|
||||
originalTrainer?.let { nbt.putString(DataKeys.POKEMON_ORIGINAL_TRAINER, it) }
|
||||
moves?.let { nbt.putString(DataKeys.POKEMON_PROPERTIES_MOVES, it.joinToString(separator = ",")) }
|
||||
movesetBuilders?.let { nbt.putString(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS, it.joinToString(separator = ",") { source -> source.toString() }) }
|
||||
heldItem?.let {nbt.putString(DataKeys.POKEMON_PROPERTIES_HELDITEM, it)}
|
||||
scaleModifier?.let { nbt.putFloat(DataKeys.POKEMON_SCALE_MODIFIER, it) }
|
||||
val custom = ListTag()
|
||||
@@ -712,6 +729,7 @@ open class PokemonProperties {
|
||||
originalTrainerType = if (tag.contains(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE)) OriginalTrainerType.valueOf(tag.getString(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE)) else null
|
||||
originalTrainer = if (tag.contains(DataKeys.POKEMON_ORIGINAL_TRAINER)) tag.getString(DataKeys.POKEMON_ORIGINAL_TRAINER) else null
|
||||
moves = if (tag.contains(DataKeys.POKEMON_PROPERTIES_MOVES)) tag.getString(DataKeys.POKEMON_PROPERTIES_MOVES).split(",") else null
|
||||
movesetBuilders = if (tag.contains(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS)) tag.getString(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS).split(",").map { it.asIdentifierDefaultingNamespace() } else null
|
||||
heldItem = if (tag.contains(DataKeys.POKEMON_PROPERTIES_HELDITEM)) tag.getString(DataKeys.POKEMON_PROPERTIES_HELDITEM) else null
|
||||
scaleModifier = if (tag.contains(DataKeys.POKEMON_SCALE_MODIFIER)) tag.getFloat(DataKeys.POKEMON_SCALE_MODIFIER) else null
|
||||
val custom = tag.getList(DataKeys.POKEMON_PROPERTIES_CUSTOM, Tag.TAG_STRING.toInt())
|
||||
@@ -747,6 +765,7 @@ open class PokemonProperties {
|
||||
originalTrainerType?.let { json.addProperty(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE, it.name) }
|
||||
originalTrainer?.let { json.addProperty(DataKeys.POKEMON_ORIGINAL_TRAINER, it) }
|
||||
moves?.let { json.addProperty(DataKeys.POKEMON_PROPERTIES_MOVES, it.joinToString(separator = ",")) }
|
||||
movesetBuilders?.let { json.addProperty(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS, it.joinToString(separator = ",") { source -> source.toString() }) }
|
||||
heldItem?.let {json.addProperty(DataKeys.POKEMON_PROPERTIES_HELDITEM, it)}
|
||||
scaleModifier?.let { json.addProperty(DataKeys.POKEMON_SCALE_MODIFIER, it) }
|
||||
val custom = JsonArray()
|
||||
@@ -781,6 +800,7 @@ open class PokemonProperties {
|
||||
originalTrainerType = json.get(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE)?.asString?.let { OriginalTrainerType.valueOf(it) }
|
||||
originalTrainer = json.get(DataKeys.POKEMON_ORIGINAL_TRAINER)?.asString
|
||||
moves = json.get(DataKeys.POKEMON_PROPERTIES_MOVES)?.asString?.split(",")
|
||||
movesetBuilders = json.get(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS)?.asString?.split(",")?.map { it.asIdentifierDefaultingNamespace() }
|
||||
heldItem = json.get(DataKeys.POKEMON_PROPERTIES_HELDITEM)?.asString
|
||||
scaleModifier = json.get(DataKeys.POKEMON_SCALE_MODIFIER)?.asFloat
|
||||
val custom = json.get(DataKeys.POKEMON_PROPERTIES_CUSTOM)?.asJsonArray
|
||||
@@ -820,6 +840,7 @@ open class PokemonProperties {
|
||||
originalTrainer?.let { pieces.add("originaltrainer=$it") }
|
||||
customProperties.forEach { pieces.add(it.asString()) }
|
||||
moves?.let { pieces.add("moves=${it.joinToString(separator = ",")}") }
|
||||
movesetBuilders?.let { pieces.add("movesetbuilders=${it.joinToString(separator = ",") { source -> source.toString() }}") }
|
||||
heldItem?.let {pieces.add("helditem=$it")}
|
||||
scaleModifier?.let { pieces.add("scale_modifier=$it") }
|
||||
return pieces.joinToString(separator)
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.cobblemon.mod.common.api.drop.ItemDropMethod
|
||||
import com.cobblemon.mod.common.api.entity.EntityDimensionsAdapter
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies.getByIdentifier
|
||||
@@ -131,6 +132,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
|
||||
.registerTypeAdapter(RidingBehaviourSettings::class.java, RidingBehaviourSettingsAdapter)
|
||||
.registerTypeAdapter(RideSoundSettingsList::class.java, RideSoundSettingsListAdapter)
|
||||
.registerTypeAdapter(ObtainableItemCondition::class.java, ObtainableItemConditionAdapter)
|
||||
.registerTypeAdapter(MovesetBuilder::class.java, MovesetBuilderReferenceAdapter)
|
||||
.disableHtmlEscaping()
|
||||
.enableComplexMapKeySerialization()
|
||||
.create()
|
||||
|
||||
@@ -44,7 +44,11 @@ abstract class SpawnAction<R>(
|
||||
|
||||
spawnablePosition.applyInfluences { it.affectAction(this) }
|
||||
val result = run()
|
||||
future.complete(result)
|
||||
if (result != null) {
|
||||
future.complete(result)
|
||||
} else {
|
||||
future.completeExceptionally(Exception("Nothing was spawned."))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
+3
-10
@@ -61,9 +61,7 @@ open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (
|
||||
val baitEVStat = effects.firstOrNull { it.type == Effects.EV }?.subcategory?.path?.let { Stats.getStat(it) }
|
||||
|
||||
if (detailSpecies != null && baitEVStat != null) {
|
||||
val detailAspects = detail.pokemon.aspects
|
||||
val formData = detailSpecies.getForm(detailAspects)
|
||||
val evYieldValue = formData.evYield[baitEVStat]?.toFloat() ?: 0f
|
||||
val evYieldValue = detailSpecies.evYield[baitEVStat]?.toFloat() ?: 0f
|
||||
return when {
|
||||
evYieldValue > 0 -> {
|
||||
markUsed()
|
||||
@@ -85,9 +83,7 @@ open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (
|
||||
val baitTypingEffect = baitEffect?.subcategory?.path?.let { ElementalTypes.get(it) }
|
||||
|
||||
if (detailSpecies != null && baitTypingEffect != null) {
|
||||
val detailAspects = detail.pokemon.aspects
|
||||
val formData = detailSpecies.getForm(detailAspects)
|
||||
val isMatchingType = formData.types.contains(baitTypingEffect)
|
||||
val isMatchingType = detailSpecies.types.contains(baitTypingEffect)
|
||||
return when {
|
||||
isMatchingType -> {
|
||||
markUsed()
|
||||
@@ -104,9 +100,6 @@ open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (
|
||||
val detailSpecies = detail.pokemon.species?.let { PokemonSpecies.getByName(it) }
|
||||
|
||||
if (detailSpecies != null) {
|
||||
val detailAspects = detail.pokemon.aspects
|
||||
val formData = detailSpecies.getForm(detailAspects)
|
||||
|
||||
// Collect all the egg group effects
|
||||
val eggGroupEffects = effects.filter { it.type == Effects.EGG_GROUP }
|
||||
|
||||
@@ -118,7 +111,7 @@ open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (
|
||||
LOGGER.warn("Unknown egg group identifier: $effectEggGroupKey")
|
||||
return@firstOrNull false
|
||||
}
|
||||
formData.eggGroups.contains(eggGroup)
|
||||
detailSpecies.eggGroups.contains(eggGroup)
|
||||
}
|
||||
|
||||
if (matchingEffect != null) {
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
|
||||
package com.cobblemon.mod.common.client
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.Cobblemon.LOGGER
|
||||
import com.cobblemon.mod.common.Cobblemon.cobblemonVR
|
||||
import com.cobblemon.mod.common.CobblemonBlockEntities
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.cobblemon.mod.common.CobblemonClientImplementation
|
||||
@@ -29,7 +27,6 @@ import com.cobblemon.mod.common.client.gui.PartyOverlayDataControl
|
||||
import com.cobblemon.mod.common.client.gui.RideControlsOverlay
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
|
||||
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotScreen
|
||||
import com.cobblemon.mod.common.client.integration.vivecraft.CobblemonVRClient
|
||||
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
|
||||
import com.cobblemon.mod.common.client.render.ClientPlayerIcon
|
||||
import com.cobblemon.mod.common.client.render.DeferredRenderer
|
||||
@@ -75,7 +72,6 @@ import com.cobblemon.mod.common.client.tooltips.TooltipManager
|
||||
import com.cobblemon.mod.common.client.trade.ClientTrade
|
||||
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.integration.vivecraft.CobblemonVR
|
||||
import com.cobblemon.mod.common.platform.events.PlatformEvents
|
||||
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext
|
||||
import com.cobblemon.mod.common.util.isLookingAt
|
||||
@@ -156,10 +152,6 @@ object CobblemonClient {
|
||||
this.registerTooltipManagers()
|
||||
this.registerMenuScreens()
|
||||
|
||||
if (Cobblemon.implementation.isModInstalled("vivecraft")) { // VR integration
|
||||
CobblemonVRClient.init()
|
||||
}
|
||||
|
||||
LOGGER.info("Registering custom BuiltinItemRenderers")
|
||||
CobblemonBuiltinItemRendererRegistry.register(CobblemonItems.POKEMON_MODEL, PokemonItemRenderer())
|
||||
|
||||
@@ -195,9 +187,7 @@ object CobblemonClient {
|
||||
|
||||
nearbyPokemon?.forEach { entity ->
|
||||
if (entity is PokemonEntity && !entity.isSilent && !entity.passengers.contains(player)) {
|
||||
if (player.isLookingAt(entity) && !player.isSpectator && entity.pokemon.shiny) entity.delegate.spawnShinyParticle(
|
||||
player
|
||||
)
|
||||
if (player.isLookingAt(entity) && !player.isSpectator && entity.pokemon.shiny) entity.delegate.spawnShinyParticle(player)
|
||||
entity.delegate.spawnAspectParticle()
|
||||
}
|
||||
}
|
||||
@@ -379,10 +369,7 @@ object CobblemonClient {
|
||||
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.DISPLAY_CASE, ::DisplayCaseRenderer)
|
||||
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.LECTERN, ::LecternBlockEntityRenderer)
|
||||
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.CAMPFIRE, ::CampfireBlockEntityRenderer)
|
||||
this.implementation.registerBlockEntityRenderer(
|
||||
CobblemonBlockEntities.POKE_SNACK,
|
||||
::PokeSnackBlockEntityRenderer
|
||||
)
|
||||
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.POKE_SNACK, ::PokeSnackBlockEntityRenderer)
|
||||
}
|
||||
|
||||
private fun registerEntityRenderers() {
|
||||
@@ -411,11 +398,7 @@ object CobblemonClient {
|
||||
|
||||
private fun registerItemColors() {
|
||||
implementation.registerItemColors(AprijuiceItemColorProvider, *CobblemonItems.aprijuices.toTypedArray())
|
||||
implementation.registerItemColors(
|
||||
PokeSnackItemColorProvider,
|
||||
CobblemonItems.POKE_SNACK,
|
||||
CobblemonItems.POKE_CAKE
|
||||
)
|
||||
implementation.registerItemColors(PokeSnackItemColorProvider, CobblemonItems.POKE_SNACK, CobblemonItems.POKE_CAKE)
|
||||
implementation.registerItemColors(PokeBaitItemColorProvider, CobblemonItems.POKE_BAIT)
|
||||
implementation.registerItemColors(PonigiriItemColorProvider, CobblemonItems.PONIGIRI)
|
||||
implementation.registerItemColors(SinisterTeaItemColorProvider, CobblemonItems.SINISTER_TEA)
|
||||
|
||||
+2
-1
@@ -138,7 +138,8 @@ abstract class BattleGimmickButton(gimmick: ShowdownMoveset.Gimmick, val x: Floa
|
||||
pp = gimmickTemplate?.pp ?: moveTemplate.pp,
|
||||
priority = gimmickTemplate?.priority ?: moveTemplate.priority,
|
||||
critRatio = gimmickTemplate?.critRatio ?: moveTemplate.critRatio,
|
||||
effectChances = gimmickTemplate?.effectChances ?: moveTemplate.effectChances
|
||||
effectChances = gimmickTemplate?.effectChances ?: moveTemplate.effectChances,
|
||||
weight = gimmickTemplate?.weight ?: moveTemplate.weight
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
package com.cobblemon.mod.common.client.integration.vivecraft
|
||||
|
||||
import com.cobblemon.mod.common.client.integration.vivecraft.party.PartyOnArm
|
||||
import com.cobblemon.mod.common.client.integration.vivecraft.proximity.ProximityManager
|
||||
import com.cobblemon.mod.common.client.integration.vivecraft.proximity.ProximityRegistration
|
||||
import com.cobblemon.mod.common.client.integration.vivecraft.listener.proximity.PokeRodBaitListener
|
||||
import org.vivecraft.api.data.VRBodyPart
|
||||
|
||||
class CobblemonVRClient {
|
||||
|
||||
companion object {
|
||||
fun init() {
|
||||
PartyOnArm.init()
|
||||
ProximityManager.init()
|
||||
ProximityManager.register(
|
||||
ProximityRegistration(
|
||||
bodyA = VRBodyPart.MAIN_HAND,
|
||||
bodyB = VRBodyPart.OFF_HAND,
|
||||
distanceThreshold = 0.25f,
|
||||
angleThreshold = 120f,
|
||||
listener = PokeRodBaitListener("Main-OffHand")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
package com.cobblemon.mod.common.client.integration.vivecraft.listener.proximity
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
|
||||
import com.cobblemon.mod.common.client.integration.vivecraft.proximity.ProximityListener
|
||||
import com.cobblemon.mod.common.client.integration.vivecraft.proximity.ProximityRegistration
|
||||
import com.cobblemon.mod.common.net.messages.server.pokerod.ApplyBaitPacket
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import org.vivecraft.api.data.VRBodyPart
|
||||
import org.vivecraft.api.data.VRPose
|
||||
|
||||
class PokeRodBaitListener(private val name: String) : ProximityListener {
|
||||
//TODO: Also add that if 1 is Rod the other needs to be seasoning
|
||||
override fun isActive(registration: ProximityRegistration): Boolean {
|
||||
val player = Minecraft.getInstance().player ?: return false
|
||||
val handA: InteractionHand = VRBodyPart.toInteractionHand(registration.bodyA) ?: return false
|
||||
val itemStackA: ItemStack = player.getItemInHand(handA)
|
||||
|
||||
val handB: InteractionHand = VRBodyPart.toInteractionHand(registration.bodyB) ?: return false
|
||||
val itemStackB: ItemStack = player.getItemInHand(handB)
|
||||
|
||||
return itemStackA.`is`(CobblemonItemTags.POKE_RODS) || itemStackB.`is`(CobblemonItemTags.POKE_RODS)
|
||||
}
|
||||
|
||||
override fun onEnter(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose) {
|
||||
Cobblemon.LOGGER.info("[$name] Enter: $bodyA <-> $bodyB")
|
||||
}
|
||||
|
||||
override fun onExit(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose) {
|
||||
Cobblemon.LOGGER.info("[$name] Exit: $bodyA <-> $bodyB")
|
||||
}
|
||||
|
||||
override fun onTick(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose) {
|
||||
Cobblemon.LOGGER.info("[$name] Proximity: $bodyA <-> $bodyB")
|
||||
CobblemonNetwork.sendToServer(ApplyBaitPacket())
|
||||
}
|
||||
}
|
||||
-237
@@ -1,237 +0,0 @@
|
||||
package com.cobblemon.mod.common.client.integration.vivecraft.listener.swing
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.vivecraft.api.client.VRClientAPI
|
||||
import org.vivecraft.api.client.tracker.swing.SwingContext
|
||||
import org.vivecraft.api.client.tracker.swing.SwingListener
|
||||
import org.vivecraft.api.data.VRBodyPart
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* Listener that classifies directional swings (up, down, forward, backward, left, right)
|
||||
* using Vivecraft VR pose history and invokes handler methods for each direction.
|
||||
*
|
||||
* @param forwardDotThreshold Dot product threshold used to classify forward/back/left/right directions.
|
||||
* @param historyTicks Number of ticks used to compute velocity from historical poses.
|
||||
*/
|
||||
abstract class DirectionalSwingListener(
|
||||
private val forwardDotThreshold: Double = 0.3,
|
||||
private val historyTicks: Int = 10
|
||||
) : SwingListener {
|
||||
|
||||
protected val minecraft: Minecraft = Minecraft.getInstance()
|
||||
protected val clientData: VRClientAPI = VRClientAPI.instance()
|
||||
|
||||
/**
|
||||
* Compute the net movement vector for the specified body part over the given number of ticks.
|
||||
* Returns null if historical pose data is unavailable, insufficient, or movement is negligible.
|
||||
*
|
||||
* @param bodyPart The VR body part to compute movement for.
|
||||
* @param ticks The number of ticks of history to consider.
|
||||
* @return The movement vector or null if unavailable/insignificant.
|
||||
*/
|
||||
private fun changeForVelocity(bodyPart: VRBodyPart, ticks: Int = historyTicks): Vec3? {
|
||||
val history = clientData.historicalVRPoses ?: return null
|
||||
if (history.ticksOfHistory() < ticks) return null
|
||||
|
||||
val delta = history.netMovement(bodyPart, ticks)
|
||||
delta?.lengthSqr()?.let { if (it < 1e-6) return null }
|
||||
|
||||
return delta
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an upward swing is detected.
|
||||
*
|
||||
* @param useHand The hand used for the swing.
|
||||
* @param handVel The velocity vector of the hand during the swing.
|
||||
* @param player The player performing the swing.
|
||||
* @return True if the upward action was handled, false otherwise.
|
||||
*/
|
||||
protected open fun onUpwardSwing(
|
||||
useHand: InteractionHand,
|
||||
player: Player
|
||||
): Boolean {
|
||||
Cobblemon.LOGGER.info(
|
||||
"Ignoring swing: upward movement. player={}, hand={}",
|
||||
player, useHand
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a downward swing is detected.
|
||||
*
|
||||
* @param useHand The hand used for the swing.
|
||||
* @param handVel The velocity vector of the hand during the swing.
|
||||
* @param player The player performing the swing.
|
||||
* @return True if the downward action was handled, false otherwise.
|
||||
*/
|
||||
protected open fun onDownwardSwing(
|
||||
useHand: InteractionHand,
|
||||
handVel: Vec3,
|
||||
player: Player
|
||||
): Boolean {
|
||||
Cobblemon.LOGGER.info(
|
||||
"Ignoring swing: downward movement. handVel={}, player={}, hand={}",
|
||||
handVel, player, useHand
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a forward swing is detected.
|
||||
*
|
||||
* @param useHand The hand used for the swing.
|
||||
* @param handVel The velocity vector of the hand during the swing.
|
||||
* @param player The player performing the swing.
|
||||
* @return True if the forward action was handled, false otherwise.
|
||||
*/
|
||||
protected open fun onForwardSwing(
|
||||
useHand: InteractionHand,
|
||||
handVel: Vec3,
|
||||
player: Player
|
||||
): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a backward swing is detected.
|
||||
*
|
||||
* @param useHand The hand used for the swing.
|
||||
* @param handVel The velocity vector of the hand during the swing.
|
||||
* @param player The player performing the swing.
|
||||
* @return True if the backward action was handled, false otherwise.
|
||||
*/
|
||||
protected open fun onBackwardSwing(
|
||||
useHand: InteractionHand,
|
||||
handVel: Vec3,
|
||||
player: Player
|
||||
): Boolean {
|
||||
Cobblemon.LOGGER.info(
|
||||
"Ignoring swing: backward movement. handVel={}, player={}, hand={}",
|
||||
handVel, player, useHand
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a leftward swing is detected.
|
||||
*
|
||||
* @param useHand The hand used for the swing.
|
||||
* @param handVel The velocity vector of the hand during the swing.
|
||||
* @param player The player performing the swing.
|
||||
* @return True if the leftward action was handled, false otherwise.
|
||||
*/
|
||||
protected open fun onLeftSwing(
|
||||
useHand: InteractionHand,
|
||||
handVel: Vec3,
|
||||
player: Player
|
||||
): Boolean {
|
||||
Cobblemon.LOGGER.info(
|
||||
"Ignoring swing: leftward movement. handVel={}, player={}, hand={}",
|
||||
handVel, player, useHand
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a rightward swing is detected.
|
||||
*
|
||||
* @param useHand The hand used for the swing.
|
||||
* @param handVel The velocity vector of the hand during the swing.
|
||||
* @param player The player performing the swing.
|
||||
* @return True if the rightward action was handled, false otherwise.
|
||||
*/
|
||||
protected open fun onRightSwing(
|
||||
useHand: InteractionHand,
|
||||
handVel: Vec3,
|
||||
player: Player
|
||||
): Boolean {
|
||||
Cobblemon.LOGGER.info(
|
||||
"Ignoring swing: rightward movement. handVel={}, player={}, hand={}",
|
||||
handVel, player, useHand
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the given item stack is valid for processing by this swing listener.
|
||||
* Subclasses must implement this to specify which items they handle.
|
||||
* @param itemStack The item stack to validate.
|
||||
* @return True if the item stack is valid, false otherwise.
|
||||
*/
|
||||
protected abstract fun isValidItem(itemStack: ItemStack): Boolean
|
||||
|
||||
/**
|
||||
* Called when a swing ends. Validates the held item, computes hand velocity from historical poses,
|
||||
* handles vertical swings first (upward/downward) and then classifies horizontal direction:
|
||||
* forward/backward by dot with player look direction; left/right by projecting onto a right vector
|
||||
* computed from world up and forward. Ambiguous swings are ignored.
|
||||
*
|
||||
* @param context Vivecraft swing context containing body part information.
|
||||
*/
|
||||
override fun onSwingEnd(context: SwingContext?) {
|
||||
val player = minecraft.player ?: return
|
||||
val ctx = context ?: return
|
||||
val useHand: InteractionHand = VRBodyPart.toInteractionHand(ctx.bodyPart) ?: return
|
||||
val itemStack: ItemStack = player.getItemInHand(useHand)
|
||||
|
||||
if (!isValidItem(itemStack)) return
|
||||
|
||||
val handVel = changeForVelocity(ctx.bodyPart) ?: run {
|
||||
Cobblemon.LOGGER.info(
|
||||
"Ignoring swing: insufficient historical pose data for velocity. bodyPart={}, player={}",
|
||||
ctx.bodyPart, player
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val vertical = handVel.y
|
||||
if (vertical > 0.0) {
|
||||
if (onUpwardSwing(useHand, player)) return
|
||||
} else if (vertical < 0.0) {
|
||||
if (onDownwardSwing(useHand, handVel, player)) return
|
||||
}
|
||||
|
||||
val forward = player.lookAngle.normalize()
|
||||
val handDir = handVel.normalize()
|
||||
val forwardDot = handDir.dot(forward)
|
||||
|
||||
val worldUp = Vec3(0.0, 1.0, 0.0)
|
||||
val right = forward.cross(worldUp).let {
|
||||
if (it.lengthSqr() < 1e-6) Vec3(1.0, 0.0, 0.0) else it.normalize()
|
||||
}
|
||||
val rightDot = handDir.dot(right)
|
||||
|
||||
if (forwardDot > forwardDotThreshold) {
|
||||
if (onForwardSwing(useHand, handVel, player)) return
|
||||
return
|
||||
}
|
||||
|
||||
if (forwardDot < -forwardDotThreshold) {
|
||||
if (onBackwardSwing(useHand, handVel, player)) return
|
||||
return
|
||||
}
|
||||
|
||||
if (abs(rightDot) > forwardDotThreshold) {
|
||||
if (rightDot > 0.0) {
|
||||
if (onRightSwing(useHand, handVel, player)) return
|
||||
} else {
|
||||
if (onLeftSwing(useHand, handVel, player)) return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Cobblemon.LOGGER.info(
|
||||
"Ignoring swing: ambiguous direction. forwardDot={}, rightDot={}, handVel={}, player={}",
|
||||
forwardDot, rightDot, handVel, player
|
||||
)
|
||||
}
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
package com.cobblemon.mod.common.client.integration.vivecraft.listener.swing
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
|
||||
import com.cobblemon.mod.common.net.messages.server.pokeball.SendOutPokeBallPacket
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class PokeBallSwingListener : DirectionalSwingListener() {
|
||||
|
||||
override fun isValidItem(itemStack: ItemStack): Boolean =
|
||||
itemStack.`is`(CobblemonItemTags.POKE_BALLS)
|
||||
|
||||
override fun onForwardSwing(
|
||||
useHand: InteractionHand,
|
||||
handVel: Vec3,
|
||||
player: Player
|
||||
): Boolean {
|
||||
CobblemonNetwork.sendToServer(SendOutPokeBallPacket())
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDownwardSwing(useHand: InteractionHand, handVel: Vec3, player: Player): Boolean {
|
||||
CobblemonNetwork.sendToServer(SendOutPokeBallPacket())
|
||||
return super.onDownwardSwing(useHand, handVel, player)
|
||||
}
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
package com.cobblemon.mod.common.client.integration.vivecraft.listener.swing
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
|
||||
import com.cobblemon.mod.common.net.messages.server.pokerod.CastPokeRodPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokerod.ReelPokeRodPacket
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class PokeRodSwingListener : DirectionalSwingListener() {
|
||||
|
||||
override fun isValidItem(itemStack: ItemStack): Boolean =
|
||||
itemStack.`is`(CobblemonItemTags.POKE_RODS)
|
||||
|
||||
override fun onUpwardSwing(useHand: InteractionHand, player: Player): Boolean {
|
||||
CobblemonNetwork.sendToServer(ReelPokeRodPacket(useHand == InteractionHand.MAIN_HAND))
|
||||
return super.onUpwardSwing(useHand, player)
|
||||
}
|
||||
|
||||
override fun onForwardSwing(useHand: InteractionHand, handVel: Vec3, player: Player): Boolean {
|
||||
CobblemonNetwork.sendToServer(CastPokeRodPacket(useHand == InteractionHand.MAIN_HAND))
|
||||
return super.onForwardSwing(useHand, handVel, player)
|
||||
}
|
||||
|
||||
override fun onDownwardSwing(useHand: InteractionHand, handVel: Vec3, player: Player): Boolean {
|
||||
CobblemonNetwork.sendToServer(CastPokeRodPacket(useHand == InteractionHand.MAIN_HAND))
|
||||
return super.onDownwardSwing(useHand, handVel, player)
|
||||
}
|
||||
}
|
||||
-188
@@ -1,188 +0,0 @@
|
||||
package com.cobblemon.mod.common.client.integration.vivecraft.party
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.platform.events.ClientTickEvent
|
||||
import com.cobblemon.mod.common.platform.events.PlatformEvents
|
||||
import com.cobblemon.mod.common.platform.events.RenderEvent
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.mojang.math.Axis
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.player.LocalPlayer
|
||||
import net.minecraft.client.renderer.LightTexture
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.joml.Vector3f
|
||||
import org.vivecraft.api.VRAPI
|
||||
import org.vivecraft.api.client.InteractModule
|
||||
import org.vivecraft.api.client.VRClientAPI
|
||||
import org.vivecraft.api.data.VRBodyPartData
|
||||
import java.util.function.Consumer
|
||||
|
||||
class PartyOnArm : InteractModule {
|
||||
|
||||
val fakePartyEntities: Array<Entity?> = arrayOfNulls(6)
|
||||
var selectedIndex = -1
|
||||
|
||||
private fun onTick(tickEvent: ClientTickEvent.Post) {
|
||||
val client = tickEvent.client
|
||||
client.player ?: return
|
||||
val playerData = VRClientAPI.instance().postTickWorldPose ?: return
|
||||
val offHandData = playerData.offHand
|
||||
|
||||
// Keep up-to-date fakePartyEntities, which is done so we always have a Pokemon or PokeBall entity available to
|
||||
// render and keep it ticking so we have proper animations.
|
||||
val party = CobblemonClient.storage.party
|
||||
|
||||
// Handle the selected index, placing either a Pokemon or null
|
||||
if (selectedIndex > -1) {
|
||||
val actual = party.get(selectedIndex)
|
||||
if (actual == null) {
|
||||
fakePartyEntities[selectedIndex] = null
|
||||
} else if (fakePartyEntities[selectedIndex] is PokemonEntity) {
|
||||
fakePartyEntities[selectedIndex]!!.tick()
|
||||
} else {
|
||||
fakePartyEntities[selectedIndex] = PokemonEntity(client.level!!, actual)
|
||||
}
|
||||
}
|
||||
// Handle the other indices, placing a pokeball or null
|
||||
for (i in 0..5) {
|
||||
if (i != selectedIndex) {
|
||||
val actual = party.get(i)
|
||||
if (actual == null) {
|
||||
fakePartyEntities[i] = null
|
||||
} else if (fakePartyEntities[i] !is EmptyPokeBallEntity) {
|
||||
val pokeBall = EmptyPokeBallEntity(actual.caughtBall, client.level!!)
|
||||
pokeBall.captureState = EmptyPokeBallEntity.CaptureState.FALL
|
||||
fakePartyEntities[i] = pokeBall
|
||||
} else {
|
||||
fakePartyEntities[i]!!.tick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check primary hand for overlap with any of the Pokemon
|
||||
val centerPos = getCenterPos(offHandData)
|
||||
selectedIndex = -1
|
||||
for (i in 0..5) {
|
||||
if (party.get(i) != null) {
|
||||
val pokemonPos = getPokemonPos(centerPos, playerData.offHand, i)
|
||||
if (playerData.mainHand.pos.distanceToSqr(pokemonPos) < DIST_SELECT_SQR) {
|
||||
selectedIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRender(renderEvent: RenderEvent) {
|
||||
if (renderEvent.stage != RenderEvent.Stage.AFTER_ENTITIES) return
|
||||
val mc = Minecraft.getInstance()
|
||||
val bufferSource = mc.renderBuffers().bufferSource()
|
||||
val guiGraphics = GuiGraphics(mc, mc.renderBuffers().bufferSource())
|
||||
val cameraPos = renderEvent.camera.getPosition()
|
||||
val poseStack = guiGraphics.pose()
|
||||
val partialTick = renderEvent.tickCounter.getGameTimeDeltaPartialTick(true)
|
||||
val offHandData = VRClientAPI.instance().getWorldRenderPose()?.offHand ?: return
|
||||
|
||||
val centerPos = getCenterPos(offHandData)
|
||||
|
||||
|
||||
for (i in 0..5) {
|
||||
val pokemon = fakePartyEntities[i]
|
||||
if (pokemon != null) {
|
||||
poseStack.pushPose()
|
||||
val pokemonPos = getPokemonPos(centerPos, offHandData, i)
|
||||
val renderPos = Vec3(
|
||||
-cameraPos.x + pokemonPos.x,
|
||||
-cameraPos.y + pokemonPos.y,
|
||||
-cameraPos.z + pokemonPos.z,
|
||||
)
|
||||
poseStack.translate(renderPos.x, renderPos.y, renderPos.z)
|
||||
poseStack.scale(0.05f, 0.05f, 0.05f)
|
||||
poseStack.mulPose(Axis.YN.rotation(offHandData.yaw.toFloat()))
|
||||
poseStack.mulPose(Axis.XN.rotation(offHandData.pitch.toFloat()))
|
||||
poseStack.mulPose(Axis.ZP.rotation(offHandData.roll.toFloat() + Math.PI.toFloat()))
|
||||
mc.entityRenderDispatcher.getRenderer(pokemon).render(pokemon, 0f, partialTick, poseStack, bufferSource,
|
||||
LightTexture.FULL_BRIGHT)
|
||||
poseStack.popPose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCenterPos(pose: VRBodyPartData): Vec3 {
|
||||
return pose.pos.subtract(pose.dir.scale(0.125))
|
||||
}
|
||||
|
||||
private fun getPokemonPos(centerPos: Vec3, controllerData: VRBodyPartData, index: Int): Vec3 {
|
||||
var pos = centerPos
|
||||
// Move to bottom of hand
|
||||
var downVecF = Vector3f(0f, -0.0625f, 0f)
|
||||
downVecF.rotate(Axis.ZP.rotation(controllerData.roll.toFloat()))
|
||||
downVecF.rotate(Axis.XN.rotation(controllerData.pitch.toFloat()))
|
||||
downVecF.rotate(Axis.YN.rotation(controllerData.yaw.toFloat()))
|
||||
pos = pos.add(Vec3(downVecF.x.toDouble(), downVecF.y.toDouble(), downVecF.z.toDouble()))
|
||||
// Move right or left based on index
|
||||
val rightF = Vector3f(0f, 0f, 1f) // Default forward vector of Minecraft
|
||||
rightF.rotate(Axis.YN.rotation(Math.PI.toFloat() / 2f))
|
||||
rightF.rotate(Axis.ZP.rotation(controllerData.roll.toFloat()))
|
||||
rightF.rotate(Axis.XN.rotation(controllerData.pitch.toFloat()))
|
||||
rightF.rotate(Axis.YN.rotation(controllerData.yaw.toFloat()))
|
||||
val right = Vec3(rightF.x.toDouble(), rightF.y.toDouble(), rightF.z.toDouble())
|
||||
if (index > 2) {
|
||||
pos = pos.add(right.scale(-0.0625))
|
||||
} else {
|
||||
pos = pos.add(right.scale(0.0625))
|
||||
}
|
||||
// Offset by -hand direction if on left, 0 if center, or +hand direction if right
|
||||
return pos.add(controllerData.dir.scale(DIST_BETWEEN_HORIZONTAL * (index % 3 - 1)))
|
||||
}
|
||||
|
||||
override fun getId(): ResourceLocation {
|
||||
return INTERACT_ID
|
||||
}
|
||||
|
||||
override fun isActive(
|
||||
player: LocalPlayer,
|
||||
hand: InteractionHand,
|
||||
handPosition: Vec3
|
||||
): Boolean {
|
||||
return hand == InteractionHand.MAIN_HAND && selectedIndex > -1
|
||||
}
|
||||
|
||||
override fun onPress(
|
||||
player: LocalPlayer,
|
||||
hand: InteractionHand
|
||||
): Boolean {
|
||||
// UUID lookup is done at press time since the Pokemon may have switched since initially hovered over, such
|
||||
// as via commands.
|
||||
val uuid = CobblemonClient.storage.party.get(selectedIndex)?.uuid ?: return false
|
||||
CobblemonClient.storage.switchToPokemon(uuid)
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DIST_BETWEEN_HORIZONTAL: Double = 0.125
|
||||
const val DIST_BETWEEN_VERTICAL: Float = 0.05f
|
||||
const val DIST_SELECT_SQR: Double = 0.1 * 0.1
|
||||
|
||||
val INTERACT_ID = ResourceLocation(Cobblemon.MODID, "party_on_arm_interact_module")
|
||||
|
||||
private val INSTANCE = PartyOnArm()
|
||||
|
||||
fun init() {
|
||||
PlatformEvents.RENDER.subscribe(Consumer { renderEvent: RenderEvent? -> INSTANCE.onRender(renderEvent!!) })
|
||||
PlatformEvents.CLIENT_TICK_POST.subscribe(Consumer { tickEvent: ClientTickEvent.Post? ->
|
||||
INSTANCE.onTick(
|
||||
tickEvent!!
|
||||
)
|
||||
})
|
||||
VRAPI.instance().register(INSTANCE)
|
||||
}
|
||||
}
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
package com.cobblemon.mod.common.client.integration.vivecraft.proximity
|
||||
|
||||
import org.vivecraft.api.data.VRBodyPart
|
||||
import org.vivecraft.api.data.VRPose
|
||||
|
||||
// TODO: Make an PR to Vivecraft that adds this feature directly to Vivecraft
|
||||
interface ProximityListener {
|
||||
fun isActive(registration: ProximityRegistration): Boolean = true
|
||||
fun onEnter(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose)
|
||||
fun onExit(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose)
|
||||
fun onTick(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose)
|
||||
}
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
package com.cobblemon.mod.common.client.integration.vivecraft.proximity
|
||||
|
||||
import com.cobblemon.mod.common.platform.events.ClientTickEvent
|
||||
import com.cobblemon.mod.common.platform.events.PlatformEvents
|
||||
import org.vivecraft.api.client.VRClientAPI
|
||||
import kotlin.math.acos
|
||||
|
||||
// TODO: Make an PR to Vivecraft that adds this feature directly to Vivecraft
|
||||
class ProximityManager {
|
||||
private val registrations = mutableListOf<ProximityRegistration>()
|
||||
private val previousInProximity = mutableMapOf<ProximityRegistration, Boolean>()
|
||||
private val api: VRClientAPI = VRClientAPI.instance()
|
||||
|
||||
fun register(registration: ProximityRegistration) {
|
||||
registrations += registration
|
||||
previousInProximity.putIfAbsent(registration, false)
|
||||
}
|
||||
|
||||
fun unregister(registration: ProximityRegistration) {
|
||||
registrations -= registration
|
||||
previousInProximity.remove(registration)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
registrations.clear()
|
||||
previousInProximity.clear()
|
||||
}
|
||||
|
||||
fun isActive(registration: ProximityRegistration): Boolean {
|
||||
return registration.listener.isActive(registration)
|
||||
}
|
||||
|
||||
fun onTick(tickEvent: ClientTickEvent.Post) {
|
||||
val vrPose = api.getPostTickWorldPose() ?: return
|
||||
|
||||
for (reg in registrations) {
|
||||
if (!isActive(reg)) continue
|
||||
val poseA = vrPose.getBodyPartData(reg.bodyA) ?: continue
|
||||
val poseB = vrPose.getBodyPartData(reg.bodyB) ?: continue
|
||||
|
||||
val distance = poseA.pos.distanceTo(poseB.pos)
|
||||
|
||||
val dot = poseA.dir.dot(poseB.dir).coerceIn(-1.0, 1.0)
|
||||
val angleDeg = Math.toDegrees(acos(dot)).toFloat()
|
||||
val angleOk = angleDeg <= reg.angleThreshold
|
||||
|
||||
val nowInProximity = distance <= reg.distanceThreshold && angleOk
|
||||
val wasInProximity = previousInProximity[reg] ?: false
|
||||
|
||||
if (!wasInProximity && nowInProximity) {
|
||||
reg.listener.onEnter(reg.bodyA, reg.bodyB, vrPose)
|
||||
} else if (wasInProximity && !nowInProximity) {
|
||||
reg.listener.onExit(reg.bodyA, reg.bodyB, vrPose)
|
||||
}
|
||||
|
||||
if (nowInProximity) {
|
||||
reg.listener.onTick(reg.bodyA, reg.bodyB, vrPose)
|
||||
}
|
||||
|
||||
previousInProximity[reg] = nowInProximity
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val INSTANCE = ProximityManager()
|
||||
|
||||
fun init() {
|
||||
PlatformEvents.CLIENT_TICK_POST.subscribe { tickEvent: ClientTickEvent.Post? ->
|
||||
INSTANCE.onTick(
|
||||
tickEvent!!
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun register(registration: ProximityRegistration) = INSTANCE.register(registration)
|
||||
fun unregister(registration: ProximityRegistration) = INSTANCE.unregister(registration)
|
||||
fun clearAll() = INSTANCE.clear()
|
||||
}
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
package com.cobblemon.mod.common.client.integration.vivecraft.proximity
|
||||
|
||||
import org.vivecraft.api.data.VRBodyPart
|
||||
|
||||
// TODO: Make an PR to Vivecraft that adds this feature directly to Vivecraft
|
||||
data class ProximityRegistration(
|
||||
val bodyA: VRBodyPart,
|
||||
val bodyB: VRBodyPart,
|
||||
val distanceThreshold: Float,
|
||||
val angleThreshold: Float = 180f,
|
||||
val listener: ProximityListener
|
||||
)
|
||||
@@ -14,6 +14,9 @@ import com.cobblemon.mod.common.CobblemonBehaviours
|
||||
import com.cobblemon.mod.common.CobblemonCallbacks
|
||||
import com.cobblemon.mod.common.CobblemonCosmeticItems
|
||||
import com.cobblemon.mod.common.CobblemonMechanics
|
||||
import com.cobblemon.mod.common.CobblemonMovesetBuilders
|
||||
import com.cobblemon.mod.common.CobblemonPartyCompositions
|
||||
import com.cobblemon.mod.common.CobblemonPartyPools
|
||||
import com.cobblemon.mod.common.CobblemonRideSettings
|
||||
import com.cobblemon.mod.common.CobblemonUnlockableWallpapers
|
||||
import com.cobblemon.mod.common.api.abilities.Abilities
|
||||
@@ -78,12 +81,15 @@ object CobblemonDataProvider : DataProvider {
|
||||
this.register(SpeciesFeatureAssignments, reloadable = false)
|
||||
this.register(ActionEffects, reloadable = true)
|
||||
this.register(Moves, reloadable = false)
|
||||
this.register(CobblemonMovesetBuilders, reloadable = false)
|
||||
this.register(Abilities, reloadable = false)
|
||||
this.register(CobblemonBehaviours, reloadable = false)
|
||||
this.register(PokemonSpecies, reloadable = false)
|
||||
this.register(SpeciesAdditions, reloadable = false)
|
||||
this.register(PokeBalls, reloadable = false)
|
||||
this.register(PropertiesCompletionProvider, reloadable = false)
|
||||
this.register(CobblemonPartyPools, reloadable = false)
|
||||
this.register(CobblemonPartyCompositions, reloadable = false)
|
||||
this.register(SpawnDetailPresets, reloadable = true)
|
||||
this.register(CobblemonSpawnRules, reloadable = true)
|
||||
this.register(CobblemonMechanics, reloadable = true)
|
||||
|
||||
+2
-3
@@ -802,14 +802,13 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
|
||||
|
||||
spawnAction.complete()
|
||||
val result = spawnAction.future
|
||||
val resultingSpawn = result.get()
|
||||
|
||||
if (resultingSpawn == null) {
|
||||
if (result.isCompletedExceptionally) {
|
||||
noBite(player)
|
||||
return false
|
||||
}
|
||||
|
||||
var spawnedPokemon: PokemonEntity? = null
|
||||
val resultingSpawn = result.get()
|
||||
|
||||
if (resultingSpawn is EntitySpawnResult) {
|
||||
for (entity in resultingSpawn.entities) {
|
||||
|
||||
+1
-1
@@ -191,7 +191,7 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setNoGravity(false)
|
||||
isNoGravity = false
|
||||
deltaMovement = Vec3.ZERO
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1282,7 +1282,7 @@ open class PokemonEntity(
|
||||
}
|
||||
|
||||
override fun shouldBeSaved(): Boolean {
|
||||
if (ownerUUID == null && !pokemon.isNPCOwned() && (Cobblemon.config.savePokemonToWorld || isPersistenceRequired)) {
|
||||
if (ownerUUID == null && !pokemon.isNPCOwned() && (Cobblemon.config.savePokemonToWorld || isPersistenceRequired || this.pokemon.canDropHeldItem)) {
|
||||
CobblemonEvents.POKEMON_ENTITY_SAVE_TO_WORLD.postThen(PokemonEntitySaveToWorldEvent(this)) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.cobblemon.mod.common.integration.vivecraft
|
||||
|
||||
import com.cobblemon.mod.common.client.integration.vivecraft.listener.swing.PokeBallSwingListener
|
||||
import com.cobblemon.mod.common.client.integration.vivecraft.listener.swing.PokeRodSwingListener
|
||||
import org.vivecraft.api.VRAPI
|
||||
|
||||
class CobblemonVR {
|
||||
|
||||
fun init() {
|
||||
VRAPI.instance()
|
||||
.register(PokeBallSwingListener())
|
||||
|
||||
VRAPI.instance()
|
||||
.register(PokeRodSwingListener())
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import net.minecraft.world.level.Level
|
||||
import kotlin.math.cos
|
||||
|
||||
class PokeBallItem(
|
||||
val pokeBall: PokeBall
|
||||
val pokeBall: PokeBall
|
||||
) : CobblemonItem(Properties().apply {
|
||||
when (pokeBall.name) {
|
||||
// Master balls are a netherite product and should be fire immune
|
||||
@@ -43,7 +43,7 @@ class PokeBallItem(
|
||||
return InteractionResultHolder.sidedSuccess(itemStack, world.isClientSide)
|
||||
}
|
||||
|
||||
fun throwPokeBall(world: Level, player: ServerPlayer) {
|
||||
private fun throwPokeBall(world: Level, player: ServerPlayer) {
|
||||
val pokeBallEntity = EmptyPokeBallEntity(pokeBall, player.level(), player).apply {
|
||||
val overhandFactor: Float = if (player.xRot < 0) {
|
||||
5f * cos(player.xRot.toRadians())
|
||||
@@ -57,5 +57,4 @@ class PokeBallItem(
|
||||
}
|
||||
world.addFreshEntity(pokeBallEntity)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -75,8 +75,7 @@ class PokerodItem(val pokeRodId: ResourceLocation, settings: Properties) : Fishi
|
||||
|
||||
if (baitCount > 1) {
|
||||
baitStack.count = baitCount - 1
|
||||
stack.set(
|
||||
CobblemonItemComponents.BAIT, RodBaitComponent(baitStack)
|
||||
stack.set(CobblemonItemComponents.BAIT, RodBaitComponent(baitStack)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -150,163 +149,90 @@ class PokerodItem(val pokeRodId: ResourceLocation, settings: Properties) : Fishi
|
||||
}
|
||||
|
||||
override fun use(world: Level, user: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
|
||||
|
||||
val itemStack = user.getItemInHand(hand)
|
||||
val offHandItem = user.getItemInHand(InteractionHand.OFF_HAND)
|
||||
|
||||
attemptApplyOffhandBait(world, user, itemStack, offHandItem)?.let { return it }
|
||||
// Check if offhand item is valid bait and the rod is not in use, if so then apply bait from offhand
|
||||
if (!world.isClientSide && user.fishing == null && SpawnBaitEffects.isFishingBait(offHandItem)) {
|
||||
CobblemonEvents.BAIT_SET_PRE.postThen(BaitSetEvent(itemStack, offHandItem), { event ->
|
||||
return InteractionResultHolder.fail(itemStack)
|
||||
}, {
|
||||
playAttachSound(user)
|
||||
|
||||
if (user.fishing != null) {
|
||||
attemptReel(world, user, hand, itemStack)?.let { return it }
|
||||
} else {
|
||||
attemptCast(world, user, hand, itemStack)?.let { return it }
|
||||
// if there is bait on the rod already then drop it on the ground before applying offhand bait
|
||||
val baitStack = getBaitStackOnRod(itemStack)
|
||||
|
||||
if (baitStack != ItemStack.EMPTY && offHandItem.item != baitStack.item) {
|
||||
user.drop(baitStack, true) // Drop the full stack
|
||||
|
||||
// apply single bait item from offhand
|
||||
val singleBait = offHandItem.copy()
|
||||
singleBait.count = 1
|
||||
setBait(itemStack, singleBait)
|
||||
offHandItem.shrink(1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return InteractionResultHolder.sidedSuccess(itemStack, world.isClientSide)
|
||||
}
|
||||
|
||||
fun applyBait(
|
||||
rodStack: ItemStack,
|
||||
baitSource: ItemStack,
|
||||
player: Player?
|
||||
): Boolean {
|
||||
if (baitSource.isEmpty || !SpawnBaitEffects.isFishingBait(baitSource)) return false
|
||||
|
||||
val existingBait = getBaitStackOnRod(rodStack)
|
||||
if (!existingBait.isEmpty && existingBait.item == baitSource.item) return false
|
||||
|
||||
var applied = false
|
||||
|
||||
CobblemonEvents.BAIT_SET_PRE.postThen(
|
||||
BaitSetEvent(rodStack, baitSource),
|
||||
{ return false },
|
||||
{
|
||||
if (!existingBait.isEmpty && player != null) {
|
||||
player.drop(existingBait, true)
|
||||
}
|
||||
|
||||
val singleBait = baitSource.copy()
|
||||
singleBait.count = 1
|
||||
setBait(rodStack, singleBait)
|
||||
|
||||
baitSource.shrink(1)
|
||||
applied = true
|
||||
var i: Int
|
||||
if (user.fishing != null) { // if the bobber is out yet
|
||||
if (!world.isClientSide) {
|
||||
CobblemonEvents.POKEROD_REEL.postThen(
|
||||
PokerodReelEvent(user, itemStack),
|
||||
{ event -> return InteractionResultHolder.fail(itemStack) },
|
||||
{ event ->
|
||||
i = user.fishing!!.retrieve(itemStack)
|
||||
itemStack.hurtAndBreak(i, user, hand.toEquipmentSlot())
|
||||
world.playSoundServer(
|
||||
Vec3(user.x,
|
||||
user.y,
|
||||
user.z),
|
||||
CobblemonSounds.FISHING_ROD_REEL_IN,
|
||||
SoundSource.PLAYERS,
|
||||
1.0f,
|
||||
1.0f / (world.getRandom().nextFloat() * 0.4f + 0.8f)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
return applied
|
||||
}
|
||||
|
||||
fun attemptApplyOffhandBait(
|
||||
world: Level,
|
||||
user: Player,
|
||||
rodStack: ItemStack,
|
||||
offHandItem: ItemStack
|
||||
): InteractionResultHolder<ItemStack>? {
|
||||
if (world.isClientSide) return null
|
||||
if (user.fishing != null) return null
|
||||
world.playSound(null as Player?, user.x, user.y, user.z, CobblemonSounds.FISHING_ROD_REEL_IN, SoundSource.PLAYERS, 1.0f, 1.0f)
|
||||
user.gameEvent(GameEvent.ITEM_INTERACT_FINISH)
|
||||
} else { // if the bobber is not out yet
|
||||
|
||||
val applied = applyBait(
|
||||
rodStack = rodStack,
|
||||
baitSource = offHandItem,
|
||||
player = user
|
||||
)
|
||||
if (!world.isClientSide) {
|
||||
val lureLevel = world.enchantmentRegistry.getHolder(Enchantments.LURE).map { EnchantmentHelper.getItemEnchantmentLevel(it, itemStack) }.orElse(0)
|
||||
val luckLevel = world.enchantmentRegistry.getHolder(Enchantments.LUCK_OF_THE_SEA).map { EnchantmentHelper.getItemEnchantmentLevel(it, itemStack) }.orElse(0)
|
||||
|
||||
if (applied) {
|
||||
playAttachSound(user)
|
||||
return InteractionResultHolder.sidedSuccess(rodStack, false)
|
||||
val bobberEntity = PokeRodFishingBobberEntity(
|
||||
user,
|
||||
pokeRodId,
|
||||
getBaitStackOnRod(itemStack),
|
||||
world,
|
||||
luckLevel,
|
||||
lureLevel,
|
||||
itemStack
|
||||
)
|
||||
CobblemonEvents.POKEROD_CAST_PRE.postThen(
|
||||
PokerodCastEvent.Pre(itemStack, bobberEntity, getBaitStackOnRod(itemStack)),
|
||||
{ event -> return InteractionResultHolder.fail(itemStack) },
|
||||
{ event ->
|
||||
world.addFreshEntity(bobberEntity)
|
||||
var baitId = getBaitStackOnRod(itemStack).takeUnless { it.isEmpty }?.itemHolder?.unwrapKey()?.orElse(null)?.location() ?: cobblemonResource("empty_bait")
|
||||
CobblemonCriteria.CAST_POKE_ROD.trigger(user as ServerPlayer, CastPokeRodContext(baitId))
|
||||
|
||||
CobblemonEvents.POKEROD_CAST_POST.post(
|
||||
PokerodCastEvent.Post(itemStack, bobberEntity, getBaitStackOnRod(itemStack))
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
user.awardStat(Stats.ITEM_USED.get(this))
|
||||
user.gameEvent(GameEvent.ITEM_INTERACT_START)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
fun attemptReel(
|
||||
world: Level,
|
||||
user: Player,
|
||||
hand: InteractionHand,
|
||||
itemStack: ItemStack
|
||||
): InteractionResultHolder<ItemStack>? {
|
||||
if (user.fishing == null) return null
|
||||
|
||||
if (!world.isClientSide) {
|
||||
CobblemonEvents.POKEROD_REEL.postThen(
|
||||
PokerodReelEvent(user, itemStack),
|
||||
{ event -> return InteractionResultHolder.fail(itemStack) },
|
||||
{ event ->
|
||||
val i = user.fishing!!.retrieve(itemStack)
|
||||
itemStack.hurtAndBreak(i, user, hand.toEquipmentSlot())
|
||||
world.playSoundServer(
|
||||
Vec3(user.x, user.y, user.z),
|
||||
CobblemonSounds.FISHING_ROD_REEL_IN,
|
||||
SoundSource.PLAYERS,
|
||||
1.0f,
|
||||
1.0f / (world.getRandom().nextFloat() * 0.4f + 0.8f)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
world.playSound(
|
||||
null as Player?,
|
||||
user.x,
|
||||
user.y,
|
||||
user.z,
|
||||
CobblemonSounds.FISHING_ROD_REEL_IN,
|
||||
SoundSource.PLAYERS,
|
||||
1.0f,
|
||||
1.0f
|
||||
)
|
||||
user.gameEvent(GameEvent.ITEM_INTERACT_FINISH)
|
||||
|
||||
return InteractionResultHolder.sidedSuccess(itemStack, world.isClientSide)
|
||||
}
|
||||
|
||||
fun attemptCast(
|
||||
world: Level,
|
||||
user: Player,
|
||||
hand: InteractionHand,
|
||||
itemStack: ItemStack
|
||||
): InteractionResultHolder<ItemStack>? {
|
||||
if (user.fishing != null) return null
|
||||
|
||||
if (!world.isClientSide) {
|
||||
val lureLevel = world.enchantmentRegistry.getHolder(Enchantments.LURE)
|
||||
.map { EnchantmentHelper.getItemEnchantmentLevel(it, itemStack) }
|
||||
.orElse(0)
|
||||
val luckLevel = world.enchantmentRegistry.getHolder(Enchantments.LUCK_OF_THE_SEA)
|
||||
.map { EnchantmentHelper.getItemEnchantmentLevel(it, itemStack) }
|
||||
.orElse(0)
|
||||
|
||||
val bobberEntity = PokeRodFishingBobberEntity(
|
||||
user,
|
||||
pokeRodId,
|
||||
getBaitStackOnRod(itemStack),
|
||||
world,
|
||||
luckLevel,
|
||||
lureLevel,
|
||||
itemStack
|
||||
)
|
||||
|
||||
CobblemonEvents.POKEROD_CAST_PRE.postThen(
|
||||
PokerodCastEvent.Pre(itemStack, bobberEntity, getBaitStackOnRod(itemStack)),
|
||||
{ event -> return InteractionResultHolder.fail(itemStack) },
|
||||
{ event ->
|
||||
world.addFreshEntity(bobberEntity)
|
||||
val baitId = getBaitStackOnRod(itemStack).takeUnless { it.isEmpty }
|
||||
?.itemHolder?.unwrapKey()?.orElse(null)?.location()
|
||||
?: cobblemonResource("empty_bait")
|
||||
CobblemonCriteria.CAST_POKE_ROD.trigger(user as ServerPlayer, CastPokeRodContext(baitId))
|
||||
|
||||
CobblemonEvents.POKEROD_CAST_POST.post(
|
||||
PokerodCastEvent.Post(itemStack, bobberEntity, getBaitStackOnRod(itemStack))
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
user.awardStat(Stats.ITEM_USED.get(this))
|
||||
user.gameEvent(GameEvent.ITEM_INTERACT_START)
|
||||
|
||||
return InteractionResultHolder.sidedSuccess(itemStack, world.isClientSide)
|
||||
}
|
||||
|
||||
|
||||
+21
-2
@@ -13,7 +13,11 @@ import com.cobblemon.mod.common.api.moves.Moves
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.battles.MoveTarget
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.readEnumConstant
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.cobblemon.mod.common.util.writeEnumConstant
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
class MovesRegistrySyncPacket(moves: List<MoveTemplate>) : DataRegistrySyncPacket<MoveTemplate, MovesRegistrySyncPacket>(moves) {
|
||||
@@ -33,6 +37,7 @@ class MovesRegistrySyncPacket(moves: List<MoveTemplate>) : DataRegistrySyncPacke
|
||||
buffer.writeDouble(entry.critRatio)
|
||||
buffer.writeInt(entry.effectChances.size)
|
||||
entry.effectChances.forEach { chance -> buffer.writeDouble(chance) }
|
||||
buffer.writeFloat(entry.weight)
|
||||
}
|
||||
|
||||
override fun decodeEntry(buffer: RegistryFriendlyByteBuf): MoveTemplate {
|
||||
@@ -50,7 +55,21 @@ class MovesRegistrySyncPacket(moves: List<MoveTemplate>) : DataRegistrySyncPacke
|
||||
repeat(buffer.readInt()) {
|
||||
effectChances += buffer.readDouble()
|
||||
}
|
||||
return MoveTemplate(name, num, type, damageCategory, power, target, accuracy, pp, priority, critRatio, effectChances.toTypedArray())
|
||||
val weight = buffer.readFloat()
|
||||
return MoveTemplate(
|
||||
name = name,
|
||||
num = num,
|
||||
elementalType = type,
|
||||
damageCategory = damageCategory,
|
||||
power = power,
|
||||
target = target,
|
||||
accuracy = accuracy,
|
||||
pp = pp,
|
||||
priority = priority,
|
||||
critRatio = critRatio,
|
||||
effectChances = effectChances.toTypedArray(),
|
||||
weight = weight
|
||||
)
|
||||
}
|
||||
|
||||
override fun synchronizeDecoded(entries: Collection<MoveTemplate>) {
|
||||
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
package com.cobblemon.mod.common.net.messages.server.pokeball
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
class SendOutPokeBallPacket() : NetworkPacket<SendOutPokeBallPacket> {
|
||||
override val id = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("send_out_pokeball")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = SendOutPokeBallPacket()
|
||||
}
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
package com.cobblemon.mod.common.net.messages.server.pokerod
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
class ApplyBaitPacket() : NetworkPacket<ApplyBaitPacket> {
|
||||
override val id = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("apply_bait")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = ApplyBaitPacket()
|
||||
}
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
package com.cobblemon.mod.common.net.messages.server.pokerod
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
class CastPokeRodPacket(val mainHand: Boolean) : NetworkPacket<CastPokeRodPacket> {
|
||||
override val id = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeBoolean(mainHand)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("cast_pokerod")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = CastPokeRodPacket(buffer.readBoolean())
|
||||
}
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
package com.cobblemon.mod.common.net.messages.server.pokerod
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
class ReelPokeRodPacket(val mainHand: Boolean) : NetworkPacket<ReelPokeRodPacket> {
|
||||
override val id = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeBoolean(mainHand)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("reel_pokerod")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = ReelPokeRodPacket(buffer.readBoolean())
|
||||
}
|
||||
}
|
||||
-29
@@ -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.serverhandling.pokeball
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.item.PokeBallItem
|
||||
import com.cobblemon.mod.common.net.messages.server.pokeball.SendOutPokeBallPacket
|
||||
import com.cobblemon.mod.common.util.isServerSide
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
object SendOutPokeBallHandler : ServerNetworkPacketHandler<SendOutPokeBallPacket> {
|
||||
|
||||
override fun handle(packet: SendOutPokeBallPacket, server: MinecraftServer, player: ServerPlayer) {
|
||||
(player.mainHandItem.item as? PokeBallItem)?.let { pokeball ->
|
||||
if (player.level().isServerSide()) {
|
||||
pokeball.throwPokeBall(player.level(), player)
|
||||
}
|
||||
// TODO: player.mainHandItem.consume(1, player)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
package com.cobblemon.mod.common.net.serverhandling.pokerod
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.item.interactive.PokerodItem
|
||||
import com.cobblemon.mod.common.net.messages.server.pokerod.ApplyBaitPacket
|
||||
import com.cobblemon.mod.common.util.isServerSide
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.InteractionHand
|
||||
|
||||
object ApplyBaitHandler : ServerNetworkPacketHandler<ApplyBaitPacket> {
|
||||
|
||||
override fun handle(packet: ApplyBaitPacket, server: MinecraftServer, player: ServerPlayer) {
|
||||
|
||||
val mainHandItem = player.getItemInHand(InteractionHand.MAIN_HAND)
|
||||
val offHandItem = player.getItemInHand(InteractionHand.OFF_HAND)
|
||||
|
||||
println("[ApplyBaitHandler] Main hand: ${mainHandItem.item}")
|
||||
println("[ApplyBaitHandler] Off hand: ${offHandItem.item}")
|
||||
|
||||
(mainHandItem.item as? PokerodItem)?.let { pokerod ->
|
||||
if (player.level().isServerSide()) {
|
||||
pokerod.applyBait(
|
||||
mainHandItem,
|
||||
offHandItem,
|
||||
player
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
(offHandItem.item as? PokerodItem)?.let { pokerod ->
|
||||
if (player.level().isServerSide()) {
|
||||
pokerod.applyBait(
|
||||
offHandItem,
|
||||
mainHandItem,
|
||||
player
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.net.serverhandling.pokerod
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.item.PokeBallItem
|
||||
import com.cobblemon.mod.common.item.interactive.PokerodItem
|
||||
import com.cobblemon.mod.common.net.messages.server.pokeball.SendOutPokeBallPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokerod.CastPokeRodPacket
|
||||
import com.cobblemon.mod.common.util.isServerSide
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.InteractionHand
|
||||
|
||||
object CastPokeRodHandler : ServerNetworkPacketHandler<CastPokeRodPacket> {
|
||||
|
||||
override fun handle(packet: CastPokeRodPacket, server: MinecraftServer, player: ServerPlayer) {
|
||||
val hand = if (packet.mainHand) InteractionHand.MAIN_HAND else InteractionHand.OFF_HAND
|
||||
(player.getItemInHand(hand).item as? PokerodItem)?.let { pokerod ->
|
||||
if (player.level().isServerSide()) pokerod.attemptCast(
|
||||
player.level(),
|
||||
player,
|
||||
hand,
|
||||
player.getItemInHand(hand)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
-36
@@ -1,36 +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.pokerod
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.item.PokeBallItem
|
||||
import com.cobblemon.mod.common.item.interactive.PokerodItem
|
||||
import com.cobblemon.mod.common.net.messages.server.pokeball.SendOutPokeBallPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokerod.CastPokeRodPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokerod.ReelPokeRodPacket
|
||||
import com.cobblemon.mod.common.util.isServerSide
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.InteractionHand
|
||||
|
||||
object ReelPokeRodHandler : ServerNetworkPacketHandler<ReelPokeRodPacket> {
|
||||
|
||||
override fun handle(packet: ReelPokeRodPacket, server: MinecraftServer, player: ServerPlayer) {
|
||||
val hand = if (packet.mainHand) InteractionHand.MAIN_HAND else InteractionHand.OFF_HAND
|
||||
(player.getItemInHand(hand).item as? PokerodItem)?.let { pokerod ->
|
||||
if (player.level().isServerSide()) pokerod.attemptReel(
|
||||
player.level(),
|
||||
player,
|
||||
hand,
|
||||
player.getItemInHand(hand)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,6 @@ data class RenderEvent(
|
||||
|
||||
/** Represents when a layer is rendered by [LevelRenderer.renderLevel]. Ordinal corresponds with its render order. */
|
||||
enum class Stage {
|
||||
AFTER_ENTITIES,
|
||||
TRANSLUCENT
|
||||
}
|
||||
}
|
||||
@@ -104,6 +104,10 @@ class FormData(
|
||||
private var _baseAI: MutableList<BehaviourConfig>? = null,
|
||||
@SerializedName("ai")
|
||||
private var _ai: MutableList<BehaviourConfig>? = null,
|
||||
@SerializedName("signatureMoves")
|
||||
private var _signatureMoves: MutableList<MoveTemplate>? = null,
|
||||
@SerializedName("defaultWildMovesetBuilder")
|
||||
private val _defaultWildMovesetBuilder: ResourceLocation? = null,
|
||||
val requiredMove: String? = null,
|
||||
val requiredItem: String? = null,
|
||||
/** For forms that can accept different items (e.g. Arceus-Grass: Meadow Plate or Grassium-Z). */
|
||||
@@ -235,6 +239,10 @@ class FormData(
|
||||
get() = _baseAI ?: species.baseAI
|
||||
val ai: List<BehaviourConfig>
|
||||
get() = _ai ?: species.ai
|
||||
val signatureMoves: List<MoveTemplate>
|
||||
get() = _signatureMoves ?: species.signatureMoves
|
||||
val defaultWildMovesetBuilder: ResourceLocation
|
||||
get() = _defaultWildMovesetBuilder ?: species.defaultWildMovesetBuilder
|
||||
|
||||
fun eyeHeight(entity: PokemonEntity): Float {
|
||||
return this.resolveEyeHeight(entity) ?: return this.species.eyeHeight(entity)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common.pokemon
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonMovesetBuilders
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacketToPlayers
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
@@ -34,6 +35,7 @@ import com.cobblemon.mod.common.api.moves.Move
|
||||
import com.cobblemon.mod.common.api.moves.MoveSet
|
||||
import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
import com.cobblemon.mod.common.api.moves.Moves
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.pokeball.PokeBalls
|
||||
import com.cobblemon.mod.common.api.pokemon.Natures
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
@@ -591,6 +593,9 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
onChange(MarkingsUpdatePacket({ this }, value))
|
||||
}
|
||||
|
||||
// Last flower fed to a Mooshtank
|
||||
var lastFlowerFed: ItemStack = ItemStack.EMPTY
|
||||
|
||||
fun asRenderablePokemon() = RenderablePokemon(species, aspects, if (heldItemVisible) heldItem else ItemStack.EMPTY)
|
||||
|
||||
/**
|
||||
@@ -673,7 +678,7 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
* Whether this Pokémon's held item can be dropped by its AI.
|
||||
*/
|
||||
internal var canDropHeldItem: Boolean = false
|
||||
get() = field || heldItem.isEmpty
|
||||
get() = field && !heldItem.isEmpty
|
||||
|
||||
val riding: RidingProperties
|
||||
get() = this.form.riding
|
||||
@@ -1556,10 +1561,8 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
originalTrainer = fakeTrainerName
|
||||
}
|
||||
|
||||
fun refreshOriginalTrainer()
|
||||
{
|
||||
when (originalTrainerType)
|
||||
{
|
||||
fun refreshOriginalTrainer() {
|
||||
when (originalTrainerType) {
|
||||
OriginalTrainerType.PLAYER -> {
|
||||
UUID.fromString(originalTrainer)?.let { uuid ->
|
||||
server()?.profileCache?.get(uuid)?.orElse(null)?.name?.let {
|
||||
@@ -1576,8 +1579,7 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
}
|
||||
}
|
||||
|
||||
fun removeOriginalTrainer()
|
||||
{
|
||||
fun removeOriginalTrainer() {
|
||||
originalTrainer = null
|
||||
originalTrainerType = OriginalTrainerType.NONE
|
||||
originalTrainerName = null
|
||||
@@ -1619,14 +1621,11 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
species = species
|
||||
checkGender()
|
||||
if (moveSet.getMoves().isEmpty()) {
|
||||
initializeMoveset()
|
||||
initializeMovesetFromDefault()
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
// Last flower fed to a Mooshtank
|
||||
var lastFlowerFed: ItemStack = ItemStack.EMPTY
|
||||
|
||||
fun checkGender() {
|
||||
var reassess = false
|
||||
if (form.maleRatio !in 0F..1F && gender != Gender.GENDERLESS) {
|
||||
@@ -1802,6 +1801,20 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
moveSet.update()
|
||||
}
|
||||
|
||||
fun initializeMovesetFromDefault() {
|
||||
initializeMovesetFrom(movesetBuilder = CobblemonMovesetBuilders.getOrThrow(form.defaultWildMovesetBuilder))
|
||||
}
|
||||
|
||||
fun initializeMovesetFrom(movesetBuilder: MovesetBuilder) {
|
||||
val newMoveset = movesetBuilder.build(form = form, level = level)
|
||||
moveSet.copyFrom(newMoveset)
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
message = "Will be removed within potentially 1 title update",
|
||||
replaceWith = ReplaceWith("initializeMovesetFromDefault"),
|
||||
level = DeprecationLevel.ERROR
|
||||
)
|
||||
fun initializeMoveset(preferLatest: Boolean = true) {
|
||||
val possibleMoves = form.moves.getLevelUpMovesUpTo(level).toMutableList()
|
||||
moveSet.doWithoutEmitting {
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.api.drop.DropTable
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addSpeciesFunctions
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffect
|
||||
@@ -42,6 +43,7 @@ import com.cobblemon.mod.common.net.IntSize
|
||||
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility
|
||||
import com.cobblemon.mod.common.pokemon.ai.PokemonBehaviour
|
||||
import com.cobblemon.mod.common.pokemon.lighthing.LightingData
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.readEntityDimensions
|
||||
import com.cobblemon.mod.common.util.readEnumConstant
|
||||
import com.cobblemon.mod.common.util.readIdentifier
|
||||
@@ -111,6 +113,8 @@ class Species : ClientDataSynchronizer<Species>, ShowdownIdentifiable {
|
||||
var implemented = false
|
||||
var baseAI: MutableList<BehaviourConfig>? = null
|
||||
var ai = mutableListOf<BehaviourConfig>()
|
||||
var signatureMoves = mutableListOf<MoveTemplate>()
|
||||
var defaultWildMovesetBuilder = cobblemonResource("wild")
|
||||
|
||||
/**
|
||||
* The height in decimeters
|
||||
|
||||
@@ -87,6 +87,7 @@ object DataKeys {
|
||||
|
||||
const val POKEMON_PROPERTIES_MOVES = "Moves"
|
||||
const val POKEMON_PROPERTIES_HELDITEM = "HeldItem"
|
||||
const val POKEMON_PROPERTIES_MOVESET_BUILDERS = "MovesetBuilders"
|
||||
|
||||
// Entity effects
|
||||
const val ENTITY_EFFECTS = "EntityEffects"
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
|
||||
/**
|
||||
* An IntRange where the minimum and maximum values are defined by MoLang expressions. When deserializing from
|
||||
* JSON the [com.cobblemon.mod.common.util.adapters.ScriptableIntRangeAdapter] will figure out if it's a single
|
||||
* value in which case it's not using any scripting stuff. Otherwise, if it's an object with "min" and "max" properties
|
||||
* it will respect them as expressions.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 15th, 2025
|
||||
*/
|
||||
class ScriptableIntRange(
|
||||
val min: ExpressionLike = "0".asExpressionLike(),
|
||||
val max: ExpressionLike = "0".asExpressionLike()
|
||||
) {
|
||||
constructor(min: Int, max: Int): this(
|
||||
min = min.toString().asExpressionLike(),
|
||||
max = max.toString().asExpressionLike()
|
||||
)
|
||||
|
||||
fun resolve(runtime: MoLangRuntime): IntRange {
|
||||
val minimum = min.resolveInt(runtime)
|
||||
val maximum = max.resolveInt(runtime)
|
||||
if (minimum > maximum) {
|
||||
Cobblemon.LOGGER.error("ScriptableIntRange(${min.getString()}, ${max.getString()}) resolved a minimum ($minimum) greater than maximum ($maximum). Returning empty range.")
|
||||
return IntRange.EMPTY
|
||||
}
|
||||
return minimum..maximum
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.util
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.joml.Vector3f
|
||||
import kotlin.math.sqrt
|
||||
|
||||
/**
|
||||
* For conversion from Vec3dto BlockPos, loses accuracy
|
||||
@@ -23,17 +22,9 @@ fun Vec3.toBlockPos(): BlockPos {
|
||||
fun Vec3.toVec3f(): Vector3f = Vector3f(x.toFloat(), y.toFloat(), z.toFloat())
|
||||
fun Vector3f.toVec3d(): Vec3 =
|
||||
Vec3(x.toDouble(), y.toDouble(), z.toDouble())
|
||||
|
||||
fun Vector3f.set(vec3d: Vec3): Vector3f {
|
||||
x = vec3d.x.toFloat()
|
||||
y = vec3d.y.toFloat()
|
||||
z = vec3d.z.toFloat()
|
||||
return this
|
||||
}
|
||||
|
||||
fun Vector3f.distance(other: Vector3f): Float {
|
||||
val dx = other.x - x
|
||||
val dy = other.y - y
|
||||
val dz = other.z - z
|
||||
return sqrt(dx * dx + dy * dy + dz * dz)
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.util.adapters
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
import java.lang.reflect.Type
|
||||
|
||||
object MovesetBuilderAdapter : JsonDeserializer<MovesetBuilder> {
|
||||
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): MovesetBuilder {
|
||||
val jsonObject = json.asJsonObject
|
||||
val type = jsonObject.get("type")?.asString ?: "cobblemon:default"
|
||||
val clazz = MovesetBuilder.movesetBuilderTypes[type.asIdentifierDefaultingNamespace()]
|
||||
?: throw IllegalArgumentException("Unknown MovesetBuilder type: $type")
|
||||
return context.deserialize(json, clazz)
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.util.adapters
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMovesetBuilders
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
import java.lang.reflect.Type
|
||||
|
||||
object MovesetBuilderReferenceAdapter : JsonDeserializer<MovesetBuilder> {
|
||||
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): MovesetBuilder {
|
||||
return CobblemonMovesetBuilders.getOrThrow(json.asString.asIdentifierDefaultingNamespace())
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common.util.adapters
|
||||
|
||||
import com.cobblemon.mod.common.api.npc.NPCPartyProvider
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
@@ -16,8 +17,8 @@ import java.lang.reflect.Type
|
||||
|
||||
object NPCPartyProviderAdapter : JsonDeserializer<NPCPartyProvider> {
|
||||
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): NPCPartyProvider {
|
||||
val typeName = if (json.isJsonPrimitive) json.asString else json.asJsonObject.get("type").asString
|
||||
return NPCPartyProvider.types[typeName]?.invoke(typeName)?.also { it.loadFromJSON(json) }
|
||||
val typeName = (if (json.isJsonPrimitive) json.asString else json.asJsonObject.get("type").asString).asIdentifierDefaultingNamespace()
|
||||
return NPCPartyProvider.types[typeName]?.invoke()?.also { it.loadFromJSON(json) }
|
||||
?: throw IllegalStateException("Unable to find party provider by type: $typeName")
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.util.adapters
|
||||
|
||||
import com.cobblemon.mod.common.util.ScriptableIntRange
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonPrimitive
|
||||
import java.lang.reflect.Type
|
||||
|
||||
/**
|
||||
* Parses a [ScriptableIntRange] by two paths. One is if it's a primitive in which
|
||||
* case they're just doing a normal [IntRange] and it will parse it using the same
|
||||
* method as IntRange normally gets and then returns a ScriptableIntRange of literals.
|
||||
*
|
||||
* The other path is if they provide an object with "min" and "max" in which case
|
||||
* each property is deserialized as an [ExpressionLike].
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 15th, 2025
|
||||
*/
|
||||
object ScriptableIntRangeAdapter : JsonDeserializer<ScriptableIntRange> {
|
||||
override fun deserialize(
|
||||
json: JsonElement,
|
||||
typeOfT: Type,
|
||||
context: JsonDeserializationContext
|
||||
): ScriptableIntRange {
|
||||
if (json is JsonPrimitive) {
|
||||
IntRangeAdapter.deserialize(json, typeOfT, context).let {
|
||||
return ScriptableIntRange(
|
||||
it.first.toString().asExpressionLike(),
|
||||
it.last.toString().asExpressionLike()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val obj = json.asJsonObject
|
||||
val min = obj.get("min").asString
|
||||
val max = obj.get("max").asString
|
||||
return ScriptableIntRange(
|
||||
min.toString().asExpressionLike(),
|
||||
max.toString().asExpressionLike()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -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.util.adapters
|
||||
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
import java.lang.reflect.Type
|
||||
|
||||
/**
|
||||
* A simple adapter that maps string values to objects based on a provided mapping.
|
||||
*
|
||||
* I could swear I already made something like this before.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 5th, 2025
|
||||
*/
|
||||
class StringToObjectAdapter<T>(
|
||||
val mapping: MutableMap<String, T>
|
||||
) : JsonDeserializer<T> {
|
||||
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): T = mapping[json.asString]
|
||||
?: throw IllegalArgumentException("Unknown mapping for string: ${json.asString}")
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"focusblast": 50
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"type": "cobblemon:default",
|
||||
"slot1": ["last_suitable_offensive", "last_offensive", "last_levelup"],
|
||||
"slot2": ["last_offensive", "last_levelup"],
|
||||
"slot3": ["last_levelup"],
|
||||
"slot4": ["tm", "last_levelup"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"type": "cobblemon:default",
|
||||
"slot1": ["last_offensive", "last_levelup"],
|
||||
"slot2": ["last_levelup"],
|
||||
"slot3": ["last_levelup"],
|
||||
"slot4": ["last_levelup"]
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,194 @@
|
||||
Composed pool parties are a type of NPC party that allows for a more complex party than usual. It relies on several
|
||||
datapack folders to function.
|
||||
|
||||
### Moveset Builders
|
||||
The `moveset_builders` datapack folder has all the ways that a moveset can be composed
|
||||
logically. This consists of up to 4 slots of moves defined with some list of move selectors.
|
||||
|
||||
The full list of possible move selectors is as follows:
|
||||
|
||||
| Type | Description |
|
||||
|---------------------------|---------------------------------------------------------------------------------------------------------------|
|
||||
| `none` | Blank move slot |
|
||||
| `last_levelup` | Picks the last level-up move (rolls if there are several) |
|
||||
| `last_offensive` | Same as last_levelup but no status moves |
|
||||
| `last_suitable_offensive` | Same as last_offensive but restricts to whichever is stronger of physical/special in the Pokémon's base stats |
|
||||
| `levelup` | Picks a random level-up move |
|
||||
| `stab` | Picks a random STAB level-up move |
|
||||
| `stab_physical` | Picks a random physical STAB level-up move |
|
||||
| `stab_special` | Picks a random special STAB level-up move |
|
||||
| `physical` | Picks a random physical level-up move |
|
||||
| `special` | Picks a random special level-up move |
|
||||
| `offensive` | Picks a random physical or special level-up move |
|
||||
| `status` | Picks a random status level-up move |
|
||||
| `tm` | Picks a random TM move |
|
||||
| `stab_tm` | Picks a random STAB TM move |
|
||||
| `last_status` | Same as last_levelup but only status moves |
|
||||
| `egg` | Picks a random egg move |
|
||||
|
||||
When selecting from one of multiple possible moves, the selection is a weighted random. The base move weight is drawn
|
||||
from the `move_weights.json` file in the root data folder. If a move is not in that JSON then the weight will be `50`.
|
||||
If a move is mentioned in the `signatureMoves` list of the Pokémon's species or form, its weight is multiplied by `5`.
|
||||
|
||||
If you add a datapack with a `move_weights.json` in the `cobblemon` namespace, it will combine with the default values,
|
||||
with the datapack layering over the top. In other words, you can override a few of the existing weights with a small
|
||||
JSON file, if you want.
|
||||
|
||||
For example, take `alpha.json`, used for alpha Pokémon:
|
||||
|
||||
```json
|
||||
{
|
||||
"slot1": ["last_suitable_offensive", "last_offensive", "last_levelup"],
|
||||
"slot2": ["last_offensive", "last_levelup"],
|
||||
"slot3": ["last_levelup"],
|
||||
"slot4": ["tm", "last_levelup"]
|
||||
}
|
||||
```
|
||||
|
||||
This moveset builder will try to fill slot 1 with the last suitable offensive move the Pokémon can learn,
|
||||
falling back to the last offensive move, and then the last level-up move if all else fails.
|
||||
|
||||
### Molang Expressions in Composed Parties
|
||||
For the Molang expression properties in the following sections, the following Molang queries will be available:
|
||||
|
||||
`q.players` - A list of players that are challenging the NPC.
|
||||
|
||||
`q.player` - The player challenging the NPC, specifically if being challenged only by one player. If there are multiple players, this will be 0.
|
||||
|
||||
`q.npc` - The NPC entity.
|
||||
|
||||
`q.level` - The seed level of the NPC.
|
||||
|
||||
|
||||
### Party Pools
|
||||
The `party_pools` datapack folder has all the pools of Pokémon that can be used to draw from when composing a party.
|
||||
Each entry of the pool has some number of labels used by the party composition.
|
||||
|
||||
| Property | Description |
|
||||
|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `pokemon` | The guaranteed Pokémon information, such as `geodude shiny=true` |
|
||||
| `labels` | The identifying labels of the Pokémon. These can be anything you want. Definitely shouldn't be empty though. |
|
||||
| `npcLevels` | The seed levels that the NPC can be for which this entry is possible. 1-30 would mean a level 40 NPC cannot use that entry. You can also provide an object with `min` and `max` properties to use MoLang expressions. |
|
||||
| `npcAspects` | The aspects that the NPC must have for this entry to be possible. If the list is empty, as is the default, then it won't check the NPC aspects. |
|
||||
| `weight` | The relative weight of this entry compared to others in the pool. Defaults to 50. You can also use a Molang expression here. |
|
||||
| `levelVariation` | The possible variation around the NPC's seed level that is possible. `-2-3` would mean the Pokémon's level could be anywhere from 2 levels below the NPC seed level to 3 levels above. You can also provide an object with `min` and `max` properties to use MoLang expressions. |
|
||||
| `maxTimesSelectable` | The maximum number of times this entry can be selected for a party. Defaults to 6. You can also use a Molang expression here. |
|
||||
| `movesetBuilders` | A list of moveset builders from the `moveset_builders` folder that can be used to create this Pokémon's moveset. One will be selected at random if there are multiple. |
|
||||
| `requires` | A list of labels that must be present in the party for this entry to be selected. These are from the `labels` of entries that were already selected. |
|
||||
| `incompatible` | A list of labels that cannot be present in the party for this entry to be selected. These are from the `labels` of entries that were already selected. |
|
||||
|
||||
As an example, a trivially small pool might look like:
|
||||
```json
|
||||
{
|
||||
"displayName": "Some Name",
|
||||
"entries": [
|
||||
{
|
||||
"pokemon": "geodude",
|
||||
"labels": [
|
||||
"basic",
|
||||
"rocky"
|
||||
],
|
||||
"npcLevels": "1-30",
|
||||
"weight": 20,
|
||||
"levelVariation": {
|
||||
"min": "q.level < 10 ? -1 : -3",
|
||||
"max": "q.level < 10 ? 1 : 3"
|
||||
},
|
||||
"maxTimesSelectable": 4,
|
||||
"movesetBuilders": ["cobblemon:npc_pokemon"],
|
||||
"requires": [
|
||||
"golem-ace"
|
||||
],
|
||||
"incompatible": [
|
||||
"steelix-ace"
|
||||
]
|
||||
},
|
||||
{
|
||||
"pokemon": "golem",
|
||||
"labels": [
|
||||
"ace",
|
||||
"strong"
|
||||
],
|
||||
"npcLevels": "1-30",
|
||||
"weight": 20,
|
||||
"levelVariation": "1-5",
|
||||
"maxTimesSelectable": 1,
|
||||
"movesetBuilders": ["cobblemon:offensive_npc_pokemon", "cobblemon:npc_pokemon"],
|
||||
"incompatible": [
|
||||
"steelix-ace",
|
||||
"ace"
|
||||
]
|
||||
},
|
||||
{
|
||||
"pokemon": "steelix shiny=true",
|
||||
"labels": [
|
||||
"ace",
|
||||
"strong"
|
||||
],
|
||||
"npcLevels": "15-30",
|
||||
"npcAspects": ["steel-boots"],
|
||||
"weight": "q.level < 20 ? 10 : 30",
|
||||
"levelVariation": "0-2",
|
||||
"maxTimesSelectable": "q.level > 25 ? 2 : 1",
|
||||
"movesetBuilders": ["cobblemon:offensive_npc_pokemon"],
|
||||
"requires": [],
|
||||
"incompatible": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Party Compositions
|
||||
The `party_compositions` datapack folder has all the ways that a party can be composed of a pool of Pokémon. This allows
|
||||
you to dictate the preferred Pokémon categories for each slot of the party.
|
||||
|
||||
For example, a standard NPC's party composition might look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"scrambleOrder": true,
|
||||
"slot1": ["ace", "strong", "fodder"],
|
||||
"slot2": ["strong", "fodder"],
|
||||
"slot3": ["strong", "fodder"],
|
||||
"slot4": ["fodder"],
|
||||
"slot5": ["fodder"],
|
||||
"slot6": ["fodder"]
|
||||
}
|
||||
```
|
||||
|
||||
This will make a party that is randomly ordered. One slot will have a Pokémon with the `ace` label if it can find any
|
||||
suitable ones for this seed level and NPC aspects, falling back to `strong` or `fodder` if not, and if all else fails
|
||||
the slot will be empty. That logic is applied to all the remaining slots. The party composition is given the number of
|
||||
Pokémon that is desired, and in the case of a 3 Pokémon party, slot1, slot, and slot3 will be used even if
|
||||
`scrambleOrder` is true. The scrambling occurs only after the Pokémon have been selected. The only exception is if any
|
||||
of slots 1-3 failed to produce a Pokémon, in which case the later slots will be used to make up the desired number of
|
||||
Pokémon.
|
||||
|
||||
### Composed Pool Party Provider
|
||||
The `composed_pool` party provider type uses a party pool and a party composition to create a potentially quite complex
|
||||
party. When generating the party, it first decides how many Pokémon to select, based on the `minPokemon` and `maxPokemon`
|
||||
expressions. It then uses the party composition and pool to select that many Pokémon and builds a party.
|
||||
|
||||
|
||||
| Property | Description |
|
||||
|------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `isStatic` | True if the party will be permanently saved onto the NPC. This will mean that if defeated, the NPC's party will remain fainted until it can heal. |
|
||||
| `useFixedRandom` | Whether the randomizer should produce the same party consistently. This is for when you have a non-static party but once it generates a party, it should always generate an identical one each time it's challenged. |
|
||||
| `minPokemon` | A Molang expression representing how many Pokémon is the minimum it will have. Defaults to "1" |
|
||||
| `maxPokemon` | A Molang expression representing how many Pokémon is the minimum it will have. Defaults to "6" |
|
||||
| `pool` | The party pool to choose from, such as `"youraddon:rocky_pokemon"`. |
|
||||
| `composition` | The party composition to use to compose the pool into a party, such as `"youraddon:gym_leader"` |
|
||||
|
||||
An example `composed_pool` party provider might look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "composed_pool",
|
||||
"isStatic": false,
|
||||
"useFixedRandom": true,
|
||||
"minPokemon": "q.level > 30 ? 3 : 1",
|
||||
"maxPokemon": "q.level > 50 ? 6 : 4",
|
||||
"pool": "youraddon:rocky_pokemon",
|
||||
"composition": "youraddon:gym_leader"
|
||||
}
|
||||
```
|
||||
@@ -51,16 +51,6 @@ dependencies {
|
||||
modImplementation(libs.fabric.loader)
|
||||
modApi(libs.fabric.api)
|
||||
modApi(libs.bundles.fabric)
|
||||
|
||||
// VR Support
|
||||
modRuntimeOnly(files("temp/vivecraft-1.21.1-1.3.4-fabric.jar"))
|
||||
runtimeOnly("com.illposed.osc:javaosc-core:0.9")
|
||||
runtimeOnly("com.electronwill.night-config:toml:3.6.6")
|
||||
runtimeOnly("com.electronwill.night-config:core:3.6.6")
|
||||
runtimeOnly("org.lwjgl:lwjgl-openvr:3.3.3")
|
||||
runtimeOnly("org.lwjgl:lwjgl-openvr:3.3.3:natives-linux")
|
||||
runtimeOnly("org.lwjgl:lwjgl-openvr:3.3.3:natives-macos")
|
||||
runtimeOnly("org.lwjgl:lwjgl-openvr:3.3.3:natives-windows")
|
||||
|
||||
modCompileOnly(libs.bundles.common.integrations.compileOnly) {
|
||||
isTransitive = false
|
||||
|
||||
@@ -170,18 +170,6 @@ class CobblemonFabricClient: ClientModInitializer, CobblemonClientImplementation
|
||||
)
|
||||
)
|
||||
}
|
||||
WorldRenderEvents.AFTER_ENTITIES.register { context -> PlatformEvents.RENDER.post(
|
||||
RenderEvent(
|
||||
stage = RenderEvent.Stage.AFTER_ENTITIES,
|
||||
levelRenderer = context.worldRenderer(),
|
||||
poseStack = context.matrixStack() ?: PoseStack(),
|
||||
modelViewMatrix = context.positionMatrix(),
|
||||
projectionMatrix = context.projectionMatrix(),
|
||||
tickCounter = context.tickCounter(),
|
||||
camera = context.camera()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
CobblemonModelPredicateRegistry.registerPredicates()
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -39,14 +39,6 @@ dependencies {
|
||||
//include(libs.flywheelForge)
|
||||
modLocalRuntime(libs.neoforge.debugutils)
|
||||
modRuntimeOnly(libs.bundles.neoforge.integrations.runtimeOnly)
|
||||
|
||||
// VR Support
|
||||
modRuntimeOnly(files("temp/vivecraft-1.21.1-1.3.4-neoforge.jar"))
|
||||
runtimeOnly("com.illposed.osc:javaosc-core:0.9")
|
||||
runtimeOnly("org.lwjgl:lwjgl-openvr:3.3.3")
|
||||
runtimeOnly("org.lwjgl:lwjgl-openvr:3.3.3:natives-linux")
|
||||
runtimeOnly("org.lwjgl:lwjgl-openvr:3.3.3:natives-macos")
|
||||
runtimeOnly("org.lwjgl:lwjgl-openvr:3.3.3:natives-windows")
|
||||
|
||||
implementation(libs.neo.kotlin.forge)
|
||||
|
||||
|
||||
-1
@@ -74,7 +74,6 @@ object NeoForgeClientPlatformEventHandler {
|
||||
fun onRenderLevelStageEvent(event: RenderLevelStageEvent) {
|
||||
val stage = when (event.stage) {
|
||||
RenderLevelStageEvent.Stage.AFTER_PARTICLES -> RenderEvent.Stage.TRANSLUCENT
|
||||
RenderLevelStageEvent.Stage.AFTER_ENTITIES -> RenderEvent.Stage.AFTER_ENTITIES
|
||||
else -> return
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -7,7 +7,7 @@ from io import StringIO
|
||||
from sqlalchemy import create_engine
|
||||
from tqdm import tqdm
|
||||
from scriptutils import printCobblemonHeader, print_cobblemon_script_description, print_cobblemon_script_footer, \
|
||||
print_list_filtered, print_warning, sanitize_pokemon
|
||||
print_list_filtered, print_warning, sanitize_pokemon, download_spreadsheet_data
|
||||
|
||||
# Initialize lists for report
|
||||
no_behaviour_base_forms = []
|
||||
@@ -21,23 +21,6 @@ form_entry_mapping = {
|
||||
"Hisuian": "Hisui",
|
||||
"Paldean": "Paldea",
|
||||
}
|
||||
#
|
||||
# controller_keys = {
|
||||
# 'Standard': 'cobblemon:land/generic',
|
||||
# 'Vehicle': 'cobblemon:land/vehicle',
|
||||
# #'Submarine': 'cobblemon:water/submarine',
|
||||
# #'Dolphin': 'cobblemon:water/dolphin',
|
||||
# #'Burst': 'cobblemon:water/burst',
|
||||
# 'Boat': 'cobblemon:water/boat',
|
||||
# #'Roll': 'cobblemon:land/roll',
|
||||
# #'Jet': 'cobblemon:air/jet',
|
||||
# 'Bird': 'cobblemon:air/bird',
|
||||
# #'UFO': 'cobblemon:air/ufo',
|
||||
# 'Glider': 'cobblemon:air/glider',
|
||||
# #'Airship': 'cobblemon:air/airship',
|
||||
# 'Fall To Flight': 'cobblemon:composite/fall_to_flight',
|
||||
# 'Run Up To Flight': 'cobblemon:composite/run_up_to_flight'
|
||||
# }
|
||||
|
||||
def main(dex_range=None):
|
||||
"""
|
||||
@@ -355,21 +338,6 @@ def build_herd(behaviour_row, herd):
|
||||
herd['toleratedLeaders'] = leaders
|
||||
return herd
|
||||
|
||||
def download_spreadsheet_data(url, max_retries=10):
|
||||
delay = 1
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
response = requests.get(url, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.content.decode('utf-8')
|
||||
except requests.RequestException as e:
|
||||
if attempt < max_retries - 1:
|
||||
time.sleep(delay)
|
||||
delay *= 2
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
||||
def load_data_from_csv(csv_data):
|
||||
return pd.read_csv(StringIO(csv_data), encoding='utf8', engine='python', skiprows=1, dtype={'Species': str, 'Composite': str, 'Water': str, 'Flying': str, 'Special': str }, na_filter=False)
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import re
|
||||
from io import StringIO
|
||||
import pandas as pd
|
||||
from mutagen.oggvorbis import OggVorbis
|
||||
from cobblemon_drops_csv_to_json import download_spreadsheet_data
|
||||
from scriptutils import printCobblemonHeader, print_list_filtered, print_cobblemon_script_description, \
|
||||
print_cobblemon_script_footer, print_problems_and_paths, print_warning, sanitize_pokemon, print_separator
|
||||
print_cobblemon_script_footer, print_problems_and_paths, print_warning, sanitize_pokemon, print_separator \
|
||||
download_spreadsheet_data
|
||||
|
||||
DEFAULT_POKEMON_MODELS_PATH = "../common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/pokemon/"
|
||||
DEFAULT_POKEMON_POSER_PATH = "../common/src/main/resources/assets/cobblemon/bedrock/pokemon/posers/"
|
||||
|
||||
@@ -7,7 +7,7 @@ from io import StringIO
|
||||
from sqlalchemy import create_engine
|
||||
from tqdm import tqdm
|
||||
from scriptutils import printCobblemonHeader, print_cobblemon_script_description, print_cobblemon_script_footer, \
|
||||
print_list_filtered, print_warning, sanitize_pokemon
|
||||
print_list_filtered, print_warning, sanitize_pokemon, download_spreadsheet_data
|
||||
|
||||
# Initialize lists for report
|
||||
no_drops_base_forms = []
|
||||
@@ -264,21 +264,6 @@ def replace_names_in__specific_drops_string(special_drops_str, mapping_dict):
|
||||
return ";".join(output_drops)
|
||||
|
||||
|
||||
def download_spreadsheet_data(url, max_retries=10):
|
||||
delay = 1
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
response = requests.get(url, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.content.decode('utf-8')
|
||||
except requests.RequestException as e:
|
||||
if attempt < max_retries - 1:
|
||||
time.sleep(delay)
|
||||
delay *= 2
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
||||
def load_data_from_csv(csv_data):
|
||||
return pd.read_csv(StringIO(csv_data), encoding='utf8', engine='python', dtype={'Pokémon': str, 'Drops': str})
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from io import StringIO
|
||||
from sqlalchemy import create_engine
|
||||
from tqdm import tqdm
|
||||
from scriptutils import printCobblemonHeader, print_cobblemon_script_description, print_cobblemon_script_footer, \
|
||||
print_list_filtered, print_warning, sanitize_pokemon
|
||||
print_list_filtered, print_warning, sanitize_pokemon, download_spreadsheet_data
|
||||
|
||||
# Initialize lists for report
|
||||
no_riding_base_forms = []
|
||||
@@ -304,21 +304,6 @@ def has_flavour_ranges(riding_row, category_index):
|
||||
return True
|
||||
return False
|
||||
|
||||
def download_spreadsheet_data(url, max_retries=10):
|
||||
delay = 1
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
response = requests.get(url, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.content.decode('utf-8')
|
||||
except requests.RequestException as e:
|
||||
if attempt < max_retries - 1:
|
||||
time.sleep(delay)
|
||||
delay *= 2
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
||||
def load_data_from_csv(csv_data):
|
||||
return pd.read_csv(StringIO(csv_data), encoding='utf8', engine='python', skiprows=2, dtype={'Species': str, 'Composite': str, 'Water': str, 'Flying': str, 'Special': str }, na_filter=False)
|
||||
def has_riding(riding_row):
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
import os
|
||||
import time
|
||||
import requests
|
||||
import pandas as pd
|
||||
import json
|
||||
from io import StringIO
|
||||
from sqlalchemy import create_engine
|
||||
from tqdm import tqdm
|
||||
from scriptutils import printCobblemonHeader, print_cobblemon_script_description, print_cobblemon_script_footer, \
|
||||
print_list_filtered, print_warning, download_spreadsheet_data
|
||||
|
||||
trainer_preset = "cobblemon:trainer"
|
||||
|
||||
def main(dex_range=None):
|
||||
"""
|
||||
The cobblemon_trainer_data_csvs_to_json script does the following:
|
||||
1. Retrieves the DataFrame containing the data for moveset builders, party pools, party arrangements, and trainer classes.
|
||||
2. Converts each into JSONs
|
||||
3. Writes the data into the appropriate files in the mod's data directory.
|
||||
"""
|
||||
# Print header
|
||||
printCobblemonHeader()
|
||||
script_name = "Cobblemon Trainer Data Generator"
|
||||
script_description = "This script generates the trainer data for the mod based on the data in a Google Spreadsheet."
|
||||
print_cobblemon_script_description(script_name, script_description)
|
||||
|
||||
update_moveset_builders()
|
||||
update_party_compositions()
|
||||
update_party_pools()
|
||||
update_npc_classes()
|
||||
|
||||
def load_data_from_csv(csv_data, skip_rows):
|
||||
return pd.read_csv(StringIO(csv_data), encoding='utf8', engine='python', skiprows=skip_rows, na_filter=False)
|
||||
|
||||
def update_moveset_builders():
|
||||
moveset_builders_spreadsheet_csv_url = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vRrzepexgCvAMsvxhN_u_WV0VzIqcjJQb65Rtk-UhUtndRoXS-80lFlWgqLTHHThGMz3CqPrV8memOb/pub?gid=883883971&single=true&output=csv'
|
||||
moveset_builders_data_dir = '../common/src/main/resources/data/cobblemon/moveset_builders'
|
||||
# Download the CSV from the Google Spreadsheet
|
||||
csv_data = download_spreadsheet_data(moveset_builders_spreadsheet_csv_url)
|
||||
# Load the data into a DataFrame
|
||||
moveset_builders_df = load_data_from_csv(csv_data, 0)
|
||||
|
||||
for index, row in moveset_builders_df.iterrows():
|
||||
id = row['Moveset ID']
|
||||
if (pd.isna(id) or id == ""):
|
||||
continue
|
||||
slot1 = row['Slot 1']
|
||||
slot2 = row['Slot 2']
|
||||
slot3 = row['Slot 3']
|
||||
slot4 = row['Slot 4']
|
||||
|
||||
moveset_builder_json = {
|
||||
"type": "cobblemon:default",
|
||||
"slot1": slot1.split(', ') if pd.notna(slot1) else [],
|
||||
"slot2": slot2.split(', ') if pd.notna(slot2) else [],
|
||||
"slot3": slot3.split(', ') if pd.notna(slot3) else [],
|
||||
"slot4": slot4.split(', ') if pd.notna(slot4) else []
|
||||
}
|
||||
|
||||
# Make file with name ${id}.json inside moveset_builders_data_dir
|
||||
|
||||
with open(f'{moveset_builders_data_dir}/{id}.json', 'w') as f:
|
||||
import re
|
||||
json_str = json.dumps(moveset_builder_json, indent=2)
|
||||
json_str = re.sub(r'\[\n\s+', r'[', json_str)
|
||||
json_str = re.sub(r'\n\s+\]', r']', json_str)
|
||||
json_str = re.sub(r',\n\s+(?="[^"]*"\s*[,\]])', r', ', json_str)
|
||||
f.write(json_str)
|
||||
|
||||
print("Moveset builders updated successfully!")
|
||||
|
||||
def update_party_compositions():
|
||||
party_compositions_spreadsheet_csv_url = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vRrzepexgCvAMsvxhN_u_WV0VzIqcjJQb65Rtk-UhUtndRoXS-80lFlWgqLTHHThGMz3CqPrV8memOb/pub?gid=1855392430&single=true&output=csv'
|
||||
party_compositions_data_dir = '../common/src/main/resources/data/cobblemon/party_compositions'
|
||||
# Download the CSV from the Google Spreadsheet
|
||||
csv_data = download_spreadsheet_data(party_compositions_spreadsheet_csv_url)
|
||||
# Load the data into a DataFrame
|
||||
party_compositions_df = load_data_from_csv(csv_data, 1)
|
||||
|
||||
for index, row in party_compositions_df.iterrows():
|
||||
# print all keys in the row
|
||||
id = row['Composition ID']
|
||||
if (pd.isna(id) or id == ""):
|
||||
continue
|
||||
scrambled_order = row['Scrambled Order']
|
||||
slot1 = row['Slot 1']
|
||||
slot2 = row['Slot 2']
|
||||
slot3 = row['Slot 3']
|
||||
slot4 = row['Slot 4']
|
||||
slot5 = row['Slot 5']
|
||||
slot6 = row['Slot 6']
|
||||
|
||||
composition_json = {
|
||||
"scrambleOrder": scrambled_order,
|
||||
"slot1": slot1.split(', ') if pd.notna(slot1) else [],
|
||||
"slot2": slot2.split(', ') if pd.notna(slot2) else [],
|
||||
"slot3": slot3.split(', ') if pd.notna(slot3) else [],
|
||||
"slot4": slot4.split(', ') if pd.notna(slot4) else [],
|
||||
"slot5": slot5.split(', ') if pd.notna(slot5) else [],
|
||||
"slot6": slot6.split(', ') if pd.notna(slot6) else []
|
||||
}
|
||||
|
||||
|
||||
# Make file with name ${id}.json inside party_compositions_data_dir
|
||||
|
||||
with open(f'{party_compositions_data_dir}/{id}.json', 'w') as f:
|
||||
import re
|
||||
json_str = json.dumps(composition_json, indent=2)
|
||||
json_str = re.sub(r'\[\n\s+', r'[', json_str)
|
||||
json_str = re.sub(r'\n\s+\]', r']', json_str)
|
||||
json_str = re.sub(r',\n\s+(?="[^"]*"\s*[,\]])', r', ', json_str)
|
||||
f.write(json_str)
|
||||
|
||||
print("Party compositions updated successfully!")
|
||||
|
||||
def update_party_pools():
|
||||
party_pools_spreadsheet_csv_url = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vRrzepexgCvAMsvxhN_u_WV0VzIqcjJQb65Rtk-UhUtndRoXS-80lFlWgqLTHHThGMz3CqPrV8memOb/pub?gid=295609571&single=true&output=csv'
|
||||
party_pools_data_dir = '../common/src/main/resources/data/cobblemon/party_pools'
|
||||
# Download the CSV from the Google Spreadsheet
|
||||
csv_data = download_spreadsheet_data(party_pools_spreadsheet_csv_url)
|
||||
# Load the data into a DataFrame
|
||||
party_pools_df = load_data_from_csv(csv_data, 1)
|
||||
|
||||
pools = {}
|
||||
|
||||
for index, row in party_pools_df.iterrows():
|
||||
# print all keys in the row
|
||||
id = row['Pool ID']
|
||||
if (pd.isna(id) or id == ""):
|
||||
continue
|
||||
pokemon = row['Pokémon']
|
||||
movesets = row['Movesets']
|
||||
weight = row['Weight']
|
||||
max_times = row['Max Times']
|
||||
level_min = row['L.Min']
|
||||
level_max = row['L.Max']
|
||||
variation_min = row['V.Min']
|
||||
variation_max = row['V.Max']
|
||||
labels = row['Labels']
|
||||
required_labels = row['Required']
|
||||
incompatible_labels = row['Incompatible']
|
||||
|
||||
entry = {
|
||||
"pokemon": pokemon,
|
||||
"labels": labels.split(', ') if labels != '' else [],
|
||||
"required": required_labels.split(', ') if required_labels != '' else [],
|
||||
"incompatible": incompatible_labels.split(', ') if incompatible_labels != '' else [],
|
||||
"movesetBuilders": movesets.split(', ') if movesets != '' else [],
|
||||
"weight": weight,
|
||||
"maxTimesSelectable": max_times,
|
||||
"npcLevels": f'{level_min}-{level_max}',
|
||||
"levelVariation": f'{variation_min}-{variation_max}'
|
||||
}
|
||||
|
||||
if id not in pools:
|
||||
pools[id] = {
|
||||
"entries": []
|
||||
}
|
||||
pools[id]["entries"].append(entry)
|
||||
|
||||
|
||||
# Make file with name ${id}.json inside party_pools_data_dir
|
||||
# iterate over each key in pools, use its value to make the json file named by the key
|
||||
for id, pool_json in pools.items():
|
||||
pool_json["entries"] = sorted(pool_json["entries"], key=lambda x: x["weight"], reverse=True)
|
||||
|
||||
with open(f'{party_pools_data_dir}/{id}.json', 'w') as f:
|
||||
import re
|
||||
json_str = json.dumps(pool_json, indent=2)
|
||||
json_str = re.sub(r'\[\n\s+', r'[', json_str)
|
||||
json_str = re.sub(r'\n\s+\]', r']', json_str)
|
||||
json_str = re.sub(r',\n\s+(?="[^"]*"\s*[,\]])', r', ', json_str)
|
||||
json_str = json_str.replace('[{', '[\n {')
|
||||
json_str = json_str.replace('}]', '}\n ]')
|
||||
f.write(json_str)
|
||||
|
||||
print("Party pools updated successfully!")
|
||||
|
||||
def convert_to_resource_location(str):
|
||||
# if it contains : then return as is, otherwise prefix cobblemon:
|
||||
return str if ':' in str else f'cobblemon:{str}'
|
||||
|
||||
def update_npc_classes():
|
||||
trainer_classes_spreadsheet_csv_url = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vRrzepexgCvAMsvxhN_u_WV0VzIqcjJQb65Rtk-UhUtndRoXS-80lFlWgqLTHHThGMz3CqPrV8memOb/pub?gid=0&single=true&output=csv'
|
||||
trainer_classes_data_dir = '../common/src/main/resources/data/cobblemon/npcs'
|
||||
# Download the CSV from the Google Spreadsheet
|
||||
csv_data = download_spreadsheet_data(trainer_classes_spreadsheet_csv_url)
|
||||
# Load the data into a DataFrame
|
||||
trainer_classes_df = load_data_from_csv(csv_data, 1)
|
||||
|
||||
for index, row in trainer_classes_df.iterrows():
|
||||
# print all keys in the row
|
||||
id = row['Trainer Class Name'].lower().replace(' ', '_')
|
||||
if (pd.isna(id) or id == ""):
|
||||
continue
|
||||
hitbox = row['Hitbox']
|
||||
ai = row['AI'] # unusedish rn
|
||||
drops = row['Drops'] # unused rn
|
||||
composition = row['Composition']
|
||||
pool = row['Pool']
|
||||
min_party_size = row['Min Party Size']
|
||||
max_party_size = row['Max Party Size']
|
||||
|
||||
# if there is an existing JSON with this name, read its contents in so we can modify it
|
||||
trainer_class_json = {
|
||||
"presets": [ trainer_preset ]
|
||||
}
|
||||
if os.path.exists(f'{trainer_classes_data_dir}/{id}.json'):
|
||||
with open(f'{trainer_classes_data_dir}/{id}.json', 'r') as f:
|
||||
trainer_class_json = json.load(f)
|
||||
trainer_class_json.update({
|
||||
"hitbox": hitbox,
|
||||
# "ai": ai,
|
||||
# "drops": drops,
|
||||
"party": {
|
||||
"type": "cobblemon:composed_pool",
|
||||
"composition": convert_to_resource_location(composition),
|
||||
"pool": convert_to_resource_location(pool),
|
||||
"isStatic": True,
|
||||
"useFixedRandom": False,
|
||||
"minPokemon": min_party_size,
|
||||
"maxPokemon": max_party_size
|
||||
}
|
||||
})
|
||||
|
||||
with open(f'{trainer_classes_data_dir}/{id}.json', 'w') as f:
|
||||
f.write(json.dumps(trainer_class_json, indent=2))
|
||||
|
||||
print("NPC classes updated successfully!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,4 +1,6 @@
|
||||
import shutil
|
||||
import time
|
||||
import requests
|
||||
import textwrap
|
||||
|
||||
cobblemonheader = """
|
||||
@@ -80,7 +82,19 @@ def print_separator():
|
||||
print("\n")
|
||||
|
||||
|
||||
|
||||
def download_spreadsheet_data(url, max_retries=10):
|
||||
delay = 1
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
response = requests.get(url, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.content.decode('utf-8')
|
||||
except requests.RequestException as e:
|
||||
if attempt < max_retries - 1:
|
||||
time.sleep(delay)
|
||||
delay *= 2
|
||||
else:
|
||||
raise e
|
||||
|
||||
def sanitize_pokemon(pokemon):
|
||||
return (pokemon.replace("-", "").replace("♂", "m").replace("♀", "f")
|
||||
|
||||
Reference in New Issue
Block a user