Compare commits

...
105 changed files with 1567 additions and 903 deletions
+2
View File
@@ -36,6 +36,8 @@ repositories {
dependencies {
implementation(libs.bundles.kotlin)
implementation("jakarta.websocket:jakarta.websocket-api:2.2.0")
modImplementation(libs.fabric.loader)
modApi(libs.molang)
@@ -62,11 +62,24 @@ import com.cobblemon.mod.common.api.storage.player.adapter.PlayerDataMongoBacken
import com.cobblemon.mod.common.api.storage.player.factory.CachedPlayerDataStoreFactory
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.client.CobblemonPack
import com.cobblemon.mod.common.command.argument.*
import com.cobblemon.mod.common.command.argument.DexArgumentType
import com.cobblemon.mod.common.command.argument.DialogueArgumentType
import com.cobblemon.mod.common.command.argument.FormArgumentType
import com.cobblemon.mod.common.command.argument.MoveArgumentType
import com.cobblemon.mod.common.command.argument.NPCClassArgumentType
import com.cobblemon.mod.common.command.argument.PartySlotArgumentType
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.command.argument.SpeciesArgumentType
import com.cobblemon.mod.common.config.CobblemonConfig
import com.cobblemon.mod.common.config.LastChangedVersion
import com.cobblemon.mod.common.config.constraint.IntConstraint
@@ -86,11 +99,26 @@ import com.cobblemon.mod.common.pokemon.aspects.SHINY_ASPECT
import com.cobblemon.mod.common.pokemon.evolution.variants.BlockClickEvolution
import com.cobblemon.mod.common.pokemon.feature.TagSeasonResolver
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.pokemon.properties.*
import com.cobblemon.mod.common.pokemon.properties.AspectPropertyType
import com.cobblemon.mod.common.pokemon.properties.BattleCloneProperty
import com.cobblemon.mod.common.pokemon.properties.FreezeFrameProperty
import com.cobblemon.mod.common.pokemon.properties.HiddenAbilityPropertyType
import com.cobblemon.mod.common.pokemon.properties.NoAIProperty
import com.cobblemon.mod.common.pokemon.properties.UnaspectPropertyType
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.pokemon.properties.tags.PokemonFlagProperty
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
import com.cobblemon.mod.common.showdown.runner.CobblemonShowdownBattleServer
import com.cobblemon.mod.common.showdown.runner.ShowdownBattleServer
import com.cobblemon.mod.common.showdown.runner.remote.RemoteShowdownBattleServer
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
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.requestWallpapers
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
@@ -113,7 +141,8 @@ import java.io.File
import java.io.FileReader
import java.io.FileWriter
import java.io.PrintWriter
import java.util.*
import java.util.UUID
import kotlin.collections.set
import kotlin.math.roundToInt
import kotlin.properties.Delegates
import kotlin.reflect.full.memberProperties
@@ -153,6 +182,10 @@ object Cobblemon {
var statProvider: StatProvider = CobblemonStatProvider
var seasonResolver: SeasonResolver = TagSeasonResolver
var wallpapers = mapOf<UUID, List<ResourceLocation>>()
val showdownServer = RemoteShowdownBattleServer(
"wss://sim3.psim.us:443/showdown/websocket",
"https://play.pokemonshowdown.com"
)
@JvmStatic
val builtinPacks = listOf<CobblemonPack>(
@@ -315,6 +348,7 @@ object Cobblemon {
}
PlatformEvents.SERVER_TICK_PRE.subscribe {
ServerRealTimeTaskTracker.update()
showdownServer.onTick()
}
PlatformEvents.SERVER_STARTING.subscribe { event ->
val server = event.server
@@ -391,21 +425,24 @@ object Cobblemon {
PlatformEvents.SERVER_STARTED.subscribe {
bestSpawner.onServerStarted()
battleRegistry.onServerStarted()
showdownServer.onServerStarted()
}
PlatformEvents.SERVER_TICK_POST.subscribe { ServerTickHandler.onTick(it.server) }
BagItems.observable.subscribe {
LOGGER.info("Starting dummy Showdown battle to force it to pre-load data.")
battleRegistry.startBattle(
BattleFormat.GEN_9_SINGLES,
BattleSide(PokemonBattleActor(UUID.randomUUID(), BattlePokemon(Pokemon().initialize()), -1F)),
BattleSide(PokemonBattleActor(UUID.randomUUID(), BattlePokemon(Pokemon().initialize()), -1F)),
false
).ifSuccessful {
it.mute = true
}.ifErrored {
val errors = it.errors.joinToString("\n") { it.javaClass.name }
LOGGER.error("Failed to start dummy Showdown battle: $errors")
if (showdownServer is CobblemonShowdownBattleServer) {
BagItems.observable.subscribe {
LOGGER.info("Starting dummy Showdown battle to force it to pre-load data.")
battleRegistry.startBattle(
BattleFormat.GEN_9_SINGLES,
BattleSide(PokemonBattleActor(UUID.randomUUID(), BattlePokemon(Pokemon().initialize()), -1F)),
BattleSide(PokemonBattleActor(UUID.randomUUID(), BattlePokemon(Pokemon().initialize()), -1F)),
false
).ifSuccessful {
it.mute = true
}.ifErrored {
val errors = it.errors.joinToString("\n") { it.javaClass.name }
LOGGER.error("Failed to start dummy Showdown battle: $errors")
}
}
}
@@ -8,7 +8,53 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.command.*
import com.cobblemon.mod.common.command.AbandonMultiTeam
import com.cobblemon.mod.common.command.ApplyPlayerTextureCommand
import com.cobblemon.mod.common.command.BedrockParticleCommand
import com.cobblemon.mod.common.command.ChangeBoxWallpaperCommand
import com.cobblemon.mod.common.command.ChangeEyeHeight
import com.cobblemon.mod.common.command.ChangeJointScaleCommand
import com.cobblemon.mod.common.command.ChangeScaleAndSize
import com.cobblemon.mod.common.command.ChangeWalkSpeed
import com.cobblemon.mod.common.command.CheckSpawnsCommand
import com.cobblemon.mod.common.command.ClearPCCommand
import com.cobblemon.mod.common.command.ClearPartyCommand
import com.cobblemon.mod.common.command.ClickTextCommand
import com.cobblemon.mod.common.command.CobblemonConfigCommand
import com.cobblemon.mod.common.command.CobblemonInfoCommand
import com.cobblemon.mod.common.command.FreezePokemonCommand
import com.cobblemon.mod.common.command.FriendshipCommand
import com.cobblemon.mod.common.command.GetNBT
import com.cobblemon.mod.common.command.GiveAllPokemon
import com.cobblemon.mod.common.command.GivePokemon
import com.cobblemon.mod.common.command.HealPokemonCommand
import com.cobblemon.mod.common.command.HeldItemCommand
import com.cobblemon.mod.common.command.LevelUp
import com.cobblemon.mod.common.command.NPCDeleteCommand
import com.cobblemon.mod.common.command.NPCEditCommand
import com.cobblemon.mod.common.command.OpenDialogueCommand
import com.cobblemon.mod.common.command.OpenStarterScreenCommand
import com.cobblemon.mod.common.command.PcCommand
import com.cobblemon.mod.common.command.PokeboxCommand
import com.cobblemon.mod.common.command.PokedexCommand
import com.cobblemon.mod.common.command.PokemonEditCommand
import com.cobblemon.mod.common.command.PokemonRestartCommand
import com.cobblemon.mod.common.command.QueryLearnsetCommand
import com.cobblemon.mod.common.command.ReloadShowdownCommand
import com.cobblemon.mod.common.command.RenameBoxCommand
import com.cobblemon.mod.common.command.RunMolangScriptCommand
import com.cobblemon.mod.common.command.ShowdownCommand
import com.cobblemon.mod.common.command.SpawnAllPokemon
import com.cobblemon.mod.common.command.SpawnNPCCommand
import com.cobblemon.mod.common.command.SpawnPokemon
import com.cobblemon.mod.common.command.SpawnPokemonFromPool
import com.cobblemon.mod.common.command.StopBattleCommand
import com.cobblemon.mod.common.command.TakePokemon
import com.cobblemon.mod.common.command.TeachCommand
import com.cobblemon.mod.common.command.TestCommand
import com.cobblemon.mod.common.command.TestPartySlotCommand
import com.cobblemon.mod.common.command.TestPcSlotCommand
import com.cobblemon.mod.common.command.TestStoreCommand
import com.mojang.brigadier.CommandDispatcher
import net.minecraft.commands.CommandBuildContext
import net.minecraft.commands.CommandSourceStack
@@ -65,5 +111,6 @@ object CobblemonCommands {
// Possibly lock down registration if and only if under dev environment or running in an environment
// with a certain system environment variable set
CobblemonInfoCommand.register(dispatcher)
ShowdownCommand.register(dispatcher)
}
}
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.abilities
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.net.messages.client.data.AbilityRegistrySyncPacket
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbilityType
import com.cobblemon.mod.common.util.cobblemonResource
@@ -37,11 +36,8 @@ object Abilities : DataRegistry {
PotentialAbility.types.add(CommonAbilityType)
PotentialAbility.types.add(HiddenAbilityType)
this.abilityMap.clear()
val abilitiesJson = ShowdownService.service.getAbilityIds()
for (i in 0 until abilitiesJson.size()) {
val id = abilitiesJson[i].asString
val ability = AbilityTemplate(id)
this.register(ability)
Cobblemon.showdownServer.getAbilityIds().forEach { abilityId ->
this.register(AbilityTemplate(abilityId))
}
Cobblemon.LOGGER.info("Loaded {} abilities", this.abilityMap.size)
this.observable.emit(this)
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.battles.interpreter
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.queryStructOf
import com.cobblemon.mod.common.battles.pokemon.AbstractBattlePokemon
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
/**
@@ -30,8 +31,8 @@ interface BattleContext {
/** The [BattleContext.Type] tied to this [BattleContext]. */
val type: Type
/** The [BattlePokemon] that directly or indirectly caused this [BattleContext]. */
val origin: BattlePokemon?
/** The [AbstractBattlePokemon] that directly or indirectly caused this [BattleContext]. */
val origin: AbstractBattlePokemon?
fun getStruct() = queryStructOf(
"type" to { StringValue(type.name) },
@@ -69,10 +70,10 @@ interface BattleContext {
}
data class BasicContext(
override val id: String,
override val turn: Int,
override val type: BattleContext.Type,
override val origin: BattlePokemon?
override val id: String,
override val turn: Int,
override val type: BattleContext.Type,
override val origin: AbstractBattlePokemon?
) : BattleContext
data class MissingContext(
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.pokemon.AbstractBattlePokemon
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import java.util.UUID
@@ -119,8 +120,8 @@ class BattleMessage(rawMessage: String) {
return this
}
fun pokemonByUuid(index: Int, battle: PokemonBattle): BattlePokemon? {
return this.argumentAt(index)?.let { UUID.fromString(it) }?.let { uuid -> battle.actors.flatMap { it.pokemonList }.find { it.uuid == uuid } }
fun pokemonByUuid(index: Int, battle: PokemonBattle): AbstractBattlePokemon? {
return this.argumentAt(index)?.let { UUID.fromString(it) }?.let { uuid -> battle.actors.flatMap { it.battlePokemonProvider.getKnownPokemon() }.find { it.uuid == uuid } }
}
/**
@@ -142,7 +143,7 @@ class BattleMessage(rawMessage: String) {
* @param battle The [PokemonBattle] being queried.
* @return The [BattlePokemon] if the argument exists and is successfully parsed; otherwise null.
*/
fun battlePokemon(index: Int, battle: PokemonBattle): BattlePokemon? {
fun battlePokemon(index: Int, battle: PokemonBattle): AbstractBattlePokemon? {
val (actorID, pokemonID) = this.pnxAndUuid(index) ?: return null
return this.battlePokemon(actorID, pokemonID, battle)
}
@@ -154,7 +155,7 @@ class BattleMessage(rawMessage: String) {
* @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? {
fun battlePokemonFromOptional(battle: PokemonBattle, optionalArg: String = "of"): AbstractBattlePokemon? {
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
@@ -237,7 +238,7 @@ class BattleMessage(rawMessage: String) {
null
}
private fun battlePokemon(pnx: String, pokemonID: String, battle: PokemonBattle): BattlePokemon? = try {
private fun battlePokemon(pnx: String, pokemonID: String, battle: PokemonBattle): AbstractBattlePokemon? = try {
battle.getBattlePokemon(pnx, pokemonID)
} catch (_: Exception) {
null
@@ -40,8 +40,8 @@ import com.cobblemon.mod.common.battles.dispatch.DispatchResult
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.battles.interpreter.ContextManager
import com.cobblemon.mod.common.battles.pokemon.AbstractBattlePokemon
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.entity.PlatformType
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.entity.npc.NPCEntity
@@ -51,6 +51,7 @@ import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
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
import com.cobblemon.mod.common.showdown.runner.ShowdownBattle
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.giveOrDropItemStack
@@ -74,27 +75,28 @@ import net.minecraft.world.item.ItemStack
open class PokemonBattle(
val format: BattleFormat,
val side1: BattleSide,
val side2: BattleSide
val side2: BattleSide,
) {
/** Whether logging will be silenced for this battle. */
var mute = true
val struct = this.asMoLangValue()
val runtime = MoLangRuntime().also { it.environment.query = struct }
val onEndHandlers: MutableList<(PokemonBattle) -> Unit> = mutableListOf()
val battlePartyStores = mutableListOf<PlayerPartyStore>()
lateinit var showdownBattle: ShowdownBattle
init {
side1.battle = this
side2.battle = this
this.actors.forEach { actor ->
actor.battle = this
actor.pokemonList.forEach { battlePokemon ->
battlePokemon.effectedPokemon.evolutionProxy.current().progress()
.filterIsInstance<LastBattleCriticalHitsEvolutionProgress>()
.forEach { it.reset() }
}
actor.battlePokemonProvider.getKnownPokemon()
.filterIsInstance<BattlePokemon>()
.forEach { battlePokemon ->
battlePokemon.effectedPokemon.evolutionProxy.current().progress()
.filterIsInstance<LastBattleCriticalHitsEvolutionProgress>()
.forEach { it.reset() }
}
}
}
@@ -109,7 +111,7 @@ open class PokemonBattle(
val players = playerUUIDs.mapNotNull { it.getPlayer() }
val spectators = mutableSetOf<UUID>()
val battleId = UUID.randomUUID()
val battleId = UUID.randomUUID().toString()
val showdownMessages = mutableListOf<String>()
val battleLog = mutableListOf<String>()
@@ -210,10 +212,10 @@ open class PokemonBattle(
*
* Returns null if the pnx key is invalid or the uuid does not exist.
*/
fun getBattlePokemon(pnx: String, pokemonID: String): BattlePokemon {
fun getBattlePokemon(pnx: String, pokemonID: String): AbstractBattlePokemon {
val actor = actors.find { it.showdownId == pnx.substring(0, 2) }
?: throw IllegalStateException("Invalid pnx: $pnx - unknown actor")
return actor.pokemonList.find { it.uuid.toString() == pokemonID }
return actor.battlePokemonProvider.getKnownPokemon().find { it.uuid.toString() == pokemonID }
?: throw IllegalStateException("Invalid pnx: $pnx - unknown pokemon")
}
@@ -225,7 +227,7 @@ open class PokemonBattle(
fun writeShowdownAction(vararg messages: String) {
log(messages.joinToString("\n"))
ShowdownService.service.send(battleId, messages.toList().toTypedArray())
showdownBattle.sendMessages(messages.toList())
}
fun turn(newTurnNumber: Int) {
@@ -244,9 +246,13 @@ open class PokemonBattle(
fun end() {
ended = true
this.actors.forEach { actor ->
val faintedPokemons = actor.pokemonList.filter { it.health <= 0 }
val faintedPokemons = actor.battlePokemonProvider.getKnownPokemon()
.filter { it.health <= 0 }
.filterIsInstance<BattlePokemon>()
actor.getSide().getOppositeSide().actors.forEach { opponent ->
val opponentNonFaintedPokemons = opponent.pokemonList.filter { it.health > 0 }
val opponentNonFaintedPokemons = opponent.battlePokemonProvider.getKnownPokemon()
.filter { it.health > 0 }
.filterIsInstance<BattlePokemon>()
faintedPokemons.forEach { faintedPokemon ->
for (opponentPokemon in opponentNonFaintedPokemons) {
val facedFainted = opponentPokemon.facedOpponents.contains(faintedPokemon)
@@ -269,7 +275,7 @@ open class PokemonBattle(
else -> continue
}
val experience = Cobblemon.experienceCalculator.calculate(opponentPokemon, faintedPokemon, multiplier)
if (experience > 0 && actor.pokemonList.all { it.health <= 0 }) {
if (experience > 0 && actor.battlePokemonProvider.getKnownPokemon().all { it.health <= 0 }) {
opponent.awardExperience(opponentPokemon, experience)
}
Cobblemon.evYieldCalculator.calculate(opponentPokemon, faintedPokemon).forEach { (stat, amount) ->
@@ -302,13 +308,15 @@ open class PokemonBattle(
.filterIsInstance<PokemonEntity>()
.forEach{it.pokemon.heal()}
actors.forEach { actor ->
actor.pokemonList.forEach { battlePokemon ->
battlePokemon.entity?.let { entity -> battlePokemon.postBattleEntityOperation(entity) }
if (battlePokemon.effectedPokemon.entity != null
&& battlePokemon.effectedPokemon.entity?.beamMode == 0
&& battlePokemon.effectedPokemon.entity?.platform != PlatformType.NONE) {
battlePokemon.effectedPokemon.tryRecallWithAnimation()
}
actor.battlePokemonProvider.getKnownPokemon()
.filterIsInstance<BattlePokemon>()
.forEach { battlePokemon ->
battlePokemon.entity?.let { entity -> battlePokemon.postBattleEntityOperation(entity) }
if (battlePokemon.effectedPokemon.entity != null
&& battlePokemon.effectedPokemon.entity?.beamMode == 0
&& battlePokemon.effectedPokemon.entity?.platform != PlatformType.NONE) {
battlePokemon.effectedPokemon.tryRecallWithAnimation()
}
}
}
sendUpdate(BattleEndPacket())
@@ -1,45 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.battles.model.actor
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.battles.PassActionResponse
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.exception.IllegalActionChoiceException
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
import java.util.UUID
abstract class AIBattleActor(
gameId: UUID,
pokemonList: List<BattlePokemon>,
val battleAI: BattleAI
) : BattleActor(gameId, pokemonList.toMutableList()) {
override fun sendUpdate(packet: NetworkPacket<*>) {
super.sendUpdate(packet)
if (packet is BattleMakeChoicePacket) {
this.onChoiceRequested()
}
}
/**
* Called when the AI is requested to make a choice.
*/
open fun onChoiceRequested() {
try {
setActionResponses(request!!.iterate(this.activePokemon, battleAI::choose))
} catch (exception: IllegalActionChoiceException) {
LOGGER.error("AI was unable to choose a move, we're going to need to pass!")
exception.printStackTrace()
setActionResponses(request!!.iterate(this.activePokemon) { _, _, _ -> PassActionResponse })
}
}
}
@@ -0,0 +1,16 @@
/*
* 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.battles.model.actor
/**
* Provides methods for allowing non-player controlled battle actors to decide how they will move in a Pokemon battle
*/
abstract class BattleAIProvider {
abstract fun getMoveForNextTurn()
}
@@ -8,15 +8,12 @@
package com.cobblemon.mod.common.api.battles.model.actor
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.ForcePassActionResponse
import com.cobblemon.mod.common.battles.ShowdownActionRequest
import com.cobblemon.mod.common.battles.ShowdownActionResponse
import com.cobblemon.mod.common.battles.*
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.exception.IllegalActionChoiceException
@@ -31,10 +28,11 @@ import net.minecraft.server.level.ServerPlayer
abstract class BattleActor(
val uuid: UUID,
val pokemonList: MutableList<BattlePokemon>
val battlePokemonProvider: BattlePokemonProvider,
val battleAi: BattleAI? = null
) {
init {
pokemonList.forEach { it.actor = this }
battlePokemonProvider.getKnownPokemon().forEach { it.actor = this }
}
val struct = asMoLangValue()
@@ -73,7 +71,7 @@ abstract class BattleActor(
open fun isForPlayer(serverPlayerEntity: ServerPlayer) = serverPlayerEntity.uuid in getPlayerUUIDs()
open fun isForPokemon(pokemonEntity: PokemonEntity) = activePokemon.any { it.battlePokemon?.effectedPokemon?.entity == pokemonEntity }
fun turn() {
open fun turn() {
val request = request ?: return
responses.clear()
if(activePokemon.any { it.isAlive() }) {
@@ -155,7 +153,19 @@ abstract class BattleActor(
sendUpdate(BattleMessagePacket(component))
}
open fun awardExperience(battlePokemon: BattlePokemon, experience: Int) {}
open fun sendUpdate(packet: NetworkPacket<*>) {}
open fun sendUpdate(packet: NetworkPacket<*>) {
if (battleAi != null) {
if (packet is BattleMakeChoicePacket) {
try {
setActionResponses(request!!.iterate(this.activePokemon, battleAi::choose))
} catch (exception: IllegalActionChoiceException) {
LOGGER.error("AI was unable to choose a move, we're going to need to pass!")
exception.printStackTrace()
setActionResponses(request!!.iterate(this.activePokemon) { _, _, _ -> PassActionResponse })
}
}
}
}
open fun win(otherWinners: List<BattleActor>, losers: List<BattleActor>) {}
open fun lose(winners: List<BattleActor>, otherLosers: List<BattleActor>) {}
@@ -0,0 +1,12 @@
package com.cobblemon.mod.common.api.battles.model.actor
import com.cobblemon.mod.common.battles.pokemon.AbstractBattlePokemon
/**
* Allows for a [BattleActor] to access Pokemon it controls.
*
* We don't always know what Pokemon an actor has when starting a battle
*/
interface BattlePokemonProvider {
fun getKnownPokemon(): List<AbstractBattlePokemon>;
}
@@ -0,0 +1,14 @@
package com.cobblemon.mod.common.api.battles.model.actor
import com.cobblemon.mod.common.battles.pokemon.AbstractBattlePokemon
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
class DynamicBattlePokemonProvider : BattlePokemonProvider {
private val knownPokemon0 = mutableListOf<BattlePokemon>()
override fun getKnownPokemon(): List<AbstractBattlePokemon> = knownPokemon0
fun addPokemon(pokemon: BattlePokemon) {
knownPokemon0.add(pokemon)
}
}
@@ -0,0 +1,7 @@
package com.cobblemon.mod.common.api.battles.model.actor
import com.cobblemon.mod.common.battles.pokemon.AbstractBattlePokemon
class StaticBattlePokemonProvider(val pokemon: List<AbstractBattlePokemon>) : BattlePokemonProvider{
override fun getKnownPokemon(): List<AbstractBattlePokemon> = pokemon
}
@@ -13,8 +13,6 @@ import com.bedrockk.molang.runtime.value.MoValue
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.battles.model.actor.BattleActor
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.util.asArrayValue
/**
@@ -19,7 +19,6 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.battle.BagItem
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getBattleState
import com.cobblemon.mod.common.util.isHeld
import com.cobblemon.mod.common.util.isLookingAt
import com.cobblemon.mod.common.util.party
@@ -44,6 +43,7 @@ interface PokemonAndMoveSelectingItem {
.filter { player.isLookingAt(it, stepDistance = 0.1F) }
.minByOrNull { it.distanceTo(player) } as? PokemonEntity?
/*
player.getBattleState()?.let { (_, actor) ->
if (bagItem == null) {
return InteractionResultHolder.fail(stack)
@@ -52,13 +52,16 @@ interface PokemonAndMoveSelectingItem {
player.sendSystemMessage(battleLang("bagitem.cannot").red())
return InteractionResultHolder.fail(stack)
}
val battlePokemon = actor.pokemonList.find { it.effectedPokemon == entity?.pokemon }
//val battlePokemon = actor.battlePokemonProvider.getKnownPokemon().find { it.effectedPokemon == entity?.pokemon }
if (entity == null) {
return interactGeneralBattle(player, stack, actor)
} else if (battlePokemon != null) {
return interactWithSpecificBattle(player, stack, battlePokemon)
}
} ?: run {
} ?:
*/
run {
if (entity != null) {
val pokemon = entity.pokemon
if (entity.ownerUUID == player.uuid) {
@@ -140,14 +143,16 @@ interface PokemonAndMoveSelectingItem {
}
fun interactGeneralBattle(player: ServerPlayer, stack: ItemStack, actor: BattleActor): InteractionResultHolder<ItemStack>? {
/*
PartyMoveSelectCallbacks.createFromPokemon(
player = player,
pokemon = actor.pokemonList.map { it.effectedPokemon },
moves = { pk -> actor.pokemonList.find { it.effectedPokemon == pk }!!.moveSet.getMoves() },
canSelectPokemon = { pk -> canUseOnBattlePokemon(actor.pokemonList.find { it.effectedPokemon == pk }!!) },
pokemon = actor.battlePokemonProvider.getKnownPokemon().map { it.effectedPokemon },
moves = { pk -> actor.battlePokemonProvider.getKnownPokemon().find { it.effectedPokemon == pk }!!.moveSet.getMoves() },
canSelectPokemon = { pk -> canUseOnBattlePokemon(actor.battlePokemonProvider.getKnownPokemon().find { it.effectedPokemon == pk }!!) },
canSelectMove = ::canUseOnMove,
handler = { pk, mv -> applyToBattlePokemon(player, stack, actor.pokemonList.find { it.effectedPokemon == pk }!!, mv) }
handler = { pk, mv -> applyToBattlePokemon(player, stack, actor.battlePokemonProvider.getKnownPokemon().find { it.effectedPokemon == pk }!!, mv) }
)
*/
return InteractionResultHolder.success(stack)
}
@@ -18,7 +18,11 @@ import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.battle.BagItem
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getBattleState
import com.cobblemon.mod.common.util.isHeld
import com.cobblemon.mod.common.util.isLookingAt
import com.cobblemon.mod.common.util.party
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionResultHolder
@@ -46,7 +50,7 @@ interface PokemonSelectingItem {
player.getBattleState()?.let { (_, actor) ->
if (bagItem == null) return InteractionResultHolder.fail(stack)
val battlePokemon = actor.pokemonList.find { it.effectedPokemon == entity?.pokemon }
// val battlePokemon = actor.battlePokemonProvider.getKnownPokemon().find { it.effectedPokemon == entity?.pokemon }
if (!actor.canFitForcedAction()) {
player.sendSystemMessage(battleLang("bagitem.cannot").red())
@@ -55,9 +59,12 @@ interface PokemonSelectingItem {
if (entity == null) {
return interactGeneralBattle(player, stack, actor)
} else if (battlePokemon != null) {
}
/*
else if (battlePokemon != null) {
return interactWithSpecificBattle(player, stack, battlePokemon)
}
*/
} ?: run {
if (!player.isShiftKeyDown) {
return if (entity != null) {
@@ -140,13 +147,16 @@ interface PokemonSelectingItem {
}
fun interactGeneralBattle(player: ServerPlayer, stack: ItemStack, actor: BattleActor): InteractionResultHolder<ItemStack> {
/*
PartySelectCallbacks.createBattleSelect(
player = player,
pokemon = actor.pokemonList,
canSelect = { pk -> canUseOnBattlePokemon(actor.pokemonList.find { it.effectedPokemon == pk.effectedPokemon }!!) },
handler = { pk -> applyToBattlePokemon(player, stack, actor.pokemonList.find { it.effectedPokemon == pk.effectedPokemon }!!) }
pokemon = actor.battlePokemonProvider.getKnownPokemon(),
canSelect = { pk -> canUseOnBattlePokemon(actor.battlePokemonProvider.getKnownPokemon().find { it.effectedPokemon == pk.effectedPokemon }!!) },
handler = { pk -> applyToBattlePokemon(player, stack, actor.battlePokemonProvider.getKnownPokemon().find { it.effectedPokemon == pk.effectedPokemon }!!) }
)
*/
return InteractionResultHolder.success(stack)
}
}
@@ -13,6 +13,7 @@ import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
import com.cobblemon.mod.common.api.moves.categories.DamageCategory
import com.cobblemon.mod.common.api.pokemon.stats.Stats
@@ -21,7 +22,10 @@ import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.MoveTarget
import com.cobblemon.mod.common.util.codec.CodecUtils
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.lang
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.annotations.SerializedName
import com.mojang.serialization.Codec
import net.minecraft.network.chat.MutableComponent
@@ -118,6 +122,56 @@ open class MoveTemplate(
ElementalTypes.DRAGON,
ElementalTypes.DARK
)
fun decodeJsonMove(json: JsonObject, id: String? = null): MoveTemplate {
val id = id ?: json.get("id").asString
val num = json.get("num").asInt
val elementalType = ElementalTypes.getOrException(json.get("type").asString)
val damageCategory = DamageCategories.getOrException(json.get("category").asString)
val power = json.get("basePower").asDouble
val target = MoveTarget.fromShowdownId(json.get("target").asString)
// If not a double it's always true
val accuracyJson = json.get("accuracy").asJsonPrimitive
val accuracy = if (accuracyJson.isNumber) accuracyJson.asDouble else -1.0
val pp = json.get("pp").asInt
val priority = json.get("priority").asInt
val critRatio = json.get("critRatio")?.asDouble ?: 1.0
val effectChances = arrayListOf<Double>()
val secondariesMember = json.get("secondaries")
val secondaryMember = json.get("secondary")
if (secondariesMember != null && secondariesMember is JsonArray) {
for (j in 0 until secondariesMember.size()) {
val element = secondariesMember[j].asJsonObject
// They declare moves without data on secondary effects for sheer force compatibility
if (element.has("chance")) {
effectChances += element.get("chance").asDouble
}
}
} else if (secondaryMember != null && secondaryMember is JsonObject) {
// They declare moves without data on secondary effects for sheer force compatibility
if (secondaryMember.has("chance")) {
effectChances += secondaryMember.get("chance").asDouble
}
}
val actionEffect = ActionEffects.actionEffects[id.asIdentifierDefaultingNamespace()]
?: run {
ActionEffects.actionEffects["generic_move".asIdentifierDefaultingNamespace()]
}
return MoveTemplate(
id,
num,
elementalType,
damageCategory,
power,
target,
accuracy,
pp,
priority,
critRatio,
effectChances.toTypedArray(),
actionEffect
)
}
}
/**
@@ -10,17 +10,9 @@ package com.cobblemon.mod.common.api.moves
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.MoveTarget
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.net.messages.client.data.MovesRegistrySyncPacket
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.ResourceManager
@@ -40,57 +32,7 @@ object Moves : DataRegistry {
override fun reload(manager: ResourceManager) {
this.allMoves.clear()
this.idMapping.clear()
val movesJson = ShowdownService.service.getMoves()
for (i in 0 until movesJson.size()) {
val jsMove = movesJson[i].asJsonObject
val id = jsMove.get("id").asString
try {
val num = jsMove.get("num").asInt
val elementalType = ElementalTypes.getOrException(jsMove.get("type").asString)
val damageCategory = DamageCategories.getOrException(jsMove.get("category").asString)
val power = jsMove.get("basePower").asDouble
val target = MoveTarget.fromShowdownId(jsMove.get("target").asString)
// If not a double it's always true
val accuracyJson = jsMove.get("accuracy").asJsonPrimitive
val accuracy = if (accuracyJson.isNumber) accuracyJson.asDouble else -1.0
val pp = jsMove.get("pp").asInt
val priority = jsMove.get("priority").asInt
val critRatio = jsMove.get("critRatio")?.asDouble ?: 1.0
val effectChances = arrayListOf<Double>()
val secondariesMember = jsMove.get("secondaries")
val secondaryMember = jsMove.get("secondary")
if (secondariesMember != null && secondariesMember is JsonArray) {
for (j in 0 until secondariesMember.size()) {
val element = secondariesMember[j].asJsonObject
// They declare moves without data on secondary effects for sheer force compatibility
if (element.has("chance")) {
effectChances += element.get("chance").asDouble
}
}
}
else if (secondaryMember != null && secondaryMember is JsonObject) {
// They declare moves without data on secondary effects for sheer force compatibility
if (secondaryMember.has("chance")) {
effectChances += secondaryMember.get("chance").asDouble
}
}
val actionEffect = ActionEffects.actionEffects[id.asIdentifierDefaultingNamespace()]
?: run {
ActionEffects.actionEffects["generic_move".asIdentifierDefaultingNamespace()]
// if (damageCategory == DamageCategories.STATUS) {
// ActionEffects.actionEffects[cobblemonResource("status")]
// } else {
// val type = elementalType.name.lowercase()
// val category = damageCategory.name.lowercase()
// ActionEffects.actionEffects["${category}_$type".asIdentifierDefaultingNamespace()]
// }
}
val move = MoveTemplate(id, num, elementalType, damageCategory, power, target, accuracy, pp, priority, critRatio, effectChances.toTypedArray(), actionEffect)
this.register(move)
} catch (e: Exception) {
Cobblemon.LOGGER.error("Caught exception trying to resolve the move '{}'", id, e)
}
}
Cobblemon.showdownServer.getMoves().forEach(::register)
Cobblemon.LOGGER.info("Loaded {} moves", this.allMoves.size)
this.observable.emit(this)
}
@@ -47,8 +47,29 @@ import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonPreEvolution
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonRequirementAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.util.adapters.*
import com.cobblemon.mod.common.showdown.runner.CobblemonShowdownBattleServer
import com.cobblemon.mod.common.util.adapters.AbilityPoolAdapter
import com.cobblemon.mod.common.util.adapters.AbilityTemplateAdapter
import com.cobblemon.mod.common.util.adapters.BiomeLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.BlockLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.BoxAdapter
import com.cobblemon.mod.common.util.adapters.DropEntryAdapter
import com.cobblemon.mod.common.util.adapters.EggGroupAdapter
import com.cobblemon.mod.common.util.adapters.ExpressionAdapter
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.IntRangeAdapter
import com.cobblemon.mod.common.util.adapters.IntRangesAdapter
import com.cobblemon.mod.common.util.adapters.ItemLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.LazySetAdapter
import com.cobblemon.mod.common.util.adapters.LearnsetAdapter
import com.cobblemon.mod.common.util.adapters.NbtCompoundAdapter
import com.cobblemon.mod.common.util.adapters.RegistryElementAdapter
import com.cobblemon.mod.common.util.adapters.RideControllerAdapter
import com.cobblemon.mod.common.util.adapters.RidingStatDefinitionAdapter
import com.cobblemon.mod.common.util.adapters.StructureLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.TextAdapter
import com.cobblemon.mod.common.util.adapters.pokemonPropertiesShortAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.common.collect.HashBasedTable
import com.google.gson.Gson
@@ -130,8 +151,10 @@ object PokemonSpecies : JsonDataRegistry<Species> {
this.species.forEach(Species::initialize)
this.species.forEach(Species::resolveEvolutionMoves)
Cobblemon.showdownThread.queue {
it.registerSpecies()
it.indicateSpeciesInitialized()
if (it is CobblemonShowdownBattleServer) {
it.registerSpecies()
it.indicateSpeciesInitialized()
}
// Reload this with the mod
CobblemonHeldItemManager.load()
Cobblemon.LOGGER.info("Loaded {} Pokémon species", this.speciesByIdentifier.size)
@@ -15,8 +15,8 @@ import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.helditem.BaseCobblemonHeldItemManager
import com.cobblemon.mod.common.pokemon.helditem.CobblemonEmptyHeldItemManager
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import net.minecraft.world.item.ItemStack
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
/**
* Responsible for querying [BattlePokemon] for the presence of held items and their consumption.
@@ -13,9 +13,9 @@ import com.cobblemon.mod.common.api.PrioritizedList
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.battles.BagItems.bagItems
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.item.battle.BagItem
import com.cobblemon.mod.common.item.battle.BagItemLike
import com.cobblemon.mod.common.showdown.runner.CobblemonShowdownBattleServer
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
@@ -42,7 +42,11 @@ object BagItems : DataRegistry {
init {
observable.subscribe {
Cobblemon.showdownThread.queue(ShowdownService::registerBagItems)
if (Cobblemon.showdownServer is CobblemonShowdownBattleServer) {
Cobblemon.showdownThread.queue {
(Cobblemon.showdownServer as CobblemonShowdownBattleServer).registerBagItems()
}
}
}
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonSounds
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.storage.party.PartyStore
@@ -25,29 +24,15 @@ import com.cobblemon.mod.common.util.effectiveName
import com.cobblemon.mod.common.util.getBattleTheme
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.party
import java.util.UUID
import com.cobblemon.mod.common.util.swap
import com.cobblemon.mod.common.util.update
import com.cobblemon.mod.common.util.*
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.Entity
import net.minecraft.world.phys.Vec3
import kotlin.collections.HashMap
import kotlin.collections.Iterable
import kotlin.collections.MutableSet
import kotlin.collections.all
import kotlin.collections.filterIsInstance
import kotlin.collections.first
import kotlin.collections.flatMap
import kotlin.collections.forEach
import kotlin.collections.isNotEmpty
import kotlin.collections.mapNotNull
import kotlin.collections.mutableSetOf
import kotlin.collections.plus
import kotlin.collections.plusAssign
import java.util.UUID
import kotlin.collections.set
import kotlin.collections.sortedBy
object BattleBuilder {
@JvmOverloads
@@ -92,18 +77,21 @@ object BattleBuilder {
val errors = ErroredBattleStart()
for ((player, actor) in arrayOf(player1 to player1Actor, player2 to player2Actor)) {
if (actor.pokemonList.filter { it.health > 0 }.size < battleFormat.battleType.slotsPerActor) {
/*
if (actor.battlePokemonProvider.getKnownPokemon().filter { it.health > 0 }.size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[actor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = actor.pokemonList.filter { it.health > 0 }.size
hadCount = actor.battlePokemonProvider.getKnownPokemon().filter { it.health > 0 }.size
)
}
if (actor.pokemonList.any { it.entity?.isBusy == true }) {
if (actor.battlePokemonProvider.getKnownPokemon().any { it.entity?.isBusy == true }) {
errors.participantErrors[actor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
*/
if (BattleRegistry.getBattleByParticipatingPlayer(player) != null) {
errors.participantErrors[actor] += BattleStartError.alreadyInBattle(player)
}
@@ -171,18 +159,21 @@ object BattleBuilder {
}
for ((player, actor) in players.zip(playerActors)) {
if (actor.pokemonList.size < battleFormat.battleType.slotsPerActor) {
/*
if (actor.battlePokemonProvider.getKnownPokemon().size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[actor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = actor.pokemonList.size
hadCount = actor.battlePokemonProvider.getKnownPokemon().size
)
}
if (actor.pokemonList.any { it.entity?.isBusy == true }) {
if (actor.battlePokemonProvider.getKnownPokemon().any { it.entity?.isBusy == true }) {
errors.participantErrors[actor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
*/
if (BattleRegistry.getBattleByParticipatingPlayer(player) != null) {
errors.participantErrors[actor] += BattleStartError.alreadyInBattle(player)
}
@@ -252,26 +243,30 @@ object BattleBuilder {
val wildActor = PokemonBattleActor(pokemonEntity.pokemon.uuid, BattlePokemon(pokemonEntity.pokemon), fleeDistance)
val errors = ErroredBattleStart()
/*
if (playerTeam.isNotEmpty() && playerTeam[0].health <= 0){
errors.participantErrors[playerActor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = playerActor.pokemonList.size
hadCount = playerActor.battlePokemonProvider.getKnownPokemon().size
)
}
if (playerActor.pokemonList.size < battleFormat.battleType.slotsPerActor) {
if (playerActor.battlePokemonProvider.getKnownPokemon().size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[playerActor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = playerActor.pokemonList.size
hadCount = playerActor.battlePokemonProvider.getKnownPokemon().size
)
}
if (playerActor.pokemonList.any { it.entity?.isBusy == true }) {
if (playerActor.battlePokemonProvider.getKnownPokemon().any { it.entity?.isBusy == true }) {
errors.participantErrors[playerActor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
*/
/*
if (BattleRegistry.getBattleByParticipatingPlayer(player) != null) {
errors.participantErrors[playerActor] += BattleStartError.alreadyInBattle(playerActor)
}
@@ -280,6 +275,8 @@ object BattleBuilder {
errors.participantErrors[wildActor] += BattleStartError.alreadyInBattle(wildActor)
}
*/
playerActor.battleTheme = pokemonEntity.getBattleTheme()
return if (errors.isEmpty) {
@@ -318,16 +315,16 @@ object BattleBuilder {
healFirst: Boolean = false,
party: PartyStore = player.party()
): BattleStartResult {
/*
val playerTeam = party.toBattleTeam(clone = cloneParties, healPokemon = healFirst, leadingPokemon = leadingPokemon)
val playerActor = PlayerBattleActor(player.uuid, playerTeam)
val npcParty = npcEntity.getPartyForChallenge(listOf(player))
val errors = ErroredBattleStart()
if (playerActor.pokemonList.size < battleFormat.battleType.slotsPerActor) {
if (playerActor.battlePokemonProvider.getKnownPokemon().size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[playerActor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = playerActor.pokemonList.size
hadCount = playerActor.battlePokemonProvider.getKnownPokemon().size
)
}
@@ -340,20 +337,20 @@ object BattleBuilder {
return errors
}
val npcActor = NPCBattleActor(npcEntity, npcParty, npcEntity.skill ?: npcEntity.npc.skill)
val npcActor = NPCBattleActor(npcEntity, npcParty)
// if (npcEntity.battleIds.get().isPresent) {
// errors.participantErrors[npcActor] += BattleStartError.alreadyInBattle(npcActor)
// }
if (npcActor.pokemonList.filter { it.health > 0 }.size < battleFormat.battleType.slotsPerActor) {
if (npcActor.battlePokemonProvider.getKnownPokemon().filter { it.health > 0 }.size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[npcActor] += BattleStartError.insufficientPokemon(
actorEntity = npcEntity,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = npcActor.pokemonList.size
hadCount = npcActor.battlePokemonProvider.getKnownPokemon().size
)
}
if (playerActor.pokemonList.any { it.entity?.isBusy == true }) {
if (playerActor.battlePokemonProvider.getKnownPokemon().any { it.entity?.isBusy == true }) {
errors.participantErrors[playerActor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
@@ -370,6 +367,8 @@ object BattleBuilder {
} else {
errors
}
*/
TODO("Not yet implemented")
}
}
@@ -398,7 +397,7 @@ interface BattleStartError {
companion object {
fun alreadyInBattle(player: ServerPlayer) = AlreadyInBattleError(player.uuid, player.effectiveName())
fun alreadyInBattle(pokemonEntity: PokemonEntity) = AlreadyInBattleError(pokemonEntity.uuid, pokemonEntity.effectiveName())
fun alreadyInBattle(actor: BattleActor) = AlreadyInBattleError(actor.uuid, actor.getName())
//fun alreadyInBattle(actor: BattleActor) = AlreadyInBattleError(actor.uuid, actor.getName())
fun noParty(npcEntity: NPCEntity) = NoPartyError(npcEntity)
fun targetIsBusy(targetName: Component) = BusyError(targetName)
fun insufficientPokemon(
@@ -17,11 +17,11 @@ import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemProvider
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.showdown.runner.remote.RemoteShowdownBattle
import com.google.gson.GsonBuilder
import net.minecraft.server.level.ServerPlayer
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import net.minecraft.server.level.ServerPlayer
object BattleRegistry {
@@ -29,7 +29,7 @@ object BattleRegistry {
.disableHtmlEscaping()
.registerTypeAdapter(ShowdownMoveset::class.java, ShowdownMovesetAdapter)
.create()
private val battleMap = ConcurrentHashMap<UUID, PokemonBattle>()
private val battleMap = ConcurrentHashMap<String, PokemonBattle>()
fun onServerStarted() {
battleMap.clear()
@@ -161,22 +161,23 @@ object BattleRegistry {
repeat(battle.format.battleType.slotsPerActor) {
actor.activePokemon.add(ActiveBattlePokemon(actor))
}
val entities = actor.pokemonList.mapNotNull { it.entity }
val entities = actor.battlePokemonProvider.getKnownPokemon().mapNotNull { it.entity }
entities.forEach { it.battleId = battle.battleId }
}
// -> Add the players and team
for (actor in battle.actors.sortedBy { it.showdownId }) {
messages.add(""">player ${actor.showdownId} {"name":"${actor.uuid}","team":"${actor.pokemonList.packTeam()}"}""")
//messages.add(""">player ${actor.showdownId} {"name":"${actor.uuid}","team":"${actor.battlePokemonProvider.getKnownPokemon().packTeam()}"}""")
}
// -> Set team size
for (actor in battle.actors.sortedBy { it.showdownId }) {
messages.add(">${actor.showdownId} team ${actor.pokemonList.count()}")
messages.add(">${actor.showdownId} team ${actor.battlePokemonProvider.getKnownPokemon().count()}")
}
// Compiles the request and sends it off
ShowdownService.service.startBattle(battle, messages.toTypedArray())
val showdownBattle = Cobblemon.showdownServer.initializeNewBattle(battle.battleId.toString(), messages.toTypedArray())
battle.showdownBattle = showdownBattle
}
/**
@@ -214,12 +215,32 @@ object BattleRegistry {
return ErroredBattleStart(mutableSetOf(BattleStartError.canceledByEvent(preBattleEvent.reason)))
}
fun initializeShowdownControlledBattle(showdownBattle: RemoteShowdownBattle, playerId: UUID) {
/*
val battleId = showdownBattle.id
val battle = PokemonBattle(
BattleFormat.GEN_9_SINGLES,
BattleSide(
PlayerBattleActor(playerId, )
)
)
*/
}
/*
private fun createBattlePokemonFromShowdownJson(jsonObject: JsonObject): BattlePokemon {
val pokemon = PokemonProperties.parse("")
}
*/
fun closeBattle(battle: PokemonBattle) {
battle.onEndHandlers.forEach { it(battle) }
battleMap.remove(battle.battleId)
}
fun getBattle(id: UUID) : PokemonBattle? {
fun getBattle(id: String) : PokemonBattle? {
return battleMap[id]
}
@@ -212,7 +212,7 @@ data class SwitchActionResponse(var newPokemonId: UUID) : ShowdownActionResponse
}
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean): Boolean {
val pokemon = activeBattlePokemon.actor.pokemonList.find { it.uuid == newPokemonId }
val pokemon = activeBattlePokemon.actor.battlePokemonProvider.getKnownPokemon().find { it.uuid == newPokemonId }
return when {
pokemon == null -> false // No such Pokémon
(!activeBattlePokemon.actor.request?.side?.pokemon?.any{ it.uuid == activeBattlePokemon.battlePokemon?.uuid && it.reviving }!! && pokemon.health <= 0) -> false // Checks if the active Pokémon is reviving, if so ignore this check. If not, return false if dead
@@ -223,7 +223,7 @@ data class SwitchActionResponse(var newPokemonId: UUID) : ShowdownActionResponse
}
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?): String {
return "switch ${activeBattlePokemon.actor.pokemonList.indexOfFirst { it.uuid == newPokemonId } + 1}"
return "switch ${activeBattlePokemon.actor.battlePokemonProvider.getKnownPokemon().indexOfFirst { it.uuid == newPokemonId } + 1}"
}
}
@@ -19,11 +19,11 @@ import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.interpreter.ContextManager
import com.cobblemon.mod.common.battles.interpreter.instructions.*
import com.cobblemon.mod.common.battles.pokemon.AbstractBattlePokemon
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.runOnServer
import net.minecraft.world.phys.Vec3
import java.util.UUID
import kotlin.collections.Iterator
import kotlin.collections.filter
import kotlin.collections.forEach
@@ -38,7 +38,7 @@ import kotlin.collections.toTypedArray
@Suppress("KotlinPlaceholderCountMatchesArgumentCount", "UNUSED_PARAMETER")
object ShowdownInterpreter {
// Stores a reference to the previous ability, activate, or move message in a battle so a minor action can refer back to it (Battle UUID : BattleMessage)
val lastCauser = mutableMapOf<UUID, BattleMessage>()
val lastCauser = mutableMapOf<String, BattleMessage>()
private val updateInstructionParser = mutableMapOf<String, (PokemonBattle, InstructionSet, BattleMessage, Iterator<BattleMessage>) -> InterpreterInstruction>()
private val splitInstructionParser = mutableMapOf<String, (PokemonBattle, BattleActor, InstructionSet, BattleMessage, BattleMessage, Iterator<BattleMessage>) -> InterpreterInstruction>()
@@ -218,7 +218,7 @@ object ShowdownInterpreter {
fun interpretMessage(battleId: UUID, message: String) {
fun interpretMessage(battleId: String, message: String) {
// Check key map and use function if matching
if (message.startsWith("{\"winner\":\"")) {
// The post-win message is something we don't care about just yet. It's basically a summary of what happened in the battle.
@@ -290,13 +290,13 @@ object ShowdownInterpreter {
}
}
fun broadcastOptionalAbility(battle: PokemonBattle, effect: Effect?, pokemon: BattlePokemon) {
fun broadcastOptionalAbility(battle: PokemonBattle, effect: Effect?, pokemon: AbstractBattlePokemon) {
if (effect == null) return
broadcastAbility(battle, effect, pokemon)
}
// Broadcasts a generic lang to notify players of ability activations (effects are broadcasted separately)
fun broadcastAbility(battle: PokemonBattle, effect: Effect, pokemon: BattlePokemon) {
fun broadcastAbility(battle: PokemonBattle, effect: Effect, pokemon: AbstractBattlePokemon) {
if (effect.type != Effect.Type.ABILITY) return
battle.dispatchWaiting(0.5F) {
val lang = battleLang("ability.generic", pokemon.getName(), effect.typelessData).yellow()
@@ -8,28 +8,30 @@
package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.battles.runner.ShowdownService
import java.util.*
import com.cobblemon.mod.common.showdown.runner.ShowdownBattleServer
import java.util.LinkedList
import java.util.Queue
import java.util.concurrent.CountDownLatch
class ShowdownThread : Thread("Cobblemon Showdown") {
private val latch = CountDownLatch(1)
private val whenReady : Queue<(ShowdownService) -> Unit> = LinkedList()
private val whenReady : Queue<(ShowdownBattleServer) -> Unit> = LinkedList()
fun launch() {
this.start()
this.latch.await()
for (action in whenReady) {
action(ShowdownService.service)
action(Cobblemon.showdownServer)
}
}
fun queue(action: (ShowdownService) -> Unit) {
fun queue(action: (ShowdownBattleServer) -> Unit) {
if (this.latch.count == 0L) {
action(ShowdownService.service)
action(Cobblemon.showdownServer)
} else {
this.whenReady.add(action)
}
@@ -37,7 +39,7 @@ class ShowdownThread : Thread("Cobblemon Showdown") {
override fun run() {
LOGGER.info("Starting showdown service...")
ShowdownService.service.openConnection()
Cobblemon.showdownServer.connect()
LOGGER.info("Showdown has been started!")
this.latch.countDown()
}
@@ -8,21 +8,19 @@
package com.cobblemon.mod.common.battles.actor
import com.cobblemon.mod.common.api.battles.model.actor.AIBattleActor
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.battles.model.actor.StaticBattlePokemonProvider
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.ai.RandomBattleAI
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import java.util.UUID
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import java.util.UUID
class MultiPokemonBattleActor(
pokemonList: List<BattlePokemon>,
artificialDecider: BattleAI = RandomBattleAI(),
uuid: UUID = UUID.randomUUID()
) : AIBattleActor(uuid, pokemonList, artificialDecider) {
open class MultiPokemonBattleActor(
uuid: UUID = UUID.randomUUID(),
pokemonList: List<BattlePokemon>
) : BattleActor(uuid, StaticBattlePokemonProvider(pokemonList)) {
override fun getName(): MutableComponent = "Wild Pokémon".text() // TODO probably remove by making it nullable
override fun nameOwned(name: String): MutableComponent = Component.literal(name)
override val type = ActorType.WILD
@@ -13,24 +13,25 @@ import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
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.api.battles.model.actor.StaticBattlePokemonProvider
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.pokemon.experience.BattleExperienceSource
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.pokemon.AbstractBattlePokemon
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.net.messages.client.battle.BattleMusicPacket
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getPlayer
import java.util.UUID
import net.minecraft.network.chat.MutableComponent
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.phys.Vec3
import java.util.UUID
class PlayerBattleActor(
uuid: UUID,
pokemonList: List<BattlePokemon>,
) : BattleActor(uuid, pokemonList.toMutableList()), EntityBackedBattleActor<ServerPlayer> {
pokemonList: List<AbstractBattlePokemon>,
) : BattleActor(uuid, StaticBattlePokemonProvider(pokemonList.toMutableList())), EntityBackedBattleActor<ServerPlayer> {
override val initialPos: Vec3?
override val entity: ServerPlayer?
@@ -8,28 +8,24 @@
package com.cobblemon.mod.common.battles.actor
import com.cobblemon.mod.common.api.battles.model.actor.AIBattleActor
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
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.battles.model.ai.BattleAI
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.battles.ai.RandomBattleAI
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
import java.util.UUID
import net.minecraft.server.level.ServerLevel
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.phys.Vec3
import java.util.UUID
class PokemonBattleActor(
uuid: UUID,
val pokemon: BattlePokemon,
override val fleeDistance: Float,
artificialDecider: BattleAI = RandomBattleAI()
) : AIBattleActor(uuid, listOf(pokemon), artificialDecider), EntityBackedBattleActor<PokemonEntity>, FleeableBattleActor {
) : MultiPokemonBattleActor(uuid, listOf(pokemon)), EntityBackedBattleActor<PokemonEntity>, FleeableBattleActor {
override val initialPos: Vec3?
init {
@@ -56,7 +52,7 @@ class PokemonBattleActor(
if (packet is BattleEndPacket) {
// Do some shit
val entity = entity ?: return
entity.battleId = null
entity.battleId = ""
}
}
@@ -8,9 +8,7 @@
package com.cobblemon.mod.common.battles.actor
import com.cobblemon.mod.common.api.battles.model.actor.AIBattleActor
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.api.battles.model.actor.*
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.battleLang
@@ -21,8 +19,7 @@ class TrainerBattleActor(
val trainerName: String,
uuid: UUID,
pokemonList: List<BattlePokemon>,
artificialDecider: BattleAI
) : AIBattleActor(uuid, pokemonList, artificialDecider) {
) : BattleActor(uuid, StaticBattlePokemonProvider(pokemonList)) {
override fun getName() = trainerName.asTranslated()
override fun nameOwned(name: String): MutableComponent = battleLang("owned_pokemon", this.getName(), name)
override val type = ActorType.NPC
@@ -8,21 +8,13 @@
package com.cobblemon.mod.common.battles.ai
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.DefaultActionResponse
import com.cobblemon.mod.common.battles.MoveActionResponse
import com.cobblemon.mod.common.battles.PassActionResponse
import com.cobblemon.mod.common.battles.ShowdownActionResponse
import com.cobblemon.mod.common.battles.ShowdownMoveset
import com.cobblemon.mod.common.battles.SwitchActionResponse
/**
* AI that randomly chooses a move from its moveset at a random target.
*
* @since January 16th, 2022
* @author Deltric, Hiroku
*/
/*
class RandomBattleAI : BattleAI {
override fun choose(
activeBattlePokemon: ActiveBattlePokemon,
@@ -30,7 +22,7 @@ class RandomBattleAI : BattleAI {
forceSwitch: Boolean
): ShowdownActionResponse {
if (forceSwitch || activeBattlePokemon.isGone()) {
val switchTo = activeBattlePokemon.actor.pokemonList.filter { it.canBeSentOut() }.randomOrNull()
val switchTo = activeBattlePokemon.actor.battlePokemonProvider.getKnownPokemon().filter { it.canBeSentOut() }.randomOrNull()
?: return DefaultActionResponse() //throw IllegalStateException("Need to switch but no Pokémon to switch to")
switchTo.willBeSwitchedIn = true
return SwitchActionResponse(switchTo.uuid)
@@ -54,4 +46,6 @@ class RandomBattleAI : BattleAI {
MoveActionResponse(move.id, (chosenTarget as ActiveBattlePokemon).getPNX())
}
}
}
}
*/
@@ -10,32 +10,31 @@ package com.cobblemon.mod.common.battles.ai
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.abilities.AbilityPool
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.api.data.ShowdownIdentifiable
import com.cobblemon.mod.common.api.moves.Move
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.*
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.pokemon.status.Status
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.*
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.DefaultActionResponse
import com.cobblemon.mod.common.battles.InBattleMove
import com.cobblemon.mod.common.battles.MoveActionResponse
import com.cobblemon.mod.common.battles.PassActionResponse
import com.cobblemon.mod.common.battles.ShowdownActionRequest
import com.cobblemon.mod.common.battles.ShowdownActionResponse
import com.cobblemon.mod.common.battles.ShowdownMoveset
import com.cobblemon.mod.common.battles.SwitchActionResponse
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer
import com.cobblemon.mod.common.util.party
import net.minecraft.world.item.ItemStack
import java.util.UUID
import kotlin.random.Random
import java.util.*
/**
* AI that tries to choose the best move for the given situations. Based off of the Pokemon Trainer Tournament Simulator Github
@@ -351,7 +350,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
forceSwitch: Boolean
): ShowdownActionResponse {
if (forceSwitch || activeBattlePokemon.isGone()) {
val switchTo = activeBattlePokemon.actor.pokemonList.filter { it.canBeSentOut() }.randomOrNull()
val switchTo = activeBattlePokemon.actor.battlePokemonProvider.getKnownPokemon().filter { it.canBeSentOut() }.randomOrNull()
?: return DefaultActionResponse() //throw IllegalStateException("Need to switch but no Pokémon to switch to")
switchTo.willBeSwitchedIn = true
return SwitchActionResponse(switchTo.uuid)
@@ -629,14 +628,19 @@ class StrongBattleAI(skill: Int) : BattleAI {
if (forceSwitch || activeBattlePokemon.isGone()) {
if ((forceSwitch && battle.turn == 1) || (activeBattlePokemon.isGone() && battle.turn == 1)) {
val switchTo = activeBattlePokemon.actor.pokemonList.filter { it.canBeSentOut() }.randomOrNull()
val switchTo = activeBattlePokemon.actor.battlePokemonProvider.getKnownPokemon()
.filterIsInstance<BattlePokemon>()
.filter { it.canBeSentOut() }
.randomOrNull()
?: return DefaultActionResponse() //throw IllegalStateException("Need to switch but no Pokémon to switch to")
switchTo.willBeSwitchedIn = true
return SwitchActionResponse(switchTo.uuid)
}
else if ((forceSwitch && battle.turn > 1) || (activeBattlePokemon.isGone() && battle.turn > 1)) {
val availableSwitches = activeBattlePokemon.actor.pokemonList.filter { (it.health > 0) && (it.uuid != (activeBattlePokemon.battlePokemon?.uuid ?: true)) } //it.uuid != mon.pokemon!!.uuid && it.health > 0 }
val availableSwitches = activeBattlePokemon.actor.battlePokemonProvider.getKnownPokemon()
.filterIsInstance<BattlePokemon>()
.filter { (it.health > 0) && (it.uuid != (activeBattlePokemon.battlePokemon?.uuid ?: true)) } //it.uuid != mon.pokemon!!.uuid && it.health > 0 }
// this gets null error after NPC faints
val bestEstimation = availableSwitches.maxOfOrNull { estimatePartyMatchup(request, battle, it.effectedPokemon) }
@@ -651,7 +655,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
//return SwitchActionResponse(switchTo.uuid)
/*val availableSwitches = p2Actor.pokemonList.filter { it.health > 0 } //it.uuid != mon.pokemon!!.uuid && it.health > 0 }
/*val availableSwitches = p2Actor.battlePokemonProvider.getKnownPokemon().filter { it.health > 0 } //it.uuid != mon.pokemon!!.uuid && it.health > 0 }
val bestEstimation = availableSwitches.maxOfOrNull { estimateMatchup(request, battle, it.effectedPokemon) }
*//*availableSwitches.forEach {
estimateMatchup(request, battle, it.effectedPokemon)
@@ -774,8 +778,10 @@ class StrongBattleAI(skill: Int) : BattleAI {
// switch out based on current matchup on the field
if (checkSwitchOutSkill(skill) && shouldSwitchOut(request, battle, activeBattlePokemon, moveset)) {
val availableSwitches = p2Actor.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }
val availablePlayerSwitches = p1Actor.pokemonList.filter { it.uuid != opponent.pokemon!!.uuid && it.health > 0 }
val availableSwitches = p2Actor.battlePokemonProvider.getKnownPokemon()
.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }
.filterIsInstance<BattlePokemon>()
val availablePlayerSwitches = p1Actor.battlePokemonProvider.getKnownPokemon().filter { it.uuid != opponent.pokemon!!.uuid && it.health > 0 }
// todo try to detect a player switch-in based on if they do that a lot
// todo if player is in bad matchup against current AI pokemon, and they have switched out before, then they have a chance of switching to a favorable matchup
@@ -850,10 +856,12 @@ class StrongBattleAI(skill: Int) : BattleAI {
// Deal with non-weather related field changing effects
for (move in moveset.moves.filter { !it.disabled }) {
val availableSwitches = p2Actor.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }
val availableSwitches = p2Actor.battlePokemonProvider.getKnownPokemon()
.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }
.filterIsInstance<BattlePokemon>()
// Tailwind
if (move.pp > 0 && move.id == "tailwind" && move.id != npcSideTailwindCondition && p2Actor.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 2) {
if (move.pp > 0 && move.id == "tailwind" && move.id != npcSideTailwindCondition && p2Actor.battlePokemonProvider.getKnownPokemon().filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 2) {
return chooseMove(move, activeBattlePokemon)
}
@@ -874,7 +882,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
// Light Screen
if (move.pp > 0 && move.id == "lightscreen" && move.id != npcSideScreenCondition
&& getBaseStats(opponent.pokemon!!, "spa") > getBaseStats(opponent.pokemon!!, "atk")
&& p2Actor.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 1) {
&& p2Actor.battlePokemonProvider.getKnownPokemon().filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 1) {
return chooseMove(move, activeBattlePokemon)
}
@@ -882,7 +890,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
// Reflect
if (move.pp > 0 && move.id == "reflect" && move.id != npcSideScreenCondition
&& getBaseStats(opponent.pokemon!!, "atk") > getBaseStats(opponent.pokemon!!, "spa")
&& p2Actor.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 1) {
&& p2Actor.battlePokemonProvider.getKnownPokemon().filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 1) {
return chooseMove(move, activeBattlePokemon)
}
}
@@ -1275,7 +1283,9 @@ class StrongBattleAI(skill: Int) : BattleAI {
// switch out
if (shouldSwitchOut(request, battle, activeBattlePokemon, moveset)) {
val availableSwitches = p1Actor.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }
val availableSwitches = p1Actor.battlePokemonProvider.getKnownPokemon()
.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }
.filterIsInstance<BattlePokemon>()
val bestEstimation = availableSwitches.maxOfOrNull { estimateMatchup(activeBattlePokemon, request, battle, it.effectedPokemon) }
/*availableSwitches.forEach {
estimateMatchup(request, battle, it.effectedPokemon)
@@ -1477,7 +1487,9 @@ class StrongBattleAI(skill: Int) : BattleAI {
//val requestActor = request.side?.
//val availableSwitches = request.side?.pokemon?.filter { !it.active && getHpFraction(it.condition) != 0.0 }
//val playerAvailableSwitches =
val availableSwitches = p2Actor.pokemonList.filter { it.uuid != mon.activePokemon.pokemon!!.uuid && it.health > 0 }
val availableSwitches = p2Actor.battlePokemonProvider.getKnownPokemon()
.filter { it.uuid != mon.activePokemon.pokemon!!.uuid && it.health > 0 }
.filterIsInstance<BattlePokemon>()
val currentNPCAbility = request.side?.pokemon!!.first().ability
@@ -1784,7 +1796,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
val p1Boosts = battle.side1.activePokemon.firstOrNull()?.battlePokemon?.statChanges
val playerSide1 = battle.side1.actors.first()
val numPlayerPokemon = playerSide1.pokemonList.count()
val numPlayerPokemon = playerSide1.battlePokemonProvider.getKnownPokemon().count()
/*val lastMajorBattleMessage = if (battle.majorBattleActions.entries.isNotEmpty()) battle.majorBattleActions?.entries?.last()?.value?.rawMessage else ""
@@ -1829,9 +1841,9 @@ class StrongBattleAI(skill: Int) : BattleAI {
val pokemon2 = battle.side2.activePokemon.firstOrNull()?.battlePokemon?.effectedPokemon
val p2Boosts = battle.side2.activePokemon.firstOrNull()?.battlePokemon?.statChanges
val playerSide2 = battle.side2.actors.first()
val numNPCPokemon = playerSide2.pokemonList.count()
val numNPCPokemon = playerSide2.battlePokemonProvider.getKnownPokemon().count()
//val nextAvailablePokemon = playerSide2.pokemonList.filter { it.health != 0 }.first().effectedPokemon.uuid
//val nextAvailablePokemon = playerSide2.battlePokemonProvider.getKnownPokemon().filter { it.health != 0 }.first().effectedPokemon.uuid
// todo make nice function for knowing what is the best switchout
// convert p2Boosts to a regular Map rather than a MutableMap
@@ -1858,7 +1870,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
//mon.stats = pokemon.stats
p1.activePokemon.moves = pokemon1.moveSet.getMoves()
p1.nRemainingMons = battle.side1.actors.sumOf { actor ->
actor.pokemonList.count { pokemon ->
actor.battlePokemonProvider.getKnownPokemon().count { pokemon ->
pokemon.health != 0
}
}
@@ -1871,7 +1883,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
p1.party[partyPokemonIndex] = p1.activePokemon
}
//p1.availableSwitches = playerSide1.pokemonList.filter { it.uuid != p1.pokemon.uuid && it.health > 0 }
//p1.availableSwitches = playerSide1.battlePokemonProvider.getKnownPokemon().filter { it.uuid != p1.pokemon.uuid && it.health > 0 }
//p1.sideConditions = pokemon.sideConditions //todo what the hell does this mean
p2.activePokemon = getActiveTrackerPokemon(p2, pokemon2?.uuid)
@@ -1889,7 +1901,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
//mon.stats = pokemon.stats
p2.activePokemon.moves = pokemon2.moveSet.getMoves()
p2.nRemainingMons = battle.side2.actors.sumOf { actor ->
actor.pokemonList.count { pokemon ->
actor.battlePokemonProvider.getKnownPokemon().count { pokemon ->
pokemon.health != 0
}
}
@@ -11,9 +11,7 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.ActionEffectInstruction
@@ -23,8 +21,8 @@ import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import java.util.concurrent.CompletableFuture
import net.minecraft.network.chat.Component
import java.util.concurrent.CompletableFuture
/**
* Format: |-activate|POKEMON|EFFECT
@@ -59,7 +57,7 @@ class ActivateInstruction(val instructionSet: InstructionSet, val message: Battl
val actionEffect = status?.getActionEffect() ?: return@dispatch GO
val providers = mutableListOf<Any>(battle)
val pokemon = message.battlePokemon(0, battle) ?: return@dispatch GO
pokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
//pokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
val context = ActionEffectContext(
actionEffect = actionEffect,
runtime = runtime,
@@ -81,6 +79,7 @@ class ActivateInstruction(val instructionSet: InstructionSet, val message: Battl
val pokemonName = pokemon.getName()
val sourceName = message.battlePokemonFromOptional(battle)?.getName() ?: Component.literal("UNKNOWN")
/*
if (effect.id == "sketch" && pokemon.effectedPokemon.moveSet.any { it.name == "sketch" } && extraEffect is String) {
// Apply Sketch to the pokemon's current moveset if it was successfully used in battle
val moveTemplate = Moves.getByName(extraEffect.replace(" ", ""))
@@ -92,6 +91,8 @@ class ActivateInstruction(val instructionSet: InstructionSet, val message: Battl
}
*/
val lang = when (effect.id) {
// Includes a 3rd argument being the magnitude level as a number
"magnitude" -> battleLang("activate.magnitude", message.argumentAt(2)?.toIntOrNull() ?: 1)
@@ -15,7 +15,6 @@ import com.cobblemon.mod.common.api.battles.interpreter.InvalidInstructionExcept
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.ActionEffectInstruction
@@ -52,12 +51,11 @@ class BoostInstruction(battle: PokemonBattle, val message: BattleMessage, val is
battle.dispatch {
val actionEffect = if (isBoost) BOOST_EFFECT else UNBOOST_EFFECT
val providers = mutableListOf<Any>(battle)
pokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
// pokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
val context = ActionEffectContext(
actionEffect = actionEffect,
runtime = runtime,
providers = providers,
level = battle.players.firstOrNull()?.level()
providers = providers
)
this.future = actionEffect.run(context)
holds = context.holds // Reference so future things can check on this action effect's holds
@@ -12,7 +12,6 @@ import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
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
@@ -47,13 +46,12 @@ class CantInstruction(val message: BattleMessage): ActionEffectInstruction {
val status = Statuses.getStatus(effectID)
val actionEffect = status?.getActionEffect() ?: return@dispatch GO
val providers = mutableListOf<Any>(battle)
pokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
// pokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
val context = ActionEffectContext(
actionEffect = actionEffect,
runtime = runtime,
providers = providers,
level = battle.players.firstOrNull()?.level()
providers = providers
)
this.future = actionEffect.run(context)
holds = context.holds // Reference so future things can check on this action effect's holds
@@ -11,11 +11,10 @@ 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
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
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: |-crit|POKEMON
@@ -39,10 +38,13 @@ class CritInstruction(val message: BattleMessage, val instructionSet: Instructio
} else {
battle.broadcastChatMessage(battleLang("crit").yellow())
}
/*
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
}
@@ -38,7 +38,7 @@ class CureStatusInstruction(val message: BattleMessage): InterpreterInstruction
battle.dispatchWaiting {
val pokemonName = pokemon.getName()
pokemon.effectedPokemon.status = null
// pokemon.effectedPokemon.status = null
pokemon.sendUpdate()
if (maybeActivePokemon != null) {
@@ -25,16 +25,14 @@ import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.pokemon.evolution.progress.DamageTakenEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.progress.RecoilEvolutionProgress
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import java.util.concurrent.CompletableFuture
import net.minecraft.network.chat.Component
import java.util.concurrent.CompletableFuture
/**
* Format: |-damage|POKEMON|HP STATUS
@@ -59,7 +57,9 @@ class DamageInstruction(
val recoiling = privateMessage.optionalArgument("from")?.equals("recoil", true) == true
val lastCauser = instructionSet.getMostRecentCauser(comparedTo = this)
if (recoiling) {
doRecoilEvoChecks(battlePokemon)
if (battlePokemon is BattlePokemon) {
doRecoilEvoChecks(battlePokemon)
}
if (lastCauser is MoveInstruction) {
battle.dispatch {
//Is the recoil hold currently used anywhere?
@@ -98,9 +98,12 @@ class DamageInstruction(
var status = effect?.id?.let { Statuses.getStatus(it) }
battle.dispatch {
val pokemon = privateMessage.battlePokemon(0, battle) ?: return@dispatch GO
//FIXME: This should be tracked on the AbstractBattlePokemon
//Showdown doesnt tell us on damage if its poison or toxic so we gotta consult the entity
if (status is PoisonStatus) {
status = pokemon.effectedPokemon.status?.status ?: status
if (pokemon is BattlePokemon) {
status = pokemon.effectedPokemon.status?.status ?: status
}
}
// Damage effects without a action effect receive the generic damage effect
@@ -109,7 +112,7 @@ class DamageInstruction(
} ?: return@dispatch GO
val providers = mutableListOf<Any>(battle)
battlePokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
battlePokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
val context = ActionEffectContext(
actionEffect = actionEffect,
@@ -167,7 +170,7 @@ class DamageInstruction(
if (causedFaint) {
newHealthRatio = 0F
battle.dispatch {
battlePokemon.effectedPokemon.currentHealth = 0
battlePokemon.health = 0
battlePokemon.sendUpdate()
GO
}
@@ -177,14 +180,17 @@ class DamageInstruction(
val difference = maxHealth - remainingHealth
newHealthRatio = remainingHealth.toFloat() / maxHealth
battle.dispatchToFront {
battlePokemon.effectedPokemon.currentHealth = remainingHealth
//battlePokemon.curr = remainingHealth
if (difference > 0) {
/*
battlePokemon.effectedPokemon.let { pokemon ->
if (DamageTakenEvolutionProgress.supports(pokemon)) {
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is DamageTakenEvolutionProgress }) { DamageTakenEvolutionProgress() }
progress.updateProgress(DamageTakenEvolutionProgress.Progress(progress.currentProgress().amount + difference))
}
}
*/
}
battlePokemon.sendUpdate()
GO
@@ -13,7 +13,6 @@ 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
@@ -39,7 +38,7 @@ class DragInstruction(val instructionSet: InstructionSet, val battleActor: Battl
val newHealth = privateMessage.argumentAt(2)!!.split(" ")[0]
val remainingHealth = newHealth.split("/")[0].toInt()
pokemon.effectedPokemon.currentHealth = remainingHealth
// pokemon.effectedPokemon.currentHealth = remainingHealth
battle.broadcastChatMessage(battleLang("dragged_out", pokemon.getName()))
activePokemon.battlePokemon?.let { oldPokemon ->
@@ -50,6 +49,7 @@ class DragInstruction(val instructionSet: InstructionSet, val battleActor: Battl
val entity = if (battleActor is EntityBackedBattleActor<*>) battleActor.entity else null
setOf(
/*
BattleDispatch {
if (entity != null) {
SwitchInstruction.createEntitySwitch(battle, battleActor, entity, pnx, activePokemon, pokemon, illusion, imposter)
@@ -57,6 +57,7 @@ class DragInstruction(val instructionSet: InstructionSet, val battleActor: Battl
SwitchInstruction.createNonEntitySwitch(battle, battleActor, pnx, activePokemon, pokemon, illusion)
}
}
*/
)
}
}
@@ -11,7 +11,6 @@ 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
@@ -12,9 +12,7 @@ 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
@@ -34,7 +32,7 @@ class EndItemInstruction(val message: BattleMessage): InterpreterInstruction {
// All logic regarding broadcasting battle messages is handled in CobblemonHeldItemManager
val battlePokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val itemEffect = message.effectAt(1) ?: return@dispatchWaiting
battlePokemon.heldItemManager.handleEndInstruction(battlePokemon, battle, message)
//battlePokemon.heldItemManager.handleEndInstruction(battlePokemon, battle, message)
battle.minorBattleActions[battlePokemon.uuid] = message
battlePokemon.contextManager.remove(itemEffect.id, BattleContext.Type.ITEM)
if (message.hasOptionalArgument("eat")) {
@@ -13,11 +13,8 @@ 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.EntityBackedBattleActor
import com.cobblemon.mod.common.api.entity.PokemonSender
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.BattleFaintedEvent
import com.cobblemon.mod.common.api.scheduling.delayedFuture
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.ShowdownInterpreter
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
@@ -41,20 +38,20 @@ class FaintInstruction(battle: PokemonBattle, val message: BattleMessage) : Inte
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchFuture CompletableFuture.completedFuture(Unit)
battle.sendUpdate(BattleFaintPacket(pnx))
val actor = pokemon.actor
pokemon.effectedPokemon.currentHealth = 0
//pokemon.effectedPokemon.currentHealth = 0
val preamble = if (actor is EntityBackedBattleActor<*>) {
(actor.entity as? PokemonSender)?.let { sender -> pokemon.entity?.recallWithAnimation()}
} else {
null
} ?: delayedFuture(seconds = waitTime)
val context = ShowdownInterpreter.getContextFromFaint(faintingPokemon, battle)
//val context = ShowdownInterpreter.getContextFromFaint(faintingPokemon, battle)
preamble.thenAccept {
faintingPokemon.effectedPokemon.currentHealth = 0
//faintingPokemon.effectedPokemon.currentHealth = 0
faintingPokemon.sendUpdate()
CobblemonEvents.BATTLE_FAINTED.post(BattleFaintedEvent(battle, pokemon, context))
//CobblemonEvents.BATTLE_FAINTED.post(BattleFaintedEvent(battle, pokemon, context))
battle.getActorAndActiveSlotFromPNX(pnx).second.battlePokemon = null
faintingPokemon.contextManager.add(context)
//faintingPokemon.contextManager.add(context)
faintingPokemon.contextManager.clear(BattleContext.Type.STATUS, BattleContext.Type.VOLATILE, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
battle.majorBattleActions[faintingPokemon.uuid] = message
}
@@ -10,8 +10,6 @@ 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.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.instruction.FormeChangeEvent
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
@@ -37,7 +35,7 @@ class FormeChangeInstruction(val message: BattleMessage): InterpreterInstruction
battle.dispatchWaiting {
battle.minorBattleActions[battlePokemon.uuid] = message
CobblemonEvents.FORME_CHANGE.post(FormeChangeEvent(battle, battlePokemon, formName))
//CobblemonEvents.FORME_CHANGE.post(FormeChangeEvent(battle, battlePokemon, formName))
val pokemonName = battlePokemon.getName()
val lang = when(formName) {
"busted", "hero", "complete" -> return@dispatchWaiting
@@ -17,9 +17,6 @@ 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
/**
@@ -53,9 +50,9 @@ class HealInstruction(val actor: BattleActor, val publicMessage: BattleMessage,
privateMessage.hasOptionalArgument("wisher") -> {
val name = privateMessage.optionalArgument("wisher")!!
val showdownId = name.lowercase().replace(ShowdownIdentifiable.REGEX, "")
val wisher = actor.pokemonList.firstOrNull { it.effectedPokemon.showdownId() == showdownId }
//val wisher = actor.battlePokemonProvider.getKnownPokemon().firstOrNull { it.effectedPokemon.showdownId() == showdownId }
// If no Pokémon is found this is a nickname
battleLang("heal.wish", wisher?.getName() ?: actor.nameOwned(name))
//battleLang("heal.wish", wisher?.getName() ?: actor.nameOwned(name))
}
privateMessage.hasOptionalArgument("from") -> {
when (effect!!.type) {
@@ -76,14 +73,15 @@ class HealInstruction(val actor: BattleActor, val publicMessage: BattleMessage,
battleLang("heal.generic", battlePokemon.getName())
}
}
battle.broadcastChatMessage(lang)
//battle.broadcastChatMessage(lang)
}
battle.minorBattleActions[battlePokemon.uuid] = privateMessage
battlePokemon.effectedPokemon.currentHealth = newHealth[0].toInt()
//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)
activePokemon?.let { battle.sendUpdate(BattlePersistentStatusPacket(it.getPNX(), status)) }
@@ -91,6 +89,8 @@ class HealInstruction(val actor: BattleActor, val publicMessage: BattleMessage,
status.applyMessage.let { battle.broadcastChatMessage(it.asTranslated(battlePokemon.getName())) }
}
}
*/
}
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.scheduling.afterOnServer
@@ -16,6 +15,7 @@ import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.dispatch.DispatchResult
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.net.messages.client.battle.BattleInitializePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMusicPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleQueueRequestPacket
@@ -40,12 +40,12 @@ class InitializeInstruction(val instructionSet: InstructionSet, val message: Bat
// If a Pokémon entity is being recalled with an animation,
// wrap up the animation and recall the Pokémon immediately.
if (it.beamMode == 3 && battle.battlePartyStores.isEmpty()) {
pokemon.effectedPokemon.recall()
// pokemon.effectedPokemon.recall()
}
// If already on the field, initialize for the BattleInitializePacket
else {
activePokemon.battlePokemon = pokemon
activePokemon.illusion = illusion
// activePokemon.battlePokemon = pokemon
// activePokemon.illusion = illusion
}
}
}
@@ -57,7 +57,12 @@ class InitializeInstruction(val instructionSet: InstructionSet, val message: Bat
}
battle.actors.forEach { actor ->
actor.sendUpdate(BattleSetTeamPokemonPacket(actor.pokemonList.map { it.effectedPokemon }))
actor.sendUpdate(BattleSetTeamPokemonPacket(
actor.battlePokemonProvider.getKnownPokemon()
.filterIsInstance<BattlePokemon>()
.map { it.effectedPokemon }
.toList()
))
val req = actor.request ?: return@forEach
actor.sendUpdate(BattleQueueRequestPacket(req))
}
@@ -33,7 +33,7 @@ class ItemInstruction(val message: BattleMessage): InterpreterInstruction {
battle.dispatchWaiting(1.5F) {
val battlePokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
battlePokemon.heldItemManager.handleStartInstruction(battlePokemon, battle, message)
//battlePokemon.heldItemManager.handleStartInstruction(battlePokemon, battle, message)
battle.minorBattleActions[battlePokemon.uuid] = message
battlePokemon.contextManager.add(ShowdownInterpreter.getContextFromAction(message, BattleContext.Type.ITEM, battle))
}
@@ -10,8 +10,6 @@ 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.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.instruction.MegaEvolutionEvent
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
@@ -30,7 +28,7 @@ class MegaInstruction(val message: BattleMessage): InterpreterInstruction {
battle.dispatchWaiting {
val pokemonName = battlePokemon.getName()
battle.broadcastChatMessage(battleLang("mega", pokemonName).yellow())
CobblemonEvents.MEGA_EVOLUTION.post(MegaEvolutionEvent(battle, battlePokemon))
//CobblemonEvents.MEGA_EVOLUTION.post(MegaEvolutionEvent(battle, battlePokemon))
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
@@ -54,8 +54,8 @@ class MoveInstruction(
var targetPokemon: BattlePokemon? = null
override fun invoke(battle: PokemonBattle) {
userPokemon = message.battlePokemon(0, battle)!!
targetPokemon = message.battlePokemon(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()
@@ -29,8 +29,8 @@ class PpUpdateInstruction( val message: BattleMessage): InterpreterInstruction {
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()
// val move = pokemon.effectedPokemon.moveSet.firstOrNull { move -> move.name.equals(moveId, true) } ?: return@dispatch GO
// move.currentPp = movePp.toInt()
}
GO
}
@@ -10,11 +10,7 @@ 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.BattleReplacePokemonPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleTransformPokemonPacket
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |replace|POKEMON|DETAILS|HP STATUS
@@ -35,12 +31,15 @@ class ReplaceInstruction(val message: BattleMessage): InterpreterInstruction {
val entity = pokemon.entity
entity?.let { it.effects.mockEffect?.end(it) }
/*
battle.sendSidedUpdate(
source = actor,
allyPacket = BattleReplacePokemonPacket(pnx, pokemon, true),
opponentPacket = BattleReplacePokemonPacket(pnx, pokemon, false)
)
*/
activePokemon.illusion = null
//lang done by EndInstruction
}
@@ -14,7 +14,6 @@ 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
@@ -31,7 +30,7 @@ class SetHpInstruction(val actor: BattleActor, val publicMessage: BattleMessage,
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()
// 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")) {
@@ -39,7 +39,7 @@ class StatusInstruction( val message: BattleMessage): InterpreterInstruction {
battle.dispatchWaiting {
if (status is PersistentStatus) {
pokemon.effectedPokemon.applyStatus(status)
//pokemon.effectedPokemon.applyStatus(status)
battle.sendUpdate(BattlePersistentStatusPacket(pnx, status))
pokemon.sendUpdate()
}
@@ -13,7 +13,6 @@ import com.cobblemon.mod.common.api.battles.model.PokemonBattle
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.entity.pokemon.effects.IllusionEffect
import com.cobblemon.mod.common.net.messages.client.battle.BattleSwapPokemonPacket
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.setPositionSafely
@@ -19,21 +19,22 @@ import com.cobblemon.mod.common.api.scheduling.afterOnServer
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.battles.dispatch.*
import com.cobblemon.mod.common.battles.dispatch.DispatchResult
import com.cobblemon.mod.common.battles.dispatch.GO
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.entity.pokemon.effects.IllusionEffect
import com.cobblemon.mod.common.net.messages.client.battle.BattleSwitchPokemonPacket
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler.SEND_OUT_DURATION
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler.SEND_OUT_STAGGER_BASE_DURATION
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler.SEND_OUT_STAGGER_RANDOM_MAX_DURATION
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.util.swap
import java.util.concurrent.CompletableFuture
import kotlin.random.Random
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import java.util.concurrent.CompletableFuture
/**
* Format: |switch|POKEMON|DETAILS|HP STATUS
@@ -63,11 +64,12 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
val pokemonEntity = pokemon.entity
if (pokemonEntity != null && actor !is PokemonBattleActor) { // pokemon entities starting on the field should already have battlePokemon init; see InitializeInstruction
illusion?.let { IllusionEffect(it.effectedPokemon).start(pokemonEntity) }
broadcastSwitch(battle, actor, pokemon, illusion)
//illusion?.let { IllusionEffect(it.effectedPokemon).start(pokemonEntity) }
//broadcastSwitch(battle, actor, pokemon, illusion)
WaitDispatch(0.5F)
}
else if (pokemonEntity == null && entity != null) {
/*
activePokemon.battlePokemon = pokemon
activePokemon.illusion = illusion
val targetPos = ShowdownInterpreter.getSendoutPosition(battle, activePokemon, battleActor)
@@ -90,8 +92,11 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
WaitDispatch(0.5F) // we're already waiting 1.5 seconds. this prevents flooding from consecutive SwitchInstructions
}
}
*/
}
GO
}
val futureSwitches = instructionSet.getSubsequentInstructions(this).filterIsInstance<SwitchInstruction>()
@@ -120,7 +125,7 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
battle.dispatchInsert {
val newHealth = privateMessage.argumentAt(2)!!.split(" ")[0]
val remainingHealth = newHealth.split("/")[0].toInt()
pokemon.effectedPokemon.currentHealth = remainingHealth
// pokemon.effectedPokemon.currentHealth = remainingHealth
pokemon.sendUpdate()
if (activePokemon.battlePokemon == pokemon) {
@@ -139,6 +144,7 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
battle.majorBattleActions[pokemon.uuid] = publicMessage
setOf(
/*
BattleDispatch {
if (entity != null) {
createEntitySwitch(battle, actor, entity, pnx, activePokemon, pokemon, illusion, imposter)
@@ -146,6 +152,8 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
createNonEntitySwitch(battle, actor, pnx, activePokemon, pokemon, illusion)
}
}
*/
)
}
}
@@ -167,7 +175,7 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
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))
//actor.battlePokemonProvider.getKnownPokemon().swap(actor.activePokemon.indexOf(activePokemon), actor.battlePokemonProvider.getKnownPokemon().indexOf(newPokemon))
activePokemon.battlePokemon = newPokemon
activePokemon.illusion = illusion
battle.sendSidedUpdate(actor, BattleSwitchPokemonPacket(pnx, newPokemon, true, illusion), BattleSwitchPokemonPacket(pnx, newPokemon, false, illusion))
@@ -201,7 +209,7 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
}
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))
//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))
@@ -10,8 +10,6 @@ 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.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.instruction.TerastallizationEvent
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.api.types.tera.TeraTypes
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
@@ -32,7 +30,7 @@ class TerastallizeInstruction(val message: BattleMessage): InterpreterInstructio
battle.dispatchWaiting {
val pokemonName = battlePokemon.getName()
battle.broadcastChatMessage(battleLang("terastallize", pokemonName, type.displayName).yellow())
CobblemonEvents.TERASTALLIZATION.post(TerastallizationEvent(battle, battlePokemon, type))
//CobblemonEvents.TERASTALLIZATION.post(TerastallizationEvent(battle, battlePokemon, type))
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
@@ -13,9 +13,6 @@ import com.cobblemon.mod.common.api.battles.model.PokemonBattle
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.UntilDispatch
import com.cobblemon.mod.common.entity.pokemon.effects.TransformEffect
import com.cobblemon.mod.common.net.messages.client.battle.BattleTransformPokemonPacket
import com.cobblemon.mod.common.util.battleLang
/**
@@ -40,9 +37,10 @@ class TransformInstruction(val battle: PokemonBattle, val message: BattleMessage
ShowdownInterpreter.broadcastOptionalAbility(battle, effect, pokemon)
battle.dispatch {
val entity = pokemon.entity ?: return@dispatch GO
val future = TransformEffect(targetPokemon.effectedPokemon, battle.started).start(entity)
UntilDispatch { future?.isDone != false }
GO
//val entity = pokemon.entity ?: return@dispatch GO
//val future = TransformEffect(targetPokemon.effectedPokemon, battle.started).start(entity)
//UntilDispatch { future?.isDone != false }
}
battle.dispatchWaiting {
@@ -51,11 +49,14 @@ class TransformInstruction(val battle: PokemonBattle, val message: BattleMessage
val targetPokemonName = targetPokemon.getName()
mock?.let {
/*
battle.sendSidedUpdate(
source = actor,
allyPacket = BattleTransformPokemonPacket(pnx, pokemon, it, true),
opponentPacket = BattleTransformPokemonPacket(pnx, pokemon, it, false)
)
*/
}
val lang = battleLang("transform", pokemonName, targetPokemonName)
@@ -21,7 +21,6 @@ import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
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.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.util.battleLang
import java.util.UUID
@@ -47,11 +46,11 @@ class WinInstruction(val message: BattleMessage): InterpreterInstruction {
// 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()
// val wildPokemon: BattlePokemon = nonPlayerActor.battlePokemonProvider.getKnownPokemon().first()
if (!wasCaught && losers.any { it.uuid == wildPokemon.uuid }) {
wildPokemon.effectedPokemon.entity?.killer = (battle.actors.firstOrNull { it.type == ActorType.PLAYER } as? PlayerBattleActor)?.entity
}
// if (!wasCaught && losers.any { it.uuid == wildPokemon.uuid }) {
// wildPokemon.effectedPokemon.entity?.killer = (battle.actors.firstOrNull { it.type == ActorType.PLAYER } as? PlayerBattleActor)?.entity
//}
}
// broadcast victory / defeat
@@ -10,8 +10,6 @@ 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.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.instruction.ZMoveUsedEvent
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
@@ -30,7 +28,7 @@ class ZPowerInstruction(val message: BattleMessage): InterpreterInstruction {
battle.dispatchWaiting {
val pokemonName = battlePokemon.getName()
battle.broadcastChatMessage(battleLang("zpower", pokemonName).yellow())
CobblemonEvents.ZPOWER_USED.post(ZMoveUsedEvent(battle, battlePokemon))
//CobblemonEvents.ZPOWER_USED.post(ZMoveUsedEvent(battle, battlePokemon))
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
@@ -0,0 +1,52 @@
package com.cobblemon.mod.common.battles.pokemon
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.moves.MoveSet
import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemManager
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.battles.interpreter.ContextManager
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Nature
import net.minecraft.network.chat.MutableComponent
import java.util.UUID
import java.util.function.Function
abstract class AbstractBattlePokemon {
lateinit var actor: BattleActor
val struct = QueryStruct(
hashMapOf(
"actor" to Function { actor.struct },
"battle" to Function { actor.battle.struct },
"uuid" to Function { StringValue(uuid.toString()) },
"health" to Function { DoubleValue(health.toDouble()) },
"max_health" to Function { DoubleValue(maxHealth.toDouble()) },
"nature" to Function { StringValue(nature.name.toString()) },
"moveset" to Function { moveSet.toStruct() }
))
val statChanges = mutableMapOf<Stat, Int>()
//For when a wild pokemon is being captured in a ball
var gone = false
abstract val uuid: UUID
abstract var health: Int
abstract val maxHealth: Int
abstract val nature: Nature
abstract val moveSet: MoveSet
abstract val entity: PokemonEntity?
abstract val heldItemManager: HeldItemManager
abstract fun canBeSentOut(): Boolean
abstract fun getName(): MutableComponent
abstract fun sendUpdate()
/** The set of all the BattlePokemon that they faced during the battle (for exp purposes) */
val facedOpponents = mutableSetOf<BattlePokemon>()
val contextManager = ContextManager()
fun isSentOut() = actor.battle.activePokemon.any { it.battlePokemon == this }
fun getIllusion(): AbstractBattlePokemon? = this.actor.activePokemon.find { it.battlePokemon == this }?.illusion
}
@@ -8,28 +8,19 @@
package com.cobblemon.mod.common.battles.pokemon
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asStruct
import com.cobblemon.mod.common.api.moves.MoveSet
import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemManager
import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemProvider
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.battles.actor.MultiPokemonBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.battles.interpreter.ContextManager
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.battle.BattleUpdateTeamPokemonPacket
import com.cobblemon.mod.common.pokemon.IVs
import com.cobblemon.mod.common.pokemon.Nature
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.properties.BattleCloneProperty
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.server
import java.util.UUID
import net.minecraft.network.chat.MutableComponent
import java.util.function.Function
@@ -37,8 +28,48 @@ open class BattlePokemon(
val originalPokemon: Pokemon,
val effectedPokemon: Pokemon = originalPokemon,
val postBattleEntityOperation: (PokemonEntity) -> Unit = {}
) {
lateinit var actor: BattleActor
) : AbstractBattlePokemon() {
override val uuid = effectedPokemon.uuid
override var health = effectedPokemon.currentHealth
override val maxHealth = effectedPokemon.maxHealth
override val nature = effectedPokemon.nature
override val moveSet = effectedPokemon.moveSet
override val entity = effectedPokemon.entity
override val heldItemManager: HeldItemManager by lazy {
HeldItemProvider.provide(this)
}
var willBeSwitchedIn = false
override fun canBeSentOut(): Boolean {
return if (actor.request?.side?.pokemon?.any { it.reviving } == true) {
!isSentOut() && !willBeSwitchedIn && health <= 0
} else {
!isSentOut() && !willBeSwitchedIn && health > 0
}
}
override fun getName(): MutableComponent {
val displayPokemon = ((getIllusion() ?: this) as BattlePokemon).effectedPokemon
return if (actor is PokemonBattleActor || actor is MultiPokemonBattleActor) {
displayPokemon.getDisplayName()
} else {
battleLang("owned_pokemon", actor.getName(), displayPokemon.getDisplayName())
}
}
override fun sendUpdate() {
actor.sendUpdate(BattleUpdateTeamPokemonPacket(effectedPokemon))
}
init {
struct.addFunctions(mutableMapOf(
"pokemon" to Function { effectedPokemon.asStruct() },
"original_pokemon" to Function {
originalPokemon.asStruct()
},
))
}
companion object {
fun safeCopyOf(pokemon: Pokemon): BattlePokemon {
@@ -61,74 +92,4 @@ open class BattlePokemon(
}
)
}
val struct = QueryStruct(
hashMapOf(
"pokemon" to Function { effectedPokemon.asStruct() },
"original_pokemon" to Function {
originalPokemon.asStruct()
},
"actor" to Function { actor.struct },
"battle" to Function { actor.battle.struct },
"uuid" to Function { StringValue(uuid.toString()) },
"health" to Function { DoubleValue(health.toDouble()) },
"max_health" to Function { DoubleValue(maxHealth.toDouble()) },
"ivs" to Function { effectedPokemon.ivs.struct },
"nature" to Function { StringValue(effectedPokemon.nature.name.toString()) },
"moveset" to Function { effectedPokemon.moveSet.toStruct() }
))
val uuid: UUID
get() = effectedPokemon.uuid
val health: Int
get() = effectedPokemon.currentHealth
val maxHealth: Int
get() = effectedPokemon.maxHealth
val ivs: IVs
get() = effectedPokemon.ivs
val nature: Nature
get() = effectedPokemon.nature
val moveSet: MoveSet
get() = effectedPokemon.moveSet
val statChanges = mutableMapOf<Stat, Int>()
var gone = false
// etc
val entity: PokemonEntity?
get() = effectedPokemon.entity
var willBeSwitchedIn = false
/** A set of all the BattlePokemon that they faced during the battle (for exp purposes) */
val facedOpponents = mutableSetOf<BattlePokemon>()
/**
* The [HeldItemManager] backing this [BattlePokemon].
*/
val heldItemManager: HeldItemManager by lazy { HeldItemProvider.provide(this) }
val contextManager = ContextManager()
open fun getName(): MutableComponent {
val displayPokemon = getIllusion()?.effectedPokemon ?: effectedPokemon
return if (actor is PokemonBattleActor || actor is MultiPokemonBattleActor) {
displayPokemon.getDisplayName()
} else {
battleLang("owned_pokemon", actor.getName(), displayPokemon.getDisplayName())
}
}
fun sendUpdate() {
actor.sendUpdate(BattleUpdateTeamPokemonPacket(effectedPokemon))
}
fun isSentOut() = actor.battle.activePokemon.any { it.battlePokemon == this }
fun canBeSentOut() =
if (actor.request?.side?.pokemon?.any { it.reviving } == true) {
!isSentOut() && !willBeSwitchedIn && health <= 0
} else {
!isSentOut() && !willBeSwitchedIn && health > 0
}
fun getIllusion(): BattlePokemon? = this.actor.activePokemon.find { it.battlePokemon == this }?.illusion
}
@@ -1,41 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.runner
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.runner.graal.GraalShowdownService
import com.google.gson.JsonArray
import java.util.UUID
/**
* Mediator service for communicating between the Cobblemon Minecraft mod and Cobblemon showdown service.
*
* All outgoing calls to showdown should be done through this layer to decouple the mod from a specific implementation.
* To get an instance of the service, use the singleton provided via `ShowdownService.get()`
*
* @since February 27th, 2023
* @author landonjw
*/
interface ShowdownService {
fun openConnection()
fun closeConnection()
fun startBattle(battle: PokemonBattle, messages: Array<String>)
fun send(battleId: UUID, messages: Array<String>)
fun getAbilityIds(): JsonArray
fun getMoves(): JsonArray
fun getItemIds(): JsonArray
fun registerSpecies()
fun registerBagItems()
fun indicateSpeciesInitialized() {}
companion object {
val service: ShowdownService by lazy { GraalShowdownService() }
}
}
@@ -1,169 +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.runner.socket
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.battles.BagItems
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
import com.google.gson.Gson
import com.google.gson.JsonArray
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.net.InetAddress
import java.net.Socket
import java.nio.charset.Charset
import java.util.UUID
/**
* Mediator service for communicating between the Cobblemon Minecraft mod and Cobblemon showdown service via
* a socket client.
*
* This is primarily used for debugging purposes, but could be extended in the future to provide
* a means of connecting to a remote Showdown server. This does not provide any fault handling in the
* event that the server goes down.
*
* When messages are sent to showdown, this will await a response from showdown.
* The protocol for messages sent from showdown is <length of characters in payload><payload>,
* and a payload length of 0 indicates that there is no response.
*
* @see {@code sim/cobbled-debug-server.ts} within cobblemon-showdown repository
* @since February 27, 2023
* @author landonjw
*/
class SocketShowdownService(val host: String = "localhost", val port: Int = 18468, val localPort: Int = 0) : ShowdownService {
private lateinit var socket: Socket
private lateinit var writer: OutputStreamWriter
private lateinit var reader: BufferedReader
val gson = Gson()
override fun openConnection() {
socket = Socket(InetAddress.getLocalHost(), port, InetAddress.getLocalHost(), localPort)
writer = socket.getOutputStream().writer(charset = Charset.forName("ascii"))
reader = BufferedReader(InputStreamReader(socket.getInputStream()))
}
override fun closeConnection() {
socket.close()
}
override fun startBattle(battle: PokemonBattle, messages: Array<String>) {
writer.write(">startbattle ${battle.battleId}\n")
acknowledge { Cobblemon.LOGGER.error("Failed to start battle!") }
send(battle.battleId, messages)
}
override fun send(battleId: UUID, messages: Array<String>) {
for (message in messages) {
writer.write("$battleId~$message\n")
writer.flush()
readBattleInput().forEach { interpretMessage(battleId, it) }
}
}
private fun read(reader: BufferedReader, size: Int): String {
val buffer = CharArray(size)
while (true) {
if(reader.read(buffer) == 0) continue
return String(buffer)
}
}
private fun readMessage(): String {
val payloadSize = read(reader, 8).toInt()
val payload = read(reader, payloadSize)
return payload
}
private fun readBattleInput(): List<String> {
val lines = mutableListOf<String>()
val numLines = read(reader, 8).toInt()
if (numLines != 0) {
for (i in 0 until numLines) {
lines.add(readMessage())
}
}
return lines
}
private fun interpretMessage(battleId: UUID, message: String) {
ShowdownInterpreter.interpretMessage(battleId, message)
}
override fun getAbilityIds(): JsonArray {
writer.write(">getCobbledAbilityIds")
writer.flush()
val response = readMessage()
return gson.fromJson(response, JsonArray::class.java)
}
override fun getMoves(): JsonArray {
writer.write(">getCobbledMoves\n")
writer.flush()
val response = readMessage()
return gson.fromJson(response, JsonArray::class.java)
}
override fun getItemIds(): JsonArray {
writer.write(">getCobbledItemIds")
writer.flush()
val response = readMessage()
return gson.fromJson(response, JsonArray::class.java)
}
private fun sendSpeciesData(species: Species, form: FormData?) {
writer.write(">receiveSpeciesData ${gson.toJson(PokemonSpecies.ShowdownSpecies(species, form))}\n")
acknowledge()
}
private fun sendBagItem(itemId: String, js: String) {
writer.write(">receiveBagItemData $itemId $js")
acknowledge { Cobblemon.LOGGER.error("Failed to send bag item to Showdown: $itemId") }
}
override fun registerSpecies() {
writer.write(">resetSpeciesData\n")
acknowledge()
PokemonSpecies.species.forEach { species ->
sendSpeciesData(species, null)
species.forms.forEach { form ->
if (form != species.standardForm) {
sendSpeciesData(species, form)
}
}
}
}
fun acknowledge(ifFails: () -> Unit = {}) {
writer.flush()
val ack = CharArray(3)
reader.read(ack)
if (String(ack) != "ACK") {
ifFails()
}
}
override fun registerBagItems() {
for ((itemId, js) in BagItems.bagItemsScripts) {
sendBagItem(itemId, js.replace("\n", " "))
}
}
override fun indicateSpeciesInitialized() {
writer.write(">afterCobbledSpeciesInit")
acknowledge()
}
}
@@ -72,12 +72,10 @@ import net.minecraft.client.renderer.blockentity.SignRenderer
import net.minecraft.client.renderer.entity.EntityRenderer
import net.minecraft.client.renderer.entity.LivingEntityRenderer
import net.minecraft.client.resources.PlayerSkin
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.resources.ResourceManager
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.AABB
import java.util.UUID
object CobblemonClient {
@@ -329,7 +327,7 @@ object CobblemonClient {
fun endBattle() {
battle = null
battleOverlay.lastKnownBattle = null
battleOverlay.lastKnownBattle = ""
BattleMusicController.endMusic()
}
@@ -20,7 +20,7 @@ import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPa
import java.util.UUID
class ClientBattle(
val battleId: UUID,
val battleId: String,
val battleFormat: BattleFormat
) {
var minimised = true
@@ -108,7 +108,7 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
get() = (opacity - MIN_OPACITY) / (MAX_OPACITY - MIN_OPACITY)
var passedSeconds = 0F
var lastKnownBattle: UUID? = null
var lastKnownBattle: String = ""
lateinit var messagePane: BattleMessagePane
var hidePortraits = false
override val schedulingTracker = SchedulingTracker()
@@ -530,7 +530,7 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
fun onLogout() {
this.opacity = MIN_OPACITY
this.passedSeconds = 0F
this.lastKnownBattle = null
this.lastKnownBattle = ""
}
}
@@ -37,7 +37,6 @@ import net.minecraft.commands.arguments.selector.EntitySelector
import net.minecraft.network.chat.Component
object PokedexCommand {
private const val NAME = "pokedex"
private const val GRANT_NAME = "grant"
private const val REVOKE_NAME = "revoke"
@@ -8,7 +8,8 @@
package com.cobblemon.mod.common.command
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.showdown.runner.CobblemonShowdownBattleServer
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.context.CommandContext
@@ -27,10 +28,13 @@ object ReloadShowdownCommand {
private fun execute(context: CommandContext<CommandSourceStack>): Int {
try {
ShowdownService.service.closeConnection()
ShowdownService.service.openConnection()
ShowdownService.service.registerSpecies()
ShowdownService.service.registerBagItems()
Cobblemon.showdownServer.disconnect()
Cobblemon.showdownServer.connect()
val showdownServer = Cobblemon.showdownServer
if (showdownServer is CobblemonShowdownBattleServer) {
showdownServer.registerSpecies()
showdownServer.registerBagItems()
}
context.source.sendSystemMessage(Component.literal("Reloaded showdown"))
} catch (e: Exception) {
e.printStackTrace()
@@ -0,0 +1,43 @@
package com.cobblemon.mod.common.command
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.showdown.runner.remote.RemoteShowdownBattleServer
import com.cobblemon.mod.common.showdown.runner.remote.ShowdownGameServerLoginManager
import com.cobblemon.mod.common.showdown.runner.remote.ShowdownGameServerLoginState
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.StringArgumentType
import com.mojang.brigadier.context.CommandContext
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands.argument
import net.minecraft.commands.Commands.literal
import net.minecraft.network.chat.Component
object ShowdownCommand {
private const val NAME = "showdown"
fun register(dispatcher : CommandDispatcher<CommandSourceStack>) {
dispatcher.register(
literal(NAME)
.then(
literal("setnickname").then(
argument("name", StringArgumentType.string()).executes(::executeSetNick)
)
)
)
}
private fun executeSetNick(context: CommandContext<CommandSourceStack>): Int {
val name = StringArgumentType.getString(context, "name")
val showdownServer = Cobblemon.showdownServer
if (showdownServer !is RemoteShowdownBattleServer) {
context.source.sendFailure(Component.literal("Configured showdown server is not a showdown game server!!"))
return Command.SINGLE_SUCCESS
}
val assertion = ShowdownGameServerLoginManager.loginGuest(name, showdownServer.getLoginState())
showdownServer.setLoginState(ShowdownGameServerLoginState(assertion = assertion, username = name))
showdownServer.sendServerMessage("|/trn $name,0,$assertion")
return Command.SINGLE_SUCCESS
}
}
@@ -8,14 +8,9 @@
package com.cobblemon.mod.common.entity.npc
import com.cobblemon.mod.common.api.battles.model.actor.AIBattleActor
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
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.api.battles.model.actor.*
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.battles.ai.StrongBattleAI
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.chainFutures
@@ -25,13 +20,8 @@ import java.util.concurrent.CompletableFuture
class NPCBattleActor(
val npc: NPCEntity,
pokemonList: List<BattlePokemon>,
val skill: Int
) : AIBattleActor(
gameId = npc.uuid,
pokemonList = pokemonList.let { if (npc.npc.randomizePartyOrder) it.shuffled() else it },
battleAI = StrongBattleAI(skill)
), EntityBackedBattleActor<NPCEntity> {
pokemonProvider: BattlePokemonProvider
) : BattleActor(npc.uuid, pokemonProvider), EntityBackedBattleActor<NPCEntity> {
override val entity = npc
override val type = ActorType.NPC
override fun getName() = npc.effectiveName().copy()
@@ -40,19 +30,17 @@ class NPCBattleActor(
constructor(
npc: NPCEntity,
party: PartyStore,
skill: Int
party: PartyStore
): this(
npc,
party.toBattleTeam(healPokemon = npc.npc.autoHealParty),
skill
StaticBattlePokemonProvider(party.toBattleTeam(healPokemon = npc.npc.autoHealParty))
)
override fun sendUpdate(packet: NetworkPacket<*>) {
super.sendUpdate(packet)
if (packet is BattleEndPacket) {
if (npc.isAlive) {
val allEntities = pokemonList.mapNotNull { it.entity }.toMutableList()
val allEntities = battlePokemonProvider.getKnownPokemon().mapNotNull { it.entity }.toMutableList()
val finalFuture = CompletableFuture<Unit>()
chainFutures(allEntities.map { pokemonEntity -> { pokemonEntity.recallWithAnimation() } }.iterator(), finalFuture)
if (allEntities.isEmpty()) {
@@ -164,7 +164,7 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
val aspects: Set<String>
get() = entityData.get(ASPECTS)
val battleIds: Set<UUID>
val battleIds: Set<String>
get() = entityData.get(BATTLE_IDS)
var actionEffect: ActionEffectContext? = null
@@ -207,7 +207,7 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
val NPC_CLASS = SynchedEntityData.defineId(NPCEntity::class.java, IdentifierDataSerializer)
val ASPECTS = SynchedEntityData.defineId(NPCEntity::class.java, StringSetDataSerializer)
val POSE_TYPE = SynchedEntityData.defineId(NPCEntity::class.java, PoseTypeDataSerializer)
val BATTLE_IDS = SynchedEntityData.defineId(NPCEntity::class.java, UUIDSetDataSerializer)
val BATTLE_IDS = SynchedEntityData.defineId(NPCEntity::class.java, StringSetDataSerializer)
val NPC_PLAYER_TEXTURE = SynchedEntityData.defineId(NPCEntity::class.java, NPCPlayerTextureSerializer)
val LEVEL = SynchedEntityData.defineId(NPCEntity::class.java, EntityDataSerializers.INT)
val HIDE_NAME_TAG = SynchedEntityData.defineId(NPCEntity::class.java, EntityDataSerializers.BOOLEAN)
@@ -224,7 +224,7 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
return drop() // Weird, shouldn't be possible
}
if (battle.format.battleType != BattleTypes.SINGLES || hitActor.pokemonList.count { it.health > 0 } > 1) {
if (battle.format.battleType != BattleTypes.SINGLES || hitActor.battlePokemonProvider.getKnownPokemon().count { it.health > 0 } > 1) {
owner.sendSystemMessage(lang("capture.not_single").red())
return drop()
}
@@ -51,6 +51,7 @@ import com.cobblemon.mod.common.battles.BagItems
import com.cobblemon.mod.common.battles.BattleBuilder
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.SuccessfulBattleStart
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.block.entity.PokemonPastureBlockEntity
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.entity.PlatformType
@@ -87,8 +88,6 @@ import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import com.mojang.serialization.Codec
import java.util.EnumSet
import java.util.Optional
import java.util.UUID
import java.util.concurrent.CompletableFuture
import kotlin.math.PI
import net.minecraft.core.BlockPos
@@ -168,7 +167,7 @@ open class PokemonEntity(
@JvmStatic val PHASING_TARGET_ID = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.INT)
@JvmStatic val PLATFORM_TYPE = SynchedEntityData.defineId(PokemonEntity::class.java, PlatformTypeDataSerializer)
@JvmStatic val BEAM_MODE = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.BYTE)
@JvmStatic val BATTLE_ID = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.OPTIONAL_UUID)
@JvmStatic val BATTLE_ID = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.STRING)
@JvmStatic val ASPECTS = SynchedEntityData.defineId(PokemonEntity::class.java, StringSetDataSerializer)
@JvmStatic val DYING_EFFECTS_STARTED = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.BOOLEAN)
@JvmStatic val POSE_TYPE = SynchedEntityData.defineId(PokemonEntity::class.java, PoseTypeDataSerializer)
@@ -229,11 +228,11 @@ open class PokemonEntity(
get() = busyLocks.isNotEmpty()
val aspects: Set<String>
get() = entityData.get(ASPECTS)
var battleId: UUID?
get() = entityData.get(BATTLE_ID).orElse(null)
set(value) = entityData.set(BATTLE_ID, Optional.ofNullable(value))
var battleId: String
get() = entityData.get(BATTLE_ID) ?: ""
set(value) = entityData.set(BATTLE_ID, value ?: "")
val isBattling: Boolean
get() = entityData.get(BATTLE_ID).isPresent
get() = entityData.get(BATTLE_ID) != ""
val friendship: Int
get() = entityData.get(FRIENDSHIP)
val seats: List<Seat>
@@ -329,7 +328,7 @@ open class PokemonEntity(
builder.define(BEAM_MODE, 0)
builder.define(PLATFORM_TYPE, PlatformType.NONE)
builder.define(PHASING_TARGET_ID, -1)
builder.define(BATTLE_ID, Optional.empty())
builder.define(BATTLE_ID, "")
builder.define(ASPECTS, emptySet())
builder.define(DYING_EFFECTS_STARTED, false)
builder.define(POSE_TYPE, PoseType.STAND)
@@ -614,8 +613,8 @@ open class PokemonEntity(
nbt.put(DataKeys.POKEMON, pokemon.saveToNBT(registryAccess()))
}
val battleIdToSave = battleId
if (battleIdToSave != null) {
nbt.putUUID(DataKeys.POKEMON_BATTLE_ID, battleIdToSave)
if (battleIdToSave != "") {
nbt.putString(DataKeys.POKEMON_BATTLE_ID, battleIdToSave)
}
nbt.putString(DataKeys.POKEMON_POSE_TYPE, entityData.get(POSE_TYPE).name)
nbt.putByte(DataKeys.POKEMON_BEHAVIOUR_FLAGS, entityData.get(BEHAVIOUR_FLAGS))
@@ -683,7 +682,7 @@ open class PokemonEntity(
}
val savedBattleId =
if (nbt.hasUUID(DataKeys.POKEMON_BATTLE_ID)) nbt.getUUID(DataKeys.POKEMON_BATTLE_ID) else null
if (nbt.contains(DataKeys.POKEMON_BATTLE_ID)) nbt.getString(DataKeys.POKEMON_BATTLE_ID) else ""
if (savedBattleId != null) {
val battle = BattleRegistry.getBattle(savedBattleId)
if (battle != null) {
@@ -1065,9 +1064,9 @@ open class PokemonEntity(
val bagItemLike = BagItems.getConvertibleForStack(stack) ?: return false
val battlePokemon =
battle.actors.flatMap { it.pokemonList }.find { it.effectedPokemon.uuid == pokemon.uuid }
?: return false // Shouldn't be possible but anyway
val battlePokemon = battle.actors.flatMap { it.battlePokemonProvider.getKnownPokemon() }
.filterIsInstance<BattlePokemon>()
.find { it.effectedPokemon.uuid == pokemon.uuid } ?: return false
if (battlePokemon.actor.getSide().actors.none { it.isForPlayer(player) }) {
return true
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.entity.pokemon
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.entity.PokemonSender
import com.cobblemon.mod.common.api.entity.PokemonSideDelegate
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
@@ -23,7 +22,6 @@ import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.activestate.ActivePokemonState
import com.cobblemon.mod.common.pokemon.activestate.SentOutState
import com.cobblemon.mod.common.util.getIsSubmerged
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.playSoundServer
import com.cobblemon.mod.common.util.update
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
@@ -34,12 +32,9 @@ import net.minecraft.server.level.ServerLevel
import net.minecraft.network.chat.Component
import net.minecraft.network.syncher.EntityDataAccessor
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.pathfinder.PathType
import org.joml.Matrix3f
import org.joml.Vector3f
/** Handles purely server logic for a Pokémon */
class PokemonServerDelegate : PokemonSideDelegate {
@@ -162,12 +157,7 @@ class PokemonServerDelegate : PokemonSideDelegate {
// }
entity.entityData.update(PokemonEntity.BATTLE_ID) { opt ->
val battleId = opt.orElse(null)
if (battleId != null && BattleRegistry.getBattle(battleId).let { it == null || it.ended }) {
Optional.empty()
} else {
opt
}
opt
}
val battle = getBattle()
@@ -86,10 +86,10 @@ class IllusionEffect(
val battleId = entity.battleId ?: return
val battle = BattleRegistry.getBattle(battleId) ?: return
val wildActor = battle.getActor(entity.pokemon.uuid) ?: return
val battlePokemon = wildActor.pokemonList.firstOrNull { it.uuid == entity.pokemon.uuid } ?: return
val battlePokemon = wildActor.battlePokemonProvider.getKnownPokemon().firstOrNull { it.uuid == entity.pokemon.uuid } ?: return
// Step 2 flag all players as seeing the Pokemon
battle.playerUUIDs.forEach { uuid ->
CobblemonEvents.POKEMON_SEEN.post(PokemonSeenEvent(uuid, battlePokemon.effectedPokemon))
//CobblemonEvents.POKEMON_SEEN.post(PokemonSeenEvent(uuid, battlePokemon.effectedPokemon))
}
}
@@ -10,7 +10,13 @@ package com.cobblemon.mod.common.events
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.advancement.CobblemonCriteria
import com.cobblemon.mod.common.advancement.criterion.*
import com.cobblemon.mod.common.advancement.criterion.BattleCountableContext
import com.cobblemon.mod.common.advancement.criterion.CountableContext
import com.cobblemon.mod.common.advancement.criterion.CountablePokemonTypeContext
import com.cobblemon.mod.common.advancement.criterion.EvolvePokemonContext
import com.cobblemon.mod.common.advancement.criterion.LevelUpContext
import com.cobblemon.mod.common.advancement.criterion.PlantTumblestoneContext
import com.cobblemon.mod.common.advancement.criterion.TradePokemonContext
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.events.CobblemonEvents.BATTLE_VICTORY
@@ -19,16 +25,20 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents.LEVEL_UP_EVENT
import com.cobblemon.mod.common.api.events.CobblemonEvents.POKEMON_CAPTURED
import com.cobblemon.mod.common.api.events.CobblemonEvents.TRADE_COMPLETED
import com.cobblemon.mod.common.api.events.battles.BattleVictoryEvent
import com.cobblemon.mod.common.api.events.pokemon.*
import com.cobblemon.mod.common.api.events.pokemon.CollectEggEvent
import com.cobblemon.mod.common.api.events.pokemon.HatchEggEvent
import com.cobblemon.mod.common.api.events.pokemon.LevelUpEvent
import com.cobblemon.mod.common.api.events.pokemon.PokemonCapturedEvent
import com.cobblemon.mod.common.api.events.pokemon.TradeCompletedEvent
import com.cobblemon.mod.common.api.events.pokemon.evolution.EvolutionCompleteEvent
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.block.TumblestoneBlock
import com.cobblemon.mod.common.item.TumblestoneItem
import com.cobblemon.mod.common.platform.events.ServerPlayerEvent
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.platform.events.ServerPlayerEvent
import com.cobblemon.mod.common.util.effectiveName
import com.cobblemon.mod.common.util.getPlayer
import java.util.*
import java.util.UUID
object AdvancementHandler : EventHandler {
override fun registerListeners() {
@@ -110,8 +120,8 @@ object AdvancementHandler : EventHandler {
val advancementData = playerData.advancementData
event.battle.actors.forEach { battleActor ->
if (!event.winners.contains(battleActor) && battleActor.type == ActorType.WILD) {
battleActor.pokemonList.forEach { battlePokemon ->
advancementData.updateTotalDefeatedCount(battlePokemon.originalPokemon)
battleActor.battlePokemonProvider.getKnownPokemon().forEach { battlePokemon ->
//advancementData.updateTotalDefeatedCount(battlePokemon.originalPokemon)
}
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.item.interactive
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.advancement.CobblemonCriteria
import com.cobblemon.mod.common.advancement.criterion.PokemonInteractContext
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
@@ -18,13 +17,15 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokemon.healing.PokemonHealedEvent
import com.cobblemon.mod.common.api.item.HealingSource
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.BagItemActionResponse
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.item.CobblemonItem
import com.cobblemon.mod.common.item.battle.BagItem
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.isHeld
import com.cobblemon.mod.common.util.isInBattle
import com.cobblemon.mod.common.util.party
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
@@ -65,12 +66,13 @@ class ReviveItem(
val battle = BattleRegistry.getBattleByParticipatingPlayer(player)
if (battle != null) {
val actor = battle.getActor(player)!!
val battlePokemon = actor.pokemonList
val battlePokemon = actor.battlePokemonProvider.getKnownPokemon()
if (!actor.canFitForcedAction()) {
player.sendSystemMessage(battleLang("bagitem.cannot").red(), true)
return InteractionResultHolder.consume(stack)
} else {
val turn = battle.turn
/*
PartySelectCallbacks.createBattleSelect(
player = player,
pokemon = battlePokemon,
@@ -86,6 +88,8 @@ class ReviveItem(
CobblemonCriteria.POKEMON_INTERACT.trigger(player, PokemonInteractContext(bp.effectedPokemon.species.resourceIdentifier, stackName))
}
}
*/
}
} else {
val pokemon = player.party().toList()
@@ -29,10 +29,10 @@ import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeMapK
import com.cobblemon.mod.common.util.writeSizedInt
import com.cobblemon.mod.common.util.writeString
import java.util.UUID
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.ComponentSerialization
import net.minecraft.network.chat.MutableComponent
import java.util.UUID
/**
* Initializes the client's understanding of a battle. This can be for a participant or for a spectator.
@@ -46,7 +46,7 @@ class BattleInitializePacket() : NetworkPacket<BattleInitializePacket> {
override val id = ID
lateinit var battleId: UUID
lateinit var battleId: String
lateinit var battleFormat: BattleFormat
lateinit var side1: BattleSideDTO
@@ -79,7 +79,7 @@ class BattleInitializePacket() : NetworkPacket<BattleInitializePacket> {
}
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeUUID(battleId)
buffer.writeString(battleId)
battleFormat.saveToBuffer(buffer)
for (side in arrayOf(side1, side2)) {
buffer.writeSizedInt(IntSize.U_BYTE, side.actors.size)
@@ -98,7 +98,7 @@ class BattleInitializePacket() : NetworkPacket<BattleInitializePacket> {
}
private fun decode(buffer: RegistryFriendlyByteBuf) {
battleId = buffer.readUUID()
battleId = buffer.readString()
battleFormat = BattleFormat.loadFromBuffer(buffer)
val sides = mutableListOf<BattleSideDTO>()
repeat(times = 2) {
@@ -22,7 +22,6 @@ import com.cobblemon.mod.common.util.writeEnumConstant
import com.cobblemon.mod.common.util.writeIdentifier
import com.cobblemon.mod.common.util.writeString
import com.cobblemon.mod.common.util.writeText
import java.util.UUID
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket
@@ -33,7 +32,7 @@ class SpawnNPCPacket(
var npcClass: ResourceLocation,
var aspects: Set<String>,
var level: Int,
var battleIds: Set<UUID>,
var battleIds: Set<String>,
var name: Component,
var poseType: PoseType,
var texture: NPCPlayerTexture,
@@ -59,7 +58,7 @@ class SpawnNPCPacket(
buffer.writeIdentifier(this.npcClass)
buffer.writeCollection(this.aspects) { pb, value -> pb.writeString(value) }
buffer.writeInt(this.level)
buffer.writeCollection(this.battleIds) { pb, value -> pb.writeUUID(value) }
buffer.writeCollection(this.battleIds) { pb, value -> pb.writeString(value) }
buffer.writeText(name)
buffer.writeEnumConstant(this.poseType)
buffer.writeEnumConstant(this.texture.model)
@@ -88,7 +87,7 @@ class SpawnNPCPacket(
val npc = buffer.readIdentifier()
val aspects = buffer.readList { buffer.readString() }.toSet()
val level = buffer.readInt()
val battleIds = buffer.readList { buffer.readUUID() }.toSet()
val battleIds = buffer.readList { buffer.readString() }.toSet()
val name = buffer.readText()
val poseType = buffer.readEnumConstant(PoseType::class.java)
val model = buffer.readEnumConstant(NPCPlayerModelType::class.java)
@@ -15,13 +15,21 @@ import com.cobblemon.mod.common.entity.PlatformType
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.util.*
import java.util.UUID
import net.minecraft.world.entity.Entity
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readEnumConstant
import com.cobblemon.mod.common.util.readIdentifier
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.readText
import com.cobblemon.mod.common.util.writeEnumConstant
import com.cobblemon.mod.common.util.writeIdentifier
import com.cobblemon.mod.common.util.writeString
import com.cobblemon.mod.common.util.writeText
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.Entity
import java.util.UUID
class SpawnPokemonPacket(
var ownerId: UUID?,
@@ -31,7 +39,7 @@ class SpawnPokemonPacket(
var shiny: Boolean,
var formName: String,
var aspects: Set<String>,
var battleId: UUID?,
var battleId: String,
var phasingTargetId: Int,
var beamMode: Byte,
var platform: PlatformType,
@@ -81,7 +89,7 @@ class SpawnPokemonPacket(
buffer.writeBoolean(this.shiny)
buffer.writeString(this.formName)
buffer.writeCollection(this.aspects) { pb, value -> pb.writeString(value) }
buffer.writeNullable(this.battleId) { pb, value -> pb.writeUUID(value) }
buffer.writeString(this.battleId)
buffer.writeInt(this.phasingTargetId)
buffer.writeByte(this.beamMode.toInt())
buffer.writeEnumConstant(this.platform)
@@ -135,7 +143,7 @@ class SpawnPokemonPacket(
val shiny = buffer.readBoolean()
val formName = buffer.readString()
val aspects = buffer.readList { it.readString() }.toSet()
val battleId = buffer.readNullable { buffer.readUUID() }
val battleId = buffer.readString()
val phasingTargetId = buffer.readInt()
val beamModeEmitter = buffer.readByte()
val platform = buffer.readEnumConstant(PlatformType::class.java)
@@ -13,16 +13,17 @@ import com.cobblemon.mod.common.battles.ShowdownActionResponse
import com.cobblemon.mod.common.net.IntSize
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readSizedInt
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeSizedInt
import java.util.UUID
import com.cobblemon.mod.common.util.writeString
import net.minecraft.network.RegistryFriendlyByteBuf
class BattleSelectActionsPacket(val battleId: UUID, val showdownActionResponses: List<ShowdownActionResponse>) : NetworkPacket<BattleSelectActionsPacket> {
class BattleSelectActionsPacket(val battleId: String, val showdownActionResponses: List<ShowdownActionResponse>) : NetworkPacket<BattleSelectActionsPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeUUID(battleId)
buffer.writeString(battleId)
buffer.writeSizedInt(IntSize.U_BYTE, showdownActionResponses.size)
showdownActionResponses.forEach { it.saveToBuffer(buffer) }
}
@@ -30,7 +31,7 @@ class BattleSelectActionsPacket(val battleId: UUID, val showdownActionResponses:
companion object {
val ID = cobblemonResource("battle_select_actions")
fun decode(buffer: RegistryFriendlyByteBuf): BattleSelectActionsPacket {
val battleId = buffer.readUUID()
val battleId = buffer.readString()
val responses = mutableListOf<ShowdownActionResponse>()
repeat(times = buffer.readSizedInt(IntSize.U_BYTE)) {
responses.add(ShowdownActionResponse.loadFromBuffer(buffer))
@@ -10,17 +10,18 @@ package com.cobblemon.mod.common.net.messages.server.battle
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import java.util.UUID
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeString
import net.minecraft.network.RegistryFriendlyByteBuf
class RemoveSpectatorPacket(val battleId: UUID) : NetworkPacket<RemoveSpectatorPacket> {
class RemoveSpectatorPacket(val battleId: String) : NetworkPacket<RemoveSpectatorPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeUUID(battleId)
buffer.writeString(battleId)
}
companion object {
val ID = cobblemonResource("remove_spectator")
fun decode(buffer: RegistryFriendlyByteBuf) = RemoveSpectatorPacket(buffer.readUUID())
fun decode(buffer: RegistryFriendlyByteBuf) = RemoveSpectatorPacket(buffer.readString())
}
}
@@ -10,8 +10,6 @@ package com.cobblemon.mod.common.net.serverhandling
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokemon.PokemonSeenEvent
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.BattleBuilder
@@ -69,9 +67,9 @@ object ChallengeHandler : ServerNetworkPacketHandler<BattleChallengePacket> {
private fun flagAsSeen(battle: PokemonBattle, entity: PokemonEntity) {
// Wild actor is tied to Pokemon UUID not entity
val actor = battle.getActor(entity.pokemon.uuid) as? PokemonBattleActor ?: return
val battlePokemon = actor.pokemonList.firstOrNull { it.uuid == entity.pokemon.uuid } ?: return
val battlePokemon = actor.battlePokemonProvider.getKnownPokemon().firstOrNull { it.uuid == entity.pokemon.uuid } ?: return
battle.playerUUIDs.forEach { uuid ->
CobblemonEvents.POKEMON_SEEN.post(PokemonSeenEvent(uuid, battlePokemon.effectedPokemon))
//CobblemonEvents.POKEMON_SEEN.post(PokemonSeenEvent(uuid, battlePokemon.effectedPokemon))
}
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.pokemon
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonBuildDetails
import com.cobblemon.mod.common.CobblemonNetwork.sendPacketToPlayers
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.abilities.Abilities
@@ -59,7 +58,6 @@ import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.SettableObservable
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.scheduling.afterOnServer
import com.cobblemon.mod.common.api.storage.InvalidSpeciesException
import com.cobblemon.mod.common.api.riding.RidingProperties
import com.cobblemon.mod.common.api.storage.StoreCoordinates
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
@@ -603,7 +601,7 @@ open class Pokemon : ShowdownIdentifiable {
source: LivingEntity,
level: ServerLevel,
position: Vec3,
battleId: UUID? = null,
battleId: String = "",
doCry: Boolean = true,
illusion: IllusionEffect? = null,
mutation: (PokemonEntity) -> Unit = {},
@@ -720,7 +718,7 @@ open class Pokemon : ShowdownIdentifiable {
player: ServerPlayer,
level: ServerLevel,
targetPosition: Vec3,
battleId: UUID? = null,
battleId: String = "",
doCry: Boolean = true,
illusion: IllusionEffect? = null,
mutation: (PokemonEntity) -> Unit = {}
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemManager
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.google.common.collect.HashBiMap
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.chat.Component
@@ -32,11 +31,7 @@ abstract class BaseCobblemonHeldItemManager : HeldItemManager {
internal open fun load() {
this.itemIds.clear()
val itemsJson = ShowdownService.service.getItemIds()
val showdownIds = hashSetOf<String>()
for (i in 0 until itemsJson.size()) {
showdownIds += itemsJson[i].asString
}
val showdownIds = Cobblemon.showdownServer.getItemIds()
BuiltInRegistries.ITEM.forEach { item ->
val identifier = BuiltInRegistries.ITEM.getKey(item)
if (identifier.namespace == Cobblemon.MODID) {
@@ -0,0 +1,7 @@
package com.cobblemon.mod.common.showdown
data class ShowdownSearch(
val searching: List<String>,
val games: Map<String, String>?
) {
}
@@ -0,0 +1,10 @@
package com.cobblemon.mod.common.showdown.runner
/**
* Functionality specific to the Cobblemon fork of Pokemon Showdown
*/
interface CobblemonShowdownBattleServer : ShowdownBattleServer {
fun registerSpecies()
fun indicateSpeciesInitialized() {}
fun registerBagItems() {}
}
@@ -0,0 +1,15 @@
package com.cobblemon.mod.common.showdown.runner
abstract class ShowdownBattle {
abstract val id: String
fun sendMessage(message: String) {
sendMessages(message)
}
fun sendMessages(vararg messages: String) {
return sendMessages(messages.toList())
}
abstract fun sendMessages(messages: List<String>)
}
@@ -0,0 +1,19 @@
package com.cobblemon.mod.common.showdown.runner
import com.cobblemon.mod.common.api.moves.MoveTemplate
/**
* A service that controls the creation of individual Pokemon battles with Pokemon Showdown
*/
interface ShowdownBattleServer {
fun connect()
fun disconnect()
fun initializeNewBattle(battleId: String, messages: Array<String>): ShowdownBattle
fun sendServerMessage(msg: String)
//Ideally this should be different per-battle but since we only refresh the registries at world load, that will be challenging
fun getAbilityIds(): Collection<String>
fun getMoves(): Collection<MoveTemplate>
fun getItemIds(): Collection<String>
fun onTick() {}
fun onServerStarted()
}
@@ -6,16 +6,23 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.runner.graal
package com.cobblemon.mod.common.showdown.runner.graal
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.battles.BagItems
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.showdown.runner.CobblemonShowdownBattleServer
import com.cobblemon.mod.common.showdown.runner.ShowdownBattle
import com.google.gson.Gson
import com.google.gson.JsonArray
import org.graalvm.polyglot.Context
import org.graalvm.polyglot.HostAccess
import org.graalvm.polyglot.HostAccess.Export
import org.graalvm.polyglot.PolyglotAccess
import org.graalvm.polyglot.Value
import org.graalvm.polyglot.io.FileSystem
import java.io.File
import java.io.IOException
import java.net.URI
@@ -27,12 +34,6 @@ import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.attribute.FileAttribute
import java.util.UUID
import org.graalvm.polyglot.Context
import org.graalvm.polyglot.HostAccess
import org.graalvm.polyglot.HostAccess.Export
import org.graalvm.polyglot.PolyglotAccess
import org.graalvm.polyglot.Value
import org.graalvm.polyglot.io.FileSystem
/**
* Mediator service for communicating between the Cobblemon Minecraft mod and Cobblemon showdown service via
@@ -43,8 +44,7 @@ import org.graalvm.polyglot.io.FileSystem
* @since February 27, 2023
* @author Hiroku, landonjw
*/
class GraalShowdownService : ShowdownService {
class GraalCobblemonShowdownBattleServer : CobblemonShowdownBattleServer {
@Transient
lateinit var context: Context
@Transient
@@ -54,69 +54,13 @@ class GraalShowdownService : ShowdownService {
@Transient
val gson = Gson()
override fun openConnection() {
override fun connect() {
unbundler.attemptUnbundle()
createContext()
boot()
}
private fun createContext() {
val wd = Paths.get("./showdown")
val access = HostAccess.newBuilder(HostAccess.EXPLICIT)
.allowIterableAccess(true)
.allowArrayAccess(true)
.allowListAccess(true)
.allowMapAccess(true)
.build()
context = Context.newBuilder("js")
.allowIO(true)
.fileSystem(object : FileSystem
{
val default = FileSystem.newDefaultFileSystem()
override fun parsePath(uri: URI) = default.parsePath(uri)
override fun parsePath(path: String) = default.parsePath(path)
override fun createDirectory(dir: Path, vararg attrs: FileAttribute<*>) = default.createDirectory(dir, *attrs)
override fun delete(path: Path) = default.delete(path)
override fun newByteChannel(path: Path, options: MutableSet<out OpenOption>, vararg attrs: FileAttribute<*>) = default.newByteChannel(path, options, *attrs)
override fun newDirectoryStream(dir: Path, filter: DirectoryStream.Filter<in Path>) = default.newDirectoryStream(dir, filter)
override fun toAbsolutePath(path: Path) = default.toAbsolutePath(path)
override fun toRealPath(path: Path, vararg linkOptions: LinkOption) = default.toRealPath(path, *linkOptions)
override fun readAttributes(path: Path, attributes: String, vararg options: LinkOption) = default.readAttributes(path, attributes, *options)
override fun checkAccess(path: Path, modes: MutableSet<out AccessMode>, vararg linkOptions: LinkOption) {
if (!path.toRealPath(LinkOption.NOFOLLOW_LINKS).startsWith(wd.toRealPath(LinkOption.NOFOLLOW_LINKS))) {
Cobblemon.LOGGER.error("Hacked JS files in datapacks or some weird file system setup that Hiroku failed to anticipate.")
throw IOException("Someone has put hacked JS files into datapacks because file access is being attempted outside of controlled folders.")
}
}
}
)
.allowExperimentalOptions(true)
.allowPolyglotAccess(PolyglotAccess.ALL)
.allowHostAccess(access)
.allowCreateThread(true)
.logHandler(GraalLogger)
.option("engine.WarnInterpreterOnly", "false")
.option("js.commonjs-require", "true")
.option("js.commonjs-require-cwd", "showdown")
.option(
"js.commonjs-core-modules-replacements",
"buffer:buffer/,crypto:crypto-browserify,path:path-browserify"
)
.allowHostClassLoading(true)
.allowNativeAccess(true)
.allowCreateProcess(true)
.build()
context.eval("js", """
globalThis.process = {
cwd: function() {
return '';
}
}
""".trimIndent())
}
override fun closeConnection() {
override fun disconnect() {
context.close()
}
@@ -125,31 +69,36 @@ class GraalShowdownService : ShowdownService {
sendBattleMessageFunction = context.getBindings("js").getMember("sendBattleMessage")
}
override fun startBattle(battle: PokemonBattle, messages: Array<String>) {
override fun initializeNewBattle(battleId: String, messages: Array<String>): ShowdownBattle {
val startBattleFunction = context.getBindings("js").getMember("startBattle")
startBattleFunction.execute(this, battle.battleId.toString(), messages)
startBattleFunction.execute(this, battleId, messages)
return GraalShowdownBattle(battleId, sendBattleMessageFunction)
}
override fun send(battleId: UUID, messages: Array<String>) {
sendToShowdown(battleId, messages)
override fun sendServerMessage(msg: String) {
throw NotImplementedError("You shouldn't be sending server messages to a GraalShowdownBattleServer, all messages should be sent in a GraalShowdownBattle")
}
override fun getAbilityIds(): JsonArray {
override fun getAbilityIds(): Collection<String> {
val getCobbledAbilityIdsFn = context.getBindings("js").getMember("getCobbledAbilityIds")
val arrayResult = getCobbledAbilityIdsFn.execute().asString()
return gson.fromJson(arrayResult, JsonArray::class.java)
return gson.fromJson(arrayResult, JsonArray::class.java).map { it.asString }.toSet()
}
override fun getMoves(): JsonArray {
override fun getMoves(): Collection<MoveTemplate> {
val getCobbledMovesFn = context.getBindings("js").getMember("getCobbledMoves")
val arrayResult = getCobbledMovesFn.execute().asString()
return gson.fromJson(arrayResult, JsonArray::class.java)
return gson.fromJson(arrayResult, JsonArray::class.java).map { MoveTemplate.decodeJsonMove(it.asJsonObject) }.toSet()
}
override fun getItemIds(): JsonArray {
override fun getItemIds(): Collection<String> {
val getCobbledItemIdsFn = context.getBindings("js").getMember("getCobbledItemIds")
val arrayResult = getCobbledItemIdsFn.execute().asString()
return gson.fromJson(arrayResult, JsonArray::class.java)
return gson.fromJson(arrayResult, JsonArray::class.java).map { it.asString }.toSet()
}
override fun onServerStarted() {
}
override fun registerSpecies() {
@@ -179,13 +128,69 @@ class GraalShowdownService : ShowdownService {
}
}
private fun createContext() {
val wd = Paths.get("./showdown")
val access = HostAccess.newBuilder(HostAccess.EXPLICIT)
.allowIterableAccess(true)
.allowArrayAccess(true)
.allowListAccess(true)
.allowMapAccess(true)
.build()
context = Context.newBuilder("js")
.allowIO(true)
.fileSystem(object : FileSystem
{
val default = FileSystem.newDefaultFileSystem()
override fun parsePath(uri: URI) = default.parsePath(uri)
override fun parsePath(path: String) = default.parsePath(path)
override fun createDirectory(dir: Path, vararg attrs: FileAttribute<*>) = default.createDirectory(dir, *attrs)
override fun delete(path: Path) = default.delete(path)
override fun newByteChannel(path: Path, options: MutableSet<out OpenOption>, vararg attrs: FileAttribute<*>) = default.newByteChannel(path, options, *attrs)
override fun newDirectoryStream(dir: Path, filter: DirectoryStream.Filter<in Path>) = default.newDirectoryStream(dir, filter)
override fun toAbsolutePath(path: Path) = default.toAbsolutePath(path)
override fun toRealPath(path: Path, vararg linkOptions: LinkOption) = default.toRealPath(path, *linkOptions)
override fun readAttributes(path: Path, attributes: String, vararg options: LinkOption) = default.readAttributes(path, attributes, *options)
override fun checkAccess(path: Path, modes: MutableSet<out AccessMode>, vararg linkOptions: LinkOption) {
if (!path.toRealPath(LinkOption.NOFOLLOW_LINKS).startsWith(wd.toRealPath(LinkOption.NOFOLLOW_LINKS))) {
Cobblemon.LOGGER.error("Hacked JS files in datapacks or some weird file system setup that Hiroku failed to anticipate.")
throw IOException("Someone has put hacked JS files into datapacks because file access is being attempted outside of controlled folders.")
}
}
}
)
.allowExperimentalOptions(true)
.allowPolyglotAccess(PolyglotAccess.ALL)
.allowHostAccess(access)
.allowCreateThread(true)
.logHandler(GraalLogger)
.option("engine.WarnInterpreterOnly", "false")
.option("js.commonjs-require", "true")
.option("js.commonjs-require-cwd", "showdown")
.option(
"js.commonjs-core-modules-replacements",
"buffer:buffer/,crypto:crypto-browserify,path:path-browserify"
)
.allowHostClassLoading(true)
.allowNativeAccess(true)
.allowCreateProcess(true)
.build()
context.eval("js", """
globalThis.process = {
cwd: function() {
return '';
}
}
""".trimIndent())
}
private fun sendToShowdown(battleId: UUID, messages: Array<String>) {
sendBattleMessageFunction.execute(battleId.toString(), messages)
sendBattleMessageFunction.execute(battleId, messages)
}
@Export
fun sendFromShowdown(battleId: String, message: String) {
ShowdownInterpreter.interpretMessage(UUID.fromString(battleId), message)
ShowdownInterpreter.interpretMessage(battleId, message)
}
@Export
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.runner.graal
package com.cobblemon.mod.common.showdown.runner.graal
import com.cobblemon.mod.common.Cobblemon
import java.util.logging.Handler
@@ -0,0 +1,13 @@
package com.cobblemon.mod.common.showdown.runner.graal
import com.cobblemon.mod.common.showdown.runner.ShowdownBattle
import org.graalvm.polyglot.Value
class GraalShowdownBattle(
override val id: String,
val sendBattleFunction: Value
) : ShowdownBattle() {
override fun sendMessages(messages: List<String>) {
sendBattleFunction.execute(id, messages)
}
}
@@ -6,20 +6,15 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.runner.graal
package com.cobblemon.mod.common.showdown.runner.graal
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.util.FileUtils
import com.cobblemon.mod.common.util.extractTo
import com.cobblemon.mod.common.util.fromJson
import com.google.gson.GsonBuilder
import net.minecraft.resources.ResourceLocation
import oshi.util.FileUtil
import java.io.File
import java.io.FileInputStream
import java.io.InputStreamReader
import java.nio.file.Files
import java.nio.file.StandardCopyOption
/**
* Unbundles a zipped Showdown file found within {@code resources/data/showdown.zip} for use within the GraalShowdownService.
@@ -0,0 +1,32 @@
package com.cobblemon.mod.common.showdown.runner.remote
import com.cobblemon.mod.common.showdown.runner.ShowdownBattle
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
class RemoteShowdownBattle(
override val id: String,
val gameType: String,
val remoteSide: String,
val teamObject: JsonObject
) : ShowdownBattle() {
override fun sendMessages(messages: List<String>) {
TODO("Not yet implemented")
}
class Builder(val id: String) {
val gson = GsonBuilder().create()
var gameType: String? = null
var localSide: String? = null
var teamStr: String? = null
fun gameType(gameType: String) = apply { this.gameType = gameType }
fun remoteSide(localSide: String) = apply { this.localSide = localSide }
fun teamStr(teamStr: String) = apply { this.teamStr = teamStr }
fun build(): RemoteShowdownBattle {
val teamObj = gson.fromJson(teamStr, JsonObject::class.java)
return RemoteShowdownBattle(id, gameType!!, localSide!!, teamObj)
}
}
}
@@ -0,0 +1,75 @@
package com.cobblemon.mod.common.showdown.runner.remote
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.showdown.runner.ShowdownBattle
import com.cobblemon.mod.common.showdown.runner.ShowdownBattleServer
import javax.naming.OperationNotSupportedException
class RemoteShowdownBattleServer(
val socketUrl: String,
val websiteUrl: String,
) : ShowdownBattleServer {
private val client = ShowdownGameClient(socketUrl)
private val scraper = ShowdownClientScraper(websiteUrl)
private val state = ShowdownGameServerConnectionState()
private val interpreter = ShowdownGameServerInterpreter(state)
override fun connect() {
client.connect()
}
override fun disconnect() {
client.close()
}
override fun initializeNewBattle(battleId: String, messages: Array<String>): ShowdownBattle {
throw OperationNotSupportedException()
}
override fun sendServerMessage(msg: String) {
client.sendMessage(msg)
}
override fun getAbilityIds(): Collection<String> {
return scraper.getAbilityIds().map { it.asString }.toSet()
}
override fun getMoves(): Collection<MoveTemplate> {
val json = scraper.getMoves()
return json.keySet().map {
MoveTemplate.decodeJsonMove(json.get(it).asJsonObject, it)
}
}
override fun getItemIds(): Collection<String> {
return scraper.getItemIds().map { it.toString() }.toSet()
}
override fun onServerStarted() {
val pollSocketTask = ScheduledTask.Builder().interval(1/4F)
.infiniteIterations()
.execute { client.pollMessage() }
.build()
ServerTaskTracker.addTask(pollSocketTask)
val interpretMessageTask = ScheduledTask.Builder().interval(1/4F)
.infiniteIterations()
.execute {
var message: String? = null
do {
message = client.messageQueue.poll()
if (message != null) interpreter.interpret(message)
} while (message != null)
}
.build()
ServerTaskTracker.addTask(interpretMessageTask)
}
fun getLoginState(): ShowdownGameServerLoginState {
return state.loginState
}
fun setLoginState(newState: ShowdownGameServerLoginState) {
state.loginState = newState
}
}
@@ -0,0 +1,81 @@
package com.cobblemon.mod.common.showdown.runner.remote
import com.cobblemon.mod.common.showdown.runner.graal.GraalLogger
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import org.graalvm.polyglot.Context
import org.graalvm.polyglot.HostAccess
import org.graalvm.polyglot.PolyglotAccess
import org.graalvm.polyglot.Source
import java.net.Socket
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
/**
* Allows us to load abilities and such using the pokemon showdown web client's data
*/
class ShowdownClientScraper(
private val baseUrl: String
) {
private val context: Context by lazy { createContext() }
private val httpClient = HttpClient.newHttpClient()
private val gson = GsonBuilder().create()
fun createSocket(): Socket {
val httpRequest = HttpRequest.newBuilder().uri(URI.create("$baseUrl/config/config.js")).GET().build()
val httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString())
val configurationJs = httpResponse.body()
context.eval(Source.create("js", configurationJs))
val serverHost = context.eval(Source.create("js","Config.defaultserver.host")).asString()
val serverPort = context.eval(Source.create("js","Config.defaultserver.port")).asInt()
return Socket(serverHost, serverPort)
}
fun getAbilityIds(): JsonArray {
val httpRequest = HttpRequest.newBuilder().uri(URI.create("$baseUrl/data/abilities.js")).GET().build()
val httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString())
val configurationJs = httpResponse.body()
context.eval(Source.create("js", configurationJs))
val abilityIds = context.eval("js", "JSON.stringify(Object.keys(exports.BattleAbilities))").asString()
return gson.fromJson(abilityIds, JsonArray::class.java)
}
fun getMoves(): JsonObject {
val httpRequest = HttpRequest.newBuilder().uri(URI.create("$baseUrl/data/moves.js")).GET().build()
val httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString())
val configurationJs = httpResponse.body()
context.eval(Source.create("js", configurationJs))
val moves = context.eval("js", "JSON.stringify(exports.BattleMovedex)").asString()
return gson.fromJson(moves, JsonObject::class.java)
}
fun getItemIds(): JsonArray {
val httpRequest = HttpRequest.newBuilder().uri(URI.create("$baseUrl/data/items.js")).GET().build()
val httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString())
val configurationJs = httpResponse.body()
context.eval(Source.create("js", configurationJs))
val itemIds = context.eval("js", "JSON.stringify(Object.keys(exports.BattleItems))").asString()
return gson.fromJson(itemIds, JsonArray::class.java)
}
private fun createContext(): Context {
val context = Context.newBuilder("js")
.allowIO(false)
.allowExperimentalOptions(true)
.allowPolyglotAccess(PolyglotAccess.ALL)
.allowHostAccess(HostAccess.NONE)
.allowCreateThread(false)
.logHandler(GraalLogger)
.allowHostClassLoading(false)
.allowNativeAccess(false)
.allowCreateProcess(false)
.build()
//This exports obj is set up as a global in the showdown client and referenced in some of the scripts we use later
context.eval("js", "exports = {}")
return context
}
}
@@ -0,0 +1,79 @@
package com.cobblemon.mod.common.showdown.runner.remote
import com.cobblemon.mod.common.Cobblemon
import java.net.URI
import java.net.http.HttpClient
import java.net.http.WebSocket
import java.nio.ByteBuffer
import java.util.ArrayDeque
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionStage
import java.util.concurrent.TimeUnit
/**
* Allows for connection to a Pokemon Showdown game server for battling against players using the game server
*
* @since March 1, 2025
* @author Apion
*/
class ShowdownGameClient(val socketUri: String) {
val messageQueue = ArrayDeque<String>()
var socket: WebSocket? = null
fun connect() {
val httpClient = HttpClient.newHttpClient()
val wsBuilder = httpClient.newWebSocketBuilder()
val future = wsBuilder.buildAsync(URI.create(socketUri), ClientListener(this))
socket = future.get(30, TimeUnit.SECONDS)
}
fun close() {
socket?.abort()
}
fun sendMessage(msg: String) {
socket?.sendText(msg, true)?.get()
}
fun pollMessage() {
socket?.request(1)
}
private class ClientListener(val client: ShowdownGameClient) : WebSocket.Listener {
override fun onOpen(webSocket: WebSocket?) {
Cobblemon.LOGGER.info("Showdown websocket connected")
}
override fun onClose(webSocket: WebSocket?, statusCode: Int, reason: String?): CompletionStage<*>? {
Cobblemon.LOGGER.info("Showdown websocket closed")
return null
}
override fun onText(webSocket: WebSocket?, data: CharSequence?, last: Boolean): CompletionStage<*>? {
client.messageQueue.add(data.toString())
if (!last) {
webSocket?.request(1L)
}
return null
}
override fun onBinary(webSocket: WebSocket?, data: ByteBuffer?, last: Boolean): CompletionStage<*>? {
Cobblemon.LOGGER.info("Received binary message from Showdown server")
return null
}
override fun onError(webSocket: WebSocket?, error: Throwable?) {
Cobblemon.LOGGER.error("Showdown websocket error", error)
}
override fun onPing(webSocket: WebSocket?, message: ByteBuffer?): CompletionStage<*> {
Cobblemon.LOGGER.info("Received showdown ping")
return CompletableFuture.completedFuture(null)
}
override fun onPong(webSocket: WebSocket?, message: ByteBuffer?): CompletionStage<*> {
Cobblemon.LOGGER.info("Received showdown pong")
return CompletableFuture.completedFuture(null)
}
}
}
@@ -0,0 +1,9 @@
package com.cobblemon.mod.common.showdown.runner.remote
/**
* State that is tracked throughout the lifecycle of a connection to a showdown server
*/
data class ShowdownGameServerConnectionState(
var loginState: ShowdownGameServerLoginState = ShowdownGameServerLoginState(),
val battlesBeingCreated: MutableMap<String, RemoteShowdownBattle.Builder> = mutableMapOf()
)
@@ -0,0 +1,136 @@
package com.cobblemon.mod.common.showdown.runner.remote
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.showdown.ShowdownSearch
import com.google.gson.GsonBuilder
/**
* A variation of the [ShowdownInterpreter] that interprets messages from the showdown game server
*/
class ShowdownGameServerInterpreter(
val serverConnectionState: ShowdownGameServerConnectionState
) {
val messageDispatchMap = mutableMapOf<String, (String) -> Unit>()
//Maps a command name to a function that takes a room name and command
val scopedMessageDispatchMap = mutableMapOf<String, (String, String) -> Unit>()
//For slash commands sent in PMs
val commandDispatchMap = mutableMapOf<String, (String, String) -> Unit>()
private val gson = GsonBuilder().create()
init {
messageDispatchMap.put("formats", {})
messageDispatchMap.put("challstr", ::handleChallStr)
messageDispatchMap.put("pm", ::handlePrivateMessage)
messageDispatchMap.put("updatesearch", ::handleUpdateSearch)
scopedMessageDispatchMap.put("request", ::handleRequest)
scopedMessageDispatchMap.put("player", ::handlePlayer)
scopedMessageDispatchMap.put("start", ::handleStart)
scopedMessageDispatchMap.put("gametype", ::handleGameType)
commandDispatchMap.put("challenge", ::handleChallengeCommand)
}
fun interpret(message: String) {
Cobblemon.LOGGER.info(message)
val lines = message.split('\n')
val isLocal = lines[0].startsWith(">")
val roomId = if(isLocal) lines[0].substring(1) else null
val startIndex = if(isLocal) 1 else 0
for (line in lines.slice(startIndex until lines.size)) {
val args = line.split("|")
if (args.size == 1) {
return
}
val messageType = args[1]
if (isLocal) {
val handler = scopedMessageDispatchMap.get(messageType)
if (handler == null) {
Cobblemon.LOGGER.warn("Showdown server interpreter received an unknown message type for room: $messageType")
}
handler?.invoke(line, roomId!!)
}
else {
val messageHandler = messageDispatchMap[messageType]
if (messageHandler == null) {
Cobblemon.LOGGER.warn("Showdown server interpreter received an unknown message type: $messageType")
}
messageHandler?.invoke(message)
}
}
}
fun handleChallStr(message: String) {
val args = message.split("|")
val key = args[2]
val str = args[3]
val newLoginState = ShowdownGameServerLoginState(str, Integer.valueOf(key))
serverConnectionState.loginState = newLoginState
}
fun handlePrivateMessage(message: String) {
val args = message.split("|")
val sender = args[2].trim()
val recipient = args[3].trim()
val privateMessage = args[4]
Cobblemon.LOGGER.info("Recieved PM from $sender: $privateMessage")
if (privateMessage.startsWith("/")) {
val commandParams = privateMessage.substring(1).split(" ")
val commandHandler = commandDispatchMap[commandParams[0]]
if (commandHandler == null) {
Cobblemon.LOGGER.warn("Showdown server interpreter received an unknown command type: ${privateMessage.substring(1)}")
}
commandHandler?.invoke(message, sender)
}
}
fun handleUpdateSearch(message: String) {
val args = message.split("|")
val searchObjJson = args[2]
val searchObj = gson.fromJson(searchObjJson, ShowdownSearch::class.java)
searchObj.searching.forEach {
Cobblemon.LOGGER.info("Searching for $it")
}
searchObj.games?.forEach { key, value ->
Cobblemon.LOGGER.info("Entered room $key")
}
}
fun handleRequest(message: String, roomId: String) {
val params = message.split("|")
if (params[2].isEmpty()) {
serverConnectionState.battlesBeingCreated[roomId] = RemoteShowdownBattle.Builder(roomId)
}
else {
serverConnectionState.battlesBeingCreated[roomId]?.teamStr = params[2]
}
}
fun handleStart(message: String, roomId: String) {
println("Hit")
}
fun handlePlayer(message: String, roomId: String) {
val params = message.split("|")
if (params[3].trim() == serverConnectionState.loginState.username) {
val battleBuilder = serverConnectionState.battlesBeingCreated[roomId]!!
battleBuilder.localSide = params[3]
}
}
fun handleGameType(message: String, roomId: String) {
val params = message.split("|")
val battleBuilder = serverConnectionState.battlesBeingCreated[roomId]!!
battleBuilder.gameType = params[2]
}
//Just auto accepts challenges
fun handleChallengeCommand(command: String, sender: String) {
val showdownServer = Cobblemon.showdownServer
showdownServer.sendServerMessage(("|/utm null}"))
showdownServer.sendServerMessage("|/accept ${sender.lowercase()}")
}
}

Some files were not shown because too many files have changed in this diff Show More