Compare commits

..
1151 changed files with 5396 additions and 8132 deletions

No files matched your search

+2 -7
View File
@@ -154,14 +154,9 @@
- Poké Ball recipes now use tags for their center ingredient, rather than specificied Copper Ingot, Iron Ingot, Gold Ingot, and Diamond, allowing for further datapack customisation.
- Starter Toast now closes once corresponding button is pressed once.
- Moved some items in the vanilla Food & Drinks category.
- Stacked Cobblemon Potion Bases can now be quick moved into Brewing Stands.
- Changed Healer recipe to swap the Max Revive for a Revive.
- Upped the rate that Gastly drop Ghast Tears from 2.5% to 5%.
- Pokémon with a low Blaze Powder drop chance now drop it more frequently in the Nether.
- Reorganised a few advancements.
### Fixes
- Resized Snorlax.
- Resized Snorlax
- Re-added the recipe for the Iron Vitamin.
- Cleaned up empty evolutions declaration in species files (no, I'm not listing all 169 by name).
- Fixed Exeggcute not being able to evolve.
@@ -193,13 +188,13 @@
- Made PokemonProperties.parse more interoperable with Java.
- Added the ``HeldItemEvent``, this comes with 2 implementation ``HeldItemEvent.Pre`` and ``HeldItemEvent.Post``.
- API to support dynamic lighting in any mod that implements this feature has been added to all Species & Forms, see LightingData class for details.
- Added a Mixin for the BrewingStandScreenHandler to fix the incorrect handling of quickMove in Fabric.
- Corrections to the ability implementation have been made that make ``Ability.forced`` function as intended and never reroll an ability for a Pokémon, please check your implementations to ensure the corrected behavior is intended for your use case.
- ``Pokemon.ability`` no longer has a public setter, please migrate to using ``Pokemon#updateAbility``, this handles the ability coordinate storage for you when necessary.
- ``Ability.forced``, ``Ability.index`` and ``Ability.priority`` have all had their setters internalized, there is no longer any need to manually adjust these migrate to the method mentioned above to handle that process for you.
- ``AbilityChanger`` has been added to API alongside some implementations, this is subject to change as the ability capsule and patch currently only expect the traditional behaviour in the Pokémon games of 1 to 2 regular abilities and 1 hidden.
- ``Pokemon#rollAbility`` has been added, this rerolls a legal ability for the Pokémon.
- `docs/cobblemon-tags` tags and spawn-presets can now be auto-generated from the current tags in the cobblemon mod with the `generateSpawnPresetList.py` and `generateTagList.py` scripts.
- `public_spawns_to_json.py` script has been added to give the powerful spawn.json generator a simplistic UI.
### Datapack & Resourcepack Creators
- The maximum amount of fossils that can fit in the Fossil Analyzer can be adjusted in the config.
@@ -67,13 +67,24 @@ import com.cobblemon.mod.common.api.storage.player.factory.JsonPlayerDataStoreFa
import com.cobblemon.mod.common.api.storage.player.factory.MongoPlayerDataStoreFactory
import com.cobblemon.mod.common.api.tags.CobblemonEntityTypeTags
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.battles.*
import com.cobblemon.mod.common.battles.BagItems
import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.battles.ShowdownThread
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.command.argument.*
import com.cobblemon.mod.common.command.argument.DialogueArgumentType
import com.cobblemon.mod.common.command.argument.MoveArgumentType
import com.cobblemon.mod.common.command.argument.PartySlotArgumentType
import com.cobblemon.mod.common.command.argument.PokemonArgumentType
import com.cobblemon.mod.common.command.argument.PokemonPropertiesArgumentType
import com.cobblemon.mod.common.command.argument.PokemonStoreArgumentType
import com.cobblemon.mod.common.command.argument.SpawnBucketArgumentType
import com.cobblemon.mod.common.config.CobblemonConfig
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.AdvancementHandler
import com.cobblemon.mod.common.events.ServerTickHandler
@@ -94,7 +105,13 @@ import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
import com.cobblemon.mod.common.sherds.CobblemonSherds
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
import com.cobblemon.mod.common.trade.TradeManager
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.ifDedicatedServer
import com.cobblemon.mod.common.util.isLaterVersion
import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.util.removeAmountIf
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.world.feature.CobblemonPlacedFeatures
import com.cobblemon.mod.common.world.feature.ore.CobblemonOrePlacedFeatures
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
@@ -115,16 +132,13 @@ import org.apache.logging.log4j.Logger
import java.io.File
import java.io.FileReader
import java.io.FileWriter
import java.io.PrintWriter
import java.util.*
import kotlin.collections.filterIsInstance
import kotlin.collections.forEach
import kotlin.collections.listOf
import kotlin.collections.set
import kotlin.math.roundToInt
import kotlin.properties.Delegates
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaField
import kotlin.math.roundToInt
object Cobblemon {
const val MODID = "cobblemon"
@@ -152,6 +166,7 @@ object Cobblemon {
var storage = PokemonStoreManager()
var molangData = NbtMoLangDataStoreFactory
lateinit var playerData: PlayerDataStoreManager
lateinit var starterConfig: StarterConfig
val dataProvider: DataProvider = CobblemonDataProvider
var permissionValidator: PermissionValidator by Delegates.observable(LaxPermissionValidator().also { it.initialize() }) { _, _, newValue -> newValue.initialize() }
var statProvider: StatProvider = CobblemonStatProvider
@@ -462,6 +477,27 @@ object Cobblemon {
this.saveConfig()
bestSpawner.loadConfig()
PokemonSpecies.observable.subscribe { starterConfig = this.loadStarterConfig() }
}
fun loadStarterConfig(): StarterConfig {
if (config.exportStarterConfig) {
val file = File("config/cobblemon/starters.json")
file.parentFile.mkdirs()
if (!file.exists()) {
val config = StarterConfig()
val pw = PrintWriter(file)
StarterConfig.GSON.toJson(config, pw)
pw.close()
return config
}
val reader = FileReader(file)
val config = StarterConfig.GSON.fromJson(reader, StarterConfig::class.java)
reader.close()
return config
} else {
return StarterConfig()
}
}
fun saveConfig() {
@@ -160,7 +160,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, RegistryKey<Registry<
@JvmField
val BIG_ROOT = this.create("big_root", BigRootBlock(AbstractBlock.Settings.create().pistonBehavior(PistonBehavior.DESTROY).burnable().mapColor(MapColor.DARK_GREEN).noCollision().ticksRandomly().breakInstantly().sounds(CobblemonSounds.BIG_ROOT_SOUNDS)))
@JvmField
val REVIVAL_HERB = this.create("revival_herb", RevivalHerbBlock(AbstractBlock.Settings.create().pistonBehavior(PistonBehavior.DESTROY).mapColor(MapColor.DARK_GREEN).burnable().noCollision().breakInstantly().sounds(CobblemonSounds.REVIVAL_HERB_SOUNDS)))
val REVIVAL_HERB = this.create("revival_herb", RevivalHerbBlock(AbstractBlock.Settings.create().pistonBehavior(PistonBehavior.DESTROY).mapColor(MapColor.DARK_GREEN).burnable().noCollision().breakInstantly().sounds(BlockSoundGroup.CROP)))
@JvmField
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
@@ -428,7 +428,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, RegistryKey<Registry<
)
@JvmField
val VIVICHOKE_SEEDS = this.create("vivichoke_seeds", VivichokeBlock(AbstractBlock.Settings.create().pistonBehavior(PistonBehavior.DESTROY).burnable().mapColor(MapColor.DARK_GREEN).noCollision().ticksRandomly().breakInstantly().sounds(CobblemonSounds.VIVICHOKE_SOUNDS)))
val VIVICHOKE_SEEDS = this.create("vivichoke_seeds", VivichokeBlock(AbstractBlock.Settings.create().pistonBehavior(PistonBehavior.DESTROY).burnable().mapColor(MapColor.DARK_GREEN).noCollision().ticksRandomly().breakInstantly().sounds(BlockSoundGroup.CROP)))
@JvmField
val PEP_UP_FLOWER = this.create("pep_up_flower", FlowerBlock(StatusEffects.LEVITATION, 10, AbstractBlock.Settings.create().mapColor(MapColor.DARK_GREEN).noCollision().breakInstantly().sounds(BlockSoundGroup.GRASS).offset(AbstractBlock.OffsetType.XZ).pistonBehavior(PistonBehavior.DESTROY)))
@JvmField
@@ -99,27 +99,27 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, RegistryKey<Regi
val MULCH_REMOVE = this.create("mulch.remove")
@JvmField
val FOSSIL_MACHINE_ACTIVATE = this.create("fossil_machine.activate")
val FOSSIL_MACHINE_ACTIVATE = this.create("fossilmachine.activate")
@JvmField
val FOSSIL_MACHINE_ACTIVE_LOOP = this.create("fossil_machine.active_loop")
val FOSSIL_MACHINE_ACTIVE_LOOP = this.create("fossilmachine.active_loop")
@JvmField
val FOSSIL_MACHINE_ASSEMBLE = this.create("fossil_machine.assemble")
val FOSSIL_MACHINE_ASSEMBLE = this.create("fossilmachine.assemble")
@JvmField
val FOSSIL_MACHINE_DNA_FULL = this.create("fossil_machine.dna_full")
val FOSSIL_MACHINE_DNA_FULL = this.create("fossilmachine.dna_full")
@JvmField
val FOSSIL_MACHINE_FINISHED = this.create("fossil_machine.finished")
val FOSSIL_MACHINE_FINISHED = this.create("fossilmachine.finished")
@JvmField
val FOSSIL_MACHINE_INSERT_DNA = this.create("fossil_machine.insert_dna")
val FOSSIL_MACHINE_INSERT_DNA = this.create("fossilmachine.insert_dna")
@JvmField
val FOSSIL_MACHINE_INSERT_DNA_SMALL = this.create("fossil_machine.insert_dna_small")
val FOSSIL_MACHINE_INSERT_DNA_SMALL = this.create("fossilmachine.insert_dna_small")
@JvmField
val FOSSIL_MACHINE_INSERT_FOSSIL = this.create("fossil_machine.insert_fossil")
val FOSSIL_MACHINE_INSERT_FOSSIL = this.create("fossilmachine.insert_fossil")
@JvmField
val FOSSIL_MACHINE_RETRIEVE_FOSSIL = this.create("fossil_machine.retrieve_fossil")
val FOSSIL_MACHINE_RETRIEVE_FOSSIL = this.create("fossilmachine.retrieve_fossil")
@JvmField
val FOSSIL_MACHINE_RETRIEVE_POKEMON = this.create("fossil_machine.retrieve_pokemon")
val FOSSIL_MACHINE_RETRIEVE_POKEMON = this.create("fossilmachine.retrieve_pokemon")
@JvmField
val FOSSIL_MACHINE_UNPROTECTED = this.create("fossil_machine.unprotected")
val FOSSIL_MACHINE_UNPROTECTED = this.create("fossilmachine.unprotected")
@JvmField
val COIN_POUCH_BREAK = this.create("coin_pouch.break")
@@ -155,21 +155,11 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, RegistryKey<Regi
@JvmField
val ENERGY_ROOT_PLACE = this.create("energy_root.place")
@JvmField
val VIVICHOKE_BREAK = this.create("vivichoke.break")
@JvmField
val VIVICHOKE_PLACE = this.create("vivichoke.place")
@JvmField
val MINT_BREAK = this.create("mint.break")
@JvmField
val MINT_PLACE = this.create("mint.place")
@JvmField
val REVIVAL_HERB_BREAK = this.create("revival_herb.break")
@JvmField
val REVIVAL_HERB_PLACE = this.create("revival_herb.place")
@JvmField
val MEDICINAL_LEEK_BREAK = this.create("medicinal_leek.break")
@JvmField
@@ -287,15 +277,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, RegistryKey<Regi
SoundEvents.BLOCK_GRASS_FALL
)
@JvmField
val VIVICHOKE_SOUNDS = BlockSoundGroup(1f, 1f,
VIVICHOKE_BREAK,
SoundEvents.BLOCK_GRASS_STEP,
VIVICHOKE_PLACE,
SoundEvents.BLOCK_GRASS_HIT,
SoundEvents.BLOCK_GRASS_FALL
)
@JvmField
val MINT_SOUNDS = BlockSoundGroup(1f, 1f,
MINT_BREAK,
@@ -305,15 +286,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, RegistryKey<Regi
SoundEvents.BLOCK_GRASS_FALL
)
@JvmField
val REVIVAL_HERB_SOUNDS = BlockSoundGroup(1f, 1f,
REVIVAL_HERB_BREAK,
SoundEvents.BLOCK_GRASS_STEP,
REVIVAL_HERB_PLACE,
SoundEvents.BLOCK_GRASS_HIT,
SoundEvents.BLOCK_GRASS_FALL
)
@JvmField
val GILDED_CHEST_SOUNDS = BlockSoundGroup(1f, 1f,
GILDED_CHEST_BREAK,
@@ -135,28 +135,31 @@ class BattleMessage(rawMessage: String) {
return this.actorAndActivePokemon(pnx, battle)
}
/*
Example:
|-curestatus|p1: a8e5710e-6835-4387-99d3-331b45f848e6|slp|[msg]
|-curestatus|p1: 0686b20d-3019-4167-92e2-786834245743|slp|[msg]
// Function used to grab BattlePokemon (As we need to be able to grab non active pokemon to cure their status)
// by using playerNumber(actorID) and pokemon uuid(pokemonID)
/**
* Queries an argument at the given [index] for a 'pnx' and uuid that will be parsed into a [BattlePokemon].
*
* @param index The index of the argument referencing the [BattlePokemon].
* @param battle The [PokemonBattle] being queried.
* @return The [BattlePokemon] if the argument exists and is successfully parsed; otherwise null.
*/
fun getBattlePokemon(index: Int, battle: PokemonBattle): BattlePokemon? {
val actorAndPokemonID = this.argumentAt(index)?.takeIf { it.length >= 2 }?.split(": ") ?: return null
if (actorAndPokemonID.count() < 2) return null
val actorID = actorAndPokemonID[0]
val pokemonID = actorAndPokemonID[1]
return this.getBattlePokemon(actorID, pokemonID, battle)
fun battlePokemon(index: Int, battle: PokemonBattle): BattlePokemon? {
val (actorID, pokemonID) = this.pnxAndUuid(index) ?: return null
return this.battlePokemon(actorID, pokemonID, battle)
}
fun getSourceBattlePokemon(battle: PokemonBattle): BattlePokemon? {
val sourcePokemonArgument = this.optionalArguments.get("of") ?: return null
val actorAndPokemonID = sourcePokemonArgument.takeIf { it.length >= 2 }?.split(": ") ?: return null
if (actorAndPokemonID.count() < 2) return null
val actorID = actorAndPokemonID[0]
val pokemonID = actorAndPokemonID[1]
return this.getBattlePokemon(actorID, pokemonID, battle)
/**
* Queries an optional argument identified by [optionalArg] for a 'pnx' and uuid that will be parsed into a [BattlePokemon].
*
* @param optionalArg The id of the optional argument referencing the [BattlePokemon].
* @param battle The [PokemonBattle] being queried.
* @return The [BattlePokemon] if the argument exists and is successfully parsed; otherwise null.
*/
fun battlePokemonFromOptional(battle: PokemonBattle, optionalArg: String = "of"): BattlePokemon? {
val optional = this.optionalArguments.get(optionalArg) ?: return null
val pokemonID = optional.takeIf { it.length >= 2 }?.split(":")?.takeIf { it.size == 2 } ?: return null
val pnx = pokemonID[0].takeIf { it.matches(PNX_MATCHER) || it.matches(PN_MATCHER) } ?: return null
val uuid = pokemonID[1].trim()
return this.battlePokemon(pnx, uuid, battle)
}
/**
@@ -234,7 +237,7 @@ class BattleMessage(rawMessage: String) {
null
}
private fun getBattlePokemon(pnx: String, pokemonID: String, battle: PokemonBattle): BattlePokemon? = try {
private fun battlePokemon(pnx: String, pokemonID: String, battle: PokemonBattle): BattlePokemon? = try {
battle.getBattlePokemon(pnx, pokemonID)
} catch (_: Exception) {
null
@@ -8,13 +8,11 @@
package com.cobblemon.mod.common.api.battles.model
import com.bedrockk.molang.runtime.MoLangEnvironment
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.Environment
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
@@ -22,8 +20,6 @@ import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
import com.cobblemon.mod.common.api.battles.model.actor.FleeableBattleActor
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunction
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.api.text.red
@@ -44,7 +40,6 @@ import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMusicPacket
import com.cobblemon.mod.common.pokemon.evolution.progress.DefeatEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.requirements.DefeatRequirement
@@ -107,6 +102,11 @@ open class PokemonBattle(
var turn: Int = 1
private set
private var ticks: Int = 0
set(value) { field = value.coerceAtMost(Int.MAX_VALUE) } // go outside
/** The current duration of the battle in seconds. */
val time: Int get() = ticks % 20
var dispatchResult = GO
val dispatches = ConcurrentLinkedDeque<BattleDispatch>()
@@ -166,7 +166,7 @@ open class PokemonBattle(
fun getActor(player: ServerPlayerEntity) = actors.firstOrNull { it.isForPlayer(player) }
/**
* Gets a BattleActor and an [ActiveBattlePokemon] from a pnx key, e.g. p2a
* Gets a [BattleActor] and an [ActiveBattlePokemon] from a pnx key, e.g. p2a
*
* Returns null if either the pn or x is invalid.
*/
@@ -179,6 +179,11 @@ open class PokemonBattle(
return actor to pokemon
}
/**
* Gets a [BattlePokemon] from a pnx key and uuid.
*
* Returns null if the pnx key is invalid or the uuid does not exist.
*/
fun getBattlePokemon(pnx: String, pokemonID: String): BattlePokemon {
val actor = actors.find { it.showdownId == pnx.substring(0, 2) }
?: throw IllegalStateException("Invalid pnx: $pnx - unknown actor")
@@ -390,8 +395,9 @@ open class PokemonBattle(
return
}
if (started && isPvW && !ended && dispatches.isEmpty()) {
checkFlee()
if (started) {
ticks++
if (isPvW && !ended && dispatches.isEmpty()) checkFlee()
}
}
@@ -0,0 +1,89 @@
package com.cobblemon.mod.common.api.entity.pokemon
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.effects.IllusionEffect
import com.cobblemon.mod.common.entity.pokemon.effects.TransformEffect
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import net.minecraft.nbt.NbtCompound
import java.util.concurrent.CompletableFuture
import kotlin.reflect.KClass
/**
* Represents a temporary alteration to how a [PokemonEntity] is rendered and behaves. May include a temporary visual effect
* when applied.
*
* @author Segfault Guy
* @since March 5th, 2024
*/
interface EntityEffect {
/**
* Starts this effect for the provided [PokemonEntity].
*
* @return A [CompletableFuture] that completes after the effect has been applied. Or null if the effect failed to start.
*/
fun start(entity: PokemonEntity): CompletableFuture<PokemonEntity>?
/**
* Ends this effect for the provided [PokemonEntity].
*
* @return A [CompletableFuture] that completes after the effect has been reverted. Or null if the effect failed to end.
*/
fun end(entity: PokemonEntity): CompletableFuture<PokemonEntity>?
/** Saves this effect to NBT. */
fun saveToNbt(): NbtCompound
/** Loads this effect from NBT. */
fun loadFromNBT(nbt: NbtCompound)
companion object {
private val effects = mutableMapOf<String, KClass<out EntityEffect>>()
private val defaults = mutableMapOf<String, () -> EntityEffect>()
init {
register(IllusionEffect.ID, IllusionEffect::class, ::IllusionEffect)
register(TransformEffect.ID, TransformEffect::class, ::TransformEffect)
}
fun <T : EntityEffect> register(id: String, type: KClass<T>, default: () -> T) {
effects[id] = type
defaults[id] = default
}
fun createDefault(id: String): EntityEffect? = defaults[id]?.invoke()
fun loadFromNbt(nbt: NbtCompound): EntityEffect? {
if (nbt.contains(DataKeys.ENTITY_EFFECT_ID)) {
val id = nbt.getString(DataKeys.ENTITY_EFFECT_ID)
return createDefault(id)?.also { it.loadFromNBT(nbt) }
}
return null
}
}
}
/** An [EntityEffect] that modifies the dimensions of a [PokemonEntity]. */
interface PhysicalEffect : EntityEffect {
val scale: Float
}
/** An [EntityEffect] that alters the physical appearance of a [PokemonEntity] to match a [mock]. */
interface MocKEffect : PhysicalEffect {
val mock: PokemonProperties
val exposedSpecies: Species?
get() = this.mock.species?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
val exposedForm: FormData?
get() = this.mock.form?.let {
formID -> this.exposedSpecies?.forms?.firstOrNull { it.formOnlyShowdownId().equals(formID, true) } }
?: this.exposedSpecies?.standardForm
}
@@ -35,8 +35,8 @@ abstract class MultiblockBlock(properties: Settings) : BlockWithEntity(propertie
itemStack: ItemStack?
) {
if (world is ServerWorld) {
val multiblockEntity = world.getBlockEntity(pos) as? MultiblockEntity
multiblockEntity?.multiblockBuilder?.validate(world)
val multiblockEntity = world.getBlockEntity(pos) as MultiblockEntity
multiblockEntity.multiblockBuilder?.validate(world)
}
super.onPlaced(world, pos, state, placer, itemStack)
}
@@ -52,8 +52,8 @@ abstract class MultiblockBlock(properties: Settings) : BlockWithEntity(propertie
if(hand == Hand.OFF_HAND) {
return ActionResult.SUCCESS
}
val entity = world.getBlockEntity(pos) as MultiblockEntity?
if (entity?.multiblockStructure != null) {
val entity = world.getBlockEntity(pos) as MultiblockEntity
if (entity.multiblockStructure != null) {
return entity.multiblockStructure!!.onUse(state, world, pos, player, hand, hit)
}
return super.onUse(state, world, pos, player, hand, hit)
@@ -397,7 +397,7 @@ open class PokemonProperties {
return false
}
}
nickname?.takeIf { it.string != pokemon.nickname?.string }?.let { return false }
nickname?.takeIf { it != pokemon.nickname }?.let { return false }
form?.takeIf { !it.equals(pokemon.form.name, true) }?.let { return false }
friendship?.takeIf { it != pokemon.friendship }?.let { return false }
pokeball?.takeIf { it != pokemon.caughtBall.name.toString() }?.let { return false }
@@ -437,7 +437,7 @@ open class PokemonProperties {
}
} catch (_: InvalidIdentifierException) {}
}
nickname?.takeIf { it.string != properties.nickname?.string }?.let { return false }
nickname?.takeIf { it != properties.nickname }?.let { return false }
form?.takeIf { !it.equals(properties.form, true) }?.let { return false }
friendship?.takeIf { it != properties.friendship }?.let { return false }
pokeball?.takeIf { it != properties.pokeball }?.let { return false }
@@ -48,6 +48,11 @@ fun interface PokemonPropertyExtractor {
@JvmField
val STATUS = add { pokemon, properties -> properties.status = pokemon.status?.status?.showdownName }
@JvmField
val ILLUSION = mutableListOf(SPECIES, FORM, ASPECTS, GENDER, NICKNAME, SHINY)
@JvmField
val TRANSFORM = mutableListOf(SPECIES, FORM, ASPECTS, GENDER)
fun add(extractor: PokemonPropertyExtractor): PokemonPropertyExtractor {
ALL.add(extractor)
return extractor
@@ -49,6 +49,25 @@ enum class Stats(override val identifier: Identifier, override val displayName:
*/
val BATTLE_ONLY: Set<Stat> = EnumSet.of(EVASION, ACCURACY)
/** Gets the [Stat] from the respective Showdown id. */
fun getStat(statKey: String) = when(statKey) {
"atk" -> ATTACK
"def" -> DEFENCE
"spa" -> SPECIAL_ATTACK
"spd" -> SPECIAL_DEFENCE
"spe" -> SPEED
"evasion" -> EVASION
else -> ACCURACY
}
/** Gets the severity lang key from the respective boost/unboost stage. */
fun getSeverity(stages: Int) = when(stages) {
0 -> "cap.single"
1 -> "slight"
2 -> "sharp"
else -> "severe"
}
}
}
@@ -12,8 +12,6 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawnerFactory
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import java.util.UUID
import net.minecraft.server.network.ServerPlayerEntity
@@ -36,7 +34,7 @@ object CobblemonWorldSpawnerManager : SpawnerManager() {
fun onPlayerLogin(player: ServerPlayerEntity) {
// Disables spawning
if (!Cobblemon.config.enableSpawning || server()?.gameRules?.getBoolean(CobblemonGameRules.DO_POKEMON_SPAWNING) == false) {
if (!Cobblemon.config.enableSpawning) {
return;
}
@@ -12,8 +12,6 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.TickingSpawner
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules.DO_POKEMON_SPAWNING
/**
* A manager of various spawners. This is a class in which you should register
@@ -51,7 +49,7 @@ open class SpawnerManager {
open fun onServerTick() {
// Disables spawning
if (!Cobblemon.config.enableSpawning || server()?.gameRules?.getBoolean(DO_POKEMON_SPAWNING) == false) {
if (!Cobblemon.config.enableSpawning) {
return
}
influences.removeIf { it.isExpired() }
@@ -63,6 +63,9 @@ abstract class AreaSpawner(
override fun run(cause: SpawnCause): Pair<SpawningContext, SpawnDetail>? {
val area = getArea(cause)
if (area?.world?.gameRules?.getBoolean(DO_POKEMON_SPAWNING) == false) {
return null
}
val constrainedArea = if (area != null) constrainArea(area) else null
if (constrainedArea != null) {
@@ -8,12 +8,12 @@
package com.cobblemon.mod.common.api.starter
import com.cobblemon.mod.common.config.starter.StarterCategory
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
interface StarterHandler {
fun getStarters(player: ServerPlayerEntity): Map<Identifier, StarterCategory>
fun getStarterList(player: ServerPlayerEntity): List<StarterCategory>
fun handleJoin(player: ServerPlayerEntity)
fun requestStarterChoice(player: ServerPlayerEntity)
fun chooseStarter(player: ServerPlayerEntity, categoryId: Identifier, index: Int)
fun chooseStarter(player: ServerPlayerEntity, categoryName: String, index: Int)
}
@@ -1,69 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.starter
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
import com.cobblemon.mod.common.util.adapters.PlainJsonTextAdapter
import com.cobblemon.mod.common.util.adapters.pokemonPropertiesShortAdapter
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
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.resource.ResourceType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.Text
import net.minecraft.util.Identifier
/**
* The registry for starter Pokémon.
*
* The data here is used by the [CobblemonStarterHandler].
*
* 3rd party is not enforced to respect this registry.
*/
object StarterRegistry : JsonDataRegistry<StarterCategory> {
override val gson: Gson = GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
.registerTypeAdapter(Text::class.java, PlainJsonTextAdapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.create()
override val typeToken: TypeToken<StarterCategory> = TypeToken.get(StarterCategory::class.java)
override val id: Identifier = cobblemonResource("starter")
override val resourcePath: String = id.path
override val type: ResourceType = ResourceType.SERVER_DATA
override val observable: SimpleObservable<out DataRegistry> = SimpleObservable()
private val categories = hashMapOf<Identifier, StarterCategory>()
// We let the default starter handler sync the client
// This is to prevent unnecessary packets depending on impl
override fun sync(player: ServerPlayerEntity) {}
override fun reload(data: Map<Identifier, StarterCategory>) {
this.categories.clear()
this.categories += data
Cobblemon.LOGGER.info("Loaded {} starters ({} categories)", this.categories.values.sumOf { it.pokemon.size }, this.categories.size)
}
fun all(): Map<Identifier, StarterCategory> = this.categories
fun keys(): Set<Identifier> = this.categories.keys
fun categories(): Collection<StarterCategory> = this.categories.values
fun categoryOf(key: Identifier): StarterCategory? = this.categories[key]
fun categoryOf(key: String): StarterCategory? = this.categories[key.asIdentifierDefaultingNamespace()]
}
@@ -260,7 +260,7 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
return@mapNotNull if (clone) {
BattlePokemon.safeCopyOf(it)
} else {
BattlePokemon(it)
BattlePokemon.playerOwned(it)
}
}.sortedBy { if (it.uuid == leadingPokemon) 0 else (indexOf(it.originalPokemon) + 1) }
@@ -37,7 +37,7 @@ data class PlayerData(
fun defaultData(forPlayer: UUID) = PlayerData(
uuid = forPlayer,
starterPrompted = false,
starterLocked = !Cobblemon.config.allowStarterOnJoin,
starterLocked = !Cobblemon.starterConfig.allowStarterOnJoin,
starterSelected = false,
starterUUID = null,
keyItems = mutableSetOf(),
@@ -17,6 +17,8 @@ class ActiveBattlePokemon(val actor: BattleActor, var battlePokemon: BattlePokem
fun getSide() = actor.getSide()
var position: Pair<ServerWorld, Vec3d>? = null
var illusion: BattlePokemon? = null
override fun getAllActivePokemon() = battle.activePokemon
override fun getActorShowdownId() = actor.showdownId
override fun getActorPokemon() = actor.activePokemon
File diff suppressed because it is too large. Load diff
@@ -19,23 +19,23 @@ import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.util.battleLang
/**
* Format:
* |-ability|POKEMON|ABILITY|(from)EFFECT
* Format: |-ability|POKEMON|ABILITY|(from)EFFECT
*
* The ABILITY of the POKEMON has been changed due to a move/ability EFFECT.
* The ABILITY of POKEMON has been changed due to a move/ability EFFECT.
*
* Format:
* |-ability|POKEMON|ABILITY
* Alt format: |-ability|POKEMON|ABILITY
*
* POKEMON has just switched-in, and its ability ABILITY is being announced to have a long-term effect.
* POKEMON has just switched-in, and its ABILITY is being announced to have a long-term effect.
* @author Xylopia
* @since January 31, 2023
*/
class AbilityInstruction(val instructionSet: InstructionSet, val message: BattleMessage) : InterpreterInstruction, CauserInstruction {
override fun invoke(battle: PokemonBattle) {
val pokemon = message.getBattlePokemon(0, battle) ?: return
val pokemon = message.battlePokemon(0, battle) ?: return
val pokemonName = pokemon.getName()
val effect = message.effectAt(1) ?: return
val optionalEffect = message.effect()
val optionalPokemon = message.getSourceBattlePokemon(battle)
val optionalPokemon = message.battlePokemonFromOptional(battle)
val optionalPokemonName = optionalPokemon?.getName()
// If there is an optional effect causing the activation, broadcast that instead of the standard effect
@@ -20,16 +20,19 @@ import com.cobblemon.mod.common.util.battleLang
import net.minecraft.text.Text
/**
* Format:
* |-activate|POKEMON|EFFECT
* Format: |-activate|POKEMON|EFFECT
*
* A miscellaneous effect has activated.This is triggered whenever an effect could not be better described by one of the other minor messages.
* A miscellaneous EFFECT has activated for POKEMON. This is triggered whenever an effect could not be better described
* by one of the other minor messages.
*
* @author Hunter
* @since September 25, 2022
*/
class ActivateInstruction(val instructionSet: InstructionSet, val message: BattleMessage) : InterpreterInstruction, CauserInstruction {
override fun invoke(battle: PokemonBattle) {
val pokemon = message.getBattlePokemon(0, battle) ?: return
val pokemon = message.battlePokemon(0, battle) ?: return
val pokemonName = pokemon.getName()
val sourceName = message.getSourceBattlePokemon(battle)?.getName() ?: Text.literal("UNKNOWN")
val sourceName = message.battlePokemonFromOptional(battle)?.getName() ?: Text.literal("UNKNOWN")
val effect = message.effectAt(1) ?: return
val extraEffect = message.effectAt(2)?.typelessData ?: Text.literal("UNKNOWN")
ShowdownInterpreter.broadcastOptionalAbility(battle, effect, pokemonName)
@@ -0,0 +1,29 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |bagitem|POKEMON|ITEM
*
* POKEMON had ITEM used on it from the 'bag'.
* @author landonjw
* @since July 31, 2023
*/
class BagItemInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchGo {
val pokemon = message.pokemonByUuid(0, battle)!!
val item = message.argumentAt(1)!!
val ownerName = pokemon.actor.getName()
val itemName = item.asTranslated()
battle.broadcastChatMessage(battleLang("bagitem.use", ownerName, itemName, pokemon.getName()))
}
}
}
@@ -0,0 +1,30 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-block|POKEMON|EFFECT|MOVE|ATTACKER|(of)SOURCE
*
* An effect targeted at POKEMON was blocked by EFFECT. This may optionally specify that the effect was a MOVE from ATTACKER.
*
* An optional SOURCE will note the owner of the EFFECT, in the case that it's not EFFECT (for instance, an ally with Aroma Veil.)
* @author jeffw773
* @since November 6, 2023
*/
class BlockInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(1.5F) {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val pokemonName = pokemon.getName()
val effectID = message.effectAt(1)?.id ?: return@dispatchWaiting
val lang = battleLang("block.$effectID", pokemonName)
battle.broadcastChatMessage(lang)
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,44 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-boost|POKEMON|STAT|AMOUNT or |-unboost|POKEMON|STAT|AMOUNT
*
* POKEMON has gained or lost AMOUNT in STAT, using the standard rules for stat changes in-battle.
* STAT is a standard three-letter abbreviation fot the stat in question.
* @author Hiroku
* @since August 20, 2022
*/
class BoostInstruction(val instructionSet: InstructionSet, val message: BattleMessage, val remainingLines: Iterator<BattleMessage>, val isBoost: Boolean = true): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val pokemon = message.battlePokemon(0, battle) ?: return
val statKey = message.argumentAt(1) ?: return
val stages = message.argumentAt(2)?.toInt() ?: return
val stat = Stats.getStat(statKey).displayName
val severity = Stats.getSeverity(stages)
val rootKey = if (isBoost) "boost" else "unboost"
battle.dispatchWaiting(1.5F) {
val lang = when {
message.hasOptionalArgument("zeffect") -> battleLang("$rootKey.$severity.zeffect", pokemon.getName(), stat)
else -> battleLang("$rootKey.$severity", pokemon.getName(), stat)
}
battle.broadcastChatMessage(lang)
val boostBucket = if (isBoost) BattleContext.Type.BOOST else BattleContext.Type.UNBOOST
val context = ShowdownInterpreter.getContextFromAction(message, boostBucket, battle)
// TODO: replace with context that tracks detailed information such as # of stages
repeat(stages) { pokemon.contextManager.add(context) }
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,43 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.lang
/**
* Formats: |cant|POKEMON|REASON and |cant|POKEMON|REASON|MOVE
*
* The POKEMON could not perform a move because of the indicated REASON.
* @author Deltric
* @since January 22, 2022
*/
class CantInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val effectID = message.effectAt(1)?.id ?: return@dispatchWaiting
val name = pokemon.getName()
// Move may be null as it's not always given
val moveName = message.moveAt(2)?.displayName ?: run { "(Unrecognized: ${message.argumentAt(2)})".text() }
val lang = when (effectID) {
// TODO: in the games they use a generic image because there is a popup of the ability and the sprite of the mon, it may be good to have a similar system here
"armortail", "damp", "dazzling", "queenlymajesty" -> battleLang("cant.generic", name, moveName)
"par", "slp", "frz" -> {
val status = Statuses.getStatus(effectID)?.name?.path ?: return@dispatchWaiting
lang("status.$status.is", name)
}
else -> battleLang("cant.$effectID", name, moveName)
}
battle.broadcastChatMessage(lang.red())
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,26 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-clearallboost|
*
* Clears all boosts from all Pokémon on both sides.
* @author Segfault Guy
* @since April 26, 2023
*/
class ClearAllBoostInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(1.5F) {
battle.activePokemon.forEach {
it.battlePokemon?.contextManager?.clear(BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
}
battle.broadcastChatMessage(battleLang("clearallboost"))
}
}
}
@@ -0,0 +1,34 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-clearnegativeboost|POKEMON
*
* Clear the negative boosts from the target POKEMON (usually as the result of a zeffect).
* @author Segfault Guy
* @since September 10, 2023
*/
class ClearNegativeBoostInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val battlePokemon = message.battlePokemon(0, battle) ?: return
val pokemonName = battlePokemon.getName()
battle.dispatchWaiting(1.5F) {
val lang = when {
message.hasOptionalArgument("zeffect") -> battleLang("clearallnegativeboost.zeffect", pokemonName)
else -> battleLang("clearallnegativeboost", pokemonName)
}
if (!message.hasOptionalArgument("silent")) {
battle.broadcastChatMessage(lang)
}
battlePokemon.contextManager.clear(BattleContext.Type.UNBOOST)
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
}
@@ -0,0 +1,31 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-copyboost|SOURCE|TARGET|(from)EFFECT
*
* Copies any stat changes from the TARGET Pokémon to the SOURCE Pokémon due to EFFECT.
* @author JMMCP
* @since November 27, 2023
*/
class CopyBoostInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val pokemonName = pokemon.getName()
val targetPokemon = message.battlePokemon(1, battle) ?: return@dispatchWaiting
val targetPokemonName = targetPokemon.getName()
val lang = battleLang("copyboost.generic", pokemonName, targetPokemonName)
battle.broadcastChatMessage(lang)
pokemon.contextManager.copy(targetPokemon.contextManager, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,34 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-crit|POKEMON
*
* POKEMON received a critical hit.
* @author Hunter
* @since August 18, 2022
*/
class CritInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchGo {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchGo
battle.broadcastChatMessage(battleLang("crit").yellow())
ShowdownInterpreter.lastCauser[battle.battleId]?.let { message ->
val battlePokemon = message.battlePokemon(0, battle) ?: return@let
if (LastBattleCriticalHitsEvolutionProgress.supports(battlePokemon.effectedPokemon)) {
val progress = battlePokemon.effectedPokemon.evolutionProxy.current().progressFirstOrCreate({ it is LastBattleCriticalHitsEvolutionProgress }) { LastBattleCriticalHitsEvolutionProgress() }
progress.updateProgress(LastBattleCriticalHitsEvolutionProgress.Progress(progress.currentProgress().amount + 1))
}
}
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,50 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.interpreter.Effect
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.net.messages.client.battle.BattlePersistentStatusPacket
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-curestatus|POKEMON|STATUS
*
* The POKEMON has recovered from its STATUS.
* @author Hiroku
* @since November 5, 2022
*/
class CureStatusInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val maybeActivePokemon = message.actorAndActivePokemon(0, battle)?.second?.battlePokemon
val maybePartyPokemon = message.battlePokemon(0, battle)
val pokemon = maybeActivePokemon ?: maybePartyPokemon ?: return
val pokemonName = pokemon.getName()
val status = message.argumentAt(1)?.let(Statuses::getStatus) ?: return
val effect = message.effect()
ShowdownInterpreter.broadcastOptionalAbility(battle, effect, pokemonName)
battle.dispatchWaiting {
pokemon.effectedPokemon.status = null
pokemon.sendUpdate()
if (maybeActivePokemon != null) {
message.pnxAndUuid(0)?.let {
battle.sendUpdate(BattlePersistentStatusPacket(it.first, null))
}
}
val lang = when (effect?.type) {
Effect.Type.ABILITY -> battleLang("curestatus.${effect.id}", pokemonName)
else -> status.removeMessage.asTranslated(pokemonName)
}
battle.broadcastChatMessage(lang)
pokemon.contextManager.remove(status.showdownName, BattleContext.Type.STATUS)
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.net.messages.client.animation.PlayPoseableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
@@ -30,6 +31,13 @@ import java.util.concurrent.CompletableFuture
import kotlin.math.roundToInt
import net.minecraft.text.Text
/**
* Format: |-damage|POKEMON|HP STATUS
*
* POKEMON has taken damage and is now at HP STATUS
* @author Hiroku
* @since March 11, 2022
*/
class DamageInstruction(
val instructionSet: InstructionSet,
val actor: BattleActor,
@@ -37,10 +45,10 @@ class DamageInstruction(
val privateMessage: BattleMessage
) : InterpreterInstruction {
var future = CompletableFuture.completedFuture(Unit)
val battlePokemon = publicMessage.getBattlePokemon(0, actor.battle)
val expectedTarget = publicMessage.battlePokemon(0, actor.battle)
override fun invoke(battle: PokemonBattle) {
battlePokemon ?: return
val battlePokemon = publicMessage.battlePokemon(0, actor.battle) ?: return
val recoiling = privateMessage.optionalArgument("from")?.equals("recoil", true) == true
val lastCauser = instructionSet.getMostRecentCauser(comparedTo = this)
if (recoiling) {
@@ -59,7 +67,7 @@ class DamageInstruction(
val newHealth = privateMessage.argumentAt(1)?.split(" ")?.get(0) ?: return
val effect = privateMessage.effect()
val pokemonName = battlePokemon.getName()
val sourceName = privateMessage.getSourceBattlePokemon(battle)?.getName() ?: Text.literal("UNKNOWN")
val sourceName = privateMessage.battlePokemonFromOptional(battle)?.getName() ?: Text.literal("UNKNOWN")
var causedFaint = newHealth == "0"
battle.dispatch {
@@ -1,19 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
class DeprecatedInstruction(val message: BattleMessage, val function: (PokemonBattle, BattleMessage, MutableList<String>) -> Unit) : InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
function(battle, message, mutableListOf())
}
}
@@ -1,25 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
class DeprecatedSplitInstruction(
val battleActor: BattleActor,
val publicMessage: BattleMessage,
val privateMessage: BattleMessage,
val function: (PokemonBattle, BattleActor, BattleMessage, BattleMessage) -> Unit
) : InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
function(battle, battleActor, publicMessage, privateMessage)
}
}
@@ -0,0 +1,26 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |detailschange|POKEMON|DETAILS|HP STATUS
*
* POKEMON has changed formes permanently (i.e. Mega Evolution) to DETAILS.
* @author Segfault Guy
* @since September 10, 2023
*/
class DetailsChangeInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val battlePokemon = message.battlePokemon(0, battle) ?: return
val pokemonName = battlePokemon.getName()
val formName = message.argumentAt(1)?.split(',')?.get(0)?.substringAfter('-')?.lowercase() ?: return
battle.dispatchWaiting {
battle.broadcastChatMessage(battleLang("detailschange.$formName", pokemonName))
battle.majorBattleActions[battlePokemon.uuid] = message
}
}
}
@@ -0,0 +1,50 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
import com.cobblemon.mod.common.battles.dispatch.BattleDispatch
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |drag|POKEMON|DETAILS|HP STATUS
*
* POKEMON has switched in (if there was an old Pokémon in that position, it is switched out).
* POKEMON|DETAILS represents all the information that can be used to tell Pokémon apart.
* The switched Pokémon has HP health points and STATUS status.
* @author
*/
class DragInstruction(val instructionSet: InstructionSet, val battleActor: BattleActor, val publicMessage: BattleMessage, val privateMessage: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchInsert {
val (pnx, _) = publicMessage.pnxAndUuid(0)!!
val (_, activePokemon) = battle.getActorAndActiveSlotFromPNX(pnx)
val illusion = publicMessage.battlePokemonFromOptional(battle , "is")
val pokemon = publicMessage.battlePokemon(0, battle) ?: return@dispatchInsert emptySet()
battle.broadcastChatMessage(battleLang("dragged_out", pokemon.getName()))
activePokemon.battlePokemon?.let { oldPokemon ->
oldPokemon.contextManager.clear(BattleContext.Type.VOLATILE, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
battle.majorBattleActions[oldPokemon.uuid] = publicMessage
}
battle.majorBattleActions[pokemon.uuid] = publicMessage
val entity = if (battleActor is EntityBackedBattleActor<*>) battleActor.entity else null
setOf(
BattleDispatch {
if (entity != null) {
SwitchInstruction.createEntitySwitch(battle, battleActor, entity, pnx, activePokemon, pokemon, illusion)
} else {
SwitchInstruction.createNonEntitySwitch(battle, battleActor, pnx, activePokemon, pokemon, illusion)
}
}
)
}
}
}
@@ -0,0 +1,27 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-endability|POKEMON
*
* The POKEMON's Ability was suppressed.
* @author jeffw773
* @since November 28, 2023
*/
class EndAbilityInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(1.5F) {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val pokemonName = pokemon.getName()
val lang = battleLang("endability", pokemonName)
battle.broadcastChatMessage(lang)
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,36 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.lang
/**
* Format: |-end|POKEMON|EFFECT
*
* The volatile status from EFFECT inflicted on POKEMON has ended.
* @author Hiroku
* @since October 3, 2022
*/
class EndInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val pokemonName = pokemon.getName()
val effectID = message.effectAt(1)?.id ?: return@dispatchWaiting
if (!message.hasOptionalArgument("silent")) {
val lang = when (effectID) {
"yawn" -> lang("status.sleep.apply", pokemonName)
else -> battleLang("end.$effectID", pokemonName)
}
battle.broadcastChatMessage(lang)
}
pokemon.contextManager.remove(effectID, BattleContext.Type.VOLATILE)
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,37 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-enditem|POKEMON|ITEM|(from)EFFECT
*
* ITEM held by POKEMON has been destroyed by a move or ability EFFECT, and it now holds no item.
*
* Alt format: |-enditem|POKEMON|ITEM
*
* POKEMON's ITEM has destroyed itself (consumed or used).
* @author Licious
* @since December 30, 2022
*/
class EndItemInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchGo {
// All logic regarding broadcasting battle messages is handled in CobblemonHeldItemManager
val battlePokemon = message.battlePokemon(0, battle) ?: return@dispatchGo
val itemEffect = message.effectAt(1) ?: return@dispatchGo
battlePokemon.heldItemManager.handleEndInstruction(battlePokemon, battle, message)
battle.minorBattleActions[battlePokemon.uuid] = message
battlePokemon.contextManager.remove(itemEffect.id, BattleContext.Type.ITEM)
if (message.hasOptionalArgument("eat")) {
battlePokemon.entity?.playSound(CobblemonSounds.BERRY_EAT, 1F, 1F)
}
}
}
}
@@ -0,0 +1,35 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.net.messages.client.battle.BattleMadeInvalidChoicePacket
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |error|(Invalid choice) ERROR
*
* The actor needs to send a different decision due to ERROR.
* @author Yaseen
* @since April 22, 2023
*/
class ErrorInstruction(val battleActor: BattleActor, val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.log("Error Instruction")
battle.dispatchGo {
//TODO: some lang stuff for the error messages (Whats the protocol for adding to other langs )
//Also is it okay to ignore the team preview error for now? - You bet!
val lang = when(message.rawMessage) {
"|error|[Unavailable choice] Can't switch: The active Pokémon is trapped" -> battleLang("error.pokemon_is_trapped").red()
"|error|[Invalid choice] Can't choose for Team Preview: You're not in a Team Preview phase" -> return@dispatchGo
else -> battle.createUnimplemented(message)
}
battleActor.sendMessage(lang)
battleActor.mustChoose = true
battleActor.sendUpdate(BattleMadeInvalidChoicePacket())
}
}
}
@@ -0,0 +1,48 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-fail|POKEMON|ACTION
*
* ACTION has failed against POKEMON.
* @author Hunter
* @since September 25, 2022
*/
class FailInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(1.5F){
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val pokemonName = pokemon.getName()
val effectID = message.effectAt(1)?.id
val cause = message.effect("from")
val of = message.battlePokemonFromOptional(battle)
val lang = when (effectID) {
null, "burnup", "doubleshock" -> battleLang("fail") // Moves that use default fail lang. (Null included for moves that fail with no effect, for example: Baton Pass.)
"shedtail" -> battleLang("fail.substitute", pokemonName)
"hyperspacefury", "aurawheel" -> battleLang("fail.darkvoid", pokemonName) // Moves that can only be used by one species and fail when any others try
"corrosivegas" -> battleLang("fail.healblock", pokemonName)
"dynamax" -> battleLang("fail.grassknot", pokemonName) // Covers weight moves that fail against dynamaxed Pokémon
"unboost" -> {
val statKey = message.argumentAt(2)
val stat = statKey?.let { Stats.getStat(it).displayName }
if (stat != null) {
battleLang("fail.$effectID.single", pokemonName, stat)
} else {
battleLang("fail.$effectID", pokemonName)
}
}
else -> battleLang("fail.$effectID", pokemonName)
}
battle.broadcastChatMessage(lang.red())
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -19,9 +19,16 @@ import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.net.messages.client.battle.BattleFaintPacket
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |faint|POKEMON
*
* POKEMON has fainted.
* @author Deltric
* @since January 22, 2022
*/
class FaintInstruction(battle: PokemonBattle, val message: BattleMessage) : InterpreterInstruction {
var waitTime = 2.5F
val faintingPokemon = message.getBattlePokemon(0, battle)!!
val faintingPokemon = message.battlePokemon(0, battle)!!
override fun invoke(battle: PokemonBattle) {
val faintMessage = battleLang("fainted", faintingPokemon.getName()).red()
@@ -0,0 +1,33 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-fieldactivate|EFFECT
*
* A miscellaneous EFFECT has activated for the entire field.
* @author Segfault Guy
* @since April 5, 2023
*/
class FieldActivateInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(2.5F) {
val effectID = message.effectAt(0)?.id ?: return@dispatchWaiting
val lang = battleLang("fieldactivate.$effectID")
battle.broadcastChatMessage(lang.red())
// share this action with all active Pokemon
battle.activePokemon.forEach {
it.battlePokemon?.contextManager?.addUnique(ShowdownInterpreter.getContextFromAction(message, BattleContext.Type.VOLATILE, battle))
}
}
}
}
@@ -0,0 +1,31 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-fieldend|CONDITION
*
* The field CONDITION has ended.
* @author Licious
* @since February 8, 2023
*/
class FieldEndInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(1.5F) {
val effect = message.effectAt(0) ?: return@dispatchWaiting
val lang = battleLang("fieldend.${effect.id}")
battle.broadcastChatMessage(lang)
val type = BattleContext.Type.valueOf(effect.rawData.substringAfterLast(" ").uppercase())
battle.contextManager.remove(effect.id, type)
}
}
}
@@ -0,0 +1,34 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
import net.minecraft.text.Text
/**
* Format: |-fieldstart|CONDITION
*
* The field CONDITION has started.
* @author Xylopia
* @since January 31, 2023
*/
class FieldStartInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val effect = message.effectAt(0) ?: return
val user = message.battlePokemonFromOptional(battle)?.getName() ?: Text.literal("UNKNOWN")
ShowdownInterpreter.broadcastOptionalAbility(battle, effect, user)
battle.dispatchWaiting(1.5F) {
// Note persistent is a CAP ability only we can ignore the flag
val lang = battleLang("fieldstart.${effect.id}", user)
battle.broadcastChatMessage(lang)
val type = BattleContext.Type.valueOf(effect.rawData.substringAfterLast(" ").uppercase())
battle.contextManager.add(ShowdownInterpreter.getContextFromAction(message, type, battle))
}
}
}
@@ -0,0 +1,91 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.interpreter.Effect
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.data.ShowdownIdentifiable
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattlePersistentStatusPacket
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-heal|POKEMON|HP STATUS
*
* POKEMON has healed damage and is now at HP STATUS.
* @author Licious
* @since February 6, 2023
*/
class HealInstruction(val actor: BattleActor, val publicMessage: BattleMessage, val privateMessage: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val pnx = privateMessage.pnxAndUuid(0)?.first
val battlePokemon = privateMessage.battlePokemon(0, battle) ?: return
val rawHpAndStatus = privateMessage.argumentAt(1)?.split(" ") ?: return
val rawHpRatio = rawHpAndStatus.getOrNull(0) ?: return
val newHealth = rawHpRatio.split("/").map { it.toFloatOrNull() ?: return }
val newHealthRatio = rawHpRatio.split("/").map { it.toFloatOrNull()?.div(newHealth[1]) ?: return }
val effect = privateMessage.effect()
val pokemonName = battlePokemon.getName()
ShowdownInterpreter.broadcastOptionalAbility(battle, effect, pokemonName)
battle.dispatchWaiting {
if (pnx != null) {
// dynamax changes max health
battle.sendSidedUpdate(actor, BattleHealthChangePacket(pnx, newHealth[0], newHealth[1]), BattleHealthChangePacket(pnx, newHealthRatio[0]))
}
val silent = privateMessage.hasOptionalArgument("silent")
if (!silent) {
val lang = when {
privateMessage.hasOptionalArgument("zeffect") -> battleLang("heal.zeffect", battlePokemon.getName())
privateMessage.hasOptionalArgument("wisher") -> {
val name = privateMessage.optionalArgument("wisher")!!
val showdownId = name.lowercase().replace(ShowdownIdentifiable.REGEX, "")
val wisher = actor.pokemonList.firstOrNull { it.effectedPokemon.showdownId() == showdownId }
// If no Pokémon is found this is a nickname
battleLang("heal.wish", wisher?.getName() ?: actor.nameOwned(name))
}
privateMessage.hasOptionalArgument("from") -> {
when (effect!!.type) {
Effect.Type.ITEM -> when (effect.id) {
"leftovers", "shellbell", "blacksludge" -> battleLang("heal.leftovers", battlePokemon.getName(), effect.typelessData)
else -> battleLang("heal.item", battlePokemon.getName(), effect.typelessData)
}
else -> when (effect.id) {
"drain" -> {
val drained = privateMessage.battlePokemonFromOptional(battle) ?: return@dispatchWaiting
battleLang("heal.drain", drained.getName())
}
else -> battleLang("heal.${effect.id}", battlePokemon.getName())
}
}
}
else -> {
battleLang("heal.generic", battlePokemon.getName())
}
}
battle.broadcastChatMessage(lang)
}
battle.minorBattleActions[battlePokemon.uuid] = privateMessage
battlePokemon.effectedPokemon.currentHealth = newHealth[0].toInt()
// This part is not always present
val rawStatus = rawHpAndStatus.getOrNull(1) ?: return@dispatchWaiting
val status = Statuses.getStatus(rawStatus) ?: return@dispatchWaiting
if (status is PersistentStatus && battlePokemon.effectedPokemon.status?.status != status) {
battlePokemon.effectedPokemon.applyStatus(status)
if (pnx != null) {
battle.sendUpdate(BattlePersistentStatusPacket(pnx, status))
}
if (!silent) {
status.applyMessage.let { battle.broadcastChatMessage(it.asTranslated(battlePokemon.getName())) }
}
}
}
}
}
@@ -0,0 +1,26 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-hitcount|POKEMON|NUM
*
* A multi-hit move hit the POKEMON NUM times.
* @author Licious
* @since December 30, 2022
*/
class HitCountInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchGo {
val battlePokemon = message.battlePokemon(0, battle) ?: return@dispatchGo
val hitCount = message.argumentAt(1)?.toIntOrNull() ?: return@dispatchGo
val lang = if (hitCount == 1) battleLang("hit_count_singular") else battleLang("hit_count", hitCount)
battle.minorBattleActions[battlePokemon.uuid] = message
battle.broadcastChatMessage(lang)
}
}
}
@@ -11,6 +11,12 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
/**
* Catch-all for instruction from Showdown that are not parsed.
*
* @author Hiroku
* @since December 25, 2023
*/
class IgnoredInstruction : InterpreterInstruction {
override fun invoke(pokemonBattle: PokemonBattle) = Unit
}
@@ -0,0 +1,26 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-immune|POKEMON
*
* POKEMON was immune to a move.
* @author Hiroku
* @since October 3, 2022
*/
class ImmuneInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val name = pokemon.getName()
battle.broadcastChatMessage(battleLang("immune", name).red())
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,41 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BasicContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-invertboost|POKEMON
*
* POKEMON had its stat changes inverted.
* @author JMMCP
* @since November 26, 2023
*/
class InvertBoostInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val name = pokemon.getName()
battle.broadcastChatMessage(battleLang("invertboost", name))
// update and invert BOOST and UNBOOST contexts
val context = ShowdownInterpreter.getContextFromAction(message, BattleContext.Type.BOOST, battle)
val newUnboosts = pokemon.contextManager.get(BattleContext.Type.BOOST)?.map {
BasicContext(it.id, context.turn, BattleContext.Type.UNBOOST, context.origin)
}?.toTypedArray()
val newBoosts = pokemon.contextManager.get(BattleContext.Type.UNBOOST)?.map {
BasicContext(it.id, context.turn, BattleContext.Type.BOOST, context.origin)
}?.toTypedArray()
pokemon.contextManager.clear(BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
newBoosts?.let { pokemon.contextManager.add(*it) }
newUnboosts?.let { pokemon.contextManager.add(*it) }
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,34 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import net.minecraft.text.Text
/**
* Format: |-item|POKEMON|ITEM|(from)EFFECT
*
* ITEM held by POKEMON has been changed or revealed due to a move or ability EFFECT.
*
* Alt format: |-item|POKEMON|ITEM
*
* POKEMON has just switched in, and its ITEM is being announced to have a long-term effect.
* @author Licious
* @since December 30, 2022
*/
class ItemInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val sourceName = message.battlePokemonFromOptional(battle)?.getName() ?: Text.literal("UNKOWN")
ShowdownInterpreter.broadcastOptionalAbility(battle, message.effect(), sourceName)
battle.dispatchGo {
val battlePokemon = message.battlePokemon(0, battle) ?: return@dispatchGo
battlePokemon.heldItemManager.handleStartInstruction(battlePokemon, battle, message)
battle.minorBattleActions[battlePokemon.uuid] = message
battlePokemon.contextManager.add(ShowdownInterpreter.getContextFromAction(message, BattleContext.Type.ITEM, battle))
}
}
}
@@ -0,0 +1,26 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-mega|POKEMON|MEGASTONE
*
* POKEMON used item MEGASTONE to Mega Evolve.
* @author Segfault Guy
* @since September 10, 2023
*/
class MegaInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val battlePokemon = message.battlePokemon(0, battle) ?: return
val pokemonName = battlePokemon.getName()
battle.dispatchWaiting {
battle.broadcastChatMessage(battleLang("mega", pokemonName).yellow())
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
}
@@ -11,22 +11,22 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format:
* |-miss|SOURCE|TARGET
* Format: |-miss|SOURCE|TARGET
*
* The move used by the SOURCE Pokémon missed (maybe absent) the TARGET Pokémon.
* @author Hiroku
* @since October 3, 2022
*/
class MissInstruction(val battle: PokemonBattle, val instructionSet: InstructionSet, val message: BattleMessage): InterpreterInstruction {
val target = message.getBattlePokemon(1, battle)
class MissInstruction(val battle: PokemonBattle, val message: BattleMessage): InterpreterInstruction {
val target = message.battlePokemon(1, battle)
override fun invoke(battle: PokemonBattle) {
battle.dispatchGo {
val pokemon = message.getBattlePokemon(0, battle) ?: return@dispatchGo
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchGo
battle.broadcastChatMessage(battleLang("missed").red())
battle.minorBattleActions[pokemon.uuid] = message
}
@@ -31,6 +31,13 @@ import com.cobblemon.mod.common.pokemon.evolution.progress.UseMoveEvolutionProgr
import com.cobblemon.mod.common.util.battleLang
import java.util.concurrent.CompletableFuture
/**
* Format: |move|POKEMON|MOVE|TARGET
*
* POKEMON has used MOVE at TARGET.
* @author Deltric
* @since January 22, 2022
*/
class MoveInstruction(
val instructionSet: InstructionSet,
val message: BattleMessage
@@ -46,8 +53,8 @@ class MoveInstruction(
var targetPokemon: BattlePokemon? = null
override fun invoke(battle: PokemonBattle) {
userPokemon = message.getBattlePokemon(0, battle)!!
targetPokemon = message.getBattlePokemon(2, battle)
userPokemon = message.battlePokemon(0, battle)!!
targetPokemon = message.battlePokemon(2, battle)
val targetPokemon = targetPokemon // So smart non-null casts can happen
val optionalEffect = message.effect()
@@ -103,7 +110,7 @@ class MoveInstruction(
}
}
val hurtTargets = subsequentInstructions.filterIsInstance<DamageInstruction>().mapNotNull { it.battlePokemon }
val hurtTargets = subsequentInstructions.filterIsInstance<DamageInstruction>().mapNotNull { it.expectedTarget }
runtime.environment.getQueryStruct().addFunction("hurt") { params ->
if (params.params.size == 0) {
return@addFunction DoubleValue(hurtTargets.isNotEmpty())
@@ -0,0 +1,19 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-nothing
*
* Pathetic. Your move did absolutely nothing. You should feel bad for being bad.
* @author Hiroku
* @since August 20, 2022
*/
class NothingInstruction() : InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchGo { battle.broadcastChatMessage(battleLang("nothing")) }
}
}
@@ -0,0 +1,30 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
/**
* Format: |pp_update|<side_id>: <pokemon_uuid>|...<move_id>: <move_pp>
*
* @author Licious
* @since September 22, 2022
*/
class PpUpdateInstruction( val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatch {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatch GO
val moveDatum = message.argumentAt(1)?.split(", ") ?: return@dispatch GO
moveDatum.forEach { moveData ->
val moveIdAndPp = moveData.split(": ")
val moveId = moveIdAndPp[0]
val movePp = moveIdAndPp[1]
val move = pokemon.effectedPokemon.moveSet.firstOrNull { move -> move.name.equals(moveId, true) } ?: return@dispatch GO
move.currentPp = movePp.toInt()
}
GO
}
}
}
@@ -0,0 +1,32 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-prepare|ATTACKER|MOVE and |-prepare|ATTACKER|MOVE|DEFENDER
*
* ATTACKER Pokémon is preparing to use a charge MOVE on DEFENDER or an unknown target.
* @author Renaissance
* @since March 24, 2023
*/
class PrepareInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(1.5F) {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val pokemonName = pokemon.getName()
val effectID = message.effectAt(1)?.id ?: return@dispatchWaiting
//Prevents spam when the move Role Play is used
val lang = when (effectID) {
"shadowforce" -> battleLang("prepare.phantomforce", pokemonName) //Phantom Force and Shadow Force share the same text
"solarblade" -> battleLang("prepare.solarbeam", pokemonName) //Solar Beam and Solar Blade share the same text
else -> battleLang("prepare.$effectID", pokemonName)
}
battle.broadcastChatMessage(lang)
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,24 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-mustrecharge|POKEMON
*
* POKEMON must spend the turn recharging from a previous move.
* @author Hunter
* @since September 25, 2022
*/
class RechargeInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(2F){
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
battle.broadcastChatMessage(battleLang("recharge", pokemon.getName()))
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,30 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.net.messages.client.battle.BattleSwitchPokemonPacket
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |replace|POKEMON|DETAILS|HP STATUS
*
* Illusion has ended for POKEMON. Syntax is the same as [SwitchInstruction].
* @author Segfault Guy
* @since March 18th, 2024
*/
class ReplaceInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(1F) {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val entity = pokemon.entity
val actor = pokemon.actor
val pnx = message.pnxAndUuid(0)?.first
entity?.let { it.effects.mockEffect?.end(it) }
pnx?.let {battle.sendSidedUpdate(actor, BattleSwitchPokemonPacket(pnx, pokemon, true, null), BattleSwitchPokemonPacket(pnx, pokemon, false, null)) }
}
}
}
@@ -0,0 +1,49 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.ShowdownActionRequest
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleQueueRequestPacket
/**
* Format: |request|REQUEST
*
* It's time for the actor to make a decision.
* @author Deltric
* @since January 22, 2022
*/
class RequestInstruction(val battleActor: BattleActor, val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.log("Request Instruction")
if (message.rawMessage.contains("teamPreview")) // TODO probably change when we're allowing team preview
return
// Parse Json message and update state info for actor
val request = BattleRegistry.gson.fromJson(message.rawMessage.split("|request|")[1], ShowdownActionRequest::class.java)
request.sanitize(battle, battleActor)
if (battle.started) {
battle.dispatchGo {
// This request won't be acted on until the start of next turn
battleActor.sendUpdate(BattleQueueRequestPacket(request))
battleActor.request = request
battleActor.responses.clear()
// We need to send this out because 'upkeep' isn't received until the request is handled since the turn won't swap
if (request.forceSwitch.contains(true)) {
battle.doWhenClear {
battleActor.mustChoose = true
battleActor.sendUpdate(BattleMakeChoicePacket())
}
}
}
} else {
battleActor.request = request
battleActor.responses.clear()
}
}
}
@@ -0,0 +1,24 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-resisted|POKEMON
*
* POKEMON resisted the attack.
* @author
* @since
*/
class ResistedInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchGo {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchGo
battle.broadcastChatMessage(battleLang("resisted"))
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,30 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-setboost|POKEMON|STAT|AMOUNT
*
* Same as -boost and -unboost, but STAT is set to AMOUNT instead of boosted by AMOUNT.\
* @author Renaissance
* @since February 16, 2023
*/
class SetBoostInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(1.5F) {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val pokemonName = pokemon.getName()
val effectID = message.effect()?.id ?: return@dispatchWaiting
val lang = battleLang("setboost.$effectID", pokemonName)
battle.broadcastChatMessage(lang)
pokemon.contextManager.add(ShowdownInterpreter.getContextFromAction(message, BattleContext.Type.BOOST, battle))
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,37 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
import com.cobblemon.mod.common.util.battleLang
import kotlin.math.roundToInt
/**
* Format: |-sethp|POKEMON|HP
*
* POKEMON now has HP hit points.
* @author Licious
* @since February 8, 2023
*/
class SetHpInstruction(val actor: BattleActor, val publicMessage: BattleMessage, val privateMessage: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting {
val (pnx, _) = privateMessage.pnxAndUuid(0) ?: return@dispatchWaiting
val flatHp = privateMessage.argumentAt(1)?.split("/")?.getOrNull(0)?.toFloatOrNull() ?: return@dispatchWaiting
val ratioHp = publicMessage.argumentAt(1)?.split("/")?.getOrNull(0)?.toFloatOrNull()?.times(0.01F) ?: return@dispatchWaiting
val battlePokemon = privateMessage.battlePokemon(0, battle) ?: return@dispatchWaiting
battlePokemon.effectedPokemon.currentHealth = flatHp.roundToInt()
battle.sendSidedUpdate(actor, BattleHealthChangePacket(pnx, flatHp), BattleHealthChangePacket(pnx, ratioHp))
// It doesn't matter which we check when silent both have it
if (!publicMessage.hasOptionalArgument("silent")) {
val effectID = publicMessage.effect()?.id ?: return@dispatchWaiting
val lang = battleLang("sethp.$effectID")
battle.broadcastChatMessage(lang)
}
battle.minorBattleActions[battlePokemon.uuid] = publicMessage
}
}
}
@@ -0,0 +1,37 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-sideend|SIDE|CONDITION
*
* Indicates that the side CONDITION ended for the given SIDE.
* @author Segfault Guy
* @since April 5, 2023
*/
class SideEndInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(2F) {
val side = if (message.argumentAt(0)?.get(1) == '1') battle.side1 else battle.side2
val effect = message.effectAt(1) ?: return@dispatchWaiting
battle.sides.forEach {
val subject = if (it == side) battleLang("side_subject.ally") else battleLang("side_subject.opponent")
val lang = battleLang("sideend.${effect.id}", subject)
it.broadcastChatMessage(lang)
}
val bucket = when(effect.rawData.substringAfterLast(" ").lowercase()) {
"reflect", "screen", "veil" -> BattleContext.Type.SCREEN
"spikes", "rock", "web" -> BattleContext.Type.HAZARD
"tailwind" -> BattleContext.Type.TAILWIND
else -> BattleContext.Type.MISC
}
side.contextManager.remove(effect.id, bucket)
}
}
}
@@ -0,0 +1,38 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-sidestart|SIDE|CONDITION
*
* A side CONDITION has started on SIDE.
* @author Segfault Guy
* @since April 5, 2023
*/
class SideStartInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(2F) {
val side = if (message.argumentAt(0)?.get(1) == '1') battle.side1 else battle.side2
val effect = message.effectAt(1) ?: return@dispatchWaiting
battle.sides.forEach {
val subject = if (it == side) battleLang("side_subject.ally") else battleLang("side_subject.opponent")
val lang = battleLang("sidestart.${effect.id}", subject)
it.broadcastChatMessage(lang)
}
val bucket = when(effect.rawData.substringAfterLast(" ").lowercase()) {
"reflect", "screen", "veil" -> BattleContext.Type.SCREEN
"spikes", "rock", "web" -> BattleContext.Type.HAZARD
"tailwind" -> BattleContext.Type.TAILWIND
else -> BattleContext.Type.MISC
}
side.contextManager.add(ShowdownInterpreter.getContextFromAction(message, bucket, battle))
}
}
}
@@ -0,0 +1,17 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/** Placeholder for instructions that only */
class SilentInstruction(val instructionSet: InstructionSet, val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchGo { }
}
}
@@ -0,0 +1,27 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-singlemove|POKEMON|MOVE
*
* POKEMON used MOVE which causes a temporary effect lasting the duration of the move.
* @author Renaissance
* @since March 24, 2023
*/
class SingleMoveInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(1.5F) {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val pokemonName = pokemon.getName()
val effectID = message.effectAt(1)?.id ?: return@dispatchWaiting
val lang = battleLang("singlemove.$effectID", pokemonName)
battle.broadcastChatMessage(lang)
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,29 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
import net.minecraft.text.Text
/**
* Format: |-singleturn|POKEMON|MOVE
*
* POKEMON used MOVE which causes a temporary effect lasting the duration of the turn.
* @author Renaissance
* @since March 24, 2023
*/
class SingleTurnInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(1.5F) {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val pokemonName = pokemon.getName()
val sourceName = message.battlePokemonFromOptional(battle)?.getName() ?: Text.literal("UNKOWN")
val effectID = message.effectAt(1)?.id ?: return@dispatchWaiting
val lang = battleLang("singleturn.$effectID", pokemonName, sourceName)
battle.broadcastChatMessage(lang)
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,57 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.util.battleLang
import net.minecraft.text.Text
/**
* Format: |-start|POKEMON|EFFECT
*
* A volatile status has been inflicted on POKEMON by EFFECT.
* @author Deltric
* @since January 21, 2022
*/
class StartInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatch {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatch GO
val effectID = message.effectAt(1)?.id ?: return@dispatch GO
val optionalEffect = message.effect()
val optionalPokemon = message.battlePokemonFromOptional(battle)
val optionalPokemonName = optionalPokemon?.getName()
val extraEffect = message.effectAt(2)?.typelessData ?: Text.literal("UNKOWN")
// skip adding contexts for every time the perish counter decrements
if (!effectID.contains("perish")) {
// don't need to add unique: showdown won't send -start instruction if volatile status is already present
pokemon.contextManager.add(ShowdownInterpreter.getContextFromAction(message, BattleContext.Type.VOLATILE, battle))
}
battle.minorBattleActions[pokemon.uuid] = message
if (!message.hasOptionalArgument("silent")) {
val lang = if (optionalEffect?.id == "reflecttype" && optionalPokemonName != null)
battleLang("start.reflecttype", pokemon.getName(), optionalPokemonName)
else
when (effectID) {
"confusion", "perish3" -> return@dispatch GO // Skip
"perish2", "perish1", "perish0",
"stockpile1", "stockpile2", "stockpile3" -> battleLang("start.${effectID.dropLast(1)}", pokemon.getName(), effectID.last().digitToInt())
"dynamax" -> battleLang("start.${message.effectAt(2)?.id ?: effectID}", pokemon.getName()).yellow()
"curse" -> battleLang("start.curse", message.battlePokemonFromOptional(battle)!!.getName(), pokemon.getName())
else -> battleLang("start.$effectID", pokemon.getName(), extraEffect)
}
battle.broadcastChatMessage(lang)
}
WaitDispatch(1F)
}
}
}
@@ -0,0 +1,45 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.net.messages.client.battle.BattlePersistentStatusPacket
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
import com.cobblemon.mod.common.util.asTranslated
/**
* Format: |-status|POKEMON|STATUS
*
* POKEMON has been inflicted with STATUS.
* @author Hiroku
* @since October 3, 2022
*/
class StatusInstruction( val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val (pnx, _) = message.pnxAndUuid(0) ?: return
val pokemon = message.battlePokemon(0, battle) ?: return
val otherPokemon = message.actorAndActivePokemonFromOptional(battle, "of")?.second?.battlePokemon
val statusLabel = message.argumentAt(1) ?: return
val status = Statuses.getStatus(statusLabel) ?: return Cobblemon.LOGGER.error("Unrecognized status: $statusLabel")
if (otherPokemon == null)
ShowdownInterpreter.broadcastOptionalAbility(battle, message.effect(), pokemon.getName())
else
ShowdownInterpreter.broadcastOptionalAbility(battle, message.effect(), otherPokemon.getName())
battle.dispatchWaiting {
if (status is PersistentStatus) {
pokemon.effectedPokemon.applyStatus(status)
battle.sendUpdate(BattlePersistentStatusPacket(pnx, status))
}
battle.broadcastChatMessage(status.applyMessage.asTranslated(pokemon.getName()))
pokemon.contextManager.add(ShowdownInterpreter.getContextFromAction(message, BattleContext.Type.STATUS, battle))
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,24 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-supereffective|POKEMON
*
* A move was super effective against POKEMON.
* @author Hunter
* @since August 18, 2022
*/
class SuperEffectiveInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchGo {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchGo
battle.broadcastChatMessage(battleLang("superEffective"))
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,36 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-swapboost|SOURCE|TARGET|STATS
*
* Swaps the boosts from STATS between the SOURCE Pokémon and TARGET Pokémon.
* @author Renaissance
* @since March 24, 2023
*/
class SwapBoostInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(2F) {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val pokemonName = pokemon.getName()
val targetPokemon = message.battlePokemon(1, battle) ?: return@dispatchWaiting
val targetPokemonName = targetPokemon.getName()
val effectID = message.effect()?.id ?: return@dispatchWaiting
val lang = when (effectID) {
"guardswap", "powerswap", "heartswap" -> battleLang("swapboost.$effectID", pokemonName)
else -> battleLang("swapboost.generic", pokemonName, targetPokemonName)
}
battle.broadcastChatMessage(lang)
pokemon.contextManager.swap(targetPokemon.contextManager, BattleContext.Type.BOOST)
pokemon.contextManager.swap(targetPokemon.contextManager, BattleContext.Type.UNBOOST)
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -0,0 +1,30 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
/**
* Format: |-swapsideconditions
*
* Swaps side conditions between sides. Used for Court Change.
* @author Segfault Guy
* @since February 19, 2024
*/
class SwapSideConditionsInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchGo {
val sides = mutableListOf<BattleSide>()
battle.sides.forEach { side ->
if (!sides.contains(side)) {
val oppositeManager = side.getOppositeSide().contextManager
side.contextManager.swap(oppositeManager, BattleContext.Type.TAILWIND, BattleContext.Type.SCREEN, BattleContext.Type.HAZARD)
}
sides.add(side)
}
}
}
}
@@ -0,0 +1,135 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.dispatch.*
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.entity.pokemon.effects.IllusionEffect
import com.cobblemon.mod.common.net.messages.client.battle.BattleSwitchPokemonPacket
import com.cobblemon.mod.common.util.swap
import net.minecraft.entity.LivingEntity
import net.minecraft.server.world.ServerWorld
import java.util.concurrent.CompletableFuture
/**
* Format: |switch|POKEMON|DETAILS|HP STATUS or |drag|POKEMON|DETAILS|HP STATUS
*
* POKEMON has switched in (if there was an old Pokémon at that position, it is switched out).
* POKEMON|DETAILS represents all the information that can be used to tell Pokémon apart.
* The switched Pokémon has HP health points and STATUS status.
* @author Deltric
* @since January 22, 2022
*/
class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: BattleActor, val publicMessage: BattleMessage, val privateMessage: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val (pnx, _) = publicMessage.pnxAndUuid(0) ?: return
val (actor, activePokemon) = battle.getActorAndActiveSlotFromPNX(pnx)
val entity = if (actor is EntityBackedBattleActor<*>) actor.entity else null
val illusion = publicMessage.battlePokemonFromOptional(battle, "is")
val pokemon = publicMessage.battlePokemon(0, battle) ?: return
if (!battle.started) {
activePokemon.battlePokemon = pokemon
activePokemon.illusion = illusion
val pokemonEntity = pokemon.entity
if (pokemonEntity == null && entity != null) {
val targetPos = battleActor.getSide().getOppositeSide().actors.filterIsInstance<EntityBackedBattleActor<*>>().firstOrNull()?.entity?.pos?.let { pos ->
val offset = pos.subtract(entity.pos)
val idealPos = entity.pos.add(offset.multiply(0.33))
idealPos
} ?: entity.pos
actor.stillSendingOutCount++
pokemon.effectedPokemon.sendOutWithAnimation(
source = entity,
battleId = battle.battleId,
level = entity.world as ServerWorld,
doCry = false,
position = targetPos,
illusion = illusion?.let { IllusionEffect(it.effectedPokemon) }
).thenApply {
actor.stillSendingOutCount--
}
}
else if (pokemonEntity != null) {
illusion?.let { IllusionEffect(it.effectedPokemon).start(pokemonEntity) }
}
} else {
battle.dispatchInsert {
pokemon.sendUpdate()
if (activePokemon.battlePokemon == pokemon) {
return@dispatchInsert emptySet() // Already switched in, Showdown does this if the pokemon is going to die before it can switch
}
activePokemon.battlePokemon?.let { oldPokemon ->
if (publicMessage.effect()?.id == "batonpass") oldPokemon.contextManager.swap(pokemon.contextManager, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
oldPokemon.contextManager.clear(BattleContext.Type.VOLATILE, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
battle.majorBattleActions[oldPokemon.uuid] = publicMessage
}
battle.majorBattleActions[pokemon.uuid] = publicMessage
setOf(
BattleDispatch {
if (entity != null) {
createEntitySwitch(battle, actor, entity, pnx, activePokemon, pokemon, illusion)
} else {
createNonEntitySwitch(battle, actor, pnx, activePokemon, pokemon, illusion)
}
}
)
}
}
}
companion object{
fun createEntitySwitch(battle: PokemonBattle, actor: BattleActor, entity: LivingEntity, pnx: String, activePokemon: ActiveBattlePokemon, newPokemon: BattlePokemon, illusion: BattlePokemon? = null): DispatchResult {
val pokemonEntity = activePokemon.battlePokemon?.entity
// If we can't find the entity for some reason then we're going to skip the recall animation
val sendOutFuture = CompletableFuture<Unit>()
(pokemonEntity?.recallWithAnimation() ?: CompletableFuture.completedFuture(Unit)).thenApply {
// Queue actual swap and send-in after the animation has ended
actor.pokemonList.swap(actor.activePokemon.indexOf(activePokemon), actor.pokemonList.indexOf(newPokemon))
activePokemon.battlePokemon = newPokemon
activePokemon.illusion = illusion
battle.sendSidedUpdate(actor, BattleSwitchPokemonPacket(pnx, newPokemon, true, illusion), BattleSwitchPokemonPacket(pnx, newPokemon, false, illusion))
if (newPokemon.entity != null) {
illusion?.let { IllusionEffect(it.effectedPokemon).start(newPokemon.entity!!) }
newPokemon.entity?.cry()
sendOutFuture.complete(Unit)
} else {
val lastPosition = activePokemon.position
// Send out at previous Pokémon's location if it is known, otherwise actor location
val world = lastPosition?.first ?: entity.world as ServerWorld
val pos = lastPosition?.second ?: entity.pos
newPokemon.effectedPokemon.sendOutWithAnimation(
source = entity,
battleId = battle.battleId,
level = world,
position = pos,
illusion = illusion?.let { IllusionEffect(it.effectedPokemon) }
).thenAccept { sendOutFuture.complete(Unit) }
}
}
return UntilDispatch { sendOutFuture.isDone }
}
fun createNonEntitySwitch(battle: PokemonBattle, actor: BattleActor, pnx: String, activePokemon: ActiveBattlePokemon, newPokemon: BattlePokemon, illusion: BattlePokemon? = null): DispatchResult {
actor.pokemonList.swap(actor.activePokemon.indexOf(activePokemon), actor.pokemonList.indexOf(newPokemon))
activePokemon.battlePokemon = newPokemon
activePokemon.illusion = illusion
battle.sendSidedUpdate(actor, BattleSwitchPokemonPacket(pnx, newPokemon, true, illusion), BattleSwitchPokemonPacket(pnx, newPokemon, false, illusion))
return WaitDispatch(1.5F)
}
}
}
@@ -0,0 +1,28 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-terastallize|POKEMON|TYPE
*
* POKEMON terastallized into TYPE.
* @author Segfault Guy
* @since September 10, 2023
*/
class TerastallizeInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val battlePokemon = message.battlePokemon(0, battle) ?: return
val pokemonName = battlePokemon.getName()
val type = message.effectAt(1)?.let { ElementalTypes.get(it.id) } ?: return
battle.dispatchWaiting {
battle.broadcastChatMessage(battleLang("terastallize", pokemonName, type.displayName).yellow())
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
}
@@ -0,0 +1,32 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.entity.pokemon.effects.TransformEffect
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-transform|POKEMON|POKEMON
*
* POKEMON used Transform to turn into target POKEMON.
* @author jeffw773
* @since November 28, 2023
*/
class TransformInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(1.5F) {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val pokemonName = pokemon.getName()
val targetPokemon = message.battlePokemon(1, battle) ?: return@dispatchWaiting
val targetPokemonName = targetPokemon.getName()
pokemon.entity?.let { TransformEffect(targetPokemon.effectedPokemon).start(it) }
val lang = battleLang("transform", pokemonName, targetPokemonName)
battle.broadcastChatMessage(lang)
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -24,13 +24,17 @@ import com.cobblemon.mod.common.net.messages.client.battle.BattleSetTeamPokemonP
import com.cobblemon.mod.common.util.battleLang
/**
* Format:
* |turn|NUMBER
* Format: |turn|NUMBER
*
* It is now turn NUMBER.
* @author Deltric
* @since January 22, 2022
*/
class TurnInstruction(val message: BattleMessage) : InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
// TODO maybe tell the client that the turn number has changed
val turnNumber = message.argumentAt(0)?.toInt() ?: return
if (!battle.started) {
battle.started = true
battle.actors.filterIsInstance<PlayerBattleActor>().forEach { actor ->
@@ -54,9 +58,6 @@ class TurnInstruction(val message: BattleMessage) : InterpreterInstruction {
}
}
// TODO maybe tell the client that the turn number has changed
val turnNumber = message.argumentAt(0)?.toInt() ?: return
battle.dispatch {
battle.sendToActors(BattleMakeChoicePacket())
battle.broadcastChatMessage(battleLang("turn", turnNumber).aqua())
@@ -12,7 +12,14 @@ import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.ShowdownInterpreter
/**
* Catch-all for unimplemented instructions that need to be added to the [ShowdownInterpreter].
*
* @author Hiroku
* @since December 25, 2023
*/
class UnknownInstruction(val battleMessage: BattleMessage) : InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchGo { battle.broadcastChatMessage(battleMessage.rawMessage.red()) }
@@ -13,10 +13,11 @@ import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
/**
* Format:
* |upkeep|
* Format: |upkeep|
*
* Signals the upkeep phase of the turn where the number of turns left for field conditions are updated.
* @author Hiroku
* @since March 12, 2022
*/
class UpkeepInstruction : InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
@@ -0,0 +1,44 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
import net.minecraft.text.Text
/**
* Format: |-weather|WEATHER
*
* Indicates WEATHER is currently in effect.
*
* If upkeep is present, it means that WEATHER was active previously and is still in effect that turn.
* Otherwise, it means that the weather has changed due to a move or ability, or has expired, in which case WEATHER will be none.
* @author Hunter
* @since September 25, 2022
*/
class WeatherInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val weather = message.effectAt(0)?.id ?: return
val user = message.battlePokemonFromOptional(battle)?.getName() ?: Text.literal("UNKNOWN")
ShowdownInterpreter.broadcastOptionalAbility(battle, message.effect(), user)
battle.dispatchWaiting(1.5F) {
val lang = when {
message.hasOptionalArgument("upkeep") -> battleLang("weather.$weather.upkeep")
weather != "none" -> {
battle.contextManager.add(ShowdownInterpreter.getContextFromAction(message, BattleContext.Type.WEATHER, battle))
battleLang("weather.$weather.start")
}
else -> {
val oldWeather = battle.contextManager.get(BattleContext.Type.WEATHER)?.iterator()?.next()?.id ?: return@dispatchWaiting
battle.contextManager.clear(BattleContext.Type.WEATHER)
battleLang("weather.$oldWeather.end")
}
}
battle.broadcastChatMessage(lang)
}
}
}
@@ -0,0 +1,55 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.BattleVictoryEvent
import com.cobblemon.mod.common.api.text.gold
import com.cobblemon.mod.common.api.text.plus
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.util.battleLang
import java.util.UUID
/**
* Format: |win|USER
*
* USER has won the battle.
* @author Deltric
* @since January 22, 2022
*/
class WinInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
battle.dispatchGo {
val user = message.argumentAt(0) ?: return@dispatchGo
val ids = user.split("&").map { it.trim() }
val winners = ids.map { battle.getActor(UUID.fromString(it))!! }
val losers = battle.actors.filter { !winners.contains(it) }
val winnersText = winners.map { it.getName() }.reduce { acc, next -> acc + " & " + next }
battle.broadcastChatMessage(battleLang("win", winnersText).gold())
battle.end()
val wasCaught = battle.showdownMessages.any { "capture" in it }
// If the battle was a PvW battle, we need to set the killer of the wild Pokémon to the player
if (battle.isPvW) {
val nonPlayerActor = battle.actors.first { it.type == ActorType.WILD }
val wildPokemon: BattlePokemon = nonPlayerActor.pokemonList.first()
if (!wasCaught && losers.any { it.uuid == wildPokemon.uuid }) {
wildPokemon.effectedPokemon.entity?.killer = (battle.actors.firstOrNull { it.type == ActorType.PLAYER } as? PlayerBattleActor)?.entity
}
}
CobblemonEvents.BATTLE_VICTORY.post(BattleVictoryEvent(battle, winners, losers, wasCaught))
ShowdownInterpreter.lastCauser.remove(battle.battleId)
}
}
}
@@ -0,0 +1,26 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-zbroken|POKEMON
*
* A z-move has broken through protect and hit POKEMON.
* @author Segfault Guy
* @since September 10, 2023
*/
class ZBrokenInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val battlePokemon = message.battlePokemon(0, battle) ?: return
val pokemonName = battlePokemon.getName()
battle.dispatchWaiting {
battle.broadcastChatMessage(battleLang("zbroken", pokemonName).red())
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
}
@@ -0,0 +1,26 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-zpower|POKEMON
*
* POKEMON has used the z-move variant of its move.
* @author Segfault Guy
* @since September 10, 2023
*/
class ZPowerInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val battlePokemon = message.battlePokemon(0, battle) ?: return
val pokemonName = battlePokemon.getName()
battle.dispatchWaiting {
battle.broadcastChatMessage(battleLang("zpower", pokemonName).yellow())
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
}
@@ -38,6 +38,14 @@ open class BattlePokemon(
effectedPokemon = pokemon.clone(),
postBattleEntityOperation = { entity -> entity.discard() }
)
fun playerOwned(pokemon: Pokemon): BattlePokemon = BattlePokemon(
originalPokemon = pokemon,
effectedPokemon = pokemon,
postBattleEntityOperation = { entity ->
entity.effects.wipe()
}
)
}
val uuid: UUID
@@ -72,10 +80,11 @@ open class BattlePokemon(
val contextManager = ContextManager()
open fun getName(): MutableText {
val displayPokemon = getIllusion()?.effectedPokemon ?: effectedPokemon
return if (actor is PokemonBattleActor || actor is MultiPokemonBattleActor) {
effectedPokemon.getDisplayName()
displayPokemon.getDisplayName()
} else {
battleLang("owned_pokemon", actor.getName(), effectedPokemon.getDisplayName())
battleLang("owned_pokemon", actor.getName(), displayPokemon.getDisplayName())
}
}
@@ -94,4 +103,6 @@ open class BattlePokemon(
fun writeVariables(struct: VariableStruct) {
effectedPokemon.writeVariables(struct)
}
fun getIllusion(): BattlePokemon? = this.actor.activePokemon.find { it.battlePokemon == this }?.illusion
}
@@ -62,8 +62,8 @@ class MonitorBlock(properties: Settings) : MultiblockBlock(properties) {
if(world == null || pos == null) {
return 0
}
val monitorEntity = world.getBlockEntity(pos) as? MultiblockEntity
val multiBlockEntity = monitorEntity?.multiblockStructure
val monitorEntity = world.getBlockEntity(pos) as MultiblockEntity
val multiBlockEntity = monitorEntity.multiblockStructure
if(multiBlockEntity != null) {
return multiBlockEntity.getComparatorOutput(state, world, pos)
}
@@ -149,8 +149,8 @@ class RestorationTankBlock(properties: Settings) : MultiblockBlock(properties),
if(world == null || pos == null) {
return 0
}
val tankEntity = world.getBlockEntity(pos) as? MultiblockEntity
val multiBlockEntity = tankEntity?.multiblockStructure
val tankEntity = world.getBlockEntity(pos) as MultiblockEntity
val multiBlockEntity = tankEntity.multiblockStructure
if(multiBlockEntity != null) {
return multiBlockEntity.getComparatorOutput(state, world, pos)
}
@@ -180,7 +180,7 @@ class RestorationTankBlock(properties: Settings) : MultiblockBlock(properties),
return
}
val tankEntity = world.getBlockEntity(pos) as? MultiblockEntity
tankEntity?.multiblockStructure?.onTriggerEvent(state, world, pos, random)
tankEntity?.multiblockStructure!!.onTriggerEvent(state, world, pos, random)
}
override fun getOutlineShape(
@@ -9,23 +9,15 @@
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import com.cobblemon.mod.common.block.chest.GildedChestBlock
import net.minecraft.block.*
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.fluid.FluidState
import net.minecraft.fluid.Fluids
import net.minecraft.item.ItemPlacementContext
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.server.world.ServerWorld
import net.minecraft.state.StateManager
import net.minecraft.state.property.BooleanProperty
import net.minecraft.state.property.Properties
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.util.math.random.Random
import net.minecraft.util.shape.VoxelShape
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.minecraft.world.WorldView
@@ -36,18 +28,7 @@ class TumblestoneBlock(
height: Int,
xzOffset: Int,
nextStage: Block?
) : GrowableStoneBlock(settings, stage, height, xzOffset, nextStage), Waterloggable {
init {
this.defaultState = this.stateManager.defaultState
.with(FACING, Direction.DOWN)
.with(WATERLOGGED, false)
}
companion object {
val WATERLOGGED = BooleanProperty.of("waterlogged")
}
) : GrowableStoneBlock(settings, stage, height, xzOffset, nextStage) {
override fun canGrow(pos: BlockPos, world: BlockView): Boolean {
if (stage == MAX_STAGE) return false
val iterator: Iterator<BlockPos> =
@@ -62,31 +43,4 @@ class TumblestoneBlock(
return true
}
override fun getFluidState(state: BlockState): FluidState {
return if (state.get(GildedChestBlock.WATERLOGGED)) {
Fluids.WATER.getStill(false)
} else super.getFluidState(state)
}
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
super.appendProperties(builder)
builder.add(GildedChestBlock.WATERLOGGED)
}
override fun getPlacementState(blockPlaceContext: ItemPlacementContext): BlockState? {
return super.getPlacementState(blockPlaceContext)?.with(GildedChestBlock.WATERLOGGED, blockPlaceContext.world.getFluidState(blockPlaceContext.blockPos).fluid == Fluids.WATER)
}
override fun getStateForNeighborUpdate(
state: BlockState,
direction: Direction,
neighborState: BlockState,
world: WorldAccess,
pos: BlockPos,
neighborPos: BlockPos
): BlockState? {
if (state.get(GildedChestBlock.WATERLOGGED)) world.scheduleFluidTick(pos, Fluids.WATER, Fluids.WATER.getTickRate(world))
return super.getStateForNeighborUpdate(state, direction, neighborState, world, pos, neighborPos)
}
}
@@ -16,15 +16,20 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.util.toVec3d
import net.minecraft.block.*
import net.minecraft.entity.LivingEntity
import net.minecraft.block.Block
import net.minecraft.block.BlockRenderType
import net.minecraft.block.BlockState
import net.minecraft.block.BlockWithEntity
import net.minecraft.block.Blocks
import net.minecraft.block.HorizontalFacingBlock
import net.minecraft.block.ShapeContext
import net.minecraft.block.Waterloggable
import net.minecraft.entity.ai.pathing.NavigationType
import net.minecraft.entity.mob.PiglinBrain
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.fluid.FluidState
import net.minecraft.fluid.Fluids
import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemStack
import net.minecraft.screen.ScreenHandler
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.sound.SoundCategory
@@ -33,7 +38,12 @@ import net.minecraft.state.property.BooleanProperty
import net.minecraft.state.property.Properties
import net.minecraft.text.MutableText
import net.minecraft.text.Text
import net.minecraft.util.*
import net.minecraft.util.ActionResult
import net.minecraft.util.BlockMirror
import net.minecraft.util.BlockRotation
import net.minecraft.util.Hand
import net.minecraft.util.Identifier
import net.minecraft.util.ItemScatterer
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
@@ -96,7 +106,7 @@ class GildedChestBlock(settings: Settings, val type: Type = Type.RED) : BlockWit
neighborPos: BlockPos
): BlockState {
if (state.get(WATERLOGGED)) world.scheduleFluidTick(pos, Fluids.WATER, Fluids.WATER.getTickRate(world))
return super.getStateForNeighborUpdate(state, direction, neighborState, world, pos, neighborPos)
return super.getStateForNeighborUpdate(state, direction, neighborState, world, pos, neighborPos)
}
override fun getFluidState(state: BlockState): FluidState {
@@ -248,17 +258,4 @@ class GildedChestBlock(settings: Settings, val type: Type = Type.RED) : BlockWit
return false
}
override fun onPlaced(
world: World,
pos: BlockPos,
state: BlockState,
placer: LivingEntity?,
itemStack: ItemStack
) {
val blockEntity = world.getBlockEntity(pos)
if (itemStack.hasCustomName() && blockEntity is GildedChestBlockEntity) {
blockEntity.customName = itemStack.name
}
}
}
@@ -42,7 +42,7 @@ open class FossilMultiblockEntity(
field
} else if (masterBlockPos != null && masterBlockPos != pos) {
val chunkPos = ChunkPos(masterBlockPos)
if (world?.chunkManager?.isChunkLoaded(chunkPos.x, chunkPos.z) == true) {
if (world?.chunkManager?.isChunkLoaded(chunkPos.x, chunkPos.z) == true) {
val entity: FossilMultiblockEntity? = world?.getBlockEntity(masterBlockPos) as FossilMultiblockEntity?
field = entity?.multiblockStructure
}
@@ -140,7 +140,7 @@ class PokemonPastureBlockEntity(pos: BlockPos, val state: BlockState) : BlockEnt
if (fixedPosition != null) {
entity.setPosition(fixedPosition.toCenterPos().subtract(0.0, 0.5, 0.0))
val pc = Cobblemon.storage.getPC(player.uuid)
entity.beamMode = 2
entity.beamMode = 1
afterOnServer(seconds = SendOutPokemonHandler.SEND_OUT_DURATION) {
entity.beamMode = 0
}
@@ -117,10 +117,10 @@ class FossilMultiblockBuilder(val centerPos: BlockPos) : MultiblockStructureBuil
val monitorPos = monitorPositions[fossilTankIndex]
val fossilAnalyzerPos = fossilAnalyzerPositions[fossilTankIndex]!!
val restorationTankPos = restorationTankPositions[fossilTankIndex]!!
val monitorEntity = world.getBlockEntity(monitorPos) as? MultiblockEntity
val analyzerEntity = world.getBlockEntity(fossilAnalyzerPos) as? MultiblockEntity
val tankBaseEntity = world.getBlockEntity(restorationTankPos) as? MultiblockEntity
val tankTopEntity = world.getBlockEntity(restorationTankPos.up()) as? MultiblockEntity
val monitorEntity = world.getBlockEntity(monitorPos) as MultiblockEntity
val analyzerEntity = world.getBlockEntity(fossilAnalyzerPos) as MultiblockEntity
val tankBaseEntity = world.getBlockEntity(restorationTankPos) as MultiblockEntity
val tankTopEntity = world.getBlockEntity(restorationTankPos.up()) as MultiblockEntity
val structure = FossilMultiblockStructure(monitorPos, fossilAnalyzerPos, restorationTankPos)
structure.tankConnectorDirection = dirsToCheck.filter {
@@ -128,20 +128,20 @@ class FossilMultiblockBuilder(val centerPos: BlockPos) : MultiblockStructureBuil
return@filter adjPos == fossilAnalyzerPos
}.first()
analyzerEntity?.multiblockStructure = structure
tankBaseEntity?.multiblockStructure = structure
tankTopEntity?.multiblockStructure = structure
monitorEntity?.multiblockStructure = structure
analyzerEntity.multiblockStructure = structure
tankBaseEntity.multiblockStructure = structure
tankTopEntity.multiblockStructure = structure
monitorEntity.multiblockStructure = structure
structure.syncToClient(world)
structure.markDirty(world)
world.playSound(null, centerPos, CobblemonSounds.FOSSIL_MACHINE_ASSEMBLE, SoundCategory.BLOCKS)
//Set these to null so the builders can be freed
analyzerEntity?.multiblockBuilder = null
tankBaseEntity?.multiblockBuilder = null
tankTopEntity?.multiblockBuilder = null
monitorEntity?.multiblockBuilder = null
analyzerEntity.multiblockBuilder = null
tankBaseEntity.multiblockBuilder = null
tankTopEntity.multiblockBuilder = null
monitorEntity.multiblockBuilder = null
}
companion object {
@@ -306,22 +306,22 @@ class FossilMultiblockStructure (
}
override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity?) {
val monitorEntity = world.getBlockEntity(monitorPos) as? MultiblockEntity
val analyzerEntity = world.getBlockEntity(analyzerPos) as? MultiblockEntity
val tankBaseEntity = world.getBlockEntity(tankBasePos) as? MultiblockEntity
val tankTopEntity = world.getBlockEntity(tankBasePos.up()) as? MultiblockEntity
val state = world.getBlockState(tankBaseEntity?.pos)
val monitorEntity = world.getBlockEntity(monitorPos) as MultiblockEntity
val analyzerEntity = world.getBlockEntity(analyzerPos) as MultiblockEntity
val tankBaseEntity = world.getBlockEntity(tankBasePos) as MultiblockEntity
val tankTopEntity = world.getBlockEntity(tankBasePos.up()) as MultiblockEntity
val state = world.getBlockState(tankBaseEntity.pos)
val direction = state.get(HorizontalFacingBlock.FACING).getOpposite()
val wildPokemon: Pokemon? = this.createdPokemon
monitorEntity?.multiblockStructure = null
analyzerEntity?.multiblockStructure = null
tankBaseEntity?.multiblockStructure = null
tankTopEntity?.multiblockStructure = null
monitorEntity?.masterBlockPos = null
analyzerEntity?.masterBlockPos = null
tankBaseEntity?.masterBlockPos = null
tankTopEntity?.masterBlockPos = null
monitorEntity.multiblockStructure = null
analyzerEntity.multiblockStructure = null
tankBaseEntity.multiblockStructure = null
tankTopEntity.multiblockStructure = null
monitorEntity.masterBlockPos = null
analyzerEntity.masterBlockPos = null
tankBaseEntity.masterBlockPos = null
tankTopEntity.masterBlockPos = null
// Drop fossils from machine as long as the machine is not started or near completion
if (this.timeRemaining == -1 || this.timeRemaining >= 20) {
@@ -619,8 +619,8 @@ class FossilMultiblockStructure (
}
const val TICKS_PER_MINUTE = 1200
const val MATERIAL_TO_START = 128
const val TIME_TO_TAKE = TICKS_PER_MINUTE * 12
const val MATERIAL_TO_START = 256
const val TIME_TO_TAKE = TICKS_PER_MINUTE * 15
const val TIME_PER_STAGE = TIME_TO_TAKE / 8
const val PROTECTION_TIME = TICKS_PER_MINUTE * 5
@@ -100,8 +100,6 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
this.addPrimaryAnimation(primaryAnimation)
}
} else if (data == PokemonEntity.BEAM_MODE) {
// If you make adjustments to this code, make sure to Find Usages for both PokemonEntity.beamMode and PokemonEntity.BEAM_MODE
// TODO: change beamMode to an enum, or a set of booleans (send-out vs recall, delay vs delay)
val beamMode = currentEntity.beamMode
when (beamMode) {
0 -> { /* Do nothing */ }
@@ -138,37 +136,39 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
currentEntity.after(seconds = POKEBALL_AIR_TIME){
beamStartTime = System.currentTimeMillis()
ballDone = true
if (client.soundManager.get(CobblemonSounds.POKE_BALL_SEND_OUT.id) != null && !playedSendOutSound) {
client.world?.playSound(client.player, soundPos.x, soundPos.y, soundPos.z, SoundEvent.of(CobblemonSounds.POKE_BALL_SEND_OUT.id), SoundCategory.PLAYERS, 0.6f, 1f)
playedSendOutSound = true
}
// Display ball-specific particles
sendOutPosition?.let{
val newPos = it.add(sendOutOffset)
val ballType = currentEntity.pokemon.caughtBall.name.path.lowercase().replace("_","")
val mode = if(currentEntity.isBattling) "battle" else "casual"
val sendflash = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/${mode}/sendflash"))
sendflash?.let { effect ->
val wrapper = MatrixWrapper()
val matrix = MatrixStack()
matrix.translate(newPos.x, newPos.y, newPos.z)
wrapper.updateMatrix(matrix.peek().positionMatrix)
val world = MinecraftClient.getInstance().world ?: return@let
ParticleStorm(effect, wrapper, world).spawn()
val ballsparks = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/${mode}/ballsparks"))
val ballsendsparkle = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/${mode}/ballsendsparkle"))
// using afterOnClient because it's such a small timeframe that it's unlikely the entity has been removed & we'd like the precision
afterOnClient(seconds = 0.01667f) {
ballsparks?.let { effect ->
if (client.soundManager.get(CobblemonSounds.POKE_BALL_OPEN.id) != null && !playedSendOutSound) {
currentEntity.owner?.let {
client.world?.playSound(client.player, soundPos.x, soundPos.y, soundPos.z, SoundEvent.of(CobblemonSounds.POKE_BALL_SEND_OUT.id), SoundCategory.PLAYERS, 0.6f, 1f)
playedSendOutSound = true
/// create end rod particles in a 0.1 radius around the soundPos with a count of 50 and a random velocity of 0.1
sendOutPosition?.let{
val newPos = it.add(sendOutOffset)
val ballType = currentEntity.pokemon.caughtBall.name.path.toLowerCase().replace("_","")
val mode = if(currentEntity.isBattling) "battle" else "casual"
val sendflash = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/${mode}/sendflash"))
sendflash?.let { effect ->
val wrapper = MatrixWrapper()
val matrix = MatrixStack()
matrix.translate(newPos.x, newPos.y, newPos.z)
wrapper.updateMatrix(matrix.peek().positionMatrix)
val world = MinecraftClient.getInstance().world ?: return@let
ParticleStorm(effect, wrapper, world).spawn()
}
ballsendsparkle?.let { effect ->
ParticleStorm(effect, wrapper, world).spawn()
}
currentEntity.after(seconds = 0.4f) {
val ballsparkle = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/ballsparkle"))
ballsparkle?.let { effect ->
ParticleStorm(effect, wrapper, world).spawn()
val ballsparks = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/${mode}/ballsparks"))
val ballsendsparkle = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/${mode}/ballsendsparkle"))
// using afterOnClient because it's such a small timeframe that it's unlikely the entity has been removed & we'd like the precision
afterOnClient(seconds = 0.01667f) {
ballsparks?.let { effect ->
ParticleStorm(effect, wrapper, world).spawn()
}
ballsendsparkle?.let { effect ->
ParticleStorm(effect, wrapper, world).spawn()
}
currentEntity.after(seconds = 0.4f) {
val ballsparkle = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/ballsparkle"))
ballsparkle?.let { effect ->
ParticleStorm(effect, wrapper, world).spawn()
}
}
}
}
}
@@ -185,35 +185,7 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
}
}
}
2 -> {
// Scaling up with no delay and no particles
playedSendOutSound = false
entityScaleModifier = 0F
currentEntity.isInvisible = false
val soundPos = currentEntity.pos
val client = MinecraftClient.getInstance()
if (client.soundManager.get(CobblemonSounds.POKE_BALL_SEND_OUT.id) != null && !playedSendOutSound) {
client.world?.playSound(
client.player,
soundPos.x,
soundPos.y,
soundPos.z,
CobblemonSounds.POKE_BALL_SEND_OUT,
SoundCategory.PLAYERS,
0.6f,
1f
)
playedSendOutSound = true
}
lerpOnClient(BEAM_SHRINK_TIME) { entityScaleModifier = it }
currentEntity.after(seconds = BEAM_SHRINK_TIME * 2) {
ballOffset = 0f
ballRotOffset = 0f
sendOutPosition = null
}
}
3 -> {
else -> {
// Scaling down into pokeball
entityScaleModifier = 1F
beamStartTime = System.currentTimeMillis()
@@ -226,7 +198,6 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
}
}
}
else -> { /* Do nothing */ }
}
} else if (data == PokemonEntity.LABEL_LEVEL) {
currentEntity.dataTracker.get(PokemonEntity.LABEL_LEVEL)
@@ -27,7 +27,7 @@ import com.cobblemon.mod.common.client.gui.summary.widgets.type.DualTypeWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.type.SingleTypeWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.type.TypeWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.api.starter.RenderableStarterCategory
import com.cobblemon.mod.common.config.starter.RenderableStarterCategory
import com.cobblemon.mod.common.net.messages.server.SelectStarterPacket
import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.util.asTranslated
@@ -40,7 +40,6 @@ import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.toast.Toast
import net.minecraft.text.Text
import net.minecraft.util.Identifier
/**
* Starterselection Screen Thingy
@@ -48,7 +47,7 @@ import net.minecraft.util.Identifier
* @author Qu
* @since 2022-06-18
*/
class StarterSelectionScreen(categories: Map<Identifier, RenderableStarterCategory>): Screen("cobblemon.ui.starter.title".asTranslated()) {
class StarterSelectionScreen(private val categories: List<RenderableStarterCategory>): Screen("cobblemon.ui.starter.title".asTranslated()) {
companion object {
// Size of UI at scale 1
@@ -65,9 +64,8 @@ class StarterSelectionScreen(categories: Map<Identifier, RenderableStarterCatego
private val doubleTypeBackground = cobblemonResource("textures/gui/starterselection/starterselection_type_slot2.png")
}
private val categories = categories.toSortedMap { a, b -> a.compareTo(b) }
private var currentSelection = 0
private lateinit var currentCategoryId: Identifier
private lateinit var currentCategory: RenderableStarterCategory
private lateinit var modelWidget: ModelWidget
private lateinit var currentPokemon: RenderablePokemon
private lateinit var typeWidget: TypeWidget
@@ -97,7 +95,7 @@ class StarterSelectionScreen(categories: Map<Identifier, RenderableStarterCatego
paneWidth = 71, paneHeight = BASE_HEIGHT - 11,
topOffset = 6, bottomOffset = 5,
entryHeight = 20, entryWidth = 57,
categories = this.categories,
categories = categories,
x = x - 2, y = y + 8,
starterSelectionScreen = this
)
@@ -122,8 +120,8 @@ class StarterSelectionScreen(categories: Map<Identifier, RenderableStarterCatego
addDrawableChild(rightButton)
addDrawableChild(leftButton)
currentCategoryId = categories.firstKey()
currentPokemon = this.currentCategory().pokemon[currentSelection]
currentCategory = categories.first()
currentPokemon = currentCategory.pokemon[currentSelection]
with(currentPokemon) {
modelWidget = ModelWidget(
@@ -144,11 +142,11 @@ class StarterSelectionScreen(categories: Map<Identifier, RenderableStarterCatego
) {
CobblemonNetwork.sendPacketToServer(
SelectStarterPacket(
categoryId = currentCategoryId,
categoryName = currentCategory.name,
selected = currentSelection
)
)
this.close()
MinecraftClient.getInstance().setScreen(null)
}
addDrawableChild(selectionButton)
@@ -163,7 +161,7 @@ class StarterSelectionScreen(categories: Map<Identifier, RenderableStarterCatego
starterRoundaboutLeft = StarterRoundabout(
pX = x + 89, pY = height / 2 + 84,
pWidth = StarterRoundabout.MODEL_WIDTH, pHeight = StarterRoundabout.MODEL_HEIGHT,
pokemon = this.currentCategory().pokemon[leftOfCurrentSelection()],
pokemon = currentCategory.pokemon[leftOfCurrentSelection()],
clickAction = { _, _ -> this.left() },
rotationVector = this.modelWidget.rotVec
)
@@ -171,7 +169,7 @@ class StarterSelectionScreen(categories: Map<Identifier, RenderableStarterCatego
starterRoundaboutRight = StarterRoundabout(
pX = x + 149, pY = height / 2 + 84,
pWidth = StarterRoundabout.MODEL_WIDTH, pHeight = StarterRoundabout.MODEL_HEIGHT,
pokemon = this.currentCategory().pokemon[rightOfCurrentSelection()],
pokemon = currentCategory.pokemon[rightOfCurrentSelection()],
clickAction = { _, _ -> this.right() },
rotationVector = this.modelWidget.rotVec
)
@@ -278,8 +276,8 @@ class StarterSelectionScreen(categories: Map<Identifier, RenderableStarterCatego
super.render(context, mouseX, mouseY, delta)
}
fun changeCategory(id: Identifier) {
currentCategoryId = id
fun changeCategory(category: RenderableStarterCategory) {
currentCategory = category
currentSelection = 0
updateSelection()
}
@@ -290,7 +288,7 @@ class StarterSelectionScreen(categories: Map<Identifier, RenderableStarterCatego
updateSelection()
}
private fun rightOfCurrentSelection() : Int = if (currentSelection + 1 <= this.currentCategory().pokemon.size - 1) currentSelection + 1 else 0
private fun rightOfCurrentSelection() : Int = if (currentSelection + 1 <= currentCategory.pokemon.size - 1) currentSelection + 1 else 0
private fun left() {
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK, 1.0F))
@@ -298,16 +296,16 @@ class StarterSelectionScreen(categories: Map<Identifier, RenderableStarterCatego
updateSelection()
}
private fun leftOfCurrentSelection() : Int = if (currentSelection - 1 == -1) this.currentCategory().pokemon.size - 1 else currentSelection - 1
private fun leftOfCurrentSelection() : Int = if (currentSelection - 1 == -1) currentCategory.pokemon.size - 1 else currentSelection - 1
private fun updateSelection() {
currentPokemon = this.currentCategory().pokemon[currentSelection].also {
currentPokemon = currentCategory.pokemon[currentSelection].also {
modelWidget.pokemon = it
typeWidget = typeWidget(it, (width - BASE_WIDTH) / 2, (height - BASE_HEIGHT) / 2)
}
starterRoundaboutLeft.pokemon = this.currentCategory().pokemon[leftOfCurrentSelection()]
starterRoundaboutLeft.pokemon = currentCategory.pokemon[leftOfCurrentSelection()]
starterRoundaboutCenter.pokemon = currentPokemon
starterRoundaboutRight.pokemon = this.currentCategory().pokemon[rightOfCurrentSelection()]
starterRoundaboutRight.pokemon = currentCategory.pokemon[rightOfCurrentSelection()]
}
private fun typeWidget(pokemon: RenderablePokemon, x: Int, y: Int) : TypeWidget {
@@ -326,7 +324,5 @@ class StarterSelectionScreen(categories: Map<Identifier, RenderableStarterCatego
)
}
private fun currentCategory(): RenderableStarterCategory = this.categories[this.currentCategoryId]!!
override fun shouldPause() = true
}
@@ -12,14 +12,14 @@ import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.startselection.StarterSelectionScreen
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.api.starter.RenderableStarterCategory
import com.cobblemon.mod.common.config.starter.RenderableStarterCategory
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.widget.AlwaysSelectedEntryListWidget
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
import net.minecraft.util.Identifier
class CategoryList(
private val paneWidth: Int,
@@ -28,7 +28,7 @@ class CategoryList(
bottomOffset: Int,
private val entryWidth: Int,
entryHeight: Int,
private val categories: Map<Identifier, RenderableStarterCategory>,
private val categories: List<RenderableStarterCategory>,
val x: Int, val y: Int,
private val minecraft: MinecraftClient = MinecraftClient.getInstance(),
private val starterSelectionScreen: StarterSelectionScreen
@@ -44,6 +44,7 @@ class CategoryList(
companion object {
private const val CATEGORY_BUTTON_WIDTH = 51.5f
private const val CATEGORY_BUTTON_HEIGHT = 16f
private const val ENTRY_X_OFFSET = 10f
private val categoryResource = cobblemonResource("textures/gui/starterselection/starterselection_slot.png")
}
@@ -52,12 +53,19 @@ class CategoryList(
this.setRenderHorizontalShadows(false)
this.setRenderBackground(false)
this.setRenderSelection(false)
this.categories.forEach { (id, category) ->
this.addEntry(Category(id, category))
}
}
private var entriesCreated = false
private fun createEntries() = categories.map {
Category(it)
}
override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
if (!entriesCreated) {
createEntries().forEach { addEntry(it) }
entriesCreated = true
}
context.enableScissor(
x,
y,
@@ -72,13 +80,15 @@ class CategoryList(
this.updateSize(this.paneWidth, this.paneHeight, this.y, this.y + this.paneHeight)
this.setLeftPos(this.x)
}
private fun scale(n: Int): Int = (this.client.window.scaleFactor * n).toInt()
override fun getRowWidth() = this.entryWidth
override fun getScrollbarPositionX(): Int {
return this.left + this.width - 5
}
inner class Category(private val id: Identifier, private val category: RenderableStarterCategory) : Entry<Category>() {
inner class Category(private val category: RenderableStarterCategory) : Entry<Category>() {
override fun render(
context: DrawContext,
@@ -111,7 +121,7 @@ class CategoryList(
)
drawScaledText(
context = context,
text = category.displayName.copy(),
text = category.displayNameText,
x = x + 28,
y = y + 4.5F,
scale = 1F,
@@ -122,7 +132,7 @@ class CategoryList(
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
starterSelectionScreen.changeCategory(this.id)
starterSelectionScreen.changeCategory(category = category)
minecraft.soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK, 1.0F))
return true
}
@@ -72,7 +72,7 @@ class RestorationTankRenderer(ctx: BlockEntityRendererFactory.Context) : BlockEn
val fluidModel = if (struct.isRunning()) FLUID_MODELS[8]
else if (struct.createdPokemon != null) FLUID_MODELS[7]
else FLUID_MODELS[fillLevel.coerceAtMost(FLUID_MODELS.size - 1) - 1]
else FLUID_MODELS[fillLevel-1]
fluidModel.getQuads(entity.cachedState, null, entity.world?.random).forEach { quad ->
transparentBuffer?.quad(matrices.peek(), quad, 0.75f, 0.75f, 0.75f, light, OverlayTexture.DEFAULT_UV)
}
@@ -67,11 +67,6 @@ abstract class PokeBallPoseableState : PoseableEntityState<EmptyPokeBallEntity>(
setStatefulAnimations(currentModel!!.bedrockStateful(group, "critical"))
}
}
EmptyPokeBallEntity.CaptureState.BROKEN_FREE -> {
doLater {
setStatefulAnimations(currentModel!!.bedrockStateful(group, "break"))
}
}
else -> {}
}
}
@@ -32,6 +32,7 @@ import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.DoubleRange
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.math.remap
import kotlin.math.*
import net.minecraft.client.MinecraftClient
import net.minecraft.client.font.TextRenderer
import net.minecraft.client.render.*
@@ -52,7 +53,6 @@ import org.joml.Math
import org.joml.Quaternionf
import org.joml.Vector3f
import org.joml.Vector4f
import kotlin.math.*
class PokemonRenderer(
context: EntityRendererFactory.Context
@@ -60,7 +60,7 @@ class PokemonRenderer(
companion object {
val recallBeamColour = Vector4f(1F, 0.1F, 0.1F, 1F)
fun ease(x: Double): Double {
return 1 - (1 - x).pow(3)
return 1 - (1 - x).pow(3);
}
}
@@ -86,7 +86,7 @@ class PokemonRenderer(
if (entity.beamMode != 0) {
renderTransition(
modelNow,
entity.beamMode,
entity.beamMode == 1,
entity,
partialTicks,
poseMatrix,
@@ -130,7 +130,7 @@ class PokemonRenderer(
fun renderTransition(
modelNow: PoseableEntityModel<PokemonEntity>,
beamMode: Int,
sendingOut: Boolean,
entity: PokemonEntity,
partialTicks: Float,
poseMatrix: MatrixStack,
@@ -139,7 +139,7 @@ class PokemonRenderer(
clientDelegate: PokemonClientDelegate
) {
val s = clientDelegate.secondsSinceBeamEffectStarted
if (modelNow is PokemonPoseableModel && beamMode == 3) {
if (modelNow is PokemonPoseableModel && !sendingOut) {
if (s > BEAM_EXTEND_TIME) {
val value = (s - BEAM_EXTEND_TIME) / BEAM_SHRINK_TIME
val colourValue = 1F - min(0.6F, value)
@@ -163,9 +163,9 @@ class PokemonRenderer(
}
}
if (clientDelegate.sendOutPosition == null && beamMode == 1) {
if (clientDelegate.sendOutPosition == null && sendingOut) {
clientDelegate.sendOutPosition = beamSourcePosition
} else if (beamMode == 1) {
} else if (sendingOut) {
clientDelegate.sendOutPosition = clientDelegate.sendOutPosition!!.add(0.0, 0.04, 0.0)
beamSourcePosition = clientDelegate.sendOutPosition!!
}
@@ -184,9 +184,9 @@ class PokemonRenderer(
poseMatrix.multiply(RotationAxis.POSITIVE_Y.rotation(-angle.toFloat() + (180 * Math.PI / 180).toFloat()))
// TODO: if you want to remove the open ball, add `!clientDelegate.ballDone` to the if statement
if (beamMode == 1 && !clientDelegate.ballDone){
if (sendingOut && !clientDelegate.ballDone){
if(entity.pokemon.caughtBall.name.toString().contains("beast")){
// get rotation angle on x-axis for facingDir
// get rotation angle on x acis for facingDir
val xAngleFacingDir = MathHelper.atan2(facingDir.y, sqrt(facingDir.x * facingDir.x + facingDir.z * facingDir.z))
poseMatrix.multiply(RotationAxis.POSITIVE_X.rotation(-xAngleFacingDir.toFloat()))
}
@@ -203,7 +203,7 @@ class PokemonRenderer(
)
}
poseMatrix.pop()
if (beamMode == 3) {
if (!sendingOut) {
renderBeam(poseMatrix, partialTicks, entity, phaseTarget, buffer, offsetDirection)
}
}
@@ -9,7 +9,11 @@
package com.cobblemon.mod.common.command
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.net.messages.client.starter.OpenStarterUIPacket
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.permission
import com.mojang.brigadier.Command.SINGLE_SUCCESS
import com.mojang.brigadier.CommandDispatcher
@@ -34,7 +38,18 @@ object OpenStarterScreenCommand {
private fun execute(context: CommandContext<ServerCommandSource>) : Int {
val player = EntityArgumentType.getPlayer(context, "player")
Cobblemon.starterHandler.requestStarterChoice(player)
val playerData = Cobblemon.playerData.get(player)
if (playerData.starterSelected) {
context.source.sendFeedback({ lang("ui.starter.hasalreadychosen", player.name).red() }, true)
return 0
}
if (playerData.starterLocked) {
playerData.starterLocked = false
playerData.sendToPlayer(player)
}
playerData.starterPrompted = true
Cobblemon.playerData.saveSingle(playerData)
player.sendPacket(OpenStarterUIPacket(Cobblemon.starterHandler.getStarterList(player)))
return SINGLE_SUCCESS
}
@@ -45,7 +45,7 @@ object SpawnAllPokemon {
for (species in PokemonSpecies.implemented) {
if (species.nationalPokedexNumber in range) {
LOGGER.debug(species.name)
species.create().sendOut(player.world as ServerWorld, player.pos)
species.create().sendOut(player.world as ServerWorld, player.pos, null)
}
}
Loaded 100 of 1151 files, more files were not shown because too many files have changed in this diff. Show more