Compare commits

..
Author SHA1 Message Date
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
15 changed files with 511 additions and 254 deletions
+1
View File
@@ -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)
}
}
@@ -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.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",
@@ -855,7 +855,7 @@
"advancements.cobblemon.obtain_berry.description": "地表に育ち多くの用途を持つ、きのみを手に入れる",
"advancements.cobblemon.obtain_mutated_berry": "想定外の結果",
"advancements.cobblemon.obtain_mutated_berry.description": "違う種類のきのみの木を隣り合わせに植えて突然変異を目撃する",
"advancements.cobblemon.obtain_all_berries": "もぎってナッツ",
"advancements.cobblemon.obtain_all_berries": "もぎもぎフルーツ",
"advancements.cobblemon.obtain_all_berries.description": "Cobblemonに登場する全てのきのみを集める",
"advancements.cobblemon.use_pasture": "みんなのポケモン牧場",
"advancements.cobblemon.use_pasture.description": "ぼくじょうブロックを作りポケモンを放し飼いにする",
@@ -6219,7 +6219,7 @@
"cobblemon.egg_group.water_2": "水中②",
"cobblemon.egg_group.ditto": "メタモン",
"cobblemon.egg_group.dragon": "ドラゴン",
"cobblemon.egg_group.undiscovered": "未発見",
"cobblemon.egg_group.undiscovered": "タマゴ未発見",
"cobblemon.trade.canceled.sender": "%1$s との交換リクエストをキャンセルしました。",
"cobblemon.trade.canceled.receiver": "%1$s が交換リクエストをキャンセルしました...",
"cobblemon.trade.sent": "%1$s との交換をリクエストしました。",
@@ -6981,7 +6981,7 @@
"cobblemon.mark.ribbon_contest_sinnoh_tough_3.desc": "スーパーコンテスト たくましさ部門 ウルトラランク 優勝!",
"cobblemon.mark.ribbon_contest_sinnoh_tough_4": "パワフルリボンマスター",
"cobblemon.mark.ribbon_contest_super_master_beauty": "うつくしさマスターリボン",
"cobblemon.mark.ribbon_contest_super_master_beauty.title": "とうじからきれいな %1$s",
"cobblemon.mark.ribbon_contest_super_master_beauty.title": "とうじきれいだった %1$s",
"cobblemon.mark.ribbon_contest_super_master_beauty.desc": "コンテストで うつくしさを 発揮した ポケモンに 贈られる 記念リボン",
"cobblemon.mark.ribbon_contest_super_master_cleverness": "かしこさマスターリボン",
"cobblemon.mark.ribbon_contest_super_master_cleverness.title": "てんさいとよばれた %1$s",
@@ -7002,7 +7002,7 @@
"cobblemon.mark.ribbon_contest_super_master_coolness": "かっこよさマスターリボン",
"cobblemon.mark.ribbon_contest_super_master_cuteness.desc": "コンテストで かわいさを 発揮した ポケモンに 贈られる 記念リボン",
"cobblemon.mark.ribbon_contest_super_master_toughness": "たくましさマスターリボン",
"cobblemon.mark.ribbon_contest_super_master_toughness.title": "むかしからムキムキの %1$s",
"cobblemon.mark.ribbon_contest_super_master_toughness.title": "ムキムキの %1$s",
"cobblemon.mark.ribbon_contest_super_master_toughness.desc": "コンテストで たくましさを 発揮した ポケモンに 贈られる 記念リボン",
"cobblemon.mark.ribbon_contest_super_star": "コンテストスターリボン",
"cobblemon.mark.ribbon_contest_super_star.title": "でんせつてきスターの %1$s",
@@ -9243,7 +9243,7 @@
"cobblemon.ui.moves.learnset.tm.discovered": "発見済み",
"cobblemon.ui.moves.level": "レベルアップ",
"cobblemon.ui.moves.egg": "タマゴ技",
"cobblemon.ui.moves.tm": "わざマシン",
"cobblemon.ui.moves.tm": "わざマシン",
"cobblemon.ui.tm_machine.can_learn": "覚えられる",
"cobblemon.ui.tm_machine.cannot_learn": "覚えられない",
"cobblemon.ui.tm_machine.learned": "覚えている",
@@ -9596,42 +9596,5 @@
"cobblemon.config.ui.world_spawning_blocklist": "ワールドごとのスポーンブロックリスト",
"cobblemon.config.ui.world_spawning_blocklist.tooltip": "ポケモンの出現ロジックを無効にするワールド。",
"advancements.cobblemon.obtain_tm": "Show Me Your Moves!",
"advancements.cobblemon.obtain_tm.description": "わざマシンを見つけるか、印刷する",
"itemGroup.cobblemon.tm_item": "Cobblemon:わざマシン",
"block.cobblemon.wistensu": "フジブヨウ",
"block.cobblemon.lobaila": "ミゾグレン",
"block.cobblemon.pauvillea": "フラビリア",
"block.cobblemon.pomohana": "ポンノハナ",
"block.cobblemon.voltaconia": "ボルトコニア",
"block.cobblemon.verdroot": "ミノルート",
"block.cobblemon.lucibell": "サイヴェル",
"block.cobblemon.mistleaf": "ミストリーフ",
"cobblemon.ui.pokedex.info.evolution_tree": "進化の流れ",
"cobblemon.ui.pokedex.evolution.item_interact.tooltip": "%1$sが必要",
"cobblemon.ui.pokedex.evolution.trade.tooltip.with_species": "%1$sとのポケモン交換で進化",
"cobblemon.ui.pokedex.evolution.trade.tooltip.with_any_species": "ポケモン交換で進化",
"cobblemon.ui.pokedex.evolution.shedder.tooltip": "予備のモンスターボールと手持ちの空きが必要",
"cobblemon.ui.pokedex.evolution.or": "または",
"cobblemon.ui.pokedex.evolution.one_of": "以下のいずれか:",
"cobblemon.ui.pokedex.requirement.advancement.tooltip": "プレイヤーが進捗 %1$sを達成している必要あり",
"cobblemon.ui.pokedex.requirement.battle_critical_hits.tooltip": "バトルで%1$s回急所に当てる",
"cobblemon.ui.pokedex.requirement.biome.anticondition.tooltip": "%1$sバイオームでは進化出来ない",
"cobblemon.ui.pokedex.requirement.biome.both.tooltip": "%2$sを除いた%1$sバイオームでのみ進化",
"cobblemon.ui.pokedex.requirement.biome.tooltip": "%1$sバイオームでのみ進化",
"cobblemon.ui.pokedex.requirement.blocks_traveled.tooltip": "%1$sブロック分旅をする",
"cobblemon.ui.pokedex.requirement.damage_taken.tooltip": "バトルで倒れず%1$sダメージ受ける",
"cobblemon.ui.pokedex.requirement.defeat.tooltip": "進化するためにはバトルで%1$sを倒す",
"cobblemon.ui.pokedex.requirement.friendship.tooltip": "なかよし度%1$sが必要",
"cobblemon.ui.pokedex.requirement.has_move.tooltip": "%1$sを覚える必要がある",
"cobblemon.ui.pokedex.requirement.has_move_type.tooltip": "%1$sタイプの技を覚えておく必要がある",
"cobblemon.ui.pokedex.requirement.held_item.tooltip": "%1$sを持っている必要がある",
"cobblemon.ui.pokedex.requirement.level.tooltip.min": "レベル%1$sから進化",
"cobblemon.ui.pokedex.requirement.party_member.tooltip": "レベルが%1$sから%2$sの間で進化",
"cobblemon.ui.pokedex.requirement.pokemon_properties.tooltip": "%1$sでなければいけない",
"cobblemon.ui.pokedex.requirement.properties.and": "%1$s と %2$s",
"cobblemon.ui.pokedex.requirement.level.tooltip.between": "レベルが%1$sから%2$sの間で進化",
"cobblemon.ui.pokedex.requirement.moon_phase.tooltip": "月の満ち欠けが%1$sでなければいけない",
"cobblemon.ui.pokedex.requirement.nature.toxtricity_amped": "性格が§aがんばりや§r、 §aゆうかん§r、 §aいじっぱり§r、 §aやんちゃ§r、 §aすなお§r、 §aわんぱく§r、 §aのうてんき§r、 §aせっかち§r、 §aようき§r、 §aむじゃき§r、 §aうっかりや§r、 §aなまいき§r、 §aきまぐれ§rのどれか",
"cobblemon.ui.pokedex.requirement.nature.toxtricity_low_key": "性格が§aさみしがり§r、 §aずぶとい§r、 §aのんき§r、 §aおくびょう§r、 §aまじめ§r、 §aひかえめ§r、 §aおっとり§r、 §aれいせい§r、 §aてれや§r、 §aおだやか§r、 §a\tおとなしい§r、 §aしんちょう§rのどれか",
"cobblemon.ui.pokedex.requirement.nickname.malamar": "ニックネームを§aDinnerbone§rか§aGrumm§rにする"
"advancements.cobblemon.obtain_tm.description": "わざマシンを見つけるか、印刷する"
}
@@ -9413,114 +9413,5 @@
"cobblemon.ui.pokedex.info.form.tatsugiri-stretchymega": "Mega Tatsugiri Forma Estendida",
"cobblemon.species.tatsugiri-stretchymega.desc": "Usando a energia da Mega Evolução, cria um prato onde repousar, permitindo com que se mova com mobilidade total até mesmo pelo ar.",
"cobblemon.ui.pokedex.info.form.baxcalibur-mega": "Mega Baxcalibur",
"cobblemon.species.baxcalibur-mega.desc": "A lâmina dorsal de Baxcalibur ficou ainda maior graças à Mega Evolução. Esse Pokémon dispara feixes do cabo localizado em seu peito.",
"itemGroup.cobblemon.tm_item": "Cobblemon: Máquinas Técnicas",
"block.cobblemon.pomohana": "Pomohana",
"block.cobblemon.pauvillea": "Pa'uvillea",
"block.cobblemon.lobaila": "Lobaila",
"block.cobblemon.wistensu": "Wistensu",
"block.cobblemon.voltaconia": "Voltaconia",
"block.cobblemon.verdroot": "Verdroot",
"block.cobblemon.lucibell": "Lucibell",
"block.cobblemon.mistleaf": "Mistleaf",
"cobblemon.ui.pokedex.info.evolution_tree": "Árvore Evolutiva",
"cobblemon.ui.pokedex.evolution.item_interact.tooltip": "Precisa do item %1$s para evoluir",
"cobblemon.ui.pokedex.evolution.trade.tooltip.with_species": "Evolui quando trocado segurando %1$s",
"cobblemon.ui.pokedex.evolution.trade.tooltip.with_any_species": "Evolui quando trocado",
"cobblemon.ui.pokedex.evolution.shedder.tooltip": "Também requer uma Pokébola extra e um espaço vazio na equipe",
"cobblemon.ui.pokedex.evolution.or": "OU",
"cobblemon.ui.pokedex.evolution.one_of": "Um de:",
"cobblemon.ui.pokedex.requirement.advancement.tooltip": "Jogador precisa ter a conquista %1$s",
"cobblemon.ui.pokedex.requirement.battle_critical_hits.tooltip": "Precisa acertar %1$s acertos críticos em batalha",
"cobblemon.ui.pokedex.requirement.biome.anticondition.tooltip": "Não pode estar no bioma %1$s",
"cobblemon.ui.pokedex.requirement.biome.both.tooltip": "Precisa estar no bioma %1$s e não no bioma %2$s",
"cobblemon.ui.pokedex.requirement.biome.tooltip": "Precisa estar no bioma %1$s",
"cobblemon.ui.pokedex.requirement.blocks_traveled.tooltip": "Precisa viajar %1$s blocos",
"cobblemon.ui.pokedex.requirement.damage_taken.tooltip": "Precisa receber %1$s de dano em uma batalha sem desmaiar",
"cobblemon.ui.pokedex.requirement.defeat.tooltip": "Precisa derrotar %1$s em batalha para evoluir",
"cobblemon.ui.pokedex.requirement.friendship.tooltip": "Requer no mínimo %1$s de amizade",
"cobblemon.ui.pokedex.requirement.has_move.tooltip": "Precisa conhecer o movimento %1$s",
"cobblemon.ui.pokedex.requirement.has_move_type.tooltip": "Precisa conhecer um movimento do tipo %1$s",
"cobblemon.ui.pokedex.requirement.held_item.tooltip": "Precisa estar segurando %1$s",
"cobblemon.ui.pokedex.requirement.level.tooltip.min": "Evolui ao ultrapassar o nível %1$s",
"cobblemon.ui.pokedex.requirement.level.tooltip.between": "Evoluiu entre os níveis %1$s e %2$s",
"cobblemon.ui.pokedex.requirement.moon_phase.tooltip": "Precisa evoluir na fase %1$s da lua",
"cobblemon.ui.pokedex.requirement.nature.toxtricity_amped": "Precisa ter a natureza §aDestemida§r, §aValente§r, §aRígida§r, §aTeimosa§r, §aDócil§r, §aInquieta§r, §aRelaxada§r, §aApressada§r, §aAlegre§r, §aInguênua§r, §aImprudente§r, §aAtrevida§r, ou §aPeculiar§r",
"cobblemon.ui.pokedex.requirement.nature.toxtricity_low_key": "Precisa ter a natureza §aSolitária§r, §aCorajosa§r, §aDescontraída§r, §aTímida§r, §aSéria§r, §aModesta§r, §aMansa§r, §aQuieta§r, §aAtrapalhada§r, §aCalma§r, §aGentil§r, ou §aCuidadosa§r",
"cobblemon.ui.pokedex.requirement.negate.tooltip": "Não: %1$s",
"cobblemon.ui.pokedex.requirement.nickname.malamar": "Precisa se chamar §aDinnerbone§r ou §aGrumm§r",
"cobblemon.ui.pokedex.requirement.party_member.anticondition.tooltip": "Equipe não pode ter um Pokémon com %1$s",
"cobblemon.ui.pokedex.requirement.party_member.tooltip": "Equipe precisa ter um Pokémon com %1$s",
"cobblemon.ui.pokedex.requirement.pokemon_properties.tooltip": "Precisa estar %1$s",
"cobblemon.ui.pokedex.requirement.properties.and": "%1$s e %2$s",
"cobblemon.ui.pokedex.requirement.properties.custom.cocoon_species": "Casulo %1$s",
"cobblemon.ui.pokedex.requirement.properties.held_item": "Segurando %1$s",
"cobblemon.ui.pokedex.requirement.properties.int_feature": "%1$s %2$s",
"cobblemon.ui.pokedex.requirement.properties.nature": "Natureza %1$s",
"cobblemon.ui.pokedex.requirement.properties.nickname": "Nomeado %1$s",
"cobblemon.ui.pokedex.requirement.properties.type": "Tipo %1$s",
"cobblemon.ui.pokedex.requirement.property_range.tooltip": "Requer %1$s entre %2$s e %3$s",
"cobblemon.ui.pokedex.requirement.recoil.tooltip": "Precisa sofrer %1$s unidades de recuo em batalha",
"cobblemon.ui.pokedex.requirement.stat_compare.tooltip": "Precisa ter seu %1$s acima de %2$s",
"cobblemon.ui.pokedex.requirement.stat_equal.tooltip": "Precisa ter seu %1$s igual ao seu %2$s",
"cobblemon.ui.pokedex.requirement.structure.anticondition.tooltip": "Não pode estar na estrutura %1$s",
"cobblemon.ui.pokedex.requirement.structure.both.tooltip": "Precisa estar na estrutura %1$s e não na estrutura %2$s",
"cobblemon.ui.pokedex.requirement.structure.tooltip": "Precisa estar na estrutura %1$s",
"cobblemon.ui.pokedex.requirement.time_range.tooltip": "Precisa evoluir durante %1$s",
"cobblemon.ui.pokedex.requirement.use_move.tooltip": "Precisa usar o movimento %1$s em batalha",
"cobblemon.ui.pokedex.requirement.weather.tooltip.raining": "Precisa estar na chuva para evoluir",
"cobblemon.ui.pokedex.requirement.weather.tooltip.thundering": "Precisa estar em uma tempestade para evoluir",
"cobblemon.ui.moon_phase.full_moon": "Lua Cheia",
"cobblemon.ui.moon_phase.waning_gibbous": "Crescente Gibosa",
"cobblemon.ui.moon_phase.third_quarter": "Quarto Crescente",
"cobblemon.ui.moon_phase.waning_crescent": "Crescente",
"cobblemon.ui.moon_phase.new_moon": "Lua Nova",
"cobblemon.ui.moon_phase.waxing_crescent": "Minguante",
"cobblemon.ui.moon_phase.first_quarter": "Quarto Minguante",
"cobblemon.ui.moon_phase.waxing_gibbous": "Minguante Gibosa",
"cobblemon.ui.time_range.any": "Qualquer",
"cobblemon.ui.time_range.day": "Dia",
"cobblemon.ui.time_range.night": "Noite",
"cobblemon.ui.time_range.noon": "Meio-dia",
"cobblemon.ui.time_range.midnight": "Meia-noite",
"cobblemon.ui.time_range.dawn": "Alvorecer",
"cobblemon.ui.time_range.dusk": "Entardecer",
"cobblemon.ui.time_range.twilight": "Crepúsculo",
"cobblemon.ui.time_range.morning": "Manhã",
"cobblemon.ui.time_range.afternoon": "Tarde",
"cobblemon.ui.time_range.predawn": "Pré-amanhecer",
"cobblemon.ui.time_range.evening": "Tardinha",
"cobblemon.subtitle.item.bright_powder.use": "Pó Brilhante ilumina",
"cobblemon.config.ui.type_gem_max_connected": "Máximo de Blocos de Gema de Tipo Conectados",
"cobblemon.config.ui.type_gem_min_growth": "Mínimo de Crescimento de Gema de Tipo",
"cobblemon.config.ui.type_gem_growth_chance": "Chance de Crescimento de Gema de Tipo",
"cobblemon.config.ui.type_gem_initial_expansion_cycles": "Cíclos de Expansão Iniciais de Gemas de Tipo",
"cobblemon.battle.activate.ficklebeam": "%1$s vai com tudo neste próximo ataque!",
"tag.item.cobblemon.oricorio_flowers": "Flores de Néctar",
"tag.item.cobblemon.terrain_plants": "Plantas de Terreno",
"tag.item.cobblemon.tall_flowers": "Flores Altas",
"tag.worldgen.biome.cobblemon.evolution.regional.bergmite_hisuibiome": "Hisuiano",
"tag.worldgen.biome.cobblemon.evolution.regional.bergmite_kalosbiome": "Kalosiano",
"tag.worldgen.biome.cobblemon.evolution.regional.cubone_alolabiome": "Alolano",
"tag.worldgen.biome.cobblemon.evolution.regional.cubone_kantobiome": "Kantoniano",
"tag.worldgen.biome.cobblemon.evolution.regional.dartrix_alolabiome": "Alolano",
"tag.worldgen.biome.cobblemon.evolution.regional.dartrix_hisuibiome": "Hisuiano",
"tag.worldgen.biome.cobblemon.evolution.regional.dewott_hisuibiome": "Hisuiano",
"tag.worldgen.biome.cobblemon.evolution.regional.dewott_unovabiome": "Unovano",
"tag.worldgen.biome.cobblemon.evolution.regional.exeggcute_alolabiome": "Alolano",
"tag.worldgen.biome.cobblemon.evolution.regional.exeggcute_kantobiome": "Kantoniano",
"tag.worldgen.biome.cobblemon.evolution.regional.goomy_hisuibiome": "Hisuiano",
"tag.worldgen.biome.cobblemon.evolution.regional.goomy_kalosbiome": "Kalosiano",
"tag.worldgen.biome.cobblemon.evolution.regional.koffing_galarbiome": "Galariano",
"tag.worldgen.biome.cobblemon.evolution.regional.koffing_kantobiome": "Kantoniano",
"tag.worldgen.biome.cobblemon.evolution.regional.mimejr_galarbiome": "Galariano",
"tag.worldgen.biome.cobblemon.evolution.regional.mimejr_sinnohbiome": "Sinnohano",
"tag.worldgen.biome.cobblemon.evolution.regional.petilil_hisuibiome": "Hisuiano",
"tag.worldgen.biome.cobblemon.evolution.regional.petilil_unovabiome": "Unovano",
"tag.worldgen.biome.cobblemon.evolution.regional.pikachu_alolabiome": "Alolano",
"tag.worldgen.biome.cobblemon.evolution.regional.pikachu_kantobiome": "Kantoniano",
"tag.worldgen.biome.cobblemon.evolution.regional.quilava_hisuibiome": "Hisuiano",
"tag.worldgen.biome.cobblemon.evolution.regional.quilava_johtobiome": "Johtoniano",
"tag.worldgen.biome.cobblemon.evolution.regional.rufflet_hisuibiome": "Hisuiano",
"tag.worldgen.biome.cobblemon.evolution.regional.rufflet_unovabiome": "Unovano"
"cobblemon.species.baxcalibur-mega.desc": "A lâmina dorsal de Baxcalibur ficou ainda maior graças à Mega Evolução. Esse Pokémon dispara feixes do cabo localizado em seu peito."
}
@@ -9021,34 +9021,5 @@
"cobblemon.config.ui.world_spawning_blocklist.tooltip": "Миры, в которых отключается принцип появления покемонов.",
"advancements.cobblemon.obtain_tm.description": "Найдите или напечатайте ТМ",
"cobblemon.species.victreebel-mega.desc": "В результате Мега эволюции объём кислоты у этого покемона увеличился, заполняя весь его рот. Если он не будет осторожен, кислота переполнится и выльется наружу.",
"cobblemon.ui.pokedex.info.form.victreebel-mega": "Мега Виктрибелл",
"itemGroup.cobblemon.tm_item": "Технические машины",
"block.cobblemon.pomohana": "Помохана",
"block.cobblemon.pauvillea": "Па'увилия",
"block.cobblemon.lobaila": "Лобалия",
"block.cobblemon.wistensu": "Вистенсу",
"block.cobblemon.voltaconia": "Вольтакония",
"block.cobblemon.verdroot": "Вердрут",
"block.cobblemon.lucibell": "Люсибель",
"block.cobblemon.mistleaf": "Мистлиф",
"cobblemon.ui.pokedex.info.evolution_tree": "Древо эволюции",
"cobblemon.ui.pokedex.evolution.item_interact.tooltip": "Требуется %1$s для эволюции",
"cobblemon.ui.pokedex.evolution.trade.tooltip.with_species": "Эволюционирует при обмене с %1$s",
"cobblemon.ui.pokedex.evolution.trade.tooltip.with_any_species": "Эволюционирует при обмене",
"cobblemon.ui.pokedex.evolution.shedder.tooltip": "Также требует запасной покебол и свободное место в команде",
"cobblemon.ui.pokedex.evolution.or": "ИЛИ",
"cobblemon.ui.pokedex.evolution.one_of": "Один из:",
"cobblemon.ui.pokedex.requirement.advancement.tooltip": "У игрока должно быть достижение %1$s",
"cobblemon.ui.pokedex.requirement.battle_critical_hits.tooltip": "Должен попасть %1$s критическими ударами в битве",
"cobblemon.ui.pokedex.requirement.biome.anticondition.tooltip": "Не должен быть в %1$s биоме",
"cobblemon.ui.pokedex.requirement.biome.both.tooltip": "Должен быть в %1$s биоме и не в %2$s биоме",
"cobblemon.ui.pokedex.requirement.biome.tooltip": "Должен быть в %1$s биоме",
"cobblemon.ui.pokedex.requirement.blocks_traveled.tooltip": "Должен пройти %1$s блоков",
"cobblemon.ui.pokedex.requirement.damage_taken.tooltip": "Должен получить %1$s урона в битве без поражения",
"cobblemon.ui.pokedex.requirement.defeat.tooltip": "Должен победить против %1$s в битве для эволюции",
"cobblemon.ui.pokedex.requirement.friendship.tooltip": "Требует как минимум %1$s дружбы",
"cobblemon.ui.pokedex.requirement.has_move.tooltip": "Должен знать приём %1$s",
"cobblemon.ui.pokedex.requirement.has_move_type.tooltip": "Должен знать приём %1$s типа",
"cobblemon.ui.pokedex.requirement.held_item.tooltip": "Должен удерживать %1$s",
"cobblemon.ui.pokedex.requirement.level.tooltip.min": "Эволюционирует начиная с %1$s уровня"
"cobblemon.ui.pokedex.info.form.victreebel-mega": "Мега Виктрибелл"
}