Compare commits

..
Author SHA1 Message Date
Plastered_Crab 071f05337c Scale tab in Implementation GUI moves test wool in X direction based on hitbox size of pokemon to prevent overlap 2026-03-02 15:47:59 -08:00
Plastered_Crab e9d274fbfd Scale tab in Implementation GUI renders test wool block nicely 2026-03-02 15:23:45 -08:00
Plastered_Crab 5709527bf2 Merge branch 'main' into ta-daaa 2026-03-02 13:17:58 -08:00
Plastered_Crab bc9f28811d Implementation Tab Animation option improvements 2026-02-19 13:43:41 -08:00
Plastered_Crab ff9eb60cd3 Implementation Tab Animation option improvements 2026-02-19 12:39:39 -08:00
Plastered_Crab de0b53e529 Implementation tabs updated. Walk Speed tab blocks rendering 2026-02-18 16:15:05 -08:00
Plastered_Crab 56ecbfe3c0 Move tab testing 2026-02-16 15:45:35 -08:00
Plastered_Crab 62f03c536d Polishing up of the General Tab 2026-02-16 12:02:54 -08:00
Plastered_Crab 2fa03b6013 Adjust position of elements 2026-02-16 08:40:53 -08:00
Plastered_Crab 59af4053a1 Held Item Item Slot Searching 2026-02-15 22:09:33 -08:00
Plastered_Crab 1aba8c52b6 Held Item Item Slot Renders now 2026-02-15 21:11:23 -08:00
Plastered_Crab 6a386937c8 Held Item Item Slot 2026-02-15 20:01:58 -08:00
Plastered_Crab 00132f6428 Cosmetic Item Slot 2026-02-15 19:10:55 -08:00
Plastered_Crab 7fe222422b Cosmetic Item filtering 2026-02-15 18:37:11 -08:00
Plastered_Crab 812f389231 Cosmetic Item fixes 2026-02-15 18:08:17 -08:00
Plastered_Crab 24acddf750 Aspects Tab adjustments 2026-02-15 16:22:51 -08:00
Plastered_Crab 1d696c58be Abra kadabra 2026-02-15 10:59:27 -08:00
2149 changed files with 7924 additions and 66057 deletions

No files matched your search

-1
View File
@@ -36,7 +36,6 @@ common/src/main/resources/assets/cobblemon/showdown
# Generated files
*/generated/resources/*
/fabric/src/generated/resources/cobblemon-common.accesswidener
/fabric/src/main/generated/*
/.ignored/
common/.fabric
.kotlin/
-11
View File
@@ -3,19 +3,8 @@
### Additions
- Added a new `cobblemon:structure`, which is a feature without any restrictions allowing the Builders to place certain structures in much more specific manner.
- Added Configs for Infinite Use TMs and Unlocking all TMs
- Added Type Gem blocks
- Added Type Gem Clusters
- Added TMs
- Added TM Machine
- Added Deepslate Core Block
- Added /givetm Command
- Added Move Dex to Pokedex
- Added Disk Rack for storing disks and being an audio sequencer for Noteblocks
### Changes
- Data Monitor can accept Disks (TMs, Music Disks, Upgrade, Dubious Disk, etc) to change look
### Fixes
- Fixed rare Pokémon being far too likely to spawn multiple times in a single spawn cycle. The dev told me that the exact bug is too complicated to explain.
-2
View File
@@ -37,7 +37,6 @@
- [Various UFO Ride Improvements](https://gitlab.com/cable-mc/cobblemon/-/merge_requests/2156)
- Fixed NeoForge brewing stand unexpectedly crashing users when shift clicking items within it's menu
- Fixed NPCs switching pokemon very often during battles
- Fixed Campfire pot causing crashes under certain circumstances
### Developer
- Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters
@@ -47,7 +46,6 @@
- q.item.is_enchanted()
- q.item.has_enchantment(minecraft:sharpness, 3)
- Added `has_chosen_starter` & `get_starter_uuid` as available Molang functions
- Added `marks`, `has_mark`, & `remove_marks` as available Molang functions for pokemon
### Localization
- Updated translations for:
@@ -1,52 +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;
import com.cobblemon.mod.common.block.entity.TMShelfBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.NoteBlock;
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(NoteBlock.class)
public abstract class NoteBlockMixin {
@Inject(method = "triggerEvent", at = @At("HEAD"), cancellable = true)
private void cobblemon$triggerTMShelfSequencer(BlockState state, Level level, BlockPos pos, int type, int data, CallbackInfoReturnable<Boolean> cir) {
final BlockPos shelfPos = pos.below();
final BlockEntity blockEntity = level.getBlockEntity(shelfPos);
if (!(blockEntity instanceof TMShelfBlockEntity tmShelfBlockEntity)) {
return;
}
if (!level.isClientSide) {
final BlockPos instrumentPos = shelfPos.below();
final SoundEvent soundEvent;
if (level.getBlockState(instrumentPos).isAir()) {
soundEvent = SoundEvents.NOTE_BLOCK_BIT.value();
} else {
final NoteBlockInstrument instrument = level.getBlockState(instrumentPos).instrument();
final Holder<SoundEvent> holder = instrument.getSoundEvent();
soundEvent = holder.value();
}
tmShelfBlockEntity.onNoteBlockPulse(level, pos, soundEvent);
}
// Suppress vanilla note-block playback while it is on top of a TM Shelf.
cir.setReturnValue(false);
}
}
@@ -23,7 +23,6 @@ import net.minecraft.client.sounds.ChannelAccess;
import net.minecraft.client.sounds.SoundEngine;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
@@ -33,7 +32,6 @@ 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;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.util.Iterator;
@@ -110,17 +108,6 @@ public abstract class SoundEngineMixin implements SoundEngineDuck {
}
}
/**
* TM Shelf can intentionally push note-block pitches above vanilla's 2.0f ceiling for octave-up playback.
* Keep vanilla clamping for all other sounds.
*/
@Inject(method = "calculatePitch", at = @At("HEAD"), cancellable = true)
private void cobblemon$allowExtendedNoteblockPitch(SoundInstance sound, CallbackInfoReturnable<Float> cir) {
if (sound.getLocation().getPath().startsWith("block.note_block.")) {
cir.setReturnValue(Mth.clamp(sound.getPitch(), 0.5F, 4.0F));
}
}
/** Different behavior for resuming SoundInstances while a BattleMusicInstance is being played. We do not want to resume filtered sounds. */
@Inject(method = "resume()V", at = @At("HEAD"), cancellable = true)
public void resume(CallbackInfo cb) {
@@ -62,8 +62,6 @@ import com.cobblemon.mod.common.api.storage.player.adapter.DexDataMongoBackend
import com.cobblemon.mod.common.api.storage.player.adapter.DexDataNbtBackend
import com.cobblemon.mod.common.api.storage.player.adapter.PlayerDataJsonBackend
import com.cobblemon.mod.common.api.storage.player.adapter.PlayerDataMongoBackend
import com.cobblemon.mod.common.api.storage.player.adapter.TMMoveMongoBackend
import com.cobblemon.mod.common.api.storage.player.adapter.TMMoveNbtBackend
import com.cobblemon.mod.common.api.storage.player.factory.CachedPlayerDataStoreFactory
import com.cobblemon.mod.common.api.tags.CobblemonEntityTypeTags
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
@@ -111,7 +109,13 @@ import com.cobblemon.mod.common.pokemon.properties.UnaspectPropertyType
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.ifDedicatedServer
import com.cobblemon.mod.common.util.isLaterVersion
import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.util.requestWallpapers
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.world.feature.CobblemonPlacedFeatures
import com.cobblemon.mod.common.world.feature.ore.CobblemonOrePlacedFeatures
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
@@ -240,8 +244,6 @@ object Cobblemon {
DATA_SYNCHRONIZED.subscribe {
storage.onPlayerDataSync(it)
it.tmList().scheduleFullSyncFromStores(it.party(), it.pc())
it.pokedex().scheduleFullSyncFromStores(it.party(), it.pc())
playerDataManager.syncAllToPlayer(it)
starterHandler.handleJoin(it)
it.requestWallpapers()
@@ -304,7 +306,6 @@ object Cobblemon {
this.config.maxPokemonLevel,
this.config.maxPokemonFriendship,
this.config.maxDynamaxLevel,
this.config.unlockAllMoveDexMovesByDefault
).sendToPlayer(player)
}
@@ -374,12 +375,8 @@ object Cobblemon {
val pokedexNbtFactory = CachedPlayerDataStoreFactory(DexDataNbtBackend())
pokedexNbtFactory.setup(server)
val tmMoveFactory = CachedPlayerDataStoreFactory(TMMoveNbtBackend())
tmMoveFactory.setup(server)
playerDataManager.setFactory(generalJsonFactory, PlayerInstancedDataStoreTypes.GENERAL)
playerDataManager.setFactory(pokedexNbtFactory, PlayerInstancedDataStoreTypes.POKEDEX)
playerDataManager.setFactory(tmMoveFactory, PlayerInstancedDataStoreTypes.TM_MOVES)
if (config.storageFormat == "nbt") {
NBTStoreAdapter(pokemonStoreRoot.absolutePath, useNestedFolders = true, folderPerClass = true)
@@ -406,14 +403,8 @@ object Cobblemon {
val pokedexMongoFactory = CachedPlayerDataStoreFactory(DexDataMongoBackend(mongoClient, config.mongoDBDatabaseName, "PokeDexCollection"))
pokedexMongoFactory.setup(server)
val tmMovesMongoFactory = CachedPlayerDataStoreFactory(
TMMoveMongoBackend(mongoClient, config.mongoDBDatabaseName, "TMMovesCollection")
)
tmMovesMongoFactory.setup(server)
playerDataManager.setFactory(generalMongoFactory, PlayerInstancedDataStoreTypes.GENERAL)
playerDataManager.setFactory(pokedexMongoFactory, PlayerInstancedDataStoreTypes.POKEDEX)
playerDataManager.setFactory(tmMovesMongoFactory, PlayerInstancedDataStoreTypes.TM_MOVES)
MongoDBStoreAdapter(mongoClient, config.mongoDBDatabaseName)
} catch (e: ClassNotFoundException) {
LOGGER.error("MongoDB driver not found.")
@@ -597,7 +588,6 @@ object Cobblemon {
this.implementation.registerCommandArgument(cobblemonResource("mark"), MarkArgumentType::class, SingletonArgumentInfo.contextFree(MarkArgumentType::mark))
this.implementation.registerCommandArgument(cobblemonResource("transform_type"), TransformTypeArgumentType::class, SingletonArgumentInfo.contextFree(TransformTypeArgumentType::transformType))
this.implementation.registerCommandArgument(cobblemonResource("model_part"), ModelPartArgumentType::class, SingletonArgumentInfo.contextFree(ModelPartArgumentType::modelPart))
this.implementation.registerCommandArgument(cobblemonResource("tm"), TmArgumentType::class, SingletonArgumentInfo.contextFree(TmArgumentType::tm))
}
}
@@ -39,9 +39,6 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
@JvmField
val LECTERN: BlockEntityType<LecternBlockEntity> = this.create("lectern", BlockEntityType.Builder.of(::LecternBlockEntity, CobblemonBlocks.LECTERN).build(null))
@JvmField
val TM_SHELF: BlockEntityType<TMShelfBlockEntity> = this.create("tm_shelf", BlockEntityType.Builder.of(::TMShelfBlockEntity, CobblemonBlocks.TM_SHELF).build(null))
@JvmField
val BERRY = this.create("berry", BlockEntityType.Builder.of(::BerryBlockEntity, *CobblemonBlocks.berries().values.toTypedArray()).build(null))
@@ -51,8 +48,6 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
val SIGN: BlockEntityType<CobblemonSignBlockEntity> = this.create("sign", BlockEntityType.Builder.of(::CobblemonSignBlockEntity, CobblemonBlocks.APRICORN_SIGN, CobblemonBlocks.APRICORN_WALL_SIGN, CobblemonBlocks.SACCHARINE_SIGN, CobblemonBlocks.SACCHARINE_WALL_SIGN).build(null))
@JvmField
val HANGING_SIGN: BlockEntityType<CobblemonHangingSignBlockEntity> = this.create("hanging_sign", BlockEntityType.Builder.of(::CobblemonHangingSignBlockEntity, CobblemonBlocks.APRICORN_HANGING_SIGN, CobblemonBlocks.APRICORN_WALL_HANGING_SIGN, CobblemonBlocks.SACCHARINE_HANGING_SIGN, CobblemonBlocks.SACCHARINE_WALL_HANGING_SIGN).build(null))
@JvmField
val TM_MACHINE: BlockEntityType<TMMachineBlockEntity> = this.create("tm_machine", BlockEntityType.Builder.of(::TMMachineBlockEntity, CobblemonBlocks.TM_MACHINE).build(null))
@JvmField
val GILDED_CHEST: BlockEntityType<GildedChestBlockEntity> = this.create("chest", BlockEntityType.Builder.of(::GildedChestBlockEntity,
@@ -22,7 +22,6 @@ import com.cobblemon.mod.common.block.sign.CobblemonHangingSignBlock
import com.cobblemon.mod.common.block.sign.CobblemonSignBlock
import com.cobblemon.mod.common.block.sign.CobblemonWallHangingSignBlock
import com.cobblemon.mod.common.block.sign.CobblemonWallSignBlock
import com.cobblemon.mod.common.block.tmmachine.TMMachineBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
import com.cobblemon.mod.common.mixin.invoker.*
@@ -250,31 +249,31 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val POKE_SNACK = this.create("poke_snack", PokeSnackBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY).lightLevel { if (it.getValue(BlockStateProperties.LIT)) 3 else 0 }, true))
@JvmField
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null)
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
@JvmField
val LARGE_BUDDING_TUMBLESTONE = tumblestoneBlock("large_budding_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, TUMBLESTONE_CLUSTER)
val LARGE_BUDDING_TUMBLESTONE = tumblestoneBlock("large_budding_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, TUMBLESTONE_CLUSTER)
@JvmField
val MEDIUM_BUDDING_TUMBLESTONE = tumblestoneBlock("medium_budding_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_TUMBLESTONE)
val MEDIUM_BUDDING_TUMBLESTONE = tumblestoneBlock("medium_budding_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_TUMBLESTONE)
@JvmField
val SMALL_BUDDING_TUMBLESTONE = tumblestoneBlock("small_budding_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_TUMBLESTONE)
val SMALL_BUDDING_TUMBLESTONE = tumblestoneBlock("small_budding_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_TUMBLESTONE)
@JvmField
val SKY_TUMBLESTONE_CLUSTER = tumblestoneBlock("sky_tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null)
val SKY_TUMBLESTONE_CLUSTER = tumblestoneBlock("sky_tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
@JvmField
val LARGE_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("large_budding_sky_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, SKY_TUMBLESTONE_CLUSTER)
val LARGE_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("large_budding_sky_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, SKY_TUMBLESTONE_CLUSTER)
@JvmField
val MEDIUM_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("medium_budding_sky_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_SKY_TUMBLESTONE)
val MEDIUM_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("medium_budding_sky_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_SKY_TUMBLESTONE)
@JvmField
val SMALL_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("small_budding_sky_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_SKY_TUMBLESTONE)
val SMALL_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("small_budding_sky_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_SKY_TUMBLESTONE)
@JvmField
val BLACK_TUMBLESTONE_CLUSTER = tumblestoneBlock("black_tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null)
val BLACK_TUMBLESTONE_CLUSTER = tumblestoneBlock("black_tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
@JvmField
val LARGE_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("large_budding_black_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, BLACK_TUMBLESTONE_CLUSTER)
val LARGE_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("large_budding_black_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, BLACK_TUMBLESTONE_CLUSTER)
@JvmField
val MEDIUM_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("medium_budding_black_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_BLACK_TUMBLESTONE)
val MEDIUM_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("medium_budding_black_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_BLACK_TUMBLESTONE)
@JvmField
val SMALL_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("small_budding_black_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_BLACK_TUMBLESTONE)
val SMALL_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("small_budding_black_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_BLACK_TUMBLESTONE)
@JvmField
val TUMBLESTONE_BLOCK = this.create("tumblestone_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_ORANGE).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@@ -283,87 +282,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val BLACK_TUMBLESTONE_BLOCK = this.create("black_tumblestone_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val TYPE_GEM_CORE = this.create("type_gem_core", TypeGemCoreBlock(BlockBehaviour.Properties.of().strength(4.0f, 10.0f).requiresCorrectToolForDrops()))
private val typeGemBlocks = mutableMapOf<ResourceLocation, TypeGemBlock>()
@JvmField
val TYPE_GEM_BLOCK_NORMAL = typeGemBlock("type_gem_block_normal")
@JvmField
val TYPE_GEM_BLOCK_FIRE = typeGemBlock("type_gem_block_fire")
@JvmField
val TYPE_GEM_BLOCK_WATER = typeGemBlock("type_gem_block_water")
@JvmField
val TYPE_GEM_BLOCK_ELECTRIC = typeGemBlock("type_gem_block_electric")
@JvmField
val TYPE_GEM_BLOCK_GRASS = typeGemBlock("type_gem_block_grass")
@JvmField
val TYPE_GEM_BLOCK_ICE = typeGemBlock("type_gem_block_ice")
@JvmField
val TYPE_GEM_BLOCK_FIGHTING = typeGemBlock("type_gem_block_fighting")
@JvmField
val TYPE_GEM_BLOCK_POISON = typeGemBlock("type_gem_block_poison")
@JvmField
val TYPE_GEM_BLOCK_GROUND = typeGemBlock("type_gem_block_ground")
@JvmField
val TYPE_GEM_BLOCK_FLYING = typeGemBlock("type_gem_block_flying")
@JvmField
val TYPE_GEM_BLOCK_PSYCHIC = typeGemBlock("type_gem_block_psychic")
@JvmField
val TYPE_GEM_BLOCK_BUG = typeGemBlock("type_gem_block_bug")
@JvmField
val TYPE_GEM_BLOCK_ROCK = typeGemBlock("type_gem_block_rock")
@JvmField
val TYPE_GEM_BLOCK_GHOST = typeGemBlock("type_gem_block_ghost")
@JvmField
val TYPE_GEM_BLOCK_DRAGON = typeGemBlock("type_gem_block_dragon")
@JvmField
val TYPE_GEM_BLOCK_DARK = typeGemBlock("type_gem_block_dark")
@JvmField
val TYPE_GEM_BLOCK_STEEL = typeGemBlock("type_gem_block_steel")
@JvmField
val TYPE_GEM_BLOCK_FAIRY = typeGemBlock("type_gem_block_fairy")
private val typeGemClusters = mutableMapOf<ResourceLocation, TypeGemClusterBlock>()
@JvmField
val TYPE_GEM_CLUSTER_NORMAL = typeGemCluster("type_gem_cluster_normal", TYPE_GEM_BLOCK_NORMAL, cobblemonResource("normal_gem"))
@JvmField
val TYPE_GEM_CLUSTER_FIRE = typeGemCluster("type_gem_cluster_fire", TYPE_GEM_BLOCK_FIRE, cobblemonResource("fire_gem"))
@JvmField
val TYPE_GEM_CLUSTER_WATER = typeGemCluster("type_gem_cluster_water", TYPE_GEM_BLOCK_WATER, cobblemonResource("water_gem"))
@JvmField
val TYPE_GEM_CLUSTER_ELECTRIC = typeGemCluster("type_gem_cluster_electric", TYPE_GEM_BLOCK_ELECTRIC, cobblemonResource("electric_gem"))
@JvmField
val TYPE_GEM_CLUSTER_GRASS = typeGemCluster("type_gem_cluster_grass", TYPE_GEM_BLOCK_GRASS, cobblemonResource("grass_gem"))
@JvmField
val TYPE_GEM_CLUSTER_ICE = typeGemCluster("type_gem_cluster_ice", TYPE_GEM_BLOCK_ICE, cobblemonResource("ice_gem"))
@JvmField
val TYPE_GEM_CLUSTER_FIGHTING = typeGemCluster("type_gem_cluster_fighting", TYPE_GEM_BLOCK_FIGHTING, cobblemonResource("fighting_gem"))
@JvmField
val TYPE_GEM_CLUSTER_POISON = typeGemCluster("type_gem_cluster_poison", TYPE_GEM_BLOCK_POISON, cobblemonResource("poison_gem"))
@JvmField
val TYPE_GEM_CLUSTER_GROUND = typeGemCluster("type_gem_cluster_ground", TYPE_GEM_BLOCK_GROUND, cobblemonResource("ground_gem"))
@JvmField
val TYPE_GEM_CLUSTER_FLYING = typeGemCluster("type_gem_cluster_flying", TYPE_GEM_BLOCK_FLYING, cobblemonResource("flying_gem"))
@JvmField
val TYPE_GEM_CLUSTER_PSYCHIC = typeGemCluster("type_gem_cluster_psychic", TYPE_GEM_BLOCK_PSYCHIC, cobblemonResource("psychic_gem"))
@JvmField
val TYPE_GEM_CLUSTER_BUG = typeGemCluster("type_gem_cluster_bug", TYPE_GEM_BLOCK_BUG, cobblemonResource("bug_gem"))
@JvmField
val TYPE_GEM_CLUSTER_ROCK = typeGemCluster("type_gem_cluster_rock", TYPE_GEM_BLOCK_ROCK, cobblemonResource("rock_gem"))
@JvmField
val TYPE_GEM_CLUSTER_GHOST = typeGemCluster("type_gem_cluster_ghost", TYPE_GEM_BLOCK_GHOST, cobblemonResource("ghost_gem"))
@JvmField
val TYPE_GEM_CLUSTER_DRAGON = typeGemCluster("type_gem_cluster_dragon", TYPE_GEM_BLOCK_DRAGON, cobblemonResource("dragon_gem"))
@JvmField
val TYPE_GEM_CLUSTER_DARK = typeGemCluster("type_gem_cluster_dark", TYPE_GEM_BLOCK_DARK, cobblemonResource("dark_gem"))
@JvmField
val TYPE_GEM_CLUSTER_STEEL = typeGemCluster("type_gem_cluster_steel", TYPE_GEM_BLOCK_STEEL, cobblemonResource("steel_gem"))
@JvmField
val TYPE_GEM_CLUSTER_FAIRY = typeGemCluster("type_gem_cluster_fairy", TYPE_GEM_BLOCK_FAIRY, cobblemonResource("fairy_gem"))
@JvmField
val POLISHED_TUMBLESTONE = this.create("polished_tumblestone", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_ORANGE).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
@@ -538,7 +456,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val SOUL_CAMPFIRE = create("soul_campfire", CampfireBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.PODZOL).strength(2.0F).lightLevel{9}, true))
@JvmField
val RELIC_COIN_POUCH = create("relic_coin_pouch", CoinPouchBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.RELIC_COIN_POUCH_SOUNDS).pushReaction(PushReaction.DESTROY).noOcclusion(), true))
@JvmField
@@ -667,25 +584,9 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val PC = create("pc", PCBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F).noOcclusion().lightLevel { if ((it.getValue(PCBlock.ON) as Boolean) && (it.getValue(PCBlock.PART) == PCBlock.PCPart.TOP)) 13 else 0 }))
val TM_MACHINE = create(
"tm_machine",
TMMachineBlock(
BlockBehaviour.Properties.of()
.mapColor(MapColor.COLOR_GREEN)
.sound(SoundType.METAL)
.pushReaction(PushReaction.BLOCK)
.strength(2F)
.noOcclusion()
)
)
@JvmField
val LECTERN = create("lectern", LecternBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).sound(SoundType.WOOD).strength(2.5F).ignitedByLava().lightLevel { if (it.getValue(LecternBlock.EMIT_LIGHT) as Boolean) 13 else 0 }))
@JvmField
val TM_SHELF = create("tm_shelf", TMShelfBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).sound(SoundType.METAL).strength(2.5F)))
@JvmField
val DISPLAY_CASE = create("display_case", DisplayCaseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3F).requiresCorrectToolForDrops()))
@@ -989,28 +890,4 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val block = BlocksInvoker.createLeavesBlock(SoundType.GRASS)
return this.create(name, block)
}
private fun typeGemBlock(name: String): TypeGemBlock {
val typeGemBlock = this.create(name, TypeGemBlock(BlockBehaviour.Properties.of().strength(3.0f, 6.0f)))
val identifier = cobblemonResource(name)
typeGemBlocks[identifier] = typeGemBlock
return typeGemBlock
}
fun typeGemBlocks() = typeGemBlocks.toMap()
private fun typeGemCluster(name: String, gemBlock: Block, dropItemId: ResourceLocation): Block {
val gemClusterBlock = this.create(name, TypeGemClusterBlock(BlockBehaviour.Properties.of().strength(2.0f, 3.0f).pushReaction(PushReaction.DESTROY), gemBlock, dropItemId))
TypeGemClusterBlock.gemToClusterMap[gemBlock] = gemClusterBlock
val identifier = cobblemonResource(name)
typeGemClusters[identifier] = gemClusterBlock
return gemClusterBlock
}
fun typeGemClusters() = typeGemClusters.toMap()
}
@@ -71,12 +71,10 @@ object CobblemonCommands {
MarkGiveAllCommand.register(dispatcher)
PcSearchCommand.register(dispatcher)
PcTakeCommand.register(dispatcher)
GiveTmCommand.register(dispatcher)
TmCommand.register(dispatcher)
// Possibly lock down registration if and only if under dev environment or running in an environment
// with a certain system environment variable set
CobblemonInfoCommand.register(dispatcher)
CalculateSeatPositionCommand.register(dispatcher)
}
}
}
@@ -19,7 +19,6 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.PreparableReloadListener
import net.minecraft.tags.TagKey
import net.minecraft.world.inventory.MenuType
import net.minecraft.world.level.GameRules
import net.minecraft.world.level.ItemLike
import net.minecraft.world.level.biome.Biome
@@ -69,12 +69,6 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
.networkSynchronized(FoodColourComponent.PACKET_CODEC)
.build())
@JvmField
val TM_MOVE: DataComponentType<TMMoveComponent> = create("tm_move", DataComponentType.builder<TMMoveComponent>()
.persistent(TMMoveComponent.CODEC)
.networkSynchronized(TMMoveComponent.PACKET_CODEC)
.build())
@JvmField
val INGREDIENT: DataComponentType<IngredientComponent> = create("ingredient", DataComponentType.builder<IngredientComponent>()
.persistent(IngredientComponent.CODEC)
@@ -107,7 +101,6 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:flavour"), FLAVOUR)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ride_boosts"), RIDE_BOOST)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food_colour"), FOOD_COLOUR)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:tm_move"), TM_MOVE)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ingredient"), INGREDIENT)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food"), FOOD)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:mob_effects"), MOB_EFFECTS)
@@ -74,6 +74,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val NPC_EDITOR = create("npc_editor", CobblemonItem(Item.Properties().stacksTo(1)))
@JvmField
val POKEEDIT_WAND = create("pokeedit_wand", PokeEditWandItem())
@JvmField
val pokeBalls = mutableListOf<PokeBallItem>()
@@ -457,10 +459,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val PC = blockItem("pc", CobblemonBlocks.PC)
@JvmField
val TM_MACHINE = blockItem("tm_machine", CobblemonBlocks.TM_MACHINE)
@JvmField
val TM_SHELF = blockItem("tm_shelf", CobblemonBlocks.TM_SHELF)
@JvmField
val PASTURE = blockItem("pasture", CobblemonBlocks.PASTURE)
@JvmField
val DISPLAY_CASE = blockItem("display_case", CobblemonBlocks.DISPLAY_CASE)
@@ -1212,83 +1210,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val BLACK_TUMBLESTONE_BLOCK = blockItem("black_tumblestone_block", CobblemonBlocks.BLACK_TUMBLESTONE_BLOCK)
@JvmField
val TYPE_GEM_CORE = blockItem("type_gem_core", CobblemonBlocks.TYPE_GEM_CORE)
@JvmField
val TYPE_GEM_CLUSTER_NORMAL = blockItem("type_gem_cluster_normal", CobblemonBlocks.TYPE_GEM_CLUSTER_NORMAL)
@JvmField
val TYPE_GEM_CLUSTER_FIRE = blockItem("type_gem_cluster_fire", CobblemonBlocks.TYPE_GEM_CLUSTER_FIRE)
@JvmField
val TYPE_GEM_CLUSTER_WATER = blockItem("type_gem_cluster_water", CobblemonBlocks.TYPE_GEM_CLUSTER_WATER)
@JvmField
val TYPE_GEM_CLUSTER_ELECTRIC = blockItem("type_gem_cluster_electric", CobblemonBlocks.TYPE_GEM_CLUSTER_ELECTRIC)
@JvmField
val TYPE_GEM_CLUSTER_GRASS = blockItem("type_gem_cluster_grass", CobblemonBlocks.TYPE_GEM_CLUSTER_GRASS)
@JvmField
val TYPE_GEM_CLUSTER_ICE = blockItem("type_gem_cluster_ice", CobblemonBlocks.TYPE_GEM_CLUSTER_ICE)
@JvmField
val TYPE_GEM_CLUSTER_FIGHTING = blockItem("type_gem_cluster_fighting", CobblemonBlocks.TYPE_GEM_CLUSTER_FIGHTING)
@JvmField
val TYPE_GEM_CLUSTER_POISON = blockItem("type_gem_cluster_poison", CobblemonBlocks.TYPE_GEM_CLUSTER_POISON)
@JvmField
val TYPE_GEM_CLUSTER_GROUND = blockItem("type_gem_cluster_ground", CobblemonBlocks.TYPE_GEM_CLUSTER_GROUND)
@JvmField
val TYPE_GEM_CLUSTER_FLYING = blockItem("type_gem_cluster_flying", CobblemonBlocks.TYPE_GEM_CLUSTER_FLYING)
@JvmField
val TYPE_GEM_CLUSTER_PSYCHIC = blockItem("type_gem_cluster_psychic", CobblemonBlocks.TYPE_GEM_CLUSTER_PSYCHIC)
@JvmField
val TYPE_GEM_CLUSTER_BUG = blockItem("type_gem_cluster_bug", CobblemonBlocks.TYPE_GEM_CLUSTER_BUG)
@JvmField
val TYPE_GEM_CLUSTER_ROCK = blockItem("type_gem_cluster_rock", CobblemonBlocks.TYPE_GEM_CLUSTER_ROCK)
@JvmField
val TYPE_GEM_CLUSTER_GHOST = blockItem("type_gem_cluster_ghost", CobblemonBlocks.TYPE_GEM_CLUSTER_GHOST)
@JvmField
val TYPE_GEM_CLUSTER_DRAGON = blockItem("type_gem_cluster_dragon", CobblemonBlocks.TYPE_GEM_CLUSTER_DRAGON)
@JvmField
val TYPE_GEM_CLUSTER_DARK = blockItem("type_gem_cluster_dark", CobblemonBlocks.TYPE_GEM_CLUSTER_DARK)
@JvmField
val TYPE_GEM_CLUSTER_STEEL = blockItem("type_gem_cluster_steel", CobblemonBlocks.TYPE_GEM_CLUSTER_STEEL)
@JvmField
val TYPE_GEM_CLUSTER_FAIRY = blockItem("type_gem_cluster_fairy", CobblemonBlocks.TYPE_GEM_CLUSTER_FAIRY)
@JvmField
val TYPE_GEM_BLOCK_NORMAL = blockItem("type_gem_block_normal", CobblemonBlocks.TYPE_GEM_BLOCK_NORMAL)
@JvmField
val TYPE_GEM_BLOCK_FIRE = blockItem("type_gem_block_fire", CobblemonBlocks.TYPE_GEM_BLOCK_FIRE)
@JvmField
val TYPE_GEM_BLOCK_WATER = blockItem("type_gem_block_water", CobblemonBlocks.TYPE_GEM_BLOCK_WATER)
@JvmField
val TYPE_GEM_BLOCK_ELECTRIC = blockItem("type_gem_block_electric", CobblemonBlocks.TYPE_GEM_BLOCK_ELECTRIC)
@JvmField
val TYPE_GEM_BLOCK_GRASS = blockItem("type_gem_block_grass", CobblemonBlocks.TYPE_GEM_BLOCK_GRASS)
@JvmField
val TYPE_GEM_BLOCK_ICE = blockItem("type_gem_block_ice", CobblemonBlocks.TYPE_GEM_BLOCK_ICE)
@JvmField
val TYPE_GEM_BLOCK_FIGHTING = blockItem("type_gem_block_fighting", CobblemonBlocks.TYPE_GEM_BLOCK_FIGHTING)
@JvmField
val TYPE_GEM_BLOCK_POISON = blockItem("type_gem_block_poison", CobblemonBlocks.TYPE_GEM_BLOCK_POISON)
@JvmField
val TYPE_GEM_BLOCK_GROUND = blockItem("type_gem_block_ground", CobblemonBlocks.TYPE_GEM_BLOCK_GROUND)
@JvmField
val TYPE_GEM_BLOCK_FLYING = blockItem("type_gem_block_flying", CobblemonBlocks.TYPE_GEM_BLOCK_FLYING)
@JvmField
val TYPE_GEM_BLOCK_PSYCHIC = blockItem("type_gem_block_psychic", CobblemonBlocks.TYPE_GEM_BLOCK_PSYCHIC)
@JvmField
val TYPE_GEM_BLOCK_BUG = blockItem("type_gem_block_bug", CobblemonBlocks.TYPE_GEM_BLOCK_BUG)
@JvmField
val TYPE_GEM_BLOCK_ROCK = blockItem("type_gem_block_rock", CobblemonBlocks.TYPE_GEM_BLOCK_ROCK)
@JvmField
val TYPE_GEM_BLOCK_GHOST = blockItem("type_gem_block_ghost", CobblemonBlocks.TYPE_GEM_BLOCK_GHOST)
@JvmField
val TYPE_GEM_BLOCK_DRAGON = blockItem("type_gem_block_dragon", CobblemonBlocks.TYPE_GEM_BLOCK_DRAGON)
@JvmField
val TYPE_GEM_BLOCK_DARK = blockItem("type_gem_block_dark", CobblemonBlocks.TYPE_GEM_BLOCK_DARK)
@JvmField
val TYPE_GEM_BLOCK_STEEL = blockItem("type_gem_block_steel", CobblemonBlocks.TYPE_GEM_BLOCK_STEEL)
@JvmField
val TYPE_GEM_BLOCK_FAIRY = blockItem("type_gem_block_fairy", CobblemonBlocks.TYPE_GEM_BLOCK_FAIRY)
@JvmField
val POKEROD_SMITHING_TEMPLATE = create("pokerod_smithing_template", SmithingTemplateItem(
Component.translatable("item.minecraft.fishing_rod").blue(),
@@ -1513,49 +1434,44 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val RELIC_COIN_POUCH = blockItem("relic_coin_pouch", CobblemonBlocks.RELIC_COIN_POUCH)
@JvmField
val RELIC_COIN_SACK = blockItem("relic_coin_sack", CobblemonBlocks.RELIC_COIN_SACK)
@JvmField
val TECHNICAL_MACHINE = this.create("technical_machine", TechnicalMachineItem(Properties()))
@JvmField
val BLANK_TM = this.create("blank_tm", CobblemonItem(Item.Properties()))
// Type Gems
@JvmField
val NORMAL_GEM = this.create("normal_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_NORMAL, Item.Properties()))
val NORMAL_GEM = noSettingsItem("normal_gem")
@JvmField
val FIRE_GEM = this.create("fire_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_FIRE, Item.Properties()))
val FIRE_GEM = noSettingsItem("fire_gem")
@JvmField
val WATER_GEM = this.create("water_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_WATER, Item.Properties()))
val WATER_GEM = noSettingsItem("water_gem")
@JvmField
val GRASS_GEM = this.create("grass_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_GRASS, Item.Properties()))
val GRASS_GEM = noSettingsItem("grass_gem")
@JvmField
val ELECTRIC_GEM = this.create("electric_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_ELECTRIC, Item.Properties()))
val ELECTRIC_GEM = noSettingsItem("electric_gem")
@JvmField
val ICE_GEM = this.create("ice_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_ICE, Item.Properties()))
val ICE_GEM = noSettingsItem("ice_gem")
@JvmField
val FIGHTING_GEM = this.create("fighting_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_FIGHTING, Item.Properties()))
val FIGHTING_GEM = noSettingsItem("fighting_gem")
@JvmField
val POISON_GEM = this.create("poison_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_POISON, Item.Properties()))
val POISON_GEM = noSettingsItem("poison_gem")
@JvmField
val GROUND_GEM = this.create("ground_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_GROUND, Item.Properties()))
val GROUND_GEM = noSettingsItem("ground_gem")
@JvmField
val FLYING_GEM = this.create("flying_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_FLYING, Item.Properties()))
val FLYING_GEM = noSettingsItem("flying_gem")
@JvmField
val PSYCHIC_GEM = this.create("psychic_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_PSYCHIC, Item.Properties()))
val PSYCHIC_GEM = noSettingsItem("psychic_gem")
@JvmField
val BUG_GEM = this.create("bug_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_BUG, Item.Properties()))
val BUG_GEM = noSettingsItem("bug_gem")
@JvmField
val ROCK_GEM = this.create("rock_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_ROCK, Item.Properties()))
val ROCK_GEM = noSettingsItem("rock_gem")
@JvmField
val GHOST_GEM = this.create("ghost_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_GHOST, Item.Properties()))
val GHOST_GEM = noSettingsItem("ghost_gem")
@JvmField
val DRAGON_GEM = this.create("dragon_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_DRAGON, Item.Properties()))
val DRAGON_GEM = noSettingsItem("dragon_gem")
@JvmField
val DARK_GEM = this.create("dark_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_DARK, Item.Properties()))
val DARK_GEM = noSettingsItem("dark_gem")
@JvmField
val STEEL_GEM = this.create("steel_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_STEEL, Item.Properties()))
val STEEL_GEM = noSettingsItem("steel_gem")
@JvmField
val FAIRY_GEM = this.create("fairy_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_FAIRY, Item.Properties()))
val FAIRY_GEM = noSettingsItem("fairy_gem")
//@JvmField
//val BINDING_SOIL = blockItem("binding_soil", CobblemonBlocks.BINDING_SOIL)
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common
import com.cobblemon.mod.common.block.campfirepot.CookingPotMenu
import com.cobblemon.mod.common.block.tmmachine.TMMachineMenu
import com.cobblemon.mod.common.platform.PlatformRegistry
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
@@ -20,8 +19,7 @@ import net.minecraft.world.inventory.MenuType
object CobblemonMenuType : PlatformRegistry<Registry<MenuType<*>>, ResourceKey<Registry<MenuType<*>>>, MenuType<*>>() {
val COOKING_POT = MenuType.register("cooking_pot_menu", ::CookingPotMenu)
val TM_MACHINE = MenuType.register("tm_machine_menu", ::TMMachineMenu)
val COOKING_POT = MenuType.register("cooking_pot", ::CookingPotMenu)
override val registry: Registry<MenuType<*>>
get() = BuiltInRegistries.MENU
@@ -25,15 +25,15 @@ import com.cobblemon.mod.common.client.net.data.RideSettingsSyncHandler
import com.cobblemon.mod.common.client.net.debug.OpenRidingStatsDebugGUIHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
import com.cobblemon.mod.common.client.net.effect.*
import com.cobblemon.mod.common.client.net.gui.*
import com.cobblemon.mod.common.client.net.effect.PokeSnackBlockParticlesHandler
import com.cobblemon.mod.common.client.net.effect.RunPosableMoLangHandler
import com.cobblemon.mod.common.client.net.effect.SaccharineLogBlockParticlesHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormEntityParticleHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormParticleHandler
import com.cobblemon.mod.common.client.net.gui.ExpGainedDataPacketHandler
import com.cobblemon.mod.common.client.net.gui.*
import com.cobblemon.mod.common.client.net.gui.InteractPokemonUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.PokedexUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
import com.cobblemon.mod.common.client.net.npc.CloseNPCEditorHandler
import com.cobblemon.mod.common.client.net.npc.OpenNPCEditorHandler
import com.cobblemon.mod.common.client.net.orientation.ClientboundUpdateOrientationHandler
@@ -137,7 +137,9 @@ import com.cobblemon.mod.common.net.messages.client.trade.TradeProcessStartedPac
import com.cobblemon.mod.common.net.messages.client.trade.TradeStartedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeUpdatedPacket
import com.cobblemon.mod.common.net.messages.client.ui.ExpGainedDataPacket
import com.cobblemon.mod.common.net.messages.client.ui.*
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.PokedexUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
import com.cobblemon.mod.common.net.messages.server.*
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamLeavePacket
@@ -148,7 +150,6 @@ import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
import com.cobblemon.mod.common.net.messages.server.behaviour.DamageOnCollisionPacket
import com.cobblemon.mod.common.net.messages.server.behaviour.SetEntityBehaviourPacket
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
import com.cobblemon.mod.common.net.messages.server.block.TMMachineTeachMovePacket
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.net.messages.server.callback.party.PartyPokemonSelectedPacket
@@ -169,6 +170,7 @@ import com.cobblemon.mod.common.net.messages.server.pasture.UnpastureAllPokemonP
import com.cobblemon.mod.common.net.messages.server.pasture.UnpasturePokemonPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.FinishScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.StartScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokeedit.PokeEditActionPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.interact.InteractPokemonPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetActiveMarkPacket
@@ -204,7 +206,6 @@ import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestResponseHan
import com.cobblemon.mod.common.net.serverhandling.behaviour.DamageOnCollisionPacketHandler
import com.cobblemon.mod.common.net.serverhandling.behaviour.SetEntityBehaviourHandler
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
import com.cobblemon.mod.common.net.serverhandling.block.TMMachineTeachMoveHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectCancelledHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectedHandler
import com.cobblemon.mod.common.net.serverhandling.callback.party.PartyPokemonSelectedHandler
@@ -227,9 +228,8 @@ import com.cobblemon.mod.common.net.serverhandling.pasture.UnpastureAllPokemonHa
import com.cobblemon.mod.common.net.serverhandling.pasture.UnpasturePokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.FinishScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokeedit.PokeEditActionHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.interact.InteractPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.*
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.*
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.ServerboundUpdateRidingStateHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetActiveMarkHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetItemHiddenHandler
@@ -563,6 +563,9 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(StartScanningPacket.ID, StartScanningPacket::decode, StartScanningHandler))
list.add(PacketRegisterInfo(FinishScanningPacket.ID, FinishScanningPacket::decode, FinishScanningHandler))
// PokeEdit actions
list.add(PacketRegisterInfo(PokeEditActionPacket.ID, PokeEditActionPacket::decode, PokeEditActionHandler))
// Pasture
list.add(PacketRegisterInfo(PasturePokemonPacket.ID, PasturePokemonPacket::decode, PasturePokemonHandler))
list.add(PacketRegisterInfo(UnpasturePokemonPacket.ID, UnpasturePokemonPacket::decode, UnpasturePokemonHandler))
@@ -572,7 +575,6 @@ object CobblemonNetwork {
// Block entity
list.add(PacketRegisterInfo(AdjustBlockEntityViewerCountPacket.ID, AdjustBlockEntityViewerCountPacket::decode, AdjustBlockEntityViewerCountHandler))
list.add(PacketRegisterInfo(TMMachineTeachMovePacket.ID, TMMachineTeachMovePacket::decode, TMMachineTeachMoveHandler))
// Move select packets
list.add(PacketRegisterInfo(MoveSelectedPacket.ID, MoveSelectedPacket::decode, MoveSelectedHandler))
@@ -609,10 +611,6 @@ object CobblemonNetwork {
// Cooking
list.add(PacketRegisterInfo(ToggleCookingPotLidPacket.ID, ToggleCookingPotLidPacket::decode, ToggleCookingPotLidHandler))
// TM Machine
list.add(PacketRegisterInfo(SetActiveTMPacket.ID, SetActiveTMPacket::decode, SetActiveTMPacketHandler))
list.add(PacketRegisterInfo(SetTMMachineContainerDataPacket.ID, SetTMMachineContainerDataPacket::decode, SetTMMachineContainerDataPacketHandler))
// Behaviour Packets
list.add(PacketRegisterInfo(SetEntityBehaviourPacket.ID, SetEntityBehaviourPacket::decode, SetEntityBehaviourHandler))
list.add(PacketRegisterInfo(DamageOnCollisionPacket.ID, DamageOnCollisionPacket::decode, DamageOnCollisionPacketHandler))
@@ -626,4 +624,4 @@ object CobblemonNetwork {
fun sendPacketToPlayer(player: ServerPlayer, packet: NetworkPacket<*>) {
Cobblemon.implementation.networkManager.sendPacketToPlayer(player, packet)
}
}
}
@@ -200,22 +200,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val TUMBLESTONE_STEP = this.create("block.tumblestone.step")
@JvmField
val TM_USE = this.create("item.tm.use")
@JvmField
val TM_MACHINE_CRAFT = this.create("block.tm_machine.craft")
@JvmField
val TM_MACHINE_OFF = this.create("block.tm_machine.off")
@JvmField
val TM_MACHINE_ON = this.create("block.tm_machine.on")
@JvmField
val TM_MACHINE_OPEN = this.create("block.tm_machine.open")
@JvmField
val TM_MACHINE_CLOSE = this.create("block.tm_machine.close")
@JvmField
val TM_MACHINE_PLACE_DISC = this.create("block.tm_machine.place_disc")
@JvmField
val EVOLUTION_STONE_BLOCK_BREAK = this.create("block.evolution_stone_block.break")
@JvmField
@@ -14,7 +14,7 @@ import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerPlayer
import java.util.*
import java.util.Optional
class PlantTumblestoneContext(val pos: BlockPos, val block: TumblestoneBlock)
@@ -29,6 +29,42 @@ class PlantTumblestoneCriterion(
}
override fun matches(player: ServerPlayer, context: PlantTumblestoneContext): Boolean {
return context.block.canGrow(player.level().getBlockState(context.pos), context.pos, player.level())
return context.block.canGrow(context.pos, player.level())
}
}
}
///**
// * A criterion condition for when a tumblestone block is planted
// * @param id The identifier of the criterion
// * @param predicate The predicate for the criterion
// *
// * @author Aethen
// * @since 02/28/2024
// */
//class PlantTumblestoneCriterionCondition(id: Identifier, predicate: LootContextPredicate) :
// SimpleCriterionCondition<PlantTumblestoneContext>(id, predicate) {
// override fun toJson(json: JsonObject) {
// // Add properties to json if needed. None needed for this criterion
// }
//
// override fun fromJson(json: JsonObject) {
// // Parse properties from json if needed. None needed for this criterion
// }
//
// /**
// * Checks if the tumblestone block can grow at the given position
// * @param player The player that planted the tumblestone block
// * @param context The context of the criterion
// * @return True if the tumblestone block can grow at the given position, false otherwise
// */
// override fun matches(player: ServerPlayer, context: PlantTumblestoneContext): Boolean {
// return context.tumbleStoneBlock.canGrow(context.pos, player.level())
// }
//}
//
///**
// * The context of the [PlantTumblestoneCriterionCondition]
// * @param pos The position of the tumblestone block
// * @param tumbleStoneBlock The tumblestone block
// */
//open class PlantTumblestoneContext(var pos: BlockPos, var tumbleStoneBlock: TumblestoneBlock)
@@ -1842,30 +1842,6 @@ object MoLangFunctions {
map.put("remove_cosmetic_item") { _ ->
pokemon.removeCosmeticItem()
}
map.put("marks") { _ -> pokemon.marks.asArrayValue { StringValue(it.identifier.toString()) } }
map.put("has_mark") { params ->
var hasMark = false
val identifier = params.getString(0).asIdentifierDefaultingNamespace()
val mark = Marks.getByIdentifier(identifier)
if (mark != null) {
hasMark = pokemon.marks.contains(mark)
}
DoubleValue(hasMark)
}
map.put("remove_marks") { params ->
var removedMark = false
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
val mark = Marks.getByIdentifier(identifier)
if (mark != null) {
pokemon.exchangeMark(mark, false)
removedMark = true
}
}
DoubleValue(removedMark)
}
map.put("add_marks") { params ->
var appliedMark = false
for (param in params.params) {
@@ -24,7 +24,6 @@ import net.minecraft.network.RegistryFriendlyByteBuf
class BenchedMoves : Iterable<BenchedMove> {
var changeFunction: ((BenchedMoves) -> Unit) = {}
// var moveAddedFunction: (BenchedMove) -> Unit = {}
private var emit = true
private val benchedMoves = mutableListOf<BenchedMove>()
@@ -59,9 +58,6 @@ class BenchedMoves : Iterable<BenchedMove> {
return false
}
/*if (emit) {
moveAddedFunction(benchedMove)
}*/
doThenEmit { benchedMoves.add(benchedMove) }
return true
}
@@ -178,4 +174,4 @@ data class BenchedMove(val moveTemplate: MoveTemplate, val ppRaisedStages: Int)
Codec.intRange(0, 3).fieldOf(DataKeys.POKEMON_MOVESET_RAISED_PP_STAGES).forGetter(BenchedMove::ppRaisedStages)
).apply(it, ::BenchedMove) }
}
}
}
@@ -54,7 +54,6 @@ class MoveSet : Iterable<Move> {
*/
fun getMoves() = moves.filterNotNull()
fun getMovesWithNulls() = moves.toList()
fun getMoveTemplates() = getMoves().map { move -> move.template }
fun hasSpace() = moves.any { it == null }
/**
@@ -223,4 +222,4 @@ class MoveSet : Iterable<Move> {
{ moveSet -> moveSet.toList() }
)
}
}
}
@@ -143,8 +143,6 @@ object CobblemonPermissions {
val CHANGE_BOX_COUNT = this.create("${COMMAND_PREFIX}boxcount", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val SPECTATE_BATTLE = this.create("${COMMAND_PREFIX}spectatebattle", PermissionLevel.ALL_COMMANDS)
@JvmStatic
val TECHNICAL_MACHINE = this.create("${COMMAND_PREFIX}technicalmachine", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val SEE_HIDDEN_NPCS = this.create("seehiddennpcs", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@@ -29,6 +29,7 @@ abstract class AbstractPokedexManager {
fun deleteSpeciesRecord(speciesId: ResourceLocation) {
speciesRecords.remove(speciesId)
markDirty()
}
fun deleteFormRecord(speciesId: ResourceLocation, formName: String) {
@@ -39,6 +40,8 @@ abstract class AbstractPokedexManager {
deleteSpeciesRecord(speciesId)
return
}
markDirty()
}
fun getSpeciesRecord(speciesId: ResourceLocation): SpeciesDexRecord? {
@@ -161,4 +164,8 @@ abstract class AbstractPokedexManager {
return newValue
}
}
open fun markDirty() {
// Save stuff
}
}
@@ -10,9 +10,7 @@ package com.cobblemon.mod.common.api.pokedex
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.struct.VariableStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokemon.PokedexDataChangedEvent
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
@@ -27,7 +25,6 @@ import com.google.common.collect.Sets
import com.mojang.datafixers.util.Either
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.ListCodec
import com.mojang.serialization.codecs.PrimitiveCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -44,13 +41,11 @@ class FormDexRecord {
instance.group(
ListCodec(Codec.STRING, 0, 3).fieldOf("genders").forGetter { it.genders.map { it.name } },
ListCodec(Codec.STRING, 0, 2).fieldOf("seenShinyStates").forGetter { it.seenShinyStates.toList() },
PrimitiveCodec.INT.optionalFieldOf("highest_level", -1).forGetter { it.highestLevel },
Codec.STRING.fieldOf("knowledge").forGetter { it.knowledge.name }
).apply(instance) { genders, seenShinyStates, highestLevel, knowledge ->
).apply(instance) { genders, seenShinyStates, knowledge ->
FormDexRecord().also {
it.genders.addAll(genders.map(Gender::valueOf))
it.seenShinyStates.addAll(seenShinyStates)
it.highestLevel = highestLevel
it.knowledge = PokedexEntryProgress.valueOf(knowledge)
}
}
@@ -64,9 +59,6 @@ class FormDexRecord {
private val seenShinyStates =
mutableSetOf<String>() // consider: radiants in the future (radiants should just be a resource pack tbh)
var highestLevel = -1
private set
/** The current awareness of the form that the dex has. */
var knowledge = PokedexEntryProgress.NONE
private set
@@ -88,17 +80,11 @@ class FormDexRecord {
struct = QueryStruct(hashMapOf())
.addFunction("data") { data }
.addFunction("knowledge") { StringValue(knowledge.name) }
.addFunction("has_seen_gender") { params -> DoubleValue(genders.contains(Gender.valueOf(params.getString(0).uppercase()))) }
.addFunction("highest_level") { _ -> DoubleValue(highestLevel) }
.addFunction("set_highest_level") { params ->
highestLevel = maxOf(highestLevel, params.getDouble(0).toInt())
DoubleValue.ONE
}
.addFunction("has_seen_gender") { params -> genders.contains(Gender.valueOf(params.getString(0).uppercase())) }
}
fun clone() = FormDexRecord().also {
it.genders.addAll(genders)
it.highestLevel = highestLevel
it.seenShinyStates.addAll(seenShinyStates)
it.knowledge = knowledge
}
@@ -128,7 +114,7 @@ class FormDexRecord {
fun addAllShinyStatesAndGenders() {
val form = PokemonSpecies.getByIdentifier(speciesDexRecord.id)?.getFormByName(formName)
genders.addAll(form?.possibleGenders ?: listOf(Gender.MALE, Gender.FEMALE))
highestLevel = Cobblemon.config.maxPokemonLevel
seenShinyStates.addAll(listOf("shiny", "normal"))
speciesDexRecord.onFormRecordUpdated(this)
}
@@ -149,9 +135,6 @@ class FormDexRecord {
),
ifSucceeded = {
genders.add(pokedexEntityData.pokemon.gender)
if (knowledge == PokedexEntryProgress.CAUGHT) {
highestLevel = maxOf(highestLevel, pokedexEntityData.pokemon.level)
}
seenShinyStates.add(if (pokedexEntityData.pokemon.shiny) "shiny" else "normal")
if (knowledge.ordinal > this.knowledge.ordinal) {
this.knowledge = knowledge
@@ -176,7 +159,6 @@ class FormDexRecord {
|| (pokedexEntityData.pokemon.shiny && "shiny" !in seenShinyStates)
|| (!pokedexEntityData.pokemon.shiny && "normal" !in seenShinyStates)
|| knowledge.ordinal > this.knowledge.ordinal
|| (highestLevel < pokedexEntityData.pokemon.level && knowledge == PokedexEntryProgress.CAUGHT)
|| speciesDexRecord.wouldBeDifferent(pokedexEntityData)
}
@@ -9,8 +9,6 @@
package com.cobblemon.mod.common.api.pokedex
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.api.storage.player.InstancedPlayerData
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
@@ -50,44 +48,10 @@ class PokedexManager(
fun obtain(pokemon: Pokemon) {
val speciesId = pokemon.species.resourceIdentifier
val formName = pokemon.form.name
getOrCreateSpeciesRecord(speciesId).getOrCreateFormRecord(formName).obtained(PokedexEntityData(pokemon = pokemon, disguise = null))
getOrCreateSpeciesRecord(speciesId).getOrCreateFormRecord(formName).caught(PokedexEntityData(pokemon = pokemon, disguise = null))
}
fun scheduleFullSyncFromStores(
party: Iterable<Pokemon?>,
pc: Iterable<Pokemon?>,
batchSize: Int = 50
) {
val iterator = sequence {
for (pokemon in party) {
if (pokemon != null) yield(pokemon)
}
for (pokemon in pc) {
if (pokemon != null) yield(pokemon)
}
}.iterator()
if (!iterator.hasNext()) {
return
}
ScheduledTask.Builder()
.tracker(ServerTaskTracker)
.interval(0F)
.infiniteIterations()
.execute { task ->
var processed = 0
while (processed < batchSize && iterator.hasNext()) {
val pokemon = iterator.next()
obtain(pokemon)
processed++
}
if (!iterator.hasNext()) {
task.expire()
}
}
.build()
override fun markDirty() {
}
override fun initialize() {
@@ -26,8 +26,7 @@ import net.minecraft.resources.ResourceLocation
/**
* Dex recorded information about a particular Pokémon species. This includes all forms and their information.
* Across all forms there is a single list of known aspects which is generally used to track cosmetic variation
* though technically things like gender and form-motivating aspects will get collected inadvertently. The highest
* level of the species that the player has owned is also tracked.
* though technically things like gender and form-motivating aspects will get collected inadvertently.
*
* @author Hiroku
* @since August 23rd, 2024
@@ -37,12 +36,10 @@ class SpeciesDexRecord {
val CODEC: Codec<SpeciesDexRecord> = RecordCodecBuilder.create { instance ->
instance.group(
ListCodec(PrimitiveCodec.STRING, 0, 512).fieldOf("aspects").forGetter { it.aspects.toList() },
PrimitiveCodec.INT.optionalFieldOf("highestLevel", -1).forGetter { it.highestLevel },
Codec.unboundedMap(Codec.STRING, FormDexRecord.CODEC).fieldOf("formRecords").forGetter { it.formRecords }
).apply(instance) { aspects, highestLevel, formRecords ->
).apply(instance) { aspects, formRecords ->
SpeciesDexRecord().also {
it.aspects.addAll(aspects)
it.highestLevel = highestLevel
it.formRecords.putAll(formRecords)
}
}
@@ -51,16 +48,13 @@ class SpeciesDexRecord {
@Transient
lateinit var id: ResourceLocation
/** The highest level of the species that the player has owned. */
var highestLevel = -1
private set
private val aspects: MutableSet<String> = mutableSetOf()
private val formRecords: MutableMap<String, FormDexRecord> = mutableMapOf()
val isFormRecordsEmpty: Boolean
get() = formRecords.isEmpty()
fun describe(): String {
return "SpeciesDexRecord(aspects=$aspects, highestLevel=$highestLevel, formRecords=$formRecords)"
return "SpeciesDexRecord(aspects=$aspects, formRecords=$formRecords)"
}
@Transient
@@ -83,14 +77,7 @@ class SpeciesDexRecord {
.addFunction("is_caught") { params ->
DoubleValue(formRecords.values.any { it.knowledge.name.equals("CAUGHT", ignoreCase = true) })
}
.addFunction("highest_level") { params ->
DoubleValue(highestLevel)
}
.addFunction("set_highest_level") { params ->
highestLevel = params.getDouble(0).toInt()
DoubleValue.ONE
}
// more, also worth moving to something like MoLangFunctions.kt so it's easier to API in more values
// more, also worth moving to something like MoLangFunctions.kt so it's easier to API in more values
@Transient
lateinit var pokedexManager: AbstractPokedexManager
@@ -107,16 +94,10 @@ class SpeciesDexRecord {
fun addInformation(pokemon: Pokemon, knowledge: PokedexEntryProgress) {
aspects.addAll(pokemon.aspects)
if (knowledge == PokedexEntryProgress.CAUGHT) {
highestLevel = maxOf(highestLevel, pokemon.level)
}
}
fun addInformation(pokedexEntityData: PokedexEntityData, knowledge: PokedexEntryProgress) {
aspects.addAll(pokedexEntityData.pokemon.aspects)
if (knowledge == PokedexEntryProgress.CAUGHT) {
highestLevel = maxOf(highestLevel, pokedexEntityData.pokemon.level)
}
}
fun addAspects(addedAspects: Set<String>) {
@@ -147,7 +128,6 @@ class SpeciesDexRecord {
fun clone() = SpeciesDexRecord().also {
it.aspects.addAll(aspects)
it.highestLevel = highestLevel
it.formRecords.putAll(formRecords.mapValues { it.value.clone() })
}
@@ -159,7 +139,6 @@ class SpeciesDexRecord {
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeCollection(aspects) { _, it -> buffer.writeString(it) }
buffer.writeInt(highestLevel)
buffer.writeInt(formRecords.size)
for ((formName, formRecord) in formRecords) {
buffer.writeString(formName)
@@ -170,7 +149,6 @@ class SpeciesDexRecord {
fun decode(buffer: RegistryFriendlyByteBuf) {
aspects.clear()
aspects.addAll(buffer.readCollection(Sets::newHashSetWithExpectedSize) { buffer.readString() })
highestLevel = buffer.readInt()
formRecords.clear()
val numForms = buffer.readInt()
for (i in 0 until numForms) {
@@ -11,6 +11,5 @@ package com.cobblemon.mod.common.api.pokedex.filter
enum class SearchByType {
SPECIES,
ABILITIES,
MOVES,
DROPS;
}
@@ -9,22 +9,14 @@
package com.cobblemon.mod.common.api.pokedex.filter
import com.cobblemon.mod.common.api.drop.ItemDropEntry
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.client.Minecraft
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
/**
@@ -55,23 +47,6 @@ class SearchFilter(val pokedexManager: AbstractPokedexManager, val searchString:
}
return abilityList.any { it.contains(searchString.trim().lowercase()) }
}
SearchByType.MOVES -> {
if (pokedexManager.getHighestKnowledgeFor(entry) !== PokedexEntryProgress.CAUGHT) return false
val search = searchString.trim().lowercase()
val learnedTMs = CobblemonClient.clientTMMoveData.learnedTMs
val forms = pokedexManager.getCaughtForms(entry)
val formData = if (forms.isEmpty()) {
listOf(species.standardForm)
} else {
forms.map { form ->
species.forms.find { it.name.equals(form.displayForm, ignoreCase = true) } ?: species.standardForm
}
}
return formData.any { form ->
val highestLevel = pokedexManager.getSpeciesRecord(form.species.resourceIdentifier)?.getFormRecord(form.name)?.highestLevel ?: -1
hasDiscoveredMove(form, search, highestLevel, pokedexManager, learnedTMs)
}
}
SearchByType.DROPS -> {
if (pokedexManager.getHighestKnowledgeFor(entry) !== PokedexEntryProgress.CAUGHT) return false
val dropsList = mutableListOf<String>()
@@ -93,132 +68,4 @@ class SearchFilter(val pokedexManager: AbstractPokedexManager, val searchString:
}
}
private fun hasDiscoveredMove(
form: FormData,
search: String,
highestLevel: Int,
pokedex: AbstractPokedexManager,
learnedTMs: Set<ResourceLocation>
): Boolean {
val evolutionMoveLevels = buildEvolutionMoveLevelIndex(form)
fun matches(move: MoveTemplate): Boolean {
return move.displayName.string.lowercase().contains(search)
}
fun isLevelUpDiscovered(move: MoveTemplate, level: Int): Boolean {
if (highestLevel >= level) return true
for ((evolutionSpeciesId, moveLevels) in evolutionMoveLevels) {
val evolutionLevel = moveLevels[move.name] ?: continue
val evolutionHighest = pokedex.getSpeciesRecord(evolutionSpeciesId)?.highestLevel ?: -1
if (evolutionHighest >= evolutionLevel) return true
}
return false
}
form.moves.levelUpMoves.forEach { (level, moves) ->
for (move in moves) {
if (matches(move) && isLevelUpDiscovered(move, level)) return true
}
}
form.moves.tmMoves.forEach { move ->
val tmId = TechnicalMachines.moveToTM[move]?.id
val discovered = tmId == null || tmId in learnedTMs
if (discovered && matches(move)) return true
}
val alwaysDiscovered = sequenceOf(
form.moves.tutorMoves,
form.moves.eggMoves,
form.moves.evolutionMoves,
form.moves.formChangeMoves,
form.moves.specialMoves,
form.moves.legacyMoves
)
for (moves in alwaysDiscovered) {
for (move in moves) {
if (matches(move)) return true
}
}
return false
}
private fun buildEvolutionMoveLevelIndex(form: FormData): Map<ResourceLocation, Map<String, Int>> {
val evolutionForms = collectEvolutionForms(form)
val levelsBySpecies = mutableMapOf<ResourceLocation, MutableMap<String, Int>>()
for (evolutionForm in evolutionForms) {
val speciesId = evolutionForm.species.resourceIdentifier
val moveLevels = levelsBySpecies.getOrPut(speciesId) { mutableMapOf() }
buildMoveLevelIndex(evolutionForm).forEach { (moveName, level) ->
val current = moveLevels[moveName]
if (current == null || level < current) {
moveLevels[moveName] = level
}
}
}
return levelsBySpecies
}
private fun buildMoveLevelIndex(form: FormData): Map<String, Int> {
val levels = mutableMapOf<String, Int>()
form.moves.levelUpMoves.forEach { (level, moves) ->
moves.forEach { move ->
val current = levels[move.name]
if (current == null || level < current) {
levels[move.name] = level
}
}
}
return levels
}
private fun collectEvolutionForms(rootForm: FormData): List<FormData> {
val results = mutableListOf<FormData>()
val visited = mutableSetOf<String>()
fun key(form: FormData): String {
return "${form.species.resourceIdentifier}|${form.name.lowercase()}"
}
fun nextEvolutions(current: FormData): Set<Evolution> {
return if (current.evolutions.isNotEmpty()) {
current.evolutions
} else {
current.species.evolutions
}
}
fun traverse(current: FormData) {
for (evolution in nextEvolutions(current)) {
val evolutionForm = resolveEvolutionForm(evolution) ?: continue
val evolutionKey = key(evolutionForm)
if (!visited.add(evolutionKey)) continue
results.add(evolutionForm)
traverse(evolutionForm)
}
}
traverse(rootForm)
return results
}
private fun resolveEvolutionForm(evolution: Evolution): FormData? {
val speciesId = evolution.result.species?.asIdentifierDefaultingNamespace() ?: return null
val species = PokemonSpecies.getByIdentifier(speciesId) ?: return null
val formId = evolution.result.form
return if (formId == null) {
species.standardForm
} else {
species.forms.firstOrNull {
it.formOnlyShowdownId().equals(formId, ignoreCase = true) || it.name.equals(formId, ignoreCase = true)
} ?: species.standardForm
}
}
}
}
@@ -1,166 +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.api.pokedex.filter
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import net.minecraft.resources.ResourceLocation
enum class SecondaryPokedexFilterType {
ALL,
CAUGHT,
SEEN,
UNDISCOVERED,
LEVEL_UP_TM_UNDISCOVERED
}
class SecondaryPokedexFilter(
private val pokedexManager: AbstractPokedexManager,
private val filterType: SecondaryPokedexFilterType
) : EntryFilter() {
override fun test(entry: PokedexEntry): Boolean {
return when (filterType) {
SecondaryPokedexFilterType.ALL -> true
SecondaryPokedexFilterType.CAUGHT -> pokedexManager.getHighestKnowledgeFor(entry) >= PokedexEntryProgress.CAUGHT
SecondaryPokedexFilterType.SEEN -> pokedexManager.getHighestKnowledgeFor(entry) == PokedexEntryProgress.ENCOUNTERED
SecondaryPokedexFilterType.UNDISCOVERED -> pokedexManager.getHighestKnowledgeFor(entry) == PokedexEntryProgress.NONE
SecondaryPokedexFilterType.LEVEL_UP_TM_UNDISCOVERED -> hasUndiscoveredLevelUpTM(entry)
}
}
private fun hasUndiscoveredLevelUpTM(entry: PokedexEntry): Boolean {
if (pokedexManager.getHighestKnowledgeFor(entry) != PokedexEntryProgress.CAUGHT) return false
val species = PokemonSpecies.getByIdentifier(entry.speciesId) ?: return false
val forms = pokedexManager.getCaughtForms(entry)
val formData = if (forms.isEmpty()) {
listOf(species.standardForm)
} else {
forms.map { form ->
species.forms.find { it.name.equals(form.displayForm, ignoreCase = true) } ?: species.standardForm
}
}
return formData.any { form ->
val highestLevel = pokedexManager.getSpeciesRecord(form.species.resourceIdentifier)?.getFormRecord(form.name)?.highestLevel ?: -1
hasUndiscoveredLevelUpTM(form, highestLevel)
}
}
private fun hasUndiscoveredLevelUpTM(
form: FormData,
highestLevel: Int
): Boolean {
val evolutionMoveLevels = buildEvolutionMoveLevelIndex(form)
fun isLevelUpDiscovered(move: MoveTemplate, level: Int): Boolean {
if (highestLevel >= level) return true
for ((evolutionSpeciesId, moveLevels) in evolutionMoveLevels) {
val evolutionLevel = moveLevels[move.name] ?: continue
val evolutionHighest = pokedexManager.getSpeciesRecord(evolutionSpeciesId)?.highestLevel ?: -1
if (evolutionHighest >= evolutionLevel) return true
}
return false
}
form.moves.levelUpMoves.forEach { (level, moves) ->
for (move in moves) {
if (TechnicalMachines.moveToTM[move] != null && !isLevelUpDiscovered(move, level)) {
return true
}
}
}
return false
}
private fun buildEvolutionMoveLevelIndex(form: FormData): Map<ResourceLocation, Map<String, Int>> {
val evolutionForms = collectEvolutionForms(form)
val levelsBySpecies = mutableMapOf<ResourceLocation, MutableMap<String, Int>>()
for (evolutionForm in evolutionForms) {
val speciesId = evolutionForm.species.resourceIdentifier
val moveLevels = levelsBySpecies.getOrPut(speciesId) { mutableMapOf() }
buildMoveLevelIndex(evolutionForm).forEach { (moveName, level) ->
val current = moveLevels[moveName]
if (current == null || level < current) {
moveLevels[moveName] = level
}
}
}
return levelsBySpecies
}
private fun buildMoveLevelIndex(form: FormData): Map<String, Int> {
val levels = mutableMapOf<String, Int>()
form.moves.levelUpMoves.forEach { (level, moves) ->
moves.forEach { move ->
val current = levels[move.name]
if (current == null || level < current) {
levels[move.name] = level
}
}
}
return levels
}
private fun collectEvolutionForms(rootForm: FormData): List<FormData> {
val results = mutableListOf<FormData>()
val visited = mutableSetOf<String>()
fun key(form: FormData): String {
return "${form.species.resourceIdentifier}|${form.name.lowercase()}"
}
fun nextEvolutions(current: FormData): Set<Evolution> {
return if (current.evolutions.isNotEmpty()) {
current.evolutions
} else {
current.species.evolutions
}
}
fun traverse(current: FormData) {
for (evolution in nextEvolutions(current)) {
val evolutionForm = resolveEvolutionForm(evolution) ?: continue
val evolutionKey = key(evolutionForm)
if (!visited.add(evolutionKey)) continue
results.add(evolutionForm)
traverse(evolutionForm)
}
}
traverse(rootForm)
return results
}
private fun resolveEvolutionForm(evolution: Evolution): FormData? {
val speciesId = evolution.result.species?.asIdentifierDefaultingNamespace() ?: return null
val species = PokemonSpecies.getByIdentifier(speciesId) ?: return null
val formId = evolution.result.form
return if (formId == null) {
species.standardForm
} else {
species.forms.firstOrNull {
it.formOnlyShowdownId().equals(formId, ignoreCase = true) || it.name.equals(formId, ignoreCase = true)
} ?: species.standardForm
}
}
}
@@ -316,11 +316,11 @@ object PokemonSpecies : JsonDataRegistry<Species> {
val result = mutableMapOf<String, String>()
this.species.forEach {species ->
val baseSpecies = ShowdownSpecies(species, null)
result[ShowdownIdentifiable.REGEX.replace(baseSpecies.name.lowercase(), "")] = this.gson.toJson(baseSpecies)
result[ShowdownIdentifiable.REGEX.replace(baseSpecies.name, "")] = this.gson.toJson(baseSpecies)
species.forms.forEach { form ->
if (form != species.standardForm) {
val formSpecies = ShowdownSpecies(species, form)
result[ShowdownIdentifiable.REGEX.replace(formSpecies.name.lowercase(), "")] = this.gson.toJson(formSpecies)
result[ShowdownIdentifiable.REGEX.replace(formSpecies.name, "")] = this.gson.toJson(formSpecies)
}
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.pokemon.moves
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.data.ClientDataSynchronizer
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
@@ -20,7 +19,9 @@ import com.google.gson.JsonElement
import net.minecraft.network.RegistryFriendlyByteBuf
open class Learnset : ClientDataSynchronizer<Learnset> {
class Interpreter(val loadMove: (JsonElement, Learnset) -> Boolean) {
class Interpreter(
val loadMove: (JsonElement, Learnset) -> Boolean
) {
companion object {
fun parseFromPrefixIntoList(prefix: String, list: (Learnset) -> MutableList<MoveTemplate>): Interpreter {
return Interpreter { element, learnset ->
@@ -72,7 +73,8 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
eggInterpreter,
tutorInterpreter,
legacyInterpreter,
specialInterpreter, levelUpInterpreter,
specialInterpreter,
levelUpInterpreter,
formChangeInterpreter
)
}
@@ -97,20 +99,6 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
.flatMap { it.value }
.toSet()
fun tmLearnableMoves(): MutableList<MoveTemplate> {
val moves = mutableListOf<MoveTemplate>()
moves.addAll(tmMoves)
moves.addAll(tutorMoves)
moves.addAll(eggMoves)
moves.addAll(getLevelUpMovesUpTo(Cobblemon.config.maxPokemonLevel))
moves.addAll(evolutionMoves)
moves.addAll(formChangeMoves)
return moves
}
fun getAllLegalMoves(): Set<MoveTemplate> {
return levelUpMoves.values.flatten().toSet() +
eggMoves +
@@ -77,7 +77,6 @@ abstract class PokemonStore<T : StorePosition> : Iterable<Pokemon> {
remove(pokemon)
val position = getFirstAvailablePosition() ?: return false // Couldn't fit, shrug emoji
set(position, pokemon)
Cobblemon.playerDataManager.getTMData(uuid)?.syncTMsFromPokemon(pokemon)
return true
}
@@ -171,4 +170,4 @@ abstract class PokemonStore<T : StorePosition> : Iterable<Pokemon> {
* each change, accessed by [Pokemon.getChangeObservable].
*/
abstract fun getAnyChangeObservable(): Observable<Unit>
}
}
@@ -332,7 +332,7 @@ open class PCStore(
val toast = Toast(
title = lang("wallpaper_unlocked"),
description = unlockableWallpaper.displayName?.let { "\"".text().add(it.asTranslated()).add("\"") } ?: lang("unknown_wallpaper") ,
icons = listOf(ItemStack(CobblemonItems.PC))
icon = ItemStack(CobblemonItems.PC)
)
toast.addListeners(player)
toast.expireAfter(5F)
@@ -8,17 +8,14 @@
package com.cobblemon.mod.common.api.storage.player
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.MODID
import com.cobblemon.mod.common.api.pokedex.PokedexManager
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.api.storage.player.client.ClientInstancedPlayerData
import com.cobblemon.mod.common.api.storage.player.factory.CachedPlayerDataStoreFactory
import com.cobblemon.mod.common.api.tms.TMMoveManager
import com.google.common.util.concurrent.ThreadFactoryBuilder
import java.util.UUID
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Player
@@ -33,6 +30,7 @@ import net.minecraft.world.entity.player.Player
* @author Apion
* @since February 21, 2024
*/
open class PlayerInstancedDataStoreManager {
val saveExecutor = Executors.newSingleThreadExecutor(
ThreadFactoryBuilder()
@@ -55,27 +53,19 @@ open class PlayerInstancedDataStoreManager {
//Should put this somewhere else
saveTasks[PlayerInstancedDataStoreTypes.GENERAL] = ScheduledTask.Builder()
.execute { saveAllOfOneType(PlayerInstancedDataStoreTypes.GENERAL) }
.delay(30F)
.interval(120F)
.delay(30f)
.interval(120f)
.infiniteIterations()
.tracker(ServerTaskTracker)
.build()
saveTasks[PlayerInstancedDataStoreTypes.POKEDEX] = ScheduledTask.Builder()
.execute { saveAllOfOneType(PlayerInstancedDataStoreTypes.POKEDEX) }
.delay(30F)
.interval(120F)
.delay(30f)
.interval(120f)
.infiniteIterations()
.tracker(ServerTaskTracker)
.build()
saveTasks[PlayerInstancedDataStoreTypes.TM_MOVES] = ScheduledTask.Builder()
.execute { saveAllOfOneType(PlayerInstancedDataStoreTypes.TM_MOVES) }
.delay(30F)
.interval(120F)
.infiniteIterations()
.tracker(ServerTaskTracker)
.build()
}
open fun get(playerId: UUID, dataType: PlayerInstancedDataStoreType): InstancedPlayerData {
@@ -136,16 +126,4 @@ open class PlayerInstancedDataStoreManager {
open fun getPokedexData(playerId: UUID): PokedexManager {
return get(playerId, PlayerInstancedDataStoreTypes.POKEDEX) as PokedexManager
}
fun getTMData(player: ServerPlayer): TMMoveManager {
return getTMData(player.uuid)
}
fun getTMData(playerId: UUID): TMMoveManager {
val data = get(playerId, PlayerInstancedDataStoreTypes.TM_MOVES)
if (data !is TMMoveManager) {
Cobblemon.LOGGER.warn("getTMData: No TMMoveManager found for $playerId (got ${data?.javaClass?.name})")
}
return data as TMMoveManager
}
}
}
@@ -8,10 +8,8 @@
package com.cobblemon.mod.common.api.storage.player
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
import com.cobblemon.mod.common.api.storage.player.client.ClientGeneralPlayerData
import com.cobblemon.mod.common.api.storage.player.client.ClientTMMoveManager
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.resources.ResourceLocation
@@ -29,22 +27,11 @@ object PlayerInstancedDataStoreTypes {
ClientPokedexManager::runAction,
ClientPokedexManager::runIncremental
))
val TM_MOVES = register(PlayerInstancedDataStoreType(
cobblemonResource("tm_moves"),
ClientTMMoveManager::decode,
ClientTMMoveManager::runAction,
ClientTMMoveManager::runIncremental
))
fun register(type: PlayerInstancedDataStoreType): PlayerInstancedDataStoreType {
Cobblemon.LOGGER.debug("Registering PlayerInstancedDataStoreType: {}", type.id)
types[type.id] = type
return type
}
fun getTypeById(id: ResourceLocation): PlayerInstancedDataStoreType? {
val type = types[id]
if (type == null) Cobblemon.LOGGER.warn("Unknown PlayerInstancedDataStoreType ID: {}", id)
return type
}
}
fun getTypeById(id: ResourceLocation) = types[id]
}
@@ -1,35 +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.api.storage.player.adapter
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.api.tms.TMMoveManager
import com.cobblemon.mod.common.util.adapters.CodecBackedAdapter
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import java.util.UUID
class TMMoveJsonBackend : JsonBackedPlayerDataStoreBackend<TMMoveManager>("tm_moves", PlayerInstancedDataStoreTypes.TM_MOVES) {
override val gson = GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
.registerTypeAdapter(TMMoveManager::class.java, CodecBackedAdapter(TMMoveManager.CODEC))
.create()
override val classToken = TypeToken.get(TMMoveManager::class.java)
override val defaultData = defaultDataFunc
override fun initialize(store: TMMoveManager) {
store.initialize()
}
companion object {
val defaultDataFunc = { uuid: UUID -> TMMoveManager(uuid) }
}
}
@@ -1,42 +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.api.storage.player.adapter
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.api.tms.TMMoveManager
import com.cobblemon.mod.common.util.adapters.CodecBackedAdapter
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import com.mongodb.client.MongoClient
import java.util.UUID
class TMMoveMongoBackend(
mongoClient: MongoClient,
databaseName: String,
collectionName: String
) : MongoBackedPlayerDataStoreBackend<TMMoveManager>(
mongoClient,
databaseName,
collectionName,
PlayerInstancedDataStoreTypes.TM_MOVES
) {
override val gson = GsonBuilder()
.registerTypeAdapter(TMMoveManager::class.java, CodecBackedAdapter(TMMoveManager.CODEC))
.create()
override val classToken = TypeToken.get(TMMoveManager::class.java)
override val defaultData = defaultDataFunc
override fun initialize(store: TMMoveManager) {
store.initialize()
}
companion object {
val defaultDataFunc = { uuid: UUID -> TMMoveManager(uuid) }
}
}
@@ -1,18 +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.api.storage.player.adapter
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.api.tms.TMMoveManager
import java.util.UUID
class TMMoveNbtBackend : NbtBackedPlayerData<TMMoveManager>("tm_moves", PlayerInstancedDataStoreTypes.TM_MOVES) {
override val codec = TMMoveManager.CODEC
override val defaultData = TMMoveJsonBackend.defaultDataFunc
}
@@ -1,49 +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.api.storage.player.client
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeString
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
class ClientTMMoveManager(
val learnedTMs: MutableSet<ResourceLocation>
) : ClientInstancedPlayerData {
override fun encode(buf: RegistryFriendlyByteBuf) {
buf.writeCollection(learnedTMs) { _, id -> buf.writeString(id.toString()) }
}
companion object {
fun decode(buf: RegistryFriendlyByteBuf): SetClientPlayerDataPacket {
val tms = buf.readCollection(
{ mutableSetOf<ResourceLocation>() },
{ buf.readString().let(ResourceLocation::tryParse) ?: ResourceLocation("minecraft", "empty") }
)
return SetClientPlayerDataPacket(
PlayerInstancedDataStoreTypes.TM_MOVES,
ClientTMMoveManager(tms)
)
}
fun runAction(data: ClientInstancedPlayerData) {
if (data !is ClientTMMoveManager) return
CobblemonClient.clientTMMoveData = data
}
fun runIncremental(data: ClientInstancedPlayerData) {
if (data !is ClientTMMoveManager) return
CobblemonClient.clientTMMoveData.learnedTMs.addAll(data.learnedTMs)
}
}
}
@@ -40,6 +40,8 @@ class CachedPlayerDataStoreFactory<T : InstancedPlayerData>(val backend: PlayerD
}
}
override fun saveAll() {
cache.forEach { (_, pd) -> backend.save(pd) }
cache.removeIf { (uuid, _) -> uuid.getPlayer() == null }
@@ -91,7 +91,6 @@ object CobblemonBlockTags {
@JvmField val TUMBLESTONE_BRICKS = createTag("tumblestone_bricks")
@JvmField val TUMBLESTONES = createTag("tumblestones")
@JvmField val TUMBLESTONE_HEAT_SOURCE = createTag("tumblestone_heat_source")
@JvmField val TYPE_GEM_BLOCKS = createTag("type_gem_blocks")
@JvmField val WALL_HANGING_SIGNS = createTag("wall_hanging_signs")
@JvmField val WALL_SIGNS = createTag("wall_signs")
@JvmField val WATER_STONE_ORES = createTag("water_stone_ores")
@@ -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.api.tms
import com.cobblemon.mod.common.tms.obtain.DefaultObtainMethod
import com.cobblemon.mod.common.tms.obtain.PlayerHasAdvancementObtainMethod
import com.cobblemon.mod.common.tms.obtain.PlayerYObtainMethod
import com.cobblemon.mod.common.tms.obtain.UnlockableObtainMethod
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.server.level.ServerPlayer
interface ObtainMethod {
val passive: Boolean
/**
* Checks if this [ObtainMethod] matches the given player.
*/
fun matches(player: ServerPlayer): Boolean
/**
* Serializes this [ObtainMethod] to the buffer.
*/
fun writeToBuffer(buffer: RegistryFriendlyByteBuf)
companion object {
/**
* Deserializes an [ObtainMethod] from the buffer.
*/
fun readFromBuffer(buffer: RegistryFriendlyByteBuf): ObtainMethod {
val variant = buffer.readUtf()
return when (variant) {
"cobblemon:unlockable" -> UnlockableObtainMethod()
"cobblemon:default" -> DefaultObtainMethod()
"cobblemon:advancement" -> {
val advancement = buffer.readResourceLocation()
PlayerHasAdvancementObtainMethod(advancement)
}
"cobblemon:y_level" -> {
val yLevel = buffer.readVarInt()
val operator = buffer.readUtf()
PlayerYObtainMethod(yLevel, operator)
}
else -> throw IllegalArgumentException("Unknown ObtainMethod variant: $variant")
}
}
}
}
@@ -1,177 +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.api.tms
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.api.storage.player.InstancedPlayerData
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.api.storage.player.client.ClientTMMoveManager
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
import com.cobblemon.mod.common.net.messages.client.toast.ToastPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.lang
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import java.util.UUID
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
class TMMoveManager(
override val uuid: UUID,
val learnedTMs: MutableSet<ResourceLocation> = mutableSetOf()
) : InstancedPlayerData {
val storeType = PlayerInstancedDataStoreTypes.TM_MOVES
override fun toClientData() = ClientTMMoveManager(learnedTMs.toMutableSet())
fun toClientDataFrom(set: Set<ResourceLocation>) = ClientTMMoveManager(set.toMutableSet())
fun syncTMsFromPokemon(pokemon: Pokemon) {
learn(getLearnableTMsFromPokemon(pokemon))
}
fun syncTMFromMove(moveTemplate: MoveTemplate) {
val tmId = TechnicalMachines.moveToTM[moveTemplate]?.id ?: return
learn(listOf(tmId))
}
fun getLearnableTMsFromPokemon(pokemon: Pokemon): Collection<ResourceLocation> {
return TechnicalMachines.tmMap.values
.filter { tm -> tm.moveName in pokemon.allAccessibleMoves }
.map(TechnicalMachine::id)
}
fun scheduleFullSyncFromStores(
party: Iterable<Pokemon?>,
pc: Iterable<Pokemon?>,
batchSize: Int = 25
) {
val iterator = sequence {
for (pokemon in party) {
if (pokemon != null) yield(pokemon)
}
for (pokemon in pc) {
if (pokemon != null) yield(pokemon)
}
}.iterator()
if (!iterator.hasNext()) return
val tmIds = mutableSetOf<ResourceLocation>()
ScheduledTask.Builder()
.tracker(ServerTaskTracker)
.interval(0F)
.infiniteIterations()
.execute { task ->
var processed = 0
while (processed < batchSize && iterator.hasNext()) {
val pokemon = iterator.next()
for (move in pokemon.allAccessibleMoves) {
TechnicalMachines.moveToTM[move]?.id?.let { tmIds.add(it) }
}
processed++
}
if (!iterator.hasNext()) {
learn(tmIds)
task.expire()
}
}
.build()
}
fun learn(tmIds: Collection<ResourceLocation>): Boolean {
val newLearnedTms = mutableListOf<ResourceLocation>()
for (tmId in tmIds) {
if (learnedTMs.add(tmId)) {
newLearnedTms.add(tmId)
}
}
if (newLearnedTms.isEmpty()) return false
syncClient(newLearnedTms.toSet())
sendTMToast(newLearnedTms)
return true
}
fun unlearn(tmIds: Collection<ResourceLocation>): Boolean {
val removedTms = mutableListOf<ResourceLocation>()
for (tmId in tmIds) {
if (learnedTMs.remove(tmId)) {
removedTms.add(tmId)
}
}
if (removedTms.isEmpty()) return false
syncClient(learnedTMs, isIncremental = false)
return true
}
fun sendTMToast(tmIds: List<ResourceLocation>) {
val player = uuid.getPlayer() ?: return
var moveName: Component = Component.empty()
val icons = tmIds.mapNotNull {
val tm = TechnicalMachines.tmMap[it] ?: return@mapNotNull null
moveName = tm.translatedMoveName()
return@mapNotNull tm.createItemStack()
}
val description = if (icons.size == 1) moveName else lang("tms.check_tmm")
val packet = ToastPacket(
title = lang("tms.new_tms_learned"),
description = description,
icons = icons,
frameTexture = ResourceLocation.parse("minecraft:toast/advancement"),
progress = -1F,
progressColor = 0x00FF00,
// Keeping it a fixed UUID makes it so the ToastTracker merges different toasts on the client
uuid = UUID.nameUUIDFromBytes("tm_toast".toByteArray()),
behaviour = ToastPacket.Behaviour.SHOW_OR_UPDATE,
durationMs = 4000L
)
player.sendPacket(packet)
}
fun syncClient(updateSet: Set<ResourceLocation> = learnedTMs, isIncremental: Boolean = true) {
uuid.getPlayer()?.sendPacket(
SetClientPlayerDataPacket(
type = storeType,
playerData = toClientDataFrom(updateSet),
isIncremental = isIncremental
)
)
}
companion object {
val CODEC: Codec<TMMoveManager> = RecordCodecBuilder.create { instance ->
instance.group(
Codec.STRING.xmap(UUID::fromString, UUID::toString)
.fieldOf("uuid")
.forGetter { it.uuid },
Codec.list(ResourceLocation.CODEC)
.xmap({ it.toMutableSet() }, { it.toList() })
.fieldOf("learnedTMs")
.forGetter { it.learnedTMs }
).apply(instance, ::TMMoveManager)
}
}
}
@@ -1,126 +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.api.tms
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.item.components.TMMoveComponent
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.tms.obtain.DefaultObtainMethod
import com.cobblemon.mod.common.util.lang
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
class TechnicalMachine(
val moveName: MoveTemplate,
val recipe: List<TechnicalMachineRecipe>?,
val obtainMethods: List<ObtainMethod> = emptyList(),
val type: String
) {
companion object {
/**
* Filters all available [TechnicalMachines] based on three optional arguments
*
* @param search A [String] to check against move names
* @param type An [ElementalType] to check for
* @param pokemon A [Pokemon] to check the [Learnset] of
* @return A [MutableSet] of [TechnicalMachine] that passed the filter
*/
fun filterTms(
search: String?,
type: ElementalType?,
pokemon: Pokemon?,
player: Player? = null,
includeUnlearned: Boolean = false
): MutableSet<TechnicalMachine> {
val tms = TechnicalMachines.tmMap.values.toMutableSet()
type?.let {
val iterator = tms.iterator()
while (iterator.hasNext()) {
val tm = iterator.next()
if (ElementalTypes.get(tm.type) != type) {
iterator.remove()
}
}
}
pokemon?.let {
val iterator = tms.iterator()
while (iterator.hasNext()) {
val tm = iterator.next()
if (tm.moveName !in pokemon.form.moves.tmLearnableMoves()) {
iterator.remove()
}
}
}
search?.let {
val iterator = tms.iterator()
while (iterator.hasNext()) {
val tm = iterator.next()
if (!tm.translatedMoveName().string.contains(search, ignoreCase = true)) {
iterator.remove()
}
}
}
if (player != null && !includeUnlearned) {
val learnedTMs = CobblemonClient.clientTMMoveData.learnedTMs
tms.retainAll { tm -> tm.isPassivelyObtained() || tm.id in learnedTMs }
}
return tms
}
}
lateinit var id: ResourceLocation
/**
* Gets the recipe list limited to the provided amount to match the slot amount of the TM Machine.
*
* @param limit The amount of recipe item slots to take.
* @return The recipe list limited to 3 ingredients.
*/
fun getClampedRecipe(limit: Int = 3): List<TechnicalMachineRecipe>? = recipe?.take(limit)
/**
* Unlocks this [TechnicalMachine] for the player.
*
* @param player The [ServerPlayer] to give this [TechnicalMachine] to.
* @return Whether the player was successfully granted the [TechnicalMachine]
*/
fun unlock(player: ServerPlayer): Boolean {
if (!obtainMethods.any { it is DefaultObtainMethod }) {
player.sendSystemMessage(lang("tms.unlock_tm", moveName.displayName))
}
return true
}
fun isPassivelyObtained() = obtainMethods.any { it is DefaultObtainMethod }
/**
* Returns a [MutableComponent] of the translated move name of this [TechnicalMachine]
*
* @return This [TechnicalMachine]'s move name, translated
*/
fun translatedMoveName(): MutableComponent = moveName.displayName
fun createItemStack(): ItemStack {
val stack = ItemStack(CobblemonItems.TECHNICAL_MACHINE)
TMMoveComponent.setTMMove(stack, moveName)
return stack
}
}
@@ -1,30 +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.api.tms
import com.cobblemon.mod.common.api.moves.MoveTemplate
/**
* DTO used for [TechnicalMachine] JSON loading because Gson cannot deserialize Minecraft Ingredients.
*/
data class TechnicalMachineDTO(
val moveName: MoveTemplate,
val recipe: List<TechnicalMachineRecipeDTO>?,
val obtainMethods: List<ObtainMethod> = emptyList(),
val type: String
) {
fun toTechnicalMachine(): TechnicalMachine {
return TechnicalMachine(
moveName = moveName,
recipe = recipe?.map { it.toTechnicalMachineRecipe() },
obtainMethods = obtainMethods,
type = type
)
}
}
@@ -1,19 +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.api.tms
import net.minecraft.world.item.crafting.Ingredient
/**
* Represents the ingredients necessary to craft a [TechnicalMachine] in the [TMBlock]
*/
data class TechnicalMachineRecipe(
val ingredient: Ingredient,
val count: Int
)
@@ -1,50 +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.api.tms
import com.google.gson.annotations.SerializedName
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation
import net.minecraft.tags.TagKey
import net.minecraft.world.item.crafting.Ingredient
/**
* DTO used for [TechnicalMachineRecipe] JSON loading because Gson cannot deserialize Minecraft Ingredients.
*/
data class TechnicalMachineRecipeDTO(
val item: ResourceLocation?,
val tag: ResourceLocation?,
@SerializedName("count") private val _count: Int?,
) {
private val count: Int
get() = _count ?: 1
fun toTechnicalMachineRecipe(): TechnicalMachineRecipe {
val ingredient = when {
item != null ->
Ingredient.of(
BuiltInRegistries.ITEM.get(item)
)
tag != null ->
Ingredient.of(
TagKey.create(Registries.ITEM, tag)
)
else ->
error("Recipe entry must define either 'item' or 'tag'")
}
return TechnicalMachineRecipe(
ingredient = ingredient,
count = count
)
}
}
@@ -1,74 +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.api.tms
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.util.adapters.TMObtainMethodAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import java.io.BufferedReader
import java.util.concurrent.ExecutionException
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object TechnicalMachines : JsonDataRegistry<TechnicalMachine> {
override val gson = GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(ObtainMethod::class.java, TMObtainMethodAdapter)
.registerTypeAdapter(MoveTemplate::class.java, MoveTemplateAdapter)
.create()
override val typeToken = TypeToken.get(TechnicalMachine::class.java)
override val resourcePath = "tms"
override val id = cobblemonResource("technical_machines")
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<TechnicalMachines>()
val tmMap = mutableMapOf<ResourceLocation, TechnicalMachine>()
val moveToTM = mutableMapOf<MoveTemplate, TechnicalMachine>()
/**
* Overridden to deserialize a DTO first and convert it to a runtime
* [TechnicalMachine], since Gson cannot deserialize Minecraft Ingredients
* directly.
*/
override fun parse(
reader: BufferedReader,
identifier: ResourceLocation
): TechnicalMachine {
try {
val technicalMachineDto = gson.fromJson(reader, TechnicalMachineDTO::class.java)
val technicalMachine = technicalMachineDto.toTechnicalMachine()
return technicalMachine
} catch (exception: Exception) {
throw ExecutionException(
"Error loading Technical Machine JSON: $identifier",
exception
)
}
}
override fun reload(data: Map<ResourceLocation, TechnicalMachine>) {
data.forEach { (id, tm) ->
tmMap[id] = tm
tm.id = id
moveToTM[tm.moveName] = tm
}
}
fun getByResourceLocation(resourceLocation: ResourceLocation) = tmMap[resourceLocation]
fun getAllResourceLocations() = tmMap.keys.toSet()
override fun sync(player: ServerPlayer) {}
}
@@ -28,7 +28,7 @@ import net.minecraft.world.item.ItemStack
*
* @param title The [Component] representing the toast title, this can be a [Component.empty].
* @param description The [Component] representing the toast description, this can be a [Component.empty].
* @param icons The [ItemStack] that is rendered as the toast icon, this can be a [ItemStack.EMPTY].
* @param icon The [ItemStack] that is rendered as the toast icon, this can be a [ItemStack.EMPTY].
* @param frameTexture The texture of the frame, default is the [VANILLA_BACKGROUND_SPRITE].
* @param progress The value of the progress bar, this accepts a value between 0.0F and 1.0F, any other value will not render a progress bar.
* @param progressColor The progress bar color in RGB, default is the [VANILLA_PROGRESS_COLOR].
@@ -38,7 +38,7 @@ import net.minecraft.world.item.ItemStack
class Toast(
title: Component,
description: Component,
icons: List<ItemStack>,
icon: ItemStack,
frameTexture: ResourceLocation = VANILLA_BACKGROUND_SPRITE,
progress: Float = -1F,
progressColor: Int = VANILLA_PROGRESS_COLOR
@@ -57,7 +57,7 @@ class Toast(
/**
* The [ItemStack] that is rendered as the toast icon, this can be a [ItemStack.EMPTY].
*/
var icons: List<ItemStack> by Delegates.observable(icons) { _, old, new -> if (old != new) this.launchUpdate() }
var icon: ItemStack by Delegates.observable(icon) { _, old, new -> if (old != new) this.launchUpdate() }
/**
* The texture of the frame.
@@ -185,7 +185,7 @@ class Toast(
private fun toPacket(behaviour: ToastPacket.Behaviour): ToastPacket = ToastPacket(
this.title,
this.description,
this.icons,
this.icon,
this.frameTexture,
this.progress,
this.progressColor,
@@ -29,12 +29,9 @@ class ElementalType(
val displayName: MutableComponent,
val hue: Int,
val textureXMultiplier: Int,
val primaryColor: Int,
val secondaryColor: Int,
val resourceLocation: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, "ui/types.png"),
val showdownId: String = ShowdownIdentifiable.REGEX.replace(name.lowercase(), ""),
val typeGem: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, showdownId + "_gem"),
) : ShowdownIdentifiable {
val showdownId: String = ShowdownIdentifiable.REGEX.replace(name.lowercase(), "")
) : ShowdownIdentifiable {
override fun showdownId(): String {
return showdownId
@@ -23,9 +23,7 @@ object ElementalTypes {
name = "Normal",
displayName = Component.translatable("cobblemon.type.normal"),
hue = 0xE8E8DA,
textureXMultiplier = 0,
primaryColor = 0x8FA689,
secondaryColor = 0xFFFBE2
textureXMultiplier = 0
)
@JvmField
@@ -33,9 +31,7 @@ object ElementalTypes {
name = "Fire",
displayName = Component.translatable("cobblemon.type.fire"),
hue = 0xFF6E21,
textureXMultiplier = 1,
primaryColor = 0xF92D2D,
secondaryColor = 0xF99B1D
textureXMultiplier = 1
)
@JvmField
@@ -43,9 +39,7 @@ object ElementalTypes {
name = "Water",
displayName = Component.translatable("cobblemon.type.water"),
hue = 0x3FA5FF,
textureXMultiplier = 2,
primaryColor = 0x377AF6,
secondaryColor = 0x50DDF9
textureXMultiplier = 2
)
@JvmField
@@ -53,9 +47,7 @@ object ElementalTypes {
name = "Grass",
displayName = Component.translatable("cobblemon.type.grass"),
hue = 0x62D14F,
textureXMultiplier = 3,
primaryColor = 0x0EAE95,
secondaryColor = 0xC6F554
textureXMultiplier = 3
)
@JvmField
@@ -63,9 +55,7 @@ object ElementalTypes {
name = "Electric",
displayName = Component.translatable("cobblemon.type.electric"),
hue = 0xFFD314,
textureXMultiplier = 4,
primaryColor = 0xF7AB19,
secondaryColor = 0xFFF257
textureXMultiplier = 4
)
@JvmField
@@ -73,9 +63,7 @@ object ElementalTypes {
name = "Ice",
displayName = Component.translatable("cobblemon.type.ice"),
hue = 0x54F2F2,
textureXMultiplier = 5,
primaryColor = 0x41BAED,
secondaryColor = 0x91F4F9
textureXMultiplier = 5
)
@JvmField
@@ -83,9 +71,7 @@ object ElementalTypes {
name = "Fighting",
displayName = Component.translatable("cobblemon.type.fighting"),
hue = 0xEF565D,
textureXMultiplier = 6,
primaryColor = 0xC8356B,
secondaryColor = 0xF36A3F
textureXMultiplier = 6
)
@JvmField
@@ -93,9 +79,7 @@ object ElementalTypes {
name = "Poison",
displayName = Component.translatable("cobblemon.type.poison"),
hue = 0xD651FF,
textureXMultiplier = 7,
primaryColor = 0x7B41DD,
secondaryColor = 0xE568F4
textureXMultiplier = 7
)
@JvmField
@@ -103,9 +87,7 @@ object ElementalTypes {
name = "Ground",
displayName = Component.translatable("cobblemon.type.ground"),
hue = 0xF4A453,
textureXMultiplier = 8,
primaryColor = 0xE08835,
secondaryColor = 0xFFD057
textureXMultiplier = 8
)
@JvmField
@@ -113,9 +95,7 @@ object ElementalTypes {
name = "Flying",
displayName = Component.translatable("cobblemon.type.flying"),
hue = 0xB8B2FF,
textureXMultiplier = 9,
primaryColor = 0x6083F6,
secondaryColor = 0xD5CCFA
textureXMultiplier = 9
)
@JvmField
@@ -123,9 +103,7 @@ object ElementalTypes {
name = "Psychic",
displayName = Component.translatable("cobblemon.type.psychic"),
hue = 0xFF5E9E,
textureXMultiplier = 10,
primaryColor = 0xE553CE,
secondaryColor = 0xF69DD5
textureXMultiplier = 10
)
@JvmField
@@ -133,9 +111,7 @@ object ElementalTypes {
name = "Bug",
displayName = Component.translatable("cobblemon.type.bug"),
hue = 0xD3D319,
textureXMultiplier = 11,
primaryColor = 0x9FBE1B,
secondaryColor = 0xE9FB5B
textureXMultiplier = 11
)
@JvmField
@@ -143,9 +119,7 @@ object ElementalTypes {
name = "Rock",
displayName = Component.translatable("cobblemon.type.rock"),
hue = 0xB7A16E,
textureXMultiplier = 12,
primaryColor = 0x9F814A,
secondaryColor = 0xEECB7A
textureXMultiplier = 12
)
@JvmField
@@ -153,9 +127,7 @@ object ElementalTypes {
name = "Ghost",
displayName = Component.translatable("cobblemon.type.ghost"),
hue = 0x9C80F7,
textureXMultiplier = 13,
primaryColor = 0x4F57DD,
secondaryColor = 0xC182F1
textureXMultiplier = 13
)
@JvmField
@@ -163,9 +135,7 @@ object ElementalTypes {
name = "Dragon",
displayName = Component.translatable("cobblemon.type.dragon"),
hue = 0x7580FF,
textureXMultiplier = 14,
primaryColor = 0x4E68E2,
secondaryColor = 0x47C7F7
textureXMultiplier = 14
)
@JvmField
@@ -173,9 +143,7 @@ object ElementalTypes {
name = "Dark",
displayName = Component.translatable("cobblemon.type.dark"),
hue = 0x587DA0,
textureXMultiplier = 15,
primaryColor = 0x465B99,
secondaryColor = 0x689EBF
textureXMultiplier = 15
)
@JvmField
@@ -183,9 +151,7 @@ object ElementalTypes {
name = "Steel",
displayName = Component.translatable("cobblemon.type.steel"),
hue = 0xABD1F4,
textureXMultiplier = 16,
primaryColor = 0x628CB9,
secondaryColor = 0xD6F7F3
textureXMultiplier = 16
)
@JvmField
@@ -193,20 +159,16 @@ object ElementalTypes {
name = "Fairy",
displayName = Component.translatable("cobblemon.type.fairy"),
hue = 0xFF7FE5,
textureXMultiplier = 17,
primaryColor = 0xE2678C,
secondaryColor = 0xF5A7B7
textureXMultiplier = 17
)
@JvmStatic
fun register(name: String, displayName: MutableComponent, hue: Int, textureXMultiplier: Int, primaryColor: Int, secondaryColor: Int): ElementalType {
fun register(name: String, displayName: MutableComponent, hue: Int, textureXMultiplier: Int): ElementalType {
return ElementalType(
name = name,
displayName = displayName,
hue = hue,
textureXMultiplier = textureXMultiplier,
primaryColor = primaryColor,
secondaryColor = secondaryColor
textureXMultiplier = textureXMultiplier
).also {
allTypes.add(it)
}
@@ -311,4 +273,4 @@ object ElementalTypes {
@JvmName("getFAIRY")
@Deprecated("Use ElementalTypes.FAIRY, provided for backwards compatibility until Cobblemon 1.8.")
fun getFAIRY() = FAIRY
}
}
@@ -21,7 +21,6 @@ import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.DirectionalBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.Property
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape
@@ -43,8 +42,6 @@ abstract class GrowableStoneBlock(
val nextStage: Block?
) : DirectionalBlock(settings) {
abstract val growthChance: Int
private val upShape: VoxelShape = Block.box(
xzOffset.toDouble(),
0.0,
@@ -98,26 +95,20 @@ abstract class GrowableStoneBlock(
.setValue(FACING, Direction.DOWN))
}
abstract fun canGrow(state: BlockState, pos: BlockPos, world: BlockGetter): Boolean
abstract fun canGrow(pos: BlockPos, world: BlockGetter): Boolean
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING)
}
override fun isRandomlyTicking(state: BlockState): Boolean = stage < MAX_STAGE
override fun randomTick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
if (world.random.nextInt(growthChance) == 0 && canGrow(state, pos, world)) {
if (world.random.nextInt(5) == 0 && canGrow(pos, world)) {
val block = nextStage
if (block != null) {
var newState = block.defaultBlockState()
@Suppress("UNCHECKED_CAST")
for (property in state.properties) {
if (newState.hasProperty(property)) {
newState = newState.setValue(property as Property<Comparable<Any>>, state.getValue(property) as Comparable<Any>)
}
}
val newState = block.defaultBlockState().setValue(FACING, state.getValue(FACING)) as BlockState
world.setBlockAndUpdate(pos, newState)
}
}
@@ -164,4 +155,14 @@ abstract class GrowableStoneBlock(
else -> upShape
}
}
companion object {
const val STAGE_0 = 0
const val STAGE_1 = 1
const val STAGE_2 = 2
const val STAGE_3 = 3
const val MAX_STAGE = STAGE_3
const val MIN_STAGE = STAGE_0
}
}
@@ -16,11 +16,6 @@ import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.util.StringRepresentable
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
@@ -33,7 +28,6 @@ import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
import net.minecraft.world.level.block.state.properties.EnumProperty
import net.minecraft.world.level.pathfinder.PathComputationType
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
@@ -41,8 +35,7 @@ import net.minecraft.world.phys.shapes.VoxelShape
class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
init {
registerDefaultState(stateDefinition.any()
.setValue(HORIZONTAL_FACING, Direction.NORTH)
.setValue(SCREEN, MonitorScreen.OFF))
.setValue(HORIZONTAL_FACING, Direction.NORTH))
}
override fun createMultiBlockEntity(
@@ -62,47 +55,6 @@ class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
return defaultBlockState().setValue(HORIZONTAL_FACING, blockPlaceContext.horizontalDirection)
}
override fun useWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hit: BlockHitResult
): InteractionResult {
val entity = level.getBlockEntity(pos) as? FossilMultiblockEntity ?: return InteractionResult.PASS
return entity.handleUseWithoutItem(state, level, pos, player)
}
override fun useItemOn(
stack: ItemStack,
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hand: InteractionHand,
hit: BlockHitResult
): ItemInteractionResult {
val entity = level.getBlockEntity(pos) as? FossilMultiblockEntity
?: return ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION
val result = entity.handleUseItem(stack, state, level, pos, player, hand)
if (result == ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION) {
return if (entity.handleUseWithoutItem(state, level, pos, player).consumesAction()) {
ItemInteractionResult.sidedSuccess(level.isClientSide)
} else {
ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION
}
}
return result
}
override fun onRemove(state: BlockState, level: Level, pos: BlockPos, newState: BlockState, movedByPiston: Boolean) {
if (state.block != newState.block && !level.isClientSide && !movedByPiston) {
val entity = level.getBlockEntity(pos) as? FossilMultiblockEntity
entity?.dropDisk(level, pos, state)
}
super.onRemove(state, level, pos, newState, movedByPiston)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(HORIZONTAL_FACING)
builder.add(SCREEN)
@@ -191,27 +143,8 @@ class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
BLUE_PROGRESS_7,
BLUE_PROGRESS_8,
BLUE_PROGRESS_9,
GREEN_PROGRESS_9,
TM_NORMAL,
TM_FIRE,
TM_WATER,
TM_GRASS,
TM_ELECTRIC,
TM_ICE,
TM_FIGHTING,
TM_POISON,
TM_GROUND,
TM_FLYING,
TM_PSYCHIC,
TM_BUG,
TM_ROCK,
TM_GHOST,
TM_DRAGON,
TM_DARK,
TM_STEEL,
TM_FAIRY,
MUSIC;
GREEN_PROGRESS_9;
override fun getSerializedName(): String = this.name.lowercase()
}
}
}
@@ -1,114 +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.block
import com.cobblemon.mod.common.block.entity.TMShelfBlockEntity
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.block.state.properties.DirectionProperty
import net.minecraft.world.Containers
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.VoxelShape
class TMShelfBlock(properties: Properties) : BaseEntityBlock(properties) {
companion object {
val CODEC: MapCodec<TMShelfBlock> = simpleCodec(::TMShelfBlock)
val FACING: DirectionProperty = HorizontalDirectionalBlock.FACING
val SLOT_OCCUPIED_PROPERTIES = (0 until 14).map {
BooleanProperty.create("slot_${it}_occupied")
}
}
init {
val baseState = stateDefinition.any().setValue(FACING, Direction.NORTH)
registerDefaultState(SLOT_OCCUPIED_PROPERTIES.fold(baseState) { state, prop ->
state.setValue(prop, false)
})
}
override fun codec() = CODEC
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
return TMShelfBlockEntity(pos, state)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING)
SLOT_OCCUPIED_PROPERTIES.forEach { builder.add(it) }
}
override fun getRenderShape(state: BlockState): RenderShape = RenderShape.MODEL
override fun getStateForPlacement(ctx: BlockPlaceContext): BlockState {
return defaultBlockState().setValue(FACING, ctx.horizontalDirection.opposite)
}
override fun useWithoutItem(
state: BlockState, level: Level, pos: BlockPos,
player: Player, hit: BlockHitResult
): InteractionResult {
val entity = level.getBlockEntity(pos) as? TMShelfBlockEntity ?: return InteractionResult.PASS
return entity.handleUseWithoutItem(state, level, pos, player, hit)
}
override fun useItemOn(
stack: ItemStack, state: BlockState, level: Level, pos: BlockPos,
player: Player, hand: InteractionHand, hit: BlockHitResult
): ItemInteractionResult {
val entity = level.getBlockEntity(pos) as? TMShelfBlockEntity ?: return ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION
return entity.handleUseItem(stack, state, level, pos, player, hit)
}
override fun hasAnalogOutputSignal(state: BlockState) = true
override fun getAnalogOutputSignal(state: BlockState, level: Level, pos: BlockPos): Int {
val entity = level.getBlockEntity(pos) as? TMShelfBlockEntity
return entity?.items?.count { !it.isEmpty }?.coerceIn(0, 14) ?: 0
}
override fun onRemove(state: BlockState, level: Level, pos: BlockPos, newState: BlockState, movedByPiston: Boolean) {
if (!state.`is`(newState.block)) {
if (!level.isClientSide && !movedByPiston) {
val entity = level.getBlockEntity(pos) as? TMShelfBlockEntity
if (entity != null) {
for (index in entity.items.indices) {
val stack = entity.items[index]
if (!stack.isEmpty) {
Containers.dropItemStack(level, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, stack)
entity.items[index] = ItemStack.EMPTY
}
}
level.updateNeighbourForOutputSignal(pos, this)
}
}
super.onRemove(state, level, pos, newState, movedByPiston)
} else {
super.onRemove(state, level, pos, newState, movedByPiston)
}
}
override fun getShape(
state: BlockState, level: BlockGetter,
pos: BlockPos, context: net.minecraft.world.phys.shapes.CollisionContext
): VoxelShape = Block.box(0.0, 0.0, 0.0, 16.0, 16.0, 16.0)
}
@@ -44,25 +44,15 @@ class TumblestoneBlock(
PrimitiveCodec.INT.fieldOf("xzOffset").forGetter { it.xzOffset },
Block.CODEC.fieldOf("nextStage").forGetter { it.nextStage }
).apply(it, ::TumblestoneBlock) }
const val STAGE_0 = 0
const val STAGE_1 = 1
const val STAGE_2 = 2
const val STAGE_3 = 3
const val MAX_STAGE = STAGE_3
const val MIN_STAGE = STAGE_0
}
override val growthChance = 5
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.DOWN)
.setValue(WATERLOGGED, false))
}
override fun canGrow(state: BlockState, pos: BlockPos, world: BlockGetter): Boolean {
override fun canGrow(pos: BlockPos, world: BlockGetter): Boolean {
if (stage == MAX_STAGE) return false
val iterator: Iterator<BlockPos> =
BlockPos.betweenClosed(pos.offset(-1, -1, -1), pos.offset(1, 1, 1))
@@ -76,8 +66,6 @@ class TumblestoneBlock(
return true
}
override fun isRandomlyTicking(state: BlockState): Boolean = stage < MAX_STAGE
override fun codec(): MapCodec<out DirectionalBlock> {
return CODEC
}
@@ -1,23 +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.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonItems
import net.minecraft.world.item.Item
import net.minecraft.world.level.block.Block
class TypeGemBlock(
settings: Properties
) : Block(settings) {
companion object {
}
}
@@ -1,284 +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.block
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.component.DataComponents
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.component.BlockItemStateProperties
import net.minecraft.world.item.component.CustomModelData
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.item.enchantment.Enchantments
import net.minecraft.world.item.enchantment.ItemEnchantments
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.AirBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.DirectionalBlock
import net.minecraft.world.level.block.Mirror
import net.minecraft.world.level.block.Rotation
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.block.state.properties.DirectionProperty
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.level.material.PushReaction
import net.minecraft.world.level.storage.loot.LootContext
import net.minecraft.world.level.storage.loot.LootParams
import net.minecraft.world.level.storage.loot.parameters.LootContextParams
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
import javax.swing.text.html.HTML.Attribute.SHAPES
import kotlin.io.path.Path
class TypeGemClusterBlock(
settings: Properties,
val nextStage: Block,
val dropItemId: ResourceLocation
) : DirectionalBlock(settings.pushReaction(PushReaction.DESTROY)) {
companion object {
val CODEC: MapCodec<TypeGemClusterBlock> = RecordCodecBuilder.mapCodec { instance ->
instance.group(
propertiesCodec(),
Block.CODEC.fieldOf("nextStage").forGetter { it.nextStage },
ResourceLocation.CODEC.fieldOf("dropItem").forGetter { it.dropItemId }
).apply(instance) { settings, nextStage, dropItemId ->
TypeGemClusterBlock(settings, nextStage, dropItemId).also {
gemToClusterMap[nextStage] = it
}
}
}
val gemToClusterMap: MutableMap<Block, TypeGemClusterBlock> = mutableMapOf()
val SHOULD_GROW: BooleanProperty = BooleanProperty.create("should_grow")
val FACING: DirectionProperty = DirectionalBlock.FACING
val STAGE: IntegerProperty = IntegerProperty.create("stage", 0, 3)
val STUNTED: BooleanProperty = BooleanProperty.create("stunted")
fun clusterFromGemBlock(gemBlock: Block): TypeGemClusterBlock? {
return gemToClusterMap[gemBlock]
}
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.UP)
.setValue(SHOULD_GROW, true)
.setValue(STAGE, 0)
.setValue(STUNTED, false)
)
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val facing = context.clickedFace
val world = context.level
val pos = context.clickedPos.relative(facing.opposite)
val blockBelow = world.getBlockState(pos).block
val shouldGrow = blockBelow is TypeGemCoreBlock
return defaultBlockState()
.setValue(FACING, facing)
.setValue(SHOULD_GROW, shouldGrow)
.setValue(STAGE, 0)
.setValue(STUNTED, false)
}
override fun isRandomlyTicking(state: BlockState): Boolean = state.getValue(SHOULD_GROW)
override fun randomTick(state: BlockState, level: ServerLevel, pos: BlockPos, random: RandomSource) {
advanceGrowth(state, level, pos, random)
}
fun advanceGrowth(state: BlockState, level: LevelAccessor, pos: BlockPos, random: RandomSource) {
if (!state.getValue(SHOULD_GROW)) return
val currentStage = state.getValue(STAGE)
if (currentStage < 3) {
// Progress through stages regardless of STUNTED
level.setBlock(pos, state.setValue(STAGE, currentStage + 1), Block.UPDATE_ALL)
return
}
// At STAGE_3
val isStunted = state.getValue(STUNTED)
val facing = state.getValue(FACING)
if (isStunted) {
// println("[TypeGemClusterBlock] Cluster at $pos is STUNTED at STAGE_3. Finalizing growth.")
level.setBlock(pos, state.setValue(SHOULD_GROW, false), Block.UPDATE_ALL)
return
}
// Log type of cluster based on nextStage
val clusterType = level.registryAccess()
.registryOrThrow(BuiltInRegistries.BLOCK.key())
.getKey(nextStage)
.toString()
// println("[TypeGemClusterBlock] Attempting to grow into TypeGemBlock at $pos")
// println("[TypeGemClusterBlock] Cluster type: $clusterType")
// Check neighboring gem blocks
val hasConflict = Direction.entries
.filter { it != facing.opposite }
.any { dir ->
val neighborPos = pos.relative(dir)
val neighborState = level.getBlockState(neighborPos)
val neighborBlockId = BuiltInRegistries.BLOCK.getKey(neighborState.block)
// println("[TypeGemClusterBlock] Neighbor at $dir -> $neighborBlockId")
isGem(neighborState)
}
if (hasConflict) {
// println("[TypeGemClusterBlock] Found nearby TypeGemBlock(s). Stunting cluster at $pos.")
level.setBlock(
pos,
state.setValue(STUNTED, true).setValue(SHOULD_GROW, false),
Block.UPDATE_ALL
)
} else {
// println("[TypeGemClusterBlock] No conflicts. Converting cluster to TypeGemBlock.")
var nextState = nextStage.defaultBlockState()
if (nextState.hasProperty(FACING)) {
nextState = nextState.setValue(FACING, facing)
}
level.setBlock(pos, nextState, Block.UPDATE_ALL)
attachDecorativeClusters(level, pos, random)
}
}
private fun isGem(state: BlockState): Boolean {
return state.`is`(CobblemonBlockTags.TYPE_GEM_BLOCKS)
}
private fun attachDecorativeClusters(level: LevelAccessor, gemPos: BlockPos, random: RandomSource) {
val openDirections = Direction.entries.filter { direction ->
level.getBlockState(gemPos.relative(direction)).isAir
}.toMutableList()
if (openDirections.isEmpty()) {
return
}
val minClusters = maxOf(1, (openDirections.size * 0.75f).toInt())
val clusterCount = random.nextInt(minClusters, openDirections.size + 1)
repeat(clusterCount) {
if (openDirections.isEmpty()) return@repeat
val index = random.nextInt(openDirections.size)
val direction = openDirections.removeAt(index)
val clusterPos = gemPos.relative(direction)
val stage = random.nextInt(0, 4)
val clusterState = defaultBlockState()
.setValue(FACING, direction)
.setValue(STAGE, stage)
.setValue(SHOULD_GROW, false) // so we can reduce lag... hopefully
.setValue(STUNTED, false)
level.setBlock(clusterPos, clusterState, Block.UPDATE_ALL)
}
}
override fun getDrops(state: BlockState, params: LootParams.Builder): List<ItemStack> {
val drops = super.getDrops(state, params)
for (stack in drops) {
val blockItem = stack.item as? BlockItem ?: continue
if (blockItem.block !is TypeGemClusterBlock) continue
stack.set(
DataComponents.BLOCK_STATE,
BlockItemStateProperties.EMPTY
.with(STAGE, state.getValue(STAGE))
.with(SHOULD_GROW, false)
.with(STUNTED, state.getValue(STUNTED))
)
stack.set(
DataComponents.CUSTOM_MODEL_DATA,
CustomModelData(state.getValue(STAGE))
)
}
return drops
}
override fun getShape(
state: BlockState,
level: net.minecraft.world.level.BlockGetter,
pos: BlockPos,
context: net.minecraft.world.phys.shapes.CollisionContext
): VoxelShape {
val stage = state.getValue(STAGE)
val facing = state.getValue(FACING)
val (min, max) = when (stage) {
0 -> 5.0 to 11.0
1 -> 4.0 to 12.0
2 -> 3.0 to 13.0
3 -> 2.0 to 14.0
else -> return Shapes.empty()
}
return when (facing) {
Direction.UP -> Shapes.box(min / 16, 0.0, min / 16, max / 16, (min + 1) / 16, max / 16)
Direction.DOWN -> Shapes.box(min / 16, (16 - (min + 1)) / 16, min / 16, max / 16, 1.0, max / 16)
Direction.NORTH -> Shapes.box(min / 16, min / 16, (16 - (min + 1)) / 16, max / 16, max / 16, 1.0)
Direction.SOUTH -> Shapes.box((16 - max) / 16, min / 16, 0.0, (16 - min) / 16, max / 16, (min + 1) / 16)
Direction.WEST -> Shapes.box((16 - (min + 1)) / 16, min / 16, min / 16, 1.0, max / 16, max / 16)
Direction.EAST -> Shapes.box(0.0, min / 16, min / 16, (min + 1) / 16, max / 16, max / 16)
}
}
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, level: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
val facing = state.getValue(FACING)
if (direction == facing.opposite && !canSurvive(state, level, pos)) {
return Blocks.AIR.defaultBlockState()
}
return super.updateShape(state, direction, neighborState, level, pos, neighborPos)
}
override fun rotate(state: BlockState, rotation: Rotation): BlockState {
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)))
}
override fun mirror(state: BlockState, mirror: Mirror): BlockState {
return state.rotate(mirror.getRotation(state.getValue(FACING)))
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
val supportPos = pos.relative(state.getValue(FACING).opposite)
val supportBlock = level.getBlockState(supportPos).block
return supportBlock !is AirBlock
}
override fun codec(): MapCodec<out DirectionalBlock> = CODEC
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
super.createBlockStateDefinition(builder)
builder.add(FACING, SHOULD_GROW, STAGE, STUNTED)
}
}
@@ -1,215 +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.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import com.cobblemon.mod.common.block.TypeGemClusterBlock.Companion.SHOULD_GROW
import com.cobblemon.mod.common.block.TypeGemClusterBlock.Companion.STUNTED
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.WorldGenLevel
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.DirectionalBlock
import net.minecraft.world.level.block.state.BlockState
import java.util.*
class TypeGemCoreBlock(properties: Properties) : Block(properties) {
companion object {
const val MAX_CONNECTED_GEMS = 7
const val MIN_DISTANCE_BETWEEN_GEMS = 1
const val CONTINUATION_CHANCE = 0.8f
val BLOCK_TO_CLUSTER: Map<ResourceLocation, Block> by lazy {
mapOf(
ResourceLocation.parse("cobblemon:type_gem_block_normal") to CobblemonBlocks.TYPE_GEM_CLUSTER_NORMAL,
ResourceLocation.parse("cobblemon:type_gem_block_fire") to CobblemonBlocks.TYPE_GEM_CLUSTER_FIRE,
ResourceLocation.parse("cobblemon:type_gem_block_water") to CobblemonBlocks.TYPE_GEM_CLUSTER_WATER,
ResourceLocation.parse("cobblemon:type_gem_block_electric") to CobblemonBlocks.TYPE_GEM_CLUSTER_ELECTRIC,
ResourceLocation.parse("cobblemon:type_gem_block_grass") to CobblemonBlocks.TYPE_GEM_CLUSTER_GRASS,
ResourceLocation.parse("cobblemon:type_gem_block_ice") to CobblemonBlocks.TYPE_GEM_CLUSTER_ICE,
ResourceLocation.parse("cobblemon:type_gem_block_fighting") to CobblemonBlocks.TYPE_GEM_CLUSTER_FIGHTING,
ResourceLocation.parse("cobblemon:type_gem_block_poison") to CobblemonBlocks.TYPE_GEM_CLUSTER_POISON,
ResourceLocation.parse("cobblemon:type_gem_block_ground") to CobblemonBlocks.TYPE_GEM_CLUSTER_GROUND,
ResourceLocation.parse("cobblemon:type_gem_block_flying") to CobblemonBlocks.TYPE_GEM_CLUSTER_FLYING,
ResourceLocation.parse("cobblemon:type_gem_block_psychic") to CobblemonBlocks.TYPE_GEM_CLUSTER_PSYCHIC,
ResourceLocation.parse("cobblemon:type_gem_block_bug") to CobblemonBlocks.TYPE_GEM_CLUSTER_BUG,
ResourceLocation.parse("cobblemon:type_gem_block_rock") to CobblemonBlocks.TYPE_GEM_CLUSTER_ROCK,
ResourceLocation.parse("cobblemon:type_gem_block_ghost") to CobblemonBlocks.TYPE_GEM_CLUSTER_GHOST,
ResourceLocation.parse("cobblemon:type_gem_block_dragon") to CobblemonBlocks.TYPE_GEM_CLUSTER_DRAGON,
ResourceLocation.parse("cobblemon:type_gem_block_dark") to CobblemonBlocks.TYPE_GEM_CLUSTER_DARK,
ResourceLocation.parse("cobblemon:type_gem_block_steel") to CobblemonBlocks.TYPE_GEM_CLUSTER_STEEL,
ResourceLocation.parse("cobblemon:type_gem_block_fairy") to CobblemonBlocks.TYPE_GEM_CLUSTER_FAIRY
)
}
fun setStuntState(level: LevelAccessor, gemBlocks: List<Pair<BlockState, BlockPos>>, stunted: Boolean) {
for ((_, gemPos) in gemBlocks) {
for (dir in Direction.entries) {
val neighborPos = gemPos.relative(dir)
val neighborState = level.getBlockState(neighborPos)
if (neighborState.block is TypeGemClusterBlock) {
if (neighborState.hasProperty(STUNTED) && neighborState.getValue(STUNTED) != stunted) {
var updatedState = neighborState.setValue(STUNTED, stunted)
if (updatedState.hasProperty(SHOULD_GROW)) {
updatedState = updatedState.setValue(SHOULD_GROW, !stunted)
}
level.setBlock(neighborPos, updatedState, 3)
}
}
}
}
}
fun getConnectedGemBlocks(level: BlockGetter, pos: BlockPos): List<Pair<BlockState, BlockPos>> {
val connectedGems = mutableListOf<Pair<BlockState, BlockPos>>()
val visited = mutableSetOf<BlockPos>()
val queue: Queue<BlockPos> = LinkedList()
visited.add(pos)
queue.add(pos)
connectedGems.add(level.getBlockState(pos) to pos)
while (queue.isNotEmpty()) {
val current = queue.poll()
for (direction in Direction.entries) {
val neighbor = current.relative(direction)
if (visited.add(neighbor)) {
val state = level.getBlockState(neighbor)
if (state.`is`(CobblemonBlockTags.TYPE_GEM_BLOCKS)) {
connectedGems.add(state to neighbor)
queue.add(neighbor)
}
}
}
}
return connectedGems
}
}
override fun randomTick(state: BlockState, level: ServerLevel, pos: BlockPos, random: RandomSource) {
grow(level, pos, random)
}
fun forceGrow(level: WorldGenLevel, pos: BlockPos, random: RandomSource, percentage: Float) {
val desiredLength = MAX_CONNECTED_GEMS * percentage
while (true) {
val growInfo = grow(level, pos, random, true)
val grown = growInfo.first
val clusterSize = growInfo.second
if (!grown || clusterSize >= desiredLength) break
}
}
private fun grow(
level: WorldGenLevel,
pos: BlockPos,
random: RandomSource,
forced: Boolean = false
): Pair<Boolean, Int> {
val connectedGems = getConnectedGemBlocks(level, pos) // maybe we can store this and access it for better performance? check for changes in list rather than checking each time grow is called? idk
val gemCount = connectedGems.size
val overLimit = gemCount >= MAX_CONNECTED_GEMS
if (overLimit) {
// if there is no open air pockets to grow any new clusters then exit early
if (!hasBreathingRoom(level, connectedGems)) {
return Pair(false, gemCount)
}
// max type gem block count met, tell clusters to grow but not turn into blocks
setStuntState(level, connectedGems, true)
//return Pair(false, gemCount)
} else {
// make sure connected clusters know they can grow and turn into blocks
setStuntState(level, connectedGems, false)
}
val javaRandom = java.util.Random(random.nextLong())
for ((gemState, gemPos) in connectedGems.shuffled(javaRandom)) {
val registryKey = BuiltInRegistries.BLOCK.getKey(gemState.block)
val clusterBlock = BLOCK_TO_CLUSTER[registryKey]
for (dir in Direction.entries.shuffled(javaRandom)) {
val targetPos = gemPos.relative(dir)
val targetState = level.getBlockState(targetPos)
if (!targetState.isAir) continue
if (!isPositionValidForGrowth(level, targetPos)) continue
// Place gem cluster
if (clusterBlock != null) {
var placeState = clusterBlock.defaultBlockState()
.setValue(DirectionalBlock.FACING, dir)
.setValue(TypeGemClusterBlock.STAGE, 0)
.setValue(SHOULD_GROW, true)
level.setBlock(targetPos, placeState, UPDATE_ALL)
}
if (forced && clusterBlock != null) {
forceAdvanceClusterGrowth(level, targetPos, random)
}
val updatedGemCount = if (forced) getConnectedGemBlocks(level, pos).size else connectedGems.size
return Pair(true, updatedGemCount)
}
}
// println("[TypeGemCoreBlock] No valid spot found for gem placement.")
return Pair(false, connectedGems.size)
}
private fun forceAdvanceClusterGrowth(level: WorldGenLevel, clusterPos: BlockPos, random: RandomSource) {
repeat(5) {
val clusterState = level.getBlockState(clusterPos)
val clusterBlock = clusterState.block as? TypeGemClusterBlock ?: return
clusterBlock.advanceGrowth(clusterState, level, clusterPos, random)
}
}
private fun hasBreathingRoom(level: WorldGenLevel, gems: List<Pair<BlockState, BlockPos>>): Boolean {
for ((_, gemPos) in gems) {
for (dir in Direction.entries) {
if (level.getBlockState(gemPos.relative(dir)).isAir) {
return true
}
}
}
return false
}
private fun isPositionValidForGrowth(level: WorldGenLevel, pos: BlockPos): Boolean {
return Direction.entries.none { dir ->
val nearby = pos.relative(dir)
isGem(level.getBlockState(nearby)) &&
pos.distManhattan(nearby) < MIN_DISTANCE_BETWEEN_GEMS
}
}
private fun isGem(state: BlockState): Boolean {
return state.`is`(CobblemonBlockTags.TYPE_GEM_BLOCKS)
}
override fun isRandomlyTicking(state: BlockState): Boolean = true
}
@@ -151,15 +151,13 @@ class CampfireBlock(settings: Properties, val isSoul: Boolean) : BaseEntityBlock
override fun placeLiquid(level: LevelAccessor, pos: BlockPos, state: BlockState, fluidState: FluidState): Boolean {
val blockEntity = level.getBlockEntity(pos)
if (fluidState.type === Fluids.WATER && blockEntity is CampfireBlockEntity) {
if (level is Level) {
if (!level.isClientSide) {
removePotItem(blockEntity, state, level, pos, null, true)
level.playSoundServer(pos.center, SoundEvents.GENERIC_EXTINGUISH_FIRE, SoundSource.BLOCKS, 1.0F, 1.0F)
}
if (level.isClientSide) for (i in 0..19) MCCampfireBlock.makeParticles(level, pos, false, true)
if (!level.isClientSide) {
removePotItem(blockEntity, state, level as Level, pos, null, true)
level.playSoundServer(pos.center, SoundEvents.GENERIC_EXTINGUISH_FIRE, SoundSource.BLOCKS, 1.0F, 1.0F)
}
if (level.isClientSide) for (i in 0..19) MCCampfireBlock.makeParticles(level as Level, pos, false, true)
level.scheduleTick(pos, fluidState.type, fluidState.type.getTickDelay(level))
return true
} else {
@@ -12,35 +12,15 @@ import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.api.multiblock.MultiblockEntity
import com.cobblemon.mod.common.api.multiblock.MultiblockStructure
import com.cobblemon.mod.common.api.multiblock.builder.MultiblockStructureBuilder
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.block.MonitorBlock
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockStructure
import com.cobblemon.mod.common.item.components.TMMoveComponent
import com.cobblemon.mod.common.item.interactive.TechnicalMachineItem
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.tmList
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.NbtUtils
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.Containers
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.gameevent.GameEvent
open class FossilMultiblockEntity(
pos: BlockPos,
@@ -48,30 +28,8 @@ open class FossilMultiblockEntity(
multiblockBuilder: MultiblockStructureBuilder,
type: BlockEntityType<*> = CobblemonBlockEntities.FOSSIL_MULTIBLOCK
) : MultiblockEntity(type, pos, state, multiblockBuilder) {
private val musicDiscs = setOf(
Items.MUSIC_DISC_13,
Items.MUSIC_DISC_CAT,
Items.MUSIC_DISC_BLOCKS,
Items.MUSIC_DISC_CHIRP,
Items.MUSIC_DISC_FAR,
Items.MUSIC_DISC_MALL,
Items.MUSIC_DISC_MELLOHI,
Items.MUSIC_DISC_STAL,
Items.MUSIC_DISC_STRAD,
Items.MUSIC_DISC_WARD,
Items.MUSIC_DISC_11,
Items.MUSIC_DISC_WAIT,
Items.MUSIC_DISC_OTHERSIDE,
Items.MUSIC_DISC_5,
Items.MUSIC_DISC_RELIC,
Items.MUSIC_DISC_PRECIPICE,
Items.MUSIC_DISC_CREATOR,
Items.MUSIC_DISC_CREATOR_MUSIC_BOX,
Items.MUSIC_DISC_PIGSTEP
)
override var masterBlockPos: BlockPos? = null
private var diskStack: ItemStack = ItemStack.EMPTY
override var multiblockStructure: MultiblockStructure? = null
set(structure) {
@@ -118,164 +76,6 @@ open class FossilMultiblockEntity(
} else {
null
}
diskStack = if (nbt.contains(DataKeys.MONITOR_DISK)) {
ItemStack.parse(registryLookup, nbt.get(DataKeys.MONITOR_DISK)).orElse(ItemStack.EMPTY)
} else {
ItemStack.EMPTY
}
updateMonitorScreen()
}
override fun saveAdditional(nbt: CompoundTag, registryLookup: HolderLookup.Provider) {
super.saveAdditional(nbt, registryLookup)
if (!diskStack.isEmpty) {
nbt.put(DataKeys.MONITOR_DISK, ItemStack.CODEC.encodeStart(NbtOps.INSTANCE, diskStack).orThrow)
}
}
fun handleUseItem(
stack: ItemStack,
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hand: InteractionHand
): ItemInteractionResult {
val handStack = player.getItemInHand(hand)
if (!isValidDisk(handStack)) return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION
if (multiblockStructure != null) return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION
if (level.isClientSide) return ItemInteractionResult.SUCCESS
val newDisk = handStack.copyWithCount(1)
// In creative, prevent duplicate ejection spam when repeatedly inserting the same disk.
if (player.isCreative && !diskStack.isEmpty && ItemStack.isSameItemSameComponents(diskStack, newDisk)) {
return ItemInteractionResult.sidedSuccess(level.isClientSide)
}
val oldStack = diskStack
if (!oldStack.isEmpty) {
if (player is ServerPlayer) {
unlockTmForPlayer(player, oldStack)
}
ejectDiskStack(level, pos, state, oldStack)
}
diskStack = newDisk
if (!player.isCreative) {
handStack.shrink(1)
}
if (player is ServerPlayer) {
unlockTmForPlayer(player, diskStack)
}
updateMonitorScreen()
markUpdated(level, pos, state)
return ItemInteractionResult.sidedSuccess(level.isClientSide)
}
fun handleUseWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player
): InteractionResult {
if (diskStack.isEmpty) return InteractionResult.PASS
if (multiblockStructure != null) return InteractionResult.PASS
if (player is ServerPlayer) {
unlockTmForPlayer(player, diskStack)
}
ejectDiskStack(level, pos, state, diskStack)
diskStack = ItemStack.EMPTY
updateMonitorScreen()
markUpdated(level, pos, state)
return InteractionResult.sidedSuccess(level.isClientSide)
}
fun dropDisk(level: Level, pos: BlockPos, state: BlockState) {
if (diskStack.isEmpty) return
ejectDiskStack(level, pos, state, diskStack)
diskStack = ItemStack.EMPTY
}
private fun ejectDiskStack(level: Level, pos: BlockPos, state: BlockState, stack: ItemStack) {
val facing = if (state.hasProperty(BlockStateProperties.HORIZONTAL_FACING)) {
state.getValue(HorizontalDirectionalBlock.FACING)
} else {
null
}
if (facing == null) {
Containers.dropItemStack(level, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, stack)
return
}
val ejectFacing = facing.opposite
val x = pos.x + 0.5 + ejectFacing.stepX * 0.6
val y = pos.y + 0.5
val z = pos.z + 0.5 + ejectFacing.stepZ * 0.6
val itemEntity = ItemEntity(level, x, y, z, stack)
itemEntity.setDeltaMovement(ejectFacing.stepX * 0.15, 0.05, ejectFacing.stepZ * 0.15)
level.addFreshEntity(itemEntity)
}
private fun isValidDisk(stack: ItemStack): Boolean {
return stack.item is TechnicalMachineItem || musicDiscs.contains(stack.item)
}
private fun unlockTmForPlayer(player: ServerPlayer, stack: ItemStack) {
if (stack.item !is TechnicalMachineItem) return
val move = TMMoveComponent.getTMMove(stack) ?: return
val tmId = TechnicalMachines.moveToTM[move]?.id ?: return
player.tmList()?.learn(listOf(tmId))
}
private fun updateMonitorScreen() {
val level = level ?: return
if (level.isClientSide) return
if (multiblockStructure != null) return
val state = blockState
if (!state.hasProperty(MonitorBlock.SCREEN)) return
val targetScreen = getDiskScreen()
if (state.getValue(MonitorBlock.SCREEN) != targetScreen) {
level.setBlockAndUpdate(blockPos, state.setValue(MonitorBlock.SCREEN, targetScreen))
}
}
private fun getDiskScreen(): MonitorBlock.MonitorScreen {
if (diskStack.isEmpty) return MonitorBlock.MonitorScreen.OFF
if (musicDiscs.contains(diskStack.item)) return MonitorBlock.MonitorScreen.MUSIC
val move = TMMoveComponent.getTMMove(diskStack) ?: return MonitorBlock.MonitorScreen.TM_NORMAL
return when (move.elementalType) {
ElementalTypes.FIRE -> MonitorBlock.MonitorScreen.TM_FIRE
ElementalTypes.WATER -> MonitorBlock.MonitorScreen.TM_WATER
ElementalTypes.GRASS -> MonitorBlock.MonitorScreen.TM_GRASS
ElementalTypes.ELECTRIC -> MonitorBlock.MonitorScreen.TM_ELECTRIC
ElementalTypes.ICE -> MonitorBlock.MonitorScreen.TM_ICE
ElementalTypes.FIGHTING -> MonitorBlock.MonitorScreen.TM_FIGHTING
ElementalTypes.POISON -> MonitorBlock.MonitorScreen.TM_POISON
ElementalTypes.GROUND -> MonitorBlock.MonitorScreen.TM_GROUND
ElementalTypes.FLYING -> MonitorBlock.MonitorScreen.TM_FLYING
ElementalTypes.PSYCHIC -> MonitorBlock.MonitorScreen.TM_PSYCHIC
ElementalTypes.BUG -> MonitorBlock.MonitorScreen.TM_BUG
ElementalTypes.ROCK -> MonitorBlock.MonitorScreen.TM_ROCK
ElementalTypes.GHOST -> MonitorBlock.MonitorScreen.TM_GHOST
ElementalTypes.DRAGON -> MonitorBlock.MonitorScreen.TM_DRAGON
ElementalTypes.DARK -> MonitorBlock.MonitorScreen.TM_DARK
ElementalTypes.STEEL -> MonitorBlock.MonitorScreen.TM_STEEL
ElementalTypes.FAIRY -> MonitorBlock.MonitorScreen.TM_FAIRY
else -> MonitorBlock.MonitorScreen.TM_NORMAL
}
}
private fun markUpdated(level: Level, pos: BlockPos, state: BlockState) {
level.sendBlockUpdated(pos, state, state, 3)
level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state))
setChanged()
}
}
}
@@ -1,383 +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.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.block.tmmachine.TMMachineBlock
import com.cobblemon.mod.common.block.tmmachine.TMMachineMenu
import com.cobblemon.mod.common.client.gui.tmmachine.TMMachineScreen
import com.cobblemon.mod.common.item.components.TMMoveComponent
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.core.NonNullList
import net.minecraft.network.chat.Component
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.world.ContainerHelper
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.nbt.CompoundTag
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
import net.minecraft.world.Container
import net.minecraft.world.inventory.ContainerData
import net.minecraft.world.level.Level
class TMMachineBlockEntity(pos: BlockPos, state: BlockState) :
BaseContainerBlockEntity(CobblemonBlockEntities.TM_MACHINE, pos, state) {
companion object {
const val BURN_ACTIVE_TAG = "burnActive"
const val BURN_PROGRESS_TAG = "burnProgress"
const val REPEAT_PROCESS_TAG = "repeatProcess"
const val ACTIVE_MOVE_TAG = "activeMove"
const val BURN_PROGRESS_PER_TICK = 2
const val BURN_TOTAL_TIME = 200
const val TOTAL_PROCESS_TIME =
BURN_TOTAL_TIME + ((TMMachineScreen.CRAFT_TICKS + TMMachineScreen.RESET_DISC_TICKS) * BURN_PROGRESS_PER_TICK)
// Container data IDs
const val BURN_PROGRESS_INDEX = 0
const val BURN_ACTIVE_INDEX = 1
const val REPEAT_PROCESS_INDEX = 2
const val POSITION_X_INDEX = 3
const val POSITION_Y_INDEX = 4
const val POSITION_Z_INDEX = 5
fun serverTick(level: Level, pos: BlockPos, state: BlockState, blockEntity: TMMachineBlockEntity) {
if (level.isClientSide) return
// check if the TMM is powered by redstone. For toggling automation
val powered = level.hasNeighborSignal(pos)
val data = blockEntity.containerData
// Progress while active
if (data.get(BURN_ACTIVE_INDEX) == 1) {
val currentProgress = data.get(BURN_PROGRESS_INDEX)
if (currentProgress < TOTAL_PROCESS_TIME) {
val progressPerTick = if (currentProgress >= BURN_TOTAL_TIME) 1 else BURN_PROGRESS_PER_TICK
data.set(BURN_PROGRESS_INDEX, currentProgress + progressPerTick)
// comparator progress bar updates
level.updateNeighbourForOutputSignal(pos, state.block)
}
}
// auto start with batching when powered
if (powered && data.get(BURN_ACTIVE_INDEX) == 0) {
if (blockEntity.canCraftSelectedTM()) {
data.set(BURN_ACTIVE_INDEX, 1)
data.set(BURN_PROGRESS_INDEX, 0)
blockEntity.setChanged()
level.updateNeighbourForOutputSignal(pos, state.block)
}
}
val burnProgressValue = data.get(BURN_PROGRESS_INDEX)
val postCraftTicks = if (burnProgressValue >= BURN_TOTAL_TIME) (burnProgressValue - BURN_TOTAL_TIME) else 0
if (postCraftTicks > 0) {
if (postCraftTicks == TMMachineScreen.CRAFT_TICKS) {
blockEntity.craftTM()
level.updateNeighbourForOutputSignal(pos, state.block)
}
if (postCraftTicks >= (TMMachineScreen.CRAFT_TICKS + TMMachineScreen.RESET_DISC_TICKS)) {
data.set(BURN_PROGRESS_INDEX, 0)
// use batch processing while powered with redstone
val keepRunning = powered && blockEntity.canCraftSelectedTM()
data.set(BURN_ACTIVE_INDEX, if (keepRunning) 1 else 0)
blockEntity.setChanged()
level.updateNeighbourForOutputSignal(pos, state.block)
}
}
}
}
var tmMachineInventory = TMMachineBlockInventory(this)
var partialTicks: Float = 0F
var burnProgress: Int = 0
var burnActive: Boolean = false
var repeatProcess: Boolean = false
var activeMove: String = ""
var containerData: ContainerData = object : ContainerData {
override fun getCount() = 6
override fun get(index: Int): Int {
return when (index) {
BURN_PROGRESS_INDEX -> burnProgress
BURN_ACTIVE_INDEX -> if (burnActive) 1 else 0
REPEAT_PROCESS_INDEX -> if (repeatProcess) 1 else 0
POSITION_X_INDEX -> blockPos.x
POSITION_Y_INDEX -> blockPos.y
POSITION_Z_INDEX -> blockPos.z
else -> 0
}
}
override fun set(index: Int, value: Int) {
when (index) {
BURN_PROGRESS_INDEX -> burnProgress = value
BURN_ACTIVE_INDEX -> burnActive = value == 1
REPEAT_PROCESS_INDEX -> repeatProcess = value == 1
}
}
}
private fun canCraftSelectedTM(): Boolean {
val move = Moves.getByName(activeMove) ?: return false
val tm = TechnicalMachines.moveToTM[move] ?: return false
// We must have the Output be empty or stackable and not full
val resultStack = getItem(TMMachineMenu.RESULT_SLOT)
val crafted = ItemStack(CobblemonItems.TECHNICAL_MACHINE).also { TMMoveComponent.setTMMove(it, tm.moveName) }
val outputOk = resultStack.isEmpty || (
resultStack.count < resultStack.maxStackSize &&
ItemStack.isSameItemSameComponents(resultStack, crafted)
)
if (!outputOk) return false
// Blank disc slot
if (getItem(TMMachineMenu.BLANK_TM_SLOT).item != CobblemonItems.BLANK_TM) return false
// Ingredient slots
val recipes = tm.getClampedRecipe() ?: emptyList()
for ((index, recipe) in recipes.withIndex()) {
val slot = TMMachineMenu.INGREDIENT_SLOTS.first + index
val provided = getItem(slot)
if (!recipe.ingredient.test(provided) || provided.count < recipe.count) return false
}
return true
}
private fun craftTM() {
if (!(level?.isClientSide ?: true)) {
Moves.getByName(activeMove)?.let { move ->
TechnicalMachines.moveToTM[move]?.let { tm ->
val resultStack = getItem(TMMachineMenu.RESULT_SLOT)
if (!resultStack.isEmpty && resultStack.count >= resultStack.maxStackSize) return
// Craft the TM
val craftedTmStack = ItemStack(CobblemonItems.TECHNICAL_MACHINE)
.also { TMMoveComponent.setTMMove(it, tm.moveName) }
if (!resultStack.isEmpty && !ItemStack.isSameItemSameComponents(resultStack, craftedTmStack)) return
val blankTmStack = getItem(TMMachineMenu.BLANK_TM_SLOT)
if (blankTmStack.item != CobblemonItems.BLANK_TM) return
val recipes = tm.getClampedRecipe() ?: emptyList()
for ((index, recipe) in recipes.withIndex()) {
val slot = TMMachineMenu.INGREDIENT_SLOTS.first + index
val provided = getItem(slot)
if (!recipe.ingredient.test(provided) || provided.count < recipe.count) return
}
// Consume ingredients
blankTmStack.shrink(1)
for ((index, ingredient) in recipes.withIndex()) {
getItem(TMMachineMenu.INGREDIENT_SLOTS.first + index).shrink(ingredient.count)
}
// Add crafted TM to result slot
if (resultStack.isEmpty) {
setItem(TMMachineMenu.RESULT_SLOT, craftedTmStack)
} else if (resultStack.count < resultStack.maxStackSize) {
resultStack.grow(1)
}
tmMachineInventory.setChanged()
setChanged()
}
}
}
}
override fun createMenu(containerId: Int, inventory: Inventory): AbstractContainerMenu {
return TMMachineMenu(containerId, inventory, tmMachineInventory, containerData)
}
override fun saveAdditional(compound: CompoundTag, registries: HolderLookup.Provider) {
super.saveAdditional(compound, registries)
compound.putBoolean(BURN_ACTIVE_TAG, burnActive)
compound.putBoolean(REPEAT_PROCESS_TAG, repeatProcess)
compound.putInt(BURN_PROGRESS_TAG, burnProgress)
compound.putString(ACTIVE_MOVE_TAG, activeMove)
ContainerHelper.saveAllItems(compound, tmMachineInventory.items, registries)
}
override fun loadAdditional(compound: CompoundTag, registries: HolderLookup.Provider) {
super.loadAdditional(compound, registries)
burnActive = compound.getBoolean(BURN_ACTIVE_TAG)
repeatProcess = compound.getBoolean(REPEAT_PROCESS_TAG)
burnProgress = compound.getInt(BURN_PROGRESS_TAG)
activeMove = compound.getString(ACTIVE_MOVE_TAG)
ContainerHelper.loadAllItems(compound, tmMachineInventory.items, registries)
}
override fun getDisplayName(): Component = Component.translatable("block.cobblemon.tm_machine")
override fun getDefaultName(): Component = Component.translatable("cobblemon.container.tm_machine")
override fun getItems(): NonNullList<ItemStack> = tmMachineInventory.items
override fun setItems(items: NonNullList<ItemStack>) {
for (i in items.indices) {
tmMachineInventory.setItem(i, items[i])
}
}
override fun getUpdatePacket(): Packet<ClientGamePacketListener> = ClientboundBlockEntityDataPacket.create(this)
override fun getUpdateTag(registryLookup: HolderLookup.Provider): CompoundTag = saveWithoutMetadata(registryLookup)
override fun getContainerSize(): Int = tmMachineInventory.containerSize
override fun isEmpty(): Boolean = tmMachineInventory.isEmpty
override fun getItem(slot: Int): ItemStack = tmMachineInventory.getItem(slot)
override fun removeItem(slot: Int, amount: Int): ItemStack = tmMachineInventory.removeItem(slot, amount)
override fun removeItemNoUpdate(slot: Int): ItemStack = tmMachineInventory.removeItemNoUpdate(slot)
override fun setItem(slot: Int, stack: ItemStack) = tmMachineInventory.setItem(slot, stack)
override fun setChanged() {
var currentState = blockState
level?.getBlockState(worldPosition)?.let { state ->
currentState = state
var updated = false
if (state.hasProperty(TMMachineBlock.EMPTY)) {
val isEmpty = getItem(TMMachineMenu.BLANK_TM_SLOT).isEmpty
if (currentState.getValue(TMMachineBlock.EMPTY) != isEmpty) {
if (!isEmpty) playSound(CobblemonSounds.TM_MACHINE_PLACE_DISC, 0.5F)
currentState = currentState.setValue(TMMachineBlock.EMPTY, isEmpty)
updated = true
}
}
if (state.hasProperty(TMMachineBlock.ACTIVE)) {
val isActive = containerData.get(BURN_ACTIVE_INDEX) == 1
if (currentState.getValue(TMMachineBlock.ACTIVE) != isActive) {
currentState = currentState.setValue(TMMachineBlock.ACTIVE, isActive)
updated = true
}
}
if (updated) level?.setBlockAndUpdate(worldPosition, currentState)
}
// Notify the block entity's level that this block entity has changed
level?.blockEntityChanged(worldPosition)
// Mark the chunk containing this block entity as dirty, ensuring it is saved
level?.getChunkAt(worldPosition)?.isUnsaved = true
// Update Neighbours
level?.updateNeighborsAt(blockPos, currentState.block)
}
override fun stillValid(player: Player): Boolean = tmMachineInventory.stillValid(player)
override fun canPlaceItem(slot: Int, stack: ItemStack): Boolean = tmMachineInventory.canPlaceItem(slot, stack)
override fun canTakeItem(target: Container, slot: Int, stack: ItemStack): Boolean =
tmMachineInventory.canTakeItem(target, slot, stack)
fun playSound(soundEvent: SoundEvent, volume: Float = 1F, pitch: Float = 1F) {
level?.playSound(null, worldPosition, soundEvent, SoundSource.BLOCKS, volume, pitch)
}
fun isReadyToCraft(): Boolean = canCraftSelectedTM()
fun isOutputBlocked(): Boolean {
val level = level ?: return false
val move = Moves.getByName(activeMove) ?: return false
val tm = TechnicalMachines.moveToTM[move] ?: return false
val resultStack = getItem(TMMachineMenu.RESULT_SLOT)
if (resultStack.isEmpty) return false
val crafted = ItemStack(CobblemonItems.TECHNICAL_MACHINE)
.also { TMMoveComponent.setTMMove(it, tm.moveName) }
// If wrong TM in output OR output is full
if (!ItemStack.isSameItemSameComponents(resultStack, crafted)) return true
return resultStack.count >= resultStack.maxStackSize
}
class TMMachineBlockInventory(val blockEntity: TMMachineBlockEntity) : SimpleContainer(6) {
override fun canTakeItem(target: Container, slot: Int, stack: ItemStack) = slot == 0
override fun canPlaceItem(slot: Int, stack: ItemStack): Boolean {
val powered = blockEntity.level?.hasNeighborSignal(blockEntity.blockPos) == true
val allowAutomation = blockEntity.containerData.get(BURN_ACTIVE_INDEX) == 1 || powered
if (!allowAutomation) return false
Moves.getByName(blockEntity.activeMove)?.let {
val tm = TechnicalMachines.moveToTM[it] ?: return false
val item = stack.item
return when (slot) {
1 -> item == CobblemonItems.BLANK_TM
2, 3, 4 -> {
val recipe = tm.getClampedRecipe()
val recipeIndex = slot - 2
if (recipe == null || recipeIndex >= recipe.size) {
return false
}
recipe[recipeIndex].ingredient.test(stack)
}
else -> false
}
}
return false
}
override fun setChanged() {
super.setChanged()
blockEntity.setChanged()
val allowAutomation =
blockEntity.containerData.get(BURN_ACTIVE_INDEX) == 1 || blockEntity.containerData.get(
REPEAT_PROCESS_INDEX
) == 1
// force comparators to refresh if inventory changes and batch mode is on
if (allowAutomation) {
blockEntity.level?.updateNeighbourForOutputSignal(blockEntity.blockPos, blockEntity.blockState.block)
}
}
override fun startOpen(player: Player) {
blockEntity.startOpen(player)
}
override fun stopOpen(player: Player) {
blockEntity.stopOpen(player)
}
}
}
@@ -1,302 +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.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.block.TMShelfBlock
import com.cobblemon.mod.common.item.interactive.TechnicalMachineItem
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.HolderLookup
import net.minecraft.core.NonNullList
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
import net.minecraft.world.ContainerHelper
import net.minecraft.world.Containers
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.WorldlyContainer
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.level.gameevent.GameEvent
import java.util.OptionalInt
import kotlin.math.pow
class TMShelfBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(CobblemonBlockEntities.TM_SHELF, pos, state), WorldlyContainer {
val items: NonNullList<ItemStack> = NonNullList.withSize(14, ItemStack.EMPTY)
var lastInteractedSlot: Int = -1
private val accessibleSlots = IntArray(14) { it }
private var lastNotePulseTick: Long = Long.MIN_VALUE
fun handleUseItem(stack: ItemStack, state: BlockState, level: Level, pos: BlockPos, player: Player, hit: BlockHitResult): ItemInteractionResult {
if (stack.isEmpty) return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION
val slot = getHitSlot(hit, state).orElse(-1)
if (slot !in 0 until 14) return ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION
if (!isValidItem(stack)) return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION
val oldStack = items[slot]
if (!oldStack.isEmpty) {
if (!player.addItem(oldStack)) {
Containers.dropItemStack(level, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, oldStack)
}
}
items[slot] = stack.copyWithCount(1)
stack.shrink(1)
lastInteractedSlot = slot
updateBlockState(level, pos)
markUpdated()
return ItemInteractionResult.sidedSuccess(level.isClientSide)
}
fun handleUseWithoutItem(state: BlockState, level: Level, pos: BlockPos, player: Player, hit: BlockHitResult): InteractionResult {
val slot = getHitSlot(hit, state).orElse(-1)
if (slot !in 0 until 14 || items[slot].isEmpty) return InteractionResult.PASS
val removed = items[slot]
items[slot] = ItemStack.EMPTY
if (!player.addItem(removed)) {
Containers.dropItemStack(level, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, removed)
}
lastInteractedSlot = slot
updateBlockState(level, pos)
return InteractionResult.sidedSuccess(level.isClientSide)
}
private fun updateBlockState(level: Level, pos: BlockPos) {
val newState = TMShelfBlock.SLOT_OCCUPIED_PROPERTIES.fold(blockState) { acc, prop ->
val index = TMShelfBlock.SLOT_OCCUPIED_PROPERTIES.indexOf(prop)
acc.setValue(prop, !items[index].isEmpty)
}
level.setBlock(pos, newState, 3)
level.updateNeighbourForOutputSignal(pos, newState.block)
level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(newState))
}
private fun getHitSlot(hit: BlockHitResult, state: BlockState): OptionalInt {
val facing = state.getValue(TMShelfBlock.FACING)
if (hit.direction != facing) {
return OptionalInt.empty()
}
val relative = hit.location.subtract(hit.blockPos.x.toDouble(), hit.blockPos.y.toDouble(), hit.blockPos.z.toDouble())
val (x, y) = when (facing) {
// Face-local X where 0 = visual left and 1 = visual right.
Direction.NORTH -> 1.0 - relative.x to relative.y
Direction.SOUTH -> relative.x to relative.y
Direction.WEST -> 1.0 - relative.z to relative.y
Direction.EAST -> relative.z to relative.y
else -> return OptionalInt.empty()
}
// Have interaction segments match the renderer areas visually
val leftSlotMinX = 1.0 / 16.0
val leftSlotMaxX = 7.0 / 16.0
val rightSlotMinX = 9.0 / 16.0
val rightSlotMaxX = 15.0 / 16.0
val slotMinY = 1.0 / 16.0
val slotMaxY = 15.0 / 16.0
val slotHeight = 2.0 / 16.0
val col = when {
x in leftSlotMinX..leftSlotMaxX -> 0
x in rightSlotMinX..rightSlotMaxX -> 1
else -> return OptionalInt.empty()
}
if (y < slotMinY || y > slotMaxY) {
return OptionalInt.empty()
}
val rowFromBottom = ((y - slotMinY) / slotHeight).toInt().coerceIn(0, 6)
val row = 6 - rowFromBottom
return OptionalInt.of(row * 2 + col)
}
fun isValidItem(stack: ItemStack): Boolean {
return stack.item is TechnicalMachineItem
|| stack.item == CobblemonItems.UPGRADE
|| stack.item == CobblemonItems.DUBIOUS_DISC
|| stack.item == Items.MUSIC_DISC_13
|| stack.item == Items.MUSIC_DISC_CAT
|| stack.item == Items.MUSIC_DISC_BLOCKS
|| stack.item == Items.MUSIC_DISC_CHIRP
|| stack.item == Items.MUSIC_DISC_FAR
|| stack.item == Items.MUSIC_DISC_MALL
|| stack.item == Items.MUSIC_DISC_MELLOHI
|| stack.item == Items.MUSIC_DISC_STAL
|| stack.item == Items.MUSIC_DISC_STRAD
|| stack.item == Items.MUSIC_DISC_WARD
|| stack.item == Items.MUSIC_DISC_11
|| stack.item == Items.MUSIC_DISC_WAIT
|| stack.item == Items.MUSIC_DISC_OTHERSIDE
|| stack.item == Items.MUSIC_DISC_5
|| stack.item == Items.MUSIC_DISC_RELIC
|| stack.item == Items.MUSIC_DISC_PRECIPICE
|| stack.item == Items.MUSIC_DISC_CREATOR
|| stack.item == Items.MUSIC_DISC_CREATOR_MUSIC_BOX
|| stack.item == Items.MUSIC_DISC_PIGSTEP
}
fun onNoteBlockPulse(level: Level, noteBlockPos: BlockPos, soundEvent: SoundEvent) {
val gameTime = level.gameTime
if (lastNotePulseTick == gameTime) {
return
}
lastNotePulseTick = gameTime
val octaveUp = !items[1].isEmpty
val octaveDown = !items[12].isEmpty
val octaveShift = when {
octaveUp && !octaveDown -> 12
octaveDown && !octaveUp -> -12
else -> 0
}
NOTE_SLOT_TO_SEMITONE.forEach { (slot, semitone) ->
if (items[slot].isEmpty) return@forEach
val pitch = 2.0.pow((semitone + octaveShift) / 12.0).toFloat()
level.playSound(null, noteBlockPos, soundEvent, SoundSource.BLOCKS, 3.0f, pitch)
}
}
fun markUpdated() {
level?.setBlock(blockPos, blockState, 3)
level?.sendBlockUpdated(blockPos, blockState, blockState, 3)
setChanged()
}
override fun clearContent() {
var changed = false
for (index in items.indices) {
if (!items[index].isEmpty) {
items[index] = ItemStack.EMPTY
changed = true
}
}
if (changed) {
level?.let { updateBlockState(it, blockPos) }
markUpdated()
}
}
override fun getContainerSize(): Int = items.size
override fun isEmpty(): Boolean = items.all { it.isEmpty }
override fun getItem(slot: Int): ItemStack {
if (slot !in items.indices) return ItemStack.EMPTY
return items[slot]
}
override fun removeItem(slot: Int, amount: Int): ItemStack {
val removed = ContainerHelper.removeItem(items, slot, amount)
if (!removed.isEmpty) {
if (items[slot].isEmpty) {
lastInteractedSlot = slot
}
level?.let { updateBlockState(it, blockPos) }
markUpdated()
}
return removed
}
override fun removeItemNoUpdate(slot: Int): ItemStack {
val removed = ContainerHelper.takeItem(items, slot)
if (!removed.isEmpty) {
level?.let { updateBlockState(it, blockPos) }
markUpdated()
}
return removed
}
override fun setItem(slot: Int, stack: ItemStack) {
if (slot !in items.indices) return
if (!stack.isEmpty && !isValidItem(stack)) return
items[slot] = if (stack.isEmpty) ItemStack.EMPTY else stack.copyWithCount(1)
lastInteractedSlot = slot
level?.let { updateBlockState(it, blockPos) }
markUpdated()
}
override fun stillValid(player: Player): Boolean {
return level?.getBlockEntity(blockPos) === this &&
player.distanceToSqr(blockPos.x + 0.5, blockPos.y + 0.5, blockPos.z + 0.5) <= 64.0
}
override fun getMaxStackSize(): Int = 1
override fun canPlaceItem(slot: Int, stack: ItemStack): Boolean {
return slot in items.indices && items[slot].isEmpty && isValidItem(stack)
}
override fun getSlotsForFace(side: Direction): IntArray = accessibleSlots
override fun canPlaceItemThroughFace(slot: Int, stack: ItemStack, direction: Direction?): Boolean {
return canPlaceItem(slot, stack)
}
override fun canTakeItemThroughFace(slot: Int, stack: ItemStack, direction: Direction): Boolean {
return slot in items.indices
}
override fun getUpdateTag(registries: HolderLookup.Provider): CompoundTag {
return saveWithoutMetadata(registries)
}
override fun getUpdatePacket(): ClientboundBlockEntityDataPacket {
return ClientboundBlockEntityDataPacket.create(this)
}
override fun saveAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.saveAdditional(tag, registries)
ContainerHelper.saveAllItems(tag, items, true, registries)
tag.putInt("LastInteractedSlot", lastInteractedSlot)
}
override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.loadAdditional(tag, registries)
items.clear()
ContainerHelper.loadAllItems(tag, items, registries)
lastInteractedSlot = tag.getInt("LastInteractedSlot")
}
companion object {
// C=0 chromatic semitone mapping.
private val NOTE_SLOT_TO_SEMITONE = linkedMapOf(
0 to 5, // F
2 to 4, // E
4 to 3, // D#
6 to 2, // D
8 to 1, // C#
10 to 0, // C
3 to 11, // B
5 to 10, // A#
7 to 9, // A
9 to 8, // G#
11 to 7, // G
13 to 6 // F#
)
}
}
@@ -14,9 +14,7 @@ import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.multiblock.MultiblockEntity
import com.cobblemon.mod.common.api.multiblock.builder.MultiblockStructureBuilder
import com.cobblemon.mod.common.api.multiblock.condition.BlockRelativeCondition
import com.cobblemon.mod.common.block.MonitorBlock
import com.cobblemon.mod.common.block.RestorationTankBlock
import com.cobblemon.mod.common.block.entity.FossilMultiblockEntity
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.blockPositionsAsList
import net.minecraft.advancements.critereon.BlockPredicate
@@ -123,13 +121,6 @@ class FossilMultiblockBuilder(val centerPos: BlockPos) : MultiblockStructureBuil
val analyzerEntity = world.getBlockEntity(fossilAnalyzerPos) as? MultiblockEntity
val tankBaseEntity = world.getBlockEntity(restorationTankPos) as? MultiblockEntity
val tankTopEntity = world.getBlockEntity(restorationTankPos.above()) as? MultiblockEntity
if (monitorEntity is FossilMultiblockEntity) {
val monitorState = world.getBlockState(monitorPos)
monitorEntity.dropDisk(world, monitorPos, monitorState)
if (monitorState.hasProperty(MonitorBlock.SCREEN)) {
world.setBlockAndUpdate(monitorPos, monitorState.setValue(MonitorBlock.SCREEN, MonitorBlock.MonitorScreen.OFF))
}
}
val structure = FossilMultiblockStructure(monitorPos, fossilAnalyzerPos, restorationTankPos)
structure.tankConnectorDirection = dirsToCheck.filter {
@@ -177,4 +168,4 @@ class FossilMultiblockBuilder(val centerPos: BlockPos) : MultiblockStructureBuil
//lol thanks mojang for not allowing nbt puts to be chained
}
}
}
@@ -1,255 +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.block.tmmachine
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.block.entity.TMMachineBlockEntity
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.playSoundServer
import com.cobblemon.mod.common.util.toVec3d
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.Containers
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.RenderShape
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
import org.jetbrains.annotations.Nullable
class TMMachineBlock(properties: Properties) : BaseEntityBlock(properties), SimpleWaterloggedBlock {
companion object {
val EMPTY: BooleanProperty = BooleanProperty.create("empty")
val FACING = BlockStateProperties.HORIZONTAL_FACING
val WATERLOGGED = BlockStateProperties.WATERLOGGED
val ACTIVE: BooleanProperty = BooleanProperty.create("active")
val OPEN: BooleanProperty = BooleanProperty.create("open")
private val SHAPE: VoxelShape = Shapes.box(0.0, 0.0, 0.0, 1.0, 0.375, 1.0)
private val SHAPE_OPEN: VoxelShape = Shapes.box(0.0, 0.0, 0.0, 1.0, 0.3125, 1.0)
private val SHAPE_OPEN_NORTH: VoxelShape = Shapes.or(
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.3125, 1.0),
Shapes.box(0.0, 0.3125, 0.8125, 1.0, 0.4375, 0.9375),
Shapes.box(0.0, 0.4375, 0.6875, 1.0, 0.5625, 0.8125),
Shapes.box(0.0, 0.5625, 0.5625, 1.0, 0.6875, 0.6875),
Shapes.box(0.0, 0.6875, 0.4375, 1.0, 0.8125, 0.5625),
Shapes.box(0.0, 0.8125, 0.3125, 1.0, 0.9375, 0.4375),
Shapes.box(0.0, 0.9375, 0.3125, 1.0, 1.0, 0.375)
)
private val SHAPE_OPEN_SOUTH: VoxelShape = Shapes.or(
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.3125, 1.0),
Shapes.box(0.0, 0.3125, 0.0625, 1.0, 0.4375, 0.1875),
Shapes.box(0.0, 0.4375, 0.1875, 1.0, 0.5625, 0.3125),
Shapes.box(0.0, 0.5625, 0.3125, 1.0, 0.6875, 0.4375),
Shapes.box(0.0, 0.6875, 0.4375, 1.0, 0.8125, 0.5625),
Shapes.box(0.0, 0.8125, 0.5625, 1.0, 0.9375, 0.6875),
Shapes.box(0.0, 0.9375, 0.625, 1.0, 1.0, 0.6875)
)
private val SHAPE_OPEN_EAST: VoxelShape = Shapes.or(
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.3125, 1.0),
Shapes.box(0.0625, 0.3125, 0.0, 0.1875, 0.4375, 1.0),
Shapes.box(0.1875, 0.4375, 0.0, 0.3125, 0.5625, 1.0),
Shapes.box(0.3125, 0.5625, 0.0, 0.4375, 0.6875, 1.0),
Shapes.box(0.4375, 0.6875, 0.0, 0.5625, 0.8125, 1.0),
Shapes.box(0.5625, 0.8125, 0.0, 0.6875, 0.9375, 1.0),
Shapes.box(0.625, 0.9375, 0.0, 0.6875, 1.0, 1.0)
)
private val SHAPE_OPEN_WEST: VoxelShape = Shapes.or(
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.3125, 1.0),
Shapes.box(0.8125, 0.3125, 0.0, 0.9375, 0.4375, 1.0),
Shapes.box(0.6875, 0.4375, 0.0, 0.8125, 0.5625, 1.0),
Shapes.box(0.5625, 0.5625, 0.0, 0.6875, 0.6875, 1.0),
Shapes.box(0.4375, 0.6875, 0.0, 0.5625, 0.8125, 1.0),
Shapes.box(0.3125, 0.8125, 0.0, 0.4375, 0.9375, 1.0),
Shapes.box(0.3125, 0.9375, 0.0, 0.375, 1.0, 1.0)
)
val CODEC: MapCodec<TMMachineBlock> = RecordCodecBuilder.mapCodec { instance ->
instance.group(
Properties.CODEC.fieldOf("properties").forGetter { it.properties }
).apply(instance, ::TMMachineBlock)
}
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(WATERLOGGED, false)
.setValue(ACTIVE, false)
.setValue(EMPTY, true))
}
override fun <T : BlockEntity> getTicker(level: Level, state: BlockState, blockEntityType: BlockEntityType<T>): BlockEntityTicker<T>? {
return createTicker(level, blockEntityType, CobblemonBlockEntities.TM_MACHINE)
}
@Nullable
protected fun <T : BlockEntity> createTicker(level: Level, serverType: BlockEntityType<T>, clientType: BlockEntityType<out TMMachineBlockEntity>): BlockEntityTicker<T>? {
return if (!level.isClientSide) createTickerHelper(serverType, clientType, TMMachineBlockEntity::serverTick)
else null
}
override fun fallOn(level: Level, state: BlockState, pos: BlockPos, entity: Entity, fallDistance: Float) {
if (!level.isClientSide && (entity is LivingEntity) &&
(entity.bbWidth * entity.bbWidth * entity.bbHeight > 0.512F) &&
(entity.y >= (pos.y + 0.55)) &&
state.hasProperty(OPEN) && state.getValue(OPEN)
) {
level.setBlockAndUpdate(pos, state.setValue(OPEN, false))
level.playSoundServer(position = pos.toVec3d(), sound = CobblemonSounds.TM_MACHINE_CLOSE, volume = 1F, pitch = 1F)
}
}
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity = TMMachineBlockEntity(pos, state)
override fun getStateForPlacement(context: BlockPlaceContext): BlockState {
return defaultBlockState()
.setValue(FACING, context.horizontalDirection.opposite)
.setValue(WATERLOGGED, context.level.getFluidState(context.clickedPos).type == Fluids.WATER)
.setValue(EMPTY, true)
.setValue(ACTIVE, false)
.setValue(OPEN, false)
}
override fun useWithoutItem(state: BlockState, level: Level, pos: BlockPos, player: Player, hit: BlockHitResult): InteractionResult {
if (!level.isClientSide) {
if (player.isCrouching) {
if (state.hasProperty(OPEN)) {
val isOpen = state.getValue(OPEN)
level.setBlockAndUpdate(pos, state.setValue(OPEN, !isOpen))
level.playSoundServer(
position = pos.toVec3d(),
sound = if (!isOpen) CobblemonSounds.TM_MACHINE_CLOSE else CobblemonSounds.TM_MACHINE_OPEN,
volume = 1F,
pitch = 1F
)
}
} else {
val serverPlayer = player as? ServerPlayer
if (serverPlayer != null) {
val recipeId = cobblemonResource("tm_machine")
val recipeOpt = level.server?.recipeManager?.byKey(recipeId)
recipeOpt?.ifPresent { recipe ->
if (!serverPlayer.recipeBook.contains(recipe)) {
serverPlayer.awardRecipes(listOf(recipe))
}
}
}
val blockEntity = level.getBlockEntity(pos)
if (blockEntity is TMMachineBlockEntity) {
player.openMenu(blockEntity)
level.playSoundServer(
position = pos.toVec3d(),
sound = CobblemonSounds.TM_MACHINE_ON,
volume = 0.5F,
pitch = 1F
)
level.gameEvent(player, GameEvent.BLOCK_OPEN, pos)
}
}
}
return InteractionResult.sidedSuccess(level.isClientSide)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING, WATERLOGGED, EMPTY, ACTIVE, OPEN)
}
override fun codec(): MapCodec<TMMachineBlock> = CODEC
override fun getRenderShape(state: BlockState): RenderShape = RenderShape.MODEL
override fun getFluidState(state: BlockState): FluidState =
if (state.getValue(WATERLOGGED)) Fluids.WATER.defaultFluidState() else super.getFluidState(state)
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
return if (state.getValue(OPEN)) {
when (state.getValue(FACING)) {
Direction.NORTH -> SHAPE_OPEN_NORTH
Direction.SOUTH -> SHAPE_OPEN_SOUTH
Direction.WEST -> SHAPE_OPEN_WEST
Direction.EAST -> SHAPE_OPEN_EAST
else -> Shapes.empty()
}
} else SHAPE
}
override fun getShape(state: BlockState, getter: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape = if (state.getValue(OPEN)) SHAPE_OPEN else SHAPE
override fun onRemove(state: BlockState, level: Level, pos: BlockPos, newState: BlockState, movedByPiston: Boolean) {
Containers.dropContentsOnDestroy(state, newState, level, pos)
super.onRemove(state, level, pos, newState, movedByPiston)
}
override fun isSignalSource(state: BlockState): Boolean = false
override fun getSignal(state: BlockState, level: BlockGetter, pos: BlockPos, direction: Direction): Int = 0
override fun hasAnalogOutputSignal(state: BlockState): Boolean = true
/*override fun getAnalogOutputSignal(state: BlockState, level: Level, pos: BlockPos): Int {
val be = level.getBlockEntity(pos) as? TMMachineBlockEntity ?: return 0
// Have comparator output act like a progress bar when burning because that might be super dope to see without going into the menu
val scaled = (be.burnProgress.toFloat() / TMMachineBlockEntity.TOTAL_PROCESS_TIME.toFloat() * 15f).toInt()
return scaled.coerceIn(0, 15)
}*/
override fun getAnalogOutputSignal(state: BlockState, level: Level, pos: BlockPos): Int {
val be = level.getBlockEntity(pos) as? TMMachineBlockEntity ?: return 0
// Have comparator output act like a progress bar when burning because that might be super dope to see without going into the menu
if (be.burnActive) {
val scaled = (be.burnProgress.toFloat() / TMMachineBlockEntity.TOTAL_PROCESS_TIME.toFloat() * 15F).toInt()
return scaled.coerceIn(0, 15)
}
// if batch mode is off but it is ready to craft then show signal of 15
if (!be.repeatProcess) {
if (be.isReadyToCraft()) return 15
else return 0
}
// When not burning but idling in batch mode maybe show some cool states? Could be nice
return when {
be.isOutputBlocked() -> 8
else -> 4
}
}
}
@@ -1,210 +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.block.tmmachine
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonMenuType
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.block.entity.TMMachineBlockEntity
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.Mth
import net.minecraft.world.Container
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.*
import net.minecraft.world.item.ItemStack
class TMMachineMenu(menuType: MenuType<*>, syncId: Int) : AbstractContainerMenu(menuType, syncId), ContainerListener {
companion object {
const val RESULT_SLOT = 0
const val BLANK_TM_SLOT = 1
val INGREDIENT_SLOTS = 2..4
}
var playerInventory: Inventory? = null
var tmMachineEntity: TMMachineBlockEntity? = null
var inventory: Container? = null
var containerData: ContainerData? = null
constructor(syncId: Int, playerInventory: Inventory) : this(CobblemonMenuType.TM_MACHINE, syncId) {
this.playerInventory = playerInventory
this.inventory = SimpleContainer(5)
this.containerData = SimpleContainerData(6)
this.tmMachineEntity = (inventory as? TMMachineBlockEntity.TMMachineBlockInventory)?.blockEntity
this.addDataSlots(containerData!!)
inventory?.startOpen(playerInventory.player)
val startX = 15
val startY = 144
val slotWidth = 18
val slotHeight = 18
for (row in 0..2) {
for (col in 0..8) {
this.addSlot(Slot(playerInventory, 9 + (row * 9) + col, startX + (slotWidth * col), startY + (slotHeight * row)))
}
}
for (col in 0..8) {
this.addSlot(Slot(playerInventory, col, startX + (slotWidth * col), startY + 58))
}
val container = inventory
container?.let {
this.addSlot(TMResultSlot(container, RESULT_SLOT, startX + 37, startY - 28)) // Output slot
this.addSlot(BlankTMSlot(container, BLANK_TM_SLOT, startX + 90, startY - 28)) // Blank TM
for (index in INGREDIENT_SLOTS) {
this.addSlot(Slot(container, index, startX + 114 + (18 * (index - INGREDIENT_SLOTS.first)), startY - 28))
}
}
this.addSlotListener(this)
}
constructor(syncId: Int, playerInventory: Inventory, inventory: Container, containerData: ContainerData) : this(CobblemonMenuType.TM_MACHINE, syncId) {
this.playerInventory = playerInventory
this.inventory = inventory
this.containerData = containerData
this.tmMachineEntity = (inventory as? TMMachineBlockEntity.TMMachineBlockInventory)?.blockEntity
this.addDataSlots(containerData)
inventory.startOpen(playerInventory.player)
val startX = 15
val startY = 144
val slotWidth = 18
val slotHeight = 18
for (row in 0..2) {
for (col in 0..8) {
this.addSlot(Slot(playerInventory, 9 + (row * 9) + col, startX + (slotWidth * col), startY + (slotHeight * row)))
}
}
for (col in 0..8) {
this.addSlot(Slot(playerInventory, col, startX + (slotWidth * col), startY + 58))
}
val container = inventory
container.let {
this.addSlot(TMResultSlot(container, RESULT_SLOT, startX + 37, startY - 28)) // Output slot
this.addSlot(BlankTMSlot(container, BLANK_TM_SLOT, startX + 90, startY - 28)) // Blank TM
for (index in INGREDIENT_SLOTS) {
this.addSlot(Slot(container, index, startX + 114 + (18 * (index - INGREDIENT_SLOTS.first)), startY - 28))
}
}
this.addSlotListener(this)
}
override fun quickMoveStack(player: Player, index: Int): ItemStack {
var itemStack = ItemStack.EMPTY
val slot = this.slots[index]
val maxPlayerInventoryIndex = Inventory.INVENTORY_SIZE - 1
if (slot.hasItem()) {
val movedStack = slot.item
itemStack = movedStack.copy()
// If in container slots, move to player inventory
// 36: Output slot, 37: Blank TM slot, 38-40: Ingredient slots
if (index in 36..40) {
if (!this.moveItemStackTo(movedStack, 0, maxPlayerInventoryIndex, false)) return ItemStack.EMPTY
}
// If in player inventory, move to container slots. 'endIndex' is non-inclusive
else if (index <= maxPlayerInventoryIndex) {
if (!this.moveItemStackTo(movedStack, 37, 40 + 1, false)) return ItemStack.EMPTY
}
if (movedStack.isEmpty) {
slot.setByPlayer(ItemStack.EMPTY)
} else {
slot.setChanged()
}
}
return itemStack
}
override fun stillValid(player: Player): Boolean {
return player.let { inventory?.stillValid(it) } ?: false
}
fun getBurnProgressValue(): Int = this.containerData?.get(TMMachineBlockEntity.BURN_PROGRESS_INDEX) ?: 0
fun getBurnProgressRatio(): Float {
val amount = getBurnProgressValue()
val max = TMMachineBlockEntity.BURN_TOTAL_TIME
return if (amount != 0) {
Mth.clamp((amount.toFloat() / max.toFloat()), 0.0F, 1.0F)
} else {
0.0F
}
}
fun getPostCraftTicks(): Int {
return if (getBurnProgressValue() >= TMMachineBlockEntity.BURN_TOTAL_TIME) {
getBurnProgressValue() - TMMachineBlockEntity.BURN_TOTAL_TIME
} else 0
}
override fun removed(player: Player) {
super.removed(player)
tmMachineEntity?.setChanged()
}
class TMResultSlot(container: Container, slot: Int, x: Int, y: Int) : Slot(container, slot, x, y) {
override fun mayPlace(stack: ItemStack): Boolean = false
}
class BlankTMSlot(container: Container, slot: Int, x: Int, y: Int) : Slot(container, slot, x, y) {
override fun mayPlace(stack: ItemStack): Boolean = super.mayPlace(stack) && (stack.item == CobblemonItems.BLANK_TM)
}
override fun slotChanged(containerToSend: AbstractContainerMenu, dataSlotIndex: Int, stack: ItemStack) {
val level = playerInventory?.player?.level()
// If ingredient slots have been modified and level is server side and process is active
// 36: Output slot, 37: Blank TM slot, 38-40: Ingredient slots
if ((dataSlotIndex in 37 .. 40) && (level?.isClientSide == false) && (containerData?.get(TMMachineBlockEntity.BURN_ACTIVE_INDEX) == 1)) {
// Stop process if there are not enough ingredients after slots have been modified
var validCost = !(inventory?.getItem(BLANK_TM_SLOT)?.isEmpty ?: true)
if (validCost) {
Moves.getByName(tmMachineEntity?.activeMove ?: "")?.let { moveTemplate ->
TechnicalMachines.moveToTM[moveTemplate]?.let { tm ->
val recipes = tm.getClampedRecipe() ?: emptyList()
for ((index, recipe) in recipes.withIndex()) {
val slot = INGREDIENT_SLOTS.first + index
val provided = inventory?.getItem(slot) ?: ItemStack.EMPTY
if (!recipe.ingredient.test(provided) || provided.count < recipe.count) {
validCost = false
break
}
}
}
}
}
if (!validCost) {
containerData?.set(TMMachineBlockEntity.BURN_ACTIVE_INDEX, 0)
containerData?.set(TMMachineBlockEntity.BURN_PROGRESS_INDEX, 0)
}
}
tmMachineEntity?.setChanged()
inventory?.setChanged()
playerInventory?.setChanged()
broadcastChanges()
}
override fun dataChanged(containerMenu: AbstractContainerMenu, dataSlotIndex: Int, value: Int) {
broadcastChanges()
}
}
@@ -19,7 +19,6 @@ import com.cobblemon.mod.common.api.berry.Berries
import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker
import com.cobblemon.mod.common.api.storage.player.client.ClientGeneralPlayerData
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
import com.cobblemon.mod.common.api.storage.player.client.ClientTMMoveManager
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.block.entity.TintBlockEntity
import com.cobblemon.mod.common.client.battle.ClientBattle
@@ -28,7 +27,6 @@ import com.cobblemon.mod.common.client.gui.PartyOverlayDataControl
import com.cobblemon.mod.common.client.gui.RideControlsOverlay
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotScreen
import com.cobblemon.mod.common.client.gui.tmmachine.TMMachineScreen
import com.cobblemon.mod.common.client.gui.party.PartyTutorialToasts
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
import com.cobblemon.mod.common.client.render.ClientPlayerIcon
@@ -42,9 +40,7 @@ import com.cobblemon.mod.common.client.render.block.HealingMachineRenderer
import com.cobblemon.mod.common.client.render.block.LecternBlockEntityRenderer
import com.cobblemon.mod.common.client.render.block.PokeSnackBlockEntityRenderer
import com.cobblemon.mod.common.client.render.block.RestorationTankRenderer
import com.cobblemon.mod.common.client.render.block.TMShelfBlockEntityRenderer
import com.cobblemon.mod.common.client.render.boat.CobblemonBoatRenderer
import com.cobblemon.mod.common.client.render.color.TechnicalMachineItemColorProvider
import com.cobblemon.mod.common.client.render.color.AprijuiceItemColorProvider
import com.cobblemon.mod.common.client.render.color.PokeBaitItemColorProvider
import com.cobblemon.mod.common.client.render.color.PokeSnackItemColorProvider
@@ -73,7 +69,6 @@ import com.cobblemon.mod.common.client.tooltips.FishingRodTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.PokePuffTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.RecipeSeasoningAbsorptionTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.SeasoningTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.TechnicalMachineTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.TooltipManager
import com.cobblemon.mod.common.client.trade.ClientTrade
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
@@ -94,7 +89,6 @@ 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.renderer.entity.layers.RenderLayer
import net.minecraft.client.resources.PlayerSkin
import net.minecraft.server.packs.resources.ResourceManager
import net.minecraft.world.InteractionHand
@@ -109,7 +103,6 @@ object CobblemonClient {
var battle: ClientBattle? = null
var clientPlayerData = ClientGeneralPlayerData()
var clientPokedexData = ClientPokedexManager(mutableMapOf())
var clientTMMoveData = ClientTMMoveManager(mutableSetOf())
/** If true then we won't bother them anymore about choosing a starter even if it's a thing they can do. */
var checkedStarterScreen = false
@@ -126,7 +119,6 @@ object CobblemonClient {
requests = ClientPlayerActionRequests()
teamData = ClientPlayerTeamData()
clientPokedexData = ClientPokedexManager(mutableMapOf())
clientTMMoveData = ClientTMMoveManager(mutableSetOf())
storage.onLogin()
// CobblemonDataProvider.canReload = false
}
@@ -214,7 +206,6 @@ object CobblemonClient {
TooltipManager.registerTooltipGenerator(SeasoningTooltipGenerator)
TooltipManager.registerTooltipGenerator(FishingRodTooltipGenerator)
TooltipManager.registerTooltipGenerator(AprijuiceTooltipGenerator)
TooltipManager.registerTooltipGenerator(TechnicalMachineTooltipGenerator)
TooltipManager.registerTooltipGenerator(PokePuffTooltipGenerator)
}
@@ -306,24 +297,6 @@ object CobblemonClient {
CobblemonBlocks.MEDIUM_BUDDING_BLACK_TUMBLESTONE,
CobblemonBlocks.LARGE_BUDDING_BLACK_TUMBLESTONE,
CobblemonBlocks.BLACK_TUMBLESTONE_CLUSTER,
CobblemonBlocks.TYPE_GEM_CLUSTER_NORMAL,
CobblemonBlocks.TYPE_GEM_CLUSTER_FIRE,
CobblemonBlocks.TYPE_GEM_CLUSTER_WATER,
CobblemonBlocks.TYPE_GEM_CLUSTER_ELECTRIC,
CobblemonBlocks.TYPE_GEM_CLUSTER_GRASS,
CobblemonBlocks.TYPE_GEM_CLUSTER_ICE,
CobblemonBlocks.TYPE_GEM_CLUSTER_FIGHTING,
CobblemonBlocks.TYPE_GEM_CLUSTER_POISON,
CobblemonBlocks.TYPE_GEM_CLUSTER_GROUND,
CobblemonBlocks.TYPE_GEM_CLUSTER_FLYING,
CobblemonBlocks.TYPE_GEM_CLUSTER_PSYCHIC,
CobblemonBlocks.TYPE_GEM_CLUSTER_BUG,
CobblemonBlocks.TYPE_GEM_CLUSTER_ROCK,
CobblemonBlocks.TYPE_GEM_CLUSTER_GHOST,
CobblemonBlocks.TYPE_GEM_CLUSTER_DRAGON,
CobblemonBlocks.TYPE_GEM_CLUSTER_DARK,
CobblemonBlocks.TYPE_GEM_CLUSTER_STEEL,
CobblemonBlocks.TYPE_GEM_CLUSTER_FAIRY,
CobblemonBlocks.SMALL_BUDDING_SKY_TUMBLESTONE,
CobblemonBlocks.MEDIUM_BUDDING_SKY_TUMBLESTONE,
CobblemonBlocks.LARGE_BUDDING_SKY_TUMBLESTONE,
@@ -389,8 +362,7 @@ object CobblemonClient {
CobblemonBlocks.X_DEFENSE,
CobblemonBlocks.X_SP_ATK,
CobblemonBlocks.X_SP_DEF,
CobblemonBlocks.X_SPEED,
CobblemonBlocks.TM_MACHINE
CobblemonBlocks.X_SPEED
)
this.createBoatModelLayers()
@@ -418,7 +390,6 @@ object CobblemonClient {
private fun registerMenuScreens() {
MenuScreens.register(CobblemonMenuType.COOKING_POT, ::CookingPotScreen)
MenuScreens.register(CobblemonMenuType.TM_MACHINE, ::TMMachineScreen)
}
private fun registerBlockEntityRenderers() {
@@ -442,7 +413,6 @@ object CobblemonClient {
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.LECTERN, ::LecternBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.CAMPFIRE, ::CampfireBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.POKE_SNACK, ::PokeSnackBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.TM_SHELF, ::TMShelfBlockEntityRenderer)
}
private fun registerEntityRenderers() {
@@ -451,14 +421,14 @@ object CobblemonClient {
LOGGER.info("Registering PokéBall renderer")
this.implementation.registerEntityRenderer(CobblemonEntities.EMPTY_POKEBALL, ::PokeBallRenderer)
LOGGER.info("Registering Boat renderer")
this.implementation.registerEntityRenderer(CobblemonEntities.BOAT) { ctx ->
this.implementation.registerEntityRenderer(CobblemonEntities.BOAT) { ctx -> CobblemonBoatRenderer(ctx, false) }
LOGGER.info("Registering Boat with Chest renderer")
this.implementation.registerEntityRenderer(CobblemonEntities.CHEST_BOAT) { ctx ->
CobblemonBoatRenderer(
ctx,
true
)
}
LOGGER.info("Registering Boat with Chest renderer")
this.implementation.registerEntityRenderer(CobblemonEntities.CHEST_BOAT) { ctx -> CobblemonBoatRenderer(ctx, true) }
LOGGER.info("Registering Generic Bedrock renderer")
this.implementation.registerEntityRenderer(CobblemonEntities.GENERIC_BEDROCK_ENTITY, ::GenericBedrockRenderer)
LOGGER.info("Registering Generic Bedrock Entity renderer")
@@ -475,7 +445,6 @@ object CobblemonClient {
implementation.registerItemColors(PokeBaitItemColorProvider, CobblemonItems.POKE_BAIT)
implementation.registerItemColors(PonigiriItemColorProvider, CobblemonItems.PONIGIRI)
implementation.registerItemColors(SinisterTeaItemColorProvider, CobblemonItems.SINISTER_TEA)
implementation.registerItemColors(TechnicalMachineItemColorProvider, CobblemonItems.TECHNICAL_MACHINE)
}
fun reloadCodedAssets(resourceManager: ResourceManager) {
@@ -512,4 +481,4 @@ object CobblemonClient {
)
}
}
}
}
@@ -93,7 +93,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
val starterToast = CobblemonToast(
Mth.createInsecureUUID(),
listOf(CobblemonItems.POKE_BALL.defaultInstance),
CobblemonItems.POKE_BALL.defaultInstance,
lang("ui.starter.choose_starter_title", SummaryBinding.boundKey().displayName).red(),
lang("ui.starter.choose_starter_description", SummaryBinding.boundKey().displayName).darkGray(),
AdvancementToast.BACKGROUND_SPRITE,
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.client.gui.interact.moveselect
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.gold
import com.cobblemon.mod.common.api.text.red
@@ -20,7 +21,6 @@ import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.toRGB
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
@@ -31,92 +31,96 @@ import net.minecraft.util.Mth
class MoveSlotButton(
x: Int, y: Int,
var move: MoveTemplate? = null,
var pp: Int = 0,
var ppMax: Int = 0,
var ppAsFraction: Boolean = true,
var enabled: Boolean = true,
val move: MoveTemplate,
val pp: Int,
val ppMax: Int,
val enabled: Boolean = true,
onPress: OnPress
) : Button(x, y, WIDTH, HEIGHT, move?.name?.text() ?: lang("ui.moves"), onPress, CreateNarration { move?.name?.text() ?: lang("ui.moves") }), CobblemonRenderable {
) : Button(x, y, WIDTH, HEIGHT, Component.literal("Move"), onPress, CreateNarration { "".text() }), CobblemonRenderable {
companion object {
private val moveResource = cobblemonResource("textures/gui/summary/summary_move.png")
private val moveDisabledResource = cobblemonResource("textures/gui/summary/summary_move_disabled.png")
private val moveOverlayResource = cobblemonResource("textures/gui/summary/summary_move_overlay.png")
const val WIDTH = 108
const val HEIGHT = 22
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, pPartialTicks: Float) {
move?.let {
isHovered = isMouseOver(mouseX.toDouble(), mouseY.toDouble()) && enabled
val rgb = it.elementalType.hue.toRGB()
val alpha = if (enabled) 1.0F else 0.7F
val matrices = context.pose()
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
isHovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height && enabled
blitk(
matrixStack = matrices,
texture = if (enabled) moveResource else moveDisabledResource,
x = x,
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered) HEIGHT else 0,
textureHeight = HEIGHT * 2,
red = rgb.first,
green = rgb.second,
blue = rgb.third
)
val moveTemplate = Moves.getByNameOrDummy(move.name)
val rgb = moveTemplate.elementalType.hue.toRGB()
blitk(
matrixStack = matrices,
texture = moveOverlayResource,
x = x,
y = y,
width = WIDTH,
height = HEIGHT
)
val alpha = if (enabled) 1.0 else 0.5
if (pp != -1 && ppMax != -1) {
var movePPText = lang("ui.moves.pp", pp).bold()
if (ppAsFraction) {
movePPText = Component.literal("$pp/$ppMax").bold()
val matrices = context.pose()
blitk(
matrixStack = matrices,
texture = moveResource,
x = x,
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered) HEIGHT else 0,
textureHeight = HEIGHT * 2,
red = rgb.first,
green = rgb.second,
blue = rgb.third,
alpha = alpha
)
if (pp <= Mth.floor(ppMax / 2F)) {
movePPText = if (pp == 0) movePPText.red() else movePPText.gold()
}
}
blitk(
matrixStack = matrices,
texture = moveOverlayResource,
x = x,
y = y,
width = WIDTH,
height = HEIGHT,
alpha = alpha
)
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = movePPText,
x = x + 93,
y = y + 13,
centered = true,
opacity = alpha
)
if (pp != -1 && ppMax != -1) {
var movePPText = Component.literal("$pp/$ppMax").bold()
if (pp <= Mth.floor(ppMax / 2F)) {
movePPText = if (pp == 0) movePPText.red() else movePPText.gold()
}
// Type Icon
TypeIcon(x = x + 2, y = y + 2, type = it.elementalType, opacity = alpha).render(context)
// Move Category
MoveCategoryIcon(x = x + 66, y = y + 13.5, category = it.damageCategory, opacity = alpha).render(context)
// Move Name
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = it.displayName.bold(),
x = x + 28,
y = y + 2,
shadow = true,
opacity = alpha
text = movePPText,
x = x + 93,
y = y + 13,
centered = true
)
}
// Type Icon
TypeIcon(
x = x + 2,
y = y + 2,
type = moveTemplate.elementalType
).render(context)
// Move Category
MoveCategoryIcon(
x = x + 66,
y = y + 13.5,
category = move.damageCategory
).render(context)
// Move Name
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = move.displayName.bold(),
x = x + 28,
y = y + 2,
shadow = true
)
}
override fun playDownSound(soundManager: SoundManager) {}
}
}
@@ -45,7 +45,7 @@ object PartyTutorialToasts {
private val toast = CobblemonToast(
Mth.createInsecureUUID(),
listOf(CobblemonItems.POKEDEX_RED.defaultInstance),
CobblemonItems.POKEDEX_RED.defaultInstance,
lang("ui.party.party_select_title").red(),
lang(
"ui.party.party_select_description",
@@ -96,4 +96,4 @@ object PartyTutorialToasts {
private fun hideToast() {
if (toastAttached) toast.nextVisibility = Toast.Visibility.HIDE
}
}
}
@@ -22,8 +22,6 @@ import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
import com.cobblemon.mod.common.api.pokedex.filter.EntryFilter
import com.cobblemon.mod.common.api.pokedex.filter.SearchByType
import com.cobblemon.mod.common.api.pokedex.filter.SearchFilter
import com.cobblemon.mod.common.api.pokedex.filter.SecondaryPokedexFilter
import com.cobblemon.mod.common.api.pokedex.filter.SecondaryPokedexFilterType
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
import com.cobblemon.mod.common.api.text.bold
@@ -41,7 +39,6 @@ import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_ABILI
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_DESCRIPTION
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_DROPS
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_ICON_SIZE
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.*
@@ -86,14 +83,12 @@ class PokedexGUI private constructor(
private val arrowDownIcon = cobblemonResource("textures/gui/pokedex/arrow_down.png")
private val tabSelectArrow = cobblemonResource("textures/gui/pokedex/select_arrow.png")
private val secondaryFilterIcon = cobblemonResource("textures/gui/pokedex/filter_icon_button.png")
private val tabIcons = arrayOf(
cobblemonResource("textures/gui/pokedex/tab_info.png"),
cobblemonResource("textures/gui/pokedex/tab_abilities.png"),
cobblemonResource("textures/gui/pokedex/tab_size.png"),
cobblemonResource("textures/gui/pokedex/tab_stats.png"),
cobblemonResource("textures/gui/pokedex/tab_drops.png"),
cobblemonResource("textures/gui/pokedex/tab_moves.png")
cobblemonResource("textures/gui/pokedex/tab_drops.png")
)
/**
@@ -132,13 +127,11 @@ class PokedexGUI private constructor(
private lateinit var regionSelectWidgetUp: ScaledButton
private lateinit var regionSelectWidgetDown: ScaledButton
private lateinit var searchByTypeButton: ScaledButton
private lateinit var secondaryFilterButton: ScaledButton
private lateinit var scrollScreen: EntriesScrollingWidget
private lateinit var pokemonInfoWidget: PokemonInfoWidget
private lateinit var searchWidget: SearchWidget
private var selectedSearchByType: SearchByType = SearchByType.SPECIES
private var selectedSecondaryFilter: SecondaryPokedexFilterType = SecondaryPokedexFilterType.ALL
private val tabButtons: MutableList<ScaledButton> = mutableListOf()
lateinit var tabInfoElement: GuiEventListener
@@ -170,7 +163,7 @@ class PokedexGUI private constructor(
displaytabInfoElement(tabInfoIndex, false)
if (::searchWidget.isInitialized) removeWidget(searchWidget)
searchWidget = SearchWidget(x + 26, y + 28, 120, HEADER_BAR_HEIGHT, update = ::updateFilters)
searchWidget = SearchWidget(x + 26, y + 28, 128, HEADER_BAR_HEIGHT, update = ::updateFilters)
addRenderableWidget(searchWidget)
if (::regionSelectWidgetUp.isInitialized) removeWidget(regionSelectWidgetUp)
@@ -215,23 +208,6 @@ class PokedexGUI private constructor(
)
addRenderableWidget(searchByTypeButton)
if (::secondaryFilterButton.isInitialized) removeWidget(secondaryFilterButton)
secondaryFilterButton = ScaledButton(
buttonX = (x + 146.5).toFloat(),
buttonY = (y + 29.5).toFloat(),
buttonWidth = 16,
buttonHeight = 16,
scale = SCALE,
resource = secondaryFilterIcon,
clickAction = {
val filters = SecondaryPokedexFilterType.entries.toList()
val selectedIndex = filters.indexOf(selectedSecondaryFilter)
selectedSecondaryFilter = filters[if (selectedIndex == filters.lastIndex) 0 else (selectedIndex + 1)]
updateFilters()
}
)
addRenderableWidget(secondaryFilterButton)
updateFilters(true)
}
@@ -333,7 +309,8 @@ class PokedexGUI private constructor(
blitk(
matrixStack = matrices,
texture = tabSelectArrow,
x = (x + 191.5F + (22F * tabInfoIndex)) / SCALE,
x = (x + 198 + (25 * tabInfoIndex)) / SCALE,
// (x + 191.5 + (22 * tabInfoIndex)) / SCALE for 6 tabs
y = (y + 177) / SCALE,
width = 12,
height = 6,
@@ -355,18 +332,6 @@ class PokedexGUI private constructor(
-14
)
}
if (secondaryFilterButton.isButtonHovered(mouseX, mouseY)) {
val filterText = lang("ui.pokedex.filter.${selectedSecondaryFilter.name.lowercase()}").bold()
renderTooltip(
context,
filterText,
mouseX,
mouseY,
delta,
-14
)
}
}
override fun onClose() {
@@ -379,7 +344,6 @@ class PokedexGUI private constructor(
val canDisplayEntry = true //selectedForm?.unlockForms
if (::pokemonInfoWidget.isInitialized
&& !pokemonInfoWidget.isSuppressed
&& pokemonInfoWidget.isWithinPortraitSpace(mouseX, mouseY)
&& canDisplayEntry == true
) {
@@ -411,7 +375,7 @@ class PokedexGUI private constructor(
}
override fun tick() {
if (::pokemonInfoWidget.isInitialized && !pokemonInfoWidget.isSuppressed) pokemonInfoWidget.tick()
if (::pokemonInfoWidget.isInitialized) pokemonInfoWidget.tick()
}
fun updatePokedexRegion(nextIndex: Boolean) {
@@ -477,7 +441,6 @@ class PokedexGUI private constructor(
fun getFilters(): Collection<EntryFilter> {
val filters: MutableList<EntryFilter> = mutableListOf()
filters.add(SecondaryPokedexFilter(CobblemonClient.clientPokedexData, selectedSecondaryFilter))
filters.add(SearchFilter(CobblemonClient.clientPokedexData, searchWidget.value, selectedSearchByType))
return filters
@@ -499,7 +462,7 @@ class PokedexGUI private constructor(
for (i in tabIcons.indices) {
tabButtons.add(ScaledButton(
x + 190.5F + (i * 22F),
x + 197F + (i * 25F), // x + 190.5F + (i * 22F) for 6 tabs
y + 181.5F,
TAB_ICON_SIZE,
TAB_ICON_SIZE,
@@ -534,9 +497,6 @@ class PokedexGUI private constructor(
}
tabInfoIndex = tabIndex
if (::pokemonInfoWidget.isInitialized) {
pokemonInfoWidget.isSuppressed = tabInfoIndex == TAB_MOVES
}
if (::tabInfoElement.isInitialized) removeWidget(tabInfoElement)
val x = (width - BASE_WIDTH) / 2
@@ -560,19 +520,11 @@ class PokedexGUI private constructor(
TAB_DROPS -> {
tabInfoElement = DropsScrollingWidget(x + 189, y + 135)
}
TAB_MOVES -> {
tabInfoElement = MovesLearnsetWidget(x + 180, y + 28)
}
}
val element = tabInfoElement
if (element is Renderable && element is NarratableEntry) {
addRenderableWidget(element)
}
// Keep tab icons above the moves overlay.
if (tabButtons.isNotEmpty()) {
tabButtons.forEach { removeWidget(it) }
tabButtons.forEach { addRenderableWidget(it) }
}
if (update) updateTabInfoElement()
}
@@ -626,17 +578,6 @@ class PokedexGUI private constructor(
(tabInfoElement as DropsScrollingWidget).dropTable = form.drops
(tabInfoElement as DropsScrollingWidget).setEntries()
}
TAB_MOVES -> {
val availableForms = selectedEntry?.let { CobblemonClient.clientPokedexData.getEncounteredForms(it) } ?: emptyList()
val activeForm = this.selectedForm ?: availableForms.firstOrNull()
val fallbackForm = activeForm ?: PokedexForm().apply { displayForm = form.name }
(tabInfoElement as MovesLearnsetWidget).setLearnset(
species = species,
form = form,
availableForms = availableForms,
selectedForm = fallbackForm
) { next -> cycleSelectedForm(next) }
}
// TAB_MOVES -> {
// form.moves.getLevelUpMovesUpTo(100)
// }
@@ -659,29 +600,6 @@ class PokedexGUI private constructor(
displaytabInfoElement(tabInfoIndex)
}
fun cycleSelectedForm(next: Boolean) {
val entry = selectedEntry ?: return
val forms = CobblemonClient.clientPokedexData.getEncounteredForms(entry)
if (forms.isEmpty()) return
val current = selectedForm ?: forms.first()
val currentIndex = forms.indexOfFirst { it.displayForm.equals(current.displayForm, ignoreCase = true) }
.let { if (it == -1) 0 else it }
val nextIndex = if (next) {
(currentIndex + 1) % forms.size
} else {
(currentIndex - 1 + forms.size) % forms.size
}
val newForm = forms[nextIndex]
selectedForm = newForm
if (::pokemonInfoWidget.isInitialized) {
pokemonInfoWidget.setSelectedForm(newForm)
}
updateTabInfoElement()
}
fun canSelectTab(tabIndex: Int): Boolean {
val selectedForm = this.selectedForm ?: return false
val selectedEntry = this.selectedEntry ?: return false
@@ -703,4 +621,4 @@ class PokedexGUI private constructor(
fun playSound(soundEvent: SoundEvent) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
}
}
}
@@ -35,10 +35,9 @@ object PokedexGUIConstants {
const val TAB_SIZE = 2
const val TAB_STATS = 3
const val TAB_DROPS = 4
const val TAB_MOVES = 5
// RowScrollingWidget
const val SCROLL_BASE_HEIGHT = 141
const val SCROLL_SLOT_SIZE = 25
const val SCROLL_SLOT_SPACING = 2
}
}
@@ -132,4 +132,4 @@ abstract class InfoTextScrollWidget(val pX: Int, val pY: Int): ScrollingWidget<I
return Component.literal(text)
}
}
}
}
@@ -125,7 +125,6 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
var ticksElapsed = 0
var pokeBallBackgroundFrame = 0
var isSuppressed = false
class VariationButtonWrapper(
val parent: PokemonInfoWidget,
@@ -260,9 +259,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) {
if (isSuppressed) {
return
}
val currentEntry = this.currentEntry ?: return
val hasKnowledge = CobblemonClient.clientPokedexData.getKnowledgeForSpecies(currentEntry.speciesId) != PokedexEntryProgress.NONE
@@ -516,40 +512,6 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
matrices.popPose()
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (isSuppressed) {
return false
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (isSuppressed) {
return false
}
return super.mouseReleased(mouseX, mouseY, button)
}
override fun mouseScrolled(mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double): Boolean {
if (isSuppressed) {
return false
}
return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
if (isSuppressed) {
return false
}
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
override fun mouseMoved(mouseX: Double, mouseY: Double) {
if (!isSuppressed) {
super.mouseMoved(mouseX, mouseY)
}
}
fun setDexEntry(pokedexEntry : PokedexEntry) {
this.currentEntry = pokedexEntry
this.renderablePokemon = null
@@ -662,19 +624,6 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
updateAspects()
}
fun setSelectedForm(newForm: PokedexForm) {
if (visibleForms.isEmpty() || currentEntry == null) {
return
}
val index = visibleForms.indexOfFirst { it.displayForm.equals(newForm.displayForm, ignoreCase = true) }
if (index == -1) {
return
}
selectedFormIndex = index
setupButtons(currentEntry!!, visibleForms[selectedFormIndex])
updateAspects()
}
fun updateAspects() {
if (visibleForms.isEmpty()) {
return
@@ -757,4 +706,4 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
fun playSound(soundEvent: SoundEvent) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
}
}
}
@@ -0,0 +1,46 @@
/*
* 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.gui.pokeedit
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.pokemon.EVs
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.IVs
import com.cobblemon.mod.common.pokemon.Species
data class PokeEditDraft(
var species: Species,
var form: FormData = species.standardForm,
var shiny: Boolean = false,
var gender: Gender = defaultGenderFor(form),
var level: Int = 1,
var originalTrainer: String = "",
var abilityIndex: Int = 0,
var ivs: IVs = IVs(),
var evs: EVs = EVs(),
var cosmeticItem: String? = null,
var cosmeticAspects: Set<String> = emptySet(),
var heldItem: String? = null,
val forcedAspects: MutableSet<String> = mutableSetOf(),
val choiceFeatures: MutableMap<String, String> = mutableMapOf(),
val flagFeatures: MutableSet<String> = mutableSetOf(),
val activeMoves: MutableList<MoveTemplate?> = MutableList(4) { null },
val benchedMoves: MutableSet<MoveTemplate> = mutableSetOf()
) {
companion object {
fun defaultGenderFor(form: FormData): Gender {
return when {
form.maleRatio !in 0F..1F -> Gender.GENDERLESS
form.maleRatio == 0F -> Gender.FEMALE
else -> Gender.MALE
}
}
}
}
@@ -0,0 +1,600 @@
/*
* 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.gui.pokeedit
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCALE
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_ICON_SIZE
import com.cobblemon.mod.common.client.gui.pokedex.ScaledButton
import com.cobblemon.mod.common.client.gui.pokeedit.widgets.PokeEditAnimationListWidget
import com.cobblemon.mod.common.client.gui.pokeedit.widgets.PokeEditAnimationTab
import com.cobblemon.mod.common.client.gui.pokeedit.widgets.PokeEditAspectsTab
import com.cobblemon.mod.common.client.gui.pokeedit.widgets.PokeEditEVIVTab
import com.cobblemon.mod.common.client.gui.pokeedit.widgets.PokeEditGeneralTab
import com.cobblemon.mod.common.client.gui.pokeedit.widgets.PokeEditLocatorListWidget
import com.cobblemon.mod.common.client.gui.pokeedit.widgets.PokeEditLocatorTab
import com.cobblemon.mod.common.client.gui.pokeedit.widgets.PokeEditMovesTab
import com.cobblemon.mod.common.client.gui.pokeedit.widgets.PokeEditImplementationBlankTab
import com.cobblemon.mod.common.client.gui.pokeedit.widgets.PokeEditWalkSpeedTab
import com.cobblemon.mod.common.client.gui.pokeedit.widgets.PokeEditSpeciesList
import com.cobblemon.mod.common.client.gui.pokedex.widgets.SearchWidget
import com.cobblemon.mod.common.client.gui.summary.SummaryButton
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.pokeedit.PokeEditAction
import com.cobblemon.mod.common.net.messages.server.pokeedit.PokeEditActionPacket
import com.cobblemon.mod.common.api.pokemon.feature.ChoiceSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.FlagSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.pokemon.properties.AspectPropertyType
import com.cobblemon.mod.common.pokemon.OriginalTrainerType
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.AbstractWidget
import net.minecraft.client.gui.components.EditBox
import net.minecraft.client.gui.screens.Screen
import net.minecraft.network.chat.Component
class PokeEditGUI private constructor() : Screen(Component.translatable("cobblemon.ui.pokeedit.title")) {
companion object {
private val screenBackground = cobblemonResource("textures/gui/pokedex/pokedex_screen.png")
private val baseBackground = cobblemonResource("textures/gui/pokedex/pokedex_base_red.png")
private val tabSelectArrow = cobblemonResource("textures/gui/pokedex/select_arrow.png")
private val actionButtonResource = cobblemonResource("textures/gui/summary/summary_evolve_button.png")
private val tabIcons = arrayOf(
cobblemonResource("textures/gui/pokedex/tab_info.png"),
cobblemonResource("textures/gui/pokedex/tab_stats.png"),
cobblemonResource("textures/gui/pokedex/tab_moves.png"),
cobblemonResource("textures/gui/pokedex/tab_species.png"),
cobblemonResource("textures/gui/pokedex/tab_locations.png"),
)
const val TAB_GENERAL = 0
const val TAB_EV_IV = 1
const val TAB_MOVES = 2
const val TAB_ASPECTS = 3
const val TAB_IMPLEMENTATION_BUTTON = 4
const val IMPL_TAB_LOCATORS = 0
const val IMPL_TAB_ANIMATION = 1
const val IMPL_TAB_SCALE = 2
const val IMPL_TAB_WALK_SPEED = 3
fun open() {
Minecraft.getInstance().setScreen(PokeEditGUI())
}
}
private val allSpecies = PokemonSpecies.species.sortedBy { it.nationalPokedexNumber }
private var filteredSpecies = allSpecies
private lateinit var searchWidget: SearchWidget
private lateinit var speciesList: PokeEditSpeciesList
private lateinit var locatorListWidget: PokeEditLocatorListWidget
private lateinit var animationListWidget: PokeEditAnimationListWidget
private var highlightedLocator: String? = null
private var animationTab: PokeEditAnimationTab? = null
private val tabButtons: MutableList<ScaledButton> = mutableListOf()
private var tabIndex = TAB_GENERAL
private var implementationMode = false
private var implementationTabIndex = IMPL_TAB_LOCATORS
private lateinit var tabElement: AbstractWidget
private lateinit var createButton: SummaryButton
private lateinit var spawnButton: SummaryButton
private lateinit var exitButton: ExitButton
private lateinit var implementationButton: SummaryButton
private lateinit var implementationExitButton: SummaryButton
private val implementationTabButtons: MutableList<SummaryButton> = mutableListOf()
private val draft = PokeEditDraft(allSpecies.firstOrNull() ?: PokemonSpecies.random())
override fun renderBlurredBackground(delta: Float) {}
override fun renderMenuBackground(context: GuiGraphics) {}
override fun isPauseScreen() = false
override fun init() {
super.init()
clearWidgets()
val x = (width - BASE_WIDTH) / 2
val y = (height - BASE_HEIGHT) / 2
if (::searchWidget.isInitialized) removeWidget(searchWidget)
searchWidget = SearchWidget(x + 26, y + 28, 128, 11, update = ::updateFilters)
addRenderableWidget(searchWidget)
if (::speciesList.isInitialized) removeWidget(speciesList)
speciesList = PokeEditSpeciesList(x + 26, y + 39, ::setSelectedSpecies)
if (::locatorListWidget.isInitialized) removeWidget(locatorListWidget)
locatorListWidget = PokeEditLocatorListWidget(
pX = x + 26,
pY = y + 190,
listWidth = 139,
listHeight = 151,
onSelectLocator = { name ->
highlightedLocator = name
(tabElement as? PokeEditLocatorTab)?.setHighlightedLocator(name)
}
)
locatorListWidget.setLocators(emptyList())
if (::animationListWidget.isInitialized) removeWidget(animationListWidget)
animationListWidget = PokeEditAnimationListWidget(
pX = x + 26,
pY = y + 190,
listWidth = 139,
listHeight = 151,
isPlaying = { name -> animationTab?.isAnimationPlaying(name) == true },
isLooping = { name -> animationTab?.isAnimationLooping(name) == true },
getDuration = { name -> animationTab?.getAnimationDuration(name) ?: 0F },
getProgress = { name -> animationTab?.getAnimationProgress(name) ?: 0F },
onPlay = { name -> animationTab?.playAnimation(name) },
onPause = { name -> animationTab?.pauseAnimation(name) },
onToggleLoop = { name, enabled -> animationTab?.setAnimationLooping(name, enabled) }
)
animationListWidget.setAnimations(emptyList())
setUpTabs()
setUpActionButtons()
setUpImplementationControls()
setImplementationMode(implementationMode, force = true)
updateFilters(true)
if (implementationMode) {
displayImplementationTab(implementationTabIndex, recreate = true)
} else {
displayTab(tabIndex, recreate = true)
}
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val matrices = context.pose()
renderBackground(context, mouseX, mouseY, delta)
val x = (width - BASE_WIDTH) / 2
val y = (height - BASE_HEIGHT) / 2
blitk(
matrixStack = matrices,
texture = baseBackground,
x = x,
y = y,
width = BASE_WIDTH,
height = BASE_HEIGHT
)
blitk(
matrixStack = matrices,
texture = screenBackground,
x = x,
y = y,
width = BASE_WIDTH,
height = BASE_HEIGHT
)
drawScaledText(
context = context,
text = lang("ui.pokeedit.title"),
x = x + 192,
y = y + 15,
shadow = true
)
super.render(context, mouseX, mouseY, delta)
tabButtons.forEach { it.render(context, mouseX, mouseY, delta) }
if (!implementationMode) {
// Draw the tab arrow above overlay widgets so it stays visible.
blitk(
matrixStack = matrices,
texture = tabSelectArrow,
x = (x + 198 + (25 * tabIndex)) / SCALE,
y = (y + 177) / SCALE,
width = 12,
height = 6,
scale = SCALE
)
}
}
private fun updateFilters(init: Boolean = false) {
val search = searchWidget.value.trim()
filteredSpecies = if (search.isEmpty()) {
allSpecies
} else {
allSpecies.filter { species ->
species.translatedName.string.contains(search, true)
|| species.resourceIdentifier.path.contains(search, true)
}
}
speciesList.setEntries(filteredSpecies)
if (init || draft.species !in filteredSpecies) {
filteredSpecies.firstOrNull()?.let { setSelectedSpecies(it) }
}
}
private fun setSelectedSpecies(species: com.cobblemon.mod.common.pokemon.Species) {
if (draft.species == species) return
draft.species = species
draft.form = species.standardForm
draft.gender = PokeEditDraft.defaultGenderFor(draft.form)
draft.shiny = false
draft.abilityIndex = 0
val aspectOptions = PokeEditAspectsTab.collectAspectOptions(
species,
draft.form,
draft.gender,
draft.shiny
)
draft.forcedAspects.retainAll(aspectOptions.toggles)
draft.flagFeatures.retainAll(aspectOptions.flagKeys)
draft.choiceFeatures.entries.removeIf { (key, choice) ->
val group = aspectOptions.choiceGroups.firstOrNull { it.key == key } ?: return@removeIf true
choice !in group.choices
}
val cosmetic = aspectOptions.cosmeticChoices.firstOrNull { it.itemId.toString() == draft.cosmeticItem }
draft.cosmeticItem = cosmetic?.itemId?.toString()
draft.cosmeticAspects = cosmetic?.aspects?.toSet() ?: emptySet()
if (implementationMode) {
displayImplementationTab(implementationTabIndex, recreate = true)
} else {
displayTab(tabIndex, recreate = true)
}
}
private fun setUpTabs() {
val x = (width - BASE_WIDTH) / 2
val y = (height - BASE_HEIGHT) / 2
tabButtons.clear()
for (i in tabIcons.indices) {
tabButtons.add(
ScaledButton(
x + 197F + (i * 25F),
y + 181.5F,
TAB_ICON_SIZE,
TAB_ICON_SIZE,
resource = tabIcons[i],
clickAction = {
if (i == TAB_IMPLEMENTATION_BUTTON) {
setImplementationMode(true)
} else {
displayTab(i)
}
}
)
)
}
tabButtons.forEach(::addRenderableWidget)
}
private fun setUpActionButtons() {
val x = (width - BASE_WIDTH) / 2
val y = (height - BASE_HEIGHT) / 2
val actionY = y + 200F
val exitX = x + 312F
val spawnX = exitX - 56F
val createX = spawnX - 56F
val implX = x + 26F
if (::createButton.isInitialized) removeWidget(createButton)
createButton = SummaryButton(
buttonX = createX,
buttonY = actionY,
buttonWidth = 54,
buttonHeight = 15,
clickAction = { sendPokeEditAction(PokeEditAction.CREATE) },
text = lang("ui.pokeedit.give"),
resource = actionButtonResource
)
addRenderableWidget(createButton)
if (::spawnButton.isInitialized) removeWidget(spawnButton)
spawnButton = SummaryButton(
buttonX = spawnX,
buttonY = actionY,
buttonWidth = 54,
buttonHeight = 15,
clickAction = { sendPokeEditAction(PokeEditAction.SPAWN) },
text = lang("ui.pokeedit.spawn"),
resource = actionButtonResource
)
addRenderableWidget(spawnButton)
if (::exitButton.isInitialized) removeWidget(exitButton)
exitButton = ExitButton(pX = exitX.toInt(), pY = y + 200) {
Minecraft.getInstance().setScreen(null)
}
addRenderableWidget(exitButton)
if (::implementationButton.isInitialized) removeWidget(implementationButton)
implementationButton = SummaryButton(
buttonX = implX,
buttonY = actionY,
buttonWidth = 54,
buttonHeight = 15,
clickAction = { setImplementationMode(true) },
text = Component.literal("Impl"),
resource = actionButtonResource,
renderRequirement = { !implementationMode },
clickRequirement = { !implementationMode },
largeText = false,
boldText = false,
textScale = 0.7F
)
addRenderableWidget(implementationButton)
}
private fun setUpImplementationControls() {
val x = (width - BASE_WIDTH) / 2
val y = (height - BASE_HEIGHT) / 2
val sideX = (x - 60F).coerceAtLeast(6F)
if (::implementationExitButton.isInitialized) removeWidget(implementationExitButton)
implementationExitButton = SummaryButton(
buttonX = sideX,
buttonY = y + 28F,
buttonWidth = 54,
buttonHeight = 15,
clickAction = { setImplementationMode(false) },
text = Component.literal("Back"),
resource = actionButtonResource,
renderRequirement = { implementationMode },
clickRequirement = { implementationMode },
largeText = false,
boldText = false,
textScale = 0.7F
)
addRenderableWidget(implementationExitButton)
implementationTabButtons.forEach(::removeWidget)
implementationTabButtons.clear()
val baseY = y + 48F
val labels = listOf(
Component.literal("Locators"),
Component.literal("Animation"),
Component.literal("Scale"),
Component.literal("Walk Speed")
)
labels.forEachIndexed { index, label ->
val button = SummaryButton(
buttonX = sideX,
buttonY = baseY + (index * 18F),
buttonWidth = 54,
buttonHeight = 15,
clickAction = { displayImplementationTab(index) },
text = label,
resource = actionButtonResource,
renderRequirement = { implementationMode },
clickRequirement = { implementationMode },
largeText = false,
boldText = false,
textScale = 0.7F
)
implementationTabButtons.add(button)
addRenderableWidget(button)
}
}
private fun displayTab(newIndex: Int, recreate: Boolean = false) {
if (implementationMode) {
return
}
if (tabIndex == newIndex && ::tabElement.isInitialized && !recreate) {
return
}
tabIndex = newIndex
if (::tabElement.isInitialized) removeWidget(tabElement)
val x = (width - BASE_WIDTH) / 2
val y = (height - BASE_HEIGHT) / 2
val tabX = x + 180
val tabY = y + 28
tabElement = when (newIndex) {
TAB_GENERAL -> PokeEditGeneralTab(tabX, tabY, draft) { displayTab(tabIndex, recreate = true) }
TAB_EV_IV -> PokeEditEVIVTab(tabX, tabY, draft)
TAB_MOVES -> PokeEditMovesTab(tabX, tabY, draft)
TAB_ASPECTS -> PokeEditAspectsTab(tabX, tabY, draft)
else -> PokeEditGeneralTab(tabX, tabY, draft) { displayTab(tabIndex, recreate = true) }
}
addRenderableWidget(tabElement)
bringControlsToFront()
}
private fun displayImplementationTab(newIndex: Int, recreate: Boolean = false) {
if (!implementationMode) {
return
}
if (implementationTabIndex == newIndex && ::tabElement.isInitialized && !recreate) {
return
}
implementationTabIndex = newIndex
if (::tabElement.isInitialized) removeWidget(tabElement)
if (::locatorListWidget.isInitialized) removeWidget(locatorListWidget)
if (::animationListWidget.isInitialized) removeWidget(animationListWidget)
animationTab = null
val x = (width - BASE_WIDTH) / 2
val y = (height - BASE_HEIGHT) / 2
val tabX = x + 180
val tabY = y + 28
tabElement = when (newIndex) {
IMPL_TAB_LOCATORS -> PokeEditLocatorTab(
tabX,
tabY,
draft,
highlightedLocator,
::updateLocatorList
)
IMPL_TAB_ANIMATION -> PokeEditAnimationTab(tabX, tabY, draft) { animations ->
animationListWidget.setAnimations(animations)
}
IMPL_TAB_SCALE -> PokeEditImplementationBlankTab(tabX, tabY, draft, Component.literal("Scale"))
IMPL_TAB_WALK_SPEED -> PokeEditWalkSpeedTab(tabX, tabY, draft)
else -> PokeEditLocatorTab(
tabX,
tabY,
draft,
highlightedLocator,
::updateLocatorList
)
}
addRenderableWidget(tabElement)
when (newIndex) {
IMPL_TAB_LOCATORS -> addRenderableWidget(locatorListWidget)
IMPL_TAB_ANIMATION -> {
animationTab = tabElement as? PokeEditAnimationTab
animationListWidget.setAnimations(animationTab?.getAnimations().orEmpty())
addRenderableWidget(animationListWidget)
}
}
bringControlsToFront()
}
private fun setImplementationMode(enabled: Boolean, force: Boolean = false) {
if (!force && implementationMode == enabled) return
implementationMode = enabled
if (implementationMode) {
if (::searchWidget.isInitialized) removeWidget(searchWidget)
if (::speciesList.isInitialized) removeWidget(speciesList)
implementationTabIndex = IMPL_TAB_LOCATORS
displayImplementationTab(implementationTabIndex, recreate = true)
} else {
if (::searchWidget.isInitialized) addRenderableWidget(searchWidget)
if (::speciesList.isInitialized) addRenderableWidget(speciesList)
if (::locatorListWidget.isInitialized) removeWidget(locatorListWidget)
if (::animationListWidget.isInitialized) removeWidget(animationListWidget)
displayTab(tabIndex, recreate = true)
}
tabButtons.forEach { it.isVisible = !implementationMode }
if (!implementationMode) {
searchWidget.isFocused = false
}
bringControlsToFront()
}
private fun bringControlsToFront() {
if (::createButton.isInitialized) {
removeWidget(createButton)
addRenderableWidget(createButton)
}
if (::spawnButton.isInitialized) {
removeWidget(spawnButton)
addRenderableWidget(spawnButton)
}
if (::exitButton.isInitialized) {
removeWidget(exitButton)
addRenderableWidget(exitButton)
}
if (::implementationButton.isInitialized) {
removeWidget(implementationButton)
addRenderableWidget(implementationButton)
}
if (tabButtons.isEmpty()) return
tabButtons.forEach { removeWidget(it) }
tabButtons.forEach(::addRenderableWidget)
}
private fun sendPokeEditAction(action: PokeEditAction) {
val properties = buildDraftProperties()
val moveNames = draft.activeMoves.map { it?.name ?: "" }
val cosmeticItem = draft.cosmeticItem?.trim().orEmpty()
PokeEditActionPacket(action, properties.asString(), moveNames, cosmeticItem).sendToServer()
Minecraft.getInstance().setScreen(null)
}
private fun updateLocatorList(locators: Collection<String>) {
if (::locatorListWidget.isInitialized) {
locatorListWidget.setLocators(locators)
}
}
private fun buildDraftProperties(): PokemonProperties {
val props = PokemonProperties()
props.species = draft.species.resourceIdentifier.toString()
props.form = draft.form.formOnlyShowdownId()
props.shiny = draft.shiny
props.gender = draft.gender
props.level = draft.level
val ot = draft.originalTrainer.trim()
if (ot.isBlank()) {
props.originalTrainerType = OriginalTrainerType.NONE
} else {
props.originalTrainerType = OriginalTrainerType.NPC
props.originalTrainer = ot
}
props.heldItem = draft.heldItem?.takeIf { it.isNotBlank() }
props.ivs = draft.ivs
props.evs = draft.evs
val abilities = draft.form.abilities.map { it.template }
props.ability = abilities.getOrNull(draft.abilityIndex)?.name
val customProperties = mutableListOf<com.cobblemon.mod.common.api.properties.CustomPokemonProperty>()
draft.forcedAspects.mapNotNullTo(customProperties) { aspect -> AspectPropertyType.fromString(aspect) }
draft.flagFeatures.forEach { key ->
val provider = SpeciesFeatures.getFeature(key) as? FlagSpeciesFeatureProvider ?: return@forEach
provider.fromString("true")?.let { customProperties.add(it) }
}
draft.choiceFeatures.forEach { (key, choice) ->
val provider = SpeciesFeatures.getFeature(key) as? ChoiceSpeciesFeatureProvider ?: return@forEach
provider.fromString(choice)?.let { customProperties.add(it) }
}
props.customProperties = customProperties
props.updateAspects()
return props
}
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
if (focused !is EditBox && (minecraft?.options?.keyInventory?.matches(keyCode, scanCode) == true)) {
onClose()
return true
}
return super.keyPressed(keyCode, scanCode, modifiers)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
return try {
super.mouseClicked(mouseX, mouseY, button)
} catch (_: ConcurrentModificationException) {
false
}
}
override fun mouseScrolled(mouseX: Double, mouseY: Double, amount: Double, verticalAmount: Double): Boolean {
if (implementationMode) {
if (implementationTabIndex == IMPL_TAB_LOCATORS && ::locatorListWidget.isInitialized) {
if (locatorListWidget.mouseScrolled(mouseX, mouseY, amount, verticalAmount)) return true
}
if (implementationTabIndex == IMPL_TAB_ANIMATION && ::animationListWidget.isInitialized) {
if (animationListWidget.mouseScrolled(mouseX, mouseY, amount, verticalAmount)) return true
}
}
return super.mouseScrolled(mouseX, mouseY, amount, verticalAmount)
}
}
@@ -0,0 +1,100 @@
/*
* 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.gui.pokeedit.widgets
import com.cobblemon.mod.common.api.abilities.AbilityTemplate
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.pokedex.ScaledButton
import com.cobblemon.mod.common.client.gui.pokedex.widgets.InfoTextScrollWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.client.gui.pokeedit.PokeEditDraft
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
class PokeEditAbilityTab(
pX: Int,
pY: Int,
private val draft: PokeEditDraft
) : SoundlessWidget(pX, pY, 139, 163, lang("ui.pokeedit.abilities")) {
companion object {
private val arrowLeft = cobblemonResource("textures/gui/pokedex/info_arrow_left.png")
private val arrowRight = cobblemonResource("textures/gui/pokedex/info_arrow_right.png")
}
private val description = object : InfoTextScrollWidget(pX, pY + 60) {}
private val leftButton = ScaledButton(
pX + 2.5F,
pY + 30F,
7,
10,
arrowLeft,
clickAction = { switchAbility(false) }
).also { addWidget(it) }
private val rightButton = ScaledButton(
pX + 133F,
pY + 30F,
7,
10,
arrowRight,
clickAction = { switchAbility(true) }
).also { addWidget(it) }
private var abilitiesList: List<AbilityTemplate> = emptyList()
init {
refreshAbilities()
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
refreshAbilities()
if (abilitiesList.isEmpty()) return
val ability = abilitiesList[draft.abilityIndex.coerceIn(0, abilitiesList.lastIndex)]
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = Component.translatable("cobblemon.ui.pokedex.info.ability", ability.displayName.asTranslated()).bold(),
x = x + 9,
y = y + 30,
shadow = true
)
description.setText(listOf(ability.description))
description.render(context, mouseX, mouseY, delta)
leftButton.render(context, mouseX, mouseY, delta)
rightButton.render(context, mouseX, mouseY, delta)
}
private fun refreshAbilities() {
val form = draft.form
abilitiesList = form.abilities.map { it.template }
if (draft.abilityIndex !in abilitiesList.indices) {
draft.abilityIndex = 0
}
}
private fun switchAbility(nextIndex: Boolean) {
if (abilitiesList.isEmpty()) return
val size = abilitiesList.size
draft.abilityIndex = if (nextIndex) {
(draft.abilityIndex + 1) % size
} else {
(draft.abilityIndex - 1 + size) % size
}
}
}
@@ -0,0 +1,362 @@
/*
* 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.gui.pokeedit.widgets
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.ScrollingWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.util.Mth
import java.util.Locale
class PokeEditAnimationListWidget(
val pX: Int,
val pY: Int,
val listWidth: Int,
val listHeight: Int,
private val isPlaying: (String) -> Boolean,
private val isLooping: (String) -> Boolean,
private val getDuration: (String) -> Float,
private val getProgress: (String) -> Float,
private val onPlay: (String) -> Unit,
private val onPause: (String) -> Unit,
private val onToggleLoop: (String, Boolean) -> Unit
) : ScrollingWidget<PokeEditAnimationListWidget.AnimationRow>(
left = pX,
top = pY - listHeight,
width = listWidth,
height = listHeight,
slotHeight = ROW_HEIGHT
) {
companion object {
private const val ROW_HEIGHT = 12
private const val TEXT_SCALE = 0.5F
private const val BORDER_COLOR = 0xFF1B1B1B.toInt()
private const val FILL_COLOR = 0xFF3B3B3B.toInt()
private const val TEXT_COLOR = 0xFFFCFCFC.toInt()
private const val BUTTON_WIDTH = 12
private const val BUTTON_HEIGHT = 9
private const val BUTTON_GAP = 3
private const val BUTTON_PAD_RIGHT = 4
private const val BUTTON_PAD_TOP = 1
private const val BUTTON_ACTIVE = 0xFF6BD46B.toInt()
private const val BUTTON_INACTIVE = 0xFF5B5B5B.toInt()
private const val BUTTON_BORDER = 0xFF1B1B1B.toInt()
private const val LOOP_BOX_SIZE = 8
private const val LOOP_GAP = 3
private const val LOOP_LABEL_SCALE = 0.45F
private const val PROGRESS_HEIGHT = 2
private const val PROGRESS_BG = 0xFF2C2C2C.toInt()
private const val PROGRESS_FG = 0xFF8AD5FF.toInt()
private const val DURATION_SCALE = 0.45F
private const val DURATION_WIDTH = 28
}
private val listTop = pY - listHeight
private val listBottom = pY
private var draggingScrollbar = false
fun setAnimations(animations: Collection<String>) {
clearEntries()
animations.sorted().forEach { addEntry(AnimationRow(it)) }
scrollAmount = 0.0
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
context.fill(pX - 1, listTop - 1, pX + listWidth + 1, listBottom + 1, BORDER_COLOR)
context.fill(pX, listTop, pX + listWidth, listBottom, FILL_COLOR)
focused = if (mouseX in pX..(pX + listWidth) && mouseY in listTop..listBottom) {
getEntryAtPosition(mouseX.toDouble(), mouseY.toDouble())
} else {
null
}
context.enableScissor(pX, listTop, pX + listWidth, listBottom)
renderListItems(context, mouseX, mouseY, delta)
context.disableScissor()
if (maxScroll > 0) {
renderScrollbar(context, mouseX, mouseY, delta)
}
}
override fun renderListItems(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val rowX = rowLeft
for (index in 0 until itemCount) {
val rowY = getRowTop(index)
val rowBottom = getRowBottom(index)
if (rowBottom >= listTop && rowY <= listBottom) {
val entry = getEntry(index)
if (entry is AnimationRow) {
entry.isPlaying = isPlaying(entry.name)
entry.isLooping = isLooping(entry.name)
entry.durationSeconds = getDuration(entry.name)
entry.progress = getProgress(entry.name)
}
renderItem(context, mouseX, mouseY, delta, index, rowX, rowY, rowWidth, itemHeight)
}
}
}
override fun mouseScrolled(mouseX: Double, mouseY: Double, amount: Double, verticalAmount: Double): Boolean {
if (mouseX !in pX.toDouble()..(pX + listWidth).toDouble()
|| mouseY !in listTop.toDouble()..listBottom.toDouble()
) {
return false
}
val maxScroll = (itemCount * itemHeight - listHeight).coerceAtLeast(0)
if (maxScroll <= 0) return true
val step = if (verticalAmount != 0.0) verticalAmount else amount
scrollAmount = Mth.clamp(scrollAmount - step * itemHeight, 0.0, maxScroll.toDouble())
return true
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (mouseX !in pX.toDouble()..(pX + listWidth).toDouble()
|| mouseY !in listTop.toDouble()..listBottom.toDouble()
|| button != 0
) {
return false
}
if (isOverScrollbar(mouseX, mouseY) && maxScroll > 0) {
draggingScrollbar = true
return true
}
val localY = mouseY.toInt() - listTop + scrollAmount.toInt()
if (localY < 0) return false
val index = localY / itemHeight
if (index !in 0 until itemCount) return false
val entry = getEntry(index)
if (entry is AnimationRow) {
val rowX = rowLeft
val rowY = getRowTop(index)
val (playX, pauseX, loopX, buttonY, durationX) = buttonPositions(rowX, rowY)
val inPlay = mouseX.toInt() in playX..(playX + BUTTON_WIDTH)
&& mouseY.toInt() in buttonY..(buttonY + BUTTON_HEIGHT)
val inPause = mouseX.toInt() in pauseX..(pauseX + BUTTON_WIDTH)
&& mouseY.toInt() in buttonY..(buttonY + BUTTON_HEIGHT)
val inLoop = mouseX.toInt() in loopX..(loopX + LOOP_BOX_SIZE)
&& mouseY.toInt() in buttonY..(buttonY + LOOP_BOX_SIZE)
if (inPlay) {
onPlay(entry.name)
return true
}
if (inPause) {
onPause(entry.name)
return true
}
if (inLoop) {
val newValue = !isLooping(entry.name)
onToggleLoop(entry.name, newValue)
return true
}
}
return false
}
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (button == 0 && draggingScrollbar) {
draggingScrollbar = false
return true
}
return super.mouseReleased(mouseX, mouseY, button)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
if (!draggingScrollbar || maxScroll <= 0) {
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
val barHeight = listBottom - listTop
var thumbHeight = ((barHeight * barHeight).toFloat() / maxPosition.toFloat()).toInt()
thumbHeight = Mth.clamp(thumbHeight, 16, barHeight - 4)
val trackHeight = barHeight - thumbHeight
if (trackHeight <= 0) return true
val clampedY = Mth.clamp(mouseY.toInt() - listTop - thumbHeight / 2, 0, trackHeight)
scrollAmount = (clampedY.toDouble() * maxScroll.toDouble()) / trackHeight.toDouble()
return true
}
override fun isMouseOver(mouseX: Double, mouseY: Double): Boolean {
return mouseX in pX.toDouble()..(pX + listWidth).toDouble()
&& mouseY in listTop.toDouble()..listBottom.toDouble()
}
override fun renderScrollbar(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val xLeft = this.scrollbarPosition
val xRight = xLeft + 3
val yStart = listTop
val barHeight = listBottom - listTop
var yBottom = ((barHeight * barHeight).toFloat() / this.maxPosition.toFloat()).toInt()
yBottom = Mth.clamp(yBottom, 16, barHeight - 4)
var yTop = scrollAmount.toInt() * (barHeight - yBottom) / this.maxScroll + yStart
if (yTop < yStart) yTop = yStart
context.fill(xLeft + 1, yStart, xRight - 1, listBottom, 0xFF7AEEF1.toInt())
context.fill(xLeft, yTop, xRight, yTop + yBottom, 0xFF3A96B6.toInt())
}
override fun getRowTop(index: Int): Int {
return listTop - scrollAmount.toInt() + (index * itemHeight)
}
override fun getRowBottom(index: Int): Int {
return getRowTop(index) + itemHeight
}
override fun getBottom(): Int {
return listBottom
}
private fun isOverScrollbar(mouseX: Double, mouseY: Double): Boolean {
val xLeft = scrollbarPosition
val xRight = xLeft + 3
return mouseX >= xLeft && mouseX <= xRight && mouseY >= listTop && mouseY <= listBottom
}
private fun buttonPositions(rowX: Int, rowY: Int): List<Int> {
val loopX = rowX + listWidth - BUTTON_PAD_RIGHT - LOOP_BOX_SIZE
val pauseX = loopX - LOOP_GAP - BUTTON_WIDTH
val playX = pauseX - BUTTON_GAP - BUTTON_WIDTH
val buttonY = rowY + BUTTON_PAD_TOP
val durationX = playX - BUTTON_GAP - DURATION_WIDTH
return listOf(playX, pauseX, loopX, buttonY, durationX)
}
class AnimationRow(val name: String) : Slot<AnimationRow>() {
var isPlaying: Boolean = false
var isLooping: Boolean = false
var durationSeconds: Float = 0F
var progress: Float = 0F
override fun render(
context: GuiGraphics,
index: Int,
y: Int,
x: Int,
entryWidth: Int,
entryHeight: Int,
mouseX: Int,
mouseY: Int,
hovered: Boolean,
tickDelta: Float
) {
val bg = if (hovered) 0xFF2F2F2F.toInt() else null
if (bg != null) {
context.fill(x + 1, y, x + entryWidth - 1, y + ROW_HEIGHT, bg)
}
val loopX = x + entryWidth - BUTTON_PAD_RIGHT - LOOP_BOX_SIZE
val pauseX = loopX - LOOP_GAP - BUTTON_WIDTH
val playX = pauseX - BUTTON_GAP - BUTTON_WIDTH
val durationX = playX - BUTTON_GAP - DURATION_WIDTH
val buttonY = y + BUTTON_PAD_TOP
drawButton(context, playX, buttonY, BUTTON_WIDTH, BUTTON_HEIGHT, !isPlaying)
drawPlayIcon(context, playX, buttonY, BUTTON_WIDTH, BUTTON_HEIGHT)
drawButton(context, pauseX, buttonY, BUTTON_WIDTH, BUTTON_HEIGHT, isPlaying)
drawPauseIcon(context, pauseX, buttonY, BUTTON_WIDTH, BUTTON_HEIGHT)
drawLoopBox(context, loopX, buttonY, isLooping)
drawDuration(context, durationX, buttonY, durationSeconds)
drawScaledText(
context = context,
text = name.text(),
x = x + 3,
y = y + 2,
scale = TEXT_SCALE,
colour = TEXT_COLOR,
shadow = false
)
drawProgressBar(context, x + 2, y + ROW_HEIGHT - PROGRESS_HEIGHT, entryWidth - 4, progress)
}
override fun getNarration(): Component {
return Component.literal(name)
}
private fun drawButton(
context: GuiGraphics,
x: Int,
y: Int,
width: Int,
height: Int,
active: Boolean
) {
val fill = if (active) BUTTON_ACTIVE else BUTTON_INACTIVE
context.fill(x - 1, y - 1, x + width + 1, y + height + 1, BUTTON_BORDER)
context.fill(x, y, x + width, y + height, fill)
}
private fun drawLoopBox(context: GuiGraphics, x: Int, y: Int, enabled: Boolean) {
val fill = if (enabled) BUTTON_ACTIVE else BUTTON_INACTIVE
context.fill(x - 1, y - 1, x + LOOP_BOX_SIZE + 1, y + LOOP_BOX_SIZE + 1, BUTTON_BORDER)
context.fill(x, y, x + LOOP_BOX_SIZE, y + LOOP_BOX_SIZE, fill)
drawLoopIcon(context, x, y, LOOP_BOX_SIZE)
}
private fun drawPlayIcon(context: GuiGraphics, x: Int, y: Int, width: Int, height: Int) {
val iconWidth = 6
val iconHeight = 7
val startX = x + (width - iconWidth) / 2
val startY = y + (height - iconHeight) / 2
for (i in 0 until iconWidth) {
val top = startY + i / 2
val bottom = startY + iconHeight - i / 2
context.fill(startX + i, top, startX + i + 1, bottom, TEXT_COLOR)
}
}
private fun drawPauseIcon(context: GuiGraphics, x: Int, y: Int, width: Int, height: Int) {
val barWidth = 2
val barHeight = 7
val gap = 2
val startX = x + (width - (barWidth * 2 + gap)) / 2
val startY = y + (height - barHeight) / 2
context.fill(startX, startY, startX + barWidth, startY + barHeight, TEXT_COLOR)
context.fill(startX + barWidth + gap, startY, startX + barWidth * 2 + gap, startY + barHeight, TEXT_COLOR)
}
private fun drawLoopIcon(context: GuiGraphics, x: Int, y: Int, size: Int) {
val startX = x + 1
val startY = y + 1
val endX = x + size - 1
val endY = y + size - 1
context.fill(startX, startY, endX, startY + 1, TEXT_COLOR)
context.fill(startX, endY - 1, endX, endY, TEXT_COLOR)
context.fill(startX, startY, startX + 1, endY, TEXT_COLOR)
context.fill(endX - 1, startY, endX, endY, TEXT_COLOR)
context.fill(endX - 2, startY, endX, startY + 2, TEXT_COLOR)
}
private fun drawDuration(context: GuiGraphics, x: Int, y: Int, duration: Float) {
val text = String.format(Locale.US, "%.2fs", duration.coerceAtLeast(0F))
drawScaledText(
context = context,
text = text.text(),
x = x + DURATION_WIDTH / 2,
y = y + 2,
scale = DURATION_SCALE,
colour = TEXT_COLOR,
maxCharacterWidth = DURATION_WIDTH,
centered = true,
shadow = false
)
}
private fun drawProgressBar(context: GuiGraphics, x: Int, y: Int, width: Int, progress: Float) {
val clamped = Mth.clamp(progress, 0F, 1F)
context.fill(x, y, x + width, y + PROGRESS_HEIGHT, PROGRESS_BG)
context.fill(x, y, x + (width * clamped).toInt(), y + PROGRESS_HEIGHT, PROGRESS_FG)
}
}
}
@@ -0,0 +1,496 @@
/*
* 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.gui.pokeedit.widgets
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
import com.cobblemon.mod.common.client.gui.pokeedit.PokeEditDraft
import com.cobblemon.mod.common.client.gui.pokeedit.widgets.PokeEditAspectsTab.Companion.choiceAspectId
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.item.HeldItemRenderer
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PrimaryAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.pokemon.aspects.SHINY_ASPECT
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import com.cobblemon.mod.common.util.toHex
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.platform.Lighting
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.AbstractWidget
import net.minecraft.client.renderer.LightTexture
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.client.Minecraft
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
import org.joml.Quaternionf
import org.joml.Vector3f
class PokeEditAnimationTab(
pX: Int,
pY: Int,
private val draft: PokeEditDraft,
private val onAnimationsChanged: (List<String>) -> Unit
) : SoundlessWidget(pX, pY, HALF_OVERLAY_WIDTH, HALF_OVERLAY_HEIGHT, Component.literal("Animation")) {
companion object {
private val backgroundOverlay = cobblemonResource("textures/gui/pokedex/pokedex_screen_info_overlay.png")
private val pokeBallOverlay = cobblemonResource("textures/gui/pokedex/pokedex_screen_poke_ball.png")
private val platformBase = cobblemonResource("textures/gui/pokedex/platform_base.png")
private val platformShadow = cobblemonResource("textures/gui/pokedex/platform_shadow.png")
private const val POKEBALL_OVERLAY_Y = 25
private const val PLATFORM_Y = 69
private const val SHADOW_X = 25
private const val SHADOW_Y = 76
private const val VIEW_SCALE = 1.2F
private const val VIEW_SHIFT_X = 0F
private const val VIEW_SHIFT_Y = 36F
private const val MODEL_VIEW_SCALE = 2.4F
private const val MODEL_DRAW_SCALE = 24F
private const val MODEL_OFFSET_Y = 20.0
}
private val state = FloatingState().also { state ->
state.runtime.environment.query.addFunction("is_holding_item") {
val held = currentHeldStack()
DoubleValue(!held.isEmpty && !held.`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS) && !held.`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS))
}
state.runtime.environment.query.addFunction("is_wearing_hat") {
DoubleValue(currentHeldStack().`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS))
}
state.runtime.environment.query.addFunction("is_wearing_face") {
DoubleValue(currentHeldStack().`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS))
}
}
private val heldItemRenderer = HeldItemRenderer()
private var renderablePokemon: RenderablePokemon? = null
private var rotationY = 30F
private var poseList: Array<PoseType> = arrayOf(PoseType.PROFILE, PoseType.STAND, PoseType.WALK, PoseType.SLEEP)
private var selectedPoseIndex: Int = 0
private var oldDragPosX = 0.0
private var canDragRender = false
private var isDraggingRender = false
private var availableAnimations: List<String> = emptyList()
private val activeAnimationsByName = mutableMapOf<String, ActiveAnimation>()
private val loopingAnimations = mutableMapOf<String, Boolean>()
private val animationStartTimes = mutableMapOf<String, Float>()
private val animationDurations = mutableMapOf<String, Float>()
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
context.enableScissor(x, y, x + HALF_OVERLAY_WIDTH, y + HALF_OVERLAY_HEIGHT)
val viewWidth = HALF_OVERLAY_WIDTH * VIEW_SCALE
val viewHeight = HALF_OVERLAY_HEIGHT * VIEW_SCALE
val viewX = x + (HALF_OVERLAY_WIDTH - viewWidth) / 2F + VIEW_SHIFT_X
val viewY = y + (HALF_OVERLAY_HEIGHT - viewHeight) / 2F + VIEW_SHIFT_Y
val scaledBaseX = viewX / VIEW_SCALE
val scaledBaseY = viewY / VIEW_SCALE
blitk(
matrixStack = context.pose(),
texture = backgroundOverlay,
x = scaledBaseX,
y = scaledBaseY,
width = HALF_OVERLAY_WIDTH,
height = HALF_OVERLAY_HEIGHT,
scale = VIEW_SCALE
)
blitk(
matrixStack = context.pose(),
texture = pokeBallOverlay,
x = scaledBaseX + 15,
y = scaledBaseY + POKEBALL_OVERLAY_Y,
width = 109,
height = 68,
vOffset = 0,
textureHeight = 1744,
scale = VIEW_SCALE
)
blitk(
matrixStack = context.pose(),
texture = platformBase,
x = scaledBaseX + 13,
y = scaledBaseY + PLATFORM_Y,
width = 113,
height = 24,
textureHeight = 30,
scale = VIEW_SCALE
)
blitk(
matrixStack = context.pose(),
texture = platformShadow,
x = scaledBaseX + SHADOW_X,
y = scaledBaseY + SHADOW_Y,
width = 90,
height = 20,
scale = VIEW_SCALE
)
val fillTop = (viewY + 114F * VIEW_SCALE).toInt()
val fillBottom = (viewY + (HALF_OVERLAY_HEIGHT - 6) * VIEW_SCALE).toInt()
context.fill(viewX.toInt(), fillTop, (viewX + viewWidth).toInt(), fillBottom, 0xFF7AEEF1.toInt())
context.disableScissor()
renderPokemon(context, delta)
refreshAnimationList()
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (isWithinViewport(mouseX, mouseY)) {
canDragRender = true
isDraggingRender = true
oldDragPosX = mouseX
return true
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun mouseReleased(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (canDragRender) {
canDragRender = false
}
if (isDraggingRender) {
isDraggingRender = false
}
return super.mouseReleased(pMouseX, pMouseY, pButton)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
val handledDrag = isDraggingRender && canDragRender
if (handledDrag) {
val dragOffsetY = (oldDragPosX - mouseX).toFloat()
rotationY = (((rotationY + dragOffsetY) % 360 + 360) % 360)
}
oldDragPosX = mouseX
return handledDrag || super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
fun getAnimations(): List<String> = availableAnimations
fun isAnimationPlaying(name: String): Boolean = activeAnimationsByName.containsKey(name)
fun isAnimationLooping(name: String): Boolean = loopingAnimations[name] == true
fun getAnimationDuration(name: String): Float {
return animationDurations[name] ?: resolveDuration(name).also { animationDurations[name] = it }
}
fun getAnimationProgress(name: String): Float {
val duration = getAnimationDuration(name)
if (duration <= 0F) return 0F
val start = animationStartTimes[name] ?: return 0F
val elapsed = state.animationSeconds - start
val raw = if (isAnimationLooping(name)) {
(elapsed % duration) / duration
} else {
elapsed / duration
}
return raw.coerceIn(0F, 1F)
}
fun playAnimation(name: String) {
if (activeAnimationsByName.containsKey(name)) return
val model = VaryingModelRepository.getPoser(draft.species.resourceIdentifier, state)
val animation = model.getAnimation(state, name, state.runtime)
val active = if (animation != null) {
if (animation is PrimaryAnimation) animation.animation else animation
} else {
val pose = resolvePoseAnimation(model, name) ?: return
PoseActiveAnimation(pose)
}
state.addActiveAnimation(active)
activeAnimationsByName[name] = active
animationStartTimes[name] = state.animationSeconds
animationDurations[name] = active.duration
}
fun pauseAnimation(name: String) {
val active = activeAnimationsByName.remove(name) ?: return
state.activeAnimations.remove(active)
animationStartTimes.remove(name)
}
fun setAnimationLooping(name: String, enabled: Boolean) {
loopingAnimations[name] = enabled
}
private fun refreshAnimationList() {
val model = VaryingModelRepository.getPoser(draft.species.resourceIdentifier, state)
val names = mutableSetOf<String>()
names.addAll(model.animations.keys)
model.poses.values.forEach { pose ->
names.addAll(pose.namedAnimations.keys)
names.add(pose.poseName)
}
val sorted = names.sorted()
if (sorted != availableAnimations) {
availableAnimations = sorted
loopingAnimations.keys.retainAll(sorted.toSet())
animationDurations.keys.retainAll(sorted.toSet())
animationStartTimes.keys.retainAll(sorted.toSet())
onAnimationsChanged(sorted)
}
}
private fun renderPokemon(context: GuiGraphics, delta: Float) {
buildRenderable()?.let { renderable ->
state.currentAspects = renderable.aspects
context.enableScissor(
x + 1,
y + 1,
x + HALF_OVERLAY_WIDTH - 1,
y + HALF_OVERLAY_HEIGHT - 1
)
val matrices = context.pose()
matrices.pushPose()
matrices.translate(
x.toDouble() + (HALF_OVERLAY_WIDTH.toDouble()) / 2,
y.toDouble() + MODEL_OFFSET_Y,
1000.0
)
matrices.scale(MODEL_VIEW_SCALE, MODEL_VIEW_SCALE, MODEL_VIEW_SCALE)
renderAnimationModel(context, renderable, delta)
if (!renderable.heldItem.isEmpty) {
heldItemRenderer.renderOnModel(
renderable.heldItem,
state,
matrices,
context.bufferSource(),
light = 0xF000F0,
frontLight = true
)
}
matrices.popPose()
context.disableScissor()
}
}
private fun renderAnimationModel(context: GuiGraphics, renderable: RenderablePokemon, delta: Float) {
val speciesId = renderable.species.resourceIdentifier
val model = VaryingModelRepository.getPoser(speciesId, state)
val texture = VaryingModelRepository.getTexture(speciesId, state)
val renderContext = RenderContext()
model.context = renderContext
VaryingModelRepository.getTextureNoSubstitute(speciesId, state).let { renderContext.put(RenderContext.TEXTURE, it) }
val baseScale = PokemonSpecies.getByIdentifier(speciesId)!!.getForm(state.currentAspects).baseScale
renderContext.put(RenderContext.SCALE, baseScale)
renderContext.put(RenderContext.SPECIES, speciesId)
renderContext.put(RenderContext.ASPECTS, state.currentAspects)
renderContext.put(RenderContext.RENDER_STATE, RenderContext.RenderState.PROFILE)
renderContext.put(RenderContext.POSABLE_STATE, state)
renderContext.put(RenderContext.DO_QUIRKS, false)
state.currentModel = model
if (state.currentPose == null) {
state.currentPose = model.getPose(PoseType.PROFILE)?.poseName
}
state.updatePartialTicks(delta)
RenderSystem.applyModelViewMatrix()
val matrices = context.pose()
val rotationVector = Vector3f(13F, rotationY, 0F)
val rotation = Quaternionf().fromEulerXYZDegrees(rotationVector)
matrices.pushPose()
matrices.scale(MODEL_DRAW_SCALE, MODEL_DRAW_SCALE, -MODEL_DRAW_SCALE)
matrices.translate(
model.profileTranslation.x,
model.profileTranslation.y + 1.5 * model.profileScale,
model.profileTranslation.z - 4.0
)
matrices.scale(model.profileScale, model.profileScale, 1 / model.profileScale)
matrices.mulPose(rotation)
model.setDefault()
state.preRender()
state.renderMarkers.clear()
runActiveAnimations(renderContext, model)
model.updateLocators(null, state)
Lighting.setupForEntityInInventory()
val bufferSource = Minecraft.getInstance().renderBuffers().bufferSource()
val buffer = bufferSource.getBuffer(RenderType.entityCutout(texture))
val light1 = Vector3f(-1F, 1F, 1.0F)
val light2 = Vector3f(1.3F, -1F, 1.0F)
RenderSystem.setShaderLights(light1, light2)
val packedLight = LightTexture.pack(11, 7)
val colour = toHex(1F, 1F, 1F, 1F)
model.withLayerContext(bufferSource, state, VaryingModelRepository.getLayers(speciesId, state)) {
model.render(renderContext, matrices, buffer, packedLight, OverlayTexture.NO_OVERLAY, colour)
bufferSource.endBatch()
}
model.setDefault()
Lighting.setupFor3DItems()
matrices.popPose()
}
private fun runActiveAnimations(context: RenderContext, model: com.cobblemon.mod.common.client.render.models.blockbench.PosableModel) {
val toRestart = mutableListOf<String>()
val iterator = activeAnimationsByName.entries.iterator()
while (iterator.hasNext()) {
val entry = iterator.next()
val animation = entry.value
val stillRunning = animation.run(
context,
model,
state,
0F,
0F,
state.animationSeconds,
0F,
0F,
1F
)
if (!stillRunning) {
state.activeAnimations.remove(animation)
iterator.remove()
animationStartTimes.remove(entry.key)
if (loopingAnimations[entry.key] == true) {
toRestart.add(entry.key)
}
}
}
if (toRestart.isNotEmpty()) {
toRestart.forEach { name ->
val animation = model.getAnimation(state, name, state.runtime)
val active = if (animation != null) {
if (animation is PrimaryAnimation) animation.animation else animation
} else {
val pose = resolvePoseAnimation(model, name) ?: return@forEach
PoseActiveAnimation(pose)
}
state.addActiveAnimation(active)
activeAnimationsByName[name] = active
animationStartTimes[name] = state.animationSeconds
animationDurations[name] = active.duration
}
}
}
private fun resolvePoseAnimation(
model: com.cobblemon.mod.common.client.render.models.blockbench.PosableModel,
name: String
): Pose? {
return model.poses[name] ?: model.poses.values.firstOrNull { it.poseName.equals(name, ignoreCase = true) }
}
private fun resolveDuration(name: String): Float {
val model = VaryingModelRepository.getPoser(draft.species.resourceIdentifier, state)
val animation = model.getAnimation(state, name, state.runtime)
val active = if (animation != null) {
if (animation is PrimaryAnimation) animation.animation else animation
} else {
return 0F
}
return active.duration
}
private class PoseActiveAnimation(private val pose: Pose) : ActiveAnimation {
override val isTransition: Boolean = false
override val duration: Float = 0F
override val enduresPrimaryAnimations: Boolean = true
override fun run(
context: RenderContext,
model: com.cobblemon.mod.common.client.render.models.blockbench.PosableModel,
state: com.cobblemon.mod.common.client.render.models.blockbench.PosableState,
limbSwing: Float,
limbSwingAmount: Float,
ageInTicks: Float,
headYaw: Float,
headPitch: Float,
intensity: Float
): Boolean {
if (!pose.isSuitable(state)) return true
pose.apply(context, model, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch)
return true
}
}
private fun buildRenderable(): RenderablePokemon? {
val species = draft.species
val form = draft.form
val aspects = mutableSetOf<String>()
if (draft.shiny) aspects.add(SHINY_ASPECT.aspect)
aspects.addAll(form.aspects)
aspects.addAll(draft.forcedAspects)
aspects.addAll(draft.flagFeatures)
aspects.addAll(draft.cosmeticAspects)
draft.choiceFeatures.forEach { (key, choice) ->
choiceAspectId(key, choice)?.let { aspects.add(it) }
}
when (draft.gender) {
Gender.MALE -> aspects.add("male")
Gender.FEMALE -> aspects.add("female")
Gender.GENDERLESS -> aspects.add("genderless")
}
val heldStack = draft.heldItem
?.let { ResourceLocation.tryParse(it) }
?.let { id -> BuiltInRegistries.ITEM.getOptional(id).orElse(null) }
?.let { item -> ItemStack(item) }
?: ItemStack.EMPTY
val prev = renderablePokemon
if (prev == null || prev.species != species || prev.aspects != aspects) {
activeAnimationsByName.clear()
state.activeAnimations.clear()
}
renderablePokemon = RenderablePokemon(species, aspects, heldStack).also { recalculatePoses(it) }
return renderablePokemon
}
private fun recalculatePoses(renderablePokemon: RenderablePokemon) {
val poseState = FloatingState().also { it.currentAspects = renderablePokemon.aspects }
val poser = VaryingModelRepository.getPoser(renderablePokemon.species.resourceIdentifier, poseState)
poseList = poser.poses
.map { it.value.poseTypes.minBy { it.ordinal } }
.toSet()
.filterNot { it in PoseType.SHOULDER_POSES }
.sortedBy { it.ordinal }
.toTypedArray()
if (selectedPoseIndex !in poseList.indices) {
selectedPoseIndex = 0
}
}
private fun currentHeldStack(): ItemStack {
return renderablePokemon?.heldItem ?: ItemStack.EMPTY
}
private fun isWithinViewport(mouseX: Double, mouseY: Double): Boolean =
mouseX.toInt() in x..(x + HALF_OVERLAY_WIDTH)
&& mouseY.toInt() in y..(y + HALF_OVERLAY_HEIGHT)
&& children.none { child ->
child is AbstractWidget && child.visible && child.isMouseOver(mouseX, mouseY)
}
}
@@ -0,0 +1,620 @@
/*
* 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.gui.pokeedit.widgets
import com.cobblemon.mod.common.CobblemonCosmeticItems
import com.cobblemon.mod.common.api.conditional.RegistryLikeIdentifierCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeTagCondition
import com.cobblemon.mod.common.api.pokemon.feature.ChoiceSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.FlagSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.ScrollingWidget
import com.cobblemon.mod.common.client.gui.pokeedit.PokeEditDraft
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.substitute
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.language.I18n
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.tags.TagKey
import net.minecraft.util.Mth
class PokeEditAspectsTab(
pX: Int,
pY: Int,
private val draft: PokeEditDraft
) : SoundlessWidget(pX, pY, WIDTH, HEIGHT, lang("ui.aspects")) {
companion object {
private const val WIDTH = 139
private const val HEIGHT = 163
private const val SLOT_HEIGHT = 16
private const val SCALE = 0.7F
private const val TOP_MARGIN = 10
private const val BOTTOM_MARGIN = 10
private const val COLUMN_GAP = 6
data class ChoiceGroup(
val key: String,
val choices: List<String>,
val aspectFormat: String
)
data class CosmeticChoice(
val itemId: ResourceLocation,
val aspects: List<String>,
val label: MutableComponent
)
data class AspectOptions(
val toggles: Set<String>,
val choiceGroups: List<ChoiceGroup>,
val flagKeys: Set<String>,
val cosmeticChoices: List<CosmeticChoice>
)
fun collectAspectOptions(
species: Species,
form: FormData? = null,
gender: Gender? = null,
shiny: Boolean? = null
): AspectOptions {
val toggles = mutableSetOf<String>()
val choiceGroups = mutableListOf<ChoiceGroup>()
val flagKeys = mutableSetOf<String>()
val forms = if (species.forms.isEmpty()) listOf(species.standardForm) else species.forms
forms.forEach { form -> toggles.addAll(form.aspects) }
SpeciesFeatures.getFeaturesFor(species).forEach { provider ->
when (provider) {
is ChoiceSpeciesFeatureProvider -> {
if (provider.isAspect && provider.keys.isNotEmpty() && provider.choices.isNotEmpty()) {
choiceGroups.add(ChoiceGroup(provider.keys.first(), provider.choices, provider.aspectFormat))
}
}
is FlagSpeciesFeatureProvider -> {
if (provider.isAspect && provider.keys.isNotEmpty()) {
flagKeys.add(provider.keys.first())
}
}
}
}
toggles.removeAll(flagKeys)
val filteredChoiceGroups = choiceGroups
.filterNot { it.key in setOf("cosmetic", "cosmetic_item", "cosmeticitem") }
.sortedBy { it.key }
return AspectOptions(
toggles = toggles.filter { it.isNotBlank() && it != "cosmetic" && it != "cosmetic_item" && it != "cosmeticitem" }.toSet(),
choiceGroups = filteredChoiceGroups,
flagKeys = flagKeys.filter { it.isNotBlank() }.toSet(),
cosmeticChoices = collectCosmeticChoices(species, form, gender, shiny)
)
}
fun collectCosmeticChoices(
species: Species,
form: FormData?,
gender: Gender?,
shiny: Boolean?
): List<CosmeticChoice> {
val pokemon = Pokemon().also {
it.isClient = true
it.species = species
it.form = form ?: species.standardForm
if (gender != null) it.gender = gender
if (shiny != null) it.shiny = shiny
}
val registry = BuiltInRegistries.ITEM
val choices = linkedMapOf<ResourceLocation, CosmeticChoice>()
CobblemonCosmeticItems.findValidForPokemon(pokemon).forEach { assignment ->
assignment.cosmeticItems.forEach { cosmetic ->
val aspects = cosmetic.aspects.filter { it.isNotBlank() }
when (val condition = cosmetic.consumedItem) {
is RegistryLikeIdentifierCondition<*> -> {
val id = condition.identifier
val label = registry.getOptional(id)
.map { it.defaultInstance.hoverName.copy() }
.orElseGet { Component.literal(id.toString()) }
choices.putIfAbsent(id, CosmeticChoice(id, aspects, label))
}
is RegistryLikeTagCondition<*> -> {
@Suppress("UNCHECKED_CAST")
val tag = condition.tag as? TagKey<net.minecraft.world.item.Item> ?: return@forEach
val tagItems = registry.getTag(tag).orElse(null) ?: return@forEach
tagItems.forEach { holder ->
val item = holder.value()
val id = registry.getKey(item) ?: return@forEach
val label = item.defaultInstance.hoverName.copy()
choices.putIfAbsent(id, CosmeticChoice(id, aspects, label))
}
}
}
}
}
return choices.values.sortedBy { it.label.string.lowercase() }
}
fun choiceAspectId(key: String, choice: String): String? {
val provider = SpeciesFeatures.getFeature(key) as? ChoiceSpeciesFeatureProvider ?: return null
return provider.aspectFormat.substitute("choice", choice)
}
}
private sealed class AspectItem {
data class Group(val group: ChoiceGroup) : AspectItem()
data class Toggle(val id: String, val source: ToggleSource) : AspectItem()
data class Cosmetic(val choices: List<CosmeticChoice>) : AspectItem()
}
private sealed class OptionItem {
data class Choice(val group: ChoiceGroup, val choice: String) : OptionItem()
data class ClearChoice(val group: ChoiceGroup) : OptionItem()
data class Toggle(val aspect: AspectItem.Toggle, val enabled: Boolean) : OptionItem()
data class CosmeticChoice(val choice: PokeEditAspectsTab.Companion.CosmeticChoice) : OptionItem()
object ClearCosmetic : OptionItem()
}
private enum class ToggleSource {
FORCED_ASPECT,
FLAG_FEATURE
}
private val listX = x + 4
private val listBottom = y + HEIGHT - BOTTOM_MARGIN
private val listAreaWidth = WIDTH - 8
private val listTop = y + TOP_MARGIN
private val listHeight = listBottom - listTop
private val leftWidth = (listAreaWidth - COLUMN_GAP) / 2
private val rightWidth = listAreaWidth - leftWidth - COLUMN_GAP
private val leftX = listX
private val rightX = listX + leftWidth + COLUMN_GAP
private val aspectList = AspectList(
leftX,
listTop,
leftWidth,
listHeight
).also { addWidget(it) }
private val optionList = OptionList(
rightX,
listTop,
rightWidth,
listHeight
).also { addWidget(it) }
private var aspectOptions: AspectOptions = collectAspectOptions(
draft.species,
draft.form,
draft.gender,
draft.shiny
)
private var selectedAspect: AspectItem? = null
private var lastSpeciesId: ResourceLocation? = null
init {
rebuildAspectEntries()
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val currentSpeciesId = draft.species.resourceIdentifier
if (lastSpeciesId != currentSpeciesId) {
refreshAspects()
}
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.aspects").bold(),
x = x + (WIDTH / 2.0),
y = y + 2,
scale = SCALE,
centered = true
)
context.fill(
leftX,
listTop,
leftX + leftWidth,
listBottom,
0x22000000
)
context.fill(
rightX,
listTop,
rightX + rightWidth,
listBottom,
0x22000000
)
aspectList.render(context, mouseX, mouseY, delta)
optionList.render(context, mouseX, mouseY, delta)
}
private fun refreshAspects() {
aspectOptions = collectAspectOptions(
draft.species,
draft.form,
draft.gender,
draft.shiny
)
val cosmetic = aspectOptions.cosmeticChoices.firstOrNull { it.itemId.toString() == draft.cosmeticItem }
draft.cosmeticItem = cosmetic?.itemId?.toString()
draft.cosmeticAspects = cosmetic?.aspects?.toSet() ?: emptySet()
val valid = buildAspectEntries()
if (selectedAspect !in valid) {
selectedAspect = valid.firstOrNull()
}
rebuildAspectEntries()
lastSpeciesId = draft.species.resourceIdentifier
}
private fun buildAspectEntries(): List<AspectItem> {
val entries = mutableListOf<AspectItem>()
aspectOptions.choiceGroups.sortedBy { it.key }.forEach { entries.add(AspectItem.Group(it)) }
aspectOptions.flagKeys.sortedBy { it }.forEach { entries.add(AspectItem.Toggle(it, ToggleSource.FLAG_FEATURE)) }
aspectOptions.toggles.sortedBy { it }.forEach { entries.add(AspectItem.Toggle(it, ToggleSource.FORCED_ASPECT)) }
return entries
}
private fun rebuildAspectEntries() {
val entries = buildAspectEntries()
aspectList.setEntries(entries)
if (selectedAspect == null) {
selectedAspect = entries.firstOrNull()
}
rebuildOptionEntries()
}
private fun rebuildOptionEntries() {
optionList.setEntries(buildOptionEntries(selectedAspect))
}
private fun buildOptionEntries(aspect: AspectItem?): List<OptionItem> {
if (aspect == null) return emptyList()
return when (aspect) {
is AspectItem.Group -> {
val entries = mutableListOf<OptionItem>()
entries.add(OptionItem.ClearChoice(aspect.group))
aspect.group.choices.forEach { choice ->
entries.add(OptionItem.Choice(aspect.group, choice))
}
entries
}
is AspectItem.Cosmetic -> {
val entries = mutableListOf<OptionItem>()
entries.add(OptionItem.ClearCosmetic)
aspect.choices.forEach { choice ->
entries.add(OptionItem.CosmeticChoice(choice))
}
entries
}
is AspectItem.Toggle -> {
listOf(
OptionItem.Toggle(aspect, enabled = true),
OptionItem.Toggle(aspect, enabled = false)
)
}
}
}
private fun humanizeId(raw: String): String {
return raw
.replace('_', ' ')
.replace('-', ' ')
.split(" ")
.filter { it.isNotBlank() }
.joinToString(" ") { part ->
part.lowercase().replaceFirstChar { ch -> if (ch.isLowerCase()) ch.titlecase() else ch.toString() }
}
}
private fun labelForId(id: String): MutableComponent {
val key = "cobblemon.ui.aspect.$id"
return if (I18n.exists(key)) {
Component.translatable(key)
} else {
Component.literal(humanizeId(id))
}
}
private fun isAspectActive(entry: AspectItem): Boolean = when (entry) {
is AspectItem.Toggle -> when (entry.source) {
ToggleSource.FORCED_ASPECT -> entry.id in draft.forcedAspects
ToggleSource.FLAG_FEATURE -> entry.id in draft.flagFeatures
}
is AspectItem.Group -> entry.group.key in draft.choiceFeatures
is AspectItem.Cosmetic -> draft.cosmeticItem != null
}
private fun labelFor(entry: AspectItem): MutableComponent = when (entry) {
is AspectItem.Group -> {
val name = labelForId(entry.group.key).string
val choice = draft.choiceFeatures[entry.group.key]?.let { labelForId(it).string }
Component.literal(if (choice != null) "$name: $choice" else name)
}
is AspectItem.Toggle -> labelForId(entry.id)
is AspectItem.Cosmetic -> {
val base = lang("cosmetic_item").string
val choice = draft.cosmeticItem?.let { id ->
entry.choices.firstOrNull { it.itemId.toString() == id }?.label?.string
}
Component.literal(if (choice != null) "$base: $choice" else base)
}
}
private fun labelFor(option: OptionItem): MutableComponent = when (option) {
is OptionItem.ClearChoice -> lang("ui.none")
is OptionItem.Choice -> labelForId(option.choice)
is OptionItem.Toggle -> if (option.enabled) lang("ui.enabled") else lang("ui.disabled")
is OptionItem.CosmeticChoice -> option.choice.label
OptionItem.ClearCosmetic -> lang("ui.none")
}
private fun selectAspect(entry: AspectItem) {
if (selectedAspect == entry) return
selectedAspect = entry
rebuildOptionEntries()
}
private fun handleOption(option: OptionItem) {
when (option) {
is OptionItem.ClearChoice -> {
draft.choiceFeatures.remove(option.group.key)
}
is OptionItem.Choice -> {
draft.choiceFeatures[option.group.key] = option.choice
}
is OptionItem.Toggle -> {
when (option.aspect.source) {
ToggleSource.FORCED_ASPECT -> {
if (option.enabled) {
draft.forcedAspects.add(option.aspect.id)
} else {
draft.forcedAspects.remove(option.aspect.id)
}
}
ToggleSource.FLAG_FEATURE -> {
if (option.enabled) {
draft.flagFeatures.add(option.aspect.id)
} else {
draft.flagFeatures.remove(option.aspect.id)
}
}
}
}
is OptionItem.CosmeticChoice -> {
draft.cosmeticItem = option.choice.itemId.toString()
draft.cosmeticAspects = option.choice.aspects.toSet()
}
OptionItem.ClearCosmetic -> {
draft.cosmeticItem = null
draft.cosmeticAspects = emptySet()
}
}
}
private inner class AspectList(
pX: Int,
pY: Int,
width: Int,
height: Int
) : ScrollingWidget<AspectList.AspectEntry>(
width = width,
height = height,
left = pX,
top = pY - height,
slotHeight = SLOT_HEIGHT + 2,
scrollBarWidth = 3
) {
private val listX = pX
fun setEntries(aspects: List<AspectItem>) {
clearEntries()
aspects.forEach { aspect -> addEntry(AspectEntry(aspect)) }
scrollAmount = 0.0
}
override fun getScrollbarPosition(): Int = listX + width - 3
override fun renderScrollbar(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val xLeft = this.scrollbarPosition
val xRight = xLeft + 3
val barHeight = this.bottom - this.y
var yBottom = ((barHeight * barHeight).toFloat() / this.maxPosition.toFloat()).toInt()
yBottom = Mth.clamp(yBottom, 32, barHeight - 8)
var yTop = scrollAmount.toInt() * (barHeight - yBottom) / this.maxScroll + this.y
if (yTop < this.y) yTop = this.y
context.fill(xLeft, this.y + 3, xRight, this.bottom - 3, 0xFF3A96B6.toInt())
context.fill(xLeft, yTop + 3, xRight, yTop + yBottom - 3, 0xFFFCFCFC.toInt())
}
inner class AspectEntry(
private val entry: AspectItem
) : Slot<AspectEntry>() {
private var x: Int = 0
private var y: Int = 0
override fun render(
context: GuiGraphics,
index: Int,
y: Int,
x: Int,
entryWidth: Int,
entryHeight: Int,
mouseX: Int,
mouseY: Int,
hovered: Boolean,
tickDelta: Float
) {
this.x = x
this.y = y + 1
if (selectedAspect == entry) {
context.fill(this.x - 1, this.y - 1, this.x + entryWidth + 1, this.y + SLOT_HEIGHT + 1, 0x442A7FFF)
} else if (isAspectActive(entry)) {
context.fill(this.x - 1, this.y - 1, this.x + entryWidth + 1, this.y + SLOT_HEIGHT + 1, 0x4436B33F)
} else if (hovered) {
context.fill(this.x - 1, this.y - 1, this.x + entryWidth + 1, this.y + SLOT_HEIGHT + 1, 0x442A7FFF)
}
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = labelFor(entry),
x = this.x + 4,
y = this.y + 3,
scale = SCALE,
shadow = true
)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (isMouseOver(mouseX, mouseY)) {
selectAspect(entry)
return true
}
return false
}
override fun getNarration(): Component {
return labelFor(entry)
}
}
}
private inner class OptionList(
pX: Int,
pY: Int,
width: Int,
height: Int
) : ScrollingWidget<OptionList.OptionEntry>(
width = width,
height = height,
left = pX,
top = pY - height,
slotHeight = SLOT_HEIGHT + 2,
scrollBarWidth = 3
) {
private val listX = pX
fun setEntries(options: List<OptionItem>) {
clearEntries()
options.forEach { option -> addEntry(OptionEntry(option)) }
scrollAmount = 0.0
}
override fun getScrollbarPosition(): Int = listX + width - 3
override fun renderScrollbar(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val xLeft = this.scrollbarPosition
val xRight = xLeft + 3
val barHeight = this.bottom - this.y
var yBottom = ((barHeight * barHeight).toFloat() / this.maxPosition.toFloat()).toInt()
yBottom = Mth.clamp(yBottom, 32, barHeight - 8)
var yTop = scrollAmount.toInt() * (barHeight - yBottom) / this.maxScroll + this.y
if (yTop < this.y) yTop = this.y
context.fill(xLeft, this.y + 3, xRight, this.bottom - 3, 0xFF3A96B6.toInt())
context.fill(xLeft, yTop + 3, xRight, yTop + yBottom - 3, 0xFFFCFCFC.toInt())
}
inner class OptionEntry(
private val option: OptionItem
) : Slot<OptionEntry>() {
private var x: Int = 0
private var y: Int = 0
private fun isSelectedOption(): Boolean = when (option) {
is OptionItem.ClearChoice -> {
draft.choiceFeatures[option.group.key] == null
}
is OptionItem.Choice -> {
draft.choiceFeatures[option.group.key] == option.choice
}
is OptionItem.Toggle -> {
val isEnabled = when (option.aspect.source) {
ToggleSource.FORCED_ASPECT -> option.aspect.id in draft.forcedAspects
ToggleSource.FLAG_FEATURE -> option.aspect.id in draft.flagFeatures
}
isEnabled == option.enabled
}
is OptionItem.CosmeticChoice -> {
draft.cosmeticItem == option.choice.itemId.toString()
}
OptionItem.ClearCosmetic -> {
draft.cosmeticItem == null
}
}
override fun render(
context: GuiGraphics,
index: Int,
y: Int,
x: Int,
entryWidth: Int,
entryHeight: Int,
mouseX: Int,
mouseY: Int,
hovered: Boolean,
tickDelta: Float
) {
this.x = x
this.y = y + 1
if (isSelectedOption()) {
context.fill(this.x - 1, this.y - 1, this.x + entryWidth + 1, this.y + SLOT_HEIGHT + 1, 0x4436B33F)
} else if (hovered) {
context.fill(this.x - 1, this.y - 1, this.x + entryWidth + 1, this.y + SLOT_HEIGHT + 1, 0x442A7FFF)
}
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = labelFor(option),
x = this.x + 4,
y = this.y + 3,
scale = SCALE,
shadow = true
)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (isMouseOver(mouseX, mouseY)) {
handleOption(option)
return true
}
return false
}
override fun getNarration(): Component {
return labelFor(option)
}
}
}
}
@@ -0,0 +1,335 @@
/*
* 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.gui.pokeedit.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.pokeedit.PokeEditDraft
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.pokemon.EVs
import com.cobblemon.mod.common.pokemon.IVs
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.BufferUploader
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.Tesselator
import com.mojang.blaze3d.vertex.VertexFormat
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.Screen
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.phys.Vec2
import org.joml.Vector3f
class PokeEditEVIVTab(
pX: Int,
pY: Int,
private val draft: PokeEditDraft
) : SoundlessWidget(pX, pY, WIDTH, HEIGHT, lang("ui.pokeedit.stats")) {
companion object {
private const val WIDTH = 139
private const val HEIGHT = 163
private const val CHART_WIDTH = 134
private const val CHART_HEIGHT = 148
private const val SCALE = 0.5F
private const val TAB_MARKER_WIDTH = 8
private const val TAB_IV = 0
private const val TAB_EV = 1
private val statsBaseResource = cobblemonResource("textures/gui/summary/summary_stats_chart_base.png")
private val statsChartResource = cobblemonResource("textures/gui/summary/summary_stats_chart.png")
private val tabMarkerResource = cobblemonResource("textures/gui/summary/summary_stats_tab_marker.png")
private val statLabels = listOf(
lang("ui.stats.hp").bold() to Stats.HP,
lang("ui.stats.atk").bold() to Stats.ATTACK,
lang("ui.stats.def").bold() to Stats.DEFENCE,
lang("ui.stats.speed").bold() to Stats.SPEED,
lang("ui.stats.sp_def").bold() to Stats.SPECIAL_DEFENCE,
lang("ui.stats.sp_atk").bold() to Stats.SPECIAL_ATTACK
)
private val hexagonVerticesOffset = listOf(
Pair(67.0, 10.5), // 12 o'clock
Pair(122.0, 42.5), // 2 o'clock
Pair(122.0, 93.5), // 4 o'clock
Pair(67.0, 124.5), // 6 o'clock
Pair(12.0, 93.5), // 8 o'clock
Pair(12.0, 42.5) // 10 o'clock
)
}
private var tabIndex = TAB_IV
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val chartX = x + 2
val chartY = y + 6
blitk(
matrixStack = context.pose(),
texture = statsBaseResource,
x = chartX,
y = chartY,
width = CHART_WIDTH,
height = CHART_HEIGHT
)
blitk(
matrixStack = context.pose(),
texture = statsChartResource,
x = (chartX + 25.5) / SCALE,
y = (chartY + 22) / SCALE,
width = 166,
height = 192,
scale = SCALE
)
if (tabIndex == TAB_IV) {
drawStatPolygon(
chartX.toFloat(),
chartY.toFloat(),
statLabels.map { (_, stat) -> draft.ivs.getOrDefault(stat).toFloat() / IVs.MAX_VALUE.toFloat() },
colour = Vector3f(216F / 255F, 100F / 255F, 1F)
)
} else {
drawStatPolygon(
chartX.toFloat(),
chartY.toFloat(),
statLabels.map { (_, stat) -> draft.evs.getOrDefault(stat).toFloat() / EVs.MAX_STAT_VALUE.toFloat() },
colour = Vector3f(1F, 1F, 100F / 255F)
)
}
val labelsHovered = statLabelsHovered(chartX, chartY, mouseX, mouseY)
val labels = statLabels.map { (label, stat) ->
if (labelsHovered) {
if (tabIndex == TAB_IV) {
"${draft.ivs.getOrDefault(stat)}/${IVs.MAX_VALUE}".text()
} else {
"${draft.evs.getOrDefault(stat)}/${EVs.MAX_STAT_VALUE}".text()
}
} else {
label
}
}
renderPolygonLabels(
context = context,
chartX = chartX.toDouble(),
chartY = chartY.toDouble(),
labels = labels,
verticesOffset = hexagonVerticesOffset
)
val values = statLabels.map { (_, stat) ->
if (tabIndex == TAB_IV) {
draft.ivs.getOrDefault(stat).toString().text()
} else {
draft.evs.getOrDefault(stat).toString().text()
}
}
renderPolygonLabels(
context = context,
chartX = chartX.toDouble(),
chartY = chartY.toDouble(),
labels = values,
verticesOffset = hexagonVerticesOffset,
offsetY = 5.5
)
if (tabIndex == TAB_EV) {
drawScaledText(
context = context,
text = lang("ui.pokeedit.evs.total", draft.evs.total(), EVs.MAX_TOTAL_VALUE),
x = chartX + 10,
y = chartY + 126,
scale = SCALE,
shadow = true
)
}
renderTabs(context, chartX, chartY)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
val tabIndexAtPos = getTabIndexFromPos(mouseX, mouseY)
if (tabIndexAtPos != -1 && tabIndexAtPos != tabIndex) {
tabIndex = tabIndexAtPos
return true
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun mouseScrolled(mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double): Boolean {
val chartX = x + 2
val chartY = y + 6
val stat = getHoveredStat(chartX, chartY, mouseX.toInt(), mouseY.toInt()) ?: return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)
val step = when {
Screen.hasControlDown() -> 25
Screen.hasShiftDown() -> 10
else -> 1
}
val delta = if (verticalAmount > 0) step else -step
if (tabIndex == TAB_IV) {
val current = draft.ivs.getOrDefault(stat)
val next = (current + delta).coerceIn(0, IVs.MAX_VALUE)
draft.ivs[stat] = next
} else {
val current = draft.evs.getOrDefault(stat)
val totalWithout = draft.evs.total() - current
val maxForStat = minOf(EVs.MAX_STAT_VALUE, EVs.MAX_TOTAL_VALUE - totalWithout)
val next = (current + delta).coerceIn(0, maxForStat)
draft.evs[stat] = next
}
return true
}
private fun renderTabs(context: GuiGraphics, chartX: Int, chartY: Int) {
val tabWidth = 40
val tabLeft = chartX + ((CHART_WIDTH - (tabWidth * 2)) / 2)
val tabCenters = listOf(
tabLeft + (tabWidth / 2),
tabLeft + tabWidth + (tabWidth / 2)
)
val tabY = chartY + 137
drawScaledText(
context = context,
text = lang("ui.stats.ivs").bold(),
x = tabCenters[0],
y = tabY,
scale = SCALE,
colour = if (tabIndex == TAB_IV) 0x00FFFFFF else 0x008F8F8F,
centered = true
)
drawScaledText(
context = context,
text = lang("ui.stats.evs").bold(),
x = tabCenters[1],
y = tabY,
scale = SCALE,
colour = if (tabIndex == TAB_EV) 0x00FFFFFF else 0x008F8F8F,
centered = true
)
blitk(
matrixStack = context.pose(),
texture = tabMarkerResource,
x = (tabCenters[tabIndex] - (TAB_MARKER_WIDTH / 2)) / SCALE,
y = (tabY - 3) / SCALE,
width = TAB_MARKER_WIDTH,
height = 4,
scale = SCALE
)
}
private fun getTabIndexFromPos(mouseX: Double, mouseY: Double): Int {
val chartX = x + 2
val chartY = y + 6
val tabWidth = 40
val tabLeft = chartX + ((CHART_WIDTH - (tabWidth * 2)) / 2)
val tabTop = chartY + 134.0
val tabBottom = tabTop + 9.0
if (mouseY !in tabTop..tabBottom) return -1
if (mouseX in tabLeft.toDouble()..(tabLeft + tabWidth).toDouble()) return TAB_IV
if (mouseX in (tabLeft + tabWidth).toDouble()..(tabLeft + tabWidth * 2).toDouble()) return TAB_EV
return -1
}
private fun getHoveredStat(chartX: Int, chartY: Int, mouseX: Int, mouseY: Int): Stat? {
statLabels.forEachIndexed { index, (_, stat) ->
val pos = hexagonVerticesOffset[index]
if (mouseX.toDouble() in (chartX + pos.first - 12)..(chartX + pos.first + 12)
&& mouseY.toDouble() in (chartY + pos.second - 1)..(chartY + pos.second + 10)
) {
return stat
}
}
return null
}
private fun statLabelsHovered(chartX: Int, chartY: Int, mouseX: Int, mouseY: Int): Boolean {
return getHoveredStat(chartX, chartY, mouseX, mouseY) != null
}
private fun renderPolygonLabels(
context: GuiGraphics,
chartX: Double,
chartY: Double,
labels: List<MutableComponent>,
verticesOffset: List<Pair<Double, Double>>,
offsetY: Double = 0.0
) {
if (labels.size != verticesOffset.size) return
labels.forEachIndexed { index, label ->
drawScaledText(
context = context,
text = label,
x = chartX + verticesOffset[index].first,
y = chartY + verticesOffset[index].second + offsetY,
scale = SCALE,
centered = true
)
}
}
private fun drawTriangle(colour: Vector3f, v1: Vec2, v2: Vec2, v3: Vec2, opacity: Float = 0.6F) {
RenderSystem.setShaderTexture(0, com.cobblemon.mod.common.client.CobblemonResources.WHITE)
RenderSystem.setShaderColor(colour.x, colour.y, colour.z, opacity)
val bufferBuilder = Tesselator.getInstance().begin(VertexFormat.Mode.TRIANGLES, DefaultVertexFormat.POSITION)
bufferBuilder.addVertex(v1.x, v1.y, 10F)
bufferBuilder.addVertex(v2.x, v2.y, 10F)
bufferBuilder.addVertex(v3.x, v3.y, 10F)
BufferUploader.drawWithShader(bufferBuilder.buildOrThrow())
RenderSystem.setShaderColor(1F, 1F, 1F, 1F)
}
private fun drawStatPolygon(chartX: Float, chartY: Float, ratios: List<Float>, colour: Vector3f) {
if (ratios.size != 6) return
val topVertexY = chartY + 22F
val centerX = chartX + 67F
val centerY = topVertexY + 48F
val radius = centerY - topVertexY
val startAngleDeg = -90.0
val angleStepDeg = 360.0 / 6
val anglesDeg = List(6) { i -> startAngleDeg + i * angleStepDeg }
val anglesRad = anglesDeg.map { Math.toRadians(it) }
val maxPoints = anglesRad.map { angle ->
Vec2(
centerX + radius * Math.cos(angle).toFloat(),
centerY + radius * Math.sin(angle).toFloat()
)
}
val coercedRatios = ratios.map { it.coerceIn(5 / radius, 1F) }
val vertices = maxPoints.mapIndexed { index, maxPoint ->
Vec2(
centerX + (maxPoint.x - centerX) * coercedRatios[index],
centerY + (maxPoint.y - centerY) * coercedRatios[index]
)
}
val centerPoint = Vec2(centerX, centerY)
RenderSystem.disableDepthTest()
for (i in vertices.indices) {
val nextIndex = (i + 1) % vertices.size
drawTriangle(colour, vertices[i], centerPoint, vertices[nextIndex])
}
RenderSystem.enableDepthTest()
}
}
@@ -0,0 +1,354 @@
/*
* 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.gui.pokeedit.widgets
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
import com.cobblemon.mod.common.client.gui.pokeedit.PokeEditDraft
import com.cobblemon.mod.common.client.gui.pokeedit.widgets.PokeEditAspectsTab.Companion.choiceAspectId
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.item.HeldItemRenderer
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.pokemon.aspects.SHINY_ASPECT
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import com.mojang.blaze3d.platform.Lighting
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.AbstractWidget
import net.minecraft.client.renderer.LightTexture
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.Blocks
import org.joml.Quaternionf
import org.joml.Vector3f
class PokeEditImplementationBlankTab(
pX: Int,
pY: Int,
private val draft: PokeEditDraft,
title: Component
) : SoundlessWidget(pX, pY, HALF_OVERLAY_WIDTH, HALF_OVERLAY_HEIGHT, title) {
companion object {
private val backgroundOverlay = cobblemonResource("textures/gui/pokedex/pokedex_screen_info_overlay.png")
private val pokeBallOverlay = cobblemonResource("textures/gui/pokedex/pokedex_screen_poke_ball.png")
private val platformBase = cobblemonResource("textures/gui/pokedex/platform_base.png")
private val platformShadow = cobblemonResource("textures/gui/pokedex/platform_shadow.png")
private const val POKEBALL_OVERLAY_Y = 25
private const val PLATFORM_Y = 69
private const val SHADOW_X = 25
private const val SHADOW_Y = 76
private const val VIEW_SCALE = 1.2F
private const val VIEW_SHIFT_X = 0F
private const val VIEW_SHIFT_Y = 36F
private const val MODEL_VIEW_SCALE = 2.4F
private const val MODEL_DRAW_SCALE = 24F
private const val MODEL_OFFSET_Y = 20.0
private const val CUBE_BASE_OFFSET_X = 1.0F
private const val CUBE_SIZE_OFFSET_MULTIPLIER = 0.18F
private const val CUBE_OFFSET_Z = -0.5F
}
private val state = FloatingState().also { state ->
state.runtime.environment.query.addFunction("is_holding_item") {
DoubleValue(hasNonWearableAttachment())
}
state.runtime.environment.query.addFunction("is_wearing_hat") {
DoubleValue(hasWearableHatAttachment())
}
state.runtime.environment.query.addFunction("is_wearing_face") {
DoubleValue(hasWearableFaceAttachment())
}
}
private val heldItemRenderer = HeldItemRenderer()
private var renderablePokemon: RenderablePokemon? = null
private var attachmentStacks: List<ItemStack> = emptyList()
private var rotationY = 30F
private var poseList: Array<PoseType> = arrayOf(PoseType.PROFILE)
private var selectedPoseIndex: Int = 0
private var oldDragPosX = 0.0
private var canDragRender = false
private var isDraggingRender = false
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
context.enableScissor(x, y, x + HALF_OVERLAY_WIDTH, y + HALF_OVERLAY_HEIGHT)
val viewWidth = HALF_OVERLAY_WIDTH * VIEW_SCALE
val viewHeight = HALF_OVERLAY_HEIGHT * VIEW_SCALE
val viewX = x + (HALF_OVERLAY_WIDTH - viewWidth) / 2F + VIEW_SHIFT_X
val viewY = y + (HALF_OVERLAY_HEIGHT - viewHeight) / 2F + VIEW_SHIFT_Y
val scaledBaseX = viewX / VIEW_SCALE
val scaledBaseY = viewY / VIEW_SCALE
blitk(
matrixStack = context.pose(),
texture = backgroundOverlay,
x = scaledBaseX,
y = scaledBaseY,
width = HALF_OVERLAY_WIDTH,
height = HALF_OVERLAY_HEIGHT,
scale = VIEW_SCALE
)
blitk(
matrixStack = context.pose(),
texture = pokeBallOverlay,
x = scaledBaseX + 15,
y = scaledBaseY + POKEBALL_OVERLAY_Y,
width = 109,
height = 68,
vOffset = 0,
textureHeight = 1744,
scale = VIEW_SCALE
)
blitk(
matrixStack = context.pose(),
texture = platformBase,
x = scaledBaseX + 13,
y = scaledBaseY + PLATFORM_Y,
width = 113,
height = 24,
textureHeight = 30,
scale = VIEW_SCALE
)
blitk(
matrixStack = context.pose(),
texture = platformShadow,
x = scaledBaseX + SHADOW_X,
y = scaledBaseY + SHADOW_Y,
width = 90,
height = 20,
scale = VIEW_SCALE
)
val fillTop = (viewY + 114F * VIEW_SCALE).toInt()
val fillBottom = (viewY + (HALF_OVERLAY_HEIGHT - 6) * VIEW_SCALE).toInt()
context.fill(viewX.toInt(), fillTop, (viewX + viewWidth).toInt(), fillBottom, 0xFF7AEEF1.toInt())
context.disableScissor()
buildRenderable()?.let { renderable ->
context.enableScissor(
x + 1,
y + 1,
x + HALF_OVERLAY_WIDTH - 1,
y + HALF_OVERLAY_HEIGHT - 1
)
val matrices = context.pose()
matrices.pushPose()
matrices.translate(
x.toDouble() + (HALF_OVERLAY_WIDTH.toDouble()) / 2,
y.toDouble() + MODEL_OFFSET_Y,
1000.0
)
matrices.scale(MODEL_VIEW_SCALE, MODEL_VIEW_SCALE, MODEL_VIEW_SCALE)
val rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, rotationY, 0F))
val cubeOffsetX = getCubeOffsetX()
matrices.translate((-cubeOffsetX / 2F).toDouble(), 0.0, 0.0)
renderReferenceCube(renderable, matrices, rotation, cubeOffsetX)
drawProfilePokemon(
renderablePokemon = renderable,
poseType = poseList[selectedPoseIndex],
matrixStack = matrices,
partialTicks = delta,
rotation = Quaternionf(rotation),
state = state,
scale = MODEL_DRAW_SCALE
)
if (attachmentStacks.isNotEmpty()) {
val bufferSource = Minecraft.getInstance().renderBuffers().bufferSource()
attachmentStacks.forEach { stack ->
heldItemRenderer.renderOnModel(
stack,
state,
matrices,
bufferSource,
light = 0xF000F0,
frontLight = true
)
}
bufferSource.endBatch()
}
matrices.popPose()
context.disableScissor()
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (isWithinViewport(mouseX, mouseY)) {
canDragRender = true
isDraggingRender = true
oldDragPosX = mouseX
return true
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun mouseReleased(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (canDragRender) {
canDragRender = false
}
if (isDraggingRender) {
isDraggingRender = false
}
return super.mouseReleased(pMouseX, pMouseY, pButton)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
val handledDrag = isDraggingRender && canDragRender
if (handledDrag) {
val dragOffsetY = (oldDragPosX - mouseX).toFloat()
rotationY = (((rotationY + dragOffsetY) % 360 + 360) % 360)
}
oldDragPosX = mouseX
return handledDrag || super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
private fun buildRenderable(): RenderablePokemon? {
val species = draft.species
val form = draft.form
val aspects = mutableSetOf<String>()
if (draft.shiny) aspects.add(SHINY_ASPECT.aspect)
aspects.addAll(form.aspects)
aspects.addAll(draft.forcedAspects)
aspects.addAll(draft.flagFeatures)
aspects.addAll(draft.cosmeticAspects)
draft.choiceFeatures.forEach { (key, choice) ->
choiceAspectId(key, choice)?.let { aspects.add(it) }
}
when (draft.gender) {
Gender.MALE -> aspects.add("male")
Gender.FEMALE -> aspects.add("female")
Gender.GENDERLESS -> aspects.add("genderless")
}
val heldStack = parseItemStack(draft.heldItem)
val cosmeticStack = parseItemStack(draft.cosmeticItem)
attachmentStacks = buildAttachmentStacks(heldStack, cosmeticStack)
renderablePokemon = RenderablePokemon(species, aspects, heldStack).also { recalculatePoses(it) }
return renderablePokemon
}
private fun recalculatePoses(renderablePokemon: RenderablePokemon) {
val poseState = FloatingState().also { it.currentAspects = renderablePokemon.aspects }
val poser = VaryingModelRepository.getPoser(renderablePokemon.species.resourceIdentifier, poseState)
poseList = poser.poses
.map { it.value.poseTypes.minBy { it.ordinal } }
.toSet()
.filterNot { it in PoseType.SHOULDER_POSES }
.sortedBy { it.ordinal }
.toTypedArray()
if (selectedPoseIndex !in poseList.indices) {
selectedPoseIndex = 0
}
}
private fun parseItemStack(rawId: String?): ItemStack {
return rawId
?.let { ResourceLocation.tryParse(it) }
?.let { id -> BuiltInRegistries.ITEM.getOptional(id).orElse(null) }
?.let { item -> ItemStack(item) }
?: ItemStack.EMPTY
}
private fun buildAttachmentStacks(heldStack: ItemStack, cosmeticStack: ItemStack): List<ItemStack> {
if (heldStack.isEmpty && cosmeticStack.isEmpty) return emptyList()
val stacks = mutableListOf<ItemStack>()
if (!heldStack.isEmpty) {
stacks.add(heldStack)
}
if (!cosmeticStack.isEmpty && (heldStack.isEmpty || cosmeticStack.item != heldStack.item)) {
stacks.add(cosmeticStack)
}
return stacks
}
private fun hasNonWearableAttachment(): Boolean {
return attachmentStacks.any { stack ->
!stack.isEmpty && !stack.`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS) && !stack.`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS)
}
}
private fun hasWearableHatAttachment(): Boolean {
return attachmentStacks.any { stack -> !stack.isEmpty && stack.`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS) }
}
private fun hasWearableFaceAttachment(): Boolean {
return attachmentStacks.any { stack -> !stack.isEmpty && stack.`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS) }
}
private fun renderReferenceCube(
renderable: RenderablePokemon,
matrices: com.mojang.blaze3d.vertex.PoseStack,
rotation: Quaternionf,
cubeOffsetX: Float
) {
val model = VaryingModelRepository.getPoser(renderable.species.resourceIdentifier, state)
val bufferSource = Minecraft.getInstance().renderBuffers().bufferSource()
matrices.pushPose()
matrices.scale(MODEL_DRAW_SCALE, MODEL_DRAW_SCALE, -MODEL_DRAW_SCALE)
matrices.translate(
model.profileTranslation.x,
model.profileTranslation.y + 1.5 * model.profileScale,
model.profileTranslation.z - 4.0
)
matrices.scale(model.profileScale, model.profileScale, 1 / model.profileScale)
matrices.mulPose(rotation)
matrices.mulPose(com.mojang.math.Axis.XP.rotationDegrees(180F))
matrices.translate(cubeOffsetX.toDouble(), 0.0, CUBE_OFFSET_Z.toDouble())
RenderSystem.applyModelViewMatrix()
RenderSystem.enableDepthTest()
RenderSystem.disableCull()
Lighting.setupForEntityInInventory()
Minecraft.getInstance().blockRenderer.renderSingleBlock(
Blocks.RED_WOOL.defaultBlockState(),
matrices,
bufferSource,
LightTexture.pack(15, 15),
OverlayTexture.NO_OVERLAY
)
bufferSource.endBatch()
Lighting.setupFor3DItems()
RenderSystem.enableCull()
matrices.popPose()
}
private fun getCubeOffsetX(): Float {
val hitboxWidth = draft.form.hitbox.width * draft.form.baseScale
val hitboxHeight = draft.form.hitbox.height * draft.form.baseScale
val hitboxSize = maxOf(hitboxWidth, hitboxHeight)
val extraOffset = (hitboxSize - 1.0F).coerceAtLeast(0.0F) * CUBE_SIZE_OFFSET_MULTIPLIER
return CUBE_BASE_OFFSET_X + extraOffset
}
private fun isWithinViewport(mouseX: Double, mouseY: Double): Boolean =
mouseX.toInt() in x..(x + HALF_OVERLAY_WIDTH)
&& mouseY.toInt() in y..(y + HALF_OVERLAY_HEIGHT)
&& children.none { child ->
child is AbstractWidget && child.visible && child.isMouseOver(mouseX, mouseY)
}
}
@@ -0,0 +1,227 @@
/*
* 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.gui.pokeedit.widgets
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.ScrollingWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.util.Mth
class PokeEditLocatorListWidget(
val pX: Int,
val pY: Int,
val listWidth: Int,
val listHeight: Int,
private val onSelectLocator: (String?) -> Unit
) : ScrollingWidget<PokeEditLocatorListWidget.LocatorRow>(
left = pX,
top = pY - listHeight,
width = listWidth,
height = listHeight,
slotHeight = ROW_HEIGHT
) {
companion object {
private const val ROW_HEIGHT = 9
private const val TEXT_SCALE = 0.5F
private const val BORDER_COLOR = 0xFF1B1B1B.toInt()
private const val FILL_COLOR = 0xFF3B3B3B.toInt()
private const val TEXT_COLOR = 0xFFFCFCFC.toInt()
}
private val listTop = pY - listHeight
private val listBottom = pY
private var selectedLocator: String? = null
private var draggingScrollbar = false
fun setLocators(locators: Collection<String>) {
clearEntries()
locators.sorted().forEach { addEntry(LocatorRow(it)) }
if (selectedLocator != null && locators.none { it == selectedLocator }) {
selectedLocator = null
onSelectLocator(null)
}
scrollAmount = 0.0
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
context.fill(pX - 1, listTop - 1, pX + listWidth + 1, listBottom + 1, BORDER_COLOR)
context.fill(pX, listTop, pX + listWidth, listBottom, FILL_COLOR)
focused = if (mouseX in pX..(pX + listWidth) && mouseY in listTop..listBottom) {
getEntryAtPosition(mouseX.toDouble(), mouseY.toDouble())
} else {
null
}
context.enableScissor(pX, listTop, pX + listWidth, listBottom)
renderListItems(context, mouseX, mouseY, delta)
context.disableScissor()
if (maxScroll > 0) {
renderScrollbar(context, mouseX, mouseY, delta)
}
}
override fun mouseScrolled(mouseX: Double, mouseY: Double, amount: Double, verticalAmount: Double): Boolean {
if (mouseX !in pX.toDouble()..(pX + listWidth).toDouble()
|| mouseY !in listTop.toDouble()..listBottom.toDouble()
) {
return false
}
val maxScroll = (itemCount * itemHeight - listHeight).coerceAtLeast(0)
if (maxScroll <= 0) return true
val step = if (verticalAmount != 0.0) verticalAmount else amount
scrollAmount = Mth.clamp(scrollAmount - step * itemHeight, 0.0, maxScroll.toDouble())
return true
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (mouseX !in pX.toDouble()..(pX + listWidth).toDouble()
|| mouseY !in listTop.toDouble()..listBottom.toDouble()
) {
return false
}
if (button != 0) {
return false
}
if (isOverScrollbar(mouseX, mouseY) && maxScroll > 0) {
draggingScrollbar = true
return true
}
val localY = mouseY.toInt() - listTop + scrollAmount.toInt()
if (localY < 0) return false
val index = localY / itemHeight
if (index !in 0 until itemCount) return false
val entry = getEntry(index)
if (entry is LocatorRow) {
selectedLocator = entry.name
onSelectLocator(selectedLocator)
return true
}
return false
}
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (button == 0 && draggingScrollbar) {
draggingScrollbar = false
return true
}
return super.mouseReleased(mouseX, mouseY, button)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
if (!draggingScrollbar || maxScroll <= 0) {
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
val barHeight = listBottom - listTop
var thumbHeight = ((barHeight * barHeight).toFloat() / maxPosition.toFloat()).toInt()
thumbHeight = Mth.clamp(thumbHeight, 16, barHeight - 4)
val trackHeight = barHeight - thumbHeight
if (trackHeight <= 0) return true
val clampedY = Mth.clamp(mouseY.toInt() - listTop - thumbHeight / 2, 0, trackHeight)
scrollAmount = (clampedY.toDouble() * maxScroll.toDouble()) / trackHeight.toDouble()
return true
}
override fun isMouseOver(mouseX: Double, mouseY: Double): Boolean {
return mouseX in pX.toDouble()..(pX + listWidth).toDouble()
&& mouseY in listTop.toDouble()..listBottom.toDouble()
}
override fun renderListItems(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val rowX = rowLeft
for (index in 0 until itemCount) {
val rowY = getRowTop(index)
val rowBottom = getRowBottom(index)
if (rowBottom >= listTop && rowY <= listBottom) {
val entry = getEntry(index)
if (entry is LocatorRow) {
entry.isSelected = entry.name == selectedLocator
}
renderItem(context, mouseX, mouseY, delta, index, rowX, rowY, rowWidth, itemHeight)
}
}
}
override fun renderScrollbar(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val xLeft = this.scrollbarPosition
val xRight = xLeft + 3
val yStart = listTop
val barHeight = listBottom - listTop
var yBottom = ((barHeight * barHeight).toFloat() / this.maxPosition.toFloat()).toInt()
yBottom = Mth.clamp(yBottom, 16, barHeight - 4)
var yTop = scrollAmount.toInt() * (barHeight - yBottom) / this.maxScroll + yStart
if (yTop < yStart) yTop = yStart
context.fill(xLeft + 1, yStart, xRight - 1, listBottom, 0xFF7AEEF1.toInt())
context.fill(xLeft, yTop, xRight, yTop + yBottom, 0xFF3A96B6.toInt())
}
override fun getRowTop(index: Int): Int {
return listTop - scrollAmount.toInt() + (index * itemHeight)
}
override fun getRowBottom(index: Int): Int {
return getRowTop(index) + itemHeight
}
override fun getBottom(): Int {
return listBottom
}
private fun isOverScrollbar(mouseX: Double, mouseY: Double): Boolean {
val xLeft = scrollbarPosition
val xRight = xLeft + 3
return mouseX >= xLeft && mouseX <= xRight && mouseY >= listTop && mouseY <= listBottom
}
class LocatorRow(val name: String) : Slot<LocatorRow>() {
var isSelected: Boolean = false
override fun render(
context: GuiGraphics,
index: Int,
y: Int,
x: Int,
entryWidth: Int,
entryHeight: Int,
mouseX: Int,
mouseY: Int,
hovered: Boolean,
tickDelta: Float
) {
val bg = when {
isSelected -> 0xFF4A4A4A.toInt()
hovered -> 0xFF2F2F2F.toInt()
else -> null
}
if (bg != null) {
context.fill(x + 1, y, x + entryWidth - 1, y + ROW_HEIGHT, bg)
}
drawScaledText(
context = context,
text = name.text(),
x = x + 3,
y = y + 2,
scale = TEXT_SCALE,
colour = TEXT_COLOR,
shadow = false
)
}
override fun getNarration(): Component {
return Component.literal(name)
}
}
}
@@ -0,0 +1,476 @@
/*
* 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.gui.pokeedit.widgets
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
import com.cobblemon.mod.common.client.gui.summary.SummaryButton
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.item.HeldItemRenderer
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.client.gui.pokeedit.PokeEditDraft
import com.cobblemon.mod.common.client.gui.pokeedit.widgets.PokeEditAspectsTab.Companion.choiceAspectId
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.pokemon.aspects.SHINY_ASPECT
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.AbstractWidget
import net.minecraft.client.renderer.LevelRenderer
import net.minecraft.client.renderer.RenderType
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import org.joml.Matrix4f
import org.joml.Quaternionf
import org.joml.Vector4f
import org.joml.Vector3f
class PokeEditLocatorTab(
pX: Int,
pY: Int,
private val draft: PokeEditDraft,
private var highlightedLocator: String?,
private val onDumpLocators: (List<String>) -> Unit
) : SoundlessWidget(pX, pY, HALF_OVERLAY_WIDTH, HALF_OVERLAY_HEIGHT, "locators".text()) {
companion object {
private val backgroundOverlay = cobblemonResource("textures/gui/pokedex/pokedex_screen_info_overlay.png")
private val pokeBallOverlay = cobblemonResource("textures/gui/pokedex/pokedex_screen_poke_ball.png")
private val platformBase = cobblemonResource("textures/gui/pokedex/platform_base.png")
private val platformShadow = cobblemonResource("textures/gui/pokedex/platform_shadow.png")
private val actionButtonResource = cobblemonResource("textures/gui/summary/summary_evolve_button.png")
private const val LOCATOR_CUBE_SIZE = 0.1
private const val LOCATOR_TOOLTIP_RADIUS = 8
private const val BUTTON_WIDTH = 54
private const val BUTTON_HEIGHT = 15
private const val POKEBALL_OVERLAY_Y = 25
private const val PLATFORM_Y = 69
private const val SHADOW_X = 25
private const val SHADOW_Y = 76
private const val VIEW_SCALE = 1.2F
private const val VIEW_SHIFT_X = 0F
private const val VIEW_SHIFT_Y = 36F
private const val MODEL_VIEW_SCALE = 2.4F
private const val MODEL_DRAW_SCALE = 24F
private const val MODEL_OFFSET_Y = 20.0
private const val AXIS_LINE_LENGTH = 0.35
private const val LEGEND_SCALE = 0.5F
private const val LEGEND_X = 6
private const val LEGEND_Y = 60
}
private val state = FloatingState().also { state ->
state.runtime.environment.query.addFunction("is_holding_item") {
val held = currentHeldStack()
DoubleValue(!held.isEmpty && !held.`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS) && !held.`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS))
}
state.runtime.environment.query.addFunction("is_wearing_hat") {
DoubleValue(currentHeldStack().`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS))
}
state.runtime.environment.query.addFunction("is_wearing_face") {
DoubleValue(currentHeldStack().`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS))
}
}
private val heldItemRenderer = HeldItemRenderer()
private var renderablePokemon: RenderablePokemon? = null
private var rotationY = 30F
private var poseList: Array<PoseType> = arrayOf(PoseType.PROFILE)
private var selectedPoseIndex: Int = 0
private var showLocators = false
private var showLocatorAxes = false
private var oldDragPosX = 0.0
private var canDragRender = false
private var isDraggingRender = false
private var cachedLocatorNames: List<String> = emptyList()
private val showLocatorsButton = SummaryButton(
buttonX = pX + 6F,
buttonY = pY + 6F,
buttonWidth = BUTTON_WIDTH,
buttonHeight = BUTTON_HEIGHT,
clickAction = { showLocators = true },
text = "Locators On".text(),
resource = actionButtonResource,
renderRequirement = { !showLocators },
largeText = false,
boldText = false,
textScale = 0.7F
).also { addWidget(it) }
private val hideLocatorsButton = SummaryButton(
buttonX = pX + 66F,
buttonY = pY + 6F,
buttonWidth = BUTTON_WIDTH,
buttonHeight = BUTTON_HEIGHT,
clickAction = { showLocators = false },
text = "Locators Off".text(),
resource = actionButtonResource,
renderRequirement = { showLocators },
largeText = false,
boldText = false,
textScale = 0.7F
).also { addWidget(it) }
private val showLocatorAxesButton = SummaryButton(
buttonX = pX + 6F,
buttonY = pY + 24F,
buttonWidth = BUTTON_WIDTH,
buttonHeight = BUTTON_HEIGHT,
clickAction = { showLocatorAxes = true },
text = "Axes On".text(),
resource = actionButtonResource,
renderRequirement = { !showLocatorAxes },
largeText = false,
boldText = false,
textScale = 0.7F
).also { addWidget(it) }
private val hideLocatorAxesButton = SummaryButton(
buttonX = pX + 66F,
buttonY = pY + 24F,
buttonWidth = BUTTON_WIDTH,
buttonHeight = BUTTON_HEIGHT,
clickAction = { showLocatorAxes = false },
text = "Axes Off".text(),
resource = actionButtonResource,
renderRequirement = { showLocatorAxes },
largeText = false,
boldText = false,
textScale = 0.7F
).also { addWidget(it) }
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val matrices = context.pose()
showLocatorsButton.buttonActive = showLocators
hideLocatorsButton.buttonActive = !showLocators
showLocatorAxesButton.buttonActive = showLocatorAxes
hideLocatorAxesButton.buttonActive = !showLocatorAxes
context.enableScissor(x, y, x + HALF_OVERLAY_WIDTH, y + HALF_OVERLAY_HEIGHT)
val viewWidth = HALF_OVERLAY_WIDTH * VIEW_SCALE
val viewHeight = HALF_OVERLAY_HEIGHT * VIEW_SCALE
val viewX = x + (HALF_OVERLAY_WIDTH - viewWidth) / 2F + VIEW_SHIFT_X
val viewY = y + (HALF_OVERLAY_HEIGHT - viewHeight) / 2F + VIEW_SHIFT_Y
val scaledBaseX = viewX / VIEW_SCALE
val scaledBaseY = viewY / VIEW_SCALE
blitk(
matrixStack = matrices,
texture = backgroundOverlay,
x = scaledBaseX,
y = scaledBaseY,
width = HALF_OVERLAY_WIDTH,
height = HALF_OVERLAY_HEIGHT,
scale = VIEW_SCALE
)
blitk(
matrixStack = matrices,
texture = pokeBallOverlay,
x = scaledBaseX + 15,
y = scaledBaseY + POKEBALL_OVERLAY_Y,
width = 109,
height = 68,
vOffset = 0,
textureHeight = 1744,
scale = VIEW_SCALE
)
blitk(
matrixStack = matrices,
texture = platformBase,
x = scaledBaseX + 13,
y = scaledBaseY + PLATFORM_Y,
width = 113,
height = 24,
textureHeight = 30,
scale = VIEW_SCALE
)
blitk(
matrixStack = matrices,
texture = platformShadow,
x = scaledBaseX + SHADOW_X,
y = scaledBaseY + SHADOW_Y,
width = 90,
height = 20,
scale = VIEW_SCALE
)
val fillTop = (viewY + 114F * VIEW_SCALE).toInt()
val fillBottom = (viewY + (HALF_OVERLAY_HEIGHT - 6) * VIEW_SCALE).toInt()
context.fill(viewX.toInt(), fillTop, (viewX + viewWidth).toInt(), fillBottom, 0xFF7AEEF1.toInt())
context.disableScissor()
buildRenderable()?.let { renderable ->
context.enableScissor(
x + 1,
y + 1,
x + HALF_OVERLAY_WIDTH - 1,
y + HALF_OVERLAY_HEIGHT - 1
)
matrices.pushPose()
matrices.translate(
x.toDouble() + (HALF_OVERLAY_WIDTH.toDouble()) / 2,
y.toDouble() + MODEL_OFFSET_Y,
1000.0
)
matrices.scale(MODEL_VIEW_SCALE, MODEL_VIEW_SCALE, MODEL_VIEW_SCALE)
val rotationVector = Vector3f(13F, rotationY, 0F)
drawProfilePokemon(
renderablePokemon = renderable,
poseType = poseList[selectedPoseIndex],
matrixStack = matrices,
partialTicks = delta,
rotation = Quaternionf().fromEulerXYZDegrees(rotationVector),
state = state,
scale = MODEL_DRAW_SCALE
)
refreshLocatorList()
if (!renderable.heldItem.isEmpty) {
heldItemRenderer.renderOnModel(
renderable.heldItem,
state,
matrices,
context.bufferSource(),
light = 0xF000F0,
frontLight = true
)
}
if (showLocators) {
renderLocators(context, mouseX, mouseY, showLocatorAxes)
}
matrices.popPose()
context.disableScissor()
}
showLocatorsButton.render(context, mouseX, mouseY, delta)
hideLocatorsButton.render(context, mouseX, mouseY, delta)
showLocatorAxesButton.render(context, mouseX, mouseY, delta)
hideLocatorAxesButton.render(context, mouseX, mouseY, delta)
if (showLocators && showLocatorAxes) {
renderAxisLegend(context)
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (isWithinViewport(mouseX, mouseY)) {
canDragRender = true
isDraggingRender = true
oldDragPosX = mouseX
return true
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun mouseReleased(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (canDragRender) {
canDragRender = false
}
if (isDraggingRender) {
isDraggingRender = false
}
return super.mouseReleased(pMouseX, pMouseY, pButton)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
val handledDrag = isDraggingRender && canDragRender
if (handledDrag) {
val dragOffsetY = (oldDragPosX - mouseX).toFloat()
rotationY = (((rotationY + dragOffsetY) % 360 + 360) % 360)
}
oldDragPosX = mouseX
return handledDrag || super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
private fun renderLocators(context: GuiGraphics, mouseX: Int, mouseY: Int, showAxes: Boolean) {
val bufferSource = Minecraft.getInstance().renderBuffers().bufferSource()
val buffer = bufferSource.getBuffer(RenderType.lines())
val baseMatrix = Matrix4f(context.pose().last().pose())
var closestName: String? = null
var closestDistance = LOCATOR_TOOLTIP_RADIUS.toFloat()
for ((name, matrix) in state.locatorStates.entries) {
val highlight = name == highlightedLocator
val origin = matrix.getOrigin()
context.pose().pushPose()
context.pose().mulPose(matrix.matrix)
LevelRenderer.renderLineBox(
context.pose(),
buffer,
AABB.ofSize(Vec3.ZERO, LOCATOR_CUBE_SIZE, LOCATOR_CUBE_SIZE, LOCATOR_CUBE_SIZE),
if (highlight) 1F else 0F,
if (highlight) 0F else 1F,
0F,
1F
)
if (showAxes) {
renderLocatorAxes(context, buffer)
}
context.pose().popPose()
val screenPos = projectToScreen(baseMatrix, origin) ?: continue
val dx = screenPos.first - mouseX
val dy = screenPos.second - mouseY
val dist = kotlin.math.sqrt((dx * dx + dy * dy).toDouble()).toFloat()
if (dist <= closestDistance) {
closestDistance = dist
closestName = name
}
}
bufferSource.endBatch(RenderType.lines())
if (closestName != null) {
context.renderTooltip(Minecraft.getInstance().font, Component.literal(closestName), mouseX, mouseY)
}
}
fun setHighlightedLocator(name: String?) {
highlightedLocator = name
}
private fun refreshLocatorList() {
val names = state.locatorStates.keys.sorted()
if (names != cachedLocatorNames) {
cachedLocatorNames = names
onDumpLocators(names)
}
}
private fun renderLocatorAxes(context: GuiGraphics, buffer: com.mojang.blaze3d.vertex.VertexConsumer) {
val pose = context.pose().last().pose()
val length = AXIS_LINE_LENGTH.toFloat()
addAxisLine(buffer, pose, 0f, 0f, 0f, length, 0f, 0f, 1f, 0f, 0f)
addAxisLine(buffer, pose, 0f, 0f, 0f, 0f, length, 0f, 0f, 1f, 0f)
addAxisLine(buffer, pose, 0f, 0f, 0f, 0f, 0f, length, 0f, 0f, 1f)
}
private fun addAxisLine(
buffer: com.mojang.blaze3d.vertex.VertexConsumer,
pose: Matrix4f,
x1: Float,
y1: Float,
z1: Float,
x2: Float,
y2: Float,
z2: Float,
r: Float,
g: Float,
b: Float
) {
buffer.addVertex(pose, x1, y1, z1).setColor(r, g, b, 1f).setNormal(0f, 1f, 0f)
buffer.addVertex(pose, x2, y2, z2).setColor(r, g, b, 1f).setNormal(0f, 1f, 0f)
}
private fun renderAxisLegend(context: GuiGraphics) {
val legendX = x + LEGEND_X
val legendY = y + LEGEND_Y
context.fill(legendX - 2, legendY - 2, legendX + 46, legendY + 16, 0xAA1B1B1B.toInt())
drawLegendLine(context, legendX, legendY, 0xFFFF4D4D.toInt(), "X")
drawLegendLine(context, legendX, legendY + 5, 0xFF4DFF4D.toInt(), "Y")
drawLegendLine(context, legendX, legendY + 10, 0xFF4D4DFF.toInt(), "Z")
}
private fun drawLegendLine(context: GuiGraphics, x: Int, y: Int, color: Int, label: String) {
context.fill(x, y + 2, x + 6, y + 3, color)
drawScaledText(
context = context,
text = Component.literal(label),
x = x + 8,
y = y,
scale = LEGEND_SCALE,
colour = 0xFFFFFFFF.toInt(),
shadow = false
)
}
private fun buildRenderable(): RenderablePokemon? {
val species = draft.species
val form = draft.form
val aspects = mutableSetOf<String>()
if (draft.shiny) aspects.add(SHINY_ASPECT.aspect)
aspects.addAll(form.aspects)
aspects.addAll(draft.forcedAspects)
aspects.addAll(draft.flagFeatures)
aspects.addAll(draft.cosmeticAspects)
draft.choiceFeatures.forEach { (key, choice) ->
choiceAspectId(key, choice)?.let { aspects.add(it) }
}
when (draft.gender) {
Gender.MALE -> aspects.add("male")
Gender.FEMALE -> aspects.add("female")
Gender.GENDERLESS -> aspects.add("genderless")
}
val heldStack = draft.heldItem
?.let { ResourceLocation.tryParse(it) }
?.let { id -> BuiltInRegistries.ITEM.getOptional(id).orElse(null) }
?.let { item -> ItemStack(item) }
?: ItemStack.EMPTY
renderablePokemon = RenderablePokemon(species, aspects, heldStack).also { recalculatePoses(it) }
return renderablePokemon
}
private fun recalculatePoses(renderablePokemon: RenderablePokemon) {
val poseState = FloatingState().also { it.currentAspects = renderablePokemon.aspects }
val poser = VaryingModelRepository.getPoser(renderablePokemon.species.resourceIdentifier, poseState)
poseList = poser.poses
.map { it.value.poseTypes.minBy { it.ordinal } }
.toSet()
.filterNot { it in PoseType.SHOULDER_POSES }
.sortedBy { it.ordinal }
.toTypedArray()
if (selectedPoseIndex !in poseList.indices) {
selectedPoseIndex = 0
}
}
private fun currentHeldStack(): ItemStack {
return renderablePokemon?.heldItem ?: ItemStack.EMPTY
}
private fun projectToScreen(baseMatrix: Matrix4f, position: Vec3): Pair<Float, Float>? {
val projection = RenderSystem.getProjectionMatrix()
val vec = Vector4f(position.x.toFloat(), position.y.toFloat(), position.z.toFloat(), 1f)
vec.mul(baseMatrix)
vec.mul(projection)
if (vec.w == 0f) return null
vec.div(vec.w)
val window = Minecraft.getInstance().window
val screenX = (vec.x * 0.5f + 0.5f) * window.guiScaledWidth.toFloat()
val screenY = (1f - (vec.y * 0.5f + 0.5f)) * window.guiScaledHeight.toFloat()
return Pair(screenX, screenY)
}
private fun isWithinViewport(mouseX: Double, mouseY: Double): Boolean =
mouseX.toInt() in x..(x + HALF_OVERLAY_WIDTH)
&& mouseY.toInt() in y..(y + HALF_OVERLAY_HEIGHT)
&& children.none { child ->
child is AbstractWidget && child.visible && child.isMouseOver(mouseX, mouseY)
}
}
@@ -0,0 +1,659 @@
/*
* 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.gui.pokeedit.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.pokeedit.PokeEditDraft
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.common.SummaryScrollList
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MoveDescriptionScrollList
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.ReorderMoveButton
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.toRGB
import java.math.RoundingMode
import java.text.DecimalFormat
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.util.Mth
class PokeEditMovesTab(
pX: Int,
pY: Int,
private val draft: PokeEditDraft
) : SoundlessWidget(pX, pY, WIDTH, HEIGHT, lang("ui.moves")) {
companion object {
private const val WIDTH = 139
private const val HEIGHT = 163
private const val BASE_WIDTH = 134
private const val BASE_HEIGHT = 148
private const val BASE_SCALE = 0.80F
private const val SLOT_SPACING = 3
private const val SCALE = 0.5F
private val decimalFormat = DecimalFormat("#.##").also {
it.roundingMode = RoundingMode.CEILING
}
private val movesBaseResource = cobblemonResource("textures/gui/summary/summary_moves_base.png")
private val moveResource = cobblemonResource("textures/gui/summary/summary_move.png")
private val moveOverlayResource = cobblemonResource("textures/gui/summary/summary_move_overlay.png")
private val moveSelectedOverlayResource = cobblemonResource("textures/gui/summary/summary_move_selected_overlay.png")
private val moveSwapButtonResource = cobblemonResource("textures/gui/summary/summary_move_swap.png")
private val moveAddButtonResource = cobblemonResource("textures/gui/summary/summary_move_add.png")
private val movesPowerIconResource = cobblemonResource("textures/gui/summary/summary_moves_icon_power.png")
private val movesAccuracyIconResource = cobblemonResource("textures/gui/summary/summary_moves_icon_accuracy.png")
private val movesEffectIconResource = cobblemonResource("textures/gui/summary/summary_moves_icon_effect.png")
private val moveListResource = cobblemonResource("textures/gui/summary/summary_move_condensed.png")
private val moveListOverlayResource = cobblemonResource("textures/gui/summary/summary_move_overlay_condensed.png")
private const val MOVE_WIDTH = 108
private const val MOVE_HEIGHT = 22
private const val MOVE_LIST_SLOT_HEIGHT = 18
private const val MOVE_LIST_SLOT_SPACING = 3
private const val MOVE_LIST_X_OFFSET = 0
private const val MOVE_LIST_Y_OFFSET = 0
}
private val baseScaledWidth = (BASE_WIDTH * BASE_SCALE).toInt()
private val baseScaledHeight = (BASE_HEIGHT * BASE_SCALE).toInt()
private val moveWidthScaled = (MOVE_WIDTH * BASE_SCALE).toInt()
private val moveHeightScaled = (MOVE_HEIGHT * BASE_SCALE).toInt()
private val slotSpacingScaled = (SLOT_SPACING * BASE_SCALE).toInt().coerceAtLeast(1)
private val baseX = x + (WIDTH - baseScaledWidth) / 2 - 10
private val baseY = y + 7
private val slotX = baseX + (13 * BASE_SCALE).toInt()
private val slotY = baseY + (6 * BASE_SCALE).toInt()
private val descriptionListX = (baseX + (69 * BASE_SCALE).toInt())
.coerceAtMost(baseX + baseScaledWidth - 60)
private val descriptionListY = baseY + (113 * BASE_SCALE).toInt()
private val slots = (0 until 4).map { index ->
MoveSlot(
slotX,
slotY + (moveHeightScaled + slotSpacingScaled) * index,
index
).also { addWidget(it) }
}
private val descriptionList = MoveDescriptionScrollList(
descriptionListX,
descriptionListY,
5
)
private val movePicker = MovePickerList(
baseX + baseScaledWidth + MOVE_LIST_X_OFFSET,
baseY + (11 * BASE_SCALE).toInt() + MOVE_LIST_Y_OFFSET
)
private var selectedSlotIndex = -1
private var pickerSlotIndex = -1
private var movePickerOpen = false
private var lastDescriptionKey: String? = null
init {
refreshMoves()
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val matrices = context.pose()
blitk(
matrixStack = matrices,
texture = movesBaseResource,
x = baseX,
y = baseY,
width = baseScaledWidth,
height = baseScaledHeight
)
slots.forEach { it.render(context, mouseX, mouseY, delta) }
renderMoveInfo(context)
renderDescription(context, mouseX, mouseY, delta)
if (movePickerOpen) {
movePicker.renderWidget(context, mouseX, mouseY, delta)
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (movePickerOpen) {
val handled = movePicker.mouseClicked(mouseX, mouseY, button)
if (handled) return true
if (!movePicker.isMouseOver(mouseX, mouseY)) {
closeMovePicker()
}
}
if (descriptionList.isHovered) {
descriptionList.mouseClicked(mouseX, mouseY, button)
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun mouseScrolled(mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double): Boolean {
if (movePickerOpen) {
if (movePicker.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)) return true
}
if (descriptionList.isHovered) {
descriptionList.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)
}
return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
if (movePickerOpen) {
if (movePicker.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) return true
}
if (descriptionList.isHovered) {
descriptionList.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
private fun refreshMoves() {
val moves = Moves.all().sortedBy { it.displayName.string }
movePicker.setEntries(moves)
}
private fun selectedMove(): MoveTemplate? {
return if (selectedSlotIndex in draft.activeMoves.indices) draft.activeMoves[selectedSlotIndex] else null
}
private fun renderMoveInfo(context: GuiGraphics) {
val move = selectedMove()
val mcFont = Minecraft.getInstance().font
// Power
blitk(
matrixStack = context.pose(),
texture = movesPowerIconResource,
x = (baseX + (7 * BASE_SCALE)) / SCALE,
y = (baseY + (114.5 * BASE_SCALE)) / SCALE,
width = 10,
height = 10,
scale = SCALE
)
drawScaledText(
context = context,
text = lang("ui.power"),
x = baseX + (14 * BASE_SCALE),
y = baseY + (115 * BASE_SCALE),
scale = SCALE * BASE_SCALE,
shadow = true
)
val powerText = if (move != null && move.power.toInt() > 0) move.power.toInt().toString() else "-"
val powerComponent = Component.literal(powerText)
drawScaledText(
context = context,
text = powerComponent,
x = (baseX + (62.5 * BASE_SCALE)) - (mcFont.width(powerComponent) * SCALE * BASE_SCALE),
y = baseY + (115 * BASE_SCALE),
scale = SCALE * BASE_SCALE,
shadow = true
)
// Accuracy
blitk(
matrixStack = context.pose(),
texture = movesAccuracyIconResource,
x = (baseX + (7 * BASE_SCALE)) / SCALE,
y = (baseY + (125.5 * BASE_SCALE)) / SCALE,
width = 10,
height = 10,
scale = SCALE
)
drawScaledText(
context = context,
text = lang("ui.accuracy"),
x = baseX + (14 * BASE_SCALE),
y = baseY + (126 * BASE_SCALE),
scale = SCALE * BASE_SCALE,
shadow = true
)
val accuracyText = if (move != null) formatPercent(move.accuracy) else "-"
val accuracyComponent = Component.literal(accuracyText)
drawScaledText(
context = context,
text = accuracyComponent,
x = (baseX + (62.5 * BASE_SCALE)) - (mcFont.width(accuracyComponent) * SCALE * BASE_SCALE),
y = baseY + (126 * BASE_SCALE),
scale = SCALE * BASE_SCALE,
shadow = true
)
// Category
blitk(
matrixStack = context.pose(),
texture = movesEffectIconResource,
x = (baseX + (7 * BASE_SCALE)) / SCALE,
y = (baseY + (136.5 * BASE_SCALE)) / SCALE,
width = 10,
height = 10,
scale = SCALE
)
drawScaledText(
context = context,
text = lang("ui.category"),
x = baseX + (14 * BASE_SCALE),
y = baseY + (137 * BASE_SCALE),
scale = SCALE * BASE_SCALE,
shadow = true
)
if (move != null) {
MoveCategoryIcon(
x = baseX + (51 * BASE_SCALE),
y = baseY + (137 * BASE_SCALE),
category = move.damageCategory
).render(context)
drawScaledText(
context = context,
text = move.damageCategory.displayName.copy(),
x = baseX + (61 * BASE_SCALE),
y = baseY + (137 * BASE_SCALE),
scale = SCALE * BASE_SCALE,
shadow = true
)
}
}
private fun renderDescription(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val move = selectedMove()
val nextKey = move?.name ?: ""
if (nextKey != lastDescriptionKey) {
descriptionList.setMoveDescription(move?.description ?: Component.literal(""))
lastDescriptionKey = nextKey
}
if (move != null) {
descriptionList.renderWidget(context, mouseX, mouseY, delta)
}
}
private fun formatPercent(input: Double): String {
if (input <= 0) return "-"
return "${decimalFormat.format(input)}%"
}
private fun toggleMovePicker(slotIndex: Int) {
selectedSlotIndex = slotIndex
if (movePickerOpen && pickerSlotIndex == slotIndex) {
closeMovePicker()
return
}
movePickerOpen = true
pickerSlotIndex = slotIndex
}
private fun closeMovePicker() {
movePickerOpen = false
pickerSlotIndex = -1
}
private fun assignMove(slotIndex: Int, move: MoveTemplate) {
draft.activeMoves.indices.forEach { index ->
if (index != slotIndex && draft.activeMoves[index] == move) {
draft.activeMoves[index] = null
}
}
draft.activeMoves[slotIndex] = move
}
private fun reorderMove(slotIndex: Int, up: Boolean) {
val size = draft.activeMoves.size
if (size == 0) return
val target = if (up) {
if (slotIndex > 0) slotIndex - 1 else size - 1
} else {
if (slotIndex < size - 1) slotIndex + 1 else 0
}
val temp = draft.activeMoves[target]
draft.activeMoves[target] = draft.activeMoves[slotIndex]
draft.activeMoves[slotIndex] = temp
if (selectedSlotIndex == slotIndex) selectedSlotIndex = target
else if (selectedSlotIndex == target) selectedSlotIndex = slotIndex
if (pickerSlotIndex == slotIndex) pickerSlotIndex = target
else if (pickerSlotIndex == target) pickerSlotIndex = slotIndex
}
private inner class MoveSlot(
pX: Int,
pY: Int,
private val slotIndex: Int
) : SoundlessWidget(pX, pY, moveWidthScaled, moveHeightScaled, Component.literal("move-slot-$slotIndex")) {
private val moveUpButton = ReorderMoveButton(x, y, true) {
reorderMove(slotIndex, true)
}.apply { addWidget(this) }
private val moveDownButton = ReorderMoveButton(x, y, false) {
reorderMove(slotIndex, false)
}.apply { addWidget(this) }
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val move = draft.activeMoves[slotIndex]
val matrices = context.pose()
isHovered = mouseX >= x && mouseY >= y && mouseX < x + width && mouseY < y + height
if (move != null) {
val rgb = move.elementalType.hue.toRGB()
if (selectedSlotIndex == slotIndex) {
blitk(
matrixStack = matrices,
texture = moveSelectedOverlayResource,
x = x - 1,
y = y - 1,
width = moveWidthScaled + 2,
height = moveHeightScaled + 2
)
}
blitk(
matrixStack = matrices,
texture = moveResource,
x = x,
y = y,
width = moveWidthScaled,
height = moveHeightScaled,
vOffset = if (isHovered) moveHeightScaled else 0,
textureHeight = moveHeightScaled * 2,
red = rgb.first,
green = rgb.second,
blue = rgb.third
)
blitk(
matrixStack = matrices,
texture = moveOverlayResource,
x = x,
y = y,
width = moveWidthScaled,
height = moveHeightScaled
)
val ppText = Component.literal("${move.pp}/${move.maxPp}").bold()
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = ppText,
x = x + (93 * BASE_SCALE),
y = y + (13 * BASE_SCALE),
centered = true,
scale = BASE_SCALE
)
TypeIcon(
x = x + (2 * BASE_SCALE),
y = y + (2 * BASE_SCALE),
type = move.elementalType
).render(context)
MoveCategoryIcon(
x = x + (66 * BASE_SCALE),
y = y + (13.5 * BASE_SCALE),
category = move.damageCategory
).render(context)
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = move.displayName.bold(),
x = x + (28 * BASE_SCALE),
y = y + (2 * BASE_SCALE),
shadow = true,
scale = BASE_SCALE
)
moveUpButton.render(context, mouseX, mouseY, delta)
moveDownButton.render(context, mouseX, mouseY, delta)
}
renderSwapButton(context, mouseX, mouseY, move)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (isSwapHovered(mouseX, mouseY)) {
toggleMovePicker(slotIndex)
return true
}
if (isHovered) {
selectedSlotIndex = slotIndex
}
return super.mouseClicked(mouseX, mouseY, button)
}
private fun renderSwapButton(context: GuiGraphics, mouseX: Int, mouseY: Int, move: MoveTemplate?) {
val selected = movePickerOpen && pickerSlotIndex == slotIndex
val swapOffsetX = 114.5F * BASE_SCALE
val swapOffsetY = 6.5F * BASE_SCALE
val swapWidth = 12F * BASE_SCALE
val swapHeight = 18F * BASE_SCALE
blitk(
matrixStack = context.pose(),
texture = if (move == null) moveAddButtonResource else moveSwapButtonResource,
x = (x + swapOffsetX) / SCALE,
y = (y + swapOffsetY) / SCALE,
width = swapWidth,
height = swapHeight,
vOffset = if (isSwapHovered(mouseX.toDouble(), mouseY.toDouble()) || selected) swapHeight else 0F,
textureHeight = swapHeight * 2,
scale = SCALE
)
}
private fun isSwapHovered(mouseX: Double, mouseY: Double): Boolean {
val swapOffsetX = 114.5F * BASE_SCALE
val swapOffsetY = 6.5F * BASE_SCALE
val swapWidth = 12F * BASE_SCALE
val swapHeight = 18F * BASE_SCALE
val swapX = x + swapOffsetX
val swapY = y + swapOffsetY
return mouseX.toFloat() in (swapX..(swapX + (swapWidth * SCALE)))
&& mouseY.toFloat() in (swapY..(swapY + (swapHeight * SCALE) - 0.5F))
}
}
private inner class MovePickerList(
listX: Int,
listY: Int
) : SummaryScrollList<MovePickerList.MoveEntry>(
listX,
listY,
lang("ui.moves.switch"),
MOVE_LIST_SLOT_HEIGHT + MOVE_LIST_SLOT_SPACING
) {
private var entries: List<MoveTemplate> = emptyList()
fun setEntries(moves: List<MoveTemplate>) {
clearEntries()
entries = moves
moves.forEach { move -> addEntry(MoveEntry(move)) }
scrollAmount = 0.0
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
if (!movePickerOpen) return
super.renderWidget(context, mouseX, mouseY, partialTicks)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (!movePickerOpen) return false
return super.mouseClicked(mouseX, mouseY, button)
}
override fun mouseScrolled(mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double): Boolean {
if (!movePickerOpen) return false
return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
if (!movePickerOpen) return false
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
inner class MoveEntry(private val move: MoveTemplate) : Entry<MoveEntry>() {
override fun getNarration(): MutableComponent = move.displayName.copy()
override fun render(
context: GuiGraphics,
index: Int,
rowTop: Int,
rowLeft: Int,
rowWidth: Int,
rowHeight: Int,
mouseX: Int,
mouseY: Int,
isHovered: Boolean,
partialTicks: Float
) {
val matrices = context.pose()
val tweakedRowTop = rowTop - (MOVE_LIST_SLOT_SPACING / 2) + 1
val rgb = move.elementalType.hue.toRGB()
blitk(
matrixStack = matrices,
texture = moveListResource,
x = rowLeft,
y = tweakedRowTop,
height = MOVE_LIST_SLOT_HEIGHT,
width = rowWidth,
vOffset = if (isHovered) MOVE_LIST_SLOT_HEIGHT else 0,
textureHeight = MOVE_LIST_SLOT_HEIGHT * 2,
red = rgb.first,
green = rgb.second,
blue = rgb.third
)
blitk(
matrixStack = matrices,
texture = moveListOverlayResource,
x = rowLeft,
y = tweakedRowTop,
height = MOVE_LIST_SLOT_HEIGHT,
width = rowWidth
)
TypeIcon(
x = rowLeft - 9,
y = tweakedRowTop,
type = move.elementalType
).render(context)
MoveCategoryIcon(
x = rowLeft + 77,
y = tweakedRowTop + 1.5,
category = move.damageCategory
).render(context)
drawScaledText(
context = context,
text = move.displayName,
x = rowLeft + 14,
y = tweakedRowTop + 3.5,
scale = SCALE,
shadow = true
)
blitk(
matrixStack = matrices,
texture = movesPowerIconResource,
x = (rowLeft + 10) / SCALE,
y = (tweakedRowTop + 11) / SCALE,
width = 10,
height = 10,
scale = SCALE
)
blitk(
matrixStack = matrices,
texture = movesAccuracyIconResource,
x = (rowLeft + 30) / SCALE,
y = (tweakedRowTop + 11) / SCALE,
width = 10,
height = 10,
scale = SCALE
)
blitk(
matrixStack = matrices,
texture = movesEffectIconResource,
x = (rowLeft + 53.5) / SCALE,
y = (tweakedRowTop + 11) / SCALE,
width = 10,
height = 10,
scale = SCALE
)
val movePower = if (move.power.toInt() > 0) move.power.toInt().toString() else "-"
drawScaledText(
context = context,
text = Component.literal(movePower),
x = rowLeft + 16.5,
y = tweakedRowTop + 12,
scale = SCALE,
shadow = true
)
drawScaledText(
context = context,
text = Component.literal(formatPercent(move.accuracy)),
x = rowLeft + 37,
y = tweakedRowTop + 12,
scale = SCALE,
shadow = true
)
drawScaledText(
context = context,
text = Component.literal(formatPercent(move.effectChances.firstOrNull() ?: 0.0)),
x = rowLeft + 60.5,
y = tweakedRowTop + 12,
scale = SCALE,
shadow = true
)
val pp = move.maxPp
drawScaledText(
context = context,
text = lang("ui.moves.pp", pp),
x = rowLeft + 76,
y = tweakedRowTop + 12,
scale = SCALE,
shadow = true
)
}
override fun mouseClicked(d: Double, e: Double, i: Int): Boolean {
if (isMouseOver(d, e) && pickerSlotIndex >= 0) {
assignMove(pickerSlotIndex, move)
selectedSlotIndex = pickerSlotIndex
closeMovePicker()
return true
}
return false
}
}
}
}
@@ -0,0 +1,212 @@
/*
* 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.gui.pokeedit.widgets
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.gui.ScrollingWidget
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCROLL_BASE_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCROLL_SLOT_SIZE
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCROLL_SLOT_SPACING
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCALE
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
import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.util.Mth
import org.joml.Quaternionf
import org.joml.Vector3f
class PokeEditSpeciesList(
val pX: Int,
val pY: Int,
val onSelect: (Species) -> Unit
) : ScrollingWidget<PokeEditSpeciesList.SpeciesRow>(
width = 139,
height = SCROLL_BASE_HEIGHT,
left = pX,
top = pY - SCROLL_BASE_HEIGHT,
slotHeight = SCROLL_SLOT_SIZE + 2
) {
private var entries: List<Species> = emptyList()
fun setEntries(species: List<Species>) {
clearEntries()
entries = species
species.chunked(5).forEach { chunk ->
addEntry(SpeciesRow(chunk, onSelect))
}
scrollAmount = 0.0
}
override fun addEntry(entry: SpeciesRow): Int = super.addEntry(entry)
override fun getScrollbarPosition(): Int = pX + width - 3
override fun renderScrollbar(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val xLeft = this.scrollbarPosition
val xRight = xLeft + 3
val barHeight = this.bottom - this.y
var yBottom = ((barHeight * barHeight).toFloat() / this.maxPosition.toFloat()).toInt()
yBottom = Mth.clamp(yBottom, 32, barHeight - 8)
var yTop = scrollAmount.toInt() * (barHeight - yBottom) / this.maxScroll + this.y
if (yTop < this.y) yTop = this.y
context.fill(xLeft, this.y + 3, xRight, this.bottom - 3, 0xFF3A96B6.toInt())
context.fill(xLeft, yTop + 3, xRight, yTop + yBottom - 3, 0xFFFCFCFC.toInt())
}
class SpeciesRow(
private val speciesList: List<Species>,
private val onSelect: (Species) -> Unit
) : Slot<SpeciesRow>() {
companion object {
private val slotResource = cobblemonResource("textures/gui/pokedex/pokedex_slot.png")
private val slotHighlight = cobblemonResource("textures/gui/pokedex/slot_select.png")
}
private val runtime = MoLangRuntime().setupClient().setup().also {
it.environment.query.addFunction("get_pokedex") { null }
}
private var x: Int = 0
private var y: Int = 0
override fun render(
context: GuiGraphics,
index: Int,
y: Int,
x: Int,
entryWidth: Int,
entryHeight: Int,
mouseX: Int,
mouseY: Int,
hovered: Boolean,
tickDelta: Float
) {
this.x = x
this.y = y
speciesList.forEachIndexed { indexInRow, species ->
val startPosX = x + ((SCROLL_SLOT_SPACING + SCROLL_SLOT_SIZE) * indexInRow)
val startPosY = y + SCROLL_SLOT_SPACING + 1
blitk(
matrixStack = context.pose(),
texture = slotResource,
x = startPosX,
y = startPosY,
width = SCROLL_SLOT_SIZE,
height = SCROLL_SLOT_SIZE
)
if (getHoveredSlotIndex(mouseX, mouseY) == indexInRow) {
blitk(
matrixStack = context.pose(),
texture = slotHighlight,
x = startPosX,
y = startPosY,
width = SCROLL_SLOT_SIZE,
height = SCROLL_SLOT_SIZE,
vOffset = SCROLL_SLOT_SIZE,
textureHeight = SCROLL_SLOT_SIZE * 2
)
}
val form = species.standardForm
val aspects = mutableSetOf<String>()
aspects.addAll(form.aspects)
if (form.maleRatio in 0F..1F) {
aspects.add(Gender.MALE.name.lowercase())
} else {
aspects.add(Gender.GENDERLESS.name.lowercase())
}
context.enableScissor(
startPosX + 1,
startPosY + 1,
startPosX + SCROLL_SLOT_SIZE - 1,
startPosY + SCROLL_SLOT_SIZE - 2
)
val state = FloatingState().also { it.currentAspects = aspects }
val matrices = context.pose()
matrices.pushPose()
matrices.translate(startPosX + (SCROLL_SLOT_SIZE / 2.0), startPosY + 1.0, 0.0)
matrices.scale(2.5F, 2.5F, 1F)
drawProfilePokemon(
renderablePokemon = RenderablePokemon(species, aspects),
matrixStack = matrices,
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = state,
partialTicks = 0F,
scale = 4.5F
)
matrices.popPose()
context.disableScissor()
matrices.pushPose()
matrices.translate(0.0, 0.0, 100.0)
val pokemonNumber = species.nationalPokedexNumber.toString().padStart(4, '0').text()
drawScaledText(
context = context,
text = pokemonNumber,
x = startPosX + 1.5,
y = startPosY + 2.5,
shadow = true,
scale = SCALE
)
matrices.popPose()
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
val hoverIndex = getHoveredSlotIndex(mouseX.toInt(), mouseY.toInt())
if (hoverIndex in speciesList.indices) {
onSelect(speciesList[hoverIndex])
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(com.cobblemon.mod.common.CobblemonSounds.POKEDEX_CLICK, 1.0F))
}
return true
}
private fun getHoveredSlotIndex(mouseX: Int, mouseY: Int): Int {
speciesList.forEachIndexed { index, _ ->
val startPosX = x + ((SCROLL_SLOT_SPACING + SCROLL_SLOT_SIZE) * index)
val startPosY = y + SCROLL_SLOT_SPACING + 1
if (mouseX in startPosX..(startPosX + SCROLL_SLOT_SIZE)
&& mouseY in startPosY..(startPosY + SCROLL_SLOT_SIZE)
) {
return index
}
}
return -1
}
override fun getNarration(): Component {
return if (speciesList.isNotEmpty()) {
"${speciesList.first().name}-${speciesList.last().name}".text()
} else {
"".text()
}
}
}
}
@@ -0,0 +1,445 @@
/*
* 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.gui.pokeedit.widgets
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
import com.cobblemon.mod.common.client.gui.pokeedit.PokeEditDraft
import com.cobblemon.mod.common.client.gui.pokeedit.widgets.PokeEditAspectsTab.Companion.choiceAspectId
import com.cobblemon.mod.common.client.gui.summary.SummaryButton
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.item.HeldItemRenderer
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.pokemon.aspects.SHINY_ASPECT
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import com.cobblemon.mod.common.util.resolveFloat
import com.mojang.math.Axis
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.platform.Lighting
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.AbstractWidget
import net.minecraft.client.gui.components.EditBox
import net.minecraft.client.renderer.LightTexture
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.Blocks
import org.joml.Quaternionf
import org.joml.Vector3f
import java.util.Locale
class PokeEditWalkSpeedTab(
pX: Int,
pY: Int,
private val draft: PokeEditDraft
) : SoundlessWidget(pX, pY, HALF_OVERLAY_WIDTH, HALF_OVERLAY_HEIGHT, Component.literal("Walk Speed")) {
companion object {
private val backgroundOverlay = cobblemonResource("textures/gui/pokedex/pokedex_screen_info_overlay.png")
private val pokeBallOverlay = cobblemonResource("textures/gui/pokedex/pokedex_screen_poke_ball.png")
private val platformBase = cobblemonResource("textures/gui/pokedex/platform_base.png")
private val platformShadow = cobblemonResource("textures/gui/pokedex/platform_shadow.png")
private val actionButtonResource = cobblemonResource("textures/gui/summary/summary_evolve_button.png")
private const val POKEBALL_OVERLAY_Y = 25
private const val PLATFORM_Y = 69
private const val SHADOW_X = 25
private const val SHADOW_Y = 76
private const val VIEW_SCALE = 1.2F
private const val VIEW_SHIFT_X = 0F
private const val VIEW_SHIFT_Y = 36F
private const val MODEL_VIEW_SCALE = 2.4F
private const val MODEL_DRAW_SCALE = 24F
private const val MODEL_OFFSET_Y = 20.0
private const val SPEED_INPUT_WIDTH = 40
private const val SPEED_INPUT_HEIGHT = 10
private const val SPEED_LABEL_SCALE = 0.5F
private const val BUTTON_WIDTH = 54
private const val BUTTON_HEIGHT = 15
private const val BLOCK_SPACING = 1.0F
private const val BLOCK_PLANE_OFFSET = 2.3
private const val BLOCK_COLUMNS = 7
private const val BLOCK_ROWS = 3
}
private val state = FloatingState().also { state ->
state.runtime.environment.query.addFunction("is_holding_item") {
val held = currentHeldStack()
DoubleValue(!held.isEmpty && !held.`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS) && !held.`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS))
}
state.runtime.environment.query.addFunction("is_wearing_hat") {
DoubleValue(currentHeldStack().`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS))
}
state.runtime.environment.query.addFunction("is_wearing_face") {
DoubleValue(currentHeldStack().`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS))
}
}
private val heldItemRenderer = HeldItemRenderer()
private var renderablePokemon: RenderablePokemon? = null
private var rotationY = 30F
private var poseList: Array<PoseType> = arrayOf(PoseType.WALK, PoseType.PROFILE)
private var selectedPoseIndex: Int = 0
private var oldDragPosX = 0.0
private var canDragRender = false
private var isDraggingRender = false
private var cachedSpeedKey: String? = null
private var defaultWalkSpeed = 0.35F
private var customWalkSpeed: Float? = null
private var syncingSpeedInput = false
private val walkSpeedInput = EditBox(
Minecraft.getInstance().font,
pX + 6,
pY + 18,
SPEED_INPUT_WIDTH,
SPEED_INPUT_HEIGHT,
Component.literal("speed")
).also { editBox ->
editBox.setBordered(false)
editBox.setTextColor(0xFFFFFF)
editBox.setTextColorUneditable(0xFFFFFF)
editBox.setMaxLength(6)
editBox.active = true
editBox.visible = true
editBox.setFilter { value ->
value.isBlank() || value.matches(Regex("\\d*(\\.\\d*)?"))
}
editBox.setResponder { value ->
if (syncingSpeedInput) return@setResponder
customWalkSpeed = value.toFloatOrNull()
}
addWidget(editBox)
}
private val resetSpeedButton = SummaryButton(
buttonX = pX + 52F,
buttonY = pY + 14F,
buttonWidth = BUTTON_WIDTH,
buttonHeight = BUTTON_HEIGHT,
clickAction = { resetWalkSpeed() },
text = Component.literal("Reset"),
resource = actionButtonResource,
largeText = false,
boldText = false,
textScale = 0.7F
).also { addWidget(it) }
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
refreshDefaultWalkSpeed()
walkSpeedInput.active = true
walkSpeedInput.visible = true
if (!walkSpeedInput.isFocused && customWalkSpeed == null) {
val formatted = formatSpeed(defaultWalkSpeed)
if (walkSpeedInput.value != formatted) {
syncingSpeedInput = true
walkSpeedInput.value = formatted
syncingSpeedInput = false
}
}
val matrices = context.pose()
context.enableScissor(x, y, x + HALF_OVERLAY_WIDTH, y + HALF_OVERLAY_HEIGHT)
val viewWidth = HALF_OVERLAY_WIDTH * VIEW_SCALE
val viewHeight = HALF_OVERLAY_HEIGHT * VIEW_SCALE
val viewX = x + (HALF_OVERLAY_WIDTH - viewWidth) / 2F + VIEW_SHIFT_X
val viewY = y + (HALF_OVERLAY_HEIGHT - viewHeight) / 2F + VIEW_SHIFT_Y
val scaledBaseX = viewX / VIEW_SCALE
val scaledBaseY = viewY / VIEW_SCALE
blitk(
matrixStack = matrices,
texture = backgroundOverlay,
x = scaledBaseX,
y = scaledBaseY,
width = HALF_OVERLAY_WIDTH,
height = HALF_OVERLAY_HEIGHT,
scale = VIEW_SCALE
)
blitk(
matrixStack = matrices,
texture = pokeBallOverlay,
x = scaledBaseX + 15,
y = scaledBaseY + POKEBALL_OVERLAY_Y,
width = 109,
height = 68,
vOffset = 0,
textureHeight = 1744,
scale = VIEW_SCALE
)
blitk(
matrixStack = matrices,
texture = platformBase,
x = scaledBaseX + 13,
y = scaledBaseY + PLATFORM_Y,
width = 113,
height = 24,
textureHeight = 30,
scale = VIEW_SCALE
)
blitk(
matrixStack = matrices,
texture = platformShadow,
x = scaledBaseX + SHADOW_X,
y = scaledBaseY + SHADOW_Y,
width = 90,
height = 20,
scale = VIEW_SCALE
)
val fillTop = (viewY + 114F * VIEW_SCALE).toInt()
val fillBottom = (viewY + (HALF_OVERLAY_HEIGHT - 6) * VIEW_SCALE).toInt()
context.fill(viewX.toInt(), fillTop, (viewX + viewWidth).toInt(), fillBottom, 0xFF7AEEF1.toInt())
context.disableScissor()
walkSpeedInput.render(context, mouseX, mouseY, delta)
resetSpeedButton.render(context, mouseX, mouseY, delta)
drawScaledText(
context = context,
text = Component.literal("Walk Speed"),
x = x + 6,
y = y + 6,
scale = SPEED_LABEL_SCALE,
colour = 0xFFFFFFFF.toInt(),
shadow = false
)
buildRenderable()?.let { renderable ->
state.currentAspects = renderable.aspects
context.enableScissor(
x + 1,
y + 1,
x + HALF_OVERLAY_WIDTH - 1,
y + HALF_OVERLAY_HEIGHT - 1
)
matrices.pushPose()
matrices.translate(
x.toDouble() + (HALF_OVERLAY_WIDTH.toDouble()) / 2,
y.toDouble() + MODEL_OFFSET_Y,
1000.0
)
matrices.scale(MODEL_VIEW_SCALE, MODEL_VIEW_SCALE, MODEL_VIEW_SCALE)
renderWalkBlocks(context, renderable, effectiveWalkSpeed(), delta)
val rotationVector = Vector3f(13F, rotationY, 0F)
drawProfilePokemon(
renderablePokemon = renderable,
poseType = poseList[selectedPoseIndex],
matrixStack = matrices,
partialTicks = delta,
rotation = Quaternionf().fromEulerXYZDegrees(rotationVector),
state = state,
scale = MODEL_DRAW_SCALE
)
if (!renderable.heldItem.isEmpty) {
heldItemRenderer.renderOnModel(
renderable.heldItem,
state,
matrices,
context.bufferSource(),
light = 0xF000F0,
frontLight = true
)
}
matrices.popPose()
context.disableScissor()
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (isWithinViewport(mouseX, mouseY)) {
canDragRender = true
isDraggingRender = true
oldDragPosX = mouseX
return true
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun mouseReleased(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (canDragRender) {
canDragRender = false
}
if (isDraggingRender) {
isDraggingRender = false
}
return super.mouseReleased(pMouseX, pMouseY, pButton)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
val handledDrag = isDraggingRender && canDragRender
if (handledDrag) {
val dragOffsetY = (oldDragPosX - mouseX).toFloat()
rotationY = (((rotationY + dragOffsetY) % 360 + 360) % 360)
}
oldDragPosX = mouseX
return handledDrag || super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
private fun resetWalkSpeed() {
customWalkSpeed = null
syncingSpeedInput = true
walkSpeedInput.value = formatSpeed(defaultWalkSpeed)
syncingSpeedInput = false
}
private fun refreshDefaultWalkSpeed() {
val key = "${draft.species.resourceIdentifier}#${draft.form.formOnlyShowdownId()}"
if (key == cachedSpeedKey) return
cachedSpeedKey = key
defaultWalkSpeed = resolveDefaultWalkSpeed()
if (customWalkSpeed == null) {
syncingSpeedInput = true
walkSpeedInput.value = formatSpeed(defaultWalkSpeed)
syncingSpeedInput = false
}
}
private fun resolveDefaultWalkSpeed(): Float {
return try {
state.runtime.resolveFloat(draft.form.behaviour.moving.walk.walkSpeed).coerceAtLeast(0.0F)
} catch (_: Exception) {
0.35F
}
}
private fun effectiveWalkSpeed(): Float {
val speed = customWalkSpeed ?: defaultWalkSpeed
return if (speed.isFinite() && speed > 0F) speed else defaultWalkSpeed
}
private fun formatSpeed(value: Float): String = String.format(Locale.US, "%.2f", value)
private fun renderWalkBlocks(context: GuiGraphics, renderable: RenderablePokemon, speed: Float, delta: Float) {
val model = VaryingModelRepository.getPoser(renderable.species.resourceIdentifier, state)
val ticks = (Minecraft.getInstance().level?.gameTime ?: (System.currentTimeMillis() / 50L)).toFloat() + delta
val offset = (ticks * speed) % BLOCK_SPACING
val travel = offset
val halfCols = BLOCK_COLUMNS / 2
val halfRows = BLOCK_ROWS / 2
val blockState = Blocks.GRASS_BLOCK.defaultBlockState()
val bufferSource = Minecraft.getInstance().renderBuffers().bufferSource()
val packedLight = LightTexture.pack(15, 15)
val hitboxHeight = draft.form.hitbox.height * draft.form.baseScale
val blockPlaneY = (-hitboxHeight * 0.5F + BLOCK_PLANE_OFFSET).toDouble()
val matrices = context.pose()
matrices.pushPose()
matrices.scale(MODEL_DRAW_SCALE, MODEL_DRAW_SCALE, -MODEL_DRAW_SCALE)
matrices.translate(
model.profileTranslation.x,
model.profileTranslation.y + 1.5 * model.profileScale,
model.profileTranslation.z - 4.0
)
matrices.scale(model.profileScale, model.profileScale, 1 / model.profileScale)
matrices.translate(0.0, blockPlaneY, 0.0)
val rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, rotationY, 0F))
matrices.mulPose(rotation)
RenderSystem.applyModelViewMatrix()
RenderSystem.enableDepthTest()
RenderSystem.disableCull()
Lighting.setupForEntityInInventory()
for (xIndex in -halfRows..halfRows) {
for (zIndex in -halfCols..halfCols) {
val z = zIndex * BLOCK_SPACING + travel
matrices.pushPose()
matrices.translate((xIndex * BLOCK_SPACING).toDouble(), 0.0, z.toDouble())
matrices.mulPose(Axis.XP.rotationDegrees(180F))
Minecraft.getInstance().blockRenderer.renderSingleBlock(
blockState,
matrices,
bufferSource,
packedLight,
OverlayTexture.NO_OVERLAY
)
matrices.popPose()
}
}
bufferSource.endBatch()
Lighting.setupFor3DItems()
RenderSystem.enableCull()
matrices.popPose()
}
private fun buildRenderable(): RenderablePokemon? {
val species = draft.species
val form = draft.form
val aspects = mutableSetOf<String>()
if (draft.shiny) aspects.add(SHINY_ASPECT.aspect)
aspects.addAll(form.aspects)
aspects.addAll(draft.forcedAspects)
aspects.addAll(draft.flagFeatures)
aspects.addAll(draft.cosmeticAspects)
draft.choiceFeatures.forEach { (key, choice) ->
choiceAspectId(key, choice)?.let { aspects.add(it) }
}
when (draft.gender) {
Gender.MALE -> aspects.add("male")
Gender.FEMALE -> aspects.add("female")
Gender.GENDERLESS -> aspects.add("genderless")
}
val heldStack = draft.heldItem
?.let { ResourceLocation.tryParse(it) }
?.let { id -> BuiltInRegistries.ITEM.getOptional(id).orElse(null) }
?.let { item -> ItemStack(item) }
?: ItemStack.EMPTY
renderablePokemon = RenderablePokemon(species, aspects, heldStack).also { recalculatePoses(it) }
return renderablePokemon
}
private fun recalculatePoses(renderablePokemon: RenderablePokemon) {
val poseState = FloatingState().also { it.currentAspects = renderablePokemon.aspects }
val poser = VaryingModelRepository.getPoser(renderablePokemon.species.resourceIdentifier, poseState)
poseList = poser.poses
.map { it.value.poseTypes.minBy { it.ordinal } }
.toSet()
.filterNot { it in PoseType.SHOULDER_POSES }
.sortedBy { it.ordinal }
.toTypedArray()
if (PoseType.WALK in poseList) {
selectedPoseIndex = poseList.indexOf(PoseType.WALK)
} else if (selectedPoseIndex !in poseList.indices) {
selectedPoseIndex = 0
}
}
private fun currentHeldStack(): ItemStack {
return renderablePokemon?.heldItem ?: ItemStack.EMPTY
}
private fun isWithinViewport(mouseX: Double, mouseY: Double): Boolean =
mouseX.toInt() in x..(x + HALF_OVERLAY_WIDTH)
&& mouseY.toInt() in y..(y + HALF_OVERLAY_HEIGHT)
&& children.none { child ->
child is AbstractWidget && child.visible && child.isMouseOver(mouseX, mouseY)
}
}
@@ -36,9 +36,9 @@ class MoveSlotWidget(
): SoundlessWidget(pX, pY, MOVE_WIDTH, MOVE_HEIGHT, Component.literal(move?.name ?: "")) {
companion object {
val moveResource = cobblemonResource("textures/gui/summary/summary_move.png")
val moveOverlayResource = cobblemonResource("textures/gui/summary/summary_move_overlay.png")
val moveSelectedOverlayResource = cobblemonResource("textures/gui/summary/summary_move_selected_overlay.png")
private val moveResource = cobblemonResource("textures/gui/summary/summary_move.png")
private val moveOverlayResource = cobblemonResource("textures/gui/summary/summary_move_overlay.png")
private val moveSelectedOverlayResource = cobblemonResource("textures/gui/summary/summary_move_selected_overlay.png")
const val MOVE_WIDTH = 108
const val MOVE_HEIGHT = 22
@@ -50,7 +50,6 @@ class MoveSlotWidget(
}.apply {
addWidget(this)
}
private val moveDownButton = ReorderMoveButton(x, y, false) {
movesWidget.selectMove(null)
movesWidget.reorderMove(this, false)
@@ -68,46 +67,46 @@ class MoveSlotWidget(
}.apply {
addWidget(this)
}
val elementalType:ElementalType = Moves.getByNameOrDummy(move?.name ?: "").getEffectiveElementalType(pokemon)
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
val matrices = context.pose()
isHovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height
if (move != null) {
val moveTemplate = Moves.getByNameOrDummy(move.name)
val rgb = elementalType.hue.toRGB()
if (movesWidget.selectedMove == move) {
blitk(
matrixStack = matrices,
texture = moveSelectedOverlayResource,
x = x - 1,
y = y - 1,
width = MOVE_WIDTH + 2,
height = MOVE_HEIGHT + 2
matrixStack = matrices,
texture = moveSelectedOverlayResource,
x = x - 1,
y = y - 1,
width = MOVE_WIDTH + 2,
height = MOVE_HEIGHT + 2
)
}
blitk(
matrixStack = matrices,
texture = moveResource,
x = x,
y = y,
width = MOVE_WIDTH,
height = MOVE_HEIGHT,
vOffset = if (isHovered) MOVE_HEIGHT else 0,
textureHeight = MOVE_HEIGHT * 2,
red = rgb.first,
green = rgb.second,
blue = rgb.third
matrixStack = matrices,
texture = moveResource,
x = x,
y = y,
width = MOVE_WIDTH,
height = MOVE_HEIGHT,
vOffset = if (isHovered) MOVE_HEIGHT else 0,
textureHeight = MOVE_HEIGHT * 2,
red = rgb.first,
green = rgb.second,
blue = rgb.third
)
blitk(
matrixStack = matrices,
texture = moveOverlayResource,
x = x,
y = y,
width = MOVE_WIDTH,
height = MOVE_HEIGHT
matrixStack = matrices,
texture = moveOverlayResource,
x = x,
y = y,
width = MOVE_WIDTH,
height = MOVE_HEIGHT
)
var movePPText = Component.literal("${move.currentPp}/${move.maxPp}").bold()
@@ -117,28 +116,36 @@ class MoveSlotWidget(
}
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = movePPText,
x = x + 93,
y = y + 13,
centered = true
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = movePPText,
x = x + 93,
y = y + 13,
centered = true
)
// Type Icon
TypeIcon(x = x + 2, y = y + 2, type = elementalType).render(context)
TypeIcon(
x = x + 2,
y = y + 2,
type = elementalType
).render(context)
// Move Category
MoveCategoryIcon(x = x + 66, y = y + 13.5, category = move.damageCategory).render(context)
MoveCategoryIcon(
x = x + 66,
y = y + 13.5,
category = move.damageCategory
).render(context)
// Move Name
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = move.displayName.bold(),
x = x + 28,
y = y + 2,
shadow = true
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = move.displayName.bold(),
x = x + 28,
y = y + 2,
shadow = true
)
// Reorder Buttons
@@ -151,7 +158,9 @@ class MoveSlotWidget(
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (isHovered) movesWidget.selectMove(move)
if (isHovered) {
movesWidget.selectMove(move)
}
return super.mouseClicked(mouseX, mouseY, button)
}
}
}
@@ -162,7 +162,7 @@ class MoveSwapScreen(
drawScaledText(
context = context,
text = MovesWidget.format(move.accuracy).text(),
text = pane.movesWidget.format(move.accuracy).text(),
x = rowLeft + 37,
y = tweakedRowTop + 12,
scale = MovesWidget.SCALE,
@@ -171,7 +171,7 @@ class MoveSwapScreen(
drawScaledText(
context = context,
text = MovesWidget.format(move.effectChances.firstOrNull() ?: 0.0).text(),
text = pane.movesWidget.format(move.effectChances.firstOrNull() ?: 0.0).text(),
x = rowLeft + 60.5,
y = tweakedRowTop + 12,
scale = MovesWidget.SCALE,
@@ -232,4 +232,4 @@ class MoveSwapScreen(
return false
}
}
}
}
@@ -39,11 +39,6 @@ class MovesWidget(
it.roundingMode = RoundingMode.CEILING
}
fun format(input: Double): String {
if (input <= 0) return "—"
return "${decimalFormat.format(input)}%"
}
private val movesBaseResource = cobblemonResource("textures/gui/summary/summary_moves_base.png")
val movesPowerIconResource = cobblemonResource("textures/gui/summary/summary_moves_icon_power.png")
val movesAccuracyIconResource = cobblemonResource("textures/gui/summary/summary_moves_icon_accuracy.png")
@@ -213,6 +208,11 @@ class MovesWidget(
)
}
fun format(input: Double): String {
if (input <= 0) return "—"
return "${decimalFormat.format(input)}%"
}
fun selectMove(move: Move?) {
selectedMove = if (selectedMove == move || move == null) null else move
descriptionScrollList.setMoveDescription(selectedMove?.description ?: "".text())
@@ -232,4 +232,4 @@ class MovesWidget(
if (descriptionScrollList.isHovered) descriptionScrollList.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
}
}
@@ -1,74 +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.gui.tmmachine
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.narration.NarrationElementOutput
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.resources.ResourceLocation
class IconButton(
var buttonX: Float,
var buttonY: Float,
val buttonWidth: Number,
val buttonHeight: Number,
var resource: ResourceLocation? = null,
val scale: Float = 0.5F,
val hoverExtendRight: Int = 0,
val silent: Boolean = false,
val clickAction: OnPress
): Button(buttonX.toInt(), buttonY.toInt(), buttonWidth.toInt(), buttonHeight.toInt(), "".text(), clickAction, DEFAULT_NARRATION), CobblemonRenderable {
override fun mouseDragged(d: Double, e: Double, i: Int, f: Double, g: Double) = false
override fun defaultButtonNarrationText(builder: NarrationElementOutput) {}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
resource?.let {
blitk(
matrixStack = context.pose(),
texture = it,
x = buttonX / scale,
y = buttonY / scale,
width = buttonWidth,
height = buttonHeight,
vOffset = if (isMouseOver(mouseX.toDouble(), mouseY.toDouble())) buttonHeight else 0,
textureHeight = buttonHeight.toFloat() * 2,
scale = scale
)
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (active && visible && button == 0 && isMouseOver(mouseX, mouseY)) {
playDownSound(Minecraft.getInstance().soundManager)
onClick(mouseX, mouseY)
return true
}
return false
}
override fun isMouseOver(mouseX: Double, mouseY: Double): Boolean {
val minX = x.toDouble()
val maxX = minX + width + hoverExtendRight
val minY = y.toDouble()
val maxY = minY + (height * scale)
return active && visible && mouseX >= minX && mouseX < maxX && mouseY >= minY && mouseY < maxY
}
override fun playDownSound(soundManager: SoundManager) {
if (visible && !this.silent) soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
}
@@ -1,77 +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.gui.tmmachine
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
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.pc.TextWidget
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.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
class MoveSearchWidget(
private var pX: Int,
private var pY: Int,
text: Component = "MoveSearchWidget".text(),
update: () -> (Unit)
): TextWidget(pX, pY, text = text, update = update) {
companion object {
const val ICON_SIZE = 16
const val SCALE = 0.5F
private val iconFilterResource = cobblemonResource("textures/gui/tmmachine/icon_search.png")
private val labelFilter = lang("ui.pokedex.search")
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
blitk(
matrixStack = context.pose(),
x = (x - 10.5) / SCALE,
y = (y + 2.5) / SCALE,
texture = iconFilterResource,
width = ICON_SIZE,
height = ICON_SIZE,
vOffset = if (isFocused || !value.isEmpty() || iconHovered(mouseX, mouseY)) ICON_SIZE else 0,
textureHeight = ICON_SIZE * 2,
scale = SCALE
)
val input = applyTextCursor(value, labelFilter)
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = input.bold(),
x = pX,
y = pY + 2,
shadow = true
)
renderCursor(context, input)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (iconHovered(mouseX.toInt(), mouseY.toInt())) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F, 0.25F))
// Simulate clicking on text area if icon is clicked
if (!isFocused) return super.mouseClicked(x + (width / 2.0), y + (height / 2.0), button)
}
return super.mouseClicked(mouseX, mouseY, button)
}
fun iconHovered(mouseX: Int, mouseY: Int): Boolean = mouseX >= (x - 10.5) && mouseY >= (y + 2.5)
&& mouseX <= ((x - 10.5) + (ICON_SIZE * SCALE)) && mouseY <= ((y + 2.5) + (ICON_SIZE * SCALE))
}
@@ -1,200 +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.gui.tmmachine
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.reactive.SettableObservable
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.tms.TechnicalMachine
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
import com.cobblemon.mod.common.client.gui.ScrollingWidget
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MoveSlotWidget
import com.cobblemon.mod.common.client.gui.tmmachine.MovesScrollingWidget.ScrollSlot
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.toRGB
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.FastColor
import net.minecraft.util.Mth
class MovesScrollingWidget(val pX: Int, val pY: Int, val tmList: SettableObservable<MutableList<TechnicalMachine>>, val setTM: (TechnicalMachine?, Boolean) -> (Unit)): ScrollingWidget<ScrollSlot>(
width = WIDTH,
height = HEIGHT,
left = pX,
top = pY - HEIGHT,
slotHeight = SLOT_HEIGHT + SLOT_SPACING
) {
companion object {
const val WIDTH = 115
const val HEIGHT = 82
const val SLOT_HEIGHT = 22
const val SLOT_SPACING = 4
val searchBar = cobblemonResource("textures/gui/tmmachine/search_bar.png")
}
var highlightedMoveId: ResourceLocation? = null
init {
tmList.subscribeIncludingCurrent { tmList ->
replaceEntries(tmList.map { tm -> ScrollSlot(tm, setTM) })
}
}
fun setSlotHighlighted(id: ResourceLocation?) {
highlightedMoveId = id
for (child in children()) {
if (child is ScrollSlot) {
child.highlighted = child.tm.id == highlightedMoveId
}
}
}
override fun getScrollbarPosition(): Int = rowLeft + width - 3
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
blitk(
matrixStack = context.pose(),
texture = searchBar,
x = left - 4,
y = y - 19,
width = 120,
height = 19
)
super.renderWidget(context, mouseX, mouseY, delta)
}
override fun renderScrollbar(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val xLeft = this.scrollbarPosition
val xRight = xLeft + 3
val barHeight = this.bottom - this.y
var yBottom = ((barHeight * barHeight).toFloat() / this.maxPosition.toFloat()).toInt()
yBottom = Mth.clamp(yBottom, 32, barHeight - 8)
var yTop = scrollAmount.toInt() * (barHeight - yBottom) / this.maxScroll + this.y
if (yTop < this.y) {
yTop = this.y
}
context.fill(xLeft, this.y, xRight, this.bottom, FastColor.ARGB32.color(255, 75, 75, 75)) // background
context.fill(xLeft,yTop, xRight, yTop + yBottom, FastColor.ARGB32.color(255, 141, 141, 141)) // base
}
override fun getEntry(index: Int): ScrollSlot = children()[index] as ScrollSlot
class ScrollSlot(val tm: TechnicalMachine, val setTM : (TechnicalMachine?, clicked: Boolean) -> Unit): Slot<ScrollSlot>() {
var posX: Int = 0
var posY: Int = 0
var highlighted = false
override fun render(context: GuiGraphics, index: Int, y: Int, x: Int, entryWidth: Int, entryHeight: Int, mouseX: Int, mouseY: Int, hovered: Boolean, tickDelta: Float) {
posX = x
posY = y
val moveTemplate = tm.moveName
val rgb = moveTemplate.elementalType.hue.toRGB()
val isHovered = hovered && (mouseX < x + (108)) // Prevent hover when over scrollbar
val startPosY = y + SLOT_SPACING
val startPosX = x + 1 // Hover border for move renders at x, so render move element to the right by 1
val matrices = context.pose()
blitk(
matrixStack = matrices,
texture = MoveSlotWidget.moveResource,
x = startPosX,
y = startPosY,
width = MoveSlotWidget.MOVE_WIDTH,
height = MoveSlotWidget.MOVE_HEIGHT,
vOffset = if (isHovered) MoveSlotWidget.MOVE_HEIGHT else 0,
textureHeight = MoveSlotWidget.MOVE_HEIGHT * 2,
red = rgb.first,
green = rgb.second,
blue = rgb.third
)
blitk(
matrixStack = matrices,
texture = MoveSlotWidget.moveOverlayResource,
x = startPosX,
y = startPosY,
width = MoveSlotWidget.MOVE_WIDTH,
height = MoveSlotWidget.MOVE_HEIGHT
)
if (highlighted || isHovered) {
blitk(
matrixStack = matrices,
texture = MoveSlotWidget.moveSelectedOverlayResource,
x = startPosX - 1,
y = startPosY - 1,
width = MoveSlotWidget.MOVE_WIDTH + 2,
height = MoveSlotWidget.MOVE_HEIGHT + 2
)
}
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.moves.pp", moveTemplate.pp).bold(),
x = startPosX + 93,
y = startPosY + 13,
centered = true
)
// Type Icon
TypeIcon(
x = x + 2,
y = startPosY + 2,
type = moveTemplate.elementalType
).render(context)
// Move Category
MoveCategoryIcon(
x = x + 66,
y = startPosY + 13.5,
category = moveTemplate.damageCategory
).render(context)
// Move Name
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = moveTemplate.displayName.bold(),
x = x + 28,
y = startPosY + 2,
shadow = true
)
if (isHovered) {
setTM(tm, false)
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
// Prevent clicking through scroll bar
if (mouseX < posX + (108)) {
setTM(tm, true)
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun getNarration(): Component {
return tm.moveName.displayName
}
}
}
@@ -1,89 +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.gui.tmmachine
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.narration.NarrationElementOutput
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
class StartButton(
var buttonX: Float,
var buttonY: Float,
val clickAction: OnPress
): Button(buttonX.toInt(), buttonY.toInt(), WIDTH, HEIGHT, "".text(), clickAction, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
const val WIDTH = 22
const val HEIGHT = 12
val base = cobblemonResource("textures/gui/tmmachine/button_start.png")
val baseStop = cobblemonResource("textures/gui/tmmachine/button_stop.png")
val baseDisabled = cobblemonResource("textures/gui/tmmachine/button_start_disabled.png")
val iconStart = cobblemonResource("textures/gui/tmmachine/button_icon_start.png")
val iconStop = cobblemonResource("textures/gui/tmmachine/button_icon_stop.png")
val iconRepeat = cobblemonResource("textures/gui/tmmachine/button_icon_repeat.png")
}
var disabled = false
var processing = false
var shouldRepeat = false
override fun mouseDragged(d: Double, e: Double, i: Int, f: Double, g: Double) = false
override fun defaultButtonNarrationText(builder: NarrationElementOutput) {}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
val resource = if (disabled) baseDisabled
else (if (processing) baseStop else base)
blitk(
matrixStack = context.pose(),
texture = resource,
x = buttonX,
y = buttonY,
width = WIDTH,
height = HEIGHT,
vOffset = if (isMouseOver(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
textureHeight = HEIGHT * 2,
)
blitk(
matrixStack = context.pose(),
texture = if (processing) iconStop else (if (shouldRepeat) iconRepeat else iconStart),
x = buttonX,
y = buttonY,
width = WIDTH,
height = HEIGHT
)
}
/*override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (active && visible && !disabled && isMouseOver(mouseX, mouseY)) {
super.mouseClicked(mouseX, mouseY, button)
}
return false
}*/
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (!active || !visible || disabled) return false
return super.mouseClicked(mouseX, mouseY, button)
}
override fun playDownSound(soundManager: SoundManager) {
if (visible && !disabled) {
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
}
}
@@ -1,187 +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.gui.tmmachine
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
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
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.gui.components.Button
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import org.joml.Quaternionf
import org.joml.Vector3f
class TMPartySlotWidget(
pX: Number,
pY: Number,
val pokemon: Pokemon?,
onPress: OnPress
) : Button(pX.toInt(), pY.toInt(), WIDTH, HEIGHT, Component.literal("PartyMember"), onPress, DEFAULT_NARRATION) {
val state = FloatingState()
var teachable: Int? = null
var clickable: Boolean = true
companion object {
const val WIDTH = 57
const val HEIGHT = 31
private const val PORTRAIT_DIAMETER = 30
const val HALF_SCALE = 0.5F
const val CAN_LEARN = 0
const val CANNOT_LEARN = 1
const val LEARNED = 2
private val slotResource = cobblemonResource("textures/gui/tmmachine/party_slot.png")
private val slotLearnableResource = cobblemonResource("textures/gui/tmmachine/party_slot_learnable.png")
private val slotLearnableDisabledResource = cobblemonResource("textures/gui/tmmachine/party_slot_learnable_disabled.png")
private val slotDisabledResource = cobblemonResource("textures/gui/tmmachine/party_slot_disabled.png")
private val slotLearnedResource = cobblemonResource("textures/gui/tmmachine/party_slot_learned.png")
val genderIconMale = cobblemonResource("textures/gui/pc/gender_icon_male.png")
val genderIconFemale = cobblemonResource("textures/gui/pc/gender_icon_female.png")
private val canLearnLabel = lang("ui.tm_machine.can_learn")
private val cannotLearnLabel = lang("ui.tm_machine.cannot_learn")
private val learnedLabel = lang("ui.tm_machine.learned")
}
private fun getResource(): ResourceLocation = when (teachable) {
CAN_LEARN -> if (clickable) slotLearnableResource else slotLearnableDisabledResource
CANNOT_LEARN -> slotDisabledResource
LEARNED -> slotLearnedResource
else -> slotResource
}
private fun getLabel(): MutableComponent? = when (teachable) {
CAN_LEARN -> canLearnLabel
CANNOT_LEARN -> cannotLearnLabel
LEARNED -> learnedLabel
else -> null
}
override fun renderWidget(context: net.minecraft.client.gui.GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
state.currentAspects = pokemon?.aspects ?: emptySet()
val matrices = context.pose()
if (pokemon != null) {
blitk(
matrixStack = matrices,
texture = getResource(),
x = x,
y = y,
width = width,
height = height,
vOffset = if (clickable && isMouseOver(mouseX.toDouble(), mouseY.toDouble())) height else 0,
textureHeight = height * 2,
)
val ballIcon = cobblemonResource("textures/gui/ball/" + pokemon.caughtBall.name.path + ".png")
val ballHeight = 22
blitk(
matrixStack = matrices,
texture = ballIcon,
x = (x - 2) / HALF_SCALE,
y = (y - 3) / HALF_SCALE,
height = ballHeight,
width = 18,
textureHeight = ballHeight * 2,
scale = HALF_SCALE
)
// Render Pokémon
matrices.pushPose()
matrices.translate(x + (PORTRAIT_DIAMETER / 2.0) - 2, y - 1.0, 0.0)
matrices.scale(2.5F, 2.5F, 1F)
drawProfilePokemon(
species = pokemon.species.resourceIdentifier,
matrixStack = matrices,
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = state,
scale = 4.5F,
partialTicks = delta
)
matrices.popPose()
drawScaledTextJustifiedRight(
context = context,
text = lang("ui.lv.number", pokemon.level),
x = x + 54,
y = y + 13,
shadow = true,
scale = HALF_SCALE
)
if (pokemon.gender != Gender.GENDERLESS) {
blitk(
matrixStack = matrices,
texture = if (pokemon.gender == Gender.MALE) genderIconMale else genderIconFemale,
x = (x + 51) / HALF_SCALE,
y = (y + 19) / HALF_SCALE,
height = 8,
width = 6,
scale = HALF_SCALE
)
}
drawScaledTextJustifiedRight(
context = context,
text = pokemon.getDisplayName(),
x = x + ( if (pokemon.gender != Gender.GENDERLESS) 50 else 54),
y = y + 19,
shadow = true,
scale = HALF_SCALE
)
getLabel()?.let { label ->
drawScaledText(
context = context,
text = label,
x = x + (width / 2F),
y = y + 26,
centered = true,
scale = HALF_SCALE
)
}
// Held Item
val heldItem = pokemon.heldItemNoCopy()
if (!heldItem.isEmpty) {
renderScaledGuiItemIcon(
itemStack = heldItem,
x = x + 15.0,
y = y + 13.5,
scale = HALF_SCALE.toDouble(),
matrixStack = matrices
)
}
} else {
blitk(
matrixStack = matrices,
texture = slotDisabledResource,
x = x + 55,
y = y,
width = 5,
height = height,
textureWidth = width,
textureHeight = height * 2
)
}
}
override fun playDownSound(soundManager: SoundManager) {}
}
@@ -1,151 +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.gui.tmmachine
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.client.gui.ScrollingWidget
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.tmmachine.TypesScrollingWidget.ScrollSlotRow
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.util.FastColor
import net.minecraft.util.Mth
class TypesScrollingWidget(val pX: Int, val pY: Int, val setType: (ElementalType?) -> (Unit)): ScrollingWidget<ScrollSlotRow>(
width = WIDTH,
height = HEIGHT,
left = pX,
top = pY - HEIGHT,
slotHeight = SLOT_SIZE + SLOT_SPACING
) {
companion object {
const val WIDTH = 112
const val HEIGHT = 100
const val SLOT_SIZE = 22
const val SLOT_SPACING = 5
}
init {
(listOf(null) + ElementalTypes.all()).chunked(4).forEachIndexed { index, listChunk ->
addEntry(ScrollSlotRow(listChunk, setType))
}
}
override fun getScrollbarPosition(): Int = rowLeft + width - 3
override fun renderScrollbar(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val xLeft = this.scrollbarPosition
val xRight = xLeft + 3
val barHeight = this.bottom - this.y
var yBottom = ((barHeight * barHeight).toFloat() / this.maxPosition.toFloat()).toInt()
yBottom = Mth.clamp(yBottom, 32, barHeight - 8)
var yTop = scrollAmount.toInt() * (barHeight - yBottom) / this.maxScroll + this.y
if (yTop < this.y) {
yTop = this.y
}
context.fill(xLeft, this.y, xRight, this.bottom, FastColor.ARGB32.color(255, 75, 75, 75)) // background
context.fill(xLeft,yTop, xRight, yTop + yBottom, FastColor.ARGB32.color(255, 141, 141, 141)) // base
}
fun renderEntry(context: GuiGraphics, mouseX: Int, mouseY: Int, index: Int, x: Int, y: Int) {
val entry = this.getEntry(index)
entry.x = x
entry.y = y
entry.renderRow( context, y, x, mouseX, mouseY)
}
override fun getEntry(index: Int): ScrollSlotRow = children()[index] as ScrollSlotRow
override fun renderListItems(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val rowX = rowLeft
for (index in 0 until this.itemCount) {
val rowY = this.getRowTop(index)
val rowBottom = this.getRowBottom(index)
if (rowBottom >= this.y && rowY <= this.bottom) {
this.renderEntry(context!!, mouseX, mouseY, index, rowX, rowY)
}
}
}
class ScrollSlotRow(val types: List<ElementalType?>, val setType : (ElementalType?) -> Unit): Slot<ScrollSlotRow>() {
companion object {
private val allSlotResource = cobblemonResource("textures/gui/tmmachine/type_slot_all.png")
private val slotResource = cobblemonResource("textures/gui/tmmachine/type_slot.png")
}
var x: Int = 0
var y: Int = 0
fun renderRow(context: GuiGraphics, y: Int, x: Int, mouseX: Int, mouseY: Int) {
types.forEachIndexed { index, type ->
val matrices = context.pose()
val startPosX = x + ((SLOT_SPACING + SLOT_SIZE) * index)
val startPosY = y + SLOT_SPACING
val slotHovered = getHoveredSlotIndex(mouseX, mouseY) == index
blitk(
matrixStack = matrices,
texture = if (type != null) slotResource else allSlotResource,
x = startPosX,
y = startPosY,
width = SLOT_SIZE,
height = SLOT_SIZE,
vOffset = if (slotHovered) SLOT_SIZE else 0,
textureHeight = SLOT_SIZE * 2
)
if (type != null) {
TypeIcon(startPosX + 2, startPosY + 2, type).render(context)
}
}
}
override fun render(context: GuiGraphics, index: Int, y: Int, x: Int, entryWidth: Int, entryHeight: Int, mouseX: Int, mouseY: Int, hovered: Boolean, tickDelta: Float) {}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
val hoverIndex = getHoveredSlotIndex(mouseX.toInt(), mouseY.toInt())
if (hoverIndex > -1 && hoverIndex < types.size) {
setType.invoke(types[hoverIndex])
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
return true
}
private fun getHoveredSlotIndex(mouseX: Int, mouseY: Int): Int {
types.forEachIndexed { index, _ ->
val startPosX = x + ((SLOT_SPACING + SLOT_SIZE) * index)
val startPosY = y + SLOT_SPACING + 1
if (mouseX in startPosX..(startPosX + SLOT_SIZE) && mouseY in startPosY..(startPosY + SLOT_SIZE)) {
return index
}
}
return -1
}
override fun getNarration(): Component {
if (types.isNotEmpty()) {
return "${types[0]}-${types[types.size - 1]}".text()
}
return "".text()
}
}
}
@@ -26,27 +26,16 @@ import net.minecraft.util.FormattedCharSequence
@Environment(EnvType.CLIENT)
class CobblemonToast(
val id: UUID,
var icons: List<ItemStack>,
var icon: ItemStack,
var title: Component,
var description: Component,
var frameTexture: ResourceLocation,
var progress: Float,
var progressColor: Int,
var durationMs: Long? = null
var progressColor: Int
) : Toast {
constructor(packet: ToastPacket) : this(
packet.uuid,
packet.icons,
packet.title,
packet.description,
packet.frameTexture,
packet.progress,
packet.progressColor,
packet.durationMs
)
constructor(packet: ToastPacket) : this(packet.uuid, packet.icon, packet.title, packet.description, packet.frameTexture, packet.progress, packet.progressColor)
private var startTime: Long = -1
private var lastProgress = 0F
private var lastTime = 0L
internal var nextVisibility: Toast.Visibility = Toast.Visibility.SHOW
@@ -56,17 +45,6 @@ class CobblemonToast(
}
override fun render(context: GuiGraphics, manager: ToastComponent, startTime: Long): Toast.Visibility {
if (this.startTime == -1L) this.startTime = startTime
val elapsedTime = startTime - this.startTime
val localDuration = this.durationMs
if (localDuration != null && elapsedTime >= localDuration) {
ToastTracker.remove(this)
return Toast.Visibility.HIDE
}
context.blitSprite(this.frameTexture, 0, 0, this.width(), this.height())
val maxWidth = 125
val textRenderer = manager.minecraft.font
@@ -74,38 +52,19 @@ class CobblemonToast(
context.drawString(textRenderer, this.title, 30, 7, this.title.style.color?.value ?: -1, false)
context.drawWordWrap(textRenderer, this.description, 30, 18, maxWidth, this.description.style.color?.value ?: -1)
val iconIndex = if (localDuration != null && localDuration > 0) {
val repeatsPerIcon = 2
val totalIcons = icons.size * repeatsPerIcon
val timePerIcon = (localDuration / totalIcons).coerceAtLeast(1L)
val iconIndex = (elapsedTime / timePerIcon)
.toInt()
.coerceIn(0, totalIcons - 1)
iconIndex / repeatsPerIcon
} else {
val switchIntervalMs = 500L
((elapsedTime / switchIntervalMs) % icons.size).toInt()
}
val icon = this.icons[iconIndex]
context.renderFakeItem(icon, 8, 8)
context.renderFakeItem(this.icon, 8, 8)
if (this.hasProgressBar()) {
context.fill(3, 28, 157, 29, -1)
val f = Mth.clampedLerp(this.lastProgress, this.progress, (startTime - this.lastTime).toFloat() / 100F)
context.fill(3, 28, (3F + 154F * f).toInt(), 29, this.progressColor)
this.lastProgress = f
}
this.lastTime = startTime
return this.nextVisibility
}
internal fun updateFrom(packet: ToastPacket) {
val newIcons = this.icons + packet.icons
this.icons = newIcons
this.icon = packet.icon
this.title = packet.title
this.description = packet.description
this.frameTexture = packet.frameTexture
@@ -30,7 +30,4 @@ object ToastTracker {
}
}
fun remove(toast: CobblemonToast) {
toasts.remove(toast.id)
}
}
@@ -1,19 +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.net.gui
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.net.messages.client.ui.OpenTMMPacket
import net.minecraft.client.Minecraft
object OpenTMMPacketHandler : ClientNetworkPacketHandler<OpenTMMPacket> {
override fun handle(packet: OpenTMMPacket, client: Minecraft) {
//client.setScreen()
}
}
@@ -1,38 +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.net.gui
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.block.tmmachine.TMMachineMenu
import com.cobblemon.mod.common.net.messages.client.ui.SetActiveTMPacket
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.level.block.Block
object SetActiveTMPacketHandler : ServerNetworkPacketHandler<SetActiveTMPacket> {
override fun handle(packet: SetActiveTMPacket, server: MinecraftServer, player: ServerPlayer) {
val menu = player.containerMenu as? TMMachineMenu ?: return
val inventory = menu.inventory ?: return
val tmData = Cobblemon.playerDataManager.getTMData(player)
if (packet.tm != null && (!packet.tm.isPassivelyObtained() && packet.tm.id !in tmData.learnedTMs)) {
return // Hacker smh
}
menu.tmMachineEntity?.let { blockEntity ->
blockEntity.activeMove = packet.tm?.moveName?.name ?: ""
blockEntity.setChanged()
player.level().sendBlockUpdated(blockEntity.blockPos, blockEntity.blockState, blockEntity.blockState, Block.UPDATE_CLIENTS)
}
inventory.setChanged()
menu.broadcastChanges()
}
}
Loaded 100 of 2149 files, more files were not shown because too many files have changed in this diff. Show more