Compare commits

..
249 changed files with 8314 additions and 5955 deletions
-72
View File
@@ -1,76 +1,4 @@
# 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,10 +32,6 @@ 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()
}
@@ -66,11 +62,6 @@ 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,14 +21,12 @@ 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;
@@ -151,12 +149,6 @@ 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();
@@ -1,53 +0,0 @@
/*
* 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);
}
}
@@ -35,6 +35,9 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
@JvmField
val DISC_SHELF: BlockEntityType<DiscShelfBlockEntity> = this.create("disc_shelf", BlockEntityType.Builder.of(::DiscShelfBlockEntity, CobblemonBlocks.DISC_SHELF).build(null))
@JvmField
val SILK_THREAD: BlockEntityType<SilkThreadBlockEntity> = this.create("silk_thread", BlockEntityType.Builder.of(::SilkThreadBlockEntity, CobblemonBlocks.SILK_THREAD).build(null))
@JvmField
val BERRY = this.create("berry", BlockEntityType.Builder.of(::BerryBlockEntity, *CobblemonBlocks.berries().values.toTypedArray()).build(null))
@@ -248,6 +248,8 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val BIG_ROOT = this.create("big_root", BigRootBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.PLANT).noCollission().randomTicks().instabreak().sound(CobblemonSounds.BIG_ROOT_SOUNDS)))
@JvmField
val REVIVAL_HERB = this.create("revival_herb", RevivalHerbBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).mapColor(MapColor.PLANT).ignitedByLava().noCollission().instabreak().sound(CobblemonSounds.REVIVAL_HERB_SOUNDS)))
@JvmField
val SILK_THREAD = this.create("silk_thread", SilkThreadBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.WOOL).noCollission().instabreak().sound(SoundType.WOOL)))
@JvmField
val POKE_CAKE = this.create("poke_cake", PokeSnackBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY).lightLevel { if (it.getValue(BlockStateProperties.LIT)) 3 else 0 }, false))
@@ -957,7 +959,8 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
Triple(SACCHARINE_FENCE, 5, 20),
Triple(SACCHARINE_FENCE_GATE, 5, 20),
Triple(SACCHARINE_SLAB, 5, 20),
Triple(SACCHARINE_STAIRS, 5, 20)
Triple(SACCHARINE_STAIRS, 5, 20),
Triple(SILK_THREAD, 30, 60)
).onEach{ data -> setFlammable(data.first, data.second, data.third) }
}
@@ -49,6 +49,8 @@ object CobblemonMemories {
@JvmField
val PATH_COOLDOWN = register<Boolean>("path_cooldown")
@JvmField
val POKEMON_SLEEP_SEARCH_FAILED = register<Boolean>("pokemon_sleep_search_failed")
@JvmField
val TARGETED_BATTLE_POKEMON = register<UUID>("targeted_battle_pokemon")
@JvmField
val NEAREST_VISIBLE_ATTACKER = register<LivingEntity>("nearest_visible_attacker")
@@ -106,4 +108,4 @@ object CobblemonMemories {
memories[id] = memoryModule
return memoryModule
}
}
}
@@ -45,6 +45,7 @@ class FindRestingPlaceTaskConfig : SingleTaskConfig {
behaviourConfigurationContext.addMemories(
CobblemonMemories.PATH_COOLDOWN,
CobblemonMemories.POKEMON_DROWSY,
CobblemonMemories.POKEMON_SLEEPING,
MemoryModuleType.WALK_TARGET
)
behaviourConfigurationContext.addSensors(CobblemonSensors.POKEMON_DROWSY)
@@ -175,4 +175,4 @@ interface TaskConfig {
fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable>
/** Given the entity in construction, returns a list of tasks. */
fun createTasks(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<BehaviorControl<in LivingEntity>>
}
}
@@ -28,6 +28,7 @@ import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cloneFrom
import com.cobblemon.mod.common.util.getStringOrNull
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.core.Direction
object PokemonEntityMoLangFunctions : AbstractMoLangFunctionHolder<PokemonEntity>() {
override fun PokemonEntity.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
@@ -37,6 +38,10 @@ object PokemonEntityMoLangFunctions : AbstractMoLangFunctionHolder<PokemonEntity
map["in_battle"] = { DoubleValue(pokemonEntity.isBattling) }
map["is_moving"] = { DoubleValue((pokemonEntity.moveControl as? PokemonMoveControl)?.hasWanted() == true) }
map["is_flying"] = { DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.FLYING)) }
map["cling_face"] = {
val face = pokemonEntity.entityData.get(PokemonEntity.CLINGING_FACE)
StringValue(if (face == Direction.DOWN) "none" else face.getName())
}
map["get_riding_state"] = { params ->
val name = params.getStringOrNull(0)
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
@@ -145,4 +150,4 @@ object PokemonEntityMoLangFunctions : AbstractMoLangFunctionHolder<PokemonEntity
}
return map
}
}
}
@@ -22,7 +22,6 @@ 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
@@ -145,12 +144,11 @@ 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() && forward && !state.sprinting.get() && state.sprintTickTimer.get() == 0) {
if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && !state.sprinting.get() && state.sprintTickTimer.get() == 0) {
state.sprintTickTimer.set(sprintTimerTickLength)
} else if (!state.forwardHeldLastTick.get() && forward && state.sprintTickTimer.get() != 0) { // If has double tapped
} else if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && 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)
@@ -158,9 +156,9 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
state.sprintTickTimer.set(0)
}
val tryingToSprint = hasDoubleTapped || CobblemonClient.ridingSprintHandler.any { it.value.invoke() }
val tryingToSprint = (Minecraft.getInstance().options.keySprint.isDown() && Minecraft.getInstance().options.keyUp.isDown()) || hasDoubleTapped
if (state.stamina.get() <= 0.0f || !forward) {
if (state.stamina.get() <= 0.0f || !Minecraft.getInstance().options.keyUp.isDown()) {
// If stamina runs out or the player is not holding forward then stop sprinting
state.sprinting.set(false)
if (state.stamina.get() <= 0.0f) {
@@ -174,8 +172,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
state.sprinting.set(true)
}
// Take note of whether forward was held last tick
state.forwardHeldLastTick.set(forward)
// Take not of whether forward was held last tick
state.forwardHeldLastTick.set(Minecraft.getInstance().options.keyUp.isDown())
}
fun tickStamina(
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.entity.Despawner
import com.cobblemon.mod.common.api.spawning.condition.AreaSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.BasicSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.ClingingSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.FishingSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.GroundedSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SeafloorSpawningCondition
@@ -26,14 +27,18 @@ import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePositionResolver
import com.cobblemon.mod.common.api.spawning.position.ClingingSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.FishingSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.GroundedSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.HangingSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.LavafloorSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SeafloorSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SubmergedSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SurfaceSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.calculators.ClingingSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.GroundedSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.HangingSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.LavafloorSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SeafloorSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator
@@ -97,6 +102,7 @@ object BestSpawner {
SpawningCondition.register(AreaSpawningCondition.NAME, AreaSpawningCondition::class.java)
SpawningCondition.register(SubmergedSpawningCondition.NAME, SubmergedSpawningCondition::class.java)
SpawningCondition.register(GroundedSpawningCondition.NAME, GroundedSpawningCondition::class.java)
SpawningCondition.register(ClingingSpawningCondition.NAME, ClingingSpawningCondition::class.java)
SpawningCondition.register(SurfaceSpawningCondition.NAME, SurfaceSpawningCondition::class.java)
SpawningCondition.register(SeafloorSpawningCondition.NAME, SeafloorSpawningCondition::class.java)
SpawningCondition.register(FishingSpawningCondition.NAME, FishingSpawningCondition::class.java)
@@ -104,12 +110,16 @@ object BestSpawner {
LOGGER.info("Loaded ${SpawningCondition.conditionTypes.size} spawning condition types.")
SpawnablePositionCalculator.register(GroundedSpawnablePositionCalculator)
SpawnablePositionCalculator.register(HangingSpawnablePositionCalculator)
SpawnablePositionCalculator.register(ClingingSpawnablePositionCalculator)
SpawnablePositionCalculator.register(SeafloorSpawnablePositionCalculator)
SpawnablePositionCalculator.register(LavafloorSpawnablePositionCalculator)
SpawnablePositionCalculator.register(SubmergedSpawnablePositionCalculator)
SpawnablePositionCalculator.register(SurfaceSpawnablePositionCalculator)
SpawnablePosition.register(name = "grounded", clazz = GroundedSpawnablePosition::class.java, defaultCondition = GroundedSpawningCondition.NAME)
SpawnablePosition.register(name = "hanging", clazz = HangingSpawnablePosition::class.java, defaultCondition = AreaSpawningCondition.NAME)
SpawnablePosition.register(name = "clinging", clazz = ClingingSpawnablePosition::class.java, defaultCondition = ClingingSpawningCondition.NAME)
SpawnablePosition.register(name = "seafloor", clazz = SeafloorSpawnablePosition::class.java, defaultCondition = SeafloorSpawningCondition.NAME)
SpawnablePosition.register(name = "lavafloor", clazz = LavafloorSpawnablePosition::class.java, defaultCondition = GroundedSpawningCondition.NAME)
SpawnablePosition.register(name = "submerged", clazz = SubmergedSpawnablePosition::class.java, defaultCondition = SubmergedSpawningCondition.NAME)
@@ -143,4 +153,4 @@ object BestSpawner {
CobblemonSpawnPools.onServerLoad(server)
fishingSpawner = FishingSpawnerFactory.createSharedSpawner()
}
}
}
@@ -65,4 +65,4 @@ class AreaSpawningCondition : AreaTypeSpawningCondition<AreaSpawnablePosition>()
companion object {
const val NAME = "area"
}
}
}
@@ -0,0 +1,64 @@
/*
* 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.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.position.ClingingSpawnablePosition
import com.cobblemon.mod.common.util.Merger
import net.minecraft.core.Direction
import net.minecraft.world.level.block.Block
/**
* Base type for a spawning condition that applies to some kind of [ClingingSpawnablePosition].
*
* The [attachedTo] list restricts which blocks the Pokemon may attach to, and [faces] restricts
* which faces of those blocks are usable: "floor", "sides", or "ceiling".
*/
abstract class ClingingTypeSpawningCondition<T : ClingingSpawnablePosition> : AreaTypeSpawningCondition<T>() {
var attachedTo: MutableList<RegistryLikeCondition<Block>>? = null
var faces: MutableList<String>? = null
override fun fits(spawnablePosition: T): Boolean {
if (!super.fits(spawnablePosition)) return false
return spawnablePosition.attachments.keys.any { allowsAttachment(spawnablePosition, it) }
}
fun allowsAttachment(spawnablePosition: ClingingSpawnablePosition, direction: Direction): Boolean {
if (faces != null && faces!!.none { faceMatches(it, direction) }) return false
if (attachedTo != null && attachedTo!!.none { it.fits(spawnablePosition.attachmentHolder(direction)) }) return false
return true
}
private fun faceMatches(face: String, direction: Direction): Boolean = when (face.lowercase()) {
"sides" -> direction.axis.isHorizontal
"floor", "top" -> direction == Direction.DOWN
"ceiling", "bottom" -> direction == Direction.UP
else -> false
}
override fun copyFrom(other: SpawningCondition<*>, merger: Merger) {
super.copyFrom(other, merger)
if (other is ClingingTypeSpawningCondition) {
attachedTo = merger.merge(attachedTo, other.attachedTo)?.toMutableList()
faces = merger.merge(faces, other.faces)?.toMutableList()
}
}
override fun isValid(): Boolean {
val containsNullValues = attachedTo != null && attachedTo!!.any { it == null }
return super.isValid() && !containsNullValues
}
}
class ClingingSpawningCondition : ClingingTypeSpawningCondition<ClingingSpawnablePosition>() {
override fun spawnablePositionClass() = ClingingSpawnablePosition::class.java
companion object {
const val NAME = "clinging"
}
}
@@ -85,4 +85,4 @@ open class AreaSpawnablePosition(
return true
}
}
}
@@ -8,13 +8,22 @@
package com.cobblemon.mod.common.api.spawning.position
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.SpawningZone
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.condition.ClingingTypeSpawningCondition
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.block.SilkThreadBlock
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockRegistry
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.Holder
import net.minecraft.world.entity.Entity
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.EmptyBlockGetter
import net.minecraft.tags.FluidTags
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
@@ -61,6 +70,179 @@ open class GroundedSpawnablePosition(
zone: SpawningZone
) : FlooredSpawnablePosition(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, zone)
/**
* A spawnable position for Pokemon that hang beneath leaves from a column of [SilkThreadBlock]s.
*/
open class HangingSpawnablePosition(
cause: SpawnCause,
world: ServerLevel,
position: BlockPos,
light: Int,
skyLight: Int,
canSeeSky: Boolean,
influences: MutableList<SpawningInfluence>,
height: Int,
nearbyBlocks: List<BlockState>,
zone: SpawningZone
) : AreaSpawnablePosition(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, zone) {
override fun postFilter(detail: SpawnDetail): Boolean {
val sizeX = detail.width.takeIf { it > 0 } ?: 1
val sizeY = detail.height.takeIf { it > 0 } ?: 1
if (sizeX <= 1 && sizeY <= 1) return true
val monPos = position.above()
val anchorOffset = (1..SilkThreadBlock.MAX_LENGTH).firstOrNull { offset ->
SilkThreadBlock.isValidAnchor(zone.getBlockState(monPos.above(offset)))
} ?: return false
val anchorY = monPos.y + anchorOffset
val radius = sizeX / 2 + 1
val mutable = BlockPos.MutableBlockPos()
for (x in (position.x - radius)..(position.x + radius)) {
for (z in (position.z - radius)..(position.z + radius)) {
if (!SilkThreadBlock.isValidAnchor(zone.getBlockState(mutable.set(x, anchorY, z)))) {
return false
}
for (y in (anchorY - sizeY) until anchorY) {
if (zone.getBlockState(mutable.set(x, y, z)).isSolid) {
return false
}
}
}
}
return true
}
override fun afterSpawn(action: SpawnAction<*>, entity: Entity) {
super.afterSpawn(action, entity)
if (entity !is PokemonEntity || !entity.pokemon.isWild()) return
if (entity.behaviour.hanging.usesThread) {
if (placeThreadColumn(entity.blockPosition())) {
entity.startHangingFromThread()
}
} else {
snapToAnchor(entity)
}
}
private fun snapToAnchor(entity: PokemonEntity) {
val monPos = entity.blockPosition()
val anchorOffset = (1..SilkThreadBlock.MAX_LENGTH).firstOrNull { offset ->
SilkThreadBlock.isValidAnchor(world.getBlockState(monPos.above(offset)))
} ?: return
val anchor = monPos.above(anchorOffset)
entity.setPos(monPos.x + 0.5, anchor.y - entity.bbHeight + entity.behaviour.hanging.yOffset, monPos.z + 0.5)
entity.startHangingFromThread()
}
// we need to place the super special thread block for our hanging boys for **immersion**
private fun placeThreadColumn(monPos: BlockPos): Boolean {
val anchorOffset = (1..SilkThreadBlock.MAX_LENGTH).firstOrNull { offset ->
SilkThreadBlock.isValidAnchor(world.getBlockState(monPos.above(offset)))
} ?: return false
for (offset in (anchorOffset - 1) downTo 0) {
val pos = monPos.above(offset)
if (world.getBlockState(pos).isAir) {
world.setBlockAndUpdate(pos, CobblemonBlocks.SILK_THREAD.defaultBlockState())
}
}
return world.getBlockState(monPos).`is`(CobblemonBlocks.SILK_THREAD)
}
}
/**
* A spawnable position for Pokemon that cling to a face of an adjacent block without gravity.
* The [attachments] map holds every neighbouring block, keyed by the direction from the
* Pokemon towards that block. This can be used for us making it so cocoon mons spawn clung
* to surfaces and maybe allow for some mons like monkeys or spiders / worms to spawn on the
* sides of logs or caves
*/
open class ClingingSpawnablePosition(
cause: SpawnCause,
world: ServerLevel,
position: BlockPos,
light: Int,
skyLight: Int,
canSeeSky: Boolean,
influences: MutableList<SpawningInfluence>,
height: Int,
nearbyBlocks: List<BlockState>,
zone: SpawningZone,
val attachments: Map<Direction, BlockState>
) : AreaSpawnablePosition(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, zone) {
fun attachmentHolder(direction: Direction): Holder<Block> =
world.blockRegistry.wrapAsHolder(attachments.getValue(direction).block)
override fun postFilter(detail: SpawnDetail): Boolean {
val sizeX = detail.width.takeIf { it > 0 } ?: 1
val sizeY = detail.height.takeIf { it > 0 } ?: 1
if (sizeX <= 1 && sizeY <= 1) return true
return attachments.keys.any { hasFootprintSpace(it, sizeX, sizeY) }
}
// attempt at trying to make sure it has space for the mon
private fun hasFootprintSpace(face: Direction, sizeX: Int, sizeY: Int): Boolean {
val monPos = position.above()
val mutable = BlockPos.MutableBlockPos()
if (face == Direction.UP) {
val radius = sizeX / 2 + 1
val ceilingY = monPos.y + 1
for (x in (monPos.x - radius)..(monPos.x + radius)) {
for (z in (monPos.z - radius)..(monPos.z + radius)) {
val ceilingState = zone.getBlockState(mutable.set(x, ceilingY, z))
if (!ceilingState.isFaceSturdy(EmptyBlockGetter.INSTANCE, BlockPos.ZERO, Direction.DOWN)) {
return false
}
for (y in (ceilingY - sizeY) until ceilingY) {
if (zone.getBlockState(mutable.set(x, y, z)).isSolid) {
return false
}
}
}
}
return true
}
if (!face.axis.isHorizontal) return false
val along = face.clockWise
val radius = sizeX / 2
for (a in -radius..radius) {
for (up in 0 until sizeX) {
val wallPos = monPos.relative(face).relative(along, a).above(up)
if (!zone.getBlockState(mutable.set(wallPos)).isFaceSturdy(EmptyBlockGetter.INSTANCE, BlockPos.ZERO, face.opposite)) {
return false
}
for (depth in 0 until sizeY) {
val bodyPos = wallPos.relative(face.opposite, depth + 1)
if (zone.getBlockState(mutable.set(bodyPos)).isSolid) {
return false
}
}
}
}
return true
}
override fun afterSpawn(action: SpawnAction<*>, entity: Entity) {
super.afterSpawn(action, entity)
if (entity !is PokemonEntity || !entity.pokemon.isWild()) return
val condition = action.detail.conditions.filterIsInstance<ClingingTypeSpawningCondition<*>>().firstOrNull()
val face = attachments.keys
.filter { condition == null || condition.allowsAttachment(this, it) }
.filter { direction ->
val supportPos = entity.blockPosition().relative(direction)
val state = world.getBlockState(supportPos)
state.isFaceSturdy(world, supportPos, direction.opposite) && entity.canClingTo(direction, state)
}
.randomOrNull()
if (face == null) {
entity.discard()
return
}
entity.startClinging(face)
}
}
/**
* A spawnable position that occurs at the bottom of a body of water.
*
@@ -14,6 +14,10 @@ import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePosit
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator.Companion.isLavaCondition
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator.Companion.isSolidCondition
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator.Companion.isWaterCondition
import com.cobblemon.mod.common.block.SilkThreadBlock
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.level.EmptyBlockGetter
import net.minecraft.world.level.block.state.BlockState
/**
@@ -65,6 +69,84 @@ object GroundedSpawnablePositionCalculator : FlooredSpawnablePositionCalculator<
}
}
/**
* The spawnable position calculator used for [HangingSpawnablePosition]s. Requires an air pocket
* beneath leaves, with at most [SilkThreadBlock.MAX_LENGTH] air blocks of thread gap between them.
* SilkThreadBlock currently only accepts leaves.... but maybe if we find other mons that hang from
* string we can expand it to a tag json (might maybe it should be anyways....) or expand it to a
* system where things can hang without string and use this same method (like bats maybe)
*/
object HangingSpawnablePositionCalculator : AreaSpawnablePositionCalculator<HangingSpawnablePosition> {
override val name = "hanging"
override fun fits(input: AreaSpawningInput): Boolean {
if (!isAirCondition(input.zone.getBlockState(input.position))) return false
if (!isAirCondition(input.zone.getBlockState(input.position.above()))) return false
for (offset in 2..SilkThreadBlock.MAX_LENGTH + 1) {
val state = input.zone.getBlockState(input.position.above(offset))
when {
SilkThreadBlock.isValidAnchor(state) -> return true
!isAirCondition(state) -> return false
}
}
return false
}
override fun calculate(input: AreaSpawningInput): HangingSpawnablePosition {
return HangingSpawnablePosition(
cause = input.cause,
world = input.world,
position = input.position.immutable(),
light = getLight(input),
skyLight = getSkyLight(input),
canSeeSky = getCanSeeSky(input),
influences = input.spawner.copyInfluences(),
height = 1,
zone = input.zone,
nearbyBlocks = getNearbyBlocks(input)
)
}
}
/**
* The spawnable position calculator used for [ClingingSpawnablePosition]s. Requires an air block
* for the Pokemon with at least one solid neighbouring block the mon is allowed to attach to
*/
object ClingingSpawnablePositionCalculator : AreaSpawnablePositionCalculator<ClingingSpawnablePosition> {
override val name = "clinging"
override fun fits(input: AreaSpawningInput): Boolean {
if (!isAirCondition(input.zone.getBlockState(input.position.above()))) return false
return findAttachments(input).isNotEmpty()
}
override fun calculate(input: AreaSpawningInput): ClingingSpawnablePosition {
return ClingingSpawnablePosition(
cause = input.cause,
world = input.world,
position = input.position.immutable(),
light = getLight(input),
skyLight = getSkyLight(input),
canSeeSky = getCanSeeSky(input),
influences = input.spawner.copyInfluences(),
height = 1,
zone = input.zone,
nearbyBlocks = getNearbyBlocks(input),
attachments = findAttachments(input)
)
}
private fun findAttachments(input: AreaSpawningInput): Map<Direction, BlockState> {
val monPos = input.position.above()
return Direction.entries
.map { it to input.zone.getBlockState(monPos.relative(it)) }
.filter { (direction, state) ->
isSolidCondition(state) && state.isFaceSturdy(EmptyBlockGetter.INSTANCE, BlockPos.ZERO, direction.opposite)
}
.toMap()
}
}
/**
* The spawnable position calculator used for [SeafloorSpawnablePosition]s. Requires a solid block below it and
* water blocks in its surroundings.
@@ -0,0 +1,90 @@
/*
* 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.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.block.entity.SilkThreadBlockEntity
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.BlockTags
import net.minecraft.tags.TagKey
import net.minecraft.util.RandomSource
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.projectile.Projectile
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.RenderShape
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape
/**
* A thin strand of silk anchored to leaves, placed when a Pokemon spawns hanging from a tree.
* Each strand survives only while the block above it is another strand or a valid anchor,
* so breaking any part of the column pops everything below it.
*/
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
class SilkThreadBlock(settings: Properties) : Block(settings), EntityBlock {
override fun newBlockEntity(pos: BlockPos, state: BlockState) = SilkThreadBlockEntity(pos, state)
override fun getRenderShape(state: BlockState): RenderShape = RenderShape.INVISIBLE
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape = SHAPE
override fun canSurvive(state: BlockState, world: LevelReader, pos: BlockPos) = isSupport(world.getBlockState(pos.above()))
override fun updateShape(
state: BlockState,
direction: Direction,
neighborState: BlockState,
world: LevelAccessor,
pos: BlockPos,
neighborPos: BlockPos
): BlockState {
if (direction == Direction.UP && !isSupport(neighborState)) {
world.scheduleTick(pos, this, 1)
}
return super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun tick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
if (!state.canSurvive(world, pos)) {
world.destroyBlock(pos, true)
}
}
override fun entityInside(state: BlockState, world: Level, pos: BlockPos, entity: Entity) {
if (entity is Projectile && !world.isClientSide) {
world.destroyBlock(pos, true, entity)
}
}
override fun codec(): MapCodec<out Block> = CODEC
companion object {
val CODEC = simpleCodec(::SilkThreadBlock)
const val MAX_LENGTH = 2
private val COMMON_LEAVES = TagKey.create(Registries.BLOCK, ResourceLocation.fromNamespaceAndPath("c", "leaves"))
private val SHAPE = box(7.0, 0.0, 7.0, 9.0, 16.0, 9.0)
fun isValidAnchor(state: BlockState) = state.`is`(BlockTags.LEAVES) || state.`is`(COMMON_LEAVES)
fun isSupport(state: BlockState) = state.`is`(CobblemonBlocks.SILK_THREAD) || isValidAnchor(state)
}
}
@@ -0,0 +1,16 @@
/*
* 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.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
class SilkThreadBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(CobblemonBlockEntities.SILK_THREAD, pos, state)
@@ -70,7 +70,6 @@ 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
@@ -80,19 +79,16 @@ 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 {
@@ -113,8 +109,6 @@ 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()
@@ -200,8 +194,6 @@ object CobblemonClient {
ClientPlayerIcon.onTick()
PartyOverlayDataControl.tick(event.client.isPaused)
}
ridingSprintHandler.put(cobblemonResource("normal")) { Minecraft.getInstance().options.keySprint.isDown }
}
private fun registerTooltipManagers() {
@@ -445,6 +437,7 @@ object CobblemonClient {
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.CAMPFIRE, ::CampfireBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.POKE_SNACK, ::PokeSnackBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.DISC_SHELF, ::DiscShelfBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.SILK_THREAD, ::SilkThreadRenderer)
}
private fun registerEntityRenderers() {
@@ -129,10 +129,6 @@ 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 &&
@@ -186,11 +182,10 @@ 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,
-50.0
0.0
)
val shouldAnimate = when (Cobblemon.config.partyPortraitAnimations) {
@@ -562,7 +557,5 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
}
}
}
matrices.popPose()
}
}
@@ -195,7 +195,7 @@ abstract class ScrollingWidget<T : ObjectSelectionList.Entry<T>>(
return this.width
}
public override fun getRowTop(index: Int): Int {
override fun getRowTop(index: Int): Int {
return this.y - scrollAmount.toInt() + (index * this.itemHeight)
}
@@ -205,19 +205,6 @@ 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,23 +68,25 @@ 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 ->
val yTop = getRowTop(entry)
if (yTop + entry.height > y || yTop < y + height) {
entry.render(guiGraphics, index, x, yTop, mouseX, mouseY, partialTick)
if (stackHeight in 1..<height || stackHeight + entry.height in 1..<height) {
entry.render(guiGraphics, index, x, y + stackHeight, 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 {
val yTop = getRowTop(it)
if (mouseY in (yTop)..(yTop + it.height)) {
if (mouseY.toDouble() in (y + stackHeight)..(y + stackHeight + it.height)) {
return it
}
stackHeight += it.height
}
}
return null
@@ -92,16 +94,10 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
fun ensureVisible(index: Int) {
val entry = children[index]
ensureVisible(entry)
}
open fun ensureVisible(entry: Entry) {
if (entry !in children) return
val yTop = entry.scrolledY - scrollAmount.toInt()
if (yTop < 0) {
if (entry.scrolledY - scrollAmount < 0) {
setScroll(entry.scrolledY.toDouble())
} else if (yTop + entry.height > height) {
setScroll((entry.scrolledY + entry.height - height).toDouble())
} else if (entry.scrolledY + entry.height > height) {
setScroll(entry.scrolledY.toDouble() + entry.height)
}
}
@@ -125,12 +121,14 @@ open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int):
}
fun getRowTop(entry: Entry): Int {
if (entry !in children) return -1
return y + entry.scrolledY - scrollAmount.toInt()
}
fun getLeft(): Int {
return x
var stackHeight = -scrollAmount.toInt()
children.forEach {
if (it == entry) {
return y + stackHeight
}
stackHeight += it.height
}
return y
}
override fun defaultButtonNarrationText(pNarrationElementOutput: NarrationElementOutput) {}
@@ -138,7 +136,6 @@ 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) {
@@ -148,14 +145,6 @@ 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,7 +8,6 @@
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
@@ -18,18 +17,9 @@ 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
@@ -39,11 +29,8 @@ 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, ControllerScreen {
class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
companion object {
const val OPTION_VERTICAL_SPACING = 3
const val OPTION_HORIZONTAL_SPACING = 3
@@ -64,49 +51,11 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable, Controll
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?) {
@@ -116,11 +65,6 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable, Controll
if (newSelection != null) {
addRenderableWidget(newSelection)
}
if (controllerComponentManager.currentSelectedComponent == "battle_action" && controllerComponentManager.active) {
focused = null
getCurrentActionSelection()?.controllerComponentManager?.active = true
controllerComponentManager.focused?.onFocus?.invoke()
}
}
}
@@ -141,38 +85,6 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable, Controll
}
}
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)
@@ -210,10 +122,9 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable, Controll
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", hideLabelButton),
text = battleLang("ui.hide_label", PartySendBinding.boundKey().displayName),
x = Minecraft.getInstance().window.guiScaledWidth / 2,
y = (Minecraft.getInstance().window.guiScaledHeight / 5),
opacity = 0.75F * opacity,
@@ -230,17 +141,6 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable, Controll
)
}
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()
@@ -251,6 +151,8 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable, Controll
}
fun deriveRootActionSelection(actor: ClientBattleActor, request: SingleActionRequest): BattleActionSelection? {
return if (request.forceSwitch) {
BattleSwitchPokemonSelection(this, request)
} else {
@@ -290,21 +192,15 @@ class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable, Controll
return minimizeBattle()
}
if (specBackButton.isHovered(mouseX, mouseY)) {
quitSpectating()
val battle = CobblemonClient.battle
if (battle?.spectating == true && specBackButton.isHovered(mouseX, mouseY)) {
RemoveSpectatorPacket(battle.battleId).sendToServer()
CobblemonClient.endBattle()
}
return super.mouseClicked(mouseX, mouseY, button)
}
fun quitSpectating() {
val battle = CobblemonClient.battle
if (battle?.spectating == true) {
RemoveSpectatorPacket(battle.battleId).sendToServer()
CobblemonClient.endBattle()
}
}
fun minimizeBattle(): Boolean {
private fun minimizeBattle(): Boolean {
val battle = CobblemonClient.battle ?: return false
battle.minimised = !battle.minimised
PartySendBinding.actioned()
@@ -21,7 +21,6 @@ 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
@@ -115,10 +114,6 @@ 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
@@ -147,14 +142,9 @@ 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", bindingComponent),
text = battleLang("ui.actions_label", PartySendBinding.boundKey().displayName),
x = Minecraft.getInstance().window.guiScaledWidth / 2,
y = Minecraft.getInstance().window.guiScaledHeight / 5,
opacity = textOpacity,
@@ -170,8 +160,6 @@ 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) {
@@ -309,11 +297,10 @@ 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,
-50.0
0.0
)
if (ballState != null && ballState.currentPose != "shut") {
@@ -11,7 +11,6 @@ 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
@@ -27,8 +26,6 @@ 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,7 +15,6 @@ 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
@@ -42,7 +41,6 @@ class BattleGeneralActionSelection(
val lastAnwseredRequest = CobblemonClient.battle?.getLastAnsweredRequest()
val tiles = mutableListOf<BattleOptionTile>()
init {
var rank = 0
@@ -78,41 +76,22 @@ 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
val tile = BattleOptionTile(
battleGUI = battleGUI,
x = x,
y = y,
resource = texture,
text = text,
onClick = onClick
tiles.add(
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,12 +21,10 @@ 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
@@ -68,10 +66,6 @@ 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 ->
@@ -82,53 +76,6 @@ 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,
@@ -152,8 +99,6 @@ 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
@@ -165,7 +110,7 @@ class BattleMoveSelection(
y = y,
width = MOVE_WIDTH,
height = MOVE_HEIGHT,
vOffset = if (selectable && (isHovered(mouseX.toDouble(), mouseY.toDouble()) || controllerSelected)) MOVE_HEIGHT else 0,
vOffset = if (selectable && isHovered(mouseX.toDouble(), mouseY.toDouble())) MOVE_HEIGHT else 0,
textureHeight = MOVE_HEIGHT * 2,
red = rgb.first,
green = rgb.second,
@@ -230,7 +175,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
@@ -239,10 +184,6 @@ 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)
@@ -251,7 +192,7 @@ class BattleMoveSelection(
gimmickButtons.forEach {
it.render(context.pose(), mouseX, mouseY, delta)
}
if(shiftButtonActive()) {
if(this.request.activePokemon.getFormat().battleType.slotsPerActor == 3 && (request.activePokemon.getPNX()[2] == 'a' || request.activePokemon.getPNX()[2] == 'c')) {
shiftButton.render(context, mouseX, mouseY, delta)
}
}
@@ -273,7 +214,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) && shiftButtonActive()) {
} else if(shiftButton.isHovered(mouseX,mouseY)) {
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.selectAction(request, ShiftActionResponse())
}
@@ -21,7 +21,6 @@ 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
@@ -69,8 +68,6 @@ 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 }
@@ -95,33 +92,6 @@ 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> {
@@ -184,15 +154,11 @@ class BattleSwitchPokemonSelection(
playDownSound(Minecraft.getInstance().soundManager)
return true
}
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
val clicked = tiles.find { it.isHovered(mouseX, mouseY) && if (isReviving) it.isFainted else (!it.isFainted && !it.isCurrentlyInBattle) } ?: return false
val pokemon = clicked.pokemon
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.selectAction(request, SwitchActionResponse(pokemon.uuid))
return true
}
@@ -224,9 +190,7 @@ class BattleSwitchPokemonSelection(
val state = FloatingState()
var controllerSelected = false
fun isHovered(mouseX: Double, mouseY: Double) = (mouseX in x..(x + SELECT_WIDTH) && mouseY in (y..(y + SELECT_HEIGHT)))
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
@@ -265,7 +229,7 @@ class BattleSwitchPokemonSelection(
y = y,
width = SELECT_WIDTH,
height = SELECT_HEIGHT,
vOffset = if (!isFainted && (isHovered(mouseX, mouseY) || isCurrentlyInBattle || controllerSelected)) SELECT_HEIGHT else 0,
vOffset = if (!isFainted && (isHovered(mouseX, mouseY) || isCurrentlyInBattle)) SELECT_HEIGHT else 0,
textureHeight = SELECT_HEIGHT * 2,
)
@@ -26,7 +26,6 @@ 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
@@ -112,35 +111,10 @@ class BattleTargetSelection(
else -> if (isAlly) ArrowDirection.UP else ArrowDirection.DOWN
}
}
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
TargetTile(this, target, x, y, arrowDirection)
}
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,
@@ -177,8 +151,6 @@ 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
@@ -332,9 +304,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) || it.controllerSelected) } != null
return targetTiles.firstOrNull { it.selectable && mouseX >= it.x && mouseX <= it.x + TARGET_WIDTH && mouseY >= it.y && mouseY <= it.y + TARGET_HEIGHT } != null
}
return mouseX >= x && mouseX <= x + TARGET_WIDTH && mouseY >= y && mouseY <= y + TARGET_HEIGHT || controllerSelected
return mouseX >= x && mouseX <= x + TARGET_WIDTH && mouseY >= y && mouseY <= y + TARGET_HEIGHT
}
fun onClick() {
@@ -17,7 +17,6 @@ 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
@@ -52,52 +51,21 @@ 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)) {
onDecline()
battleGUI.changeActionSelection(null)
} else {
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()
}
}
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()) || focused) OPTION_HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) 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,10 +10,6 @@ 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
@@ -26,14 +22,11 @@ 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, ControllerScreen {
) : Screen("gui.cobblemon_config.title".asTranslated()), CobblemonRenderable {
companion object {
const val HEIGHT = 240
@@ -62,13 +55,6 @@ class CobblemonConfigScreen(
private var searchString: String = ""
val controllerComponentManager = ControllerComponentManager(this)
var lastSelectedEntry = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
override fun init() {
super.init()
@@ -112,46 +98,6 @@ 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,7 +17,6 @@ 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
@@ -135,63 +134,6 @@ 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,9 +26,6 @@ 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
@@ -48,10 +45,9 @@ 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, ControllerScreen {
class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTranslated()), CobblemonRenderable {
val speakers = dialogueDTO.speakers?.mapNotNull { (key, value) ->
val name = value.name
when (val face = value.face) {
@@ -145,12 +141,6 @@ 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) {}
@@ -252,34 +242,6 @@ 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
@@ -303,8 +265,6 @@ 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,17 +160,10 @@ 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 (mouseX > this.x && mouseX < this.scrollbarPosition &&
if (!dialogueScreen.waitingForServerUpdate &&
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) {
@@ -184,7 +177,8 @@ class DialogueBox(
return true
}
}
return false
return super.mouseClicked(mouseX, mouseY, button)
}
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 || isFocused) height else 0) else (height * 2),
vOffset = if (selectable) (if (isHovered) height else 0) else (height * 2),
textureHeight = height * 3,
)
@@ -58,14 +58,10 @@ 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,7 +8,6 @@
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
@@ -17,9 +16,6 @@ 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.*
@@ -31,18 +27,16 @@ 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, ControllerScreen {
) : Screen(lang("ui.challenge.challenge_title")), CobblemonRenderable {
companion object {
const val SIZE = 113
private val backgroundResource = cobblemonResource("textures/gui/interact/request/battle_request.png")
@@ -206,12 +200,6 @@ 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) { }
@@ -230,29 +218,29 @@ class BattleConfigureGUI(
}
val (x, y) = getDimensions()
var battleAccept: BattleResponseButton? = null
var battleDecline: BattleResponseButton? = null
if(pendingRequest != null) {
// Draw Accept/Decline buttons
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 + 22,
y + 99,
true
) {
battleRequestMap[options[currentPage]]?.onResponse?.let { it1 -> it1(packet, pendingRequest.requestID, true) }
closeGUI()
}
)
battleDecline = BattleResponseButton(
x + 56,
y + 99,
false
) {
battleRequestMap[options[currentPage]]?.onResponse?.let { it1 -> it1(packet, pendingRequest.requestID, false) }
closeGUI()
}
this.addRenderableWidget(battleDecline)
this.addRenderableWidget(
BattleResponseButton(
x + 56,
y + 99,
false
) {
battleRequestMap[options[currentPage]]?.onResponse?.let { it1 -> it1(packet, pendingRequest.requestID, false) }
closeGUI()
}
)
}
else {
@@ -318,36 +306,6 @@ 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() {
@@ -480,8 +438,6 @@ class BattleConfigureGUI(
)
super.render(context, mouseX, mouseY, delta)
renderSelection(context.pose())
}
private fun getDimensions(): Pair<Int, Int> {
@@ -498,7 +454,4 @@ 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()) || isFocused)
val hovered = (isHovered(pMouseX.toDouble(), pMouseY.toDouble()))
blitk(
matrixStack = context.pose(),
x = x / SCALE,
@@ -14,9 +14,6 @@ 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
@@ -39,7 +36,7 @@ class MoveSelectConfiguration(
class MoveSelectGUI(
val config: MoveSelectConfiguration
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable, ControllerScreen {
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable {
companion object {
const val WIDTH = 122
const val HEIGHT = 133
@@ -49,8 +46,6 @@ class MoveSelectGUI(
var closed = false
val controllerComponentManager = ControllerComponentManager(this)
constructor(
title: MutableComponent,
moves: List<MoveSelectDTO>,
@@ -68,10 +63,6 @@ class MoveSelectGUI(
)
)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
fun closeProperly() {
closed = true
onClose()
@@ -82,21 +73,16 @@ class MoveSelectGUI(
val y = (height - HEIGHT) / 2
config.moves.forEachIndexed { index, 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}"] }
}
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) }
)
}
// Add Exit Button
@@ -111,10 +97,6 @@ class MoveSelectGUI(
)
super.init()
controllerComponentManager.let {
it.focused = it.componentList["move_0"]
}
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
@@ -132,8 +114,6 @@ class MoveSelectGUI(
// Render all added Widgets
super.render(context, mouseX, mouseY, partialTicks)
renderSelection(context.pose())
}
private fun onPress(move: MoveSelectDTO) {
@@ -16,9 +16,6 @@ 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
@@ -41,7 +38,7 @@ class PartySelectConfiguration(
)
class PartySelectGUI(
val config: PartySelectConfiguration
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable, ControllerScreen {
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable {
companion object {
const val WIDTH = 163
const val HEIGHT = 132
@@ -53,8 +50,6 @@ class PartySelectGUI(
var closed = false
val controllerComponentManager = ControllerComponentManager(this)
constructor(
title: MutableComponent,
pokemon: List<PartySelectPokemonDTO>,
@@ -72,10 +67,6 @@ class PartySelectGUI(
)
)
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
fun closeProperly() {
closed = true
onClose()
@@ -99,29 +90,20 @@ class PartySelectGUI(
slotY += (31 * offsetIndex) + offsetY
}
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}"] }
}
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) }
)
}
// Add Exit Button
@@ -136,10 +118,6 @@ class PartySelectGUI(
)
super.init()
controllerComponentManager.let {
it.focused = it.componentList["slot_0_0"]
}
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
@@ -177,8 +155,6 @@ 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) || isFocused) && enabled
isHovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height && 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,
val isEnabled: Boolean,
private 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()) || isFocused) && isEnabled) height else 0,
vOffset = if (isHovered(mouseX.toFloat(), mouseY.toFloat()) && isEnabled) height else 0,
textureHeight = height * 2,
alpha = if (isEnabled) 1f else 0.4F
)
if(isEnabled && (isHovered(mouseX.toFloat(), mouseY.toFloat()) || isFocused)){
if(isEnabled && isHovered(mouseX.toFloat(), mouseY.toFloat())){
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()) || isFocused) super.mouseClicked(mouseX, mouseY, button) else false
return if (isHovered(mouseX.toFloat(), mouseY.toFloat())) super.mouseClicked(mouseX, mouseY, button) else false
}
override fun getTooltip(): Tooltip? {
@@ -32,10 +32,9 @@ class InteractWheelGUI(private val options: Multimap<Orientation, InteractWheelO
return super.keyPressed(keyCode, scanCode, modifiers)
}
val buttons = mutableListOf<InteractWheelButton>()
private val buttons = mutableListOf<InteractWheelButton>()
private var maxPage = 1
private var currentPage = 0
override fun renderBlurredBackground(delta: Float) { }
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -87,7 +86,7 @@ class InteractWheelGUI(private val options: Multimap<Orientation, InteractWheelO
currentPage = page
buttons.forEach { removeWidget(it) }
buttons.clear()
val orientations = Orientation.entries.toTypedArray()
val orientations = Orientation.values()
orientations.forEach { orientation ->
val option = options[orientation].toList().getOrNull(page)
addButton(orientation, option)
@@ -19,7 +19,6 @@ 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
@@ -77,58 +76,11 @@ 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) {}
@@ -214,8 +166,6 @@ 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
@@ -232,7 +182,7 @@ class PasturePokemonScrollList(
setEnabled(PokemonBehaviourFlag.PASTURE_CONFLICT in pokemon.behaviourFlags)
}
val moveButton: PastureSlotIconButton = PastureSlotIconButton(
private val moveButton: PastureSlotIconButton = PastureSlotIconButton(
xPos = 0,
yPos = 0,
onPress = {
@@ -241,8 +191,7 @@ class PasturePokemonScrollList(
pastureId = parent.pasturePCGUIConfiguration.pastureId,
pokemonId = pokemon.pokemonId
).sendToServer()
},
parent = this@PastureSlot
}
)
override fun getNarration() = pokemon.displayName
@@ -270,7 +219,7 @@ class PasturePokemonScrollList(
y = y,
height = SLOT_HEIGHT,
width = rowWidth,
vOffset = if (isHovered || controllerFocused) SLOT_HEIGHT else 0,
vOffset = if (isHovered) SLOT_HEIGHT else 0,
textureHeight = SLOT_HEIGHT * 2
)
@@ -18,8 +18,7 @@ import net.minecraft.network.chat.Component
class PastureSlotIconButton(
var xPos: Int, var yPos: Int,
onPress: OnPress,
var parent: PasturePokemonScrollList.PastureSlot
onPress: OnPress
) : Button(xPos, yPos, (SIZE * SCALE).toInt(), (SIZE * SCALE).toInt(), Component.literal("Pasture Move"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
@@ -36,7 +35,7 @@ class PastureSlotIconButton(
y = yPos / SCALE,
width = SIZE,
height = SIZE,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || parent.controllerFocused) SIZE else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) SIZE else 0,
textureHeight = SIZE * 2,
texture = baseResource,
scale = SCALE
@@ -15,8 +15,6 @@ 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
@@ -36,9 +34,6 @@ 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
@@ -53,23 +48,6 @@ 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()) || isFocused) HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
@@ -21,7 +21,6 @@ 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
@@ -29,10 +28,6 @@ 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
@@ -49,15 +44,12 @@ 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,
@@ -65,7 +57,7 @@ class PCGUI(
val configuration: PCGUIConfiguration,
val openOnBox: Int = CobblemonClient.lastPcBoxViewed,
val unseenWallpapers: MutableSet<ResourceLocation> = mutableSetOf()
) : Screen(Component.translatable("cobblemon.ui.pc.title")), CobblemonRenderable, ControllerScreen {
) : Screen(Component.translatable("cobblemon.ui.pc.title")), CobblemonRenderable {
companion object {
const val BASE_WIDTH = 349
@@ -136,14 +128,6 @@ 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) {}
@@ -214,39 +198,30 @@ 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()
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)
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 }
)
// Add Wallpaper Widget
this.wallpaperWidget = WallpapersScrollingWidget(
@@ -262,7 +237,7 @@ class PCGUI(
this.addRenderableWidget(wallpaperWidget)
// Add Wallpaper Settings Button
wallpaperButton = IconButton(
this.addRenderableWidget(IconButton(
pX = x + 242,
pY = y + 31,
buttonWidth = 20,
@@ -275,16 +250,14 @@ 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()
val button = IconButton(
this.addRenderableWidget(IconButton(
pX = x + 92 + (12 * index),
pY = y + 31,
buttonWidth = 20,
@@ -295,58 +268,33 @@ class PCGUI(
label = "sort_${typeName}"
) {
storageWidget.resetSelected()
SortPCBoxPacket(pc.uuid, storageWidget.box, sortType, hasShiftDown() || (controllerComponentManager.active && controllerShiftToggle)).sendToServer()
SortPCBoxPacket(pc.uuid, storageWidget.box, sortType, hasShiftDown()).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)
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(
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
}
)
)
addRenderableWidget(
IconButton(
@@ -364,149 +312,6 @@ 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) {
@@ -880,7 +685,7 @@ class PCGUI(
)
if (!optionButtons.isEmpty() && displayOptions) {
for (button in optionButtons) button.showAlt = hasShiftDown() || (controllerComponentManager.active && controllerShiftToggle)
for (button in optionButtons) button.showAlt = hasShiftDown()
}
super.render(context, mouseX, mouseY, delta)
@@ -895,8 +700,6 @@ 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 {
const val WIDTH = 58
const val HEIGHT = 16
private const val WIDTH = 58
private 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()) || isFocused) HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) 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 {
const val WIDTH = 30
const val HEIGHT = 13
private const val WIDTH = 30
private 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()) || isFocused) HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
@@ -20,9 +20,6 @@ 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
@@ -44,7 +41,6 @@ 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,
@@ -95,9 +91,6 @@ class StorageWidget(
this.setupStorageSlots()
}
val controllerComponentManager = ControllerComponentManager(pcGui)
var grabbedSlotControllerPos = Vector2i()
init {
this.setupStorageSlots()
@@ -109,7 +102,6 @@ class StorageWidget(
if (!displayConfirmRelease && canDeleteSelected()) {
displayConfirmRelease = true
playSound(CobblemonSounds.PC_CLICK)
controllerComponentManager.focused = controllerComponentManager.componentList["release_no"]
}
}
)
@@ -134,7 +126,6 @@ class StorageWidget(
playSound(CobblemonSounds.PC_RELEASE)
resetSelected()
displayConfirmRelease = false
controllerComponentManager.focused = controllerComponentManager.componentList["slot_0_0"]
}
}
)
@@ -148,7 +139,6 @@ class StorageWidget(
if (displayConfirmRelease) {
displayConfirmRelease = false
playSound(CobblemonSounds.PC_CLICK)
controllerComponentManager.focused = controllerComponentManager.componentList["release"]
}
}
)
@@ -156,47 +146,6 @@ 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 {
@@ -215,7 +164,7 @@ class StorageWidget(
for (row in 1..5) {
for (col in 1..6) {
val slot = BoxStorageSlot(
BoxStorageSlot(
x = boxStartX + ((col - 1) * (StorageSlot.SIZE + BOX_SLOT_PADDING)),
y = boxStartY + ((row - 1) * (StorageSlot.SIZE + BOX_SLOT_PADDING)),
parent = this,
@@ -227,27 +176,6 @@ 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() }
}
}
}
@@ -266,7 +194,7 @@ class StorageWidget(
partyY += ((StorageSlot.SIZE + PARTY_SLOT_PADDING) * offsetIndex) + offsetY
}
val slot = PartyStorageSlot(
PartyStorageSlot(
x = partyX,
y = partyY,
parent = this,
@@ -277,22 +205,6 @@ 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() }
}
}
}
@@ -395,7 +307,7 @@ class StorageWidget(
this.boxSlots.forEach { slot ->
slot.render(context, mouseX, mouseY, delta)
val pokemon = slot.getPokemon()
if (grabbedSlot == null && (slot.isHovered(mouseX, mouseY) || slot.isFocused)
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
&& pokemon != null && pokemon != pcGui.previewPokemon
) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: false)
@@ -411,7 +323,7 @@ class StorageWidget(
this.partySlots.forEach { slot ->
slot.render(context, mouseX, mouseY, delta)
val pokemon = slot.getPokemon()
if (grabbedSlot == null && (slot.isHovered(mouseX, mouseY) || slot.isFocused)
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
&& pokemon != null && pokemon != pcGui.previewPokemon
) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: true)
@@ -423,11 +335,7 @@ class StorageWidget(
pastureWidget?.render(context, mouseX, mouseY, delta)
matrices.pushPose()
matrices.translate(0.0, 0.0, 1000.0)
if (controllerComponentManager.active) {
grabbedSlot?.render(context, grabbedSlotControllerPos.x, grabbedSlotControllerPos.y, delta)
} else {
grabbedSlot?.render(context, mouseX, mouseY, delta)
}
grabbedSlot?.render(context, mouseX, mouseY, delta)
matrices.popPose()
}
@@ -464,9 +372,6 @@ 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,8 +15,6 @@ 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
@@ -51,14 +49,10 @@ 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
@@ -96,24 +90,6 @@ 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
}
@@ -140,7 +116,7 @@ class WallpapersScrollingWidget(
partialTick: Float
) {
val matrices = guiGraphics.pose()
val resource = if ((Screen.hasShiftDown() || (pcGui.controllerComponentManager.active && pcGui.controllerShiftToggle)) && altWallpaper !== null) altWallpaper else wallpaper
val resource = if (Screen.hasShiftDown() && altWallpaper !== null) altWallpaper else wallpaper
blitk(
matrixStack = matrices,
texture = resource,
@@ -157,28 +133,24 @@ class WallpapersScrollingWidget(
y = top + SLOT_PADDING,
width = width,
height = SLOT_HEIGHT,
vOffset = if (hovering || isFocused) SLOT_HEIGHT else 0,
vOffset = if (hovering) 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)) {
selectWallpaper()
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))
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,7 +15,6 @@ 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
@@ -47,10 +46,6 @@ 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
@@ -79,8 +74,6 @@ 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
@@ -92,7 +85,7 @@ class PokedexGUI private constructor(
val type: PokedexType,
val initSpecies: ResourceLocation?,
val blockPos: BlockPos?
): Screen(Component.translatable("cobblemon.ui.pokedex.title")), CobblemonRenderable, ControllerScreen {
): Screen(Component.translatable("cobblemon.ui.pokedex.title")), CobblemonRenderable {
companion object {
private val screenBackground = cobblemonResource("textures/gui/pokedex/pokedex_screen.png")
@@ -170,14 +163,6 @@ 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) {}
@@ -283,222 +268,6 @@ 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) {
@@ -643,8 +412,6 @@ 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()
@@ -700,7 +467,6 @@ 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)
@@ -716,18 +482,6 @@ 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) {
@@ -787,45 +541,6 @@ 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() }
fun switchAbility(nextIndex: Boolean) {
private fun switchAbility(nextIndex: Boolean) {
val size = abilitiesList.size
selectedAbilitiesIndex = if (nextIndex) {
(selectedAbilitiesIndex + 1) % size
@@ -149,8 +149,6 @@ 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,
@@ -192,7 +190,7 @@ class EntriesScrollingWidget(val pX: Int, val pY: Int, val setPokedexEntry: (Pok
height = SCROLL_SLOT_SIZE
)
if (getHoveredSlotIndex(mouseX, mouseY) == index || slotControllerSelected == index) {
if (getHoveredSlotIndex(mouseX, mouseY) == index) {
blitk(
matrixStack = matrices,
texture = slotHighlight,
@@ -56,7 +56,6 @@ 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
@@ -124,7 +123,6 @@ 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
@@ -190,7 +188,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
val variationButtons = mutableListOf<VariationButtonWrapper>()
val genderButton: ScaledButton = ScaledButton(
private val genderButton: ScaledButton = ScaledButton(
pX + 114F,
pY + 27F,
20,
@@ -202,7 +200,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
}
).apply { addWidget(this) }
val shinyButton: ScaledButton = ScaledButton(
private val shinyButton: ScaledButton = ScaledButton(
(pX + 126F),
(pY + 27F),
20,
@@ -263,8 +261,6 @@ 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 =
@@ -379,10 +375,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,
10.0 // Dont use too big numbers otherwise side mod overlays can break (controllify keyboard)
1000.0 // Prevent model from clipping into background
)
matrices.scale(scaleAmount, scaleAmount, 0.2F)
val rotationVector = Vector3f(13F, Mth.rotLerp(realDetla, lastRotationY, rotationY), 0F)
matrices.scale(scaleAmount, scaleAmount, scaleAmount)
val rotationVector = Vector3f(13F, rotationY, 0F)
drawProfilePokemon(
renderablePokemon = renderablePokemon!!,
@@ -411,7 +407,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, 20.0)
matrices.translate(0.0, 0.0, 2000.0)
if (isSelectedPokemonOwned()) {
val primaryType = type[0]
@@ -642,12 +638,12 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
type = arrayOf(form.primaryType, form.secondaryType)
}
fun playCry() {
private fun playCry() {
state.activeAnimations.clear()
state.addFirstAnimation(setOf("cry"))
}
fun switchForm(nextIndex: Boolean) {
private fun switchForm(nextIndex: Boolean) {
if (visibleForms.isEmpty() || currentEntry == null) {
return
}
@@ -668,7 +664,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
updateAspects()
}
fun switchPose(nextIndex: Boolean) {
private fun switchPose(nextIndex: Boolean) {
if (nextIndex) {
if (selectedPoseIndex < poseList.lastIndex) selectedPoseIndex++
else selectedPoseIndex = 0
@@ -760,8 +756,6 @@ 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,9 +9,6 @@
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
@@ -23,12 +20,10 @@ 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), ControllerScreen {
class SnapshotWarningScreen(val consumer: (Acknowledgement, Boolean) -> Unit) : Screen(TITLE) {
companion object {
@@ -41,12 +36,6 @@ 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()
@@ -56,42 +45,17 @@ 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)
val checkbox = Checkbox.builder(Component.translatable("cobblemon.snapshots.warning.dont_show_again"), Minecraft.getInstance().font)
helper.addChild(Checkbox.builder(Component.translatable("cobblemon.snapshots.warning.dont_show_again"), Minecraft.getInstance().font)
.selected(dontShowAgain)
.tooltip(Tooltip.create(DONT_SHOW_AGAIN_TIP))
.build()
helper.addChild(checkbox, 2, settings)
.build(), 2, settings)
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)
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())
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,10 +25,9 @@ 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
@@ -42,9 +41,8 @@ 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.sounds.SoundEvent
import org.joml.Vector4i
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import java.io.FileNotFoundException
/**
@@ -53,7 +51,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, ControllerScreen {
class StarterSelectionScreen(private val categories: List<RenderableStarterCategory>): Screen("cobblemon.ui.starter.title".asTranslated()), CobblemonRenderable {
companion object {
// Size of UI at scale 1
private const val BASE_WIDTH = 239
@@ -75,14 +73,6 @@ 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
@@ -161,91 +151,6 @@ 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) {
@@ -402,8 +307,6 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
matrices.popPose()
super.render(context, mouseX, mouseY, delta)
renderSelection(matrices)
}
fun getPlatformResource(type: ElementalType): ResourceLocation? {
@@ -59,12 +59,9 @@ 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))
}
@@ -76,18 +73,7 @@ class CategoryList(
Category(category, 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
inner class Category(private val category: RenderableStarterCategory, height: Int) : Entry(height) {
override fun render(
guiGraphics: GuiGraphics,
@@ -184,7 +170,7 @@ class CategoryList(
width = CONTAINER_BUTTON_SIZE,
height = CONTAINER_BUTTON_SIZE,
textureHeight = CONTAINER_BUTTON_SIZE * 2,
vOffset = if (hovered || controllerButtonSelected == index) CONTAINER_BUTTON_SIZE else 0
vOffset = if (hovered) CONTAINER_BUTTON_SIZE else 0
)
matrices.pushPose()
@@ -8,6 +8,8 @@
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
@@ -15,8 +17,10 @@ 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
@@ -46,7 +50,7 @@ class SelectionButton(
width = BUTTON_WIDTH,
height = BUTTON_HEIGHT,
textureHeight = BUTTON_HEIGHT * 2,
vOffset = if (isHovered || isFocused) BUTTON_HEIGHT else 0
vOffset = if (isHovered) BUTTON_HEIGHT else 0
)
drawScaledText(
@@ -20,27 +20,14 @@ 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
@@ -63,8 +50,6 @@ 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.
@@ -75,7 +60,7 @@ import org.joml.Vector4i
* @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, ControllerScreen {
Component.translatable("cobblemon.ui.summary.title")), Schedulable, CobblemonRenderable {
companion object {
const val BASE_WIDTH = 331
@@ -84,10 +69,10 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
private const val SCALE = 0.5F
// Main Screen Index
const val INFO = 0
const val MOVES = 1
const val STATS = 2
const val MARKS = 3
private const val INFO = 0
private const val MOVES = 1
private const val STATS = 2
private const val MARKS = 3
// Side Screen Index
const val PARTY = 0
@@ -131,32 +116,19 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
override val schedulingTracker = SchedulingTracker()
val party = ArrayList(party)
private val party = ArrayList(party)
internal var selectedPokemon: Pokemon = this.party[selection] ?: this.party.first { it != null }!!
lateinit var mainScreen: AbstractWidget
private lateinit var mainScreen: AbstractWidget
lateinit var sideScreen: GuiEventListener
private lateinit var modelWidget: ModelWidget
lateinit var nicknameEntryWidget: NicknameEntryWidget
private lateinit var nicknameEntryWidget: NicknameEntryWidget
private lateinit var markingsWidget: MarkingsWidget
val summaryTabs = mutableListOf<SummaryTab>()
private val summaryTabs = mutableListOf<SummaryTab>()
private var showCosmeticItem = false
lateinit var heldItemVisibilityButton: SummaryButton
lateinit var heldItemSwitchTypeButton: SummaryButton
lateinit var evolveButton: SummaryButton
var mainScreenIndex = INFO
private lateinit var heldItemVisibilityButton: SummaryButton
private 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) { }
/**
@@ -186,7 +158,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
addRenderableWidget(markingsWidget)
// Evolve Button
evolveButton = addRenderableWidget(
addRenderableWidget(
SummaryButton(
buttonX = x + 12F,
buttonY = y + 145F,
@@ -232,22 +204,22 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
heldItemVisibilityButton.buttonActive = !selectedPokemon.heldItemVisible
addRenderableWidget(heldItemVisibilityButton)
// Held/Cosmetic Item Button
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(
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()
@@ -281,7 +253,14 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
// Add Exit Button
addRenderableWidget(
ExitButton(pX = x + 302, pY = y + 145) {
onBack()
playSound(CobblemonSounds.GUI_CLICK)
if (sideScreenIndex != PARTY) {
displaySideScreen(PARTY)
} else {
saveActiveMark()
saveMarkings()
Minecraft.getInstance().setScreen(null)
}
}
)
@@ -315,214 +294,9 @@ 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
}
@@ -583,11 +357,11 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
}
}
fun saveMarkings() {
private fun saveMarkings() {
if (::markingsWidget.isInitialized) markingsWidget.saveMarkingsToPokemon()
}
fun saveActiveMark() {
private fun saveActiveMark() {
if (mainScreenIndex == MARKS && mainScreen is MarksWidget) (mainScreen as MarksWidget).saveActiveMarkToPokemon()
}
@@ -599,16 +373,12 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
/**
* Switch center screen
*/
fun displayMainScreen(screen: Int) {
private 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).rawStatTabIndex else 0
if (screen != MOVES) {
moveSwitchingMode = false
}
val subIndex = if (mainScreenIndex == STATS && mainScreen is StatWidget) (mainScreen as StatWidget).statTabIndex else 0
mainScreenIndex = screen
if (::mainScreen.isInitialized) removeWidget(mainScreen)
@@ -628,7 +398,6 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
pY = y + 12,
pokemon = this.selectedPokemon
)
mainScreenComponentManager = null
}
MOVES -> {
@@ -636,13 +405,7 @@ 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 -> {
@@ -650,38 +413,25 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
pX = x + 77,
pY = y + 12,
pokemon = this.selectedPokemon,
tabIndex = subIndex,
summary = this
tabIndex = subIndex
)
mainScreenComponentManager = null
}
MARKS -> {
mainScreen = MarksWidget(
pX = x + 77,
pY = y + 12,
pokemon = this.selectedPokemon,
summary = this
).also {
mainScreenComponentManager = it.controllerComponentManager
}
pokemon = this.selectedPokemon
)
}
}
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)
@@ -696,9 +446,7 @@ 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 -> {
@@ -720,9 +468,7 @@ 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"]
}
}
@@ -732,67 +478,14 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
sideScreen = EvolutionSelectScreen(
x + 216,
y + 23,
pokemon = selectedPokemon,
summary = this
).also {
sideScreenComponentManager = it.controllerComponentManager
}
controllerComponentManager.focused = controllerComponentManager.componentList["right_screen"]
pokemon = selectedPokemon
)
}
}
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) {}
@@ -932,11 +625,8 @@ 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(
@@ -965,25 +655,19 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
scale = SCALE
)
// 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
}
matrices.pushPose()
// Prevent widgets from being overlapped by other components
matrices.translate(0.0, 0.0, 1000.0)
// 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,
val renderRequirement: ((button: SummaryButton) -> Boolean) = { true },
val clickRequirement: ((button: SummaryButton) -> Boolean) = { true },
private val renderRequirement: ((button: SummaryButton) -> Boolean) = { true },
private 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) || isFocused)) buttonHeight else 0,
vOffset = if (hoverTexture && isButtonHovered(pMouseX, pMouseY)) buttonHeight else 0,
textureHeight = if (hoverTexture) (buttonHeight.toFloat() * 2) else buttonHeight,
scale = scale
)
@@ -15,11 +15,8 @@ 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
@@ -37,8 +34,7 @@ import org.joml.Vector3f
class EvolutionSelectScreen(
x: Int,
y: Int,
val pokemon: Pokemon,
val summary: Summary
val pokemon: Pokemon
): SummaryScrollList<EvolutionSelectScreen.EvolveSlot>(
x,
y,
@@ -56,36 +52,8 @@ 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 {
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
return super.addEntry(entry)
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
@@ -21,7 +21,6 @@ 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(
@@ -60,8 +59,6 @@ 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
@@ -91,7 +88,7 @@ class ModelWidget(
val timeLooking = lookStartTime?.let { (System.currentTimeMillis() - it) / 1000 } ?: MAX_SECONDS_LOOKING
val lookedTooMuch = timeLooking >= MAX_SECONDS_LOOKING
val resetToCenter = (!isHovered && lookedTooMuch && overrideLookPos == null) || !shouldFollowCursor || !Cobblemon.config.summaryPokemonFollowCursor
val resetToCenter = (!isHovered && lookedTooMuch) || !shouldFollowCursor || !Cobblemon.config.summaryPokemonFollowCursor
if (resetToCenter) {
lookStartTime = null
@@ -100,19 +97,12 @@ 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,
xPos,
yPos,
mouseX,
mouseY,
currentYawAndPitch.first,
currentYawAndPitch.second,
resetToCenter
@@ -130,7 +120,6 @@ 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 || isFocused, isSelected),
vOffset = getSlotVOffset(slotPokemon, isHovered, isSelected),
textureHeight = height * 2,
)
@@ -223,21 +223,16 @@ class PartySlotWidget(
if (partyWidget.swapEnabled) {
toggleDrag(true)
} else {
if (onPress()) return true
if ((index > -1) && (summary.selectedPokemon.uuid != pokemon?.uuid)) {
summary.switchSelection(index)
if (pokemon != null) partyWidget.playSound(CobblemonSounds.GUI_CLICK)
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,8 +14,6 @@ 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
@@ -69,8 +67,6 @@ class PartyWidget(
}
)
var controllerComponentManager = ControllerComponentManager(summary)
init {
if (partySize > 6 || partySize < 1)
throw InvalidParameterException("Invalid party size")
@@ -89,7 +85,7 @@ class PartyWidget(
y += (32 * offsetIndex) + offsetY
}
val slot = PartySlotWidget(
PartySlotWidget(
pX = x,
pY = y,
partyWidget = this,
@@ -101,86 +97,6 @@ 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,23 +125,6 @@ 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()
@@ -152,6 +135,4 @@ abstract class SummaryScrollList<T : ObjectSelectionList.Entry<T>>(
private fun correctSize() {
setRectangle(WIDTH, HEIGHT, listX, listY)
}
open fun onPress(entry: T) {}
}
@@ -106,11 +106,10 @@ 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)
@@ -134,7 +133,7 @@ class MarksScrollingWidget(val pX: Int, val pY: Int, val setSelectedMark: () ->
y = startPosY,
width = SLOT_SIZE,
height = SLOT_SIZE,
vOffset = if (slotHovered || (mark != null && slotSelected.invoke(index))) SLOT_SIZE else 0,
vOffset = if (slotHovered) SLOT_SIZE else 0,
textureHeight = SLOT_SIZE * 2
)
@@ -161,7 +160,7 @@ class MarksScrollingWidget(val pX: Int, val pY: Int, val setSelectedMark: () ->
return true
}
fun isScrollVisible(): Boolean = parent.children().size > 5
private 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,10 +15,7 @@ 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
@@ -30,8 +27,7 @@ import net.minecraft.network.chat.Component
class MarksWidget(
pX: Int, pY: Int,
val pokemon: Pokemon,
val summary: Summary
val pokemon: Pokemon
): SoundlessWidget(pX, pY, WIDTH, HEIGHT, Component.literal("MarksWidget")) {
companion object {
private const val WIDTH = 134
@@ -57,9 +53,6 @@ class MarksWidget(
setHoveredMark = { hoveredMark = it }
)
val controllerComponentManager = ControllerComponentManager(summary)
var lastSelectedMark = 0
init {
children.clear()
activeMark = pokemon.activeMark
@@ -71,54 +64,6 @@ 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
}
val moveUpButton = ReorderMoveButton(x, y, true) {
private val moveUpButton = ReorderMoveButton(x, y, true) {
movesWidget.selectMove(null)
movesWidget.reorderMove(this, true)
}.apply {
addWidget(this)
}
val moveDownButton = ReorderMoveButton(x, y, false) {
private val moveDownButton = ReorderMoveButton(x, y, false) {
movesWidget.selectMove(null)
movesWidget.reorderMove(this, false)
}.apply {
@@ -20,8 +20,6 @@ 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
@@ -51,45 +49,13 @@ 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 {
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
return super.addEntry(entry)
}
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,
@@ -114,7 +80,7 @@ class MoveSwapScreen(
y = tweakedRowTop,
height = SLOT_HEIGHT,
width = rowWidth,
vOffset = if (isHovered || slotSelected) SLOT_HEIGHT else 0,
vOffset = if (isHovered) SLOT_HEIGHT else 0,
textureHeight = SLOT_HEIGHT * 2,
red = rgb.first,
green = rgb.second,
@@ -230,7 +196,7 @@ class MoveSwapScreen(
y = tweakedRowTop,
height = SLOT_HEIGHT,
width = 100,
vOffset = if (isHovered || slotSelected) SLOT_HEIGHT else 0,
vOffset = if (isHovered) SLOT_HEIGHT else 0,
textureHeight = SLOT_HEIGHT * 2,
)
blitk(
@@ -240,7 +206,7 @@ class MoveSwapScreen(
y = tweakedRowTop + SLOT_HEIGHT / 2 - 4,
height = 7,
width = 7,
vOffset = if (isHovered || slotSelected) 7 else 0,
vOffset = if (isHovered) 7 else 0,
textureHeight = 14,
)
}
@@ -249,29 +215,21 @@ class MoveSwapScreen(
override fun mouseClicked(d: Double, e: Double, i: Int): Boolean {
if (isMouseOver(d, e)) {
onPress()
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)
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,15 +9,12 @@
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
@@ -55,8 +52,8 @@ class MovesWidget(
var selectedMove: Move? = null
var index = -1
val moves = (
private var index = -1
private val moves = (
if (summary.selectedPokemon.relearnableMoves.count() > 0)
summary.selectedPokemon.moveSet.getMovesWithNulls()
else summary.selectedPokemon.moveSet
@@ -79,79 +76,6 @@ 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()) || isFocused) HEIGHT else 0,
vOffset = if (isHovered(pMouseX.toDouble(), pMouseY.toDouble(), offsetY.toFloat())) HEIGHT else 0,
textureHeight = HEIGHT * 2,
scale = SCALE
)
@@ -19,7 +19,6 @@ 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
@@ -50,17 +49,16 @@ import org.joml.Vector3f
class StatWidget(
pX: Int, pY: Int,
val pokemon: Pokemon,
val tabIndex: Int = 0,
val summary: Summary
val tabIndex: Int = 0
): SoundlessWidget(pX, pY, WIDTH, HEIGHT, Component.literal("StatWidget")) {
companion object {
// Stat tab options
const val STATS = "stats"
const val IV = "ivs"
const val EV = "evs"
const val RIDE = "ride"
const val OTHER = "other"
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"
private const val OTHER_STAT_BARS_PER_PAGE = 4
private const val STAT_TAB_WIDTH_6 = 22
@@ -115,13 +113,13 @@ class StatWidget(
)
}
val statOptions = if (pokemon.form.riding.behaviours != null) listOf(STATS, IV, EV, RIDE, OTHER) else listOf(STATS, IV, EV, OTHER)
private val statOptions = if (pokemon.form.riding.behaviours != null) listOf(STATS, IV, EV, RIDE, OTHER) else listOf(STATS, IV, EV, OTHER)
var rawStatTabIndex: Int = tabIndex
private var _statTabIndex: Int = tabIndex
var statTabIndex: Int
get() = rawStatTabIndex.coerceIn(0, statOptions.size - 1)
get() = _statTabIndex.coerceIn(0, statOptions.size - 1)
set(value) {
rawStatTabIndex = value.coerceIn(0, statOptions.size - 1)
_statTabIndex = value.coerceIn(0, statOptions.size - 1)
}
private var _rideBehaviourIndex: Int = 0
@@ -304,7 +302,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) || (summary.controllerComponentManager.let { it.active && it.currentSelectedComponent == "center_screen" }))
val canSwitchStyle = behaviours.size > 1 && centerHovered(pMouseX, pMouseY)
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) || isFocused)) {
if (!isOpposing && isHovered(mouseX, mouseY)) {
blitk(
matrixStack = matrices,
texture = hoverBackgroundResource,
@@ -49,7 +49,7 @@ class TradeButton(
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble()) || isFocused) HEIGHT else 0,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
@@ -22,9 +22,6 @@ 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
@@ -58,7 +55,7 @@ class TradeGUI(
val traderName: MutableComponent,
val traderParty: MutableList<TradeablePokemon?>,
val party: MutableList<TradeablePokemon?>
): Screen(lang("trade.gui.title")), CobblemonRenderable, ControllerScreen {
): Screen(lang("trade.gui.title")), CobblemonRenderable {
companion object {
const val BASE_WIDTH = 293
@@ -121,13 +118,6 @@ class TradeGUI(
var tradeSoundInstance: SoundInstance? = null
val controllerComponentManager = ControllerComponentManager(this)
var lastSelectedRow = 0
override fun getComponentManager(): ControllerComponentManager {
return controllerComponentManager
}
init {
trade.cancelEmitter.subscribe {
cancelTradeSound()
@@ -188,25 +178,21 @@ class TradeGUI(
)
// Trade Button
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(
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) {
@@ -224,7 +210,7 @@ class TradeGUI(
}
val pokemon = party[partyIndex]
val slot = PartySlot(
PartySlot(
x = slotX,
y = slotY,
pokemon = pokemon,
@@ -237,22 +223,6 @@ 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
@@ -287,10 +257,6 @@ 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) {
@@ -479,8 +445,6 @@ 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 {
@@ -1,363 +0,0 @@
/*
* 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
}
}
}
@@ -1,247 +0,0 @@
/*
* 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)
}
}
@@ -1,68 +0,0 @@
/*
* 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()
}
}
}
}
@@ -1,55 +0,0 @@
/*
* 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 }
}
}
@@ -1,163 +0,0 @@
/*
* 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
}
}
@@ -1,97 +0,0 @@
/*
* 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
)
}
}
@@ -1,34 +0,0 @@
/*
* 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()
}
}
}
@@ -0,0 +1,56 @@
/*
* 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.render
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.math.Axis
import kotlin.math.min
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.resources.ResourceLocation
import org.joml.Matrix4f
object SilkLineRenderer {
private val TEXTURE = ResourceLocation.withDefaultNamespace("textures/block/tripwire.png")
private const val WIDTH = 0.5F / 16F
private const val V0 = 2F / 16F
private const val V1 = 4F / 16F
private const val SEGMENT_LENGTH = 4F / 16F
fun renderStrip(poseStack: PoseStack, buffer: MultiBufferSource, bottom: Float, top: Float, light: Int) {
poseStack.pushPose()
poseStack.mulPose(Axis.YP.rotationDegrees(-Minecraft.getInstance().gameRenderer.mainCamera.yRot))
val consumer = buffer.getBuffer(RenderType.entityCutoutNoCull(TEXTURE))
val matrix = poseStack.last().pose()
val half = WIDTH / 2
var segmentStart = bottom
while (segmentStart < top) {
val segmentEnd = min(segmentStart + SEGMENT_LENGTH, top)
val u = (segmentEnd - segmentStart) / SEGMENT_LENGTH
vertex(consumer, matrix, -half, segmentStart, 0F, V0, light)
vertex(consumer, matrix, -half, segmentEnd, u, V0, light)
vertex(consumer, matrix, half, segmentEnd, u, V1, light)
vertex(consumer, matrix, half, segmentStart, 0F, V1, light)
segmentStart = segmentEnd
}
poseStack.popPose()
}
private fun vertex(consumer: VertexConsumer, matrix: Matrix4f, x: Float, y: Float, u: Float, v: Float, light: Int) {
consumer.addVertex(matrix, x, y, 0F)
.setColor(255, 255, 255, 255)
.setUv(u, v)
.setOverlay(OverlayTexture.NO_OVERLAY)
.setLight(light)
.setNormal(0F, 1F, 0F)
}
}
@@ -0,0 +1,49 @@
/*
* 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.render.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.block.entity.SilkThreadBlockEntity
import com.cobblemon.mod.common.client.render.SilkLineRenderer
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.core.BlockPos
import net.minecraft.world.level.Level
import net.minecraft.world.phys.AABB
class SilkThreadRenderer(ctx: BlockEntityRendererProvider.Context) : BlockEntityRenderer<SilkThreadBlockEntity> {
override fun render(
entity: SilkThreadBlockEntity,
partialTicks: Float,
poseStack: PoseStack,
buffer: MultiBufferSource,
light: Int,
overlay: Int
) {
val world = entity.level ?: return
val pos = entity.blockPos
val bottom = if (world.getBlockState(pos.below()).`is`(CobblemonBlocks.SILK_THREAD)) 0F else findHangingMonCentre(world, pos)
poseStack.pushPose()
poseStack.translate(0.5, 0.0, 0.5)
SilkLineRenderer.renderStrip(poseStack, buffer, bottom, 1F, light)
poseStack.popPose()
}
private fun findHangingMonCentre(world: Level, pos: BlockPos): Float {
val mon = world.getEntitiesOfClass(PokemonEntity::class.java, AABB(pos).expandTowards(0.0, -1.0, 0.0))
.firstOrNull { it.getCurrentPoseType() == PoseType.HANG }
?: return 0F
return (mon.y + mon.bbHeight / 2.0 - pos.y).toFloat()
}
}
@@ -569,7 +569,19 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
* If none exists, return the first pose. Only possible with bad configuration, though.
*/
fun getFirstSuitablePose(state: PosableState, poseType: PoseType?): Pose {
return poses.values.firstOrNull { (poseType == null || poseType in it.poseTypes) && it.isSuitable(state) } ?: poses.values.first()
val exact = poses.values.firstOrNull { (poseType == null || poseType in it.poseTypes) && it.isSuitable(state) }
if (exact != null) {
return exact
}
// Clinging mons without a dedicated pose use their ground idle, which the renderer
// rotates against the surface, rather than whatever pose happens to be first.
if (poseType == PoseType.CLING || poseType == PoseType.CLING_CEILING) {
val standing = poses.values.firstOrNull { PoseType.STAND in it.poseTypes && it.isSuitable(state) }
if (standing != null) {
return standing
}
}
return poses.values.first()
}
/**
@@ -46,6 +46,7 @@ import net.minecraft.client.Minecraft
import net.minecraft.client.multiplayer.ClientLevel
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.commands.arguments.item.ItemParser
import net.minecraft.core.Direction
import net.minecraft.sounds.SoundEvent
import net.minecraft.world.entity.Entity
import net.minecraft.world.item.ItemStack
@@ -274,6 +275,12 @@ abstract class PosableState : Schedulable {
.addFunction("current_aspects") { ArrayStruct(currentAspects.mapIndexed { index, s -> "$index" to StringValue(s)}.toMap())}
.addFunction("has_aspect") { params -> DoubleValue(params.get<MoValue>(0).asString() in currentAspects) }
.addFunction("has_entity") { DoubleValue(getEntity() != null) }
.addFunction("entity_id") { DoubleValue(getEntity()?.id?.toDouble() ?: 0.0) }
.addFunction("cling_face") {
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction StringValue("none")
val face = pokemon.entityData.get(PokemonEntity.CLINGING_FACE)
StringValue(if (face == Direction.DOWN) "none" else face.getName())
}
.addFunction("pose") { StringValue(currentPose ?: "") }
.addFunction("sound") { params ->
if (params.get<MoValue>(0) !is StringValue) {
@@ -37,10 +37,25 @@ class MetapodModel(root: ModelPart) : PokemonPosableModel(root) {
animations = arrayOf(bedrock("metapod", "sleep"))
)
registerPose(
poseType = PoseType.HANG,
animations = arrayOf(bedrock("metapod", "hang"))
)
registerPose(
poseType = PoseType.CLING,
animations = arrayOf(bedrock("metapod", "cling_side"))
)
registerPose(
poseType = PoseType.CLING_CEILING,
animations = arrayOf(bedrock("metapod", "cling_ceiling"))
)
standing = registerPose(
poseName = "standing",
quirks = arrayOf(blink),
poseTypes = ALL_POSES - PoseType.SLEEP,
poseTypes = ALL_POSES - PoseType.SLEEP - PoseType.HANG - PoseType.CLING - PoseType.CLING_CEILING,
animations = arrayOf(bedrock("metapod", "ground_idle"))
)
}
@@ -0,0 +1,127 @@
/*
* 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.render.models.blockbench.pokemon.gen2
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.CobblemonPose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.isBattling
import com.cobblemon.mod.common.util.isUnderWater
import com.cobblemon.mod.common.util.isInWater
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class ChinchouModel (root: ModelPart) : PokemonPosableModel(root) {
override val rootPart = root.registerChildWithAllChildren("chinchou")
override var portraitScale = 1.75F
override var portraitTranslation = Vec3(-0.3, -0.8, 0.0)
override var profileScale = 0.65F
override var profileTranslation = Vec3(-0.05, 0.45, 0.0)
lateinit var standing: CobblemonPose
lateinit var walk: CobblemonPose
lateinit var floating: CobblemonPose
lateinit var swimming: CobblemonPose
lateinit var sleep: CobblemonPose
lateinit var watersleep: CobblemonPose
lateinit var battleidle: CobblemonPose
lateinit var waterbattleidle: CobblemonPose
// override val cryAnimation = CryProvider { bedrockStateful("chinchou", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("chinchou", "blink")}
val idleQuirk = quirk(secondsBetweenOccurrences = 60F to 120F) { bedrockStateful("chinchou", "ground_quirk")}
val waterQuirk = quirk(secondsBetweenOccurrences = 60F to 120F) { bedrockStateful("chinchou", "water_idle_quirk")}
sleep = registerPose(
poseName = "sleeping",
poseType = PoseType.SLEEP,
condition = { !it.isInWater },
animations = arrayOf(bedrock("chinchou", "sleep"))
)
watersleep = registerPose(
poseName = "water_sleeping",
poseType = PoseType.SLEEP,
condition = { it.isInWater },
animations = arrayOf(bedrock("chinchou", "water_sleep"))
)
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.UI_POSES + PoseType.STAND,
transformTicks = 10,
condition = { !it.isBattling && !it.isInWater && !it.isUnderWater},
quirks = arrayOf(blink, idleQuirk),
animations = arrayOf(
bedrock("chinchou", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
transformTicks = 10,
poseType = PoseType.WALK,
condition = { !it.isInWater && !it.isUnderWater},
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("chinchou", "ground_walk")
)
)
floating = registerPose(
poseName = "floating",
transformTicks = 10,
poseType = PoseType.FLOAT,
condition = { it.isInWater },
quirks = arrayOf(blink, waterQuirk),
animations = arrayOf(
bedrock("chinchou", "water_idle")
)
)
swimming = registerPose(
poseName = "swimming",
transformTicks = 10,
condition = { it.isInWater },
poseTypes = PoseType.MOVING_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("chinchou", "water_swim"),
)
)
battleidle = registerPose(
poseName = "battle_idle",
poseTypes = PoseType.STATIONARY_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
condition = { it.isBattling && !it.isInWater },
animations = arrayOf(
bedrock("chinchou", "battle_idle")
)
)
waterbattleidle = registerPose(
poseName = "water_battle_idle",
poseTypes = PoseType.STATIONARY_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
condition = { it.isBattling && it.isInWater },
animations = arrayOf(
bedrock("chinchou", "water_battle_idle")
)
)
}
override fun getFaintAnimation(state: PosableState) = if (state.isPosedIn(standing, walk, battleidle, sleep)) bedrockStateful("chinchou", "faint") else if (state.isPosedIn( waterbattleidle, watersleep, floating, swimming )) bedrockStateful("chinchou", "water_faint") else null
}
@@ -368,6 +368,7 @@ object VaryingModelRepository {
inbuilt("magcargo", ::MagcargoModel)
inbuilt("slugma_shiny", ::SlugmaShinyModel)
inbuilt("magcargo_shiny", ::MagcargoShinyModel)
inbuilt("chinchou", ::ChinchouModel)
inbuilt("clamperl", ::ClamperlModel)
inbuilt("huntail", ::HuntailModel)
inbuilt("gorebyss", ::GorebyssModel)
@@ -31,6 +31,7 @@ import com.cobblemon.mod.common.client.render.renderBeaconBeam
import com.cobblemon.mod.common.client.settings.ServerSettings
import com.cobblemon.mod.common.entity.PlatformType
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -54,6 +55,7 @@ import net.minecraft.client.renderer.entity.EntityRendererProvider
import net.minecraft.client.renderer.entity.ItemRenderer
import net.minecraft.client.renderer.entity.MobRenderer
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.core.Direction
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.chat.Style
@@ -303,6 +305,30 @@ class PokemonRenderer(
pPoseStack.scale(scale, scale, scale)
}
override fun setupRotations(entity: PokemonEntity, poseStack: PoseStack, bob: Float, yBodyRot: Float, partialTick: Float, scale: Float) {
super.setupRotations(entity, poseStack, bob, yBodyRot, partialTick, scale)
applyDefaultClingRotation(entity, poseStack, yBodyRot)
}
private fun applyDefaultClingRotation(entity: PokemonEntity, poseStack: PoseStack, yBodyRot: Float) {
val poseType = entity.getCurrentPoseType()
if (poseType != PoseType.CLING && poseType != PoseType.CLING_CEILING) return
val matched = model.posableModel.getPose(poseType)
if (matched != null && PoseType.STAND !in matched.poseTypes) return
val pivot = entity.bbHeight / 2.0
if (poseType == PoseType.CLING) {
val face = entity.entityData.get(PokemonEntity.CLINGING_FACE)
if (face.axis.isHorizontal) {
poseStack.mulPose(Axis.YP.rotationDegrees(yBodyRot - face.toYRot()))
}
val wallDistance = 0.5 - entity.behaviour.clinging.xOffset
poseStack.translate(0.0, 0.0, -(wallDistance - pivot))
}
poseStack.translate(0.0, pivot, 0.0)
poseStack.mulPose(Axis.XP.rotationDegrees(if (poseType == PoseType.CLING) 90F else 180F))
poseStack.translate(0.0, -pivot, 0.0)
}
/**
* Renders a beam between the Cobblemon and the target.
*
@@ -529,4 +555,4 @@ class PokemonRenderer(
matrixStack.popPose()
}
}
}
@@ -27,6 +27,12 @@ enum class PoseType {
FLOAT,
SWIM,
GLIDE,
/** A pose for hanging beneath a block surface.... probably only ever with our string block entity idk */
HANG,
/** A pose for clinging to the face of an adjacent block. Maybe we want monke to cling to trees or from leaves */
CLING,
/** A pose for clinging to the underside of a block above, different from thread hanging. */
CLING_CEILING,
/** A pose for rendering on the left shoulder. Pose animations are given the player head yaw, pitch, and ageInTicks. */
SHOULDER_LEFT,
/** A pose for rendering on the right shoulder. Pose animations are given the player head yaw, pitch, and ageInTicks. */
@@ -49,7 +55,7 @@ enum class PoseType {
val UI_POSES = EnumSet.of(PROFILE, PORTRAIT)
val MOVING_POSES = EnumSet.of(WALK, SWIM, FLY)
val STATIONARY_POSES = EnumSet.of(STAND, FLOAT, HOVER)
val NO_GRAV_POSES = EnumSet.of(FLY, HOVER, SWIM)
val NO_GRAV_POSES = EnumSet.of(FLY, HOVER, SWIM, HANG, CLING, CLING_CEILING)
fun identified() : List<ResourceLocation> {
return ALL_POSES.stream()
@@ -13,8 +13,6 @@ import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer
import com.cobblemon.mod.common.util.getMemorySafely
import com.cobblemon.mod.common.util.toBlockPos
import com.google.common.collect.ImmutableMap
@@ -57,6 +55,15 @@ class FollowHerdLeaderTask : Behavior<PokemonEntity>(
override fun tick(level: ServerLevel, entity: PokemonEntity, gameTime: Long) {
val leader = leader ?: return
if (entity.isSleepClinging || entity.isSettlingToSleep) {
return // Settled or settling sleepers stay put until they wake on their own
}
// Drowsy followers break off to find a sleeping place
if (entity.brain.getMemorySafely(CobblemonMemories.POKEMON_DROWSY).orElse(false)) {
return
}
if (entity.brain.hasMemoryValue(MemoryModuleType.WALK_TARGET)) {
return // We're busy goin' n' shit
}
@@ -135,16 +142,12 @@ class FollowHerdLeaderTask : Behavior<PokemonEntity>(
)
)
} else if (leader.getCurrentPoseType() == PoseType.SLEEP) {
// Leader is sleeping, if I'm drowsy and can sleep here then I will sleep too.
if (entity.canSleepAt(entity.blockPosition().offset(0, -1, 0)) && entity.brain.getMemorySafely(CobblemonMemories.POKEMON_DROWSY).orElse(false)) {
if (entity.random.nextFloat() < 1 / 40F) {
applySleep(entity)
}
}
} else if (entity.brain.getMemorySafely(CobblemonMemories.POKEMON_SLEEPING).orElse(false)) {
// Leader is awake, so we should be awake too.
if (entity.random.nextFloat() < 1 / 15F) {
removeSleep(entity)
// Leader is sleeping, so followers get drowsy faster and settle via the usual sleep tasks.
if (entity.behaviour.resting.canSleep &&
!entity.brain.getMemorySafely(CobblemonMemories.POKEMON_DROWSY).orElse(false) &&
entity.random.nextFloat() < 1 / 40F
) {
entity.brain.setMemory(CobblemonMemories.POKEMON_DROWSY, true)
}
}
}
@@ -168,11 +171,6 @@ class FollowHerdLeaderTask : Behavior<PokemonEntity>(
return offset
}
fun applySleep(entity: PokemonEntity) {
entity.brain.setMemory(CobblemonMemories.POKEMON_SLEEPING, true)
entity.pokemon.status = PersistentStatusContainer(Statuses.SLEEP)
}
fun removeSleep(entity: PokemonEntity) {
if (entity.brain.hasMemoryValue(CobblemonMemories.POKEMON_SLEEPING)) {
entity.brain.eraseMemory(CobblemonMemories.POKEMON_SLEEPING)
@@ -180,6 +178,9 @@ class FollowHerdLeaderTask : Behavior<PokemonEntity>(
if (status != null && status.status == Statuses.SLEEP) {
entity.pokemon.status = null
}
// Without this the woken follower stays in the sleeping activity, where neither the
// wake task (blocked while drowsy) nor the settle task (not registered there) can run.
entity.brain.useDefaultActivity()
}
}
}
@@ -43,6 +43,7 @@ import com.cobblemon.mod.common.api.pokemon.feature.ChoiceSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.FlagSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.StringSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.reactive.ObservableSubscription
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.riding.Rideable
@@ -62,12 +63,14 @@ import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.api.scheduling.afterOnServer
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.BagItems
import com.cobblemon.mod.common.battles.BattleBuilder
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.SuccessfulBattleStart
import com.cobblemon.mod.common.block.SilkThreadBlock
import com.cobblemon.mod.common.block.entity.PokemonPastureBlockEntity
import com.cobblemon.mod.common.client.MountedCameraTypeHandler
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
@@ -78,6 +81,7 @@ import com.cobblemon.mod.common.entity.generic.GenericBedrockEntity
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
import com.cobblemon.mod.common.entity.pokemon.ai.SleepDebug
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.PokemonItemSensor
import com.cobblemon.mod.common.entity.pokemon.effects.EffectTracker
import com.cobblemon.mod.common.entity.pokemon.effects.IllusionEffect
@@ -212,6 +216,7 @@ open class PokemonEntity(
@JvmStatic val FREEZE_FRAME = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.FLOAT)
@JvmStatic val CAUGHT_BALL = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.STRING)
@JvmStatic val EVOLUTION_STARTED = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.BOOLEAN)
@JvmStatic val CLINGING_FACE = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.DIRECTION)
@JvmStatic var SHOWN_HELD_ITEM = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.ITEM_STACK)
@JvmStatic var RIDE_BOOSTS = SynchedEntityData.defineId(PokemonEntity::class.java, RideBoostsDataSerializer)
@JvmStatic var RIDE_STAMINA = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.FLOAT)
@@ -221,6 +226,7 @@ open class PokemonEntity(
const val BATTLE_LOCK = "battle"
const val EVOLUTION_LOCK = "evolving"
const val CLING_SPACE_BLOCKS = 2
const val FALL_DAMAGE_MULT_FLYINGTYPE = 0.5f
const val FALL_DAMAGE_MULT_MOVEMENT_FLY = 0.2f
@@ -423,6 +429,14 @@ open class PokemonEntity(
var tickSpawned = 0
var isHangingFromThread = false
var clingingFace: Direction? = null
var isSleepClinging = false
var isSettlingToSleep = false
var occupiedSeats = mutableMapOf<Seat, Entity>()
init {
@@ -462,6 +476,7 @@ open class PokemonEntity(
builder.define(FREEZE_FRAME, -1F)
builder.define(CAUGHT_BALL, "")
builder.define(EVOLUTION_STARTED, false)
builder.define(CLINGING_FACE, Direction.DOWN)
builder.define(SHOWN_HELD_ITEM, ItemStack.EMPTY)
builder.define(RIDE_BOOSTS, emptyMap())
builder.define(RIDE_STAMINA, 1F)
@@ -701,6 +716,13 @@ open class PokemonEntity(
evolutionEntity!!.setPos(pokemon.entity!!.x, pokemon.entity!!.y, pokemon.entity!!.z)
pokemon.entity!!.navigation.stop()
}
if (!level().isClientSide) {
tickThreadHanging()
tickClinging()
if (tickCount % 20 == 0) {
SleepDebug.status(this)
}
}
delegate.tick(this)
ticksLived++
@@ -943,6 +965,13 @@ open class PokemonEntity(
if (!enablePoseTypeRecalculation) {
nbt.putBoolean(DataKeys.POKEMON_RECALCULATE_POSE, enablePoseTypeRecalculation)
}
if (isHangingFromThread) {
nbt.putBoolean(DataKeys.POKEMON_HANGING_FROM_THREAD, true)
}
clingingFace?.let { nbt.putByte(DataKeys.POKEMON_CLINGING_FACE, it.get3DDataValue().toByte()) }
if (isSleepClinging) {
nbt.putBoolean(DataKeys.POKEMON_SLEEP_CLINGING, true)
}
val dataResult = this.brain.serializeStart(NbtOps.INSTANCE)
dataResult.resultOrPartial(::error).ifPresent { brain ->
nbt.put("Brain", brain)
@@ -1036,6 +1065,20 @@ open class PokemonEntity(
if (nbt.contains(DataKeys.POKEMON_COUNTS_TOWARDS_SPAWN_CAP)) {
countsTowardsSpawnCap = nbt.getBoolean(DataKeys.POKEMON_COUNTS_TOWARDS_SPAWN_CAP)
}
isHangingFromThread = nbt.getBoolean(DataKeys.POKEMON_HANGING_FROM_THREAD)
clingingFace = if (nbt.contains(DataKeys.POKEMON_CLINGING_FACE)) {
Direction.from3DDataValue(nbt.getByte(DataKeys.POKEMON_CLINGING_FACE).toInt())
} else {
null
}
entityData.set(CLINGING_FACE, clingingFace ?: Direction.DOWN)
isSleepClinging = nbt.getBoolean(DataKeys.POKEMON_SLEEP_CLINGING)
// The saved pose can be stale for attached mons so try to it from the attachment
when {
isHangingFromThread -> entityData.set(POSE_TYPE, PoseType.HANG)
clingingFace == Direction.UP -> entityData.set(POSE_TYPE, PoseType.CLING_CEILING)
clingingFace?.axis?.isHorizontal == true -> entityData.set(POSE_TYPE, PoseType.CLING)
}
if (nbt.contains(DataKeys.POKEMON_RECALCULATE_POSE)) {
enablePoseTypeRecalculation = nbt.getBoolean(DataKeys.POKEMON_RECALCULATE_POSE)
}
@@ -1341,6 +1384,12 @@ open class PokemonEntity(
override fun hurt(source: DamageSource, amount: Float): Boolean {
return if (super.hurt(source, amount)) {
if (clingingFace != null || isHangingFromThread) {
SleepDebug.log(this, "hurt by ${source.msgId} while attached")
}
releaseFromThread()
releaseFromClinging()
effects.mockEffect?.takeIf { it is IllusionEffect && this.battleId == null }?.end(this)
if (this.health == 0F) {
@@ -1353,6 +1402,172 @@ open class PokemonEntity(
} else false
}
fun startHangingFromThread() {
isHangingFromThread = true
isNoGravity = true
setDeltaMovement(Vec3.ZERO)
navigation.stop()
}
fun releaseFromThread() {
if (!isHangingFromThread) return
isHangingFromThread = false
breakThreadColumn()
isNoGravity = false
}
private fun tickThreadHanging() {
if (!isHangingFromThread) return
val isSupported = if (behaviour.hanging.usesThread) {
level().getBlockState(blockPosition()).`is`(CobblemonBlocks.SILK_THREAD)
} else {
hasHangAnchorAbove()
}
if (isEvolving || !isSupported) {
releaseFromThread()
return
}
setDeltaMovement(Vec3.ZERO)
navigation.stop()
brain.eraseMemory(MemoryModuleType.WALK_TARGET)
}
private fun hasHangAnchorAbove(): Boolean {
var pos = blockPosition().above()
repeat(CLING_SPACE_BLOCKS) {
val state = level().getBlockState(pos)
if (!state.isAir) {
return SilkThreadBlock.isValidAnchor(state)
}
pos = pos.above()
}
return false
}
private fun breakThreadColumn() {
val world = level() as? ServerLevel ?: return
var pos = blockPosition()
repeat(SilkThreadBlock.MAX_LENGTH) {
if (world.getBlockState(pos).`is`(CobblemonBlocks.SILK_THREAD)) {
world.destroyBlock(pos, true)
}
pos = pos.above()
}
}
fun startClinging(face: Direction) {
clingingFace = face
entityData.set(CLINGING_FACE, face)
isNoGravity = true
setDeltaMovement(Vec3.ZERO)
navigation.stop()
val column = blockPosition()
val cling = behaviour.clinging
val faceY = findCeilingFaceY()
// Things get a bit weird with mons of different sizes so we try to get the size ratio to make the adjustment right on the clingg
val sizeRatio = bbHeight / (exposedForm.hitbox.height * exposedForm.baseScale)
val newY = if (face == Direction.UP) (faceY ?: column.above().y.toDouble()) - bbHeight + cling.yOffset * sizeRatio else y
SleepDebug.log(this, "startClinging face=$face faceY=$faceY bbHeight=$bbHeight yOffset=${cling.yOffset} sizeRatio=$sizeRatio newY=$newY fromY=$y")
setPos(
column.x + 0.5 + face.stepX * cling.xOffset,
newY,
column.z + 0.5 + face.stepZ * cling.xOffset
)
faceClingedBlock(face)
}
/** The block above the feet is not the ceiling for mons taller than a block, so the snap
* scans up from the head for the first collision block available */
private fun findCeilingFaceY(): Double? {
var pos = BlockPos.containing(x, y + bbHeight, z)
repeat(CLING_SPACE_BLOCKS + 1) {
if (!level().getBlockState(pos).getCollisionShape(level(), pos).isEmpty) {
return pos.y.toDouble()
}
pos = pos.above()
}
return null
}
fun releaseFromClinging() {
if (clingingFace == null) return
clingingFace = null
isSleepClinging = false
entityData.set(CLINGING_FACE, Direction.DOWN)
if (behaviour.moving.fly.canFly) {
setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
} else {
isNoGravity = false
}
}
fun canClingTo(face: Direction, state: BlockState): Boolean {
val cling = behaviour.clinging
val isFaceAllowed = when {
face == Direction.UP -> cling.canClingToCeilings
face.axis.isHorizontal -> cling.canClingToWalls
else -> false
}
if (!isFaceAllowed) return false
return cling.blocks.isEmpty() || cling.blocks.any { it.fits(state.block, level().blockRegistry) }
}
private fun tickClinging() {
val face = clingingFace ?: return
val isSupported = if (face == Direction.UP) {
hasClingCeilingAbove()
} else {
val supportPos = blockPosition().relative(face)
val state = level().getBlockState(supportPos)
state.isFaceSturdy(level(), supportPos, face.opposite) && (isSleepClinging || canClingTo(face, state))
}
val isRoused = !brain.getMemorySafely(CobblemonMemories.POKEMON_DROWSY).orElse(false) &&
pokemon.storeCoordinates.get()?.store !is PartyStore
val isSleepOver = isSleepClinging && (pokemon.status?.status != Statuses.SLEEP || isRoused)
if (isEvolving || !isSupported || isSleepOver) {
SleepDebug.log(this, "released from clinging: supported=$isSupported sleepOver=$isSleepOver roused=$isRoused evolving=$isEvolving")
if (isSleepOver) {
// we want to make sure we clear the sleep from the mon (might happen already somewhere else so this might not be needed)
if (pokemon.status?.status == Statuses.SLEEP) {
pokemon.status = null
}
brain.eraseMemory(CobblemonMemories.POKEMON_SLEEPING)
brain.useDefaultActivity()
}
releaseFromClinging()
return
}
if (isSleepClinging && !brain.getMemorySafely(CobblemonMemories.POKEMON_SLEEPING).orElse(false)) {
brain.setMemory(CobblemonMemories.POKEMON_SLEEPING, true)
brain.setActiveActivityToFirstValid(listOf(CobblemonActivities.POKEMON_SLEEPING_ACTIVITY))
}
setDeltaMovement(Vec3.ZERO)
navigation.stop()
brain.eraseMemory(MemoryModuleType.WALK_TARGET)
faceClingedBlock(face)
}
private fun hasClingCeilingAbove(): Boolean {
var pos = BlockPos.containing(x, y + bbHeight, z)
repeat(CLING_SPACE_BLOCKS) {
val state = level().getBlockState(pos)
if (!state.getCollisionShape(level(), pos).isEmpty) {
if (isSleepClinging) return true
return state.isFaceSturdy(level(), pos, Direction.DOWN) && canClingTo(Direction.UP, state)
}
pos = pos.above()
}
return false
}
private fun faceClingedBlock(face: Direction) {
if (!face.axis.isHorizontal) return
val yaw = face.toYRot()
yRot = yaw
yBodyRot = yaw
yHeadRot = yaw
}
override fun shouldBeSaved(): Boolean {
if (ownerUUID == null && !pokemon.isNPCOwned() && (Cobblemon.config.savePokemonToWorld || isPersistenceRequired)) {
CobblemonEvents.POKEMON_ENTITY_SAVE_TO_WORLD.postThen(PokemonEntitySaveToWorldEvent(this)) {
@@ -1785,6 +2000,10 @@ open class PokemonEntity(
removalObservable.emit(reason)
this.brain.clearMemories()
if (reason.shouldDestroy()) {
releaseFromThread()
}
if (reason.shouldDestroy() && pokemon.tetheringId != null) {
pokemon.tetheringId = null
}
@@ -1975,6 +2194,7 @@ open class PokemonEntity(
}
override fun isPushable(): Boolean {
if (isSettlingToSleep || isHangingFromThread || clingingFace != null) return false
return beamMode != 3 && super.isPushable()
}
@@ -31,6 +31,7 @@ import com.cobblemon.mod.common.pokemon.activestate.ActivePokemonState
import com.cobblemon.mod.common.pokemon.activestate.SentOutState
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import net.minecraft.core.Direction
import net.minecraft.network.chat.Component
import net.minecraft.network.syncher.EntityDataAccessor
import net.minecraft.server.level.ServerLevel
@@ -390,6 +391,9 @@ class PokemonServerDelegate : PokemonSideDelegate {
val poseType = when {
isPassenger -> PoseType.STAND
entity.isHangingFromThread -> PoseType.HANG
entity.clingingFace == Direction.UP -> PoseType.CLING_CEILING
entity.clingingFace?.axis?.isHorizontal == true -> PoseType.CLING
isSleeping -> PoseType.SLEEP
isMoving && isUnderwater -> PoseType.SWIM
isUnderwater -> PoseType.FLOAT
@@ -492,4 +496,4 @@ class PokemonServerDelegate : PokemonSideDelegate {
entity.z + offset.z
)
}
}
}
@@ -8,42 +8,415 @@
package com.cobblemon.mod.common.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonActivities
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
import com.cobblemon.mod.common.entity.pokemon.ai.SleepDebug
import com.cobblemon.mod.common.pokemon.ai.SleepPlace
import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer
import com.cobblemon.mod.common.util.PositionType
import com.cobblemon.mod.common.util.blockRegistry
import com.cobblemon.mod.common.util.canEntityStayAt
import com.cobblemon.mod.common.util.getBlockPositions
import net.minecraft.world.entity.ai.behavior.OneShot
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import com.cobblemon.mod.common.util.getMemorySafely
import com.google.common.collect.ImmutableMap
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.Mth
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.level.EmptyBlockGetter
import net.minecraft.world.level.LightLayer
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
object FindRestingPlaceTask {
fun create(horizontalSearchDistance: Int, verticalSearchDistance: Int): OneShot<PokemonEntity> {
return BehaviorBuilder.create {
it.group(
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.POKEMON_DROWSY),
it.absent(CobblemonMemories.PATH_COOLDOWN)
).apply(it) { walkTarget, pokemonDrowsy, restPathCooldown ->
Trigger { world, entity, _ ->
return@Trigger if (it.get(pokemonDrowsy) && entity.pokemon.status?.status != Statuses.SLEEP) {
entity.brain.setMemoryWithExpiry(CobblemonMemories.PATH_COOLDOWN, true, 80)
val position = entity.level()
.getBlockPositions(AABB.ofSize(entity.position(), horizontalSearchDistance.toDouble(), verticalSearchDistance.toDouble(), horizontalSearchDistance.toDouble()))
.filter(entity::canSleepAt)
// .randomOrNull()
.minByOrNull { it.distToCenterSqr(entity.position()) }
if (position != null) {
walkTarget.set(WalkTarget(position.below(), 0.3F, 1))
}
true
} else {
false
class FindRestingPlaceTask(
private val horizontalSearchDistance: Int,
private val verticalSearchDistance: Int
) : Behavior<PokemonEntity>(
ImmutableMap.of(
MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT,
CobblemonMemories.POKEMON_DROWSY, MemoryStatus.VALUE_PRESENT,
CobblemonMemories.PATH_COOLDOWN, MemoryStatus.VALUE_ABSENT
),
600
) {
private var targetPos = BlockPos.ZERO
private var targetFace: Direction? = null
private var targetPlace: SleepPlace? = null
private var stallTicks = 0
private var lastApproachPos = Vec3.ZERO
private var failedSpot: BlockPos? = null
private var failedSpotExpiry = 0L
override fun checkExtraStartConditions(world: ServerLevel, entity: PokemonEntity): Boolean {
if (entity.isBattling || entity.clingingFace != null || entity.isHangingFromThread) return false
if (entity.pokemon.status?.status == Statuses.SLEEP) {
if (world.gameTime % 40 == 0L) SleepDebug.log(entity, "task blocked: already asleep")
return false
}
// Navigation refuses to compute paths for airborne mons that arent flagged as flying so
// the search waits for a pathable moment and looks for valid spots
if (!entity.onGround() && !entity.isInWater && !entity.getBehaviourFlag(PokemonBehaviourFlag.FLYING)) return false
if ((world.gameTime + entity.id) % SEARCH_INTERVAL != 0L) return false
val target = findTarget(world, entity)
if (target == null) {
SleepDebug.log(entity, "no resting spot found")
return false
}
SleepDebug.log(entity, "chose spot=${target.first.toShortString()} face=${target.second ?: "ground"}")
targetPos = target.first
targetFace = target.second
return true
}
override fun start(world: ServerLevel, entity: PokemonEntity, time: Long) {
SleepDebug.log(entity, "task start target=${targetPos.toShortString()} face=${targetFace ?: "ground"}")
entity.isSettlingToSleep = true
stallTicks = 0
lastApproachPos = entity.position()
entity.brain.setMemoryWithExpiry(CobblemonMemories.PATH_COOLDOWN, true, 80)
}
override fun canStillUse(world: ServerLevel, entity: PokemonEntity, time: Long): Boolean {
return entity.isSettlingToSleep &&
entity.pokemon.status?.status != Statuses.SLEEP &&
entity.brain.getMemorySafely(CobblemonMemories.POKEMON_DROWSY).orElse(false)
}
override fun tick(world: ServerLevel, entity: PokemonEntity, time: Long) {
if (targetFace == null) {
tickGroundApproach(entity)
return
}
if (targetFace == Direction.UP) {
tickCeilingApproach(world, entity)
return
}
if (!hasArrived(entity)) {
walkTowardsTarget(entity, completionRange = 1)
return
}
entity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
settleAgainstWall(world, entity)
}
/** Mons will try to get to the air block below the ceiling and if they are large they might need a little shift into it to get the task running */
private fun tickCeilingApproach(world: ServerLevel, entity: PokemonEntity) {
if (!isValidCeiling(world, targetPos.above(), targetPlace) || isSpotClaimed(world, entity, targetPos)) {
SleepDebug.log(entity, "ceiling approach aborted at ${targetPos.toShortString()}: ceiling gone or spot claimed")
entity.isSettlingToSleep = false
return
}
// A mon who can not progress should ideely wait for a bit to check for any new spots
stallTicks = if (entity.position().distanceToSqr(lastApproachPos) < 0.001) stallTicks + 1 else 0
lastApproachPos = entity.position()
if (stallTicks > STALL_GIVE_UP_TICKS) {
SleepDebug.log(entity, "approach stalled, shunning ${targetPos.toShortString()}")
failedSpot = targetPos
failedSpotExpiry = world.gameTime + FAILED_SPOT_MEMORY_TICKS
entity.isSettlingToSleep = false
return
}
val dx = targetPos.x + 0.5 - entity.x
val dz = targetPos.z + 0.5 - entity.z
val horizontalSqr = dx * dx + dz * dz
val isAtColumn = horizontalSqr <= COLUMN_RADIUS_SQR
if (world.gameTime % 10 == 0L) {
SleepDebug.log(entity, "ceiling approach target=${targetPos.toShortString()}" +
" dx=%.2f dz=%.2f headGap=%.2f".format(dx, dz, targetPos.y + 1.0 - (entity.y + entity.bbHeight)) +
" atColumn=$isAtColumn ceilingAboveHead=${isUnderTargetCeiling(world, entity)} navDone=${entity.navigation.isDone}" +
" spotBlock=${world.getBlockState(targetPos).block.name.string}" +
" ceilingBlock=${world.getBlockState(targetPos.above()).block.name.string}" +
" feetPosBlock=${world.getBlockState(hangPos(entity)).block.name.string}")
}
if (isAtColumn && entity.y + entity.bbHeight >= targetPos.y && isUnderTargetCeiling(world, entity)) {
SleepDebug.log(entity, "grabbing ceiling at ${targetPos.toShortString()}")
entity.setDeltaMovement(Vec3.ZERO)
settle(entity, Direction.UP)
return
}
// try to nudge to the air space below the ceiling here, but rather than nudge them we might need to check a radius from the tardet area instead and snap to that location?
// Currently this looks smoother.... but we might need to do more testing.
if (horizontalSqr <= DRIFT_RADIUS_SQR && entity.navigation.isDone) {
if (world.gameTime % 10 == 0L) SleepDebug.log(entity, "nav stalled near column, drifting in")
entity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
haltSteering(entity)
if (entity.behaviour.moving.fly.canFly) {
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
}
val remaining = targetPos.y + 1.0 - (entity.y + entity.bbHeight)
entity.setDeltaMovement(dx * 0.1, min(0.15, remaining), dz * 0.1)
return
}
walkTowardsTarget(entity, completionRange = 0, pos = hangPos(entity))
}
/** Block position of where our mons feets will be when hanging from and wanting to go towards */
private fun hangPos(entity: PokemonEntity, spot: BlockPos = targetPos): BlockPos =
BlockPos.containing(spot.x + 0.5, spot.y + 1.0 - entity.bbHeight, spot.z + 0.5)
/** Try to get a read on where the head of the mon is based on the target ceiling spot for knowing when to stop */
private fun isUnderTargetCeiling(world: ServerLevel, entity: PokemonEntity): Boolean {
val headPos = BlockPos.containing(entity.x, entity.y + entity.bbHeight, entity.z)
return findCeilingAbove(world, headPos.below(), targetPlace) == targetPos.above()
}
private fun tickGroundApproach(entity: PokemonEntity) {
if (entity.canSleepAt(entity.blockPosition().below()) || entity.blockPosition() == targetPos) {
SleepDebug.log(entity, "ground approach done at ${entity.blockPosition().toShortString()}")
entity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
entity.isSettlingToSleep = false
return
}
walkTowardsTarget(entity, completionRange = 0)
}
private fun walkTowardsTarget(entity: PokemonEntity, completionRange: Int, pos: BlockPos = targetPos) {
if (!entity.brain.hasMemoryValue(MemoryModuleType.WALK_TARGET)) {
SleepDebug.log(entity, "setting walk target ${pos.toShortString()} range=$completionRange")
entity.brain.setMemory(MemoryModuleType.WALK_TARGET, WalkTarget(pos, WALK_SPEED, completionRange))
}
}
override fun stop(world: ServerLevel, entity: PokemonEntity, time: Long) {
entity.isSettlingToSleep = false
}
private fun hasArrived(entity: PokemonEntity): Boolean {
val dx = targetPos.x + 0.5 - entity.x
val dz = targetPos.z + 0.5 - entity.z
val dy = targetPos.y + 0.5 - entity.y
return dx * dx + dz * dz + dy * dy <= ARRIVAL_DISTANCE_SQR
}
/** The move control keeps steering towards its last wanted position even after the navigation
* stops and it ticks after the brain so we have to stop it otehrwise it overwrites the nudging into placement */
private fun haltSteering(entity: PokemonEntity) {
entity.navigation.stop()
(entity.moveControl as? PokemonMoveControl)?.stop()
}
private fun settleAgainstWall(world: ServerLevel, entity: PokemonEntity) {
val face = targetFace ?: return
val place = targetPlace
if (place == null || !wallFits(world, entity, place, targetPos, face) || isSpotClaimed(world, entity, targetPos, claimSpacing(face))) {
SleepDebug.log(entity, "wall settle aborted at ${targetPos.toShortString()}: wall gone or spot claimed")
entity.isSettlingToSleep = false
return
}
entity.setPos(targetPos.x + 0.5, targetPos.y.toDouble(), targetPos.z + 0.5)
settle(entity, face)
}
private fun settle(entity: PokemonEntity, face: Direction) {
SleepDebug.log(entity, "settled on face=$face at ${targetPos.toShortString()}")
entity.startClinging(face)
entity.isSleepClinging = true
entity.isSettlingToSleep = false
if (entity.pokemon.status?.status != Statuses.SLEEP) {
entity.pokemon.status = PersistentStatusContainer(Statuses.SLEEP)
entity.brain.setMemory(CobblemonMemories.POKEMON_SLEEPING, true)
entity.brain.setActiveActivityToFirstValid(listOf(CobblemonActivities.POKEMON_SLEEPING_ACTIVITY))
}
}
private fun findTarget(world: ServerLevel, entity: PokemonEntity): Pair<BlockPos, Direction?>? {
val searchBox = AABB.ofSize(entity.position(), horizontalSearchDistance.toDouble(), verticalSearchDistance.toDouble(), horizontalSearchDistance.toDouble())
for (place in entity.behaviour.resting.places) {
val found = searchPlace(world, entity, place, searchBox)
if (found != null) {
targetPlace = place
return found
}
}
targetPlace = null
entity.brain.setMemoryWithExpiry(CobblemonMemories.POKEMON_SLEEP_SEARCH_FAILED, true, PLACE_SEARCH_FAILURE_TICKS)
val ground = world.getBlockPositions(searchBox)
.filter(entity::canSleepAt)
.minByOrNull { it.distToCenterSqr(entity.position()) }
return ground?.let { it.above() to null }
}
private fun searchPlace(world: ServerLevel, entity: PokemonEntity, place: SleepPlace, searchBox: AABB): Pair<BlockPos, Direction?>? {
val spots = mutableListOf<Pair<BlockPos, Direction>>()
if (place.allowsCeiling()) spots += ceilingSpots(world, entity, place, searchBox)
if (place.allowsSides()) spots += wallSpots(world, entity, place, searchBox)
if (place.allowsFloor()) spots += floorSpots(world, entity, place, searchBox)
SleepDebug.log(entity, "place faces=${place.faces} spots=${spots.size}")
// try to do some lazy checks on valid spots
var pathChecks = 0
val match = spots
.sortedBy { (spot, _) -> spot.distToCenterSqr(entity.position()) }
.asSequence()
.filter { (spot, face) ->
if (spot == failedSpot && world.gameTime < failedSpotExpiry) return@filter false
val hasBodyRoom = face != Direction.UP || bodyFits(
world, entity, spot.x + 0.5,
spot.y + 1.0 - entity.bbHeight + entity.behaviour.clinging.yOffset, spot.z + 0.5
)
hasBodyRoom && fitsPlaceSpace(world, entity, place, spot) &&
!isSpotClaimed(world, entity, spot, claimSpacing(face))
}
.takeWhile { pathChecks < REACHABILITY_CHECKS }
.firstOrNull { (spot, face) ->
pathChecks++
val navPos = if (face == Direction.UP) hangPos(entity, spot) else spot
val path = entity.navigation.createPath(navPos, 0)
val endPos = path?.endNode?.asBlockPos()
SleepDebug.log(entity, "candidate ${spot.toShortString()} face=$face navPos=${navPos.toShortString()}" +
" path=${path?.let { p -> "${if (p.canReach()) "reaches" else "partial"} end=${endPos?.toShortString()} endDistSqr=${endPos?.distSqr(navPos)}" } ?: "null"}")
path != null
}
return match?.let { (spot, face) -> spot to face.takeUnless { it == Direction.DOWN } }
}
/** Upwawrd scans from each area scan around the mon looking for open air spots with solid
* blocks above them, but ignoring ones below floors stood on to make it have an easier time*/
private fun ceilingSpots(world: ServerLevel, entity: PokemonEntity, place: SleepPlace, searchBox: AABB): List<Pair<BlockPos, Direction>> {
val spots = mutableListOf<Pair<BlockPos, Direction>>()
val feetY = entity.blockPosition().y
val boxTop = Mth.floor(searchBox.maxY)
val pos = BlockPos.MutableBlockPos()
for (x in Mth.floor(searchBox.minX)..Mth.floor(searchBox.maxX)) {
for (z in Mth.floor(searchBox.minZ)..Mth.floor(searchBox.maxZ)) {
var seenAirInBox = false
for (y in Mth.floor(searchBox.minY)..(boxTop + MAX_ASCENT)) {
pos.set(x, y, z)
val state = world.getBlockState(pos)
if (state.getCollisionShape(world, pos).isEmpty) {
if (y <= boxTop && state.isAir) seenAirInBox = true
continue
}
if (seenAirInBox && y >= feetY && isValidCeiling(world, pos, place)) {
spots += BlockPos(x, y - 1, z) to Direction.UP
}
seenAirInBox = false
}
}
}
SleepDebug.log(entity, "ceilings=${spots.size}")
return spots
}
private fun bodyFits(world: ServerLevel, entity: PokemonEntity, centerX: Double, minY: Double, centerZ: Double): Boolean {
val half = entity.bbWidth / 2.0
val box = AABB(centerX - half, minY, centerZ - half, centerX + half, minY + entity.bbHeight, centerZ + half)
return world.noCollision(entity, box)
}
/** Fliers may cling anywhere along a wall but want open air below so they perch above the
* ground so we dont see them clipping, but walkers only cling where they can stand beside the wall.
* Might need tweaked so that we add a check for an air block above too? we will see */
private fun wallSpots(world: ServerLevel, entity: PokemonEntity, place: SleepPlace, searchBox: AABB): List<Pair<BlockPos, Direction>> {
val spots = if (entity.behaviour.moving.fly.canFly) {
world.getBlockPositions(searchBox).filter { pos ->
val below = pos.below()
world.getBlockState(pos).isAir && world.getBlockState(below).getCollisionShape(world, below).isEmpty
}
} else {
val width = ceil(entity.bbWidth).toInt()
val height = ceil(entity.bbHeight).toInt()
world.getBlockPositions(searchBox)
.filter { world.canEntityStayAt(it, width, height, PositionType.LAND) }
.map { it.above() }
}
val xOffset = entity.behaviour.clinging.xOffset
return spots.mapNotNull { spot ->
Direction.Plane.HORIZONTAL.firstOrNull { direction ->
wallFits(world, entity, place, spot, direction) &&
bodyFits(world, entity, spot.x + 0.5 + direction.stepX * xOffset, spot.y.toDouble(), spot.z + 0.5 + direction.stepZ * xOffset)
}?.let { spot to it }
}
}
/** We need to try to make sure the mon will fit on the wall it is attached to and that it is sturdy block */
private fun wallFits(world: ServerLevel, entity: PokemonEntity, place: SleepPlace, spot: BlockPos, direction: Direction): Boolean {
val verticalSpan = ceil(max(entity.bbWidth, entity.bbHeight)).toInt().coerceAtLeast(1)
val horizontalSpan = ceil(entity.bbWidth - 0.2).toInt().coerceAtLeast(1)
val sideways = direction.clockWise
for (up in 0 until verticalSpan) {
for (side in 0 until horizontalSpan) {
val wallPos = spot.above(up).relative(direction).relative(sideways, side - (horizontalSpan - 1) / 2)
val state = world.getBlockState(wallPos)
if (!state.isFaceSturdy(world, wallPos, direction.opposite) || !placeBlockMatches(world, place, state)) {
return false
}
}
}
return true
}
private fun floorSpots(world: ServerLevel, entity: PokemonEntity, place: SleepPlace, searchBox: AABB): List<Pair<BlockPos, Direction>> =
world.getBlockPositions(searchBox)
.filter { entity.canSleepAt(it) && placeBlockMatches(world, place, world.getBlockState(it)) }
.map { it.above() to Direction.DOWN }
private fun fitsPlaceSpace(world: ServerLevel, entity: PokemonEntity, place: SleepPlace, spot: BlockPos): Boolean {
val state = world.getBlockState(spot)
if (place.onlyAir && !state.isAir) return false
val rest = entity.behaviour.resting
val light = place.light ?: rest.light
if (world.getMaxLocalRawBrightness(spot) !in light) return false
val canSeeSky = place.canSeeSky ?: rest.canSeeSky
if (canSeeSky != null && world.canSeeSky(spot.above()) != canSeeSky) return false
val skyLight = place.skyLight ?: rest.skyLight
return skyLight == null || world.lightEngine.getLayerListener(LightLayer.SKY).getLightValue(spot) in skyLight
}
private fun findCeilingAbove(world: ServerLevel, from: BlockPos, place: SleepPlace?): BlockPos? {
for (offset in 1..MAX_ASCENT) {
val pos = from.above(offset)
if (world.getBlockState(pos).getCollisionShape(world, pos).isEmpty) continue
return pos.takeIf { isValidCeiling(world, pos, place) }
}
return null
}
/** A place that whitelists blocks may attach to not sturdy undersides like leaves;
* without a whitelist only sturdy undersides count */
private fun isValidCeiling(world: ServerLevel, pos: BlockPos, place: SleepPlace?): Boolean {
val state = world.getBlockState(pos)
if (state.getCollisionShape(world, pos).isEmpty) return false
if (place != null && place.blocks.isNotEmpty()) return placeBlockMatches(world, place, state)
return state.isFaceSturdy(EmptyBlockGetter.INSTANCE, BlockPos.ZERO, Direction.DOWN)
}
private fun placeBlockMatches(world: ServerLevel, place: SleepPlace, state: BlockState): Boolean =
place.blocks.isEmpty() || place.blocks.any { it.fits(state.block, world.blockRegistry) }
/** Wall clingers keep an air block of spacing so they don't overlap along the same surface;
* ceiling sleepers may pack together. */
private fun claimSpacing(face: Direction) = if (face.axis.isHorizontal) 1.0 else 0.0
private fun isSpotClaimed(world: ServerLevel, entity: PokemonEntity, pos: BlockPos, spacing: Double = 0.0): Boolean {
return world.getEntitiesOfClass(PokemonEntity::class.java, AABB(pos).inflate(spacing)) {
it !== entity && (it.clingingFace != null || it.isSettlingToSleep)
}.isNotEmpty()
}
companion object {
fun create(horizontalSearchDistance: Int, verticalSearchDistance: Int) = FindRestingPlaceTask(horizontalSearchDistance, verticalSearchDistance)
private const val MAX_ASCENT = 20
private const val ARRIVAL_DISTANCE_SQR = 4.0
private const val COLUMN_RADIUS_SQR = 0.25
private const val DRIFT_RADIUS_SQR = 4.0
private const val REACHABILITY_CHECKS = 4
private const val SEARCH_INTERVAL = 20L
private const val STALL_GIVE_UP_TICKS = 60
private const val FAILED_SPOT_MEMORY_TICKS = 1200L
private const val PLACE_SEARCH_FAILURE_TICKS = 100L
private const val WALK_SPEED = 0.35F
}
}
@@ -29,7 +29,11 @@ object GoToSleepTask {
it.registered(CobblemonMemories.POKEMON_DROWSY)
).apply(it) { _, pokemonDrowsy ->
Trigger { _, entity, _ ->
if (entity.isSettlingToSleep || entity.clingingFace != null) return@Trigger false
val hasSleepStatus = entity.pokemon.status?.status === Statuses.SLEEP
val wantsPlace = entity.behaviour.resting.places.isNotEmpty() &&
!entity.brain.hasMemoryValue(CobblemonMemories.POKEMON_SLEEP_SEARCH_FAILED)
if (!hasSleepStatus && wantsPlace) return@Trigger false
if (entity.behaviour.resting.canSleep && ((it.tryGet(pokemonDrowsy).orElse(false) && entity.canSleepAt(entity.blockPosition().below())) || hasSleepStatus)) {
if (!hasSleepStatus) {
if (onlyFromStatus) {
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.ai
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.molang.ObjectValue
import net.minecraft.world.level.block.Block
class ClingBehaviour {
val canClingToWalls = false
val canClingToCeilings = false
val blocks = mutableListOf<RegistryLikeCondition<Block>>()
val xOffset = 0.0
val yOffset = 0.0
@Transient
val struct = ObjectValue<ClingBehaviour>(this).also {
it.addFunction("can_cling_to_walls") { DoubleValue(canClingToWalls) }
it.addFunction("can_cling_to_ceilings") { DoubleValue(canClingToCeilings) }
it.addFunction("x_offset") { DoubleValue(xOffset) }
it.addFunction("y_offset") { DoubleValue(yOffset) }
}
}

Some files were not shown because too many files have changed in this diff Show More