Compare commits

...
Author SHA1 Message Date
whatsy ec4dea23fc fix flashing 2026-10-26 16:11:26 -04:00
whatsy ed2ad46832 pc health bar display toggle 2026-10-26 15:31:48 -04:00
whatsy 48ff865c55 cleanup 2026-10-17 19:33:20 -04:00
whatsy e0abdb900d im running out of commit message ideas 2026-10-13 22:58:32 -04:00
whatsy 1b74c2906d Merge remote-tracking branch 'origin/pc-multiselect' into pc-multiselect
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/gui/pc/PCGUI.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/gui/pc/StorageWidget.kt
2026-10-13 20:12:50 -04:00
whatsy d3a616e308 tweaks + bugfixes 2026-10-13 14:14:26 -04:00
whatsy 9a06463871 mass release 2026-10-13 10:56:45 -04:00
whatsy 46b5a046ed highlight selected slots 2026-10-13 10:56:45 -04:00
whatsy ab811ead56 you can now batch move pokemon in the pc 2026-10-13 10:56:45 -04:00
whatsy 09a549ffcd implement draggable selection box in PC GUI 2026-10-13 10:56:45 -04:00
whatsy b393293025 mass release 2026-10-13 10:52:35 -04:00
whatsy 68a760a595 highlight selected slots 2026-10-10 18:18:24 -04:00
whatsy ae7bc6e681 you can now batch move pokemon in the pc 2026-10-09 22:57:48 -04:00
whatsy ed007fe429 implement draggable selection box in PC GUI 2026-10-09 20:51:04 -04:00
14 changed files with 593 additions and 75 deletions
+1
View File
@@ -2,6 +2,7 @@
## [1.8.3 (MONTH xth, 2026)](#1-8-3) ## [1.8.3 (MONTH xth, 2026)](#1-8-3)
### Additions ### Additions
- You can now drag to select multiple Pokémon inside the PC menu.
### Pokémon Added ### Pokémon Added
#### Generation 4 #### Generation 4
@@ -566,6 +566,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(SwapPartyPokemonPacket.ID, SwapPartyPokemonPacket::decode, SwapPartyPokemonHandler)) list.add(PacketRegisterInfo(SwapPartyPokemonPacket.ID, SwapPartyPokemonPacket::decode, SwapPartyPokemonHandler))
list.add(PacketRegisterInfo(MovePCPokemonPacket.ID, MovePCPokemonPacket::decode, MovePCPokemonHandler)) list.add(PacketRegisterInfo(MovePCPokemonPacket.ID, MovePCPokemonPacket::decode, MovePCPokemonHandler))
list.add(PacketRegisterInfo(MovePCPokemonBatchPacket.ID, MovePCPokemonBatchPacket::decode, MovePCPokemonBatchHandler))
list.add(PacketRegisterInfo(MovePartyPokemonPacket.ID, MovePartyPokemonPacket::decode, MovePartyPokemonHandler)) list.add(PacketRegisterInfo(MovePartyPokemonPacket.ID, MovePartyPokemonPacket::decode, MovePartyPokemonHandler))
list.add(PacketRegisterInfo(SwapPCPartyPokemonPacket.ID, SwapPCPartyPokemonPacket::decode, SwapPCPartyPokemonHandler)) list.add(PacketRegisterInfo(SwapPCPartyPokemonPacket.ID, SwapPCPartyPokemonPacket::decode, SwapPCPartyPokemonHandler))
@@ -29,6 +29,7 @@ import com.cobblemon.mod.common.net.messages.client.storage.pc.InitializePCPacke
import com.cobblemon.mod.common.net.messages.client.storage.pc.MoveClientPCPokemonPacket import com.cobblemon.mod.common.net.messages.client.storage.pc.MoveClientPCPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCPokemonPacket import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.UnlockPCBoxWallpaperPacket import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.UnlockPCBoxWallpaperPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonBatchPacket
import com.cobblemon.mod.common.pokemon.Pokemon import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.asTranslated import com.cobblemon.mod.common.util.asTranslated
@@ -270,6 +271,34 @@ open class PCStore(
} }
} }
fun moveBatch(moves: List<Triple<UUID, PCPosition, PCPosition>>): Boolean {
if (moves.isEmpty() || moves.size > MovePCPokemonBatchPacket.MAX_MOVES) return false
if (moves.map { it.second }.toSet().size != moves.size ||
moves.map { it.third }.toSet().size != moves.size ||
moves.map { it.first }.toSet().size != moves.size
) return false
if (moves.any { (_, source, target) -> !isValidPosition(source) || !isValidPosition(target) }) return false
val sourcePositions = moves.mapTo(mutableSetOf()) { it.second }
val pokemonMoves = moves.map { (pokemonID, source, target) ->
val pokemon = get(source)?.takeIf { it.uuid == pokemonID } ?: return false
Triple(source, target, pokemon)
}
if (pokemonMoves.any { (_, target, _) -> get(target) != null && target !in sourcePositions }) return false
pokemonMoves.forEach { (source, _, _) -> setAtPosition(source, null) }
pokemonMoves.forEach { (_, target, pokemon) -> setAtPosition(target, pokemon) }
pokemonMoves.forEach { (_, _, pokemon) ->
sendPacketToObservers(RemoveClientPokemonPacket(this, pokemon.uuid))
}
pokemonMoves.forEach { (_, target, pokemon) ->
sendPacketToObservers(SetPCPokemonPacket(uuid, target) { pokemon })
}
pcChangeObservable.emit(Unit)
return true
}
override fun loadPositionFromNBT(nbt: CompoundTag): StoreCoordinates<PCPosition> { override fun loadPositionFromNBT(nbt: CompoundTag): StoreCoordinates<PCPosition> {
return StoreCoordinates(this, PCPosition(nbt.getShort(DataKeys.STORE_BOX).toInt(), nbt.getByte(DataKeys.STORE_SLOT).toInt())) return StoreCoordinates(this, PCPosition(nbt.getShort(DataKeys.STORE_BOX).toInt(), nbt.getByte(DataKeys.STORE_SLOT).toInt()))
} }
@@ -111,6 +111,7 @@ object CobblemonClient {
val battleOverlay: BattleOverlay by lazy { BattleOverlay() } val battleOverlay: BattleOverlay by lazy { BattleOverlay() }
val pokedexUsageContext: PokedexUsageContext by lazy { PokedexUsageContext() } val pokedexUsageContext: PokedexUsageContext by lazy { PokedexUsageContext() }
val rideControlsOverlay: RideControlsOverlay by lazy { RideControlsOverlay() } val rideControlsOverlay: RideControlsOverlay by lazy { RideControlsOverlay() }
var pcHealthToggle: Boolean = false
fun onLogin() { fun onLogin() {
clientPlayerData = ClientGeneralPlayerData() clientPlayerData = ClientGeneralPlayerData()
@@ -29,7 +29,8 @@ class BoxStorageSlot(
val grabbedSlot = parent.grabbedSlot val grabbedSlot = parent.grabbedSlot
return if (grabbedSlot == null) { return if (grabbedSlot == null) {
true val pokemon = getPokemon()
pokemon == null || !parent.isInMultiSelection(pokemon, position)
} else { } else {
grabbedSlot.getPokemon() != getPokemon() grabbedSlot.getPokemon() != getPokemon()
} }
@@ -50,6 +50,7 @@ import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvent import net.minecraft.sounds.SoundEvent
import kotlin.isInitialized import kotlin.isInitialized
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.util.FastColor
class PCGUI( class PCGUI(
val pc: ClientPC, val pc: ClientPC,
@@ -98,6 +99,8 @@ class PCGUI(
private val buttonWallpaperResource = cobblemonResource("textures/gui/pc/pc_button_set_wallpaper.png") private val buttonWallpaperResource = cobblemonResource("textures/gui/pc/pc_button_set_wallpaper.png")
private val buttonInfoArrow = cobblemonResource("textures/gui/pc/info_arrow.png") private val buttonInfoArrow = cobblemonResource("textures/gui/pc/info_arrow.png")
private val buttonHealthToggleResource = cobblemonResource("textures/gui/pc/pc_button_toggle_health.png")
private val topSpacerResource = cobblemonResource("textures/gui/pc/pc_spacer_top.png") private val topSpacerResource = cobblemonResource("textures/gui/pc/pc_spacer_top.png")
private val bottomSpacerResource = cobblemonResource("textures/gui/pc/pc_spacer_bottom.png") private val bottomSpacerResource = cobblemonResource("textures/gui/pc/pc_spacer_bottom.png")
private val rightSpacerResource = cobblemonResource("textures/gui/pc/pc_spacer_right.png") private val rightSpacerResource = cobblemonResource("textures/gui/pc/pc_spacer_right.png")
@@ -129,6 +132,55 @@ class PCGUI(
var selectPointerOffsetIncrement = false var selectPointerOffsetIncrement = false
var displayOptions = false var displayOptions = false
private var dragStartX: Int? = null
private var dragStartY: Int? = null
private var dragEndX: Int? = null
private var dragEndY: Int? = null
private var selectionDragActive = false
data class SelectionArea(val left: Int, val top: Int, val right: Int, val bottom: Int)
private data class BoxGridBounds(val left: Int, val top: Int, val right: Int, val bottom: Int) {
fun contains(x: Int, y: Int) = x in left..< right && y >= top && y < bottom
}
private fun getBoxGridBounds(): BoxGridBounds {
val left = storageWidget.x
val top = storageWidget.y
return BoxGridBounds(
left,
top,
left + StorageWidget.SCREEN_WIDTH,
top + StorageWidget.SCREEN_HEIGHT
)
}
fun getSelectionArea(): SelectionArea? {
if (!selectionDragActive) return null
val startX = dragStartX ?: return null
val startY = dragStartY ?: return null
val endX = dragEndX ?: return null
val endY = dragEndY ?: return null
val bounds = getBoxGridBounds()
if (!bounds.contains(startX, startY)) return null
val columns = 6
val rows = 5
val slotPitch = StorageSlot.SIZE + StorageWidget.BOX_SLOT_PADDING
val gridLeft = bounds.left + StorageWidget.BOX_SLOT_START_OFFSET_X
val gridTop = bounds.top + StorageWidget.BOX_SLOT_START_OFFSET_Y + if (displayOptions) 5 else 0
val startColumn = ((startX - gridLeft) / slotPitch).coerceIn(0, columns - 1)
val endColumn = (((endX.coerceIn(bounds.left, bounds.right - 1)) - gridLeft) / slotPitch).coerceIn(0, columns - 1)
val startRow = ((startY - gridTop) / slotPitch).coerceIn(0, rows - 1)
val endRow = (((endY.coerceIn(bounds.top, bounds.bottom - 1)) - gridTop) / slotPitch).coerceIn(0, rows - 1)
return SelectionArea(
gridLeft + minOf(startColumn, endColumn) * slotPitch, // left
gridTop + minOf(startRow, endRow) * slotPitch, // top
gridLeft + (maxOf(startColumn, endColumn) * slotPitch) + StorageSlot.SIZE, // right
gridTop + (maxOf(startRow, endRow) * slotPitch) + StorageSlot.SIZE // bottom
)
}
override fun renderBlurredBackground(delta: Float) {} override fun renderBlurredBackground(delta: Float) {}
override fun renderMenuBackground(context: GuiGraphics) {} override fun renderMenuBackground(context: GuiGraphics) {}
@@ -219,7 +271,6 @@ class PCGUI(
storageWidget.setupStorageSlots() storageWidget.setupStorageSlots()
for (button in optionButtons) { for (button in optionButtons) {
button.visible = displayOptions button.visible = displayOptions
button.highlighted = false
} }
}.also { it.highlighted = displayOptions } }.also { it.highlighted = displayOptions }
) )
@@ -250,12 +301,28 @@ class PCGUI(
configuration.showParty = isVisible configuration.showParty = isVisible
wallpaperWidget.visible = !isVisible wallpaperWidget.visible = !isVisible
(it as IconButton).highlighted = !isVisible (it as IconButton).highlighted = !isVisible
if (!unseenWallpapers.isEmpty()) MarkPCBoxWallpapersSeenPacket(unseenWallpapers).sendToServer() if (unseenWallpapers.isNotEmpty()) MarkPCBoxWallpapersSeenPacket(unseenWallpapers).sendToServer()
}.also { }.also {
it.visible = displayOptions it.visible = displayOptions
optionButtons.add(it) optionButtons.add(it)
}) })
val healthButton = this.addRenderableWidget(IconButton(
pX = x + 230,
pY = y + 31,
buttonWidth = 20,
buttonHeight = 20,
resource = buttonHealthToggleResource,
label = "toggle_health_bars",
) {
CobblemonClient.pcHealthToggle = !CobblemonClient.pcHealthToggle
(it as IconButton).highlighted = CobblemonClient.pcHealthToggle
}.also {
it.visible = displayOptions
optionButtons.add(it)
})
healthButton.highlighted = CobblemonClient.pcHealthToggle
PokemonSortMode.entries.forEachIndexed { index, sortType -> PokemonSortMode.entries.forEachIndexed { index, sortType ->
val typeName = sortType.name.lowercase() val typeName = sortType.name.lowercase()
this.addRenderableWidget(IconButton( this.addRenderableWidget(IconButton(
@@ -315,6 +382,20 @@ class PCGUI(
super.init() super.init()
} }
private fun renderSelectionBox(context: GuiGraphics, area: SelectionArea) {
context.pose().pushPose()
context.pose().translate(0.0, 0.0, 1500.0)
val borderColor = FastColor.ARGB32.color(255, 205, 212, 255)
val bgColor = FastColor.ARGB32.color(75, 133, 183, 230)
val thickness = 1
context.fill(area.left, area.top, area.right, area.bottom, bgColor)
context.fill(area.left, area.top, area.right, area.top + thickness, borderColor)
context.fill(area.left, area.bottom - thickness, area.right, area.bottom, borderColor)
context.fill(area.left, area.top, area.left + thickness, area.bottom, borderColor)
context.fill(area.right - thickness, area.top, area.right, area.bottom, borderColor)
context.pose().popPose()
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) { override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val matrices = context.pose() val matrices = context.pose()
renderBackground(context, mouseX, mouseY, delta) renderBackground(context, mouseX, mouseY, delta)
@@ -455,6 +536,20 @@ class PCGUI(
shadow = true shadow = true
) )
// var dexNo = pokemon.species.nationalPokedexNumber.toString()
// while (dexNo.length < 4) {
// dexNo = "0$dexNo"
// }
// drawScaledText(
// context = context,
// font = CobblemonResources.DEFAULT_LARGE,
// text = Component.literal(dexNo).bold(),
// x = x + 38,
// y = y + 1.5,
// shadow = true,
// scale = 0.85f
// )
drawScaledText( drawScaledText(
context = context, context = context,
font = CobblemonResources.DEFAULT_LARGE, font = CobblemonResources.DEFAULT_LARGE,
@@ -685,12 +780,14 @@ class PCGUI(
scale = SCALE scale = SCALE
) )
if (!optionButtons.isEmpty() && displayOptions) { if (optionButtons.isNotEmpty() && displayOptions) {
for (button in optionButtons) button.showAlt = hasShiftDown() for (button in optionButtons) button.showAlt = hasShiftDown()
} }
super.render(context, mouseX, mouseY, delta) super.render(context, mouseX, mouseY, delta)
getSelectionArea()?.let { renderSelectionBox(context, it) }
// Item Tooltip // Item Tooltip
if (pokemon != null) { if (pokemon != null) {
val displayedItem = if (showCosmeticItem) pokemon.cosmeticItem else pokemon.heldItemNoCopy() val displayedItem = if (showCosmeticItem) pokemon.cosmeticItem else pokemon.heldItemNoCopy()
@@ -712,6 +809,20 @@ class PCGUI(
} }
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
storageWidget.clearPendingStorageSlotClick()
selectionDragActive = false
val clickX = mouseX.toInt()
val clickY = mouseY.toInt()
if (storageWidget.multiSelection == null && button == 0 &&
getBoxGridBounds().contains(clickX, clickY) &&
storageWidget.pastureWidget == null) {
dragStartX = clickX
dragStartY = clickY
} else {
dragStartX = null
dragStartY = null
}
// Trigger child component function as they will need to check the entire screen click area // Trigger child component function as they will need to check the entire screen click area
if (::boxNameWidget.isInitialized) boxNameWidget.mouseClicked(mouseX, mouseY, button) if (::boxNameWidget.isInitialized) boxNameWidget.mouseClicked(mouseX, mouseY, button)
return super.mouseClicked(mouseX, mouseY, button) return super.mouseClicked(mouseX, mouseY, button)
@@ -740,9 +851,43 @@ class PCGUI(
storageWidget.pastureWidget?.let { pasture -> storageWidget.pastureWidget?.let { pasture ->
if (pasture.pastureScrollList.isHovered) pasture.pastureScrollList.mouseDragged(mouseX, mouseY, button, deltaX, deltaY) if (pasture.pastureScrollList.isHovered) pasture.pastureScrollList.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
} }
if (button == 0 && storageWidget.pastureWidget == null) {
val startX = dragStartX
val startY = dragStartY
if (startX != null && startY != null) {
val offsetX = mouseX - startX
val offsetY = mouseY - startY
if (offsetX * offsetX + offsetY * offsetY >= 25.0) {
selectionDragActive = true
storageWidget.clearPendingStorageSlotClick()
dragEndX = mouseX.toInt()
dragEndY = mouseY.toInt()
}
}
}
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY) return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
} }
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
val handled = super.mouseReleased(mouseX, mouseY, button)
if (button == 0) {
if (selectionDragActive) {
dragEndX = mouseX.toInt()
dragEndY = mouseY.toInt()
storageWidget.updateSelection()
storageWidget.clearPendingStorageSlotClick()
} else {
storageWidget.completePendingStorageSlotClick(mouseX, mouseY)
}
}
dragStartX = null
dragStartY = null
dragEndX = null
dragEndY = null
selectionDragActive = false
return handled
}
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
val boxNameFocused = this::boxNameWidget.isInitialized && boxNameWidget.isFocused val boxNameFocused = this::boxNameWidget.isInitialized && boxNameWidget.isFocused
val filterFocused = this::filterWidget.isInitialized && filterWidget.isFocused val filterFocused = this::filterWidget.isInitialized && filterWidget.isFocused
@@ -0,0 +1,37 @@
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.pokemon.Pokemon
data class PCMultiSelection(
private val grid: MutableList<MutableList<Pokemon?>> = mutableListOf(),
val sourceBox: Int = 0,
val sourceColumn: Int = 0,
val sourceRow: Int = 0
) {
fun getHorizontalSize(): Int = grid.firstOrNull()?.size ?: 0
fun getVerticalSize(): Int = grid.size
constructor(horizontalSize: Int, verticalSize: Int, sourceBox: Int, sourceColumn: Int, sourceRow: Int) : this(
MutableList(verticalSize) { MutableList<Pokemon?>(horizontalSize) { null } },
sourceBox,
sourceColumn,
sourceRow
)
fun getPokemon(x: Int, y: Int): Pokemon? {
return grid[y][x]
}
fun setPokemon(x: Int, y: Int, pokemon: Pokemon?) {
grid[y][x] = pokemon
}
fun pokemonEntries(): List<Triple<Int, Int, Pokemon>> {
return grid.flatMapIndexed { y, row ->
row.mapIndexedNotNull { x, pokemon -> pokemon?.let { Triple(x, y, it) } }
}
}
fun getPokemonCount(): Int = pokemonEntries().size
}
@@ -45,11 +45,13 @@ class ReleaseButton(
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0, vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
textureHeight = HEIGHT * 2 textureHeight = HEIGHT * 2
) )
val pokemonCount = parent.multiSelection?.getPokemonCount() ?: 0
drawScaledText( drawScaledText(
context = context, context = context,
font = CobblemonResources.DEFAULT_LARGE, font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.pc.release").bold(), text = if (pokemonCount > 1)
{ lang("ui.pc.release_all").bold() } else { lang("ui.pc.release").bold() },
x = x + (WIDTH / 2), x = x + (WIDTH / 2),
y = y + 3.5, y = y + 3.5,
centered = true, centered = true,
@@ -10,10 +10,11 @@ package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.Cobblemon import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.gui.blitk import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.gui.CobblemonRenderable import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.PokemonGUIAnimationStyle
import com.cobblemon.mod.common.client.gui.drawProfilePokemon import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pasture.PasturePCGUIConfiguration import com.cobblemon.mod.common.client.gui.pasture.PasturePCGUIConfiguration
import com.cobblemon.mod.common.client.gui.PokemonGUIAnimationStyle
import com.cobblemon.mod.common.client.render.drawScaledText import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
@@ -22,13 +23,19 @@ import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import juuxel.adorn.util.Colors
import net.minecraft.client.Minecraft import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button import net.minecraft.client.gui.components.Button
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.sounds.SoundManager import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.util.Mth
import org.joml.Quaternionf import org.joml.Quaternionf
import org.joml.Vector3f import org.joml.Vector3f
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.roundToInt
open class StorageSlot( open class StorageSlot(
x: Int, y: Int, x: Int, y: Int,
@@ -54,9 +61,17 @@ open class StorageSlot(
} }
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) { override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val selectionArea = parent.pcGui.getSelectionArea()
if (selectionArea != null && parent.getSelectedPokemon(selectionArea).contains(this)) {
val highlightColor = 0x40FFFFFF
context.fill(x + 1, y, x + (SIZE - 1), y + SIZE, highlightColor)
context.fill(x, y + 1, x + 1, y + (SIZE - 1), highlightColor)
context.fill(x + SIZE - 1, y + 1, x + SIZE, y + (SIZE - 1), highlightColor)
}
if (shouldRender()) { if (shouldRender()) {
renderSlot(context, x, y, delta) renderSlot(context, x, y, delta)
} }
} }
fun renderSlot(context: GuiGraphics, posX: Int, posY: Int, partialTicks: Float) { fun renderSlot(context: GuiGraphics, posX: Int, posY: Int, partialTicks: Float) {
@@ -130,6 +145,23 @@ open class StorageSlot(
matrixStack = matrices matrixStack = matrices
) )
} }
// Pokemon health
// Taken from vanilla durability rendering
val currentHealth = pokemon.currentHealth
val maxHealth = pokemon.maxHealth.toFloat()
if (currentHealth < maxHealth && CobblemonClient.pcHealthToggle) {
val damageValue = maxHealth - currentHealth
val f = max(0.0f, (maxHealth - (maxHealth - currentHealth)) / maxHealth)
val color = Mth.hsvToRgb(f / 3.0f, 1.0f, 1.0f)
val totalWidth = 12
val width = (totalWidth - damageValue * totalWidth / pokemon.maxHealth.toFloat()).roundToInt();
val k = posX + 1
val l = posY + 6
val bgColor = if (this is PartyStorageSlot) Colors.BLACK else 0xFF3E3E3E.toInt()
context.fill(RenderType.guiOverlay(), k, l, k + totalWidth, l + 1, bgColor)
context.fill(RenderType.guiOverlay(), k, l, k + width, l + 1, color or bgColor)
}
matrices.popPose() matrices.popPose()
} }
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.CobblemonNetwork import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.CobblemonSounds import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.storage.StorePosition
import com.cobblemon.mod.common.api.storage.party.PartyPosition import com.cobblemon.mod.common.api.storage.party.PartyPosition
import com.cobblemon.mod.common.api.storage.pc.PCPosition import com.cobblemon.mod.common.api.storage.pc.PCPosition
import com.cobblemon.mod.common.api.text.bold import com.cobblemon.mod.common.api.text.bold
@@ -39,8 +38,9 @@ import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.screens.Screen import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent import net.minecraft.sounds.SoundEvent
import java.util.UUID
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonBatchPacket.PCPokemonMove
class StorageWidget( class StorageWidget(
pX: Int, pY: Int, pX: Int, pY: Int,
@@ -77,9 +77,14 @@ class StorageWidget(
var displayConfirmRelease = false var displayConfirmRelease = false
var screenLoaded = false var screenLoaded = false
var selectedPosition: StorePosition? = null var selectedPosition: PartyPosition? = null
var grabbedSlot: GrabbedStorageSlot? = null var grabbedSlot: GrabbedStorageSlot? = null
var multiSelection: PCMultiSelection? = null
private val multiGrabbedSlots = mutableMapOf<UUID, GrabbedStorageSlot>()
private var pendingStorageSlotClick: Button? = null
private var movePending = false
var box = 0 var box = 0
set(value) { set(value) {
field = field =
@@ -113,16 +118,22 @@ class StorageWidget(
subKey = "ui.generic.yes", subKey = "ui.generic.yes",
onPress = { onPress = {
if (canDeleteSelected() && displayConfirmRelease) { if (canDeleteSelected() && displayConfirmRelease) {
val position = selectedPosition ?: return@ReleaseConfirmButton val selection = multiSelection
val pokemon = getSelectedPokemon() ?: return@ReleaseConfirmButton if (selection != null) {
selection.pokemonEntries().forEach { (column, row, pokemon) ->
val position = PCPosition(
selection.sourceBox,
(selection.sourceRow + row) * 6 + selection.sourceColumn + column
)
CobblemonNetwork.sendToServer(ReleasePCPokemonPacket(pokemon.uuid, position))
}
} else {
val position = selectedPosition ?: return@ReleaseConfirmButton
val pokemon = getSelectedPokemon() ?: return@ReleaseConfirmButton
val packet = when (position) { CobblemonNetwork.sendToServer(ReleasePartyPokemonPacket(pokemon.uuid, position))
is PartyPosition -> ReleasePartyPokemonPacket(pokemon.uuid, position)
is PCPosition -> ReleasePCPokemonPacket(pokemon.uuid, position)
else -> return@ReleaseConfirmButton
} }
CobblemonNetwork.sendToServer(packet)
playSound(CobblemonSounds.PC_RELEASE) playSound(CobblemonSounds.PC_RELEASE)
resetSelected() resetSelected()
displayConfirmRelease = false displayConfirmRelease = false
@@ -149,9 +160,8 @@ class StorageWidget(
} }
fun canDeleteSelected(): Boolean { fun canDeleteSelected(): Boolean {
return !(selectedPosition is PartyPosition && party.filterNotNull().size <= 1) multiSelection?.let { return it.pokemonEntries().isNotEmpty() }
&& selectedPosition != null return party.filterNotNull().size > 1 && selectedPosition != null && grabbedSlot != null
&& grabbedSlot != null
} }
fun setupStorageSlots() { fun setupStorageSlots() {
@@ -170,7 +180,7 @@ class StorageWidget(
parent = this, parent = this,
pc = pc, pc = pc,
position = PCPosition(box, index), position = PCPosition(box, index),
onPress = { this.onStorageSlotClicked(it) } onPress = { pendingStorageSlotClick = it }
).also { widget -> ).also { widget ->
this.addWidget(widget) this.addWidget(widget)
this.boxSlots.add(widget) this.boxSlots.add(widget)
@@ -200,7 +210,7 @@ class StorageWidget(
parent = this, parent = this,
party = party, party = party,
position = PartyPosition(partyIndex), position = PartyPosition(partyIndex),
onPress = { this.onStorageSlotClicked(it) } onPress = { pendingStorageSlotClick = it }
).also { widget -> ).also { widget ->
this.addWidget(widget) this.addWidget(widget)
this.partySlots.add(widget) this.partySlots.add(widget)
@@ -210,14 +220,117 @@ class StorageWidget(
private fun getSelectedPokemon(): Pokemon? { private fun getSelectedPokemon(): Pokemon? {
val selectedPosition = this.selectedPosition ?: return null val selectedPosition = this.selectedPosition ?: return null
return when (selectedPosition) { return party.get(selectedPosition)
is PCPosition -> pc.get(selectedPosition) }
is PartyPosition -> party.get(selectedPosition)
else -> null fun getSelectedPokemon(area: PCGUI.SelectionArea): List<BoxStorageSlot> {
return boxSlots.filter { slot ->
slot.x < area.right && slot.x + StorageSlot.SIZE > area.left &&
slot.y < area.bottom && slot.y + StorageSlot.SIZE > area.top &&
slot.getPokemon()?.let(pcGui.search::passes) == true
} }
} }
fun clearPendingStorageSlotClick() {
pendingStorageSlotClick = null
}
fun completePendingStorageSlotClick(mouseX: Double, mouseY: Double) {
val button = pendingStorageSlotClick ?: return
pendingStorageSlotClick = null
if (button.isMouseOver(mouseX, mouseY)) {
onStorageSlotClicked(button)
}
}
fun updateSelection() {
val area = pcGui.getSelectionArea()
if (area == null) {
multiSelection = null
return
}
selectedPosition = null
grabbedSlot = null
pcGui.setPreviewPokemon(null)
val selectedSlots = getSelectedPokemon(area)
if (selectedSlots.isEmpty()) {
multiSelection = null
return
}
val minColumn = selectedSlots.minOf { it.position.slot % 6 }
val maxColumn = selectedSlots.maxOf { it.position.slot % 6 }
val minRow = selectedSlots.minOf { it.position.slot / 6 }
val maxRow = selectedSlots.maxOf { it.position.slot / 6 }
val selection = PCMultiSelection(maxColumn - minColumn + 1, maxRow - minRow + 1, box, minColumn, minRow)
selectedSlots.forEach { slot ->
val pokemon = slot.getPokemon() ?: return@forEach
val slotIndex = slot.position.slot
val column = slotIndex % 6
val row = slotIndex / 6
if (column in minColumn..maxColumn && row in minRow..maxRow) {
selection.setPokemon(column - minColumn, row - minRow, pokemon)
}
}
playSound(CobblemonSounds.PC_GRAB)
multiSelection = selection
}
private fun moveMultiSelectionTo(destination: PCPosition): Boolean {
val selection = multiSelection ?: return false
val width = selection.getHorizontalSize()
val height = selection.getVerticalSize()
val destinationColumn = destination.slot % 6
val destinationRow = destination.slot / 6
if (destinationColumn + width > 6 || destinationRow + height > 5) return false
val moves = mutableListOf<PCPokemonMove>()
val selectedSources = mutableSetOf<PCPosition>()
for (row in 0 until height) {
for (column in 0 until width) {
val pokemon = selection.getPokemon(column, row) ?: continue
val source = PCPosition(selection.sourceBox, (selection.sourceRow + row) * 6 + selection.sourceColumn + column)
val target = PCPosition(destination.box, (destinationRow + row) * 6 + destinationColumn + column)
selectedSources.add(source)
moves.add(PCPokemonMove(pokemon.uuid, source, target))
}
}
if (moves.isEmpty()) return false
for (row in 0 until height) {
for (column in 0 until width) {
if (selection.getPokemon(column, row) == null) continue
val target = PCPosition(destination.box, (destinationRow + row) * 6 + destinationColumn + column)
val displacedPokemon = pc.get(target) ?: continue
if (target in selectedSources) continue
val displacedSource = PCPosition(
selection.sourceBox,
(selection.sourceRow + row) * 6 + selection.sourceColumn + column
)
if (pc.get(displacedSource) != null && displacedSource !in selectedSources) return false
moves.add(PCPokemonMove(displacedPokemon.uuid, target, displacedSource))
}
}
if (moves.map { it.newPosition }.toSet().size != moves.size) return false
MovePCPokemonBatchPacket(moves).sendToServer()
return true
}
fun isInMultiSelection(pokemon: Pokemon, position: PCPosition): Boolean {
val selection = multiSelection ?: return false
return selection.sourceBox == position.box && selection.pokemonEntries().any { it.third == pokemon }
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) { override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
updatePendingMove()
val matrices = context.pose() val matrices = context.pose()
// Party Label // Party Label
if (pcGui.configuration.showParty) { if (pcGui.configuration.showParty) {
@@ -240,11 +353,14 @@ class StorageWidget(
shadow = true shadow = true
) )
val pokemonCount = multiSelection?.getPokemonCount() ?: 0
if (canDeleteSelected() && displayConfirmRelease) { if (canDeleteSelected() && displayConfirmRelease) {
drawScaledText( drawScaledText(
context = context, context = context,
font = CobblemonResources.DEFAULT_LARGE, font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.pc.release").bold(), text = if (pokemonCount > 1)
{ lang("ui.pc.release_all").bold() } else { lang("ui.pc.release").bold() },
x = x + 223, x = x + 223,
y = y + 119, y = y + 119,
centered = true centered = true
@@ -308,6 +424,7 @@ class StorageWidget(
slot.render(context, mouseX, mouseY, delta) slot.render(context, mouseX, mouseY, delta)
val pokemon = slot.getPokemon() val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY) if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
&& multiSelection == null
&& pokemon != null && pokemon != pcGui.previewPokemon && pokemon != null && pokemon != pcGui.previewPokemon
) { ) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: false) pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: false)
@@ -324,6 +441,7 @@ class StorageWidget(
slot.render(context, mouseX, mouseY, delta) slot.render(context, mouseX, mouseY, delta)
val pokemon = slot.getPokemon() val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY) if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
&& multiSelection == null
&& pokemon != null && pokemon != pcGui.previewPokemon && pokemon != null && pokemon != pcGui.previewPokemon
) { ) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: true) pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: true)
@@ -335,10 +453,32 @@ class StorageWidget(
pastureWidget?.render(context, mouseX, mouseY, delta) pastureWidget?.render(context, mouseX, mouseY, delta)
matrices.pushPose() matrices.pushPose()
matrices.translate(0.0, 0.0, 1000.0) matrices.translate(0.0, 0.0, 1000.0)
grabbedSlot?.render(context, mouseX, mouseY, delta) renderSelectedPokemon(context, mouseX, mouseY, delta)
matrices.popPose() matrices.popPose()
} }
private fun renderSelectedPokemon(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
if (movePending) return
val selection = multiSelection
if (selection == null) {
grabbedSlot?.render(context, mouseX, mouseY, delta)
return
}
val slotOffset = StorageSlot.SIZE + BOX_SLOT_PADDING
selection.pokemonEntries().forEach { (column, row, pokemon) ->
val grabbed = multiGrabbedSlots.getOrPut(pokemon.uuid) {
GrabbedStorageSlot(0, 0, this, pokemon)
}
grabbed.render(
context,
mouseX + column * slotOffset,
mouseY + row * slotOffset,
delta
)
}
}
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean { override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (displayConfirmRelease) { if (displayConfirmRelease) {
if (releaseYesButton.isHovered(pMouseX, pMouseY)) releaseYesButton.mouseClicked(pMouseX, pMouseY, pButton) if (releaseYesButton.isHovered(pMouseX, pMouseY)) releaseYesButton.mouseClicked(pMouseX, pMouseY, pButton)
@@ -371,23 +511,55 @@ class StorageWidget(
else -> return else -> return
} }
val clickedPokemon = when (clickedPosition) {
is PCPosition -> pc.get(clickedPosition)
is PartyPosition -> party.get(clickedPosition)
else -> null
}
multiSelection?.let { selection ->
when (clickedPosition) {
is PCPosition -> {
if (clickedPosition.box == selection.sourceBox &&
clickedPosition.slot == selection.sourceRow * 6 + selection.sourceColumn
) {
resetSelected()
} else if (moveMultiSelectionTo(clickedPosition)) {
playSound(CobblemonSounds.PC_DROP)
movePending = true
}
}
is PartyPosition -> {
val selectedPokemon = selection.pokemonEntries().singleOrNull() ?: return
val (column, row, pokemon) = selectedPokemon
val source = PCPosition(
selection.sourceBox,
(selection.sourceRow + row) * 6 + selection.sourceColumn + column
)
if (pc.get(source) !== pokemon) return
val packet = clickedPokemon?.let {
SwapPCPartyPokemonPacket(it.uuid, clickedPosition, pokemon.uuid, source)
} ?: MovePCPokemonToPartyPacket(pokemon.uuid, source, clickedPosition)
packet.sendToServer()
playSound(CobblemonSounds.PC_DROP)
movePending = true
}
else -> Unit
}
return
}
// Reset release confirmation // Reset release confirmation
displayConfirmRelease = false displayConfirmRelease = false
// Clicking on itself, so unselect position // Clicking on itself, so unselect position
if (this.selectedPosition != null && this.selectedPosition == clickedPosition) { if (selectedPosition == clickedPosition) {
if (grabbedSlot != null) playSound(CobblemonSounds.PC_DROP) if (grabbedSlot != null) playSound(CobblemonSounds.PC_DROP)
resetSelected() resetSelected()
return return
} }
// Check for selecting a Pokémon // Check for selecting a Pokémon
val clickedPokemon = when(button) {
is BoxStorageSlot -> pc.get(clickedPosition as PCPosition)
is PartyStorageSlot -> party.get(clickedPosition as PartyPosition)
else -> null
}
val selectOverride = pcGui.configuration.selectOverride val selectOverride = pcGui.configuration.selectOverride
if (selectOverride != null) { if (selectOverride != null) {
selectOverride(pcGui, clickedPosition, clickedPokemon) selectOverride(pcGui, clickedPosition, clickedPokemon)
@@ -420,59 +592,47 @@ class StorageWidget(
} }
} }
this.selectedPosition = clickedPosition if (clickedPosition is PCPosition) {
this.pcGui.setPreviewPokemon(clickedPokemon, pcGui.isPreviewInParty ?: false) val column = clickedPosition.slot % 6
grabbedSlot = GrabbedStorageSlot( val row = clickedPosition.slot / 6
x = button.x, multiSelection = PCMultiSelection(1, 1, clickedPosition.box, column, row).also {
y = button.y, it.setPokemon(0, 0, clickedPokemon)
parent = this, }
pokemon = clickedPokemon playSound(CobblemonSounds.PC_GRAB)
) return
playSound(CobblemonSounds.PC_GRAB) } else if (clickedPosition is PartyPosition) {
this.selectedPosition = clickedPosition
this.pcGui.setPreviewPokemon(clickedPokemon, pcGui.isPreviewInParty ?: false)
grabbedSlot = GrabbedStorageSlot(
x = button.x,
y = button.y,
parent = this,
pokemon = clickedPokemon
)
playSound(CobblemonSounds.PC_GRAB)
}
} }
} else { } else {
// Handle movement within the Box val sourcePosition = selectedPosition ?: return
val selectedPokemon = when(this.selectedPosition) { val selectedPokemon = party.get(sourcePosition) ?: return
is PCPosition -> pc.get(this.selectedPosition as PCPosition)
is PartyPosition -> party.get(this.selectedPosition as PartyPosition)
else -> null
} ?: return
// Box to Box
if (this.selectedPosition is PCPosition && clickedPosition is PCPosition) {
val packet = clickedPokemon?.let { SwapPCPokemonPacket(it.uuid, clickedPosition, selectedPokemon.uuid, this.selectedPosition as PCPosition) }
?: MovePCPokemonPacket(selectedPokemon.uuid, selectedPosition as PCPosition, clickedPosition)
packet.sendToServer()
playSound(CobblemonSounds.PC_DROP)
resetSelected()
}
// Box to Party
else if (this.selectedPosition is PCPosition && clickedPosition is PartyPosition) {
val packet = clickedPokemon?.let { SwapPCPartyPokemonPacket(clickedPokemon.uuid, clickedPosition, selectedPokemon.uuid, this.selectedPosition as PCPosition) }
?: MovePCPokemonToPartyPacket(selectedPokemon.uuid, this.selectedPosition as PCPosition, clickedPosition)
packet.sendToServer()
playSound(CobblemonSounds.PC_DROP)
resetSelected()
}
// Party to Box // Party to Box
else if (this.selectedPosition is PartyPosition && clickedPosition is PCPosition) { if (clickedPosition is PCPosition) {
if (ServerSettings.preventCompletePartyDeposit && this.party.filterNotNull().size == 1 && clickedPokemon == null) { if (ServerSettings.preventCompletePartyDeposit && this.party.filterNotNull().size == 1 && clickedPokemon == null) {
return return
} }
val packet = clickedPokemon?.let { SwapPCPartyPokemonPacket(selectedPokemon.uuid, this.selectedPosition as PartyPosition, clickedPokemon.uuid, clickedPosition) } val packet = clickedPokemon?.let { SwapPCPartyPokemonPacket(selectedPokemon.uuid, sourcePosition, clickedPokemon.uuid, clickedPosition) }
?: MovePartyPokemonToPCPacket(selectedPokemon.uuid, this.selectedPosition as PartyPosition, clickedPosition) ?: MovePartyPokemonToPCPacket(selectedPokemon.uuid, sourcePosition, clickedPosition)
packet.sendToServer() packet.sendToServer()
playSound(CobblemonSounds.PC_DROP) playSound(CobblemonSounds.PC_DROP)
resetSelected() movePending = true
} }
// Party to Party // Party to Party
else if (this.selectedPosition is PartyPosition && clickedPosition is PartyPosition) { else if (clickedPosition is PartyPosition) {
val packet = clickedPokemon?.let { SwapPartyPokemonPacket(it.uuid, clickedPosition, selectedPokemon.uuid, this.selectedPosition as PartyPosition) } val packet = clickedPokemon?.let { SwapPartyPokemonPacket(it.uuid, clickedPosition, selectedPokemon.uuid, sourcePosition) }
?: MovePartyPokemonPacket(selectedPokemon.uuid, selectedPosition as PartyPosition, clickedPosition) ?: MovePartyPokemonPacket(selectedPokemon.uuid, sourcePosition, clickedPosition)
packet.sendToServer() packet.sendToServer()
playSound(CobblemonSounds.PC_DROP) playSound(CobblemonSounds.PC_DROP)
resetSelected() movePending = true
} }
} }
} }
@@ -480,6 +640,36 @@ class StorageWidget(
fun resetSelected() { fun resetSelected() {
selectedPosition = null selectedPosition = null
grabbedSlot = null grabbedSlot = null
multiSelection = null
multiGrabbedSlots.clear()
pcGui.setPreviewPokemon(null) pcGui.setPreviewPokemon(null)
} }
// annoyingly complex solution, but server/client desync causes flashing when moving pokemon. this should fix it
// if you can think of a simpler solution, feel free to implement it
private fun updatePendingMove() {
if (!movePending) return
val selection = multiSelection
if (selection != null) {
val updated = selection.pokemonEntries().any { (column, row, pokemon) ->
val source = PCPosition(selection.sourceBox, (selection.sourceRow + row) * 6 + selection.sourceColumn + column)
pc.get(source)?.uuid != pokemon.uuid
}
if (updated) {
movePending = false
resetSelected()
}
return
}
val source = selectedPosition
val pokemon = grabbedSlot?.getPokemon()
if (source != null && pokemon != null) {
if (party.get(source)?.uuid != pokemon.uuid) {
movePending = false
resetSelected()
}
}
}
} }
@@ -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://www.mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.server.storage.pc
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.storage.pc.PCPosition
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readPCPosition
import com.cobblemon.mod.common.util.writePCPosition
import java.util.UUID
import net.minecraft.network.RegistryFriendlyByteBuf
class MovePCPokemonBatchPacket(val moves: List<PCPokemonMove>) : NetworkPacket<MovePCPokemonBatchPacket> {
override val id = ID
data class PCPokemonMove(
val pokemonID: UUID,
val oldPosition: PCPosition,
val newPosition: PCPosition
)
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeVarInt(moves.size)
moves.forEach { move ->
buffer.writeUUID(move.pokemonID)
buffer.writePCPosition(move.oldPosition)
buffer.writePCPosition(move.newPosition)
}
}
companion object {
val ID = cobblemonResource("move_pc_pokemon_batch")
const val MAX_MOVES = 60
fun decode(buffer: RegistryFriendlyByteBuf): MovePCPokemonBatchPacket {
val size = buffer.readVarInt()
require(size in 1..MAX_MOVES) { "Invalid batch PC move size: $size" }
return MovePCPokemonBatchPacket(
List(size) {
PCPokemonMove(
pokemonID = buffer.readUUID(),
oldPosition = buffer.readPCPosition(),
newPosition = buffer.readPCPosition()
)
}
)
}
}
}
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://www.mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.serverhandling.storage.pc
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.api.storage.pc.link.PCLinkManager
import com.cobblemon.mod.common.net.messages.client.storage.pc.ClosePCPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonBatchPacket
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
object MovePCPokemonBatchHandler : ServerNetworkPacketHandler<MovePCPokemonBatchPacket> {
override fun handle(packet: MovePCPokemonBatchPacket, server: MinecraftServer, player: ServerPlayer) {
val pc = PCLinkManager.getPC(player) ?: return run { ClosePCPacket(null).sendToPlayer(player) }
pc.moveBatch(packet.moves.map { Triple(it.pokemonID, it.oldPosition, it.newPosition) })
}
}
@@ -1299,6 +1299,7 @@
"cobblemon.ui.pc.filter.tooltip": "Format: pikachu shiny held_item lvl=1...", "cobblemon.ui.pc.filter.tooltip": "Format: pikachu shiny held_item lvl=1...",
"cobblemon.ui.pc.options": "Options", "cobblemon.ui.pc.options": "Options",
"cobblemon.ui.pc.release": "Release", "cobblemon.ui.pc.release": "Release",
"cobblemon.ui.pc.release_all": "Release All",
"cobblemon.ui.sort.name": "Sort by name", "cobblemon.ui.sort.name": "Sort by name",
"cobblemon.ui.sort.level": "Sort by level", "cobblemon.ui.sort.level": "Sort by level",
"cobblemon.ui.sort.pokedex_number": "Sort by Pokédex number", "cobblemon.ui.sort.pokedex_number": "Sort by Pokédex number",
Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B