Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
071f05337c | ||
|
|
e9d274fbfd | ||
|
|
5709527bf2 | ||
|
|
bc9f28811d | ||
|
|
ff9eb60cd3 | ||
|
|
de0b53e529 | ||
|
|
56ecbfe3c0 | ||
|
|
62f03c536d | ||
|
|
2fa03b6013 | ||
|
|
59af4053a1 | ||
|
|
1aba8c52b6 | ||
|
|
6a386937c8 | ||
|
|
00132f6428 | ||
|
|
7fe222422b | ||
|
|
812f389231 | ||
|
|
24acddf750 | ||
|
|
1d696c58be |
No files matched your search
@@ -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/
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
+39
-3
@@ -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;
|
||||
}
|
||||
+1
-154
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
-166
@@ -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)
|
||||
|
||||
+7
-29
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-16
@@ -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]
|
||||
}
|
||||
-35
@@ -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) }
|
||||
}
|
||||
}
|
||||
-42
@@ -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) }
|
||||
}
|
||||
}
|
||||
-18
@@ -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
|
||||
}
|
||||
-49
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -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 {
|
||||
|
||||
+1
-201
@@ -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#
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
+1
-10
@@ -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,
|
||||
|
||||
+69
-65
@@ -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) {}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -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
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -132,4 +132,4 @@ abstract class InfoTextScrollWidget(val pX: Int, val pY: Int): ScrollingWidget<I
|
||||
return Component.literal(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-1050
File diff suppressed because it is too large.
Load diff
+1
-52
@@ -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)
|
||||
}
|
||||
}
|
||||
+100
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+362
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+496
@@ -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)
|
||||
}
|
||||
}
|
||||
+620
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+335
@@ -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()
|
||||
}
|
||||
}
|
||||
+1422
File diff suppressed because it is too large.
Load diff
+354
@@ -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)
|
||||
}
|
||||
}
|
||||
+227
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+476
@@ -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)
|
||||
}
|
||||
}
|
||||
+659
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+212
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+445
@@ -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)
|
||||
}
|
||||
}
|
||||
+53
-44
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -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))
|
||||
}
|
||||
}
|
||||
-77
@@ -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))
|
||||
}
|
||||
-200
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
-1040
File diff suppressed because it is too large.
Load diff
-187
@@ -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) {}
|
||||
}
|
||||
-151
@@ -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)
|
||||
}
|
||||
}
|
||||
-19
@@ -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()
|
||||
}
|
||||
}
|
||||
-38
@@ -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
Reference in new issue
Block a user