Compare commits

...
Author SHA1 Message Date
Gertimoshka 72999ff600 Fixes to Rendering Order 2026-08-15 14:37:59 +03:00
Gertimoshka ec18bb9154 Starter Screen Implementation and Pokedex Partial Fixes 2026-08-12 23:56:44 +03:00
Gertimoshka 1e1fba5f7f Innitial Merge Fixes 2026-08-12 10:20:33 +03:00
Gertimoshka 69497bc3cc The Painful Merge Complete: The Revival of Controlify Support 2026-08-12 09:49:01 +03:00
Gertimoshka 64f7556bdc Dont forget the licenses 2026-03-10 22:12:16 +02:00
Gertimoshka 9fea42bce0 Controlify Binds English Localization 2026-02-24 12:08:51 +02:00
Gertimoshka 7abe83c413 Controller Hotbar navigation buttons will now zoom with with pokedex and switch party when holding the sendout button 2026-02-23 15:33:53 +02:00
Gertimoshka 2d70f48176 Additional Changes + Fixes 2026-02-13 16:28:53 +02:00
JPAKx4 8f9c1c40d7 Merge tag '1.7.3' into controllify-compat-ui-navigation 2026-02-09 15:22:27 -06:00
Gertimoshka 89c9cd9fb4 Added Missing Button Guides 2026-02-09 19:54:21 +02:00
Gertimoshka 9335eb9650 And... Done, Gameplay Important GUIs complete 2026-02-08 11:31:06 +02:00
Gertimoshka 7a7be6f25a Even more GUIs done for Controlify Support 2026-02-07 23:28:48 +02:00
Gertimoshka 76ed55ebe8 Fixes to when Button Guides are Rendered 2026-01-29 09:52:26 +02:00
Gertimoshka b004b13eda Finally PC Block Controller Support (+Pasture) 2026-01-29 09:30:28 +02:00
Gertimoshka dbe8a1a9f5 A Working Version of Interaction Wheel 2026-01-26 23:03:30 +02:00
JPAKx4 25e8e3c990 Try to begin fixing the interactionWheel 2026-01-26 13:46:41 -06:00
Gertimoshka b84012f98f Rapid Fire 3 Easier Guis for Controlify Support 2026-01-26 21:35:53 +02:00
JPAKx4 a0979b28d3 Merge tag '1.7.2' into controllify-compat-ui-navigation 2026-01-25 09:58:19 -06:00
Gertimoshka f459c64073 Button Guides for our Screens 2026-01-24 15:02:56 +02:00
Gertimoshka 8a7abb9a11 Starter Selection Controlified 2026-01-22 23:56:47 +02:00
Gertimoshka 7be8beb71e Pokedex GUI Controlify Support + Rendering Layering fixes 2026-01-18 20:47:56 +02:00
Gertimoshka 637274c7ee Battle GUI now has working Controller Support 2026-01-12 19:44:51 +02:00
Gertimoshka 63786357b9 Finally working Summary GUI for contrilify with Visuals 2025-12-31 15:40:55 +02:00
Gertimoshka fa3e85b054 Summary GUI Controllify Support, Functional Implementation 2025-12-25 16:54:51 +02:00
Gertimoshka 61c1c6b21f Additional Fixes for Rebase 2025-12-23 09:46:12 +02:00
Gertimoshka a347844986 Summary GUI - navigation for moves tab and party tab 2025-12-23 09:45:14 +02:00
Gertimoshka d78a92f069 Rebasing for New Changes 2025-12-23 09:44:57 +02:00
JPAKx4 ba3207d176 Work on a framework to make UI stuff not suck 2025-12-20 12:23:48 -06:00
JPAKx4 762f9ce5f1 Merge branch 'main' into controllify-compat 2025-12-17 22:24:43 -06:00
JPAKx4 6c12198344 Improve InteractWheelProcessor.kt and use controlify sensitivity 2025-12-17 22:24:29 -06:00
JPAKx4 99a0ec7f0d Merge remote-tracking branch 'origin/controllify-compat' into controllify-compat
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/integration/controlify/CobblemonControlifyEntrypoint.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/integration/controlify/gui/InteractWheelProcessor.kt
2025-12-08 22:38:47 -06:00
JPAKx4 aacebc9726 Add changelog, handle horse sprinting, add all UIs to virtual mouse. 2025-12-08 22:37:43 -06:00
JPAKx4 378c85b7dd More work on keybinds for Controlify compat 2025-12-08 22:20:26 -06:00
JPAKx4 c4614ff418 Add radial menu support for controlify 2025-12-08 22:20:26 -06:00
JPAK 8cbbd2de27 Move compat to common for neoforge and fabric support. 2025-12-08 22:20:26 -06:00
JPAKx4 067c59a636 Continue work on controlify compat 2025-12-08 22:20:26 -06:00
JPAKx4 98a02438da Begin ControlifyCompat 2025-12-08 22:20:25 -06:00
JPAKx4 26b25c8c39 More work on keybinds for Controlify compat 2025-12-07 17:01:04 -06:00
JPAKx4 bfd78c1a85 Merge remote-tracking branch 'origin/controllify-compat' into controllify-compat 2025-12-06 17:10:33 -06:00
JPAKx4 239485ccb5 Add radial menu support for controlify 2025-12-06 17:10:20 -06:00
JPAKx4 ff959daaef Add radial menu support for controlify 2025-12-06 16:57:09 -06:00
JPAK 32bb340260 Move compat to common for neoforge and fabric support. 2025-12-05 21:36:59 -06:00
JPAKx4 b7bfc6d0a1 Continue work on controlify compat 2025-12-03 07:01:01 -06:00
JPAKx4 d86879095f Begin ControlifyCompat 2025-11-29 17:02:35 -06:00
79 changed files with 3709 additions and 350 deletions
+72
View File
@@ -1,4 +1,76 @@
# Changelog
## [1.7.2 (January 25th, 2026)](#1-7-2)
### Additions
- Added Saccharine wood furniture for Adorn.
- Added the Partner Mark, which party Pokémon have a very small chance to earn as the player walks, provided they have enough friendship.
- Added trades for Saccharine Saplings, Hearty Grains, Chipped Pot, and Masterpiece Teacup to the Wandering Trader.
- Added brewing recipe for Throat Spray.
- Added official Controlify Support
- Added new effects
- Mental Restoration: Reduces "Time Since Last Rest" statistic, which controls Insomnia, by half a minute per second per effect level
- Negative Effect Cleanse: Removes all negative effects
- Effect Cleanse: Removes all effects
- Added more seasonings for sinister tea
- Mental Herb - Mental Restoration I for 10 seconds
- White Herb - Negative Effect Cleanse
- Milk - Effect Cleanse
- Moomoo Milk - Effect Cleanse
- Added decorative block models for existing items: Potion, Super Potion, Hyper Potion, Max Potion, Full Restore, Antidote, Awakening, Burn Heal, Ice Heal, Paralyze Heal, Full Heal, Ether, Elixir, Max Ether, Max Elixir, X Accuracy, X Attack, X Defence, X Special Attack, X Special Defence, X Speed, Dire Hit, Guard Spec, Weakness Policy, Blunder Policy, Cleanse Tag, and Spell Tag.
- Added decorative sign blocks called Plaques.
- Added ride style labels to ride stats in Pokédex, as well as ride style icons in the summary GUI.
- Added more seasonings for sinister tea:
- Mental Herb - Mental Restoration I for 10 seconds, which staves off Insomnia.
- White Herb - Removes negative effects.
- Milk, Moomoo Milk - Removes effects.
- Added dispenser support for:
- Applying a Honey Bottle to Saccharine Logs.
- Washing honey off of Saccharine Slathered Logs.
- Applying and removing honey on Saccharine Leaves.
- Added riding statistics: `Distance by Pokémon on Land`, `Distance by Pokémon in Air` and `Distance by Pokémon in Liquid`.
### New rideable Pokémon
- Honchkrow
- Mantine
### Pokémon Added
#### Gen 5
- Throh
- Sawk
#### Gen 6
- Litleo
- Pyroar
- Spritzee
- Aromatisse
- Swirlix
- Slurpuff
### Model updates for the following Pokémon
- Falinks
- Cottonee
- Whimsicott
- Ferroseed
- Ferrothorn
- Chingling
- Chimecho
### Animation updates for the following Pokémon
- Mantine (ride animations)
- Flygon (ride jump animation)
- Honchkrow (ride animations)
- Croagunk
- Toxicroak
### Changes
- Added a new config option, `Enable In-Flight Dismounting` (default: off), which lets you dismount while riding a Pokémon in the air. This is the textbook definition of caveat emptor.
- Ride sounds have been separated into stereo for passengers and mono for other players. Riding will now sound more spacious.
- Battle AI now uses a smarter threshold for switching, reducing unnecessary switches.
- AI will always use the most damaging move when at low HP and when it is not switching, improving endgame decision-making.
- Items given from interacting with Saccharine Leaves are placed in the active hotbar slot if possible.
- Added `label` as an alternate key to the `tag` property.
## [1.7.3 (January 31st, 2026)](#1-7-3)
## [1.8.0 (MONTH Xth, 2026)](#1-8-0)
### Additions
+9
View File
@@ -32,6 +32,10 @@ repositories {
maven(url = "https://api.modrinth.com/maven")
maven(url = "https://maven.neoforged.net/releases")
maven(url = "https://maven.gegy.dev")
exclusiveContent {
forRepository { maven(url = "https://maven.isxander.dev/releases") }
filter { includeGroup("dev.isxander") }
}
mavenLocal()
}
@@ -62,6 +66,11 @@ dependencies {
// Unit Testing
testImplementation(libs.bundles.unitTesting)
// Controlify Compat, same as flywheel so we pick one and only use the api
modCompileOnlyApi("dev.isxander:controlify:2.4.3+1.21.1-fabric") {
isTransitive = false
}
}
tasks.withType<Test> {
@@ -21,12 +21,14 @@ import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.Blaze3D;
import kotlin.Pair;
import kotlin.jvm.functions.Function4;
import net.minecraft.client.Minecraft;
import net.minecraft.client.MouseHandler;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.util.Mth;
import net.minecraft.util.SmoothDouble;
import net.minecraft.world.phys.Vec3;
import org.joml.Vector2d;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -149,6 +151,12 @@ public abstract class MouseHandlerMixin {
pokeVehicle.clampPassengerRotation(player);
}
for(Function4<LocalPlayer, Double, Double, Double, Vector2d> rideFun : CobblemonClient.INSTANCE.getRidingTurnInputHandler().values()) {
Vector2d vec = rideFun.invoke(player, cursorDeltaX, cursorDeltaY, d);
cursorDeltaX = vec.x;
cursorDeltaY = vec.y;
}
// If the ride is not orientation controllable then return and perform the normal turnPlayer logic
if (!(vehicle instanceof OrientationControllable controllableVehicle) || !controllableVehicle.getOrientationController().isActive()) {
xMouseSmoother.reset();
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.integration.controlify;
import com.cobblemon.mod.common.client.CobblemonClient;
import com.cobblemon.mod.common.client.integration.controlify.CobblemonControlifyEntrypoint;
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding;
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext;
import dev.isxander.controlify.controller.ControllerEntity;
import dev.isxander.controlify.ingame.InGameInputHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.player.Inventory;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(InGameInputHandler.class)
public class InGameInputHandlerMixin {
@Shadow @Final private ControllerEntity controller;
@Redirect(
method = "handleKeybinds()V",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/player/Inventory;swapPaint(D)V"
)
)
public void cobblemon$onHotbarSwitch(Inventory instance, double direction) {
PokedexUsageContext usageContext = CobblemonClient.INSTANCE.getPokedexUsageContext();
if (usageContext.getScanningGuiOpen()) {
usageContext.adjustZoom(direction);
return;
}
if (CobblemonControlifyEntrypoint.Companion.getSEND_OUT_POKEMON().on(controller).digitalNow()) {
CobblemonClient.INSTANCE.getStorage().shiftSelected(direction < 0);
PartySendBinding.INSTANCE.actioned();
return;
}
instance.swapPaint(direction);
}
}
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
@@ -144,11 +145,12 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
) {
val sprintTimerTickLength = 7 // Emulate vanillas tick timer length
var hasDoubleTapped = false
val forward = Minecraft.getInstance().player?.input?.up == true
// If you are not sprinting and you just pressed down and theres no
if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && !state.sprinting.get() && state.sprintTickTimer.get() == 0) {
if (!state.forwardHeldLastTick.get() && forward && !state.sprinting.get() && state.sprintTickTimer.get() == 0) {
state.sprintTickTimer.set(sprintTimerTickLength)
} else if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && state.sprintTickTimer.get() != 0) { // If has double tapped
} else if (!state.forwardHeldLastTick.get() && forward && state.sprintTickTimer.get() != 0) { // If has double tapped
hasDoubleTapped = true
}else if(!state.sprinting.get() && state.sprintTickTimer.get() > 0) {
state.sprintTickTimer.set(state.sprintTickTimer.get() - 1)
@@ -156,9 +158,9 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
state.sprintTickTimer.set(0)
}
val tryingToSprint = (Minecraft.getInstance().options.keySprint.isDown() && Minecraft.getInstance().options.keyUp.isDown()) || hasDoubleTapped
val tryingToSprint = hasDoubleTapped || CobblemonClient.ridingSprintHandler.any { it.value.invoke() }
if (state.stamina.get() <= 0.0f || !Minecraft.getInstance().options.keyUp.isDown()) {
if (state.stamina.get() <= 0.0f || !forward) {
// If stamina runs out or the player is not holding forward then stop sprinting
state.sprinting.set(false)
if (state.stamina.get() <= 0.0f) {
@@ -172,8 +174,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
state.sprinting.set(true)
}
// Take not of whether forward was held last tick
state.forwardHeldLastTick.set(Minecraft.getInstance().options.keyUp.isDown())
// Take note of whether forward was held last tick
state.forwardHeldLastTick.set(forward)
}
fun tickStamina(
@@ -70,6 +70,7 @@ import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.isLookingAt
import net.minecraft.client.Minecraft
import net.minecraft.client.color.block.BlockColor
@@ -79,16 +80,19 @@ import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.model.BoatModel
import net.minecraft.client.model.ChestBoatModel
import net.minecraft.client.model.PlayerModel
import net.minecraft.client.player.LocalPlayer
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.blockentity.HangingSignRenderer
import net.minecraft.client.renderer.blockentity.SignRenderer
import net.minecraft.client.renderer.entity.EntityRenderer
import net.minecraft.client.renderer.entity.LivingEntityRenderer
import net.minecraft.client.resources.PlayerSkin
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.resources.ResourceManager
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.AABB
import org.joml.Vector2d
object CobblemonClient {
@@ -109,6 +113,8 @@ object CobblemonClient {
val battleOverlay: BattleOverlay by lazy { BattleOverlay() }
val pokedexUsageContext: PokedexUsageContext by lazy { PokedexUsageContext() }
val rideControlsOverlay: RideControlsOverlay by lazy { RideControlsOverlay() }
val ridingTurnInputHandler: MutableMap<ResourceLocation, (LocalPlayer, Double, Double, Double) -> Vector2d> = mutableMapOf()
val ridingSprintHandler: MutableMap<ResourceLocation, () -> Boolean> = mutableMapOf()
fun onLogin() {
clientPlayerData = ClientGeneralPlayerData()
@@ -194,6 +200,8 @@ object CobblemonClient {
ClientPlayerIcon.onTick()
PartyOverlayDataControl.tick(event.client.isPaused)
}
ridingSprintHandler.put(cobblemonResource("normal")) { Minecraft.getInstance().options.keySprint.isDown }
}
private fun registerTooltipManagers() {
@@ -129,6 +129,10 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
val panelX = 0
val party = CobblemonClient.storage.party
val matrices = context.pose()
matrices.pushPose()
matrices.translate(0.0, 0.0, -100.0)
if (party.slots.none { it != null }) {
if (!this.attachedToast &&
CobblemonClient.clientPlayerData.promptStarter &&
@@ -182,10 +186,11 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
)
matrices.pushPose()
matrices.scale(1F, 1F, 0.2F)
matrices.translate(
panelX + portraitFrameOffsetX + selectedOffsetX + PORTRAIT_DIAMETER / 2.0 - 1.0,
y.toDouble() - 12,
0.0
-50.0
)
val shouldAnimate = when (Cobblemon.config.partyPortraitAnimations) {
@@ -557,5 +562,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
}
}
}
matrices.popPose()
}
}
@@ -195,7 +195,7 @@ abstract class ScrollingWidget<T : ObjectSelectionList.Entry<T>>(
return this.width
}
override fun getRowTop(index: Int): Int {
public override fun getRowTop(index: Int): Int {
return this.y - scrollAmount.toInt() + (index * this.itemHeight)
}
@@ -205,6 +205,19 @@ abstract class ScrollingWidget<T : ObjectSelectionList.Entry<T>>(
override fun getScrollbarPosition(): Int = this.left + this.width - this.scrollBarWidth
public override fun ensureVisible(entry: T) {
val i = getRowTop(children().indexOf(entry))
val topDifference = i - y
if (topDifference < 0) {
scroll(topDifference)
}
val bottomDifference = bottom - i - itemHeight - 4
if (bottomDifference < 0) {
scroll(-bottomDifference)
}
}
abstract class Slot<T : Slot<T>>(): Entry<T>() {
// Override render to show each individual element
}
@@ -68,25 +68,23 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
}
override fun renderWidget(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
var stackHeight = -scrollAmount.toInt()
guiGraphics.enableScissor(x, y, x + width, y + height)
children.forEachIndexed { index, entry ->
if (stackHeight in 1..<height || stackHeight + entry.height in 1..<height) {
entry.render(guiGraphics, index, x, y + stackHeight, mouseX, mouseY, partialTick)
val yTop = getRowTop(entry)
if (yTop + entry.height > y || yTop < y + height) {
entry.render(guiGraphics, index, x, yTop, mouseX, mouseY, partialTick)
}
stackHeight += entry.height
}
guiGraphics.disableScissor()
}
fun getEntryAtPosition(mouseX: Int, mouseY: Int): Entry? {
if (mouseX in x..(x + width) && mouseY in y..(y + height)) {
var stackHeight = -scrollAmount
children.forEach {
if (mouseY.toDouble() in (y + stackHeight)..(y + stackHeight + it.height)) {
val yTop = getRowTop(it)
if (mouseY in (yTop)..(yTop + it.height)) {
return it
}
stackHeight += it.height
}
}
return null
@@ -94,10 +92,16 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
fun ensureVisible(index: Int) {
val entry = children[index]
if (entry.scrolledY - scrollAmount < 0) {
ensureVisible(entry)
}
open fun ensureVisible(entry: Entry) {
if (entry !in children) return
val yTop = entry.scrolledY - scrollAmount.toInt()
if (yTop < 0) {
setScroll(entry.scrolledY.toDouble())
} else if (entry.scrolledY + entry.height > height) {
setScroll(entry.scrolledY.toDouble() + entry.height)
} else if (yTop + entry.height > height) {
setScroll((entry.scrolledY + entry.height - height).toDouble())
}
}
@@ -121,14 +125,12 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
}
fun getRowTop(entry: Entry): Int {
var stackHeight = -scrollAmount.toInt()
children.forEach {
if (it == entry) {
return y + stackHeight
}
stackHeight += it.height
}
return y
if (entry !in children) return -1
return y + entry.scrolledY - scrollAmount.toInt()
}
fun getLeft(): Int {
return x
}
override fun defaultButtonNarrationText(pNarrationElementOutput: NarrationElementOutput) {}
@@ -136,6 +138,7 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
abstract inner class Entry(var height: Int): GuiEventListener {
var scrolledY: Int = 0
private var widgetFocused = false
override fun setFocused(focused: Boolean) {
@@ -145,6 +148,14 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
override fun isFocused(): Boolean = widgetFocused
abstract fun render(guiGraphics: GuiGraphics, index: Int, x: Int, y: Int, mouseX: Int, mouseY: Int, partialTick: Float)
fun getRowTop(): Int {
return getRowTop(this)
}
fun getRowLeft(): Int {
return getLeft()
}
}
inner class EmptySeparator(height: Int): Entry(height) {
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.client.gui.battle
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.battles.PassActionResponse
import com.cobblemon.mod.common.battles.ShowdownActionResponse
import com.cobblemon.mod.common.client.CobblemonClient
@@ -17,9 +18,18 @@ import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleActionSelection
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleBackButton
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleGeneralActionSelection
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleMoveSelection
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleSwitchPokemonSelection
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleTargetSelection
import com.cobblemon.mod.common.client.gui.battle.subscreen.ForfeitConfirmationSelection
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleMessagePane
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleOptionTile
import com.cobblemon.mod.common.client.gui.summary.Summary.Companion.PARTY
import com.cobblemon.mod.common.client.gui.summary.widgets.PartyWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.integration.controlify.gui.component.SubManagerComponent
import com.cobblemon.mod.common.client.keybind.boundKey
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding
import com.cobblemon.mod.common.client.render.drawScaledText
@@ -29,8 +39,11 @@ import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.Screen
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import org.joml.Vector4i
class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable, ControllerScreen {
companion object {
const val OPTION_VERTICAL_SPACING = 3
const val OPTION_HORIZONTAL_SPACING = 3
@@ -51,11 +64,49 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
var queuedActions = mutableListOf<() -> Unit>()
val controllerComponentManager = ControllerComponentManager(this)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun init() {
super.init()
messagePane = BattleMessagePane(CobblemonClient.battle!!.messages)
messagePane.opacity = CobblemonClient.battleOverlay.opacityRatio.toFloat().coerceAtLeast(0.3F)
addRenderableWidget(messagePane)
controllerComponentManager.componentList["spectating_quit"] = ControllerComponent().also {
it.onPress = {
quitSpectating()
}
}
controllerComponentManager.componentList["battle_action"] = SubManagerComponent().also {
it.subManager = { getCurrentActionSelection()?.controllerComponentManager }
}
controllerComponentManager.let {
it.focused = it.componentList["battle_action"]
it.onActiveChange = { state ->
if (!state) {
getCurrentActionSelection()?.controllerComponentManager?.active = false
} else {
if (it.currentSelectedComponent == "battle_action") getCurrentActionSelection()?.controllerComponentManager?.active = true
}
}
it.onBack = {
if (it.currentSelectedComponent == "battle_action") {
getCurrentActionSelection()?.controllerComponentManager?.onBack?.invoke()
} else {
minimizeBattle()
}
}
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ (getCurrentActionSelection() as? BattleMoveSelection)?.shiftButtonActive() ?: false },
Component.translatable("cobblemon.controller.battle.swap_position"),
true
))
}
}
fun changeActionSelection(newSelection: BattleActionSelection?) {
@@ -65,6 +116,11 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
if (newSelection != null) {
addRenderableWidget(newSelection)
}
if (controllerComponentManager.currentSelectedComponent == "battle_action" && controllerComponentManager.active) {
focused = null
getCurrentActionSelection()?.controllerComponentManager?.active = true
controllerComponentManager.focused?.onFocus?.invoke()
}
}
}
@@ -85,6 +141,38 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
}
}
override fun getSelectionPosAndSize(): Vector4i? {
val data = super.getSelectionPosAndSize()
if (data != null) {
return data
}
val actionSelection = getCurrentActionSelection()
var newData: Vector4i? = null
when (actionSelection) {
is BattleGeneralActionSelection -> {
val tile = actionSelection.controllerComponentManager.focused?.associatedGUIElement as? BattleOptionTile
if (tile != null) {
newData = Vector4i(tile.x - 2, tile.y - 2, BattleOptionTile.OPTION_WIDTH + 4, BattleOptionTile.OPTION_HEIGHT + 4)
}
}
is BattleMoveSelection -> {
val move = actionSelection.moveTiles[actionSelection.selectedMoveIndex]
newData = Vector4i(move.x.toInt() - 10, move.y.toInt() - 2, BattleMoveSelection.MOVE_WIDTH + 12, BattleMoveSelection.MOVE_HEIGHT + 4)
}
is BattleSwitchPokemonSelection -> {
val tile = actionSelection.tiles[actionSelection.selectedTileIndex]
newData = Vector4i(tile.x.toInt() - 2, tile.y.toInt() - 2, BattleSwitchPokemonSelection.SwitchTile.SELECT_WIDTH + 2, BattleSwitchPokemonSelection.SwitchTile.SELECT_HEIGHT - 1)
}
is BattleTargetSelection -> {
val tile = actionSelection.baseTiles[actionSelection.selectedTileIndex]
newData = Vector4i(tile.x.toInt() - 2, tile.y.toInt() - 2, BattleTargetSelection.TARGET_WIDTH + 4, BattleTargetSelection.TARGET_HEIGHT + 2)
}
}
return newData
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
super.render(context, mouseX, mouseY, delta)
@@ -122,9 +210,10 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
val currentSelection = getCurrentActionSelection()
if (currentSelection == null || currentSelection is BattleGeneralActionSelection ) {
val hideLabelButton = if (controllerComponentManager.active) controllerComponentManager.getVisual(ResourceLocation.fromNamespaceAndPath("controlify", "gui_back")) else PartySendBinding.boundKey().displayName
drawScaledText(
context = context,
text = battleLang("ui.hide_label", PartySendBinding.boundKey().displayName),
text = battleLang("ui.hide_label", hideLabelButton),
x = Minecraft.getInstance().window.guiScaledWidth / 2,
y = (Minecraft.getInstance().window.guiScaledHeight / 5),
opacity = 0.75F * opacity,
@@ -141,6 +230,17 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
)
}
renderSelection(context.pose())
val activeComponent: ControllerComponent?
if (battle.spectating) {
activeComponent = controllerComponentManager.componentList["spectator_quit"]
} else {
activeComponent = controllerComponentManager.componentList["battle_action"]
}
if (controllerComponentManager.focused != activeComponent) {
controllerComponentManager.focused = activeComponent
}
queuedActions.forEach { it() }
queuedActions.clear()
@@ -151,8 +251,6 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
}
fun deriveRootActionSelection(actor: ClientBattleActor, request: SingleActionRequest): BattleActionSelection? {
return if (request.forceSwitch) {
BattleSwitchPokemonSelection(this, request)
} else {
@@ -192,15 +290,21 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
return minimizeBattle()
}
val battle = CobblemonClient.battle
if (battle?.spectating == true && specBackButton.isHovered(mouseX, mouseY)) {
RemoveSpectatorPacket(battle.battleId).sendToServer()
CobblemonClient.endBattle()
if (specBackButton.isHovered(mouseX, mouseY)) {
quitSpectating()
}
return super.mouseClicked(mouseX, mouseY, button)
}
private fun minimizeBattle(): Boolean {
fun quitSpectating() {
val battle = CobblemonClient.battle
if (battle?.spectating == true) {
RemoveSpectatorPacket(battle.battleId).sendToServer()
CobblemonClient.endBattle()
}
}
fun minimizeBattle(): Boolean {
val battle = CobblemonClient.battle ?: return false
battle.minimised = !battle.minimised
PartySendBinding.actioned()
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.ActiveClientBattlePokemon
import com.cobblemon.mod.common.client.battle.ClientBallDisplay
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleMessagePane
import com.cobblemon.mod.common.client.integration.controlify.CobblemonControlifyEntrypoint
import com.cobblemon.mod.common.client.keybind.boundKey
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding
import com.cobblemon.mod.common.client.render.drawScaledText
@@ -114,6 +115,10 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
override val schedulingTracker = SchedulingTracker()
override fun render(context: GuiGraphics, tickCounter: DeltaTracker) {
val matrixStack = context.pose()
matrixStack.pushPose()
matrixStack.translate(0.0, 0.0, -50.0) // Push back so controlify gui renders infront
val tickDelta = tickCounter.realtimeDeltaTicks.takeIf { !Minecraft.getInstance()!!.isPaused } ?: 0F
schedulingTracker.update(tickDelta / 20F)
passedSeconds += tickDelta / 20
@@ -142,9 +147,14 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
if (Minecraft.getInstance().screen !is BattleGUI && battle.mustChoose) {
val textOpacity = PROMPT_TEXT_OPACITY_CURVE(passedSeconds)
val bindingComponent = if (Cobblemon.implementation.isModInstalled("controlify") && CobblemonControlifyEntrypoint.controllerActive())
CobblemonControlifyEntrypoint.SEND_OUT_POKEMON!!.inputGlyph()
else PartySendBinding.boundKey().displayName
drawScaledText(
context = context,
text = battleLang("ui.actions_label", PartySendBinding.boundKey().displayName),
text = battleLang("ui.actions_label", bindingComponent),
x = Minecraft.getInstance().window.guiScaledWidth / 2,
y = Minecraft.getInstance().window.guiScaledHeight / 5,
opacity = textOpacity,
@@ -160,6 +170,8 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
messagePane.opacity = 0.3F
messagePane.render(context, 0, 0, 0F)
}
matrixStack.popPose()
}
fun drawTile(context: GuiGraphics, tickDelta: Float, activeBattlePokemon: ActiveClientBattlePokemon, left: Boolean, rank: Int, dexState: PokedexEntryProgress, hasCommand: Boolean = false, isHovered: Boolean = false, isCompact: Boolean = false) {
@@ -297,10 +309,11 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
(y + portraitDiameter + portraitOffsetY).toInt(),
)
matrixStack.pushPose()
matrixStack.scale(1F, 1F, 0.2F)
matrixStack.translate(
portraitStartX + portraitDiameter / 2.0,
y.toDouble() + portraitOffsetY - if (isCompact) 15.0 else 5.0,
0.0
-50.0
)
if (ballState != null && ballState.currentPose != "shut") {
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.gui.battle.subscreen
import com.cobblemon.mod.common.api.gui.ParentWidget
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.mojang.blaze3d.platform.InputConstants
import net.minecraft.network.chat.MutableComponent
@@ -26,6 +27,8 @@ abstract class BattleActionSelection(
val opacity: Float
get() = battleGUI.opacity
val controllerComponentManager = ControllerComponentManager(battleGUI)
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if(pButton == InputConstants.MOUSE_BUTTON_LEFT){
return mousePrimaryClicked(pMouseX, pMouseY)
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleOptionTile
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.util.battleLang
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
@@ -41,6 +42,7 @@ class BattleGeneralActionSelection(
val lastAnwseredRequest = CobblemonClient.battle?.getLastAnsweredRequest()
val tiles = mutableListOf<BattleOptionTile>()
init {
var rank = 0
@@ -76,22 +78,41 @@ class BattleGeneralActionSelection(
}
}
}
controllerComponentManager.let {
it.focused = it.componentList["tile_0_0"]
it.onBack = {
battleGUI.minimizeBattle()
}
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
}
private fun addOption(rank: Int, text: MutableComponent, texture: ResourceLocation, onClick: () -> Unit) {
val startY = Minecraft.getInstance().window.guiScaledHeight - BattleGUI.OPTION_VERTICAL_OFFSET
val x = if (rank % 2 == 0) BattleGUI.OPTION_ROOT_X else BattleGUI.OPTION_ROOT_X + BattleGUI.OPTION_HORIZONTAL_SPACING + BattleOptionTile.OPTION_WIDTH
val y = if (rank > 1) startY + BattleOptionTile.OPTION_HEIGHT + BattleGUI.OPTION_HORIZONTAL_SPACING else startY
tiles.add(
BattleOptionTile(
battleGUI = battleGUI,
x = x,
y = y,
resource = texture,
text = text,
onClick = onClick
)
val tile = BattleOptionTile(
battleGUI = battleGUI,
x = x,
y = y,
resource = texture,
text = text,
onClick = onClick
)
tiles.add(tile)
val row = rank / 2
val column = rank % 2
controllerComponentManager.componentList["tile_${row}_${column}"] = ControllerComponent().also {
it.associatedGUIElement = tile
if (row == 1) it.up = { controllerComponentManager.componentList["tile_0_${column}"] }
else it.down = { controllerComponentManager.componentList["tile_1_${column}"] }
if (column == 1) it.left = { controllerComponentManager.componentList["tile_${row}_0"] }
else it.right = { controllerComponentManager.componentList["tile_${row}_1"] }
it.onPress = { tile.onClick.invoke() }
}
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -21,10 +21,12 @@ import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.toRGB
import com.cobblemon.mod.common.util.startWith
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
@@ -66,6 +68,10 @@ class BattleMoveSelection(
MoveTile(this, inBattleMove, x, y)
}
var moveTiles = baseTiles
set(value) {
field = value
recalculateMoveGrid()
}
val backButton = BattleBackButton(x - 11F, Minecraft.getInstance().window.guiScaledHeight - 22F)
val gimmickButtons = moveSet.getGimmicks().filter { it !in moveSet.pendingGimmickUsedThisTurn }.mapIndexed { index, gimmick ->
@@ -76,6 +82,53 @@ class BattleMoveSelection(
val shiftButton = BattleShiftButton(x + 22.5F, Minecraft.getInstance().window.guiScaledHeight - 22F )
private fun recalculateMoveGrid() {
controllerComponentManager.componentList.filter { it.key.startsWith("move_") }.forEach { controllerComponentManager.componentList.remove(it.key) }
moveTiles.forEachIndexed { index, tile ->
val column = index % 2
val row = index / 2
controllerComponentManager.componentList["move_${row}_${column}"] = ControllerComponent().also {
if (row == 1) it.up = { controllerComponentManager.componentList["move_${0}_${column}"] }
else it.down = { controllerComponentManager.componentList["move_${1}_${column}"] }
if (column == 1) it.left = { controllerComponentManager.componentList["move_${row}_0"] }
else it.right = { controllerComponentManager.componentList["move_${row}_1"] }
it.onPress = {
if(request.activePokemon.getFormat().battleType.pokemonPerSide == 1) {
tile.onClick()
} else {
battleGUI.changeActionSelection(BattleTargetSelection(battleGUI, request, tile.move, tile.response.gimmickID, tile.move.gimmickMove))
playDownSound(Minecraft.getInstance().soundManager)
}
}
it.onSpecial1 = {
if (shiftButtonActive()) {
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.selectAction(request, ShiftActionResponse())
}
}
it.onFocus = { tile.controllerSelected = true }
it.onUnfocus = { tile.controllerSelected = false }
it.onSelect = { selectedMoveIndex = index }
}
}
}
var selectedMoveIndex = 0
init {
recalculateMoveGrid()
controllerComponentManager.let {
it.focused = it.componentList["move_0_0"]
it.onBack = {
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.changeActionSelection(null)
}
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
}
open class MoveTile(
val moveSelection: BattleMoveSelection,
val move: InBattleMove,
@@ -99,6 +152,8 @@ class BattleMoveSelection(
}
}
var controllerSelected = false
fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val selectConditionOpacity = moveSelection.opacity * if (!selectable) 0.5F else 1F
@@ -110,7 +165,7 @@ class BattleMoveSelection(
y = y,
width = MOVE_WIDTH,
height = MOVE_HEIGHT,
vOffset = if (selectable && isHovered(mouseX.toDouble(), mouseY.toDouble())) MOVE_HEIGHT else 0,
vOffset = if (selectable && (isHovered(mouseX.toDouble(), mouseY.toDouble()) || controllerSelected)) MOVE_HEIGHT else 0,
textureHeight = MOVE_HEIGHT * 2,
red = rgb.first,
green = rgb.second,
@@ -175,7 +230,7 @@ class BattleMoveSelection(
)
}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX >= x && mouseX <= x + MOVE_WIDTH && mouseY >= y && mouseY <= y + MOVE_HEIGHT
fun isHovered(mouseX: Double, mouseY: Double) = (mouseX >= x && mouseX <= x + MOVE_WIDTH && mouseY >= y && mouseY <= y + MOVE_HEIGHT)
fun onClick() {
if (!selectable) return
@@ -184,6 +239,10 @@ class BattleMoveSelection(
}
}
fun shiftButtonActive(): Boolean {
return this.request.activePokemon.getFormat().battleType.slotsPerActor == 3 && (request.activePokemon.getPNX()[2] == 'a' || request.activePokemon.getPNX()[2] == 'c')
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
moveTiles.forEach {
it.render(context, mouseX, mouseY, delta)
@@ -192,7 +251,7 @@ class BattleMoveSelection(
gimmickButtons.forEach {
it.render(context.pose(), mouseX, mouseY, delta)
}
if(this.request.activePokemon.getFormat().battleType.slotsPerActor == 3 && (request.activePokemon.getPNX()[2] == 'a' || request.activePokemon.getPNX()[2] == 'c')) {
if(shiftButtonActive()) {
shiftButton.render(context, mouseX, mouseY, delta)
}
}
@@ -214,7 +273,7 @@ class BattleMoveSelection(
} else if (gimmick != null) {
gimmickButtons.filter { it != gimmick }.forEach { it.toggled = false }
moveTiles = if (gimmick.toggle()) gimmick.tiles else baseTiles
} else if(shiftButton.isHovered(mouseX,mouseY)) {
} else if(shiftButton.isHovered(mouseX,mouseY) && shiftButtonActive()) {
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.selectAction(request, ShiftActionResponse())
}
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.getDepletableRedGreen
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
@@ -68,6 +69,8 @@ class BattleSwitchPokemonSelection(
var isReviving = false
var selectedTileIndex = 0
init {
val pendingActionRequests = CobblemonClient.battle!!.pendingActionRequests
val switchingInPokemon = pendingActionRequests.mapNotNull { it.response }.filterIsInstance<SwitchActionResponse>().map { it.newPokemonId }
@@ -92,6 +95,33 @@ class BattleSwitchPokemonSelection(
tiles.add(SwitchTile(this, slotX, slotY, pokemon, showdownPokemon, isFainted, isCurrentlyInBattle))
}
tiles.forEachIndexed { index, tile ->
val row = index / 2
val column = index % 2
val maxRow = (tiles.size + 1) / 2
controllerComponentManager.componentList["tile_${row}_${column}"] = ControllerComponent().also {
if (row - 1 >= 0) it.up = { controllerComponentManager.componentList["tile_${row-1}_${column}"] }
if (row + 1 < maxRow) it.down = { controllerComponentManager.componentList["tile_${row+1}_${column}"] }
if (column == 1) it.left = { controllerComponentManager.componentList["tile_${row}_0"] }
else if (row != maxRow || tiles.size % 2 == 0) it.right = { controllerComponentManager.componentList["tile_${row}_1"] }
it.onPress = { clickTile(tile) }
it.onFocus = { tile.controllerSelected = true }
it.onUnfocus = { tile.controllerSelected = false }
it.onSelect = { selectedTileIndex = index }
}
}
controllerComponentManager.let {
it.focused = it.componentList["tile_0_0"]
it.onBack = {
battleGUI.changeActionSelection(null)
playDownSound(Minecraft.getInstance().soundManager)
}
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
}
fun getSlotPosition(index: Int): Pair<Float, Float> {
@@ -154,11 +184,15 @@ class BattleSwitchPokemonSelection(
playDownSound(Minecraft.getInstance().soundManager)
return true
}
val clicked = tiles.find { it.isHovered(mouseX, mouseY) && if (isReviving) it.isFainted else (!it.isFainted && !it.isCurrentlyInBattle) } ?: return false
val pokemon = clicked.pokemon
val clicked = tiles.find { it.isHovered(mouseX, mouseY) } ?: return false
return clickTile(clicked)
}
fun clickTile(tile: SwitchTile): Boolean {
if (!(if (isReviving) tile.isFainted else (!tile.isFainted && !tile.isCurrentlyInBattle))) return false
val pokemon = tile.pokemon
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.selectAction(request, SwitchActionResponse(pokemon.uuid))
return true
}
@@ -190,7 +224,9 @@ class BattleSwitchPokemonSelection(
val state = FloatingState()
fun isHovered(mouseX: Double, mouseY: Double) = mouseX in x..(x + SELECT_WIDTH) && mouseY in (y..(y + SELECT_HEIGHT))
var controllerSelected = false
fun isHovered(mouseX: Double, mouseY: Double) = (mouseX in x..(x + SELECT_WIDTH) && mouseY in (y..(y + SELECT_HEIGHT)))
fun render(context: GuiGraphics, mouseX: Double, mouseY: Double, deltaTicks: Float) {
state.currentAspects = pokemon.aspects
@@ -229,7 +265,7 @@ class BattleSwitchPokemonSelection(
y = y,
width = SELECT_WIDTH,
height = SELECT_HEIGHT,
vOffset = if (!isFainted && (isHovered(mouseX, mouseY) || isCurrentlyInBattle)) SELECT_HEIGHT else 0,
vOffset = if (!isFainted && (isHovered(mouseX, mouseY) || isCurrentlyInBattle || controllerSelected)) SELECT_HEIGHT else 0,
textureHeight = SELECT_HEIGHT * 2,
)
@@ -26,6 +26,7 @@ import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleSwitchPokemonSelection.Companion
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.pokemon.Gender
@@ -111,10 +112,35 @@ class BattleTargetSelection(
else -> if (isAlly) ArrowDirection.UP else ArrowDirection.DOWN
}
}
TargetTile(this, target, x, y, arrowDirection)
var out = TargetTile(this, target, x, y, arrowDirection)
controllerComponentManager.componentList["tile_${if (isAlly) 0 else 1}_${fieldPos}"] = ControllerComponent().also {
if (isAlly) it.up = { controllerComponentManager.componentList["tile_1_${fieldPos}"] }
else it.down = { controllerComponentManager.componentList["tile_0_${fieldPos}"] }
if (fieldPos - 1 >= 0) it.left = { controllerComponentManager.componentList["tile_${if (isAlly) 0 else 1}_${fieldPos-1}"] }
if (fieldPos + 1 < teamSize) it.right = { controllerComponentManager.componentList["tile_${if (isAlly) 0 else 1}_${fieldPos+1}"] }
it.onPress = { out.onClick() }
it.onFocus = { out.controllerSelected = true }
it.onUnfocus = { out.controllerSelected = false }
it.onSelect = { selectedTileIndex = index }
}
out
}
var targetTiles = baseTiles
var selectedTileIndex = 0
init {
controllerComponentManager.let {
it.focused = it.componentList["tile_0_0"]
it.onBack = {
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.changeActionSelection(BattleMoveSelection(battleGUI, request))
}
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
}
open inner class TargetTile(
val targetSelection: BattleTargetSelection,
@@ -151,6 +177,8 @@ class BattleTargetSelection(
ArrowDirection.DOWN -> Pair(this.x + TARGET_WIDTH / 2 - 4F, this.y - 7)
}
var controllerSelected = false
fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val passedSeconds = CobblemonClient.battleOverlay.passedSeconds // This syncs the role throbbers
val selectConditionOpacity = targetSelection.opacity * if (!selectable) 0.5F else 1F
@@ -304,9 +332,9 @@ class BattleTargetSelection(
fun isHovered(mouseX: Double, mouseY: Double): Boolean {
if(isMultiTarget) {
return targetTiles.firstOrNull { it.selectable && mouseX >= it.x && mouseX <= it.x + TARGET_WIDTH && mouseY >= it.y && mouseY <= it.y + TARGET_HEIGHT } != null
return targetTiles.firstOrNull { it.selectable && ((mouseX >= it.x && mouseX <= it.x + TARGET_WIDTH && mouseY >= it.y && mouseY <= it.y + TARGET_HEIGHT) || it.controllerSelected) } != null
}
return mouseX >= x && mouseX <= x + TARGET_WIDTH && mouseY >= y && mouseY <= y + TARGET_HEIGHT
return mouseX >= x && mouseX <= x + TARGET_WIDTH && mouseY >= y && mouseY <= y + TARGET_HEIGHT || controllerSelected
}
fun onClick() {
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.interact.battleRequest.BattleResponseButton
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
@@ -51,21 +52,52 @@ class ForfeitConfirmationSelection(
acceptButton = BattleResponseButton(xPos + 22, yPos + 18, true) {}
declineButton = BattleResponseButton(xPos + 57, yPos + 18, false) {}
controllerComponentManager.componentList["accept_button"] = ControllerComponent().also {
it.associatedGUIElement = acceptButton
it.onPress = {
onAccept()
playDownSound(Minecraft.getInstance().soundManager)
}
it.right = { controllerComponentManager.componentList["decline_button"] }
}
controllerComponentManager.componentList["decline_button"] = ControllerComponent().also {
it.associatedGUIElement = declineButton
it.onPress = {
onDecline()
playDownSound(Minecraft.getInstance().soundManager)
}
it.left = { controllerComponentManager.componentList["accept_button"] }
}
controllerComponentManager.let {
it.focused = it.componentList["decline_button"]
it.onBack = {
it.componentList["decline_button"]?.onPress?.invoke()
}
}
}
fun onAccept() {
battleGUI.selectAction(request, ForfeitActionResponse())
// Need to fill out any other pending requests
var pendingRequest = CobblemonClient.battle?.getFirstUnansweredRequest()
while (pendingRequest != null) {
battleGUI.selectAction(pendingRequest, PassActionResponse)
pendingRequest = CobblemonClient.battle?.getFirstUnansweredRequest()
}
}
fun onDecline() {
battleGUI.changeActionSelection(null)
}
override fun mousePrimaryClicked(mouseX: Double, mouseY: Double): Boolean {
if (declineButton.isHovered(mouseX, mouseY) || acceptButton.isHovered(mouseX, mouseY)) {
if (declineButton.isHovered(mouseX, mouseY)) {
battleGUI.changeActionSelection(null)
onDecline()
} else {
battleGUI.selectAction(request, ForfeitActionResponse())
// Need to fill out any other pending requests
var pendingRequest = CobblemonClient.battle?.getFirstUnansweredRequest()
while (pendingRequest != null) {
battleGUI.selectAction(pendingRequest, PassActionResponse)
pendingRequest = CobblemonClient.battle?.getFirstUnansweredRequest()
}
onAccept()
}
playDownSound(Minecraft.getInstance().soundManager)
return true
@@ -50,7 +50,7 @@ class BattleOptionTile(
alpha = opacity,
width = OPTION_WIDTH,
height = OPTION_HEIGHT,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) OPTION_HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || focused) OPTION_HEIGHT else 0,
textureHeight = OPTION_HEIGHT * 2
)
@@ -80,7 +80,7 @@ class BattleOptionTile(
override fun isFocused() = focused
fun isHovered(mouseX: Double, mouseY: Double) = mouseX > x && mouseY > y && mouseX < x + OPTION_WIDTH && mouseY < y + OPTION_HEIGHT
fun isHovered(mouseX: Double, mouseY: Double) = (mouseX > x && mouseY > y && mouseX < x + OPTION_WIDTH && mouseY < y + OPTION_HEIGHT)
override fun updateNarration(builder: NarrationElementOutput) {
builder.add(NarratedElementType.TITLE, text)
@@ -10,6 +10,10 @@ package com.cobblemon.mod.common.client.gui.config
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.config.variable.CobblemonConfigVariable
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.config.CobblemonConfig
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.ifServer
@@ -22,11 +26,14 @@ import net.minecraft.client.gui.components.StringWidget
import net.minecraft.client.gui.layouts.HeaderAndFooterLayout
import net.minecraft.client.gui.layouts.LinearLayout
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.CommonComponents
import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvents
class CobblemonConfigScreen(
private val previousScreen: Screen?,
) : Screen("gui.cobblemon_config.title".asTranslated()), CobblemonRenderable {
) : Screen("gui.cobblemon_config.title".asTranslated()), CobblemonRenderable, ControllerScreen {
companion object {
const val HEIGHT = 240
@@ -55,6 +62,13 @@ class CobblemonConfigScreen(
private var searchString: String = ""
val controllerComponentManager = ControllerComponentManager(this)
var lastSelectedEntry = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun init() {
super.init()
@@ -98,6 +112,46 @@ class CobblemonConfigScreen(
addRenderableWidget(widget)
}
arrangeLayoutElements()
controllerComponentManager.componentList["search"] = ControllerComponent().also {
it.associatedGUIElement = searchEdit
it.down = {
val variableEntries = variableList.children().filter { it is CobblemonConfigVariable }
if (variableEntries.isNotEmpty()) {
controllerComponentManager.componentList["entry_${lastSelectedEntry}_input"]
} else {
controllerComponentManager.componentList["done"]
}
}
}
controllerComponentManager.componentList["done"] = ControllerComponent().also {
it.associatedGUIElement = doneButton
it.up = {
val variableEntries = variableList.children().filter { it is CobblemonConfigVariable }
if (variableEntries.isNotEmpty()) {
controllerComponentManager.componentList["entry_${lastSelectedEntry}_input"]
} else {
controllerComponentManager.componentList["search"]
}
}
}
controllerComponentManager.let {
it.focused = it.componentList["search"]
it.onBack = {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 1.0F))
doneButtonClick()
}
it.onStart = {
it.focused = it.componentList["search"]
it.openKeyboard.invoke(it.focused!!.associatedGUIElement as EditBox)
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 1.0F))
}
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.START,
{ true },
Component.translatable("cobblemon.controller.quick_search")
))
}
}
private fun arrangeLayoutElements() {
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.client.gui.config.CobblemonConfigScreen.Companio
import com.cobblemon.mod.common.client.gui.config.CobblemonConfigScreen.Companion.SLOT_WIDTH
import com.cobblemon.mod.common.client.gui.config.CobblemonConfigVariableList.CobblemonConfigVariableListEntry
import com.cobblemon.mod.common.client.gui.config.variable.*
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.config.Category
import com.cobblemon.mod.common.config.CobblemonConfig
@@ -134,6 +135,63 @@ class CobblemonConfigVariableList(
}
scrollAmount = min(maxScroll.toDouble(), scrollAmount)
val variableEntries = children().filter { it is CobblemonConfigVariable }
variableEntries.forEachIndexed { index, entry ->
parent.controllerComponentManager.componentList["entry_${index}_input"] = ControllerComponent().also {
when (entry) {
is CobblemonConfigButtonVariable -> it.associatedGUIElement = entry.button
is CobblemonConfigEditBoxVariable -> it.associatedGUIElement = entry.editBox
}
it.right = {
if ((entry as CobblemonConfigVariable).resetButton.active) {
parent.controllerComponentManager.componentList["entry_${index}_reset"]
}
else null
}
if (index + 1 <= variableEntries.size - 1) it.down = { parent.controllerComponentManager.componentList["entry_${index+1}_input"] }
else it.down = { parent.controllerComponentManager.componentList["done"] }
if (index - 1 >= 0) it.up = { parent.controllerComponentManager.componentList["entry_${index-1}_input"] }
else it.up = { parent.controllerComponentManager.componentList["search"] }
it.onSelect = { parent.lastSelectedEntry = index }
it.onFocus = { ensureVisible(entry) }
}
parent.controllerComponentManager.componentList["entry_${index}_reset"] = ControllerComponent().also {
it.associatedGUIElement = (entry as CobblemonConfigVariable).resetButton
it.left = { parent.controllerComponentManager.componentList["entry_${index}_input"] }
if (index + 1 <= variableEntries.size - 1) it.down = {
if ((variableEntries[index+1] as CobblemonConfigVariable).resetButton.active) {
parent.controllerComponentManager.componentList["entry_${index+1}_reset"]
}
else null
}
else it.down = { parent.controllerComponentManager.componentList["done"] }
if (index - 1 >= 0) it.up = {
if ((variableEntries[index-1] as CobblemonConfigVariable).resetButton.active) {
parent.controllerComponentManager.componentList["entry_${index-1}_reset"]
}
else null
}
else it.up = { parent.controllerComponentManager.componentList["search"] }
it.onPress = {
entry.resetButton.also { button ->
button.onPress()
button.playDownSound(Minecraft.getInstance().soundManager)
parent.controllerComponentManager.also { manager ->
manager.focused = manager.componentList["entry_${index}_input"]
}
}
}
it.onSelect = { parent.lastSelectedEntry = index }
it.onFocus = { ensureVisible(entry) }
}
}
parent.controllerComponentManager.let {
if (it.currentSelectedComponent.startsWith("entry_")) {
it.focused = it.componentList["search"]
}
}
parent.lastSelectedEntry = 0
}
abstract class CobblemonConfigVariableListEntry(
@@ -26,6 +26,9 @@ import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueOptionWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialoguePortraitWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueTextInputWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueTimerWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.net.messages.client.dialogue.dto.DialogueDTO
import com.cobblemon.mod.common.net.messages.client.dialogue.dto.DialogueGibberDTO
@@ -45,9 +48,10 @@ import net.minecraft.client.gui.components.EditBox
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvent
class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTranslated()), CobblemonRenderable {
class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTranslated()), CobblemonRenderable, ControllerScreen {
val speakers = dialogueDTO.speakers?.mapNotNull { (key, value) ->
val name = value.name
when (val face = value.face) {
@@ -141,6 +145,12 @@ class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTra
)
}
var controllerComponentManager = ControllerComponentManager(this)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun renderBackground(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {}
override fun renderBlurredBackground(delta: Float) {}
@@ -242,6 +252,34 @@ class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTra
}
this.dialogueStartTick = minecraft!!.player!!.tickCount
controllerComponentManager = ControllerComponentManager(this)
controllerComponentManager.componentList["none"] = ControllerComponent().also {
it.onPress = { dialogueBox.onClick() }
}
dialogueOptionWidgets.forEachIndexed { index, option ->
controllerComponentManager.componentList["option_${index}"] = ControllerComponent().also {
it.associatedGUIElement = option
if (index + 1 <= dialogueOptionWidgets.size - 1) it.right = { controllerComponentManager.componentList["option_${index+1}"] }
if (index - 1 >= 0) it.left = { controllerComponentManager.componentList["option_${index-1}"] }
it.onPress = { if (!option.onClick()) dialogueBox.onClick() }
}
}
controllerComponentManager.let {
it.focused = if (dialogueOptionWidgets.isNotEmpty()) {
it.componentList["option_0"]
} else {
it.componentList["none"]
}
it.onSpecial1 = { dialogueBox.onClick() }
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ dialogueBox.dialogue.dialogueInput.allowSkip && dialogueBox.dialogue.dialogueInput.inputType in listOf(DialogueInputDTO.InputType.NONE, DialogueInputDTO.InputType.AUTO_CONTINUE)},
Component.translatable("cobblemon.controller.dialogue.skip"),
true
))
}
}
var gibberIndex = 0
@@ -265,6 +303,8 @@ class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTra
super.render(guiGraphics, mouseX, mouseY, delta)
expressionsToRun.forEach { runtime.resolve(it) }
expressionsToRun = emptyList()
renderSelection(guiGraphics.pose())
}
override fun isPauseScreen() = false
@@ -160,10 +160,17 @@ class DialogueBox(
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
// TODO change this coordinate check to just be "anywhere the scroll bar isn't"
if (!dialogueScreen.waitingForServerUpdate &&
mouseX > this.x && mouseX < this.scrollbarPosition &&
if (mouseX > this.x && mouseX < this.scrollbarPosition &&
mouseY > this.y && mouseY < this.bottom
) {
if (onClick()) return true
}
return super.mouseClicked(mouseX, mouseY, button)
}
fun onClick(): Boolean {
if (!dialogueScreen.waitingForServerUpdate) {
if (dialogue.dialogueInput.allowSkip && dialogue.dialogueInput.inputType in listOf(DialogueInputDTO.InputType.NONE, DialogueInputDTO.InputType.AUTO_CONTINUE)) {
val gibber = dialogueScreen.gibber
if (gibber != null && gibber.graduallyShowText && !dialogueScreen.gibberDone) {
@@ -177,8 +184,7 @@ class DialogueBox(
return true
}
}
return super.mouseClicked(mouseX, mouseY, button)
return false
}
class DialogueLine(val line: FormattedCharSequence, val lineColor: Int) : Entry<DialogueLine>() {
@@ -42,7 +42,7 @@ class DialogueOptionWidget(
y = y,
width = width,
height = height,
vOffset = if (selectable) (if (isHovered) height else 0) else (height * 2),
vOffset = if (selectable) (if (isHovered || isFocused) height else 0) else (height * 2),
textureHeight = height * 3,
)
@@ -58,10 +58,14 @@ class DialogueOptionWidget(
}
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (!isHovered) return false
return onClick()
}
fun onClick(): Boolean {
if (!dialogueScreen.renderInput()) {
return false
}
if (!isHovered) return false
if (!selectable || dialogueScreen.waitingForServerUpdate) return true
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.client.gui.interact.battleRequest
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.ColourLibrary
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
@@ -16,6 +17,9 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.ClientBattleChallenge
import com.cobblemon.mod.common.client.battle.ClientTeamRequest
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
import com.cobblemon.mod.common.net.messages.server.*
@@ -27,16 +31,18 @@ import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import java.util.*
class BattleConfigureGUI(
private val packet: PlayerInteractOptionsPacket,
private val activeRequest: ClientBattleChallenge? = null,
private val activeTeamRequest: ClientTeamRequest? = null
) : Screen(lang("ui.challenge.challenge_title")), CobblemonRenderable {
) : Screen(lang("ui.challenge.challenge_title")), CobblemonRenderable, ControllerScreen {
companion object {
const val SIZE = 113
private val backgroundResource = cobblemonResource("textures/gui/interact/request/battle_request.png")
@@ -200,6 +206,12 @@ class BattleConfigureGUI(
private var hasRequest = false
private var ticksPassed = 0F
val controllerComponentManager = ControllerComponentManager(this)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun renderBlurredBackground(delta: Float) { }
override fun renderMenuBackground(context: GuiGraphics) { }
@@ -218,29 +230,29 @@ class BattleConfigureGUI(
}
val (x, y) = getDimensions()
var battleAccept: BattleResponseButton? = null
var battleDecline: BattleResponseButton? = null
if(pendingRequest != null) {
// Draw Accept/Decline buttons
this.addRenderableWidget(
BattleResponseButton(
x + 22,
y + 99,
true
) {
battleRequestMap[options[currentPage]]?.onResponse?.let { it1 -> it1(packet, pendingRequest.requestID, true) }
closeGUI()
}
)
battleAccept = BattleResponseButton(
x + 22,
y + 99,
true
) {
battleRequestMap[options[currentPage]]?.onResponse?.let { it1 -> it1(packet, pendingRequest.requestID, true) }
closeGUI()
}
this.addRenderableWidget(battleAccept)
this.addRenderableWidget(
BattleResponseButton(
x + 56,
y + 99,
false
) {
battleRequestMap[options[currentPage]]?.onResponse?.let { it1 -> it1(packet, pendingRequest.requestID, false) }
closeGUI()
}
)
battleDecline = BattleResponseButton(
x + 56,
y + 99,
false
) {
battleRequestMap[options[currentPage]]?.onResponse?.let { it1 -> it1(packet, pendingRequest.requestID, false) }
closeGUI()
}
this.addRenderableWidget(battleDecline)
}
else {
@@ -306,6 +318,36 @@ class BattleConfigureGUI(
this.addRenderableWidget(autoLevelNavButtonRight)
}
}
controllerComponentManager.componentList["battle_accept"] = ControllerComponent().also {
it.associatedGUIElement = battleAccept
it.right = { controllerComponentManager.componentList["battle_decline"] }
}
controllerComponentManager.componentList["battle_decline"] = ControllerComponent().also {
it.associatedGUIElement = battleDecline
it.left = { controllerComponentManager.componentList["battle_accept"] }
}
controllerComponentManager.componentList["request"] = ControllerComponent().also {
it.associatedGUIElement = requestButton
}
controllerComponentManager.let {
if (pendingRequest != null) {
it.focused = it.componentList["battle_decline"]
} else {
it.focused = it.componentList["request"]
it.onTabNav = { nextTab ->
currentPage = (currentPage + (if (nextTab) 1 else -1)) % options.count()
updateRequestButtonText()
playSound(CobblemonSounds.GUI_CLICK)
}
if (battleRequestMap[options[currentPage]]?.option != PlayerInteractOptionsPacket.Options.TEAM_REQUEST) {
it.onSecondaryTabNav = { nextTab ->
levelRulesetOptionIndex += if (nextTab) 1 else -1
playSound(CobblemonSounds.GUI_CLICK)
}
}
}
}
}
fun updateRequestButtonText() {
@@ -438,6 +480,8 @@ class BattleConfigureGUI(
)
super.render(context, mouseX, mouseY, delta)
renderSelection(context.pose())
}
private fun getDimensions(): Pair<Int, Int> {
@@ -454,4 +498,7 @@ class BattleConfigureGUI(
Minecraft.getInstance().setScreen(null)
}
fun playSound(soundEvent: SoundEvent) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
}
}
@@ -35,7 +35,7 @@ class BattleResponseButton(
}
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
val hovered = (isHovered(pMouseX.toDouble(), pMouseY.toDouble()))
val hovered = (isHovered(pMouseX.toDouble(), pMouseY.toDouble()) || isFocused)
blitk(
matrixStack = context.pose(),
x = x / SCALE,
@@ -14,6 +14,9 @@ import com.cobblemon.mod.common.api.callback.MoveSelectDTO
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
import com.cobblemon.mod.common.util.cobblemonResource
@@ -36,7 +39,7 @@ class MoveSelectConfiguration(
class MoveSelectGUI(
val config: MoveSelectConfiguration
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable {
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable, ControllerScreen {
companion object {
const val WIDTH = 122
const val HEIGHT = 133
@@ -46,6 +49,8 @@ class MoveSelectGUI(
var closed = false
val controllerComponentManager = ControllerComponentManager(this)
constructor(
title: MutableComponent,
moves: List<MoveSelectDTO>,
@@ -63,6 +68,10 @@ class MoveSelectGUI(
)
)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
fun closeProperly() {
closed = true
onClose()
@@ -73,16 +82,21 @@ class MoveSelectGUI(
val y = (height - HEIGHT) / 2
config.moves.forEachIndexed { index, move ->
addRenderableWidget(
MoveSlotButton(
x = x + 7,
y = y + 7 + ((MoveSlotButton.HEIGHT + 3) * index),
move = move.moveTemplate,
pp = move.pp,
ppMax = move.ppMax,
enabled = move.enabled
) { onPress(move) }
)
val moveSlot = MoveSlotButton(
x = x + 7,
y = y + 7 + ((MoveSlotButton.HEIGHT + 3) * index),
move = move.moveTemplate,
pp = move.pp,
ppMax = move.ppMax,
enabled = move.enabled
) { onPress(move) }
addRenderableWidget(moveSlot)
controllerComponentManager.componentList["move_${index}"] = ControllerComponent().also {
it.associatedGUIElement = moveSlot
if (index + 1 <= config.moves.size - 1) it.down = { controllerComponentManager.componentList["move_${index+1}"] }
if (index - 1 >= 0) it.up = { controllerComponentManager.componentList["move_${index-1}"] }
}
}
// Add Exit Button
@@ -97,6 +111,10 @@ class MoveSelectGUI(
)
super.init()
controllerComponentManager.let {
it.focused = it.componentList["move_0"]
}
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
@@ -114,6 +132,8 @@ class MoveSelectGUI(
// Render all added Widgets
super.render(context, mouseX, mouseY, partialTicks)
renderSelection(context.pose())
}
private fun onPress(move: MoveSelectDTO) {
@@ -16,6 +16,9 @@ import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonSelectedPacket
import com.cobblemon.mod.common.net.messages.server.callback.party.PartySelectCancelledPacket
@@ -38,7 +41,7 @@ class PartySelectConfiguration(
)
class PartySelectGUI(
val config: PartySelectConfiguration
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable {
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable, ControllerScreen {
companion object {
const val WIDTH = 163
const val HEIGHT = 132
@@ -50,6 +53,8 @@ class PartySelectGUI(
var closed = false
val controllerComponentManager = ControllerComponentManager(this)
constructor(
title: MutableComponent,
pokemon: List<PartySelectPokemonDTO>,
@@ -67,6 +72,10 @@ class PartySelectGUI(
)
)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
fun closeProperly() {
closed = true
onClose()
@@ -90,20 +99,29 @@ class PartySelectGUI(
slotY += (31 * offsetIndex) + offsetY
}
addRenderableWidget(
PartySlotButton(
parent = this,
x = slotX,
y = slotY,
pokemon = pokemon.pokemonProperties,
aspects = pokemon.aspects,
heldItem = pokemon.heldItem,
currentHealth = pokemon.currentHealth,
maxHealth = pokemon.maxHealth,
enabled = pokemon.enabled,
hoverText = pokemon.hoverText,
) { onPress(pokemon) }
)
val slot = PartySlotButton(
parent = this,
x = slotX,
y = slotY,
pokemon = pokemon.pokemonProperties,
aspects = pokemon.aspects,
heldItem = pokemon.heldItem,
currentHealth = pokemon.currentHealth,
maxHealth = pokemon.maxHealth,
enabled = pokemon.enabled,
hoverText = pokemon.hoverText,
) { onPress(pokemon) }
addRenderableWidget(slot)
val row = index / 2
val column = index % 2
controllerComponentManager.componentList["slot_${row}_${column}"] = ControllerComponent().also {
it.associatedGUIElement = slot
if (column == 0) it.right = { controllerComponentManager.componentList["slot_${row}_${1}"] }
else it.left = { controllerComponentManager.componentList["slot_${row}_${0}"] }
if (row - 1 >= 0) it.up = { controllerComponentManager.componentList["slot_${row-1}_${column}"] }
if (row + 1 <= 2) it.down = { controllerComponentManager.componentList["slot_${row+1}_${column}"] }
}
}
// Add Exit Button
@@ -118,6 +136,10 @@ class PartySelectGUI(
)
super.init()
controllerComponentManager.let {
it.focused = it.componentList["slot_0_0"]
}
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
@@ -155,6 +177,8 @@ class PartySelectGUI(
// Render all added Widgets
super.render(context, mouseX, mouseY, partialTicks)
renderSelection(context.pose())
}
private fun onPress(pokemon: PartySelectPokemonDTO) {
@@ -63,7 +63,7 @@ class PartySlotButton(
private val renderablePokemon = pokemon.asRenderablePokemon().also { it.aspects = aspects }
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
isHovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height && enabled
isHovered = ((pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height) || isFocused) && enabled
val alpha = if (enabled) 1.0 else 0.7
val matrices = context.pose()
@@ -28,7 +28,7 @@ class InteractWheelButton(
private val tooltipText: String?,
x: Int,
y: Int,
private val isEnabled: Boolean,
val isEnabled: Boolean,
private val colour: () -> Vector3f?,
onPress: OnPress,
private val canHover: (Double, Double) -> Boolean
@@ -74,12 +74,12 @@ class InteractWheelButton(
y = y,
width = width,
height = height,
vOffset = if (isHovered(mouseX.toFloat(), mouseY.toFloat()) && isEnabled) height else 0,
vOffset = if ((isHovered(mouseX.toFloat(), mouseY.toFloat()) || isFocused) && isEnabled) height else 0,
textureHeight = height * 2,
alpha = if (isEnabled) 1f else 0.4F
)
if(isEnabled && isHovered(mouseX.toFloat(), mouseY.toFloat())){
if(isEnabled && (isHovered(mouseX.toFloat(), mouseY.toFloat()) || isFocused)){
tooltipText?.let {
context.renderTooltip(Minecraft.getInstance().font, Component.translatable(it), mouseX, mouseY)
}
@@ -162,7 +162,7 @@ class InteractWheelButton(
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
return if (isHovered(mouseX.toFloat(), mouseY.toFloat())) super.mouseClicked(mouseX, mouseY, button) else false
return if (isHovered(mouseX.toFloat(), mouseY.toFloat()) || isFocused) super.mouseClicked(mouseX, mouseY, button) else false
}
override fun getTooltip(): Tooltip? {
@@ -32,9 +32,10 @@ class InteractWheelGUI(private val options: Multimap<Orientation, InteractWheelO
return super.keyPressed(keyCode, scanCode, modifiers)
}
private val buttons = mutableListOf<InteractWheelButton>()
val buttons = mutableListOf<InteractWheelButton>()
private var maxPage = 1
private var currentPage = 0
override fun renderBlurredBackground(delta: Float) { }
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -86,7 +87,7 @@ class InteractWheelGUI(private val options: Multimap<Orientation, InteractWheelO
currentPage = page
buttons.forEach { removeWidget(it) }
buttons.clear()
val orientations = Orientation.values()
val orientations = Orientation.entries.toTypedArray()
orientations.forEach { orientation ->
val option = options[orientation].toList().getOrNull(page)
addButton(orientation, option)
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pc.StorageSlot
import com.cobblemon.mod.common.client.gui.summary.widgets.PartySlotWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
@@ -76,11 +77,58 @@ class PasturePokemonScrollList(
removedEntries.forEach(this::removeEntry)
newEntries.forEach { addEntry(PastureSlot(it, parent)) }
rebuildControllerSelection()
}
}
fun rebuildControllerSelection() {
if (parent.lastSelectedIndex > 0 && parent.lastSelectedIndex >= children().size) parent.lastSelectedIndex = children().size - 1
children().forEachIndexed { index, slot ->
parent.controllerComponentManager.componentList["slot_${index}"] = ControllerComponent().also {
it.associatedGUIElement = slot
if (index - 1 >= 0) it.up = { parent.controllerComponentManager.componentList["slot_${index - 1}"] }
if (index + 1 <= children().size - 1) it.down =
{ parent.controllerComponentManager.componentList["slot_${index + 1}"] }
else it.down = { parent.controllerComponentManager.componentList["recall_all"] }
it.onPress = { slot.moveButton.onPress() }
it.onSpecial1 = { slot.conflictButton.onPress() }
it.onFocus = {
slot.controllerFocused = true
ensureVisible(slot)
}
it.onUnfocus = {
slot.controllerFocused = false
}
}
}
if (parent.controllerComponentManager.currentSelectedComponent.let { it.startsWith("slot_") || it == "empty" }) {
parent.controllerComponentManager.focused =
if (children().isEmpty()) parent.controllerComponentManager.componentList["empty"]
else parent.controllerComponentManager.componentList["slot_${parent.lastSelectedIndex}"]
}
}
override fun getScrollbarPosition() = x + width - 3
public override fun ensureVisible(entry: PastureSlot) {
val i = getRowTop(children().indexOf(entry))
val topDifference = i - y
if (topDifference < 0) {
scroll(topDifference)
}
val bottomDifference = bottom - i - itemHeight - 4
if (bottomDifference < 0) {
scroll(-bottomDifference)
}
}
public override fun getRowTop(index: Int): Int {
return super.getRowTop(index)
}
public override fun addEntry(entry: PastureSlot) = super.addEntry(entry)
public override fun removeEntry(entry: PastureSlot) = super.removeEntry(entry)
override fun renderSelection(context: GuiGraphics, y: Int, entryWidth: Int, entryHeight: Int, borderColor: Int, fillColor: Int) {}
@@ -166,6 +214,8 @@ class PasturePokemonScrollList(
val client: Minecraft = Minecraft.getInstance()
val state = FloatingState()
var controllerFocused = false
fun isOwned() = client.player?.uuid == pokemon.playerId
fun canUnpasture() = isOwned() || parent.pasturePCGUIConfiguration.permissions.canUnpastureOthers
fun canDefend() = PokemonSpecies.getByIdentifier(pokemon.species)?.behaviour?.combat?.willDefendOwner
@@ -182,7 +232,7 @@ class PasturePokemonScrollList(
setEnabled(PokemonBehaviourFlag.PASTURE_CONFLICT in pokemon.behaviourFlags)
}
private val moveButton: PastureSlotIconButton = PastureSlotIconButton(
val moveButton: PastureSlotIconButton = PastureSlotIconButton(
xPos = 0,
yPos = 0,
onPress = {
@@ -191,7 +241,8 @@ class PasturePokemonScrollList(
pastureId = parent.pasturePCGUIConfiguration.pastureId,
pokemonId = pokemon.pokemonId
).sendToServer()
}
},
parent = this@PastureSlot
)
override fun getNarration() = pokemon.displayName
@@ -219,7 +270,7 @@ class PasturePokemonScrollList(
y = y,
height = SLOT_HEIGHT,
width = rowWidth,
vOffset = if (isHovered) SLOT_HEIGHT else 0,
vOffset = if (isHovered || controllerFocused) SLOT_HEIGHT else 0,
textureHeight = SLOT_HEIGHT * 2
)
@@ -18,7 +18,8 @@ import net.minecraft.network.chat.Component
class PastureSlotIconButton(
var xPos: Int, var yPos: Int,
onPress: OnPress
onPress: OnPress,
var parent: PasturePokemonScrollList.PastureSlot
) : Button(xPos, yPos, (SIZE * SCALE).toInt(), (SIZE * SCALE).toInt(), Component.literal("Pasture Move"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
@@ -35,7 +36,7 @@ class PastureSlotIconButton(
y = yPos / SCALE,
width = SIZE,
height = SIZE,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) SIZE else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || parent.controllerFocused) SIZE else 0,
textureHeight = SIZE * 2,
texture = baseResource,
scale = SCALE
@@ -15,6 +15,8 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.pc.PCGUI
import com.cobblemon.mod.common.client.gui.pc.StorageWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.pasture.UnpastureAllPokemonPacket
import com.cobblemon.mod.common.util.cobblemonResource
@@ -34,6 +36,9 @@ class PastureWidget(
private val baseResource = cobblemonResource("textures/gui/pasture/pasture_panel.png")
}
val controllerComponentManager = ControllerComponentManager(storageWidget.pcGui)
var lastSelectedIndex = 0
private val recallButton = RecallButton(
x = x + 6,
y = y + 153
@@ -48,6 +53,23 @@ class PastureWidget(
parent = this
)
init {
controllerComponentManager.componentList["recall_all"] = ControllerComponent().also {
it.associatedGUIElement = recallButton
it.up = {
if (pastureScrollList.children().isEmpty()) controllerComponentManager.componentList["empty"]
else controllerComponentManager.componentList["slot_${pastureScrollList.children().size-1}"]
}
}
controllerComponentManager.componentList["empty"] = ControllerComponent().also {
it.down = { controllerComponentManager.componentList["recall_all"] }
}
controllerComponentManager.let {
it.focused = if (pastureScrollList.children().isEmpty()) it.componentList["empty"]
else it.componentList["slot_0"]
}
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
blitk(
matrixStack = context.pose(),
@@ -40,7 +40,7 @@ class RecallButton(
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || isFocused) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.pasture.PasturePokemonScrollList
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.Summary.Companion.iconCosmeticItemResource
import com.cobblemon.mod.common.client.gui.summary.Summary.Companion.iconHeldItemResource
@@ -28,6 +29,10 @@ import com.cobblemon.mod.common.client.gui.summary.SummaryButton
import com.cobblemon.mod.common.client.gui.summary.widgets.MarkingsWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.common.reformatNatureTextIfMinted
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.integration.controlify.gui.component.SubManagerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.storage.ClientPC
import com.cobblemon.mod.common.client.storage.ClientParty
@@ -44,12 +49,15 @@ import com.cobblemon.mod.common.util.toAssetPath
import com.mojang.blaze3d.platform.InputConstants
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.EditBox
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvent
import kotlin.isInitialized
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvents
import org.joml.Vector4i
class PCGUI(
val pc: ClientPC,
@@ -57,7 +65,7 @@ class PCGUI(
val configuration: PCGUIConfiguration,
val openOnBox: Int = CobblemonClient.lastPcBoxViewed,
val unseenWallpapers: MutableSet<ResourceLocation> = mutableSetOf()
) : Screen(Component.translatable("cobblemon.ui.pc.title")), CobblemonRenderable {
) : Screen(Component.translatable("cobblemon.ui.pc.title")), CobblemonRenderable, ControllerScreen {
companion object {
const val BASE_WIDTH = 349
@@ -128,6 +136,14 @@ class PCGUI(
var selectPointerOffsetIncrement = false
var displayOptions = false
val controllerComponentManager = ControllerComponentManager(this)
var lastSelectedBeforeOptions = ""
var controllerShiftToggle = false
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun renderBlurredBackground(delta: Float) {}
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -198,30 +214,39 @@ class PCGUI(
this.addRenderableWidget(boxNameWidget)
// Initialise box options if not pasture
var optionsButton: IconButton? = null
var wallpaperButton: IconButton? = null
if (storageWidget.pastureWidget == null) {
optionButtons.clear()
addRenderableWidget(
IconButton(
pX = x + 218,
pY = y + 186,
buttonWidth = 16,
buttonHeight = 16,
resource = buttonOptionsResource,
label = "options"
) {
displayOptions = !displayOptions
if (!displayOptions && wallpaperWidget.visible) {
configuration.showParty = true
wallpaperWidget.visible = false
}
(it as IconButton).highlighted = displayOptions
storageWidget.setupStorageSlots()
for (button in optionButtons) {
button.visible = displayOptions
button.highlighted = false
}
}.also { it.highlighted = displayOptions }
)
optionsButton = IconButton(
pX = x + 218,
pY = y + 186,
buttonWidth = 16,
buttonHeight = 16,
resource = buttonOptionsResource,
label = "options"
) {
if (!displayOptions) {
lastSelectedBeforeOptions = controllerComponentManager.currentSelectedComponent
controllerComponentManager.focused = controllerComponentManager.componentList["setting_button_0"]
controllerShiftToggle = false
} else {
controllerComponentManager.focused = controllerComponentManager.componentList[lastSelectedBeforeOptions]
}
displayOptions = !displayOptions
if (!displayOptions && wallpaperWidget.visible) {
configuration.showParty = true
wallpaperWidget.visible = false
}
(it as IconButton).highlighted = displayOptions
storageWidget.setupStorageSlots()
for (button in optionButtons) {
button.visible = displayOptions
button.highlighted = false
}
}.also { it.highlighted = displayOptions }
addRenderableWidget(optionsButton)
// Add Wallpaper Widget
this.wallpaperWidget = WallpapersScrollingWidget(
@@ -237,7 +262,7 @@ class PCGUI(
this.addRenderableWidget(wallpaperWidget)
// Add Wallpaper Settings Button
this.addRenderableWidget(IconButton(
wallpaperButton = IconButton(
pX = x + 242,
pY = y + 31,
buttonWidth = 20,
@@ -250,14 +275,16 @@ class PCGUI(
wallpaperWidget.visible = !isVisible
(it as IconButton).highlighted = !isVisible
if (!unseenWallpapers.isEmpty()) MarkPCBoxWallpapersSeenPacket(unseenWallpapers).sendToServer()
controllerComponentManager.focused = if (wallpaperWidget.visible) controllerComponentManager.componentList["wallpaper_widget"] else controllerComponentManager.componentList["wallpaper_button"]
}.also {
it.visible = displayOptions
optionButtons.add(it)
})
}
this.addRenderableWidget(wallpaperButton)
PokemonSortMode.entries.forEachIndexed { index, sortType ->
val typeName = sortType.name.lowercase()
this.addRenderableWidget(IconButton(
val button = IconButton(
pX = x + 92 + (12 * index),
pY = y + 31,
buttonWidth = 20,
@@ -268,33 +295,58 @@ class PCGUI(
label = "sort_${typeName}"
) {
storageWidget.resetSelected()
SortPCBoxPacket(pc.uuid, storageWidget.box, sortType, hasShiftDown()).sendToServer()
SortPCBoxPacket(pc.uuid, storageWidget.box, sortType, hasShiftDown() || (controllerComponentManager.active && controllerShiftToggle)).sendToServer()
}.also {
it.visible = displayOptions
optionButtons.add(it)
})
}
this.addRenderableWidget(button)
controllerComponentManager.componentList["setting_button_${index}"] = ControllerComponent().also {
it.associatedGUIElement = button
if (index - 1 >= 0) it.left = { controllerComponentManager.componentList["setting_button_${index-1}"] }
if (index + 1 <= PokemonSortMode.entries.size - 1) it.right = { controllerComponentManager.componentList["setting_button_${index+1}"] }
else it.right = { controllerComponentManager.componentList["wallpaper_button"] }
it.onSpecial1 = {
controllerShiftToggle = !controllerShiftToggle
playSound(CobblemonSounds.PC_CLICK)
}
}
}
controllerComponentManager.componentList["wallpaper_button"] = ControllerComponent().also {
it.associatedGUIElement = wallpaperButton
it.left = { controllerComponentManager.componentList["setting_button_${PokemonSortMode.entries.size - 1}"] }
}
controllerComponentManager.componentList["wallpaper_widget"] = SubManagerComponent().also {
it.subManager = { wallpaperWidget.controllerComponentManager }
it.onSpecial1 = {
controllerShiftToggle = !controllerShiftToggle
playSound(CobblemonSounds.PC_CLICK)
}
}
}
this.markingsWidget = MarkingsWidget(x + 29, y + 96.5, null, false)
this.addRenderableWidget(markingsWidget)
// Held/Cosmetic Item Button
addRenderableWidget(
SummaryButton(
buttonX = x + 67F,
buttonY = y + 107F,
buttonWidth = 12,
buttonHeight = 12,
scale = 0.5F,
resource = iconCosmeticItemResource,
activeResource = iconHeldItemResource,
clickAction = {
showCosmeticItem = !showCosmeticItem
(it as SummaryButton).buttonActive = showCosmeticItem
}
)
val switchCosmetic = SummaryButton(
buttonX = x + 67F,
buttonY = y + 107F,
buttonWidth = 12,
buttonHeight = 12,
scale = 0.5F,
resource = iconCosmeticItemResource,
activeResource = iconHeldItemResource,
clickAction = {
showCosmeticItem = !showCosmeticItem
(it as SummaryButton).buttonActive = showCosmeticItem
}
)
// Held/Cosmetic Item Button
addRenderableWidget(switchCosmetic)
addRenderableWidget(
IconButton(
@@ -312,6 +364,149 @@ class PCGUI(
this.setPreviewPokemon(null)
super.init()
controllerComponentManager.componentList["box_name"] = ControllerComponent().also {
it.associatedGUIElement = boxNameWidget
it.down = { controllerComponentManager.componentList["storage"] }
}
controllerComponentManager.componentList["storage"] = SubManagerComponent().also {
it.subManager = { storageWidget.controllerComponentManager }
it.up = {
var returnVal: ControllerComponent? = null
val manager = storageWidget.controllerComponentManager
if (manager.up() && manager.currentSelectedComponent.startsWith("slot") && storageWidget.grabbedSlot == null) {
returnVal = controllerComponentManager.componentList["box_name"]
}
returnVal
}
it.down = {
var returnVal: ControllerComponent? = null
val manager = storageWidget.controllerComponentManager
if (manager.down() && manager.currentSelectedComponent.startsWith("slot") && storageWidget.grabbedSlot == null) {
returnVal = controllerComponentManager.componentList["filter"]
}
returnVal
}
it.onSpecial1 = {
if (storageWidget.controllerComponentManager.currentSelectedComponent == "pasture") {
storageWidget.controllerComponentManager.special1()
} else {
switchCosmetic.onPress()
playSound(CobblemonSounds.PC_CLICK)
}
}
it.onSpecial2 = { if (storageWidget.pastureWidget == null) displayOptions = !displayOptions }
}
controllerComponentManager.componentList["filter"] = ControllerComponent().also {
it.associatedGUIElement = filterWidget
it.up = { controllerComponentManager.componentList["storage"] }
}
controllerComponentManager.let {
it.focused = it.componentList["storage"]
it.onTabNav = { nextTab ->
storageWidget.box += if (nextTab) 1 else -1
playSound(CobblemonSounds.PC_CLICK)
}
it.onSecondaryTabNav = { nextTab ->
var nextValue = currentStatIndex + if (nextTab) 1 else -1
if (nextValue > 2) nextValue = 0
if (nextValue < 0) nextValue = 2
currentStatIndex = nextValue
playSound(CobblemonSounds.PC_CLICK)
}
it.onStart = {
if (it.currentSelectedComponent != "storage" || storageWidget.grabbedSlot == null) {
it.focused = controllerComponentManager.componentList["filter"]
storageWidget.controllerComponentManager.focused =
storageWidget.controllerComponentManager.componentList["slot_4_0"]
it.openKeyboard.invoke(it.focused!!.associatedGUIElement as EditBox)
playSound(SoundEvents.UI_BUTTON_CLICK.value())
}
}
it.onSpecial2 = {
if (storageWidget.pastureWidget == null && storageWidget.grabbedSlot == null) {
optionsButton!!.onPress()
playSound(CobblemonSounds.PC_CLICK)
}
}
it.onBack = {
if (storageWidget.pastureWidget == null && wallpaperWidget.visible) {
wallpaperButton?.onPress()
playSound(CobblemonSounds.PC_CLICK)
} else if (displayOptions) {
optionsButton!!.onPress()
playSound(CobblemonSounds.PC_CLICK)
} else if (storageWidget.grabbedSlot != null) {
storageWidget.resetSelected()
} else {
closeNormally(true)
}
}
it.onActiveChange = { state ->
if (!state) {
storageWidget.controllerComponentManager.active = false
} else {
if (it.currentSelectedComponent == "storage") storageWidget.controllerComponentManager.active = true
}
}
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.START,
{ true },
Component.translatable("cobblemon.controller.quick_search")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.TAB,
{ true },
Component.translatable("cobblemon.controller.pc.switch_box")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SECONDARY_TAB,
{ true },
Component.translatable("cobblemon.controller.pc.switch_stats")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "storage" && storageWidget.controllerComponentManager.currentSelectedComponent != "pasture" },
Component.translatable("cobblemon.controller.toggle_held_item_type"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "storage" && storageWidget.controllerComponentManager.currentSelectedComponent == "pasture" &&
storageWidget.pastureWidget!!.controllerComponentManager.currentSelectedComponent.startsWith("slot_") },
Component.translatable("cobblemon.controller.pc.toggle_defence"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_2,
{ it.currentSelectedComponent == "storage" && storageWidget.controllerComponentManager.currentSelectedComponent != "pasture" },
Component.translatable("cobblemon.controller.pc.box_settings"),
true
))
}
}
override fun getSelectionPosAndSize(): Vector4i? {
val data = super.getSelectionPosAndSize()
if (data == null) {
when (controllerComponentManager.currentSelectedComponent) {
"wallpaper_widget" -> return Vector4i(wallpaperWidget.rowLeft - 2, wallpaperWidget.getRowTop(wallpaperWidget.children().indexOf(focused)) + 2,
WallpapersScrollingWidget.SLOT_WIDTH + 4, WallpapersScrollingWidget.SLOT_HEIGHT + 4)
"storage" -> {
if (storageWidget.pastureWidget != null && storageWidget.controllerComponentManager.currentSelectedComponent == "pasture") {
val scrollWidget = storageWidget.pastureWidget!!.pastureScrollList
if (storageWidget.pastureWidget!!.controllerComponentManager.currentSelectedComponent.startsWith("slot_")) {
return Vector4i(scrollWidget.rowLeft - 6, scrollWidget.getRowTop(scrollWidget.children().indexOf(focused)),
PasturePokemonScrollList.SLOT_WIDTH + 4, PasturePokemonScrollList.SLOT_HEIGHT + 4)
} else if (storageWidget.pastureWidget!!.controllerComponentManager.currentSelectedComponent == "empty") {
return Vector4i(scrollWidget.x - 2, scrollWidget.y - 11, scrollWidget.width + 4, scrollWidget.height + 13)
}
}
}
}
}
return data
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -685,7 +880,7 @@ class PCGUI(
)
if (!optionButtons.isEmpty() && displayOptions) {
for (button in optionButtons) button.showAlt = hasShiftDown()
for (button in optionButtons) button.showAlt = hasShiftDown() || (controllerComponentManager.active && controllerShiftToggle)
}
super.render(context, mouseX, mouseY, delta)
@@ -700,6 +895,8 @@ class PCGUI(
if (itemHovered) context.renderTooltip(Minecraft.getInstance().font, displayedItem, mouseX, mouseY)
}
}
renderSelection(matrices)
}
fun closeNormally(unlink: Boolean = true) {
@@ -27,8 +27,8 @@ class ReleaseButton(
) : Button(x, y, WIDTH, HEIGHT, Component.literal("Release"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
private const val WIDTH = 58
private const val HEIGHT = 16
const val WIDTH = 58
const val HEIGHT = 16
private val buttonResource = cobblemonResource("textures/gui/pc/pc_release_button.png")
}
@@ -42,7 +42,7 @@ class ReleaseButton(
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || isFocused) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
@@ -28,8 +28,8 @@ class ReleaseConfirmButton(
) : Button(x, y, WIDTH, HEIGHT, Component.literal("ReleaseConfirm"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
private const val WIDTH = 30
private const val HEIGHT = 13
const val WIDTH = 30
const val HEIGHT = 13
private val buttonResource = cobblemonResource("textures/gui/pc/pc_release_button_confirm.png")
}
@@ -43,7 +43,7 @@ class ReleaseConfirmButton(
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || isFocused) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
@@ -20,6 +20,9 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.pasture.PasturePCGUIConfiguration
import com.cobblemon.mod.common.client.gui.pasture.PastureWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.SubManagerComponent
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.gui.PCBoxWallpaperRepository
import com.cobblemon.mod.common.client.settings.ServerSettings
@@ -41,6 +44,7 @@ import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import org.joml.Vector2i
class StorageWidget(
pX: Int, pY: Int,
@@ -91,6 +95,9 @@ class StorageWidget(
this.setupStorageSlots()
}
val controllerComponentManager = ControllerComponentManager(pcGui)
var grabbedSlotControllerPos = Vector2i()
init {
this.setupStorageSlots()
@@ -102,6 +109,7 @@ class StorageWidget(
if (!displayConfirmRelease && canDeleteSelected()) {
displayConfirmRelease = true
playSound(CobblemonSounds.PC_CLICK)
controllerComponentManager.focused = controllerComponentManager.componentList["release_no"]
}
}
)
@@ -126,6 +134,7 @@ class StorageWidget(
playSound(CobblemonSounds.PC_RELEASE)
resetSelected()
displayConfirmRelease = false
controllerComponentManager.focused = controllerComponentManager.componentList["slot_0_0"]
}
}
)
@@ -139,6 +148,7 @@ class StorageWidget(
if (displayConfirmRelease) {
displayConfirmRelease = false
playSound(CobblemonSounds.PC_CLICK)
controllerComponentManager.focused = controllerComponentManager.componentList["release"]
}
}
)
@@ -146,6 +156,47 @@ class StorageWidget(
if (pcGui.configuration is PasturePCGUIConfiguration) {
this.pastureWidget = PastureWidget(this, pcGui.configuration, x + 182, y - 19)
}
controllerComponentManager.componentList["release"] = ControllerComponent().also {
it.associatedGUIElement = releaseButton
it.up = { controllerComponentManager.componentList["party_slot_2_0"] }
it.left = { controllerComponentManager.componentList["slot_4_5"] }
it.onSelect = {
grabbedSlotControllerPos = Vector2i(releaseButton.x + ReleaseButton.WIDTH / 2 + 4, releaseButton.y + ReleaseButton.HEIGHT / 2 + 4)
}
}
controllerComponentManager.componentList["release_no"] = ControllerComponent().also {
it.associatedGUIElement = releaseNoButton
it.up = { controllerComponentManager.componentList["party_slot_2_1"] }
it.left = { controllerComponentManager.componentList["release_yes"] }
it.onSelect = {
grabbedSlotControllerPos = Vector2i(releaseNoButton.x + ReleaseConfirmButton.WIDTH / 2 + 4, releaseNoButton.y + ReleaseConfirmButton.HEIGHT / 2 + 4)
}
it.onUnselect = {
if (!controllerComponentManager.currentSelectedComponent.startsWith("release")) displayConfirmRelease = false
}
}
controllerComponentManager.componentList["release_yes"] = ControllerComponent().also {
it.associatedGUIElement = releaseYesButton
it.up = { controllerComponentManager.componentList["party_slot_2_0"] }
it.left = { controllerComponentManager.componentList["slot_4_5"] }
it.right = { controllerComponentManager.componentList["release_no"] }
it.onSelect = {
grabbedSlotControllerPos = Vector2i(releaseYesButton.x + ReleaseConfirmButton.WIDTH / 2 + 4, releaseYesButton.y + ReleaseConfirmButton.HEIGHT / 2 + 4)
}
it.onUnselect = {
if (!controllerComponentManager.currentSelectedComponent.startsWith("release")) displayConfirmRelease = false
}
}
if (pastureWidget != null) {
controllerComponentManager.componentList["pasture"] = SubManagerComponent().also {
it.subManager = { pastureWidget!!.controllerComponentManager }
it.left = { controllerComponentManager.componentList["slot_0_5"] }
}
}
controllerComponentManager.let {
it.focused = it.componentList["slot_0_0"]
}
}
fun canDeleteSelected(): Boolean {
@@ -164,7 +215,7 @@ class StorageWidget(
for (row in 1..5) {
for (col in 1..6) {
BoxStorageSlot(
val slot = BoxStorageSlot(
x = boxStartX + ((col - 1) * (StorageSlot.SIZE + BOX_SLOT_PADDING)),
y = boxStartY + ((row - 1) * (StorageSlot.SIZE + BOX_SLOT_PADDING)),
parent = this,
@@ -176,6 +227,27 @@ class StorageWidget(
this.boxSlots.add(widget)
}
index++
val adjCol = col - 1
val adjRow = row - 1
controllerComponentManager.componentList["slot_${adjRow}_${adjCol}"] = ControllerComponent().also {
it.associatedGUIElement = slot
if (adjCol - 1 >= 0) it.left = { controllerComponentManager.componentList["slot_${adjRow}_${adjCol-1}"] }
if (adjCol + 1 <= 5) it.right = { controllerComponentManager.componentList["slot_${adjRow}_${adjCol+1}"] }
else if (pastureWidget == null) {
it.right = when (adjRow) {
4 -> {{ if (canDeleteSelected()) controllerComponentManager.componentList["release"] else null }}
3 -> {{ controllerComponentManager.componentList["party_slot_2_0"] }}
else -> {{ controllerComponentManager.componentList["party_slot_${adjRow}_0"] }}
}
} else {
it.right = { controllerComponentManager.componentList["pasture"] }
}
if (adjRow - 1 >= 0) it.up = { controllerComponentManager.componentList["slot_${adjRow-1}_${adjCol}"] }
if (adjRow + 1 <= 4) it.down = { controllerComponentManager.componentList["slot_${adjRow+1}_${adjCol}"] }
it.onSelect = { grabbedSlotControllerPos = Vector2i(slot.x + slot.width / 2 + 4, slot.y + slot.height / 2 + 4) }
it.onPress = { if (slot.clickable()) slot.onPress() }
}
}
}
@@ -194,7 +266,7 @@ class StorageWidget(
partyY += ((StorageSlot.SIZE + PARTY_SLOT_PADDING) * offsetIndex) + offsetY
}
PartyStorageSlot(
val slot = PartyStorageSlot(
x = partyX,
y = partyY,
parent = this,
@@ -205,6 +277,22 @@ class StorageWidget(
this.addWidget(widget)
this.partySlots.add(widget)
}
val row = partyIndex / 2
val col = partyIndex % 2
controllerComponentManager.componentList["party_slot_${row}_${col}"] = ControllerComponent().also {
it.associatedGUIElement = slot
if (col == 1) it.left = { controllerComponentManager.componentList["party_slot_${row}_0"] }
else {
it.right = { controllerComponentManager.componentList["party_slot_${row}_1"] }
it.left = { controllerComponentManager.componentList["slot_${row}_5"] }
}
if (row - 1 >= 0) it.up = { controllerComponentManager.componentList["party_slot_${row-1}_${col}"] }
if (row + 1 <= 2) it.down = { controllerComponentManager.componentList["party_slot_${row+1}_${col}"] }
else it.down = { if (canDeleteSelected()) controllerComponentManager.componentList["release"] else null }
it.onSelect = { grabbedSlotControllerPos = Vector2i(slot.x + slot.width / 2 + 4, slot.y + slot.height / 2 + 4) }
it.onPress = { if (slot.clickable()) slot.onPress() }
}
}
}
@@ -307,7 +395,7 @@ class StorageWidget(
this.boxSlots.forEach { slot ->
slot.render(context, mouseX, mouseY, delta)
val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
if (grabbedSlot == null && (slot.isHovered(mouseX, mouseY) || slot.isFocused)
&& pokemon != null && pokemon != pcGui.previewPokemon
) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: false)
@@ -323,7 +411,7 @@ class StorageWidget(
this.partySlots.forEach { slot ->
slot.render(context, mouseX, mouseY, delta)
val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
if (grabbedSlot == null && (slot.isHovered(mouseX, mouseY) || slot.isFocused)
&& pokemon != null && pokemon != pcGui.previewPokemon
) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: true)
@@ -335,7 +423,11 @@ class StorageWidget(
pastureWidget?.render(context, mouseX, mouseY, delta)
matrices.pushPose()
matrices.translate(0.0, 0.0, 1000.0)
grabbedSlot?.render(context, mouseX, mouseY, delta)
if (controllerComponentManager.active) {
grabbedSlot?.render(context, grabbedSlotControllerPos.x, grabbedSlotControllerPos.y, delta)
} else {
grabbedSlot?.render(context, mouseX, mouseY, delta)
}
matrices.popPose()
}
@@ -372,6 +464,9 @@ class StorageWidget(
}
// Reset release confirmation
if (controllerComponentManager.currentSelectedComponent.startsWith("release")) {
controllerComponentManager.focused = controllerComponentManager.componentList["slot_0_0"]
}
displayConfirmRelease = false
// Clicking on itself, so unselect position
@@ -15,6 +15,8 @@ import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.ScrollingWidget
import com.cobblemon.mod.common.client.gui.pc.WallpapersScrollingWidget.WallpaperEntry
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.gui.PCBoxWallpaperRepository
import com.cobblemon.mod.common.net.messages.server.storage.pc.RequestChangePCBoxWallpaperPacket
@@ -49,10 +51,14 @@ class WallpapersScrollingWidget(
val backgroundResource = cobblemonResource("textures/gui/pc/wallpaper_scroll_background.png")
}
val controllerComponentManager = ControllerComponentManager(pcGui)
init {
this.visible = false
this.y = pY
createEntries()
}
override fun getScrollbarPosition(): Int = x + width - 3
@@ -90,6 +96,24 @@ class WallpapersScrollingWidget(
}
}
override fun addEntry(entry: WallpaperEntry): Int {
val i = super.addEntry(entry)
controllerComponentManager.componentList["wallpaper_${i}"] = ControllerComponent().also {
it.associatedGUIElement = entry
it.onPress = { entry.selectWallpaper() }
it.onFocus = { ensureVisible(entry) }
}
if (children().size > 1) {
controllerComponentManager.componentList["wallpaper_${i}"]?.let {
it.pairUp(controllerComponentManager.componentList["wallpaper_${i-1}"])
it.pairDown(controllerComponentManager.componentList["wallpaper_${0}"])
}
} else {
controllerComponentManager.focused = controllerComponentManager.componentList["wallpaper_${i}"]
}
return i
}
override fun getRowLeft(): Int {
return this.left + SLOT_PADDING
}
@@ -116,7 +140,7 @@ class WallpapersScrollingWidget(
partialTick: Float
) {
val matrices = guiGraphics.pose()
val resource = if (Screen.hasShiftDown() && altWallpaper !== null) altWallpaper else wallpaper
val resource = if ((Screen.hasShiftDown() || (pcGui.controllerComponentManager.active && pcGui.controllerShiftToggle)) && altWallpaper !== null) altWallpaper else wallpaper
blitk(
matrixStack = matrices,
texture = resource,
@@ -133,24 +157,28 @@ class WallpapersScrollingWidget(
y = top + SLOT_PADDING,
width = width,
height = SLOT_HEIGHT,
vOffset = if (hovering) SLOT_HEIGHT else 0,
vOffset = if (hovering || isFocused) SLOT_HEIGHT else 0,
textureHeight = SLOT_HEIGHT * 2
)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (this@WallpapersScrollingWidget.visible && isMouseOver(mouseX, mouseY)) {
val appliedWallpaper = if (Screen.hasShiftDown()) altWallpaper ?: wallpaper else wallpaper
RequestChangePCBoxWallpaperPacket(pcGui.pc.uuid, storageWidget.box, wallpaper, if (Screen.hasShiftDown()) altWallpaper else null).sendToServer()
pcGui.pc.boxes[storageWidget.box].wallpaper = appliedWallpaper
pcGui.unseenWallpapers.remove(wallpaper)
isNew = false
minecraft.soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.PC_CLICK, 1.0F))
selectWallpaper()
return true
}
return false
}
fun selectWallpaper() {
val appliedWallpaper = if (Screen.hasShiftDown() || (pcGui.controllerComponentManager.active && pcGui.controllerShiftToggle)) altWallpaper ?: wallpaper else wallpaper
RequestChangePCBoxWallpaperPacket(pcGui.pc.uuid, storageWidget.box, wallpaper, if (Screen.hasShiftDown() || (pcGui.controllerComponentManager.active && pcGui.controllerShiftToggle)) altWallpaper else null).sendToServer()
pcGui.pc.boxes[storageWidget.box].wallpaper = appliedWallpaper
pcGui.unseenWallpapers.remove(wallpaper)
isNew = false
minecraft.soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.PC_CLICK, 1.0F))
}
override fun getNarration(): Component? {
return wallpaper.toString().text()
}
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.pokedex.CaughtCount
import com.cobblemon.mod.common.api.pokedex.Dexes
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.SeenCount
import com.cobblemon.mod.common.api.pokedex.def.PokedexDef
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
@@ -46,6 +47,10 @@ import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_ICON_
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_MOVES
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_SIZE
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_STATS
import com.cobblemon.mod.common.client.gui.pokedex.widgets.*
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.gui.pokedex.widgets.AbilitiesWidget
import com.cobblemon.mod.common.client.gui.pokedex.widgets.DescriptionWidget
import com.cobblemon.mod.common.client.gui.pokedex.widgets.DropsScrollingWidget
@@ -74,6 +79,8 @@ import net.minecraft.core.BlockPos
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
import org.joml.Vector4i
/**
* Pokedex GUI
@@ -85,7 +92,7 @@ class PokedexGUI private constructor(
val type: PokedexType,
val initSpecies: ResourceLocation?,
val blockPos: BlockPos?
): Screen(Component.translatable("cobblemon.ui.pokedex.title")), CobblemonRenderable {
): Screen(Component.translatable("cobblemon.ui.pokedex.title")), CobblemonRenderable, ControllerScreen {
companion object {
private val screenBackground = cobblemonResource("textures/gui/pokedex/pokedex_screen.png")
@@ -163,6 +170,14 @@ class PokedexGUI private constructor(
lateinit var tabInfoElement: GuiEventListener
var tabInfoIndex = TAB_DESCRIPTION
val controllerComponentManager = ControllerComponentManager(this)
var lastTileComponent: ControllerComponent? = null
var lastSelectedColumn = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun renderBlurredBackground(delta: Float) {}
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -268,6 +283,222 @@ class PokedexGUI private constructor(
)
addRenderableWidget(categoryFilterButtonDown)
updateFilters(true)
fun hasKnowledge(): Boolean {
val currentEntry = pokemonInfoWidget.currentEntry
return currentEntry != null && CobblemonClient.clientPokedexData.getKnowledgeForSpecies(currentEntry.speciesId) != PokedexEntryProgress.UNREGISTERED
}
controllerComponentManager.componentList["search"] = ControllerComponent().also {
it.associatedGUIElement = searchWidget
it.down = {
val returnVal = if (scrollScreen.children().isNotEmpty()) {
controllerComponentManager.componentList["tile_0_0"]
} else {
controllerComponentManager.componentList["empty_entries"]
}
returnVal
}
it.onSpecial1 = {
searchByTypeButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
controllerComponentManager.componentList["empty_entries"] = ControllerComponent().also {
it.up = { controllerComponentManager.componentList["search"] }
it.right = { controllerComponentManager.componentList["pokemon_render"] }
}
controllerComponentManager.componentList["pokemon_render"] = ControllerComponent().also {
it.left = {
val returnVal = if (scrollScreen.children().isNotEmpty()) {
lastTileComponent
} else {
controllerComponentManager.componentList["empty_entries"]
}
returnVal
}
it.down = { controllerComponentManager.componentList["info_widget"] }
it.onPress = {
if (hasKnowledge()) {
pokemonInfoWidget.playCry()
}
}
it.onSpecial1 = {
if (hasKnowledge() && pokemonInfoWidget.genderButton.visible) {
pokemonInfoWidget.genderButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
it.onSpecial2 = {
if (hasKnowledge() && pokemonInfoWidget.shinyButton.visible) {
pokemonInfoWidget.shinyButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
}
controllerComponentManager.componentList["info_widget"] = ControllerComponent().also {
it.left = {
val returnVal = if (scrollScreen.children().isNotEmpty()) {
lastTileComponent
} else {
controllerComponentManager.componentList["empty_entries"]
}
returnVal
}
it.up = { controllerComponentManager.componentList["pokemon_render"] }
}
controllerComponentManager.let {
it.focused = it.componentList["tile_0_0"] ?: it.componentList["empty_entries"]
it.onTabNav = { nextTab ->
if (it.currentSelectedComponent == "empty_entries" || it.currentSelectedComponent.startsWith("tile_")) {
updatePokedexRegion(nextTab)
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
} else if (it.currentSelectedComponent == "pokemon_render") {
val currentEntry = pokemonInfoWidget.currentEntry
if (currentEntry != null) {
val hasKnowledge = CobblemonClient.clientPokedexData.getKnowledgeForSpecies(currentEntry.speciesId) != PokedexEntryProgress.UNREGISTERED
val showableForms = CobblemonClient.clientPokedexData.getEncounteredForms(currentEntry)
if (hasKnowledge && showableForms.size > 1 && showableForms.size > pokemonInfoWidget.selectedFormIndex) {
pokemonInfoWidget.switchForm(nextTab)
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
} else if (it.currentSelectedComponent == "info_widget") {
val trimmedTabs = tabButtons.filterIndexed { index, _ -> tabInfoIndex == index || canSelectTab(index) }.map { tabButtons.indexOf(it) }.toMutableList()
var currentTab = trimmedTabs.indexOf(tabInfoIndex) + if (nextTab) 1 else -1
if (currentTab > trimmedTabs.size - 1) currentTab = 0
else if (currentTab < 0) currentTab = trimmedTabs.size - 1
displaytabInfoElement(trimmedTabs[currentTab])
if (trimmedTabs.size > 1) playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
it.onSecondaryTabNav = { nextTab ->
if (it.currentSelectedComponent == "pokemon_render") {
pokemonInfoWidget.switchPose(nextTab)
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
} else if (it.currentSelectedComponent == "info_widget") {
(tabInfoElement as? AbilitiesWidget)?.let {
it.switchAbility(nextTab)
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
(tabInfoElement as? StatsWidget)?.let { widget ->
if (nextTab) {
if (children().contains(widget.rightButton)) {
widget.rightButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
} else {
if (children().contains(widget.leftButton)) {
widget.leftButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
}
}
}
it.onRHorizontal = { right ->
if (it.currentSelectedComponent == "info_widget") {
(tabInfoElement as? StatsWidget)?.let { widget ->
if (widget.selectedStatTypeIndex == StatsWidget.STAT_TYPE_RIDE) {
if (right) {
if (children().contains(widget.rightSubButton)) {
widget.rightSubButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
} else {
if (children().contains(widget.leftSubButton)) {
widget.leftSubButton.onPress()
playSound(CobblemonSounds.POKEDEX_CLICK_SHORT)
}
}
}
}
}
}
it.onStart = {
it.focused = it.componentList["search"]
it.openKeyboard.invoke(it.focused!!.associatedGUIElement as EditBox)
playSound(SoundEvents.UI_BUTTON_CLICK.value())
}
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.START,
{ true },
Component.translatable("cobblemon.controller.quick_search")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.TAB,
{ it.currentSelectedComponent == "empty_entries" || it.currentSelectedComponent.startsWith("tile_") },
Component.translatable("cobblemon.controller.dex.cycle_region")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.TAB,
{ it.currentSelectedComponent == "pokemon_render" && hasKnowledge() && pokemonInfoWidget.visibleForms.size > 1},
Component.translatable("cobblemon.controller.dex.cycle_form"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SECONDARY_TAB,
{ it.currentSelectedComponent == "pokemon_render" && hasKnowledge() },
Component.translatable("cobblemon.controller.dex.cycle_animation")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SECONDARY_TAB,
{ it.currentSelectedComponent == "info_widget" && tabInfoElement is AbilitiesWidget },
Component.translatable("cobblemon.controller.dex.cycle_ability")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SECONDARY_TAB,
{ it.currentSelectedComponent == "info_widget" && (tabInfoElement as? StatsWidget)?.let { children().contains(it.rightButton) } ?: false },
Component.translatable("cobblemon.controller.dex.cycle_stats")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.JOYSTICK_HORIZONTAL,
{ it.currentSelectedComponent == "info_widget" && (tabInfoElement as? StatsWidget)?.let { it.selectedStatTypeIndex == StatsWidget.STAT_TYPE_RIDE && children().contains(it.rightSubButton) } ?: false },
Component.translatable("cobblemon.controller.dex.cycle_ride_style")
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "search" },
Component.translatable("cobblemon.controller.dex.cycle_search_by"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "pokemon_render" && hasKnowledge() && pokemonInfoWidget.genderButton.visible },
Component.translatable("cobblemon.controller.dex.toggle_gender"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_2,
{ it.currentSelectedComponent == "pokemon_render" && hasKnowledge() && pokemonInfoWidget.shinyButton.visible },
Component.translatable("cobblemon.controller.dex.toggle_shiny"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.PRESS,
{ it.currentSelectedComponent == "pokemon_render" && hasKnowledge() },
Component.translatable("cobblemon.controller.dex.play_cry"),
true
))
}
}
override fun getSelectionPosAndSize(): Vector4i? {
return when (controllerComponentManager.currentSelectedComponent) {
"search" -> Vector4i(searchWidget.x - 2, searchWidget.y - 2, searchWidget.width + 15, searchWidget.height + 4)
"pokemon_render" -> Vector4i(pokemonInfoWidget.x - 2, pokemonInfoWidget.y + 12, pokemonInfoWidget.width + 6, pokemonInfoWidget.height + 16)
"info_widget" -> Vector4i(pokemonInfoWidget.x - 2, pokemonInfoWidget.y + 94, pokemonInfoWidget.width + 6, pokemonInfoWidget.height + 3)
"empty_entries" -> Vector4i(scrollScreen.x - 2, scrollScreen.y - 2, scrollScreen.width + 4, scrollScreen.height + 4)
else -> {
if (controllerComponentManager.currentSelectedComponent.startsWith("tile_")) {
val scrollWidget = controllerComponentManager.focused?.associatedGUIElement as? EntriesScrollingWidget.PokemonScrollSlotRow
val startX = scrollScreen.x + lastSelectedColumn * (PokedexGUIConstants.SCROLL_SLOT_SIZE + PokedexGUIConstants.SCROLL_SLOT_SPACING)
if (scrollWidget != null) {
Vector4i(startX - 2, scrollScreen.getRowTop(scrollScreen.children().indexOf(scrollWidget)) + 1, PokedexGUIConstants.SCROLL_SLOT_SIZE + 4, PokedexGUIConstants.SCROLL_SLOT_SIZE + 4)
} else null
} else null
}
}
}
fun getSufficientlyKnownForms(selectedEntry: PokedexEntry, tabIndex: Int) = if (tabIndex in looseUnlockTabs) {
@@ -412,6 +643,8 @@ class PokedexGUI private constructor(
super.render(context, mouseX, mouseY, delta)
renderSelection(context.pose())
// Search type tooltip
if (searchByTypeButton.isButtonHovered(mouseX, mouseY)) {
val searchTypeText = lang("ui.pokedex.search.search_by", lang("ui.pokedex.search.type.${selectedSearchByType.name.lowercase()}")).bold()
@@ -467,6 +700,7 @@ class PokedexGUI private constructor(
if (isDragging && canDragRender) {
val dragOffsetY = (oldDragPosX - mouseX).toFloat()
pokemonInfoWidget.rotationY = (((pokemonInfoWidget.rotationY + dragOffsetY) % 360 + 360) % 360)
pokemonInfoWidget.lastRotationY = pokemonInfoWidget.rotationY
}
oldDragPosX = mouseX
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
@@ -482,6 +716,18 @@ class PokedexGUI private constructor(
override fun tick() {
if (::pokemonInfoWidget.isInitialized && !pokemonInfoWidget.suppressViewport) pokemonInfoWidget.tick()
//Controller
if (controllerComponentManager.rHorizontalState != 0) {
if (controllerComponentManager.currentSelectedComponent == "pokemon_render") {
pokemonInfoWidget.rotationY = ((pokemonInfoWidget.rotationY + 5F * controllerComponentManager.rHorizontalState) % 360 + 360) % 360
}
}
if (controllerComponentManager.rVerticalState != 0) {
if (controllerComponentManager.currentSelectedComponent == "info_widget") {
(tabInfoElement as? InfoTextScrollWidget)?.scroll(-3 * controllerComponentManager.rVerticalState)
}
}
}
fun updatePokedexRegion(nextIndex: Boolean) {
@@ -541,6 +787,45 @@ class PokedexGUI private constructor(
} else {
setSelectedEntry(entries.first())
}
val totalRows = scrollScreen.children().size
val lastRowCount = scrollScreen.children()[totalRows-1].dexDataList.size
scrollScreen.children().forEachIndexed { row, scrollRow ->
scrollRow.dexDataList.forEachIndexed { column, slot ->
controllerComponentManager.componentList["tile_${row}_${column}"] = ControllerComponent().also {
it.associatedGUIElement = scrollRow
if (row - 1 >= 0) it.up = { controllerComponentManager.componentList["tile_${row-1}_${column}"] }
else it.up = { controllerComponentManager.componentList["search"] }
if (row + 1 <= scrollScreen.children().size - 1 &&
(row + 1 != totalRows || column <= lastRowCount - 1)) it.down = { controllerComponentManager.componentList["tile_${row+1}_${column}"] }
if (column - 1 >= 0) it.left = { controllerComponentManager.componentList["tile_${row}_${column-1}"] }
if (column + 1 <= scrollRow.dexDataList.size - 1) it.right = { controllerComponentManager.componentList["tile_${row}_${column+1}"] }
else it.right = { controllerComponentManager.componentList["pokemon_render"] }
it.onPress = {
scrollRow.setPokedexEntry.invoke(slot)
playSound(CobblemonSounds.POKEDEX_CLICK)
}
it.onSelect = {
lastTileComponent = it
lastSelectedColumn = column
scrollScreen.ensureVisible(scrollRow)
}
it.onFocus = {
scrollRow.slotControllerSelected = column
}
it.onUnfocus = {
scrollRow.slotControllerSelected = -1
}
}
}
}
}
if (controllerComponentManager.currentSelectedComponent.let { it == "empty_entries" || it.startsWith("tile_") }) {
if (entries.isNotEmpty()) {
controllerComponentManager.focused = controllerComponentManager.componentList["tile_0_0"]
} else {
controllerComponentManager.focused = controllerComponentManager.componentList["empty_entries"]
}
}
}
@@ -47,7 +47,7 @@ class AbilitiesWidget(x: Int, y: Int): InfoTextScrollWidget(pX = x, pY = y) {
init { setAbility() }
private fun switchAbility(nextIndex: Boolean) {
fun switchAbility(nextIndex: Boolean) {
val size = abilitiesList.size
selectedAbilitiesIndex = if (nextIndex) {
(selectedAbilitiesIndex + 1) % size
@@ -149,6 +149,8 @@ class EntriesScrollingWidget(val pX: Int, val pY: Int, val setPokedexEntry: (Pok
var x: Int = 0
var y: Int = 0
var slotControllerSelected = -1
override fun render(
context: GuiGraphics,
index: Int,
@@ -190,7 +192,7 @@ class EntriesScrollingWidget(val pX: Int, val pY: Int, val setPokedexEntry: (Pok
height = SCROLL_SLOT_SIZE
)
if (getHoveredSlotIndex(mouseX, mouseY) == index) {
if (getHoveredSlotIndex(mouseX, mouseY) == index || slotControllerSelected == index) {
blitk(
matrixStack = matrices,
texture = slotHighlight,
@@ -56,6 +56,7 @@ import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.util.Mth
import org.joml.Quaternionf
import org.joml.Vector3f
import java.io.FileNotFoundException
@@ -123,6 +124,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
var state = FloatingState()
var rotationY = 30F
var lastRotationY = rotationY // Used to smooth out rotation for controllers
var ticksElapsed = 0
var pokeBallBackgroundFrame = 0
@@ -188,7 +190,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
val variationButtons = mutableListOf<VariationButtonWrapper>()
private val genderButton: ScaledButton = ScaledButton(
val genderButton: ScaledButton = ScaledButton(
pX + 114F,
pY + 27F,
20,
@@ -200,7 +202,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
}
).apply { addWidget(this) }
private val shinyButton: ScaledButton = ScaledButton(
val shinyButton: ScaledButton = ScaledButton(
(pX + 126F),
(pY + 27F),
20,
@@ -261,6 +263,8 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
).apply { addWidget(this) }
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val realDetla = Minecraft.getInstance().timer.getGameTimeDeltaPartialTick(true)
val currentEntry = this.currentEntry ?: return
val hasKnowledge =
@@ -375,10 +379,10 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
matrices.translate(
pX.toDouble() + (POKEMON_PORTRAIT_WIDTH.toDouble() + 2) / 2,
pY.toDouble() + portraitStartY - 12,
1000.0 // Prevent model from clipping into background
10.0 // Dont use too big numbers otherwise side mod overlays can break (controllify keyboard)
)
matrices.scale(scaleAmount, scaleAmount, scaleAmount)
val rotationVector = Vector3f(13F, rotationY, 0F)
matrices.scale(scaleAmount, scaleAmount, 0.2F)
val rotationVector = Vector3f(13F, Mth.rotLerp(realDetla, lastRotationY, rotationY), 0F)
drawProfilePokemon(
renderablePokemon = renderablePokemon!!,
@@ -407,7 +411,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
// Ensure elements are not hidden behind Pokémon render
matrices.pushPose()
matrices.translate(0.0, 0.0, 2000.0)
matrices.translate(0.0, 0.0, 20.0)
if (isSelectedPokemonOwned()) {
val primaryType = type[0]
@@ -638,12 +642,12 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
type = arrayOf(form.primaryType, form.secondaryType)
}
private fun playCry() {
fun playCry() {
state.activeAnimations.clear()
state.addFirstAnimation(setOf("cry"))
}
private fun switchForm(nextIndex: Boolean) {
fun switchForm(nextIndex: Boolean) {
if (visibleForms.isEmpty() || currentEntry == null) {
return
}
@@ -664,7 +668,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
updateAspects()
}
private fun switchPose(nextIndex: Boolean) {
fun switchPose(nextIndex: Boolean) {
if (nextIndex) {
if (selectedPoseIndex < poseList.lastIndex) selectedPoseIndex++
else selectedPoseIndex = 0
@@ -756,6 +760,8 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
val delay = 3
if (ticksElapsed % delay == 0) pokeBallBackgroundFrame++
if (pokeBallBackgroundFrame == 16) pokeBallBackgroundFrame = 0
lastRotationY = rotationY
}
fun isWithinPortraitSpace(mouseX: Double, mouseY: Double): Boolean =
@@ -9,6 +9,9 @@
package com.cobblemon.mod.common.client.gui.snapshots
import com.cobblemon.mod.common.CobblemonBuildDetails
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import net.minecraft.client.Minecraft
import net.minecraft.client.OptionInstance
@@ -20,10 +23,12 @@ import net.minecraft.client.gui.components.Tooltip
import net.minecraft.client.gui.layouts.FrameLayout
import net.minecraft.client.gui.layouts.GridLayout
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.CommonComponents
import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvents
class SnapshotWarningScreen(val consumer: (Acknowledgement, Boolean) -> Unit) : Screen(TITLE) {
class SnapshotWarningScreen(val consumer: (Acknowledgement, Boolean) -> Unit) : Screen(TITLE), ControllerScreen {
companion object {
@@ -36,6 +41,12 @@ class SnapshotWarningScreen(val consumer: (Acknowledgement, Boolean) -> Unit) :
private val layout = GridLayout().columnSpacing(10).rowSpacing(20)
private val dontShowAgain = OptionInstance.createBoolean("cobblemon\$snapshots\$dont_show_again", false)
val controllerComponentManager = ControllerComponentManager(this)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun init() {
super.init()
@@ -45,17 +56,42 @@ class SnapshotWarningScreen(val consumer: (Acknowledgement, Boolean) -> Unit) :
helper.addChild(StringWidget(this.title, this.font), 2, settings)
helper.addChild(MultiLineTextWidget(DESCRIPTION, this.font).setCentered(true).setMaxWidth(310), 2, settings)
helper.addChild(Checkbox.builder(Component.translatable("cobblemon.snapshots.warning.dont_show_again"), Minecraft.getInstance().font)
val checkbox = Checkbox.builder(Component.translatable("cobblemon.snapshots.warning.dont_show_again"), Minecraft.getInstance().font)
.selected(dontShowAgain)
.tooltip(Tooltip.create(DONT_SHOW_AGAIN_TIP))
.build(), 2, settings)
.build()
helper.addChild(checkbox, 2, settings)
helper.addChild(Button.builder(CommonComponents.GUI_YES) { this.consumer.invoke(Acknowledgement.YES, dontShowAgain.get()) }.build())
helper.addChild(Button.builder(CommonComponents.GUI_NO) { this.consumer.invoke(Acknowledgement.NO, false) }.build())
val yesButton = Button.builder(CommonComponents.GUI_YES) { this.consumer.invoke(Acknowledgement.YES, dontShowAgain.get()) }.build()
helper.addChild(yesButton)
val noButton = Button.builder(CommonComponents.GUI_NO) { this.consumer.invoke(Acknowledgement.NO, false) }.build()
helper.addChild(noButton)
this.layout.visitWidgets { this.addRenderableWidget(it) }
this.layout.arrangeElements()
this.repositionElements()
controllerComponentManager.componentList["checkbox"] = ControllerComponent().also {
it.associatedGUIElement = checkbox
it.down = { controllerComponentManager.componentList["yes_button"] }
it.onPress = {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F))
checkbox.onPress()
}
}
controllerComponentManager.componentList["yes_button"] = ControllerComponent().also {
it.associatedGUIElement = yesButton
it.up = { controllerComponentManager.componentList["checkbox"] }
it.right = { controllerComponentManager.componentList["no_button"] }
}
controllerComponentManager.componentList["no_button"] = ControllerComponent().also {
it.associatedGUIElement = noButton
it.up = { controllerComponentManager.componentList["checkbox"] }
it.left = { controllerComponentManager.componentList["yes_button"] }
}
controllerComponentManager.let {
it.focused = it.componentList["yes_button"]
}
}
override fun repositionElements() {
@@ -25,9 +25,10 @@ import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.VariableObjectSelectionScrollbar
import com.cobblemon.mod.common.client.gui.startselection.widgets.CategoryList
import com.cobblemon.mod.common.client.gui.startselection.widgets.SelectionButton
import com.cobblemon.mod.common.client.gui.startselection.widgets.preview.StarterRoundabout
import com.cobblemon.mod.common.client.gui.summary.Summary.Companion.PARTY
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.keybind.keybinds.SummaryBinding
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.config.starter.RenderableStarterCategory
@@ -41,8 +42,9 @@ import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.toasts.Toast
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import org.joml.Vector4i
import net.minecraft.resources.ResourceLocation
import java.io.FileNotFoundException
/**
@@ -51,7 +53,7 @@ import java.io.FileNotFoundException
* @author Qu
* @since 2022-06-18
*/
class StarterSelectionScreen(private val categories: List<RenderableStarterCategory>): Screen("cobblemon.ui.starter.title".asTranslated()), CobblemonRenderable {
class StarterSelectionScreen(private val categories: List<RenderableStarterCategory>): Screen("cobblemon.ui.starter.title".asTranslated()), CobblemonRenderable, ControllerScreen {
companion object {
// Size of UI at scale 1
private const val BASE_WIDTH = 239
@@ -73,6 +75,14 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
private lateinit var modelWidget: ModelWidget
private lateinit var currentPokemon: RenderablePokemon
val controllerComponentManager = ControllerComponentManager(this)
var lastSelectedCategory = 0
var lastSelectedButton = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
var ticksElapsed = 0
var currentBallBackgroundFrame = 0
@@ -151,6 +161,91 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
width = 3,
height = 145
))
controllerComponentManager.componentList["select"] = ControllerComponent().also {
it.associatedGUIElement = selectionButton
it.right = { controllerComponentManager.componentList["pokemon_${lastSelectedCategory}_${lastSelectedButton}"] }
}
categoryList.generatedCategoriesWidgets.forEachIndexed { categoryIndex, category ->
for (buttonIndex in 0..<category.category.pokemon.size) {
val column = buttonIndex % 3
controllerComponentManager.componentList["pokemon_${categoryIndex}_${buttonIndex}"] = ControllerComponent().also {
it.associatedGUIElement = category
it.right = {
if (column < 2) controllerComponentManager.componentList["pokemon_${categoryIndex}_${buttonIndex+1}"]
else null
}
it.left = {
if (column > 0) controllerComponentManager.componentList["pokemon_${categoryIndex}_${buttonIndex-1}"]
else controllerComponentManager.componentList["select"]
}
it.up = {
if (buttonIndex-3 < 0) {
var returnVal: ControllerComponent? = null
var index = categoryIndex
while (index-1 >= 0) {
val lastCategory = categoryList.generatedCategoriesWidgets[index-1]
val row = (lastCategory.category.pokemon.size - 1) / 3 + if ((lastCategory.category.pokemon.size - 1) % 3 < column) -1 else 0
if (row >= 0) {
returnVal = controllerComponentManager.componentList["pokemon_${index-1}_${row*3+column}"]
break
}
index -= 1
}
returnVal
}
else controllerComponentManager.componentList["pokemon_${categoryIndex}_${buttonIndex-3}"]
}
it.down = {
if (buttonIndex+3 >= category.category.pokemon.size) {
var returnVal: ControllerComponent? = null
var index = categoryIndex
while (index+1 < categoryList.generatedCategoriesWidgets.size) {
val nextCategory = categoryList.generatedCategoriesWidgets[index+1]
if (nextCategory.category.pokemon.size > column) {
returnVal = controllerComponentManager.componentList["pokemon_${index+1}_${column}"]
break
}
index += 1
}
returnVal
}
else controllerComponentManager.componentList["pokemon_${categoryIndex}_${buttonIndex+3}"]
}
it.onPress = {
selectPokemon(category.category, buttonIndex)
playSound(CobblemonSounds.GUI_CLICK)
}
it.onSelect = {
lastSelectedCategory = categoryIndex
lastSelectedButton = buttonIndex
}
it.onFocus = {
category.controllerButtonSelected = buttonIndex
categoryList.ensureVisible(category)
}
it.onUnfocus = {
category.controllerButtonSelected = -1
}
}
}
}
controllerComponentManager.let {
it.focused = controllerComponentManager.componentList["select"]
}
}
override fun getSelectionPosAndSize(): Vector4i? {
if (controllerComponentManager.currentSelectedComponent.startsWith("pokemon_")) {
val category = controllerComponentManager.focused?.associatedGUIElement as? CategoryList.Category ?: return null
val row = category.controllerButtonSelected / 3
val column = category.controllerButtonSelected % 3
val x = category.getRowLeft() + (CategoryList.CONTAINER_BUTTON_SIZE + 2) * column + CategoryList.MARGIN + 3
val y = category.getRowTop() + (CategoryList.CONTAINER_BUTTON_SIZE + CategoryList.BUTTON_SEPERATION) * row + CategoryList.HEADER_HEIGHT + CategoryList.BUTTON_SEPERATION - 1
return Vector4i(x, y, CategoryList.CONTAINER_BUTTON_SIZE + 4, CategoryList.CONTAINER_BUTTON_SIZE + 4)
}
return super.getSelectionPosAndSize()
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -307,6 +402,8 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
matrices.popPose()
super.render(context, mouseX, mouseY, delta)
renderSelection(matrices)
}
fun getPlatformResource(type: ElementalType): ResourceLocation? {
@@ -59,9 +59,12 @@ class CategoryList(
const val CONTAINER_WIDTH = 89
}
val generatedCategoriesWidgets = mutableListOf<Category>()
init {
addEntry(EmptySeparator(MARGIN))
createEntries().forEach {
generatedCategoriesWidgets.add(it)
addEntry(it)
addEntry(EmptySeparator(MARGIN))
}
@@ -73,7 +76,18 @@ class CategoryList(
Category(category, height)
}
inner class Category(private val category: RenderableStarterCategory, height: Int) : Entry(height) {
override fun ensureVisible(entry: Entry) {
if (entry !in children) return
val yTop = entry.scrolledY - scrollAmount.toInt()
if (yTop - MARGIN < 0) {
setScroll((entry.scrolledY - MARGIN).toDouble())
} else if (yTop + entry.height + MARGIN > height) {
setScroll((entry.scrolledY + entry.height - height + MARGIN).toDouble())
}
}
inner class Category(val category: RenderableStarterCategory, height: Int) : Entry(height) {
var controllerButtonSelected = -1
override fun render(
guiGraphics: GuiGraphics,
@@ -170,7 +184,7 @@ class CategoryList(
width = CONTAINER_BUTTON_SIZE,
height = CONTAINER_BUTTON_SIZE,
textureHeight = CONTAINER_BUTTON_SIZE * 2,
vOffset = if (hovered) CONTAINER_BUTTON_SIZE else 0
vOffset = if (hovered || controllerButtonSelected == index) CONTAINER_BUTTON_SIZE else 0
)
matrices.pushPose()
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.client.gui.startselection.widgets
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.ColourLibrary
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
@@ -17,10 +15,8 @@ import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.Component
@@ -50,7 +46,7 @@ class SelectionButton(
width = BUTTON_WIDTH,
height = BUTTON_HEIGHT,
textureHeight = BUTTON_HEIGHT * 2,
vOffset = if (isHovered) BUTTON_HEIGHT else 0
vOffset = if (isHovered || isFocused) BUTTON_HEIGHT else 0
)
drawScaledText(
@@ -20,14 +20,27 @@ import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.widgets.EvolutionSelectScreen
import com.cobblemon.mod.common.client.gui.summary.widgets.MarkingsWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.NicknameEntryWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.PartySlotWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.PartyWidget
import com.cobblemon.mod.common.client.gui.*
import com.cobblemon.mod.common.client.gui.summary.widgets.*
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.SummaryTab
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.info.InfoWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.marks.MarksScrollingWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.marks.MarksWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MoveSwapScreen
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MovesWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.stats.StatWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.integration.controlify.gui.component.SubManagerComponent
import com.cobblemon.mod.common.client.keybind.keybinds.SummaryBinding
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetItemHiddenPacket
@@ -50,6 +63,8 @@ import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvent
import org.joml.Vector2i
import org.joml.Vector4i
/**
* The screen responsible for displaying various information regarding a Pokémon team.
@@ -60,7 +75,7 @@ import net.minecraft.sounds.SoundEvent
* @param selection The index the [party] will have as the base [selectedPokemon].
*/
class Summary private constructor(party: Collection<Pokemon?>, private val editable: Boolean, private val selection: Int): Screen(
Component.translatable("cobblemon.ui.summary.title")), Schedulable, CobblemonRenderable {
Component.translatable("cobblemon.ui.summary.title")), Schedulable, CobblemonRenderable, ControllerScreen {
companion object {
const val BASE_WIDTH = 331
@@ -69,10 +84,10 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
private const val SCALE = 0.5F
// Main Screen Index
private const val INFO = 0
private const val MOVES = 1
private const val STATS = 2
private const val MARKS = 3
const val INFO = 0
const val MOVES = 1
const val STATS = 2
const val MARKS = 3
// Side Screen Index
const val PARTY = 0
@@ -116,19 +131,32 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
override val schedulingTracker = SchedulingTracker()
private val party = ArrayList(party)
val party = ArrayList(party)
internal var selectedPokemon: Pokemon = this.party[selection] ?: this.party.first { it != null }!!
private lateinit var mainScreen: AbstractWidget
lateinit var mainScreen: AbstractWidget
lateinit var sideScreen: GuiEventListener
private lateinit var modelWidget: ModelWidget
private lateinit var nicknameEntryWidget: NicknameEntryWidget
lateinit var nicknameEntryWidget: NicknameEntryWidget
private lateinit var markingsWidget: MarkingsWidget
private val summaryTabs = mutableListOf<SummaryTab>()
val summaryTabs = mutableListOf<SummaryTab>()
private var showCosmeticItem = false
private lateinit var heldItemVisibilityButton: SummaryButton
private var mainScreenIndex = INFO
lateinit var heldItemVisibilityButton: SummaryButton
lateinit var heldItemSwitchTypeButton: SummaryButton
lateinit var evolveButton: SummaryButton
var mainScreenIndex = INFO
var sideScreenIndex = PARTY
val controllerComponentManager = ControllerComponentManager(this)
var mainScreenComponentManager: ControllerComponentManager? = null
var sideScreenComponentManager: ControllerComponentManager? = null
var selectedMoveIndex = 0
var moveSwitchingMode = false
var lastSelectedPartySlot = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun renderBlurredBackground(delta: Float) { }
/**
@@ -158,7 +186,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
addRenderableWidget(markingsWidget)
// Evolve Button
addRenderableWidget(
evolveButton = addRenderableWidget(
SummaryButton(
buttonX = x + 12F,
buttonY = y + 145F,
@@ -204,22 +232,22 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
heldItemVisibilityButton.buttonActive = !selectedPokemon.heldItemVisible
addRenderableWidget(heldItemVisibilityButton)
// Held/Cosmetic Item Button
addRenderableWidget(
SummaryButton(
buttonX = x + 67F,
buttonY = y + 113F,
buttonWidth = 12,
buttonHeight = 12,
scale = 0.5F,
resource = iconCosmeticItemResource,
activeResource = iconHeldItemResource,
clickAction = {
showCosmeticItem = !showCosmeticItem
(it as SummaryButton).buttonActive = showCosmeticItem
}
)
heldItemSwitchTypeButton = SummaryButton(
buttonX = x + 67F,
buttonY = y + 113F,
buttonWidth = 12,
buttonHeight = 12,
scale = 0.5F,
resource = iconCosmeticItemResource,
activeResource = iconHeldItemResource,
clickAction = {
showCosmeticItem = !showCosmeticItem
(it as SummaryButton).buttonActive = showCosmeticItem
}
)
addRenderableWidget(heldItemSwitchTypeButton)
// Init Tabs
summaryTabs.clear()
@@ -253,14 +281,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
// Add Exit Button
addRenderableWidget(
ExitButton(pX = x + 302, pY = y + 145) {
playSound(CobblemonSounds.GUI_CLICK)
if (sideScreenIndex != PARTY) {
displaySideScreen(PARTY)
} else {
saveActiveMark()
saveMarkings()
Minecraft.getInstance().setScreen(null)
}
onBack()
}
)
@@ -294,9 +315,214 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
addRenderableWidget(this.modelWidget)
updatePokemonInfo()
//Controller Support
controllerComponentManager.componentList["nickname"] = ControllerComponent().also {
it.associatedGUIElement = nicknameEntryWidget
it.right = { controllerComponentManager.componentList["center_screen"] }
}
controllerComponentManager.componentList["held_item"] = ControllerComponent().also {
it.associatedGUIElement = heldItemVisibilityButton
it.right = { controllerComponentManager.componentList["center_screen"] }
it.pairUp(controllerComponentManager.componentList["nickname"]!!)
it.down = { if (evolveButton.renderRequirement.invoke(evolveButton)) controllerComponentManager.componentList["evolve"] else null }
it.onSpecial1 = {
playSound(CobblemonSounds.GUI_CLICK)
heldItemSwitchTypeButton.onPress()
}
it.onPress = {
if (heldItemVisibilityButton.clickRequirement.invoke(heldItemVisibilityButton)) heldItemVisibilityButton.onPress()
}
}
controllerComponentManager.componentList["evolve"] = ControllerComponent().also {
it.associatedGUIElement = evolveButton
it.right = { controllerComponentManager.componentList["center_screen"] }
it.up = { controllerComponentManager.componentList["held_item"] }
}
controllerComponentManager.componentList["center_screen"] = SubManagerComponent().also {
it.subManager = { mainScreenComponentManager }
it.left = {
var returnVal: ControllerComponent? = null
if (mainScreenComponentManager?.left() ?: true && !moveSwitchingMode) {
returnVal = controllerComponentManager.componentList["nickname"]
}
returnVal
}
it.right = {
var returnVal: ControllerComponent? = null
if (mainScreenComponentManager?.right() ?: true && !moveSwitchingMode) {
returnVal = controllerComponentManager.componentList["right_screen"]
}
returnVal
}
it.up = {
if (mainScreenIndex == STATS) {
val statWidget = mainScreen as StatWidget
var index = statWidget.statTabIndex + 1
if (index > statWidget.statOptions.size - 1) {
index = 0
}
statWidget.statTabIndex = index
playSound(CobblemonSounds.GUI_CLICK)
} else {
mainScreenComponentManager?.up()
}
null
}
it.down = {
if (mainScreenIndex == STATS) {
val statWidget = mainScreen as StatWidget
var index = statWidget.statTabIndex - 1
if (index < 0) {
index = statWidget.statOptions.size - 1
}
statWidget.statTabIndex = index
playSound(CobblemonSounds.GUI_CLICK)
} else {
mainScreenComponentManager?.down()
}
null
}
it.onPress = {
if (mainScreenIndex == STATS) {
val statWidget = mainScreen as StatWidget
if (statWidget.statOptions.get(statWidget.statTabIndex) == StatWidget.RIDE && (statWidget.pokemon.riding.behaviours?.size ?: 0) > 1) {
statWidget.rideBehaviourIndex = (statWidget.rideBehaviourIndex + 1) % (statWidget.pokemon.riding.behaviours?.size ?: 1)
playSound(CobblemonSounds.GUI_CLICK)
}
} else {
mainScreenComponentManager?.press()
}
}
}
controllerComponentManager.componentList["right_screen"] = SubManagerComponent().also {
it.subManager = { sideScreenComponentManager }
it.left = {
var returnVal: ControllerComponent? = null
if (sideScreenComponentManager?.left() ?: true && sideScreenIndex == PARTY && (sideScreen as? PartyWidget)?.swapSource == null) {
returnVal = controllerComponentManager.componentList["center_screen"]
}
returnVal
}
}
controllerComponentManager.let {
it.focused = it.componentList["center_screen"]
it.onTabNav = { nextTab ->
var index = mainScreenIndex + if (nextTab) 1 else -1
if (index < 0) index = summaryTabs.size - 1
if (index > summaryTabs.size - 1) index = 0
playSound(CobblemonSounds.GUI_CLICK)
displayMainScreen(index)
}
it.onSecondaryTabNav = { nextTab ->
val pokemonIndex = party.indexOf(selectedPokemon)
val indexList = party.filter { it != null }.map { party.indexOf(it) }.toMutableList()
var index = indexList.indexOf(pokemonIndex) + if (nextTab) 1 else -1
if (index < 0) index = indexList.size - 1
if (index > indexList.size - 1) index = 0
playSound(CobblemonSounds.GUI_CLICK)
displaySideScreen(PARTY)
switchSelection(indexList[index])
}
it.onBack = {
if (moveSwitchingMode) {
playSound(CobblemonSounds.GUI_CLICK)
moveSwitchingMode = false
val movesWidget = mainScreen as? MovesWidget
if (movesWidget != null) {
movesWidget.controllerComponentManager.focused?.onUnselect()
}
} else {
val partyWidget = sideScreen as? PartyWidget
if (partyWidget != null && partyWidget.swapSource != null) {
partyWidget.draggedSlot = null
partyWidget.controllerComponentManager.let {
it.focused = it.componentList["slot_${partyWidget.swapSource}"]
}
partyWidget.swapSource = null
playSound(CobblemonSounds.PC_DROP)
} else {
onBack()
}
}
}
it.onActiveChange = { state ->
if (!state) {
moveSwitchingMode = false
}
if (mainScreenComponentManager != null) {
if (!state) {
mainScreenComponentManager?.active = false
} else {
if (it.currentSelectedComponent == "center_screen") mainScreenComponentManager?.active = true
}
}
if (sideScreenComponentManager != null) {
if (!state) {
sideScreenComponentManager?.active = false
} else {
if (it.currentSelectedComponent == "right_screen") sideScreenComponentManager?.active = true
}
}
}
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SECONDARY_TAB,
{ true },
Component.translatable("cobblemon.controller.summary.quick_switch"),
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "center_screen" && mainScreenIndex == MOVES },
Component.translatable("cobblemon.controller.summary.change_move"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_1,
{ it.currentSelectedComponent == "held_item" },
Component.translatable("cobblemon.controller.toggle_held_item_type"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_2,
{ it.currentSelectedComponent == "center_screen" && mainScreenIndex == MOVES },
Component.translatable("cobblemon.controller.summary.adjust_move"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_2,
{ it.currentSelectedComponent == "center_screen" && mainScreenIndex == MARKS },
Component.translatable("cobblemon.controller.summary.clear_mark"),
true
))
it.buttonGuides.add(ControllerComponentManager.ButtonGuide(
ControllerComponentManager.ButtonGuideType.SPECIAL_2,
{ it.currentSelectedComponent == "right_screen" && sideScreenIndex == PARTY },
Component.translatable("cobblemon.controller.summary.swap_position"),
true
))
}
}
fun onBack() {
playSound(CobblemonSounds.GUI_CLICK)
if (sideScreenIndex != PARTY) {
if (sideScreenIndex == EVOLVE) {
controllerComponentManager.focused = controllerComponentManager.componentList["evolve"]
} else {
controllerComponentManager.focused = controllerComponentManager.componentList["center_screen"]
}
displaySideScreen(PARTY)
} else {
saveActiveMark()
saveMarkings()
Minecraft.getInstance().setScreen(null)
}
}
fun swapPartySlot(sourceIndex: Int, targetIndex: Int) {
moveSwitchingMode = false
if (sourceIndex >= this.party.size || targetIndex >= this.party.size) {
return
}
@@ -357,11 +583,11 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
}
}
private fun saveMarkings() {
fun saveMarkings() {
if (::markingsWidget.isInitialized) markingsWidget.saveMarkingsToPokemon()
}
private fun saveActiveMark() {
fun saveActiveMark() {
if (mainScreenIndex == MARKS && mainScreen is MarksWidget) (mainScreen as MarksWidget).saveActiveMarkToPokemon()
}
@@ -373,12 +599,16 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
/**
* Switch center screen
*/
private fun displayMainScreen(screen: Int) {
fun displayMainScreen(screen: Int) {
// Save mark to Pokémon if any changes when switching out of marks screen
saveActiveMark()
// Get stat tab index if currently displaying stat screen
val subIndex = if (mainScreenIndex == STATS && mainScreen is StatWidget) (mainScreen as StatWidget).statTabIndex else 0
val subIndex = if (mainScreenIndex == STATS && mainScreen is StatWidget) (mainScreen as StatWidget).rawStatTabIndex else 0
if (screen != MOVES) {
moveSwitchingMode = false
}
mainScreenIndex = screen
if (::mainScreen.isInitialized) removeWidget(mainScreen)
@@ -398,6 +628,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
pY = y + 12,
pokemon = this.selectedPokemon
)
mainScreenComponentManager = null
}
MOVES -> {
@@ -405,7 +636,13 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
pX = x + 77,
pY = y + 12,
summary = this
)
).also { movesWidget ->
mainScreenComponentManager = movesWidget.controllerComponentManager
movesWidget.controllerComponentManager.let {
it.focused = it.componentList["move_${selectedMoveIndex}"]
}
}
}
STATS -> {
@@ -413,25 +650,38 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
pX = x + 77,
pY = y + 12,
pokemon = this.selectedPokemon,
tabIndex = subIndex
tabIndex = subIndex,
summary = this
)
mainScreenComponentManager = null
}
MARKS -> {
mainScreen = MarksWidget(
pX = x + 77,
pY = y + 12,
pokemon = this.selectedPokemon
)
pokemon = this.selectedPokemon,
summary = this
).also {
mainScreenComponentManager = it.controllerComponentManager
}
}
}
addRenderableWidget(mainScreen)
if (controllerComponentManager.currentSelectedComponent == "center_screen" && controllerComponentManager.active) {
focused = null
mainScreenComponentManager?.active = true
controllerComponentManager.focused?.onFocus?.invoke()
}
}
/**
* Switch right screen
*/
fun displaySideScreen(screen: Int, move: Move? = null) {
moveSwitchingMode = false
sideScreenIndex = screen
if (::sideScreen.isInitialized) removeWidget(sideScreen)
@@ -446,7 +696,9 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
isParty = selectedPokemon in CobblemonClient.storage.party,
summary = this,
partyList = this.party
)
).also {
sideScreenComponentManager = it.controllerComponentManager
}
}
MOVE_SWAP -> {
@@ -468,7 +720,9 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
moveSlotList += MoveSwapScreen.MoveSlot(switchPane, null, 0, pokemon)
}
moveSlotList.forEach { switchPane.addEntry(it) }
sideScreenComponentManager = switchPane.controllerComponentManager
}
controllerComponentManager.focused = controllerComponentManager.componentList["right_screen"]
}
}
@@ -478,14 +732,67 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
sideScreen = EvolutionSelectScreen(
x + 216,
y + 23,
pokemon = selectedPokemon
)
pokemon = selectedPokemon,
summary = this
).also {
sideScreenComponentManager = it.controllerComponentManager
}
controllerComponentManager.focused = controllerComponentManager.componentList["right_screen"]
}
}
val element = sideScreen
if (element is Renderable && element is NarratableEntry) {
addRenderableWidget(element)
}
if (controllerComponentManager.currentSelectedComponent == "right_screen" && controllerComponentManager.active) {
focused = null
sideScreenComponentManager?.active = true
controllerComponentManager.focused?.onFocus?.invoke()
}
}
override fun getSelectionPosAndSize(): Vector4i? {
val data = super.getSelectionPosAndSize()
if (data != null) {
when (focused) {
nicknameEntryWidget -> return Vector4i(nicknameEntryWidget.x - 12, nicknameEntryWidget.y - 4, 78, 17)
heldItemVisibilityButton -> return Vector4i(data.x + 1, data.y + 1, data.z / 2, data.w / 2)
is PartySlotWidget -> return data.add(-2, -2, 0, 2)
else -> return data
}
} else {
when (controllerComponentManager.currentSelectedComponent) {
"center_screen" -> {
when (mainScreenIndex) {
MARKS -> {
val marksScreen = mainScreen as MarksWidget
val horizontalSpacing = if (marksScreen.marksScrollList.children().size > 5) MarksScrollingWidget.SLOT_SPACING else (MarksScrollingWidget.SLOT_SPACING + 1)
val row = marksScreen.lastSelectedMark / 6
val column = marksScreen.lastSelectedMark % 6
val startX = marksScreen.marksScrollList.rowLeft + (horizontalSpacing + MarksScrollingWidget.SLOT_SIZE) * column
val startY = marksScreen.marksScrollList.getRowTop(row) + MarksScrollingWidget.SLOT_SPACING
return Vector4i(startX - 2, startY - 2, MarksScrollingWidget.SLOT_SIZE + 4, MarksScrollingWidget.SLOT_SIZE + 4)
}
else -> return Vector4i(mainScreen.x + 2, mainScreen.y + 2, 130, 144)
}
}
"right_screen" -> {
when (sideScreenIndex) {
EVOLVE -> {
val evolveScreen = sideScreen as EvolutionSelectScreen
return Vector4i(evolveScreen.rowLeft - 5, evolveScreen.getRowTop(evolveScreen.children().indexOf(focused)) - 2, evolveScreen.rowWidth + 4, EvolutionSelectScreen.SLOT_HEIGHT + 4)
}
MOVE_SWAP -> {
val moveSwapScreen = sideScreen as MoveSwapScreen
return Vector4i(moveSwapScreen.rowLeft - 11, moveSwapScreen.getRowTop(moveSwapScreen.children().indexOf(focused)) - 2, moveSwapScreen.rowWidth + 13, MoveSwapScreen.SLOT_HEIGHT + 4)
}
}
}
}
}
return null
}
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -625,8 +932,11 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
val itemX = x + 3
val itemY = y + 104
if (!displayedItem.isEmpty) {
matrices.pushPose()
matrices.translate(0.0,0.0,-200.0)
context.renderItem(displayedItem, itemX, itemY)
context.renderItemDecorations(Minecraft.getInstance().font, displayedItem, itemX, itemY)
matrices.popPose()
}
drawScaledText(
@@ -655,19 +965,25 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
scale = SCALE
)
matrices.pushPose()
// Prevent widgets from being overlapped by other components
matrices.translate(0.0, 0.0, 1000.0)
// Set Look View Pos
val selectionData = getSelectionPosAndSize()
if (controllerComponentManager.active && selectionData != null) {
modelWidget.overrideLookPos = Vector2i(selectionData.x + selectionData.z / 2, selectionData.y + selectionData.w / 2)
} else {
modelWidget.overrideLookPos = null
}
// Render all added Widgets
super.render(context, mouseX, mouseY, delta)
// Selection Rendering
renderSelection(matrices)
// Render Item Tooltip
if (!displayedItem.isEmpty) {
val itemHovered = mouseX.toFloat() in (itemX.toFloat()..(itemX.toFloat() + 16)) && mouseY.toFloat() in (itemY.toFloat()..(itemY.toFloat() + 16))
if (itemHovered) context.renderTooltip(Minecraft.getInstance().font, displayedItem, mouseX, mouseY)
}
matrices.popPose()
}
/**
@@ -32,8 +32,8 @@ class SummaryButton(
private val text: MutableComponent = Component.empty(),
private val resource: ResourceLocation,
private val activeResource: ResourceLocation? = null,
private val renderRequirement: ((button: SummaryButton) -> Boolean) = { true },
private val clickRequirement: ((button: SummaryButton) -> Boolean) = { true },
val renderRequirement: ((button: SummaryButton) -> Boolean) = { true },
val clickRequirement: ((button: SummaryButton) -> Boolean) = { true },
private val hoverTexture: Boolean = true,
private val silent: Boolean = false,
private val boldText: Boolean = true,
@@ -67,7 +67,7 @@ class SummaryButton(
y = buttonY / scale,
width = buttonWidth,
height = buttonHeight,
vOffset = if (hoverTexture && isButtonHovered(pMouseX, pMouseY)) buttonHeight else 0,
vOffset = if (hoverTexture && (isButtonHovered(pMouseX, pMouseY) || isFocused)) buttonHeight else 0,
textureHeight = if (hoverTexture) (buttonHeight.toFloat() * 2) else buttonHeight,
scale = scale
)
@@ -15,8 +15,11 @@ import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.SummaryButton
import com.cobblemon.mod.common.client.gui.summary.widgets.common.SummaryScrollList
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.pokemon.FormData
@@ -34,7 +37,8 @@ import org.joml.Vector3f
class EvolutionSelectScreen(
x: Int,
y: Int,
val pokemon: Pokemon
val pokemon: Pokemon,
val summary: Summary
): SummaryScrollList<EvolutionSelectScreen.EvolveSlot>(
x,
y,
@@ -52,8 +56,36 @@ class EvolutionSelectScreen(
private var entriesCreated = false
var controllerComponentManager = ControllerComponentManager(summary).also {
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
public override fun addEntry(entry: EvolveSlot): Int {
return super.addEntry(entry)
val i = super.addEntry(entry)
controllerComponentManager.componentList["moves_${i}"] = ControllerComponent().also {
it.associatedGUIElement = entry
it.onPress = {
entry.selectButton.onPress()
}
it.onSelect = {
ensureVisible(entry)
entry.selectButton.isFocused = true
}
it.onUnselect = {
entry.selectButton.isFocused = false
}
}
if (children().size > 1) {
controllerComponentManager.componentList["moves_${i}"]?.let {
it.pairUp(controllerComponentManager.componentList["moves_${i-1}"])
it.pairDown(controllerComponentManager.componentList["moves_${0}"])
}
} else {
controllerComponentManager.focused = controllerComponentManager.componentList["moves_${i}"]
}
return i
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import org.joml.Quaternionf
import org.joml.Vector2i
import org.joml.Vector3f
class ModelWidget(
@@ -59,6 +60,8 @@ class ModelWidget(
var currentYawAndPitch: Pair<Float, Float> = Pair(0f, 0f)
val rotationVector = Vector3f(13F, rotationY, 0F)
var overrideLookPos: Vector2i? = null
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, partialTicks: Float) {
if (!render) {
return
@@ -88,7 +91,7 @@ class ModelWidget(
val timeLooking = lookStartTime?.let { (System.currentTimeMillis() - it) / 1000 } ?: MAX_SECONDS_LOOKING
val lookedTooMuch = timeLooking >= MAX_SECONDS_LOOKING
val resetToCenter = (!isHovered && lookedTooMuch) || !shouldFollowCursor || !Cobblemon.config.summaryPokemonFollowCursor
val resetToCenter = (!isHovered && lookedTooMuch && overrideLookPos == null) || !shouldFollowCursor || !Cobblemon.config.summaryPokemonFollowCursor
if (resetToCenter) {
lookStartTime = null
@@ -97,12 +100,19 @@ class ModelWidget(
val rotation = Quaternionf().fromEulerXYZDegrees(rotationVector)
if (shouldFollowCursor) {
var xPos = mouseX
var yPos = mouseY
if (overrideLookPos != null) {
xPos = overrideLookPos!!.x
yPos = overrideLookPos!!.y
}
currentYawAndPitch = calculateHeadYawAndPitch(
x + width / 2f,
y + height / 2f,
rotation,
mouseX,
mouseY,
xPos,
yPos,
currentYawAndPitch.first,
currentYawAndPitch.second,
resetToCenter
@@ -120,6 +130,7 @@ class ModelWidget(
blockLight = blockLight,
profileTransformType = ProfileTransformType.SUMMARY,
)
context.flush()
heldItemRenderer.renderOnModel(
pokemon.heldItem,
@@ -88,7 +88,7 @@ class PartySlotWidget(
y = y,
width = width,
height = height,
vOffset = getSlotVOffset(slotPokemon, isHovered, isSelected),
vOffset = getSlotVOffset(slotPokemon, isHovered || isFocused, isSelected),
textureHeight = height * 2,
)
@@ -223,16 +223,21 @@ class PartySlotWidget(
if (partyWidget.swapEnabled) {
toggleDrag(true)
} else {
if ((index > -1) && (summary.selectedPokemon.uuid != pokemon?.uuid)) {
summary.switchSelection(index)
if (pokemon != null) partyWidget.playSound(CobblemonSounds.GUI_CLICK)
return true
}
if (onPress()) return true
}
}
return super.mouseClicked(pMouseX, pMouseY, pButton)
}
fun onPress(): Boolean {
if ((index > -1) && (summary.selectedPokemon.uuid != pokemon?.uuid)) {
summary.switchSelection(index)
if (pokemon != null) partyWidget.playSound(CobblemonSounds.GUI_CLICK)
return true
}
return false
}
override fun mouseReleased(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (partyWidget.swapEnabled) toggleDrag(false)
return partyWidget.mouseReleased(pMouseX, pMouseY, pButton)
@@ -14,6 +14,8 @@ import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.SummaryButton
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
@@ -67,6 +69,8 @@ class PartyWidget(
}
)
var controllerComponentManager = ControllerComponentManager(summary)
init {
if (partySize > 6 || partySize < 1)
throw InvalidParameterException("Invalid party size")
@@ -85,7 +89,7 @@ class PartyWidget(
y += (32 * offsetIndex) + offsetY
}
PartySlotWidget(
val slot = PartySlotWidget(
pX = x,
pY = y,
partyWidget = this,
@@ -97,6 +101,86 @@ class PartyWidget(
this.addWidget(widget)
partySlots.add(widget)
}
controllerComponentManager.componentList["slot_${index}"] = ControllerComponent().also {
it.associatedGUIElement = slot
it.onPress = {
if (swapEnabled) {
if (swapSource == null) {
if (partyList[index] != null) {
swapSource = index
draggedSlot = PartySlotWidget(
pX = slot.x + 4,
pY = slot.y + 4,
partyWidget = this,
summary = summary,
pokemon = partyList[index],
index = -1,
isClientPartyMember = isParty
)
draggedSlot?.isFocused = true
playSound(CobblemonSounds.PC_GRAB)
}
} else {
if (swapSource != index) {
summary.swapPartySlot(swapSource!!, index)
}
swapSource = null
draggedSlot = null
playSound(CobblemonSounds.PC_DROP)
}
} else slot.onPress()
}
it.onSpecial1 = {
swapEnabled = !swapEnabled
swapButton.buttonActive = swapEnabled
if (!swapEnabled) {
swapSource = null
draggedSlot = null
}
playSound(CobblemonSounds.GUI_CLICK)
}
fun updateDraggedSlotPos() {
draggedSlot?.let { theSlot ->
theSlot.x = slot.x + 4
theSlot.y = slot.y + 4
}
}
if (index-2 >= 0) {
it.up = {
controllerComponentManager.componentList["slot_${index-2}"]
}
}
if (index+2 <= 5) {
it.down = {
controllerComponentManager.componentList["slot_${index+2}"]
}
}
if (index.mod(2) == 0) {
it.right = {
controllerComponentManager.componentList["slot_${index+1}"]
}
} else {
it.left = {
controllerComponentManager.componentList["slot_${index-1}"]
}
}
it.onSelect = {
updateDraggedSlotPos()
summary.lastSelectedPartySlot = index
}
}
}
controllerComponentManager.let {
it.focused = it.componentList["slot_${summary.lastSelectedPartySlot}"]
it.onActiveChange = { state ->
if (!state) {
swapSource = null
draggedSlot = null
}
}
}
}
@@ -125,6 +125,23 @@ abstract class SummaryScrollList<T : ObjectSelectionList.Entry<T>>(
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
public override fun ensureVisible(entry: T) {
val i = getRowTop(children().indexOf(entry))
val topDifference = i - y - 4
if (topDifference < 0) {
scroll(topDifference)
}
val bottomDifference = bottom - i - itemHeight
if (bottomDifference < 0) {
scroll(-bottomDifference)
}
}
public override fun getRowTop(index: Int): Int {
return super.getRowTop(index)
}
private fun updateScrollingState(mouseX: Double, mouseY: Double) {
scrolling = mouseX >= this.scrollbarPosition.toDouble()
&& mouseX < (this.scrollbarPosition + 3).toDouble()
@@ -135,4 +152,6 @@ abstract class SummaryScrollList<T : ObjectSelectionList.Entry<T>>(
private fun correctSize() {
setRectangle(WIDTH, HEIGHT, listX, listY)
}
open fun onPress(entry: T) {}
}
@@ -106,10 +106,11 @@ class MarksScrollingWidget(val pX: Int, val pY: Int, val setSelectedMark: () ->
companion object {
private val slotResource = cobblemonResource("textures/gui/summary/summary_mark_slot.png")
}
var x: Int = 0
var y: Int = 0
var slotSelected: (Int) -> Boolean = { false }
fun renderRow(context: GuiGraphics, y: Int, x: Int, mouseX: Int, mouseY: Int): Boolean {
var anyHovered = false
val horizontalSpacing = if (isScrollVisible()) SLOT_SPACING else (SLOT_SPACING + 1)
@@ -133,7 +134,7 @@ class MarksScrollingWidget(val pX: Int, val pY: Int, val setSelectedMark: () ->
y = startPosY,
width = SLOT_SIZE,
height = SLOT_SIZE,
vOffset = if (slotHovered) SLOT_SIZE else 0,
vOffset = if (slotHovered || (mark != null && slotSelected.invoke(index))) SLOT_SIZE else 0,
textureHeight = SLOT_SIZE * 2
)
@@ -160,7 +161,7 @@ class MarksScrollingWidget(val pX: Int, val pY: Int, val setSelectedMark: () ->
return true
}
private fun isScrollVisible(): Boolean = parent.children().size > 5
fun isScrollVisible(): Boolean = parent.children().size > 5
private fun getHoveredSlotIndex(mouseX: Int, mouseY: Int): Int {
val horizontalSpacing = if (isScrollVisible()) SLOT_SPACING else (SLOT_SPACING + 1)
@@ -15,7 +15,10 @@ import com.cobblemon.mod.common.api.gui.MultiLineLabelK
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.mark.Mark
import com.cobblemon.mod.common.client.gui.common.MarkIcon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetActiveMarkPacket
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -27,7 +30,8 @@ import net.minecraft.network.chat.Component
class MarksWidget(
pX: Int, pY: Int,
val pokemon: Pokemon
val pokemon: Pokemon,
val summary: Summary
): SoundlessWidget(pX, pY, WIDTH, HEIGHT, Component.literal("MarksWidget")) {
companion object {
private const val WIDTH = 134
@@ -53,6 +57,9 @@ class MarksWidget(
setHoveredMark = { hoveredMark = it }
)
val controllerComponentManager = ControllerComponentManager(summary)
var lastSelectedMark = 0
init {
children.clear()
activeMark = pokemon.activeMark
@@ -64,6 +71,54 @@ class MarksWidget(
while ((marksList.size < 30) || (marksList.size > 30 && marksList.size % 6 != 0)) marksList.add(null)
marksScrollList.createEntries(marksList)
addWidget(marksScrollList)
marksScrollList.children().forEachIndexed { firstIndex, row ->
row.markList.forEachIndexed { secondIndex, mark ->
controllerComponentManager.componentList["mark_${firstIndex}_${secondIndex}"] = ControllerComponent().also {
it.associatedGUIElement = row
it.onPress = {
if (mark != null) {
selectedMark = mark
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
}
it.onSpecial1 = {
if (selectedMark !== null) {
selectedMark = null
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
}
it.onSelect = {
lastSelectedMark = firstIndex * 6 + secondIndex
marksScrollList.ensureVisible(row)
}
if (firstIndex - 1 >= 0) {
it.up = {
controllerComponentManager.componentList["mark_${firstIndex-1}_${secondIndex}"]
}
}
if (firstIndex + 1 <= marksScrollList.children().size - 1) {
it.down = {
controllerComponentManager.componentList["mark_${firstIndex+1}_${secondIndex}"]
}
}
if (secondIndex - 1 >= 0) {
it.left = {
controllerComponentManager.componentList["mark_${firstIndex}_${secondIndex-1}"]
}
}
if (secondIndex + 1 <= row.markList.size - 1) {
it.right = {
controllerComponentManager.componentList["mark_${firstIndex}_${secondIndex+1}"]
}
}
}
}
row.slotSelected = { index ->
controllerComponentManager.active && lastSelectedMark / 6 == firstIndex && lastSelectedMark % 6 == index
}
}
controllerComponentManager.let { it.focused = it.componentList["mark_${lastSelectedMark / 6}_${lastSelectedMark % 6}"] }
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
@@ -45,14 +45,14 @@ class MoveSlotWidget(
const val MOVE_HEIGHT = 22
}
private val moveUpButton = ReorderMoveButton(x, y, true) {
val moveUpButton = ReorderMoveButton(x, y, true) {
movesWidget.selectMove(null)
movesWidget.reorderMove(this, true)
}.apply {
addWidget(this)
}
private val moveDownButton = ReorderMoveButton(x, y, false) {
val moveDownButton = ReorderMoveButton(x, y, false) {
movesWidget.selectMove(null)
movesWidget.reorderMove(this, false)
}.apply {
@@ -20,6 +20,8 @@ import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.widgets.common.SummaryScrollList
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.BenchMovePacket
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -49,13 +51,45 @@ class MoveSwapScreen(
private val forgetMoveIconResource = cobblemonResource("textures/gui/summary/summary_move_condensed_empty_icon.png")
}
var controllerComponentManager = ControllerComponentManager(movesWidget.summary).also {
it.onActiveChange = { state ->
if (state) it.focused?.onSelect?.invoke() else it.focused?.onUnselect?.invoke()
}
}
public override fun addEntry(entry: MoveSlot): Int {
return super.addEntry(entry)
val i = super.addEntry(entry)
controllerComponentManager.componentList["moves_${i}"] = ControllerComponent().also {
it.associatedGUIElement = entry
it.onPress = {
entry.onPress()
movesWidget.summary.controllerComponentManager.let { controller -> controller.focused = controller.componentList["center_screen"] }
}
it.onSelect = {
ensureVisible(entry)
entry.slotSelected = true
}
it.onUnselect = {
entry.slotSelected = false
}
}
if (children().size > 1) {
controllerComponentManager.componentList["moves_${i}"]?.let {
it.pairUp(controllerComponentManager.componentList["moves_${i-1}"])
it.pairDown(controllerComponentManager.componentList["moves_${0}"])
}
} else {
controllerComponentManager.focused = controllerComponentManager.componentList["moves_${i}"]
}
return i
}
class MoveSlot(val pane: MoveSwapScreen, val move: MoveTemplate?, val ppRaisedStages: Int, pokemon: Pokemon) : Entry<MoveSlot>() {
override fun getNarration() = move?.displayName ?: lang("ui.moves.forget")
val elementalType = move?.getEffectiveElementalType(pokemon) ?: ElementalTypes.NORMAL
var slotSelected = false
override fun render(
context: GuiGraphics,
index: Int,
@@ -80,7 +114,7 @@ class MoveSwapScreen(
y = tweakedRowTop,
height = SLOT_HEIGHT,
width = rowWidth,
vOffset = if (isHovered) SLOT_HEIGHT else 0,
vOffset = if (isHovered || slotSelected) SLOT_HEIGHT else 0,
textureHeight = SLOT_HEIGHT * 2,
red = rgb.first,
green = rgb.second,
@@ -196,7 +230,7 @@ class MoveSwapScreen(
y = tweakedRowTop,
height = SLOT_HEIGHT,
width = 100,
vOffset = if (isHovered) SLOT_HEIGHT else 0,
vOffset = if (isHovered || slotSelected) SLOT_HEIGHT else 0,
textureHeight = SLOT_HEIGHT * 2,
)
blitk(
@@ -206,7 +240,7 @@ class MoveSwapScreen(
y = tweakedRowTop + SLOT_HEIGHT / 2 - 4,
height = 7,
width = 7,
vOffset = if (isHovered) 7 else 0,
vOffset = if (isHovered || slotSelected) 7 else 0,
textureHeight = 14,
)
}
@@ -215,21 +249,29 @@ class MoveSwapScreen(
override fun mouseClicked(d: Double, e: Double, i: Int): Boolean {
if (isMouseOver(d, e)) {
val pokemon = pane.movesWidget.summary.selectedPokemon
val isParty = pokemon in CobblemonClient.storage.party
CobblemonNetwork.sendToServer(
BenchMovePacket(
isParty = isParty,
uuid = pokemon.uuid,
oldMove = pane.replacedMove?.template,
newMove = move
)
)
pane.movesWidget.summary.playSound(CobblemonSounds.GUI_CLICK)
pane.movesWidget.summary.displaySideScreen(Summary.PARTY)
onPress()
return true
}
return false
}
fun onPress() {
switchMove()
pane.movesWidget.summary.playSound(CobblemonSounds.GUI_CLICK)
pane.movesWidget.summary.displaySideScreen(Summary.PARTY)
}
fun switchMove() {
val pokemon = pane.movesWidget.summary.selectedPokemon
val isParty = pokemon in CobblemonClient.storage.party
CobblemonNetwork.sendToServer(
BenchMovePacket(
isParty = isParty,
uuid = pokemon.uuid,
oldMove = pane.replacedMove?.template,
newMove = move
)
)
}
}
}
@@ -9,12 +9,15 @@
package com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves
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.moves.Move
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.RequestMoveSwapPacket
import com.cobblemon.mod.common.util.cobblemonResource
@@ -52,8 +55,8 @@ class MovesWidget(
var selectedMove: Move? = null
private var index = -1
private val moves = (
var index = -1
val moves = (
if (summary.selectedPokemon.relearnableMoves.count() > 0)
summary.selectedPokemon.moveSet.getMovesWithNulls()
else summary.selectedPokemon.moveSet
@@ -76,6 +79,79 @@ class MovesWidget(
5
)
val controllerComponentManager = ControllerComponentManager(summary).also { controller ->
var i = 0
moves.forEach { move ->
controller.componentList["move_${i}"] = ControllerComponent().also {
it.associatedGUIElement = move
var upLoc = i - 1
if (upLoc < 0) upLoc = moves.size - 1
it.up = {
var returnVal: ControllerComponent?
if (!summary.moveSwitchingMode) {
returnVal = controller.componentList["move_${upLoc}"]
} else {
returnVal = null
summary.selectedMoveIndex = upLoc
reorderMove(move, true)
}
returnVal
}
var downLoc = i + 1
if (downLoc > moves.size - 1) downLoc = 0
it.down = {
var returnVal: ControllerComponent?
if (!summary.moveSwitchingMode) {
returnVal = controller.componentList["move_${downLoc}"]
} else {
returnVal = null
summary.selectedMoveIndex = downLoc
reorderMove(move, false)
}
returnVal
}
it.onSpecial1 = {
if (!summary.moveSwitchingMode) {
summary.playSound(CobblemonSounds.GUI_CLICK)
summary.displaySideScreen(Summary.MOVE_SWAP, move.move)
}
}
it.onSpecial2 = {
summary.playSound(CobblemonSounds.GUI_CLICK)
summary.moveSwitchingMode = !summary.moveSwitchingMode
if (summary.moveSwitchingMode) {
move.moveUpButton.isFocused = true
move.moveDownButton.isFocused = true
} else {
move.moveUpButton.isFocused = false
move.moveDownButton.isFocused = false
}
}
val staticI = i
it.onSelect = {
summary.selectedMoveIndex = staticI
if (selectedMove != move.move) selectMove(move.move)
if (summary.moveSwitchingMode) {
move.moveUpButton.isFocused = true
move.moveDownButton.isFocused = true
}
}
it.onUnselect = {
move.moveUpButton.isFocused = false
move.moveDownButton.isFocused = false
}
}
i++
}
controller.onNoneSelected = {
summary.selectedMoveIndex = summary.selectedMoveIndex.coerceIn(0, moves.size - 1)
controller.focused = controller.componentList["move_${summary.selectedMoveIndex}"]
true
}
controller.onActiveChange = { state ->
if (state) controller.focused?.onSelect?.invoke() else controller.focused?.onUnselect?.invoke()
}
}
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
val matrices = context.pose()
@@ -50,7 +50,7 @@ class ReorderMoveButton(
texture = if (isUp) moveReorderUpResource else moveReorderDownResource,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(pMouseX.toDouble(), pMouseY.toDouble(), offsetY.toFloat())) HEIGHT else 0,
vOffset = if (isHovered(pMouseX.toDouble(), pMouseY.toDouble(), offsetY.toFloat()) || isFocused) HEIGHT else 0,
textureHeight = HEIGHT * 2,
scale = SCALE
)
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.SummaryButton
import com.cobblemon.mod.common.client.gui.summary.featurerenderers.BarSummarySpeciesFeatureRenderer
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
@@ -49,16 +50,17 @@ import org.joml.Vector3f
class StatWidget(
pX: Int, pY: Int,
val pokemon: Pokemon,
val tabIndex: Int = 0
val tabIndex: Int = 0,
val summary: Summary
): SoundlessWidget(pX, pY, WIDTH, HEIGHT, Component.literal("StatWidget")) {
companion object {
// Stat tab options
private const val STATS = "stats"
private const val IV = "ivs"
private const val EV = "evs"
private const val RIDE = "ride"
private const val OTHER = "other"
const val STATS = "stats"
const val IV = "ivs"
const val EV = "evs"
const val RIDE = "ride"
const val OTHER = "other"
private const val OTHER_STAT_BARS_PER_PAGE = 4
private const val STAT_TAB_WIDTH_6 = 22
@@ -113,13 +115,13 @@ class StatWidget(
)
}
private val statOptions = if (pokemon.form.riding.behaviours != null) listOf(STATS, IV, EV, RIDE, OTHER) else listOf(STATS, IV, EV, OTHER)
val statOptions = if (pokemon.form.riding.behaviours != null) listOf(STATS, IV, EV, RIDE, OTHER) else listOf(STATS, IV, EV, OTHER)
private var _statTabIndex: Int = tabIndex
var rawStatTabIndex: Int = tabIndex
var statTabIndex: Int
get() = _statTabIndex.coerceIn(0, statOptions.size - 1)
get() = rawStatTabIndex.coerceIn(0, statOptions.size - 1)
set(value) {
_statTabIndex = value.coerceIn(0, statOptions.size - 1)
rawStatTabIndex = value.coerceIn(0, statOptions.size - 1)
}
private var _rideBehaviourIndex: Int = 0
@@ -302,7 +304,7 @@ class StatWidget(
pokemon.form.riding.behaviours?.let {
val behaviours = it.entries.toList()
val selectedBehaviour = behaviours[rideBehaviourIndex]
val canSwitchStyle = behaviours.size > 1 && centerHovered(pMouseX, pMouseY)
val canSwitchStyle = behaviours.size > 1 && (centerHovered(pMouseX, pMouseY) || (summary.controllerComponentManager.let { it.active && it.currentSelectedComponent == "center_screen" }))
blitk(
matrixStack = matrices,
@@ -49,7 +49,7 @@ open class PartySlot(
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val matrices = context.pose()
if (!isOpposing && isHovered(mouseX, mouseY)) {
if (!isOpposing && (isHovered(mouseX, mouseY) || isFocused)) {
blitk(
matrixStack = matrices,
texture = hoverBackgroundResource,
@@ -49,7 +49,7 @@ class TradeButton(
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || isFocused) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
@@ -22,6 +22,9 @@ import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.widgets.MarkingsWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.common.reformatNatureTextIfMinted
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponent
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
import com.cobblemon.mod.common.client.trade.ClientTrade
@@ -55,7 +58,7 @@ class TradeGUI(
val traderName: MutableComponent,
val traderParty: MutableList<TradeablePokemon?>,
val party: MutableList<TradeablePokemon?>
): Screen(lang("trade.gui.title")), CobblemonRenderable {
): Screen(lang("trade.gui.title")), CobblemonRenderable, ControllerScreen {
companion object {
const val BASE_WIDTH = 293
@@ -118,6 +121,13 @@ class TradeGUI(
var tradeSoundInstance: SoundInstance? = null
val controllerComponentManager = ControllerComponentManager(this)
var lastSelectedRow = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
init {
trade.cancelEmitter.subscribe {
cancelTradeSound()
@@ -178,21 +188,25 @@ class TradeGUI(
)
// Trade Button
this.addRenderableWidget(
TradeButton(
x = x + 120,
y = y + 119,
parent = this,
onPress = {
if (offeredPokemon != null && opposingOfferedPokemon != null && protectiveTicks <= 0 && !tradeProcessing) {
ticksElapsed = 0
readyProgress = 0
CobblemonNetwork.sendToServer(ChangeTradeAcceptancePacket(opposingOfferedPokemon!!.uuid, !trade.acceptedOppositeOffer))
playSound(CobblemonSounds.GUI_CLICK)
}
val tradeButton = TradeButton(
x = x + 120,
y = y + 119,
parent = this,
onPress = {
if (offeredPokemon != null && opposingOfferedPokemon != null && protectiveTicks <= 0 && !tradeProcessing) {
ticksElapsed = 0
readyProgress = 0
CobblemonNetwork.sendToServer(ChangeTradeAcceptancePacket(opposingOfferedPokemon!!.uuid, !trade.acceptedOppositeOffer))
playSound(CobblemonSounds.GUI_CLICK)
}
)
}
)
this.addRenderableWidget(tradeButton)
controllerComponentManager.componentList["trade"] = ControllerComponent().also {
it.associatedGUIElement = tradeButton
it.left = { controllerComponentManager.componentList["slot_${lastSelectedRow}_${1}"] }
}
// Party
for (partyIndex in 0..5) {
@@ -210,7 +224,7 @@ class TradeGUI(
}
val pokemon = party[partyIndex]
PartySlot(
val slot = PartySlot(
x = slotX,
y = slotY,
pokemon = pokemon,
@@ -223,6 +237,22 @@ class TradeGUI(
}
}
).also { widget -> addRenderableWidget(widget) }
val row = partyIndex / 2
val column = partyIndex % 2
controllerComponentManager.componentList["slot_${row}_${column}"] = ControllerComponent().also {
it.associatedGUIElement = slot
if (column == 0) it.right = { controllerComponentManager.componentList["slot_${row}_${1}"] }
else {
it.left = { controllerComponentManager.componentList["slot_${row}_${0}"] }
it.right = { controllerComponentManager.componentList["trade"] }
}
if (row - 1 >= 0) it.up = { controllerComponentManager.componentList["slot_${row-1}_${column}"] }
if (row + 1 <= 2) it.down = { controllerComponentManager.componentList["slot_${row+1}_${column}"] }
it.onSelect = {
lastSelectedRow = row
}
}
}
// Opposing Party
@@ -257,6 +287,10 @@ class TradeGUI(
opposingOfferedPokemonMarkings = MarkingsWidget(x + 177.5, y + 21, opposingOfferedPokemon, false)
addRenderableWidget(offeredPokemonMarkings)
addRenderableWidget(opposingOfferedPokemonMarkings)
controllerComponentManager.let {
it.focused = it.componentList["slot_0_0"]
}
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -445,6 +479,8 @@ class TradeGUI(
val itemHovered = mouseX.toFloat() in (itemX.toFloat()..(itemX.toFloat() + 16)) && mouseY.toFloat() in (itemY.toFloat()..(itemY.toFloat() + 16))
if (itemHovered) context.renderTooltip(Minecraft.getInstance().font, opposingOfferedPokemon!!.heldItemNoCopy(), mouseX, mouseY)
}
renderSelection(matrices)
}
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
@@ -0,0 +1,363 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.integration.controlify
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonClient.ridingTurnInputHandler
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.config.CobblemonConfigScreen
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotScreen
import com.cobblemon.mod.common.client.gui.dialogue.DialogueScreen
import com.cobblemon.mod.common.client.gui.interact.battleRequest.BattleConfigureGUI
import com.cobblemon.mod.common.client.gui.interact.moveselect.MoveSelectGUI
import com.cobblemon.mod.common.client.gui.interact.partyselect.PartySelectGUI
import com.cobblemon.mod.common.client.gui.interact.wheel.InteractWheelGUI
import com.cobblemon.mod.common.client.gui.pc.PCGUI
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUI
import com.cobblemon.mod.common.client.gui.snapshots.SnapshotWarningScreen
import com.cobblemon.mod.common.client.gui.startselection.StarterSelectionScreen
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.trade.TradeGUI
import com.cobblemon.mod.common.client.integration.controlify.gui.CobblemonGUIProcessor
import com.cobblemon.mod.common.client.integration.controlify.gui.InteractWheelProcessor
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import com.cobblemon.mod.common.client.keybind.CobblemonKeyBinds
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import dev.isxander.controlify.Controlify
import dev.isxander.controlify.api.ControlifyApi
import dev.isxander.controlify.api.bind.ControlifyBindApi
import dev.isxander.controlify.api.bind.InputBindingBuilder
import dev.isxander.controlify.api.bind.InputBindingSupplier
import dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint
import dev.isxander.controlify.api.entrypoint.InitContext
import dev.isxander.controlify.api.entrypoint.PreInitContext
import dev.isxander.controlify.bindings.BindContext
import dev.isxander.controlify.bindings.ControlifyBindings
import dev.isxander.controlify.bindings.RadialIcons
import dev.isxander.controlify.screenop.ScreenProcessor
import dev.isxander.controlify.screenop.ScreenProcessorFactory
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.player.LocalPlayer
import net.minecraft.network.chat.Component
import net.minecraft.world.item.Items
import org.joml.Vector2d
import kotlin.jvm.optionals.getOrNull
import kotlin.reflect.KClass
import kotlin.reflect.full.primaryConstructor
class CobblemonControlifyEntrypoint : ControlifyEntrypoint {
override fun onControlifyInit(initContext: InitContext) {
ridingTurnInputHandler.put(
cobblemonResource("compat.controlify"),
({ localPlayer: LocalPlayer, xDelta: Double, yDelta: Double, deltaTime: Double ->
val vec = Vector2d(xDelta, yDelta)
val controller = initContext.controlify().currentController.getOrNull()
val vehicle = localPlayer.vehicle
if (vehicle is OrientationControllable && vehicle.orientationController.isActive() && initContext.controlify().currentInputMode().isController && controller != null){
val multiplier = deltaTime * 1500
val config = controller.input().getOrNull()?.config()?.config()
val vSensitivity = config?.vLookSensitivity ?: 0.9f
val hSensitivity = config?.hLookSensitivity ?: 1f
// Should never be null once bindings are set
var vAxis = (RIDING_DOWN!!.on(controller).analogueNow() - RIDING_UP!!.on(controller).analogueNow()).toDouble()
var hAxis = (RIDING_RIGHT!!.on(controller).analogueNow() - RIDING_LEFT!!.on(controller).analogueNow()).toDouble()
vAxis *= multiplier * vSensitivity
hAxis *= multiplier * hSensitivity
vec.x = hAxis
vec.y = vAxis
}
vec
})
)
CobblemonClient.ridingSprintHandler.put(
cobblemonResource("compat.controlify")
) {
initContext.controlify().currentController.getOrNull()?.let { ControlifyBindings.SPRINT.on(it).digitalNow() } == true
}
}
override fun onControlifyPreInit(initContext: PreInitContext) {
val bindings = ControlifyBindApi.get()
bindings.registerBindContext(RIDING_POKEMON)
bindings.registerBindContext(ORIENTATION_CONTROLLER_ACTIVE)
bindings.registerBindContext(HAS_POKEMON)
bindings.registerBindContext(INTERACT_WHEEL_SCREEN)
bindings.registerBindContext(COBBLEMON_CONTROLLER_SCREEN)
RIDING_UP =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("riding_up"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.riding_up"))
.allowedContexts(ORIENTATION_CONTROLLER_ACTIVE, BindContext.IN_GAME)
}
RIDING_DOWN =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("riding_down"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.riding_down"))
.allowedContexts(ORIENTATION_CONTROLLER_ACTIVE, BindContext.IN_GAME)
}
RIDING_LEFT =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("riding_left"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.riding_left"))
.allowedContexts(ORIENTATION_CONTROLLER_ACTIVE, BindContext.IN_GAME)
}
RIDING_RIGHT =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("riding_right"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.riding_right"))
.allowedContexts(ORIENTATION_CONTROLLER_ACTIVE, BindContext.IN_GAME)
}
HIDE_PARTY =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("hide_party"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("key.cobblemon.hideparty"))
.allowedContexts(BindContext.IN_GAME)
.radialCandidate(RadialIcons.getItem(Items.BARRIER))
.addKeyCorrelation(CobblemonKeyBinds.HIDE_PARTY)
.keyEmulation(CobblemonKeyBinds.HIDE_PARTY)
}
SUMMARY =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("summary"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("key.cobblemon.summary"))
.allowedContexts(BindContext.IN_GAME)
.radialCandidate(RadialIcons.getItem(CobblemonItems.GREAT_BALL))
.addKeyCorrelation(CobblemonKeyBinds.SUMMARY)
.keyEmulation(CobblemonKeyBinds.SUMMARY)
}
PARTY_OVERLAY_DOWN =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("party_overlay_down"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("key.cobblemon.downshiftparty"))
.allowedContexts(HAS_POKEMON, BindContext.IN_GAME)
.radialCandidate(RadialIcons.EMPTY)
.addKeyCorrelation(CobblemonKeyBinds.PARTY_OVERLAY_DOWN)
.keyEmulation(CobblemonKeyBinds.PARTY_OVERLAY_DOWN)
}
PARTY_OVERLAY_UP =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("party_overlay_up"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("key.cobblemon.upshiftparty"))
.allowedContexts(HAS_POKEMON, BindContext.IN_GAME)
.radialCandidate(RadialIcons.EMPTY)
.addKeyCorrelation(CobblemonKeyBinds.PARTY_OVERLAY_UP)
.keyEmulation(CobblemonKeyBinds.PARTY_OVERLAY_DOWN)
}
SEND_OUT_POKEMON =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("send_out_pokemon"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("key.cobblemon.throwpartypokemon"))
.allowedContexts(HAS_POKEMON, BindContext.IN_GAME)
.radialCandidate(RadialIcons.getItem(CobblemonItems.POKE_BALL))
.addKeyCorrelation(CobblemonKeyBinds.SEND_OUT_POKEMON)
.keyEmulation(CobblemonKeyBinds.SEND_OUT_POKEMON)
}
RIDING_FREELOOK =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("riding_freelook"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("key.cobblemon.ridingfreelook"))
.allowedContexts(RIDING_POKEMON, BindContext.IN_GAME)
.radialCandidate(RadialIcons.EMPTY)
.addKeyCorrelation(CobblemonKeyBinds.RIDING_FREELOOK)
.keyEmulation(CobblemonKeyBinds.RIDING_FREELOOK)
}
INTERACT_WHEEL_UP_AXIS =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("interact_wheel_up"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.interact_wheel_up"))
.allowedContexts(INTERACT_WHEEL_SCREEN, BindContext.IN_GAME)
}
INTERACT_WHEEL_DOWN_AXIS =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("interact_wheel_down"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.interact_wheel_down"))
.allowedContexts(INTERACT_WHEEL_SCREEN, BindContext.IN_GAME)
}
INTERACT_WHEEL_RIGHT_AXIS =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("interact_wheel_right"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.interact_wheel_right"))
.allowedContexts(INTERACT_WHEEL_SCREEN, BindContext.IN_GAME)
}
INTERACT_WHEEL_LEFT_AXIS =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("interact_wheel_left"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.interact_wheel_left"))
.allowedContexts(INTERACT_WHEEL_SCREEN, BindContext.IN_GAME)
}
GUI_SECONDARY_NEXT_TAB =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("gui_secondary_next_tab"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.gui_secondary_next_tab"))
.allowedContexts(COBBLEMON_CONTROLLER_SCREEN)
}
GUI_SECONDARY_PREV_TAB =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("gui_secondary_prev_tab"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.gui_secondary_prev_tab"))
.allowedContexts(COBBLEMON_CONTROLLER_SCREEN)
}
GUI_START =
bindings.registerBinding { builder: InputBindingBuilder ->
builder
.id(cobblemonResource("gui_start"))
.category(Component.translatable("controlify.category.cobblemon.cobblemon"))
.name(Component.translatable("controlify.bind.cobblemon.gui_start"))
.allowedContexts(COBBLEMON_CONTROLLER_SCREEN)
}
addProcessor(InteractWheelGUI::class, InteractWheelProcessor::class)
// This is the lazy way to do all of these (just using the vmouse), if someone with more time and dedication did this that would be better
addProcessor(DialogueScreen::class, CobblemonGUIProcessor::class)
// Below are implemented processors for our GUIs
addProcessor(Summary::class, CobblemonGUIProcessor::class)
addProcessor(BattleGUI::class, CobblemonGUIProcessor::class)
addProcessor(PokedexGUI::class, CobblemonGUIProcessor::class)
addProcessor(StarterSelectionScreen::class, CobblemonGUIProcessor::class)
addProcessor(TradeGUI::class, CobblemonGUIProcessor::class)
addProcessor(PartySelectGUI::class, CobblemonGUIProcessor::class)
addProcessor(MoveSelectGUI::class, CobblemonGUIProcessor::class)
addProcessor(PCGUI::class, CobblemonGUIProcessor::class)
addProcessor(BattleConfigureGUI::class, CobblemonGUIProcessor::class)
addProcessor(SnapshotWarningScreen::class, CobblemonGUIProcessor::class)
addProcessor(CobblemonConfigScreen::class, CobblemonGUIProcessor::class)
// This once don't need any
addProcessor(CookingPotScreen::class, CobblemonGUIProcessor::class)
}
override fun onControllersDiscovered(controlifyApi: ControlifyApi) {
}
// This is jank, but I did it after I got my wisdom teeth extracted so tbh I think I did good enough, but I won't be offended if someone changes it to something sane.
fun addProcessor(t: KClass<out Screen>, processorClazz: KClass<out ScreenProcessor<Screen>>) {
ScreenProcessorFactory.registerProvider(t.java) {
processorClazz.primaryConstructor?.call(it)
}
}
companion object {
var RIDING_UP: InputBindingSupplier? = null
var RIDING_DOWN: InputBindingSupplier? = null
var RIDING_RIGHT: InputBindingSupplier? = null
var RIDING_LEFT: InputBindingSupplier? = null
var HIDE_PARTY: InputBindingSupplier? = null
var SUMMARY: InputBindingSupplier? = null
var PARTY_OVERLAY_DOWN: InputBindingSupplier? = null
var PARTY_OVERLAY_UP: InputBindingSupplier? = null
var SEND_OUT_POKEMON: InputBindingSupplier? = null
var RIDING_FREELOOK: InputBindingSupplier? = null
var INTERACT_WHEEL_UP_AXIS: InputBindingSupplier? = null
var INTERACT_WHEEL_DOWN_AXIS: InputBindingSupplier? = null
var INTERACT_WHEEL_LEFT_AXIS: InputBindingSupplier? = null
var INTERACT_WHEEL_RIGHT_AXIS: InputBindingSupplier? = null
//gui special keybinds
var GUI_SECONDARY_NEXT_TAB: InputBindingSupplier? = null
var GUI_SECONDARY_PREV_TAB: InputBindingSupplier? = null
var GUI_START: InputBindingSupplier? = null
val RIDING_POKEMON: BindContext = BindContext(
cobblemonResource("riding"),
{ mc: Minecraft ->
mc.player?.vehicle is PokemonEntity
}
)
val ORIENTATION_CONTROLLER_ACTIVE: BindContext = BindContext(
cobblemonResource("orientation_controller_active"),
{ mc: Minecraft ->
val localPlayer = mc.player
if (localPlayer == null) return@BindContext false
val vehicle = localPlayer.vehicle
vehicle is OrientationControllable && vehicle.getOrientationController().isActive()
}
)
val HAS_POKEMON: BindContext = BindContext(
cobblemonResource("has_pokemon"),
{mc: Minecraft ->
!CobblemonClient.storage.party.isEmpty()
}
)
val INTERACT_WHEEL_SCREEN: BindContext = BindContext(
cobblemonResource("wheel_screen"),
{ it.screen is InteractWheelGUI}
)
val COBBLEMON_CONTROLLER_SCREEN: BindContext = BindContext(
cobblemonResource("cobblemon_controller_screen"),
{ mc: Minecraft ->
mc.screen is ControllerScreen
}
)
fun controllerActive(): Boolean {
return Controlify.instance().currentInputMode().isController
}
}
}
@@ -0,0 +1,247 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.integration.controlify.gui
import com.cobblemon.mod.common.client.integration.controlify.CobblemonControlifyEntrypoint
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerComponentManager.ButtonGuideType
import com.cobblemon.mod.common.client.integration.controlify.gui.component.ControllerScreen
import dev.isxander.controlify.Controlify
import dev.isxander.controlify.InputMode
import dev.isxander.controlify.bindings.ControlifyBindings
import dev.isxander.controlify.bindings.InputBindingSupplierImpl
import dev.isxander.controlify.controller.ControllerEntity
import dev.isxander.controlify.controller.input.GamepadInputs
import dev.isxander.controlify.font.BindingFontHelper
import dev.isxander.controlify.gui.guide.GuideRenderer
import dev.isxander.controlify.gui.guide.PrecomputedLines
import dev.isxander.controlify.screenop.ScreenProcessor
import dev.isxander.controlify.virtualmouse.VirtualMouseBehaviour
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.EditBox
import net.minecraft.client.gui.screens.Screen
import net.minecraft.network.chat.Component
open class CobblemonGUIProcessor(open val screen: Screen) : ScreenProcessor<Screen>(screen) {
companion object {
val typeToGlyth = mutableMapOf<ButtonGuideType, () -> Component>(
ButtonGuideType.PRESS to { ControlifyBindings.GUI_PRESS.inputGlyph() },
ButtonGuideType.BACK to { ControlifyBindings.GUI_BACK.inputGlyph() },
ButtonGuideType.SPECIAL_1 to { ControlifyBindings.GUI_ABSTRACT_ACTION_1.inputGlyph() },
ButtonGuideType.SPECIAL_2 to { ControlifyBindings.GUI_ABSTRACT_ACTION_2.inputGlyph() },
ButtonGuideType.TAB to { Component.empty().append(ControlifyBindings.GUI_PREV_TAB.inputGlyph()).append(ControlifyBindings.GUI_NEXT_TAB.inputGlyph()) },
ButtonGuideType.SECONDARY_TAB to { Component.empty().append(CobblemonControlifyEntrypoint.GUI_SECONDARY_PREV_TAB!!.inputGlyph()).append(CobblemonControlifyEntrypoint.GUI_SECONDARY_NEXT_TAB!!.inputGlyph()) },
ButtonGuideType.START to { CobblemonControlifyEntrypoint.GUI_START!!.inputGlyph() },
ButtonGuideType.JOYSTICK_HORIZONTAL to { Component.empty().append(ControlifyBindings.GUI_SECONDARY_NAVI_LEFT.inputGlyph()).append(ControlifyBindings.GUI_SECONDARY_NAVI_RIGHT.inputGlyph()) },
ButtonGuideType.JOYSTICK_VERTICAL to { Component.empty().append(ControlifyBindings.GUI_SECONDARY_NAVI_UP.inputGlyph()).append(ControlifyBindings.GUI_SECONDARY_NAVI_DOWN.inputGlyph()) }
)
}
var editBoxWidget: EditBox? = null
// Until someone with more dedication than me adds non-vmouse support, we will make heavy use of the vmouse so make that the defualt for us
override fun virtualMouseBehaviour(): VirtualMouseBehaviour {
if (screen is ControllerScreen) {
return VirtualMouseBehaviour.DISABLED
}
return VirtualMouseBehaviour.CURSOR_ONLY
}
override fun onWidgetRebuild() {
(screen as? ControllerScreen)?.getComponentManager()?.let {
it.active = Controlify.instance().let { it.currentInputMode().isController && !it.virtualMouseHandler().isVirtualMouseEnabled }
it.openKeyboard = { editBox ->
editBoxWidget = editBox
}
it.getVisual = { location ->
InputBindingSupplierImpl(location).inputGlyph()
}
}
}
override fun onInputModeChanged(mode: InputMode) {
(screen as? ControllerScreen)?.getComponentManager()?.let {
val active = mode.isController && !Controlify.instance().virtualMouseHandler().isVirtualMouseEnabled
it.active = active
}
}
override fun onVirtualMouseToggled(enabled: Boolean) {
(screen as? ControllerScreen)?.getComponentManager()?.let{
it.active = Controlify.instance().currentInputMode().isController && !enabled
}
}
override fun onControllerUpdate(controller: ControllerEntity) {
val componentManager = (screen as? ControllerScreen)?.getComponentManager()
if (componentManager == null) {
super.onControllerUpdate(controller)
return
}
//It was easier to fully handle this myself
Controlify.instance().virtualMouseHandler().handleControllerInput(controller)
val repeatAvailable = holdRepeatHelper.canNavigate()
if (!Controlify.instance().virtualMouseHandler().isVirtualMouseEnabled) {
//Handling Base
if (ControlifyBindings.GUI_PRESS.on(controller).justPressed()) {
componentManager.press()
}
if (ControlifyBindings.GUI_BACK.on(controller).justPressed()) {
componentManager.onBack.invoke()
}
//Handling Special
if (ControlifyBindings.GUI_ABSTRACT_ACTION_1.on(controller).justPressed()) {
componentManager.special1()
}
if (ControlifyBindings.GUI_ABSTRACT_ACTION_2.on(controller).justPressed()) {
componentManager.special2()
}
if (CobblemonControlifyEntrypoint.GUI_START!!.on(controller).justPressed()) {
componentManager.onStart.invoke()
}
//Handling RJoystick
val guiNaviRight = ControlifyBindings.GUI_SECONDARY_NAVI_RIGHT.on(controller)
val guiNaviLeft = ControlifyBindings.GUI_SECONDARY_NAVI_LEFT.on(controller)
val guiNaviUp = ControlifyBindings.GUI_SECONDARY_NAVI_UP.on(controller)
val guiNaviDown = ControlifyBindings.GUI_SECONDARY_NAVI_DOWN.on(controller)
if (guiNaviRight.digitalNow() && (!guiNaviRight.digitalPrev() || repeatAvailable)) {
holdRepeatHelper.reset()
componentManager.onRHorizontal.invoke(true)
} else if (guiNaviLeft.digitalNow() && (!guiNaviLeft.digitalPrev() || repeatAvailable)) {
holdRepeatHelper.reset()
componentManager.onRHorizontal.invoke(false)
}
if (guiNaviUp.digitalNow() && (!guiNaviUp.digitalPrev() || repeatAvailable)) {
holdRepeatHelper.reset()
componentManager.onRVertical.invoke(true)
} else if (guiNaviDown.digitalNow() && (!guiNaviDown.digitalPrev() || repeatAvailable)) {
holdRepeatHelper.reset()
componentManager.onRVertical.invoke(false)
}
componentManager.rHorizontalState = if (guiNaviRight.digitalNow()) 1 else if (guiNaviLeft.digitalNow()) -1 else 0
componentManager.rVerticalState = if (guiNaviUp.digitalNow()) 1 else if (guiNaviDown.digitalNow()) -1 else 0
//Handle Navigation
handleNavigation(controller, componentManager)
} else {
componentManager.rHorizontalState = 0
componentManager.rVerticalState = 0
handleScreenVMouse(controller, Controlify.instance().virtualMouseHandler())
}
//Main Tab
if (ControlifyBindings.GUI_NEXT_TAB.on(controller).digitalNow() && (repeatAvailable || !ControlifyBindings.GUI_NEXT_TAB.on(controller).digitalPrev())) {
holdRepeatHelper.reset()
componentManager.onTabNav.invoke(true)
} else if (ControlifyBindings.GUI_PREV_TAB.on(controller).digitalNow() && (repeatAvailable || !ControlifyBindings.GUI_PREV_TAB.on(controller).digitalPrev())) {
holdRepeatHelper.reset()
componentManager.onTabNav.invoke(false)
}
//Secondary Tab
if (CobblemonControlifyEntrypoint.GUI_SECONDARY_NEXT_TAB!!.on(controller).digitalNow() && (repeatAvailable || !CobblemonControlifyEntrypoint.GUI_SECONDARY_NEXT_TAB!!.on(controller).digitalPrev())) {
holdRepeatHelper.reset()
componentManager.onSecondaryTabNav.invoke(true)
} else if (CobblemonControlifyEntrypoint.GUI_SECONDARY_PREV_TAB!!.on(controller).digitalNow() && (repeatAvailable || !CobblemonControlifyEntrypoint.GUI_SECONDARY_PREV_TAB!!.on(controller).digitalPrev())) {
holdRepeatHelper.reset()
componentManager.onSecondaryTabNav.invoke(false)
}
if (editBoxWidget != null) {
tryOpenKeyboard(controller, editBoxWidget)
editBoxWidget = null
}
}
private fun handleNavigation(controller: ControllerEntity, componentManager: ControllerComponentManager) {
val repeatAvailable = holdRepeatHelper.canNavigate()
val input = controller.input().orElseThrow()
val state = input.stateNow()
val prevState = input.stateThen()
val guiNaviRight = ControlifyBindings.GUI_NAVI_RIGHT.on(controller)
val guiNaviLeft = ControlifyBindings.GUI_NAVI_LEFT.on(controller)
val guiNaviUp = ControlifyBindings.GUI_NAVI_UP.on(controller)
val guiNaviDown = ControlifyBindings.GUI_NAVI_DOWN.on(controller)
if (guiNaviRight.digitalNow() && (repeatAvailable || !guiNaviRight.digitalPrev())) {
componentManager.right()
holdRepeatHelper.reset()
} else if (guiNaviLeft.digitalNow() && (repeatAvailable || !guiNaviLeft.digitalPrev())) {
componentManager.left()
holdRepeatHelper.reset()
} else if (guiNaviUp.digitalNow() && (repeatAvailable || !guiNaviUp.digitalPrev())) {
componentManager.up()
holdRepeatHelper.reset()
} else if (guiNaviDown.digitalNow() && (repeatAvailable || !guiNaviDown.digitalPrev())) {
componentManager.down()
holdRepeatHelper.reset()
} else if (state.isButtonDown(GamepadInputs.DPAD_RIGHT_BUTTON) && (repeatAvailable || !prevState.isButtonDown(GamepadInputs.DPAD_RIGHT_BUTTON))) {
componentManager.right()
holdRepeatHelper.reset()
} else if (state.isButtonDown(GamepadInputs.DPAD_LEFT_BUTTON) && (repeatAvailable || !prevState.isButtonDown(GamepadInputs.DPAD_LEFT_BUTTON))) {
componentManager.left()
holdRepeatHelper.reset()
} else if (state.isButtonDown(GamepadInputs.DPAD_UP_BUTTON) && (repeatAvailable || !prevState.isButtonDown(GamepadInputs.DPAD_UP_BUTTON))) {
componentManager.up()
holdRepeatHelper.reset()
} else if (state.isButtonDown(GamepadInputs.DPAD_DOWN_BUTTON) && (repeatAvailable || !prevState.isButtonDown(GamepadInputs.DPAD_DOWN_BUTTON))) {
componentManager.down()
holdRepeatHelper.reset()
}
}
override fun render(controller: ControllerEntity, graphics: GuiGraphics, tickDelta: Float) {
if (!controller.genericConfig().config().showScreenGuides) return
val manager = (screen as? ControllerScreen)?.getComponentManager() ?: return
if (!manager.active) return
val minecraft = Minecraft.getInstance()
val guideList = manager.buttonGuides.filter { it.condition.invoke() }
val font = minecraft.font
val leftLinesBuilder = PrecomputedLines.Builder()
val rightLinesBuilder = PrecomputedLines.Builder()
guideList.forEach { guide ->
val builder = if (guide.rightAligned) rightLinesBuilder else leftLinesBuilder
val glyphAfter = font.isBidirectional.xor(guide.rightAligned)
val text = Component.empty()
.append(if (glyphAfter) guide.text else typeToGlyth[guide.type]!!.invoke())
.append(" ")
.append(if (!glyphAfter) guide.text else typeToGlyth[guide.type]!!.invoke())
val ruleNameWidth: Int = font.width(guide.text)
val width = font.width(text)
val glyphWidth = width - ruleNameWidth
val backgroundLeft = if (glyphAfter) 0 else glyphWidth
val backgroundRight = backgroundLeft + ruleNameWidth
val height = BindingFontHelper.getComponentHeight(font, typeToGlyth[guide.type]!!.invoke())
builder.addLine(text, width, height, backgroundLeft, backgroundRight)
}
val method = GuideRenderer::class.java.declaredMethods.first { it.name == "renderLines" } //We love Java Reflections
method.isAccessible = true
method.invoke(null,graphics, leftLinesBuilder.build(), font, minecraft.window.guiScaledWidth, minecraft.window.guiScaledHeight, false, false, true)
method.invoke(null,graphics, rightLinesBuilder.build(), font, minecraft.window.guiScaledWidth, minecraft.window.guiScaledHeight, false, true, true)
}
}
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.integration.controlify.gui
import com.cobblemon.mod.common.client.gui.interact.wheel.InteractWheelGUI
import com.cobblemon.mod.common.client.integration.controlify.CobblemonControlifyEntrypoint
import dev.isxander.controlify.bindings.ControlifyBindings
import dev.isxander.controlify.controller.ControllerEntity
import dev.isxander.controlify.controller.haptic.HapticEffects
import dev.isxander.controlify.screenop.ScreenControllerEventListener
import dev.isxander.controlify.screenop.ScreenProcessor
import dev.isxander.controlify.virtualmouse.VirtualMouseBehaviour
import net.minecraft.client.gui.screens.Screen
import net.minecraft.util.Mth
import kotlin.math.abs
// Lots of this is taken from the RadialMenu in Controlify to make this screen feel consistent with it
class InteractWheelProcessor(val screen: InteractWheelGUI) : ScreenProcessor<Screen>(screen), ScreenControllerEventListener {
private var selectedButton = -1
override fun onWidgetRebuild() {}
override fun virtualMouseBehaviour(): VirtualMouseBehaviour {
return VirtualMouseBehaviour.DISABLED
}
override fun onControllerUpdate(controller: ControllerEntity) {
if (ControlifyBindings.GUI_BACK.on(controller).justPressed()) {
screen.onClose()
return
}
// If not created from entrypoint then something is very wrong
val x = CobblemonControlifyEntrypoint.INTERACT_WHEEL_RIGHT_AXIS!!.on(controller).analogueNow() -
CobblemonControlifyEntrypoint.INTERACT_WHEEL_LEFT_AXIS!!.on(controller).analogueNow()
val y = CobblemonControlifyEntrypoint.INTERACT_WHEEL_DOWN_AXIS!!.on(controller).analogueNow() -
CobblemonControlifyEntrypoint.INTERACT_WHEEL_UP_AXIS!!.on(controller).analogueNow()
val threshold = controller.input().orElseThrow().config().config().buttonActivationThreshold
if (abs(x) >= threshold || abs(y) >= threshold) {
val angle = Mth.wrapDegrees(Mth.RAD_TO_DEG * Mth.atan2(y.toDouble(), x.toDouble()) - 90f) + 180f
val buttonCount = screen.buttons.size
val each = 360f / buttonCount
val newSelected = Mth.floor((angle + each / 2f) / each) % buttonCount
if (newSelected != selectedButton) {
selectedButton = newSelected
controller.hdHaptics().ifPresent { haptics -> haptics.playHaptic(HapticEffects.NAVIGATE) }
}
for (button in 0..<buttonCount) {
val selected = button == selectedButton
screen.buttons[button].isFocused = selected
}
} else if (selectedButton != -1) {
if (screen.buttons[selectedButton].isEnabled) {
screen.buttons[selectedButton].onPress()
}
}
}
}
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.integration.controlify.gui.component
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.components.EditBox
import net.minecraft.client.gui.components.events.GuiEventListener
import net.minecraft.client.gui.screens.Screen
import kotlin.reflect.typeOf
open class ControllerComponent {
var left: () -> ControllerComponent? = { null }
var right: () -> ControllerComponent? = { null }
var up: () -> ControllerComponent? = { null }
var down: () -> ControllerComponent? = { null }
var onPress: (() -> Unit)? = null
var onSpecial1: () -> Unit = {}
var onSpecial2: () -> Unit = {}
var onSelect: () -> Unit = {}
var onUnselect: () -> Unit = {}
var onFocus: () -> Unit = {}
var onUnfocus: () -> Unit = {}
var associatedGUIElement: GuiEventListener? = null
fun pairLeft(leftComponent: ControllerComponent?) {
left = { leftComponent }
leftComponent?.right = { this }
}
fun pairRight(rightComponent: ControllerComponent?) {
right = { rightComponent }
rightComponent?.left = { this }
}
fun pairUp(upComponent: ControllerComponent?) {
up = { upComponent }
upComponent?.down = { this }
}
fun pairDown(downComponent: ControllerComponent?) {
down = { downComponent }
downComponent?.up = { this }
}
}
@@ -0,0 +1,163 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.integration.controlify.gui.component
import com.cobblemon.mod.common.api.text.text
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.components.EditBox
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.SoundEvents
class ControllerComponentManager(val screen: Screen) {
var active: Boolean = false
get() = field
set(value) {
if (field != value) {
field = value
onActiveChange.invoke(value)
}
if (mainScreenManager()) {
if (!value) {
screen.focused = null
focused?.onUnfocus?.invoke()
} else {
screen.focused = focused?.associatedGUIElement
focused?.onFocus?.invoke()
}
}
}
var focused: ControllerComponent? = null
get() = field
set(value) {
currentSelectedComponent = (componentList.entries.firstOrNull { it.value == value }?.key ?: "")
field?.onUnselect?.invoke()
if (active) { field?.onUnfocus?.invoke() }
field = value
if (active) {
field?.associatedGUIElement.let { screen.focused = it }
field?.onFocus?.invoke()
}
field?.onSelect?.invoke()
}
val componentList: MutableMap<String, ControllerComponent> = mutableMapOf()
var currentSelectedComponent = ""
var onTabNav: (Boolean) -> Unit = {}
var onSecondaryTabNav: (Boolean) -> Unit = {}
var onBack: () -> Unit = {
screen.onClose()
}
var onStart: () -> Unit = {}
var onActiveChange: (Boolean) -> Unit = {}
var onNoneSelected: () -> Boolean = { false }
var onRHorizontal: (Boolean) -> Unit = {}
var onRVertical: (Boolean) -> Unit = {}
var rHorizontalState: Int = 0
var rVerticalState: Int = 0
var openKeyboard: (EditBox) -> Unit = {}
val buttonGuides = mutableListOf<ButtonGuide>()
var getVisual: (ResourceLocation) -> Component = { "".text() }
var onPress: (() -> Unit)? = null
var onSpecial1: () -> Unit = { focused?.onSpecial1?.invoke() }
var onSpecial2: () -> Unit = { focused?.onSpecial2?.invoke() }
fun left(): Boolean {
if (focused == null && onNoneSelected.invoke()) return false
focused?.left?.invoke()?.let {
focused = it
return false
}
return true
}
fun right(): Boolean {
if (focused == null && onNoneSelected.invoke()) return false
focused?.right?.invoke()?.let {
focused = it
return false
}
return true
}
fun up(): Boolean {
if (focused == null && onNoneSelected.invoke()) return false
focused?.up?.invoke()?.let {
focused = it
return false
}
return true
}
fun down(): Boolean {
if (focused == null && onNoneSelected.invoke()) return false
focused?.down?.invoke()?.let {
focused = it
return false
}
return true
}
fun press() {
focused?.let {
if (onPress != null) {
onPress!!.invoke()
} else {
if (it.onPress != null) {
it.onPress!!.invoke()
} else {
(it.associatedGUIElement as? Button)?.onPress()
(it.associatedGUIElement as? Button)?.playDownSound(Minecraft.getInstance().soundManager)
if (it.associatedGUIElement is EditBox) {
openKeyboard.invoke(it.associatedGUIElement as EditBox)
Minecraft.getInstance().soundManager.play(
SimpleSoundInstance.forUI(
SoundEvents.UI_BUTTON_CLICK,
1.0F
)
)
}
}
}
}
}
fun special1() = onSpecial1.invoke()
fun special2() = onSpecial2.invoke()
private fun mainScreenManager(): Boolean {
return screen is ControllerScreen && screen.getComponentManager() == this
}
data class ButtonGuide(
val type: ButtonGuideType,
val condition: () -> Boolean,
val text: Component,
val rightAligned: Boolean = false
)
enum class ButtonGuideType {
PRESS,
BACK,
SPECIAL_1,
SPECIAL_2,
TAB,
SECONDARY_TAB,
START,
JOYSTICK_HORIZONTAL,
JOYSTICK_VERTICAL
}
}
@@ -0,0 +1,97 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.integration.controlify.gui.component
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.gui.components.AbstractWidget
import org.joml.Vector4i
interface ControllerScreen {
companion object {
private val selectionTexture = cobblemonResource("textures/gui/common/selection_box.png")
const val BASE_SIZE = 3
const val SHADOW_OFFSET = 1
const val TOTAL_SIZE = BASE_SIZE + SHADOW_OFFSET
}
fun getComponentManager(): ControllerComponentManager
fun getSelectionPosAndSize(): Vector4i? {
val manager = getComponentManager()
val focusedWidget = manager.screen.focused as? AbstractWidget ?: return null
return Vector4i(focusedWidget.x - 2, focusedWidget.y - 2, focusedWidget.width + 4, focusedWidget.height + 4)
}
fun renderSelection(matrices: PoseStack) {
if (!getComponentManager().active) return
val positioningData = getSelectionPosAndSize() ?: return
val posX = positioningData.x
val posY = positioningData.y
val width = positioningData.z
val height = positioningData.w
blitk( //NW
matrixStack = matrices,
texture = selectionTexture,
x = posX,
y = posY,
width = TOTAL_SIZE,
height = TOTAL_SIZE,
uOffset = 0,
vOffset = 0,
textureWidth = TOTAL_SIZE * 2,
textureHeight = TOTAL_SIZE * 2
)
blitk( //NE
matrixStack = matrices,
texture = selectionTexture,
x = posX + width - BASE_SIZE,
y = posY,
width = TOTAL_SIZE,
height = TOTAL_SIZE,
uOffset = 4,
vOffset = 0,
textureWidth = TOTAL_SIZE * 2,
textureHeight = TOTAL_SIZE * 2
)
blitk( //SW
matrixStack = matrices,
texture = selectionTexture,
x = posX,
y = posY + height - BASE_SIZE,
width = TOTAL_SIZE,
height = TOTAL_SIZE,
uOffset = 0,
vOffset = 4,
textureWidth = TOTAL_SIZE * 2,
textureHeight = TOTAL_SIZE * 2
)
blitk( //SE
matrixStack = matrices,
texture = selectionTexture,
x = posX + width - BASE_SIZE,
y = posY + height - BASE_SIZE,
width = TOTAL_SIZE,
height = TOTAL_SIZE,
uOffset = 4,
vOffset = 4,
textureWidth = TOTAL_SIZE * 2,
textureHeight = TOTAL_SIZE * 2
)
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.integration.controlify.gui.component
class SubManagerComponent: ControllerComponent() {
var subManager: () -> ControllerComponentManager? = { null }
init {
left = { subManager.invoke()?.left(); null }
right = { subManager.invoke()?.right(); null }
up = { subManager.invoke()?.up(); null }
down = { subManager.invoke()?.down(); null }
onPress = { subManager.invoke()?.press() }
onSpecial1 = { subManager.invoke()?.special1() }
onSpecial2 = { subManager.invoke()?.special2() }
onFocus = {
subManager.invoke()?.active = true
subManager.invoke()?.let { it.screen.focused = it.focused?.associatedGUIElement }
subManager.invoke()?.focused?.onFocus?.invoke()
}
onUnfocus = {
subManager.invoke()?.active = false
subManager.invoke()?.focused?.onUnfocus?.invoke()
}
}
}
@@ -21,6 +21,7 @@ import kotlin.math.floor
import kotlin.math.min
import kotlin.random.Random
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.components.AbstractSelectionList
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.model.ModelResourceLocation
import net.minecraft.core.BlockPos
@@ -9473,5 +9473,47 @@
"stat.cobblemon.reel_ins": "PokéRod Catches",
"stat.cobblemon.riding_land": "Distance by Pokémon on Land",
"stat.cobblemon.riding_air": "Distance by Pokémon in Air",
"stat.cobblemon.riding_liquid": "Distance by Pokémon in Liquid"
"stat.cobblemon.riding_liquid": "Distance by Pokémon in Liquid",
"controlify.category.cobblemon.cobblemon": "Cobblemon",
"controlify.bind.cobblemon.riding_up": "Look Up",
"controlify.bind.cobblemon.riding_down": "Look Down",
"controlify.bind.cobblemon.riding_left": "Look Left",
"controlify.bind.cobblemon.riding_right": "Look Right",
"controlify.bind.cobblemon.interact_wheel_up": "Interact Wheel Up",
"controlify.bind.cobblemon.interact_wheel_down": "Interact Wheel Down",
"controlify.bind.cobblemon.interact_wheel_left": "Interact Wheel Left",
"controlify.bind.cobblemon.interact_wheel_right": "Interact Wheel Right",
"controlify.bind.cobblemon.gui_secondary_next_tab": "GUI Secondary Next Tab",
"controlify.bind.cobblemon.gui_secondary_prev_tab": "GUI Secondary Previous Tab",
"controlify.bind.cobblemon.gui_start": "GUI Abstract Button 4",
"cobblemon.controller.quick_search": "Quick Search",
"cobblemon.controller.toggle_held_item_type": "Toggle Held Type",
"cobblemon.controller.dex.cycle_region": "Cycle Region",
"cobblemon.controller.dex.cycle_form": "Cycle Form",
"cobblemon.controller.dex.toggle_shiny": "Toggle Shiny",
"cobblemon.controller.dex.toggle_gender": "Toggle Gender",
"cobblemon.controller.dex.cycle_animation": "Cycle Animation",
"cobblemon.controller.dex.play_cry": "Play Cry",
"cobblemon.controller.dex.cycle_ability": "Cycle Ability",
"cobblemon.controller.dex.cycle_stats": "Cycle Stats",
"cobblemon.controller.dex.cycle_ride_style": "Cycle Ride Style",
"cobblemon.controller.dex.cycle_search_by": "Cycle Search By",
"cobblemon.controller.summary.adjust_move": "Adjust Move",
"cobblemon.controller.summary.change_move": "Change Move",
"cobblemon.controller.summary.clear_mark": "Clear Mark",
"cobblemon.controller.summary.swap_position": "Swap Position",
"cobblemon.controller.summary.quick_switch": "Cycle Pokemon",
"cobblemon.controller.battle.swap_position": "Swap Position",
"cobblemon.controller.pc.switch_box": "Cycle Box",
"cobblemon.controller.pc.switch_stats": "Cycle Stats",
"cobblemon.controller.pc.toggle_defence": "Toggle Defence",
"cobblemon.controller.pc.box_settings": "Box Settings",
"cobblemon.controller.dialogue.skip": "Skip Dialogue"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

@@ -0,0 +1,40 @@
{
"defaults": {
"cobblemon:riding_up": {
"axis": "controlify:axis/right_stick_up"
},
"cobblemon:riding_down": {
"axis": "controlify:axis/right_stick_down"
},
"cobblemon:riding_left": {
"axis": "controlify:axis/right_stick_left"
},
"cobblemon:riding_right": {
"axis": "controlify:axis/right_stick_right"
},
"cobblemon:interact_wheel_up": {
"axis": "controlify:axis/right_stick_up"
},
"cobblemon:interact_wheel_down": {
"axis": "controlify:axis/right_stick_down"
},
"cobblemon:interact_wheel_left": {
"axis": "controlify:axis/right_stick_left"
},
"cobblemon:interact_wheel_right": {
"axis": "controlify:axis/right_stick_right"
},
"cobblemon:send_out_pokemon": {
},
"cobblemon:gui_secondary_next_tab": {
"axis": "controlify:axis/right_trigger"
},
"cobblemon:gui_secondary_prev_tab": {
"axis": "controlify:axis/left_trigger"
},
"cobblemon:gui_start": {
"axis": "controlify:button/start"
}
}
}
@@ -155,4 +155,6 @@ transitive-accessible method net/minecraft/world/food/FoodData add (IF)V
transitive-accessible method net/minecraft/client/model/HumanoidModel setupAttackAnimation (Lnet/minecraft/world/entity/LivingEntity;F)V
transitive-accessible method net/minecraft/client/model/HumanoidModel poseRightArm (Lnet/minecraft/world/entity/LivingEntity;)V
transitive-accessible method net/minecraft/client/model/HumanoidModel poseLeftArm (Lnet/minecraft/world/entity/LivingEntity;)V
transitive-accessible method net/minecraft/client/model/HumanoidModel poseLeftArm (Lnet/minecraft/world/entity/LivingEntity;)V
accessible method net/minecraft/client/gui/components/AbstractSelectionList scroll (I)V
@@ -109,6 +109,7 @@
"invoker.PressurePlateBlockInvoker",
"invoker.StairsBlockInvoker",
"invoker.TrapdoorBlockInvoker",
"pokeball.ThrownEntityMixin"
"pokeball.ThrownEntityMixin",
"integration.controlify.InGameInputHandlerMixin"
]
}
@@ -33,6 +33,9 @@
],
"modmenu": [
"com.cobblemon.mod.fabric.client.integration.modmenu.CobblemonModMenu"
],
"controlify": [
"com.cobblemon.mod.common.client.integration.controlify.CobblemonControlifyEntrypoint"
]
},
"mixins": [
@@ -0,0 +1 @@
com.cobblemon.mod.common.client.integration.controlify.CobblemonControlifyEntrypoint