forked from mirrors/cobblemon
Compare commits
263
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b371127ed | ||
|
|
8ee0d904ed | ||
|
|
291196eda7 | ||
|
|
b94e6d65b9 | ||
|
|
60fdd3325e | ||
|
|
eedcdafcbe | ||
|
|
34595e01a9 | ||
|
|
60b9ca7b7f | ||
|
|
83aea117c6 | ||
|
|
78b348329c | ||
|
|
b64013c995 | ||
|
|
85003c9829 | ||
|
|
d9133e854a | ||
|
|
05afe191e6 | ||
|
|
e3b5067f31 | ||
|
|
bfa2d470f0 | ||
|
|
53c0b28deb | ||
|
|
5e9208d898 | ||
|
|
23a91ec2c9 | ||
|
|
9998a328e3 | ||
|
|
9f07747cac | ||
|
|
a7998bcec5 | ||
|
|
5983b29e9c | ||
|
|
749f441913 | ||
|
|
dabbcceb9c | ||
|
|
11f8313fc3 | ||
|
|
32b813620b | ||
|
|
8147067b2e | ||
|
|
f2d2ceb422 | ||
|
|
547ceae9ed | ||
|
|
24c6544e83 | ||
|
|
a921d55812 | ||
|
|
b422ee4d29 | ||
|
|
a83867e153 | ||
|
|
523973d2c0 | ||
|
|
746160fa18 | ||
|
|
837ba9679b | ||
|
|
db7a319441 | ||
|
|
e284032c7e | ||
|
|
3db9dd02f2 | ||
|
|
e680ec8790 | ||
|
|
dc4f831e02 | ||
|
|
b3a00c40b5 | ||
|
|
f56fb0f50a | ||
|
|
ec732bc03d | ||
|
|
229abd4a13 | ||
|
|
7e92c17178 | ||
|
|
77aa3208a0 | ||
|
|
79688ebdad | ||
|
|
16d8c8bb19 | ||
|
|
e5738fecb0 | ||
|
|
a46ac372a8 | ||
|
|
b2ea715195 | ||
|
|
8b6c8d27b7 | ||
|
|
35e4aafe99 | ||
|
|
eab6fa9b40 | ||
|
|
98de423f91 | ||
|
|
ef4fbecad6 | ||
|
|
96075ed34a | ||
|
|
e7cd9237f6 | ||
|
|
84e22c68c7 | ||
|
|
300f2fc53c | ||
|
|
2dfb88467b | ||
|
|
589aab3b1a | ||
|
|
e17ca3a422 | ||
|
|
0153846c7b | ||
|
|
f598f8d645 | ||
|
|
8e3e047646 | ||
|
|
551858df57 | ||
|
|
a7e3f4a7a1 | ||
|
|
94d64bb5c6 | ||
|
|
e569acc648 | ||
|
|
7c374b6e29 | ||
|
|
3c128d9009 | ||
|
|
f5321a34c3 | ||
|
|
2754bfc525 | ||
|
|
3d1aecce13 | ||
|
|
38cd081ad6 | ||
|
|
54a14ab805 | ||
|
|
633b45287c | ||
|
|
3619f42090 | ||
|
|
695453ffdb | ||
|
|
3c2bf0e2b3 | ||
|
|
ac695b1092 | ||
|
|
dd7066e8fa | ||
|
|
416f003927 | ||
|
|
3be3dc3de2 | ||
|
|
a8f273737d | ||
|
|
a185805b3f | ||
|
|
defab6eaff | ||
|
|
72fc1f1fb9 | ||
|
|
f96c69ab54 | ||
|
|
d51039dbb8 | ||
|
|
f450d9d2d6 | ||
|
|
0752ab3cac | ||
|
|
fe35a63769 | ||
|
|
17cab186d9 | ||
|
|
e00136052d | ||
|
|
b23237ca9a | ||
|
|
95d238ada0 | ||
|
|
36456bfb0d | ||
|
|
d9661c356f | ||
|
|
a66fad69cf | ||
|
|
95488ac54b | ||
|
|
19f2b71273 | ||
|
|
ba6e136a0b | ||
|
|
28ac437987 | ||
|
|
a4935f19bb | ||
|
|
c3e470f1de | ||
|
|
4139337f85 | ||
|
|
25bc2dea93 | ||
|
|
9dc3884ea6 | ||
|
|
89d95c6ab8 | ||
|
|
7b70aa5a1e | ||
|
|
8191b4d9b6 | ||
|
|
84534fd041 | ||
|
|
e6e1446eb1 | ||
|
|
39fbadc4fb | ||
|
|
3c2a9c1dfd | ||
|
|
921fcbe477 | ||
|
|
c90b6c86ab | ||
|
|
3ecf25502b | ||
|
|
b808112e17 | ||
|
|
1b2d31db03 | ||
|
|
c9f8445d14 | ||
|
|
acd879ebef | ||
|
|
f6889fd8ea | ||
|
|
21ccbef6c9 | ||
|
|
9349e6a668 | ||
|
|
28a7fe6a04 | ||
|
|
18e1ed79ef | ||
|
|
aafdb7688a | ||
|
|
73b53f9702 | ||
|
|
33317a57c7 | ||
|
|
72d8f26cdb | ||
|
|
aea1afe145 | ||
|
|
0fec45b7ea | ||
|
|
af98b823aa | ||
|
|
07d9da2ca9 | ||
|
|
c2c86b2ef1 | ||
|
|
22733e7117 | ||
|
|
117afcea32 | ||
|
|
3f89750ee5 | ||
|
|
d88be7ae05 | ||
|
|
24245331db | ||
|
|
d6d10d33e3 | ||
|
|
4d88c9b383 | ||
|
|
a3f597c589 | ||
|
|
2a3d88c12e | ||
|
|
9561f47337 | ||
|
|
f02c92db22 | ||
|
|
181ef5d50e | ||
|
|
870397ebb9 | ||
|
|
0ed413070d | ||
|
|
f66f5ffdf9 | ||
|
|
ca280a7b3e | ||
|
|
85284bff9d | ||
|
|
fa4d33f725 | ||
|
|
4ce42cdf6d | ||
|
|
1b8562ead8 | ||
|
|
b9d9b906fb | ||
|
|
e9d079a223 | ||
|
|
e2e42ee91c | ||
|
|
4b0208201b | ||
|
|
bec3f97733 | ||
|
|
16cfe37dac | ||
|
|
7381da863c | ||
|
|
b0902825fa | ||
|
|
31c489cba0 | ||
|
|
6a2f21d75a | ||
|
|
e2c42a57c7 | ||
|
|
8592b53216 | ||
|
|
49dcf0049f | ||
|
|
076bee923f | ||
|
|
aacdb33827 | ||
|
|
53e9ece930 | ||
|
|
87f08d9bab | ||
|
|
a50a6326af | ||
|
|
3c0eec833e | ||
|
|
5b0cfdffcb | ||
|
|
d788e0402e | ||
|
|
a347dc5e2a | ||
|
|
b2fe158a7b | ||
|
|
faab7e8df0 | ||
|
|
9070e198f1 | ||
|
|
45b2a0db4d | ||
|
|
65d39050ea | ||
|
|
5da16df5ae | ||
|
|
24fc4b05ab | ||
|
|
173902feb1 | ||
|
|
fd5f08fe84 | ||
|
|
d430ad6ca1 | ||
|
|
1309c18292 | ||
|
|
b6deebac97 | ||
|
|
83711f2dcb | ||
|
|
18e1a767af | ||
|
|
6ae3efb461 | ||
|
|
84e1934134 | ||
|
|
9aed9342ee | ||
|
|
e4d0bf39ca | ||
|
|
847fa6842e | ||
|
|
08aa4f29bd | ||
|
|
539218381b | ||
|
|
9dff39b4d7 | ||
|
|
1e578e94e5 | ||
|
|
ec35c9b9bb | ||
|
|
0deaeaeb7d | ||
|
|
ec3d9e1a56 | ||
|
|
814f84ae42 | ||
|
|
904be475ef | ||
|
|
5edecbe81b | ||
|
|
f357033404 | ||
|
|
4800ae6649 | ||
|
|
60b0690c1e | ||
|
|
82e31dd766 | ||
|
|
130e767e75 | ||
|
|
b1a1d509ec | ||
|
|
0698cf495e | ||
|
|
6047660405 | ||
|
|
aaf3495f83 | ||
|
|
ae6529c9fd | ||
|
|
45a96b2a42 | ||
|
|
4212903c67 | ||
|
|
be47dbcede | ||
|
|
9556c57c69 | ||
|
|
f72b65bfac | ||
|
|
6121700c9b | ||
|
|
91c968e743 | ||
|
|
8fed6f4823 | ||
|
|
34e4b39d75 | ||
|
|
a21c70ce46 | ||
|
|
8865a71f4c | ||
|
|
142cadc187 | ||
|
|
66f4c2531d | ||
|
|
3f8ddd5f7d | ||
|
|
c076e14cbd | ||
|
|
d724eb068b | ||
|
|
d3ac263e29 | ||
|
|
a6b7fe1f17 | ||
|
|
ec714ccb84 | ||
|
|
ab0a129ba1 | ||
|
|
a3f272b2aa | ||
|
|
77d91ed979 | ||
|
|
9907ba17c1 | ||
|
|
a6f0e19a44 | ||
|
|
7ee064b04d | ||
|
|
f2e4c735f7 | ||
|
|
5345bd321f | ||
|
|
eab8d0063b | ||
|
|
6877977234 | ||
|
|
60cb81362e | ||
|
|
088acb8e86 | ||
|
|
77f59f06cb | ||
|
|
a0882c6105 | ||
|
|
d50eb52172 | ||
|
|
5e5cadd314 | ||
|
|
75b06f82f0 | ||
|
|
2ab050aa60 | ||
|
|
6d2f645e4a | ||
|
|
9f937e29f2 | ||
|
|
abc61a96c8 | ||
|
|
b1d10822d4 | ||
|
|
09d984456d |
@@ -36,6 +36,7 @@ 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/
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# Changelog
|
||||
## [1.8.0 (MONTH Xth, 2026)](#1-8-0)
|
||||
|
||||
### 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.
|
||||
|
||||
### Developer
|
||||
- Tweaked the spawning selector interface to take a bucket function rather than a bucket directly.
|
||||
|
||||
### Molang & Datapacks
|
||||
|
||||
### Particles
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
@@ -0,0 +1,16 @@
|
||||
### Additions
|
||||
|
||||
### Changes
|
||||
- Wild Pokémon now reliably have at least one offensive move, if they can have offensive moves at that level.
|
||||
|
||||
### Fixes
|
||||
|
||||
### Developer
|
||||
|
||||
### Molang & Datapacks
|
||||
- Added `moveset_builders` datapack folder for different styles of movesets.
|
||||
- Added `party_pools` datapack folder for defining groups of Pokémon for the purposes of complex NPC party definitions.
|
||||
- Added `party_compositions` datapack folder for arranging parties on NPCs that rely on party pools.
|
||||
- Added `composed_pool` NPC party provider type which combines a party pool with a party composition to create more complex NPC parties.
|
||||
|
||||
### Particles
|
||||
@@ -37,6 +37,7 @@
|
||||
- [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
|
||||
@@ -46,6 +47,7 @@
|
||||
- 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:
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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,6 +23,7 @@ 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;
|
||||
@@ -32,6 +33,7 @@ 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;
|
||||
@@ -108,6 +110,17 @@ 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,6 +62,8 @@ 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
|
||||
@@ -109,13 +111,7 @@ 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.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.util.*
|
||||
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
|
||||
@@ -244,6 +240,8 @@ 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()
|
||||
@@ -306,6 +304,7 @@ object Cobblemon {
|
||||
this.config.maxPokemonLevel,
|
||||
this.config.maxPokemonFriendship,
|
||||
this.config.maxDynamaxLevel,
|
||||
this.config.unlockAllMoveDexMovesByDefault
|
||||
).sendToPlayer(player)
|
||||
}
|
||||
|
||||
@@ -375,8 +374,12 @@ 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)
|
||||
@@ -403,8 +406,14 @@ 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.")
|
||||
@@ -588,6 +597,7 @@ 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,6 +39,9 @@ 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))
|
||||
|
||||
@@ -48,6 +51,8 @@ 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,6 +22,7 @@ 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.*
|
||||
@@ -249,31 +250,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", GrowableStoneBlock.STAGE_3, 7, 3, null)
|
||||
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null)
|
||||
@JvmField
|
||||
val LARGE_BUDDING_TUMBLESTONE = tumblestoneBlock("large_budding_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, TUMBLESTONE_CLUSTER)
|
||||
val LARGE_BUDDING_TUMBLESTONE = tumblestoneBlock("large_budding_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, TUMBLESTONE_CLUSTER)
|
||||
@JvmField
|
||||
val MEDIUM_BUDDING_TUMBLESTONE = tumblestoneBlock("medium_budding_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_TUMBLESTONE)
|
||||
val MEDIUM_BUDDING_TUMBLESTONE = tumblestoneBlock("medium_budding_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_TUMBLESTONE)
|
||||
@JvmField
|
||||
val SMALL_BUDDING_TUMBLESTONE = tumblestoneBlock("small_budding_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_TUMBLESTONE)
|
||||
val SMALL_BUDDING_TUMBLESTONE = tumblestoneBlock("small_budding_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_TUMBLESTONE)
|
||||
|
||||
@JvmField
|
||||
val SKY_TUMBLESTONE_CLUSTER = tumblestoneBlock("sky_tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
|
||||
val SKY_TUMBLESTONE_CLUSTER = tumblestoneBlock("sky_tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null)
|
||||
@JvmField
|
||||
val LARGE_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("large_budding_sky_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, SKY_TUMBLESTONE_CLUSTER)
|
||||
val LARGE_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("large_budding_sky_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, SKY_TUMBLESTONE_CLUSTER)
|
||||
@JvmField
|
||||
val MEDIUM_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("medium_budding_sky_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_SKY_TUMBLESTONE)
|
||||
val MEDIUM_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("medium_budding_sky_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_SKY_TUMBLESTONE)
|
||||
@JvmField
|
||||
val SMALL_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("small_budding_sky_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_SKY_TUMBLESTONE)
|
||||
val SMALL_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("small_budding_sky_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_SKY_TUMBLESTONE)
|
||||
|
||||
@JvmField
|
||||
val BLACK_TUMBLESTONE_CLUSTER = tumblestoneBlock("black_tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
|
||||
val BLACK_TUMBLESTONE_CLUSTER = tumblestoneBlock("black_tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null)
|
||||
@JvmField
|
||||
val LARGE_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("large_budding_black_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, BLACK_TUMBLESTONE_CLUSTER)
|
||||
val LARGE_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("large_budding_black_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, BLACK_TUMBLESTONE_CLUSTER)
|
||||
@JvmField
|
||||
val MEDIUM_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("medium_budding_black_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_BLACK_TUMBLESTONE)
|
||||
val MEDIUM_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("medium_budding_black_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_BLACK_TUMBLESTONE)
|
||||
@JvmField
|
||||
val SMALL_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("small_budding_black_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_BLACK_TUMBLESTONE)
|
||||
val SMALL_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("small_budding_black_tumblestone", TumblestoneBlock.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)))
|
||||
@@ -282,6 +283,87 @@ 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
|
||||
@@ -456,6 +538,7 @@ 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
|
||||
@@ -584,9 +667,25 @@ 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()))
|
||||
|
||||
@@ -890,4 +989,28 @@ 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,10 +71,12 @@ 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,6 +19,7 @@ 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,6 +69,12 @@ 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)
|
||||
@@ -101,6 +107,7 @@ 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)
|
||||
|
||||
@@ -457,6 +457,10 @@ 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)
|
||||
@@ -1208,6 +1212,83 @@ 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(),
|
||||
@@ -1432,44 +1513,49 @@ 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 = noSettingsItem("normal_gem")
|
||||
val NORMAL_GEM = this.create("normal_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_NORMAL, Item.Properties()))
|
||||
@JvmField
|
||||
val FIRE_GEM = noSettingsItem("fire_gem")
|
||||
val FIRE_GEM = this.create("fire_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_FIRE, Item.Properties()))
|
||||
@JvmField
|
||||
val WATER_GEM = noSettingsItem("water_gem")
|
||||
val WATER_GEM = this.create("water_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_WATER, Item.Properties()))
|
||||
@JvmField
|
||||
val GRASS_GEM = noSettingsItem("grass_gem")
|
||||
val GRASS_GEM = this.create("grass_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_GRASS, Item.Properties()))
|
||||
@JvmField
|
||||
val ELECTRIC_GEM = noSettingsItem("electric_gem")
|
||||
val ELECTRIC_GEM = this.create("electric_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_ELECTRIC, Item.Properties()))
|
||||
@JvmField
|
||||
val ICE_GEM = noSettingsItem("ice_gem")
|
||||
val ICE_GEM = this.create("ice_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_ICE, Item.Properties()))
|
||||
@JvmField
|
||||
val FIGHTING_GEM = noSettingsItem("fighting_gem")
|
||||
val FIGHTING_GEM = this.create("fighting_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_FIGHTING, Item.Properties()))
|
||||
@JvmField
|
||||
val POISON_GEM = noSettingsItem("poison_gem")
|
||||
val POISON_GEM = this.create("poison_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_POISON, Item.Properties()))
|
||||
@JvmField
|
||||
val GROUND_GEM = noSettingsItem("ground_gem")
|
||||
val GROUND_GEM = this.create("ground_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_GROUND, Item.Properties()))
|
||||
@JvmField
|
||||
val FLYING_GEM = noSettingsItem("flying_gem")
|
||||
val FLYING_GEM = this.create("flying_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_FLYING, Item.Properties()))
|
||||
@JvmField
|
||||
val PSYCHIC_GEM = noSettingsItem("psychic_gem")
|
||||
val PSYCHIC_GEM = this.create("psychic_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_PSYCHIC, Item.Properties()))
|
||||
@JvmField
|
||||
val BUG_GEM = noSettingsItem("bug_gem")
|
||||
val BUG_GEM = this.create("bug_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_BUG, Item.Properties()))
|
||||
@JvmField
|
||||
val ROCK_GEM = noSettingsItem("rock_gem")
|
||||
val ROCK_GEM = this.create("rock_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_ROCK, Item.Properties()))
|
||||
@JvmField
|
||||
val GHOST_GEM = noSettingsItem("ghost_gem")
|
||||
val GHOST_GEM = this.create("ghost_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_GHOST, Item.Properties()))
|
||||
@JvmField
|
||||
val DRAGON_GEM = noSettingsItem("dragon_gem")
|
||||
val DRAGON_GEM = this.create("dragon_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_DRAGON, Item.Properties()))
|
||||
@JvmField
|
||||
val DARK_GEM = noSettingsItem("dark_gem")
|
||||
val DARK_GEM = this.create("dark_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_DARK, Item.Properties()))
|
||||
@JvmField
|
||||
val STEEL_GEM = noSettingsItem("steel_gem")
|
||||
val STEEL_GEM = this.create("steel_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_STEEL, Item.Properties()))
|
||||
@JvmField
|
||||
val FAIRY_GEM = noSettingsItem("fairy_gem")
|
||||
val FAIRY_GEM = this.create("fairy_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_FAIRY, Item.Properties()))
|
||||
|
||||
//@JvmField
|
||||
//val BINDING_SOIL = blockItem("binding_soil", CobblemonBlocks.BINDING_SOIL)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -19,7 +20,8 @@ import net.minecraft.world.inventory.MenuType
|
||||
|
||||
object CobblemonMenuType : PlatformRegistry<Registry<MenuType<*>>, ResourceKey<Registry<MenuType<*>>>, MenuType<*>>() {
|
||||
|
||||
val COOKING_POT = MenuType.register("cooking_pot", ::CookingPotMenu)
|
||||
val COOKING_POT = MenuType.register("cooking_pot_menu", ::CookingPotMenu)
|
||||
val TM_MACHINE = MenuType.register("tm_machine_menu", ::TMMachineMenu)
|
||||
|
||||
override val registry: Registry<MenuType<*>>
|
||||
get() = BuiltInRegistries.MENU
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.moves.MoveSelector
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.MovesetBuilderAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.StringToObjectAdapter
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
|
||||
object CobblemonMovesetBuilders : JsonDataRegistry<MovesetBuilder> {
|
||||
override val gson: Gson = GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(MovesetBuilder::class.java, MovesetBuilderAdapter)
|
||||
.registerTypeAdapter(MoveSelector::class.java, StringToObjectAdapter(MoveSelector.selectors))
|
||||
.create()
|
||||
|
||||
override val typeToken = TypeToken.get(MovesetBuilder::class.java)
|
||||
override val resourcePath = "moveset_builders"
|
||||
override val id: ResourceLocation = cobblemonResource("moveset_builders")
|
||||
override val type = PackType.SERVER_DATA
|
||||
override val observable = SimpleObservable<CobblemonMovesetBuilders>()
|
||||
|
||||
val movesetBuilders = mutableMapOf<ResourceLocation, MovesetBuilder>()
|
||||
|
||||
fun getOrThrow(id: ResourceLocation): MovesetBuilder {
|
||||
return movesetBuilders[id]
|
||||
?: throw IllegalArgumentException("Unknown MovesetBuilder id: $id")
|
||||
}
|
||||
|
||||
override fun sync(player: ServerPlayer) { /* These don't sync. But what if they did? Nah probably shouldn't sync. */ }
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, MovesetBuilder>) {
|
||||
movesetBuilders.clear()
|
||||
data.forEach { (id, value) -> value.id = id }
|
||||
movesetBuilders.putAll(data)
|
||||
observable.emit(this)
|
||||
}
|
||||
}
|
||||
@@ -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.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.gui.*
|
||||
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,9 +137,7 @@ 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.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.client.ui.*
|
||||
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
|
||||
@@ -150,6 +148,7 @@ 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
|
||||
@@ -205,6 +204,7 @@ 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
|
||||
@@ -228,6 +228,8 @@ import com.cobblemon.mod.common.net.serverhandling.pasture.UnpasturePokemonHandl
|
||||
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.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
|
||||
@@ -570,6 +572,7 @@ 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))
|
||||
@@ -606,6 +609,10 @@ 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))
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.npc.PartyComposition
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
|
||||
object CobblemonPartyCompositions : JsonDataRegistry<PartyComposition> {
|
||||
override val id: ResourceLocation = cobblemonResource("party_compositions")
|
||||
override val type = PackType.SERVER_DATA
|
||||
override val typeToken: TypeToken<PartyComposition> = TypeToken.get(PartyComposition::class.java)
|
||||
override val resourcePath: String = "party_compositions"
|
||||
override val observable = SimpleObservable<CobblemonPartyCompositions>()
|
||||
override val gson: Gson = GsonBuilder()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
|
||||
val partyCompositions = mutableMapOf<ResourceLocation, PartyComposition>()
|
||||
|
||||
override fun sync(player: ServerPlayer) {
|
||||
// probably worth syncing which exist, yeah. the deeper details probably not. I'll get to this eventually.
|
||||
}
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, PartyComposition>) {
|
||||
data.entries.forEach { it.value.id = it.key }
|
||||
partyCompositions.clear()
|
||||
partyCompositions.putAll(data)
|
||||
observable.emit(this)
|
||||
Cobblemon.LOGGER.info("Loaded ${data.size} party compositions.")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.npc.PartyPool
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.util.ScriptableIntRange
|
||||
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.IntRangeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.MovesetBuilderReferenceAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.PokemonPropertiesAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.ScriptableIntRangeAdapter
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
|
||||
object CobblemonPartyPools : JsonDataRegistry<PartyPool> {
|
||||
override val id: ResourceLocation = cobblemonResource("party_pools")
|
||||
override val type = PackType.SERVER_DATA
|
||||
override val typeToken: TypeToken<PartyPool> = TypeToken.get(PartyPool::class.java)
|
||||
override val resourcePath: String = "party_pools"
|
||||
override val observable = SimpleObservable<CobblemonPartyPools>()
|
||||
override val gson: Gson = GsonBuilder()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(PokemonProperties::class.java, PokemonPropertiesAdapter(saveLong = false))
|
||||
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
|
||||
.registerTypeAdapter(ScriptableIntRange::class.java, ScriptableIntRangeAdapter)
|
||||
.registerTypeAdapter(MovesetBuilder::class.java, MovesetBuilderReferenceAdapter)
|
||||
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
|
||||
val partyPools = mutableMapOf<ResourceLocation, PartyPool>()
|
||||
|
||||
override fun sync(player: ServerPlayer) {
|
||||
// probably worth syncing. Knowing the details might help exploit the server? lot of effort for little though.
|
||||
}
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, PartyPool>) {
|
||||
data.entries.forEach { it.value.id = it.key }
|
||||
partyPools.clear()
|
||||
partyPools.putAll(data)
|
||||
observable.emit(this)
|
||||
}
|
||||
}
|
||||
@@ -200,6 +200,22 @@ 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
|
||||
|
||||
+3
-39
@@ -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.Optional
|
||||
import java.util.*
|
||||
|
||||
class PlantTumblestoneContext(val pos: BlockPos, val block: TumblestoneBlock)
|
||||
|
||||
@@ -29,42 +29,6 @@ class PlantTumblestoneCriterion(
|
||||
}
|
||||
|
||||
override fun matches(player: ServerPlayer, context: PlantTumblestoneContext): Boolean {
|
||||
return context.block.canGrow(context.pos, player.level())
|
||||
return context.block.canGrow(player.level().getBlockState(context.pos), 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,6 +1842,30 @@ 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) {
|
||||
@@ -1941,8 +1965,7 @@ object MoLangFunctions {
|
||||
return@put DoubleValue(if (pokemon.addRideBoost(stat, value)) 1.0 else 0.0)
|
||||
}
|
||||
map.put("initialize_moveset") { params ->
|
||||
val preferLatest = params.getBooleanOrNull(0) ?: true
|
||||
pokemon.initializeMoveset(preferLatest)
|
||||
pokemon.initializeMovesetFromDefault()
|
||||
return@put DoubleValue.ONE
|
||||
}
|
||||
map.put("validate_moveset") { params ->
|
||||
|
||||
@@ -24,6 +24,7 @@ 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>()
|
||||
|
||||
@@ -58,6 +59,9 @@ class BenchedMoves : Iterable<BenchedMove> {
|
||||
return false
|
||||
}
|
||||
|
||||
/*if (emit) {
|
||||
moveAddedFunction(benchedMove)
|
||||
}*/
|
||||
doThenEmit { benchedMoves.add(benchedMove) }
|
||||
return true
|
||||
}
|
||||
@@ -174,4 +178,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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.moves
|
||||
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
/**
|
||||
* This is all you need tbh. A moveset builder that picks moves from up to 4 slots of move selectors.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 5th, 2025
|
||||
*/
|
||||
class DefaultMovesetBuilder : MovesetBuilder {
|
||||
companion object {
|
||||
val TYPE = cobblemonResource("default")
|
||||
}
|
||||
|
||||
override val type: ResourceLocation = TYPE
|
||||
override var id: ResourceLocation = cobblemonResource("temp") // replaced on load.
|
||||
|
||||
val slot1: List<MoveSelector> = listOf()
|
||||
val slot2: List<MoveSelector> = listOf()
|
||||
val slot3: List<MoveSelector> = listOf()
|
||||
val slot4: List<MoveSelector> = listOf()
|
||||
|
||||
override fun build(form: FormData, level: Int): MoveSet {
|
||||
val chosenMoves = mutableSetOf<MoveTemplate>()
|
||||
tryAddMoveFromSlot(form, level, slot1, chosenMoves)
|
||||
tryAddMoveFromSlot(form, level, slot2, chosenMoves)
|
||||
tryAddMoveFromSlot(form, level, slot3, chosenMoves)
|
||||
tryAddMoveFromSlot(form, level, slot4, chosenMoves)
|
||||
if (chosenMoves.isEmpty()) {
|
||||
chosenMoves.add(Moves.getExceptional())
|
||||
}
|
||||
val moveset = MoveSet()
|
||||
chosenMoves.map(MoveTemplate::create).forEach(moveset::add)
|
||||
return moveset
|
||||
}
|
||||
|
||||
fun tryAddMoveFromSlot(form: FormData, level: Int, selectors: List<MoveSelector>, chosenMoves: MutableSet<MoveTemplate>) {
|
||||
for (selector in selectors) {
|
||||
val move = selector(form, form.moves, level, chosenMoves)
|
||||
if (move != null) {
|
||||
chosenMoves.add(move)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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.moves
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
|
||||
import com.cobblemon.mod.common.api.pokemon.moves.Learnset
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.util.weightedSelection
|
||||
|
||||
/**
|
||||
* A function that selects a move from a given [Learnset] for a particular [FormData] at a certain level,
|
||||
* taking into account any moves that have already been chosen for the moveset.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 5th, 2025
|
||||
*/
|
||||
fun interface MoveSelector {
|
||||
operator fun invoke(form: FormData, learnset: Learnset, level: Int, chosenMoves: Set<MoveTemplate>): MoveTemplate?
|
||||
|
||||
companion object {
|
||||
val NONE = MoveSelector { _, _, _, _ -> null }
|
||||
val LAST_LEVELUP = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val levels = learnset.levelUpMoves.keys.filter { it <= level }.sortedDescending()
|
||||
levels.forEach {
|
||||
val moves = (learnset.levelUpMoves[it] ?: return@forEach) - chosenMoves
|
||||
if (moves.isNotEmpty()) {
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
}
|
||||
return@MoveSelector null
|
||||
}
|
||||
val LAST_OFFENSIVE = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val levels = learnset.levelUpMoves.keys.filter { it <= level }.sortedDescending()
|
||||
levels.forEach {
|
||||
val moves = (learnset.levelUpMoves[it]?.filter { it.damageCategory != DamageCategories.STATUS } ?: return@forEach) - chosenMoves
|
||||
if (moves.isNotEmpty()) {
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
}
|
||||
return@MoveSelector null
|
||||
}
|
||||
val LAST_SUITABLE_OFFENSIVE = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val levels = learnset.levelUpMoves.keys.filter { it <= level }.sortedDescending()
|
||||
val suitableCategories = if (form.baseStats[Stats.ATTACK]!! > form.baseStats[Stats.SPECIAL_ATTACK]!!) {
|
||||
setOf(DamageCategories.PHYSICAL)
|
||||
} else if (form.baseStats[Stats.SPECIAL_ATTACK]!! > form.baseStats[Stats.ATTACK]!!) {
|
||||
setOf(DamageCategories.SPECIAL)
|
||||
} else {
|
||||
setOf(DamageCategories.PHYSICAL, DamageCategories.SPECIAL)
|
||||
}
|
||||
levels.forEach {
|
||||
val moves = (learnset.levelUpMoves[it]?.filter { it.damageCategory in suitableCategories } ?: return@forEach) - chosenMoves
|
||||
if (moves.isNotEmpty()) {
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
}
|
||||
return@MoveSelector null
|
||||
}
|
||||
val LAST_STATUS = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val levels = learnset.levelUpMoves.keys.filter { it <= level }.sortedDescending()
|
||||
levels.forEach {
|
||||
val moves = (learnset.levelUpMoves[it]?.filter { it.damageCategory == DamageCategories.STATUS } ?: return@forEach) - chosenMoves
|
||||
if (moves.isNotEmpty()) {
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
}
|
||||
return@MoveSelector null
|
||||
}
|
||||
val STAB = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val stabMoves = moves.filter { it.elementalType in form.types && it.damageCategory != DamageCategories.STATUS }
|
||||
return@MoveSelector stabMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val STAB_PHYSICAL = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val stabMoves = moves.filter { it.elementalType in form.types && it.damageCategory == DamageCategories.PHYSICAL }
|
||||
return@MoveSelector stabMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val STAB_SPECIAL = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val stabMoves = moves.filter { it.elementalType in form.types && it.damageCategory == DamageCategories.SPECIAL }
|
||||
return@MoveSelector stabMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val PHYSICAL = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val physicalMoves = moves.filter { it.damageCategory == DamageCategories.PHYSICAL }
|
||||
return@MoveSelector physicalMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val SPECIAL = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val specialMoves = moves.filter { it.damageCategory == DamageCategories.SPECIAL }
|
||||
return@MoveSelector specialMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val OFFENSIVE = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val offensiveMoves = moves.filter { it.damageCategory != DamageCategories.STATUS }
|
||||
return@MoveSelector offensiveMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val RANDOM_LEVELUP = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val STATUS = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val statusMoves = moves.filter { it.damageCategory == DamageCategories.STATUS }
|
||||
return@MoveSelector statusMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val TM = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.tmMoves - chosenMoves - learnset.getLevelUpMovesUpTo(level)
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val STAB_TM = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.tmMoves - chosenMoves - learnset.getLevelUpMovesUpTo(level)
|
||||
val stabMoves = moves.filter { it.elementalType in form.types }
|
||||
return@MoveSelector stabMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val EGG = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.eggMoves - chosenMoves - learnset.getLevelUpMovesUpTo(level)
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
|
||||
val selectors = mutableMapOf(
|
||||
"none" to NONE,
|
||||
"last_levelup" to LAST_LEVELUP,
|
||||
"last_offensive" to LAST_OFFENSIVE,
|
||||
"last_suitable_offensive" to LAST_SUITABLE_OFFENSIVE,
|
||||
"last_status" to LAST_STATUS,
|
||||
"levelup" to RANDOM_LEVELUP,
|
||||
"stab" to STAB,
|
||||
"stab_physical" to STAB_PHYSICAL,
|
||||
"stab_special" to STAB_SPECIAL,
|
||||
"physical" to PHYSICAL,
|
||||
"special" to SPECIAL,
|
||||
"offensive" to OFFENSIVE,
|
||||
"status" to STATUS,
|
||||
"tm" to TM,
|
||||
"stab_tm" to STAB_TM,
|
||||
"egg" to EGG
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ 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 }
|
||||
|
||||
/**
|
||||
@@ -222,4 +223,4 @@ class MoveSet : Iterable<Move> {
|
||||
{ moveSet -> moveSet.toList() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@ import com.bedrockk.molang.runtime.struct.MoStruct
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategory
|
||||
import com.cobblemon.mod.common.api.types.ElementalType
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.battles.MoveTarget
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.util.codec.CodecUtils
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
@@ -53,7 +53,8 @@ open class MoveTemplate(
|
||||
val pp: Int,
|
||||
val priority: Int,
|
||||
val critRatio: Double,
|
||||
val effectChances: Array<Double>
|
||||
val effectChances: Array<Double>,
|
||||
val weight: Float
|
||||
) {
|
||||
val struct: MoStruct by lazy {
|
||||
QueryStruct(hashMapOf())
|
||||
@@ -69,9 +70,9 @@ open class MoveTemplate(
|
||||
.addFunction("max_pp") { DoubleValue(maxPp) }
|
||||
.addFunction("priority") { DoubleValue(priority) }
|
||||
.addFunction("crit_ratio") { DoubleValue(critRatio) }
|
||||
.addFunction("weight") { DoubleValue(weight.toDouble()) }
|
||||
}
|
||||
|
||||
|
||||
val displayName: MutableComponent
|
||||
get() = lang("move.$name")
|
||||
val description: MutableComponent
|
||||
@@ -89,7 +90,8 @@ open class MoveTemplate(
|
||||
pp = 5,
|
||||
priority = 0,
|
||||
critRatio = 0.0,
|
||||
effectChances = emptyArray()
|
||||
effectChances = emptyArray(),
|
||||
weight = 0F,
|
||||
)
|
||||
|
||||
companion object {
|
||||
@@ -160,4 +162,12 @@ open class MoveTemplate(
|
||||
}
|
||||
return this.elementalType
|
||||
}
|
||||
|
||||
fun getSelectionWeight(form: FormData): Float {
|
||||
return if (this in form.signatureMoves) {
|
||||
weight * MovesetBuilder.signatureMoveWeightMultiplier
|
||||
} else {
|
||||
weight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,15 @@ package com.cobblemon.mod.common.api.moves
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.data.DataRegistry
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.battles.MoveTarget
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.net.messages.client.data.MovesRegistrySyncPacket
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.fromJson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
@@ -46,6 +46,14 @@ object Moves : DataRegistry {
|
||||
this.idMapping.clear()
|
||||
this.moveScripts.clear()
|
||||
|
||||
val weights = mutableMapOf<String, Number>()
|
||||
manager.getResourceStack(cobblemonResource("move_weights.json")).forEach {
|
||||
it.open().use {
|
||||
val map = GsonBuilder().create().fromJson<Map<String, Number>>(it.reader())
|
||||
weights.putAll(map)
|
||||
}
|
||||
}
|
||||
|
||||
ShowdownService.service.resetRegistryData("move")
|
||||
manager.listResources("moves") { it.path.endsWith(".js") }.forEach { (identifier, resource) ->
|
||||
resource.open().use { stream ->
|
||||
@@ -75,6 +83,7 @@ object Moves : DataRegistry {
|
||||
val priority = jsMove.get("priority").asInt
|
||||
val critRatio = jsMove.get("critRatio")?.asDouble ?: 1.0
|
||||
val effectChances = arrayListOf<Double>()
|
||||
val weight = weights[id]?.toFloat() ?: 50F
|
||||
val secondariesMember = jsMove.get("secondaries")
|
||||
val secondaryMember = jsMove.get("secondary")
|
||||
if (secondariesMember != null && secondariesMember is JsonArray) {
|
||||
@@ -92,7 +101,20 @@ object Moves : DataRegistry {
|
||||
effectChances += secondaryMember.get("chance").asDouble
|
||||
}
|
||||
}
|
||||
val move = MoveTemplate(id, num, elementalType, damageCategory, power, target, accuracy, pp, priority, critRatio, effectChances.toTypedArray())
|
||||
val move = MoveTemplate(
|
||||
name = id,
|
||||
num = num,
|
||||
elementalType = elementalType,
|
||||
damageCategory = damageCategory,
|
||||
power = power,
|
||||
target = target,
|
||||
accuracy = accuracy,
|
||||
pp = pp,
|
||||
priority = priority,
|
||||
critRatio = critRatio,
|
||||
effectChances = effectChances.toTypedArray(),
|
||||
weight = weight
|
||||
)
|
||||
this.register(move)
|
||||
} catch (e: Exception) {
|
||||
Cobblemon.LOGGER.error("Caught exception trying to resolve the move '{}'", id, e)
|
||||
@@ -113,7 +135,7 @@ object Moves : DataRegistry {
|
||||
@JvmStatic
|
||||
fun getByNameOrDummy(name: String) = allMoves[name.lowercase()] ?: MoveTemplate.dummy(name.lowercase())
|
||||
@JvmStatic
|
||||
fun getExceptional() = getByName("tackle") ?: allMoves.values.random()
|
||||
fun getExceptional() = getByName("tackle") ?: allMoves.values.first()
|
||||
@JvmStatic
|
||||
fun count() = allMoves.size
|
||||
@JvmStatic
|
||||
@@ -129,5 +151,4 @@ object Moves : DataRegistry {
|
||||
this.allMoves[move.name] = move
|
||||
this.idMapping[move.num] = move
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.moves
|
||||
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
/**
|
||||
* Builders a moveset for a particular [FormData] and level. Probably only really need [DefaultMovesetBuilder] though.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 5th, 2025
|
||||
*/
|
||||
interface MovesetBuilder {
|
||||
/** The ID of this builder. It's autofilled from the registry loader. */
|
||||
var id: ResourceLocation
|
||||
/** The subtype designation for this moveset builder. Generally unused once read from JSON. */
|
||||
val type: ResourceLocation
|
||||
|
||||
/** Builds a brand new moveset for the [form] and [level]. */
|
||||
fun build(form: FormData, level: Int): MoveSet
|
||||
|
||||
companion object {
|
||||
/** Weight multiplier for moves that are in the form's signatureMoves list. Makes them way more likely to be chosen. */
|
||||
var signatureMoveWeightMultiplier = 5F
|
||||
/** Subtypes of MovesetBuilder. I personally doubt you'll need any other subtypes, but hey. */
|
||||
val movesetBuilderTypes = mutableMapOf<ResourceLocation, Class<out MovesetBuilder>>(
|
||||
cobblemonResource("default") to DefaultMovesetBuilder::class.java
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,14 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.npc
|
||||
|
||||
import com.cobblemon.mod.common.api.npc.partyproviders.ComposedPoolPartyProvider
|
||||
import com.cobblemon.mod.common.api.npc.partyproviders.PoolPartyProvider
|
||||
import com.cobblemon.mod.common.api.npc.partyproviders.ScriptPartyProvider
|
||||
import com.cobblemon.mod.common.api.npc.partyproviders.SimplePartyProvider
|
||||
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.google.gson.JsonElement
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
/**
|
||||
@@ -25,14 +27,15 @@ import net.minecraft.server.level.ServerPlayer
|
||||
*/
|
||||
interface NPCPartyProvider {
|
||||
companion object {
|
||||
val types = mutableMapOf<String, (String) -> NPCPartyProvider>(
|
||||
SimplePartyProvider.TYPE to { SimplePartyProvider() },
|
||||
PoolPartyProvider.TYPE to { PoolPartyProvider() },
|
||||
ScriptPartyProvider.TYPE to { ScriptPartyProvider() }
|
||||
val types = mutableMapOf(
|
||||
SimplePartyProvider.TYPE to ::SimplePartyProvider,
|
||||
PoolPartyProvider.TYPE to ::PoolPartyProvider,
|
||||
ScriptPartyProvider.TYPE to ::ScriptPartyProvider,
|
||||
ComposedPoolPartyProvider.TYPE to ::ComposedPoolPartyProvider
|
||||
)
|
||||
}
|
||||
|
||||
val type: String
|
||||
val type: ResourceLocation
|
||||
val isStatic: Boolean
|
||||
fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer> = emptyList()): NPCPartyStore
|
||||
// Why did I opt for manual JSON loading??? I must have had a reason but I can't remember. Maybe for S2C? Use a codec doofus
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.npc
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* A kind of definition for how to compose a party of Pokémon given a [PartyPool].
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 7th, 2025
|
||||
*/
|
||||
class PartyComposition {
|
||||
var id = cobblemonResource("dummy_composition")
|
||||
// val displayName = "cobblemon.party_composition.$id" // dunno if i will use this yet
|
||||
val scrambleOrder: Boolean = false
|
||||
val slot1 = listOf<String>()
|
||||
val slot2 = listOf<String>()
|
||||
val slot3 = listOf<String>()
|
||||
val slot4 = listOf<String>()
|
||||
val slot5 = listOf<String>()
|
||||
val slot6 = listOf<String>()
|
||||
|
||||
fun compose(pool: PartyPool, level: Int, aspects: Set<String>, desiredPokemonCount: Int, runtime: MoLangRuntime, random: Random = Random.Default): List<Pokemon> {
|
||||
val chosenEntries = mutableListOf<PartyPool.PoolEntry>()
|
||||
val availableEntries = pool.entries.filter { level in it.npcLevels.resolve(runtime) && (it.npcAspects.isEmpty() || it.npcAspects.all(aspects::contains)) }
|
||||
|
||||
for (labelSet in listOf(slot1, slot2, slot3, slot4, slot5, slot6)) {
|
||||
pool.tryChoosingEntry(labelSet, runtime, random, availableEntries, chosenEntries)
|
||||
if (chosenEntries.size >= desiredPokemonCount) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
val finalEntries = if (scrambleOrder) {
|
||||
chosenEntries.shuffled(random)
|
||||
} else {
|
||||
chosenEntries
|
||||
}
|
||||
|
||||
val pokemon = finalEntries.map { entry ->
|
||||
val levelVariation = entry.levelVariation.resolve(runtime)
|
||||
val lvlVariation = if (levelVariation.first == levelVariation.last) {
|
||||
levelVariation.first
|
||||
} else {
|
||||
levelVariation.random(random)
|
||||
}
|
||||
|
||||
val finalLevel = level + lvlVariation
|
||||
val properties = entry.pokemon.copy()
|
||||
properties.level = properties.level ?: finalLevel
|
||||
val pokemon = properties.create()
|
||||
entry.movesetBuilders.randomOrNull()?.let(pokemon::initializeMovesetFrom)
|
||||
pokemon
|
||||
}
|
||||
|
||||
return pokemon
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.npc
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.util.ScriptableIntRange
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.toProperties
|
||||
import com.cobblemon.mod.common.util.weightedSelection
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* A pool of Pokémon to choose from, typically for generating a party for an NPC. The entries
|
||||
* of the pool work on a weight and label system which is used as part of a [PartyComposition].
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 7th, 2025
|
||||
*/
|
||||
class PartyPool {
|
||||
var id = cobblemonResource("dummy")
|
||||
val displayName = "cobblemon.party_pool.$id"
|
||||
val entries = mutableListOf<PoolEntry>()
|
||||
|
||||
class PoolEntry {
|
||||
val pokemon: PokemonProperties = "pikachu".toProperties()
|
||||
val labels: List<String> = emptyList()
|
||||
val npcAspects: List<String> = emptyList()
|
||||
val required: List<String> = emptyList()
|
||||
val excluded: List<String> = emptyList()
|
||||
val maxSelectableTimes: ExpressionLike = "6".asExpressionLike()
|
||||
val levelVariation: ScriptableIntRange = ScriptableIntRange(0, 0)
|
||||
val npcLevels: ScriptableIntRange = ScriptableIntRange(1, 100)
|
||||
val movesetBuilders: List<MovesetBuilder> = emptyList()
|
||||
val weight: ExpressionLike = "50".asExpressionLike()
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes through each of the labels in order, trying to find a valid choice. Once it finds one, it skips the later
|
||||
* labels and adds the value to [chosenEntries] and returns. If none of the labels could find a valid entry, nothing is added.
|
||||
*/
|
||||
fun tryChoosingEntry(
|
||||
labels: List<String>,
|
||||
runtime: MoLangRuntime,
|
||||
random: Random,
|
||||
availableEntries: List<PoolEntry>,
|
||||
chosenEntries: MutableList<PoolEntry>,
|
||||
) {
|
||||
for (label in labels) {
|
||||
val entries = availableEntries
|
||||
.filter { label in it.labels || label.lowercase() == "any" }
|
||||
.filter { entry ->
|
||||
// Check required labels
|
||||
entry.required.all { it in labels } &&
|
||||
// Check excluded labels
|
||||
entry.excluded.none { it in labels } &&
|
||||
// Check max selectable times
|
||||
chosenEntries.count { it == entry } < entry.maxSelectableTimes.resolveFloat(runtime)
|
||||
}
|
||||
|
||||
if (entries.isNotEmpty()) {
|
||||
val selectedEntry = entries.weightedSelection(random) { it.weight.resolveFloat(runtime) } ?: return
|
||||
chosenEntries.add(selectedEntry)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.npc.partyproviders
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.CobblemonPartyCompositions
|
||||
import com.cobblemon.mod.common.CobblemonPartyPools
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.npc.NPCPartyProvider
|
||||
import com.cobblemon.mod.common.api.npc.PartyComposition
|
||||
import com.cobblemon.mod.common.api.npc.PartyPool
|
||||
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.util.asArrayValue
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.resolveInt
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import kotlin.random.Random
|
||||
import kotlin.ranges.random
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
/**
|
||||
* An [NPCPartyProvider] which uses a [PartyComposition] and [PartyPool] to generate a party.
|
||||
*
|
||||
* There are controls for minimum and maximum Pokémon count, as well as whether to use a fixed random seed for the NPC.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 8th, 2025
|
||||
*/
|
||||
class ComposedPoolPartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
val TYPE = cobblemonResource("composed_pool")
|
||||
}
|
||||
|
||||
override val type = TYPE
|
||||
override var isStatic: Boolean = true
|
||||
var useFixedRandom: Boolean = false
|
||||
var minPokemon: Expression = "1".asExpression()
|
||||
var maxPokemon: Expression = "6".asExpression()
|
||||
lateinit var pool: PartyPool
|
||||
lateinit var composition: PartyComposition
|
||||
|
||||
override fun loadFromJSON(json: JsonElement) {
|
||||
json as JsonObject
|
||||
isStatic = json.getAsJsonPrimitive("isStatic")?.asBoolean ?: true
|
||||
useFixedRandom = json.getAsJsonPrimitive("useFixedRandom")?.asBoolean ?: false
|
||||
pool = CobblemonPartyPools.partyPools[json.get("pool").asString.asIdentifierDefaultingNamespace()]
|
||||
?: throw IllegalArgumentException("Unknown PartyPool id: ${json.get("pool").asString}")
|
||||
composition = CobblemonPartyCompositions.partyCompositions[json.get("composition").asString.asIdentifierDefaultingNamespace()]
|
||||
?: throw IllegalArgumentException("Unknown PartyComposition id: ${json.get("composition").asString}")
|
||||
minPokemon = json.getAsJsonPrimitive("minPokemon").asString?.asExpression() ?: "1".asExpression()
|
||||
maxPokemon = json.getAsJsonPrimitive("maxPokemon").asString?.asExpression() ?: "6".asExpression()
|
||||
}
|
||||
|
||||
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
|
||||
val runtime = MoLangRuntime().setup().withQueryValue("npc", npc.struct)
|
||||
val random = if (useFixedRandom) Random(npc.uuid.hashCode()) else Random.Default
|
||||
runtime.withQueryValue("level", DoubleValue(level))
|
||||
runtime.withQueryValue("players", players.asArrayValue { it.asMoLangValue() })
|
||||
if (players.size == 1) {
|
||||
// This is for the convenience, most cases will be pvn one player
|
||||
runtime.withQueryValue("player", players.first().asMoLangValue())
|
||||
}
|
||||
val minPokemon = runtime.resolveInt(this.minPokemon)
|
||||
val maxPokemon = runtime.resolveInt(this.maxPokemon)
|
||||
val desiredPokemonCount = (minPokemon..maxPokemon).random(random)
|
||||
|
||||
val pokemonList = composition.compose(
|
||||
pool = pool,
|
||||
level = level,
|
||||
aspects = npc.aspects,
|
||||
desiredPokemonCount = desiredPokemonCount,
|
||||
runtime = runtime,
|
||||
random = random
|
||||
)
|
||||
|
||||
val partyStore = NPCPartyStore(npc)
|
||||
pokemonList.forEach(partyStore::add)
|
||||
return partyStore
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -48,7 +48,7 @@ import kotlin.random.Random
|
||||
*/
|
||||
class PoolPartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
const val TYPE = "pool"
|
||||
val TYPE = cobblemonResource("pool")
|
||||
}
|
||||
|
||||
@Transient
|
||||
@@ -150,10 +150,10 @@ class PoolPartyProvider : NPCPartyProvider {
|
||||
val instance = selected.pokemon.copy().also { it.level = it.level ?: dictatedLevel ?: randomLevel }.create()
|
||||
party.add(instance)
|
||||
}
|
||||
if (party.none()) {
|
||||
LOGGER.error("${npc} has no Pokemon on Party")
|
||||
}
|
||||
|
||||
if (party.none()) {
|
||||
LOGGER.error("$npc had no Pokémon produced by their party provider")
|
||||
}
|
||||
}
|
||||
|
||||
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
class ScriptPartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
const val TYPE = "script"
|
||||
val TYPE = cobblemonResource("script")
|
||||
}
|
||||
|
||||
override val type = TYPE
|
||||
|
||||
+2
-1
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.util.asArrayValue
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.getBooleanOrNull
|
||||
import com.cobblemon.mod.common.util.toProperties
|
||||
import com.google.gson.JsonElement
|
||||
@@ -29,7 +30,7 @@ import net.minecraft.server.level.ServerPlayer
|
||||
*/
|
||||
class SimplePartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
const val TYPE = "simple"
|
||||
val TYPE = cobblemonResource("simple")
|
||||
}
|
||||
|
||||
@Transient
|
||||
|
||||
@@ -143,6 +143,8 @@ 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,7 +29,6 @@ abstract class AbstractPokedexManager {
|
||||
|
||||
fun deleteSpeciesRecord(speciesId: ResourceLocation) {
|
||||
speciesRecords.remove(speciesId)
|
||||
markDirty()
|
||||
}
|
||||
|
||||
fun deleteFormRecord(speciesId: ResourceLocation, formName: String) {
|
||||
@@ -40,8 +39,6 @@ abstract class AbstractPokedexManager {
|
||||
deleteSpeciesRecord(speciesId)
|
||||
return
|
||||
}
|
||||
|
||||
markDirty()
|
||||
}
|
||||
|
||||
fun getSpeciesRecord(speciesId: ResourceLocation): SpeciesDexRecord? {
|
||||
@@ -164,8 +161,4 @@ abstract class AbstractPokedexManager {
|
||||
return newValue
|
||||
}
|
||||
}
|
||||
|
||||
open fun markDirty() {
|
||||
// Save stuff
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,9 @@ 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
|
||||
@@ -25,6 +27,7 @@ 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
|
||||
|
||||
@@ -41,11 +44,13 @@ 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, knowledge ->
|
||||
).apply(instance) { genders, seenShinyStates, highestLevel, knowledge ->
|
||||
FormDexRecord().also {
|
||||
it.genders.addAll(genders.map(Gender::valueOf))
|
||||
it.seenShinyStates.addAll(seenShinyStates)
|
||||
it.highestLevel = highestLevel
|
||||
it.knowledge = PokedexEntryProgress.valueOf(knowledge)
|
||||
}
|
||||
}
|
||||
@@ -59,6 +64,9 @@ 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
|
||||
@@ -80,11 +88,17 @@ class FormDexRecord {
|
||||
struct = QueryStruct(hashMapOf())
|
||||
.addFunction("data") { data }
|
||||
.addFunction("knowledge") { StringValue(knowledge.name) }
|
||||
.addFunction("has_seen_gender") { params -> genders.contains(Gender.valueOf(params.getString(0).uppercase())) }
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
||||
fun clone() = FormDexRecord().also {
|
||||
it.genders.addAll(genders)
|
||||
it.highestLevel = highestLevel
|
||||
it.seenShinyStates.addAll(seenShinyStates)
|
||||
it.knowledge = knowledge
|
||||
}
|
||||
@@ -114,7 +128,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)
|
||||
}
|
||||
@@ -135,6 +149,9 @@ 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
|
||||
@@ -159,6 +176,7 @@ 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,6 +9,8 @@
|
||||
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
|
||||
@@ -48,10 +50,44 @@ class PokedexManager(
|
||||
fun obtain(pokemon: Pokemon) {
|
||||
val speciesId = pokemon.species.resourceIdentifier
|
||||
val formName = pokemon.form.name
|
||||
getOrCreateSpeciesRecord(speciesId).getOrCreateFormRecord(formName).caught(PokedexEntityData(pokemon = pokemon, disguise = null))
|
||||
getOrCreateSpeciesRecord(speciesId).getOrCreateFormRecord(formName).obtained(PokedexEntityData(pokemon = pokemon, disguise = null))
|
||||
}
|
||||
|
||||
override fun markDirty() {
|
||||
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 initialize() {
|
||||
|
||||
@@ -26,7 +26,8 @@ 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.
|
||||
* 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.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since August 23rd, 2024
|
||||
@@ -36,10 +37,12 @@ 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, formRecords ->
|
||||
).apply(instance) { aspects, highestLevel, formRecords ->
|
||||
SpeciesDexRecord().also {
|
||||
it.aspects.addAll(aspects)
|
||||
it.highestLevel = highestLevel
|
||||
it.formRecords.putAll(formRecords)
|
||||
}
|
||||
}
|
||||
@@ -48,13 +51,16 @@ 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, formRecords=$formRecords)"
|
||||
return "SpeciesDexRecord(aspects=$aspects, highestLevel=$highestLevel, formRecords=$formRecords)"
|
||||
}
|
||||
|
||||
@Transient
|
||||
@@ -77,7 +83,14 @@ class SpeciesDexRecord {
|
||||
.addFunction("is_caught") { params ->
|
||||
DoubleValue(formRecords.values.any { it.knowledge.name.equals("CAUGHT", ignoreCase = true) })
|
||||
}
|
||||
// more, also worth moving to something like MoLangFunctions.kt so it's easier to API in more values
|
||||
.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
|
||||
|
||||
@Transient
|
||||
lateinit var pokedexManager: AbstractPokedexManager
|
||||
@@ -94,10 +107,16 @@ 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>) {
|
||||
@@ -128,6 +147,7 @@ class SpeciesDexRecord {
|
||||
|
||||
fun clone() = SpeciesDexRecord().also {
|
||||
it.aspects.addAll(aspects)
|
||||
it.highestLevel = highestLevel
|
||||
it.formRecords.putAll(formRecords.mapValues { it.value.clone() })
|
||||
}
|
||||
|
||||
@@ -139,6 +159,7 @@ 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)
|
||||
@@ -149,6 +170,7 @@ 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,5 +11,6 @@ package com.cobblemon.mod.common.api.pokedex.filter
|
||||
enum class SearchByType {
|
||||
SPECIES,
|
||||
ABILITIES,
|
||||
MOVES,
|
||||
DROPS;
|
||||
}
|
||||
+154
-1
@@ -9,14 +9,22 @@
|
||||
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
|
||||
|
||||
/**
|
||||
@@ -47,6 +55,23 @@ 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>()
|
||||
@@ -68,4 +93,132 @@ 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
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.pokemon
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.Cobblemon.config
|
||||
import com.cobblemon.mod.common.CobblemonMovesetBuilders
|
||||
import com.cobblemon.mod.common.api.abilities.Abilities
|
||||
import com.cobblemon.mod.common.api.abilities.Ability
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
@@ -148,6 +149,13 @@ open class PokemonProperties {
|
||||
props.originalTrainerType = OriginalTrainerType.entries.toList().parsePropertyOfCollection(keyPairs, listOf("originaltrainertype", "ottype"), labelsOptional = true) { it.name.lowercase() }
|
||||
props.originalTrainer = parsePlayerProperty(keyPairs, listOf("originaltrainer", "ot"))
|
||||
props.moves = parseString(keyPairs, listOf("moves"))?.split(",")
|
||||
props.movesetBuilders = parseString(keyPairs, listOf("moveset_builders", "movesetbuilders", "moveset_builder", "movesetbuilder"))?.split(",")?.mapNotNull {
|
||||
try {
|
||||
it.asIdentifierDefaultingNamespace()
|
||||
} catch (_: ResourceLocationException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
props.heldItem = parseString(keyPairs, listOf("helditem", "held_item"))
|
||||
props.minPerfectIVs = parseIntProperty(keyPairs, listOf("min_perfect_ivs"))?.coerceIn(0, Stats.PERMANENT.size)
|
||||
props.scaleModifier = parseFloatProperty(keyPairs, listOf("scale_modifier"))
|
||||
@@ -356,6 +364,7 @@ open class PokemonProperties {
|
||||
var originalTrainerType: OriginalTrainerType? = null
|
||||
var originalTrainer: String? = null // Original Trainer by Username or UUID
|
||||
var moves: List<String>? = null
|
||||
var movesetBuilders: List<ResourceLocation>? = null
|
||||
var heldItem: String? = null
|
||||
var cosmeticItem: String? = null
|
||||
var scaleModifier: Float? = null
|
||||
@@ -462,7 +471,7 @@ open class PokemonProperties {
|
||||
}
|
||||
moves?.let { moves ->
|
||||
if (pokemon.moveSet.getMoves().isEmpty()) {
|
||||
pokemon.initializeMoveset(preferLatest = true)
|
||||
pokemon.initializeMovesetFromDefault()
|
||||
}
|
||||
val moveTemplates = moves.mapNotNull(Moves::getByName).shuffled()
|
||||
val replaceableIndices = (0..3).filterNot { pokemon.moveSet[it]?.template?.let(moveTemplates::contains) == true }.shuffled()
|
||||
@@ -475,6 +484,13 @@ open class PokemonProperties {
|
||||
pokemon.moveSet.update()
|
||||
}
|
||||
}
|
||||
movesetBuilders?.let { movesetBuilders ->
|
||||
val builders = movesetBuilders.mapNotNull { CobblemonMovesetBuilders.movesetBuilders[it] }
|
||||
if (builders.isNotEmpty()) {
|
||||
val movesetBuilder = builders.random()
|
||||
pokemon.initializeMovesetFrom(movesetBuilder)
|
||||
}
|
||||
}
|
||||
heldItem?.let { itemKey ->
|
||||
val server = server() ?: return@let
|
||||
val parser = ItemParser(server.registryAccess())
|
||||
@@ -678,6 +694,7 @@ open class PokemonProperties {
|
||||
originalTrainerType?.let { nbt.putInt(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE, it.ordinal) }
|
||||
originalTrainer?.let { nbt.putString(DataKeys.POKEMON_ORIGINAL_TRAINER, it) }
|
||||
moves?.let { nbt.putString(DataKeys.POKEMON_PROPERTIES_MOVES, it.joinToString(separator = ",")) }
|
||||
movesetBuilders?.let { nbt.putString(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS, it.joinToString(separator = ",") { source -> source.toString() }) }
|
||||
heldItem?.let {nbt.putString(DataKeys.POKEMON_PROPERTIES_HELDITEM, it)}
|
||||
scaleModifier?.let { nbt.putFloat(DataKeys.POKEMON_SCALE_MODIFIER, it) }
|
||||
val custom = ListTag()
|
||||
@@ -712,6 +729,7 @@ open class PokemonProperties {
|
||||
originalTrainerType = if (tag.contains(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE)) OriginalTrainerType.valueOf(tag.getString(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE)) else null
|
||||
originalTrainer = if (tag.contains(DataKeys.POKEMON_ORIGINAL_TRAINER)) tag.getString(DataKeys.POKEMON_ORIGINAL_TRAINER) else null
|
||||
moves = if (tag.contains(DataKeys.POKEMON_PROPERTIES_MOVES)) tag.getString(DataKeys.POKEMON_PROPERTIES_MOVES).split(",") else null
|
||||
movesetBuilders = if (tag.contains(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS)) tag.getString(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS).split(",").map { it.asIdentifierDefaultingNamespace() } else null
|
||||
heldItem = if (tag.contains(DataKeys.POKEMON_PROPERTIES_HELDITEM)) tag.getString(DataKeys.POKEMON_PROPERTIES_HELDITEM) else null
|
||||
scaleModifier = if (tag.contains(DataKeys.POKEMON_SCALE_MODIFIER)) tag.getFloat(DataKeys.POKEMON_SCALE_MODIFIER) else null
|
||||
val custom = tag.getList(DataKeys.POKEMON_PROPERTIES_CUSTOM, Tag.TAG_STRING.toInt())
|
||||
@@ -747,6 +765,7 @@ open class PokemonProperties {
|
||||
originalTrainerType?.let { json.addProperty(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE, it.name) }
|
||||
originalTrainer?.let { json.addProperty(DataKeys.POKEMON_ORIGINAL_TRAINER, it) }
|
||||
moves?.let { json.addProperty(DataKeys.POKEMON_PROPERTIES_MOVES, it.joinToString(separator = ",")) }
|
||||
movesetBuilders?.let { json.addProperty(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS, it.joinToString(separator = ",") { source -> source.toString() }) }
|
||||
heldItem?.let {json.addProperty(DataKeys.POKEMON_PROPERTIES_HELDITEM, it)}
|
||||
scaleModifier?.let { json.addProperty(DataKeys.POKEMON_SCALE_MODIFIER, it) }
|
||||
val custom = JsonArray()
|
||||
@@ -781,6 +800,7 @@ open class PokemonProperties {
|
||||
originalTrainerType = json.get(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE)?.asString?.let { OriginalTrainerType.valueOf(it) }
|
||||
originalTrainer = json.get(DataKeys.POKEMON_ORIGINAL_TRAINER)?.asString
|
||||
moves = json.get(DataKeys.POKEMON_PROPERTIES_MOVES)?.asString?.split(",")
|
||||
movesetBuilders = json.get(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS)?.asString?.split(",")?.map { it.asIdentifierDefaultingNamespace() }
|
||||
heldItem = json.get(DataKeys.POKEMON_PROPERTIES_HELDITEM)?.asString
|
||||
scaleModifier = json.get(DataKeys.POKEMON_SCALE_MODIFIER)?.asFloat
|
||||
val custom = json.get(DataKeys.POKEMON_PROPERTIES_CUSTOM)?.asJsonArray
|
||||
@@ -820,6 +840,7 @@ open class PokemonProperties {
|
||||
originalTrainer?.let { pieces.add("originaltrainer=$it") }
|
||||
customProperties.forEach { pieces.add(it.asString()) }
|
||||
moves?.let { pieces.add("moves=${it.joinToString(separator = ",")}") }
|
||||
movesetBuilders?.let { pieces.add("movesetbuilders=${it.joinToString(separator = ",") { source -> source.toString() }}") }
|
||||
heldItem?.let {pieces.add("helditem=$it")}
|
||||
scaleModifier?.let { pieces.add("scale_modifier=$it") }
|
||||
return pieces.joinToString(separator)
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.cobblemon.mod.common.api.drop.ItemDropMethod
|
||||
import com.cobblemon.mod.common.api.entity.EntityDimensionsAdapter
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies.getByIdentifier
|
||||
@@ -131,6 +132,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
|
||||
.registerTypeAdapter(RidingBehaviourSettings::class.java, RidingBehaviourSettingsAdapter)
|
||||
.registerTypeAdapter(RideSoundSettingsList::class.java, RideSoundSettingsListAdapter)
|
||||
.registerTypeAdapter(ObtainableItemCondition::class.java, ObtainableItemConditionAdapter)
|
||||
.registerTypeAdapter(MovesetBuilder::class.java, MovesetBuilderReferenceAdapter)
|
||||
.disableHtmlEscaping()
|
||||
.enableComplexMapKeySerialization()
|
||||
.create()
|
||||
@@ -314,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, "")] = this.gson.toJson(baseSpecies)
|
||||
result[ShowdownIdentifiable.REGEX.replace(baseSpecies.name.lowercase(), "")] = this.gson.toJson(baseSpecies)
|
||||
species.forms.forEach { form ->
|
||||
if (form != species.standardForm) {
|
||||
val formSpecies = ShowdownSpecies(species, form)
|
||||
result[ShowdownIdentifiable.REGEX.replace(formSpecies.name, "")] = this.gson.toJson(formSpecies)
|
||||
result[ShowdownIdentifiable.REGEX.replace(formSpecies.name.lowercase(), "")] = this.gson.toJson(formSpecies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
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
|
||||
@@ -19,9 +20,7 @@ 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 ->
|
||||
@@ -73,8 +72,7 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
|
||||
eggInterpreter,
|
||||
tutorInterpreter,
|
||||
legacyInterpreter,
|
||||
specialInterpreter,
|
||||
levelUpInterpreter,
|
||||
specialInterpreter, levelUpInterpreter,
|
||||
formChangeInterpreter
|
||||
)
|
||||
}
|
||||
@@ -99,6 +97,20 @@ 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 +
|
||||
|
||||
+71
-51
@@ -44,7 +44,7 @@ import kotlin.random.Random
|
||||
* @since July 10th, 2022
|
||||
*/
|
||||
open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelectionData> {
|
||||
class SelectingSpawnInformation {
|
||||
class SelectingSpawnInformation(val spawnDetail: SpawnDetail) {
|
||||
val spawnablePositions = mutableMapOf<SpawnablePosition, Float>()
|
||||
var highestWeight = 0F
|
||||
|
||||
@@ -58,61 +58,81 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
|
||||
fun chooseSpawnablePosition() = spawnablePositions.entries.weightedSelection { it.value }!!.key
|
||||
}
|
||||
|
||||
class SpawnablePositionSelectionData(
|
||||
val spawnToSpawnablePosition: MutableMap<SpawnDetail, SelectingSpawnInformation>,
|
||||
var percentSum: Float
|
||||
): SpawnSelectionData {
|
||||
val size: Int
|
||||
get() = spawnToSpawnablePosition.size
|
||||
class SpawnablePositionBucketSelectionData(
|
||||
val selectingSpawnInformation: MutableList<SelectingSpawnInformation>
|
||||
) {
|
||||
var percentSum: Float = 0F
|
||||
val seenSpawns = mutableListOf<SpawnDetail>()
|
||||
|
||||
fun add(spawnDetail: SpawnDetail, spawnablePosition: SpawnablePosition) {
|
||||
// Only add to percentSum if this is the first time we've seen this SpawnDetail, otherwise
|
||||
// the percentage will get amplified for every spawnable position the thing was possible, completely
|
||||
// ruining the point of this pre-selection percentage.
|
||||
if (spawnDetail.percentage > 0 && spawnDetail !in seenSpawns) {
|
||||
seenSpawns.add(spawnDetail)
|
||||
percentSum += spawnDetail.percentage
|
||||
}
|
||||
val spawnInformation = selectingSpawnInformation.find { it.spawnDetail == spawnDetail }
|
||||
?: SelectingSpawnInformation(spawnDetail).also { selectingSpawnInformation.add(it) }
|
||||
spawnInformation.add(spawnablePosition, spawnablePosition.getWeight(spawnDetail))
|
||||
}
|
||||
|
||||
fun remove(spawnInformation: SelectingSpawnInformation) {
|
||||
selectingSpawnInformation -= spawnInformation
|
||||
if (spawnInformation.spawnDetail.percentage > 0) {
|
||||
percentSum -= spawnInformation.spawnDetail.percentage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SpawnablePositionSelectionData(
|
||||
val spawner: Spawner,
|
||||
val spawnablePositions: List<SpawnablePosition>,
|
||||
val selectingSpawnInformation: MutableMap<SpawnBucket, SpawnablePositionBucketSelectionData>,
|
||||
): SpawnSelectionData {
|
||||
override val spawnActions = mutableListOf<SpawnAction<*>>()
|
||||
override val context = mutableMapOf<String, Any>()
|
||||
|
||||
fun getDataForBucket(bucket: SpawnBucket): SpawnablePositionBucketSelectionData {
|
||||
val existing = selectingSpawnInformation[bucket]
|
||||
if (existing != null) {
|
||||
return existing
|
||||
}
|
||||
|
||||
val bucketSpawnInformation = SpawnablePositionBucketSelectionData(mutableListOf())
|
||||
spawnablePositions.forEach { spawnablePosition ->
|
||||
spawner.getMatchingSpawns(bucket, spawnablePosition).forEach {
|
||||
bucketSpawnInformation.add(it, spawnablePosition)
|
||||
}
|
||||
}
|
||||
|
||||
selectingSpawnInformation[bucket] = bucketSpawnInformation
|
||||
return bucketSpawnInformation
|
||||
}
|
||||
|
||||
override fun removeSpawnDetails(shouldRemove: (SpawnDetail) -> Boolean) {
|
||||
val toRemove = spawnToSpawnablePosition.entries.filter { shouldRemove(it.key) }
|
||||
toRemove.forEach { spawnToSpawnablePosition.remove(it.key) }
|
||||
percentSum -= toRemove.sumOf { it.key.percentage.toDouble().takeIf { it > 0 } ?: 0.0 }.toFloat()
|
||||
selectingSpawnInformation.flatMap { it.value.selectingSpawnInformation.filter { shouldRemove(it.spawnDetail) } }
|
||||
.forEach { selectingSpawnInformation[it.spawnDetail.bucket]?.remove(it) }
|
||||
}
|
||||
|
||||
override fun removeSpawnablePositions(shouldRemove: (SpawnDetail, SpawnablePosition) -> Boolean) {
|
||||
val toRemove = spawnToSpawnablePosition.entries.filter { (spawnDetail, positionData) ->
|
||||
positionData.spawnablePositions.removeIf { shouldRemove(spawnDetail, it.key) }
|
||||
val toRemove = selectingSpawnInformation.flatMap { it.value.selectingSpawnInformation }.filter { positionData ->
|
||||
positionData.spawnablePositions.removeIf { shouldRemove(positionData.spawnDetail, it.key) }
|
||||
positionData.highestWeight = positionData.spawnablePositions.maxOfOrNull { it.value } ?: 0F
|
||||
positionData.spawnablePositions.isEmpty()
|
||||
}
|
||||
|
||||
toRemove.forEach { spawnToSpawnablePosition.remove(it.key) }
|
||||
|
||||
percentSum -= toRemove.sumOf { it.key.percentage.toDouble().takeIf { it > 0 } ?: 0.0 }.toFloat()
|
||||
toRemove.forEach { selectingSpawnInformation[it.spawnDetail.bucket]?.remove(it) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSelectionData(
|
||||
spawner: Spawner,
|
||||
bucket: SpawnBucket,
|
||||
spawnablePositions: List<SpawnablePosition>
|
||||
): SpawnablePositionSelectionData {
|
||||
val spawnToSpawnablePosition: MutableMap<SpawnDetail, SelectingSpawnInformation> = mutableMapOf()
|
||||
var percentSum = 0F
|
||||
val selectingSpawnInformation: MutableMap<SpawnBucket, SpawnablePositionBucketSelectionData> = mutableMapOf()
|
||||
|
||||
spawnablePositions.forEach { spawnablePosition ->
|
||||
spawner.getMatchingSpawns(bucket, spawnablePosition).forEach {
|
||||
// Only add to percentSum if this is the first time we've seen this SpawnDetail, otherwise
|
||||
// the percentage will get amplified for every spawnable position the thing was possible, completely
|
||||
// ruining the point of this pre-selection percentage.
|
||||
if (it.percentage > 0 && !spawnToSpawnablePosition.containsKey(it)) {
|
||||
percentSum += it.percentage
|
||||
}
|
||||
|
||||
val selectingSpawnInformation = spawnToSpawnablePosition.getOrPut(
|
||||
it,
|
||||
::SelectingSpawnInformation
|
||||
)
|
||||
selectingSpawnInformation.add(spawnablePosition, spawnablePosition.getWeight(it))
|
||||
}
|
||||
}
|
||||
|
||||
return SpawnablePositionSelectionData(spawnToSpawnablePosition, percentSum)
|
||||
return SpawnablePositionSelectionData(spawner, spawnablePositions, selectingSpawnInformation)
|
||||
}
|
||||
|
||||
override fun selectSpawnAction(
|
||||
@@ -120,8 +140,8 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
|
||||
bucket: SpawnBucket,
|
||||
selectionData: SpawnablePositionSelectionData,
|
||||
): SpawnAction<*>? {
|
||||
val spawnToSpawnablePosition = selectionData.spawnToSpawnablePosition
|
||||
var percentSum = selectionData.percentSum
|
||||
val selectingSpawnInformation = selectionData.getDataForBucket(bucket) ?: return null
|
||||
var percentSum = selectingSpawnInformation.percentSum
|
||||
|
||||
// First pass is doing percentage checks.
|
||||
if (percentSum > 0) {
|
||||
@@ -129,7 +149,7 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
|
||||
if (percentSum > 100) {
|
||||
Cobblemon.LOGGER.warn(
|
||||
"""
|
||||
A spawn list for ${spawner.name} exceeded 100% on percentage sums...
|
||||
A spawn list for ${spawner.name} exceeded 100% on percentage sums in bucket ${bucket.name}...
|
||||
This means you don't understand how this option works.
|
||||
""".trimIndent()
|
||||
)
|
||||
@@ -140,13 +160,13 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
|
||||
* It's [0, 1) and I want (0, 1]
|
||||
* See half-open intervals here https://en.wikipedia.org/wiki/Interval_(mathematics)#Terminology
|
||||
*/
|
||||
val selectedPercentage = 100 - Random.Default.nextFloat() * 100
|
||||
val selectedPercentage = 100 - Random.nextFloat() * 100
|
||||
percentSum = 0F
|
||||
for ((spawnDetail, info) in spawnToSpawnablePosition) {
|
||||
if (spawnDetail.percentage > 0) {
|
||||
percentSum += spawnDetail.percentage
|
||||
for (info in selectingSpawnInformation.selectingSpawnInformation) {
|
||||
if (info.spawnDetail.percentage > 0) {
|
||||
percentSum += info.spawnDetail.percentage
|
||||
if (percentSum >= selectedPercentage) {
|
||||
return spawnDetail.choose(
|
||||
return info.spawnDetail.choose(
|
||||
spawnablePosition = info.chooseSpawnablePosition(),
|
||||
bucket = bucket,
|
||||
selectionData = selectionData
|
||||
@@ -156,9 +176,9 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
|
||||
}
|
||||
}
|
||||
|
||||
val selectedSpawn = spawnToSpawnablePosition.entries.toList().weightedSelection { it.value.highestWeight }!!
|
||||
return selectedSpawn.key.choose(
|
||||
spawnablePosition = selectedSpawn.value.chooseSpawnablePosition(),
|
||||
val selectedSpawn = selectingSpawnInformation.selectingSpawnInformation.toList().weightedSelection { it.highestWeight }!!
|
||||
return selectedSpawn.spawnDetail.choose(
|
||||
spawnablePosition = selectedSpawn.chooseSpawnablePosition(),
|
||||
bucket = bucket,
|
||||
selectionData = selectionData
|
||||
)
|
||||
@@ -169,16 +189,16 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
|
||||
bucket: SpawnBucket,
|
||||
spawnablePositions: List<SpawnablePosition>
|
||||
): Map<SpawnDetail, Float> {
|
||||
val selectionData = getSelectionData(spawner, bucket, spawnablePositions)
|
||||
val selectionData = getSelectionData(spawner, spawnablePositions).getDataForBucket(bucket)
|
||||
|
||||
if (selectionData.size == 0) {
|
||||
if (selectionData.selectingSpawnInformation.isEmpty()) {
|
||||
return emptyMap()
|
||||
}
|
||||
|
||||
val totalWeights = mutableMapOf<SpawnDetail, Float>()
|
||||
|
||||
for ((spawnDetail, info) in selectionData.spawnToSpawnablePosition.entries) {
|
||||
totalWeights[spawnDetail] = info.highestWeight
|
||||
for (info in selectionData.selectingSpawnInformation) {
|
||||
totalWeights[info.spawnDetail] = info.highestWeight
|
||||
}
|
||||
|
||||
return totalWeights
|
||||
|
||||
+67
-55
@@ -18,7 +18,6 @@ import com.cobblemon.mod.common.api.spawning.spawner.Spawner
|
||||
import com.cobblemon.mod.common.util.removeIf
|
||||
import com.cobblemon.mod.common.util.weightedSelection
|
||||
import kotlin.random.Random
|
||||
import kotlin.reflect.full.createInstance
|
||||
|
||||
/**
|
||||
* A spawning selector that compiles a distinct list of all spawn details that
|
||||
@@ -33,9 +32,9 @@ import kotlin.reflect.full.createInstance
|
||||
* of an entire spawnable position type to favour whichever are the more populous.
|
||||
*
|
||||
* The weight for a spawn when doing primary selection is whichever spawnable position-influenced
|
||||
* weight is highest (as weight multipliers exist per spawnable position) and then the
|
||||
* selection of which spawnable position to spawn the primary selected spawn uses the spawnable
|
||||
* position-adjusted weight.
|
||||
* weight is highest (as weight multipliers exist per spawnable position). After the spawn has been
|
||||
* chosen, the selection of which spawnable position to spawn it at is selected via another weighted
|
||||
* selection using spawnable position-adjusted weights.
|
||||
*
|
||||
* At a glance:
|
||||
* - Spawn detail selection is flat across spawnable position quantity
|
||||
@@ -45,7 +44,6 @@ import kotlin.reflect.full.createInstance
|
||||
* @since July 10th, 2022
|
||||
*/
|
||||
open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnablePositionWeightedSelector.SeparatedSelectionData> {
|
||||
open fun getWeight(spawnablePositionType: SpawnablePositionType<*>) = spawnablePositionType.getWeight()
|
||||
|
||||
class SelectingSpawnInformation {
|
||||
val spawnablePositionWeights = mutableMapOf<SpawnablePosition, Float>()
|
||||
@@ -62,13 +60,25 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
|
||||
fun chooseSpawnablePosition() = spawnablePositionWeights.keys.toList().weightedSelection { spawnablePositionWeights[it]!! }!!
|
||||
}
|
||||
|
||||
class SpawnablePositionSelectionData(
|
||||
val spawnToSpawnablePosition: MutableMap<SpawnDetail, SelectingSpawnInformation>,
|
||||
var percentSum: Float
|
||||
) {
|
||||
class SpawnablePositionSelectionData {
|
||||
val spawnToSpawnablePosition = mutableMapOf<SpawnDetail, SelectingSpawnInformation>()
|
||||
var percentSum: Float = 0F
|
||||
val seenSpawns = mutableListOf<SpawnDetail>()
|
||||
val size: Int
|
||||
get() = spawnToSpawnablePosition.size
|
||||
|
||||
fun add(spawnDetail: SpawnDetail, spawnablePosition: SpawnablePosition) {
|
||||
// Only add to percentSum if this is the first time we've seen this SpawnDetail for this spawnable
|
||||
// position type, otherwise the percentage will get amplified for every spawnable position the thing
|
||||
// was possible, completely ruining the point of this pre-selection percentage.
|
||||
if (spawnDetail.percentage > 0 && spawnDetail !in seenSpawns) {
|
||||
percentSum += spawnDetail.percentage
|
||||
seenSpawns.add(spawnDetail)
|
||||
}
|
||||
val selectingSpawnInformation = spawnToSpawnablePosition.getOrPut(spawnDetail, ::SelectingSpawnInformation)
|
||||
selectingSpawnInformation.add(spawnDetail, spawnablePosition, 1F)
|
||||
}
|
||||
|
||||
fun removeSpawnDetails(shouldRemove: (SpawnDetail) -> Boolean) {
|
||||
val toRemove = spawnToSpawnablePosition.entries.filter { shouldRemove(it.key) }
|
||||
toRemove.forEach { spawnToSpawnablePosition.remove(it.key) }
|
||||
@@ -89,67 +99,68 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
|
||||
}
|
||||
|
||||
class SeparatedSelectionData(
|
||||
val spawnablePositionTypeToSpawns: MutableMap<SpawnablePositionType<*>, SpawnablePositionSelectionData>
|
||||
): SpawnSelectionData {
|
||||
val spawner: Spawner,
|
||||
val spawnablePositions: List<SpawnablePosition>
|
||||
) : SpawnSelectionData {
|
||||
override val spawnActions = mutableListOf<SpawnAction<*>>()
|
||||
override val context = mutableMapOf<String, Any>()
|
||||
|
||||
val bucketData = mutableMapOf<SpawnBucket, MutableMap<SpawnablePositionType<*>, SpawnablePositionSelectionData>>()
|
||||
|
||||
override fun removeSpawnDetails(shouldRemove: (SpawnDetail) -> Boolean) {
|
||||
spawnablePositionTypeToSpawns.values.forEach { it.removeSpawnDetails(shouldRemove) }
|
||||
spawnablePositionTypeToSpawns.removeIf { it.value.size == 0 }
|
||||
bucketData.values.forEach {
|
||||
it.values.forEach { it.removeSpawnDetails(shouldRemove) }
|
||||
it.removeIf { it.value.size == 0 }
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeSpawnablePositions(shouldRemove: (SpawnDetail, SpawnablePosition) -> Boolean) {
|
||||
spawnablePositionTypeToSpawns.values.forEach { it.removeSpawnablePositions(shouldRemove) }
|
||||
spawnablePositionTypeToSpawns.removeIf { it.value.size == 0 }
|
||||
bucketData.values.forEach {
|
||||
it.values.forEach { it.removeSpawnablePositions(shouldRemove) }
|
||||
it.removeIf { it.value.size == 0 }
|
||||
}
|
||||
}
|
||||
|
||||
fun getDataForBucket(bucket: SpawnBucket): MutableMap<SpawnablePositionType<*>, SpawnablePositionSelectionData> {
|
||||
bucketData[bucket]?.let { return it }
|
||||
|
||||
val spawnablePositionTypeToSpawns = mutableMapOf<SpawnablePositionType<*>, SpawnablePositionSelectionData>()
|
||||
|
||||
spawnablePositions.forEach { spawnablePosition ->
|
||||
val spawnablePositionType = SpawnablePosition.getByClass(spawnablePosition)!!
|
||||
|
||||
val possible = spawner.getMatchingSpawns(bucket, spawnablePosition)
|
||||
if (possible.isNotEmpty()) {
|
||||
val spawnablePositionSelectionData = spawnablePositionTypeToSpawns
|
||||
.getOrPut(spawnablePositionType, ::SpawnablePositionSelectionData)
|
||||
possible.forEach { spawnablePositionSelectionData.add(it, spawnablePosition) }
|
||||
}
|
||||
}
|
||||
|
||||
bucketData[bucket] = spawnablePositionTypeToSpawns
|
||||
return spawnablePositionTypeToSpawns
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSelectionData(
|
||||
spawner: Spawner,
|
||||
bucket: SpawnBucket,
|
||||
spawnablePositions: List<SpawnablePosition>
|
||||
): SeparatedSelectionData {
|
||||
val spawnablePositionTypeToSpawns = mutableMapOf<SpawnablePositionType<*>, SpawnablePositionSelectionData>()
|
||||
|
||||
spawnablePositions.forEach { spawnablePosition ->
|
||||
val spawnablePositionType = SpawnablePosition.getByClass(spawnablePosition)!!
|
||||
|
||||
val possible = spawner.getMatchingSpawns(bucket, spawnablePosition)
|
||||
if (possible.isNotEmpty()) {
|
||||
val spawnablePositionSelectionData = spawnablePositionTypeToSpawns.getOrPut(spawnablePositionType) { SpawnablePositionSelectionData(mutableMapOf(), 0F) }
|
||||
possible.forEach {
|
||||
// Only add to percentSum if this is the first time we've seen this SpawnDetail for this spawnable
|
||||
// position type, otherwise the percentage will get amplified for every spawnable position the thing
|
||||
// was possible, completely ruining the point of this pre-selection percentage.
|
||||
if (it.percentage > 0 && !spawnablePositionSelectionData.spawnToSpawnablePosition.containsKey(it)) {
|
||||
spawnablePositionSelectionData.percentSum += it.percentage
|
||||
}
|
||||
|
||||
val selectingSpawnInformation = spawnablePositionSelectionData.spawnToSpawnablePosition.getOrPut(
|
||||
it,
|
||||
SelectingSpawnInformation::class::createInstance
|
||||
)
|
||||
selectingSpawnInformation.add(it, spawnablePosition, getWeight(spawnablePositionType))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SeparatedSelectionData(spawnablePositionTypeToSpawns)
|
||||
}
|
||||
) = SeparatedSelectionData(spawner, spawnablePositions)
|
||||
|
||||
override fun selectSpawnAction(
|
||||
spawner: Spawner,
|
||||
bucket: SpawnBucket,
|
||||
selectionData: SeparatedSelectionData
|
||||
): SpawnAction<*>? {
|
||||
if (selectionData.spawnablePositionTypeToSpawns.isEmpty()) {
|
||||
val bucketData = selectionData.getDataForBucket(bucket)
|
||||
|
||||
if (bucketData.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Which spawnable position type should we use?
|
||||
val spawnablePositionSelectionData = selectionData.spawnablePositionTypeToSpawns.entries.toList()
|
||||
.weightedSelection { getWeight(it.key) * it.value.size }
|
||||
val spawnablePositionSelectionData = bucketData.entries.toList()
|
||||
.weightedSelection { it.key.getWeight() * it.value.size }
|
||||
?.value
|
||||
?: return null
|
||||
|
||||
@@ -161,7 +172,7 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
|
||||
if (percentSum > 100) {
|
||||
LOGGER.warn(
|
||||
"""
|
||||
A spawn list for ${spawner.name} exceeded 100% on percentage sums...
|
||||
A spawn list for ${spawner.name} exceeded 100% on percentage sums in bucket ${bucket.name}...
|
||||
This means you don't understand how this option works.
|
||||
""".trimIndent()
|
||||
)
|
||||
@@ -173,7 +184,7 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
|
||||
* It's [0, 1) and I want (0, 1]
|
||||
* See half-open intervals here https://en.wikipedia.org/wiki/Interval_(mathematics)#Terminology
|
||||
*/
|
||||
val selectedPercentage = 100 - Random.Default.nextFloat() * 100
|
||||
val selectedPercentage = 100 - Random.nextFloat() * 100
|
||||
percentSum = 0F
|
||||
for ((spawnDetail, info) in spawnToSpawnablePosition) {
|
||||
if (spawnDetail.percentage > 0) {
|
||||
@@ -224,7 +235,8 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
|
||||
val percentageWeight = (rescaledTotalWeight - totalWeight) / percentSum
|
||||
|
||||
for ((spawnDetail, info) in spawnToSpawnablePosition.entries) {
|
||||
totalWeights[spawnDetail] = info.highestWeight + if (spawnDetail.percentage > 0) spawnDetail.percentage * percentageWeight else 0F
|
||||
totalWeights[spawnDetail] =
|
||||
info.highestWeight + if (spawnDetail.percentage > 0) spawnDetail.percentage * percentageWeight else 0F
|
||||
}
|
||||
|
||||
return totalWeights
|
||||
@@ -235,18 +247,18 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
|
||||
bucket: SpawnBucket,
|
||||
spawnablePositions: List<SpawnablePosition>
|
||||
): Map<SpawnDetail, Float> {
|
||||
val selectionData = getSelectionData(spawner, bucket, spawnablePositions)
|
||||
val selectionData = getSelectionData(spawner, spawnablePositions).getDataForBucket(bucket)
|
||||
|
||||
if (selectionData.spawnablePositionTypeToSpawns.isEmpty()) {
|
||||
if (selectionData.isEmpty()) {
|
||||
return mapOf()
|
||||
}
|
||||
|
||||
val totalWeights = mutableMapOf<SpawnDetail, Float>()
|
||||
|
||||
val totalSpawnablePositionWeight = selectionData.spawnablePositionTypeToSpawns.keys.sumOf { getWeight(it).toDouble() }.toFloat()
|
||||
val totalSpawnablePositionWeight = selectionData.keys.sumOf { it.getWeight().toDouble() }.toFloat()
|
||||
|
||||
for ((spawnablePositionType, spawnPositionSelectionData) in selectionData.spawnablePositionTypeToSpawns) {
|
||||
val spawnPositionWeightCorrection = getWeight(spawnablePositionType) / totalSpawnablePositionWeight
|
||||
for ((spawnablePositionType, spawnPositionSelectionData) in selectionData) {
|
||||
val spawnPositionWeightCorrection = spawnablePositionType.getWeight() / totalSpawnablePositionWeight
|
||||
val spawnPositionProbabilities = getProbabilitiesFromSpawnablePositionType(spawner, spawnPositionSelectionData)
|
||||
|
||||
spawnPositionProbabilities.entries.forEach {
|
||||
|
||||
+9
-3
@@ -29,7 +29,7 @@ interface SpawningSelector<T : SpawnSelectionData> {
|
||||
val DEFAULT = FlatSpawnablePositionWeightedSelector()
|
||||
}
|
||||
|
||||
fun getSelectionData(spawner: Spawner, bucket: SpawnBucket, spawnablePositions: List<SpawnablePosition>): T
|
||||
fun getSelectionData(spawner: Spawner, spawnablePositions: List<SpawnablePosition>): T
|
||||
|
||||
fun selectSpawnAction(
|
||||
spawner: Spawner,
|
||||
@@ -37,12 +37,18 @@ interface SpawningSelector<T : SpawnSelectionData> {
|
||||
selectionData: T
|
||||
): SpawnAction<*>?
|
||||
|
||||
fun select(spawner: Spawner, bucket: SpawnBucket, spawnablePositions: List<SpawnablePosition>, maxSpawns: Int): List<SpawnAction<*>> {
|
||||
val selectionData = getSelectionData(spawner, bucket, spawnablePositions)
|
||||
fun select(
|
||||
spawner: Spawner,
|
||||
bucketSelector: () -> SpawnBucket,
|
||||
spawnablePositions: List<SpawnablePosition>,
|
||||
maxSpawns: Int
|
||||
): List<SpawnAction<*>> {
|
||||
val selectionData = getSelectionData(spawner, spawnablePositions)
|
||||
|
||||
val spawnActions = selectionData.spawnActions
|
||||
|
||||
while (spawnActions.size < maxSpawns) {
|
||||
val bucket = bucketSelector()
|
||||
val spawnAction = selectSpawnAction(
|
||||
spawner = spawner,
|
||||
bucket = bucket,
|
||||
|
||||
@@ -29,13 +29,6 @@ import com.cobblemon.mod.common.util.isBoxLoaded
|
||||
import com.cobblemon.mod.common.util.squeezeWithinBounds
|
||||
import com.cobblemon.mod.common.util.toVec3f
|
||||
import com.cobblemon.mod.common.util.weightedSelection
|
||||
import kotlin.Any
|
||||
import kotlin.Boolean
|
||||
import kotlin.Float
|
||||
import kotlin.Int
|
||||
import kotlin.Pair
|
||||
import kotlin.String
|
||||
import kotlin.collections.plus
|
||||
import kotlin.math.max
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.SectionPos
|
||||
@@ -95,10 +88,9 @@ interface Spawner {
|
||||
): SpawnAction<*>? {
|
||||
influences.removeIf { it.isExpired() }
|
||||
spawnablePosition.influences.addAll(influences)
|
||||
val bucket = chooseBucket(cause, spawnablePosition.influences)
|
||||
return selector.select(
|
||||
spawner = this,
|
||||
bucket = bucket,
|
||||
bucketSelector = { chooseBucket(cause, spawnablePosition.influences) },
|
||||
spawnablePositions = listOf(spawnablePosition),
|
||||
maxSpawns = 1
|
||||
).firstOrNull()
|
||||
@@ -116,9 +108,9 @@ interface Spawner {
|
||||
|
||||
val areaBox = AABB.ofSize(
|
||||
Vec3(constrainedArea.getCenter().toVec3f()),
|
||||
ENTITY_LIMIT_CHUNK_RANGE * 16.0 * 2,
|
||||
ENTITY_LIMIT_CHUNK_RANGE * 16.0,
|
||||
1000.0,
|
||||
ENTITY_LIMIT_CHUNK_RANGE * 16.0 * 2
|
||||
ENTITY_LIMIT_CHUNK_RANGE * 16.0
|
||||
)
|
||||
|
||||
if (!constrainedArea.world.isBoxLoaded(areaBox)) {
|
||||
@@ -140,11 +132,10 @@ interface Spawner {
|
||||
val zone = generator.generate(this, constrainedArea)
|
||||
val spawnablePositions = resolver.resolve(this, prioritizedAreaCalculators, zone)
|
||||
val influences = influences + zone.unconditionalInfluences
|
||||
val bucket = chooseBucket(zoneInput.cause, influences)
|
||||
|
||||
return selector.select(
|
||||
spawner = this,
|
||||
bucket = bucket,
|
||||
bucketSelector = { chooseBucket(zoneInput.cause, influences) },
|
||||
spawnablePositions = spawnablePositions,
|
||||
maxSpawns = maxSpawns
|
||||
)
|
||||
|
||||
@@ -77,6 +77,7 @@ 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
|
||||
}
|
||||
|
||||
@@ -170,4 +171,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") ,
|
||||
icon = ItemStack(CobblemonItems.PC)
|
||||
icons = listOf(ItemStack(CobblemonItems.PC))
|
||||
)
|
||||
toast.addListeners(player)
|
||||
toast.expireAfter(5F)
|
||||
|
||||
+29
-7
@@ -8,14 +8,17 @@
|
||||
|
||||
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
|
||||
@@ -30,7 +33,6 @@ import net.minecraft.world.entity.player.Player
|
||||
* @author Apion
|
||||
* @since February 21, 2024
|
||||
*/
|
||||
|
||||
open class PlayerInstancedDataStoreManager {
|
||||
val saveExecutor = Executors.newSingleThreadExecutor(
|
||||
ThreadFactoryBuilder()
|
||||
@@ -53,19 +55,27 @@ 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 {
|
||||
@@ -126,4 +136,16 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
+16
-3
@@ -8,8 +8,10 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.storage.player
|
||||
|
||||
import com.cobblemon.mod.common.api.storage.player.client.ClientGeneralPlayerData
|
||||
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.util.cobblemonResource
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
@@ -27,11 +29,22 @@ 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) = types[id]
|
||||
}
|
||||
fun getTypeById(id: ResourceLocation): PlayerInstancedDataStoreType? {
|
||||
val type = types[id]
|
||||
if (type == null) Cobblemon.LOGGER.warn("Unknown PlayerInstancedDataStoreType ID: {}", id)
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.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,8 +40,6 @@ class CachedPlayerDataStoreFactory<T : InstancedPlayerData>(val backend: PlayerD
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun saveAll() {
|
||||
cache.forEach { (_, pd) -> backend.save(pd) }
|
||||
cache.removeIf { (uuid, _) -> uuid.getPlayer() == null }
|
||||
|
||||
@@ -91,6 +91,7 @@ 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")
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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
|
||||
)
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 icon The [ItemStack] that is rendered as the toast icon, this can be a [ItemStack.EMPTY].
|
||||
* @param icons 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,
|
||||
icon: ItemStack,
|
||||
icons: List<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 icon: ItemStack by Delegates.observable(icon) { _, old, new -> if (old != new) this.launchUpdate() }
|
||||
var icons: List<ItemStack> by Delegates.observable(icons) { _, 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.icon,
|
||||
this.icons,
|
||||
this.frameTexture,
|
||||
this.progress,
|
||||
this.progressColor,
|
||||
|
||||
@@ -29,9 +29,12 @@ 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(), "")
|
||||
) : ShowdownIdentifiable {
|
||||
val showdownId: String = ShowdownIdentifiable.REGEX.replace(name.lowercase(), ""),
|
||||
val typeGem: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, showdownId + "_gem"),
|
||||
) : ShowdownIdentifiable {
|
||||
|
||||
override fun showdownId(): String {
|
||||
return showdownId
|
||||
|
||||
@@ -23,7 +23,9 @@ object ElementalTypes {
|
||||
name = "Normal",
|
||||
displayName = Component.translatable("cobblemon.type.normal"),
|
||||
hue = 0xE8E8DA,
|
||||
textureXMultiplier = 0
|
||||
textureXMultiplier = 0,
|
||||
primaryColor = 0x8FA689,
|
||||
secondaryColor = 0xFFFBE2
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -31,7 +33,9 @@ object ElementalTypes {
|
||||
name = "Fire",
|
||||
displayName = Component.translatable("cobblemon.type.fire"),
|
||||
hue = 0xFF6E21,
|
||||
textureXMultiplier = 1
|
||||
textureXMultiplier = 1,
|
||||
primaryColor = 0xF92D2D,
|
||||
secondaryColor = 0xF99B1D
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -39,7 +43,9 @@ object ElementalTypes {
|
||||
name = "Water",
|
||||
displayName = Component.translatable("cobblemon.type.water"),
|
||||
hue = 0x3FA5FF,
|
||||
textureXMultiplier = 2
|
||||
textureXMultiplier = 2,
|
||||
primaryColor = 0x377AF6,
|
||||
secondaryColor = 0x50DDF9
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -47,7 +53,9 @@ object ElementalTypes {
|
||||
name = "Grass",
|
||||
displayName = Component.translatable("cobblemon.type.grass"),
|
||||
hue = 0x62D14F,
|
||||
textureXMultiplier = 3
|
||||
textureXMultiplier = 3,
|
||||
primaryColor = 0x0EAE95,
|
||||
secondaryColor = 0xC6F554
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -55,7 +63,9 @@ object ElementalTypes {
|
||||
name = "Electric",
|
||||
displayName = Component.translatable("cobblemon.type.electric"),
|
||||
hue = 0xFFD314,
|
||||
textureXMultiplier = 4
|
||||
textureXMultiplier = 4,
|
||||
primaryColor = 0xF7AB19,
|
||||
secondaryColor = 0xFFF257
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -63,7 +73,9 @@ object ElementalTypes {
|
||||
name = "Ice",
|
||||
displayName = Component.translatable("cobblemon.type.ice"),
|
||||
hue = 0x54F2F2,
|
||||
textureXMultiplier = 5
|
||||
textureXMultiplier = 5,
|
||||
primaryColor = 0x41BAED,
|
||||
secondaryColor = 0x91F4F9
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -71,7 +83,9 @@ object ElementalTypes {
|
||||
name = "Fighting",
|
||||
displayName = Component.translatable("cobblemon.type.fighting"),
|
||||
hue = 0xEF565D,
|
||||
textureXMultiplier = 6
|
||||
textureXMultiplier = 6,
|
||||
primaryColor = 0xC8356B,
|
||||
secondaryColor = 0xF36A3F
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -79,7 +93,9 @@ object ElementalTypes {
|
||||
name = "Poison",
|
||||
displayName = Component.translatable("cobblemon.type.poison"),
|
||||
hue = 0xD651FF,
|
||||
textureXMultiplier = 7
|
||||
textureXMultiplier = 7,
|
||||
primaryColor = 0x7B41DD,
|
||||
secondaryColor = 0xE568F4
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -87,7 +103,9 @@ object ElementalTypes {
|
||||
name = "Ground",
|
||||
displayName = Component.translatable("cobblemon.type.ground"),
|
||||
hue = 0xF4A453,
|
||||
textureXMultiplier = 8
|
||||
textureXMultiplier = 8,
|
||||
primaryColor = 0xE08835,
|
||||
secondaryColor = 0xFFD057
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -95,7 +113,9 @@ object ElementalTypes {
|
||||
name = "Flying",
|
||||
displayName = Component.translatable("cobblemon.type.flying"),
|
||||
hue = 0xB8B2FF,
|
||||
textureXMultiplier = 9
|
||||
textureXMultiplier = 9,
|
||||
primaryColor = 0x6083F6,
|
||||
secondaryColor = 0xD5CCFA
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -103,7 +123,9 @@ object ElementalTypes {
|
||||
name = "Psychic",
|
||||
displayName = Component.translatable("cobblemon.type.psychic"),
|
||||
hue = 0xFF5E9E,
|
||||
textureXMultiplier = 10
|
||||
textureXMultiplier = 10,
|
||||
primaryColor = 0xE553CE,
|
||||
secondaryColor = 0xF69DD5
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -111,7 +133,9 @@ object ElementalTypes {
|
||||
name = "Bug",
|
||||
displayName = Component.translatable("cobblemon.type.bug"),
|
||||
hue = 0xD3D319,
|
||||
textureXMultiplier = 11
|
||||
textureXMultiplier = 11,
|
||||
primaryColor = 0x9FBE1B,
|
||||
secondaryColor = 0xE9FB5B
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -119,7 +143,9 @@ object ElementalTypes {
|
||||
name = "Rock",
|
||||
displayName = Component.translatable("cobblemon.type.rock"),
|
||||
hue = 0xB7A16E,
|
||||
textureXMultiplier = 12
|
||||
textureXMultiplier = 12,
|
||||
primaryColor = 0x9F814A,
|
||||
secondaryColor = 0xEECB7A
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -127,7 +153,9 @@ object ElementalTypes {
|
||||
name = "Ghost",
|
||||
displayName = Component.translatable("cobblemon.type.ghost"),
|
||||
hue = 0x9C80F7,
|
||||
textureXMultiplier = 13
|
||||
textureXMultiplier = 13,
|
||||
primaryColor = 0x4F57DD,
|
||||
secondaryColor = 0xC182F1
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -135,7 +163,9 @@ object ElementalTypes {
|
||||
name = "Dragon",
|
||||
displayName = Component.translatable("cobblemon.type.dragon"),
|
||||
hue = 0x7580FF,
|
||||
textureXMultiplier = 14
|
||||
textureXMultiplier = 14,
|
||||
primaryColor = 0x4E68E2,
|
||||
secondaryColor = 0x47C7F7
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -143,7 +173,9 @@ object ElementalTypes {
|
||||
name = "Dark",
|
||||
displayName = Component.translatable("cobblemon.type.dark"),
|
||||
hue = 0x587DA0,
|
||||
textureXMultiplier = 15
|
||||
textureXMultiplier = 15,
|
||||
primaryColor = 0x465B99,
|
||||
secondaryColor = 0x689EBF
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -151,7 +183,9 @@ object ElementalTypes {
|
||||
name = "Steel",
|
||||
displayName = Component.translatable("cobblemon.type.steel"),
|
||||
hue = 0xABD1F4,
|
||||
textureXMultiplier = 16
|
||||
textureXMultiplier = 16,
|
||||
primaryColor = 0x628CB9,
|
||||
secondaryColor = 0xD6F7F3
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -159,16 +193,20 @@ object ElementalTypes {
|
||||
name = "Fairy",
|
||||
displayName = Component.translatable("cobblemon.type.fairy"),
|
||||
hue = 0xFF7FE5,
|
||||
textureXMultiplier = 17
|
||||
textureXMultiplier = 17,
|
||||
primaryColor = 0xE2678C,
|
||||
secondaryColor = 0xF5A7B7
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
fun register(name: String, displayName: MutableComponent, hue: Int, textureXMultiplier: Int): ElementalType {
|
||||
fun register(name: String, displayName: MutableComponent, hue: Int, textureXMultiplier: Int, primaryColor: Int, secondaryColor: Int): ElementalType {
|
||||
return ElementalType(
|
||||
name = name,
|
||||
displayName = displayName,
|
||||
hue = hue,
|
||||
textureXMultiplier = textureXMultiplier
|
||||
textureXMultiplier = textureXMultiplier,
|
||||
primaryColor = primaryColor,
|
||||
secondaryColor = secondaryColor
|
||||
).also {
|
||||
allTypes.add(it)
|
||||
}
|
||||
@@ -273,4 +311,4 @@ object ElementalTypes {
|
||||
@JvmName("getFAIRY")
|
||||
@Deprecated("Use ElementalTypes.FAIRY, provided for backwards compatibility until Cobblemon 1.8.")
|
||||
fun getFAIRY() = FAIRY
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ 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
|
||||
|
||||
@@ -42,6 +43,8 @@ abstract class GrowableStoneBlock(
|
||||
val nextStage: Block?
|
||||
) : DirectionalBlock(settings) {
|
||||
|
||||
abstract val growthChance: Int
|
||||
|
||||
private val upShape: VoxelShape = Block.box(
|
||||
xzOffset.toDouble(),
|
||||
0.0,
|
||||
@@ -95,20 +98,26 @@ abstract class GrowableStoneBlock(
|
||||
.setValue(FACING, Direction.DOWN))
|
||||
}
|
||||
|
||||
abstract fun canGrow(pos: BlockPos, world: BlockGetter): Boolean
|
||||
abstract fun canGrow(state: BlockState, 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(5) == 0 && canGrow(pos, world)) {
|
||||
if (world.random.nextInt(growthChance) == 0 && canGrow(state, pos, world)) {
|
||||
val block = nextStage
|
||||
|
||||
if (block != null) {
|
||||
val newState = block.defaultBlockState().setValue(FACING, state.getValue(FACING)) as BlockState
|
||||
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>)
|
||||
}
|
||||
}
|
||||
|
||||
world.setBlockAndUpdate(pos, newState)
|
||||
}
|
||||
}
|
||||
@@ -155,14 +164,4 @@ 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,6 +16,11 @@ 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
|
||||
@@ -28,6 +33,7 @@ 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
|
||||
@@ -35,7 +41,8 @@ import net.minecraft.world.phys.shapes.VoxelShape
|
||||
class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(HORIZONTAL_FACING, Direction.NORTH))
|
||||
.setValue(HORIZONTAL_FACING, Direction.NORTH)
|
||||
.setValue(SCREEN, MonitorScreen.OFF))
|
||||
}
|
||||
|
||||
override fun createMultiBlockEntity(
|
||||
@@ -55,6 +62,47 @@ 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)
|
||||
@@ -143,8 +191,27 @@ class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
|
||||
BLUE_PROGRESS_7,
|
||||
BLUE_PROGRESS_8,
|
||||
BLUE_PROGRESS_9,
|
||||
GREEN_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;
|
||||
|
||||
override fun getSerializedName(): String = this.name.lowercase()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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,15 +44,25 @@ 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(pos: BlockPos, world: BlockGetter): Boolean {
|
||||
override fun canGrow(state: BlockState, 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))
|
||||
@@ -66,6 +76,8 @@ class TumblestoneBlock(
|
||||
return true
|
||||
}
|
||||
|
||||
override fun isRandomlyTicking(state: BlockState): Boolean = stage < MAX_STAGE
|
||||
|
||||
override fun codec(): MapCodec<out DirectionalBlock> {
|
||||
return CODEC
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://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 {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* 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,12 +151,14 @@ 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.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 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 as Level, pos, false, true)
|
||||
if (level.isClientSide) for (i in 0..19) MCCampfireBlock.makeParticles(level, pos, false, true)
|
||||
}
|
||||
|
||||
level.scheduleTick(pos, fluidState.type, fluidState.type.getTickDelay(level))
|
||||
return true
|
||||
|
||||
+201
-1
@@ -12,15 +12,35 @@ 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,
|
||||
@@ -28,8 +48,30 @@ 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) {
|
||||
@@ -76,6 +118,164 @@ 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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,383 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* 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#
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
+10
-1
@@ -14,7 +14,9 @@ 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
|
||||
@@ -121,6 +123,13 @@ 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 {
|
||||
@@ -168,4 +177,4 @@ class FossilMultiblockBuilder(val centerPos: BlockPos) : MultiblockStructureBuil
|
||||
//lol thanks mojang for not allowing nbt puts to be chained
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* 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,6 +19,7 @@ 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
|
||||
@@ -27,6 +28,7 @@ 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
|
||||
@@ -40,7 +42,9 @@ 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
|
||||
@@ -69,6 +73,7 @@ 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
|
||||
@@ -89,6 +94,7 @@ 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
|
||||
@@ -103,6 +109,7 @@ 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
|
||||
@@ -119,6 +126,7 @@ object CobblemonClient {
|
||||
requests = ClientPlayerActionRequests()
|
||||
teamData = ClientPlayerTeamData()
|
||||
clientPokedexData = ClientPokedexManager(mutableMapOf())
|
||||
clientTMMoveData = ClientTMMoveManager(mutableSetOf())
|
||||
storage.onLogin()
|
||||
// CobblemonDataProvider.canReload = false
|
||||
}
|
||||
@@ -206,6 +214,7 @@ object CobblemonClient {
|
||||
TooltipManager.registerTooltipGenerator(SeasoningTooltipGenerator)
|
||||
TooltipManager.registerTooltipGenerator(FishingRodTooltipGenerator)
|
||||
TooltipManager.registerTooltipGenerator(AprijuiceTooltipGenerator)
|
||||
TooltipManager.registerTooltipGenerator(TechnicalMachineTooltipGenerator)
|
||||
TooltipManager.registerTooltipGenerator(PokePuffTooltipGenerator)
|
||||
}
|
||||
|
||||
@@ -297,6 +306,24 @@ 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,
|
||||
@@ -362,7 +389,8 @@ object CobblemonClient {
|
||||
CobblemonBlocks.X_DEFENSE,
|
||||
CobblemonBlocks.X_SP_ATK,
|
||||
CobblemonBlocks.X_SP_DEF,
|
||||
CobblemonBlocks.X_SPEED
|
||||
CobblemonBlocks.X_SPEED,
|
||||
CobblemonBlocks.TM_MACHINE
|
||||
)
|
||||
|
||||
this.createBoatModelLayers()
|
||||
@@ -390,6 +418,7 @@ object CobblemonClient {
|
||||
|
||||
private fun registerMenuScreens() {
|
||||
MenuScreens.register(CobblemonMenuType.COOKING_POT, ::CookingPotScreen)
|
||||
MenuScreens.register(CobblemonMenuType.TM_MACHINE, ::TMMachineScreen)
|
||||
}
|
||||
|
||||
private fun registerBlockEntityRenderers() {
|
||||
@@ -413,6 +442,7 @@ 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() {
|
||||
@@ -421,14 +451,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 -> CobblemonBoatRenderer(ctx, false) }
|
||||
LOGGER.info("Registering Boat with Chest renderer")
|
||||
this.implementation.registerEntityRenderer(CobblemonEntities.CHEST_BOAT) { ctx ->
|
||||
this.implementation.registerEntityRenderer(CobblemonEntities.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")
|
||||
@@ -445,6 +475,7 @@ 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) {
|
||||
@@ -481,4 +512,4 @@ object CobblemonClient {
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
|
||||
|
||||
val starterToast = CobblemonToast(
|
||||
Mth.createInsecureUUID(),
|
||||
CobblemonItems.POKE_BALL.defaultInstance,
|
||||
listOf(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,
|
||||
|
||||
+2
-1
@@ -138,7 +138,8 @@ abstract class BattleGimmickButton(gimmick: ShowdownMoveset.Gimmick, val x: Floa
|
||||
pp = gimmickTemplate?.pp ?: moveTemplate.pp,
|
||||
priority = gimmickTemplate?.priority ?: moveTemplate.priority,
|
||||
critRatio = gimmickTemplate?.critRatio ?: moveTemplate.critRatio,
|
||||
effectChances = gimmickTemplate?.effectChances ?: moveTemplate.effectChances
|
||||
effectChances = gimmickTemplate?.effectChances ?: moveTemplate.effectChances,
|
||||
weight = gimmickTemplate?.weight ?: moveTemplate.weight
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+65
-69
@@ -10,7 +10,6 @@ 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
|
||||
@@ -21,6 +20,7 @@ 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,96 +31,92 @@ import net.minecraft.util.Mth
|
||||
|
||||
class MoveSlotButton(
|
||||
x: Int, y: Int,
|
||||
val move: MoveTemplate,
|
||||
val pp: Int,
|
||||
val ppMax: Int,
|
||||
val enabled: Boolean = true,
|
||||
var move: MoveTemplate? = null,
|
||||
var pp: Int = 0,
|
||||
var ppMax: Int = 0,
|
||||
var ppAsFraction: Boolean = true,
|
||||
var enabled: Boolean = true,
|
||||
onPress: OnPress
|
||||
) : Button(x, y, WIDTH, HEIGHT, Component.literal("Move"), onPress, CreateNarration { "".text() }), CobblemonRenderable {
|
||||
) : Button(x, y, WIDTH, HEIGHT, move?.name?.text() ?: lang("ui.moves"), onPress, CreateNarration { move?.name?.text() ?: lang("ui.moves") }), 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, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
|
||||
isHovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height && enabled
|
||||
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()
|
||||
|
||||
val moveTemplate = Moves.getByNameOrDummy(move.name)
|
||||
val rgb = moveTemplate.elementalType.hue.toRGB()
|
||||
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 alpha = if (enabled) 1.0 else 0.5
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = moveOverlayResource,
|
||||
x = x,
|
||||
y = y,
|
||||
width = WIDTH,
|
||||
height = HEIGHT
|
||||
)
|
||||
|
||||
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 != -1 && ppMax != -1) {
|
||||
var movePPText = lang("ui.moves.pp", pp).bold()
|
||||
if (ppAsFraction) {
|
||||
movePPText = Component.literal("$pp/$ppMax").bold()
|
||||
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = moveOverlayResource,
|
||||
x = x,
|
||||
y = y,
|
||||
width = WIDTH,
|
||||
height = HEIGHT,
|
||||
alpha = alpha
|
||||
)
|
||||
if (pp <= Mth.floor(ppMax / 2F)) {
|
||||
movePPText = if (pp == 0) movePPText.red() else movePPText.gold()
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
drawScaledText(
|
||||
context = context,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = movePPText,
|
||||
x = x + 93,
|
||||
y = y + 13,
|
||||
centered = true,
|
||||
opacity = alpha
|
||||
)
|
||||
}
|
||||
|
||||
// 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 = movePPText,
|
||||
x = x + 93,
|
||||
y = y + 13,
|
||||
centered = true
|
||||
text = it.displayName.bold(),
|
||||
x = x + 28,
|
||||
y = y + 2,
|
||||
shadow = true,
|
||||
opacity = alpha
|
||||
)
|
||||
}
|
||||
|
||||
// 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(),
|
||||
CobblemonItems.POKEDEX_RED.defaultInstance,
|
||||
listOf(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,6 +22,8 @@ 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
|
||||
@@ -39,6 +41,7 @@ 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.*
|
||||
@@ -83,12 +86,14 @@ 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_drops.png"),
|
||||
cobblemonResource("textures/gui/pokedex/tab_moves.png")
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -127,11 +132,13 @@ 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
|
||||
@@ -163,7 +170,7 @@ class PokedexGUI private constructor(
|
||||
displaytabInfoElement(tabInfoIndex, false)
|
||||
|
||||
if (::searchWidget.isInitialized) removeWidget(searchWidget)
|
||||
searchWidget = SearchWidget(x + 26, y + 28, 128, HEADER_BAR_HEIGHT, update = ::updateFilters)
|
||||
searchWidget = SearchWidget(x + 26, y + 28, 120, HEADER_BAR_HEIGHT, update = ::updateFilters)
|
||||
addRenderableWidget(searchWidget)
|
||||
|
||||
if (::regionSelectWidgetUp.isInitialized) removeWidget(regionSelectWidgetUp)
|
||||
@@ -208,6 +215,23 @@ 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)
|
||||
}
|
||||
|
||||
@@ -309,8 +333,7 @@ class PokedexGUI private constructor(
|
||||
blitk(
|
||||
matrixStack = matrices,
|
||||
texture = tabSelectArrow,
|
||||
x = (x + 198 + (25 * tabInfoIndex)) / SCALE,
|
||||
// (x + 191.5 + (22 * tabInfoIndex)) / SCALE for 6 tabs
|
||||
x = (x + 191.5F + (22F * tabInfoIndex)) / SCALE,
|
||||
y = (y + 177) / SCALE,
|
||||
width = 12,
|
||||
height = 6,
|
||||
@@ -332,6 +355,18 @@ 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() {
|
||||
@@ -344,6 +379,7 @@ class PokedexGUI private constructor(
|
||||
val canDisplayEntry = true //selectedForm?.unlockForms
|
||||
|
||||
if (::pokemonInfoWidget.isInitialized
|
||||
&& !pokemonInfoWidget.isSuppressed
|
||||
&& pokemonInfoWidget.isWithinPortraitSpace(mouseX, mouseY)
|
||||
&& canDisplayEntry == true
|
||||
) {
|
||||
@@ -375,7 +411,7 @@ class PokedexGUI private constructor(
|
||||
}
|
||||
|
||||
override fun tick() {
|
||||
if (::pokemonInfoWidget.isInitialized) pokemonInfoWidget.tick()
|
||||
if (::pokemonInfoWidget.isInitialized && !pokemonInfoWidget.isSuppressed) pokemonInfoWidget.tick()
|
||||
}
|
||||
|
||||
fun updatePokedexRegion(nextIndex: Boolean) {
|
||||
@@ -441,6 +477,7 @@ 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
|
||||
@@ -462,7 +499,7 @@ class PokedexGUI private constructor(
|
||||
|
||||
for (i in tabIcons.indices) {
|
||||
tabButtons.add(ScaledButton(
|
||||
x + 197F + (i * 25F), // x + 190.5F + (i * 22F) for 6 tabs
|
||||
x + 190.5F + (i * 22F),
|
||||
y + 181.5F,
|
||||
TAB_ICON_SIZE,
|
||||
TAB_ICON_SIZE,
|
||||
@@ -497,6 +534,9 @@ 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
|
||||
@@ -520,11 +560,19 @@ 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()
|
||||
}
|
||||
|
||||
@@ -578,6 +626,17 @@ 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)
|
||||
// }
|
||||
@@ -600,6 +659,29 @@ 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
|
||||
@@ -621,4 +703,4 @@ class PokedexGUI private constructor(
|
||||
fun playSound(soundEvent: SoundEvent) {
|
||||
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -35,9 +35,10 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
+1050
File diff suppressed because it is too large
Load Diff
+52
-1
@@ -125,6 +125,7 @@ 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,
|
||||
@@ -259,6 +260,9 @@ 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
|
||||
@@ -512,6 +516,40 @@ 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
|
||||
@@ -624,6 +662,19 @@ 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
|
||||
@@ -706,4 +757,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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+44
-53
@@ -36,9 +36,9 @@ class MoveSlotWidget(
|
||||
): SoundlessWidget(pX, pY, MOVE_WIDTH, MOVE_HEIGHT, Component.literal(move?.name ?: "")) {
|
||||
|
||||
companion object {
|
||||
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")
|
||||
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")
|
||||
|
||||
const val MOVE_WIDTH = 108
|
||||
const val MOVE_HEIGHT = 22
|
||||
@@ -50,6 +50,7 @@ class MoveSlotWidget(
|
||||
}.apply {
|
||||
addWidget(this)
|
||||
}
|
||||
|
||||
private val moveDownButton = ReorderMoveButton(x, y, false) {
|
||||
movesWidget.selectMove(null)
|
||||
movesWidget.reorderMove(this, false)
|
||||
@@ -67,46 +68,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()
|
||||
@@ -116,36 +117,28 @@ 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
|
||||
@@ -158,9 +151,7 @@ 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 = pane.movesWidget.format(move.accuracy).text(),
|
||||
text = MovesWidget.format(move.accuracy).text(),
|
||||
x = rowLeft + 37,
|
||||
y = tweakedRowTop + 12,
|
||||
scale = MovesWidget.SCALE,
|
||||
@@ -171,7 +171,7 @@ class MoveSwapScreen(
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = pane.movesWidget.format(move.effectChances.firstOrNull() ?: 0.0).text(),
|
||||
text = 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,6 +39,11 @@ 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")
|
||||
@@ -208,11 +213,6 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user