mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-29 05:18:54 -04:00
Compare commits
89
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9309c53a40 | ||
|
|
3a759eccf7 | ||
|
|
d8bae04239 | ||
|
|
3d8935136e | ||
|
|
462a8502bf | ||
|
|
d8645530de | ||
|
|
1e27298f36 | ||
|
|
35cc7cc956 | ||
|
|
a34bc3ef49 | ||
|
|
e7f1092264 | ||
|
|
077669d34f | ||
|
|
d3784e918e | ||
|
|
d0f2d816fe | ||
|
|
d54d91e60f | ||
|
|
f76d0511b9 | ||
|
|
ebe47700b2 | ||
|
|
f1de8adc68 | ||
|
|
ee0e76a172 | ||
|
|
12da3a428f | ||
|
|
0ddd8a499a | ||
|
|
3a3fe49c7c | ||
|
|
cf06bed1f3 | ||
|
|
e7cbd74b05 | ||
|
|
bd9ebce842 | ||
|
|
94a1943398 | ||
|
|
41cf571958 | ||
|
|
c09f534746 | ||
|
|
3f389bd351 | ||
|
|
e169009c6c | ||
|
|
3102189a66 | ||
|
|
1be9325a65 | ||
|
|
d01989343c | ||
|
|
fd40ec47ab | ||
|
|
eed218b994 | ||
|
|
a2145b6bf7 | ||
|
|
e14c8b5264 | ||
|
|
22385421ef | ||
|
|
dc103d286b | ||
|
|
0e7b9bdd0c | ||
|
|
9c8ff24bf2 | ||
|
|
3738b035d1 | ||
|
|
22565a0ed5 | ||
|
|
e9085a3339 | ||
|
|
5d32d043a1 | ||
|
|
1028ba07af | ||
|
|
86bd2240c3 | ||
|
|
8fdb551fe8 | ||
|
|
b851fa0d5f | ||
|
|
04d1f8c37e | ||
|
|
2363d76f85 | ||
|
|
419cb48327 | ||
|
|
8a91a58caa | ||
|
|
eee563f07a | ||
|
|
a231250372 | ||
|
|
14f486d086 | ||
|
|
c2f7f3d506 | ||
|
|
593f9cee03 | ||
|
|
f0821bc1d2 | ||
|
|
ef7ad797d1 | ||
|
|
56f86e15ae | ||
|
|
a285dbbe43 | ||
|
|
cc2783a553 | ||
|
|
2ca67c805f | ||
|
|
4b0f1e3d7d | ||
|
|
06a8c9e713 | ||
|
|
a447228de1 | ||
|
|
c6032fdaab | ||
|
|
103af083a3 | ||
|
|
e67c0ebbb8 | ||
|
|
a51d282007 | ||
|
|
3004c2a44c | ||
|
|
a2a2d8860e | ||
|
|
af81763577 | ||
|
|
c3dafcc615 | ||
|
|
0340f54e2c | ||
|
|
e2917b044a | ||
|
|
34f00dbb05 | ||
|
|
93801ae6d5 | ||
|
|
b50bf547f8 | ||
|
|
01ac831c24 | ||
|
|
adcccfee02 | ||
|
|
a97e8c819d | ||
|
|
4eeaf5102f | ||
|
|
d15307a809 | ||
|
|
935fd53b70 | ||
|
|
db33633406 | ||
|
|
49095f73d5 | ||
|
|
2ffe107789 | ||
|
|
f2f6c3d0ef |
No files matched your search
@@ -6,6 +6,7 @@
|
||||
- Fixed a scenario where a Pokémon that rolled to spawn with a special Tera type sometimes has a Tera type that is already a part of their natural typing.
|
||||
- Fixed PokemonProperty argument suggestions for ``tera`` and ``tera_type`` not suggesting ``stellar``.
|
||||
- Fixed being able to stack Relic Coin Pouches on top of each other.
|
||||
- Bag Items used during a turn will now be refunded if the battle ends before the next turn begins.
|
||||
|
||||
### Additions
|
||||
- Added Pokémon (and item) fishing using modified fishing rods - Poké Rods! You'll need a Poké Rod smithing template, a fishing rod, and some type of Poké Ball. Each Poké Ball makes a differently themed rod. Why not?
|
||||
@@ -13,6 +14,12 @@
|
||||
- Added property chaining support for duplicate CustomPokemonPropertyType elements.
|
||||
- Added ``aspect`` and ``unaspect`` PokemonProperty arguments (which also includes commands such as /pokemonedit, /spawnpokemon, and /givepokemon) to allow forcing or un-forcing an aspect on a Pokémon.
|
||||
- Added ``type`` alternatively ``elemental_type`` PokemonProperty argument, this is only used for filtering and is not applied to Pokémon. Example ``type=fire`` would be true for Charmander but false for Squirtle.
|
||||
- Added support for Double Battles, Triple Battles, and Multi-battles.
|
||||
- Adjusted Pokémon sendout positions for single battles.
|
||||
- Wild Pokémon interaction range increased to 12 blocks (from 10 blocks)
|
||||
- Player trade range increased to 12 blocks (from 10 blocks)
|
||||
- PvP battle range increased to 32 blocks (from 10 blocks)
|
||||
- Spectate range increased to 64 blocks (from 10 blocks)
|
||||
|
||||
### Developer
|
||||
- SpawnCause is now an implementation of SpawningInfluence.
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.mixin;
|
||||
|
||||
import com.cobblemon.mod.common.client.CobblemonClient;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(PlayerRenderer.class)
|
||||
public class PlayerRendererMixin {
|
||||
@Inject(
|
||||
method = "render(Lnet/minecraft/client/player/AbstractClientPlayer;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V",
|
||||
at = @At(value = "TAIL")
|
||||
)
|
||||
public void renderPlayer(AbstractClientPlayer player, float f, float g, PoseStack poseStack, MultiBufferSource multiBufferSource, int i, CallbackInfo ci) {
|
||||
CobblemonClient.INSTANCE.getRequests().onRenderPlayer(player, g);
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,7 @@ import com.cobblemon.mod.common.battles.BattleSide
|
||||
import com.cobblemon.mod.common.battles.ShowdownThread
|
||||
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.command.argument.DialogueArgumentType
|
||||
import com.cobblemon.mod.common.command.argument.MoveArgumentType
|
||||
import com.cobblemon.mod.common.command.argument.PartySlotArgumentType
|
||||
@@ -205,7 +206,7 @@ object Cobblemon {
|
||||
}
|
||||
PlatformEvents.SERVER_PLAYER_LOGOUT.subscribe {
|
||||
PCLinkManager.removeLink(it.player.uuid)
|
||||
BattleRegistry.getBattleByParticipatingPlayer(it.player)?.stop()
|
||||
BattleRegistry.onPlayerDisconnect(it.player)
|
||||
storage.onPlayerDisconnect(it.player)
|
||||
playerData.onPlayerDisconnect(it.player)
|
||||
TradeManager.onLogoff(it.player)
|
||||
|
||||
@@ -49,6 +49,7 @@ object CobblemonCommands {
|
||||
BedrockParticleCommand.register(dispatcher)
|
||||
OpenDialogueCommand.register(dispatcher)
|
||||
NPCEditCommand.register(dispatcher)
|
||||
AbandonMultiTeam.register(dispatcher)
|
||||
|
||||
// Possibly lock down registration if and only if under dev environment or running in an environment
|
||||
// with a certain system environment variable set
|
||||
|
||||
@@ -111,12 +111,7 @@ import com.cobblemon.mod.common.net.messages.client.trade.TradeStartedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.TradeUpdatedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.BattleChallengePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.BenchMovePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.RequestMoveSwapPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.RequestPlayerInteractionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.SelectStarterPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.SendOutPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.*
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.RemoveSpectatorPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
|
||||
@@ -151,8 +146,7 @@ import com.cobblemon.mod.common.net.messages.server.trade.CancelTradePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.ChangeTradeAcceptancePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.OfferTradePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.UpdateTradeOfferPacket
|
||||
import com.cobblemon.mod.common.net.serverhandling.ChallengeHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.RequestInteractionsHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.*
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.BattleSelectActionsHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.RemoveSpectatorHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateBattleHandler
|
||||
@@ -284,6 +278,7 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(BattleHealthChangePacket.ID, BattleHealthChangePacket::decode, BattleHealthChangeHandler))
|
||||
list.add(PacketRegisterInfo(BattleSetTeamPokemonPacket.ID, BattleSetTeamPokemonPacket::decode, BattleSetTeamPokemonHandler))
|
||||
list.add(PacketRegisterInfo(BattleSwitchPokemonPacket.ID, BattleSwitchPokemonPacket::decode, BattleSwitchPokemonHandler))
|
||||
list.add(PacketRegisterInfo(BattleSwapPokemonPacket.ID, BattleSwapPokemonPacket::decode, BattleSwapPokemonHandler))
|
||||
list.add(PacketRegisterInfo(BattleMessagePacket.ID, BattleMessagePacket::decode, BattleMessageHandler))
|
||||
list.add(PacketRegisterInfo(BattleCaptureStartPacket.ID, BattleCaptureStartPacket::decode, BattleCaptureStartHandler))
|
||||
list.add(PacketRegisterInfo(BattleCaptureEndPacket.ID, BattleCaptureEndPacket::decode, BattleCaptureEndHandler))
|
||||
@@ -298,6 +293,12 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(BattleReplacePokemonPacket.ID, BattleReplacePokemonPacket::decode, BattleReplacePokemonHandler))
|
||||
list.add(PacketRegisterInfo(BattleTransformPokemonPacket.ID, BattleTransformPokemonPacket::decode, BattleTransformPokemonHandler))
|
||||
|
||||
// MultiBattleTeam Packets
|
||||
list.add(PacketRegisterInfo(TeamRequestNotificationPacket.ID, TeamRequestNotificationPacket::decode, TeamRequestNotificationHandler))
|
||||
list.add(PacketRegisterInfo(TeamRequestExpiredPacket.ID, TeamRequestExpiredPacket::decode, TeamRequestExpiredHandler))
|
||||
list.add(PacketRegisterInfo(TeamMemberRemoveNotificationPacket.ID, TeamMemberRemoveNotificationPacket::decode, TeamMemberRemoveNotificationHandler))
|
||||
list.add(PacketRegisterInfo(TeamJoinNotificationPacket.ID, TeamJoinNotificationPacket::decode, TeamJoinNotificationHandler))
|
||||
|
||||
// Settings packets
|
||||
list.add(PacketRegisterInfo(ServerSettingsPacket.ID, ServerSettingsPacket::decode, ServerSettingsPacketHandler))
|
||||
|
||||
@@ -385,6 +386,10 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(RequestMoveSwapPacket.ID, RequestMoveSwapPacket::decode, RequestMoveSwapHandler))
|
||||
list.add(PacketRegisterInfo(BenchMovePacket.ID, BenchMovePacket::decode, BenchMoveHandler))
|
||||
list.add(PacketRegisterInfo(BattleChallengePacket.ID, BattleChallengePacket::decode, ChallengeHandler))
|
||||
list.add(PacketRegisterInfo(BattleChallengeResponsePacket.ID, BattleChallengeResponsePacket::decode, ChallengeResponseHandler))
|
||||
list.add(PacketRegisterInfo(BattleTeamRequestPacket.ID, BattleTeamRequestPacket::decode, TeamRequestHandler))
|
||||
list.add(PacketRegisterInfo(BattleTeamResponsePacket.ID, BattleTeamResponsePacket::decode, TeamRequestResponseHandler))
|
||||
list.add(PacketRegisterInfo(BattleTeamLeavePacket.ID, BattleTeamLeavePacket::decode, TeamLeaveHandler))
|
||||
|
||||
list.add(PacketRegisterInfo(MovePCPokemonToPartyPacket.ID, MovePCPokemonToPartyPacket::decode, MovePCPokemonToPartyHandler))
|
||||
list.add(PacketRegisterInfo(MovePartyPokemonToPCPacket.ID, MovePartyPokemonToPCPacket::decode, MovePartyPokemonToPCHandler))
|
||||
|
||||
@@ -46,14 +46,19 @@ import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.DefeatEvolutionProgress
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
|
||||
import com.cobblemon.mod.common.pokemon.evolution.requirements.DefeatRequirement
|
||||
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import net.minecraft.network.chat.Component
|
||||
import com.cobblemon.mod.common.util.giveOrDropItemStack
|
||||
import com.cobblemon.mod.common.util.itemRegistry
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ConcurrentLinkedDeque
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.item.ItemStack
|
||||
|
||||
/**
|
||||
* Individual battle instance
|
||||
@@ -214,11 +219,12 @@ open class PokemonBattle(
|
||||
|
||||
fun turn(newTurnNumber: Int) {
|
||||
actors.forEach { it.turn() }
|
||||
// TODO: If a pokemon switches in the same turn another pokemon is KO'd it will not receive exp for the KO
|
||||
for (side in sides) {
|
||||
val opposite = side.getOppositeSide()
|
||||
side.activePokemon.forEach {
|
||||
val battlePokemon = it.battlePokemon ?: return@forEach
|
||||
battlePokemon.facedOpponents.addAll(opposite.activePokemon.mapNotNull { it.battlePokemon })
|
||||
side.activePokemon.filter { it.isAlive() }.forEach { activePokemon ->
|
||||
val battlePokemon = activePokemon.battlePokemon ?: return@forEach
|
||||
battlePokemon.facedOpponents.addAll(opposite.activePokemon.filter { opposingActivePokemon -> opposingActivePokemon.isAlive() }.mapNotNull { it.battlePokemon })
|
||||
}
|
||||
}
|
||||
this.turn = newTurnNumber
|
||||
@@ -261,6 +267,13 @@ open class PokemonBattle(
|
||||
}
|
||||
}
|
||||
}
|
||||
if(actor.itemsUsed.isNotEmpty() && actor.getPlayerUUIDs().count() > 0) {
|
||||
val player = actor.getPlayerUUIDs().first().getPlayer()
|
||||
player?.level()?.itemRegistry.let { registry ->
|
||||
actor.itemsUsed.mapNotNull { registry?.get(ResourceLocation.tryBySeparator(it.itemName.substringAfter('.'), '.')) }
|
||||
.forEach { player?.giveOrDropItemStack(ItemStack(it))}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Heal Mon if wild
|
||||
actors.filter { it.type == ActorType.WILD }
|
||||
|
||||
+8
-3
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.battles.ShowdownActionResponse
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.exception.IllegalActionChoiceException
|
||||
import com.cobblemon.mod.common.item.battle.BagItem
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleApplyPassResponsePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
|
||||
@@ -44,6 +45,7 @@ abstract class BattleActor(
|
||||
val expectingPassActions = mutableListOf<ShowdownActionResponse>()
|
||||
var mustChoose = false
|
||||
|
||||
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) */
|
||||
var stillSendingOutCount = 0
|
||||
|
||||
@@ -65,11 +67,14 @@ abstract class BattleActor(
|
||||
open fun isForPlayer(serverPlayerEntity: ServerPlayer) = serverPlayerEntity.uuid in getPlayerUUIDs()
|
||||
open fun isForPokemon(pokemonEntity: PokemonEntity) = activePokemon.any { it.battlePokemon?.effectedPokemon?.entity == pokemonEntity }
|
||||
|
||||
fun turn() {
|
||||
fun turn() {
|
||||
val request = request ?: return
|
||||
responses.clear()
|
||||
mustChoose = true
|
||||
sendUpdate(BattleMakeChoicePacket())
|
||||
if(activePokemon.any { it.isAlive() }) {
|
||||
mustChoose = true
|
||||
sendUpdate(BattleMakeChoicePacket())
|
||||
}
|
||||
|
||||
|
||||
val requestActive = request.active
|
||||
if (requestActive == null || requestActive.isEmpty() || request.wait) {
|
||||
|
||||
+2
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common.api.battles.model.actor
|
||||
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
/**
|
||||
* Allows a [BattleActor] to attach a [LivingEntity] to itself.
|
||||
@@ -21,5 +22,6 @@ interface EntityBackedBattleActor<T : LivingEntity> {
|
||||
* The [LivingEntity] attached to the [BattleActor].
|
||||
*/
|
||||
val entity: T?
|
||||
val initialPos: Vec3?
|
||||
|
||||
}
|
||||
@@ -17,4 +17,6 @@ object ColourLibrary {
|
||||
const val SIDE_1_ALLY_BATTLE_COLOUR = 0x34C2AC
|
||||
const val SIDE_2_BATTLE_COLOUR = 0xE43838
|
||||
const val SIDE_2_ALLY_BATTLE_COLOUR = 0xD55ABA
|
||||
|
||||
const val BATTLE_COMMAND_COLOUR = 0xEBBA15
|
||||
}
|
||||
@@ -114,6 +114,21 @@ fun drawCenteredText(
|
||||
context.drawString(textRenderer, comp, x.toInt() - textRenderer.width(comp) / 2, y.toInt(), colour, shadow)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun drawTextJustifiedRight(
|
||||
context: GuiGraphics,
|
||||
font: ResourceLocation? = null,
|
||||
text: MutableComponent,
|
||||
x: Number,
|
||||
y: Number,
|
||||
colour: Int,
|
||||
shadow: Boolean = true
|
||||
) {
|
||||
val comp = text.let { if (font != null) it.font(font) else it }
|
||||
val font = Minecraft.getInstance().font
|
||||
context.drawString(font, comp, x.toInt() - font.width(comp), y.toInt(), colour, shadow)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun drawText(
|
||||
context: GuiGraphics,
|
||||
|
||||
@@ -97,6 +97,7 @@ interface PokemonSelectingItem {
|
||||
battlePokemon.actor.forceChoose(BagItemActionResponse(bagItem, battlePokemon))
|
||||
if (!player.isCreative) {
|
||||
stack.shrink(1)
|
||||
battlePokemon.actor.itemsUsed.add(bagItem)
|
||||
}
|
||||
CobblemonCriteria.POKEMON_INTERACT.trigger(player, PokemonInteractContext(battlePokemon.entity!!.pokemon.species.resourceIdentifier, BuiltInRegistries.ITEM.getKey(stack.item)))
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.permission
|
||||
|
||||
import com.cobblemon.mod.common.command.AbandonMultiTeam
|
||||
|
||||
object CobblemonPermissions {
|
||||
|
||||
private const val COMMAND_PREFIX = "command."
|
||||
@@ -72,6 +74,8 @@ object CobblemonPermissions {
|
||||
|
||||
val NPC_EDIT = this.create("${COMMAND_PREFIX}npcedit", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
|
||||
|
||||
val ABANDON_MULTITEAM = this.create("${COMMAND_PREFIX}abandonmultiteam", PermissionLevel.NONE)
|
||||
|
||||
fun all(): Iterable<Permission> = this.permissions
|
||||
|
||||
private fun create(node: String, level: PermissionLevel): Permission {
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.google.gson.JsonObject
|
||||
import java.util.UUID
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import java.util.Collections
|
||||
|
||||
/**
|
||||
* A [PokemonStore] for a party of Pokémon. This is a simple structure that by default will hold 6 nullable slots of Pokémon.
|
||||
@@ -255,15 +256,18 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
|
||||
return totalPercent
|
||||
}
|
||||
|
||||
fun toBattleTeam(clone: Boolean = false, checkHealth: Boolean = true, leadingPokemon: UUID? = null) = mapNotNull {
|
||||
// TODO Other 'able to battle' checks
|
||||
return@mapNotNull if (clone) {
|
||||
BattlePokemon.safeCopyOf(it)
|
||||
} else {
|
||||
BattlePokemon.playerOwned(it)
|
||||
}
|
||||
}.sortedBy { if (it.uuid == leadingPokemon) 0 else (indexOf(it.originalPokemon) + 1) }
|
||||
|
||||
fun toBattleTeam(clone: Boolean = false, checkHealth: Boolean = true, leadingPokemon: UUID? = null) : List<BattlePokemon> {
|
||||
val result = this.mapNotNull {
|
||||
return@mapNotNull if (clone) {
|
||||
BattlePokemon.safeCopyOf(it)
|
||||
} else {
|
||||
BattlePokemon.playerOwned(it)
|
||||
}
|
||||
}.toMutableList()
|
||||
Collections.rotate(result, result.size - this.indexOfFirst { it.uuid == leadingPokemon })
|
||||
return result
|
||||
}
|
||||
fun clearParty() {
|
||||
forEach {
|
||||
it.tryRecallWithAnimation()
|
||||
|
||||
@@ -18,11 +18,19 @@ import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.effectiveName
|
||||
import com.cobblemon.mod.common.util.getBattleTheme
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import com.cobblemon.mod.common.util.party
|
||||
import java.util.UUID
|
||||
import com.cobblemon.mod.common.util.update
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.collections.Iterable
|
||||
@@ -53,8 +61,8 @@ object BattleBuilder {
|
||||
healFirst: Boolean = false,
|
||||
partyAccessor: (ServerPlayer) -> PartyStore = { it.party() }
|
||||
): BattleStartResult {
|
||||
val team1 = partyAccessor(player1).toBattleTeam(clone = cloneParties, checkHealth = !healFirst, leadingPokemonPlayer1)
|
||||
val team2 = partyAccessor(player2).toBattleTeam(clone = cloneParties, checkHealth = !healFirst, leadingPokemonPlayer2)
|
||||
val team1 = partyAccessor(player1).toBattleTeam(clone = cloneParties, checkHealth = !healFirst, leadingPokemonPlayer1).sortedBy { it.health <= 0 }
|
||||
val team2 = partyAccessor(player2).toBattleTeam(clone = cloneParties, checkHealth = !healFirst, leadingPokemonPlayer2).sortedBy { it.health <= 0 }
|
||||
|
||||
val player1Actor = PlayerBattleActor(player1.uuid, team1)
|
||||
val player2Actor = PlayerBattleActor(player2.uuid, team2)
|
||||
@@ -62,11 +70,11 @@ object BattleBuilder {
|
||||
val errors = ErroredBattleStart()
|
||||
|
||||
for ((player, actor) in arrayOf(player1 to player1Actor, player2 to player2Actor)) {
|
||||
if (actor.pokemonList.size < battleFormat.battleType.slotsPerActor) {
|
||||
if (actor.pokemonList.filter { it.health > 0 }.size < battleFormat.battleType.slotsPerActor) {
|
||||
errors.participantErrors[actor] += BattleStartError.insufficientPokemon(
|
||||
player = player,
|
||||
requiredCount = battleFormat.battleType.slotsPerActor,
|
||||
hadCount = actor.pokemonList.size
|
||||
hadCount = actor.pokemonList.filter { it.health > 0 }.size
|
||||
)
|
||||
}
|
||||
|
||||
@@ -92,6 +100,79 @@ object BattleBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun pvp2v2(
|
||||
players: List<ServerPlayer> = emptyList(),
|
||||
leadingPokemon: List<UUID> = emptyList(),
|
||||
battleFormat: BattleFormat = BattleFormat.GEN_9_MULTI,
|
||||
cloneParties: Boolean = false,
|
||||
healFirst: Boolean = false,
|
||||
partyAccessor: (ServerPlayer) -> PartyStore = { it.party() }
|
||||
): BattleStartResult {
|
||||
val teams = players.mapIndexed { index, it -> partyAccessor(it).toBattleTeam(clone = cloneParties, checkHealth = !healFirst, leadingPokemon[index]) }
|
||||
val playerActors = teams.mapIndexed { index, team -> PlayerBattleActor(players[index].uuid, team)}.toMutableList()
|
||||
|
||||
val errors = ErroredBattleStart()
|
||||
|
||||
if (players.size != BattleRegistry.MAX_TEAM_MEMBER_COUNT * 2) {
|
||||
playerActors.forEach {actor ->
|
||||
errors.participantErrors[actor] += BattleStartError.incorrectActorCount(
|
||||
requiredCount = BattleRegistry.MAX_TEAM_MEMBER_COUNT * 2,
|
||||
hadCount = players.size
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for ((player, actor) in players.zip(playerActors)) {
|
||||
if (actor.pokemonList.size < battleFormat.battleType.slotsPerActor) {
|
||||
errors.participantErrors[actor] += BattleStartError.insufficientPokemon(
|
||||
player = player,
|
||||
requiredCount = battleFormat.battleType.slotsPerActor,
|
||||
hadCount = actor.pokemonList.size
|
||||
)
|
||||
}
|
||||
|
||||
if (BattleRegistry.getBattleByParticipatingPlayer(player) != null) {
|
||||
errors.participantErrors[actor] += BattleStartError.alreadyInBattle(player)
|
||||
}
|
||||
}
|
||||
|
||||
// Rearrange actors so that Showdown's player arrangement hopefully matches the player arrangement in the world
|
||||
val playersPositions = players.map { p -> p.position() }
|
||||
val side1Center = playersPositions.subList(0, players.size / 2).fold(Vec3(0.0, 0.0, 0.0)) { acc, vec3 -> acc.add(vec3.scale(1.0 / (players.size / 2))) }
|
||||
val side2Center = playersPositions.subList(players.size / 2, players.size).fold(Vec3(0.0, 0.0, 0.0)) { acc, vec3 -> acc.add(vec3.scale(1.0 / (players.size / 2))) }
|
||||
|
||||
if ((side2Center.x - side1Center.x)*(playersPositions[1].z - side1Center.z) - (side2Center.z - side1Center.z)*(playersPositions[1].x - side1Center.x) < 0) {
|
||||
// swap
|
||||
playerActors.swap(0,1)
|
||||
}
|
||||
|
||||
if ((side1Center.x - side2Center.x)*(playersPositions[3].z - side2Center.z) - (side1Center.z - side2Center.z)*(playersPositions[3].x - side2Center.x) < 0) {
|
||||
// swap
|
||||
playerActors.swap(2,3)
|
||||
}
|
||||
|
||||
return if (errors.isEmpty) {
|
||||
BattleRegistry.startBattle(
|
||||
battleFormat = battleFormat,
|
||||
side1 = BattleSide(playerActors[0], playerActors[1]),
|
||||
side2 = BattleSide(playerActors[2], playerActors[3])
|
||||
).ifSuccessful {
|
||||
// TODO: less hard coding
|
||||
playerActors[0].battleTheme = players[2].getBattleTheme()
|
||||
playerActors[1].battleTheme = players[2].getBattleTheme()
|
||||
|
||||
playerActors[2].battleTheme = players[0].getBattleTheme()
|
||||
playerActors[3].battleTheme = players[0].getBattleTheme()
|
||||
}
|
||||
errors
|
||||
} else {
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to create a PvE battle against the given Pokémon.
|
||||
*
|
||||
@@ -260,7 +341,10 @@ interface BattleStartError {
|
||||
requiredCount: Int,
|
||||
hadCount: Int
|
||||
) = InsufficientPokemonError(player, requiredCount, hadCount)
|
||||
|
||||
fun incorrectActorCount(
|
||||
requiredCount: Int,
|
||||
hadCount: Int
|
||||
) = IncorrectActorCountError(requiredCount, hadCount)
|
||||
fun canceledByEvent(reason: MutableComponent?) = CanceledError(reason)
|
||||
}
|
||||
}
|
||||
@@ -285,19 +369,32 @@ class InsufficientPokemonError(
|
||||
val key = if (hadCount == 0) "no_pokemon" else "insufficient_pokemon.personal"
|
||||
battleLang(
|
||||
"error.$key",
|
||||
requiredCount,
|
||||
hadCount
|
||||
hadCount,
|
||||
requiredCount
|
||||
)
|
||||
} else {
|
||||
battleLang(
|
||||
"error.insufficient_pokemon",
|
||||
player.effectiveName(),
|
||||
requiredCount,
|
||||
hadCount
|
||||
hadCount,
|
||||
requiredCount
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IncorrectActorCountError(
|
||||
val requiredCount: Int,
|
||||
val hadCount: Int
|
||||
) : BattleStartError {
|
||||
override fun getMessageFor(entity: Entity): MutableComponent {
|
||||
return battleLang(
|
||||
"error.incorrect_actor_count",
|
||||
requiredCount,
|
||||
hadCount
|
||||
)
|
||||
}
|
||||
}
|
||||
class AlreadyInBattleError(
|
||||
val actorUUID: UUID,
|
||||
val name: Component
|
||||
|
||||
@@ -35,12 +35,22 @@ data class BattleFormat(
|
||||
|
||||
val GEN_9_DOUBLES = BattleFormat(
|
||||
battleType = BattleTypes.DOUBLES,
|
||||
ruleSet = setOf(BattleRules.OBTAINABLE)
|
||||
ruleSet = setOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
|
||||
)
|
||||
|
||||
val GEN_9_TRIPLES = BattleFormat(
|
||||
battleType = BattleTypes.TRIPLES,
|
||||
ruleSet = setOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
|
||||
)
|
||||
|
||||
val GEN_9_MULTI = BattleFormat(
|
||||
battleType = BattleTypes.MULTI,
|
||||
ruleSet = setOf(BattleRules.OBTAINABLE)
|
||||
ruleSet = mutableSetOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
|
||||
)
|
||||
|
||||
val GEN_9_ROYAL = BattleFormat(
|
||||
battleType = BattleTypes.ROYAL,
|
||||
ruleSet = mutableSetOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
|
||||
)
|
||||
|
||||
fun loadFromBuffer(buffer: RegistryFriendlyByteBuf): BattleFormat {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
package com.cobblemon.mod.common.battles
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
@@ -19,7 +20,11 @@ import com.cobblemon.mod.common.api.pokemon.status.Statuses
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleChallengeExpiredPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.TeamMemberRemoveNotificationPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.TeamRequestExpiredPacket
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import com.cobblemon.mod.common.util.server
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.google.gson.GsonBuilder
|
||||
import java.time.Instant
|
||||
import java.util.UUID
|
||||
@@ -28,16 +33,34 @@ import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
object BattleRegistry {
|
||||
|
||||
const val MAX_TEAM_MEMBER_COUNT = 2
|
||||
const val MAX_BATTLE_RADIUS = 15.0
|
||||
class BattleChallenge(
|
||||
val challengeId: UUID,
|
||||
val challengedPlayerUUID: UUID,
|
||||
val selectedPokemonId: UUID,
|
||||
var expiryTimeSeconds: Int = 60
|
||||
val challengeId: UUID,
|
||||
val challengedPlayerUUID: UUID,
|
||||
val selectedPokemonId: UUID,
|
||||
val battleFormat: BattleFormat,
|
||||
var expiryTimeSeconds: Int = 60
|
||||
) {
|
||||
val challengedTime = Instant.now()
|
||||
fun isExpired() = Instant.now().isAfter(challengedTime.plusSeconds(expiryTimeSeconds.toLong()))
|
||||
}
|
||||
|
||||
|
||||
|
||||
class TeamRequest(
|
||||
val requestID: UUID,
|
||||
val requestedPlayerUUID: UUID,
|
||||
var expiryTimeSeconds: Int = 60
|
||||
) {
|
||||
val challengedTime = Instant.now()
|
||||
fun isExpired() = Instant.now().isAfter(challengedTime.plusSeconds(expiryTimeSeconds.toLong()))
|
||||
}
|
||||
class MultiBattleTeam(
|
||||
val teamID: UUID,
|
||||
val teamPlayersUUID: MutableList<UUID>,
|
||||
)
|
||||
|
||||
val gson = GsonBuilder()
|
||||
.disableHtmlEscaping()
|
||||
.registerTypeAdapter(ShowdownMoveset::class.java, ShowdownMovesetAdapter)
|
||||
@@ -46,18 +69,92 @@ object BattleRegistry {
|
||||
// Challenger to challenge
|
||||
val pvpChallenges = mutableMapOf<UUID, BattleChallenge>()
|
||||
|
||||
// Multi-battle teams
|
||||
val playerToTeam = mutableMapOf<UUID, MultiBattleTeam>()
|
||||
val multiBattleTeams = mutableMapOf<UUID, MultiBattleTeam>()
|
||||
val multiBattleTeamRequests = mutableMapOf<UUID, TeamRequest>()
|
||||
|
||||
fun onServerStarted() {
|
||||
battleMap.clear()
|
||||
pvpChallenges.clear()
|
||||
playerToTeam.clear()
|
||||
}
|
||||
|
||||
fun removeChallenge(challengerId: UUID, challengeId: UUID? = null) {
|
||||
fun removeChallenge(challengerId: UUID, challengeId: UUID? = null, isMultiBattleTeam: Boolean = false) {
|
||||
val existing = pvpChallenges[challengerId] ?: return
|
||||
if (existing.challengeId != challengeId) {
|
||||
return
|
||||
}
|
||||
pvpChallenges.remove(challengerId)
|
||||
existing.challengedPlayerUUID.getPlayer()?.sendPacket(BattleChallengeExpiredPacket(existing.challengeId))
|
||||
if (isMultiBattleTeam) {
|
||||
multiBattleTeams[existing.challengedPlayerUUID]?.teamPlayersUUID?.let {
|
||||
CobblemonNetwork.sendPacketToPlayers(it.mapNotNull { serverPlayer -> serverPlayer.getPlayer() }, BattleChallengeExpiredPacket(existing.challengeId))
|
||||
}
|
||||
} else {
|
||||
existing.challengedPlayerUUID.getPlayer()?.sendPacket(BattleChallengeExpiredPacket(existing.challengeId))
|
||||
}
|
||||
}
|
||||
|
||||
fun removeTeamUpRequest(requesterId: UUID, requestId: UUID? = null) {
|
||||
val existing = multiBattleTeamRequests[requesterId] ?: return
|
||||
if (existing.requestID != requestId) {
|
||||
return
|
||||
}
|
||||
multiBattleTeamRequests.remove(requesterId)
|
||||
existing.requestedPlayerUUID.getPlayer()?.sendPacket(TeamRequestExpiredPacket(existing.requestID))
|
||||
}
|
||||
|
||||
fun onPlayerDisconnect(player: ServerPlayer) {
|
||||
// Stop battles
|
||||
getBattleByParticipatingPlayer(player)?.stop()
|
||||
// Remove player from any teams they may be a part of
|
||||
removeTeamMember(player)
|
||||
}
|
||||
|
||||
fun removeTeamMember(player: ServerPlayer) {
|
||||
val teamEntry = playerToTeam[player.uuid]
|
||||
if (teamEntry != null) {
|
||||
teamEntry.teamPlayersUUID.remove(player.uuid)
|
||||
playerToTeam.remove(player.uuid)
|
||||
val notificationPacket = TeamMemberRemoveNotificationPacket(player.uuid)
|
||||
CobblemonNetwork.sendPacketToPlayer(player, notificationPacket)
|
||||
|
||||
// Notify remaining members that the player has left the group
|
||||
server()?.let {
|
||||
val teamServerPlayers = teamEntry.teamPlayersUUID.mapNotNull { uuid -> it.playerList.getPlayer(uuid) }
|
||||
CobblemonNetwork.sendPacketToPlayers(teamServerPlayers, notificationPacket)
|
||||
}
|
||||
|
||||
|
||||
if (teamEntry.teamPlayersUUID.count() == 1) {
|
||||
val remainingPlayerUUID = teamEntry.teamPlayersUUID.first()
|
||||
|
||||
// Remove any outstanding battle requests for the team
|
||||
// TODO: Find a way to not do a reverse map look up
|
||||
val battleChallengeEntry = pvpChallenges.entries.firstOrNull { it -> it.value.challengedPlayerUUID == teamEntry.teamID }
|
||||
if (battleChallengeEntry != null) {
|
||||
val battleChallenge = battleChallengeEntry.value
|
||||
removeChallenge(battleChallengeEntry.key, battleChallenge.challengeId, true)
|
||||
|
||||
// Notify opposing team that the challenge was cancelled
|
||||
val opposingTeam = multiBattleTeams[battleChallengeEntry.key]
|
||||
opposingTeam?.teamPlayersUUID?.map { it.getPlayer() }?.forEach { serverPlayerEntity ->
|
||||
serverPlayerEntity?.sendSystemMessage(lang("challenge.multi.decline.disband"))
|
||||
}
|
||||
}
|
||||
|
||||
// Disband the team
|
||||
playerToTeam.remove(remainingPlayerUUID)
|
||||
multiBattleTeams.remove(teamEntry.teamID)
|
||||
|
||||
// Notify remaining player that the team has been disbanded
|
||||
server()?.let {
|
||||
it.playerList.getPlayer(remainingPlayerUUID)?.let { serverPlayerEntity ->
|
||||
CobblemonNetwork.sendPacketToPlayer(serverPlayerEntity, TeamMemberRemoveNotificationPacket(remainingPlayerUUID))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,7 +259,7 @@ object BattleRegistry {
|
||||
* See the lines about multi battles and free for alls. The same side of the battle will share 'parity' (even or odd) across
|
||||
* all participants. So side 1 will be 1, 3, 5, ... while side 2 will be 2, 4, 6, ...
|
||||
*
|
||||
* That isn't how our code works, as we have the BattleSide thing, but it's how Showdown works so we need to play along a bit.
|
||||
* That isn't how our code works, as we have the BattleSide thing, but it's how Showdown works, so we need to play along a bit.
|
||||
*/
|
||||
|
||||
var actorIndex = 1
|
||||
|
||||
@@ -21,6 +21,7 @@ object BattleTypes {
|
||||
val DOUBLES = makeBattleType("doubles", actorsPerSide = 1, slotsPerActor = 2)
|
||||
val TRIPLES = makeBattleType("triples", actorsPerSide = 1, slotsPerActor = 3)
|
||||
val MULTI = makeBattleType("multi", actorsPerSide = 2, slotsPerActor = 1)
|
||||
val ROYAL = makeBattleType("freeforall", actorsPerSide = 1, slotsPerActor = 1)
|
||||
// maybe one day we can add MULTI-3 for triple battles with 6 fuckers in it, that'd be sick. We could game it with partial actors though
|
||||
|
||||
fun makeBattleType(
|
||||
|
||||
@@ -61,7 +61,7 @@ interface Targetable {
|
||||
|
||||
fun getLetter(): Char {
|
||||
var index = 0
|
||||
for (activePokemon in getActorPokemon()) {
|
||||
for (activePokemon in getSidePokemon()) {
|
||||
if (activePokemon == this) {
|
||||
break
|
||||
} else {
|
||||
@@ -79,6 +79,32 @@ interface Targetable {
|
||||
else -> throw IllegalStateException("Battle has more than 6 in the active slot, makes no sense.")
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the list of valid targets for a move that the user is not allowed to select a target
|
||||
* This is needed for the target menu to show the client what targets will be hit in a
|
||||
* multitarget move.
|
||||
*/
|
||||
fun getMultiTargetList(targetType: MoveTarget) : List<Targetable>? {
|
||||
return when (targetType) {
|
||||
MoveTarget.any -> null
|
||||
MoveTarget.all -> getAllActivePokemon().toList()
|
||||
MoveTarget.allAdjacent -> getAdjacent()
|
||||
MoveTarget.allAdjacentFoes -> getAdjacentOpponents()
|
||||
MoveTarget.self -> listOf(this)
|
||||
MoveTarget.normal -> null
|
||||
MoveTarget.randomNormal -> listOf(this)
|
||||
MoveTarget.allies -> getAllActivePokemon().filter { it.isAllied(this) }
|
||||
MoveTarget.allySide -> getAllActivePokemon().filter { it.isAllied(this) }
|
||||
MoveTarget.allyTeam -> getAllActivePokemon().filter { it.isAllied(this) }
|
||||
MoveTarget.adjacentAlly -> null
|
||||
MoveTarget.adjacentAllyOrSelf -> null
|
||||
MoveTarget.adjacentFoe -> null
|
||||
MoveTarget.foeSide -> getAllActivePokemon().filter { !it.isAllied(this) }
|
||||
MoveTarget.scripted -> null
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class MoveTarget(val targetList: (Targetable) -> List<Targetable>? = { null }) {
|
||||
|
||||
@@ -92,6 +92,7 @@ enum class ShowdownActionResponseType(val loader: (RegistryFriendlyByteBuf) -> S
|
||||
DEFAULT({ DefaultActionResponse() }),
|
||||
FORCE_PASS({ ForcePassActionResponse() }),
|
||||
PASS({ PassActionResponse }),
|
||||
SHIFT({ ShiftActionResponse()}),
|
||||
HEAL_ITEM({ HealItemActionResponse("potion") }),
|
||||
FORFEIT({ ForfeitActionResponse() });
|
||||
}
|
||||
@@ -130,7 +131,7 @@ data class MoveActionResponse(var moveName: String, var targetPnx: String? = nul
|
||||
|
||||
val pnx = targetPnx ?: return false // If the targets list is non-null then they need to have specified a target
|
||||
val (_, targetPokemon) = activeBattlePokemon.actor.battle.getActorAndActiveSlotFromPNX(pnx)
|
||||
if (targetPokemon !in availableTargets || targetPokemon.battlePokemon == null || targetPokemon.battlePokemon!!.health <= 0) {
|
||||
if (targetPokemon !in availableTargets) {
|
||||
return false // It's not a possible target.
|
||||
}
|
||||
|
||||
@@ -188,6 +189,16 @@ data class HealItemActionResponse(var item: String) : ShowdownActionResponse(Sho
|
||||
}
|
||||
}
|
||||
|
||||
class ShiftActionResponse() : ShowdownActionResponse(ShowdownActionResponseType.SHIFT) {
|
||||
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean): Boolean {
|
||||
return !forceSwitch && activeBattlePokemon.getPNX()[1] != 'b' && activeBattlePokemon.battle.format.battleType.pokemonPerSide == 3
|
||||
}
|
||||
|
||||
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?): String {
|
||||
return "shift"
|
||||
}
|
||||
|
||||
}
|
||||
data class SwitchActionResponse(var newPokemonId: UUID) : ShowdownActionResponse(ShowdownActionResponseType.SWITCH) {
|
||||
override fun saveToBuffer(buffer: RegistryFriendlyByteBuf) {
|
||||
super.saveToBuffer(buffer)
|
||||
@@ -204,7 +215,7 @@ data class SwitchActionResponse(var newPokemonId: UUID) : ShowdownActionResponse
|
||||
val pokemon = activeBattlePokemon.actor.pokemonList.find { it.uuid == newPokemonId }
|
||||
return when {
|
||||
pokemon == null -> false // No such Pokémon
|
||||
(!activeBattlePokemon.actor.request?.side?.pokemon?.get(0)?.reviving!! && pokemon.health <= 0) -> false // Checks if the active Pokémon is reviving, if so ignore this check. If not, return false if dead
|
||||
(!activeBattlePokemon.actor.request?.side?.pokemon?.any{ it.uuid == activeBattlePokemon.battlePokemon?.uuid && it.reviving }!! && pokemon.health <= 0) -> false // Checks if the active Pokémon is reviving, if so ignore this check. If not, return false if dead
|
||||
showdownMoveSet != null && showdownMoveSet.trapped -> false // You're not allowed to switch
|
||||
activeBattlePokemon.actor.getSide().activePokemon.any { it.battlePokemon?.uuid == newPokemonId } -> false // Pokémon is already sent out
|
||||
else -> true
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.api.battles.interpreter.Effect
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.MissingContext
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
|
||||
import com.cobblemon.mod.common.api.text.yellow
|
||||
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
|
||||
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
|
||||
@@ -24,6 +25,7 @@ import com.cobblemon.mod.common.battles.interpreter.instructions.*
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.runOnServer
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.UUID
|
||||
import kotlin.collections.Iterator
|
||||
import kotlin.collections.filter
|
||||
@@ -67,7 +69,7 @@ object ShowdownInterpreter {
|
||||
updateInstructionParser["-clearallboost"] = { _, _, message, _ -> ClearAllBoostInstruction(message) }
|
||||
updateInstructionParser["-clearnegativeboost"] = { _, _, message, _ -> ClearNegativeBoostInstruction(message) }
|
||||
updateInstructionParser["-copyboost"] = { _, _, message, _ -> CopyBoostInstruction(message) }
|
||||
updateInstructionParser["-crit"] = { _, _, message, _ -> CritInstruction(message) }
|
||||
updateInstructionParser["-crit"] = { _, instructionSet, message, _ -> CritInstruction(message, instructionSet) }
|
||||
updateInstructionParser["-curestatus"] = { _, _, message, _ -> CureStatusInstruction(message) }
|
||||
updateInstructionParser["detailschange"] = { _, _, message, _ -> DetailsChangeInstruction(message) }
|
||||
updateInstructionParser["-endability"] = { _, _, message, _ -> EndAbilityInstruction(message) }
|
||||
@@ -90,8 +92,7 @@ object ShowdownInterpreter {
|
||||
updateInstructionParser["-prepare"] = { _, _, message, _ -> PrepareInstruction(message) }
|
||||
updateInstructionParser["-mustrecharge"] = { _, _, message, _ -> RechargeInstruction(message) }
|
||||
updateInstructionParser["replace"] = { _, _, message, _ -> ReplaceInstruction(message) }
|
||||
updateInstructionParser["-resisted"] = { _, _, message, _ -> ResistedInstruction(message) }
|
||||
updateInstructionParser["-resisted"] = { _, _, message, _ -> ResistedInstruction(message) }
|
||||
updateInstructionParser["-resisted"] = { _, instructionSet, message, _ -> ResistedInstruction(message, instructionSet) }
|
||||
updateInstructionParser["-setboost"] = { _, _, message, _ -> SetBoostInstruction(message) }
|
||||
updateInstructionParser["-sideend"] = { _, _, message, _ -> SideEndInstruction(message) }
|
||||
updateInstructionParser["-sidestart"] = { _, _, message, _ -> SideStartInstruction(message) }
|
||||
@@ -99,7 +100,7 @@ object ShowdownInterpreter {
|
||||
updateInstructionParser["-singleturn"] = { _, _, message, _ -> SingleTurnInstruction(message) }
|
||||
updateInstructionParser["-start"] = { _, _, message, _ -> StartInstruction(message) }
|
||||
updateInstructionParser["-status"] = { _, _, message, _ -> StatusInstruction(message) }
|
||||
updateInstructionParser["-supereffective"] = { _, _, message, _ -> SuperEffectiveInstruction(message) }
|
||||
updateInstructionParser["-supereffective"] = { _, instructionSet, message, _ -> SuperEffectiveInstruction(message, instructionSet) }
|
||||
updateInstructionParser["-swapboost"] = { _, _, message, _ -> SwapBoostInstruction(message) }
|
||||
updateInstructionParser["-swapsideconditions"] = { _, _, message, _ -> SwapSideConditionsInstruction(message) }
|
||||
updateInstructionParser["-terastallize"] = { _, _, message, _ -> TerastallizeInstruction(message) }
|
||||
@@ -111,10 +112,14 @@ object ShowdownInterpreter {
|
||||
updateInstructionParser["win"] = { _, _, message, _ -> WinInstruction(message) }
|
||||
updateInstructionParser["-zbroken"] = { _, _, message, _ -> ZBrokenInstruction(message) }
|
||||
updateInstructionParser["-zpower"] = { _, _, message, _ -> ZPowerInstruction(message) }
|
||||
updateInstructionParser["swap"] = { _, instructionSet, message, _ -> SwapInstruction(message, instructionSet) }
|
||||
|
||||
sideInstructionParser["error"] = { _, targetActor, _, message -> ErrorInstruction(targetActor, message) }
|
||||
sideInstructionParser["request"] = { _, targetActor, _, message -> RequestInstruction(targetActor, message) }
|
||||
|
||||
splitInstructionParser["-candynamax"] = { _, targetActor, instructionSet, publicMessage, privateMessage, _ ->
|
||||
CanDynamaxInstruction(instructionSet, targetActor, publicMessage, privateMessage)
|
||||
}
|
||||
splitInstructionParser["-damage"] = { _, targetActor, instructionSet, publicMessage, privateMessage, _ ->
|
||||
DamageInstruction(instructionSet, targetActor, publicMessage, privateMessage)
|
||||
}
|
||||
@@ -131,9 +136,67 @@ object ShowdownInterpreter {
|
||||
SwitchInstruction(instructionSet, targetActor, publicMessage, privateMessage)
|
||||
}
|
||||
|
||||
|
||||
// Note '-cureteam' is a legacy thing that is only used in generation 2 and 4 mods for heal bell and aromatherapy respectively as such we can just ignore that
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Figures out the sendout position for a pokemon in a battle
|
||||
*
|
||||
*
|
||||
*/
|
||||
fun getSendoutPosition(battle: PokemonBattle, pnx:String, battleActor: BattleActor): Vec3? {
|
||||
val entityPosList = battleActor.getSide().actors.mapNotNull { if (it is EntityBackedBattleActor<*>) it.initialPos else null }
|
||||
var entityPos = if (entityPosList.size == 1)
|
||||
entityPosList[0]
|
||||
else if (entityPosList.size > 1)
|
||||
entityPosList.fold(Vec3(0.0, 0.0, 0.0)) { acc, vec3 -> acc.add(vec3.scale(1.0 / entityPosList.size)) }
|
||||
else
|
||||
null
|
||||
val opposingEntityList = battleActor.getSide().getOppositeSide().actors.mapNotNull { if (it is EntityBackedBattleActor<*>) it.initialPos else null }
|
||||
val opposingEntityPos = if (opposingEntityList.size == 1)
|
||||
opposingEntityList[0]
|
||||
else if (opposingEntityList.size > 1)
|
||||
opposingEntityList.fold(Vec3(0.0, 0.0, 0.0)) { acc, vec3 -> acc.add(vec3.scale(1.0 / opposingEntityList.size)) }
|
||||
else null
|
||||
|
||||
var baseOffset = entityPos?.let { opposingEntityPos?.subtract(it) }
|
||||
|
||||
if (baseOffset != null) {
|
||||
val minDistance = if (battle.isPvW) 8.0 else 5.0
|
||||
val length = baseOffset.length()
|
||||
if (length < minDistance) {
|
||||
val temp = baseOffset.scale(minDistance / length) ?: baseOffset
|
||||
entityPos = entityPos?.subtract(temp.subtract(baseOffset))
|
||||
baseOffset = temp
|
||||
}
|
||||
var vector = Vec3(baseOffset.x, 0.0, baseOffset.z).normalize()
|
||||
vector = vector.cross(Vec3(0.0, 1.0, 0.0))
|
||||
|
||||
if (battle.format.battleType.pokemonPerSide == 1) { // Singles
|
||||
entityPos = entityPos?.add(baseOffset.scale(if (battle.isPvW) 0.4 else 0.3))?.add(vector.scale(-2.0))
|
||||
} else if (battle.format.battleType.pokemonPerSide == 2) { // Doubles
|
||||
if (battle.actors.first() !== battle.actors.last()) {
|
||||
val offsetB = if (pnx[2] == 'a') vector.scale(-1.0) else vector
|
||||
entityPos = entityPos?.add(baseOffset.scale(0.33))?.add(offsetB.scale(2.5))
|
||||
}
|
||||
} else if (battle.format.battleType.pokemonPerSide == 3) { // Triples
|
||||
if (battle.actors.first() !== battle.actors.last()) {
|
||||
entityPos = when (pnx[2]) {
|
||||
'a' -> entityPos?.add(baseOffset.scale(0.15))?.add(vector.scale(-3.5))
|
||||
'b' -> entityPos?.add(baseOffset.scale(0.3))
|
||||
'c' -> entityPos?.add(baseOffset.scale(0.15))?.add(vector.scale(3.5))
|
||||
else -> entityPos
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return entityPos
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun interpretMessage(battleId: UUID, message: String) {
|
||||
// Check key map and use function if matching
|
||||
if (message.startsWith("{\"winner\":\"")) {
|
||||
|
||||
@@ -20,18 +20,23 @@ import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMusicPacket
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import java.util.UUID
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.sounds.SoundEvent
|
||||
import java.util.*
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class PlayerBattleActor(
|
||||
uuid: UUID,
|
||||
pokemonList: List<BattlePokemon>
|
||||
) : BattleActor(uuid, pokemonList.toMutableList()), EntityBackedBattleActor<ServerPlayer> {
|
||||
|
||||
override val initialPos: Vec3?
|
||||
override val entity: ServerPlayer?
|
||||
get() = this.uuid.getPlayer()
|
||||
init {
|
||||
initialPos = entity?.position();
|
||||
}
|
||||
|
||||
/** The [SoundEvent] to play to the player during a battle. Will start playing as soon as the battle starts. */
|
||||
var battleTheme: SoundEvent? = null
|
||||
|
||||
@@ -24,12 +24,17 @@ import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
open class PokemonBattleActor(
|
||||
class PokemonBattleActor(
|
||||
uuid: UUID,
|
||||
val pokemon: BattlePokemon,
|
||||
override val fleeDistance: Float,
|
||||
artificialDecider: BattleAI = RandomBattleAI()
|
||||
) : AIBattleActor(uuid, listOf(pokemon), artificialDecider), EntityBackedBattleActor<PokemonEntity>, FleeableBattleActor {
|
||||
|
||||
override val initialPos: Vec3?
|
||||
init {
|
||||
initialPos = entity?.position()
|
||||
}
|
||||
override fun getName() = pokemon.effectedPokemon.species.translatedName
|
||||
override fun nameOwned(name: String): MutableComponent = Component.literal(name)
|
||||
override fun getWorldAndPosition(): Pair<ServerLevel, Vec3>? {
|
||||
|
||||
+11
@@ -13,6 +13,9 @@ import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import com.cobblemon.mod.common.util.giveOrDropItemStack
|
||||
import net.minecraft.world.item.ItemStack
|
||||
|
||||
/**
|
||||
* Format: |bagitem|POKEMON|ITEM
|
||||
@@ -25,9 +28,17 @@ class BagItemInstruction(val message: BattleMessage): InterpreterInstruction {
|
||||
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
battle.dispatchGo {
|
||||
|
||||
val pokemon = message.pokemonByUuid(0, battle)!!
|
||||
val item = message.argumentAt(1)!!
|
||||
|
||||
val index = pokemon.actor.itemsUsed.indexOfFirst { item == it.itemName }
|
||||
if (index >= 0) {
|
||||
val player = pokemon.actor.getPlayerUUIDs().first().getPlayer()
|
||||
player?.giveOrDropItemStack(ItemStack(pokemon.actor.itemsUsed[index].returnItem))
|
||||
pokemon.actor.itemsUsed.removeAt(index)
|
||||
}
|
||||
|
||||
val ownerName = pokemon.actor.getName()
|
||||
val itemName = item.asTranslated()
|
||||
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.battles.interpreter.instructions
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.api.text.yellow
|
||||
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
|
||||
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
|
||||
/**
|
||||
* Format: |-candynamax|SIDE
|
||||
*
|
||||
* SIDE can choose to dynamax this turn.
|
||||
* In multi battles, actors on a team are alternatively given the option to dynamax each turn.
|
||||
* On turn 1, the actors on their team's respective left have the first chance (p1 and p2; in multi battles, each actor is their own side).
|
||||
* @author Segfault Guy
|
||||
* @since July 16th, 2024
|
||||
*/
|
||||
class CanDynamaxInstruction(val instructionSet: InstructionSet, val battleActor: BattleActor, val publicMessage: BattleMessage, val privateMessage: BattleMessage): InterpreterInstruction {
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
battle.dispatchWaiting {
|
||||
val side = privateMessage.argumentAt(0) ?: return@dispatchWaiting
|
||||
battle.getActor(side)?.let {
|
||||
it.canDynamax = true
|
||||
battle.broadcastChatMessage(battleLang("candynamax", it.getName()).yellow())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-4
@@ -11,10 +11,11 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.text.yellow
|
||||
import com.cobblemon.mod.common.battles.ShowdownInterpreter
|
||||
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
|
||||
import com.cobblemon.mod.common.battles.ShowdownInterpreter
|
||||
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
|
||||
|
||||
/**
|
||||
* Format: |-crit|POKEMON
|
||||
@@ -23,14 +24,21 @@ import com.cobblemon.mod.common.util.battleLang
|
||||
* @author Hunter
|
||||
* @since August 18th, 2022
|
||||
*/
|
||||
class CritInstruction(val message: BattleMessage): InterpreterInstruction {
|
||||
class CritInstruction(val message: BattleMessage, val instructionSet: InstructionSet,
|
||||
): InterpreterInstruction {
|
||||
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
val lastCauser = instructionSet.getMostRecentCauser(comparedTo = this)
|
||||
battle.dispatchGo {
|
||||
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchGo
|
||||
battle.broadcastChatMessage(battleLang("crit").yellow())
|
||||
ShowdownInterpreter.lastCauser[battle.battleId]?.let { message ->
|
||||
val battlePokemon = message.battlePokemon(0, battle) ?: return@let
|
||||
if (lastCauser is MoveInstruction && lastCauser.spreadTargets.isNotEmpty()) {
|
||||
val pokemonName = battlePokemon.getName()
|
||||
battle.broadcastChatMessage(battleLang("crit_spread", pokemonName).yellow())
|
||||
} else {
|
||||
battle.broadcastChatMessage(battleLang("crit").yellow())
|
||||
}
|
||||
if (LastBattleCriticalHitsEvolutionProgress.supports(battlePokemon.effectedPokemon)) {
|
||||
val progress = battlePokemon.effectedPokemon.evolutionProxy.current().progressFirstOrCreate({ it is LastBattleCriticalHitsEvolutionProgress }) { LastBattleCriticalHitsEvolutionProgress() }
|
||||
progress.updateProgress(LastBattleCriticalHitsEvolutionProgress.Progress(progress.currentProgress().amount + 1))
|
||||
|
||||
+10
@@ -195,6 +195,16 @@ class DamageInstruction(
|
||||
privateMessage.pnxAndUuid(0)?.let { (pnx, _) -> battle.sendSidedUpdate(actor, BattleHealthChangePacket(pnx, remainingHealth.toFloat()), BattleHealthChangePacket(pnx, newHealthRatio)) }
|
||||
|
||||
battle.minorBattleActions[battlePokemon.uuid] = privateMessage
|
||||
if (lastCauser is MoveInstruction) {
|
||||
if (lastCauser.spreadTargets.size > 1) {
|
||||
val subsequentInstructions = instructionSet.findInstructionsCausedBy(lastCauser)
|
||||
// Only wait on the last target of a multitarget attack
|
||||
if (subsequentInstructions.filterIsInstance<DamageInstruction>().last() != this) {
|
||||
// TODO: Dragon darts?
|
||||
return@dispatch GO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If they faint from this damage then don't bother waiting for the damage to be applied.
|
||||
if (lastCauser is MoveInstruction && lastCauser.actionEffect != null && !causedFaint) {
|
||||
|
||||
+9
-2
@@ -45,6 +45,7 @@ class MoveInstruction(
|
||||
val effect = message.effectAt(1) ?: Effect.pure("", "")
|
||||
val move = Moves.getByNameOrDummy(effect.id)
|
||||
val actionEffect = move.actionEffect
|
||||
val spreadTargets = message.optionalArgument("spread")?.split(",") ?: emptyList()
|
||||
|
||||
var future = CompletableFuture.completedFuture(Unit)
|
||||
var holds = mutableSetOf<String>()
|
||||
@@ -65,6 +66,8 @@ class MoveInstruction(
|
||||
battle.dispatch {
|
||||
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
|
||||
val spreadTargetPokemon = spreadTargets.map { battle.activePokemon.firstOrNull() { poke -> poke.getPNX() == it }?.battlePokemon }
|
||||
|
||||
userPokemon.effectedPokemon.let { pokemon ->
|
||||
if (UseMoveEvolutionProgress.supports(pokemon, move)) {
|
||||
@@ -76,7 +79,7 @@ class MoveInstruction(
|
||||
val lang = when {
|
||||
optionalEffect?.id == "magicbounce" ->
|
||||
battleLang("ability.magicbounce", pokemonName, move.displayName)
|
||||
move.name != "struggle" && targetPokemon != null && targetPokemon != userPokemon ->
|
||||
move.name != "struggle" && spreadTargetPokemon.isEmpty() && targetPokemon != null && targetPokemon != userPokemon && targetPokemon.health > 0 ->
|
||||
battleLang("used_move_on", pokemonName, move.displayName, targetPokemon.getName())
|
||||
else ->
|
||||
battleLang("used_move", pokemonName, move.displayName)
|
||||
@@ -86,7 +89,11 @@ class MoveInstruction(
|
||||
|
||||
val providers = mutableListOf<Any>(battle)
|
||||
userPokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
|
||||
targetPokemon?.effectedPokemon?.entity?.let { TargetsProvider(it) }?.let(providers::add)
|
||||
if (spreadTargetPokemon.isNotEmpty()) {
|
||||
providers.add(TargetsProvider( spreadTargetPokemon.filter { it?.effectedPokemon?.entity != null}.mapNotNull { spreadTarget -> spreadTarget?.effectedPokemon?.entity } ))
|
||||
} else {
|
||||
targetPokemon?.effectedPokemon?.entity?.let { TargetsProvider(it) }?.let(providers::add)
|
||||
}
|
||||
val runtime = MoLangRuntime().also {
|
||||
battle.addQueryFunctions(it.environment.query).addStandardFunctions()
|
||||
}
|
||||
|
||||
+15
-4
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
|
||||
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
|
||||
@@ -20,13 +21,23 @@ import com.cobblemon.mod.common.util.battleLang
|
||||
* @author Hunter
|
||||
* @since August 18th, 2022
|
||||
*/
|
||||
class ResistedInstruction(val message: BattleMessage): InterpreterInstruction {
|
||||
class ResistedInstruction(
|
||||
val publicMessage: BattleMessage,
|
||||
val instructionSet: InstructionSet,
|
||||
) : InterpreterInstruction {
|
||||
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
val battlePokemon = publicMessage.battlePokemon(0, battle)
|
||||
battlePokemon ?: return
|
||||
val lastCauser = instructionSet.getMostRecentCauser(comparedTo = this)
|
||||
battle.dispatchGo {
|
||||
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchGo
|
||||
battle.broadcastChatMessage(battleLang("resisted"))
|
||||
battle.minorBattleActions[pokemon.uuid] = message
|
||||
if (lastCauser is MoveInstruction && lastCauser.spreadTargets.isNotEmpty()) {
|
||||
val pokemonName = battlePokemon.getName()
|
||||
battle.broadcastChatMessage(battleLang("resisted_spread", pokemonName))
|
||||
} else {
|
||||
battle.broadcastChatMessage(battleLang("resisted"))
|
||||
}
|
||||
battle.minorBattleActions[battlePokemon.uuid] = publicMessage
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-2
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
|
||||
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
|
||||
@@ -20,12 +21,19 @@ import com.cobblemon.mod.common.util.battleLang
|
||||
* @author Hunter
|
||||
* @since August 18th, 2022
|
||||
*/
|
||||
class SuperEffectiveInstruction(val message: BattleMessage): InterpreterInstruction {
|
||||
class SuperEffectiveInstruction(val message: BattleMessage,val instructionSet: InstructionSet,
|
||||
): InterpreterInstruction {
|
||||
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
val lastCauser = instructionSet.getMostRecentCauser(comparedTo = this)
|
||||
battle.dispatchGo {
|
||||
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchGo
|
||||
battle.broadcastChatMessage(battleLang("superEffective"))
|
||||
if (lastCauser is MoveInstruction && lastCauser.spreadTargets.isNotEmpty()) {
|
||||
val pokemonName = pokemon.getName()
|
||||
battle.broadcastChatMessage(battleLang("superEffective_spread", pokemonName))
|
||||
} else {
|
||||
battle.broadcastChatMessage(battleLang("superEffective"))
|
||||
}
|
||||
battle.minorBattleActions[pokemon.uuid] = message
|
||||
}
|
||||
}
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.battles.interpreter.instructions
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.text.yellow
|
||||
import com.cobblemon.mod.common.battles.ShowdownInterpreter
|
||||
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
|
||||
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleSwapPokemonPacket
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.setPositionSafely
|
||||
import com.cobblemon.mod.common.util.swap
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.logging.Level
|
||||
|
||||
|
||||
/**
|
||||
* Format:
|
||||
* |swap|POKEMON|(from)EFFECT
|
||||
*
|
||||
* Indicates that a pokemon has swapped its field position with an ally.
|
||||
* @author JazzMcNade
|
||||
*/
|
||||
class SwapInstruction(val message: BattleMessage, val instructionSet: InstructionSet): InterpreterInstruction {
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
// TODO: more error checks
|
||||
battle.dispatchWaiting {
|
||||
val battlePokemonA = message.battlePokemon(0, battle) ?: return@dispatchWaiting
|
||||
val pnxA = message.argumentAt(0)?.substring(0, 3)
|
||||
var posA: Vec3? = null
|
||||
if (battlePokemonA.entity == null) {
|
||||
posA = ShowdownInterpreter.getSendoutPosition(battle, pnxA!!, battlePokemonA.actor)
|
||||
} else {
|
||||
posA = battlePokemonA.entity?.position()
|
||||
}
|
||||
|
||||
val (actor, activePokemonA) = battle.getActorAndActiveSlotFromPNX(pnxA!!)
|
||||
val activeBattlePokemonB = activePokemonA.getAdjacentAllies().firstOrNull()
|
||||
if(activeBattlePokemonB != null) {
|
||||
val pnxB = activeBattlePokemonB.getPNX()
|
||||
val (actorB, activePokemonB) = battle.getActorAndActiveSlotFromPNX(pnxB)
|
||||
|
||||
// Swap the position of the 2 on the field
|
||||
var posB: Vec3? = null
|
||||
if (activePokemonB.battlePokemon?.entity == null) {
|
||||
// target slot is likely fainted
|
||||
posB = ShowdownInterpreter.getSendoutPosition(battle, activePokemonB.getPNX(), actorB)
|
||||
} else {
|
||||
posB = activePokemonB.battlePokemon?.entity?.position()
|
||||
}
|
||||
if (posB != null && battlePokemonA.entity != null) {
|
||||
battlePokemonA.entity?.setPositionSafely(posB)
|
||||
} else if(posB != null) {
|
||||
if (activePokemonA.battlePokemon?.entity != null) {
|
||||
activePokemonA.battlePokemon?.entity?.setPositionSafely(posB)
|
||||
} else {
|
||||
activePokemonB.position.let {
|
||||
if (it != null) {
|
||||
activePokemonB.position = Pair<ServerLevel, Vec3>(it.first, posB)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(posA != null) {
|
||||
if (activePokemonB.battlePokemon?.entity != null) {
|
||||
activePokemonB.battlePokemon?.entity?.setPositionSafely(posA)
|
||||
} else {
|
||||
activePokemonA.position.let {
|
||||
if (it != null) {
|
||||
activePokemonB.position = Pair<ServerLevel, Vec3>(it.first, posA)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Swap references of the 2 pokemon
|
||||
actor.activePokemon.swap((pnxA[2] - 'a'), (pnxB[2] - 'a'))
|
||||
|
||||
// Notify clients of the swap
|
||||
battle.sendUpdate(BattleSwapPokemonPacket(pnxA))
|
||||
|
||||
// Send battle message
|
||||
val lastCauser = instructionSet.getMostRecentCauser(comparedTo = this)
|
||||
val lang = if (lastCauser is MoveInstruction && lastCauser.move.name == "allyswitch") {
|
||||
// Ally Switch
|
||||
battleLang("activate.allyswitch", battlePokemonA.getName(), activePokemonB.battlePokemon?.getName() ?: "")
|
||||
} else {
|
||||
// Triple battle shift
|
||||
battleLang("shift", battlePokemonA.getName())
|
||||
}
|
||||
battle.broadcastChatMessage(lang)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+23
-17
@@ -13,15 +13,20 @@ import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
|
||||
import com.cobblemon.mod.common.api.scheduling.afterOnServer
|
||||
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
|
||||
import com.cobblemon.mod.common.battles.ShowdownInterpreter
|
||||
import com.cobblemon.mod.common.battles.dispatch.*
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.entity.pokemon.effects.IllusionEffect
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleSwitchPokemonPacket
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler.SEND_OUT_STAGGER_BASE_DURATION
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler.SEND_OUT_STAGGER_RANDOM_MAX_DURATION
|
||||
import com.cobblemon.mod.common.util.swap
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* Format: |switch|POKEMON|DETAILS|HP STATUS
|
||||
@@ -36,10 +41,11 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
|
||||
|
||||
override fun invoke(battle: PokemonBattle) {
|
||||
|
||||
val (pnx, _) = publicMessage.pnxAndUuid(0) ?: return
|
||||
val (pnx, pokemonID) = publicMessage.pnxAndUuid(0) ?: return
|
||||
val (actor, activePokemon) = battle.getActorAndActiveSlotFromPNX(pnx)
|
||||
val entity = if (actor is EntityBackedBattleActor<*>) actor.entity else null
|
||||
|
||||
|
||||
val imposter = instructionSet.getNextInstruction<TransformInstruction>(this)?.expectedTarget != null
|
||||
val illusion = publicMessage.battlePokemonFromOptional(battle, "is")
|
||||
val pokemon = publicMessage.battlePokemon(0, battle) ?: return
|
||||
@@ -58,22 +64,22 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
|
||||
}
|
||||
|
||||
if (pokemonEntity == null && entity != null) {
|
||||
val targetPos = battleActor.getSide().getOppositeSide().actors.filterIsInstance<EntityBackedBattleActor<*>>().firstOrNull()?.entity?.position()?.let { pos ->
|
||||
val offset = pos.subtract(entity.position())
|
||||
val idealPos = entity.position().add(offset.scale(0.33))
|
||||
idealPos
|
||||
} ?: entity.position()
|
||||
|
||||
actor.stillSendingOutCount++
|
||||
pokemon.effectedPokemon.sendOutWithAnimation(
|
||||
source = entity,
|
||||
battleId = battle.battleId,
|
||||
level = entity.level() as ServerLevel,
|
||||
doCry = false,
|
||||
position = targetPos,
|
||||
illusion = illusion?.let { IllusionEffect(it.effectedPokemon) }
|
||||
).thenApply {
|
||||
actor.stillSendingOutCount--
|
||||
val targetPos = ShowdownInterpreter.getSendoutPosition(battle, pnx, battleActor)
|
||||
if (targetPos != null) {
|
||||
val battleSendoutCount = activePokemon.getActorShowdownId()[1].digitToInt() - 1 + actor.stillSendingOutCount
|
||||
actor.stillSendingOutCount++
|
||||
afterOnServer(seconds = battleSendoutCount * SEND_OUT_STAGGER_BASE_DURATION + if (battleSendoutCount > 0) Random.nextFloat() * SEND_OUT_STAGGER_RANDOM_MAX_DURATION else 0F ) {
|
||||
pokemon.effectedPokemon.sendOutWithAnimation(
|
||||
source = entity,
|
||||
battleId = battle.battleId,
|
||||
level = entity.level() as ServerLevel,
|
||||
doCry = false,
|
||||
position = targetPos,
|
||||
illusion = illusion?.let { IllusionEffect(it.effectedPokemon) }
|
||||
).thenApply {
|
||||
actor.stillSendingOutCount--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (pokemonEntity != null) {
|
||||
|
||||
@@ -94,7 +94,7 @@ open class BattlePokemon(
|
||||
|
||||
fun isSentOut() = actor.battle.activePokemon.any { it.battlePokemon == this }
|
||||
fun canBeSentOut() =
|
||||
if (actor.request?.side?.pokemon?.get(0)?.reviving == true) {
|
||||
if (actor.request?.side?.pokemon?.any{ it.reviving } == true) {
|
||||
!isSentOut() && !willBeSwitchedIn && health <= 0
|
||||
} else {
|
||||
!isSentOut() && !willBeSwitchedIn && health > 0
|
||||
|
||||
@@ -9,9 +9,70 @@
|
||||
package com.cobblemon.mod.common.client
|
||||
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleChallenge
|
||||
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.client.trade.ClientTradeOffer
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import java.util.UUID
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class ClientPlayerActionRequests {
|
||||
val battleChallenges = mutableListOf<ClientBattleChallenge>()
|
||||
val multiBattleTeamRequests = mutableListOf<ClientBattleChallenge>()
|
||||
val tradeOffers = mutableListOf<ClientTradeOffer>()
|
||||
|
||||
val trackedPlayers = mutableMapOf<UUID, MatrixWrapper>()
|
||||
|
||||
fun onMultiChallengeExpired(challengeId: UUID) {
|
||||
val players = multiBattleTeamRequests.filter { it.challengeId == challengeId }.flatMap { it.challengerIds }
|
||||
players.forEach(trackedPlayers::remove)
|
||||
multiBattleTeamRequests.removeIf { it.challengeId == challengeId }
|
||||
}
|
||||
|
||||
fun onChallengeExpired(challengeId: UUID) {
|
||||
val players = battleChallenges.filter { it.challengeId == challengeId }.flatMap { it.challengerIds }
|
||||
players.forEach(trackedPlayers::remove)
|
||||
battleChallenges.removeIf { it.challengeId == challengeId }
|
||||
}
|
||||
|
||||
fun onTradeOfferExpired(uuid: UUID) {
|
||||
val tradeOffers = tradeOffers.filter { it.tradeOfferId == uuid }
|
||||
val players = tradeOffers.map { it.traderId }
|
||||
players.forEach(trackedPlayers::remove)
|
||||
this.tradeOffers.removeAll(tradeOffers)
|
||||
}
|
||||
|
||||
fun onRenderPlayer(player: Player, partialTicks: Float) {
|
||||
val matrixWrapper = trackedPlayers[player.uuid] ?: return
|
||||
val position = player.getEyePosition(partialTicks)
|
||||
matrixWrapper.updatePosition(
|
||||
Vec3(
|
||||
position.x,
|
||||
position.y + 1.1,
|
||||
position.z
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun addParticleEffect(challenge: ClientBattleChallenge) {
|
||||
challenge.challengerIds.forEach { playerId ->
|
||||
val player = Minecraft.getInstance().level?.getPlayerByUUID(playerId) ?: return@forEach
|
||||
val wrapper = trackedPlayers.getOrPut(player.uuid) { MatrixWrapper() }
|
||||
wrapper.updatePosition(player.getEyePosition(0F).add(0.0, 1.1, 0.0))
|
||||
val effect = BedrockParticleOptionsRepository.getEffect(cobblemonResource("particle_exclamation"))
|
||||
if (effect != null) {
|
||||
val particleStorm = ParticleStorm(
|
||||
effect = effect,
|
||||
matrixWrapper = wrapper,
|
||||
sourceAlive = { !player.isRemoved && trackedPlayers.containsKey(playerId) },
|
||||
world = Minecraft.getInstance().level ?: return,
|
||||
entity = player,
|
||||
)
|
||||
particleStorm.spawn()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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
|
||||
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import java.util.UUID
|
||||
|
||||
class ClientPlayerTeamData {
|
||||
var multiBattleTeamMembers = mutableListOf<ClientMultiBattleTeamMember>()
|
||||
}
|
||||
|
||||
class ClientMultiBattleTeamMember(val uuid: UUID, val name: MutableComponent) {
|
||||
}
|
||||
@@ -64,6 +64,7 @@ object CobblemonClient {
|
||||
/** If true then we won't bother them anymore about choosing a starter even if it's a thing they can do. */
|
||||
var checkedStarterScreen = false
|
||||
var requests = ClientPlayerActionRequests()
|
||||
var teamData = ClientPlayerTeamData()
|
||||
var posableModelRepositories = listOf(
|
||||
PokemonModelRepository,
|
||||
PokeBallModelRepository,
|
||||
@@ -80,6 +81,7 @@ object CobblemonClient {
|
||||
fun onLogin() {
|
||||
clientPlayerData = ClientPlayerData()
|
||||
requests = ClientPlayerActionRequests()
|
||||
teamData = ClientPlayerTeamData()
|
||||
storage.onLogin()
|
||||
CobblemonDataProvider.canReload = false
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
package com.cobblemon.mod.common.client.battle
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
|
||||
import com.cobblemon.mod.common.battles.BattleFormat
|
||||
import com.cobblemon.mod.common.battles.ForcePassActionResponse
|
||||
import com.cobblemon.mod.common.battles.PassActionResponse
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
|
||||
import java.util.UUID
|
||||
|
||||
@@ -31,6 +34,21 @@ class ClientBattle(
|
||||
var mustChoose = false
|
||||
|
||||
fun getFirstUnansweredRequest() = pendingActionRequests.firstOrNull { it.response == null }
|
||||
|
||||
fun getLastAnsweredRequest() = pendingActionRequests.lastOrNull { it.response != null && it.response != PassActionResponse && it.response !is ForcePassActionResponse }
|
||||
|
||||
fun cancelLastAnsweredRequest() {
|
||||
var index = pendingActionRequests.indexOfLast { it.response != null && it.response != PassActionResponse && it.response !is ForcePassActionResponse }
|
||||
if (index != -1) {
|
||||
while(index < pendingActionRequests.size) {
|
||||
val request = pendingActionRequests[index]
|
||||
if (request.response != PassActionResponse && request.response !is ForcePassActionResponse)
|
||||
request.response = null
|
||||
index++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkForFinishedChoosing() {
|
||||
if (getFirstUnansweredRequest() == null) {
|
||||
CobblemonNetwork.sendToServer(
|
||||
@@ -55,4 +73,15 @@ class ClientBattle(
|
||||
fun getParticipatingActor(uuid: UUID): ClientBattleActor? {
|
||||
return sides.flatMap { it.actors }.find { it.uuid == uuid }
|
||||
}
|
||||
|
||||
/** Whether or not there is one side with at least one player, and the other only has wild Pokémon. */
|
||||
val isPvW: Boolean
|
||||
get() {
|
||||
val playerSide = sides.find { it.actors.any { it.type == ActorType.PLAYER } } ?: return false
|
||||
if (playerSide.actors.any { it.type != ActorType.PLAYER }) {
|
||||
return false
|
||||
}
|
||||
val otherSide = sides.find { it != playerSide }!!
|
||||
return otherSide.actors.all { it.type == ActorType.WILD }
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -8,11 +8,11 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.battle
|
||||
|
||||
import com.cobblemon.mod.common.battles.BattleFormat
|
||||
import java.util.UUID
|
||||
|
||||
class ClientBattleChallenge(
|
||||
val challengeId: UUID,
|
||||
val challengerId: UUID
|
||||
) {
|
||||
// Some data about the rules that were proposed, probably.
|
||||
}
|
||||
val challengerIds: List<UUID>,
|
||||
val battleFormat: BattleFormat? = null
|
||||
)
|
||||
+2
-5
@@ -8,10 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.battle
|
||||
|
||||
import com.cobblemon.mod.common.battles.ShowdownActionRequest
|
||||
import com.cobblemon.mod.common.battles.ShowdownActionResponse
|
||||
import com.cobblemon.mod.common.battles.ShowdownMoveset
|
||||
import com.cobblemon.mod.common.battles.ShowdownSide
|
||||
import com.cobblemon.mod.common.battles.*
|
||||
|
||||
class SingleActionRequest(
|
||||
val activePokemon: ActiveClientBattlePokemon,
|
||||
val side: ShowdownSide?,
|
||||
@@ -27,7 +25,6 @@ class SingleActionRequest(
|
||||
SingleActionRequest(targetable, request.side, moveSet, forceSwitch, !request.noCancel)
|
||||
}
|
||||
)
|
||||
|
||||
return singleActionRequests
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.battle
|
||||
|
||||
import com.cobblemon.mod.common.battles.PassActionResponse
|
||||
import com.cobblemon.mod.common.battles.ShowdownActionResponse
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleActor
|
||||
@@ -72,7 +73,7 @@ class BattleGUI : Screen(battleLang("gui.title")) {
|
||||
}
|
||||
}
|
||||
|
||||
fun selectAction(request: SingleActionRequest, response: ShowdownActionResponse) {
|
||||
fun selectAction(request: SingleActionRequest, response: ShowdownActionResponse?) {
|
||||
val battle = CobblemonClient.battle ?: return
|
||||
if (request.response == null) {
|
||||
request.response = response
|
||||
@@ -113,7 +114,7 @@ class BattleGUI : Screen(battleLang("gui.title")) {
|
||||
}
|
||||
|
||||
if (battle.spectating) {
|
||||
specBackButton.render(context.pose(), mouseX, mouseY, delta)
|
||||
specBackButton.render(context, mouseX, mouseY, delta)
|
||||
}
|
||||
|
||||
val currentSelection = getCurrentActionSelection()
|
||||
@@ -146,11 +147,21 @@ class BattleGUI : Screen(battleLang("gui.title")) {
|
||||
|
||||
}
|
||||
|
||||
fun deriveRootActionSelection(actor: ClientBattleActor, request: SingleActionRequest): BattleActionSelection {
|
||||
fun deriveRootActionSelection(actor: ClientBattleActor, request: SingleActionRequest): BattleActionSelection? {
|
||||
|
||||
|
||||
return if (request.forceSwitch) {
|
||||
BattleSwitchPokemonSelection(this, request)
|
||||
} else {
|
||||
BattleGeneralActionSelection(this, request)
|
||||
// Known quirk of Showdown. It'll ask for actions on fainted slots
|
||||
// Also during a forced switch in doubles/triples it'll ask for actions on non-switching slots
|
||||
val pokemon = request.side?.pokemon?.firstOrNull { it.uuid == request.activePokemon.battlePokemon?.uuid }
|
||||
if(pokemon == null || pokemon.condition.contains("fnt") || request.moveSet == null) {
|
||||
this.selectAction(request, PassActionResponse)
|
||||
null
|
||||
} else {
|
||||
BattleGeneralActionSelection(this, request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+163
-75
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.client.gui.battle.widgets.BattleMessagePane
|
||||
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.client.render.drawScaledTextJustifiedRight
|
||||
import com.cobblemon.mod.common.client.render.getDepletableRedGreen
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.PokeBallModelRepository
|
||||
@@ -54,6 +55,8 @@ import net.minecraft.client.gui.Gui
|
||||
import net.minecraft.client.gui.screens.ChatScreen
|
||||
import net.minecraft.client.renderer.LightTexture
|
||||
import org.joml.Vector3f
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.sin
|
||||
|
||||
class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
|
||||
companion object {
|
||||
@@ -62,24 +65,42 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
|
||||
const val OPACITY_CHANGE_PER_SECOND = 0.1
|
||||
const val HORIZONTAL_INSET = 12
|
||||
const val VERTICAL_INSET = 10
|
||||
const val HORIZONTAL_SPACING = 15
|
||||
const val HORIZONTAL_SPACING = 4
|
||||
const val VERTICAL_SPACING = 40
|
||||
const val COMPACT_VERTICAL_SPACING = 30
|
||||
const val INFO_OFFSET_X = 7
|
||||
const val COMPACT_INFO_OFFSET_X = 6
|
||||
|
||||
const val TILE_WIDTH = 140
|
||||
const val COMPACT_TILE_WIDTH = 128
|
||||
|
||||
const val TILE_WIDTH = 131
|
||||
const val TILE_HEIGHT = 40
|
||||
const val PORTRAIT_DIAMETER = 28
|
||||
const val PORTRAIT_OFFSET_X = 5
|
||||
const val PORTRAIT_OFFSET_Y = 8
|
||||
const val COMPACT_TILE_HEIGHT = 28
|
||||
const val COMPACT_TILE_TEXTURE_HEIGHT = 56
|
||||
|
||||
const val PORTRAIT_DIAMETER = 28
|
||||
const val COMPACT_PORTRAIT_DIAMETER = 19
|
||||
const val PORTRAIT_OFFSET_X = 5
|
||||
const val COMPACT_PORTRAIT_OFFSET_X = 4
|
||||
|
||||
const val PORTRAIT_OFFSET_Y = 8
|
||||
const val COMPACT_PORTRAIT_OFFSET_Y = 7
|
||||
|
||||
const val ROLE_CYCLE_SECONDS = 5.0
|
||||
const val TARGET_SELECT_CYCLE_SECONDS = 4.0
|
||||
|
||||
private val PROMPT_TEXT_OPACITY_CURVE = sineFunction(period = 4F, verticalShift = 0.5F, amplitude = 0.5F)
|
||||
|
||||
val battleInfoBase = cobblemonResource("textures/gui/battle/battle_info_base.png")
|
||||
val battleInfoBaseFlipped = cobblemonResource("textures/gui/battle/battle_info_base_flipped.png")
|
||||
val battleInfoBaseCompact = cobblemonResource("textures/gui/battle/battle_info_base_condensed.png")
|
||||
val battleInfoBaseFlippedCompact = cobblemonResource("textures/gui/battle/battle_info_base_flipped_condensed.png")
|
||||
val battleInfoRole = cobblemonResource("textures/gui/battle/battle_info_role.png")
|
||||
val battleInfoRoleFlipped = cobblemonResource("textures/gui/battle/battle_info_role_flipped.png")
|
||||
val battleInfoUnderlay = cobblemonResource("textures/gui/battle/battle_info_underlay.png")
|
||||
val battleArrowLeft = cobblemonResource("textures/gui/battle/arrow_pointer_left.png")
|
||||
val battleArrowRight = cobblemonResource("textures/gui/battle/arrow_pointer_right.png")
|
||||
|
||||
}
|
||||
|
||||
var opacity = MIN_OPACITY
|
||||
@@ -89,7 +110,7 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
|
||||
|
||||
var lastKnownBattle: UUID? = null
|
||||
lateinit var messagePane: BattleMessagePane
|
||||
|
||||
var hidePortraits = false
|
||||
override val schedulingTracker = SchedulingTracker()
|
||||
|
||||
override fun render(context: GuiGraphics, tickCounter: DeltaTracker) {
|
||||
@@ -105,13 +126,19 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
|
||||
} else {
|
||||
min(opacity + tickDelta * OPACITY_CHANGE_PER_SECOND, MAX_OPACITY)
|
||||
}
|
||||
val currentScreen = Minecraft.getInstance().screen
|
||||
if(!hidePortraits) {
|
||||
val playerUUID = Minecraft.getInstance().player?.uuid ?: return
|
||||
val side1 = if (battle.side1.actors.any { it.uuid == playerUUID }) battle.side1 else battle.side2
|
||||
val side2 = if (side1 == battle.side1) battle.side2 else battle.side1
|
||||
|
||||
val playerUUID = Minecraft.getInstance().player?.uuid ?: return
|
||||
val side1 = if (battle.side1.actors.any { it.uuid == playerUUID }) battle.side1 else battle.side2
|
||||
val side2 = if (side1 == battle.side1) battle.side2 else battle.side1
|
||||
// Command highlight for Double and Triple Battles
|
||||
val isBattleGUIActive = currentScreen is BattleGUI && currentScreen.getCurrentActionSelection() != null
|
||||
val selectedPNX = if((battle.battleFormat.battleType.slotsPerActor > 1 || battle.battleFormat.battleType.actorsPerSide > 1) && isBattleGUIActive) battle.getFirstUnansweredRequest()?.activePokemon?.getPNX() else null
|
||||
|
||||
side1.activeClientBattlePokemon.forEachIndexed { index, activeClientBattlePokemon -> drawTile(context, tickDelta, activeClientBattlePokemon, true, index) }
|
||||
side2.activeClientBattlePokemon.forEachIndexed { index, activeClientBattlePokemon -> drawTile(context, tickDelta, activeClientBattlePokemon, false, index) }
|
||||
side1.activeClientBattlePokemon.forEachIndexed { index, activeClientBattlePokemon -> drawTile(context, tickDelta, activeClientBattlePokemon, true, index, activeClientBattlePokemon.getPNX() == selectedPNX, false, battle.battleFormat.battleType.pokemonPerSide > 1) }
|
||||
side2.activeClientBattlePokemon.forEachIndexed { index, activeClientBattlePokemon -> drawTile(context, tickDelta, activeClientBattlePokemon, false, side2.activeClientBattlePokemon.count() - index - 1, false, false, battle.battleFormat.battleType.pokemonPerSide > 1) }
|
||||
}
|
||||
|
||||
if (Minecraft.getInstance().screen !is BattleGUI && battle.mustChoose) {
|
||||
val textOpacity = PROMPT_TEXT_OPACITY_CURVE(passedSeconds)
|
||||
@@ -125,8 +152,6 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
|
||||
)
|
||||
}
|
||||
|
||||
val currentScreen = Minecraft.getInstance().screen
|
||||
|
||||
if (currentScreen == null || currentScreen is ChatScreen) {
|
||||
if (lastKnownBattle != battle.battleId) {
|
||||
lastKnownBattle = battle.battleId
|
||||
@@ -137,34 +162,36 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
|
||||
}
|
||||
}
|
||||
|
||||
fun drawTile(context: GuiGraphics, tickDelta: Float, activeBattlePokemon: ActiveClientBattlePokemon, left: Boolean, rank: Int) {
|
||||
fun drawTile(context: GuiGraphics, tickDelta: Float, activeBattlePokemon: ActiveClientBattlePokemon, left: Boolean, rank: Int, hasCommand: Boolean = false, isHovered: Boolean = false, isCompact: Boolean = false) {
|
||||
val mc = Minecraft.getInstance()
|
||||
|
||||
val battlePokemon = activeBattlePokemon.battlePokemon ?: return
|
||||
// First render the underlay
|
||||
var x = HORIZONTAL_INSET + rank * HORIZONTAL_SPACING.toFloat()
|
||||
val y = VERTICAL_INSET + rank * VERTICAL_SPACING
|
||||
val battle = CobblemonClient.battle ?: return
|
||||
val slotCount = battle.battleFormat.battleType.slotsPerActor
|
||||
val playerNumberOffset = (activeBattlePokemon.getActorShowdownId()[1].digitToInt() - 1) / 2 * 10
|
||||
|
||||
var x = HORIZONTAL_INSET + (slotCount - rank - 1) * HORIZONTAL_SPACING.toFloat()
|
||||
val y = VERTICAL_INSET + rank * (if (isCompact) COMPACT_VERTICAL_SPACING else VERTICAL_SPACING) + (if (left) playerNumberOffset else (battle.battleFormat.battleType.actorsPerSide - 1) * 10 - playerNumberOffset)
|
||||
if (!left) {
|
||||
x = mc.window.guiScaledWidth - x - TILE_WIDTH
|
||||
x = mc.window.guiScaledWidth - x - if(isCompact) COMPACT_TILE_WIDTH else TILE_WIDTH
|
||||
}
|
||||
val invisibleX = if (left) {
|
||||
-TILE_WIDTH - 1F
|
||||
-(if(isCompact) COMPACT_TILE_WIDTH else TILE_WIDTH) - 1F
|
||||
} else {
|
||||
mc.window.guiScaledWidth.toFloat()
|
||||
}
|
||||
|
||||
activeBattlePokemon.invisibleX = invisibleX
|
||||
activeBattlePokemon.xDisplacement = x
|
||||
activeBattlePokemon.animate(tickDelta)
|
||||
x = activeBattlePokemon.xDisplacement
|
||||
|
||||
|
||||
val hue = activeBattlePokemon.getHue()
|
||||
val r = ((hue shr 16) and 0b11111111) / 255F
|
||||
val g = ((hue shr 8) and 0b11111111) / 255F
|
||||
val b = (hue and 0b11111111) / 255F
|
||||
|
||||
val truePokemon = activeBattlePokemon.actor.pokemon.find { it.uuid == activeBattlePokemon.battlePokemon?.uuid }
|
||||
|
||||
drawBattleTile(
|
||||
context = context,
|
||||
x = x,
|
||||
@@ -183,7 +210,11 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
|
||||
ballState = activeBattlePokemon.ballCapturing,
|
||||
maxHealth = battlePokemon.maxHp.toInt(),
|
||||
health = battlePokemon.hpValue,
|
||||
isFlatHealth = battlePokemon.isHpFlat
|
||||
isFlatHealth = battlePokemon.isHpFlat,
|
||||
isSelected = hasCommand,
|
||||
isHovered = isHovered,
|
||||
isCompact = isCompact,
|
||||
actorDisplayName = if (!battle.isPvW && ((left && activeBattlePokemon.actor.activePokemon.first() == activeBattlePokemon) || (!left && activeBattlePokemon.actor.activePokemon.last() == activeBattlePokemon))) activeBattlePokemon.actor.displayName else null
|
||||
)
|
||||
}
|
||||
|
||||
@@ -205,31 +236,65 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
|
||||
ballState: ClientBallDisplay? = null,
|
||||
maxHealth: Int,
|
||||
health: Float,
|
||||
isFlatHealth: Boolean
|
||||
isFlatHealth: Boolean,
|
||||
isSelected: Boolean = false,
|
||||
isHovered: Boolean = false,
|
||||
isCompact: Boolean = false,
|
||||
actorDisplayName: MutableComponent? = null
|
||||
) {
|
||||
val portraitStartX = x + if (!reversed) PORTRAIT_OFFSET_X else { TILE_WIDTH - PORTRAIT_DIAMETER - PORTRAIT_OFFSET_X }
|
||||
val tileWidth = if (isCompact) COMPACT_TILE_WIDTH else TILE_WIDTH
|
||||
val portraitOffsetX = if (isCompact) COMPACT_PORTRAIT_OFFSET_X else PORTRAIT_OFFSET_X
|
||||
val portraitOffsetY = if (isCompact) COMPACT_PORTRAIT_OFFSET_Y else PORTRAIT_OFFSET_Y
|
||||
val portraitDiameter = if (isCompact) COMPACT_PORTRAIT_DIAMETER else PORTRAIT_DIAMETER
|
||||
val infoOffsetX = if (isCompact) COMPACT_INFO_OFFSET_X else INFO_OFFSET_X
|
||||
val portraitStartX = x + if (!reversed) portraitOffsetX else { tileWidth - portraitDiameter - portraitOffsetX }
|
||||
val matrixStack = context.pose()
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = battleInfoUnderlay,
|
||||
y = y + PORTRAIT_OFFSET_Y,
|
||||
y = y + portraitOffsetY,
|
||||
x = portraitStartX,
|
||||
height = PORTRAIT_DIAMETER,
|
||||
width = PORTRAIT_DIAMETER,
|
||||
height = portraitDiameter,
|
||||
width = portraitDiameter,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
if (status != null) {
|
||||
val statusWidth = 37
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = cobblemonResource("textures/gui/battle/battle_status_" + status.showdownName + ".png"),
|
||||
x = x + if (isCompact) (if (reversed) 69 else 23 ) else (if (reversed) 56 else 38),
|
||||
y = y + if (isCompact) 21 else 28,
|
||||
height = 7,
|
||||
width = statusWidth,
|
||||
uOffset = if (reversed) 0 else statusWidth,
|
||||
textureWidth = statusWidth * 2,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = lang("ui.status." + status.showdownName).bold(),
|
||||
x = x + if(isCompact) (if (reversed) 90 else 28) else (if (reversed) 78 else 42),
|
||||
y = y + if (isCompact) 22 else 27,
|
||||
scale = 0.75F,
|
||||
opacity = opacity
|
||||
)
|
||||
}
|
||||
|
||||
// Second render the Pokémon through the scissors
|
||||
context.enableScissor(
|
||||
portraitStartX.toInt(),
|
||||
(y + PORTRAIT_OFFSET_Y).toInt(),
|
||||
(portraitStartX + PORTRAIT_DIAMETER).toInt(),
|
||||
(y + PORTRAIT_DIAMETER + PORTRAIT_OFFSET_Y).toInt(),
|
||||
(y + portraitOffsetY).toInt(),
|
||||
(portraitStartX + portraitDiameter).toInt(),
|
||||
(y + portraitDiameter + portraitOffsetY).toInt(),
|
||||
)
|
||||
matrixStack.pushPose()
|
||||
matrixStack.translate(
|
||||
portraitStartX + PORTRAIT_DIAMETER / 2.0,
|
||||
y.toDouble() + PORTRAIT_OFFSET_Y - 5.0 ,
|
||||
portraitStartX + portraitDiameter / 2.0,
|
||||
y.toDouble() + portraitOffsetY - if(isCompact) 15.0 else 5.0,
|
||||
0.0
|
||||
)
|
||||
if (ballState != null && ballState.stateEmitter.get() == EmptyPokeBallEntity.CaptureState.SHAKE) {
|
||||
@@ -243,7 +308,7 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
|
||||
identifier = species.resourceIdentifier,
|
||||
aspects = aspects,
|
||||
matrixStack = matrixStack,
|
||||
scale = 18F * (ballState?.scale ?: 1F),
|
||||
scale = 18F * (ballState?.scale ?: 1F) * if (isCompact) 0.65F else 1.0f,
|
||||
contextScale = species.getForm(aspects).baseScale,
|
||||
repository = PokemonModelRepository,
|
||||
reversed = reversed,
|
||||
@@ -257,62 +322,43 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
|
||||
// Third render the tile
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = if (reversed) battleInfoBaseFlipped else battleInfoBase,
|
||||
texture = if (isCompact) (if (reversed) battleInfoBaseFlippedCompact else battleInfoBaseCompact) else (if (reversed) battleInfoBaseFlipped else battleInfoBase),
|
||||
x = x,
|
||||
y = y,
|
||||
height = TILE_HEIGHT,
|
||||
width = TILE_WIDTH,
|
||||
alpha = opacity
|
||||
height = if (isCompact) COMPACT_TILE_HEIGHT else TILE_HEIGHT,
|
||||
width = tileWidth,
|
||||
textureHeight = if (isCompact) COMPACT_TILE_TEXTURE_HEIGHT else TILE_HEIGHT,
|
||||
vOffset = if (isHovered && isCompact) COMPACT_TILE_HEIGHT else 0,
|
||||
alpha = opacity,
|
||||
)
|
||||
|
||||
if (colour != null) {
|
||||
val stage = floor((passedSeconds / ROLE_CYCLE_SECONDS % 1) * 5)
|
||||
if (colour != null && (!isSelected || stage != 4.0)) {
|
||||
val (r, g, b) = colour
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = if (reversed) battleInfoRoleFlipped else battleInfoRole,
|
||||
x = x + if (reversed) 93 else 11,
|
||||
x = x + if (isCompact) (if (reversed) 93 else 8) else (if (reversed) 102 else 11),
|
||||
y = y + 1,
|
||||
height = 3,
|
||||
textureHeight = 12,
|
||||
width = 27,
|
||||
alpha = opacity,
|
||||
vOffset = if (isSelected) stage * 3 else 9,
|
||||
red = r,
|
||||
green = g,
|
||||
blue = b
|
||||
)
|
||||
}
|
||||
|
||||
if (status != null) {
|
||||
val statusWidth = 37
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = cobblemonResource("textures/gui/battle/battle_status_" + status.showdownName + ".png"),
|
||||
x = x + if (reversed) 56 else 38,
|
||||
y = y + 28,
|
||||
height = 7,
|
||||
width = statusWidth,
|
||||
uOffset = if (reversed) 0 else statusWidth,
|
||||
textureWidth = statusWidth * 2,
|
||||
alpha = opacity
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = lang("ui.status." + status.showdownName).bold(),
|
||||
x = x + if (reversed) 78 else 42,
|
||||
y = y + 27,
|
||||
opacity = opacity
|
||||
)
|
||||
}
|
||||
|
||||
// Draw labels
|
||||
val infoBoxX = x + if (!reversed) PORTRAIT_DIAMETER + PORTRAIT_OFFSET_X + INFO_OFFSET_X else INFO_OFFSET_X
|
||||
val infoBoxX = x + if (!reversed) portraitDiameter + portraitOffsetX + infoOffsetX else infoOffsetX
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = displayName.bold(),
|
||||
x = infoBoxX,
|
||||
y = y + 7,
|
||||
y = y + if (isCompact) 5 else 7,
|
||||
opacity = opacity,
|
||||
shadow = true
|
||||
)
|
||||
@@ -324,8 +370,8 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = textSymbol,
|
||||
x = infoBoxX + 53,
|
||||
y = y + 7,
|
||||
x = infoBoxX + 63,
|
||||
y = y + if (isCompact) 5 else 7,
|
||||
colour = if (isMale) 0x32CBFF else 0xFC5454,
|
||||
opacity = opacity,
|
||||
shadow = true
|
||||
@@ -336,8 +382,8 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = lang("ui.lv").bold(),
|
||||
x = infoBoxX + 59,
|
||||
y = y + 7,
|
||||
x = infoBoxX + 69,
|
||||
y = y + if (isCompact) 5 else 7,
|
||||
opacity = opacity,
|
||||
shadow = true
|
||||
)
|
||||
@@ -346,21 +392,22 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = level.toString().text().bold(),
|
||||
x = infoBoxX + 72,
|
||||
y = y + 7,
|
||||
x = infoBoxX + 82,
|
||||
y = y + if (isCompact) 5 else 7,
|
||||
opacity = opacity,
|
||||
shadow = true
|
||||
)
|
||||
val hpRatio = if (isFlatHealth) health / maxHealth else health
|
||||
val (healthRed, healthGreen) = getDepletableRedGreen(hpRatio)
|
||||
val fullWidth = 83
|
||||
val fullWidth = 97
|
||||
val barWidth = hpRatio * fullWidth
|
||||
val barX = if (!reversed) infoBoxX - 2 else infoBoxX + 3 + (fullWidth - barWidth)
|
||||
val barOffsetX = if (isCompact) (if (reversed) 1 else 3) else 2
|
||||
val barX = if (!reversed) infoBoxX - barOffsetX else infoBoxX - barOffsetX + (fullWidth - barWidth)
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = CobblemonResources.WHITE,
|
||||
x = barX,
|
||||
y = y + 22,
|
||||
y = y + if (isCompact) 16 else 22,
|
||||
height = 4,
|
||||
width = barWidth,
|
||||
red = healthRed * 0.8F,
|
||||
@@ -378,12 +425,53 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
|
||||
context = context,
|
||||
text = text,
|
||||
x = infoBoxX + (if (!reversed) 39.5 else 44.5),
|
||||
y = y + 22,
|
||||
y = y + if(isCompact) 16 else 22,
|
||||
scale = 0.5F,
|
||||
opacity = opacity,
|
||||
centered = true,
|
||||
shadow = true
|
||||
)
|
||||
|
||||
// Target Selection Arrows
|
||||
if (isCompact && isHovered) {
|
||||
val offset = sin((passedSeconds / TARGET_SELECT_CYCLE_SECONDS % 1 * 2 * Math.PI)) * 1.5 + 1.5
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = if (reversed) battleArrowRight else battleArrowLeft,
|
||||
x = (x + if (reversed) - (6 + offset) else (COMPACT_TILE_WIDTH + 1 + offset)) * 2,
|
||||
y = (y + 6 ) * 2,
|
||||
scale = 0.5F,
|
||||
height = 17,
|
||||
width = 10,
|
||||
)
|
||||
}
|
||||
|
||||
// Actor Display Name
|
||||
if(actorDisplayName != null) {
|
||||
if (!reversed) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = actorDisplayName,
|
||||
x = x + 9,
|
||||
y = y - 5,
|
||||
scale = 0.5F,
|
||||
shadow = true,
|
||||
opacity = opacity,
|
||||
)
|
||||
} else {
|
||||
drawScaledTextJustifiedRight(
|
||||
context = context,
|
||||
text = actorDisplayName,
|
||||
x = x + tileWidth - 9,
|
||||
y = y - 5,
|
||||
scale = 0.5F,
|
||||
shadow = true,
|
||||
opacity = opacity
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun drawPokeBall(
|
||||
|
||||
+4
-2
@@ -11,6 +11,8 @@ package com.cobblemon.mod.common.client.gui.battle.subscreen
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
|
||||
class BattleBackButton(val x: Float, val y: Float) {
|
||||
companion object {
|
||||
const val WIDTH = 58
|
||||
@@ -18,9 +20,9 @@ class BattleBackButton(val x: Float, val y: Float) {
|
||||
const val SCALE = 0.5F
|
||||
}
|
||||
|
||||
fun render(matrices: PoseStack, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
matrixStack = context.pose(),
|
||||
texture = cobblemonResource("textures/gui/battle/battle_back.png"),
|
||||
x = x * 2,
|
||||
y = y * 2,
|
||||
|
||||
+12
@@ -36,6 +36,9 @@ class BattleGeneralActionSelection(
|
||||
(BattleOptionTile.OPTION_HEIGHT + 3 * BattleGUI.OPTION_VERTICAL_SPACING).toInt(),
|
||||
battleLang("choose_action")
|
||||
) {
|
||||
val backButton = BattleBackButton(BattleGUI.OPTION_ROOT_X - 3F, Minecraft.getInstance().window.guiScaledHeight - 22F)
|
||||
val lastAnwseredRequest = CobblemonClient.battle?.getLastAnsweredRequest()
|
||||
|
||||
val tiles = mutableListOf<BattleOptionTile>()
|
||||
init {
|
||||
var rank = 0
|
||||
@@ -91,12 +94,21 @@ class BattleGeneralActionSelection(
|
||||
}
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
if(lastAnwseredRequest != null) {
|
||||
backButton.render(context, mouseX, mouseY, delta)
|
||||
}
|
||||
for (tile in tiles) {
|
||||
tile.render(context, mouseX, mouseY, delta)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
if (lastAnwseredRequest != null && backButton.isHovered(mouseX, mouseY)) {
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
CobblemonClient.battle?.cancelLastAnsweredRequest()
|
||||
battleGUI.selectAction(request, null)
|
||||
battleGUI.changeActionSelection(null)
|
||||
}
|
||||
return tiles.any { it.mouseClicked(mouseX, mouseY, button) }
|
||||
}
|
||||
|
||||
|
||||
+17
-6
@@ -15,9 +15,7 @@ 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.InBattleMove
|
||||
import com.cobblemon.mod.common.battles.MoveActionResponse
|
||||
import com.cobblemon.mod.common.battles.Targetable
|
||||
import com.cobblemon.mod.common.battles.*
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.battle.SingleActionRequest
|
||||
import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
|
||||
@@ -69,13 +67,15 @@ class BattleMoveSelection(
|
||||
}
|
||||
var moveTiles = baseTiles
|
||||
|
||||
val backButton = BattleBackButton(x - 3F, Minecraft.getInstance().window.guiScaledHeight - 22F)
|
||||
val backButton = BattleBackButton(x - 11F, Minecraft.getInstance().window.guiScaledHeight - 22F)
|
||||
val gimmickButtons = moveSet.getGimmicks().mapIndexed { index, gimmick ->
|
||||
val initOff = BattleBackButton.WIDTH * 0.65F
|
||||
val xOff = initOff + BattleGimmickButton.SPACING * index
|
||||
BattleGimmickButton.create(gimmick, this, backButton.x + xOff, backButton.y)
|
||||
}
|
||||
|
||||
val shiftButton = BattleShiftButton(x + 40F, Minecraft.getInstance().window.guiScaledHeight - 22F)
|
||||
|
||||
open class MoveTile(
|
||||
val moveSelection: BattleMoveSelection,
|
||||
val move: InBattleMove,
|
||||
@@ -188,17 +188,25 @@ class BattleMoveSelection(
|
||||
moveTiles.forEach {
|
||||
it.render(context, mouseX, mouseY, delta)
|
||||
}
|
||||
backButton.render(context.pose(), mouseX, mouseY, delta)
|
||||
backButton.render(context, mouseX, mouseY, delta)
|
||||
gimmickButtons.forEach {
|
||||
it.render(context.pose(), mouseX, mouseY, delta)
|
||||
}
|
||||
if(this.request.activePokemon.getFormat().battleType.slotsPerActor == 3 && (request.activePokemon.getPNX()[2] == 'a' || request.activePokemon.getPNX()[2] == 'c')) {
|
||||
shiftButton.render(context, mouseX, mouseY, delta)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
val move = moveTiles.find { it.isHovered(mouseX, mouseY) }
|
||||
val gimmick = gimmickButtons.find { it.isHovered(mouseX, mouseY) }
|
||||
if (move != null) {
|
||||
move.onClick()
|
||||
if(this.request.activePokemon.getFormat().battleType.pokemonPerSide == 1) {
|
||||
move.onClick()
|
||||
} else {
|
||||
battleGUI.changeActionSelection(BattleTargetSelectionPortrait(battleGUI, request, move.move))
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
}
|
||||
return true
|
||||
} else if (backButton.isHovered(mouseX, mouseY)) {
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
@@ -206,6 +214,9 @@ class BattleMoveSelection(
|
||||
} else if (gimmick != null) {
|
||||
gimmickButtons.filter { it != gimmick }.forEach { it.toggled = false }
|
||||
moveTiles = if (gimmick.toggle()) gimmick.tiles else baseTiles
|
||||
} else if(shiftButton.isHovered(mouseX,mouseY)) {
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
battleGUI.selectAction(request, ShiftActionResponse())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.api.gui.blitk
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
|
||||
class BattleShiftButton(val x: Float, val y: Float) {
|
||||
companion object {
|
||||
const val WIDTH = 58
|
||||
const val HEIGHT = 34
|
||||
const val SCALE = 0.5F
|
||||
}
|
||||
|
||||
fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = cobblemonResource("textures/gui/common/back_button.png"),
|
||||
x = x * 2,
|
||||
y = y * 2,
|
||||
height = HEIGHT,
|
||||
width = WIDTH,
|
||||
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
|
||||
textureHeight = HEIGHT * 2,
|
||||
scale = SCALE
|
||||
)
|
||||
}
|
||||
|
||||
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in (x..(x + (WIDTH * SCALE))) && mouseY.toFloat() in (y..(y + (HEIGHT * SCALE)))
|
||||
}
|
||||
+12
-6
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common.client.gui.battle.subscreen
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.battles.PassActionResponse
|
||||
import com.cobblemon.mod.common.battles.ShowdownPokemon
|
||||
import com.cobblemon.mod.common.battles.SwitchActionResponse
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
@@ -33,7 +34,7 @@ class BattleSwitchPokemonSelection(
|
||||
battleGUI,
|
||||
request,
|
||||
x = 12,
|
||||
y = ceil((Minecraft.getInstance().window.guiScaledHeight / 2) - (((SWITCH_TILE_HEIGHT * 3) + (SWITCH_TILE_VERTICAL_SPACING * 2)) / 2)),
|
||||
y = ceil(SWITCH_TILE_HEIGHT*(CobblemonClient.battle?.battleFormat?.battleType?.pokemonPerSide ?: 1) + (SWITCH_TILE_VERTICAL_SPACING * 3)),
|
||||
width = 250,
|
||||
height = 100,
|
||||
battleLang("switch_pokemon")
|
||||
@@ -101,15 +102,18 @@ class BattleSwitchPokemonSelection(
|
||||
?.let { showdownPokemon to it }
|
||||
}
|
||||
.filter {
|
||||
if (request.side.pokemon[0].reviving) {
|
||||
"fnt" in it.first.condition
|
||||
if (request.side.pokemon.any { revivingPokemon -> revivingPokemon.uuid == request.activePokemon.battlePokemon?.uuid && revivingPokemon.reviving }) {
|
||||
"fnt" in it.first.condition//Fainted pokemon in doubles can be on the field
|
||||
} else {
|
||||
"fnt" !in it.first.condition
|
||||
"fnt" !in it.first.condition && it.second.uuid !in battleGUI.actor!!.activePokemon.map { it.battlePokemon?.uuid }
|
||||
}
|
||||
}
|
||||
.filter { it.second.uuid !in battleGUI.actor!!.activePokemon.map { it.battlePokemon?.uuid } }
|
||||
.filter { it.second.uuid !in switchingInPokemon }
|
||||
|
||||
if(showdownPokemonToPokemon.isEmpty() && request.forceSwitch) {
|
||||
// Occurs after a multi-knock out and the player doesn't have enough pokemon to fill every vacant slot
|
||||
battleGUI.selectAction(request, PassActionResponse)
|
||||
}
|
||||
showdownPokemonToPokemon.forEachIndexed { index, (showdownPokemon, pokemon) ->
|
||||
val row = index / 2
|
||||
val column = index % 2
|
||||
@@ -126,7 +130,9 @@ class BattleSwitchPokemonSelection(
|
||||
return
|
||||
}
|
||||
tiles.forEach { it.render(context, mouseX.toDouble(), mouseY.toDouble(), delta) }
|
||||
backButton.render(context.pose(), mouseX, mouseY, delta)
|
||||
if(!request.forceSwitch) {
|
||||
backButton.render(context, mouseX, mouseY, delta)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* 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.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
import com.cobblemon.mod.common.battles.InBattleMove
|
||||
import com.cobblemon.mod.common.battles.MoveActionResponse
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.battle.ActiveClientBattlePokemon
|
||||
import com.cobblemon.mod.common.client.battle.SingleActionRequest
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
import net.minecraft.client.sounds.SoundManager
|
||||
|
||||
class BattleTargetSelection(
|
||||
battleGUI: BattleGUI,
|
||||
request: SingleActionRequest,
|
||||
val move: InBattleMove
|
||||
) : BattleActionSelection(
|
||||
battleGUI = battleGUI,
|
||||
request = request,
|
||||
x = 20,
|
||||
y = Minecraft.getInstance().window.guiScaledHeight - 84,
|
||||
width = 100,
|
||||
height = 100,
|
||||
battleLang("ui.select_move")
|
||||
) {
|
||||
companion object {
|
||||
const val TARGET_WIDTH = 92
|
||||
const val TARGET_HEIGHT = 24
|
||||
const val MOVE_VERTICAL_SPACING = 5F
|
||||
const val MOVE_HORIZONTAL_SPACING = 13F
|
||||
|
||||
val targetTexture = cobblemonResource("textures/gui/battle/battle_move.png")
|
||||
val moveOverlayTexture = cobblemonResource("textures/gui/battle/battle_move_overlay.png")
|
||||
}
|
||||
|
||||
val targets = request.activePokemon.getAllActivePokemon()
|
||||
|
||||
val backButton = BattleBackButton(x - 3F, Minecraft.getInstance().window.guiScaledHeight - 22F)
|
||||
val selectableTargetList = move.target.targetList(request.activePokemon)
|
||||
val multiTargetList = if(selectableTargetList == null) request.activePokemon.getMultiTargetList(move.target) else null
|
||||
|
||||
val baseTiles = targets.mapIndexed { index, target ->
|
||||
val isAlly = target.isAllied(request.activePokemon)
|
||||
val teamSize = request.activePokemon.getSidePokemon().count()
|
||||
val fieldPos = if(isAlly) index % teamSize else teamSize - 1 - (index % teamSize)
|
||||
val x = this.x + MOVE_HORIZONTAL_SPACING + fieldPos * TARGET_WIDTH
|
||||
val y = if (isAlly) this.y + TARGET_HEIGHT + MOVE_VERTICAL_SPACING else this.y.toFloat()
|
||||
TargetTile(this, target, x, y)
|
||||
}
|
||||
var targetTiles = baseTiles
|
||||
|
||||
|
||||
open inner class TargetTile(
|
||||
val targetSelection: BattleTargetSelection,
|
||||
val target: ActiveClientBattlePokemon,
|
||||
val x: Float,
|
||||
val y: Float,
|
||||
) {
|
||||
var moveTemplate = MoveTemplate.dummy(target.battlePokemon?.displayName.toString()) // Moves.getByNameOrDummy(target.id)
|
||||
private val responseTarget = selectableTargetList?.firstOrNull { it.getPNX() == target.getPNX() }?.getPNX()
|
||||
private val isMultiTarget = multiTargetList?.firstOrNull { it.getPNX() == target.getPNX() } != null
|
||||
open val response: MoveActionResponse get() = MoveActionResponse(targetSelection.move.id, responseTarget)
|
||||
|
||||
open val selectable: Boolean get() = isMultiTarget || (responseTarget != null)
|
||||
val hue = target.getHue()
|
||||
val rgb = if ((target.battlePokemon?.hpValue ?: 0F) > 0)
|
||||
Triple(((hue shr 16) and 0b11111111) / 255F, ((hue shr 8) and 0b11111111) / 255F, (hue and 0b11111111) / 255F)
|
||||
else Triple(0.5f, 0.5f, 0.5f)
|
||||
|
||||
fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
|
||||
val selectConditionOpacity = targetSelection.opacity * if (!selectable) 0.5F else 1F
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = targetTexture,
|
||||
x = x,
|
||||
y = y,
|
||||
width = TARGET_WIDTH,
|
||||
height = TARGET_HEIGHT,
|
||||
vOffset = if (selectable && isHovered(mouseX.toDouble(), mouseY.toDouble())) TARGET_HEIGHT else 0,
|
||||
textureHeight = TARGET_HEIGHT * 2,
|
||||
red = rgb.first,
|
||||
green = rgb.second,
|
||||
blue = rgb.third,
|
||||
alpha = selectConditionOpacity
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = moveOverlayTexture,
|
||||
x = x,
|
||||
y = y,
|
||||
width = TARGET_WIDTH,
|
||||
height = TARGET_HEIGHT,
|
||||
alpha = targetSelection.opacity
|
||||
)
|
||||
|
||||
if(target.battlePokemon?.hpValue!! > 0) {
|
||||
target.battlePokemon?.displayName?.bold()?.let {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = it,
|
||||
x = x + 17,
|
||||
y = y + 2,
|
||||
opacity = selectConditionOpacity,
|
||||
shadow = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun isHovered(mouseX: Double, mouseY: Double): Boolean {
|
||||
if(isMultiTarget) {
|
||||
return targetTiles.firstOrNull { it.selectable && mouseX >= it.x && mouseX <= it.x + TARGET_WIDTH && mouseY >= it.y && mouseY <= it.y + TARGET_HEIGHT } != null
|
||||
}
|
||||
return mouseX >= x && mouseX <= x + TARGET_WIDTH && mouseY >= y && mouseY <= y + TARGET_HEIGHT
|
||||
}
|
||||
|
||||
fun onClick() {
|
||||
if (!selectable) return
|
||||
targetSelection.playDownSound(Minecraft.getInstance().soundManager)
|
||||
targetSelection.battleGUI.selectAction(targetSelection.request, response)
|
||||
}
|
||||
}
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
targetTiles.forEach {
|
||||
it.render(context, mouseX, mouseY, delta)
|
||||
}
|
||||
backButton.render(context, mouseX, mouseY, delta)
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
val target = targetTiles.find { it.isHovered(mouseX, mouseY) }
|
||||
if (target != null) {
|
||||
target.onClick()
|
||||
return true
|
||||
} else if (backButton.isHovered(mouseX, mouseY)) {
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
battleGUI.changeActionSelection(null)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager) {
|
||||
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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.CobblemonSounds
|
||||
import com.cobblemon.mod.common.battles.InBattleMove
|
||||
import com.cobblemon.mod.common.battles.MoveActionResponse
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.battle.ActiveClientBattlePokemon
|
||||
import com.cobblemon.mod.common.client.battle.SingleActionRequest
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
import net.minecraft.client.sounds.SoundManager
|
||||
|
||||
class BattleTargetSelectionPortrait(
|
||||
battleGUI: BattleGUI,
|
||||
request: SingleActionRequest,
|
||||
val move: InBattleMove
|
||||
) : BattleActionSelection(
|
||||
battleGUI = battleGUI,
|
||||
request = request,
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = 100,
|
||||
height = 100,
|
||||
battleLang("ui.select_move")
|
||||
) {
|
||||
|
||||
val targets = request.activePokemon.getAllActivePokemon()
|
||||
|
||||
val backButton = BattleBackButton(x + 9F, Minecraft.getInstance().window.guiScaledHeight - 22F)
|
||||
val selectableTargetList = move.target.targetList(request.activePokemon)
|
||||
val multiTargetList = if(selectableTargetList == null) request.activePokemon.getMultiTargetList(move.target) else null
|
||||
val mc = Minecraft.getInstance()
|
||||
var hideBattleOverlay = true
|
||||
|
||||
val baseTiles = targets.mapIndexed { index, target ->
|
||||
val isAlly = target.isAllied(request.activePokemon)
|
||||
val teamSize = request.activePokemon.getSidePokemon().count()
|
||||
val fieldPos = if(isAlly) index % teamSize else teamSize - 1 - (index % teamSize)
|
||||
var x = BattleOverlay.HORIZONTAL_INSET + (teamSize - fieldPos - 1) * BattleOverlay.HORIZONTAL_SPACING.toFloat()
|
||||
val y = BattleOverlay.VERTICAL_INSET + fieldPos * BattleOverlay.VERTICAL_SPACING.toFloat()
|
||||
if (!isAlly) {
|
||||
x = mc.window.guiScaledWidth - x - BattleOverlay.TILE_WIDTH
|
||||
}
|
||||
TargetTile(this, target, x, y, fieldPos)
|
||||
}
|
||||
var targetTiles = baseTiles
|
||||
|
||||
|
||||
|
||||
|
||||
open inner class TargetTile(
|
||||
val targetSelection: BattleTargetSelectionPortrait,
|
||||
val target: ActiveClientBattlePokemon,
|
||||
val x: Float,
|
||||
val y: Float,
|
||||
val index: Int,
|
||||
) {
|
||||
private val responseTarget = selectableTargetList?.firstOrNull { it.getPNX() == target.getPNX() }?.getPNX()
|
||||
private val isMultiTarget = multiTargetList?.firstOrNull { it.getPNX() == target.getPNX() } != null
|
||||
open val response: MoveActionResponse get() = MoveActionResponse(targetSelection.move.id, responseTarget)
|
||||
|
||||
open val selectable: Boolean get() = isMultiTarget || (responseTarget != null)
|
||||
val hue = target.getHue()
|
||||
val rgb = if ((target.battlePokemon?.hpValue ?: 0F) > 0)
|
||||
Triple(((hue shr 16) and 0b11111111) / 255F, ((hue shr 8) and 0b11111111) / 255F, (hue and 0b11111111) / 255F)
|
||||
else Triple(0.5f, 0.5f, 0.5f)
|
||||
|
||||
fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
val activeClientBattlePokemon = target
|
||||
val selectedPNX = request.activePokemon.getPNX()
|
||||
val isAlly = target.isAllied(request.activePokemon)
|
||||
|
||||
CobblemonClient.battleOverlay.drawTile(
|
||||
context,
|
||||
delta,
|
||||
activeClientBattlePokemon,
|
||||
isAlly,
|
||||
index,
|
||||
activeClientBattlePokemon.getPNX() == selectedPNX,
|
||||
selectable && isHovered(mouseX.toDouble(), mouseY.toDouble()),
|
||||
true
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
|
||||
fun isHovered(mouseX: Double, mouseY: Double): Boolean {
|
||||
if(isMultiTarget) {
|
||||
return targetTiles.firstOrNull { it.selectable && mouseX >= it.x && mouseX <= it.x + BattleOverlay.COMPACT_TILE_WIDTH && mouseY >= it.y && mouseY <= it.y + BattleOverlay.COMPACT_TILE_HEIGHT } != null
|
||||
}
|
||||
return mouseX >= x && mouseX <= x + BattleOverlay.COMPACT_TILE_WIDTH && mouseY >= y && mouseY <= y + BattleOverlay.COMPACT_TILE_HEIGHT
|
||||
}
|
||||
|
||||
fun onClick() {
|
||||
if (!selectable) return
|
||||
targetSelection.playDownSound(Minecraft.getInstance().soundManager)
|
||||
targetSelection.battleGUI.selectAction(targetSelection.request, response)
|
||||
}
|
||||
}
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
if(!CobblemonClient.battleOverlay.hidePortraits != hideBattleOverlay) {
|
||||
CobblemonClient.battleOverlay.hidePortraits = hideBattleOverlay
|
||||
}
|
||||
targetTiles.forEach {
|
||||
it.render(context, mouseX, mouseY, delta)
|
||||
}
|
||||
|
||||
backButton.render(context, mouseX, mouseY, delta)
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
val target = targetTiles.find { it.isHovered(mouseX, mouseY) }
|
||||
if (target != null) {
|
||||
target.onClick()
|
||||
hideBattleOverlay = false
|
||||
CobblemonClient.battleOverlay.hidePortraits = hideBattleOverlay
|
||||
return true
|
||||
} else if (backButton.isHovered(mouseX, mouseY)) {
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
battleGUI.changeActionSelection(null)
|
||||
hideBattleOverlay = false
|
||||
CobblemonClient.battleOverlay.hidePortraits = hideBattleOverlay
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager) {
|
||||
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
|
||||
}
|
||||
}
|
||||
+11
-1
@@ -9,6 +9,9 @@
|
||||
package com.cobblemon.mod.common.client.gui.battle.subscreen
|
||||
|
||||
import com.cobblemon.mod.common.battles.ForfeitActionResponse
|
||||
import com.cobblemon.mod.common.battles.PassActionResponse
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattle
|
||||
import com.cobblemon.mod.common.client.battle.SingleActionRequest
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
|
||||
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleOptionTile
|
||||
@@ -38,6 +41,13 @@ class ForfeitConfirmationSelection(
|
||||
|
||||
forfeitButton = BattleOptionTile(battleGUI, x, y, BattleGUI.runResource, battleLang("ui.forfeit")) {
|
||||
battleGUI.selectAction(request, ForfeitActionResponse())
|
||||
|
||||
// Need to fill out any other pending requests
|
||||
var pendingRequest = CobblemonClient.battle?.getFirstUnansweredRequest()
|
||||
while (pendingRequest != null) {
|
||||
battleGUI.selectAction(pendingRequest, PassActionResponse)
|
||||
pendingRequest = CobblemonClient.battle?.getFirstUnansweredRequest()
|
||||
}
|
||||
playDownSound(Minecraft.getInstance().soundManager)
|
||||
}
|
||||
}
|
||||
@@ -57,7 +67,7 @@ class ForfeitConfirmationSelection(
|
||||
return
|
||||
}
|
||||
forfeitButton.render(context, mouseX, mouseY, delta)
|
||||
backButton.render(context.pose(), mouseX, mouseY, delta)
|
||||
backButton.render(context, mouseX, mouseY, delta)
|
||||
}
|
||||
|
||||
}
|
||||
+356
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.interact.battleRequest
|
||||
|
||||
import com.cobblemon.mod.common.api.gui.ColourLibrary
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
import com.cobblemon.mod.common.battles.BattleFormat
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleChallenge
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.*
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.screens.Screen
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
class BattleConfigureGUI(
|
||||
private val packet: PlayerInteractOptionsPacket,
|
||||
private val activeRequest: ClientBattleChallenge? = null,
|
||||
private val activeTeamRequest: ClientBattleChallenge? = null
|
||||
) : Screen(lang("challenge.request_battle_title")) {
|
||||
companion object {
|
||||
const val SIZE = 113
|
||||
private val backgroundResource = cobblemonResource("textures/gui/interact/request/battle_request.png")
|
||||
private val battleArrowsResource = cobblemonResource("textures/gui/interact/request/battle_request_arrows.png")
|
||||
|
||||
val battleRequestMap = mutableMapOf(
|
||||
Pair(
|
||||
PlayerInteractOptionsPacket.Options.SINGLE_BATTLE,
|
||||
BattleTypeTile(
|
||||
option = PlayerInteractOptionsPacket.Options.SINGLE_BATTLE,
|
||||
battleFormat = BattleFormat.GEN_9_SINGLES,
|
||||
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_single.png"),
|
||||
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_overlay.png"),
|
||||
title = lang("challenge.request_battle_title").bold(),
|
||||
subTitle = lang("battle.types.singles").bold(),
|
||||
onRequest = { packet, battleFormat -> sendBattleRequest(battleFormat, packet) },
|
||||
onResponse = { packet, accept -> sendBattleResponse(packet, accept) }
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
PlayerInteractOptionsPacket.Options.DOUBLE_BATTLE,
|
||||
BattleTypeTile(
|
||||
option = PlayerInteractOptionsPacket.Options.DOUBLE_BATTLE,
|
||||
battleFormat = BattleFormat.GEN_9_DOUBLES,
|
||||
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_double.png"),
|
||||
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_overlay.png"),
|
||||
title = lang("challenge.request_battle_title").bold(),
|
||||
subTitle = lang("battle.types.doubles").bold(),
|
||||
onRequest = { packet, battleFormat -> sendBattleRequest(battleFormat, packet) },
|
||||
onResponse = { packet, accept -> sendBattleResponse(packet, accept) }
|
||||
),
|
||||
),
|
||||
Pair(
|
||||
PlayerInteractOptionsPacket.Options.TRIPLE_BATTLE,
|
||||
BattleTypeTile(
|
||||
option = PlayerInteractOptionsPacket.Options.TRIPLE_BATTLE,
|
||||
battleFormat = BattleFormat.GEN_9_TRIPLES,
|
||||
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_triple.png"),
|
||||
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_overlay.png"),
|
||||
title = lang("challenge.request_battle_title").bold(),
|
||||
subTitle = lang("battle.types.triples").bold(),
|
||||
onRequest = { packet, battleFormat -> sendBattleRequest(battleFormat, packet) },
|
||||
onResponse = { packet, accept -> sendBattleResponse(packet, accept) }
|
||||
),
|
||||
),
|
||||
Pair(
|
||||
PlayerInteractOptionsPacket.Options.TEAM_REQUEST,
|
||||
BattleTypeTile(
|
||||
option = PlayerInteractOptionsPacket.Options.TEAM_REQUEST,
|
||||
battleFormat = BattleFormat.GEN_9_MULTI,
|
||||
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi.png"),
|
||||
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi_overlay_partner.png"),
|
||||
title = lang("challenge.request_team_join_title").bold(),
|
||||
subTitle = lang("battle.types.multi").bold(),
|
||||
color = ColourLibrary.SIDE_1_ALLY_BATTLE_COLOUR,
|
||||
onRequest = { packet, battleFormat -> BattleTeamRequestPacket(packet.numericTargetId).sendToServer() },
|
||||
onResponse = { packet, accept -> BattleTeamResponsePacket(packet.numericTargetId, accept).sendToServer() }
|
||||
),
|
||||
),
|
||||
Pair(
|
||||
PlayerInteractOptionsPacket.Options.TEAM_LEAVE,
|
||||
BattleTypeTile(
|
||||
option = PlayerInteractOptionsPacket.Options.TEAM_LEAVE,
|
||||
battleFormat = BattleFormat.GEN_9_MULTI,
|
||||
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi.png"),
|
||||
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi_overlay_partner.png"),
|
||||
title = lang("challenge.request_team_leave_title").bold(),
|
||||
subTitle = lang("battle.types.multi").bold(),
|
||||
color = ColourLibrary.SIDE_1_ALLY_BATTLE_COLOUR,
|
||||
onRequest = { _, _ -> BattleTeamLeavePacket().sendToServer() },
|
||||
onResponse = { _, _ -> Unit }
|
||||
),
|
||||
),
|
||||
Pair(
|
||||
PlayerInteractOptionsPacket.Options.MULTI_BATTLE,
|
||||
BattleTypeTile(
|
||||
option = PlayerInteractOptionsPacket.Options.MULTI_BATTLE,
|
||||
battleFormat = BattleFormat.GEN_9_MULTI,
|
||||
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi.png"),
|
||||
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi_overlay_opponent.png"),
|
||||
title = lang("challenge.request_battle_title").bold(),
|
||||
subTitle = lang("battle.types.multi").bold(),
|
||||
onRequest = { packet, battleFormat -> sendBattleRequest(battleFormat, packet) },
|
||||
onResponse = { packet, accept -> sendBattleResponse(packet, accept) }
|
||||
),
|
||||
),
|
||||
Pair(
|
||||
PlayerInteractOptionsPacket.Options.ROYAL_BATTLE,
|
||||
BattleTypeTile(
|
||||
option = PlayerInteractOptionsPacket.Options.ROYAL_BATTLE,
|
||||
battleFormat = BattleFormat.GEN_9_ROYAL,
|
||||
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_royal.png"),
|
||||
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_royal_overlay.png"),
|
||||
title = lang("challenge.request_battle_title").bold(),
|
||||
subTitle = lang("battle.types.freeforall").bold(),
|
||||
onRequest = { packet, battleFormat -> sendBattleRequest(battleFormat, packet) },
|
||||
onResponse = { packet, accept -> sendBattleResponse(packet, accept) }
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
fun sendBattleRequest(battleFormat: BattleFormat, packet: PlayerInteractOptionsPacket) {
|
||||
BattleChallengePacket(packet.numericTargetId, packet.selectedPokemonId, battleFormat).sendToServer()
|
||||
}
|
||||
private fun sendBattleResponse(packet: PlayerInteractOptionsPacket, accept: Boolean) {
|
||||
BattleChallengeResponsePacket(packet.numericTargetId, packet.selectedPokemonId, accept).sendToServer()
|
||||
}
|
||||
private var options: List<PlayerInteractOptionsPacket.Options> = emptyList()
|
||||
private val blinkRate = 35
|
||||
}
|
||||
|
||||
class BattleTypeTile(
|
||||
val option: PlayerInteractOptionsPacket.Options,
|
||||
val battleFormat: BattleFormat,
|
||||
val tileTexture: ResourceLocation?,
|
||||
val overlayTexture: ResourceLocation?,
|
||||
val title: MutableComponent,
|
||||
val subTitle: MutableComponent,
|
||||
val color: Int = ColourLibrary.SIDE_1_BATTLE_COLOUR,
|
||||
val onRequest: (packet : PlayerInteractOptionsPacket, battleFormat: BattleFormat) -> Unit,
|
||||
val onResponse: (packet: PlayerInteractOptionsPacket, accept: Boolean) -> Unit,
|
||||
)
|
||||
|
||||
private var currentPage = 0
|
||||
set(value) {
|
||||
// If value is within min and max
|
||||
field = if (value > 0 && value < options.count()) value
|
||||
// If value is less than zero, wrap around to end
|
||||
else if (value < 0) options.count() - 1
|
||||
// Else it's greater than max, wrap around to start
|
||||
else 0
|
||||
}
|
||||
|
||||
private var targetName = Component.literal("Target Name").bold()
|
||||
private var hasRequest = false
|
||||
private var ticksPassed = 0F
|
||||
|
||||
override fun renderBlurredBackground(delta: Float) { }
|
||||
override fun renderMenuBackground(context: GuiGraphics) { }
|
||||
|
||||
|
||||
override fun init() {
|
||||
val challenge = activeRequest
|
||||
targetName = Minecraft.getInstance().player?.level()?.getPlayerByUUID(packet.targetId)?.name?.plainCopy()?.bold() ?: targetName
|
||||
hasRequest = challenge != null || activeTeamRequest != null
|
||||
if (activeTeamRequest != null) {
|
||||
options = listOf(PlayerInteractOptionsPacket.Options.TEAM_REQUEST)
|
||||
} else if (challenge != null) {
|
||||
options = packet.options.filter { battleRequestMap[it]?.battleFormat?.battleType?.name == challenge.battleFormat?.battleType?.name }
|
||||
} else {
|
||||
options = packet.options.filter { battleRequestMap.containsKey(it) }.toList()
|
||||
}
|
||||
val (x, y) = getDimensions()
|
||||
|
||||
|
||||
if(hasRequest) {
|
||||
// Draw Accept/Decline buttons
|
||||
this.addRenderableWidget(
|
||||
BattleResponseButton(
|
||||
x + 22,
|
||||
y + 99,
|
||||
true
|
||||
) {
|
||||
battleRequestMap[options[currentPage]]?.onResponse?.let { it1 -> it1(packet, true) }
|
||||
closeGUI()
|
||||
}
|
||||
)
|
||||
|
||||
this.addRenderableWidget(
|
||||
BattleResponseButton(
|
||||
x + 56,
|
||||
y + 99,
|
||||
false
|
||||
) {
|
||||
battleRequestMap[options[currentPage]]?.onResponse?.let { it1 -> it1(packet, false) }
|
||||
closeGUI()
|
||||
}
|
||||
)
|
||||
|
||||
} else {
|
||||
// Draw Challenge button
|
||||
this.addRenderableWidget(
|
||||
BattleRequestButton(
|
||||
x + 22,
|
||||
y + 99,
|
||||
lang("challenge.challenge"),
|
||||
) {
|
||||
//TODO: add additional battle rules, otherwise this call feels pretty silly
|
||||
battleRequestMap[options[currentPage]]?.onRequest?.let { it1 ->
|
||||
it1(packet, battleRequestMap[options[currentPage]]?.battleFormat ?: BattleFormat.GEN_9_SINGLES) }
|
||||
closeGUI()
|
||||
}
|
||||
)
|
||||
|
||||
// Selection buttons
|
||||
this.addRenderableWidget(
|
||||
BattleRequestNavigationButton(
|
||||
pX = x + 2,
|
||||
pY = y + 30,
|
||||
forward = false
|
||||
) { currentPage = (currentPage - 1) % options.count() }
|
||||
)
|
||||
|
||||
this.addRenderableWidget(
|
||||
BattleRequestNavigationButton(
|
||||
pX = x + 106,
|
||||
pY = y + 30,
|
||||
forward = true
|
||||
) { currentPage = (currentPage + 1) % options.count() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
ticksPassed += delta
|
||||
val battleTypeData = battleRequestMap[options[currentPage]] ?: return
|
||||
// Render background panel
|
||||
val (x, y) = getDimensions()
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = backgroundResource,
|
||||
x = x,
|
||||
y = y,
|
||||
width = 113,
|
||||
height = 126
|
||||
)
|
||||
|
||||
// Render Screen title
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = battleTypeData.title,
|
||||
x = x + 42,
|
||||
y = y + 2,
|
||||
centered = true,
|
||||
shadow = true
|
||||
)
|
||||
|
||||
// Render battle type icon
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = battleTypeData.tileTexture,
|
||||
x = 2*(x + 113 / 2 - 190 / 4),
|
||||
y = 2*(y + 126 / 2 - 120 / 4 - 10),
|
||||
width = 190,
|
||||
height = 120,
|
||||
scale = 0.5F,
|
||||
)
|
||||
// Render battle type overlay
|
||||
if(ticksPassed % blinkRate < blinkRate / 2) {
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = battleTypeData.overlayTexture,
|
||||
x = 2*(x + 113 / 2 - 190 / 4),
|
||||
y = 2*(y + 126 / 2 - 120 / 4 - 10),
|
||||
width = 190,
|
||||
height = 120,
|
||||
scale = 0.5F,
|
||||
red = if (battleTypeData.option == PlayerInteractOptionsPacket.Options.TEAM_LEAVE) 1.3 else 1,
|
||||
green = if (battleTypeData.option == PlayerInteractOptionsPacket.Options.TEAM_LEAVE) 0 else 1,
|
||||
blue = if (battleTypeData.option == PlayerInteractOptionsPacket.Options.TEAM_LEAVE) 0 else 1,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Battle type display text
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = battleTypeData.subTitle,
|
||||
x = x + 55,
|
||||
y = y + 14,
|
||||
centered = true,
|
||||
shadow = true
|
||||
)
|
||||
|
||||
// Opponent display name
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = targetName,
|
||||
x = x + 55,
|
||||
y = y + 87,
|
||||
centered = true,
|
||||
shadow = true
|
||||
)
|
||||
|
||||
// Draw colored decor arrows
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
texture = battleArrowsResource,
|
||||
x = x + 1,
|
||||
y = y + 106,
|
||||
width = 111,
|
||||
height = 5,
|
||||
scale = 1F,
|
||||
red = ((battleTypeData.color shr 16) and 0b11111111) / 255F,
|
||||
green = ((battleTypeData.color shr 8) and 0b11111111) / 255F,
|
||||
blue = (battleTypeData.color and 0b11111111) / 255F,
|
||||
)
|
||||
|
||||
super.render(context, mouseX, mouseY, delta)
|
||||
}
|
||||
|
||||
private fun getDimensions(): Pair<Int, Int> {
|
||||
return Pair(
|
||||
(width - SIZE) / 2,
|
||||
(height - SIZE) / 2
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
override fun isPauseScreen() = false
|
||||
|
||||
// override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
// return super.mouseClicked(mouseX, mouseY, button)
|
||||
// }
|
||||
private fun closeGUI() {
|
||||
Minecraft.getInstance().setScreen(null)
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.interact.battleRequest
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.components.Button
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
import net.minecraft.client.sounds.SoundManager
|
||||
import net.minecraft.network.chat.Component
|
||||
|
||||
class BattleRequestNavigationButton(
|
||||
pX: Int, pY: Int,
|
||||
private val forward: Boolean,
|
||||
onPress: OnPress
|
||||
): Button(pX, pY, (WIDTH * SCALE).toInt(), (CLICK_HEIGHT * SCALE).toInt(), Component.literal("Navigation"), onPress, DEFAULT_NARRATION) {
|
||||
|
||||
companion object {
|
||||
private const val WIDTH = 9F
|
||||
private const val HEIGHT = 16F
|
||||
private const val CLICK_HEIGHT = HEIGHT * 6
|
||||
private const val SCALE = 0.5F
|
||||
private val forwardButtonResource = cobblemonResource("textures/gui/interact/request/arrow_right.png")
|
||||
private val backwardsButtonResource = cobblemonResource("textures/gui/interact/request/arrow_left.png")
|
||||
}
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
|
||||
val hovered = (isHovered(pMouseX.toDouble(), pMouseY.toDouble()))
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
x = x / SCALE,
|
||||
y = (y + (CLICK_HEIGHT - HEIGHT) / 4) / SCALE,
|
||||
texture = if (forward) forwardButtonResource else backwardsButtonResource,
|
||||
width = WIDTH,
|
||||
height = HEIGHT,
|
||||
vOffset = if (hovered) HEIGHT else 0,
|
||||
textureHeight = HEIGHT * 2,
|
||||
scale = SCALE
|
||||
)
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager) {
|
||||
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.PC_CLICK, 1.0F))
|
||||
}
|
||||
|
||||
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in (x.toFloat()..(x.toFloat() + (WIDTH * SCALE))) && mouseY.toFloat() in ((y.toFloat())..(y.toFloat() + (CLICK_HEIGHT * SCALE)))
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.interact.battleRequest
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.components.Button
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
import net.minecraft.client.sounds.SoundManager
|
||||
import net.minecraft.network.chat.Component
|
||||
|
||||
class BattleResponseButton(
|
||||
pX: Int, pY: Int,
|
||||
private val accept: Boolean,
|
||||
onPress: OnPress
|
||||
): Button(pX, pY, (WIDTH * SCALE).toInt(), (HEIGHT * SCALE).toInt(), Component.literal("Navigation"), onPress, DEFAULT_NARRATION) {
|
||||
|
||||
companion object {
|
||||
private const val WIDTH = 34F
|
||||
private const val HEIGHT = 19F
|
||||
private const val SCALE = 1F
|
||||
private val acceptButtonResource = cobblemonResource("textures/gui/interact/request/button_request_accept.png")
|
||||
private val declineButtonResource = cobblemonResource("textures/gui/interact/request/button_request_decline.png")
|
||||
private val acceptIconResource = cobblemonResource("textures/gui/interact/request/icon_accept.png")
|
||||
private val declineIconResource = cobblemonResource("textures/gui/interact/request/icon_decline.png")
|
||||
}
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
|
||||
val hovered = (isHovered(pMouseX.toDouble(), pMouseY.toDouble()))
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
x = x / SCALE,
|
||||
y = y / SCALE,
|
||||
texture = if (accept) acceptButtonResource else declineButtonResource,
|
||||
width = WIDTH,
|
||||
height = HEIGHT,
|
||||
vOffset = if (hovered) HEIGHT else 0,
|
||||
textureHeight = HEIGHT * 2,
|
||||
scale = SCALE
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
x = x / 0.5F + 24,
|
||||
y = y / 0.5F + 7,
|
||||
texture = if (accept) acceptIconResource else declineIconResource,
|
||||
width = 21,
|
||||
height = 26,
|
||||
textureHeight = 26,
|
||||
scale = 0.5F
|
||||
)
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager) {
|
||||
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.PC_CLICK, 1.0F))
|
||||
}
|
||||
|
||||
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in (x.toFloat()..(x.toFloat() + (WIDTH * SCALE))) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + (HEIGHT * SCALE)))
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.interact.battleRequest
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.components.Button
|
||||
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
|
||||
|
||||
class BattleRequestButton(
|
||||
pX: Int, pY: Int,
|
||||
private val text: MutableComponent,
|
||||
onPress: OnPress
|
||||
): Button(pX, pY, (WIDTH * SCALE).toInt(), (HEIGHT * SCALE).toInt(), text, onPress, DEFAULT_NARRATION) {
|
||||
|
||||
companion object {
|
||||
private const val WIDTH = 69F
|
||||
private const val HEIGHT = 19F
|
||||
private const val SCALE = 1F
|
||||
private val buttonResource = cobblemonResource("textures/gui/interact/request/button_request_battle.png")
|
||||
}
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
|
||||
val hovered = (isHovered(pMouseX.toDouble(), pMouseY.toDouble()))
|
||||
blitk(
|
||||
matrixStack = context.pose(),
|
||||
x = x / SCALE,
|
||||
y = y / SCALE,
|
||||
texture = buttonResource,
|
||||
width = WIDTH,
|
||||
height = HEIGHT,
|
||||
vOffset = if (hovered) HEIGHT else 0,
|
||||
textureHeight = HEIGHT * 2,
|
||||
scale = SCALE
|
||||
)
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = text.bold(),
|
||||
x = x + 35,
|
||||
y = y + 5,
|
||||
centered = true,
|
||||
shadow = true
|
||||
)
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager) {
|
||||
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.PC_CLICK, 1.0F))
|
||||
}
|
||||
|
||||
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in (x.toFloat()..(x.toFloat() + (WIDTH * SCALE))) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + (HEIGHT * SCALE)))
|
||||
}
|
||||
+35
-9
@@ -19,14 +19,16 @@ import net.minecraft.resources.ResourceLocation
|
||||
import org.joml.Vector3f
|
||||
|
||||
class InteractWheelButton(
|
||||
private val iconResource: ResourceLocation?,
|
||||
private val buttonResource: ResourceLocation,
|
||||
private val tooltipText: String?,
|
||||
x: Int,
|
||||
y: Int,
|
||||
private val isEnabled: Boolean,
|
||||
private val colour: () -> Vector3f?,
|
||||
onPress: OnPress
|
||||
private val iconResource: ResourceLocation?,
|
||||
private val secondaryIconResource: ResourceLocation? = null,
|
||||
private val buttonResource: ResourceLocation,
|
||||
private val tooltipText: String?,
|
||||
x: Int,
|
||||
y: Int,
|
||||
private val isEnabled: Boolean,
|
||||
private val colour: () -> Vector3f?,
|
||||
onPress: OnPress,
|
||||
private val canHover: (Double, Double) -> Boolean
|
||||
) : Button(x, y, BUTTON_SIZE, BUTTON_SIZE, Component.literal("Interact"), onPress, DEFAULT_NARRATION) {
|
||||
|
||||
companion object {
|
||||
@@ -37,8 +39,12 @@ class InteractWheelButton(
|
||||
const val ICON_OFFSET = 26.5
|
||||
}
|
||||
|
||||
private var passedTicks = 0F
|
||||
private val blinkInterval = 35
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
val matrices = context.pose()
|
||||
passedTicks += delta
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = buttonResource,
|
||||
@@ -74,6 +80,26 @@ class InteractWheelButton(
|
||||
scale = ICON_SCALE
|
||||
)
|
||||
}
|
||||
|
||||
if (passedTicks % blinkInterval < blinkInterval / 2) {
|
||||
if (secondaryIconResource != null) {
|
||||
val (iconX, iconY) = getIconPosition()
|
||||
val colour = this.colour() ?: Vector3f(1F, 1F, 1F)
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = secondaryIconResource,
|
||||
x = iconX,
|
||||
y = (iconY.toFloat() - ICON_SIZE),
|
||||
width = ICON_SIZE,
|
||||
height = ICON_SIZE,
|
||||
alpha = if (isEnabled) 1f else 0.4f,
|
||||
red = colour.x,
|
||||
green = colour.y,
|
||||
blue = colour.z,
|
||||
scale = ICON_SCALE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIconPosition(): Pair<Number, Number> {
|
||||
@@ -90,7 +116,7 @@ class InteractWheelButton(
|
||||
val xMax = xMin + BUTTON_SIZE
|
||||
val yMin = y.toFloat()
|
||||
val yMax = yMin + BUTTON_SIZE
|
||||
return mouseX in xMin..xMax && mouseY in yMin..yMax
|
||||
return canHover(mouseX.toDouble(), mouseY.toDouble()) && mouseX in xMin..xMax && mouseY in yMin..yMax
|
||||
}
|
||||
|
||||
override fun getTooltip(): Tooltip? {
|
||||
|
||||
+6
-2
@@ -36,6 +36,8 @@ class InteractWheelGUI(private val options: Multimap<Orientation, InteractWheelO
|
||||
private val buttons = mutableListOf<InteractWheelButton>()
|
||||
private var maxPage = 1
|
||||
private var currentPage = 0
|
||||
override fun renderBlurredBackground(delta: Float) { }
|
||||
override fun renderMenuBackground(context: GuiGraphics) {}
|
||||
|
||||
override fun init() {
|
||||
calculateMaxPage()
|
||||
@@ -96,19 +98,21 @@ class InteractWheelGUI(private val options: Multimap<Orientation, InteractWheelO
|
||||
val (x, y) = getButtonPosition(orientation)
|
||||
addRenderableWidget(InteractWheelButton(
|
||||
iconResource = option?.iconResource,
|
||||
secondaryIconResource = option?.secondaryIconResource,
|
||||
buttonResource = buttonResources[orientation]!!,
|
||||
tooltipText = option?.tooltipText,
|
||||
x = x,
|
||||
y = y,
|
||||
isEnabled = option != null,
|
||||
colour = option?.colour ?: { null },
|
||||
onPress = { option?.onPress?.invoke() }
|
||||
onPress = { option?.onPress?.invoke() },
|
||||
canHover = { a: Double, b: Double -> !isMouseInCenter(a, b)}
|
||||
))
|
||||
}
|
||||
|
||||
override fun <T> addRenderableWidget(drawableElement: T): T where T : GuiEventListener?, T : Renderable?, T : NarratableEntry? {
|
||||
if (drawableElement is InteractWheelButton) {
|
||||
buttons.add(drawableElement)
|
||||
// buttons.add(drawableElement)
|
||||
}
|
||||
return super.addRenderableWidget(drawableElement)
|
||||
}
|
||||
|
||||
+21
-11
@@ -12,8 +12,8 @@ import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.pokemon.interaction.PokemonInteractionGUICreationEvent
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.gui.interact.battleRequest.BattleConfigureGUI
|
||||
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.BattleChallengePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.interact.InteractPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.AcceptTradeRequestPacket
|
||||
@@ -70,37 +70,47 @@ fun createPlayerInteractGui(optionsPacket: PlayerInteractOptionsPacket): Interac
|
||||
closeGUI()
|
||||
}
|
||||
)
|
||||
val activeBattleRequest = CobblemonClient.requests.battleChallenges.firstOrNull { it.challengerIds.contains(optionsPacket.targetId) }
|
||||
val activeTeamRequest = CobblemonClient.requests.multiBattleTeamRequests.firstOrNull { it.challengerIds.contains(optionsPacket.targetId) }
|
||||
val battle = InteractWheelOption(
|
||||
iconResource = cobblemonResource("textures/gui/interact/icon_battle.png"),
|
||||
colour = { if (CobblemonClient.requests.battleChallenges.any { it.challengerId == optionsPacket.targetId }) Vector3f(0F, 0.6F, 0F) else null },
|
||||
secondaryIconResource = if(activeBattleRequest != null|| activeTeamRequest != null)
|
||||
cobblemonResource("textures/gui/interact/icon_exclamation.png")
|
||||
else null,
|
||||
colour = { null },
|
||||
tooltipText = "cobblemon.ui.interact.battle",
|
||||
onPress = {
|
||||
val battleRequest = CobblemonClient.requests.battleChallenges.find { it.challengerId == optionsPacket.targetId }
|
||||
// This can be improved in future with more detailed battle challenge data.
|
||||
BattleChallengePacket(optionsPacket.numericTargetId, optionsPacket.selectedPokemonId).sendToServer()
|
||||
closeGUI()
|
||||
Minecraft.getInstance().setScreen(BattleConfigureGUI(optionsPacket, activeBattleRequest, activeTeamRequest))
|
||||
}
|
||||
)
|
||||
|
||||
val spectate = InteractWheelOption(
|
||||
iconResource = cobblemonResource("textures/gui/interact/icon_spectate_battle.png"),
|
||||
colour = { if (CobblemonClient.requests.battleChallenges.any { it.challengerId == optionsPacket.targetId }) Vector3f(0F, 0.6F, 0F) else null },
|
||||
colour = { if (CobblemonClient.requests.battleChallenges.any { it.challengerIds.contains(optionsPacket.targetId) }) Vector3f(0F, 0.6F, 0F) else null },
|
||||
onPress = {
|
||||
SpectateBattlePacket(optionsPacket.targetId).sendToServer()
|
||||
closeGUI()
|
||||
},
|
||||
tooltipText = "cobblemon.ui.interact.spectate"
|
||||
)
|
||||
)
|
||||
val options: Multimap<Orientation, InteractWheelOption> = ArrayListMultimap.create()
|
||||
//TODO: hasChallenge and hasTeamRequest get calculated a bunch of times. Might consider having the server just passing it over.
|
||||
val hasChallenge = CobblemonClient.requests.battleChallenges.any { it.challengerIds.contains(optionsPacket.targetId) }
|
||||
val hasTeamRequest = CobblemonClient.requests.multiBattleTeamRequests.any { it.challengerIds.contains(optionsPacket.targetId) }
|
||||
//The way things are positioned should probably be more thought out if more options are added
|
||||
optionsPacket.options.map {
|
||||
var addBattleOption = false
|
||||
optionsPacket.options.forEach {
|
||||
if (it.equals(PlayerInteractOptionsPacket.Options.TRADE)) {
|
||||
options.put(Orientation.TOP_LEFT, trade)
|
||||
}
|
||||
if (it.equals(PlayerInteractOptionsPacket.Options.BATTLE)) {
|
||||
if (!addBattleOption && (hasChallenge || hasTeamRequest || BattleConfigureGUI.battleRequestMap.containsKey(it))) {
|
||||
options.put(Orientation.TOP_RIGHT, battle)
|
||||
addBattleOption = true
|
||||
}
|
||||
if (it.equals(PlayerInteractOptionsPacket.Options.SPECTATE_BATTLE)) {
|
||||
options.put(Orientation.TOP_RIGHT, spectate)
|
||||
if(!hasChallenge) {
|
||||
options.put(Orientation.TOP_RIGHT, spectate)
|
||||
}
|
||||
}
|
||||
}
|
||||
return InteractWheelGUI(options, Component.translatable("cobblemon.ui.interact.player"))
|
||||
|
||||
+1
@@ -13,6 +13,7 @@ import org.joml.Vector3f
|
||||
|
||||
data class InteractWheelOption(
|
||||
val iconResource: ResourceLocation,
|
||||
val secondaryIconResource: ResourceLocation? = null,
|
||||
val tooltipText: String?,
|
||||
val colour: () -> Vector3f? = { null },
|
||||
val onPress: () -> Unit
|
||||
|
||||
+11
-4
@@ -8,6 +8,7 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.keybind.keybinds
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendToServer
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
|
||||
@@ -24,6 +25,8 @@ import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.player.LocalPlayer
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.level.ClipContext
|
||||
import kotlin.math.pow
|
||||
|
||||
object PartySendBinding : CobblemonBlockingKeyBinding(
|
||||
"key.cobblemon.throwpartypokemon",
|
||||
@@ -75,7 +78,11 @@ object PartySendBinding : CobblemonBlockingKeyBinding(
|
||||
if (CobblemonClient.storage.selectedSlot != -1 && Minecraft.getInstance().screen == null) {
|
||||
val pokemon = CobblemonClient.storage.myParty.get(CobblemonClient.storage.selectedSlot)
|
||||
if (pokemon != null && pokemon.currentHealth > 0) {
|
||||
val targetEntity = player.traceFirstEntityCollision(entityClass = LivingEntity::class.java, ignoreEntity = player)
|
||||
val targetEntity = player.traceFirstEntityCollision(
|
||||
entityClass = LivingEntity::class.java,
|
||||
ignoreEntity = player,
|
||||
maxDistance = Cobblemon.config.battleSpectateMaxDistance,
|
||||
collideBlock = ClipContext.Fluid.NONE)
|
||||
if (targetEntity == null || (targetEntity is PokemonEntity && targetEntity.ownerUUID == player.uuid)) {
|
||||
sendToServer(SendOutPokemonPacket(CobblemonClient.storage.selectedSlot))
|
||||
}
|
||||
@@ -94,9 +101,9 @@ object PartySendBinding : CobblemonBlockingKeyBinding(
|
||||
sendToServer(RequestPlayerInteractionsPacket(entity.uuid, entity.id, pokemon.uuid))
|
||||
}
|
||||
is PokemonEntity -> {
|
||||
if (!entity.canBattle(player)) return
|
||||
sendToServer(BattleChallengePacket(entity.id, pokemon.uuid))
|
||||
}
|
||||
if (!entity.canBattle(player) || entity.position().distanceToSqr(player.position()) > Cobblemon.config.battleWildMaxDistance.pow(2)) return
|
||||
sendToServer(BattleChallengePacket(entity.id, pokemon.uuid))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -15,6 +15,6 @@ import net.minecraft.client.Minecraft
|
||||
|
||||
object BattleChallengeExpiredHandler : ClientNetworkPacketHandler<BattleChallengeExpiredPacket> {
|
||||
override fun handle(packet: BattleChallengeExpiredPacket, client: Minecraft) {
|
||||
CobblemonClient.requests.battleChallenges.removeIf { it.challengeId == packet.battleChallengeId }
|
||||
CobblemonClient.requests.onChallengeExpired(packet.battleChallengeId)
|
||||
}
|
||||
}
|
||||
+17
-8
@@ -9,24 +9,33 @@
|
||||
package com.cobblemon.mod.common.client.net.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.api.snowstorm.BedrockParticleOptions
|
||||
import com.cobblemon.mod.common.api.text.yellow
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleChallenge
|
||||
import com.cobblemon.mod.common.client.keybind.boundKey
|
||||
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding
|
||||
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.BattleChallengeNotificationPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.mojang.authlib.minecraft.client.MinecraftClient
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.multiplayer.ClientLevel
|
||||
|
||||
object BattleChallengeNotificationHandler : ClientNetworkPacketHandler<BattleChallengeNotificationPacket> {
|
||||
override fun handle(packet: BattleChallengeNotificationPacket, client: Minecraft) {
|
||||
CobblemonClient.requests.battleChallenges.add(ClientBattleChallenge(packet.battleChallengeId, packet.challengerId))
|
||||
client.player?.displayClientMessage(
|
||||
val clientBattleChallenge = ClientBattleChallenge(packet.battleChallengeId, packet.challengerIds, packet.battleFormat)
|
||||
CobblemonClient.requests.battleChallenges.add(clientBattleChallenge)
|
||||
client.player?.sendSystemMessage(
|
||||
lang(
|
||||
"challenge.receiver",
|
||||
packet.challengerName,
|
||||
PartySendBinding.boundKey().displayName
|
||||
),
|
||||
true
|
||||
packet.challengerNames.first(),
|
||||
lang("battle.types.${packet.battleFormat.battleType.name}"),
|
||||
).yellow()
|
||||
)
|
||||
CobblemonClient.requests.addParticleEffect(clientBattleChallenge)
|
||||
}
|
||||
}
|
||||
+45
@@ -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.client.net.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.battle.ActiveClientBattlePokemon
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattlePokemon
|
||||
import com.cobblemon.mod.common.client.battle.animations.MoveTileOffscreenAnimation
|
||||
import com.cobblemon.mod.common.client.battle.animations.SwapAndMoveTileOnscreenAnimation
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleSwapPokemonPacket
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
object BattleSwapPokemonHandler : ClientNetworkPacketHandler<BattleSwapPokemonPacket> {
|
||||
override fun handle(packet: BattleSwapPokemonPacket, client: Minecraft) {
|
||||
val battle = CobblemonClient.battle ?: return
|
||||
val (actor, activeBattlePokemon) = battle.getPokemonFromPNX(packet.pnx)
|
||||
|
||||
val swapPokemon = activeBattlePokemon.getAdjacentAllies().first() as ActiveClientBattlePokemon
|
||||
val swapBattlePokemon = swapPokemon.battlePokemon
|
||||
|
||||
if (swapBattlePokemon != null && swapBattlePokemon.hpValue > 0) {
|
||||
activeBattlePokemon.animations.add(
|
||||
SwapAndMoveTileOnscreenAnimation(
|
||||
swapPokemon.battlePokemon as ClientBattlePokemon
|
||||
)
|
||||
)
|
||||
} else {
|
||||
activeBattlePokemon.animations.add(MoveTileOffscreenAnimation())
|
||||
}
|
||||
|
||||
swapPokemon.animations.add(
|
||||
SwapAndMoveTileOnscreenAnimation(
|
||||
activeBattlePokemon.battlePokemon as ClientBattlePokemon
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
+25
@@ -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.ClientMultiBattleTeamMember
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.TeamJoinNotificationPacket
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
object TeamJoinNotificationHandler : ClientNetworkPacketHandler<TeamJoinNotificationPacket> {
|
||||
override fun handle(packet: TeamJoinNotificationPacket, client: Minecraft) {
|
||||
|
||||
CobblemonClient.teamData.multiBattleTeamMembers = packet.teamMemberUUIDs.mapIndexed { index, uuid -> ClientMultiBattleTeamMember(uuid, packet.teamMemberNames[index]) }.toMutableList()
|
||||
|
||||
// TODO: print something to the client
|
||||
|
||||
}
|
||||
}
|
||||
+45
@@ -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.client.net.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.TeamMemberRemoveNotificationPacket
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
object TeamMemberRemoveNotificationHandler : ClientNetworkPacketHandler<TeamMemberRemoveNotificationPacket> {
|
||||
override fun handle(packet: TeamMemberRemoveNotificationPacket, client: Minecraft) {
|
||||
|
||||
if(packet.teamMemberUUID == client.player?.uuid) {
|
||||
// Client removes itself from its team
|
||||
val memberCount = CobblemonClient.teamData.multiBattleTeamMembers.count()
|
||||
CobblemonClient.teamData.multiBattleTeamMembers.clear()
|
||||
val langKey = if(memberCount > 1) "challenge.multi.team_remove.sender" else "challenge.multi.team_disband"
|
||||
client.player?.sendSystemMessage(
|
||||
lang(
|
||||
langKey,
|
||||
)
|
||||
)
|
||||
} else {
|
||||
// Client removes a member from the team
|
||||
val memberToRemove = CobblemonClient.teamData.multiBattleTeamMembers.find { it.uuid == packet.teamMemberUUID }
|
||||
CobblemonClient.teamData.multiBattleTeamMembers.remove(memberToRemove)
|
||||
|
||||
if(memberToRemove != null) {
|
||||
client.player?.sendSystemMessage(
|
||||
lang(
|
||||
"challenge.multi.team_remove.receiver",
|
||||
memberToRemove.name,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -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.CobblemonClient
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.TeamRequestExpiredPacket
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
object TeamRequestExpiredHandler : ClientNetworkPacketHandler<TeamRequestExpiredPacket> {
|
||||
override fun handle(packet: TeamRequestExpiredPacket, client: Minecraft) {
|
||||
CobblemonClient.requests.onMultiChallengeExpired(packet.teamRequestId)
|
||||
}
|
||||
}
|
||||
+28
@@ -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.net.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.battle.ClientBattleChallenge
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.TeamRequestNotificationPacket
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
object TeamRequestNotificationHandler : ClientNetworkPacketHandler<TeamRequestNotificationPacket> {
|
||||
override fun handle(packet: TeamRequestNotificationPacket, client: Minecraft) {
|
||||
CobblemonClient.requests.multiBattleTeamRequests.add(ClientBattleChallenge(packet.teamRequestId, listOf(packet.requesterId)))
|
||||
client.player?.sendSystemMessage(
|
||||
lang(
|
||||
"challenge.multi.team_request.receiver",
|
||||
packet.requesterName,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -15,6 +15,6 @@ import net.minecraft.client.Minecraft
|
||||
|
||||
object TradeOfferExpiredHandler : ClientNetworkPacketHandler<TradeOfferExpiredPacket> {
|
||||
override fun handle(packet: TradeOfferExpiredPacket, client: Minecraft) {
|
||||
CobblemonClient.requests.tradeOffers.removeIf { it.tradeOfferId == packet.tradeOfferId }
|
||||
CobblemonClient.requests.onTradeOfferExpired(packet.tradeOfferId)
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.client.particle
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.api.snowstorm.BedrockParticleOptions
|
||||
import com.cobblemon.mod.common.api.snowstorm.ParticleEmitterAction
|
||||
import com.cobblemon.mod.common.client.render.MatrixWrapper
|
||||
@@ -22,6 +23,7 @@ import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.multiplayer.ClientLevel
|
||||
import net.minecraft.client.particle.NoRenderParticle
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.random.Random
|
||||
|
||||
@@ -60,6 +62,8 @@ class ParticleStorm(
|
||||
}
|
||||
if (entity is PosableEntity) {
|
||||
runtime.environment.query.addFunction("entity") { entity.struct }
|
||||
} else if (entity is Player) {
|
||||
runtime.environment.query.addFunction("entity") { entity.asMoLangValue() }
|
||||
}
|
||||
// TODO replace with a generified call to if (entity is MoLangEntity) entity.applyVariables(env) or w/e
|
||||
runtime.environment.setSimpleVariable("entity_width", DoubleValue(entity.boundingBox.xsize))
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common.client.render
|
||||
|
||||
import com.cobblemon.mod.common.api.gui.drawText
|
||||
import com.cobblemon.mod.common.api.gui.drawTextJustifiedRight
|
||||
import com.cobblemon.mod.common.api.text.font
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.mojang.blaze3d.platform.GlStateManager
|
||||
@@ -171,6 +172,68 @@ fun drawScaledText(
|
||||
matrixStack.popPose()
|
||||
}
|
||||
|
||||
fun drawScaledTextJustifiedRight(
|
||||
context: GuiGraphics,
|
||||
font: ResourceLocation? = null,
|
||||
text: MutableComponent,
|
||||
x: Number,
|
||||
y: Number,
|
||||
scale: Float = 1F,
|
||||
opacity: Number = 1F,
|
||||
maxCharacterWidth: Int = Int.MAX_VALUE,
|
||||
colour: Int = 0x00FFFFFF + ((opacity.toFloat() * 255).toInt() shl 24),
|
||||
shadow: Boolean = false
|
||||
) {
|
||||
if (opacity.toFloat() < 0.05F) {
|
||||
return
|
||||
}
|
||||
val textWidth = Minecraft.getInstance().font.width(if (font != null) text.font(font) else text)
|
||||
val extraScale = if (textWidth < maxCharacterWidth) 1F else (maxCharacterWidth / textWidth.toFloat())
|
||||
val fontHeight = if (font == null) 5F else 6F
|
||||
val matrixStack = context.pose()
|
||||
matrixStack.pushPose()
|
||||
matrixStack.scale(scale * extraScale, scale * extraScale, 1F)
|
||||
drawTextJustifiedRight(
|
||||
context = context,
|
||||
font = font,
|
||||
text = text,
|
||||
x = x.toFloat() / (scale * extraScale),
|
||||
y = y.toFloat() / (scale * extraScale) + (1F - extraScale) * fontHeight * scale,
|
||||
colour = colour,
|
||||
shadow = shadow
|
||||
)
|
||||
matrixStack.popPose()
|
||||
}
|
||||
|
||||
fun drawScaledTextJustifiedRight(
|
||||
context: GuiGraphics,
|
||||
text: MutableComponent,
|
||||
x: Number,
|
||||
y: Number,
|
||||
scaleX: Float = 1F,
|
||||
scaleY: Float = 1F,
|
||||
opacity: Number = 1F,
|
||||
colour: Int = 0x00FFFFFF + ((opacity.toFloat() * 255).toInt() shl 24),
|
||||
shadow: Boolean = false
|
||||
) {
|
||||
if (opacity.toFloat() < 0.05F) {
|
||||
return
|
||||
}
|
||||
val matrixStack = context.pose()
|
||||
matrixStack.pushPose()
|
||||
matrixStack.scale(scaleX, scaleY, 1F)
|
||||
drawTextJustifiedRight(
|
||||
context = context,
|
||||
text = text,
|
||||
x = x.toFloat() / scaleX,
|
||||
y = y.toFloat() / scaleY,
|
||||
colour = colour,
|
||||
shadow = shadow
|
||||
)
|
||||
matrixStack.popPose()
|
||||
}
|
||||
|
||||
|
||||
fun renderBeaconBeam(
|
||||
matrixStack: PoseStack,
|
||||
buffer: MultiBufferSource,
|
||||
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.command
|
||||
|
||||
|
||||
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.util.alias
|
||||
import com.cobblemon.mod.common.util.permission
|
||||
import com.mojang.brigadier.Command.SINGLE_SUCCESS
|
||||
import com.mojang.brigadier.CommandDispatcher
|
||||
import com.mojang.brigadier.context.CommandContext
|
||||
import net.minecraft.commands.CommandSourceStack
|
||||
import net.minecraft.commands.Commands
|
||||
|
||||
object AbandonMultiTeam {
|
||||
|
||||
private const val ALIAS = "abandonmultibattleteam"
|
||||
|
||||
fun register(dispatcher: CommandDispatcher<CommandSourceStack>) {
|
||||
val command = dispatcher.register(
|
||||
Commands.literal("abandonmultiteam")
|
||||
.permission(CobblemonPermissions.ABANDON_MULTITEAM)
|
||||
.executes { execute(it,) }
|
||||
)
|
||||
dispatcher.register(command.alias(ALIAS))
|
||||
}
|
||||
|
||||
private fun execute(context: CommandContext<CommandSourceStack>) : Int {
|
||||
|
||||
val player = context.source.player
|
||||
if (player != null) {
|
||||
BattleRegistry.removeTeamMember(player)
|
||||
}
|
||||
return SINGLE_SUCCESS
|
||||
}
|
||||
|
||||
}
|
||||
@@ -220,6 +220,21 @@ class CobblemonConfig {
|
||||
@NodeCategory(Category.Battles)
|
||||
var walkingInBattleAnimations = false
|
||||
|
||||
@NodeCategory(Category.Battles)
|
||||
var battleWildMaxDistance = 12F
|
||||
|
||||
@NodeCategory(Category.World)
|
||||
var tradeMaxDistance = 12F
|
||||
|
||||
@NodeCategory(Category.Battles)
|
||||
var BattlePvPMaxDistance = 32F
|
||||
|
||||
@NodeCategory(Category.Battles)
|
||||
var battleSpectateMaxDistance = 64F
|
||||
|
||||
@NodeCategory(Category.World)
|
||||
var entityInteractionMaxDistance = 64F
|
||||
|
||||
@NodeCategory(Category.Debug)
|
||||
var enableDebugKeys = false
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.chainFutures
|
||||
import com.cobblemon.mod.common.util.effectiveName
|
||||
import com.cobblemon.mod.common.util.update
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class NPCBattleActor(
|
||||
@@ -34,6 +35,7 @@ class NPCBattleActor(
|
||||
override val type = ActorType.NPC
|
||||
override fun getName() = npc.effectiveName().copy()
|
||||
override fun nameOwned(name: String) = battleLang("owned_pokemon", this.getName(), name)
|
||||
override val initialPos = entity.position()
|
||||
|
||||
override fun sendUpdate(packet: NetworkPacket<*>) {
|
||||
super.sendUpdate(packet)
|
||||
|
||||
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.item
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.CobblemonMechanics
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
@@ -28,6 +29,7 @@ import net.minecraft.world.InteractionResult
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.InteractionResultHolder
|
||||
import net.minecraft.world.item.ItemNameBlockItem
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.level.Level
|
||||
|
||||
class RevivalHerbItem(block: RevivalHerbBlock) : ItemNameBlockItem(block, Properties()), PokemonSelectingItem {
|
||||
@@ -41,6 +43,7 @@ class RevivalHerbItem(block: RevivalHerbBlock) : ItemNameBlockItem(block, Proper
|
||||
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName = "item.cobblemon.revival_herb"
|
||||
override val returnItem = Items.AIR
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health <= 0
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?): String {
|
||||
battlePokemon.effectedPokemon.decrementFriendship(CobblemonMechanics.remedies.getFriendshipDrop(runtime))
|
||||
|
||||
@@ -12,6 +12,8 @@ import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.battles.BagItems
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
@@ -26,6 +28,7 @@ interface BagItem {
|
||||
companion object {
|
||||
val EMPTY: BagItem = object : BagItem {
|
||||
override val itemName = "name"
|
||||
override val returnItem: Item = Items.AIR
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = true
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?) = "none"
|
||||
}
|
||||
@@ -33,6 +36,10 @@ interface BagItem {
|
||||
|
||||
/** The name provided to Showdown so that battle messages include the name of the effect for lang. */
|
||||
val itemName: String
|
||||
|
||||
/** The return item given when the item is consumed. */
|
||||
val returnItem: Item
|
||||
|
||||
/** Whether or not the item can probably be used right now, based on the mod-side version of battle state. */
|
||||
fun canUse(battle: PokemonBattle, target: BattlePokemon): Boolean
|
||||
/** Gets the itemId and data for inputting to Showdown. Hyper potion is `potion 200` for example. */
|
||||
|
||||
@@ -12,10 +12,13 @@ import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.item.CobblemonItem
|
||||
import net.minecraft.world.item.Items
|
||||
|
||||
|
||||
class DireHitItem : CobblemonItem(Properties()), SimpleBagItemLike {
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName = "item.cobblemon.dire_hit"
|
||||
override val returnItem = Items.AIR
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health > 0
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?): String {
|
||||
battlePokemon.effectedPokemon.incrementFriendship(1)
|
||||
|
||||
@@ -12,10 +12,12 @@ import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.item.CobblemonItem
|
||||
import net.minecraft.world.item.Items
|
||||
|
||||
class GuardSpecItem : CobblemonItem(Properties()), SimpleBagItemLike {
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName = "item.cobblemon.guard_spec"
|
||||
override val returnItem = Items.AIR
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health > 0
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?): String {
|
||||
battlePokemon.effectedPokemon.incrementFriendship(1)
|
||||
|
||||
@@ -13,10 +13,12 @@ import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stat
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.item.CobblemonItem
|
||||
import net.minecraft.world.item.Items
|
||||
|
||||
class XStatItem(val stat: Stat, stages: Int = 2) : CobblemonItem(Properties()), SimpleBagItemLike {
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName = "item.cobblemon.x_${stat.identifier.path}"
|
||||
override val returnItem = Items.AIR
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health > 0
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?): String {
|
||||
battlePokemon.effectedPokemon.incrementFriendship(1)
|
||||
|
||||
@@ -25,6 +25,7 @@ import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.InteractionResultHolder
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.level.Level
|
||||
|
||||
/**
|
||||
@@ -36,6 +37,7 @@ import net.minecraft.world.level.Level
|
||||
class HealingBerryItem(block: BerryBlock, val amount: () -> ExpressionLike): BerryItem(block), PokemonSelectingItem {
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName: String get() = "item.cobblemon.${this@HealingBerryItem.berry()!!.identifier.path}"
|
||||
override val returnItem = Items.AIR
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?) = "potion ${ genericRuntime.resolveInt(amount(), battlePokemon) }"
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health < target.maxHealth && target.health > 0
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.item.berry
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
@@ -27,6 +29,7 @@ import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.InteractionResultHolder
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.level.Level
|
||||
|
||||
/**
|
||||
@@ -38,6 +41,7 @@ import net.minecraft.world.level.Level
|
||||
class PPRestoringBerryItem(block: BerryBlock, val amount: () -> ExpressionLike): BerryItem(block), PokemonAndMoveSelectingItem {
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName: String get() = "item.cobblemon.${berry()!!.identifier.path}"
|
||||
override val returnItem = Items.AIR
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health > 0 && target.moveSet.any { it.currentPp < it.maxPp }
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?) = "ether $data ${ genericRuntime.resolveInt(amount(), battlePokemon) }"
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.InteractionResultHolder
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.level.Level
|
||||
|
||||
/**
|
||||
@@ -35,6 +36,7 @@ import net.minecraft.world.level.Level
|
||||
class PortionHealingBerryItem(block: BerryBlock, val canCauseConfusion: Boolean, val portion: () -> ExpressionLike): BerryItem(block), PokemonSelectingItem {
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName: String get() = "item.cobblemon.${this@PortionHealingBerryItem.berry()!!.identifier.path}"
|
||||
override val returnItem = Items.AIR
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?): String {
|
||||
val confuse = if (canCauseConfusion) berry()!!.dislikedBy(battlePokemon.nature) else false
|
||||
return "potion_by_portion ${genericRuntime.resolveFloat(portion(), battlePokemon)} $confuse"
|
||||
|
||||
@@ -22,6 +22,7 @@ import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.InteractionResultHolder
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.level.Level
|
||||
|
||||
/**
|
||||
@@ -33,6 +34,7 @@ import net.minecraft.world.level.Level
|
||||
class StatusCuringBerryItem(block: BerryBlock, vararg val status: Status): BerryItem(block), PokemonSelectingItem {
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName: String get() = "item.cobblemon.${this@StatusCuringBerryItem.berry()!!.identifier.path}"
|
||||
override val returnItem = Items.AIR
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = canUseOnPokemon(target.effectedPokemon)
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?) = "cure_status${status.takeIf { it.isNotEmpty() }?.let { " ${it.joinToString(separator = " ") { it.showdownName } }" } ?: "" }"
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.InteractionResultHolder
|
||||
import net.minecraft.world.item.Items
|
||||
|
||||
/**
|
||||
* A type of berry that cures a volatile status.
|
||||
@@ -29,6 +30,7 @@ import net.minecraft.world.InteractionResultHolder
|
||||
class VolatileCuringBerryItem(block: BerryBlock, val volatileStatus: String): BerryItem(block), PokemonSelectingItem {
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName: String get() = "item.cobblemon.${berry()!!.identifier.path}"
|
||||
override val returnItem = Items.AIR
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = true // When we track volatiles, can check for confusion
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?) = "cure_volatile $volatileStatus"
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import net.minecraft.world.item.Items
|
||||
class BerryJuiceItem : CobblemonItem(Properties()), PokemonSelectingItem {
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName = "item.cobblemon.berry_juice"
|
||||
override val returnItem = Items.BOWL
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?) = "potion 20"
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health < target.maxHealth && target.health > 0
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
package com.cobblemon.mod.common.item.interactive
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
@@ -36,6 +37,7 @@ import net.minecraft.world.level.Level
|
||||
class ElixirItem(val max: Boolean) : CobblemonItem(Properties()), PokemonSelectingItem {
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName = "item.cobblemon.${ if (max) "max_elixir" else "elixir" }"
|
||||
override val returnItem = Items.GLASS_BOTTLE
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health > 0 && target.moveSet.any { it.currentPp < it.maxPp }
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?) = "elixir".let { if (!max) "$it 10" else it }
|
||||
}
|
||||
@@ -60,7 +62,7 @@ class ElixirItem(val max: Boolean) : CobblemonItem(Properties()), PokemonSelecti
|
||||
pokemon.moveSet.update()
|
||||
if (!player.isCreative) {
|
||||
stack.shrink(1)
|
||||
player.giveOrDropItemStack(ItemStack(Items.GLASS_BOTTLE))
|
||||
player.giveOrDropItemStack(ItemStack(bagItem.returnItem))
|
||||
}
|
||||
player.playSound(CobblemonSounds.MEDICINE_LIQUID_USE, 1F, 1F)
|
||||
InteractionResultHolder.success(stack)
|
||||
|
||||
@@ -25,6 +25,7 @@ import net.minecraft.world.InteractionResultHolder
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ItemNameBlockItem
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.level.Level
|
||||
|
||||
class EnergyRootItem(block: EnergyRootBlock, settings: Properties) : ItemNameBlockItem(block, settings), PokemonSelectingItem {
|
||||
@@ -33,6 +34,7 @@ class EnergyRootItem(block: EnergyRootBlock, settings: Properties) : ItemNameBlo
|
||||
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName = "item.cobblemon.energy_root"
|
||||
override val returnItem = Items.AIR
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health > 0 && target.health < target.maxHealth
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?): String {
|
||||
battlePokemon.effectedPokemon.decrementFriendship(CobblemonMechanics.remedies.getFriendshipDrop(runtime))
|
||||
|
||||
@@ -37,6 +37,7 @@ import net.minecraft.world.level.Level
|
||||
class EtherItem(val max: Boolean): CobblemonItem(Properties()), PokemonAndMoveSelectingItem {
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName = "item.cobblemon.${ if (max) "max_ether" else "ether" }"
|
||||
override val returnItem = Items.GLASS_BOTTLE
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health > 0 && target.moveSet.any { it.currentPp < it.maxPp }
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?) = "ether $data${ if (max) "" else " 10" }"
|
||||
}
|
||||
|
||||
@@ -22,11 +22,13 @@ import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.InteractionResultHolder
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.level.Level
|
||||
|
||||
class HealPowderItem : CobblemonItem(Properties()), PokemonSelectingItem {
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName = "item.cobblemon.heal_powder"
|
||||
override val returnItem = Items.AIR
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = canUseOnPokemon(target.effectedPokemon)
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?) = "cure_status"
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.item.interactive
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
@@ -71,26 +73,31 @@ class PotionItem(val type: PotionType) : CobblemonItem(Properties()), PokemonSel
|
||||
enum class PotionType(val amountToHeal: () -> ExpressionLike, val curesStatus: Boolean) : BagItem {
|
||||
POTION({ com.cobblemon.mod.common.CobblemonMechanics.potions.potionRestoreAmount }, false) {
|
||||
override val itemName = "item.cobblemon.potion"
|
||||
override val returnItem = Items.GLASS_BOTTLE
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?) = "potion ${genericRuntime.resolveInt(amountToHeal(), battlePokemon)}"
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health < target.maxHealth && target.health > 0
|
||||
},
|
||||
SUPER_POTION({ com.cobblemon.mod.common.CobblemonMechanics.potions.superPotionRestoreAmount }, false) {
|
||||
override val itemName = "item.cobblemon.super_potion"
|
||||
override val returnItem = Items.GLASS_BOTTLE
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?) = "potion ${genericRuntime.resolveInt(amountToHeal(), battlePokemon)}"
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health < target.maxHealth && target.health > 0
|
||||
},
|
||||
HYPER_POTION({ com.cobblemon.mod.common.CobblemonMechanics.potions.hyperPotionRestoreAmount }, false) {
|
||||
override val itemName = "item.cobblemon.hyper_potion"
|
||||
override val returnItem = Items.GLASS_BOTTLE
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?) = "potion ${genericRuntime.resolveInt(amountToHeal(), battlePokemon)}"
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health < target.maxHealth && target.health > 0
|
||||
},
|
||||
MAX_POTION({ 999999.0.asExpressionLike() }, false) {
|
||||
override val itemName = "item.cobblemon.max_potion"
|
||||
override val returnItem = Items.GLASS_BOTTLE
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?) = "potion ${battlePokemon.maxHealth - battlePokemon.health}"
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health < target.maxHealth && target.health > 0
|
||||
},
|
||||
FULL_RESTORE({ 999999.0.asExpressionLike() }, true) {
|
||||
override val itemName = "item.cobblemon.full_restore"
|
||||
override val returnItem = Items.GLASS_BOTTLE
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?) = "full_restore"
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health < target.maxHealth && target.health > 0
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.InteractionResultHolder
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.level.Level
|
||||
|
||||
class RemedyItem(val remedyStrength: String) : CobblemonItem(Properties()), PokemonSelectingItem {
|
||||
@@ -35,6 +36,7 @@ class RemedyItem(val remedyStrength: String) : CobblemonItem(Properties()), Poke
|
||||
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName = "item.cobblemon.${ remedyStrength.takeIf(NORMAL::equals)?.let { "${it}_" } }remedy" // remedy, fine_remedy, superb_remedy
|
||||
override val returnItem = Items.AIR
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health > 0 && target.health < target.maxHealth
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?): String {
|
||||
battlePokemon.effectedPokemon.decrementFriendship(CobblemonMechanics.remedies.getFriendshipDrop(runtime))
|
||||
|
||||
@@ -32,6 +32,7 @@ import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.InteractionResultHolder
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.level.Level
|
||||
import kotlin.math.ceil
|
||||
|
||||
@@ -44,6 +45,7 @@ import kotlin.math.ceil
|
||||
class ReviveItem(val max: Boolean): CobblemonItem(Properties()) {
|
||||
val bagItem = object : BagItem {
|
||||
override val itemName = "item.cobblemon.${ if (max) "max_revive" else "revive" }"
|
||||
override val returnItem = Items.AIR
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = target.health <= 0
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?) = "revive ${ if (max) "1" else "0.5" }"
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import net.minecraft.world.level.Level
|
||||
class StatusCureItem(val itemName: String, vararg val status: Status) : CobblemonItem(Properties()), PokemonSelectingItem {
|
||||
override val bagItem = object : BagItem {
|
||||
override val itemName = this@StatusCureItem.itemName
|
||||
override val returnItem = Items.AIR
|
||||
override fun canUse(battle: PokemonBattle, target: BattlePokemon) = canUseOnPokemon(target.effectedPokemon)
|
||||
override fun getShowdownInput(actor: BattleActor, battlePokemon: BattlePokemon, data: String?) = "cure_status${status.takeIf { it.isNotEmpty() }?.let { " ${it.joinToString(separator = " ") { it.showdownName } }" } ?: "" }"
|
||||
}
|
||||
@@ -51,7 +52,7 @@ class StatusCureItem(val itemName: String, vararg val status: Status) : Cobblemo
|
||||
player.playSound(CobblemonSounds.MEDICINE_SPRAY_USE, 1F, 1F)
|
||||
if (!player.isCreative) {
|
||||
stack.shrink(1)
|
||||
player.giveOrDropItemStack(ItemStack(Items.GLASS_BOTTLE))
|
||||
player.giveOrDropItemStack(ItemStack(bagItem.returnItem))
|
||||
}
|
||||
InteractionResultHolder.success(stack)
|
||||
} else {
|
||||
|
||||
+9
-3
@@ -25,7 +25,7 @@ class PlayerInteractOptionsPacket(
|
||||
val options: EnumSet<Options>,
|
||||
val targetId: UUID,
|
||||
val numericTargetId: Int,
|
||||
val selectedPokemonId: UUID
|
||||
val selectedPokemonId: UUID,
|
||||
) : NetworkPacket<PlayerInteractOptionsPacket> {
|
||||
companion object {
|
||||
val ID = cobblemonResource("player_interactions")
|
||||
@@ -46,9 +46,15 @@ class PlayerInteractOptionsPacket(
|
||||
}
|
||||
|
||||
enum class Options {
|
||||
BATTLE,
|
||||
SINGLE_BATTLE,
|
||||
DOUBLE_BATTLE,
|
||||
TRIPLE_BATTLE,
|
||||
MULTI_BATTLE,
|
||||
ROYAL_BATTLE,
|
||||
SPECTATE_BATTLE,
|
||||
TRADE
|
||||
TRADE,
|
||||
TEAM_REQUEST,
|
||||
TEAM_LEAVE,
|
||||
}
|
||||
|
||||
}
|
||||
+11
-6
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common.net.messages.client.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.battles.BattleFormat
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.network.chat.ComponentSerialization
|
||||
@@ -27,19 +28,23 @@ import java.util.*
|
||||
* @since August 5th, 2022
|
||||
*/
|
||||
class BattleChallengeNotificationPacket(
|
||||
val battleChallengeId: UUID,
|
||||
val challengerId: UUID,
|
||||
val challengerName: MutableComponent
|
||||
val battleChallengeId: UUID,
|
||||
val challengerIds: List<UUID>,
|
||||
val challengerNames: List<MutableComponent>,
|
||||
val battleFormat: BattleFormat
|
||||
): NetworkPacket<BattleChallengeNotificationPacket> {
|
||||
override val id = ID
|
||||
|
||||
constructor(battleChallengeId: UUID, challengerId: UUID, challengerName: MutableComponent, battleFormat: BattleFormat) : this(battleChallengeId, listOf(challengerId), listOf(challengerName), battleFormat)
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeUUID(battleChallengeId)
|
||||
buffer.writeUUID(challengerId)
|
||||
ComponentSerialization.TRUSTED_CONTEXT_FREE_STREAM_CODEC.encode(buffer, challengerName)
|
||||
buffer.writeCollection(challengerIds) { _, value -> buffer.writeUUID(value) }
|
||||
buffer.writeCollection(challengerNames) { _, value -> ComponentSerialization.TRUSTED_CONTEXT_FREE_STREAM_CODEC.encode(buffer, value) }
|
||||
battleFormat.saveToBuffer(buffer)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("battle_challenge_notification")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = BattleChallengeNotificationPacket(buffer.readUUID(), buffer.readUUID(), ComponentSerialization.TRUSTED_CONTEXT_FREE_STREAM_CODEC.decode(buffer).copy())
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = BattleChallengeNotificationPacket(buffer.readUUID(), buffer.readList { it.readUUID() }, buffer.readList { ComponentSerialization.TRUSTED_CONTEXT_FREE_STREAM_CODEC.decode(it).copy() } , BattleFormat.loadFromBuffer(buffer))
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.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.readString
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
|
||||
/**
|
||||
* Informs the client about a position swap occurring in the battle.
|
||||
*
|
||||
* Handled by [com.cobblemon.mod.common.client.net.battle.BattleSwapPokemonHandler].
|
||||
*
|
||||
* @author JazzMcNade
|
||||
* @since March 5th, 2024
|
||||
*/
|
||||
class BattleSwapPokemonPacket(val pnx: String) : NetworkPacket<BattleSwapPokemonPacket> {
|
||||
|
||||
override val id = ID
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeString(pnx)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("battle_swap_pokemon")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = BattleSwapPokemonPacket(buffer.readString())
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.callback.PartySelectPokemonDTO
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyCallbackPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.readText
|
||||
import com.cobblemon.mod.common.util.writeBox
|
||||
import com.cobblemon.mod.common.util.writeText
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import java.util.UUID
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
|
||||
/**
|
||||
* Packet sent when a player has joined a team. The responsibility
|
||||
* of this packet currently is to send a battle challenge message that includes
|
||||
* the keybind to challenge them back. In future this is likely to include information
|
||||
* about the battle.
|
||||
*
|
||||
* Handled by [com.cobblemon.mod.common.client.net.battle.TeamJoinNotificationHandler].
|
||||
*
|
||||
* @author JazzMcNade
|
||||
* @since April 9th, 2024
|
||||
*/
|
||||
class TeamJoinNotificationPacket(
|
||||
val teamMemberUUIDs: List<UUID>,
|
||||
val teamMemberNames: List<MutableComponent>,
|
||||
): NetworkPacket<TeamJoinNotificationPacket> {
|
||||
override val id = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeCollection(teamMemberUUIDs) { writer, value ->
|
||||
writer.writeUUID(value)
|
||||
}
|
||||
buffer.writeCollection(teamMemberNames) { _, v -> buffer.writeText(v) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("team_join_notification")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = TeamJoinNotificationPacket(
|
||||
buffer.readList { pb -> pb.readUUID() },
|
||||
buffer.readList { buffer.readText().copy() }.toMutableList()
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
+42
@@ -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.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.readText
|
||||
import com.cobblemon.mod.common.util.writeText
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Packet sent when a player joins or leaves a team that the client is currently a member of.
|
||||
*
|
||||
* Handled by [com.cobblemon.mod.common.client.net.battle.TeamRequestNotificationHandler].
|
||||
*
|
||||
* @author JazzMcNade
|
||||
* @since April 9th, 2024
|
||||
*/
|
||||
class TeamMemberAddNotificationPacket(
|
||||
val teamMemberUUID: UUID,
|
||||
val teamMemberName: MutableComponent,
|
||||
): NetworkPacket<TeamMemberAddNotificationPacket> {
|
||||
override val id = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeUUID(teamMemberUUID)
|
||||
buffer.writeText(teamMemberName)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("team_member_add_notification")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = TeamMemberAddNotificationPacket(buffer.readUUID(), buffer.readText().copy())
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.net.messages.client.battle
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import java.util.UUID
|
||||
|
||||
|
||||
/**
|
||||
* Packet sent when a player joins or leaves a team that the client is currently a member of.
|
||||
*
|
||||
* Handled by [com.cobblemon.mod.common.client.net.battle.TeamRequestNotificationHandler].
|
||||
*
|
||||
* @author JazzMcNade
|
||||
* @since April 9th, 2024
|
||||
*/
|
||||
class TeamMemberRemoveNotificationPacket(
|
||||
val teamMemberUUID: UUID,
|
||||
): NetworkPacket<TeamMemberRemoveNotificationPacket> {
|
||||
override val id = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeUUID(teamMemberUUID)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("team_member_remove_notification")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = TeamMemberRemoveNotificationPacket(buffer.readUUID())
|
||||
}
|
||||
}
|
||||
+32
@@ -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.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.util.UUID
|
||||
|
||||
/**
|
||||
* Packet fired to tell the client that a team request expired.
|
||||
*
|
||||
* @author JazzMcNade
|
||||
* @since April 4th, 2024
|
||||
*/
|
||||
class TeamRequestExpiredPacket(val teamRequestId: UUID) : NetworkPacket<TeamRequestExpiredPacket> {
|
||||
companion object {
|
||||
val ID = cobblemonResource("team_request_expired")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = TeamRequestExpiredPacket(buffer.readUUID())
|
||||
}
|
||||
|
||||
override val id = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeUUID(teamRequestId)
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.readText
|
||||
import com.cobblemon.mod.common.util.writeText
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Packet send when a player has requested to join a team. The responsibility
|
||||
* of this packet currently is to send a battle challenge message that includes
|
||||
* the keybind to challenge them back. In future this is likely to include information
|
||||
* about the battle.
|
||||
*
|
||||
* Handled by [com.cobblemon.mod.common.client.net.battle.TeamRequestNotificationHandler].
|
||||
*
|
||||
*/
|
||||
class TeamRequestNotificationPacket(
|
||||
val teamRequestId: UUID,
|
||||
val requesterId: UUID,
|
||||
val requesterName: MutableComponent,
|
||||
): NetworkPacket<TeamRequestNotificationPacket> {
|
||||
override val id = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeUUID(teamRequestId)
|
||||
buffer.writeUUID(requesterId)
|
||||
buffer.writeText(requesterName)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("team_request_notification")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = TeamRequestNotificationPacket(buffer.readUUID(), buffer.readUUID(), buffer.readText().copy())
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -9,18 +9,20 @@
|
||||
package com.cobblemon.mod.common.net.messages.server
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.battles.BattleFormat
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import java.util.UUID
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
class BattleChallengePacket(val targetedEntityId: Int, val selectedPokemonId: UUID) : NetworkPacket<BattleChallengePacket> {
|
||||
class BattleChallengePacket(val targetedEntityId: Int, val selectedPokemonId: UUID, val battleFormat: BattleFormat = BattleFormat.GEN_9_SINGLES) : NetworkPacket<BattleChallengePacket> {
|
||||
override val id = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeInt(this.targetedEntityId)
|
||||
buffer.writeUUID(this.selectedPokemonId)
|
||||
battleFormat.saveToBuffer(buffer)
|
||||
}
|
||||
companion object {
|
||||
val ID = cobblemonResource("battle_challenge")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = BattleChallengePacket(buffer.readInt(), buffer.readUUID())
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = BattleChallengePacket(buffer.readInt(), buffer.readUUID(), BattleFormat.loadFromBuffer(buffer))
|
||||
}
|
||||
}
|
||||
Loaded 100 of 149 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user