mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-20 22:26:24 -04:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48ff865c55 | ||
|
|
e0abdb900d | ||
|
|
1b74c2906d | ||
|
|
d3a616e308 | ||
|
|
9a06463871 | ||
|
|
46b5a046ed | ||
|
|
ab811ead56 | ||
|
|
09a549ffcd | ||
|
|
b393293025 | ||
|
|
68a760a595 | ||
|
|
ae7bc6e681 | ||
|
|
ed007fe429 |
@@ -2,6 +2,7 @@
|
||||
## [1.8.3 (MONTH xth, 2026)](#1-8-3)
|
||||
|
||||
### Additions
|
||||
- You can now drag to select multiple Pokémon inside the PC menu.
|
||||
|
||||
### Pokémon Added
|
||||
#### Generation 4
|
||||
|
||||
@@ -566,6 +566,7 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(SwapPartyPokemonPacket.ID, SwapPartyPokemonPacket::decode, SwapPartyPokemonHandler))
|
||||
|
||||
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(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.SetPCPokemonPacket
|
||||
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.util.DataKeys
|
||||
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> {
|
||||
return StoreCoordinates(this, PCPosition(nbt.getShort(DataKeys.STORE_BOX).toInt(), nbt.getByte(DataKeys.STORE_SLOT).toInt()))
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ class BoxStorageSlot(
|
||||
|
||||
val grabbedSlot = parent.grabbedSlot
|
||||
return if (grabbedSlot == null) {
|
||||
true
|
||||
val pokemon = getPokemon()
|
||||
pokemon == null || !parent.isInMultiSelection(pokemon, position)
|
||||
} else {
|
||||
grabbedSlot.getPokemon() != getPokemon()
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ import net.minecraft.network.chat.Component
|
||||
import net.minecraft.sounds.SoundEvent
|
||||
import kotlin.isInitialized
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.util.FastColor
|
||||
|
||||
class PCGUI(
|
||||
val pc: ClientPC,
|
||||
@@ -129,6 +130,55 @@ class PCGUI(
|
||||
var selectPointerOffsetIncrement = 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 renderMenuBackground(context: GuiGraphics) {}
|
||||
@@ -250,7 +300,7 @@ class PCGUI(
|
||||
configuration.showParty = isVisible
|
||||
wallpaperWidget.visible = !isVisible
|
||||
(it as IconButton).highlighted = !isVisible
|
||||
if (!unseenWallpapers.isEmpty()) MarkPCBoxWallpapersSeenPacket(unseenWallpapers).sendToServer()
|
||||
if (unseenWallpapers.isNotEmpty()) MarkPCBoxWallpapersSeenPacket(unseenWallpapers).sendToServer()
|
||||
}.also {
|
||||
it.visible = displayOptions
|
||||
optionButtons.add(it)
|
||||
@@ -315,6 +365,20 @@ class PCGUI(
|
||||
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) {
|
||||
val matrices = context.pose()
|
||||
renderBackground(context, mouseX, mouseY, delta)
|
||||
@@ -685,12 +749,14 @@ class PCGUI(
|
||||
scale = SCALE
|
||||
)
|
||||
|
||||
if (!optionButtons.isEmpty() && displayOptions) {
|
||||
if (optionButtons.isNotEmpty() && displayOptions) {
|
||||
for (button in optionButtons) button.showAlt = hasShiftDown()
|
||||
}
|
||||
|
||||
super.render(context, mouseX, mouseY, delta)
|
||||
|
||||
getSelectionArea()?.let { renderSelectionBox(context, it) }
|
||||
|
||||
// Item Tooltip
|
||||
if (pokemon != null) {
|
||||
val displayedItem = if (showCosmeticItem) pokemon.cosmeticItem else pokemon.heldItemNoCopy()
|
||||
@@ -712,6 +778,20 @@ class PCGUI(
|
||||
}
|
||||
|
||||
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
|
||||
if (::boxNameWidget.isInitialized) boxNameWidget.mouseClicked(mouseX, mouseY, button)
|
||||
return super.mouseClicked(mouseX, mouseY, button)
|
||||
@@ -740,9 +820,43 @@ class PCGUI(
|
||||
storageWidget.pastureWidget?.let { pasture ->
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
val boxNameFocused = this::boxNameWidget.isInitialized && boxNameWidget.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,
|
||||
textureHeight = HEIGHT * 2
|
||||
)
|
||||
val pokemonCount = parent.multiSelection?.getPokemonCount() ?: 0
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
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),
|
||||
y = y + 3.5,
|
||||
centered = true,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common.client.gui.pc
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.gui.ColourLibrary
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
|
||||
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
|
||||
@@ -54,9 +55,17 @@ open class StorageSlot(
|
||||
}
|
||||
|
||||
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()) {
|
||||
renderSlot(context, x, y, delta)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun renderSlot(context: GuiGraphics, posX: Int, posY: Int, partialTicks: Float) {
|
||||
|
||||
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.client.gui.pc
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
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.pc.PCPosition
|
||||
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.resources.sounds.SimpleSoundInstance
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.sounds.SoundEvent
|
||||
import java.util.UUID
|
||||
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonBatchPacket.PCPokemonMove
|
||||
|
||||
class StorageWidget(
|
||||
pX: Int, pY: Int,
|
||||
@@ -77,9 +77,13 @@ class StorageWidget(
|
||||
|
||||
var displayConfirmRelease = false
|
||||
var screenLoaded = false
|
||||
var selectedPosition: StorePosition? = null
|
||||
var selectedPosition: PartyPosition? = null
|
||||
var grabbedSlot: GrabbedStorageSlot? = null
|
||||
|
||||
var multiSelection: PCMultiSelection? = null
|
||||
private val multiGrabbedSlots = mutableMapOf<UUID, GrabbedStorageSlot>()
|
||||
private var pendingStorageSlotClick: Button? = null
|
||||
|
||||
var box = 0
|
||||
set(value) {
|
||||
field =
|
||||
@@ -113,16 +117,22 @@ class StorageWidget(
|
||||
subKey = "ui.generic.yes",
|
||||
onPress = {
|
||||
if (canDeleteSelected() && displayConfirmRelease) {
|
||||
val position = selectedPosition ?: return@ReleaseConfirmButton
|
||||
val pokemon = getSelectedPokemon() ?: return@ReleaseConfirmButton
|
||||
val selection = multiSelection
|
||||
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) {
|
||||
is PartyPosition -> ReleasePartyPokemonPacket(pokemon.uuid, position)
|
||||
is PCPosition -> ReleasePCPokemonPacket(pokemon.uuid, position)
|
||||
else -> return@ReleaseConfirmButton
|
||||
CobblemonNetwork.sendToServer(ReleasePartyPokemonPacket(pokemon.uuid, position))
|
||||
}
|
||||
|
||||
CobblemonNetwork.sendToServer(packet)
|
||||
playSound(CobblemonSounds.PC_RELEASE)
|
||||
resetSelected()
|
||||
displayConfirmRelease = false
|
||||
@@ -149,9 +159,8 @@ class StorageWidget(
|
||||
}
|
||||
|
||||
fun canDeleteSelected(): Boolean {
|
||||
return !(selectedPosition is PartyPosition && party.filterNotNull().size <= 1)
|
||||
&& selectedPosition != null
|
||||
&& grabbedSlot != null
|
||||
multiSelection?.let { return it.pokemonEntries().isNotEmpty() }
|
||||
return party.filterNotNull().size > 1 && selectedPosition != null && grabbedSlot != null
|
||||
}
|
||||
|
||||
fun setupStorageSlots() {
|
||||
@@ -170,7 +179,7 @@ class StorageWidget(
|
||||
parent = this,
|
||||
pc = pc,
|
||||
position = PCPosition(box, index),
|
||||
onPress = { this.onStorageSlotClicked(it) }
|
||||
onPress = { pendingStorageSlotClick = it }
|
||||
).also { widget ->
|
||||
this.addWidget(widget)
|
||||
this.boxSlots.add(widget)
|
||||
@@ -200,7 +209,7 @@ class StorageWidget(
|
||||
parent = this,
|
||||
party = party,
|
||||
position = PartyPosition(partyIndex),
|
||||
onPress = { this.onStorageSlotClicked(it) }
|
||||
onPress = { pendingStorageSlotClick = it }
|
||||
).also { widget ->
|
||||
this.addWidget(widget)
|
||||
this.partySlots.add(widget)
|
||||
@@ -210,13 +219,115 @@ class StorageWidget(
|
||||
|
||||
private fun getSelectedPokemon(): Pokemon? {
|
||||
val selectedPosition = this.selectedPosition ?: return null
|
||||
return when (selectedPosition) {
|
||||
is PCPosition -> pc.get(selectedPosition)
|
||||
is PartyPosition -> party.get(selectedPosition)
|
||||
else -> null
|
||||
return party.get(selectedPosition)
|
||||
}
|
||||
|
||||
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) {
|
||||
val matrices = context.pose()
|
||||
// Party Label
|
||||
@@ -240,11 +351,14 @@ class StorageWidget(
|
||||
shadow = true
|
||||
)
|
||||
|
||||
val pokemonCount = multiSelection?.getPokemonCount() ?: 0
|
||||
|
||||
if (canDeleteSelected() && displayConfirmRelease) {
|
||||
drawScaledText(
|
||||
context = context,
|
||||
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,
|
||||
y = y + 119,
|
||||
centered = true
|
||||
@@ -308,6 +422,7 @@ class StorageWidget(
|
||||
slot.render(context, mouseX, mouseY, delta)
|
||||
val pokemon = slot.getPokemon()
|
||||
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
|
||||
&& multiSelection == null
|
||||
&& pokemon != null && pokemon != pcGui.previewPokemon
|
||||
) {
|
||||
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: false)
|
||||
@@ -324,6 +439,7 @@ class StorageWidget(
|
||||
slot.render(context, mouseX, mouseY, delta)
|
||||
val pokemon = slot.getPokemon()
|
||||
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
|
||||
&& multiSelection == null
|
||||
&& pokemon != null && pokemon != pcGui.previewPokemon
|
||||
) {
|
||||
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: true)
|
||||
@@ -335,10 +451,31 @@ class StorageWidget(
|
||||
pastureWidget?.render(context, mouseX, mouseY, delta)
|
||||
matrices.pushPose()
|
||||
matrices.translate(0.0, 0.0, 1000.0)
|
||||
grabbedSlot?.render(context, mouseX, mouseY, delta)
|
||||
renderSelectedPokemon(context, mouseX, mouseY, delta)
|
||||
matrices.popPose()
|
||||
}
|
||||
|
||||
private fun renderSelectedPokemon(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
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 {
|
||||
if (displayConfirmRelease) {
|
||||
if (releaseYesButton.isHovered(pMouseX, pMouseY)) releaseYesButton.mouseClicked(pMouseX, pMouseY, pButton)
|
||||
@@ -371,23 +508,55 @@ class StorageWidget(
|
||||
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)
|
||||
resetSelected()
|
||||
}
|
||||
}
|
||||
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)
|
||||
resetSelected()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Reset release confirmation
|
||||
displayConfirmRelease = false
|
||||
|
||||
// Clicking on itself, so unselect position
|
||||
if (this.selectedPosition != null && this.selectedPosition == clickedPosition) {
|
||||
if (selectedPosition == clickedPosition) {
|
||||
if (grabbedSlot != null) playSound(CobblemonSounds.PC_DROP)
|
||||
resetSelected()
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
if (selectOverride != null) {
|
||||
selectOverride(pcGui, clickedPosition, clickedPokemon)
|
||||
@@ -420,56 +589,44 @@ class StorageWidget(
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if (clickedPosition is PCPosition) {
|
||||
val column = clickedPosition.slot % 6
|
||||
val row = clickedPosition.slot / 6
|
||||
multiSelection = PCMultiSelection(1, 1, clickedPosition.box, column, row).also {
|
||||
it.setPokemon(0, 0, clickedPokemon)
|
||||
}
|
||||
playSound(CobblemonSounds.PC_GRAB)
|
||||
return
|
||||
} 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 {
|
||||
// Handle movement within the Box
|
||||
val selectedPokemon = when(this.selectedPosition) {
|
||||
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()
|
||||
}
|
||||
val sourcePosition = selectedPosition ?: return
|
||||
val selectedPokemon = party.get(sourcePosition) ?: return
|
||||
// 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) {
|
||||
return
|
||||
}
|
||||
val packet = clickedPokemon?.let { SwapPCPartyPokemonPacket(selectedPokemon.uuid, this.selectedPosition as PartyPosition, clickedPokemon.uuid, clickedPosition) }
|
||||
?: MovePartyPokemonToPCPacket(selectedPokemon.uuid, this.selectedPosition as PartyPosition, clickedPosition)
|
||||
val packet = clickedPokemon?.let { SwapPCPartyPokemonPacket(selectedPokemon.uuid, sourcePosition, clickedPokemon.uuid, clickedPosition) }
|
||||
?: MovePartyPokemonToPCPacket(selectedPokemon.uuid, sourcePosition, clickedPosition)
|
||||
packet.sendToServer()
|
||||
playSound(CobblemonSounds.PC_DROP)
|
||||
resetSelected()
|
||||
}
|
||||
// Party to Party
|
||||
else if (this.selectedPosition is PartyPosition && clickedPosition is PartyPosition) {
|
||||
val packet = clickedPokemon?.let { SwapPartyPokemonPacket(it.uuid, clickedPosition, selectedPokemon.uuid, this.selectedPosition as PartyPosition) }
|
||||
?: MovePartyPokemonPacket(selectedPokemon.uuid, selectedPosition as PartyPosition, clickedPosition)
|
||||
else if (clickedPosition is PartyPosition) {
|
||||
val packet = clickedPokemon?.let { SwapPartyPokemonPacket(it.uuid, clickedPosition, selectedPokemon.uuid, sourcePosition) }
|
||||
?: MovePartyPokemonPacket(selectedPokemon.uuid, sourcePosition, clickedPosition)
|
||||
packet.sendToServer()
|
||||
playSound(CobblemonSounds.PC_DROP)
|
||||
resetSelected()
|
||||
@@ -480,6 +637,8 @@ class StorageWidget(
|
||||
fun resetSelected() {
|
||||
selectedPosition = null
|
||||
grabbedSlot = null
|
||||
multiSelection = null
|
||||
multiGrabbedSlots.clear()
|
||||
pcGui.setPreviewPokemon(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://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()
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -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.options": "Options",
|
||||
"cobblemon.ui.pc.release": "Release",
|
||||
"cobblemon.ui.pc.release_all": "Release All",
|
||||
"cobblemon.ui.sort.name": "Sort by name",
|
||||
"cobblemon.ui.sort.level": "Sort by level",
|
||||
"cobblemon.ui.sort.pokedex_number": "Sort by Pokédex number",
|
||||
|
||||
Reference in New Issue
Block a user