Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
508d11f3a4 | ||
|
|
8590999785 | ||
|
|
68569b790b | ||
|
|
b2302c8c80 | ||
|
|
0594525f59 | ||
|
|
54802450df | ||
|
|
c90bbac694 |
@@ -159,6 +159,9 @@ import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamRequestPack
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamResponsePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.RemoveSpectatorPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.SpectateNearestBattlePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.TeamPreviewSelectPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.TeamPreviewSelectPokemonPacket.Companion.decode
|
||||
import com.cobblemon.mod.common.net.messages.server.behaviour.DamageOnCollisionPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.behaviour.SetEntityBehaviourPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
|
||||
@@ -214,7 +217,9 @@ import com.cobblemon.mod.common.net.serverhandling.RequestInteractionsHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.BattleSelectActionsHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.RemoveSpectatorHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateBattleHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateNearestBattleHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.TeamLeaveHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.TeamPreviewSelectPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestResponseHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.behaviour.DamageOnCollisionPacketHandler
|
||||
@@ -402,6 +407,12 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(BattleChallengeExpiredPacket.ID, BattleChallengeExpiredPacket::decode, BattleChallengeExpiredHandler))
|
||||
list.add(PacketRegisterInfo(BattleReplacePokemonPacket.ID, BattleReplacePokemonPacket::decode, BattleReplacePokemonHandler))
|
||||
list.add(PacketRegisterInfo(BattleTransformPokemonPacket.ID, BattleTransformPokemonPacket::decode, BattleTransformPokemonHandler))
|
||||
list.add(PacketRegisterInfo(BattleActorInformationPacket.ID, BattleActorInformationPacket::decode, BattleActorInformationHandler))
|
||||
list.add(PacketRegisterInfo(BattleActorTeamPacket.ID, BattleActorTeamPacket::decode, BattleActorTeamHandler))
|
||||
list.add(PacketRegisterInfo(BattleInformationPacket.ID, BattleInformationPacket::decode, BattleInformationHandler))
|
||||
list.add(PacketRegisterInfo(BattleTimerPacket.ID, BattleTimerPacket::decode, BattleTimerHandler))
|
||||
list.add(PacketRegisterInfo(TeamPreviewPacket.ID, TeamPreviewPacket::decode, TeamPreviewHandler))
|
||||
list.add(PacketRegisterInfo(CloseTeamPreviewPacket.ID, CloseTeamPreviewPacket::decode, CloseTeamPreviewHandler))
|
||||
|
||||
// MultiBattleTeam Packets
|
||||
list.add(PacketRegisterInfo(TeamRequestNotificationPacket.ID, TeamRequestNotificationPacket::decode, TeamRequestNotificationHandler))
|
||||
@@ -570,6 +581,7 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(BattleSelectActionsPacket.ID, BattleSelectActionsPacket::decode, BattleSelectActionsHandler))
|
||||
list.add(PacketRegisterInfo(SpectateBattlePacket.ID, SpectateBattlePacket::decode, SpectateBattleHandler))
|
||||
list.add(PacketRegisterInfo(RemoveSpectatorPacket.ID, RemoveSpectatorPacket::decode, RemoveSpectatorHandler))
|
||||
list.add(PacketRegisterInfo(TeamPreviewSelectPokemonPacket.ID, TeamPreviewSelectPokemonPacket::decode, TeamPreviewSelectPokemonHandler))
|
||||
|
||||
// Trade
|
||||
list.add(PacketRegisterInfo(AcceptTradeRequestPacket.ID, AcceptTradeRequestPacket::decode, AcceptTradeRequestHandler))
|
||||
@@ -644,6 +656,9 @@ object CobblemonNetwork {
|
||||
// Debug / cheats
|
||||
list.add(PacketRegisterInfo(RequestOpenRidingStatsDebugGUIPacket.ID, RequestOpenRidingStatsDebugGUIPacket::decode, RequestOpenRidingStatsDebugGUIHandler))
|
||||
|
||||
list.add(PacketRegisterInfo(SpectateNearestBattlePacket.ID, SpectateNearestBattlePacket
|
||||
::decode, SpectateNearestBattleHandler))
|
||||
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.Cobblemon.LOGGER
|
||||
import com.cobblemon.mod.common.CobblemonBuildDetails
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
@@ -46,14 +48,22 @@ import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
|
||||
import com.cobblemon.mod.common.battles.interpreter.ContextManager
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.battles.timers.PlayerBattleTimer
|
||||
import com.cobblemon.mod.common.battles.timers.ShowdownTimer
|
||||
import com.cobblemon.mod.common.entity.PlatformType
|
||||
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorTeamPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleInitializePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleInformationDTO
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleInformationPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleQueueRequestPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleTimerPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.FieldEffect
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.DefeatEvolutionProgress
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
|
||||
import com.cobblemon.mod.common.pokemon.requirements.DefeatRequirement
|
||||
@@ -69,6 +79,7 @@ import net.minecraft.network.chat.Component
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Individual battle instance
|
||||
@@ -80,7 +91,8 @@ import net.minecraft.world.item.ItemStack
|
||||
open class PokemonBattle(
|
||||
val format: BattleFormat,
|
||||
val side1: BattleSide,
|
||||
val side2: BattleSide
|
||||
val side2: BattleSide,
|
||||
val playerTimer: (PokemonBattle, PlayerBattleActor) -> PlayerBattleTimer? = { battle, actor -> ShowdownTimer(battle, actor) }
|
||||
) {
|
||||
/** Whether logging will be silenced for this battle. */
|
||||
var mute = !CobblemonBuildDetails.SNAPSHOT
|
||||
@@ -95,6 +107,9 @@ open class PokemonBattle(
|
||||
side1.battle = this
|
||||
side2.battle = this
|
||||
this.actors.forEach { actor ->
|
||||
if (actor is PlayerBattleActor) {
|
||||
actor.timer = playerTimer(this, actor)
|
||||
}
|
||||
actor.battle = this
|
||||
actor.pokemonList.forEach { battlePokemon ->
|
||||
battlePokemon.effectedPokemon.evolutionProxy.current().progress()
|
||||
@@ -128,6 +143,8 @@ open class PokemonBattle(
|
||||
var announcingRules = false
|
||||
var turn: Int = 0
|
||||
private set
|
||||
var turnStartTime: Instant = Instant.now()
|
||||
private set
|
||||
|
||||
private var ticks: Int = 0
|
||||
set(value) { field = value.coerceAtMost(Int.MAX_VALUE) } // go outside
|
||||
@@ -137,13 +154,11 @@ open class PokemonBattle(
|
||||
|
||||
var dispatchResult = GO
|
||||
val dispatches = ConcurrentLinkedDeque<BattleDispatch>()
|
||||
val afterDispatches = mutableListOf<() -> Unit>()
|
||||
|
||||
val captureActions = mutableListOf<BattleCaptureAction>()
|
||||
|
||||
val majorBattleActions = hashMapOf<UUID, BattleMessage>()
|
||||
|
||||
/** Monotonically increasing counter to track faint ordering within this battle. */
|
||||
var faintCounter = 0
|
||||
val minorBattleActions = hashMapOf<UUID, BattleMessage>()
|
||||
val contextManager = ContextManager()
|
||||
|
||||
@@ -213,6 +228,23 @@ open class PokemonBattle(
|
||||
return actor to pokemon
|
||||
}
|
||||
|
||||
fun startSpectating(player: ServerPlayer) {
|
||||
if (!Cobblemon.config.allowSpectating) return
|
||||
spectators.add(player.uuid)
|
||||
player.sendPacket(BattleInitializePacket(this, null))
|
||||
player.sendPacket(BattleMessagePacket(chatLog))
|
||||
notifyOfUpdates(listOf(player.uuid))
|
||||
actors.forEach { actor ->
|
||||
val team = actor.pokemonList.map { it.toBattleDTO(false) }
|
||||
player.sendPacket(BattleActorTeamPacket(actor.uuid, team))
|
||||
}
|
||||
}
|
||||
|
||||
fun stopSpectating(player: ServerPlayer) {
|
||||
player.sendPacket(BattleEndPacket())
|
||||
spectators.remove(player.uuid)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a [BattlePokemon] from a pnx key and uuid.
|
||||
*
|
||||
@@ -238,6 +270,11 @@ open class PokemonBattle(
|
||||
|
||||
fun turn(newTurnNumber: Int) {
|
||||
actors.forEach { it.turn() }
|
||||
actors.filterIsInstance<PlayerBattleActor>().forEach { actor ->
|
||||
actor.timer?.mustChooseBy()?.let {
|
||||
actor.uuid.getPlayer()?.sendPacket(BattleTimerPacket(it))
|
||||
}
|
||||
}
|
||||
// TODO: If a pokemon switches in the same turn another pokemon is KO'd it will not receive exp for the KO
|
||||
for (side in sides) {
|
||||
val opposite = side.getOppositeSide()
|
||||
@@ -247,27 +284,49 @@ open class PokemonBattle(
|
||||
}
|
||||
}
|
||||
this.turn = newTurnNumber
|
||||
this.turnStartTime = Instant.now()
|
||||
notifyAllOfUpdates()
|
||||
}
|
||||
|
||||
fun notifyAllOfUpdates() {
|
||||
val uuids = this.actors.map { it.uuid } + this.spectators
|
||||
notifyOfUpdates(uuids)
|
||||
}
|
||||
|
||||
fun notifyOfUpdates(uuids: List<UUID>) {
|
||||
val weather = this.contextManager.get(BattleContext.Type.WEATHER)?.firstOrNull()
|
||||
val terrain = this.contextManager.get(BattleContext.Type.TERRAIN)?.firstOrNull()
|
||||
val room = this.contextManager.get(BattleContext.Type.ROOM)?.firstOrNull()
|
||||
val side1SidedEffects = getSidedFieldEffects(side1)
|
||||
val side2SidedEffects = getSidedFieldEffects(side2)
|
||||
val updatePacket = BattleInformationPacket(this.battleId, BattleInformationDTO(
|
||||
turn = turn,
|
||||
weather = weather?.let { FieldEffect(it.id, it.turn, it.origin?.entity?.id) },
|
||||
terrain = terrain?.let { FieldEffect(it.id, it.turn, it.origin?.entity?.id) },
|
||||
room = room?.let { FieldEffect(it.id, it.turn, it.origin?.entity?.id) },
|
||||
side1SidedEffects = side1SidedEffects,
|
||||
side2SidedEffects = side2SidedEffects,
|
||||
))
|
||||
uuids.mapNotNull { it.getPlayer() }.forEach {
|
||||
it.sendPacket(updatePacket)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSidedFieldEffects(side: BattleSide): List<FieldEffect> {
|
||||
val hazards = side.contextManager.get(BattleContext.Type.HAZARD)?.map { FieldEffect(it.id, it.turn, it.origin?.entity?.id) } ?: emptyList()
|
||||
val screens = side.contextManager.get(BattleContext.Type.SCREEN)?.map { FieldEffect(it.id, it.turn, it.origin?.entity?.id) } ?: emptyList()
|
||||
val tailwind = side.contextManager.get(BattleContext.Type.TAILWIND)?.firstOrNull()?.let { FieldEffect(it.id, it.turn, it.origin?.entity?.id) }
|
||||
return hazards + screens + listOfNotNull(tailwind)
|
||||
}
|
||||
|
||||
fun end() {
|
||||
ended = true
|
||||
val awardToFainted = Cobblemon.config.awardExperienceToFaintedPokemon
|
||||
val awardOnLoss = Cobblemon.config.awardExperienceOnBattleLoss
|
||||
this.actors.forEach { actor ->
|
||||
val faintedPokemons = actor.pokemonList.filter { it.health <= 0 }
|
||||
actor.getSide().getOppositeSide().actors.forEach { opponent ->
|
||||
val opponentPokemons = if (awardToFainted) opponent.pokemonList else opponent.pokemonList.filter { it.health > 0 }
|
||||
val opponentNonFaintedPokemons = opponent.pokemonList.filter { it.health > 0 }
|
||||
faintedPokemons.forEach { faintedPokemon ->
|
||||
for (opponentPokemon in opponentPokemons) {
|
||||
// Faint-order check: if awarding XP to fainted pokemon, only award for enemies that fainted before this pokemon did
|
||||
if (awardToFainted) {
|
||||
val enemyFaintedAt = faintedPokemon.faintedAt ?: continue
|
||||
val opponentFaintedAt = opponentPokemon.faintedAt
|
||||
if (opponentFaintedAt != null && opponentFaintedAt <= enemyFaintedAt) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for (opponentPokemon in opponentNonFaintedPokemons) {
|
||||
val facedFainted = opponentPokemon.facedOpponents.contains(faintedPokemon)
|
||||
val pokemon = opponentPokemon.effectedPokemon
|
||||
if (facedFainted) {
|
||||
@@ -288,8 +347,7 @@ open class PokemonBattle(
|
||||
else -> continue
|
||||
}
|
||||
val experience = Cobblemon.experienceCalculator.calculate(opponentPokemon, faintedPokemon, multiplier)
|
||||
val enemyTeamWiped = actor.pokemonList.all { it.health <= 0 }
|
||||
if (experience > 0 && (enemyTeamWiped || awardOnLoss)) {
|
||||
if (experience > 0 && actor.pokemonList.all { it.health <= 0 }) {
|
||||
opponent.awardExperience(opponentPokemon, experience)
|
||||
}
|
||||
Cobblemon.evYieldCalculator.calculate(opponentPokemon, faintedPokemon).forEach { (stat, amount) ->
|
||||
@@ -303,7 +361,7 @@ open class PokemonBattle(
|
||||
val player = actor.getPlayerUUIDs().first().getPlayer()
|
||||
player?.level()?.itemRegistry.let { registry ->
|
||||
actor.itemsUsed.mapNotNull { registry?.get(ResourceLocation.tryBySeparator(it.itemName.substringAfter('.'), '.')) }
|
||||
.forEach { player?.giveOrDropItemStack(ItemStack(it))}
|
||||
.forEach { player?.giveOrDropItemStack(ItemStack(it))}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,10 +382,16 @@ open class PokemonBattle(
|
||||
actors.forEach { actor ->
|
||||
actor.pokemonList.forEach { battlePokemon ->
|
||||
battlePokemon.postBattlePokemonOperations.forEach { it(battlePokemon) }
|
||||
battlePokemon.entity?.let { entity -> battlePokemon.postBattleEntityOperations.forEach { it(entity) } }
|
||||
|
||||
battlePokemon.clearBattleFeatures()
|
||||
battlePokemon.entity?.let { entity ->
|
||||
entity.terastallize(null)
|
||||
battlePokemon.postBattleEntityOperations.forEach { it(entity) }
|
||||
}
|
||||
|
||||
if (battlePokemon.effectedPokemon.entity != null
|
||||
&& battlePokemon.effectedPokemon.entity?.beamMode == 0
|
||||
&& battlePokemon.effectedPokemon.entity?.platform != PlatformType.NONE) {
|
||||
&& battlePokemon.effectedPokemon.entity?.beamMode == 0
|
||||
&& battlePokemon.effectedPokemon.entity?.platform != PlatformType.NONE) {
|
||||
battlePokemon.effectedPokemon.tryRecallWithAnimation()
|
||||
}
|
||||
}
|
||||
@@ -399,10 +463,12 @@ open class PokemonBattle(
|
||||
|
||||
fun dispatch(dispatcher: () -> DispatchResult) {
|
||||
dispatches.add(BattleDispatch { dispatcher() })
|
||||
|
||||
}
|
||||
|
||||
fun dispatchToFront(dispatcher: () -> DispatchResult) {
|
||||
dispatches.addFirst(BattleDispatch { dispatcher() })
|
||||
|
||||
}
|
||||
|
||||
fun dispatchWaitingToFront(delaySeconds: Float = 1F, dispatcher: () -> Unit) {
|
||||
@@ -453,12 +519,24 @@ open class PokemonBattle(
|
||||
dispatches.addFirst(dispatcher)
|
||||
}
|
||||
|
||||
fun doWhenClear(action: () -> Unit) {
|
||||
afterDispatches.add(action)
|
||||
}
|
||||
|
||||
fun tick() {
|
||||
if (turn >= 1) {
|
||||
this.actors.filterIsInstance<PlayerBattleActor>().forEach { it.timer?.tick() }
|
||||
}
|
||||
try {
|
||||
while (dispatchResult.canProceed()) {
|
||||
val dispatch = dispatches.poll() ?: break
|
||||
dispatchResult = dispatch(this)
|
||||
}
|
||||
|
||||
if (dispatches.isEmpty()) {
|
||||
afterDispatches.toList().forEach { it() }
|
||||
afterDispatches.clear()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LOGGER.error("Exception while ticking a battle. Saving battle log.", e)
|
||||
val message = battleLang("crash").red()
|
||||
@@ -514,7 +592,10 @@ open class PokemonBattle(
|
||||
fun checkForInputDispatch() {
|
||||
if (checkForfeit()) return // ignore actors that are still choosing, their choices don't matter anymore
|
||||
if (checkFleeAttempt()) return
|
||||
val readyToInput = (actors.any { !it.mustChoose && it.responses.isNotEmpty() } && actors.none { it.mustChoose })
|
||||
val readyToInput = (actors.any { !it.mustChoose && it.responses.isNotEmpty() } && actors.none {
|
||||
LOGGER.info("${it.getName().string} (Must Choose: ${it.mustChoose}): ${it.responses}")
|
||||
it.mustChoose
|
||||
})
|
||||
if (readyToInput && captureActions.isEmpty()) {
|
||||
actors.filter { it.responses.isNotEmpty() }.forEach { it.writeShowdownResponse() }
|
||||
actors.forEach { it.responses.clear() ; it.request = null }
|
||||
|
||||
@@ -8,10 +8,17 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.events
|
||||
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleChoiceMadeEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleChoiceRequestedEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleFaintedEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleFleeAttemptEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleHPEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattlePickMoveEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleStartedEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleSwitchEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleTerastallizeEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleUseMoveEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleVictoryEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.instruction.FormeChangeEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.instruction.MegaEvolutionEvent
|
||||
@@ -118,6 +125,21 @@ object CobblemonEvents {
|
||||
@JvmField
|
||||
val POKEMON_CAPTURED = EventObservable<PokemonCapturedEvent>()
|
||||
|
||||
@JvmField
|
||||
val BATTLE_USE_MOVE = EventObservable<BattleUseMoveEvent>()
|
||||
|
||||
@JvmField
|
||||
val BATTLE_PICK_MOVE = EventObservable<BattlePickMoveEvent>()
|
||||
|
||||
@JvmField
|
||||
val BATTLE_SWITCH = EventObservable<BattleSwitchEvent>()
|
||||
|
||||
@JvmField
|
||||
val BATTLE_TERA = EventObservable<BattleTerastallizeEvent>()
|
||||
|
||||
@JvmField
|
||||
val BATTLE_HP = EventObservable<BattleHPEvent>()
|
||||
|
||||
@JvmField
|
||||
val FOSSIL_REVIVED = EventObservable<FossilRevivedEvent>()
|
||||
|
||||
@@ -157,7 +179,7 @@ object CobblemonEvents {
|
||||
|
||||
@JvmField
|
||||
val POKEMON_SENT_POST = EventObservable<PokemonSentEvent.Post>()
|
||||
|
||||
|
||||
@JvmField
|
||||
val POKEMON_RECALL_PRE = CancelableObservable<PokemonRecallEvent.Pre>()
|
||||
|
||||
@@ -186,7 +208,7 @@ object CobblemonEvents {
|
||||
val POKEMON_HEALED = CancelableObservable<PokemonHealedEvent>()
|
||||
|
||||
@JvmField
|
||||
/** CLIENT ONLY! */
|
||||
/** CLIENT ONLY! */
|
||||
val POKEMON_INTERACTION_GUI_CREATION = EventObservable<PokemonInteractionGUICreationEvent>()
|
||||
|
||||
@JvmField
|
||||
@@ -382,6 +404,12 @@ object CobblemonEvents {
|
||||
@JvmField
|
||||
val SELECT_DRIVER = EventObservable<SelectDriverEvent>()
|
||||
|
||||
@JvmField
|
||||
val BATTLE_CHOICE_REQUESTED = EventObservable<BattleChoiceRequestedEvent>()
|
||||
|
||||
@JvmField
|
||||
val BATTLE_CHOICE_MADE = EventObservable<BattleChoiceMadeEvent>()
|
||||
|
||||
// Habitats
|
||||
@JvmField
|
||||
val HABITAT_SPAWN_ACTIVATED = CancelableObservable<HabitatSpawnActivatedEvent>()
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.events.battles
|
||||
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
data class BattleChoiceMadeEvent (
|
||||
val player: ServerPlayer
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.events.battles
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
data class BattleChoiceRequestedEvent (
|
||||
val player: ServerPlayer,
|
||||
val packet: BattleMakeChoicePacket
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.events.battles
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
data class BattleHPEvent(
|
||||
val battle: PokemonBattle,
|
||||
val pokemon: BattlePokemon,
|
||||
val newHealth: String
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.events.battles
|
||||
|
||||
import java.util.*
|
||||
|
||||
data class BattlePickMoveEvent(
|
||||
val player: UUID
|
||||
)
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.events.battles
|
||||
|
||||
import com.cobblemon.mod.common.api.events.Cancelable
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
|
||||
|
||||
data class BattleStartEvent (
|
||||
val playerActor: PlayerBattleActor,
|
||||
val wildActor: PokemonBattleActor
|
||||
): Cancelable()
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.events.battles
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
|
||||
data class BattleSwitchEvent(
|
||||
val battle: PokemonBattle,
|
||||
val pokemon: Pokemon
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.events.battles
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
data class BattleTerastallizeEvent(
|
||||
val battle: PokemonBattle,
|
||||
val playerEntity: ServerPlayer,
|
||||
val pokemon: BattlePokemon
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.events.battles
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
data class BattleUseMoveEvent(
|
||||
val battle: PokemonBattle,
|
||||
val players: List<ServerPlayer>,
|
||||
val userPokemon: BattlePokemon,
|
||||
val move: MoveTemplate
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.events.battles
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
/**
|
||||
* Event fired when a [BattlePokemon] faints. Exposes the [BattlePokemon] that fainted and the [BattleContext]
|
||||
* of how it fainted.
|
||||
*
|
||||
* @author Segfault Guy
|
||||
* @since April 6th, 2023
|
||||
*/
|
||||
data class GimmighoulEvent (
|
||||
val playerEntity: ServerPlayer
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.events.battles
|
||||
|
||||
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
data class NPCDefineBattleActorEvent(
|
||||
val player: ServerPlayer,
|
||||
val npcEntity: NPCEntity,
|
||||
var battleActor: NPCBattleActor
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.events.battles
|
||||
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import java.util.*
|
||||
|
||||
data class NPCEvent(
|
||||
val player: ServerPlayer,
|
||||
val entity: PokemonEntity,
|
||||
val leadingPokemon: UUID
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.events.battles
|
||||
|
||||
import com.cobblemon.mod.common.api.events.Cancelable
|
||||
import com.cobblemon.mod.common.pokeball.PokeBall
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
data class PokemonCapturePreEvent (
|
||||
val player: ServerPlayer,
|
||||
val pokemon: Pokemon,
|
||||
val pokeBall: PokeBall
|
||||
): Cancelable()
|
||||
@@ -12,6 +12,7 @@ import com.bedrockk.molang.runtime.struct.MoStruct
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategory
|
||||
import com.cobblemon.mod.common.api.types.ElementalType
|
||||
@@ -73,6 +74,7 @@ open class MoveTemplate(
|
||||
.addFunction("weight") { DoubleValue(weight.toDouble()) }
|
||||
}
|
||||
|
||||
|
||||
val displayName: MutableComponent
|
||||
get() = lang("move.$name")
|
||||
val description: MutableComponent
|
||||
|
||||
@@ -22,7 +22,10 @@ import net.minecraft.util.StringRepresentable
|
||||
enum class PokedexEntryProgress : StringRepresentable, Comparable<PokedexEntryProgress> {
|
||||
UNREGISTERED,
|
||||
SEEN,
|
||||
OWNED;
|
||||
OWNED,
|
||||
NONE,
|
||||
CAUGHT;
|
||||
|
||||
|
||||
override fun getSerializedName() = this.name
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.pokemon.feature
|
||||
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
|
||||
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
|
||||
import com.cobblemon.mod.common.client.gui.summary.featurerenderers.SummarySpeciesFeatureRenderer
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.google.gson.JsonObject
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
class BattleFormFeature(formName: String, enabled: Boolean): FlagSpeciesFeature(formName, enabled) {
|
||||
|
||||
override fun saveToJSON(pokemonJSON: JsonObject): JsonObject {
|
||||
return pokemonJSON
|
||||
}
|
||||
|
||||
override fun saveToNBT(pokemonNBT: CompoundTag): CompoundTag {
|
||||
return pokemonNBT
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class BattleFlagSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<BattleFormFeature>,
|
||||
CustomPokemonPropertyType<BattleFormFeature>, AspectProvider {
|
||||
override var keys: List<String>
|
||||
override val needsKey get() = true
|
||||
var default: String? = null
|
||||
var isAspect = true
|
||||
override var visible: Boolean = false
|
||||
override fun invoke(buffer: RegistryFriendlyByteBuf, name: String): BattleFormFeature? {
|
||||
return if (name in keys) {
|
||||
BattleFormFeature(name, false).also { it.loadFromBuffer(buffer) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun examples() = setOf("true", "false")
|
||||
|
||||
internal constructor() {
|
||||
this.keys = emptyList()
|
||||
}
|
||||
|
||||
constructor(keys: List<String>) {
|
||||
this.keys = keys
|
||||
}
|
||||
|
||||
constructor(keys: List<String>, default: Boolean) {
|
||||
this.keys = keys
|
||||
this.default = default.toString()
|
||||
}
|
||||
|
||||
constructor(vararg keys: String) : this(keys.toList())
|
||||
|
||||
override fun invoke(pokemon: Pokemon): BattleFormFeature? {
|
||||
return pokemon.getFeature(keys.first())
|
||||
?: when (default) {
|
||||
in setOf("true", "false") -> BattleFormFeature(keys.first(), default.toBoolean())
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun saveToBuffer(buffer: RegistryFriendlyByteBuf, toClient: Boolean) {
|
||||
buffer.writeCollection(keys) { _, value -> buffer.writeUtf(value) }
|
||||
buffer.writeNullable(default) { _, value -> buffer.writeUtf(value) }
|
||||
buffer.writeBoolean(isAspect)
|
||||
}
|
||||
|
||||
override fun loadFromBuffer(buffer: RegistryFriendlyByteBuf) {
|
||||
keys = buffer.readList { it.readString() }
|
||||
default = buffer.readNullable { it.readString() }
|
||||
isAspect = buffer.readBoolean()
|
||||
}
|
||||
|
||||
override fun invoke(nbt: CompoundTag): BattleFormFeature? {
|
||||
return if (nbt.contains(keys.first())) {
|
||||
BattleFormFeature(keys.first(), false).also { it.loadFromNBT(nbt) }
|
||||
} else null
|
||||
}
|
||||
|
||||
override fun invoke(json: JsonObject): BattleFormFeature? {
|
||||
return if (json.has(keys.first())) {
|
||||
BattleFormFeature(keys.first(), false).also { it.loadFromJSON(json) }
|
||||
} else null
|
||||
}
|
||||
|
||||
override fun get(pokemon: Pokemon) = pokemon.getFeature<BattleFormFeature>(keys.first())
|
||||
|
||||
override fun getRenderer(pokemon: Pokemon): SummarySpeciesFeatureRenderer<BattleFormFeature>? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun fromString(value: String?): BattleFormFeature? {
|
||||
val isWeirdValue = value != null && value !in examples()
|
||||
|
||||
if (isWeirdValue) {
|
||||
return null
|
||||
}
|
||||
|
||||
return if (value == null) {
|
||||
BattleFormFeature(keys.first(), true)
|
||||
} else {
|
||||
BattleFormFeature(keys.first(), value.toBoolean())
|
||||
}
|
||||
}
|
||||
|
||||
override fun provide(pokemon: Pokemon): Set<String> {
|
||||
return if (isAspect && pokemon.getFeature<FlagSpeciesFeature>(keys.first())?.enabled == true) {
|
||||
setOf(keys.first())
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
}
|
||||
|
||||
override fun provide(properties: PokemonProperties): Set<String> {
|
||||
return if (isAspect && properties.customProperties.filterIsInstance<FlagSpeciesFeature>().find { it.name == keys.first() }?.enabled == true) {
|
||||
setOf(keys.first())
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,16 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.pokemon.feature
|
||||
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
|
||||
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
|
||||
import com.cobblemon.mod.common.client.gui.summary.featurerenderers.SummarySpeciesFeatureRenderer
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.cobblemon.mod.common.util.substitute
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import com.google.gson.JsonObject
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
/**
|
||||
@@ -21,11 +27,16 @@ import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
* @author Hiroku
|
||||
* @since November 30th, 2022
|
||||
*/
|
||||
class ChoiceSpeciesFeatureProvider : AbstractChoiceSpeciesFeatureProvider<List<String>>(emptyList(), emptyList()) {
|
||||
open class ChoiceSpeciesFeatureProvider(
|
||||
override var keys: List<String>,
|
||||
var default: String? = null,
|
||||
var choices: List<String> = listOf(),
|
||||
var isAspect: Boolean = true,
|
||||
var aspectFormat: String = "{{choice}}"
|
||||
) : SynchronizedSpeciesFeatureProvider<StringSpeciesFeature>, CustomPokemonPropertyType<StringSpeciesFeature>, AspectProvider {
|
||||
override var needsKey = true
|
||||
override var visible = false
|
||||
|
||||
override fun examples() = choices
|
||||
fun getAspect(feature: StringSpeciesFeature) = aspectFormat.substitute("choice", feature.value)
|
||||
|
||||
override fun saveToBuffer(buffer: RegistryFriendlyByteBuf, toClient: Boolean) {
|
||||
buffer.writeCollection(keys) { _, value -> buffer.writeString(value) }
|
||||
@@ -45,13 +56,32 @@ class ChoiceSpeciesFeatureProvider : AbstractChoiceSpeciesFeatureProvider<List<S
|
||||
needsKey = buffer.readBoolean()
|
||||
}
|
||||
|
||||
override fun fromString(value: String?): StringSpeciesFeature? {
|
||||
val lower = value?.lowercase()
|
||||
if (lower == null || lower !in choices) {
|
||||
return null
|
||||
}
|
||||
override fun getRenderer(pokemon: Pokemon): SummarySpeciesFeatureRenderer<StringSpeciesFeature>? {
|
||||
return null
|
||||
}
|
||||
|
||||
return StringSpeciesFeature(keys.first(), lower)
|
||||
override fun invoke(buffer: RegistryFriendlyByteBuf, name: String): StringSpeciesFeature? {
|
||||
return if (name in keys) {
|
||||
StringSpeciesFeature(name, "").also { it.loadFromBuffer(buffer) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllAspects(): MutableList<String> {
|
||||
val aspects = choices.toMutableList()
|
||||
choices.forEach {
|
||||
aspects[choices.indexOf(it)] = (aspectFormat.substitute("choice", it))
|
||||
}
|
||||
return aspects
|
||||
}
|
||||
|
||||
override fun examples() = choices
|
||||
|
||||
internal constructor(): this(emptyList())
|
||||
|
||||
override fun get(pokemon: Pokemon): StringSpeciesFeature? {
|
||||
return pokemon.features.filterIsInstance<StringSpeciesFeature>().find { it.name in keys }
|
||||
}
|
||||
|
||||
override fun invoke(pokemon: Pokemon): StringSpeciesFeature? {
|
||||
@@ -63,8 +93,7 @@ class ChoiceSpeciesFeatureProvider : AbstractChoiceSpeciesFeatureProvider<List<S
|
||||
default!!
|
||||
} else if (default == "random") {
|
||||
// If it's mandatory, but they provided no value and no default, give it a random value.
|
||||
choices.randomOrNull()
|
||||
?: throw IllegalStateException("The 'choices' list is empty for species feature provider: ${keys.joinToString()}")
|
||||
choices.randomOrNull() ?: throw IllegalStateException("The 'choices' list is empty for species feature provider: ${keys.joinToString()}")
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
@@ -73,11 +102,45 @@ class ChoiceSpeciesFeatureProvider : AbstractChoiceSpeciesFeatureProvider<List<S
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAllAspects(): MutableList<String> {
|
||||
val aspects = choices.toMutableList()
|
||||
choices.forEach {
|
||||
aspects[choices.indexOf(it)] = (aspectFormat.substitute("choice", it))
|
||||
}
|
||||
return aspects
|
||||
override fun invoke(nbt: CompoundTag): StringSpeciesFeature? {
|
||||
val key = keys.find { nbt.contains(it) }
|
||||
if (key == null) return null
|
||||
return StringSpeciesFeature(key, "").also { it.loadFromNBT(nbt) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun invoke(json: JsonObject): StringSpeciesFeature? {
|
||||
val key = keys.find { json.has(it) }
|
||||
if (key == null) return null
|
||||
return StringSpeciesFeature(key, "").also { it.loadFromJSON(json) }
|
||||
}
|
||||
|
||||
override fun fromString(value: String?): StringSpeciesFeature? {
|
||||
val lower = value?.lowercase()
|
||||
if (lower == null || lower !in choices) {
|
||||
return null
|
||||
}
|
||||
|
||||
return StringSpeciesFeature(keys.first(), lower)
|
||||
}
|
||||
|
||||
override fun provide(pokemon: Pokemon): Set<String> {
|
||||
return if (isAspect) {
|
||||
get(pokemon)?.let { setOf(getAspect(it)) } ?: emptySet()
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
}
|
||||
|
||||
override fun provide(properties: PokemonProperties): Set<String> {
|
||||
return if (isAspect) {
|
||||
val feature = properties.customProperties.filterIsInstance<StringSpeciesFeature>().find { it.name in keys }
|
||||
if (feature != null) {
|
||||
setOf(getAspect(feature))
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,15 +70,28 @@ class ActiveBattlePokemon(val actor: BattleActor, var battlePokemon: BattlePokem
|
||||
}
|
||||
|
||||
val minDistance = 4.0 + widthSum
|
||||
val actorDistance = actorOffset.length()
|
||||
var actorDistance = actorOffset.length()
|
||||
|
||||
|
||||
if (actorDistance < minDistance) {
|
||||
val temp = actorOffset.scale(minDistance / actorDistance) ?: actorOffset
|
||||
result = actorEntityPos?.subtract(temp.subtract(actorOffset))
|
||||
actorOffset = temp
|
||||
if (actorDistance > 0.0) {
|
||||
val temp = actorOffset.scale(minDistance / actorDistance)
|
||||
result = actorEntityPos?.subtract(temp.subtract(actorOffset))
|
||||
actorOffset = temp
|
||||
} else {
|
||||
actorOffset = Vec3(minDistance, 0.0, 0.0)
|
||||
result = actorEntityPos?.add(actorOffset)
|
||||
}
|
||||
}
|
||||
|
||||
// orthogonalVector is the sideways displacement from the battle actors
|
||||
var orthogonalVector = Vec3(actorOffset.x, 0.0, actorOffset.z).normalize()
|
||||
var orthogonalVector = Vec3(actorOffset.x, 0.0, actorOffset.z)
|
||||
orthogonalVector =
|
||||
if (orthogonalVector.lengthSqr() > 0.0)
|
||||
orthogonalVector.normalize()
|
||||
else
|
||||
Vec3(0.0, 0.0, 1.0)
|
||||
|
||||
orthogonalVector = orthogonalVector.cross(Vec3(0.0, 1.0, 0.0))
|
||||
|
||||
if (battle.format.battleType.pokemonPerSide == 1) { // Singles
|
||||
@@ -121,13 +134,13 @@ class ActiveBattlePokemon(val actor: BattleActor, var battlePokemon: BattlePokem
|
||||
// If the collision is too close to the actor, try to find a more reasonable position.
|
||||
// The motivation here is to give setPositionSafely something more reasonable to work with.
|
||||
val collisionResult = entity.level().clip(
|
||||
ClipContext(
|
||||
actorEntityPos!!.add(Vec3(0.0,entity.eyeHeight.toDouble(),0.0)), // start position
|
||||
Vec3(result.x, entity.y + entity.eyeHeight.toDouble(), result.z), // end position
|
||||
ClipContext.Block.OUTLINE,
|
||||
ClipContext.Fluid.NONE,
|
||||
entity
|
||||
)
|
||||
ClipContext(
|
||||
actorEntityPos!!.add(Vec3(0.0,entity.eyeHeight.toDouble(),0.0)), // start position
|
||||
Vec3(result.x, entity.y + entity.eyeHeight.toDouble(), result.z), // end position
|
||||
ClipContext.Block.OUTLINE,
|
||||
ClipContext.Fluid.NONE,
|
||||
entity
|
||||
)
|
||||
)
|
||||
if (collisionResult.type == HitResult.Type.BLOCK) {
|
||||
// Collided with terrain
|
||||
|
||||
@@ -28,6 +28,8 @@ data class BattleFormat(
|
||||
var ruleSet: Set<String> = setOf(),
|
||||
val gen: Int = 9,
|
||||
var adjustLevel: Int = -1, // Stop gap rule before a more general system for rules enforced by Cobblemon is implemented.
|
||||
val isOpenTeamSheet: Boolean = false,
|
||||
val allowedGimmicks: Set<ShowdownMoveset.Gimmick> = ShowdownMoveset.Gimmick.entries.toSet()
|
||||
) {
|
||||
companion object {
|
||||
fun setBattleRules(
|
||||
|
||||
@@ -15,8 +15,11 @@ import com.cobblemon.mod.common.api.moves.HiddenPowerUtil
|
||||
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.actor.PlayerBattleActor
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.battles.timers.PlayerBattleTimer
|
||||
import com.cobblemon.mod.common.battles.timers.ShowdownTimer
|
||||
import com.google.gson.GsonBuilder
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import java.util.*
|
||||
@@ -39,6 +42,10 @@ object BattleRegistry {
|
||||
getBattleByParticipatingPlayer(player)?.stop()
|
||||
}
|
||||
|
||||
fun getPlayersCurrentlyBattling(): List<ServerPlayer> {
|
||||
return battleMap.values.flatMap { it.players }
|
||||
}
|
||||
|
||||
/**
|
||||
* Packs a team into the showdown format
|
||||
*
|
||||
@@ -198,9 +205,10 @@ object BattleRegistry {
|
||||
battleFormat: BattleFormat,
|
||||
side1: BattleSide,
|
||||
side2: BattleSide,
|
||||
canPreempt: Boolean = true
|
||||
canPreempt: Boolean = true,
|
||||
playerTimer: (PokemonBattle, PlayerBattleActor) -> PlayerBattleTimer? = { _, _ -> null }
|
||||
): BattleStartResult {
|
||||
val battle = PokemonBattle(battleFormat, side1, side2)
|
||||
val battle = PokemonBattle(battleFormat, side1, side2, playerTimer)
|
||||
val start: () -> Unit = {
|
||||
battleMap[battle.battleId] = battle
|
||||
startShowdown(battle)
|
||||
@@ -217,6 +225,19 @@ object BattleRegistry {
|
||||
return ErroredBattleStart(mutableSetOf(BattleStartError.canceledByEvent(preBattleEvent.reason)))
|
||||
}
|
||||
|
||||
// Wrapper for compatibility with radgyms in java
|
||||
@JvmStatic
|
||||
fun startBattle(
|
||||
battleFormat: BattleFormat,
|
||||
side1: BattleSide,
|
||||
side2: BattleSide,
|
||||
canPreempt: Boolean
|
||||
): BattleStartResult {
|
||||
// call the known kotlin method with defaults parameter
|
||||
return startBattle(battleFormat, side1, side2, canPreempt) { _, _ -> null }
|
||||
}
|
||||
|
||||
|
||||
@JvmStatic
|
||||
fun closeBattle(battle: PokemonBattle) {
|
||||
battle.onEndHandlers.forEach { it(battle) }
|
||||
@@ -239,7 +260,11 @@ object BattleRegistry {
|
||||
return battleMap.values.find { playerId in it.playerUUIDs }
|
||||
}
|
||||
|
||||
fun getBattleBySpectatorId(playerId: UUID): PokemonBattle? {
|
||||
return battleMap.values.find { playerId in it.spectators }
|
||||
}
|
||||
|
||||
fun tick() {
|
||||
battleMap.forEachValue(Long.MAX_VALUE) { it.tick() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,10 @@ class InBattleMove {
|
||||
buffer.writeEnumConstant(target)
|
||||
buffer.writeBoolean(disabled)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "InBattleMove(id='$id', move='$move', pp=$pp, maxpp=$maxpp, target=$target, disabled=$disabled, gimmickMove=$gimmickMove)"
|
||||
}
|
||||
}
|
||||
|
||||
// Defined in sim/battle-actions.ts canZMove and getMaxMove
|
||||
@@ -68,4 +72,8 @@ class InBattleGimmickMove {
|
||||
buffer.writeEnumConstant(target)
|
||||
buffer.writeBoolean(disabled)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "InBattleGimmickMove(move='$move', target=$target, disabled=$disabled)"
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
package com.cobblemon.mod.common.battles
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
@@ -80,10 +81,18 @@ class ShowdownActionRequest(
|
||||
ShowdownMoveset.Gimmick.TERASTALLIZATION -> cobblemonResource("tera_orb")
|
||||
else -> cobblemonResource("z_ring")
|
||||
}
|
||||
if (!player.hasKeyItem(triggerItem)) moveset.blockGimmick(gimmick)
|
||||
if (!battle.format.allowedGimmicks.contains(gimmick) || !player.hasKeyItem(triggerItem)) moveset.blockGimmick(gimmick)
|
||||
}
|
||||
|
||||
if (moveset.canMegaEvo && moveset.moves.any { it.id == "dragonascent" } && !Cobblemon.permissionValidator.hasPermission(player, "quests.mega_rayquaza.completed", 4)) {
|
||||
moveset.canMegaEvo = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ShowdownActionRequest(wait=$wait, active=$active, forceSwitch=$forceSwitch, noCancel=$noCancel, side=$side)"
|
||||
}
|
||||
}
|
||||
|
||||
enum class ShowdownActionResponseType(val loader: (RegistryFriendlyByteBuf) -> ShowdownActionResponse) {
|
||||
@@ -95,7 +104,8 @@ enum class ShowdownActionResponseType(val loader: (RegistryFriendlyByteBuf) -> S
|
||||
SHIFT({ ShiftActionResponse()}),
|
||||
HEAL_ITEM({ HealItemActionResponse("potion") }),
|
||||
FORFEIT({ ForfeitActionResponse() }),
|
||||
FLEE_ATTEMPT({ FleeAttemptActionResponse() }),
|
||||
|
||||
FLEE_ATTEMPT({ FleeAttemptActionResponse() });
|
||||
}
|
||||
|
||||
abstract class ShowdownActionResponse(val type: ShowdownActionResponseType) {
|
||||
@@ -117,6 +127,9 @@ abstract class ShowdownActionResponse(val type: ShowdownActionResponseType) {
|
||||
|
||||
data class MoveActionResponse(var moveName: String, var targetPnx: String? = null, var gimmickID: String? = null): ShowdownActionResponse(ShowdownActionResponseType.MOVE) {
|
||||
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean): Boolean {
|
||||
if (moveName == "struggle") {
|
||||
return true
|
||||
}
|
||||
if (forceSwitch || showdownMoveSet == null) {
|
||||
return false
|
||||
}
|
||||
@@ -168,6 +181,10 @@ data class MoveActionResponse(var moveName: String, var targetPnx: String? = nul
|
||||
gimmickID = buffer.readNullable { buffer.readString() }
|
||||
return this
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "MoveActionResponse(moveName='$moveName', targetPnx=$targetPnx, gimmickID=$gimmickID)"
|
||||
}
|
||||
}
|
||||
|
||||
data class HealItemActionResponse(var item: String) : ShowdownActionResponse(ShowdownActionResponseType.FORCE_PASS) {
|
||||
@@ -189,6 +206,10 @@ data class HealItemActionResponse(var item: String) : ShowdownActionResponse(Sho
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?): String {
|
||||
return "healitem ${activeBattlePokemon.getPNX()} $item"
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "HealItemActionResponse(item='$item')"
|
||||
}
|
||||
}
|
||||
|
||||
class ShiftActionResponse() : ShowdownActionResponse(ShowdownActionResponseType.SHIFT) {
|
||||
@@ -200,6 +221,10 @@ class ShiftActionResponse() : ShowdownActionResponse(ShowdownActionResponseType.
|
||||
return "shift"
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ShiftActionResponse()"
|
||||
}
|
||||
|
||||
}
|
||||
data class SwitchActionResponse(var newPokemonId: UUID) : ShowdownActionResponse(ShowdownActionResponseType.SWITCH) {
|
||||
override fun saveToBuffer(buffer: RegistryFriendlyByteBuf) {
|
||||
@@ -227,16 +252,28 @@ 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}"
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "SwitchActionResponse(newPokemonId=$newPokemonId)"
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultActionResponse: ShowdownActionResponse(ShowdownActionResponseType.DEFAULT) {
|
||||
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean) = true
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?) = "default"
|
||||
|
||||
override fun toString(): String {
|
||||
return "DefaultActionResponse()"
|
||||
}
|
||||
}
|
||||
|
||||
object PassActionResponse : ShowdownActionResponse(ShowdownActionResponseType.PASS) {
|
||||
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean) = true
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?) = "pass"
|
||||
|
||||
override fun toString(): String {
|
||||
return "PassActionResponse()"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,6 +291,10 @@ class ForcePassActionResponse : ShowdownActionResponse(ShowdownActionResponseTyp
|
||||
}
|
||||
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?) = "pass"
|
||||
|
||||
override fun toString(): String {
|
||||
return "ForcePassActionResponse()"
|
||||
}
|
||||
}
|
||||
|
||||
class BagItemActionResponse(val bagItem: BagItem, val target: BattlePokemon, val data: String? = null): ShowdownActionResponse(ShowdownActionResponseType.FORCE_PASS) {
|
||||
@@ -274,11 +315,19 @@ class BagItemActionResponse(val bagItem: BagItem, val target: BattlePokemon, val
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?): String {
|
||||
return "useitem ${target.uuid} ${bagItem.itemName} ${bagItem.getShowdownInput(target.actor, target, data)}"
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "BagItemActionResponse(bagItem=$bagItem, target=$target, data=$data)"
|
||||
}
|
||||
}
|
||||
|
||||
class ForfeitActionResponse : ShowdownActionResponse(ShowdownActionResponseType.FORFEIT) {
|
||||
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean) = true
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?) = "forfeit"
|
||||
|
||||
override fun toString(): String {
|
||||
return "ForfeitActionResponse()"
|
||||
}
|
||||
}
|
||||
|
||||
class FleeAttemptActionResponse : ShowdownActionResponse(ShowdownActionResponseType.FLEE_ATTEMPT) {
|
||||
@@ -286,6 +335,7 @@ class FleeAttemptActionResponse : ShowdownActionResponse(ShowdownActionResponseT
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?) = "flee-attempt"
|
||||
}
|
||||
|
||||
|
||||
class ShowdownMoveset {
|
||||
lateinit var moves: List<InBattleMove>
|
||||
var trapped = false
|
||||
@@ -379,6 +429,10 @@ class ShowdownMoveset {
|
||||
else -> this.canTerastallize = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ShowdownMoveset(moves=$moves, trapped=$trapped, canMegaEvo=$canMegaEvo, canUltraBurst=$canUltraBurst, canZMove=$canZMove, canDynamax=$canDynamax, maxMoves=$maxMoves, canTerastallize=$canTerastallize)"
|
||||
}
|
||||
}
|
||||
class ShowdownSide {
|
||||
lateinit var name: UUID
|
||||
@@ -400,6 +454,10 @@ class ShowdownSide {
|
||||
this.pokemon = pokemon
|
||||
return this
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ShowdownSide(name=$name, id='$id', pokemon=$pokemon)"
|
||||
}
|
||||
}
|
||||
class ShowdownPokemon {
|
||||
lateinit var ident: String
|
||||
@@ -456,6 +514,10 @@ class ShowdownPokemon {
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ShowdownPokemon(ident='$ident', details='$details', condition='$condition', active=$active, moves=$moves, baseAbility='$baseAbility', pokeball='$pokeball', ability='$ability', baseTypes=$baseTypes, types=$types, commanding=$commanding, reviving=$reviving)"
|
||||
}
|
||||
}
|
||||
/** Unwraps useless maxMoves object and initializes [ShowdownMoveset.gimmickMapping] */
|
||||
object ShowdownMovesetAdapter : JsonDeserializer<ShowdownMoveset> {
|
||||
|
||||
@@ -9,14 +9,10 @@
|
||||
package com.cobblemon.mod.common.battles
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon.LOGGER
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BasicContext
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.Effect
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.InvalidInstructionException
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.MissingContext
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.*
|
||||
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.api.text.red
|
||||
import com.cobblemon.mod.common.api.text.yellow
|
||||
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
|
||||
@@ -26,7 +22,21 @@ import com.cobblemon.mod.common.battles.interpreter.instructions.*
|
||||
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.level.ClipContext
|
||||
import net.minecraft.world.phys.HitResult
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.UUID
|
||||
import kotlin.collections.Iterator
|
||||
import kotlin.collections.filter
|
||||
import kotlin.collections.forEach
|
||||
import kotlin.collections.listOf
|
||||
import kotlin.collections.map
|
||||
import kotlin.collections.mutableListOf
|
||||
import kotlin.collections.mutableMapOf
|
||||
import kotlin.collections.set
|
||||
import kotlin.collections.toMutableList
|
||||
import kotlin.collections.toTypedArray
|
||||
import kotlin.time.measureTime
|
||||
|
||||
@Suppress("KotlinPlaceholderCountMatchesArgumentCount", "UNUSED_PARAMETER")
|
||||
object ShowdownInterpreter {
|
||||
@@ -56,15 +66,14 @@ object ShowdownInterpreter {
|
||||
updateInstructionParser["-boost"] = { battle, _, message, _ -> BoostInstruction(battle, message, true) }
|
||||
updateInstructionParser["-block"] = { _, _, message, _ -> BlockInstruction(message) }
|
||||
updateInstructionParser["cant"] = { _, _, message, _ -> CantInstruction(message) }
|
||||
updateInstructionParser["-center"] = { _, _, message, _ -> CenterInstruction(message) }
|
||||
updateInstructionParser["-clearallboost"] = { _, _, message, _ -> ClearAllBoostInstruction(message) }
|
||||
updateInstructionParser["-clearnegativeboost"] = { _, _, message, _ -> ClearNegativeBoostInstruction(message) }
|
||||
updateInstructionParser["-clearboost"] = { _, _, message, _ -> ClearBoostInstruction(message) }
|
||||
updateInstructionParser["-combine"] = { _, _, message, _ -> CombineInstruction(message) }
|
||||
updateInstructionParser["-copyboost"] = { _, _, message, _ -> CopyBoostInstruction(message) }
|
||||
updateInstructionParser["-crit"] = { _, instructionSet, message, _ -> CritInstruction(message, instructionSet) }
|
||||
updateInstructionParser["-curestatus"] = { _, _, message, _ -> CureStatusInstruction(message) }
|
||||
updateInstructionParser["detailschange"] = { _, _, message, _ -> FormeChangeInstruction(message) }
|
||||
updateInstructionParser["detailschange"] = { _, _, message, _ -> DetailsChangeInstruction(message) }
|
||||
updateInstructionParser["-formechange"] = { _, _, message, _ -> FormeChangeInstruction(message)}
|
||||
updateInstructionParser["-endability"] = { _, _, message, _ -> EndAbilityInstruction(message) }
|
||||
updateInstructionParser["-end"] = { _, _, message, _ -> EndInstruction(message) }
|
||||
updateInstructionParser["-enditem"] = { _, _, message, _ -> EndItemInstruction(message) }
|
||||
@@ -96,7 +105,6 @@ object ShowdownInterpreter {
|
||||
updateInstructionParser["-start"] = { _, _, message, _ -> StartInstruction(message) }
|
||||
updateInstructionParser["-status"] = { _, _, message, _ -> StatusInstruction(message) }
|
||||
updateInstructionParser["-supereffective"] = { _, instructionSet, message, _ -> SuperEffectiveInstruction(message, instructionSet) }
|
||||
updateInstructionParser["swap"] = { _, instructionSet, message, _ -> SwapInstruction(message, instructionSet) }
|
||||
updateInstructionParser["-swapboost"] = { _, _, message, _ -> SwapBoostInstruction(message) }
|
||||
updateInstructionParser["-swapsideconditions"] = { _, _, message, _ -> SwapSideConditionsInstruction(message) }
|
||||
updateInstructionParser["-terastallize"] = { _, _, message, _ -> TerastallizeInstruction(message) }
|
||||
@@ -104,35 +112,37 @@ object ShowdownInterpreter {
|
||||
updateInstructionParser["turn"] = { _, _, message, _ -> TurnInstruction(message) }
|
||||
updateInstructionParser["-unboost"] = { battle, _, message, _ -> BoostInstruction(battle, message, false) }
|
||||
updateInstructionParser["upkeep"] = { _, _, _, _ -> UpkeepInstruction() }
|
||||
updateInstructionParser["-waiting"] = { _, _, message, _ -> WaitingInstruction(message) }
|
||||
updateInstructionParser["-weather"] = { _, _, message, _ -> WeatherInstruction(message) }
|
||||
updateInstructionParser["win"] = { _, _, message, _ -> WinInstruction(message) }
|
||||
updateInstructionParser["-zbroken"] = { _, _, message, _ -> ZBrokenInstruction(message) }
|
||||
updateInstructionParser["-zpower"] = { _, _, message, _ -> ZPowerInstruction(message) }
|
||||
updateInstructionParser["swap"] = { _, instructionSet, message, _ -> SwapInstruction(message, instructionSet) }
|
||||
updateInstructionParser["-center"] = { _, _, message, _ -> CenterInstruction(message) }
|
||||
|
||||
sideInstructionParser["error"] = { _, targetActor, _, message -> ErrorInstruction(targetActor, message) }
|
||||
sideInstructionParser["request"] = { _, targetActor, _, message -> RequestInstruction(targetActor, message) }
|
||||
|
||||
splitInstructionParser["-damage"] = { _, targetActor, instructionSet, publicMessage, privateMessage, _ ->
|
||||
DamageInstruction(instructionSet, targetActor, publicMessage, privateMessage)
|
||||
}
|
||||
DamageInstruction(instructionSet, targetActor, publicMessage, privateMessage)
|
||||
}
|
||||
splitInstructionParser["drag"] = { _, targetActor, instructionSet, publicMessage, privateMessage, _ ->
|
||||
DragInstruction(instructionSet, targetActor, publicMessage, privateMessage)
|
||||
}
|
||||
DragInstruction(instructionSet, targetActor, publicMessage, privateMessage)
|
||||
}
|
||||
splitInstructionParser["-heal"] = { _, targetActor, _, publicMessage, privateMessage, _ ->
|
||||
HealInstruction(targetActor, publicMessage, privateMessage)
|
||||
}
|
||||
HealInstruction(targetActor, publicMessage, privateMessage)
|
||||
}
|
||||
splitInstructionParser["-sethp"] = { _, targetActor, _, publicMessage, privateMessage, _ ->
|
||||
SetHpInstruction(targetActor, publicMessage, privateMessage)
|
||||
}
|
||||
SetHpInstruction(targetActor, publicMessage, privateMessage)
|
||||
}
|
||||
splitInstructionParser["switch"] = { _, targetActor, instructionSet, publicMessage, privateMessage, _ ->
|
||||
SwitchInstruction(instructionSet, targetActor, publicMessage, privateMessage)
|
||||
}
|
||||
SwitchInstruction(instructionSet, targetActor, publicMessage, privateMessage)
|
||||
}
|
||||
|
||||
|
||||
// Note '-cureteam' is a legacy thing that is only used in generation 2 and 4 mods for heal bell and aromatherapy respectively as such we can just ignore that
|
||||
}
|
||||
|
||||
|
||||
fun interpretMessage(battleId: UUID, message: String) {
|
||||
// Check key map and use function if matching
|
||||
if (message.startsWith("{\"winner\":\"")) {
|
||||
@@ -161,6 +171,7 @@ object ShowdownInterpreter {
|
||||
val instructionSet = InstructionSet()
|
||||
val battleMessages = mutableListOf<BattleMessage>()
|
||||
|
||||
|
||||
try {
|
||||
val lines = rawMessage.split("\n").toMutableList()
|
||||
if (lines[0] == "update") {
|
||||
@@ -174,7 +185,6 @@ object ShowdownInterpreter {
|
||||
val instruction = updateInstructionParser[id]?.invoke(battle, instructionSet, message, iterator) ?: UnknownInstruction(message)
|
||||
instructionSet.instructions.add(instruction)
|
||||
}
|
||||
instructionSet.instructions.add(PostUpdateInstruction)
|
||||
}
|
||||
else if (lines[0] == "sideupdate") {
|
||||
val showdownId = lines[1]
|
||||
@@ -212,6 +222,9 @@ object ShowdownInterpreter {
|
||||
|
||||
// Broadcasts a generic lang to notify players of ability activations (effects are broadcasted separately)
|
||||
fun broadcastAbility(battle: PokemonBattle, effect: Effect, pokemon: BattlePokemon) {
|
||||
pokemon.revealedAbility = effect.typelessData
|
||||
pokemon.sendUpdate()
|
||||
if (effect.type != Effect.Type.ABILITY) return
|
||||
battle.dispatchWaiting(0.5F) {
|
||||
val lang = battleLang("ability.generic", pokemon.getName(), effect.typelessData).yellow()
|
||||
battle.broadcastChatMessage(lang)
|
||||
@@ -236,7 +249,7 @@ object ShowdownInterpreter {
|
||||
cause.effect()?.let { effect ->
|
||||
val damagingContexts = BattleContext.Type.values().filter { it.damaging }
|
||||
val contextBuckets = damagingContexts.map { pokemon.contextManager.get(it) ?: side.contextManager.get(it)
|
||||
?: battle.contextManager.get(it) }
|
||||
?: battle.contextManager.get(it) }
|
||||
ContextManager.scoop(effect.id, *contextBuckets.toTypedArray())
|
||||
} ?:
|
||||
// damage from moves and suicide
|
||||
@@ -321,4 +334,5 @@ object ShowdownInterpreter {
|
||||
fun registerSideInstructionParser(id: String, callback: (PokemonBattle, BattleActor, InstructionSet, BattleMessage) -> InterpreterInstruction) {
|
||||
sideInstructionParser[id] = callback
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.CloseTeamPreviewPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.TeamPreviewPacket
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import com.cobblemon.mod.common.util.party
|
||||
import com.cobblemon.mod.common.util.safeParty
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.UUID
|
||||
|
||||
object TeamPreviewManager {
|
||||
private val previews = mutableListOf<TeamPreview>()
|
||||
val TEAM_PREVIEW_TIME_SECONDS = 60
|
||||
|
||||
fun tick() {
|
||||
val expired = previews.filter { Duration.between(it.started, Instant.now()).seconds >= TEAM_PREVIEW_TIME_SECONDS }
|
||||
expired.forEach { cancel(it) }
|
||||
|
||||
val ready = previews.filter {
|
||||
hasEligibleSelections(it.sideA) && hasEligibleSelections(it.sideB)
|
||||
}
|
||||
ready.forEach { start(it) }
|
||||
}
|
||||
|
||||
fun add(sideA: TeamPreviewSide, sideB: TeamPreviewSide, openTeamSheet: Boolean, onStart: (TeamPreview) -> Unit, onCancel: (TeamPreview) -> Unit) {
|
||||
if (previews.any { it.sideA.uuid == sideA.uuid || it.sideB.uuid == sideA.uuid || it.sideA.uuid == sideB.uuid || it.sideB.uuid == sideB.uuid }) return
|
||||
val now = Instant.now()
|
||||
previews.add(TeamPreview(sideA, sideB, now, onStart, onCancel))
|
||||
if (sideA.isPlayer) {
|
||||
sideA.uuid.getPlayer()?.sendPacket(
|
||||
TeamPreviewPacket(
|
||||
sideA.neededSelections,
|
||||
sideA.party,
|
||||
sideB.party,
|
||||
openTeamSheet,
|
||||
now.plusSeconds(TEAM_PREVIEW_TIME_SECONDS.toLong()),
|
||||
sideB.name
|
||||
)
|
||||
)
|
||||
}
|
||||
if (sideB.isPlayer) {
|
||||
sideB.uuid.getPlayer()?.sendPacket(
|
||||
TeamPreviewPacket(
|
||||
sideB.neededSelections,
|
||||
sideB.party,
|
||||
sideA.party,
|
||||
openTeamSheet,
|
||||
now.plusSeconds(TEAM_PREVIEW_TIME_SECONDS.toLong()),
|
||||
sideA.name
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun remove(player: ServerPlayer) {
|
||||
val preview = previews.firstOrNull { it.sideA.isPlayer && it.sideA.uuid == player.uuid || it.sideB.isPlayer && it.sideB.uuid == player.uuid } ?: return
|
||||
cancel(preview)
|
||||
}
|
||||
|
||||
fun cancel(preview: TeamPreview) {
|
||||
preview.onCancel(preview)
|
||||
if (preview.sideA.isPlayer) {
|
||||
preview.sideA.uuid.getPlayer()?.sendPacket(CloseTeamPreviewPacket())
|
||||
}
|
||||
if (preview.sideB.isPlayer) {
|
||||
preview.sideB.uuid.getPlayer()?.sendPacket(CloseTeamPreviewPacket())
|
||||
}
|
||||
previews.remove(preview)
|
||||
}
|
||||
|
||||
fun start(preview: TeamPreview) {
|
||||
preview.onStart(preview)
|
||||
if (preview.sideA.isPlayer) {
|
||||
preview.sideA.uuid.getPlayer()?.sendPacket(CloseTeamPreviewPacket())
|
||||
}
|
||||
if (preview.sideB.isPlayer) {
|
||||
preview.sideB.uuid.getPlayer()?.sendPacket(CloseTeamPreviewPacket())
|
||||
}
|
||||
previews.remove(preview)
|
||||
}
|
||||
|
||||
fun changeSelections(uuid: UUID, selections: Set<Int>) {
|
||||
val preview = previews.firstOrNull { it.sideA.uuid == uuid || it.sideB.uuid == uuid } ?: return
|
||||
val isSideA = preview.sideA.uuid == uuid
|
||||
previews.remove(preview)
|
||||
if (isSideA) {
|
||||
previews.add(
|
||||
preview.copy(
|
||||
sideA = preview.sideA.copy(selections = selections),
|
||||
)
|
||||
)
|
||||
}
|
||||
else {
|
||||
previews.add(
|
||||
preview.copy(
|
||||
sideB = preview.sideB.copy(selections = selections),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun hasEligibleSelections(side: TeamPreviewSide): Boolean {
|
||||
if (side.selections.size < side.neededSelections) return false
|
||||
if (side.selections.any { side.party.size <= it || side.party[it] == null }) return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
data class TeamPreview(
|
||||
val sideA: TeamPreviewSide,
|
||||
val sideB: TeamPreviewSide,
|
||||
val started: Instant,
|
||||
val onStart: (TeamPreview) -> Unit,
|
||||
val onCancel: (TeamPreview) -> Unit
|
||||
)
|
||||
|
||||
data class TeamPreviewSide(
|
||||
val uuid: UUID,
|
||||
val name: Component,
|
||||
val isPlayer: Boolean,
|
||||
val party: List<Pokemon?>,
|
||||
val selections: Set<Int>,
|
||||
val neededSelections: Int
|
||||
) {
|
||||
constructor(player: ServerPlayer, neededSelections: Int): this(player.uuid, player.name, true, player.party().toGappyList(), emptySet(), neededSelections)
|
||||
constructor(pokemon: Pokemon, name: Component? = null): this(pokemon.uuid, name ?: pokemon.getDisplayName(), false, listOf(null, null, pokemon, null, null, null, null), setOf(2), 1)
|
||||
}
|
||||
@@ -17,6 +17,7 @@ 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.BattlePokemon
|
||||
import com.cobblemon.mod.common.battles.timers.PlayerBattleTimer
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMusicPacket
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
@@ -47,6 +48,8 @@ class PlayerBattleActor(
|
||||
field = value
|
||||
}
|
||||
|
||||
var timer: PlayerBattleTimer? = null
|
||||
|
||||
override fun getName(): MutableComponent = this.entity?.name?.copy() ?: "Offline Player".red()
|
||||
override fun nameOwned(name: String): MutableComponent = battleLang("owned_pokemon", this.getName(), name)
|
||||
override val type = ActorType.PLAYER
|
||||
|
||||
@@ -31,6 +31,7 @@ import kotlin.collections.associateWith
|
||||
import kotlin.collections.partition
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
|
||||
/**
|
||||
* AI that tries to choose the best move for the given situations. Based off of the Pokemon Trainer Tournament Simulator Github
|
||||
* https://github.com/cRz-Shadows/Pokemon_Trainer_Tournament_Simulator/blob/main/pokemon-showdown/sim/examples/Simulation-test-1.ts#L330
|
||||
@@ -48,7 +49,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
private val hpWeightConsideration = 0.25 // how much HP difference is a consideration for switchins
|
||||
private val hpFractionCoefficient = 0.4 // how much HP differences should be taken into account for switch ins
|
||||
private val boostWeightCoefficient = 1 // the amount of boosts considered a baseline to be removed
|
||||
private val switchOutMatchupThreshold = -2 // todo change this to get it feeling just right (-7 never switches)
|
||||
private val switchOutMatchupThreshold = 0 // todo change this to get it feeling just right (-7 never switches)
|
||||
private val selfKoMoveMatchupThreshold = 0.3
|
||||
private val trickRoomThreshold = 85
|
||||
private val recoveryMoveThreshold = 0.50
|
||||
@@ -218,14 +219,19 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
.filter { it.second.canBeSentOut() }
|
||||
|
||||
if (forceSwitch || activeBattlePokemon.isGone()) {
|
||||
// When forced to switch, pick the Pokemon with the best matchup estimation
|
||||
val bestSwitchScore = availableSwitches.maxOfOrNull { estimateMatchup(activeBattlePokemon, aiSide, battle, it.first) }
|
||||
?: return PassActionResponse
|
||||
val bestSwitch = availableSwitches.firstOrNull { estimateMatchup(activeBattlePokemon, aiSide, battle, it.first) == bestSwitchScore }
|
||||
?: return PassActionResponse
|
||||
if (battle.turn == 1) {
|
||||
val switchTo = activeBattlePokemon.actor.pokemonList.filter { it.canBeSentOut() }.randomOrNull()
|
||||
?: return DefaultActionResponse()
|
||||
switchTo.willBeSwitchedIn = true
|
||||
return SwitchActionResponse(switchTo.uuid)
|
||||
}
|
||||
else {
|
||||
val bestEstimation = availableSwitches.maxByOrNull { estimateMatchup(activeBattlePokemon, aiSide, battle, it.first) }
|
||||
?: return PassActionResponse
|
||||
|
||||
bestSwitch.second.willBeSwitchedIn = true
|
||||
return SwitchActionResponse(bestSwitch.second.uuid)
|
||||
bestEstimation.second.willBeSwitchedIn = true
|
||||
return SwitchActionResponse(bestEstimation.second.uuid)
|
||||
}
|
||||
}
|
||||
if (moveset == null) {
|
||||
return PassActionResponse
|
||||
@@ -264,6 +270,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
if (checkSwitchOutSkill() && shouldSwitchOut(aiSide, battle, activeBattlePokemon, moveset)) {
|
||||
considerSwitching(activeBattlePokemon, activeTrackerPokemon, opponents, availableMoves, availableSwitches, battle, aiSide)
|
||||
}
|
||||
activeTrackerPokemon.firstTurn = false
|
||||
|
||||
// If HP is below 30% and not switching out, always use the most damaging move
|
||||
if (activeTrackerPokemon.currentHpPercent < 0.3 && !shouldSwitchOut(aiSide, battle, activeBattlePokemon, moveset)) {
|
||||
@@ -289,12 +296,14 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
return chooseMove(fakeOut.first, activeBattlePokemon, targets.firstOrNull { validTarget != null && validTarget.id == it.battlePokemon!!.uuid})
|
||||
}
|
||||
|
||||
activeTrackerPokemon.firstTurn = false
|
||||
|
||||
|
||||
// Explosion/Self destruct
|
||||
availableMoves.firstOrNull {
|
||||
(it.first.id == "explosion" || it.first.id == "selfdestruct")
|
||||
&& activeTrackerPokemon.currentHpPercent < selfKoMoveMatchupThreshold
|
||||
&& opponents.any { opponent -> ElementalTypes.GHOST !in (opponent.form?.types ?: opponent.species!!.types) && opponent.currentHpPercent > 0.5 }
|
||||
&& activeTrackerPokemon.currentHpPercent < selfKoMoveMatchupThreshold
|
||||
&& opponents.any { opponent -> ElementalTypes.GHOST !in (opponent.form?.types ?: opponent.species!!.types) && opponent.currentHpPercent > 0.5 }
|
||||
}?.let {
|
||||
return chooseMove(it.first, activeBattlePokemon)
|
||||
}
|
||||
@@ -364,7 +373,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
for (move in availableMoves) {
|
||||
if (move.first.id == "courtchange"
|
||||
&& (!AIUtility.entryHazards.none { it in activeTracker.alliedSide.sideHazards }
|
||||
|| setOf("tailwind", "lightscreen", "reflect").any { it in activeTracker.opponentSide.sideHazards })
|
||||
|| setOf("tailwind", "lightscreen", "reflect").any { it in activeTracker.opponentSide.sideHazards })
|
||||
&& setOf("tailwind", "lightscreen", "reflect").none { it in activeTracker.opponentSide.sideHazards }
|
||||
&& AIUtility.entryHazards.none { it in activeTracker.opponentSide.sideHazards }) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
@@ -383,8 +392,8 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
for (move in availableMoves) {
|
||||
if (move.first.id == "bellydrum"
|
||||
&& (activeTrackerPokemon.currentHpPercent > 0.6
|
||||
&& activeTrackerPokemon.pokemon!!.heldItem().item == CobblemonItems.SITRUS_BERRY
|
||||
|| activeTrackerPokemon.currentHpPercent > 0.8)
|
||||
&& activeTrackerPokemon.pokemon!!.heldItem().item == CobblemonItems.SITRUS_BERRY
|
||||
|| activeTrackerPokemon.currentHpPercent > 0.8)
|
||||
&& activeTrackerPokemon.boosts.getOrDefault(Stats.ATTACK, 0) < 1) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
@@ -502,7 +511,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
// TODO: Need to check for Safeguard and Misty Terrain
|
||||
val ability = target.pokemon?.ability?.name ?: target.currentAbility ?: return false
|
||||
return listOf("comatose", "purifyingsalt").contains(ability) ||
|
||||
(activeTracker.currentWeather == "sunny" && ability == "leafguard")
|
||||
(activeTracker.currentWeather == "sunny" && ability == "leafguard")
|
||||
}
|
||||
|
||||
fun shouldSwitchOut(side: BattleSide, battle: PokemonBattle, activeBattlePokemon: ActiveBattlePokemon, moveset: ShowdownMoveset): Boolean {
|
||||
@@ -511,10 +520,6 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
return false
|
||||
|
||||
val activeTrackerPokemon = activeTracker.alliedSide.activePokemon.first { it.id == activeBattlePokemon.battlePokemon!!.uuid }
|
||||
|
||||
// Don't switch out if we just switched in
|
||||
if (activeTrackerPokemon.firstTurn)
|
||||
return false
|
||||
val actorTracker = activeTracker.alliedSide.actors.first { activeTrackerPokemon in it.activePokemon }
|
||||
val availableSwitches = actorTracker.party.filter { it.currentHp!! > 0 }
|
||||
val currentAbility = activeTrackerPokemon.pokemon!!.ability
|
||||
@@ -527,7 +532,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
// Only switch if a switch-in is significantly better than the current Pokémon
|
||||
val currentScore = estimateMatchup(activeBattlePokemon, side, battle)
|
||||
val bestSwitchScore = availableSwitches.maxOfOrNull { estimateMatchup(activeBattlePokemon, side, battle, it) } ?: currentScore
|
||||
val improvementThreshold = currentScore.absoluteValue * 0.5 + 3
|
||||
val improvementThreshold = currentScore.absoluteValue * 0.25 + 2
|
||||
if (bestSwitchScore <= currentScore + improvementThreshold) {
|
||||
return false
|
||||
}
|
||||
@@ -573,13 +578,13 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
|
||||
val (physicalAttackers, specialAttackers) = opponentActiveTracker
|
||||
.partition { (it.form?.baseStats ?: it.species!!.baseStats).getOrDefault(Stats.ATTACK, 0) >
|
||||
(it.form?.baseStats ?: it.species!!.baseStats).getOrDefault(Stats.SPECIAL_ATTACK, 0) }
|
||||
(it.form?.baseStats ?: it.species!!.baseStats).getOrDefault(Stats.SPECIAL_ATTACK, 0) }
|
||||
if (physicalAttackers.any { it.boosts.getOrDefault(Stats.ATTACK, 0) <= -3 } ||
|
||||
specialAttackers.any { it.boosts.getOrDefault(Stats.SPECIAL_ATTACK, 0) <= -3 } )
|
||||
return true
|
||||
|
||||
return estimateMatchup(activeBattlePokemon, side, battle) < switchOutMatchupThreshold &&
|
||||
activeTrackerPokemon.currentHpPercent > hpSwitchOutThreshold
|
||||
activeTrackerPokemon.currentHpPercent > hpSwitchOutThreshold
|
||||
}
|
||||
|
||||
fun statEstimationActive(mon: TrackerPokemon, stat: Stat): Double {
|
||||
@@ -843,9 +848,9 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
.filterNot { (calculateDamage(move.first, activeTrackerPokemon, it) >= (it.currentHpPercent * statusDamageConsiderationThreshold)) }
|
||||
.filter {
|
||||
(if (isPersistent) it.currentStatus == null else it.currentVolatile != status) &&
|
||||
it.currentHpPercent > 0.3 &&
|
||||
AIUtility.canAffectWithStatus(status, it.species!!.types, it.currentAbility) &&
|
||||
!hasMajorStatusImmunity(it)
|
||||
it.currentHpPercent > 0.3 &&
|
||||
AIUtility.canAffectWithStatus(status, it.species!!.types, it.currentAbility) &&
|
||||
!hasMajorStatusImmunity(it)
|
||||
}
|
||||
for (potentialTarget in sensibleTargets) {
|
||||
when (status) {
|
||||
@@ -857,9 +862,9 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
}
|
||||
Statuses.PARALYSIS.showdownName -> {
|
||||
val electricVersusGround = move.second.getEffectiveElementalType(activeTrackerPokemon.pokemon) == ElementalTypes.ELECTRIC &&
|
||||
potentialTarget.species!!.types.any { type -> type.name == "ground"}
|
||||
potentialTarget.species!!.types.any { type -> type.name == "ground"}
|
||||
val stats = potentialTarget.species!!.baseStats.getOrDefault(Stats.SPEED, 0) >
|
||||
activeTrackerPokemon.pokemon!!.species.baseStats.getOrDefault(Stats.SPEED, 0)
|
||||
activeTrackerPokemon.pokemon!!.species.baseStats.getOrDefault(Stats.SPEED, 0)
|
||||
if (!electricVersusGround && stats) {
|
||||
return chooseMove(move.first, activeBattlePokemon, singleTargetList.first { it.battlePokemon!!.uuid == potentialTarget.id })
|
||||
}
|
||||
@@ -882,14 +887,14 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
val (allies, opponents) = multiTargetList.partition { it.isAllied(activeBattlePokemon) }
|
||||
val possibleAffectedAllies = activeTracker.alliedSide.activePokemon.filter { it.id in allies.map { target -> target.battlePokemon!!.uuid } }.count {
|
||||
(if (isPersistent) it.currentStatus == null else it.currentVolatile != status) &&
|
||||
it.currentHpPercent > 0.3 &&
|
||||
AIUtility.canAffectWithStatus(status, it.species!!.types, it.currentAbility) &&
|
||||
!hasMajorStatusImmunity(it) }
|
||||
it.currentHpPercent > 0.3 &&
|
||||
AIUtility.canAffectWithStatus(status, it.species!!.types, it.currentAbility) &&
|
||||
!hasMajorStatusImmunity(it) }
|
||||
val possibleAffectedOpponents = activeTracker.opponentSide.activePokemon.filter { it.id in opponents.map { target -> target.battlePokemon!!.uuid } }.count {
|
||||
(if (isPersistent) it.currentStatus == null else it.currentVolatile != status) &&
|
||||
it.currentHpPercent > 0.3 &&
|
||||
AIUtility.canAffectWithStatus(status, it.species!!.types, it.currentAbility) &&
|
||||
!hasMajorStatusImmunity(it) }
|
||||
it.currentHpPercent > 0.3 &&
|
||||
AIUtility.canAffectWithStatus(status, it.species!!.types, it.currentAbility) &&
|
||||
!hasMajorStatusImmunity(it) }
|
||||
if (possibleAffectedAllies > possibleAffectedOpponents)
|
||||
continue
|
||||
|
||||
@@ -990,4 +995,6 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
return MoveActionResponse(move.id, chosenTarget.getPNX())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ class AbilityInstruction(val instructionSet: InstructionSet, val message: Battle
|
||||
else -> when (effect.id) {
|
||||
"sturdy", "unnerve", "anticipation" -> battleLang("ability.${effect.id}", pokemonName) // Unique message
|
||||
"airlock", "cloudnine" -> battleLang("ability.airlock") // Cloud Nine shares the same text as Air Lock
|
||||
"chromaticscales" -> battleLang("ability.chromaticscales", pokemonName, extractLastPart(message.rawMessage))
|
||||
else -> null // Effect broadcasted by a succeeding instruction
|
||||
}
|
||||
}
|
||||
@@ -63,4 +64,15 @@ class AbilityInstruction(val instructionSet: InstructionSet, val message: Battle
|
||||
else return@dispatch GO
|
||||
}
|
||||
}
|
||||
|
||||
fun extractLastPart(input: String): String {
|
||||
val pattern = Regex("\\|([^|]+)\\|([^|]+)$")
|
||||
val matchResult = pattern.find(input)
|
||||
return if (matchResult != null) {
|
||||
val (secondToLast, last) = matchResult.destructured
|
||||
"$secondToLast and $last"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,8 @@ 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 kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Format: |-boost|POKEMON|STAT|AMOUNT or |-unboost|POKEMON|STAT|AMOUNT
|
||||
@@ -77,6 +79,15 @@ class BoostInstruction(battle: PokemonBattle, val message: BattleMessage, val is
|
||||
val rootKey = if (isBoost) "boost" else "unboost"
|
||||
|
||||
battle.dispatch {
|
||||
val stagesWithSign = if (isBoost) stages else -stages
|
||||
if (pokemon.boosts[Stats.getStat(statKey)] == null) {
|
||||
pokemon.boosts[Stats.getStat(statKey)] = stagesWithSign
|
||||
}
|
||||
else {
|
||||
val curr = pokemon.boosts[Stats.getStat(statKey)]!!
|
||||
pokemon.boosts[Stats.getStat(statKey)] = min(max(curr + stagesWithSign, -6), 6)
|
||||
}
|
||||
|
||||
val lang = when {
|
||||
message.hasOptionalArgument("zeffect") -> battleLang("$rootKey.$severity.zeffect", pokemon.getName(), stat)
|
||||
else -> battleLang("$rootKey.$severity", pokemon.getName(), stat)
|
||||
@@ -88,6 +99,7 @@ class BoostInstruction(battle: PokemonBattle, val message: BattleMessage, val is
|
||||
// TODO: replace with context that tracks detailed information such as # of stages
|
||||
repeat(stages) { pokemon.contextManager.add(context) }
|
||||
battle.minorBattleActions[pokemon.uuid] = message
|
||||
pokemon.sendUpdate()
|
||||
return@dispatch UntilDispatch { "effects" !in holds }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ class ClearAllBoostInstruction(val message: BattleMessage): InterpreterInstructi
|
||||
battle.dispatchWaiting(1.5F) {
|
||||
battle.activePokemon.forEach {
|
||||
it.battlePokemon?.contextManager?.clear(BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
|
||||
it.battlePokemon?.boosts?.clear()
|
||||
it.battlePokemon?.sendUpdate()
|
||||
}
|
||||
battle.broadcastChatMessage(battleLang("clearallboost"))
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ class ClearBoostInstruction(val message: BattleMessage): InterpreterInstruction
|
||||
battle.broadcastChatMessage(lang)
|
||||
battlePokemon.contextManager.clear(BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
|
||||
battle.minorBattleActions[battlePokemon.uuid] = message
|
||||
battlePokemon.boosts.clear()
|
||||
battlePokemon.sendUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.removeIf
|
||||
|
||||
/**
|
||||
* Format: |-clearnegativeboost|POKEMON
|
||||
@@ -36,6 +37,8 @@ class ClearNegativeBoostInstruction(val message: BattleMessage): InterpreterInst
|
||||
battle.broadcastChatMessage(lang)
|
||||
}
|
||||
|
||||
battlePokemon.boosts.removeIf { it.value < 0 }
|
||||
battlePokemon.sendUpdate()
|
||||
battlePokemon.contextManager.clear(BattleContext.Type.UNBOOST)
|
||||
battle.minorBattleActions[battlePokemon.uuid] = message
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.battles.interpreter.instructions
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.text.lightPurple
|
||||
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.BattleSwitchPokemonPacket
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Format: |detailschange|POKEMON|DETAILS|HP STATUS
|
||||
*
|
||||
* POKEMON has changed formes permanently (i.e. Mega Evolution) to DETAILS.
|
||||
* @author Segfault Guy
|
||||
* @since September 10th, 2023
|
||||
*/
|
||||
class DetailsChangeInstruction(val message: BattleMessage): InterpreterInstruction {
|
||||
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
val (pnx, _) = message.pnxAndUuid(0) ?: return
|
||||
val battlePokemon = message.battlePokemon(0, battle) ?: return
|
||||
val heldItemName = battlePokemon.effectedPokemon.heldItem().hoverName.string
|
||||
val formName = message.argumentAt(1)?.split(',')?.get(0)?.substringAfter('-')?.lowercase() ?: return
|
||||
|
||||
val pokemonName = battlePokemon.getName()
|
||||
val effectiveForm = when (formName) {
|
||||
"mega-x" -> "mega_x"
|
||||
"mega-y" -> "mega_y"
|
||||
else -> formName
|
||||
}
|
||||
|
||||
val delay = if (effectiveForm in setOf("mega_x", "mega_y", "mega")) 3.5f else 1f
|
||||
|
||||
battle.dispatchWaiting(delaySeconds = delay) {
|
||||
battlePokemon.setBattleFeature(effectiveForm, true)
|
||||
battlePokemon.sendUpdate()
|
||||
|
||||
val flatHp = message.argumentAt(1)?.split("/")?.getOrNull(0)?.toFloatOrNull() ?: return@dispatchWaiting
|
||||
val ratioHp = message.argumentAt(1)?.split("/")?.getOrNull(0)?.toFloatOrNull()?.times(0.01F) ?: return@dispatchWaiting
|
||||
battlePokemon.effectedPokemon.currentHealth = flatHp.roundToInt()
|
||||
battle.sendSidedUpdate(battlePokemon.actor, BattleHealthChangePacket(pnx, flatHp), BattleHealthChangePacket(pnx, ratioHp))
|
||||
battle.sendUpdate(BattleSwitchPokemonPacket(pnx, battlePokemon, true, battlePokemon.getIllusion()))
|
||||
|
||||
battle.broadcastChatMessage(battleLang("detailschange.$formName", pokemonName, heldItemName).lightPurple())
|
||||
battle.majorBattleActions[battlePokemon.uuid] = message
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,9 +51,10 @@ class FaintInstruction(battle: PokemonBattle, val message: BattleMessage) : Inte
|
||||
|
||||
preamble.thenAccept {
|
||||
faintingPokemon.effectedPokemon.currentHealth = 0
|
||||
faintingPokemon.faintedAt = battle.faintCounter++
|
||||
pokemon.boosts.clear()
|
||||
faintingPokemon.sendUpdate()
|
||||
CobblemonEvents.BATTLE_FAINTED.post(BattleFaintedEvent(battle, pokemon, context, pnx))
|
||||
faintingPokemon.clearBattleFeatures()
|
||||
battle.getActorAndActiveSlotFromPNX(pnx).second.battlePokemon = null
|
||||
faintingPokemon.contextManager.add(context)
|
||||
faintingPokemon.contextManager.clear(BattleContext.Type.STATUS, BattleContext.Type.VOLATILE, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.cobblemon.mod.common.net.messages.client.battle.BattleInitializePacke
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMusicPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleQueueRequestPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleSetTeamPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorTeamPacket
|
||||
|
||||
/**
|
||||
* Format: |start
|
||||
@@ -58,6 +59,18 @@ class InitializeInstruction(val instructionSet: InstructionSet, val message: Bat
|
||||
|
||||
battle.actors.forEach { actor ->
|
||||
actor.sendUpdate(BattleSetTeamPokemonPacket(actor.pokemonList.map { it.effectedPokemon }))
|
||||
|
||||
val allyTeam = actor.pokemonList.map { it.toBattleDTO(ally = true) }
|
||||
val nonallyTeam = actor.pokemonList.map { it.toBattleDTO(ally = false) }
|
||||
|
||||
actor.sendUpdate(BattleActorTeamPacket(actor.uuid, allyTeam))
|
||||
|
||||
val otherActors = battle.actors.filter { it != actor }
|
||||
otherActors.forEach { other ->
|
||||
val packet = BattleActorTeamPacket(actor.uuid, nonallyTeam)
|
||||
other.sendUpdate(packet)
|
||||
}
|
||||
|
||||
val req = actor.request ?: return@forEach
|
||||
actor.sendUpdate(BattleQueueRequestPacket(req))
|
||||
}
|
||||
@@ -70,6 +83,7 @@ class InitializeInstruction(val instructionSet: InstructionSet, val message: Bat
|
||||
battle.started = true
|
||||
battle.side1.playCries()
|
||||
afterOnServer(seconds = 1.0F) { battle.side2.playCries() }
|
||||
battle.notifyAllOfUpdates()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,12 @@ import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.Effect
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleUseMoveEvent
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addBattleMessageFunctions
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
@@ -31,13 +34,14 @@ 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.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.MoveDTO
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.UseMoveEvolutionProgress
|
||||
import com.cobblemon.mod.common.util.asArrayValue
|
||||
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.getPlayer
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* Format: |move|POKEMON|MOVE|TARGET
|
||||
@@ -67,6 +71,33 @@ class MoveInstruction(
|
||||
|
||||
val actionEffect = actionEffect ?: ActionEffects.actionEffects["generic_move".asIdentifierDefaultingNamespace()]
|
||||
val targetPokemon = targetPokemon // So smart non-null casts can happen
|
||||
val players = battle.actors
|
||||
.map { it.uuid }
|
||||
.mapNotNull { it.getPlayer() }
|
||||
.toList()
|
||||
|
||||
CobblemonEvents.BATTLE_USE_MOVE.post(BattleUseMoveEvent(battle, players, userPokemon, move))
|
||||
|
||||
val moveIndex = if (userPokemon.transformed != null) {
|
||||
userPokemon.transformed!!.moveSet.getMovesWithNulls().map { it?.name }.indexOf(move.name)
|
||||
}
|
||||
else {
|
||||
userPokemon.moveSet.getMovesWithNulls().map { it?.name }.indexOf(move.name)
|
||||
}
|
||||
if (moveIndex == -1) {
|
||||
Cobblemon.LOGGER.warn("Could not find move ${move.name} on pokemon ${userPokemon.getName()}")
|
||||
}
|
||||
else {
|
||||
if (userPokemon.revealedMoves[moveIndex] == null) {
|
||||
userPokemon.revealedMoves[moveIndex] = MoveDTO(move.displayName, 1)
|
||||
}
|
||||
else {
|
||||
val update = userPokemon.revealedMoves[moveIndex]!!.timesUsed + 1
|
||||
userPokemon.revealedMoves[moveIndex]!!.timesUsed = update
|
||||
}
|
||||
}
|
||||
userPokemon.sendUpdate()
|
||||
battle.notifyAllOfUpdates()
|
||||
|
||||
val optionalEffect = message.effect()
|
||||
ShowdownInterpreter.broadcastOptionalAbility(battle, optionalEffect, userPokemon)
|
||||
@@ -92,16 +123,17 @@ class MoveInstruction(
|
||||
}
|
||||
|
||||
val lang = when {
|
||||
userPokemon.effectedPokemon.species.resourceIdentifier.toString().equals("cobblemon:ancientgene") && move.name == "revivalblessing" -> null
|
||||
optionalEffect?.id == "magicbounce" ->
|
||||
battleLang("ability.magicbounce", pokemonName, move.displayName)
|
||||
move.name != "struggle" && spreadTargetPokemon.isEmpty() && targetPokemon != null && targetPokemon != userPokemon && targetPokemon.health > 0 ->
|
||||
battleLang("used_move_on", pokemonName, move.displayName, targetPokemon.getName())
|
||||
move.name == "rebirth" ->
|
||||
battleLang("move.rebirth", pokemonName, extractLastPart(message.rawMessage))
|
||||
else ->
|
||||
battleLang("used_move", pokemonName, move.displayName)
|
||||
}
|
||||
battle.broadcastChatMessage(lang)
|
||||
// Notify the actor that one of their Pokemon used a move so they can react (eg. NPC play animation)
|
||||
userPokemon.actor.onUseMove(userPokemon, targetPokemon)
|
||||
lang?.let { battle.broadcastChatMessage(it) }
|
||||
battle.majorBattleActions[userPokemon.uuid] = message
|
||||
|
||||
val runtime = MoLangRuntime().also {
|
||||
@@ -175,4 +207,15 @@ class MoveInstruction(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun extractLastPart(input: String): String {
|
||||
val pattern = Regex("\\|([^|]+)\\|([^|]+)$")
|
||||
val matchResult = pattern.find(input)
|
||||
return if (matchResult != null) {
|
||||
val (secondToLast, last) = matchResult.destructured
|
||||
"$secondToLast and $last"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.pokemon.PokemonSeenEvent
|
||||
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.pokemon.BattlePokemon
|
||||
@@ -76,19 +75,24 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
|
||||
actor.stillSendingOutCount++
|
||||
battle.sendSidedUpdate(actor, BattleSwitchPokemonPacket(pnx, pokemon, true, illusion), BattleSwitchPokemonPacket(pnx, pokemon, false, illusion))
|
||||
broadcastSwitch(battle, actor, pokemon, illusion)
|
||||
afterOnServer(seconds = battleSendoutCount * SEND_OUT_STAGGER_BASE_DURATION + if (battleSendoutCount > 0) Random.nextFloat() * SEND_OUT_STAGGER_RANDOM_MAX_DURATION else 0F ) {
|
||||
|
||||
val seconds = battleSendoutCount * SEND_OUT_STAGGER_BASE_DURATION + if (battleSendoutCount > 0) Random.nextFloat() * SEND_OUT_STAGGER_RANDOM_MAX_DURATION else 0F
|
||||
afterOnServer(seconds = seconds) {
|
||||
//battle.log("Now sending out ${pokemon.effectedPokemon.getDisplayName()} for ${actor.getName().string}")
|
||||
pokemon.effectedPokemon.sendOutWithAnimation(
|
||||
source = entity,
|
||||
battleId = battle.battleId,
|
||||
level = entity.level() as ServerLevel,
|
||||
doCry = false,
|
||||
position = targetPos,
|
||||
illusion = illusion?.let { IllusionEffect(it.effectedPokemon) }
|
||||
source = entity,
|
||||
battleId = battle.battleId,
|
||||
level = entity.level() as ServerLevel,
|
||||
doCry = false,
|
||||
position = targetPos,
|
||||
illusion = illusion?.let { IllusionEffect(it.effectedPokemon) }
|
||||
).thenApply {
|
||||
actor.stillSendingOutCount--
|
||||
}
|
||||
WaitDispatch(0.5F) // we're already waiting 1.5 seconds. this prevents flooding from consecutive SwitchInstructions
|
||||
activePokemon.battlePokemon?.sendUpdate()
|
||||
}
|
||||
return@dispatchToFront WaitDispatch(seconds + 0.25f) // we're already waiting 1.5 seconds. this prevents flooding from consecutive SwitchInstructions
|
||||
|
||||
}
|
||||
}
|
||||
GO
|
||||
@@ -131,6 +135,12 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
|
||||
if (publicMessage.effect()?.id == "batonpass") oldPokemon.contextManager.swap(pokemon.contextManager, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
|
||||
oldPokemon.contextManager.clear(BattleContext.Type.VOLATILE, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
|
||||
battle.majorBattleActions[oldPokemon.uuid] = publicMessage
|
||||
if (oldPokemon.transformed != null) {
|
||||
for (i in 0 until 4) {
|
||||
oldPokemon.revealedMoves[i] = null
|
||||
}
|
||||
}
|
||||
oldPokemon.transformed = null
|
||||
|
||||
val publicName = (activePokemon.illusion ?: oldPokemon).effectedPokemon.getDisplayName()
|
||||
actor.sendMessage(battleLang("withdraw.self", publicName))
|
||||
@@ -141,7 +151,11 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
|
||||
setOf(
|
||||
BattleDispatch {
|
||||
if (entity != null) {
|
||||
createEntitySwitch(battle, actor, entity, pnx, activePokemon, pokemon, illusion, imposter)
|
||||
if (battle.isPvP) {
|
||||
createEntitySwitch(battle, actor, entity, pnx, activePokemon, pokemon, illusion, imposter)
|
||||
} else {
|
||||
createEntitySwitchInstant(battle, actor, entity, pnx, activePokemon, pokemon, illusion, imposter)
|
||||
}
|
||||
} else {
|
||||
createNonEntitySwitch(battle, actor, pnx, activePokemon, pokemon, illusion)
|
||||
}
|
||||
@@ -166,6 +180,9 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
|
||||
// If we can't find the entity for some reason then we're going to skip the recall animation
|
||||
val sendOutFuture = CompletableFuture<Unit>()
|
||||
(pokemonEntity?.recallWithAnimation() ?: CompletableFuture.completedFuture(Unit)).thenApply {
|
||||
activePokemon.battlePokemon?.boosts?.clear()
|
||||
activePokemon.battlePokemon?.sendUpdate()
|
||||
|
||||
if (battle.ended) {
|
||||
sendOutFuture.complete(Unit)
|
||||
return@thenApply
|
||||
@@ -186,7 +203,7 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
|
||||
} else {
|
||||
// For Singles, we modify the sendout position based on the pokemon's hitbox size
|
||||
val pos = (if (battle.format.battleType.pokemonPerSide == 1) activePokemon.getSendOutPosition()
|
||||
else activePokemon.position?.second) ?: entity.position()
|
||||
else activePokemon.position?.second) ?: entity.position()
|
||||
// Send out at previous Pokémon's location if it is known, otherwise actor location
|
||||
val world = entity.level() as ServerLevel
|
||||
newPokemon.effectedPokemon.sendOutWithAnimation(
|
||||
@@ -196,7 +213,10 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
|
||||
position = pos,
|
||||
doCry = !imposter,
|
||||
illusion = illusion?.let { IllusionEffect(it.effectedPokemon) }
|
||||
).thenAccept { sendOutFuture.complete(Unit) }
|
||||
).thenAccept { entity ->
|
||||
newPokemon.terastallized?.let { entity.terastallize(it) }
|
||||
sendOutFuture.complete(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
broadcastSwitch(battle, actor, newPokemon, illusion)
|
||||
@@ -205,6 +225,55 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
|
||||
return UntilDispatch { sendOutFuture.isDone }
|
||||
}
|
||||
|
||||
fun createEntitySwitchInstant(
|
||||
battle: PokemonBattle,
|
||||
actor: BattleActor,
|
||||
entity: LivingEntity,
|
||||
pnx: String,
|
||||
activePokemon: ActiveBattlePokemon,
|
||||
newPokemon: BattlePokemon,
|
||||
illusion: BattlePokemon? = null,
|
||||
imposter: Boolean = false
|
||||
): DispatchResult {
|
||||
val pokemonEntity = activePokemon.battlePokemon?.entity
|
||||
pokemonEntity?.pokemon?.recall()
|
||||
|
||||
if (!battle.ended) {
|
||||
activePokemon.battlePokemon?.boosts?.clear()
|
||||
activePokemon.battlePokemon?.sendUpdate()
|
||||
// Queue actual swap and send-in after the animation has ended
|
||||
actor.pokemonList.swap(actor.activePokemon.indexOf(activePokemon), actor.pokemonList.indexOf(newPokemon))
|
||||
activePokemon.battlePokemon = newPokemon
|
||||
activePokemon.illusion = illusion
|
||||
battle.sendSidedUpdate(actor, BattleSwitchPokemonPacket(pnx, newPokemon, true, illusion), BattleSwitchPokemonPacket(pnx, newPokemon, false, illusion))
|
||||
val newEntity = newPokemon.entity
|
||||
if (newEntity != null) {
|
||||
illusion?.let { IllusionEffect(it.effectedPokemon).start(newEntity) }
|
||||
afterOnServer(seconds = SEND_OUT_DURATION) {
|
||||
if (!imposter) newPokemon.entity?.cry()
|
||||
}
|
||||
} else {
|
||||
// For Singles, we modify the sendout position based on the pokemon's hitbox size
|
||||
val pos = (if (battle.format.battleType.pokemonPerSide == 1) activePokemon.getSendOutPosition()
|
||||
else activePokemon.position?.second) ?: entity.position()
|
||||
// Send out at previous Pokémon's location if it is known, otherwise actor location
|
||||
val world = entity.level() as ServerLevel
|
||||
newPokemon.effectedPokemon.sendOutWithAnimation(
|
||||
source = entity,
|
||||
battleId = battle.battleId,
|
||||
level = world,
|
||||
position = pos,
|
||||
doCry = !imposter,
|
||||
illusion = illusion?.let { IllusionEffect(it.effectedPokemon) }
|
||||
)
|
||||
}
|
||||
|
||||
broadcastSwitch(battle, actor, newPokemon, illusion)
|
||||
}
|
||||
|
||||
return WaitDispatch(0.25F)
|
||||
}
|
||||
|
||||
fun createNonEntitySwitch(battle: PokemonBattle, actor: BattleActor, pnx: String, activePokemon: ActiveBattlePokemon, newPokemon: BattlePokemon, illusion: BattlePokemon? = null): DispatchResult {
|
||||
actor.pokemonList.swap(actor.activePokemon.indexOf(activePokemon), actor.pokemonList.indexOf(newPokemon))
|
||||
activePokemon.battlePokemon = newPokemon
|
||||
|
||||
@@ -14,8 +14,10 @@ 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.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.entity.pokemon.effects.TransformEffect
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleTransformPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.MoveDTO
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
|
||||
/**
|
||||
@@ -58,9 +60,21 @@ class TransformInstruction(val battle: PokemonBattle, val message: BattleMessage
|
||||
)
|
||||
}
|
||||
|
||||
copyBoosts(pokemon, targetPokemon)
|
||||
for (i in 0 until 4) {
|
||||
pokemon.revealedMoves[i] = targetPokemon.moveSet.get(i)?.let { MoveDTO(it.displayName, 0) }
|
||||
}
|
||||
pokemon.transformed = targetPokemon
|
||||
pokemon.revealedAbility = targetPokemon.effectedPokemon.ability.name
|
||||
pokemon.sendUpdate()
|
||||
val lang = battleLang("transform", pokemonName, targetPokemonName)
|
||||
battle.broadcastChatMessage(lang)
|
||||
battle.minorBattleActions[pokemon.uuid] = message
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyBoosts(source: BattlePokemon, target: BattlePokemon) {
|
||||
source.boosts.clear()
|
||||
target.boosts.forEach { (stat, value) -> source.boosts[stat] = value }
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ class WeatherInstruction(val message: BattleMessage): InterpreterInstruction {
|
||||
battleLang("weather.$oldWeather.end")
|
||||
}
|
||||
}
|
||||
battle.notifyAllOfUpdates()
|
||||
battle.broadcastChatMessage(lang)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,26 +11,36 @@ 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.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asStruct
|
||||
import com.cobblemon.mod.common.api.moves.MoveSet
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.BattleFormFeature
|
||||
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.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.types.tera.TeraType
|
||||
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.BattleInitializePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleUpdateTeamPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorInformationPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.MoveDTO
|
||||
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.getPlayer
|
||||
import com.cobblemon.mod.common.util.server
|
||||
import java.util.UUID
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import java.util.function.Function
|
||||
|
||||
open class BattlePokemon(
|
||||
@@ -110,9 +120,6 @@ open class BattlePokemon(
|
||||
/** A set of all the BattlePokemon that they faced during the battle (for exp purposes) */
|
||||
val facedOpponents = mutableSetOf<BattlePokemon>()
|
||||
|
||||
/** The faint sequence number within the battle, or null if this pokemon has not fainted. */
|
||||
var faintedAt: Int? = null
|
||||
|
||||
/**
|
||||
* The [HeldItemManager] backing this [BattlePokemon].
|
||||
*/
|
||||
@@ -120,6 +127,14 @@ open class BattlePokemon(
|
||||
|
||||
val contextManager = ContextManager()
|
||||
|
||||
val boosts = mutableMapOf<Stats, Int>()
|
||||
var revealed = false
|
||||
var revealedAbility: String? = null
|
||||
val revealedMoves: MutableList<MoveDTO?> = mutableListOf(null, null, null, null)
|
||||
var revealedHeldItem: ItemStack? = null
|
||||
var transformed: BattlePokemon? = null
|
||||
var terastallized: TeraType? = null
|
||||
|
||||
open fun getName(): MutableComponent {
|
||||
val displayPokemon = getIllusion()?.effectedPokemon ?: effectedPokemon
|
||||
return if (actor is PokemonBattleActor || actor is MultiPokemonBattleActor) {
|
||||
@@ -129,17 +144,128 @@ open class BattlePokemon(
|
||||
}
|
||||
}
|
||||
|
||||
fun getBoostMultiplier(stat: Stats): Double {
|
||||
return when (boosts[stat]) {
|
||||
-6 -> 2.0 / 8.0
|
||||
-5 -> 2.0 / 7.0
|
||||
-4 -> 2.0 / 6.0
|
||||
-3 -> 2.0 / 5.0
|
||||
-2 -> 2.0 / 4.0
|
||||
-1 -> 2.0 / 3.0
|
||||
0 -> 2.0 / 2.0
|
||||
1 -> 3.0 / 2.0
|
||||
2 -> 4.0 / 2.0
|
||||
3 -> 5.0 / 2.0
|
||||
4 -> 6.0 / 2.0
|
||||
5 -> 7.0 / 2.0
|
||||
6 -> 8.0 / 2.0
|
||||
else -> 1.0
|
||||
}
|
||||
}
|
||||
|
||||
fun toBattleDTO(ally: Boolean, forceReveal: Boolean = true): BattlePokemonDTO {
|
||||
val moves = this.revealedMoves.toMutableList()
|
||||
for (i in 0 until 4) {
|
||||
if (moves[i] == null) {
|
||||
val move = effectedPokemon.moveSet.getMovesWithNulls()[i]
|
||||
if (move == null) continue
|
||||
moves[i] = MoveDTO(move.displayName, 0)
|
||||
}
|
||||
}
|
||||
|
||||
val boostMultipliers = boosts.mapValues { getBoostMultiplier(it.key) }.filter { it.value != 1.0 }
|
||||
|
||||
val isTeamSheet = ::actor.isInitialized && actor.battle.format.isOpenTeamSheet
|
||||
if (ally || isTeamSheet) {
|
||||
val allyDto = BattleInitializePacket.ActiveBattlePokemonDTO.fromPokemon(this, true, getIllusion())
|
||||
return BattlePokemonDTO(
|
||||
this.effectedPokemon.uuid,
|
||||
this.effectedPokemon.isFainted(),
|
||||
this.effectedPokemon.ability.name,
|
||||
moves,
|
||||
this.effectedPokemon.heldItem,
|
||||
boostMultipliers,
|
||||
effectedPokemon.speed,
|
||||
terastallized = terastallized,
|
||||
allyDto
|
||||
)
|
||||
}
|
||||
else {
|
||||
val nonAllyDto = BattleInitializePacket.ActiveBattlePokemonDTO.fromPokemon(this, false, getIllusion())
|
||||
return BattlePokemonDTO(
|
||||
this.effectedPokemon.uuid,
|
||||
this.effectedPokemon.isFainted(),
|
||||
revealedAbility,
|
||||
revealedMoves,
|
||||
revealedHeldItem,
|
||||
boostMultipliers,
|
||||
speed = null,
|
||||
terastallized = terastallized,
|
||||
if (forceReveal || revealed) nonAllyDto else null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateBattleActorInformation(uuids: List<UUID>) {
|
||||
uuids.forEach {
|
||||
if (actor.uuid == it || actor.battle.format.isOpenTeamSheet) {
|
||||
it.getPlayer()?.sendPacket(
|
||||
BattleActorInformationPacket(actor.uuid, toBattleDTO(true))
|
||||
)
|
||||
}
|
||||
else {
|
||||
it.getPlayer()?.sendPacket(
|
||||
BattleActorInformationPacket(actor.uuid, toBattleDTO(false))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sendUpdate() {
|
||||
actor.sendUpdate(BattleUpdateTeamPokemonPacket(effectedPokemon))
|
||||
this.revealed = true
|
||||
val uuids = actor.battle.actors.map { it.uuid } + actor.battle.spectators
|
||||
updateBattleActorInformation(uuids)
|
||||
}
|
||||
|
||||
fun isSentOut() = actor.battle.activePokemon.any { it.battlePokemon == this }
|
||||
fun canBeSentOut() =
|
||||
if (actor.request?.side?.pokemon?.any { it.reviving } == true) {
|
||||
!isSentOut() && !willBeSwitchedIn && health <= 0
|
||||
|
||||
fun canBeSentOut(): Boolean {
|
||||
val reviving = actor.request?.side?.pokemon?.any { it.reviving } == true
|
||||
println("${actor.getName().string} ${originalPokemon.species.name} canBeSentOut: reviving: $reviving isSentOut: ${isSentOut()} willBeSwitchedIn: $willBeSwitchedIn health: $health")
|
||||
|
||||
if (reviving) {
|
||||
return !isSentOut() && !willBeSwitchedIn && health <= 0
|
||||
} else {
|
||||
!isSentOut() && !willBeSwitchedIn && health > 0
|
||||
return !isSentOut() && !willBeSwitchedIn && health > 0
|
||||
}
|
||||
}
|
||||
|
||||
fun setBattleFeature(formName: String, enabled: Boolean) {
|
||||
this.effectedPokemon.getFeature<BattleFormFeature>(formName)?.let {
|
||||
it.enabled = enabled
|
||||
this.effectedPokemon.updateAspects()
|
||||
}
|
||||
}
|
||||
|
||||
fun clearBattleFeatures() {
|
||||
var modified = false
|
||||
|
||||
this.effectedPokemon.features.forEach {
|
||||
|
||||
if (it is BattleFormFeature) {
|
||||
it.enabled = false
|
||||
modified = true
|
||||
}
|
||||
}
|
||||
|
||||
fun getIllusion(): BattlePokemon? = this.actor.activePokemon.find { it.battlePokemon == this }?.illusion
|
||||
if (modified) {
|
||||
this.effectedPokemon.updateAspects()
|
||||
}
|
||||
}
|
||||
|
||||
fun getIllusion(): BattlePokemon? {
|
||||
if (!::actor.isInitialized) return null
|
||||
return this.actor.activePokemon.find { it.battlePokemon == this }?.illusion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.battles.timers
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
class BasicTimer(override val battle: PokemonBattle, override val actor: PlayerBattleActor, val duration: Duration) : PlayerBattleTimer {
|
||||
val endTime = Instant.now().plusSeconds(duration.toSeconds())
|
||||
|
||||
override fun startTurn() {}
|
||||
override fun selection() {}
|
||||
override fun tick() {}
|
||||
|
||||
override fun mustChooseBy(): Instant {
|
||||
return endTime
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.timers
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.battles.ForfeitActionResponse
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import java.time.Instant
|
||||
|
||||
interface PlayerBattleTimer {
|
||||
val actor: PlayerBattleActor
|
||||
val battle: PokemonBattle
|
||||
|
||||
fun startTurn()
|
||||
fun selection()
|
||||
fun tick()
|
||||
fun mustChooseBy(): Instant
|
||||
|
||||
fun timeout() {
|
||||
actor.setActionResponses(listOf(ForfeitActionResponse()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.timers
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.battles.ForfeitActionResponse
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
class ShowdownTimer(override val battle: PokemonBattle, override val actor: PlayerBattleActor): PlayerBattleTimer {
|
||||
private lateinit var turnStart: Instant
|
||||
private var secondsRemaining = 210
|
||||
private var hasSelected = false
|
||||
|
||||
init {
|
||||
checkForStreamerIncrease()
|
||||
}
|
||||
|
||||
/**
|
||||
* For streamers, we introduce a permission that doubles this timer as a deterrence of stream sniping.
|
||||
* We also have a similar system for notifying streamers when the opponent has used a move (so they can choose last.)
|
||||
*/
|
||||
fun checkForStreamerIncrease() {
|
||||
val player = actor.uuid.getPlayer() ?: return
|
||||
if (!Cobblemon.permissionValidator.hasPermission(player, "battle.mode.streamer", 4)) return
|
||||
secondsRemaining *= 2
|
||||
}
|
||||
|
||||
override fun tick() {
|
||||
if (hasSelected) return
|
||||
val secondsSinceStart = Duration.between(turnStart, Instant.now()).seconds
|
||||
if (secondsRemaining - secondsSinceStart <= 0) timeout()
|
||||
}
|
||||
|
||||
override fun selection() {
|
||||
val secondsSinceStart = Duration.between(turnStart, Instant.now()).seconds.toInt()
|
||||
secondsRemaining = secondsRemaining - secondsSinceStart + 10
|
||||
hasSelected = true
|
||||
}
|
||||
|
||||
override fun startTurn() {
|
||||
turnStart = Instant.now()
|
||||
hasSelected = false
|
||||
}
|
||||
|
||||
override fun mustChooseBy(): Instant {
|
||||
return Instant.now().plusSeconds(secondsRemaining.toLong())
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import com.cobblemon.mod.common.api.storage.player.client.ClientTMMoveManager
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
|
||||
import com.cobblemon.mod.common.block.entity.TintBlockEntity
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattle
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleInformationRepository
|
||||
import com.cobblemon.mod.common.client.gui.PartyOverlay
|
||||
import com.cobblemon.mod.common.client.gui.PartyOverlayDataControl
|
||||
import com.cobblemon.mod.common.client.gui.RideControlsOverlay
|
||||
@@ -490,6 +491,7 @@ object CobblemonClient {
|
||||
fun endBattle() {
|
||||
battle = null
|
||||
battleOverlay.lastKnownBattle = null
|
||||
ClientBattleInformationRepository.mustChooseBy = null
|
||||
BattleMusicController.endMusic()
|
||||
}
|
||||
|
||||
|
||||
@@ -38,13 +38,13 @@ class ClientBattle(
|
||||
knowledge = wildMonSpecies?.let {
|
||||
//FIXME: Better knowledge checking based on aspects/form and such
|
||||
CobblemonClient.clientPokedexData.getKnowledgeForSpecies(it)
|
||||
} ?: PokedexEntryProgress.UNREGISTERED
|
||||
} ?: PokedexEntryProgress.NONE
|
||||
}
|
||||
else {
|
||||
knowledge = PokedexEntryProgress.UNREGISTERED
|
||||
knowledge = PokedexEntryProgress.NONE
|
||||
}
|
||||
}
|
||||
var knowledge = PokedexEntryProgress.UNREGISTERED
|
||||
var knowledge = PokedexEntryProgress.NONE
|
||||
|
||||
val sides: Array<ClientBattleSide>
|
||||
get() = arrayOf(side1, side2)
|
||||
@@ -84,6 +84,7 @@ class ClientBattle(
|
||||
pendingActionRequests.map { it.response!! }
|
||||
)
|
||||
)
|
||||
ClientBattleInformationRepository.mustChooseBy = null
|
||||
mustChoose = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.battle
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleInformationDTO
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
|
||||
import java.time.Instant
|
||||
import java.util.UUID
|
||||
|
||||
object ClientBattleInformationRepository {
|
||||
val actors = mutableMapOf<UUID, MutableList<BattlePokemonDTO>>()
|
||||
val battles = mutableMapOf<UUID, BattleInformationDTO>()
|
||||
val actorActualSides = mutableMapOf<UUID, Int>()
|
||||
var mustChooseBy: Instant? = null
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.battle
|
||||
|
||||
import net.minecraft.util.FormattedCharSequence
|
||||
import net.minecraft.network.chat.Component
|
||||
|
||||
/**
|
||||
* A wrapper around the client battle message information. This is essentially an appending list of all
|
||||
@@ -19,15 +19,15 @@ import net.minecraft.util.FormattedCharSequence
|
||||
* @since June 24th, 2022
|
||||
*/
|
||||
class ClientBattleMessageQueue {
|
||||
var listeners = mutableListOf<(FormattedCharSequence) -> Unit>()
|
||||
private val messages = mutableListOf<FormattedCharSequence>()
|
||||
var listeners = mutableListOf<(Component) -> Unit>()
|
||||
private val messages = mutableListOf<Component>()
|
||||
|
||||
fun add(messages: Iterable<FormattedCharSequence>) {
|
||||
fun add(messages: Iterable<Component>) {
|
||||
this.messages.addAll(messages)
|
||||
listeners.forEach { listener -> messages.forEach(listener) }
|
||||
}
|
||||
|
||||
fun subscribe(listener: (FormattedCharSequence) -> Unit) {
|
||||
fun subscribe(listener: (Component) -> Unit) {
|
||||
this.listeners.add(listener)
|
||||
messages.forEach(listener)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.types.ElementalType
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
|
||||
object MoveEffectivenessCalculator {
|
||||
|
||||
fun getMoveEffectiveness(attack: ElementalType, defenderType1: ElementalType, defenderType2: ElementalType?): Effectiveness {
|
||||
val type1Effectiveness = getEffectiveness(attack, defenderType1)
|
||||
val type2Effectiveness = if (defenderType2 == null) 1f else getEffectiveness(attack, defenderType2)
|
||||
val typeEffectiveness = type1Effectiveness * type2Effectiveness
|
||||
|
||||
return when {
|
||||
typeEffectiveness == 0f -> Effectiveness.IMMUNE
|
||||
typeEffectiveness == 0.25f -> Effectiveness.MOSTLY_INEFFECTIVE
|
||||
typeEffectiveness == 0.5f -> Effectiveness.NOT_VERY_EFFECTIVE
|
||||
typeEffectiveness == 1f -> Effectiveness.EFFECTIVE
|
||||
typeEffectiveness == 2f -> Effectiveness.SUPER_EFFECTIVE
|
||||
typeEffectiveness == 4f -> Effectiveness.EXTREMELY_EFFECTIVE
|
||||
else -> Effectiveness.IMMUNE
|
||||
}
|
||||
}
|
||||
|
||||
fun getEffectiveness(attack: ElementalType, defender: ElementalType): Float {
|
||||
if (isImmune(attack, defender)) return 0f
|
||||
if (isSuperEffective(attack, defender)) return 2f
|
||||
if (isNotVeryEffective(attack, defender)) return 0.5f
|
||||
return 1f
|
||||
}
|
||||
|
||||
fun isImmune(attack: ElementalType, defender: ElementalType): Boolean {
|
||||
when (attack) {
|
||||
ElementalTypes.NORMAL -> return defender == ElementalTypes.GHOST
|
||||
ElementalTypes.FIRE -> return false
|
||||
ElementalTypes.WATER -> return false
|
||||
ElementalTypes.GRASS -> return false
|
||||
ElementalTypes.ELECTRIC -> return defender == ElementalTypes.GROUND
|
||||
ElementalTypes.ICE -> return false
|
||||
ElementalTypes.FIGHTING -> return defender == ElementalTypes.GHOST
|
||||
ElementalTypes.POISON -> return defender == ElementalTypes.STEEL
|
||||
ElementalTypes.GROUND -> return defender == ElementalTypes.FLYING
|
||||
ElementalTypes.FLYING -> return false
|
||||
ElementalTypes.PSYCHIC -> return defender == ElementalTypes.DARK
|
||||
ElementalTypes.BUG -> return false
|
||||
ElementalTypes.ROCK -> return false
|
||||
ElementalTypes.GHOST -> return defender == ElementalTypes.NORMAL
|
||||
ElementalTypes.DRAGON -> return defender == ElementalTypes.FAIRY
|
||||
ElementalTypes.DARK -> return false
|
||||
ElementalTypes.STEEL -> return false
|
||||
ElementalTypes.FAIRY -> return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun isSuperEffective(attack: ElementalType, defender: ElementalType): Boolean {
|
||||
when (attack) {
|
||||
ElementalTypes.NORMAL -> return false
|
||||
ElementalTypes.FIRE -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.ICE, ElementalTypes.BUG, ElementalTypes.STEEL)
|
||||
ElementalTypes.WATER -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.GROUND, ElementalTypes.ROCK)
|
||||
ElementalTypes.GRASS -> return defender in listOf(ElementalTypes.WATER, ElementalTypes.GROUND, ElementalTypes.ROCK)
|
||||
ElementalTypes.ELECTRIC -> return defender in listOf(ElementalTypes.WATER, ElementalTypes.FLYING)
|
||||
ElementalTypes.ICE -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.GROUND, ElementalTypes.FLYING, ElementalTypes.DRAGON)
|
||||
ElementalTypes.FIGHTING -> return defender in listOf(ElementalTypes.NORMAL, ElementalTypes.ICE, ElementalTypes.ROCK, ElementalTypes.DARK, ElementalTypes.STEEL)
|
||||
ElementalTypes.POISON -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.FAIRY)
|
||||
ElementalTypes.GROUND -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.ELECTRIC, ElementalTypes.POISON, ElementalTypes.ROCK, ElementalTypes.STEEL)
|
||||
ElementalTypes.FLYING -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.FIGHTING, ElementalTypes.BUG)
|
||||
ElementalTypes.PSYCHIC -> return defender in listOf(ElementalTypes.FIGHTING, ElementalTypes.POISON)
|
||||
ElementalTypes.BUG -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.PSYCHIC, ElementalTypes.DARK)
|
||||
ElementalTypes.ROCK -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.ICE, ElementalTypes.FLYING, ElementalTypes.BUG)
|
||||
ElementalTypes.GHOST -> return defender in listOf(ElementalTypes.PSYCHIC, ElementalTypes.GHOST)
|
||||
ElementalTypes.DRAGON -> return defender in listOf(ElementalTypes.DRAGON)
|
||||
ElementalTypes.DARK -> return defender in listOf(ElementalTypes.PSYCHIC, ElementalTypes.GHOST)
|
||||
ElementalTypes.STEEL -> return defender in listOf(ElementalTypes.ICE, ElementalTypes.ROCK, ElementalTypes.FAIRY)
|
||||
ElementalTypes.FAIRY -> return defender in listOf(ElementalTypes.FIGHTING, ElementalTypes.DRAGON, ElementalTypes.DARK)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun isNotVeryEffective(attack: ElementalType, defender: ElementalType): Boolean {
|
||||
when (attack) {
|
||||
ElementalTypes.NORMAL -> return defender in listOf(ElementalTypes.ROCK, ElementalTypes.STEEL)
|
||||
ElementalTypes.FIRE -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.WATER, ElementalTypes.ROCK, ElementalTypes.DRAGON)
|
||||
ElementalTypes.WATER -> return defender in listOf(ElementalTypes.WATER, ElementalTypes.GRASS, ElementalTypes.DRAGON)
|
||||
ElementalTypes.GRASS -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.GRASS, ElementalTypes.POISON, ElementalTypes.FLYING, ElementalTypes.BUG, ElementalTypes.DRAGON, ElementalTypes.STEEL)
|
||||
ElementalTypes.ELECTRIC -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.ELECTRIC, ElementalTypes.DRAGON)
|
||||
ElementalTypes.ICE -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.WATER, ElementalTypes.ICE, ElementalTypes.STEEL)
|
||||
ElementalTypes.FIGHTING -> return defender in listOf(ElementalTypes.POISON, ElementalTypes.FLYING, ElementalTypes.PSYCHIC, ElementalTypes.BUG, ElementalTypes.FAIRY)
|
||||
ElementalTypes.POISON -> return defender in listOf(ElementalTypes.POISON, ElementalTypes.GROUND, ElementalTypes.ROCK, ElementalTypes.GHOST)
|
||||
ElementalTypes.GROUND -> return defender in listOf(ElementalTypes.GRASS, ElementalTypes.BUG)
|
||||
ElementalTypes.FLYING -> return defender in listOf(ElementalTypes.ELECTRIC, ElementalTypes.ROCK, ElementalTypes.STEEL)
|
||||
ElementalTypes.PSYCHIC -> return defender in listOf(ElementalTypes.PSYCHIC, ElementalTypes.STEEL)
|
||||
ElementalTypes.BUG -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.FIGHTING, ElementalTypes.POISON, ElementalTypes.FLYING, ElementalTypes.GHOST, ElementalTypes.STEEL, ElementalTypes.FAIRY)
|
||||
ElementalTypes.ROCK -> return defender in listOf(ElementalTypes.FIGHTING, ElementalTypes.GROUND, ElementalTypes.STEEL)
|
||||
ElementalTypes.GHOST -> return defender in listOf(ElementalTypes.DARK)
|
||||
ElementalTypes.DRAGON -> return defender in listOf(ElementalTypes.STEEL)
|
||||
ElementalTypes.DARK -> return defender in listOf(ElementalTypes.FIGHTING, ElementalTypes.DARK, ElementalTypes.FAIRY)
|
||||
ElementalTypes.STEEL -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.WATER, ElementalTypes.ELECTRIC, ElementalTypes.STEEL)
|
||||
ElementalTypes.FAIRY -> return defender in listOf(ElementalTypes.FIRE, ElementalTypes.POISON, ElementalTypes.STEEL)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum class Effectiveness {
|
||||
EXTREMELY_EFFECTIVE,
|
||||
SUPER_EFFECTIVE,
|
||||
EFFECTIVE,
|
||||
NOT_VERY_EFFECTIVE,
|
||||
MOSTLY_INEFFECTIVE,
|
||||
IMMUNE
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.battle.preview
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.TeamPreviewSelectPokemonPacket
|
||||
import net.minecraft.network.chat.Component
|
||||
import java.time.Instant
|
||||
|
||||
class ClientBattleTeamPreview(
|
||||
val selections: Int,
|
||||
val team: List<BattlePokemonDTO?>,
|
||||
val opponent: List<BattlePokemonDTO?>,
|
||||
val mustPickBy: Instant,
|
||||
val opponentName: Component
|
||||
) {
|
||||
val selection = mutableListOf<BattlePokemonDTO>()
|
||||
|
||||
fun select(pokemon: BattlePokemonDTO) {
|
||||
selection.add(pokemon)
|
||||
sendChangePacket()
|
||||
}
|
||||
|
||||
fun unselect(pokemon: BattlePokemonDTO) {
|
||||
selection.remove(pokemon)
|
||||
sendChangePacket()
|
||||
}
|
||||
|
||||
private fun sendChangePacket() {
|
||||
val indexes = selection.map { team.indexOf(it) }
|
||||
if (indexes.any { it == -1 }) return
|
||||
CobblemonNetwork.sendToServer(TeamPreviewSelectPokemonPacket(indexes.toSet()))
|
||||
}
|
||||
}
|
||||
@@ -16,13 +16,14 @@ import net.minecraft.client.gui.GuiGraphics
|
||||
class TypeIcon(
|
||||
val x: Number,
|
||||
val y: Number,
|
||||
val type: ElementalType,
|
||||
val type: ElementalType? = null,
|
||||
val secondaryType: ElementalType? = null,
|
||||
val centeredX: Boolean = false,
|
||||
val small: Boolean = false,
|
||||
val secondaryOffset: Float = 15F,
|
||||
val doubleCenteredOffset: Float = 7.5F,
|
||||
val opacity: Float = 1F
|
||||
val opacity: Float = 1F,
|
||||
val isStellar: Boolean = false // TODO: Make this less hacky, make type & secondaryType take tera types instead of elemental types
|
||||
) {
|
||||
companion object {
|
||||
private const val TYPE_ICON_DIAMETER = 36
|
||||
@@ -30,6 +31,7 @@ class TypeIcon(
|
||||
|
||||
private val typesResource = cobblemonResource("textures/gui/types.png")
|
||||
private val smallTypesResource = cobblemonResource("textures/gui/types_small.png")
|
||||
val stellarType = cobblemonResource("textures/gui/battle/stellar_type.png")
|
||||
}
|
||||
|
||||
fun render(context: GuiGraphics) {
|
||||
@@ -51,17 +53,32 @@ class TypeIcon(
|
||||
)
|
||||
}
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = if (small) smallTypesResource else typesResource,
|
||||
x = (x.toFloat() - offsetX) / SCALE,
|
||||
y = y.toFloat() / SCALE,
|
||||
height = diameter,
|
||||
width = diameter,
|
||||
uOffset = diameter * type.textureXMultiplier.toFloat() + 0.1,
|
||||
textureWidth = diameter * 18,
|
||||
alpha = opacity,
|
||||
scale = SCALE
|
||||
)
|
||||
if (type != null && !isStellar) {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = if (small) smallTypesResource else typesResource,
|
||||
x = (x.toFloat() - offsetX) / SCALE,
|
||||
y = y.toFloat() / SCALE,
|
||||
height = diameter,
|
||||
width = diameter,
|
||||
uOffset = diameter * type.textureXMultiplier.toFloat() + 0.1,
|
||||
textureWidth = diameter * 18,
|
||||
alpha = opacity,
|
||||
scale = SCALE
|
||||
)
|
||||
}
|
||||
else {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = stellarType,
|
||||
x = (x.toFloat() - offsetX) / SCALE,
|
||||
y = y.toFloat() / SCALE,
|
||||
height = 36,
|
||||
width = 36,
|
||||
textureWidth = 36,
|
||||
alpha = opacity,
|
||||
scale = SCALE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,15 @@ import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleActionSelectio
|
||||
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleBackButton
|
||||
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleGeneralActionSelection
|
||||
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleSwitchPokemonSelection
|
||||
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleTeamInfoSelection
|
||||
import com.cobblemon.mod.common.client.gui.battle.subscreen.ForfeitConfirmationSelection
|
||||
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleMessagePane
|
||||
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleOptionTile
|
||||
import com.cobblemon.mod.common.client.keybind.boundKey
|
||||
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.RemoveSpectatorPacket
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.client.Minecraft
|
||||
@@ -44,10 +47,18 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
val forfeitResource = cobblemonResource("textures/gui/battle/battle_menu_forfeit.png")
|
||||
}
|
||||
|
||||
private lateinit var messagePane: BattleMessagePane
|
||||
lateinit var messagePane: BattleMessagePane
|
||||
var opacity = 0F
|
||||
val actor = CobblemonClient.battle?.side1?.actors?.find { it.uuid == Minecraft.getInstance().player?.uuid }
|
||||
val specBackButton = BattleBackButton(12f, Minecraft.getInstance().window.guiScaledHeight - 32f)
|
||||
val specBackButton = BattleBackButton(OPTION_ROOT_X - 3F, Minecraft.getInstance().window.guiScaledHeight - 22F)
|
||||
val teamInfoButton = BattleOptionTile(
|
||||
battleGUI = this,
|
||||
x = OPTION_ROOT_X,
|
||||
y = Minecraft.getInstance().window.guiScaledHeight - OPTION_VERTICAL_OFFSET + BattleOptionTile.OPTION_HEIGHT + OPTION_HORIZONTAL_SPACING,
|
||||
resource = BattleGeneralActionSelection.battleInfoSelection,
|
||||
text = "cobblemon.battle.ui.team_info.button".asTranslated(),
|
||||
onClick = {}
|
||||
)
|
||||
|
||||
var queuedActions = mutableListOf<() -> Unit>()
|
||||
|
||||
@@ -116,21 +127,13 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
}
|
||||
}
|
||||
|
||||
if (battle.spectating) {
|
||||
if (battle.spectating && !BattleTeamInfoSelection.visible) {
|
||||
specBackButton.render(context, mouseX, mouseY, delta)
|
||||
teamInfoButton.render(context, mouseX, mouseY, delta)
|
||||
}
|
||||
|
||||
val currentSelection = getCurrentActionSelection()
|
||||
if (currentSelection == null || currentSelection is BattleGeneralActionSelection ) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = battleLang("ui.hide_label", PartySendBinding.boundKey().displayName),
|
||||
x = Minecraft.getInstance().window.guiScaledWidth / 2,
|
||||
y = (Minecraft.getInstance().window.guiScaledHeight / 5),
|
||||
opacity = 0.75F * opacity,
|
||||
centered = true
|
||||
)
|
||||
} else if (currentSelection is ForfeitConfirmationSelection) {
|
||||
if (currentSelection is ForfeitConfirmationSelection) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = battleLang("ui.forfeit_confirmation", PartySendBinding.boundKey().displayName),
|
||||
@@ -144,6 +147,8 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
|
||||
queuedActions.forEach { it() }
|
||||
queuedActions.clear()
|
||||
CobblemonClient.battleOverlay.mouseX = mouseX
|
||||
CobblemonClient.battleOverlay.mouseY = mouseY
|
||||
}
|
||||
|
||||
override fun renderBackground(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
@@ -151,8 +156,6 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
}
|
||||
|
||||
fun deriveRootActionSelection(actor: ClientBattleActor, request: SingleActionRequest): BattleActionSelection? {
|
||||
|
||||
|
||||
return if (request.forceSwitch) {
|
||||
BattleSwitchPokemonSelection(this, request)
|
||||
} else {
|
||||
@@ -173,6 +176,7 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
override fun onClose() {
|
||||
super.onClose()
|
||||
CobblemonClient.battle?.minimised = true
|
||||
BattleTeamInfoSelection.visible = false
|
||||
}
|
||||
|
||||
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
|
||||
@@ -193,9 +197,15 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
}
|
||||
|
||||
val battle = CobblemonClient.battle
|
||||
if (battle?.spectating == true && specBackButton.isHovered(mouseX, mouseY)) {
|
||||
RemoveSpectatorPacket(battle.battleId).sendToServer()
|
||||
CobblemonClient.endBattle()
|
||||
if (battle?.spectating == true && !BattleTeamInfoSelection.visible) {
|
||||
if (specBackButton.isHovered(mouseX, mouseY)) {
|
||||
RemoveSpectatorPacket(battle.battleId).sendToServer()
|
||||
CobblemonClient.endBattle()
|
||||
}
|
||||
if (teamInfoButton.isHovered(mouseX, mouseY)) {
|
||||
changeActionSelection(BattleTeamInfoSelection(this))
|
||||
BattleTeamInfoSelection.visible = true
|
||||
}
|
||||
}
|
||||
return super.mouseClicked(mouseX, mouseY, button)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.battle.preview
|
||||
|
||||
import com.cobblemon.mod.common.client.battle.preview.ClientBattleTeamPreview
|
||||
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.screens.Screen
|
||||
|
||||
class TeamPreviewGUI(val preview: ClientBattleTeamPreview) : Screen(battleLang("gui.team_preview")), CobblemonRenderable {
|
||||
private lateinit var widget: TeamPreviewWidget
|
||||
|
||||
override fun init() {
|
||||
super.init()
|
||||
widget = addRenderableWidget(TeamPreviewWidget(preview))
|
||||
}
|
||||
|
||||
override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
|
||||
super.render(guiGraphics, mouseX, mouseY, partialTick)
|
||||
}
|
||||
|
||||
override fun shouldCloseOnEsc() = false
|
||||
|
||||
override fun isPauseScreen() = false
|
||||
}
|
||||
@@ -0,0 +1,973 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.battle.preview
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.abilities.Abilities
|
||||
import com.cobblemon.mod.common.api.gui.ParentWidget
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.gui.renderSprite
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
import com.cobblemon.mod.common.api.text.font
|
||||
import com.cobblemon.mod.common.api.text.green
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.api.text.yellow
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleInformationRepository
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattlePokemon
|
||||
import com.cobblemon.mod.common.client.battle.preview.ClientBattleTeamPreview
|
||||
import com.cobblemon.mod.common.client.gui.TypeIcon
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay.Companion.PORTRAIT_DIAMETER
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay.Companion.SCALE
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay.Companion.questionMarkIcon
|
||||
import com.cobblemon.mod.common.client.render.SpriteType
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.pokemon.Species
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.toHex
|
||||
import com.mojang.blaze3d.platform.InputConstants
|
||||
import com.mojang.blaze3d.platform.Lighting
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import com.mojang.math.Axis
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.narration.NarratableEntry
|
||||
import net.minecraft.client.gui.narration.NarrationElementOutput
|
||||
import net.minecraft.client.renderer.LightTexture
|
||||
import net.minecraft.client.renderer.RenderType
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
import net.minecraft.client.sounds.SoundManager
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.max
|
||||
|
||||
class TeamPreviewWidget(val preview: ClientBattleTeamPreview) : ParentWidget(
|
||||
pX = 0,
|
||||
pY = if (Minecraft.getInstance().window.guiScaledHeight > 304) (Minecraft.getInstance().window.guiScaledHeight / 2) - (BACKGROUND_HEIGHT / 2)
|
||||
else Minecraft.getInstance().window.guiScaledHeight - (BACKGROUND_HEIGHT + 78),
|
||||
pWidth = Minecraft.getInstance().window.guiScaledWidth,
|
||||
pHeight = Minecraft.getInstance().window.guiScaledHeight,
|
||||
"cobblemon.battle.ui.team_info".asTranslated()
|
||||
) {
|
||||
companion object {
|
||||
const val SLOT_HORIZONTAL_SPACING = 4F
|
||||
const val SLOT_VERTICAL_SPACING = 2F
|
||||
|
||||
const val BACKGROUND_HEIGHT = 148
|
||||
val underlayTexture = cobblemonResource("textures/gui/battle/selection_underlay.png")
|
||||
val title = cobblemonResource("textures/gui/battle/team_preview_title.png")
|
||||
val checkmark = cobblemonResource("textures/gui/battle/checkmark.png")
|
||||
var visible = false
|
||||
}
|
||||
|
||||
val teamTiles = mutableListOf<PokemonTile>()
|
||||
val opponentTiles = mutableListOf<PokemonTile>()
|
||||
|
||||
init {
|
||||
addTiles()
|
||||
}
|
||||
|
||||
fun addTiles() {
|
||||
preview.team.forEachIndexed { index, pokemon ->
|
||||
if (pokemon == null) return@forEachIndexed
|
||||
val (slotX, slotY) = getSlotPosition(index, 1)
|
||||
teamTiles.add(PokemonTile(slotX, slotY, pokemon, this, false))
|
||||
}
|
||||
preview.opponent.forEachIndexed { index, pokemon ->
|
||||
if (pokemon == null) return@forEachIndexed
|
||||
val (slotX, slotY) = getSlotPosition(index, 2)
|
||||
opponentTiles.add(PokemonTile(slotX, slotY, pokemon, this, true))
|
||||
}
|
||||
}
|
||||
|
||||
fun getSlotPosition(index: Int, side: Int): Pair<Float, Float> {
|
||||
val startX = if (side == 1) 10 else width - 10 - (41 + SLOT_HORIZONTAL_SPACING) * 2
|
||||
val startY = y + 15
|
||||
val row = index / 2
|
||||
val column = index % 2
|
||||
val slotX = startX.toFloat() + column * (SLOT_HORIZONTAL_SPACING + 41)
|
||||
val slotY = startY.toFloat() + row * (SLOT_VERTICAL_SPACING + 39)
|
||||
return Pair(slotX, slotY)
|
||||
}
|
||||
|
||||
private fun getHoveredTile(team: Boolean, mouseX: Int, mouseY: Int): Int {
|
||||
val tiles = if (team) teamTiles else opponentTiles
|
||||
return tiles.indexOfFirst { it.isHovered(mouseX.toDouble(), mouseY.toDouble()) }
|
||||
}
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
val matrixStack = context.pose()
|
||||
val mc = Minecraft.getInstance()
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = underlayTexture,
|
||||
x = x,
|
||||
y = y,
|
||||
width = width,
|
||||
height = BACKGROUND_HEIGHT
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = title,
|
||||
x = (mc.window.guiScaledWidth / 2) - (103 / 2),
|
||||
y = 4,
|
||||
width = 103,
|
||||
height = 22
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.team_preview.title".text().bold(),
|
||||
x = (mc.window.guiScaledWidth / 2),
|
||||
y = 6,
|
||||
scale = 0.75f,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
val totalSecondsRemaining = Duration.between(Instant.now(), preview.mustPickBy).seconds
|
||||
val minutesRemaining = max(0, totalSecondsRemaining / 60)
|
||||
val secondsRemaining = max(0, totalSecondsRemaining % 60)
|
||||
val secondsRemainingStr = String.format("%02d", secondsRemaining)
|
||||
val timer = "$minutesRemaining:$secondsRemainingStr"
|
||||
val text = when {
|
||||
totalSecondsRemaining <= 15 -> timer.text().red().bold()
|
||||
totalSecondsRemaining <= 30 -> timer.text().yellow().bold()
|
||||
else -> timer.text().bold()
|
||||
}
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = text,
|
||||
x = (mc.window.guiScaledWidth / 2) + 2,
|
||||
y = 15,
|
||||
scale = 0.75f,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
|
||||
val ellipsis = ".".repeat(3 - (secondsRemaining % 3).toInt())
|
||||
if (preview.selection.size == preview.selections) {
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = checkmark,
|
||||
x = (mc.window.guiScaledWidth / 2) - 39,
|
||||
y = 24,
|
||||
width = 8,
|
||||
height = 6
|
||||
)
|
||||
val offset = when (ellipsis.length) {
|
||||
1 -> 0.5
|
||||
2 -> 1.0
|
||||
else -> 2.0
|
||||
}
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.team_preview.waiting".asTranslated(ellipsis).bold(),
|
||||
x = (mc.window.guiScaledWidth / 2) + 2.0 + offset,
|
||||
y = 26,
|
||||
scale = 0.5f,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = cobblemonResource("textures/gui/battle/name_plate.png"),
|
||||
x = 10,
|
||||
y = y - 8,
|
||||
width = 128,
|
||||
height = 8
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = mc.player!!.name.string.text(),
|
||||
y = y - 8 + 2,
|
||||
x = x + 14 + 10,
|
||||
scale = SCALE,
|
||||
shadow = true
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = cobblemonResource("textures/gui/battle/nameplate_reversed.png"),
|
||||
x = mc.window.guiScaledWidth - 14 - 128,
|
||||
y = y - 8,
|
||||
width = 128,
|
||||
height = 8
|
||||
)
|
||||
|
||||
drawScaledTextJustifiedRight(
|
||||
context = context,
|
||||
text = preview.opponentName.string.text(),
|
||||
y = y - 8 + 2,
|
||||
x = mc.window.guiScaledWidth - 14 - 12,
|
||||
scale = SCALE,
|
||||
shadow = true
|
||||
)
|
||||
|
||||
teamTiles.forEachIndexed { index, tile ->
|
||||
val hoveredTile = getHoveredTile(true, mouseX, mouseY)
|
||||
if (shouldHide(hoveredTile, index, false)) return@forEachIndexed
|
||||
tile.render(context, mouseX.toDouble(), mouseY.toDouble(), delta)
|
||||
}
|
||||
opponentTiles.forEachIndexed { index, tile ->
|
||||
val hoveredTile = getHoveredTile(false, mouseX, mouseY)
|
||||
if (shouldHide(hoveredTile, index, true)) return@forEachIndexed
|
||||
tile.render(context, mouseX.toDouble(), mouseY.toDouble(), delta)
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldHide(hoveredTile: Int, currentTile: Int, reversed: Boolean): Boolean {
|
||||
if (hoveredTile != -1) {
|
||||
val hoveredRow = hoveredTile / 2
|
||||
val hoveredColumn = hoveredTile % 2
|
||||
val currentRow = currentTile / 2
|
||||
val currentColumn = currentTile % 2
|
||||
if (!reversed && hoveredColumn < currentColumn && hoveredRow <= currentRow) return true
|
||||
if (reversed && hoveredColumn > currentColumn && hoveredRow <= currentRow) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
|
||||
val tile = teamTiles.firstOrNull { it.isHovered(pMouseX, pMouseY) }
|
||||
if (tile != null && tile.pokemonDto in preview.team) {
|
||||
if (pButton == InputConstants.MOUSE_BUTTON_LEFT) {
|
||||
if (tile.pokemonDto !in preview.selection && preview.selection.size < preview.selections) {
|
||||
preview.select(tile.pokemonDto)
|
||||
}
|
||||
}
|
||||
else if (pButton == InputConstants.MOUSE_BUTTON_RIGHT) {
|
||||
if (tile.pokemonDto in preview.selection) {
|
||||
preview.unselect(tile.pokemonDto)
|
||||
}
|
||||
}
|
||||
}
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
return super.mouseClicked(pMouseX, pMouseY, pButton)
|
||||
}
|
||||
|
||||
override fun defaultButtonNarrationText(builder: NarrationElementOutput) {
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager) {
|
||||
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
}
|
||||
|
||||
override fun narrationPriority() = NarratableEntry.NarrationPriority.HOVERED
|
||||
|
||||
class PokemonTile(
|
||||
val x: Float,
|
||||
val y: Float,
|
||||
val pokemonDto: BattlePokemonDTO,
|
||||
val widget: TeamPreviewWidget,
|
||||
val reversed: Boolean
|
||||
) {
|
||||
companion object {
|
||||
const val SELECT_WIDTH = 41
|
||||
const val SELECT_HEIGHT = 39
|
||||
const val SCALE = 0.5F
|
||||
val expandedPokemonInfo = cobblemonResource("textures/gui/battle/expanded_pokemon_info_center.png")
|
||||
|
||||
val pokemonTile = cobblemonResource("textures/gui/battle/pokemon_tile.png")
|
||||
val pokemonTileReversed = cobblemonResource("textures/gui/battle/pokemon_tile_reversed.png")
|
||||
val pokemonTileDisabled = cobblemonResource("textures/gui/battle/pokemon_tile_disabled.png")
|
||||
val pokemonTileDisabledReversed = cobblemonResource("textures/gui/battle/pokemon_tile_disabled_reversed.png")
|
||||
val pokemonTileSelected = cobblemonResource("textures/gui/battle/pokemon_tile_selected.png")
|
||||
val pokemonTileSelectedReversed = cobblemonResource("textures/gui/battle/pokemon_tile_selected_reversed.png")
|
||||
|
||||
val hoverGapSelected = cobblemonResource("textures/gui/battle/hover_gap_selected.png")
|
||||
val hoverGap = cobblemonResource("textures/gui/battle/hover_gap.png")
|
||||
val hoverGapReversed = cobblemonResource("textures/gui/battle/hover_gap_reversed.png")
|
||||
|
||||
private val decimalFormat = DecimalFormat("0.00").also {
|
||||
it.roundingMode = RoundingMode.CEILING
|
||||
}
|
||||
}
|
||||
|
||||
val state = FloatingState()
|
||||
|
||||
fun isHovered(mouseX: Double, mouseY: Double) = mouseX in x..(x + SELECT_WIDTH) && mouseY in (y..(y + SELECT_HEIGHT))
|
||||
|
||||
fun render(context: GuiGraphics, mouseX: Double, mouseY: Double, deltaTicks: Float) {
|
||||
val pokemon = pokemonDto.activeBattlePokemonDTO ?: return
|
||||
val battlePokemon = ClientBattlePokemon(
|
||||
uuid = pokemon.uuid,
|
||||
properties = pokemon.properties,
|
||||
aspects = pokemon.aspects,
|
||||
displayName = pokemon.displayName,
|
||||
hpValue = pokemon.hpValue,
|
||||
maxHp = pokemon.maxHp,
|
||||
isHpFlat = pokemon.isFlatHp,
|
||||
status = pokemon.status,
|
||||
statChanges = pokemon.statChanges,
|
||||
)
|
||||
state.currentAspects = pokemon.aspects
|
||||
val matrixStack = context.pose()
|
||||
val isFainted = pokemonDto.fainted
|
||||
val isSelected = pokemonDto in widget.preview.selection
|
||||
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = cobblemonResource("textures/gui/battle/battle_info_underlay.png"),
|
||||
x = x + 5 + (if (reversed) 3 else 0),
|
||||
y = y + 4,
|
||||
width = 28,
|
||||
height = 28
|
||||
)
|
||||
val speciesToDisplay = battlePokemon.species
|
||||
val stateToDisplay = battlePokemon.state
|
||||
context.enableScissor(
|
||||
(x + 5 + (if (reversed) 3 else 0)).toInt(),
|
||||
(y + 5).toInt(),
|
||||
(x + 5 + 28 + (if (reversed) 3 else 0)).toInt(),
|
||||
(y + 5 + 28).toInt(),
|
||||
)
|
||||
matrixStack.pushPose()
|
||||
matrixStack.translate(
|
||||
x + (if (reversed) 3 else 0) + 5 + 28 / 2.0,
|
||||
y.toDouble() + 5 - 5.0,
|
||||
0.0
|
||||
)
|
||||
drawCustomPosablePortrait(
|
||||
identifier = speciesToDisplay.resourceIdentifier,
|
||||
matrixStack = matrixStack,
|
||||
scale = 18f,
|
||||
contextScale = speciesToDisplay.getForm(stateToDisplay.currentAspects).baseScale,
|
||||
reversed = reversed,
|
||||
doQuirks = false,
|
||||
state = stateToDisplay,
|
||||
partialTicks = if (Cobblemon.config.animateBattleTiles) deltaTicks else 0F
|
||||
)
|
||||
matrixStack.popPose()
|
||||
context.disableScissor()
|
||||
|
||||
val tile = if (reversed) pokemonTileReversed else pokemonTile
|
||||
val disabled = if (reversed) pokemonTileDisabledReversed else pokemonTileDisabled
|
||||
val selected = if (reversed) pokemonTileSelectedReversed else pokemonTileSelected
|
||||
if (isFainted) {
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = disabled,
|
||||
x = x,
|
||||
y = y,
|
||||
width = 41,
|
||||
height = 39
|
||||
)
|
||||
}
|
||||
else if (isSelected) {
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = selected,
|
||||
x = x,
|
||||
y = y,
|
||||
width = 41,
|
||||
height = 39
|
||||
)
|
||||
|
||||
val selectionIndex = widget.preview.selection.indexOf(pokemonDto)
|
||||
if (selectionIndex != -1) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = "${selectionIndex + 1}".text().bold(),
|
||||
x = x + 34,
|
||||
y = y + 29,
|
||||
scale = 1.25f,
|
||||
opacity = 1f,
|
||||
shadow = true
|
||||
)
|
||||
}
|
||||
}
|
||||
else {
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = tile,
|
||||
x = x,
|
||||
y = y,
|
||||
width = 41,
|
||||
height = 39
|
||||
)
|
||||
}
|
||||
|
||||
if (isHovered(mouseX, mouseY)) {
|
||||
renderExpandedInfo(context, isSelected, reversed, battlePokemon, pokemonDto)
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderExpandedInfo(context: GuiGraphics, selected: Boolean, reversed: Boolean, pokemon: ClientBattlePokemon, dto: BattlePokemonDTO) {
|
||||
context.pose().pushPose()
|
||||
context.pose().translate(0.0, 0.0, 300.0)
|
||||
val startX = if (reversed) x - 136 else x + 40
|
||||
val startY = y
|
||||
val species = pokemon.species
|
||||
val form = species.getForm(pokemon.state.currentAspects)
|
||||
val expandedInfoTexture = expandedPokemonInfo
|
||||
|
||||
if (selected) {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = hoverGapSelected,
|
||||
x = startX - 3,
|
||||
y = startY,
|
||||
height = 30,
|
||||
width = 4,
|
||||
textureHeight = 30,
|
||||
textureWidth = 4,
|
||||
)
|
||||
}
|
||||
else {
|
||||
val gap = if (reversed) hoverGapReversed else hoverGap
|
||||
val xOffset = if (reversed) 136 else -7
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = gap,
|
||||
x = startX + xOffset,
|
||||
y = startY,
|
||||
height = 36,
|
||||
width = 8,
|
||||
textureHeight = 36,
|
||||
textureWidth = 8,
|
||||
)
|
||||
}
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = expandedInfoTexture,
|
||||
x = startX,
|
||||
y = startY,
|
||||
height = 112,
|
||||
width = 137,
|
||||
textureHeight = 112,
|
||||
textureWidth = 137,
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.pokemon_form".asTranslated().bold(),
|
||||
x = startX + 36,
|
||||
y = startY + 6.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getFormText(species, form),
|
||||
x = startX + 36,
|
||||
y = startY + 13.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.buffs".asTranslated().bold(),
|
||||
x = startX + 36,
|
||||
y = startY + 23.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.attack.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 31.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.ATTACK)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 31.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.defence.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 39.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.DEFENCE)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 39.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.stats.special_attack".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 47.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.SPECIAL_ATTACK)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 47.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.stats.special_defence".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 55.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.SPECIAL_DEFENCE)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 55.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.speed.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 63.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.SPEED)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 63.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.accuracy.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 71.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.ACCURACY)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 71.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.evasion.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 79.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto?.buffs?.get(Stats.EVASION)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 79.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.ability".asTranslated().bold(),
|
||||
x = startX + 100,
|
||||
y = startY + 6.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
val ability = dto.ability?.let { Abilities.get(it) }
|
||||
val abilityName = ability?.displayName?.text() ?: "?".text()
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = abilityName,
|
||||
x = startX + 100,
|
||||
y = startY + 13.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.moves".asTranslated().bold(),
|
||||
x = startX + 100,
|
||||
y = startY + 23.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
if (dto.moves.size != 4) {
|
||||
for (i in 0 until 4) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "?".text(),
|
||||
x = startX + 100,
|
||||
y = startY + 31.5 + i * 8,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
}
|
||||
else {
|
||||
val questionMarkText = "?".text()
|
||||
dto.moves.forEachIndexed { index, moveDTO ->
|
||||
val move = moveDTO?.move ?: questionMarkText
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = move.string.text(),
|
||||
x = if (move == questionMarkText) startX + 100 else startX + 74,
|
||||
y = startY + 31.5 + index * 8,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = move == questionMarkText
|
||||
)
|
||||
|
||||
if (moveDTO != null) {
|
||||
drawScaledTextJustifiedRight(
|
||||
context = context,
|
||||
text = moveDTO.timesUsed.toString().text(),
|
||||
x = startX + 127.5,
|
||||
y = startY + 31.5 + index * 8,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dto.speed != null) {
|
||||
val speed = dto.speed
|
||||
val speedBoost = dto.buffs.get(Stats.SPEED) ?: 1.0
|
||||
val speedAfterBoost = (speed * speedBoost).toInt()
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.speed.name".asTranslated(),
|
||||
x = startX + 100,
|
||||
y = startY + 65.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = speedAfterBoost.toString().text(),
|
||||
x = startX + 100,
|
||||
y = startY + 72.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
else {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.speed_tier".asTranslated(),
|
||||
x = startX + 100,
|
||||
y = startY + 65.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
val speedBoost = dto.buffs.get(Stats.SPEED) ?: 1.0
|
||||
val speedTier = getSpeedRange(form, pokemon.level, speedBoost)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = speedTier,
|
||||
x = startX + 100,
|
||||
y = startY + 72.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.held_item".asTranslated(),
|
||||
x = startX + 40,
|
||||
y = startY + 99.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
TypeIcon(
|
||||
x = startX + 100.5,
|
||||
y = startY + 88,
|
||||
type = form.primaryType,
|
||||
secondaryType = form.secondaryType,
|
||||
small = false,
|
||||
centeredX = true
|
||||
).render(context)
|
||||
|
||||
if (form.secondaryType != null) {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = cobblemonResource("textures/gui/battle/type_spacer_double.png"),
|
||||
x = (startX + 69.0) / 0.45,
|
||||
y = (startY + 91.5) / 0.45,
|
||||
height = 24,
|
||||
width = 140,
|
||||
textureHeight = 24,
|
||||
textureWidth = 140,
|
||||
scale = 0.45f
|
||||
)
|
||||
}
|
||||
else {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = cobblemonResource("textures/gui/summary/type_spacer.png"),
|
||||
x = (startX + 70.5) / 0.45,
|
||||
y = (startY + 91.5) / 0.45,
|
||||
height = 24,
|
||||
width = 132,
|
||||
textureHeight = 24,
|
||||
textureWidth = 132,
|
||||
scale = 0.45f
|
||||
)
|
||||
}
|
||||
|
||||
val heldItem = dto.heldItem
|
||||
if (heldItem != null && !heldItem.isEmpty()) {
|
||||
context.pose().pushPose()
|
||||
context.pose().scale(0.95F, 0.95F, 0.95F)
|
||||
val itemX = (startX + 4) / 0.95
|
||||
val itemY = (startY + 91) / 0.95
|
||||
context.renderItem(heldItem, itemX.toInt(), itemY.toInt())
|
||||
context.pose().popPose()
|
||||
}
|
||||
else {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = questionMarkIcon,
|
||||
x = startX + 6,
|
||||
y = startY + 92.5,
|
||||
height = 11,
|
||||
width = 10,
|
||||
)
|
||||
}
|
||||
context.pose().popPose()
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun drawCustomPosablePortrait(
|
||||
identifier: ResourceLocation,
|
||||
matrixStack: PoseStack,
|
||||
scale: Float = 13F,
|
||||
contextScale: Float = 1F,
|
||||
reversed: Boolean = false,
|
||||
state: PosableState,
|
||||
partialTicks: Float,
|
||||
limbSwing: Float = 0F,
|
||||
limbSwingAmount: Float = 0F,
|
||||
ageInTicks: Float = 0F,
|
||||
headYaw: Float = 0F,
|
||||
headPitch: Float = 0F,
|
||||
r: Float = 1F,
|
||||
g: Float = 1F,
|
||||
b: Float = 1F,
|
||||
a: Float = 1F,
|
||||
doQuirks: Boolean = true
|
||||
) {
|
||||
RenderSystem.applyModelViewMatrix()
|
||||
matrixStack.pushPose()
|
||||
matrixStack.translate(0.0, PORTRAIT_DIAMETER.toDouble() + 2.0, 0.0)
|
||||
matrixStack.scale(scale, scale, -scale)
|
||||
matrixStack.translate(0.0, -PORTRAIT_DIAMETER / 18.0, 0.0)
|
||||
|
||||
val sprite = VaryingModelRepository.getSprite(identifier, state, SpriteType.PORTRAIT);
|
||||
|
||||
if (sprite == null) {
|
||||
val model = VaryingModelRepository.getPoser(identifier, state)
|
||||
state.currentModel = model
|
||||
val texture = VaryingModelRepository.getTexture(identifier, state)
|
||||
|
||||
val context = RenderContext()
|
||||
model.context = context
|
||||
VaryingModelRepository.getTextureNoSubstitute(identifier, state).let { context.put(RenderContext.TEXTURE, it) }
|
||||
context.put(RenderContext.SCALE, contextScale)
|
||||
context.put(RenderContext.SPECIES, identifier)
|
||||
context.put(RenderContext.ASPECTS, state.currentAspects)
|
||||
context.put(RenderContext.POSABLE_STATE, state)
|
||||
context.put(RenderContext.DO_QUIRKS, doQuirks)
|
||||
|
||||
val renderType = RenderType.entityCutout(texture)
|
||||
|
||||
val quaternion1 = Axis.YP.rotationDegrees(-32F * if (reversed) -1F else 1F)
|
||||
val quaternion2 = Axis.XP.rotationDegrees(5F)
|
||||
|
||||
val originalPose = state.currentPose
|
||||
state.setPoseToFirstSuitable(PoseType.PORTRAIT)
|
||||
state.updatePartialTicks(partialTicks)
|
||||
model.applyAnimations(null, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch)
|
||||
originalPose?.let { state.setPose(it) }
|
||||
|
||||
matrixStack.translate(
|
||||
model.portraitTranslation.x * if (reversed) -1F else 1F,
|
||||
model.portraitTranslation.y + 1.5 * model.portraitScale,
|
||||
model.portraitTranslation.z - 4
|
||||
)
|
||||
matrixStack.scale(model.portraitScale, model.portraitScale, 1 / model.portraitScale)
|
||||
matrixStack.mulPose(quaternion1)
|
||||
matrixStack.mulPose(quaternion2)
|
||||
|
||||
val light1 = Vector3f(0.2F, 1.0F, -1.0F)
|
||||
val light2 = Vector3f(0.1F, 0.0F, 8.0F)
|
||||
RenderSystem.setShaderLights(light1, light2)
|
||||
quaternion1.conjugate()
|
||||
|
||||
val immediate = Minecraft.getInstance().renderBuffers().bufferSource()
|
||||
val buffer = immediate.getBuffer(renderType)
|
||||
val packedLight = LightTexture.pack(11, 7)
|
||||
|
||||
val colour = toHex(r, g, b, a)
|
||||
model.withLayerContext(immediate, state, VaryingModelRepository.getLayers(identifier, state)) {
|
||||
model.render(context, matrixStack, buffer, packedLight, OverlayTexture.NO_OVERLAY, colour)
|
||||
immediate.endBatch()
|
||||
}
|
||||
|
||||
model.setDefault()
|
||||
|
||||
Lighting.setupFor3DItems()
|
||||
} else {
|
||||
renderSprite(matrixStack, sprite)
|
||||
}
|
||||
|
||||
matrixStack.popPose()
|
||||
}
|
||||
|
||||
private fun getSpeedRange(form: FormData, level: Int, speedBoost: Double): MutableComponent {
|
||||
val base = form.baseStats[Stats.SPEED] ?: 0
|
||||
val maxIV = 31
|
||||
val maxEV = 252
|
||||
val minNatureMod = 0.9
|
||||
val maxNatureMod = 1.1
|
||||
|
||||
val minSpeed = getStat(base, level, 0, 0, minNatureMod) * speedBoost
|
||||
val maxSpeed = getStat(base, level, maxIV, maxEV, maxNatureMod) * speedBoost
|
||||
return "cobblemon.battle.ui.speed_tier".asTranslated(minSpeed.toInt(), maxSpeed.toInt())
|
||||
}
|
||||
|
||||
private fun getStat(base: Int, level: Int, iv: Int, ev: Int, natureMod: Double): Int {
|
||||
return floor((floor(((2.0 * base + iv + floor(ev / 4.0)) * level) / 100) + 5) * natureMod).toInt()
|
||||
}
|
||||
|
||||
private fun getMultiplierText(multiplier: Double): MutableComponent {
|
||||
val rounded = decimalFormat.format(multiplier)
|
||||
val str = "x$rounded"
|
||||
return when {
|
||||
multiplier > 1.0 -> str.green()
|
||||
multiplier < 1.0 -> str.red()
|
||||
else -> str.text()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFormText(species: Species, form: FormData): MutableComponent {
|
||||
val speciesName = species.name
|
||||
val formName = form.name
|
||||
return if (formName == "Normal") speciesName.text() else "$speciesName-$formName".text()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,12 @@
|
||||
package com.cobblemon.mod.common.client.gui.battle.subscreen
|
||||
|
||||
import com.cobblemon.mod.common.api.gui.ParentWidget
|
||||
import com.cobblemon.mod.common.client.battle.SingleActionRequest
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
|
||||
import com.mojang.blaze3d.platform.InputConstants
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
|
||||
abstract class BattleActionSelection(
|
||||
val battleGUI: BattleGUI,
|
||||
val request: SingleActionRequest,
|
||||
x: Int,
|
||||
y: Int,
|
||||
width: Int,
|
||||
|
||||
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.client.gui.battle.subscreen
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
|
||||
class BattleBackButton(val x: Float, val y: Float) : CobblemonRenderable {
|
||||
|
||||
@@ -14,8 +14,10 @@ import com.cobblemon.mod.common.battles.FleeAttemptActionResponse
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.battle.SingleActionRequest
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleOptionTile
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.narration.NarratableEntry
|
||||
@@ -27,17 +29,21 @@ import net.minecraft.resources.ResourceLocation
|
||||
|
||||
class BattleGeneralActionSelection(
|
||||
battleGUI: BattleGUI,
|
||||
request: SingleActionRequest
|
||||
val request: SingleActionRequest
|
||||
) : BattleActionSelection(
|
||||
battleGUI,
|
||||
request,
|
||||
BattleGUI.OPTION_ROOT_X,
|
||||
Minecraft.getInstance().window.guiScaledHeight - BattleGUI.OPTION_VERTICAL_OFFSET,
|
||||
(BattleOptionTile.OPTION_WIDTH + 3 * BattleGUI.OPTION_HORIZONTAL_SPACING).toInt(),
|
||||
(BattleOptionTile.OPTION_HEIGHT + 3 * BattleGUI.OPTION_VERTICAL_SPACING).toInt(),
|
||||
(BattleOptionTile.OPTION_WIDTH + 3 * BattleGUI.OPTION_HORIZONTAL_SPACING),
|
||||
(BattleOptionTile.OPTION_HEIGHT + 3 * BattleGUI.OPTION_VERTICAL_SPACING),
|
||||
battleLang("choose_action")
|
||||
) {
|
||||
val backButton = BattleBackButton(BattleGUI.OPTION_ROOT_X - 3F, Minecraft.getInstance().window.guiScaledHeight - 22F)
|
||||
companion object {
|
||||
val battleInfoSelection = cobblemonResource("textures/gui/battle/team_info.png")
|
||||
}
|
||||
|
||||
val backButton =
|
||||
BattleBackButton(BattleGUI.OPTION_ROOT_X - 3F, Minecraft.getInstance().window.guiScaledHeight - 22F)
|
||||
val lastAnwseredRequest = CobblemonClient.battle?.getLastAnsweredRequest()
|
||||
|
||||
val tiles = mutableListOf<BattleOptionTile>()
|
||||
@@ -74,6 +80,12 @@ class BattleGeneralActionSelection(
|
||||
battleGUI.changeActionSelection(ForfeitConfirmationSelection(battleGUI, request))
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
}
|
||||
|
||||
addOption(rank++, "cobblemon.battle.ui.team_info.button".asTranslated(), battleInfoSelection) {
|
||||
BattleTeamInfoSelection.visible = true
|
||||
battleGUI.changeActionSelection(BattleTeamInfoSelection(battleGUI))
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,34 +10,47 @@ package com.cobblemon.mod.common.client.gui.battle.subscreen
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
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.text.bold
|
||||
import com.cobblemon.mod.common.api.text.gold
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.battles.*
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.battle.Effectiveness
|
||||
import com.cobblemon.mod.common.client.battle.MoveEffectivenessCalculator
|
||||
import com.cobblemon.mod.common.client.battle.SingleActionRequest
|
||||
import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
|
||||
import com.cobblemon.mod.common.client.gui.TypeIcon
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
|
||||
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MovesWidget
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.cobblemon.mod.common.util.math.toRGB
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
import net.minecraft.client.sounds.SoundManager
|
||||
import net.minecraft.locale.Language
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.util.Mth.floor
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
|
||||
class BattleMoveSelection(
|
||||
battleGUI: BattleGUI,
|
||||
request: SingleActionRequest,
|
||||
val request: SingleActionRequest,
|
||||
) : BattleActionSelection(
|
||||
battleGUI = battleGUI,
|
||||
request = request,
|
||||
x = 20,
|
||||
y = Minecraft.getInstance().window.guiScaledHeight - 84,
|
||||
width = 100,
|
||||
@@ -51,7 +64,21 @@ class BattleMoveSelection(
|
||||
const val MOVE_HORIZONTAL_SPACING = 13F
|
||||
|
||||
val moveTexture = cobblemonResource("textures/gui/battle/battle_move.png")
|
||||
val moveOverlayTexture = cobblemonResource("textures/gui/battle/battle_move_overlay.png")
|
||||
val moveOverlayTexture = cobblemonResource("textures/gui/battle/move_outline.png")
|
||||
val moveTooltipIcon = cobblemonResource("textures/gui/battle/move_tooltip_icon.png")
|
||||
val moveTooltip = cobblemonResource("textures/gui/battle/move_tooltip.png")
|
||||
val moveTooltipForStatus = cobblemonResource("textures/gui/battle/move_tooltip_status.png")
|
||||
|
||||
val effectiveIcon = cobblemonResource("textures/gui/battle/effective_icon.png")
|
||||
val extremelyEffectiveIcon = cobblemonResource("textures/gui/battle/extremely_effective_icon.png")
|
||||
val immuneIcon = cobblemonResource("textures/gui/battle/immune_icon.png")
|
||||
val mostlyIneffectiveIcon = cobblemonResource("textures/gui/battle/mostly_ineffective_icon.png")
|
||||
val superEffectiveIcon = cobblemonResource("textures/gui/battle/super_effective_icon.png")
|
||||
val notVeryEffectiveIcon = cobblemonResource("textures/gui/battle/not_very_effective_icon.png")
|
||||
|
||||
private val decimalFormat = DecimalFormat("#.##").also {
|
||||
it.roundingMode = RoundingMode.CEILING
|
||||
}
|
||||
}
|
||||
|
||||
val moveSet = request.moveSet!!
|
||||
@@ -74,7 +101,7 @@ class BattleMoveSelection(
|
||||
BattleGimmickButton.create(gimmick, this, backButton.x + xOff, backButton.y)
|
||||
}
|
||||
|
||||
val shiftButton = BattleShiftButton(x + 22.5F, Minecraft.getInstance().window.guiScaledHeight - 22F )
|
||||
val shiftButton = BattleShiftButton(x + 22.5F, Minecraft.getInstance().window.guiScaledHeight - 22F)
|
||||
|
||||
open class MoveTile(
|
||||
val moveSelection: BattleMoveSelection,
|
||||
@@ -138,7 +165,7 @@ class BattleMoveSelection(
|
||||
|
||||
// Move Category
|
||||
MoveCategoryIcon(
|
||||
x = x + 48,
|
||||
x = x + 41,
|
||||
y = y + 14.5,
|
||||
category = moveTemplate.damageCategory,
|
||||
opacity = moveSelection.opacity
|
||||
@@ -168,14 +195,265 @@ class BattleMoveSelection(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = movePPText,
|
||||
x = x + 75,
|
||||
x = x + 67,
|
||||
y = y + 14,
|
||||
opacity = moveSelection.opacity,
|
||||
centered = true
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = moveTooltipIcon,
|
||||
x = x + 82,
|
||||
y = y + 14,
|
||||
width = 9,
|
||||
height = 9,
|
||||
textureHeight = 18,
|
||||
textureWidth = 9,
|
||||
alpha = moveSelection.opacity,
|
||||
vOffset = if (isTooltipHovered(mouseX.toDouble(), mouseY.toDouble())) 9 else 0
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = "i".text().bold(),
|
||||
x = x + 87.375,
|
||||
y = y + 14,
|
||||
opacity = moveSelection.opacity,
|
||||
centered = true
|
||||
)
|
||||
|
||||
if (isTooltipHovered(mouseX.toDouble(), mouseY.toDouble())) {
|
||||
renderTooltip(context)
|
||||
}
|
||||
}
|
||||
|
||||
fun isHovered(mouseX: Double, mouseY: Double) = mouseX >= x && mouseX <= x + MOVE_WIDTH && mouseY >= y && mouseY <= y + MOVE_HEIGHT
|
||||
fun renderTooltip(context: GuiGraphics) {
|
||||
val tooltipY = Minecraft.getInstance().window.guiScaledHeight - 84 - 59 - 4
|
||||
val tooltipX = 20
|
||||
|
||||
val move = Moves.getByNameOrDummy(move.id)
|
||||
|
||||
val effectiveness = getMoveEffectiveness(move)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = if (effectiveness == null) moveTooltipForStatus else moveTooltip,
|
||||
x = tooltipX,
|
||||
y = tooltipY,
|
||||
width = 195,
|
||||
height = 56,
|
||||
alpha = moveSelection.opacity
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = lang("ui.power"),
|
||||
x = tooltipX + 18,
|
||||
y = tooltipY + 11,
|
||||
opacity = moveSelection.opacity,
|
||||
centered = false,
|
||||
scale = 0.65f
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = MovesWidget.movesPowerIconResource,
|
||||
x = (tooltipX + 8.5) / 0.66f,
|
||||
y = (tooltipY + 9.5) / 0.66f,
|
||||
width = 10,
|
||||
height = 10,
|
||||
alpha = moveSelection.opacity,
|
||||
scale = 0.66f
|
||||
)
|
||||
|
||||
val powerStr = if (move.power <= 0) "—" else move.power.toInt().toString()
|
||||
|
||||
drawScaledTextJustifiedRight(
|
||||
context = context,
|
||||
text = powerStr.text(),
|
||||
x = tooltipX + 73,
|
||||
y = tooltipY + 11,
|
||||
opacity = moveSelection.opacity,
|
||||
scale = 0.65f
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = MovesWidget.movesAccuracyIconResource,
|
||||
x = (tooltipX + 8.5) / 0.66f,
|
||||
y = (tooltipY + 26) / 0.66f,
|
||||
width = 10,
|
||||
height = 10,
|
||||
alpha = moveSelection.opacity,
|
||||
scale = 0.66f
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = lang("ui.accuracy"),
|
||||
x = tooltipX + 18,
|
||||
y = tooltipY + 27,
|
||||
opacity = moveSelection.opacity,
|
||||
centered = false,
|
||||
scale = 0.65f
|
||||
)
|
||||
|
||||
val accuracyStr = format(move.accuracy).text()
|
||||
|
||||
drawScaledTextJustifiedRight(
|
||||
context = context,
|
||||
text = accuracyStr,
|
||||
x = tooltipX + 73,
|
||||
y = tooltipY + 27,
|
||||
opacity = moveSelection.opacity,
|
||||
scale = 0.65f
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = MovesWidget.movesEffectIconResource,
|
||||
x = (tooltipX + 8.5) / 0.66f,
|
||||
y = (tooltipY + 42.5) / 0.66f,
|
||||
width = 10,
|
||||
height = 10,
|
||||
alpha = moveSelection.opacity,
|
||||
scale = 0.66f
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = lang("ui.effect"),
|
||||
x = tooltipX + 18,
|
||||
y = tooltipY + 43.5,
|
||||
opacity = moveSelection.opacity,
|
||||
centered = false,
|
||||
scale = 0.65f
|
||||
)
|
||||
|
||||
val moveEffect = format(move.effectChances.firstOrNull() ?: 0.0).text()
|
||||
|
||||
drawScaledTextJustifiedRight(
|
||||
context = context,
|
||||
text = moveEffect,
|
||||
x = tooltipX + 73,
|
||||
y = tooltipY + 43.5,
|
||||
opacity = moveSelection.opacity,
|
||||
scale = 0.65f
|
||||
)
|
||||
|
||||
val moveDescription = move.description
|
||||
val lines = Minecraft.getInstance().font.splitter.splitLines(moveDescription, 200, moveDescription.style)
|
||||
val orderedLines = Language.getInstance().getVisualOrder(lines)
|
||||
val maxLines = if (effectiveness == null) 6 else 4
|
||||
orderedLines.take(maxLines).forEachIndexed { index, line ->
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = line,
|
||||
x = tooltipX + 85,
|
||||
y = tooltipY + 11 + (6 * index),
|
||||
opacity = moveSelection.opacity,
|
||||
scaleX = 0.5f,
|
||||
scaleY = 0.5f,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
if (effectiveness == null) return
|
||||
val effectivenessText = getEffectivenessText(effectiveness)
|
||||
val effectivenessIcon = getEffectivenessIcon(effectiveness)
|
||||
val effectivenessValue = getEffectivenessValue(effectiveness)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = effectivenessIcon,
|
||||
x = (tooltipX + 84) / 0.5f,
|
||||
y = (tooltipY + 43) / 0.5f,
|
||||
width = 12,
|
||||
height = 12,
|
||||
alpha = moveSelection.opacity,
|
||||
scale = 0.5f
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = effectivenessText,
|
||||
x = tooltipX + 94,
|
||||
y = tooltipY + 43.5,
|
||||
opacity = moveSelection.opacity,
|
||||
centered = false,
|
||||
scale = 0.65f
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = effectivenessValue,
|
||||
x = tooltipX + 168,
|
||||
y = tooltipY + 43.5,
|
||||
opacity = moveSelection.opacity,
|
||||
centered = false,
|
||||
scale = 0.65f
|
||||
)
|
||||
}
|
||||
|
||||
private fun getEffectivenessValue(effectiveness: Effectiveness): MutableComponent {
|
||||
return when (effectiveness) {
|
||||
Effectiveness.IMMUNE -> "0.00x".text()
|
||||
Effectiveness.MOSTLY_INEFFECTIVE -> "0.25x".text()
|
||||
Effectiveness.NOT_VERY_EFFECTIVE -> "0.50x".text()
|
||||
Effectiveness.EFFECTIVE -> "1.00x".text()
|
||||
Effectiveness.SUPER_EFFECTIVE -> "2.00x".text()
|
||||
Effectiveness.EXTREMELY_EFFECTIVE -> "4.00x".text()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEffectivenessIcon(effectiveness: Effectiveness): ResourceLocation {
|
||||
return when (effectiveness) {
|
||||
Effectiveness.IMMUNE -> immuneIcon
|
||||
Effectiveness.MOSTLY_INEFFECTIVE -> mostlyIneffectiveIcon
|
||||
Effectiveness.NOT_VERY_EFFECTIVE -> notVeryEffectiveIcon
|
||||
Effectiveness.EFFECTIVE -> effectiveIcon
|
||||
Effectiveness.SUPER_EFFECTIVE -> superEffectiveIcon
|
||||
Effectiveness.EXTREMELY_EFFECTIVE -> extremelyEffectiveIcon
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEffectivenessText(effectiveness: Effectiveness): MutableComponent {
|
||||
return when (effectiveness) {
|
||||
Effectiveness.IMMUNE -> "cobblemon.battle.ui.effectiveness.has_no_effect".asTranslated()
|
||||
Effectiveness.MOSTLY_INEFFECTIVE -> "cobblemon.battle.ui.effectiveness.mostly_ineffective".asTranslated()
|
||||
Effectiveness.NOT_VERY_EFFECTIVE -> "cobblemon.battle.ui.effectiveness.not_very_effective".asTranslated()
|
||||
Effectiveness.EFFECTIVE -> "cobblemon.battle.ui.effectiveness.effective".asTranslated()
|
||||
Effectiveness.SUPER_EFFECTIVE -> "cobblemon.battle.ui.effectiveness.super_effective".asTranslated()
|
||||
Effectiveness.EXTREMELY_EFFECTIVE -> "cobblemon.battle.ui.effectiveness.extremely_effective".asTranslated()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMoveEffectiveness(move: MoveTemplate): Effectiveness? {
|
||||
val battle = CobblemonClient.battle ?: return null
|
||||
val opponents = battle.side2.activeClientBattlePokemon.toList()
|
||||
if (opponents.size != 1) return null
|
||||
val opponent = battle.side2.activeClientBattlePokemon.first().battlePokemon ?: return null
|
||||
|
||||
val aspects: Set<String> = opponent.state.currentAspects
|
||||
val opponentForm = opponent.species.getForm(aspects)
|
||||
if (move.damageCategory == DamageCategories.STATUS) return null
|
||||
return MoveEffectivenessCalculator.getMoveEffectiveness(move.elementalType, opponentForm.primaryType, opponentForm.secondaryType)
|
||||
}
|
||||
|
||||
fun format(input: Double): String {
|
||||
if (input <= 0) return "—"
|
||||
return "${decimalFormat.format(input)}%"
|
||||
}
|
||||
|
||||
fun isHovered(mouseX: Double, mouseY: Double): Boolean {
|
||||
return !isTooltipHovered(mouseX, mouseY) && mouseX >= x && mouseX <= x + MOVE_WIDTH && mouseY >= y && mouseY <= y + MOVE_HEIGHT
|
||||
}
|
||||
|
||||
fun isTooltipHovered(mouseX: Double, mouseY: Double): Boolean {
|
||||
return mouseX >= x + 82 && mouseX <= x + MOVE_WIDTH && mouseY >= y + 14 && mouseY <= y + MOVE_HEIGHT
|
||||
}
|
||||
|
||||
fun onClick() {
|
||||
if (!selectable) return
|
||||
@@ -204,7 +482,15 @@ class BattleMoveSelection(
|
||||
if(request.activePokemon.getFormat().battleType.pokemonPerSide == 1) {
|
||||
move.onClick()
|
||||
} else {
|
||||
battleGUI.changeActionSelection(BattleTargetSelection(battleGUI, request, move.move, move.response.gimmickID, move.move.gimmickMove))
|
||||
battleGUI.changeActionSelection(
|
||||
BattleTargetSelection(
|
||||
battleGUI,
|
||||
request,
|
||||
move.move,
|
||||
move.response.gimmickID,
|
||||
move.move.gimmickMove
|
||||
)
|
||||
)
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.client.gui.battle.subscreen
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
class BattleShiftButton(val x: Float, val y: Float) {
|
||||
companion object {
|
||||
@@ -19,7 +18,6 @@ class BattleShiftButton(val x: Float, val y: Float) {
|
||||
const val HEIGHT = 34
|
||||
const val SCALE = 0.5F
|
||||
val baseTexture = cobblemonResource("textures/gui/battle/triple_battle_shift.png")
|
||||
|
||||
}
|
||||
|
||||
fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
|
||||
@@ -44,13 +44,12 @@ import org.joml.Vector3f
|
||||
|
||||
class BattleSwitchPokemonSelection(
|
||||
battleGUI: BattleGUI,
|
||||
request: SingleActionRequest
|
||||
val request: SingleActionRequest
|
||||
) : BattleActionSelection(
|
||||
battleGUI,
|
||||
request,
|
||||
x = 0,
|
||||
y = if (Minecraft.getInstance().window.guiScaledHeight > 304) (Minecraft.getInstance().window.guiScaledHeight / 2) - (BACKGROUND_HEIGHT / 2)
|
||||
else Minecraft.getInstance().window.guiScaledHeight - (BACKGROUND_HEIGHT + 78),
|
||||
else Minecraft.getInstance().window.guiScaledHeight - (BACKGROUND_HEIGHT + 78),
|
||||
width = Minecraft.getInstance().window.guiScaledWidth,
|
||||
height = Minecraft.getInstance().window.guiScaledHeight,
|
||||
battleLang("switch_pokemon")
|
||||
@@ -64,7 +63,7 @@ class BattleSwitchPokemonSelection(
|
||||
}
|
||||
|
||||
val tiles = mutableListOf<SwitchTile>()
|
||||
val backButton = BattleBackButton(x + 9F, Minecraft.getInstance().window.guiScaledHeight - 22F )
|
||||
val backButton = BattleBackButton(x + 9F, Minecraft.getInstance().window.guiScaledHeight - 22F)
|
||||
|
||||
var isReviving = false
|
||||
|
||||
@@ -78,9 +77,9 @@ class BattleSwitchPokemonSelection(
|
||||
?.let { showdownPokemon to it }
|
||||
}.filter { it.second.uuid !in switchingInPokemon }
|
||||
|
||||
isReviving = request.side.pokemon.any { it.reviving && it.uuid == request.activePokemon.battlePokemon?.uuid }
|
||||
isReviving = request.side!!.pokemon.any { it.reviving && it.uuid == request.activePokemon.battlePokemon?.uuid }
|
||||
if (request.forceSwitch && !isReviving && showdownPokemonToPokemon.all {
|
||||
(it.second.uuid in battleGUI.actor!!.activePokemon.map { it.battlePokemon?.uuid } || ("fnt" in it.first.condition))}) { // on field or fainted
|
||||
(it.second.uuid in battleGUI.actor!!.activePokemon.map { it.battlePokemon?.uuid } || ("fnt" in it.first.condition))}) { // on field or fainted
|
||||
// Occurs after a multi-knock out and the player doesn't have enough pokemon to fill every vacant slot
|
||||
battleGUI.selectAction(request, PassActionResponse)
|
||||
}
|
||||
@@ -198,7 +197,7 @@ class BattleSwitchPokemonSelection(
|
||||
val healthRatioSplits = showdownPokemon.condition.split(" ")[0].split("/")
|
||||
try {
|
||||
val (hp, maxHp) = if (healthRatioSplits.size == 1) 0 to 0
|
||||
else healthRatioSplits[0].toInt() to pokemon.maxHealth
|
||||
else healthRatioSplits[0].toInt() to pokemon.maxHealth
|
||||
|
||||
val hpRatio = hp / maxHp.toFloat()
|
||||
val status = pokemon.status?.status?.showdownName
|
||||
@@ -266,7 +265,7 @@ class BattleSwitchPokemonSelection(
|
||||
matrixStack.pushPose()
|
||||
matrixStack.translate(0.0, 0.0, 100.0)
|
||||
// Held Item
|
||||
val heldItem = pokemon.heldItemNoCopy()
|
||||
val heldItem = pokemon.heldItem
|
||||
if (!heldItem.isEmpty) {
|
||||
renderScaledGuiItemIcon(
|
||||
matrixStack = matrixStack,
|
||||
|
||||
@@ -24,7 +24,6 @@ import com.cobblemon.mod.common.client.battle.ActiveClientBattlePokemon
|
||||
import com.cobblemon.mod.common.client.battle.SingleActionRequest
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
|
||||
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleSwitchPokemonSelection.Companion
|
||||
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
|
||||
@@ -44,13 +43,12 @@ import kotlin.math.sin
|
||||
|
||||
class BattleTargetSelection(
|
||||
battleGUI: BattleGUI,
|
||||
request: SingleActionRequest,
|
||||
val request: SingleActionRequest,
|
||||
val move: InBattleMove,
|
||||
val gimmickID: String?,
|
||||
gimmickMove: InBattleGimmickMove?
|
||||
) : BattleActionSelection(
|
||||
battleGUI = battleGUI,
|
||||
request = request,
|
||||
x = 0,
|
||||
y = if (Minecraft.getInstance().window.guiScaledHeight > 304) (Minecraft.getInstance().window.guiScaledHeight / 2) - (BattleSwitchPokemonSelection.BACKGROUND_HEIGHT / 2)
|
||||
else Minecraft.getInstance().window.guiScaledHeight - (BattleSwitchPokemonSelection.BACKGROUND_HEIGHT + 78),
|
||||
|
||||
@@ -0,0 +1,863 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.battle.subscreen
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.abilities.Abilities
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.gui.renderSprite
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
import com.cobblemon.mod.common.api.text.font
|
||||
import com.cobblemon.mod.common.api.text.green
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleActor
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleInformationRepository
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattlePokemon
|
||||
import com.cobblemon.mod.common.client.gui.TypeIcon
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
|
||||
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
|
||||
import com.cobblemon.mod.common.client.render.getDepletableRedGreen
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
|
||||
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.pokemon.Gender
|
||||
import com.cobblemon.mod.common.pokemon.Species
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay.Companion.PORTRAIT_DIAMETER
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay.Companion.questionMarkIcon
|
||||
import com.cobblemon.mod.common.client.gui.battle.preview.TeamPreviewWidget
|
||||
import com.cobblemon.mod.common.client.gui.battle.preview.TeamPreviewWidget.PokemonTile
|
||||
import com.cobblemon.mod.common.client.render.SpriteType
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
|
||||
import com.cobblemon.mod.common.util.toHex
|
||||
import com.mojang.blaze3d.platform.Lighting
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import com.mojang.math.Axis
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.narration.NarratableEntry
|
||||
import net.minecraft.client.gui.narration.NarrationElementOutput
|
||||
import net.minecraft.client.renderer.LightTexture
|
||||
import net.minecraft.client.renderer.RenderType
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
import net.minecraft.client.sounds.SoundManager
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
import kotlin.collections.get
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
|
||||
class BattleTeamInfoSelection(
|
||||
battleGUI: BattleGUI
|
||||
) : BattleActionSelection(
|
||||
battleGUI,
|
||||
x = 0,
|
||||
y = if (Minecraft.getInstance().window.guiScaledHeight > 304) (Minecraft.getInstance().window.guiScaledHeight / 2) - (BACKGROUND_HEIGHT / 2)
|
||||
else Minecraft.getInstance().window.guiScaledHeight - (BACKGROUND_HEIGHT + 78),
|
||||
width = Minecraft.getInstance().window.guiScaledWidth,
|
||||
height = Minecraft.getInstance().window.guiScaledHeight,
|
||||
"cobblemon.battle.ui.team_info".asTranslated()
|
||||
) {
|
||||
companion object {
|
||||
const val SLOT_HORIZONTAL_SPACING = 4F
|
||||
const val SLOT_VERTICAL_SPACING = 2F
|
||||
|
||||
const val BACKGROUND_HEIGHT = 148
|
||||
val underlayTexture = cobblemonResource("textures/gui/battle/selection_underlay.png")
|
||||
val title = cobblemonResource("textures/gui/battle/turn_counter.png")
|
||||
var visible = false
|
||||
}
|
||||
|
||||
val teamTiles = mutableListOf<PokemonTile>()
|
||||
val opponentTiles = mutableListOf<PokemonTile>()
|
||||
val backButton = BattleBackButton(x + 9F, Minecraft.getInstance().window.guiScaledHeight - 22F)
|
||||
|
||||
init {
|
||||
addTiles()
|
||||
}
|
||||
|
||||
fun addTiles() {
|
||||
val battle = CobblemonClient.battle ?: return
|
||||
|
||||
val actors = mutableListOf<ClientBattleActor>()
|
||||
battle.sides.forEach { side ->
|
||||
side.actors.forEach { actors.add(it) }
|
||||
}
|
||||
|
||||
if (actors.size != 2) return
|
||||
val displayOrder = if (actors.last().uuid == Minecraft.getInstance().player?.uuid) actors.reversed() else actors
|
||||
val actorToPokemon = displayOrder.map { it to ClientBattleInformationRepository.actors[it.uuid] }
|
||||
actorToPokemon.first().second?.forEachIndexed { index, pokemon ->
|
||||
if (pokemon == null) return@forEachIndexed
|
||||
val (slotX, slotY) = getSlotPosition(index, 1)
|
||||
teamTiles.add(PokemonTile(slotX, slotY, pokemon, this, false))
|
||||
}
|
||||
actorToPokemon.last().second?.forEachIndexed { index, pokemon ->
|
||||
if (pokemon == null) return@forEachIndexed
|
||||
val (slotX, slotY) = getSlotPosition(index, 2)
|
||||
opponentTiles.add(PokemonTile(slotX, slotY, pokemon, this, true))
|
||||
}
|
||||
}
|
||||
|
||||
fun getSlotPosition(index: Int, side: Int): Pair<Float, Float> {
|
||||
val startX = if (side == 1) 10 else width - 10 - (41 + TeamPreviewWidget.Companion.SLOT_HORIZONTAL_SPACING) * 2
|
||||
val startY = y + 15
|
||||
val row = index / 2
|
||||
val column = index % 2
|
||||
val slotX = startX.toFloat() + column * (SLOT_HORIZONTAL_SPACING + 41)
|
||||
val slotY = startY.toFloat() + row * (SLOT_VERTICAL_SPACING + 39)
|
||||
return Pair(slotX, slotY)
|
||||
}
|
||||
|
||||
private fun getHoveredTile(team: Boolean, mouseX: Int, mouseY: Int): Int {
|
||||
val tiles = if (team) teamTiles else opponentTiles
|
||||
return tiles.indexOfFirst { it.isHovered(mouseX.toDouble(), mouseY.toDouble()) }
|
||||
}
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
if (opacity <= 0.05F) return
|
||||
|
||||
val matrixStack = context.pose()
|
||||
val mc = Minecraft.getInstance()
|
||||
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = underlayTexture,
|
||||
x = x,
|
||||
y = y,
|
||||
width = width,
|
||||
height = BACKGROUND_HEIGHT
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = title,
|
||||
x = (mc.window.guiScaledWidth / 2) - (103 / 2),
|
||||
y = 4,
|
||||
width = 103,
|
||||
height = 22
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.team_info.title".text().bold(),
|
||||
x = (mc.window.guiScaledWidth / 2),
|
||||
y = 6,
|
||||
scale = 0.75f,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
teamTiles.forEachIndexed { index, tile ->
|
||||
val hoveredTile = getHoveredTile(true, mouseX, mouseY)
|
||||
if (shouldHide(hoveredTile, index, false)) return@forEachIndexed
|
||||
tile.render(context, mouseX.toDouble(), mouseY.toDouble(), delta)
|
||||
}
|
||||
opponentTiles.forEachIndexed { index, tile ->
|
||||
val hoveredTile = getHoveredTile(false, mouseX, mouseY)
|
||||
if (shouldHide(hoveredTile, index, true)) return@forEachIndexed
|
||||
tile.render(context, mouseX.toDouble(), mouseY.toDouble(), delta)
|
||||
}
|
||||
backButton.render(context, mouseX, mouseY, delta)
|
||||
}
|
||||
|
||||
private fun shouldHide(hoveredTile: Int, currentTile: Int, reversed: Boolean): Boolean {
|
||||
if (hoveredTile != -1) {
|
||||
val hoveredRow = hoveredTile / 2
|
||||
val hoveredColumn = hoveredTile % 2
|
||||
val currentRow = currentTile / 2
|
||||
val currentColumn = currentTile % 2
|
||||
if (!reversed && hoveredColumn < currentColumn && hoveredRow <= currentRow) return true
|
||||
if (reversed && hoveredColumn > currentColumn && hoveredRow <= currentRow) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun mousePrimaryClicked(mouseX: Double, mouseY: Double): Boolean {
|
||||
if (backButton.isHovered(mouseX, mouseY)) {
|
||||
battleGUI.changeActionSelection(null)
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
Companion.visible = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun defaultButtonNarrationText(builder: NarrationElementOutput) {
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager) {
|
||||
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
}
|
||||
|
||||
override fun narrationPriority() = NarratableEntry.NarrationPriority.HOVERED
|
||||
|
||||
class PokemonTile(
|
||||
val x: Float,
|
||||
val y: Float,
|
||||
val pokemonDto: BattlePokemonDTO,
|
||||
val widget: BattleTeamInfoSelection,
|
||||
val reversed: Boolean
|
||||
) {
|
||||
companion object {
|
||||
const val SELECT_WIDTH = 41
|
||||
const val SELECT_HEIGHT = 39
|
||||
const val SCALE = 0.5F
|
||||
val expandedPokemonInfo = cobblemonResource("textures/gui/battle/expanded_pokemon_info_center.png")
|
||||
|
||||
val pokemonTile = cobblemonResource("textures/gui/battle/pokemon_tile.png")
|
||||
val pokemonTileReversed = cobblemonResource("textures/gui/battle/pokemon_tile_reversed.png")
|
||||
val pokemonTileDisabled = cobblemonResource("textures/gui/battle/pokemon_tile_disabled.png")
|
||||
val pokemonTileDisabledReversed = cobblemonResource("textures/gui/battle/pokemon_tile_disabled_reversed.png")
|
||||
val pokemonTileSelected = cobblemonResource("textures/gui/battle/pokemon_tile_selected.png")
|
||||
val pokemonTileSelectedReversed = cobblemonResource("textures/gui/battle/pokemon_tile_selected_reversed.png")
|
||||
|
||||
val hoverGapSelected = cobblemonResource("textures/gui/battle/hover_gap_selected.png")
|
||||
val hoverGap = cobblemonResource("textures/gui/battle/hover_gap.png")
|
||||
val hoverGapReversed = cobblemonResource("textures/gui/battle/hover_gap_reversed.png")
|
||||
|
||||
private val decimalFormat = DecimalFormat("0.00").also {
|
||||
it.roundingMode = RoundingMode.CEILING
|
||||
}
|
||||
}
|
||||
|
||||
val state = FloatingState()
|
||||
|
||||
fun isHovered(mouseX: Double, mouseY: Double) = mouseX in x..(x + SELECT_WIDTH) && mouseY in (y..(y + SELECT_HEIGHT))
|
||||
|
||||
fun render(context: GuiGraphics, mouseX: Double, mouseY: Double, deltaTicks: Float) {
|
||||
val pokemon = pokemonDto.activeBattlePokemonDTO ?: return
|
||||
val battlePokemon = ClientBattlePokemon(
|
||||
uuid = pokemon.uuid,
|
||||
properties = pokemon.properties,
|
||||
aspects = pokemon.aspects,
|
||||
displayName = pokemon.displayName,
|
||||
hpValue = pokemon.hpValue,
|
||||
maxHp = pokemon.maxHp,
|
||||
isHpFlat = pokemon.isFlatHp,
|
||||
status = pokemon.status,
|
||||
statChanges = pokemon.statChanges,
|
||||
)
|
||||
state.currentAspects = pokemon.aspects
|
||||
val matrixStack = context.pose()
|
||||
val isFainted = pokemonDto.fainted
|
||||
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = cobblemonResource("textures/gui/battle/battle_info_underlay.png"),
|
||||
x = x + 5 + (if (reversed) 3 else 0),
|
||||
y = y + 4,
|
||||
width = 28,
|
||||
height = 28
|
||||
)
|
||||
val speciesToDisplay = battlePokemon.species
|
||||
val stateToDisplay = battlePokemon.state
|
||||
context.enableScissor(
|
||||
(x + 5 + (if (reversed) 3 else 0)).toInt(),
|
||||
(y + 5).toInt(),
|
||||
(x + 5 + 28 + (if (reversed) 3 else 0)).toInt(),
|
||||
(y + 5 + 28).toInt(),
|
||||
)
|
||||
matrixStack.pushPose()
|
||||
matrixStack.translate(
|
||||
x + (if (reversed) 3 else 0) + 5 + 28 / 2.0,
|
||||
y.toDouble() + 5 - 5.0,
|
||||
0.0
|
||||
)
|
||||
drawCustomPosablePortrait(
|
||||
identifier = speciesToDisplay.resourceIdentifier,
|
||||
matrixStack = matrixStack,
|
||||
scale = 18f,
|
||||
contextScale = speciesToDisplay.getForm(stateToDisplay.currentAspects).baseScale,
|
||||
reversed = reversed,
|
||||
doQuirks = false,
|
||||
state = stateToDisplay,
|
||||
partialTicks = if (Cobblemon.config.animateBattleTiles) deltaTicks else 0F
|
||||
)
|
||||
matrixStack.popPose()
|
||||
context.disableScissor()
|
||||
|
||||
val tile = if (reversed) pokemonTileReversed else pokemonTile
|
||||
val disabled = if (reversed) pokemonTileDisabledReversed else pokemonTileDisabled
|
||||
val selected = if (reversed) pokemonTileSelectedReversed else pokemonTileSelected
|
||||
if (isFainted) {
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = disabled,
|
||||
x = x,
|
||||
y = y,
|
||||
width = 41,
|
||||
height = 39
|
||||
)
|
||||
}
|
||||
else {
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = tile,
|
||||
x = x,
|
||||
y = y,
|
||||
width = 41,
|
||||
height = 39
|
||||
)
|
||||
}
|
||||
|
||||
if (isHovered(mouseX, mouseY)) {
|
||||
renderExpandedInfo(context, reversed, battlePokemon, pokemonDto)
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderExpandedInfo(context: GuiGraphics, reversed: Boolean, pokemon: ClientBattlePokemon, dto: BattlePokemonDTO) {
|
||||
context.pose().pushPose()
|
||||
context.pose().translate(0.0, 0.0, 300.0)
|
||||
val startX = if (reversed) x - 136 else x + 40
|
||||
val startY = y
|
||||
val species = pokemon.species
|
||||
val form = species.getForm(pokemon.state.currentAspects)
|
||||
val expandedInfoTexture = expandedPokemonInfo
|
||||
|
||||
val gap = if (reversed) hoverGapReversed else hoverGap
|
||||
val xOffset = if (reversed) 136 else -7
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = gap,
|
||||
x = startX + xOffset,
|
||||
y = startY,
|
||||
height = 36,
|
||||
width = 8,
|
||||
textureHeight = 36,
|
||||
textureWidth = 8,
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = expandedInfoTexture,
|
||||
x = startX,
|
||||
y = startY,
|
||||
height = 112,
|
||||
width = 137,
|
||||
textureHeight = 112,
|
||||
textureWidth = 137,
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.pokemon_form".asTranslated().bold(),
|
||||
x = startX + 36,
|
||||
y = startY + 6.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getFormText(species, form),
|
||||
x = startX + 36,
|
||||
y = startY + 13.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.buffs".asTranslated().bold(),
|
||||
x = startX + 36,
|
||||
y = startY + 23.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.attack.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 31.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.ATTACK)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 31.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.defence.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 39.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.DEFENCE)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 39.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.stats.special_attack".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 47.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.SPECIAL_ATTACK)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 47.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.stats.special_defence".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 55.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.SPECIAL_DEFENCE)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 55.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.speed.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 63.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.SPEED)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 63.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.accuracy.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 71.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs.get(Stats.ACCURACY)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 71.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.evasion.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 79.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto?.buffs?.get(Stats.EVASION)?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getMultiplierText(it),
|
||||
x = startX + 50,
|
||||
y = startY + 79.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.ability".asTranslated().bold(),
|
||||
x = startX + 100,
|
||||
y = startY + 6.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
val ability = dto.ability?.let { Abilities.get(it) }
|
||||
val abilityName = ability?.displayName?.text() ?: "?".text()
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = abilityName,
|
||||
x = startX + 100,
|
||||
y = startY + 13.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.moves".asTranslated().bold(),
|
||||
x = startX + 100,
|
||||
y = startY + 23.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
if (dto.moves.size != 4) {
|
||||
for (i in 0 until 4) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "?".text(),
|
||||
x = startX + 100,
|
||||
y = startY + 31.5 + i * 8,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
}
|
||||
else {
|
||||
val questionMarkText = "?".text()
|
||||
dto.moves.forEachIndexed { index, moveDTO ->
|
||||
val move = moveDTO?.move ?: questionMarkText
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = move.string.text(),
|
||||
x = if (move == questionMarkText) startX + 100 else startX + 74,
|
||||
y = startY + 31.5 + index * 8,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = move == questionMarkText
|
||||
)
|
||||
|
||||
if (moveDTO != null) {
|
||||
drawScaledTextJustifiedRight(
|
||||
context = context,
|
||||
text = moveDTO.timesUsed.toString().text(),
|
||||
x = startX + 127.5,
|
||||
y = startY + 31.5 + index * 8,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dto.speed != null) {
|
||||
val speed = dto.speed
|
||||
val speedBoost = dto.buffs.get(Stats.SPEED) ?: 1.0
|
||||
val speedAfterBoost = (speed * speedBoost).toInt()
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.speed.name".asTranslated(),
|
||||
x = startX + 100,
|
||||
y = startY + 65.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = speedAfterBoost.toString().text(),
|
||||
x = startX + 100,
|
||||
y = startY + 72.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
else {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.speed_tier".asTranslated(),
|
||||
x = startX + 100,
|
||||
y = startY + 65.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
val speedBoost = dto.buffs.get(Stats.SPEED) ?: 1.0
|
||||
val speedTier = getSpeedRange(form, pokemon.level, speedBoost)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = speedTier,
|
||||
x = startX + 100,
|
||||
y = startY + 72.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.battle.ui.label.held_item".asTranslated(),
|
||||
x = startX + 40,
|
||||
y = startY + 99.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
TypeIcon(
|
||||
x = startX + 100.5,
|
||||
y = startY + 88,
|
||||
type = form.primaryType,
|
||||
secondaryType = form.secondaryType,
|
||||
small = false,
|
||||
centeredX = true
|
||||
).render(context)
|
||||
|
||||
if (form.secondaryType != null) {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = cobblemonResource("textures/gui/battle/type_spacer_double.png"),
|
||||
x = (startX + 69.0) / 0.45,
|
||||
y = (startY + 91.5) / 0.45,
|
||||
height = 24,
|
||||
width = 140,
|
||||
textureHeight = 24,
|
||||
textureWidth = 140,
|
||||
scale = 0.45f
|
||||
)
|
||||
}
|
||||
else {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = cobblemonResource("textures/gui/summary/type_spacer.png"),
|
||||
x = (startX + 70.5) / 0.45,
|
||||
y = (startY + 91.5) / 0.45,
|
||||
height = 24,
|
||||
width = 132,
|
||||
textureHeight = 24,
|
||||
textureWidth = 132,
|
||||
scale = 0.45f
|
||||
)
|
||||
}
|
||||
|
||||
val heldItem = dto.heldItem
|
||||
if (heldItem != null && !heldItem.isEmpty()) {
|
||||
context.pose().pushPose()
|
||||
context.pose().scale(0.95F, 0.95F, 0.95F)
|
||||
val itemX = (startX + 4) / 0.95
|
||||
val itemY = (startY + 91) / 0.95
|
||||
context.renderItem(heldItem, itemX.toInt(), itemY.toInt())
|
||||
context.pose().popPose()
|
||||
}
|
||||
else {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = questionMarkIcon,
|
||||
x = startX + 6,
|
||||
y = startY + 92.5,
|
||||
height = 11,
|
||||
width = 10,
|
||||
)
|
||||
}
|
||||
context.pose().popPose()
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun drawCustomPosablePortrait(
|
||||
identifier: ResourceLocation,
|
||||
matrixStack: PoseStack,
|
||||
scale: Float = 13F,
|
||||
contextScale: Float = 1F,
|
||||
reversed: Boolean = false,
|
||||
state: PosableState,
|
||||
partialTicks: Float,
|
||||
limbSwing: Float = 0F,
|
||||
limbSwingAmount: Float = 0F,
|
||||
ageInTicks: Float = 0F,
|
||||
headYaw: Float = 0F,
|
||||
headPitch: Float = 0F,
|
||||
r: Float = 1F,
|
||||
g: Float = 1F,
|
||||
b: Float = 1F,
|
||||
a: Float = 1F,
|
||||
doQuirks: Boolean = true
|
||||
) {
|
||||
RenderSystem.applyModelViewMatrix()
|
||||
matrixStack.pushPose()
|
||||
matrixStack.translate(0.0, PORTRAIT_DIAMETER.toDouble() + 2.0, 0.0)
|
||||
matrixStack.scale(scale, scale, -scale)
|
||||
matrixStack.translate(0.0, -PORTRAIT_DIAMETER / 18.0, 0.0)
|
||||
|
||||
val sprite = VaryingModelRepository.getSprite(identifier, state, SpriteType.PORTRAIT);
|
||||
|
||||
if (sprite == null) {
|
||||
val model = VaryingModelRepository.getPoser(identifier, state)
|
||||
state.currentModel = model
|
||||
val texture = VaryingModelRepository.getTexture(identifier, state)
|
||||
|
||||
val context = RenderContext()
|
||||
model.context = context
|
||||
VaryingModelRepository.getTextureNoSubstitute(identifier, state).let { context.put(RenderContext.TEXTURE, it) }
|
||||
context.put(RenderContext.SCALE, contextScale)
|
||||
context.put(RenderContext.SPECIES, identifier)
|
||||
context.put(RenderContext.ASPECTS, state.currentAspects)
|
||||
context.put(RenderContext.POSABLE_STATE, state)
|
||||
context.put(RenderContext.DO_QUIRKS, doQuirks)
|
||||
|
||||
val renderType = RenderType.entityCutout(texture)
|
||||
|
||||
val quaternion1 = Axis.YP.rotationDegrees(-32F * if (reversed) -1F else 1F)
|
||||
val quaternion2 = Axis.XP.rotationDegrees(5F)
|
||||
|
||||
val originalPose = state.currentPose
|
||||
state.setPoseToFirstSuitable(PoseType.PORTRAIT)
|
||||
state.updatePartialTicks(partialTicks)
|
||||
model.applyAnimations(null, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch)
|
||||
originalPose?.let { state.setPose(it) }
|
||||
|
||||
matrixStack.translate(
|
||||
model.portraitTranslation.x * if (reversed) -1F else 1F,
|
||||
model.portraitTranslation.y + 1.5 * model.portraitScale,
|
||||
model.portraitTranslation.z - 4
|
||||
)
|
||||
matrixStack.scale(model.portraitScale, model.portraitScale, 1 / model.portraitScale)
|
||||
matrixStack.mulPose(quaternion1)
|
||||
matrixStack.mulPose(quaternion2)
|
||||
|
||||
val light1 = Vector3f(0.2F, 1.0F, -1.0F)
|
||||
val light2 = Vector3f(0.1F, 0.0F, 8.0F)
|
||||
RenderSystem.setShaderLights(light1, light2)
|
||||
quaternion1.conjugate()
|
||||
|
||||
val immediate = Minecraft.getInstance().renderBuffers().bufferSource()
|
||||
val buffer = immediate.getBuffer(renderType)
|
||||
val packedLight = LightTexture.pack(11, 7)
|
||||
|
||||
val colour = toHex(r, g, b, a)
|
||||
model.withLayerContext(immediate, state, VaryingModelRepository.getLayers(identifier, state)) {
|
||||
model.render(context, matrixStack, buffer, packedLight, OverlayTexture.NO_OVERLAY, colour)
|
||||
immediate.endBatch()
|
||||
}
|
||||
|
||||
model.setDefault()
|
||||
|
||||
Lighting.setupFor3DItems()
|
||||
} else {
|
||||
renderSprite(matrixStack, sprite)
|
||||
}
|
||||
|
||||
matrixStack.popPose()
|
||||
}
|
||||
|
||||
private fun getSpeedRange(form: FormData, level: Int, speedBoost: Double): MutableComponent {
|
||||
val base = form.baseStats[Stats.SPEED] ?: 0
|
||||
val maxIV = 31
|
||||
val maxEV = 252
|
||||
val minNatureMod = 0.9
|
||||
val maxNatureMod = 1.1
|
||||
|
||||
val minSpeed = getStat(base, level, 0, 0, minNatureMod) * speedBoost
|
||||
val maxSpeed = getStat(base, level, maxIV, maxEV, maxNatureMod) * speedBoost
|
||||
return "cobblemon.battle.ui.speed_tier".asTranslated(minSpeed.toInt(), maxSpeed.toInt())
|
||||
}
|
||||
|
||||
private fun getStat(base: Int, level: Int, iv: Int, ev: Int, natureMod: Double): Int {
|
||||
return floor((floor(((2.0 * base + iv + floor(ev / 4.0)) * level) / 100) + 5) * natureMod).toInt()
|
||||
}
|
||||
|
||||
private fun getMultiplierText(multiplier: Double): MutableComponent {
|
||||
val rounded = decimalFormat.format(multiplier)
|
||||
val str = "x$rounded"
|
||||
return when {
|
||||
multiplier > 1.0 -> str.green()
|
||||
multiplier < 1.0 -> str.red()
|
||||
else -> str.text()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFormText(species: Species, form: FormData): MutableComponent {
|
||||
val speciesName = species.name
|
||||
val formName = form.name
|
||||
return if (formName == "Normal") speciesName.text() else "$speciesName-$formName".text()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,10 +25,9 @@ import net.minecraft.client.gui.GuiGraphics
|
||||
|
||||
class ForfeitConfirmationSelection(
|
||||
battleGUI: BattleGUI,
|
||||
request: SingleActionRequest
|
||||
val request: SingleActionRequest
|
||||
) : BattleActionSelection(
|
||||
battleGUI,
|
||||
request,
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = WIDTH,
|
||||
|
||||
@@ -10,14 +10,21 @@ package com.cobblemon.mod.common.client.gui.battle.widgets
|
||||
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleMessageQueue
|
||||
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
|
||||
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleTeamInfoSelection
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.components.ObjectSelectionList
|
||||
import net.minecraft.locale.Language
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.util.FormattedCharSequence
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.round
|
||||
|
||||
/**
|
||||
* Pane for seeing and interacting with battle messages.
|
||||
@@ -42,23 +49,38 @@ class BattleMessagePane(
|
||||
val appropriateY: Int
|
||||
get() = minecraft.window.guiScaledHeight - (30 + (if (expanded) FRAME_EXPANDED_HEIGHT else FRAME_HEIGHT))
|
||||
|
||||
val battleMessages = mutableListOf<Component>()
|
||||
|
||||
init {
|
||||
correctSize()
|
||||
//setRenderBackground(false)
|
||||
|
||||
scrollAmount = maxScroll.toDouble()
|
||||
messageQueue.subscribe {
|
||||
val fullyScrolledDown = maxScroll - scrollAmount < 10
|
||||
addEntry(BattleMessageLine(this, it))
|
||||
if (fullyScrolledDown) {
|
||||
scrollAmount = maxScroll.toDouble()
|
||||
battleMessages.add(it)
|
||||
correctBattleText()
|
||||
}
|
||||
}
|
||||
|
||||
private fun correctBattleText() {
|
||||
val isFullyScrolled = maxScroll - scrollAmount < 10
|
||||
clearEntries()
|
||||
val textRenderer = Minecraft.getInstance().font
|
||||
for (message in battleMessages) {
|
||||
val line = message.copy().setStyle(message.style.withBold(true).withFont(CobblemonResources.DEFAULT_LARGE))
|
||||
val wrappedLines = textRenderer.splitter.splitLines(line, battleLogWidth - 27, line.style)
|
||||
val lines = Language.getInstance().getVisualOrder(wrappedLines)
|
||||
for (finalLine in lines) {
|
||||
addEntry(BattleMessageLine(this, finalLine))
|
||||
}
|
||||
}
|
||||
if (isFullyScrolled) {
|
||||
scrollAmount = maxScroll.toDouble()
|
||||
}
|
||||
}
|
||||
|
||||
private fun correctSize() {
|
||||
val textBoxHeight = if (expanded) TEXT_BOX_HEIGHT * 2 else TEXT_BOX_HEIGHT
|
||||
setRectangle(TEXT_BOX_WIDTH, textBoxHeight, appropriateY + 6, appropriateY + 6)
|
||||
this.x = appropriateX
|
||||
setRectangle(battleLogWidth - 11, battleLogHeight - 13, battleLogX.toInt(), battleLogY.toInt() + 8)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -71,10 +93,52 @@ class BattleMessagePane(
|
||||
const val TEXT_BOX_HEIGHT = 46
|
||||
const val EXPAND_TOGGLE_SIZE = 5
|
||||
|
||||
private val battleMessagePaneFrameResource = cobblemonResource("textures/gui/battle/battle_log.png")
|
||||
private val battleMessagePaneFrameExpandedResource = cobblemonResource("textures/gui/battle/battle_log_expanded.png")
|
||||
private val battleMessageHighlight = cobblemonResource("textures/gui/battle/battle_log_row_selected_color.png")
|
||||
|
||||
val topTexture = cobblemonResource("textures/gui/battle/log/top.png")
|
||||
val bottomTexture = cobblemonResource("textures/gui/battle/log/bottom.png")
|
||||
val leftTexture = cobblemonResource("textures/gui/battle/log/left.png")
|
||||
val rightTexture = cobblemonResource("textures/gui/battle/log/right.png")
|
||||
val topLeftTexture = cobblemonResource("textures/gui/battle/log/top_left.png")
|
||||
val topRightTexture = cobblemonResource("textures/gui/battle/log/top_right.png")
|
||||
val bottomLeftTexture = cobblemonResource("textures/gui/battle/log/bottom_left.png")
|
||||
val bottomRightTexture = cobblemonResource("textures/gui/battle/log/bottom_right.png")
|
||||
val centerTexture = cobblemonResource("textures/gui/battle/log/center.png")
|
||||
|
||||
private var expanded = false
|
||||
|
||||
var battleLogWidth: Int = 153
|
||||
set(value) {
|
||||
val max = Minecraft.getInstance().window.guiScaledWidth - 20
|
||||
if (battleLogX + value <= max) {
|
||||
field = value
|
||||
}
|
||||
}
|
||||
|
||||
var battleLogHeight: Int = 46
|
||||
set(value) {
|
||||
val max = Minecraft.getInstance().window.guiScaledHeight - 20
|
||||
if (battleLogY + value <= max) {
|
||||
field = value
|
||||
}
|
||||
}
|
||||
|
||||
var battleLogX: Double = defaultLogX
|
||||
get() {
|
||||
val max = Minecraft.getInstance().window.guiScaledWidth - battleLogWidth
|
||||
return max(min(field, max.toDouble() - 20), 4.0)
|
||||
}
|
||||
|
||||
var battleLogY: Double = defaultLogY
|
||||
get() {
|
||||
val max = Minecraft.getInstance().window.guiScaledHeight - battleLogHeight
|
||||
return max(min(field, max.toDouble() - 20), 4.0)
|
||||
}
|
||||
|
||||
val defaultLogX: Double
|
||||
get() = Minecraft.getInstance().window.guiScaledWidth - 181.0
|
||||
val defaultLogY: Double
|
||||
get() = Minecraft.getInstance().window.guiScaledHeight - 85.0
|
||||
}
|
||||
|
||||
override fun addEntry(entry: BattleMessageLine): Int {
|
||||
@@ -82,7 +146,7 @@ class BattleMessagePane(
|
||||
}
|
||||
|
||||
override fun getRowLeft(): Int {
|
||||
return super.getRowLeft() + 4
|
||||
return x + 10
|
||||
}
|
||||
|
||||
override fun renderSelection(guiGraphics: GuiGraphics, top: Int, width: Int, height: Int, outerColor: Int, innerColor: Int) {
|
||||
@@ -102,7 +166,7 @@ class BattleMessagePane(
|
||||
x = x + 6,
|
||||
y = top - 2,
|
||||
height = 1,
|
||||
width = LINE_WIDTH,
|
||||
width = battleLogWidth - 18,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
@@ -112,14 +176,14 @@ class BattleMessagePane(
|
||||
x = x + 6,
|
||||
y = top + 7,
|
||||
height = 1,
|
||||
width = LINE_WIDTH,
|
||||
width = battleLogWidth - 18,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = guiGraphics.pose(),
|
||||
texture = battleMessageHighlight,
|
||||
x = x + 6 + LINE_WIDTH - 1,
|
||||
x = x + 6 + battleLogWidth - 19,
|
||||
y = top - 2,
|
||||
height = 10,
|
||||
width = 1,
|
||||
@@ -128,48 +192,144 @@ class BattleMessagePane(
|
||||
}
|
||||
|
||||
override fun getRowWidth(): Int {
|
||||
return LINE_WIDTH
|
||||
return battleLogWidth - 16
|
||||
}
|
||||
|
||||
override fun getScrollbarPosition(): Int {
|
||||
return this.x + 154
|
||||
return this.x + battleLogWidth - 16
|
||||
}
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
|
||||
if (BattleTeamInfoSelection.visible) return
|
||||
correctSize()
|
||||
|
||||
val isFullyScrolled = opacity != 1f || maxScroll - scrollAmount < 2
|
||||
if (isFullyScrolled) { scrollAmount = maxScroll.toDouble() }
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = if (expanded) battleMessagePaneFrameExpandedResource else battleMessagePaneFrameResource,
|
||||
x = this.x,
|
||||
y = appropriateY,
|
||||
height = if (expanded) FRAME_EXPANDED_HEIGHT else FRAME_HEIGHT,
|
||||
width = FRAME_WIDTH,
|
||||
texture = topLeftTexture,
|
||||
x = battleLogX,
|
||||
y = battleLogY,
|
||||
height = 8,
|
||||
width = 7,
|
||||
textureHeight = 8,
|
||||
textureWidth = 7,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = topRightTexture,
|
||||
x = battleLogX + battleLogWidth - 13,
|
||||
y = battleLogY,
|
||||
height = 8,
|
||||
width = 13,
|
||||
textureHeight = 8,
|
||||
textureWidth = 13,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = bottomLeftTexture,
|
||||
x = battleLogX,
|
||||
y = battleLogY + battleLogHeight - 5,
|
||||
height = 5,
|
||||
width = 7,
|
||||
textureHeight = 5,
|
||||
textureWidth = 7,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = bottomRightTexture,
|
||||
x = battleLogX + battleLogWidth - 13,
|
||||
y = battleLogY + battleLogHeight - 10,
|
||||
height = 10,
|
||||
width = 13,
|
||||
textureHeight = 10,
|
||||
textureWidth = 13,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = topTexture,
|
||||
x = battleLogX + 7,
|
||||
y = battleLogY,
|
||||
height = 7,
|
||||
width = battleLogWidth - 7 - 13,
|
||||
textureHeight = 7,
|
||||
textureWidth = 1,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = bottomTexture,
|
||||
x = battleLogX + 7,
|
||||
y = battleLogY + battleLogHeight - 4,
|
||||
height = 4,
|
||||
width = battleLogWidth - 7 - 12,
|
||||
textureHeight = 4,
|
||||
textureWidth = 1,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = leftTexture,
|
||||
x = battleLogX,
|
||||
y = battleLogY + 8,
|
||||
height = battleLogHeight - 8 - 5,
|
||||
width = 7,
|
||||
textureHeight = 1,
|
||||
textureWidth = 7,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = rightTexture,
|
||||
x = battleLogX + battleLogWidth - 13,
|
||||
y = battleLogY + 8,
|
||||
height = battleLogHeight - 18,
|
||||
width = 13,
|
||||
textureHeight = 1,
|
||||
textureWidth = 13,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = centerTexture,
|
||||
x = battleLogX + 7,
|
||||
y = battleLogY + 7,
|
||||
height = battleLogHeight - 11,
|
||||
width = battleLogWidth - 20,
|
||||
textureHeight = battleLogHeight - 11,
|
||||
textureWidth = battleLogWidth - 20,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
val textBoxHeight = if (expanded) TEXT_BOX_HEIGHT * 2 else TEXT_BOX_HEIGHT
|
||||
context.enableScissor(
|
||||
this.x + 5,
|
||||
appropriateY + 6,
|
||||
this.x + 5 + width,
|
||||
appropriateY + 6 + textBoxHeight
|
||||
x + 5,
|
||||
round(battleLogY + 6).toInt(),
|
||||
x + 6 + battleLogWidth,
|
||||
round(battleLogY + 6 + battleLogHeight).toInt()
|
||||
)
|
||||
super.renderWidget(context, mouseX, mouseY, partialTicks)
|
||||
context.disableScissor()
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
val toggleOffsetY = if (expanded) 92 else 46
|
||||
if (mouseX > (this.x + 160) && mouseX < (this.x + 160 + EXPAND_TOGGLE_SIZE) && mouseY > (appropriateY + toggleOffsetY) && mouseY < (appropriateY + toggleOffsetY + EXPAND_TOGGLE_SIZE)) {
|
||||
expanded = !expanded
|
||||
}
|
||||
|
||||
updateScrollingState(mouseX, mouseY)
|
||||
if (scrolling) {
|
||||
focused = getEntryAtPosition(mouseX, mouseY)
|
||||
isDragging = true
|
||||
}
|
||||
|
||||
return super.mouseClicked(mouseX, mouseY, button)
|
||||
}
|
||||
|
||||
@@ -183,9 +343,38 @@ class BattleMessagePane(
|
||||
scrollAmount += deltaY
|
||||
}
|
||||
}
|
||||
if (!tryMove(mouseX, mouseY, deltaX, deltaY)) {
|
||||
tryAdjustWidth(mouseX, mouseY, button, deltaX, deltaY)
|
||||
}
|
||||
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||
}
|
||||
|
||||
private fun tryMove(mouseX: Double, mouseY: Double, deltaX: Double, deltaY: Double): Boolean {
|
||||
if (mouseY - deltaY < y - 5 || mouseY - deltaY > y + 5) return false
|
||||
if (mouseX - deltaX < x - 5 || mouseX - deltaX > (x + width + 5)) return false
|
||||
battleLogX = battleLogX + deltaX
|
||||
battleLogY = battleLogY + deltaY
|
||||
return true
|
||||
}
|
||||
|
||||
private fun tryAdjustWidth(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double) {
|
||||
if (button == 1) return
|
||||
val frameWidth = battleLogWidth + 16
|
||||
val frameHeight = battleLogHeight + 9
|
||||
val expandButtonX1 = frameWidth - 9
|
||||
val expandButtonX2 = frameWidth - 4
|
||||
val expandButtonY1 = frameHeight - 9
|
||||
val expandButtonY2 = frameHeight - 4
|
||||
if (mouseX - deltaX < x + expandButtonX1 - 15 || mouseX - deltaX > x + expandButtonX2 + 15) return
|
||||
if (mouseY - deltaY < y + expandButtonY1 - 15 || mouseY - deltaY > y + expandButtonY2 + 15) return
|
||||
val newHeight = max(mouseY.toInt() + 7 - y, TEXT_BOX_HEIGHT)
|
||||
val newWidth = max(mouseX.toInt() - x, TEXT_BOX_WIDTH)
|
||||
battleLogHeight = newHeight
|
||||
battleLogWidth = newWidth
|
||||
correctSize()
|
||||
correctBattleText()
|
||||
}
|
||||
|
||||
private fun updateScrollingState(mouseX: Double, mouseY: Double) {
|
||||
scrolling = mouseX >= this.scrollbarPosition.toDouble()
|
||||
&& mouseX < (this.scrollbarPosition + 3).toDouble()
|
||||
@@ -215,5 +404,9 @@ class BattleMessagePane(
|
||||
opacity = pane.opacity
|
||||
)
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
return super.mouseClicked(mouseX, mouseY, button)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.client.gui.CobblemonRenderable
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.components.Renderable
|
||||
import net.minecraft.client.gui.components.events.GuiEventListener
|
||||
import net.minecraft.client.gui.narration.NarratableEntry
|
||||
import net.minecraft.client.gui.narration.NarratedElementType
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.net.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleInformationRepository
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorInformationPacket
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
object BattleActorInformationHandler : ClientNetworkPacketHandler<BattleActorInformationPacket> {
|
||||
override fun handle(packet: BattleActorInformationPacket, client: Minecraft) {
|
||||
val pokemon = ClientBattleInformationRepository.actors[packet.actor]
|
||||
if (pokemon == null) return
|
||||
val previous = pokemon.find { it.uuid == packet.update.uuid }
|
||||
if (previous == null) return
|
||||
pokemon.add(pokemon.indexOf(previous), packet.update)
|
||||
pokemon.remove(previous)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.net.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleInformationRepository
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorTeamPacket
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
object BattleActorTeamHandler : ClientNetworkPacketHandler<BattleActorTeamPacket> {
|
||||
override fun handle(packet: BattleActorTeamPacket, client: Minecraft) {
|
||||
ClientBattleInformationRepository.actors[packet.actor] = packet.team.toMutableList()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.net.battle
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleInformationRepository
|
||||
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
|
||||
import com.cobblemon.mod.common.client.particle.ParticleStorm
|
||||
import com.cobblemon.mod.common.client.render.MatrixWrapper
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleInformationPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.FieldEffect
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.mojang.authlib.minecraft.client.MinecraftClient
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
object BattleInformationHandler : ClientNetworkPacketHandler<BattleInformationPacket> {
|
||||
override fun handle(packet: BattleInformationPacket, client: Minecraft) {
|
||||
val currentWeather: FieldEffect? = getCurrentWeather()
|
||||
ClientBattleInformationRepository.battles[packet.battle] = packet.informationDTO
|
||||
val newWeather: FieldEffect? = packet.informationDTO.weather
|
||||
if (newWeather != null && currentWeather?.id != newWeather.id) {
|
||||
client.executeIfPossible { playWeatherParticle(newWeather, client) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCurrentWeather(): FieldEffect? {
|
||||
val currentBattleId = CobblemonClient.battle?.battleId ?: return null
|
||||
val currentBattleInfo = ClientBattleInformationRepository.battles[currentBattleId] ?: return null
|
||||
return currentBattleInfo.weather
|
||||
}
|
||||
|
||||
private fun playWeatherParticle(weather: FieldEffect, client: Minecraft) {
|
||||
if (weather.sourceEntityId == null) return
|
||||
val source = client.level?.getEntity(weather.sourceEntityId) ?: return
|
||||
val particle = getParticleForWeather(weather) ?: return
|
||||
val wrapper = MatrixWrapper()
|
||||
val matrix = PoseStack()
|
||||
wrapper.updateMatrix(matrix.last().pose())
|
||||
wrapper.updatePosition(source.position())
|
||||
val world = Minecraft.getInstance().level ?: return
|
||||
val effect = BedrockParticleOptionsRepository.getEffect(particle) ?: return
|
||||
val runtime = MoLangRuntime()
|
||||
runtime.environment.query.addFunction("is_weather_active") { DoubleValue(getCurrentWeather()?.id == weather.id) }
|
||||
ParticleStorm(
|
||||
effect,
|
||||
wrapper,
|
||||
wrapper,
|
||||
world,
|
||||
runtime = runtime
|
||||
).spawn()
|
||||
}
|
||||
|
||||
fun getParticleForWeather(weather: FieldEffect): ResourceLocation? {
|
||||
return when (weather.id) {
|
||||
"hail" -> cobblemonResource("battle_hail")
|
||||
"snow" -> cobblemonResource("battle_snow")
|
||||
"raindance" -> cobblemonResource("battle_rain")
|
||||
"sandstorm" -> cobblemonResource("battle_sandstorm")
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,6 @@ import net.minecraft.locale.Language
|
||||
object BattleMessageHandler : ClientNetworkPacketHandler<BattleMessagePacket> {
|
||||
override fun handle(packet: BattleMessagePacket, client: Minecraft) {
|
||||
val battle = CobblemonClient.battle ?: return
|
||||
val textRenderer = Minecraft.getInstance().font
|
||||
for (message in packet.messages) {
|
||||
val line = message.copy().bold().font(CobblemonResources.DEFAULT_LARGE)
|
||||
val lines = Language.getInstance().getVisualOrder(textRenderer.splitter.splitLines(line, BattleMessagePane.LINE_WIDTH, line.style))
|
||||
battle.messages.add(lines)
|
||||
}
|
||||
battle.messages.add(packet.messages)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.net.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleInformationRepository
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleTimerPacket
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
object BattleTimerHandler : ClientNetworkPacketHandler<BattleTimerPacket> {
|
||||
override fun handle(packet: BattleTimerPacket, client: Minecraft) {
|
||||
ClientBattleInformationRepository.mustChooseBy = packet.mustChooseBy
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.net.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.gui.battle.preview.TeamPreviewGUI
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.CloseTeamPreviewPacket
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
object CloseTeamPreviewHandler : ClientNetworkPacketHandler<CloseTeamPreviewPacket> {
|
||||
override fun handle(packet: CloseTeamPreviewPacket, client: Minecraft) {
|
||||
if (client.screen is TeamPreviewGUI) {
|
||||
client.screen!!.onClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.net.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.battle.preview.ClientBattleTeamPreview
|
||||
import com.cobblemon.mod.common.client.gui.battle.preview.TeamPreviewGUI
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.TeamPreviewPacket
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
object TeamPreviewHandler : ClientNetworkPacketHandler<TeamPreviewPacket> {
|
||||
override fun handle(packet: TeamPreviewPacket, client: Minecraft) {
|
||||
Minecraft.getInstance().setScreen(TeamPreviewGUI(ClientBattleTeamPreview(
|
||||
packet.selections,
|
||||
packet.team,
|
||||
packet.opponent,
|
||||
packet.mustPickBy,
|
||||
packet.opponentName
|
||||
)))
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addPokemonFunctions
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.net.serializers.IdentifierDataSerializer
|
||||
import com.cobblemon.mod.common.api.net.serializers.PlatformTypeDataSerializer
|
||||
import com.cobblemon.mod.common.api.net.serializers.PoseTypeDataSerializer
|
||||
import com.cobblemon.mod.common.api.net.serializers.RideBoostsDataSerializer
|
||||
@@ -63,6 +64,8 @@ import com.cobblemon.mod.common.api.spawning.BestSpawner
|
||||
import com.cobblemon.mod.common.api.spawning.SpawnCause
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.api.types.tera.TeraType
|
||||
import com.cobblemon.mod.common.api.types.tera.TeraTypes
|
||||
import com.cobblemon.mod.common.battles.BagItems
|
||||
import com.cobblemon.mod.common.battles.BattleBuilder
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
@@ -216,7 +219,7 @@ open class PokemonEntity(
|
||||
@JvmStatic var RIDE_STAMINA = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.FLOAT)
|
||||
@JvmStatic var SCALE_MODIFIER = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.FLOAT)
|
||||
@JvmStatic var IS_ALPHA = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.BOOLEAN)
|
||||
|
||||
@JvmStatic var TERASTALLIZED = SynchedEntityData.defineId(PokemonEntity::class.java, IdentifierDataSerializer)
|
||||
const val BATTLE_LOCK = "battle"
|
||||
const val EVOLUTION_LOCK = "evolving"
|
||||
|
||||
@@ -466,6 +469,7 @@ open class PokemonEntity(
|
||||
builder.define(RIDE_STAMINA, 1F)
|
||||
builder.define(SCALE_MODIFIER, 1F)
|
||||
builder.define(IS_ALPHA, false)
|
||||
builder.define(TERASTALLIZED, cobblemonResource("none"))
|
||||
}
|
||||
|
||||
override fun onSyncedDataUpdated(data: EntityDataAccessor<*>) {
|
||||
@@ -528,6 +532,23 @@ open class PokemonEntity(
|
||||
super.handleEntityEvent(status)
|
||||
}
|
||||
|
||||
fun terastallize(type: TeraType?) {
|
||||
if (type != null) {
|
||||
entityData.set(TERASTALLIZED, type.id)
|
||||
setGlowingTag(true)
|
||||
}
|
||||
else {
|
||||
entityData.set(TERASTALLIZED, cobblemonResource("none"))
|
||||
setGlowingTag(false)
|
||||
}
|
||||
}
|
||||
|
||||
fun getTerastallization(): TeraType? {
|
||||
val teraId = entityData.get(TERASTALLIZED)
|
||||
if (teraId == cobblemonResource("id")) return null
|
||||
return TeraTypes.get(teraId)
|
||||
}
|
||||
|
||||
override fun sendDebugPackets() {
|
||||
super.sendDebugPackets()
|
||||
DebugPackets.sendEntityBrain(this)
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.net.messages.client.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.types.tera.TeraType
|
||||
import com.cobblemon.mod.common.api.types.tera.TeraTypes
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.readItemStack
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.cobblemon.mod.common.util.readText
|
||||
import com.cobblemon.mod.common.util.writeItemStack
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import com.cobblemon.mod.common.util.writeText
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import java.util.*
|
||||
|
||||
class BattleActorInformationPacket(val actor: UUID, val update: BattlePokemonDTO): NetworkPacket<BattleActorInformationPacket> {
|
||||
override val id = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeUUID(actor)
|
||||
buffer.writeUUID(update.uuid)
|
||||
buffer.writeBoolean(update.fainted)
|
||||
buffer.writeNullable (update.ability) { _, v -> buffer.writeString(v) }
|
||||
buffer.writeCollection(update.moves) { _, v ->
|
||||
buffer.writeNullable(v) { _, dto ->
|
||||
buffer.writeText(dto.move)
|
||||
buffer.writeInt(dto.timesUsed)
|
||||
}
|
||||
}
|
||||
buffer.writeNullable(update.heldItem) { _, item -> buffer.writeItemStack(item) }
|
||||
buffer.writeMap(update.buffs, { _, v -> buffer.writeEnum(v) }, { _, v -> buffer.writeDouble(v) })
|
||||
buffer.writeNullable(update.speed) { _, v -> buffer.writeInt(v) }
|
||||
buffer.writeNullable(update.terastallized) { _, v -> buffer.writeResourceLocation(v.id) }
|
||||
buffer.writeNullable(update.activeBattlePokemonDTO) { _, v -> v.saveToBuffer(buffer) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("battle_actor_information")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = BattleActorInformationPacket(
|
||||
actor = buffer.readUUID(),
|
||||
update = BattlePokemonDTO(
|
||||
uuid = buffer.readUUID(),
|
||||
fainted = buffer.readBoolean(),
|
||||
ability = buffer.readNullable { buffer.readString() },
|
||||
moves = buffer.readList { buffer.readNullable { MoveDTO(buffer.readText(), buffer.readInt()) } },
|
||||
heldItem = buffer.readNullable { buffer.readItemStack() },
|
||||
buffs = buffer.readMap({ buffer.readEnum(Stats::class.java) }, { buffer.readDouble() }),
|
||||
speed = buffer.readNullable { buffer.readInt() },
|
||||
terastallized = buffer.readNullable { TeraTypes.get(buffer.readResourceLocation()) },
|
||||
activeBattlePokemonDTO = buffer.readNullable { BattleInitializePacket.ActiveBattlePokemonDTO.loadFromBuffer(buffer) }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class BattlePokemonDTO(
|
||||
val uuid: UUID,
|
||||
val fainted: Boolean,
|
||||
val ability: String?,
|
||||
val moves: List<MoveDTO?>,
|
||||
val heldItem: ItemStack?,
|
||||
val buffs: Map<Stats, Double>,
|
||||
val speed: Int?,
|
||||
val terastallized: TeraType?,
|
||||
val activeBattlePokemonDTO: BattleInitializePacket.ActiveBattlePokemonDTO?
|
||||
)
|
||||
|
||||
data class MoveDTO(
|
||||
val move: Component,
|
||||
var timesUsed: Int
|
||||
)
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.net.messages.client.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.types.tera.TeraTypes
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.readItemStack
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.cobblemon.mod.common.util.readText
|
||||
import com.cobblemon.mod.common.util.writeItemStack
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import com.cobblemon.mod.common.util.writeText
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import java.util.UUID
|
||||
|
||||
class BattleActorTeamPacket(val actor: UUID, val team: List<BattlePokemonDTO>): NetworkPacket<BattleActorTeamPacket> {
|
||||
override val id = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeUUID(actor)
|
||||
buffer.writeCollection(team) { pb, value ->
|
||||
pb.writeUUID(value.uuid)
|
||||
pb.writeBoolean(value.fainted)
|
||||
buffer.writeNullable (value.ability) { _, v -> buffer.writeString(v) }
|
||||
pb.writeCollection(value.moves) { _, v ->
|
||||
pb.writeNullable(v) { _, dto ->
|
||||
buffer.writeText(dto.move)
|
||||
buffer.writeInt(dto.timesUsed)
|
||||
}
|
||||
}
|
||||
pb.writeNullable(value.heldItem) { _, item -> buffer.writeItemStack(item) }
|
||||
pb.writeMap(value.buffs, { _, v -> buffer.writeEnum(v) }, { _, v -> buffer.writeDouble(v) })
|
||||
buffer.writeNullable(value.speed) { _, v -> buffer.writeInt(v) }
|
||||
buffer.writeNullable(value.terastallized) { _, v -> buffer.writeResourceLocation(v.id) }
|
||||
buffer.writeNullable(value.activeBattlePokemonDTO) { _, v -> v.saveToBuffer(buffer) }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("battle_team")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = BattleActorTeamPacket(
|
||||
actor = buffer.readUUID(),
|
||||
team = buffer.readList {
|
||||
BattlePokemonDTO(
|
||||
uuid = buffer.readUUID(),
|
||||
fainted = buffer.readBoolean(),
|
||||
ability = buffer.readNullable { buffer.readString() },
|
||||
moves = buffer.readList { buffer.readNullable { MoveDTO(buffer.readText(), buffer.readInt()) } },
|
||||
heldItem = buffer.readNullable { buffer.readItemStack() },
|
||||
buffs = buffer.readMap({ buffer.readEnum(Stats::class.java) }, { buffer.readDouble() }),
|
||||
speed = buffer.readNullable { buffer.readInt() },
|
||||
terastallized = buffer.readNullable { TeraTypes.get(buffer.readResourceLocation()) },
|
||||
activeBattlePokemonDTO = buffer.readNullable { BattleInitializePacket.ActiveBattlePokemonDTO.loadFromBuffer(buffer) }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.net.messages.client.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.readNullable
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.cobblemon.mod.common.util.writeNullable
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import java.time.Instant
|
||||
import java.util.UUID
|
||||
|
||||
class BattleInformationPacket(val battle: UUID, val informationDTO: BattleInformationDTO): NetworkPacket<BattleInformationPacket> {
|
||||
override val id = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeUUID(battle)
|
||||
buffer.writeInt(informationDTO.turn)
|
||||
buffer.writeNullable(informationDTO.weather) { _, value -> encodeFieldEffect(buffer, value) }
|
||||
buffer.writeNullable(informationDTO.terrain) { _, value -> encodeFieldEffect(buffer, value) }
|
||||
buffer.writeNullable(informationDTO.room) { _, value -> encodeFieldEffect(buffer, value) }
|
||||
buffer.writeCollection(informationDTO.side1SidedEffects) { _, value -> encodeFieldEffect(buffer, value) }
|
||||
buffer.writeCollection(informationDTO.side2SidedEffects) { _, value -> encodeFieldEffect(buffer, value) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("battle_information")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = BattleInformationPacket(
|
||||
battle = buffer.readUUID(),
|
||||
informationDTO = BattleInformationDTO(
|
||||
turn = buffer.readInt(),
|
||||
weather = buffer.readNullable { decodeFieldEffect(buffer) },
|
||||
terrain = buffer.readNullable { decodeFieldEffect(buffer) },
|
||||
room = buffer.readNullable { decodeFieldEffect(buffer) },
|
||||
side1SidedEffects = buffer.readList { decodeFieldEffect(buffer) },
|
||||
side2SidedEffects = buffer.readList { decodeFieldEffect(buffer) }
|
||||
)
|
||||
)
|
||||
|
||||
private fun encodeFieldEffect(buffer: RegistryFriendlyByteBuf, fieldEffect: FieldEffect) {
|
||||
buffer.writeString(fieldEffect.id)
|
||||
buffer.writeInt(fieldEffect.turnStarted)
|
||||
buffer.writeNullable(fieldEffect.sourceEntityId) { _, value -> buffer.writeInt(value) }
|
||||
}
|
||||
|
||||
private fun decodeFieldEffect(buffer: RegistryFriendlyByteBuf): FieldEffect {
|
||||
return FieldEffect(
|
||||
id = buffer.readString(),
|
||||
turnStarted = buffer.readInt(),
|
||||
sourceEntityId = buffer.readNullable { buffer.readInt() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class BattleInformationDTO(
|
||||
val turn: Int,
|
||||
val weather: FieldEffect?,
|
||||
val terrain: FieldEffect?,
|
||||
val room: FieldEffect?,
|
||||
val side1SidedEffects: List<FieldEffect>,
|
||||
val side2SidedEffects: List<FieldEffect>
|
||||
)
|
||||
|
||||
data class FieldEffect(
|
||||
val id: String,
|
||||
val turnStarted: Int,
|
||||
val sourceEntityId: Int?
|
||||
)
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.net.messages.client.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import java.time.Instant
|
||||
|
||||
class BattleTimerPacket(val mustChooseBy: Instant?) : NetworkPacket<BattleTimerPacket> {
|
||||
override val id = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeNullable(mustChooseBy) { _, value -> buffer.writeInstant(value) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("battle_timer")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = BattleTimerPacket(
|
||||
mustChooseBy = buffer.readNullable { buffer.readInstant() }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.net.messages.client.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
class CloseTeamPreviewPacket : NetworkPacket<CloseTeamPreviewPacket> {
|
||||
override val id = ID
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("close_team_preview")
|
||||
|
||||
fun decode(buffer: RegistryFriendlyByteBuf): CloseTeamPreviewPacket {
|
||||
return CloseTeamPreviewPacket()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.net.messages.client.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.types.tera.TeraTypes
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleInitializePacket.ActiveBattlePokemonDTO
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.readItemStack
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.cobblemon.mod.common.util.readText
|
||||
import com.cobblemon.mod.common.util.writeItemStack
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import com.cobblemon.mod.common.util.writeText
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.network.chat.Component
|
||||
import java.time.Instant
|
||||
|
||||
class TeamPreviewPacket : NetworkPacket<TeamPreviewPacket> {
|
||||
override val id = ID
|
||||
|
||||
val selections: Int
|
||||
val team: List<BattlePokemonDTO?>
|
||||
val opponent: List<BattlePokemonDTO?>
|
||||
val mustPickBy: Instant
|
||||
val opponentName: Component
|
||||
|
||||
constructor(
|
||||
selections: Int,
|
||||
team: List<BattlePokemonDTO>,
|
||||
opponent: List<BattlePokemonDTO>,
|
||||
mustPickBy: Instant,
|
||||
opponentName: Component
|
||||
) {
|
||||
this.selections = selections
|
||||
this.team = team
|
||||
this.opponent = opponent
|
||||
this.mustPickBy = mustPickBy
|
||||
this.opponentName = opponentName
|
||||
}
|
||||
|
||||
constructor(
|
||||
selections: Int,
|
||||
team: List<Pokemon?>,
|
||||
opponent: List<Pokemon?>,
|
||||
openTeamSheet: Boolean,
|
||||
mustPickBy: Instant,
|
||||
opponentName: Component
|
||||
) {
|
||||
this.selections = selections
|
||||
this.team = team.map { it?.let { BattlePokemon(it).toBattleDTO(true) } }
|
||||
this.opponent = opponent.map { it?.let { BattlePokemon(it).toBattleDTO(openTeamSheet) } }
|
||||
this.mustPickBy = mustPickBy
|
||||
this.opponentName = opponentName
|
||||
}
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeInt(selections)
|
||||
buffer.writeCollection(team) { _, value ->
|
||||
buffer.writeNullable(value) { _, value -> encodeBattlePokemonDTO(buffer, value) }
|
||||
}
|
||||
buffer.writeCollection(opponent) { _, value ->
|
||||
buffer.writeNullable(value) { _, value -> encodeBattlePokemonDTO(buffer, value) }
|
||||
}
|
||||
buffer.writeInstant(mustPickBy)
|
||||
buffer.writeText(opponentName)
|
||||
}
|
||||
|
||||
private fun encodeBattlePokemonDTO(buffer: RegistryFriendlyByteBuf, dto: BattlePokemonDTO) {
|
||||
buffer.writeUUID(dto.uuid)
|
||||
buffer.writeBoolean(dto.fainted)
|
||||
buffer.writeNullable(dto.ability) { _, v -> buffer.writeString(v) }
|
||||
buffer.writeCollection(dto.moves) { _, v ->
|
||||
buffer.writeNullable(v) { _, dto ->
|
||||
buffer.writeText(dto.move)
|
||||
buffer.writeInt(dto.timesUsed)
|
||||
}
|
||||
}
|
||||
buffer.writeNullable(dto.heldItem) { _, item -> buffer.writeItemStack(item) }
|
||||
buffer.writeMap(dto.buffs, { _, v -> buffer.writeEnum(v) }, { _, v -> buffer.writeDouble(v) })
|
||||
buffer.writeNullable(dto.speed) { _, v -> buffer.writeInt(v) }
|
||||
buffer.writeNullable(dto.terastallized) { _, v -> buffer.writeResourceLocation(v.id) }
|
||||
buffer.writeNullable(dto.activeBattlePokemonDTO) { _, v -> v.saveToBuffer(buffer) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("team_preview")
|
||||
|
||||
fun decode(buffer: RegistryFriendlyByteBuf): TeamPreviewPacket {
|
||||
return TeamPreviewPacket(
|
||||
buffer.readInt(),
|
||||
buffer.readList { buffer.readNullable { decodeBattlePokemonDTO(buffer) } },
|
||||
buffer.readList { buffer.readNullable { decodeBattlePokemonDTO(buffer) } },
|
||||
buffer.readInstant(),
|
||||
buffer.readText()
|
||||
)
|
||||
}
|
||||
|
||||
private fun decodeBattlePokemonDTO(buffer: RegistryFriendlyByteBuf): BattlePokemonDTO {
|
||||
return BattlePokemonDTO(
|
||||
uuid = buffer.readUUID(),
|
||||
fainted = buffer.readBoolean(),
|
||||
ability = buffer.readNullable { buffer.readString() },
|
||||
moves = buffer.readList { buffer.readNullable { MoveDTO(buffer.readText(), buffer.readInt()) } },
|
||||
heldItem = buffer.readNullable { buffer.readItemStack() },
|
||||
buffs = buffer.readMap({ buffer.readEnum(Stats::class.java) }, { buffer.readDouble() }),
|
||||
speed = buffer.readNullable { buffer.readInt() },
|
||||
terastallized = buffer.readNullable { TeraTypes.get(buffer.readResourceLocation()) },
|
||||
activeBattlePokemonDTO = buffer.readNullable { ActiveBattlePokemonDTO.loadFromBuffer(buffer) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.net.messages.server.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
class SpectateNearestBattlePacket : NetworkPacket<SpectateNearestBattlePacket> {
|
||||
override val id = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("battle_spectate_nearest")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = SpectateNearestBattlePacket()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.net.messages.server.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import java.util.UUID
|
||||
|
||||
class TeamPreviewSelectPokemonPacket(val selected: Set<Int>) : NetworkPacket<TeamPreviewSelectPokemonPacket> {
|
||||
override val id = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeCollection(selected) { _, value -> buffer.writeInt(value) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("team_preview_select_pokemon")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = TeamPreviewSelectPokemonPacket(
|
||||
buffer.readList { buffer.readInt() }.toSet()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,6 @@ import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.api.text.yellow
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleInitializePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMusicPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
|
||||
import com.cobblemon.mod.common.util.*
|
||||
@@ -40,10 +38,10 @@ object SpectateBattleHandler : ServerNetworkPacketHandler<SpectateBattlePacket>
|
||||
// Check los and range
|
||||
val targetedPlayerEntity = packet.targetedEntityId.getPlayer() ?: return
|
||||
if (player.traceFirstEntityCollision(
|
||||
entityClass = LivingEntity::class.java,
|
||||
ignoreEntity = player,
|
||||
maxDistance = Cobblemon.config.battleSpectateMaxDistance,
|
||||
collideBlock = ClipContext.Fluid.NONE) != targetedPlayerEntity) {
|
||||
entityClass = LivingEntity::class.java,
|
||||
ignoreEntity = player,
|
||||
maxDistance = Cobblemon.config.battleSpectateMaxDistance,
|
||||
collideBlock = ClipContext.Fluid.NONE) != targetedPlayerEntity) {
|
||||
player.sendSystemMessage(lang("ui.interact.failed").yellow())
|
||||
return
|
||||
}
|
||||
@@ -75,10 +73,7 @@ object SpectateBattleHandler : ServerNetworkPacketHandler<SpectateBattlePacket>
|
||||
}
|
||||
|
||||
val battle = BattleRegistry.getBattleByParticipatingPlayer(target) ?: return
|
||||
|
||||
battle.spectators.add(player.uuid)
|
||||
player.sendPacket(BattleInitializePacket(battle, null))
|
||||
player.sendPacket(BattleMessagePacket(battle.chatLog))
|
||||
battle.startSpectating(player)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.net.serverhandling.battle
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMusicPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.SpectateNearestBattlePacket
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import org.apache.logging.log4j.LogManager
|
||||
|
||||
object SpectateNearestBattleHandler : ServerNetworkPacketHandler<SpectateNearestBattlePacket> {
|
||||
val LOGGER = LogManager.getLogger()
|
||||
override fun handle(
|
||||
packet: SpectateNearestBattlePacket,
|
||||
server: MinecraftServer,
|
||||
player: ServerPlayer
|
||||
) {
|
||||
if (BattleRegistry.getBattleBySpectatorId(player.uuid) != null) return
|
||||
if (BattleRegistry.getBattleByParticipatingPlayer(player) != null) return
|
||||
|
||||
val nearest = BattleRegistry.getPlayersCurrentlyBattling()
|
||||
.filter { it != player && it.serverLevel() == player.serverLevel() && it.distanceTo(player) <= 64 }
|
||||
.minByOrNull { it.distanceTo(player) }
|
||||
|
||||
if (nearest == null) return
|
||||
|
||||
val battle = BattleRegistry.getBattleByParticipatingPlayerId(nearest.uuid)
|
||||
if (battle != null && Cobblemon.config.allowSpectating) {
|
||||
val target = battle.actors.filterIsInstance<PlayerBattleActor>().firstOrNull { it.uuid == nearest.uuid }
|
||||
battle.startSpectating(player)
|
||||
target?.battleTheme?.let { player.sendPacket(BattleMusicPacket(it)) }
|
||||
}
|
||||
else {
|
||||
LOGGER.error("Battle of player id ${nearest.uuid} not found (${player.uuid} tried spectating)")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.net.serverhandling.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.battles.TeamPreviewManager
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.TeamPreviewSelectPokemonPacket
|
||||
import com.cobblemon.mod.common.util.safeParty
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
object TeamPreviewSelectPokemonHandler : ServerNetworkPacketHandler<TeamPreviewSelectPokemonPacket> {
|
||||
override fun handle(packet: TeamPreviewSelectPokemonPacket, server: MinecraftServer, player: ServerPlayer) {
|
||||
val party = player.safeParty()?.toGappyList() ?: return
|
||||
for (slot in packet.selected) {
|
||||
if (slot < 0 || slot >= party.size) return
|
||||
if (party[slot] == null) return
|
||||
}
|
||||
TeamPreviewManager.changeSelections(player.uuid, packet.selected)
|
||||
}
|
||||
}
|
||||
@@ -557,9 +557,9 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
* that they had last.
|
||||
*/
|
||||
var benchedMoves = BenchedMoves().also { it.changeFunction = {
|
||||
onChange(BenchedMovesUpdatePacket({ this }, it))
|
||||
this.getOwnerUUID()?.let { it1 -> Cobblemon.playerDataManager.getTMData(it1)?.syncTMsFromPokemon(this) } // we want to make sure to update the players TM Data store when any moves are learned
|
||||
}
|
||||
onChange(BenchedMovesUpdatePacket({ this }, it))
|
||||
this.getOwnerUUID()?.let { it1 -> Cobblemon.playerDataManager.getTMData(it1)?.syncTMsFromPokemon(this) } // we want to make sure to update the players TM Data store when any moves are learned
|
||||
}
|
||||
}
|
||||
internal set(value) {
|
||||
val oldChangeFunction = field.changeFunction
|
||||
@@ -791,6 +791,7 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
return this.form.showdownId()
|
||||
}
|
||||
|
||||
|
||||
fun sendOut(level: ServerLevel, position: Vec3, illusion: IllusionEffect?, mutation: (PokemonEntity) -> Unit = {}): PokemonEntity? {
|
||||
CobblemonEvents.POKEMON_SENT_PRE.postThen(PokemonSentEvent.Pre(this, level, position)) {
|
||||
SeasonFeatureHandler.updateSeason(this, level, position.toBlockPos())
|
||||
@@ -807,6 +808,7 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
fun sendOutWithAnimation(
|
||||
source: LivingEntity,
|
||||
level: ServerLevel,
|
||||
@@ -883,7 +885,7 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
it.battleId = battleId
|
||||
|
||||
if (it.battleId == null) {
|
||||
it.platform = PlatformType.NONE
|
||||
it.platform = PlatformType.NONE
|
||||
}
|
||||
|
||||
it.after(seconds = THROW_DURATION) {
|
||||
@@ -1474,7 +1476,7 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
this.ivs[it.key] = it.value
|
||||
}
|
||||
other.ivs.hyperTrainedIVs.forEach {
|
||||
(stat, value) -> this.ivs.setHyperTrainedIV(stat, value)
|
||||
(stat, value) -> this.ivs.setHyperTrainedIV(stat, value)
|
||||
}
|
||||
}
|
||||
this.evs.doWithoutEmitting {
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.api.dialogue.DialogueManager
|
||||
import com.cobblemon.mod.common.api.reactive.Observable.Companion.filter
|
||||
import com.cobblemon.mod.common.api.reactive.Observable.Companion.takeFirst
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner
|
||||
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.battles.TeamManager
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.RequestPCBoxWallpapersPacket
|
||||
@@ -58,6 +59,13 @@ import kotlin.math.sin
|
||||
|
||||
// Stuff like getting their party
|
||||
fun ServerPlayer.party() = Cobblemon.storage.getParty(this)
|
||||
fun ServerPlayer.safeParty(): PlayerPartyStore? {
|
||||
return try {
|
||||
Cobblemon.storage.getParty(this)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
fun ServerPlayer.pc() = Cobblemon.storage.getPC(this)
|
||||
fun ServerPlayer.pokedex() = Cobblemon.playerDataManager.getPokedexData(this)
|
||||
fun ServerPlayer.tmList() = Cobblemon.playerDataManager.getTMData(this)
|
||||
|
||||
@@ -2237,6 +2237,66 @@
|
||||
"cobblemon.battle.ui.forfeit_confirmation": "Forfeiting makes you lose the battle. Are you sure?",
|
||||
"cobblemon.battle.ui.hide_label": "Press %1$s to move around.",
|
||||
"cobblemon.battle.ui.actions_label": "You need to choose an action. Press %1$s.",
|
||||
"cobblemon.battle.ui.label.buffs": "Buffs/Debuffs",
|
||||
"cobblemon.battle.ui.label.ability": "Ability",
|
||||
"cobblemon.battle.ui.label.moves": "Moves",
|
||||
"cobblemon.battle.ui.label.held_item": "Held Item",
|
||||
"cobblemon.battle.ui.label.speed_tier": "Speed Tier",
|
||||
"cobblemon.battle.ui.label.pokemon_form": "Pokémon Form",
|
||||
"cobblemon.battle.ui.effectiveness.has_no_effect": "Has no effect",
|
||||
"cobblemon.battle.ui.effectiveness.mostly_ineffective": "Mostly ineffective",
|
||||
"cobblemon.battle.ui.effectiveness.not_very_effective": "Not very effective",
|
||||
"cobblemon.battle.ui.effectiveness.effective": "Effective",
|
||||
"cobblemon.battle.ui.effectiveness.super_effective": "Super effective",
|
||||
"cobblemon.battle.ui.effectiveness.extremely_effective": "Extremely effective",
|
||||
"cobblemon.battle.ui.turn": "Turn %1$s",
|
||||
"cobblemon.battle.ui.turn.timer": "Turn %1$s (%2$s)",
|
||||
"cobblemon.battle.ui.stats.special_attack": "Sp. Attack",
|
||||
"cobblemon.battle.ui.stats.special_defence": "Sp. Defense",
|
||||
"cobblemon.battle.ui.speed_tier": "%1$s to %2$s",
|
||||
"cobblemon.battle.ui.effect.raindance": "Rain — %1$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.raindance.range": "Rain — %1$s to %2$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.primordialsea": "Heavy Rain",
|
||||
"cobblemon.battle.ui.effect.sandstorm": "Sandstorm — %1$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.sandstorm.range": "Sandstorm — %1$s to %2$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.sunnyday": "Sun — %1$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.sunnyday.range": "Sun — %1$s to %2$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.desolateland": "Intense Sun",
|
||||
"cobblemon.battle.ui.effect.hail": "Hail — %1$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.hail.range": "Hail — %1$s to %2$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.snow": "Snow — %1$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.snow.range": "Snow — %1$s to %2$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.deltastream": "Delta Stream",
|
||||
"cobblemon.battle.ui.effect.trickroom": "Trick Room — %1$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.magicroom": "Magic Room — %1$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.wonderroom": "Wonder Room — %1$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.electricterrain": "Electric Terrain — %1$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.electricterrain.range": "Electric Terrain — %1$s to %2$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.mistyterrain": "Misty Terrain — %1$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.mistyterrain.range": "Misty Terrain — %1$s to %2$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.psychicterrain": "Psychic Terrain — %1$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.psychicterrain.range": "Psychic Terrain — %1$s to %2$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.grassyterrain": "Grassy Terrain — %1$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.grassyterrain.range": "Grassy Terrain — %1$s to %2$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.corrosiveterrain": "Corrosive Terrain — %1$s to %2$s turn(s) left",
|
||||
"cobblemon.battle.ui.effect.corrosiveterrain.range": "Corrosive Terrain — %1$s to %2$s turn(s) left",
|
||||
"cobblemon.battle.ui.sided_effect.sharpsteel": "Sharp Steel",
|
||||
"cobblemon.battle.ui.sided_effect.spikes": "Spikes",
|
||||
"cobblemon.battle.ui.sided_effect.stealthrock": "Stealth Rock",
|
||||
"cobblemon.battle.ui.sided_effect.stickyweb": "Sticky Web",
|
||||
"cobblemon.battle.ui.sided_effect.toxicspikes": "Toxic Spikes",
|
||||
"cobblemon.battle.ui.sided_effect.reflect": "Reflect — %1$s turn(s) left",
|
||||
"cobblemon.battle.ui.sided_effect.reflect.range": "Reflect — %1$s to %2$s turn(s) left",
|
||||
"cobblemon.battle.ui.sided_effect.lightscreen": "Light Screen — %1$s turn(s) left",
|
||||
"cobblemon.battle.ui.sided_effect.lightscreen.range": "Light Screen — %1$s to %2$s turn(s) left",
|
||||
"cobblemon.battle.ui.sided_effect.auroraveil": "Aurora Veil — %1$s turn(s) left",
|
||||
"cobblemon.battle.ui.sided_effect.auroraveil.range": "Aurora Veil — %1$s to %2$s turn(s) left",
|
||||
"cobblemon.battle.ui.sided_effect.tailwind": "Tailwind — %1$s turn(s) left",
|
||||
"cobblemon.battle.ui.team_info": "Team Information",
|
||||
"cobblemon.battle.ui.team_info.button": "Info",
|
||||
"cobblemon.battle.ui.team_preview.title": "Team Preview",
|
||||
"cobblemon.battle.ui.team_info.title": "Team Info",
|
||||
"cobblemon.battle.ui.team_preview.waiting": "Waiting on opponent%1$s",
|
||||
|
||||
"cobblemon.ui.evolve": "Evolve",
|
||||
"cobblemon.ui.evolution": "Evolution",
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 662 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 721 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 703 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 773 B |
|
After Width: | Height: | Size: 112 B |
|
After Width: | Height: | Size: 133 B |
|
After Width: | Height: | Size: 108 B |
|
After Width: | Height: | Size: 126 B |
|
After Width: | Height: | Size: 133 B |
|
After Width: | Height: | Size: 449 B |
|
After Width: | Height: | Size: 482 B |
|
After Width: | Height: | Size: 453 B |