Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bebdf1da5 | ||
|
|
97d3f8750b | ||
|
|
a50d174ce6 | ||
|
|
14c89872bc | ||
|
|
56687b1529 | ||
|
|
da73ba6668 | ||
|
|
c398def166 | ||
|
|
76bcc622c5 | ||
|
|
df673e7636 | ||
|
|
ab2d79463c | ||
|
|
4a5ee0902e | ||
|
|
773828f7ef |
@@ -45,3 +45,6 @@ common/.fabric
|
||||
# MacOS
|
||||
.DS_Store
|
||||
/common/config/*
|
||||
|
||||
# Temp Files
|
||||
/temp/
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.battles.TeamPreviewManager
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBuildDetails.smallCommitHash
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.api.SeasonResolver
|
||||
@@ -254,6 +256,7 @@ object Cobblemon {
|
||||
}
|
||||
PlatformEvents.SERVER_PLAYER_LOGOUT.subscribe {
|
||||
PCLinkManager.removeLink(it.player.uuid)
|
||||
TeamPreviewManager.remove(it.player)
|
||||
BattleRegistry.onPlayerDisconnect(it.player)
|
||||
storage.onPlayerDisconnect(it.player)
|
||||
playerDataManager.onPlayerDisconnect(it.player)
|
||||
@@ -450,6 +453,7 @@ object Cobblemon {
|
||||
}
|
||||
|
||||
PlatformEvents.SERVER_STOPPED.subscribe {
|
||||
TeamPreviewManager.clear()
|
||||
storage.unregisterAll(it.server.registryAccess())
|
||||
playerDataManager.saveAllStores()
|
||||
playerDataManager.saveExecutor.shutdown()
|
||||
|
||||
@@ -8,6 +8,34 @@
|
||||
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.TeamPreviewSelectPokemonHandler
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.TeamPreviewSelectPokemonPacket
|
||||
|
||||
import com.cobblemon.mod.common.client.net.battle.CloseTeamPreviewHandler
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.CloseTeamPreviewPacket
|
||||
|
||||
import com.cobblemon.mod.common.client.net.battle.TeamPreviewHandler
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.TeamPreviewPacket
|
||||
|
||||
import com.cobblemon.mod.common.client.net.battle.BattleTimerHandler
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleTimerPacket
|
||||
|
||||
import com.cobblemon.mod.common.client.net.battle.BattleInformationHandler
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleInformationPacket
|
||||
|
||||
import com.cobblemon.mod.common.client.net.battle.BattleActorTeamHandler
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorTeamPacket
|
||||
|
||||
import com.cobblemon.mod.common.client.net.battle.BattleActorInformationHandler
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorInformationPacket
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.client.net.OpenBehaviourEditorHandler
|
||||
import com.cobblemon.mod.common.client.net.PlayerInteractOptionsHandler
|
||||
@@ -386,6 +414,12 @@ object CobblemonNetwork {
|
||||
// Battle packets
|
||||
list.add(PacketRegisterInfo(BattleEndPacket.ID, BattleEndPacket::decode, BattleEndHandler))
|
||||
list.add(PacketRegisterInfo(BattleInitializePacket.ID, BattleInitializePacket::decode, BattleInitializeHandler))
|
||||
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))
|
||||
list.add(PacketRegisterInfo(BattleQueueRequestPacket.ID, BattleQueueRequestPacket::decode, BattleQueueRequestHandler))
|
||||
list.add(PacketRegisterInfo(BattleFaintPacket.ID, BattleFaintPacket::decode, BattleFaintHandler))
|
||||
list.add(PacketRegisterInfo(BattleMakeChoicePacket.ID, BattleMakeChoicePacket::decode, BattleMakeChoiceHandler))
|
||||
@@ -573,6 +607,7 @@ object CobblemonNetwork {
|
||||
|
||||
// Battle packets
|
||||
list.add(PacketRegisterInfo(BattleSelectActionsPacket.ID, BattleSelectActionsPacket::decode, BattleSelectActionsHandler))
|
||||
list.add(PacketRegisterInfo(TeamPreviewSelectPokemonPacket.ID, TeamPreviewSelectPokemonPacket::decode, TeamPreviewSelectPokemonHandler))
|
||||
list.add(PacketRegisterInfo(SpectateBattlePacket.ID, SpectateBattlePacket::decode, SpectateBattleHandler))
|
||||
list.add(PacketRegisterInfo(RemoveSpectatorPacket.ID, RemoveSpectatorPacket::decode, RemoveSpectatorHandler))
|
||||
|
||||
|
||||
@@ -8,6 +8,16 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.battles.model
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.FieldEffect
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleInformationPacket
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleInformationDTO
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
@@ -247,6 +257,38 @@ open class PokemonBattle(
|
||||
}
|
||||
}
|
||||
this.turn = newTurnNumber
|
||||
notifyAllOfUpdates()
|
||||
}
|
||||
|
||||
fun notifyAllOfUpdates() {
|
||||
val uuids = this.actors.flatMap { it.getPlayerUUIDs() } + 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() {
|
||||
@@ -454,6 +496,7 @@ open class PokemonBattle(
|
||||
}
|
||||
|
||||
fun tick() {
|
||||
if (!ended) actors.filterIsInstance<PlayerBattleActor>().forEach { it.timer?.tick() }
|
||||
try {
|
||||
while (dispatchResult.canProceed()) {
|
||||
val dispatch = dispatches.poll() ?: break
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.battles.model.actor
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleTimerPacket
|
||||
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
@@ -48,6 +52,20 @@ abstract class BattleActor(
|
||||
var responses = mutableListOf<ShowdownActionResponse>()
|
||||
val expectingPassActions = mutableListOf<ShowdownActionResponse>()
|
||||
var mustChoose = false
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
field = value
|
||||
if (this is PlayerBattleActor) {
|
||||
timer?.let {
|
||||
if (value) {
|
||||
it.startSelection()
|
||||
sendUpdate(BattleTimerPacket(it.mustChooseBy()))
|
||||
} else {
|
||||
it.endSelection()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var itemsUsed = mutableListOf<BagItem>()
|
||||
/** For when battles start, it's the number of Pokémon that are still in the process of being sent out (animation wise) */
|
||||
|
||||
@@ -129,7 +129,12 @@ class ParticleEvent(
|
||||
onDespawn = {},
|
||||
getParticleColor = storm.getParticleColor,
|
||||
targetPos = storm.targetPos,
|
||||
runtime = MoLangRuntime().setup(),
|
||||
runtime = MoLangRuntime().setup().also { runtime ->
|
||||
// Weather sub-emitters share the parent's lifetime condition.
|
||||
storm.runtime.environment.query.functions["is_weather_active"]?.let {
|
||||
runtime.environment.query.addFunction("is_weather_active", it)
|
||||
}
|
||||
},
|
||||
entity = storm.entity
|
||||
)
|
||||
//The reason this doesn't use the newStorms runtime is that the parent storm can add queries AFTER
|
||||
@@ -211,4 +216,4 @@ class EventSoundEffect(
|
||||
).apply(instance, ::EventSoundEffect)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,11 @@ 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 hasTeamPreview: Boolean
|
||||
get() = ruleSet.any { it.equals(BattleRules.TEAM_PREVIEW, ignoreCase = true) || it.equals("teampreview", ignoreCase = true) }
|
||||
|
||||
companion object {
|
||||
fun setBattleRules(
|
||||
battleFormat: BattleFormat,
|
||||
@@ -121,4 +125,4 @@ data class BattleFormat(
|
||||
}
|
||||
""".trimIndent().replace("\n", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,16 @@ object ShowdownInterpreter {
|
||||
}
|
||||
|
||||
// Broadcasts a generic lang to notify players of ability activations (effects are broadcasted separately)
|
||||
fun broadcastAbility(battle: PokemonBattle, effect: Effect, pokemon: BattlePokemon) {
|
||||
@JvmOverloads
|
||||
fun broadcastAbility(battle: PokemonBattle, effect: Effect, pokemon: BattlePokemon, revealedAbility: Effect = effect) {
|
||||
// Direct -ability messages omit the ability: prefix. A copied ability can also differ
|
||||
// from the effect announced in chat (for example, Trace).
|
||||
if (revealedAbility.type == Effect.Type.ABILITY || revealedAbility.type == Effect.Type.PURE) {
|
||||
battle.dispatchGo {
|
||||
pokemon.revealedAbility = revealedAbility.id
|
||||
pokemon.sendUpdate()
|
||||
}
|
||||
}
|
||||
battle.dispatchWaiting(0.5F) {
|
||||
val lang = battleLang("ability.generic", pokemon.getName(), effect.typelessData).yellow()
|
||||
battle.broadcastChatMessage(lang)
|
||||
@@ -321,4 +330,4 @@ object ShowdownInterpreter {
|
||||
fun registerSideInstructionParser(id: String, callback: (PokemonBattle, BattleActor, InstructionSet, BattleMessage) -> InterpreterInstruction) {
|
||||
sideInstructionParser[id] = callback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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 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) {
|
||||
require(sideA.uuid != sideB.uuid) { "A team preview requires two distinct participants" }
|
||||
require(listOf(sideA, sideB).all { it.neededSelections in 1..it.party.count { pokemon -> pokemon != null } }) {
|
||||
"Each side must be able to fill its requested selection"
|
||||
}
|
||||
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 clear() {
|
||||
previews.toList().forEach { cancel(it) }
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!previews.remove(preview)) return
|
||||
if (preview.sideA.isPlayer) {
|
||||
preview.sideA.uuid.getPlayer()?.sendPacket(CloseTeamPreviewPacket())
|
||||
}
|
||||
if (preview.sideB.isPlayer) {
|
||||
preview.sideB.uuid.getPlayer()?.sendPacket(CloseTeamPreviewPacket())
|
||||
}
|
||||
preview.onCancel(preview)
|
||||
}
|
||||
|
||||
fun start(preview: TeamPreview) {
|
||||
if (!previews.remove(preview)) return
|
||||
if (preview.sideA.isPlayer) {
|
||||
preview.sideA.uuid.getPlayer()?.sendPacket(CloseTeamPreviewPacket())
|
||||
}
|
||||
if (preview.sideB.isPlayer) {
|
||||
preview.sideB.uuid.getPlayer()?.sendPacket(CloseTeamPreviewPacket())
|
||||
}
|
||||
preview.onStart(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
|
||||
val side = if (isSideA) preview.sideA else preview.sideB
|
||||
if (!side.isPlayer || !isValidSelection(side.party, selections, side.neededSelections, requireComplete = false)) return
|
||||
previews.remove(preview)
|
||||
if (isSideA) {
|
||||
previews.add(
|
||||
preview.copy(
|
||||
sideA = preview.sideA.copy(selections = selections.toSet()),
|
||||
)
|
||||
)
|
||||
}
|
||||
else {
|
||||
previews.add(
|
||||
preview.copy(
|
||||
sideB = preview.sideB.copy(selections = selections.toSet()),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun hasEligibleSelections(side: TeamPreviewSide): Boolean =
|
||||
isValidSelection(side.party, side.selections, side.neededSelections)
|
||||
|
||||
internal fun isValidSelection(party: List<*>, selections: Set<Int>, needed: Int, requireComplete: Boolean = true): Boolean {
|
||||
if (needed < 1 || selections.size > needed || (requireComplete && selections.size != needed)) return false
|
||||
return selections.all { it in party.indices && party[it] != null }
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.battles.actor
|
||||
|
||||
import com.cobblemon.mod.common.battles.timers.PlayerBattleTimer
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
|
||||
@@ -33,6 +35,8 @@ class PlayerBattleActor(
|
||||
pokemonList: List<BattlePokemon>,
|
||||
) : BattleActor(uuid, pokemonList.toMutableList()), EntityBackedBattleActor<ServerPlayer> {
|
||||
|
||||
var timer: PlayerBattleTimer? = null
|
||||
|
||||
override val initialPos: Vec3?
|
||||
override val entity: ServerPlayer?
|
||||
get() = this.uuid.getPlayer()
|
||||
|
||||
@@ -37,7 +37,7 @@ class AbilityInstruction(val instructionSet: InstructionSet, val message: Battle
|
||||
val optionalPokemon = message.battlePokemonFromOptional(battle)
|
||||
|
||||
// If there is an optional effect causing the activation, broadcast that instead of the standard effect
|
||||
ShowdownInterpreter.broadcastAbility(battle, optionalEffect ?: effect, pokemon)
|
||||
ShowdownInterpreter.broadcastAbility(battle, optionalEffect ?: effect, pokemon, revealedAbility = effect)
|
||||
|
||||
battle.dispatch {
|
||||
val pokemonName = pokemon.getName()
|
||||
@@ -63,4 +63,4 @@ class AbilityInstruction(val instructionSet: InstructionSet, val message: Battle
|
||||
else return@dispatch GO
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,10 @@ class BoostInstruction(battle: PokemonBattle, val message: BattleMessage, val is
|
||||
}
|
||||
battle.broadcastChatMessage(lang)
|
||||
|
||||
val changedStat = Stats.getStat(statKey)
|
||||
val delta = if (isBoost) stages else -stages
|
||||
pokemon.statChanges[changedStat] = ((pokemon.statChanges[changedStat] ?: 0) + delta).coerceIn(-6, 6)
|
||||
pokemon.sendUpdate()
|
||||
val boostBucket = if (isBoost) BattleContext.Type.BOOST else BattleContext.Type.UNBOOST
|
||||
val context = ShowdownInterpreter.getContextFromAction(message, boostBucket, battle)
|
||||
// TODO: replace with context that tracks detailed information such as # of stages
|
||||
|
||||
@@ -26,6 +26,8 @@ class ClearAllBoostInstruction(val message: BattleMessage): InterpreterInstructi
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
battle.dispatchWaiting(1.5F) {
|
||||
battle.activePokemon.forEach {
|
||||
it.battlePokemon?.statChanges?.clear()
|
||||
it.battlePokemon?.sendUpdate()
|
||||
it.battlePokemon?.contextManager?.clear(BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
|
||||
}
|
||||
battle.broadcastChatMessage(battleLang("clearallboost"))
|
||||
|
||||
@@ -30,6 +30,8 @@ class ClearBoostInstruction(val message: BattleMessage): InterpreterInstruction
|
||||
val pokemonName = battlePokemon.getName()
|
||||
val lang = battleLang("clearboost", pokemonName)
|
||||
battle.broadcastChatMessage(lang)
|
||||
battlePokemon.statChanges.clear()
|
||||
battlePokemon.sendUpdate()
|
||||
battlePokemon.contextManager.clear(BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
|
||||
battle.minorBattleActions[battlePokemon.uuid] = message
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ class ClearNegativeBoostInstruction(val message: BattleMessage): InterpreterInst
|
||||
battle.broadcastChatMessage(lang)
|
||||
}
|
||||
|
||||
battlePokemon.statChanges.entries.removeIf { it.value < 0 }
|
||||
battlePokemon.sendUpdate()
|
||||
battlePokemon.contextManager.clear(BattleContext.Type.UNBOOST)
|
||||
battle.minorBattleActions[battlePokemon.uuid] = message
|
||||
}
|
||||
|
||||
@@ -32,6 +32,10 @@ class CopyBoostInstruction(val message: BattleMessage): InterpreterInstruction {
|
||||
val lang = battleLang("copyboost.generic", pokemonName, targetPokemonName)
|
||||
battle.broadcastChatMessage(lang)
|
||||
|
||||
val changes = targetPokemon.statChanges.toMap()
|
||||
pokemon.statChanges.clear()
|
||||
pokemon.statChanges.putAll(changes)
|
||||
pokemon.sendUpdate()
|
||||
pokemon.contextManager.copy(targetPokemon.contextManager, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
|
||||
battle.minorBattleActions[pokemon.uuid] = message
|
||||
}
|
||||
|
||||
@@ -155,6 +155,13 @@ class DamageInstruction(
|
||||
|
||||
val newHealthRatio: Float
|
||||
val remainingHealth = newHealth.split("/")[0].toInt()
|
||||
val previousHealth = battlePokemon.effectedPokemon.currentHealth
|
||||
val damageDealt = (previousHealth - remainingHealth).coerceAtLeast(0)
|
||||
val maxHp = battlePokemon.effectedPokemon.maxHealth
|
||||
if (damageDealt > 0 && maxHp > 0) {
|
||||
val percent = Math.round(damageDealt.toFloat() / maxHp * 100F).coerceAtLeast(1)
|
||||
battle.broadcastChatMessage(battleLang("damage_dealt", battlePokemon.getName(), "$percent%"))
|
||||
}
|
||||
|
||||
if (effect != null) {
|
||||
val lang = when (effect.id) {
|
||||
@@ -222,4 +229,4 @@ class DamageInstruction(
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ class EndItemInstruction(val message: BattleMessage): InterpreterInstruction {
|
||||
val battlePokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
|
||||
val itemEffect = message.effectAt(1) ?: return@dispatchWaiting
|
||||
battlePokemon.heldItemManager.handleEndInstruction(battlePokemon, battle, message)
|
||||
battlePokemon.revealedHeldItem = net.minecraft.world.item.ItemStack.EMPTY
|
||||
battlePokemon.sendUpdate()
|
||||
battle.minorBattleActions[battlePokemon.uuid] = message
|
||||
battlePokemon.contextManager.remove(itemEffect.id, BattleContext.Type.ITEM)
|
||||
if (message.hasOptionalArgument("eat")) {
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.battles.interpreter.instructions
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorTeamPacket
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
@@ -58,6 +60,11 @@ class InitializeInstruction(val instructionSet: InstructionSet, val message: Bat
|
||||
|
||||
battle.actors.forEach { actor ->
|
||||
actor.sendUpdate(BattleSetTeamPokemonPacket(actor.pokemonList.map { it.effectedPokemon }))
|
||||
battle.actors.forEach { viewer ->
|
||||
viewer.sendUpdate(BattleActorTeamPacket(actor.uuid, actor.pokemonList.map {
|
||||
it.toBattleDTO(ally = viewer == actor)
|
||||
}))
|
||||
}
|
||||
val req = actor.request ?: return@forEach
|
||||
actor.sendUpdate(BattleQueueRequestPacket(req))
|
||||
}
|
||||
@@ -68,8 +75,10 @@ class InitializeInstruction(val instructionSet: InstructionSet, val message: Bat
|
||||
|
||||
battle.dispatchGo {
|
||||
battle.started = true
|
||||
battle.activePokemon.mapNotNull { it.battlePokemon }.forEach { it.sendUpdate() }
|
||||
battle.notifyAllOfUpdates()
|
||||
battle.side1.playCries()
|
||||
afterOnServer(seconds = 1.0F) { battle.side2.playCries() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ class InvertBoostInstruction(val message: BattleMessage): InterpreterInstruction
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
battle.dispatchWaiting {
|
||||
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
|
||||
pokemon.statChanges.replaceAll { _, value -> -value }
|
||||
pokemon.sendUpdate()
|
||||
val name = pokemon.getName()
|
||||
battle.broadcastChatMessage(battleLang("invertboost", name))
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ class ItemInstruction(val message: BattleMessage): InterpreterInstruction {
|
||||
battle.dispatchWaiting(1.5F) {
|
||||
val battlePokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
|
||||
battlePokemon.heldItemManager.handleStartInstruction(battlePokemon, battle, message)
|
||||
battlePokemon.revealedHeldItem = battlePokemon.effectedPokemon.heldItem.copy()
|
||||
battlePokemon.sendUpdate()
|
||||
battle.minorBattleActions[battlePokemon.uuid] = message
|
||||
battlePokemon.contextManager.add(ShowdownInterpreter.getContextFromAction(message, BattleContext.Type.ITEM, battle))
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.battles.interpreter.instructions
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.MoveDTO
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
@@ -74,6 +76,14 @@ class MoveInstruction(
|
||||
battle.dispatch { UntilDispatch { instructionSet.getMostRecentInstruction<MoveInstruction>(this)?.future?.isDone != false } }
|
||||
|
||||
battle.dispatch {
|
||||
val knownMove = userPokemon.revealedMoves.firstOrNull { it?.move == move.displayName }
|
||||
if (knownMove != null) {
|
||||
knownMove.timesUsed++
|
||||
} else {
|
||||
val slot = userPokemon.revealedMoves.indexOfFirst { it == null }
|
||||
if (slot >= 0) userPokemon.revealedMoves[slot] = MoveDTO(move.displayName, 1)
|
||||
}
|
||||
userPokemon.sendUpdate()
|
||||
val pokemonName = userPokemon.getName()
|
||||
ShowdownInterpreter.lastCauser[battle.battleId] = message
|
||||
// For Spread targets the message data only gives the pnx strings. So we can't know what pokemon are actually targeted until the previous messages have been interpreted
|
||||
|
||||
@@ -19,6 +19,13 @@ import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
|
||||
*/
|
||||
object PostUpdateInstruction : InterpreterInstruction {
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
battle.dispatchGo { battle.actors.forEach { it.postUpdate() } }
|
||||
battle.dispatchGo {
|
||||
battle.actors.forEach { it.postUpdate() }
|
||||
battle.notifyAllOfUpdates()
|
||||
val viewers = battle.actors.flatMap { it.getPlayerUUIDs() } + battle.spectators
|
||||
battle.actors.forEach { actor ->
|
||||
actor.pokemonList.forEach { it.updateBattleActorInformation(viewers) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.battles.interpreter.instructions
|
||||
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
@@ -27,6 +29,12 @@ class SetBoostInstruction(val message: BattleMessage): InterpreterInstruction {
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
battle.dispatchWaiting(1.5F) {
|
||||
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
|
||||
val stat = message.argumentAt(1)?.let { Stats.getStat(it) }
|
||||
val amount = message.argumentAt(2)?.toIntOrNull()
|
||||
if (stat != null && amount != null) {
|
||||
pokemon.statChanges[stat] = amount.coerceIn(-6, 6)
|
||||
pokemon.sendUpdate()
|
||||
}
|
||||
val pokemonName = pokemon.getName()
|
||||
val effectID = message.effect()?.id ?: return@dispatchWaiting
|
||||
val lang = battleLang("setboost.$effectID", pokemonName)
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.battles.interpreter.instructions
|
||||
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
@@ -29,6 +31,15 @@ class SwapBoostInstruction(val message: BattleMessage): InterpreterInstruction {
|
||||
val pokemonName = pokemon.getName()
|
||||
val targetPokemon = message.battlePokemon(1, battle) ?: return@dispatchWaiting
|
||||
val targetPokemonName = targetPokemon.getName()
|
||||
val stats = message.argumentAt(2)?.split(',')?.map { Stats.getStat(it.trim()) }
|
||||
?: (pokemon.statChanges.keys + targetPokemon.statChanges.keys).toList()
|
||||
stats.forEach { stat ->
|
||||
val previous = pokemon.statChanges[stat] ?: 0
|
||||
pokemon.statChanges[stat] = targetPokemon.statChanges[stat] ?: 0
|
||||
targetPokemon.statChanges[stat] = previous
|
||||
}
|
||||
pokemon.sendUpdate()
|
||||
targetPokemon.sendUpdate()
|
||||
val effectID = message.effect()?.id ?: return@dispatchWaiting
|
||||
val lang = when (effectID) {
|
||||
"guardswap", "powerswap", "heartswap" -> battleLang("swapboost.$effectID", pokemonName)
|
||||
|
||||
@@ -129,6 +129,14 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
|
||||
|
||||
activePokemon.battlePokemon?.let { oldPokemon ->
|
||||
if (publicMessage.effect()?.id == "batonpass") oldPokemon.contextManager.swap(pokemon.contextManager, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
|
||||
pokemon.statChanges.clear()
|
||||
if (publicMessage.effect()?.id == "batonpass") pokemon.statChanges.putAll(oldPokemon.statChanges)
|
||||
oldPokemon.statChanges.clear()
|
||||
if (oldPokemon.transformed != null) {
|
||||
oldPokemon.revealedMoves.indices.forEach { oldPokemon.revealedMoves[it] = null }
|
||||
oldPokemon.revealedAbility = null
|
||||
}
|
||||
oldPokemon.transformed = null
|
||||
oldPokemon.contextManager.clear(BattleContext.Type.VOLATILE, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
|
||||
battle.majorBattleActions[oldPokemon.uuid] = publicMessage
|
||||
|
||||
@@ -215,6 +223,8 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
|
||||
}
|
||||
|
||||
private fun broadcastSwitch(battle: PokemonBattle, actor: BattleActor, newPokemon: BattlePokemon, illusion: BattlePokemon?) {
|
||||
// The active slot and its illusion are now set, so reveal the identity that entered battle.
|
||||
newPokemon.sendUpdate()
|
||||
val publicPokemon = (illusion ?: newPokemon).effectedPokemon
|
||||
val publicLang = publicPokemon.nickname?.let { nickname ->
|
||||
battleLang("switch.other.nickname", actor.getName(), nickname, publicPokemon.species.translatedName)
|
||||
@@ -229,4 +239,4 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ class TerastallizeInstruction(val message: BattleMessage): InterpreterInstructio
|
||||
val battlePokemon = message.battlePokemon(0, battle) ?: return
|
||||
val type = message.effectAt(1)?.let { TeraTypes.get(it.id) } ?: return
|
||||
battle.dispatchWaiting {
|
||||
battlePokemon.terastallized = type
|
||||
battlePokemon.sendUpdate()
|
||||
val pokemonName = battlePokemon.getName()
|
||||
battle.broadcastChatMessage(battleLang("terastallize", pokemonName, type.displayName).yellow())
|
||||
CobblemonEvents.TERASTALLIZATION.post(TerastallizationEvent(battle, battlePokemon, type))
|
||||
|
||||
@@ -46,6 +46,16 @@ class TransformInstruction(val battle: PokemonBattle, val message: BattleMessage
|
||||
}
|
||||
|
||||
battle.dispatchWaiting {
|
||||
pokemon.transformed = targetPokemon
|
||||
pokemon.statChanges.clear()
|
||||
pokemon.statChanges.putAll(targetPokemon.statChanges)
|
||||
for (index in pokemon.revealedMoves.indices) {
|
||||
pokemon.revealedMoves[index] = targetPokemon.moveSet.get(index)?.let {
|
||||
com.cobblemon.mod.common.net.messages.client.battle.MoveDTO(it.displayName, 0)
|
||||
}
|
||||
}
|
||||
pokemon.revealedAbility = targetPokemon.effectedPokemon.ability.name
|
||||
pokemon.sendUpdate()
|
||||
val mock = pokemon.entity?.effects?.mockEffect?.mock
|
||||
val pokemonName = pokemon.getName()
|
||||
val targetPokemonName = targetPokemon.getName()
|
||||
@@ -63,4 +73,4 @@ class TransformInstruction(val battle: PokemonBattle, val message: BattleMessage
|
||||
battle.minorBattleActions[pokemon.uuid] = message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,24 @@
|
||||
|
||||
package com.cobblemon.mod.common.battles.pokemon
|
||||
|
||||
import net.minecraft.world.item.ItemStack
|
||||
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.MoveDTO
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorInformationPacket
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleInitializePacket
|
||||
|
||||
import com.cobblemon.mod.common.api.types.tera.TeraType
|
||||
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
@@ -120,6 +138,13 @@ open class BattlePokemon(
|
||||
|
||||
val contextManager = ContextManager()
|
||||
|
||||
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,8 +154,72 @@ open class BattlePokemon(
|
||||
}
|
||||
}
|
||||
|
||||
fun toBattleDTO(ally: Boolean, forceReveal: Boolean = false): BattlePokemonDTO {
|
||||
val moves = (transformed?.effectedPokemon ?: effectedPokemon).moveSet.getMovesWithNulls().map { move ->
|
||||
move?.let { MoveDTO(it.displayName, revealedMoves.firstOrNull { known -> known?.move == it.displayName }?.timesUsed ?: 0) }
|
||||
}
|
||||
|
||||
val boostMultipliers = statChanges.entries.mapNotNull { (stat, stage) ->
|
||||
val standardStat = stat as? Stats ?: return@mapNotNull null
|
||||
val base = if (standardStat == Stats.ACCURACY || standardStat == Stats.EVASION) 3.0 else 2.0
|
||||
val clamped = stage.coerceIn(-6, 6)
|
||||
standardStat to if (clamped >= 0) (base + clamped) / base else base / (base - clamped)
|
||||
}.toMap().filterValues { it != 1.0 }
|
||||
|
||||
val format = if (::actor.isInitialized && actor.isInitialized()) actor.battle.format else null
|
||||
val isTeamSheet = format?.isOpenTeamSheet == true
|
||||
if (ally || isTeamSheet) {
|
||||
val allyDto = BattleInitializePacket.ActiveBattlePokemonDTO.fromPokemon(this, ally, getIllusion())
|
||||
return BattlePokemonDTO(
|
||||
this.effectedPokemon.uuid,
|
||||
this.effectedPokemon.isFainted(),
|
||||
(transformed?.effectedPokemon ?: effectedPokemon).ability.name,
|
||||
moves,
|
||||
this.effectedPokemon.heldItem,
|
||||
boostMultipliers,
|
||||
if (ally) effectedPokemon.speed else null,
|
||||
terastallized = terastallized,
|
||||
allyDto
|
||||
)
|
||||
}
|
||||
else {
|
||||
// Team Preview reveals identities, while moves, abilities and items still require discovery.
|
||||
val nonAllyDto = if (forceReveal || revealed || format?.hasTeamPreview == true) {
|
||||
BattleInitializePacket.ActiveBattlePokemonDTO.fromPokemon(this, false, getIllusion())
|
||||
} else null
|
||||
return BattlePokemonDTO(
|
||||
this.effectedPokemon.uuid,
|
||||
this.effectedPokemon.isFainted(),
|
||||
revealedAbility,
|
||||
revealedMoves,
|
||||
revealedHeldItem,
|
||||
boostMultipliers,
|
||||
speed = null,
|
||||
terastallized = terastallized,
|
||||
nonAllyDto
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateBattleActorInformation(uuids: List<UUID>) {
|
||||
uuids.forEach {
|
||||
if (actor.uuid == it) {
|
||||
it.getPlayer()?.sendPacket(
|
||||
BattleActorInformationPacket(actor.uuid, toBattleDTO(true))
|
||||
)
|
||||
}
|
||||
else {
|
||||
it.getPlayer()?.sendPacket(
|
||||
BattleActorInformationPacket(actor.uuid, toBattleDTO(false))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sendUpdate() {
|
||||
actor.sendUpdate(BattleUpdateTeamPokemonPacket(effectedPokemon))
|
||||
revealed = revealed || isSentOut()
|
||||
updateBattleActorInformation(actor.battle.actors.flatMap { it.getPlayerUUIDs() } + actor.battle.spectators)
|
||||
}
|
||||
|
||||
fun isSentOut() = actor.battle.activePokemon.any { it.battlePokemon == this }
|
||||
@@ -141,5 +230,7 @@ open class BattlePokemon(
|
||||
!isSentOut() && !willBeSwitchedIn && health > 0
|
||||
}
|
||||
|
||||
fun getIllusion(): BattlePokemon? = this.actor.activePokemon.find { it.battlePokemon == this }?.illusion
|
||||
}
|
||||
fun getIllusion(): BattlePokemon? = if (::actor.isInitialized) {
|
||||
actor.activePokemon.find { it.battlePokemon == this }?.illusion
|
||||
} else null
|
||||
}
|
||||
|
||||
@@ -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 startSelection()
|
||||
fun endSelection()
|
||||
fun tick()
|
||||
fun mustChooseBy(): Instant
|
||||
|
||||
fun timeout() {
|
||||
actor.setActionResponses(listOf(ForfeitActionResponse()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 ShowdownTimer(override val battle: PokemonBattle, override val actor: PlayerBattleActor): PlayerBattleTimer {
|
||||
private lateinit var turnStart: Instant
|
||||
private var secondsRemaining = 210
|
||||
private var hasSelected = true
|
||||
|
||||
override fun tick() {
|
||||
if (hasSelected || !::turnStart.isInitialized || battle.ended) return
|
||||
val secondsSinceStart = Duration.between(turnStart, Instant.now()).seconds
|
||||
if (secondsRemaining - secondsSinceStart <= 0) {
|
||||
hasSelected = true
|
||||
timeout()
|
||||
}
|
||||
}
|
||||
|
||||
override fun startSelection() {
|
||||
turnStart = Instant.now()
|
||||
hasSelected = false
|
||||
}
|
||||
|
||||
override fun endSelection() {
|
||||
if (hasSelected || !::turnStart.isInitialized) return
|
||||
hasSelected = true
|
||||
val secondsSinceStart = Duration.between(turnStart, Instant.now()).seconds.toInt()
|
||||
secondsRemaining = secondsRemaining - secondsSinceStart + 10
|
||||
}
|
||||
|
||||
override fun mustChooseBy(): Instant {
|
||||
return turnStart.plusSeconds(secondsRemaining.toLong())
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.client
|
||||
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleInformationRepository
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon.LOGGER
|
||||
import com.cobblemon.mod.common.CobblemonBlockEntities
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
@@ -124,6 +126,7 @@ object CobblemonClient {
|
||||
storage.onLogout()
|
||||
battle = null
|
||||
battleOverlay.onLogout()
|
||||
ClientBattleInformationRepository.clear()
|
||||
ClientTaskTracker.clear()
|
||||
checkedStarterScreen = false
|
||||
overlay.resetAttachedToast()
|
||||
@@ -483,6 +486,7 @@ object CobblemonClient {
|
||||
}
|
||||
|
||||
fun endBattle() {
|
||||
ClientBattleInformationRepository.clear()
|
||||
battle = null
|
||||
battleOverlay.lastKnownBattle = null
|
||||
BattleMusicController.endMusic()
|
||||
|
||||
@@ -84,6 +84,7 @@ class ClientBattle(
|
||||
pendingActionRequests.map { it.response!! }
|
||||
)
|
||||
)
|
||||
ClientBattleInformationRepository.mustChooseBy = null
|
||||
mustChoose = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.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
|
||||
|
||||
fun clear() {
|
||||
actors.clear()
|
||||
battles.clear()
|
||||
actorActualSides.clear()
|
||||
mustChooseBy = null
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,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
|
||||
@@ -21,6 +22,21 @@ import net.minecraft.util.FormattedCharSequence
|
||||
class ClientBattleMessageQueue {
|
||||
var listeners = mutableListOf<(FormattedCharSequence) -> Unit>()
|
||||
private val messages = mutableListOf<FormattedCharSequence>()
|
||||
private val components = mutableListOf<Component>()
|
||||
private val componentListeners = mutableListOf<(Component) -> Unit>()
|
||||
|
||||
fun addComponents(messages: Iterable<Component>) {
|
||||
messages.forEach { message ->
|
||||
components.add(message)
|
||||
componentListeners.toList().forEach { it(message) }
|
||||
}
|
||||
}
|
||||
|
||||
fun subscribeComponents(listener: (Component) -> Unit): () -> Unit {
|
||||
componentListeners.add(listener)
|
||||
components.forEach(listener)
|
||||
return { componentListeners.remove(listener) }
|
||||
}
|
||||
|
||||
fun add(messages: Iterable<FormattedCharSequence>) {
|
||||
this.messages.addAll(messages)
|
||||
@@ -31,4 +47,4 @@ class ClientBattleMessageQueue {
|
||||
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,42 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.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) {
|
||||
if (pokemon !in team || pokemon in selection || selection.size >= selections) return
|
||||
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()))
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,12 @@ import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleGeneralActionS
|
||||
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleSwitchPokemonSelection
|
||||
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,15 +46,16 @@ 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)
|
||||
|
||||
var queuedActions = mutableListOf<() -> Unit>()
|
||||
|
||||
override fun init() {
|
||||
super.init()
|
||||
if (this::messagePane.isInitialized) messagePane.dispose()
|
||||
messagePane = BattleMessagePane(CobblemonClient.battle!!.messages)
|
||||
messagePane.opacity = CobblemonClient.battleOverlay.opacityRatio.toFloat().coerceAtLeast(0.3F)
|
||||
addRenderableWidget(messagePane)
|
||||
@@ -121,16 +124,17 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
}
|
||||
|
||||
val currentSelection = getCurrentActionSelection()
|
||||
if (currentSelection == null || currentSelection is BattleGeneralActionSelection ) {
|
||||
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),
|
||||
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 +148,11 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
|
||||
queuedActions.forEach { it() }
|
||||
queuedActions.clear()
|
||||
CobblemonClient.battleOverlay.mouseX = mouseX
|
||||
CobblemonClient.battleOverlay.mouseY = mouseY
|
||||
// The benched-team portrait columns + active hover panels are queued during the HUD pass and drawn
|
||||
// here, last, so they sit above the switch-menu underlay, our own widgets and the chat.
|
||||
CobblemonClient.battleOverlay.renderDeferredColumns(context)
|
||||
}
|
||||
|
||||
override fun renderBackground(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
@@ -151,8 +160,6 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
}
|
||||
|
||||
fun deriveRootActionSelection(actor: ClientBattleActor, request: SingleActionRequest): BattleActionSelection? {
|
||||
|
||||
|
||||
return if (request.forceSwitch) {
|
||||
BattleSwitchPokemonSelection(this, request)
|
||||
} else {
|
||||
@@ -175,11 +182,23 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
CobblemonClient.battle?.minimised = true
|
||||
}
|
||||
|
||||
override fun removed() {
|
||||
if (this::messagePane.isInitialized) messagePane.dispose()
|
||||
super.removed()
|
||||
}
|
||||
|
||||
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
|
||||
if (this::messagePane.isInitialized) messagePane.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||
if (CobblemonClient.battleOverlay.columnMouseDragged(mouseX, mouseY)) return true
|
||||
if (this::messagePane.isInitialized && messagePane.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) return true
|
||||
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||
}
|
||||
|
||||
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
if (this::messagePane.isInitialized) messagePane.mouseReleased(mouseX, mouseY, button)
|
||||
if (CobblemonClient.battleOverlay.columnMouseReleased()) return true
|
||||
return super.mouseReleased(mouseX, mouseY, button)
|
||||
}
|
||||
|
||||
override fun charTyped(chr: Char, modifiers: Int): Boolean {
|
||||
if (chr.toString().equals(PartySendBinding.boundKey().displayName.string, ignoreCase = true) && CobblemonClient.battleOverlay.opacity == BattleOverlay.MAX_OPACITY && PartySendBinding.canAction()) {
|
||||
return minimizeBattle()
|
||||
@@ -188,14 +207,18 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
// Grab a benched-team portrait column to drag it (left-click). Consumes the click if one was hit.
|
||||
if (button == 0 && CobblemonClient.battleOverlay.columnMouseClicked(mouseX, mouseY)) return true
|
||||
if (button == PartySendBinding.boundKey().value && CobblemonClient.battleOverlay.opacity == BattleOverlay.MAX_OPACITY && PartySendBinding.canAction()) {
|
||||
return minimizeBattle()
|
||||
}
|
||||
|
||||
val battle = CobblemonClient.battle
|
||||
if (battle?.spectating == true && specBackButton.isHovered(mouseX, mouseY)) {
|
||||
RemoveSpectatorPacket(battle.battleId).sendToServer()
|
||||
CobblemonClient.endBattle()
|
||||
if (battle?.spectating == true) {
|
||||
if (specBackButton.isHovered(mouseX, mouseY)) {
|
||||
RemoveSpectatorPacket(battle.battleId).sendToServer()
|
||||
CobblemonClient.endBattle()
|
||||
}
|
||||
}
|
||||
return super.mouseClicked(mouseX, mouseY, button)
|
||||
}
|
||||
@@ -206,4 +229,4 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
|
||||
PartySendBinding.actioned()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,682 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.cobblemon.mod.common.api.abilities.Abilities
|
||||
import com.cobblemon.mod.common.api.moves.Moves
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
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.battle.ClientBattlePokemon
|
||||
import com.cobblemon.mod.common.client.gui.TypeIcon
|
||||
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.pokemon.FormData
|
||||
import com.cobblemon.mod.common.pokemon.Species
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattlePokemonDTO
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.network.chat.TextColor
|
||||
import net.minecraft.network.chat.contents.TranslatableContents
|
||||
import java.text.DecimalFormat
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
|
||||
/**
|
||||
* The "Info" style expanded stat panel for a single battle Pokémon (form, buffs/debuffs, ability,
|
||||
* moves, speed/speed-tier, types, held item). It renders to the *side* of a tile, with a connector
|
||||
* pointing back at it, and expands inwards (towards the screen centre) on both sides.
|
||||
*
|
||||
* Shared by the team Info screen (the team Info screen)
|
||||
* and the on-field benched-team portrait column ([BattleOverlay.drawPartyColumn]) so the two stay identical.
|
||||
*
|
||||
* Knowledge gating is inherited from [dto]: the team owner gets full data while opponents only carry
|
||||
* what has been revealed, so unrevealed ability/moves show "?" and speed falls back to a tier range.
|
||||
*/
|
||||
object BattlePokemonInfoPanel {
|
||||
private val expandedPokemonInfo = cobblemonResource("textures/gui/battle/expanded_pokemon_info_center.png")
|
||||
// Variant for non-active Pokémon: the left (Buffs/Debuffs) column is cleared for a Current HP gauge.
|
||||
private val expandedPokemonInfoInactive = cobblemonResource("textures/gui/battle/expanded_pokemon_info_center_inactive.png")
|
||||
// Same battle health-bar texture the active Pokémon's tile uses, for the Current HP gauge.
|
||||
private val healthBar = cobblemonResource("textures/gui/battle/health_bar.png")
|
||||
private val hoverGap = cobblemonResource("textures/gui/battle/hover_gap.png")
|
||||
private val hoverGapReversed = cobblemonResource("textures/gui/battle/hover_gap_reversed.png")
|
||||
|
||||
private val multiplierFormat = DecimalFormat("0.##")
|
||||
|
||||
// Boost value slots span x=46..65; leave one pixel of padding at the battle text scale.
|
||||
internal const val BOOST_VALUE_CENTER_X = 55.5
|
||||
internal const val BOOST_VALUE_MAX_WIDTH = 36 // 18 GUI pixels at BattleOverlay.SCALE.
|
||||
|
||||
fun renderUnknownTile(context: GuiGraphics, x: Float, y: Float, reversed: Boolean, scale: Float = 1F, opacity: Number = 1F) {
|
||||
val matrices = context.pose()
|
||||
val insetX = if (reversed) 8 else 5
|
||||
matrices.pushPose()
|
||||
matrices.translate(x.toDouble(), y.toDouble(), 0.0)
|
||||
matrices.scale(scale, scale, scale)
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = BattleOverlay.battleInfoUnderlay,
|
||||
x = insetX,
|
||||
y = 4,
|
||||
width = 28,
|
||||
height = 28,
|
||||
alpha = opacity
|
||||
)
|
||||
// Draw the complete 8x10 asset at twice its size, centred in the portrait window.
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = BattleOverlay.questionMarkIcon,
|
||||
x = (insetX + 6) / 2F,
|
||||
y = 4,
|
||||
width = 8,
|
||||
height = 10,
|
||||
scale = 2F,
|
||||
alpha = opacity
|
||||
)
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = if (reversed) BattleOverlay.partyColumnTileReversed else BattleOverlay.partyColumnTile,
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = 41,
|
||||
height = 39,
|
||||
alpha = opacity
|
||||
)
|
||||
matrices.popPose()
|
||||
}
|
||||
|
||||
fun render(
|
||||
context: GuiGraphics,
|
||||
tileX: Float,
|
||||
tileY: Float,
|
||||
tileWidth: Float,
|
||||
reversed: Boolean,
|
||||
pokemon: ClientBattlePokemon,
|
||||
dto: BattlePokemonDTO,
|
||||
showGap: Boolean = true,
|
||||
showBuffs: Boolean = true
|
||||
) {
|
||||
context.pose().pushPose()
|
||||
context.pose().translate(0.0, 0.0, 300.0)
|
||||
// Panel sits just beside the tile (inwards) with the connector bridging the gap.
|
||||
val startX = if (reversed) tileX - 136 else tileX + tileWidth - 1
|
||||
val startY = tileY
|
||||
val species = pokemon.species
|
||||
val form = species.getForm(pokemon.state.currentAspects)
|
||||
|
||||
// The connector is only shown for the Info screen tiles, not the on-field draggable column.
|
||||
if (showGap) {
|
||||
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 = if (showBuffs) expandedPokemonInfo else expandedPokemonInfoInactive,
|
||||
x = startX,
|
||||
y = startY,
|
||||
height = 112,
|
||||
width = 137,
|
||||
textureHeight = 112,
|
||||
textureWidth = 137,
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.label.pokemon_form".asTranslated().bold(),
|
||||
x = startX + 36,
|
||||
y = startY + 6.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getFormText(species, form),
|
||||
x = startX + 36,
|
||||
y = startY + 13.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
if (showBuffs) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.label.buffs".asTranslated().bold(),
|
||||
x = startX + 36,
|
||||
y = startY + 23.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.attack.name".asTranslated().statColored(Stats.ATTACK),
|
||||
x = startX + 10,
|
||||
y = startY + 31.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs[Stats.ATTACK]?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getBoostText(Stats.ATTACK, it),
|
||||
x = startX + BOOST_VALUE_CENTER_X,
|
||||
y = startY + 31.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.defence.name".asTranslated().statColored(Stats.DEFENCE),
|
||||
x = startX + 10,
|
||||
y = startY + 39.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs[Stats.DEFENCE]?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getBoostText(Stats.DEFENCE, it),
|
||||
x = startX + BOOST_VALUE_CENTER_X,
|
||||
y = startY + 39.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.stats.special_attack".asTranslated().statColored(Stats.SPECIAL_ATTACK),
|
||||
x = startX + 10,
|
||||
y = startY + 47.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs[Stats.SPECIAL_ATTACK]?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getBoostText(Stats.SPECIAL_ATTACK, it),
|
||||
x = startX + BOOST_VALUE_CENTER_X,
|
||||
y = startY + 47.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.stats.special_defence".asTranslated().statColored(Stats.SPECIAL_DEFENCE),
|
||||
x = startX + 10,
|
||||
y = startY + 55.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs[Stats.SPECIAL_DEFENCE]?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getBoostText(Stats.SPECIAL_DEFENCE, it),
|
||||
x = startX + BOOST_VALUE_CENTER_X,
|
||||
y = startY + 55.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.speed.name".asTranslated().statColored(Stats.SPEED),
|
||||
x = startX + 10,
|
||||
y = startY + 63.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs[Stats.SPEED]?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getBoostText(Stats.SPEED, it),
|
||||
x = startX + BOOST_VALUE_CENTER_X,
|
||||
y = startY + 63.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.accuracy.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 71.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs[Stats.ACCURACY]?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getBoostText(Stats.ACCURACY, it),
|
||||
x = startX + BOOST_VALUE_CENTER_X,
|
||||
y = startY + 71.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.evasion.name".asTranslated(),
|
||||
x = startX + 10,
|
||||
y = startY + 79.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = false
|
||||
)
|
||||
|
||||
dto.buffs[Stats.EVASION]?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = getBoostText(Stats.EVASION, it),
|
||||
x = startX + BOOST_VALUE_CENTER_X,
|
||||
y = startY + 79.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
maxCharacterWidth = BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Non-active Pokémon have no stat stages; the inactive panel asset already clears the
|
||||
// Buffs/Debuffs column, so we just draw a "Current HP" gauge on that clean area.
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.label.current_hp".asTranslated().bold(),
|
||||
x = startX + 36,
|
||||
y = startY + 23.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
// Owner sees flat HP (hp/maxHp); opponents carry a 0..1 ratio.
|
||||
val hpRatio = (if (pokemon.isHpFlat) pokemon.hpValue / pokemon.maxHp.toInt() else pokemon.hpValue)
|
||||
.coerceIn(0f, 1f)
|
||||
val (hpRed, hpGreen) = getDepletableRedGreen(hpRatio)
|
||||
|
||||
// Same battle health-bar texture + tint the active Pokémon uses, sized to fill the asset's
|
||||
// bar slot beneath the "Current HP" label (slot ≈ x8..64, y30..36).
|
||||
val barX = startX + 8
|
||||
val barW = hpRatio * 57f
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = healthBar,
|
||||
x = barX,
|
||||
y = startY + 30,
|
||||
width = barW,
|
||||
height = 6,
|
||||
textureWidth = 99,
|
||||
textureHeight = 6,
|
||||
red = hpRed * 0.8f,
|
||||
green = hpGreen * 0.8f,
|
||||
blue = 0.27f
|
||||
)
|
||||
// The texture's right end is angled, so a left-clipped fill has a border on every side except
|
||||
// the right. Re-draw the texture's square left-edge column (texel 0) at the fill's right edge
|
||||
// to close the border into a full rectangle.
|
||||
if (barW >= 1f) {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = healthBar,
|
||||
x = barX + barW - 1f,
|
||||
y = startY + 30,
|
||||
width = 1f,
|
||||
height = 6,
|
||||
textureWidth = 99,
|
||||
textureHeight = 6,
|
||||
red = hpRed * 0.8f,
|
||||
green = hpGreen * 0.8f,
|
||||
blue = 0.27f
|
||||
)
|
||||
}
|
||||
|
||||
// Percentage centred inside the bar — and exact values for your own Pokémon — exactly like
|
||||
// the active Pokémon's health bar does in battle.
|
||||
val hpText = if (pokemon.isHpFlat) {
|
||||
"${pokemon.hpValue.toInt()}/${pokemon.maxHp.toInt()} (${ceil(hpRatio * 100).toInt()}%)"
|
||||
} else {
|
||||
"${ceil(hpRatio * 100).toInt()}%"
|
||||
}
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = hpText.text(),
|
||||
x = startX + 36,
|
||||
y = startY + 31.0,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.label.ability".asTranslated().bold(),
|
||||
x = startX + 100,
|
||||
y = startY + 6.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
val ability = dto.ability?.let { Abilities.get(it) }
|
||||
val abilityName = ability?.displayName?.asTranslated() ?: "?".text()
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = abilityName,
|
||||
x = startX + 100,
|
||||
y = startY + 13.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.label.moves".asTranslated().bold(),
|
||||
x = startX + 100,
|
||||
y = startY + 23.5,
|
||||
scale = BattleOverlay.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.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
}
|
||||
else {
|
||||
val questionMarkText = "?".text()
|
||||
dto.moves.forEachIndexed { index, moveDTO ->
|
||||
val move = moveDTO?.move ?: questionMarkText
|
||||
val moveColor = moveDTO?.move?.let { typeColorFor(it) }
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = move.string.text().let { t -> if (moveColor != null) t.colored(moveColor) else t },
|
||||
x = if (move == questionMarkText) startX + 100 else startX + 74,
|
||||
y = startY + 31.5 + index * 8,
|
||||
scale = BattleOverlay.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.SCALE,
|
||||
shadow = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dtoSpeed = dto.speed
|
||||
if (dtoSpeed != null) {
|
||||
val speedBoost = dto.buffs[Stats.SPEED] ?: 1.0
|
||||
val speedAfterBoost = (dtoSpeed * speedBoost).toInt()
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "cobblemon.stat.speed.name".asTranslated(),
|
||||
x = startX + 100,
|
||||
y = startY + 65.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = speedAfterBoost.toString().text(),
|
||||
x = startX + 100,
|
||||
y = startY + 72.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
else {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.label.speed_tier".asTranslated(),
|
||||
x = startX + 100,
|
||||
y = startY + 65.5,
|
||||
scale = BattleOverlay.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
|
||||
val speedBoost = dto.buffs[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.SCALE,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = "deltaclient.ui.battle.label.held_item".asTranslated(),
|
||||
x = startX + 40,
|
||||
y = startY + 99.5,
|
||||
scale = BattleOverlay.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 = BattleOverlay.questionMarkIcon,
|
||||
x = startX + 6,
|
||||
y = startY + 92.5,
|
||||
height = 11,
|
||||
width = 10,
|
||||
)
|
||||
}
|
||||
context.pose().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 "deltaclient.ui.battle.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()
|
||||
}
|
||||
|
||||
internal fun getBoostText(stat: Stats, multiplier: Double): MutableComponent {
|
||||
val stage = stageFromMultiplier(stat, multiplier)
|
||||
val sign = if (stage > 0) "+" else ""
|
||||
val str = "$sign$stage (${multiplierFormat.format(multiplier)}x)"
|
||||
return when {
|
||||
multiplier > 1.0 -> str.green()
|
||||
multiplier < 1.0 -> str.red()
|
||||
else -> str.text()
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a stat-boost multiplier back to its stage. Accuracy/Evasion use a /3 base, others /2.
|
||||
private fun stageFromMultiplier(stat: Stats, multiplier: Double): Int {
|
||||
val base = if (stat == Stats.ACCURACY || stat == Stats.EVASION) 3.0 else 2.0
|
||||
return if (multiplier >= 1.0) Math.round(multiplier * base - base).toInt()
|
||||
else Math.round(base - base / multiplier).toInt()
|
||||
}
|
||||
|
||||
// The type colour for a move from its "cobblemon.move.<name>" display name, or null if unknown.
|
||||
internal fun typeColorFor(move: Component): Int? {
|
||||
val key = (move.contents as? TranslatableContents)?.key ?: return null
|
||||
if (!key.startsWith("cobblemon.move.")) return null
|
||||
val type = Moves.getByName(key.removePrefix("cobblemon.move."))?.elementalType?.name ?: return null
|
||||
return typeColor(type)
|
||||
}
|
||||
|
||||
private fun typeColor(type: String): Int = when (type.lowercase()) {
|
||||
"bug" -> 0xA8B820
|
||||
"dark" -> 0x705848
|
||||
"dragon" -> 0x7038F8
|
||||
"electric" -> 0xF8D030
|
||||
"fairy" -> 0xEE99AC
|
||||
"fighting" -> 0xC03028
|
||||
"fire" -> 0xF08030
|
||||
"flying" -> 0xA890F0
|
||||
"ghost" -> 0x705898
|
||||
"grass" -> 0x78C850
|
||||
"ground" -> 0xE0C068
|
||||
"ice" -> 0x98D8D8
|
||||
"normal" -> 0xA8A878
|
||||
"poison" -> 0xA040A0
|
||||
"psychic" -> 0xF85888
|
||||
"rock" -> 0xB8A038
|
||||
"steel" -> 0xB8B8D0
|
||||
"water" -> 0x6890F0
|
||||
else -> 0xFFFFFF
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun MutableComponent.colored(rgb: Int): MutableComponent = this.withStyle { it.withColor(TextColor.fromRgb(rgb)) }
|
||||
|
||||
// Buffs/Debuffs stat names are all white. (Per-stat colours can be re-enabled by returning them here.)
|
||||
internal fun statColor(stat: Stats): Int? = null
|
||||
|
||||
internal fun MutableComponent.statColored(stat: Stats): MutableComponent {
|
||||
val rgb = statColor(stat) ?: return this
|
||||
return this.colored(rgb)
|
||||
}
|
||||
@@ -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".asTranslated().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?.asTranslated() ?: "?".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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.battle.subscreen
|
||||
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
|
||||
import com.cobblemon.mod.common.battles.FleeAttemptActionResponse
|
||||
@@ -74,6 +76,12 @@ class BattleGeneralActionSelection(
|
||||
battleGUI.changeActionSelection(ForfeitConfirmationSelection(battleGUI, request))
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
}
|
||||
if (battle.side2.actors.all { it.type == ActorType.PLAYER }) {
|
||||
addOption(rank++, battleLang("ui.team_info.button"), cobblemonResource("textures/gui/battle/team_info.png")) {
|
||||
battleGUI.changeActionSelection(BattleTeamInfoSelection(battleGUI, request))
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,4 +129,4 @@ class BattleGeneralActionSelection(
|
||||
}
|
||||
|
||||
override fun narrationPriority() = NarratableEntry.NarrationPriority.NONE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,27 +10,41 @@ 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,
|
||||
@@ -51,7 +65,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 +102,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 +166,7 @@ class BattleMoveSelection(
|
||||
|
||||
// Move Category
|
||||
MoveCategoryIcon(
|
||||
x = x + 48,
|
||||
x = x + 41,
|
||||
y = y + 14.5,
|
||||
category = moveTemplate.damageCategory,
|
||||
opacity = moveSelection.opacity
|
||||
@@ -168,14 +196,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 = moveTemplate
|
||||
|
||||
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 +483,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
|
||||
@@ -224,4 +511,4 @@ class BattleMoveSelection(
|
||||
override fun playDownSound(soundManager: SoundManager) {
|
||||
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,880 @@
|
||||
/*
|
||||
* 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.client.battle.SingleActionRequest
|
||||
|
||||
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.BattlePokemonInfoPanel
|
||||
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.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,
|
||||
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),
|
||||
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 ->
|
||||
val (slotX, slotY) = getSlotPosition(index, 1)
|
||||
teamTiles.add(PokemonTile(slotX, slotY, pokemon, this, false, actorToPokemon.first().first))
|
||||
}
|
||||
actorToPokemon.last().second?.forEachIndexed { index, pokemon ->
|
||||
val (slotX, slotY) = getSlotPosition(index, 2)
|
||||
opponentTiles.add(PokemonTile(slotX, slotY, pokemon, this, true, actorToPokemon.last().first))
|
||||
}
|
||||
}
|
||||
|
||||
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.pokemonDto.activeBattlePokemonDTO != null && 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".asTranslated().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,
|
||||
private val initialPokemonDto: BattlePokemonDTO,
|
||||
val widget: BattleTeamInfoSelection,
|
||||
val reversed: Boolean,
|
||||
val actor: ClientBattleActor
|
||||
) {
|
||||
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()
|
||||
|
||||
val pokemonDto: BattlePokemonDTO
|
||||
get() = ClientBattleInformationRepository.actors[actor.uuid]?.firstOrNull { it.uuid == initialPokemonDto.uuid } ?: initialPokemonDto
|
||||
|
||||
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
|
||||
if (pokemon == null) {
|
||||
BattlePokemonInfoPanel.renderUnknownTile(context, x, y, reversed)
|
||||
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 = BattlePokemonInfoPanel.getBoostText(Stats.ATTACK, it),
|
||||
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
|
||||
y = startY + 31.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
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 = BattlePokemonInfoPanel.getBoostText(Stats.DEFENCE, it),
|
||||
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
|
||||
y = startY + 39.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
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 = BattlePokemonInfoPanel.getBoostText(Stats.SPECIAL_ATTACK, it),
|
||||
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
|
||||
y = startY + 47.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
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 = BattlePokemonInfoPanel.getBoostText(Stats.SPECIAL_DEFENCE, it),
|
||||
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
|
||||
y = startY + 55.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
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 = BattlePokemonInfoPanel.getBoostText(Stats.SPEED, it),
|
||||
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
|
||||
y = startY + 63.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
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 = BattlePokemonInfoPanel.getBoostText(Stats.ACCURACY, it),
|
||||
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
|
||||
y = startY + 71.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
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 = BattlePokemonInfoPanel.getBoostText(Stats.EVASION, it),
|
||||
x = startX + BattlePokemonInfoPanel.BOOST_VALUE_CENTER_X,
|
||||
y = startY + 79.5,
|
||||
scale = BattleOverlay.Companion.SCALE,
|
||||
maxCharacterWidth = BattlePokemonInfoPanel.BOOST_VALUE_MAX_WIDTH,
|
||||
shadow = true,
|
||||
centered = true
|
||||
)
|
||||
}
|
||||
|
||||
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?.asTranslated() ?: "?".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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ 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.render.drawScaledText
|
||||
@@ -17,7 +18,15 @@ 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.network.chat.Style
|
||||
import net.minecraft.network.chat.TextColor
|
||||
import net.minecraft.network.chat.contents.TranslatableContents
|
||||
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.
|
||||
@@ -36,29 +45,80 @@ class BattleMessagePane(
|
||||
), CobblemonRenderable {
|
||||
var opacity = 1F
|
||||
private var scrolling = false
|
||||
private var resizing = false
|
||||
private var movingBox = false
|
||||
// Offset from the mouse to the box's top-left when a move-drag begins, so the box tracks the cursor
|
||||
// exactly (positioning absolutely avoids the GUI-scale mismatch that accumulating deltas causes).
|
||||
private var dragOffsetX = 0.0
|
||||
private var dragOffsetY = 0.0
|
||||
|
||||
val appropriateX: Int
|
||||
get() = minecraft.window.guiScaledWidth - (FRAME_WIDTH + 12)
|
||||
val appropriateY: Int
|
||||
get() = minecraft.window.guiScaledHeight - (30 + (if (expanded) FRAME_EXPANDED_HEIGHT else FRAME_HEIGHT))
|
||||
|
||||
val battleMessages = mutableListOf<Component>()
|
||||
private val unsubscribe: () -> Unit
|
||||
|
||||
fun dispose() = unsubscribe()
|
||||
|
||||
init {
|
||||
correctSize()
|
||||
//setRenderBackground(false)
|
||||
|
||||
messageQueue.subscribe {
|
||||
val fullyScrolledDown = maxScroll - scrollAmount < 10
|
||||
addEntry(BattleMessageLine(this, it))
|
||||
if (fullyScrolledDown) {
|
||||
scrollAmount = maxScroll.toDouble()
|
||||
}
|
||||
scrollAmount = maxScroll.toDouble()
|
||||
unsubscribe = messageQueue.subscribeComponents {
|
||||
battleMessages.add(it)
|
||||
correctBattleText()
|
||||
}
|
||||
}
|
||||
|
||||
private fun correctBattleText() {
|
||||
val isFullyScrolled = maxScroll - scrollAmount < 10
|
||||
clearEntries()
|
||||
val textRenderer = Minecraft.getInstance().font
|
||||
for (message in battleMessages) {
|
||||
// Turn announcements ("It is now turn N") render as a centered "Turn N" divider header.
|
||||
val contents = message.contents
|
||||
if (contents is TranslatableContents && contents.key == "cobblemon.battle.turn") {
|
||||
val turnNum = contents.args.firstOrNull()?.toString() ?: ""
|
||||
addEntry(BattleMessageLine(this, null, Component.translatable("deltaclient.ui.battle.turn", turnNum).string))
|
||||
continue
|
||||
}
|
||||
// Tint certain message categories by their translation key: weather/field/screen → teal,
|
||||
// stat changes → purple, damage → blue. Other messages keep their existing styling.
|
||||
var style = message.style.withBold(true).withFont(CobblemonResources.DEFAULT_LARGE)
|
||||
val color = messageColor((contents as? TranslatableContents)?.key)
|
||||
if (color != null) style = style.withColor(TextColor.fromRgb(color))
|
||||
val line = message.copy().setStyle(style)
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
/** Colour for a battle message based on its translation key, or null to keep its existing styling. */
|
||||
private fun messageColor(key: String?): Int? = when {
|
||||
key == null -> null
|
||||
key == "cobblemon.battle.damage_dealt" -> 0x4298ED // damage % → blue
|
||||
key.startsWith("cobblemon.battle.weather.") -> 0x42EDAF // weather → teal
|
||||
key.startsWith("cobblemon.battle.fieldstart.") -> 0x42EDAF // terrains, rooms, gravity, etc.
|
||||
key.startsWith("cobblemon.battle.fieldend.") -> 0x42EDAF
|
||||
key.startsWith("cobblemon.battle.fieldactivate.") -> 0x42EDAF
|
||||
key.startsWith("cobblemon.battle.sidestart.") -> 0x42EDAF // Reflect, Light Screen, etc.
|
||||
key.startsWith("cobblemon.battle.sideend.") -> 0x42EDAF
|
||||
key.startsWith("cobblemon.battle.boost.") -> 0x8C42ED // stat rose → purple
|
||||
key.startsWith("cobblemon.battle.unboost.") -> 0x8C42ED // stat fell → purple
|
||||
else -> null
|
||||
}
|
||||
|
||||
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 +131,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 +184,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 +204,7 @@ class BattleMessagePane(
|
||||
x = x + 6,
|
||||
y = top - 2,
|
||||
height = 1,
|
||||
width = LINE_WIDTH,
|
||||
width = battleLogWidth - 18,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
@@ -112,14 +214,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,62 +230,198 @@ 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) {
|
||||
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
|
||||
}
|
||||
|
||||
if (button != 0) return false
|
||||
// Intentionally NOT calling super.mouseClicked — that selects the clicked log line, which we no
|
||||
// longer want. Instead the press grabs the scrollbar, the bottom-right resize handle, or (anywhere
|
||||
// else on the box) moves the whole box.
|
||||
updateScrollingState(mouseX, mouseY)
|
||||
if (scrolling) {
|
||||
focused = getEntryAtPosition(mouseX, mouseY)
|
||||
isDragging = true
|
||||
if (scrolling) return true
|
||||
if (isOnResizeHandle(mouseX, mouseY)) {
|
||||
resizing = true
|
||||
return true
|
||||
}
|
||||
if (isWithinBox(mouseX, mouseY)) {
|
||||
movingBox = true
|
||||
dragOffsetX = mouseX - battleLogX
|
||||
dragOffsetY = mouseY - battleLogY
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return super.mouseClicked(mouseX, mouseY, button)
|
||||
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
scrolling = false
|
||||
resizing = false
|
||||
movingBox = false
|
||||
return super.mouseReleased(mouseX, mouseY, button)
|
||||
}
|
||||
|
||||
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
|
||||
if (scrolling) {
|
||||
if (mouseY < this.y) {
|
||||
scrollAmount = 0.0
|
||||
} else if (mouseY > bottom) {
|
||||
scrollAmount = maxScroll.toDouble()
|
||||
} else {
|
||||
scrollAmount += deltaY
|
||||
when {
|
||||
mouseY < this.y -> scrollAmount = 0.0
|
||||
mouseY > bottom -> scrollAmount = maxScroll.toDouble()
|
||||
else -> scrollAmount += deltaY
|
||||
}
|
||||
return true
|
||||
}
|
||||
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||
if (resizing) {
|
||||
battleLogHeight = max(mouseY.toInt() + 7 - y, TEXT_BOX_HEIGHT)
|
||||
battleLogWidth = max(mouseX.toInt() - x, TEXT_BOX_WIDTH)
|
||||
correctSize()
|
||||
correctBattleText()
|
||||
return true
|
||||
}
|
||||
if (movingBox) {
|
||||
battleLogX = mouseX - dragOffsetX
|
||||
battleLogY = mouseY - dragOffsetY
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/** The whole message box (frame included) — grab it anywhere here to move it. */
|
||||
private fun isWithinBox(mouseX: Double, mouseY: Double): Boolean {
|
||||
return mouseX >= battleLogX && mouseX <= battleLogX + battleLogWidth &&
|
||||
mouseY >= battleLogY && mouseY <= battleLogY + battleLogHeight
|
||||
}
|
||||
|
||||
/** The resize handle in the bottom-right corner (drag to expand); takes priority over moving. */
|
||||
private fun isOnResizeHandle(mouseX: Double, mouseY: Double): Boolean {
|
||||
return mouseX >= battleLogX + battleLogWidth - 13 && mouseX <= battleLogX + battleLogWidth &&
|
||||
mouseY >= battleLogY + battleLogHeight - 10 && mouseY <= battleLogY + battleLogHeight
|
||||
}
|
||||
|
||||
private fun updateScrollingState(mouseX: Double, mouseY: Double) {
|
||||
@@ -193,7 +431,7 @@ class BattleMessagePane(
|
||||
&& mouseY < bottom
|
||||
}
|
||||
|
||||
class BattleMessageLine(val pane: BattleMessagePane, val line: FormattedCharSequence) : Entry<BattleMessageLine>() {
|
||||
class BattleMessageLine(val pane: BattleMessagePane, val line: FormattedCharSequence?, val headerText: String? = null) : Entry<BattleMessageLine>() {
|
||||
override fun getNarration() = "".text()
|
||||
override fun render(
|
||||
context: GuiGraphics,
|
||||
@@ -207,13 +445,54 @@ class BattleMessagePane(
|
||||
isHovered: Boolean,
|
||||
partialTicks: Float
|
||||
) {
|
||||
if (headerText != null) {
|
||||
// "Turn N" centered on the divider's true midpoint, with an equal-length line each side.
|
||||
val comp = headerText.text().setStyle(Style.EMPTY.withBold(true).withFont(CobblemonResources.DEFAULT_LARGE))
|
||||
val textWidth = Minecraft.getInstance().font.width(comp)
|
||||
val dividerLeft = rowLeft - 3
|
||||
val dividerRight = rowLeft + rowWidth - 7
|
||||
val centerX = (dividerLeft + dividerRight) / 2
|
||||
val lineY = rowTop + 2
|
||||
val leftEnd = centerX - textWidth / 2 - 3
|
||||
val rightStart = centerX + textWidth / 2 + 3
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = CobblemonResources.WHITE,
|
||||
x = dividerLeft,
|
||||
y = lineY,
|
||||
width = (leftEnd - dividerLeft).coerceAtLeast(0),
|
||||
height = 1,
|
||||
alpha = pane.opacity * 0.6f
|
||||
)
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = CobblemonResources.WHITE,
|
||||
x = rightStart,
|
||||
y = lineY,
|
||||
width = (dividerRight - rightStart).coerceAtLeast(0),
|
||||
height = 1,
|
||||
alpha = pane.opacity * 0.6f
|
||||
)
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = comp,
|
||||
x = centerX - textWidth / 2,
|
||||
y = rowTop - 2,
|
||||
opacity = pane.opacity
|
||||
)
|
||||
return
|
||||
}
|
||||
drawScaledText(
|
||||
context,
|
||||
line,
|
||||
line ?: return,
|
||||
rowLeft,
|
||||
rowTop - 2,
|
||||
opacity = pane.opacity
|
||||
)
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
return super.mouseClicked(mouseX, mouseY, button)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.net.battle
|
||||
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleInformationRepository
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.battle.ActiveClientBattlePokemon
|
||||
@@ -20,6 +22,9 @@ import net.minecraft.client.Minecraft
|
||||
|
||||
object BattleInitializeHandler : ClientNetworkPacketHandler<BattleInitializePacket> {
|
||||
override fun handle(packet: BattleInitializePacket, client: Minecraft) {
|
||||
ClientBattleInformationRepository.clear()
|
||||
packet.side1.actors.forEach { ClientBattleInformationRepository.actorActualSides[it.uuid] = 1 }
|
||||
packet.side2.actors.forEach { ClientBattleInformationRepository.actorActualSides[it.uuid] = 2 }
|
||||
val playerUUID = Minecraft.getInstance().player?.uuid
|
||||
CobblemonClient.battle = ClientBattle(
|
||||
packet.battleId,
|
||||
|
||||
@@ -21,6 +21,7 @@ import net.minecraft.locale.Language
|
||||
object BattleMessageHandler : ClientNetworkPacketHandler<BattleMessagePacket> {
|
||||
override fun handle(packet: BattleMessagePacket, client: Minecraft) {
|
||||
val battle = CobblemonClient.battle ?: return
|
||||
battle.messages.addComponents(packet.messages)
|
||||
val textRenderer = Minecraft.getInstance().font
|
||||
for (message in packet.messages) {
|
||||
val line = message.copy().bold().font(CobblemonResources.DEFAULT_LARGE)
|
||||
@@ -28,4 +29,4 @@ object BattleMessageHandler : ClientNetworkPacketHandler<BattleMessagePacket> {
|
||||
battle.messages.add(lines)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)))
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.events
|
||||
|
||||
import com.cobblemon.mod.common.battles.TeamPreviewManager
|
||||
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.util.party
|
||||
import net.minecraft.server.MinecraftServer
|
||||
@@ -16,6 +18,7 @@ object ServerTickHandler {
|
||||
private var secondsTick = 0
|
||||
|
||||
fun onTick(server: MinecraftServer) {
|
||||
TeamPreviewManager.tick()
|
||||
BattleRegistry.tick()
|
||||
|
||||
secondsTick++
|
||||
|
||||
@@ -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, forceReveal = true) } }
|
||||
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,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()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.net.serverhandling.battle
|
||||
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleActorTeamPacket
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
|
||||
@@ -79,6 +81,10 @@ object SpectateBattleHandler : ServerNetworkPacketHandler<SpectateBattlePacket>
|
||||
battle.spectators.add(player.uuid)
|
||||
player.sendPacket(BattleInitializePacket(battle, null))
|
||||
player.sendPacket(BattleMessagePacket(battle.chatLog))
|
||||
battle.notifyOfUpdates(listOf(player.uuid))
|
||||
battle.actors.forEach { actor ->
|
||||
player.sendPacket(BattleActorTeamPacket(actor.uuid, actor.pokemonList.map { it.toBattleDTO(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.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 net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
object TeamPreviewSelectPokemonHandler : ServerNetworkPacketHandler<TeamPreviewSelectPokemonPacket> {
|
||||
override fun handle(packet: TeamPreviewSelectPokemonPacket, server: MinecraftServer, player: ServerPlayer) {
|
||||
TeamPreviewManager.changeSelections(player.uuid, packet.selected)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"format_version": "1.10.0",
|
||||
"particle_effect": {
|
||||
"description": {
|
||||
"identifier": "cobblemon:battle_hail",
|
||||
"basic_render_parameters": {
|
||||
"material": "particles_blend",
|
||||
"texture": "textures/particles/weather/battle_hail"
|
||||
}
|
||||
},
|
||||
"curves": {
|
||||
"variable.size": {
|
||||
"type": "linear",
|
||||
"input": "v.particle_age",
|
||||
"horizontal_range": "v.particle_lifetime",
|
||||
"nodes": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]
|
||||
}
|
||||
},
|
||||
"events": {
|
||||
"event_pieces": {
|
||||
"particle_effect": {
|
||||
"effect": "cobblemon:battle_hail_pieces",
|
||||
"type": "particle_with_velocity"
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"minecraft:emitter_rate_steady": {
|
||||
"spawn_rate": 75,
|
||||
"max_particles": 600
|
||||
},
|
||||
"minecraft:emitter_lifetime_expression": {
|
||||
"activation_expression": 1,
|
||||
"expiration_expression": "q.is_weather_active == 0"
|
||||
},
|
||||
"minecraft:emitter_shape_disc": {
|
||||
"offset": [0, 32, 0],
|
||||
"radius": 16,
|
||||
"direction": [0, -1, 0]
|
||||
},
|
||||
"minecraft:particle_lifetime_expression": {
|
||||
"max_lifetime": 2
|
||||
},
|
||||
"minecraft:particle_initial_speed": 48,
|
||||
"minecraft:particle_motion_dynamic": {
|
||||
"linear_acceleration": [0, -24, 0]
|
||||
},
|
||||
"minecraft:particle_appearance_billboard": {
|
||||
"size": ["0.4 * v.size", "0.4 * v.size"],
|
||||
"facing_camera_mode": "rotate_xyz",
|
||||
"uv": {
|
||||
"texture_width": 32,
|
||||
"texture_height": 8,
|
||||
"uv": ["math.floor(v.particle_random_3*4)*8", 0],
|
||||
"uv_size": [8, 8]
|
||||
}
|
||||
},
|
||||
"minecraft:particle_motion_collision": {
|
||||
"collision_drag": 1000,
|
||||
"coefficient_of_restitution": 0.07,
|
||||
"collision_radius": 0.15
|
||||
},
|
||||
"minecraft:particle_appearance_tinting": {
|
||||
"color": {
|
||||
"interpolant": "v.particle_age / v.particle_lifetime",
|
||||
"gradient": {
|
||||
"0.0": "#00FFFFFF",
|
||||
"0.16": "#ffffffff",
|
||||
"1.0": "#FFFFFFFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"format_version": "1.10.0",
|
||||
"particle_effect": {
|
||||
"description": {
|
||||
"identifier": "cobblemon:battle_hail_pieces",
|
||||
"basic_render_parameters": {
|
||||
"material": "particles_alpha",
|
||||
"texture": "textures/particles/weather/battle_hail_pieces"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"minecraft:emitter_rate_instant": {
|
||||
"num_particles": 2
|
||||
},
|
||||
"minecraft:emitter_lifetime_once": {
|
||||
"active_time": 1
|
||||
},
|
||||
"minecraft:emitter_shape_point": {
|
||||
"direction": ["math.random(-0.6,0.6)", 0.2, "math.random(-0.6,0.6)"]
|
||||
},
|
||||
"minecraft:particle_lifetime_expression": {
|
||||
"max_lifetime": 0.5
|
||||
},
|
||||
"minecraft:particle_initial_spin": {
|
||||
"rotation": "math.random_integer(1,4) * 90"
|
||||
},
|
||||
"minecraft:particle_initial_speed": 5,
|
||||
"minecraft:particle_motion_dynamic": {
|
||||
"linear_acceleration": [0, -10, 0],
|
||||
"linear_drag_coefficient": 2
|
||||
},
|
||||
"minecraft:particle_appearance_billboard": {
|
||||
"size": [0.4, 0.4],
|
||||
"facing_camera_mode": "rotate_xyz",
|
||||
"uv": {
|
||||
"texture_width": 32,
|
||||
"texture_height": 8,
|
||||
"uv": ["math.floor(variable.particle_random_2 * 4) * 8", 0],
|
||||
"uv_size": [8, 8]
|
||||
}
|
||||
},
|
||||
"minecraft:particle_motion_collision": {
|
||||
"collision_drag": 0.6,
|
||||
"coefficient_of_restitution": 1,
|
||||
"collision_radius": 0.01
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"format_version": "1.10.0",
|
||||
"particle_effect": {
|
||||
"description": {
|
||||
"identifier": "cobblemon:battle_rain",
|
||||
"basic_render_parameters": {
|
||||
"material": "particles_blend",
|
||||
"texture": "textures/particles/weather/battle_rain"
|
||||
}
|
||||
},
|
||||
"events": {
|
||||
"event_splash": {
|
||||
"particle_effect": {
|
||||
"effect": "cobblemon:battle_splash",
|
||||
"type": "particle"
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"minecraft:emitter_rate_steady": {
|
||||
"spawn_rate": 300,
|
||||
"max_particles": 600
|
||||
},
|
||||
"minecraft:emitter_lifetime_expression": {
|
||||
"activation_expression": 1,
|
||||
"expiration_expression": "q.is_weather_active == 0"
|
||||
},
|
||||
"minecraft:emitter_shape_disc": {
|
||||
"offset": [0, 32, 0],
|
||||
"radius": 16,
|
||||
"direction": ["v.particle_random_1 * -v.particle_random_1", -1, "v.particle_random_1 * -v.particle_random_1"]
|
||||
},
|
||||
"minecraft:particle_lifetime_expression": {
|
||||
"max_lifetime": 2
|
||||
},
|
||||
"minecraft:particle_lifetime_events": {
|
||||
"expiration_event": ["event_splash", "event_smoke"]
|
||||
},
|
||||
"minecraft:particle_initial_speed": 8,
|
||||
"minecraft:particle_motion_dynamic": {
|
||||
"linear_acceleration": [0, -32, 0]
|
||||
},
|
||||
"minecraft:particle_appearance_billboard": {
|
||||
"size": [0.2, 0.5],
|
||||
"facing_camera_mode": "lookat_direction",
|
||||
"uv": {
|
||||
"texture_width": 16,
|
||||
"texture_height": 16,
|
||||
"uv": [0, 0],
|
||||
"uv_size": [16, 16]
|
||||
}
|
||||
},
|
||||
"minecraft:particle_motion_collision": {
|
||||
"collision_radius": 0.1,
|
||||
"expire_on_contact": true
|
||||
},
|
||||
"minecraft:particle_appearance_tinting": {
|
||||
"color": {
|
||||
"interpolant": "v.particle_age / v.particle_lifetime",
|
||||
"gradient": {
|
||||
"0.0": "#00FFFFFF",
|
||||
"0.48": "#ffffffff",
|
||||
"1.0": "#FFFFFFFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"format_version": "1.10.0",
|
||||
"particle_effect": {
|
||||
"description": {
|
||||
"identifier": "cobblemon:battle_sandstorm",
|
||||
"basic_render_parameters": {
|
||||
"material": "particles_blend",
|
||||
"texture": "textures/particles/weather/battle_sand"
|
||||
}
|
||||
},
|
||||
"curves": {
|
||||
"variable.sway": {
|
||||
"type": "linear",
|
||||
"input": "v.particle_age",
|
||||
"horizontal_range": "v.particle_lifetime",
|
||||
"nodes": [-1, 1, -1, 1, -1]
|
||||
}
|
||||
},
|
||||
"events": {
|
||||
"event_dust": {
|
||||
"particle_effect": {
|
||||
"effect": "cobblemon:battle_sandstorm_dust",
|
||||
"type": "particle"
|
||||
}
|
||||
},
|
||||
"event_obscure": {
|
||||
"particle_effect": {
|
||||
"effect": "cobblemon:battle_sandstorm_obscure",
|
||||
"type": "particle"
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"minecraft:emitter_rate_steady": {
|
||||
"spawn_rate": 200,
|
||||
"max_particles": 600
|
||||
},
|
||||
"minecraft:emitter_lifetime_expression": {
|
||||
"activation_expression": 1,
|
||||
"expiration_expression": "q.is_weather_active == 0"
|
||||
},
|
||||
"minecraft:emitter_lifetime_events": {
|
||||
"creation_event": ["event_dust", "event_obscure"]
|
||||
},
|
||||
"minecraft:emitter_shape_sphere": {
|
||||
"offset": [-10, "math.random(0,8)", 0],
|
||||
"radius": 8,
|
||||
"direction": [6, -0.2, "math.random(-1,1)"]
|
||||
},
|
||||
"minecraft:particle_lifetime_expression": {
|
||||
"max_lifetime": 1
|
||||
},
|
||||
"minecraft:particle_initial_spin": {
|
||||
"rotation": "math.random_integer(1,4)*90"
|
||||
},
|
||||
"minecraft:particle_initial_speed": "math.random(16,32)",
|
||||
"minecraft:particle_motion_dynamic": {
|
||||
"linear_acceleration": ["variable.sway + math.random(-1,1)", "-v.particle_random_1 * math.random(10,21)", 0],
|
||||
"linear_drag_coefficient": 0.05
|
||||
},
|
||||
"minecraft:particle_appearance_billboard": {
|
||||
"size": [0.6, 0.6],
|
||||
"facing_camera_mode": "rotate_xyz",
|
||||
"uv": {
|
||||
"texture_width": 8,
|
||||
"texture_height": 32,
|
||||
"uv": [0, "math.floor(variable.particle_random_2 * 4) * 8"],
|
||||
"uv_size": [8, 8]
|
||||
}
|
||||
},
|
||||
"minecraft:particle_motion_collision": {
|
||||
"collision_radius": 0.05,
|
||||
"expire_on_contact": true
|
||||
},
|
||||
"minecraft:particle_appearance_tinting": {
|
||||
"color": {
|
||||
"interpolant": "v.particle_age / v.particle_lifetime",
|
||||
"gradient": {
|
||||
"0.0": "#00FFFFFF",
|
||||
"0.18": "#FFFFFFFF",
|
||||
"0.82": "#FFFFFFFF",
|
||||
"1.0": "#00FFFFFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"format_version": "1.10.0",
|
||||
"particle_effect": {
|
||||
"description": {
|
||||
"identifier": "cobblemon:battle_sandstorm_dust",
|
||||
"basic_render_parameters": {
|
||||
"material": "particles_blend",
|
||||
"texture": "textures/particles/weather/battle_sand_dust"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"minecraft:emitter_rate_steady": {
|
||||
"spawn_rate": 200,
|
||||
"max_particles": 300
|
||||
},
|
||||
"minecraft:emitter_lifetime_expression": {
|
||||
"activation_expression": 1,
|
||||
"expiration_expression": "q.is_weather_active == 0"
|
||||
},
|
||||
"minecraft:emitter_shape_disc": {
|
||||
"offset": [0, "math.random(0,8)", 0],
|
||||
"radius": "math.random(8,16)",
|
||||
"direction": [8, 1, "math.random(-2,2)"]
|
||||
},
|
||||
"minecraft:particle_lifetime_expression": {
|
||||
"max_lifetime": "math.random(0.7,1.6)"
|
||||
},
|
||||
"minecraft:particle_initial_spin": {
|
||||
"rotation": "math.random_integer(1,4) * 90"
|
||||
},
|
||||
"minecraft:particle_initial_speed": 8,
|
||||
"minecraft:particle_motion_dynamic": {
|
||||
"linear_acceleration": [0, 1, 0],
|
||||
"linear_drag_coefficient": 1
|
||||
},
|
||||
"minecraft:particle_appearance_billboard": {
|
||||
"size": [0.7, 0.7],
|
||||
"facing_camera_mode": "rotate_xyz",
|
||||
"uv": {
|
||||
"texture_width": 64,
|
||||
"texture_height": 16,
|
||||
"uv": ["math.floor(v.particle_random_2 * 4) * 16", 0],
|
||||
"uv_size": [16, 16]
|
||||
}
|
||||
},
|
||||
"minecraft:particle_appearance_tinting": {
|
||||
"color": {
|
||||
"interpolant": "v.particle_age / v.particle_lifetime",
|
||||
"gradient": {
|
||||
"0.0": "#00FFFFFF",
|
||||
"0.24": "#8FFFFFFF",
|
||||
"0.69": "#6EFFFFFF",
|
||||
"1.0": "#00FFFFFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"format_version": "1.10.0",
|
||||
"particle_effect": {
|
||||
"description": {
|
||||
"identifier": "cobblemon:battle_sandstorm_obscure",
|
||||
"basic_render_parameters": {
|
||||
"material": "particles_blend",
|
||||
"texture": "textures/particles/weather/battle_sand_dust"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"minecraft:emitter_rate_steady": {
|
||||
"spawn_rate": 200,
|
||||
"max_particles": 300
|
||||
},
|
||||
"minecraft:emitter_lifetime_expression": {
|
||||
"activation_expression": 1,
|
||||
"expiration_expression": "q.is_weather_active == 0"
|
||||
},
|
||||
"minecraft:emitter_shape_disc": {
|
||||
"offset": [-8, "math.random(0,8)", 0],
|
||||
"radius": "math.random(4,8)",
|
||||
"direction": [6, 1, "math.random(-2,2)"]
|
||||
},
|
||||
"minecraft:particle_lifetime_expression": {
|
||||
"max_lifetime": "math.random(0.7,1.6)"
|
||||
},
|
||||
"minecraft:particle_initial_spin": {
|
||||
"rotation": "math.random_integer(1,4) * 90"
|
||||
},
|
||||
"minecraft:particle_initial_speed": 12,
|
||||
"minecraft:particle_motion_dynamic": {
|
||||
"linear_acceleration": [0, 1, 0],
|
||||
"linear_drag_coefficient": 1
|
||||
},
|
||||
"minecraft:particle_appearance_billboard": {
|
||||
"size": [0.7, 0.7],
|
||||
"facing_camera_mode": "rotate_xyz",
|
||||
"uv": {
|
||||
"texture_width": 64,
|
||||
"texture_height": 16,
|
||||
"uv": ["math.floor(v.particle_random_2 * 4) * 16", 0],
|
||||
"uv_size": [16, 16]
|
||||
}
|
||||
},
|
||||
"minecraft:particle_appearance_tinting": {
|
||||
"color": {
|
||||
"interpolant": "v.particle_age / v.particle_lifetime",
|
||||
"gradient": {
|
||||
"0.0": "#00FFFFFF",
|
||||
"0.24": "#8FFFFFFF",
|
||||
"0.69": "#6EFFFFFF",
|
||||
"1.0": "#00FFFFFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"format_version": "1.10.0",
|
||||
"particle_effect": {
|
||||
"description": {
|
||||
"identifier": "cobblemon:battle_snow",
|
||||
"basic_render_parameters": {
|
||||
"material": "particles_blend",
|
||||
"texture": "textures/particles/weather/battle_snow"
|
||||
}
|
||||
},
|
||||
"curves": {
|
||||
"variable.sway": {
|
||||
"type": "linear",
|
||||
"input": "v.particle_age",
|
||||
"horizontal_range": "v.particle_lifetime",
|
||||
"nodes": [-1, 1, -1, 1, -1]
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"minecraft:emitter_rate_steady": {
|
||||
"spawn_rate": 100,
|
||||
"max_particles": 600
|
||||
},
|
||||
"minecraft:emitter_lifetime_expression": {
|
||||
"activation_expression": 1,
|
||||
"expiration_expression": "q.is_weather_active == 0"
|
||||
},
|
||||
"minecraft:emitter_lifetime_events": {
|
||||
"creation_event": "event_snow_dust"
|
||||
},
|
||||
"minecraft:emitter_shape_disc": {
|
||||
"offset": [0, "math.random(1,32)", 0],
|
||||
"radius": 16,
|
||||
"direction": ["math.random(-1,1)", -2, "math.random(-1,1)"]
|
||||
},
|
||||
"minecraft:particle_lifetime_expression": {
|
||||
"max_lifetime": 6
|
||||
},
|
||||
"minecraft:particle_initial_speed": "math.random(1,2)",
|
||||
"minecraft:particle_motion_dynamic": {
|
||||
"linear_acceleration": ["variable.sway + math.random(-1,1)", "v.particle_random_1 * math.random(-4,4)", 0],
|
||||
"linear_drag_coefficient": 0.05
|
||||
},
|
||||
"minecraft:particle_appearance_billboard": {
|
||||
"size": [0.1, 0.1],
|
||||
"facing_camera_mode": "rotate_y",
|
||||
"uv": {
|
||||
"texture_width": 16,
|
||||
"texture_height": 4,
|
||||
"uv": ["math.floor(variable.particle_random_2 * 4) * 4", 0],
|
||||
"uv_size": [4, 4]
|
||||
}
|
||||
},
|
||||
"minecraft:particle_motion_collision": {
|
||||
"collision_radius": 0.05,
|
||||
"expire_on_contact": true
|
||||
},
|
||||
"minecraft:particle_appearance_tinting": {
|
||||
"color": {
|
||||
"interpolant": "(v.particle_age / v.particle_lifetime)",
|
||||
"gradient": {
|
||||
"0.0": "#00FFFFFF",
|
||||
"0.26": "#ffffffff",
|
||||
"0.77": "#ffffffff",
|
||||
"1.0": "#00FFFFFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"format_version": "1.10.0",
|
||||
"particle_effect": {
|
||||
"description": {
|
||||
"identifier": "cobblemon:battle_splash",
|
||||
"basic_render_parameters": {
|
||||
"material": "particles_alpha",
|
||||
"texture": "textures/particles/weather/battle_rain_splash"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"minecraft:emitter_rate_instant": {
|
||||
"num_particles": 1
|
||||
},
|
||||
"minecraft:emitter_lifetime_once": {
|
||||
"active_time": 1
|
||||
},
|
||||
"minecraft:emitter_shape_point": {
|
||||
"direction": ["math.random(-0.6,0.6)", 1, "math.random(-0.6,0.6)"]
|
||||
},
|
||||
"minecraft:particle_lifetime_expression": {
|
||||
"max_lifetime": 1
|
||||
},
|
||||
"minecraft:particle_initial_speed": 5,
|
||||
"minecraft:particle_motion_dynamic": {
|
||||
"linear_acceleration": [0, -10, 0],
|
||||
"linear_drag_coefficient": 2
|
||||
},
|
||||
"minecraft:particle_appearance_billboard": {
|
||||
"size": [0.2, 0.2],
|
||||
"facing_camera_mode": "rotate_xyz",
|
||||
"uv": {
|
||||
"texture_width": 8,
|
||||
"texture_height": 32,
|
||||
"uv": [0, "math.floor(variable.particle_random_2 * 4) * 8"],
|
||||
"uv_size": [8, 8]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9477,5 +9477,122 @@
|
||||
"stat.cobblemon.reel_ins": "PokéRod Catches",
|
||||
"stat.cobblemon.riding_land": "Distance by Pokémon on Land",
|
||||
"stat.cobblemon.riding_air": "Distance by Pokémon in Air",
|
||||
"stat.cobblemon.riding_liquid": "Distance by Pokémon in Liquid"
|
||||
"stat.cobblemon.riding_liquid": "Distance by Pokémon in Liquid",
|
||||
"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",
|
||||
"deltaclient.ui.battle.label.buffs": "Buffs/Debuffs",
|
||||
"deltaclient.ui.battle.label.current_hp": "Current HP",
|
||||
"deltaclient.ui.battle.label.ability": "Ability",
|
||||
"deltaclient.ui.battle.label.moves": "Moves",
|
||||
"deltaclient.ui.battle.label.held_item": "Held Item",
|
||||
"deltaclient.ui.battle.label.speed_tier": "Speed Tier",
|
||||
"deltaclient.ui.battle.label.pokemon_form": "Pokémon Form",
|
||||
"deltaclient.ui.battle.effectiveness.has_no_effect": "Has no effect",
|
||||
"deltaclient.ui.battle.effectiveness.mostly_ineffective": "Mostly ineffective",
|
||||
"deltaclient.ui.battle.effectiveness.not_very_effective": "Not very effective",
|
||||
"deltaclient.ui.battle.effectiveness.effective": "Effective",
|
||||
"deltaclient.ui.battle.effectiveness.super_effective": "Super effective",
|
||||
"deltaclient.ui.battle.effectiveness.extremely_effective": "Extremely effective",
|
||||
"deltaclient.ui.battle.turn": "Turn %1$s",
|
||||
"deltaclient.ui.battle.stats.special_attack": "Sp. Attack",
|
||||
"deltaclient.ui.battle.stats.special_defence": "Sp. Defense",
|
||||
"deltaclient.ui.battle.speed_tier": "%1$s to %2$s",
|
||||
"deltaclient.ui.battle.effect.raindance": "Rain — %1$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.raindance.range": "Rain — %1$s to %2$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.primordialsea": "Heavy Rain",
|
||||
"deltaclient.ui.battle.effect.sandstorm": "Sandstorm — %1$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.sandstorm.range": "Sandstorm — %1$s to %2$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.sunnyday": "Sun — %1$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.sunnyday.range": "Sun — %1$s to %2$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.desolateland": "Intense Sun",
|
||||
"deltaclient.ui.battle.effect.hail": "Hail — %1$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.hail.range": "Hail — %1$s to %2$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.snow": "Snow — %1$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.snow.range": "Snow — %1$s to %2$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.deltastream": "Delta Stream",
|
||||
"deltaclient.ui.battle.effect.trickroom": "Trick Room — %1$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.magicroom": "Magic Room — %1$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.wonderroom": "Wonder Room — %1$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.electricterrain": "Electric Terrain — %1$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.electricterrain.range": "Electric Terrain — %1$s to %2$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.mistyterrain": "Misty Terrain — %1$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.mistyterrain.range": "Misty Terrain — %1$s to %2$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.psychicterrain": "Psychic Terrain — %1$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.psychicterrain.range": "Psychic Terrain — %1$s to %2$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.grassyterrain": "Grassy Terrain — %1$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.grassyterrain.range": "Grassy Terrain — %1$s to %2$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.corrosiveterrain": "Corrosive Terrain — %1$s turn(s) left",
|
||||
"deltaclient.ui.battle.effect.corrosiveterrain.range": "Corrosive Terrain — %1$s to %2$s turn(s) left",
|
||||
"deltaclient.ui.battle.sided_effect.sharpsteel": "Sharp Steel",
|
||||
"deltaclient.ui.battle.sided_effect.spikes": "Spikes",
|
||||
"deltaclient.ui.battle.sided_effect.stealthrock": "Stealth Rock",
|
||||
"deltaclient.ui.battle.sided_effect.stickyweb": "Sticky Web",
|
||||
"deltaclient.ui.battle.sided_effect.toxicspikes": "Toxic Spikes",
|
||||
"deltaclient.ui.battle.sided_effect.reflect": "Reflect — %1$s turn(s) left",
|
||||
"deltaclient.ui.battle.sided_effect.reflect.range": "Reflect — %1$s to %2$s turn(s) left",
|
||||
"deltaclient.ui.battle.sided_effect.lightscreen": "Light Screen — %1$s turn(s) left",
|
||||
"deltaclient.ui.battle.sided_effect.lightscreen.range": "Light Screen — %1$s to %2$s turn(s) left",
|
||||
"deltaclient.ui.battle.sided_effect.auroraveil": "Aurora Veil — %1$s turn(s) left",
|
||||
"deltaclient.ui.battle.sided_effect.auroraveil.range": "Aurora Veil — %1$s to %2$s turn(s) left",
|
||||
"deltaclient.ui.battle.sided_effect.tailwind": "Tailwind — %1$s turn(s) left",
|
||||
"deltaclient.ui.battle.team_info": "Team Information",
|
||||
"deltaclient.ui.battle.team_info.button": "Info"
|
||||
}
|
||||
|
||||
@@ -9212,5 +9212,62 @@
|
||||
"stat.cobblemon.riding_air": "Distance parcourue par Pokémon dans les airs",
|
||||
"stat.cobblemon.riding_liquid": "Distance parcourue par Pokémon dans un liquide",
|
||||
"effect.cobblemon.cleanse_all": "Nettoyage des effets",
|
||||
"effect.cobblemon.cleanse_negative": "Nettoyage des effets négatifs"
|
||||
"effect.cobblemon.cleanse_negative": "Nettoyage des effets négatifs",
|
||||
"cobblemon.battle.ui.effect.desolateland": "Terre Finale",
|
||||
"cobblemon.battle.ui.effect.electricterrain": "Champ Électrifié",
|
||||
"cobblemon.battle.ui.effect.electricterrain.range": "Champ Électrifié ",
|
||||
"cobblemon.battle.ui.effect.grassyterrain": "Champ Herbu",
|
||||
"cobblemon.battle.ui.effect.grassyterrain.range": "Champ Herbu ",
|
||||
"cobblemon.battle.ui.effect.hail": "Grêle",
|
||||
"cobblemon.battle.ui.effect.hail.range": "Grêle ",
|
||||
"cobblemon.battle.ui.effect.magicroom": "Zone Magique",
|
||||
"cobblemon.battle.ui.effect.mistyterrain": "Champ Brumeux",
|
||||
"cobblemon.battle.ui.effect.mistyterrain.range": "Champ Brumeux ",
|
||||
"cobblemon.battle.ui.effect.primordialsea": "Mer Primaire",
|
||||
"cobblemon.battle.ui.effect.psychicterrain": "Champ Psychique",
|
||||
"cobblemon.battle.ui.effect.psychicterrain.range": "Champ Psychique ",
|
||||
"cobblemon.battle.ui.effect.raindance": "Danse Pluie",
|
||||
"cobblemon.battle.ui.effect.raindance.range": "Danse Pluie ",
|
||||
"cobblemon.battle.ui.effect.sandstorm": "Tempête de Sable",
|
||||
"cobblemon.battle.ui.effect.sandstorm.range": "Tempête de Sable ",
|
||||
"cobblemon.battle.ui.effect.snow": "Neige",
|
||||
"cobblemon.battle.ui.effect.snow.range": "Neige ",
|
||||
"cobblemon.battle.ui.effect.sunnyday": "Zénith",
|
||||
"cobblemon.battle.ui.effect.sunnyday.range": "Zénith ",
|
||||
"cobblemon.battle.ui.effect.trickroom": "Distorsion",
|
||||
"cobblemon.battle.ui.effect.wonderroom": "Zone Étrange",
|
||||
"cobblemon.battle.ui.effectiveness.effective": "Efficace",
|
||||
"cobblemon.battle.ui.effectiveness.extremely_effective": "Extrêmement Efficace !",
|
||||
"cobblemon.battle.ui.effectiveness.has_no_effect": "Aucun Effet",
|
||||
"cobblemon.battle.ui.effectiveness.mostly_ineffective": "Très Peu Efficace",
|
||||
"cobblemon.battle.ui.effectiveness.not_very_effective": "Peu Efficace",
|
||||
"cobblemon.battle.ui.effectiveness.super_effective": "Super Efficace !",
|
||||
"cobblemon.battle.ui.label.ability": "Talent",
|
||||
"cobblemon.battle.ui.label.buffs": "Boosts",
|
||||
"cobblemon.battle.ui.label.held_item": "Objet Tenu",
|
||||
"cobblemon.battle.ui.label.moves": "Capacités",
|
||||
"cobblemon.battle.ui.label.pokemon_form": "Forme du Pokémon",
|
||||
"cobblemon.battle.ui.label.speed_tier": "Niveau de Vitesse",
|
||||
"cobblemon.battle.ui.sided_effect.auroraveil": "Voile Aurore",
|
||||
"cobblemon.battle.ui.sided_effect.auroraveil.range": "Voile Aurore (%1$s tours)",
|
||||
"cobblemon.battle.ui.sided_effect.lightscreen": "Mur Lumière",
|
||||
"cobblemon.battle.ui.sided_effect.lightscreen.range": "Mur Lumière (%1$s tours)",
|
||||
"cobblemon.battle.ui.sided_effect.reflect": "Reflet",
|
||||
"cobblemon.battle.ui.sided_effect.reflect.range": "Reflet (%1$s tours)",
|
||||
"cobblemon.battle.ui.sided_effect.sharpsteel": "Piège d\u0027Acier ",
|
||||
"cobblemon.battle.ui.sided_effect.spikes": "Picots",
|
||||
"cobblemon.battle.ui.sided_effect.stealthrock": "Piège de Roc",
|
||||
"cobblemon.battle.ui.sided_effect.stickyweb": "Toile Gluante",
|
||||
"cobblemon.battle.ui.sided_effect.tailwind": "Vent Arrière",
|
||||
"cobblemon.battle.ui.sided_effect.toxicspikes": "Pics Toxik",
|
||||
"cobblemon.battle.ui.speed_tier": "Niveau de Vitesse : %1$s",
|
||||
"cobblemon.battle.ui.stats.special_attack": "Attaque Spéciale",
|
||||
"cobblemon.battle.ui.stats.special_defence": "Défense Spéciale",
|
||||
"cobblemon.battle.ui.team_info": "Informations sur l\u0027Équipe",
|
||||
"cobblemon.battle.ui.team_info.button": "Équipe",
|
||||
"cobblemon.battle.ui.team_info.title": "Informations sur l\u0027Équipe",
|
||||
"cobblemon.battle.ui.team_preview.title": "Aperçu de l\u0027Équipe",
|
||||
"cobblemon.battle.ui.team_preview.waiting": "En attente de l\u0027adversaire...",
|
||||
"cobblemon.battle.ui.turn": "Tour %1$s",
|
||||
"cobblemon.battle.ui.turn.timer": "Temps restant : %1$s"
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 541 B |
|
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: 223 B |
|
After Width: | Height: | Size: 449 B |
|
After Width: | Height: | Size: 482 B |
|
After Width: | Height: | Size: 453 B |
|
After Width: | Height: | Size: 454 B |
|
After Width: | Height: | Size: 447 B |
|
After Width: | Height: | Size: 454 B |
|
After Width: | Height: | Size: 451 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 459 B |
|
After Width: | Height: | Size: 447 B |
|
After Width: | Height: | Size: 467 B |
|
After Width: | Height: | Size: 455 B |
|
After Width: | Height: | Size: 452 B |
|
After Width: | Height: | Size: 471 B |
|
After Width: | Height: | Size: 454 B |