Merge branch 'main' into sega_bass_pro_fishing_2

# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonEntities.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItems.kt
#	fabric/src/main/kotlin/com/cobblemon/mod/fabric/client/CobblemonFabricClient.kt
#	forge/src/main/kotlin/com/cobblemon/mod/forge/client/CobblemonForgeClient.kt
This commit is contained in:
Lily
2024-02-15 18:54:42 -05:00
1408 changed files with 35610 additions and 5007 deletions
+1
View File
@@ -33,3 +33,4 @@ common/src/main/resources/assets/cobblemon/showdown
# Generated files
*/generated/resources/*
/fabric/src/generated/resources/cobblemon-common.accesswidener
/.ignored/
+35 -7
View File
@@ -3,16 +3,23 @@
### Additions
- Added over (number here) ruin structures, where you can find Pokémon-themed Armor Trims and Pottery Sherds, Tumblestones, and more.
- Added (number here) fossil structures, where you can brush Suspicious Sand/Gravel for Fossils.
- Added the Data Monitor, Fossil Compartment, and Restoration Tube. Placed in the correct formation, you can use these to create a Restoration Machine where you can bring fossils back to life.
- Added the Data Monitor, Fossil Compartment, and Restoration Tank. Placed in the correct formation, you can use these to create a Restoration Machine where you can bring fossils back to life.
- Added 3 variants of Tumblestones, which can be planted near Lava or Magma to grow harvestable Tumblestone Clusters.
- Added Tumblestone Blocks, a storage block crafted from 9 Tumblestones.
- Added Ancient Poké Balls, which are aesthetic variants crafted from Tumblestones.
- Added the Ancient Feather, Wing, and Jet Balls, which fly further than regular Poké Balls.
- Added the Ancient Heavy, Leaden, and Gigaton Balls, which are heavier and don't fly as far as regular Poké Balls. These will recieve more functionality in a future update.
- Added the Ancient Heavy, Leaden, and Gigaton Balls, which are heavier and don't fly as far as regular Poké Balls. These will receive more functionality in a future update.
- Added Original Trainer functionality. To edit the OT through commands, you must specify originaltrainertype=<Player/NPC>, and originaltrainer=<Username or UUID/NPC Name>
- Added support for planting Apricorn trees, berries, and mints in [Botany Pots](https://modrinth.com/mod/botany-pots)
- Added the hidden "True Vivillionaire" advancement.
- The "Poké Ball" Vivillon variant is now obtainable by evolving a Spewpa after obtaining the "Aspiring Vivillionaire" advancement.
- Added dynamic lighting support for any Species or Forms when using [LambDynamicLights](https://modrinth.com/mod/lambdynamiclights) or [Dynamic Lights Reforged](https://www.curseforge.com/minecraft/mc-mods/dynamiclights-reforged), at this time we don't ship with any defaults for this feature but addon creators can make use of this feature.
- Added 6 Pokémon-themed Pottery Sherds, obtained from ruin structures.
- Added a Pokémon-themed armor trim, obtained from ruin structures.
- You can now find Nether Fire Stone Ore in, surprisingly, the Nether.
- Added effects for many Poké Balls when sending out Pokémon or capturing.
- Added a new send-out ball toss animation that showcases the ball used for the Pokémon.
- Added simple move animations for Pokémon in battle with a proof of concept for more complicated animations.
### Changes
- Pokémon now transition more smoothly between different poses.
@@ -23,6 +30,8 @@
- Renamed the "Vivillionaire" advancement to "Aspiring Vivillionaire"
- Removed the Inferno, Forsaken, Poké Ball, and Void patterns from the Aspiring Vivillionaire advancement.
- Updated movesets to add Gen 9 DLC moves.
- Apricorn Sprouts can now be planted directly on Apricorn leaves to create a new Apricorn of the same color.
- Added tooltips to interaction GUIs to make it clearer what they do.
### Fixes
- Re-added the recipe for the Iron Vitamin.
@@ -31,21 +40,26 @@
- Fixed battle lang for Poison Touch Ability
- Fixed Cubone not being able to evolve into Alolan Marowak.
- Fixed Squawkabilly forms not being recognized.
- Fixed the bounce animation for Poké Balls being delayed by like, a full second.
- Note blocks now play the correct sound on the Apricorn Plank based blocks.
- Fixed an issue with transformed parts in posers causing whacky positional issues.
- Fixed Poké Balls freezing in an open state if you look away when it's meant to close.
- Adds a translation string name to the Empty Pokeball entity.
- Fixed very many issues with Bedrock particle effects.
### Developer
- Significantly changed the way properties in PokemonEntity work. This could break some plugins (albeit in a quick-fix way).
- Rebuilt the scheduling API to more clearly force side choices and allow more local temporal frames of reference for tasks.
- Added dialogue API.
- Significantly changed the way properties in PokemonEntity work. This could break some plugins (not in a big way, but changes will be needed).
- Rebuilt the scheduling API to more clearly force side choices and temporal frames of reference for tasks.
- Added dialogue API and data registry.
- Opened up the ItemDropEntry class to allow for it to be extended.
- Added a new "advancement" evolution variant that takes an advancement identifier and succeeds if the player has the advancement.
- Made spawning API capable of non-entity spawning
- Made PokemonProperties.parse more interoperable with Java.
- Added the ``HeldItemEvent``, this comes with 2 implementation ``HeldItemEvent.Pre`` and ``HeldItemEvent.Post``.
- API to support dynamic lighting in any mod that implements this feature has been added to all Species & Forms, see LightingData class for details.
### Datapack & Resourcepack Creators
- The maximum amount of fossils that can fit in the Fossil Compartment can be adjusted in the config.
- The maximum amount of fossils that can fit in the Fossil Analyzer can be adjusted in the config.
- Custom fossils can be defined using a list of items and the resulting Pokémon. An example of `aerodactyl.json`:
```JSON
{
@@ -64,6 +78,19 @@
- Added 3 new item tags: `ancient_poke_balls`, `fossils`, and `tumblestones`.
- Added a spawn rules system to modify general spawning behaviour, see the [wiki](https://wiki.cobblemon.com/index.php/Spawn_Rules) for more information.
- Added dialogue datapack folder and /opendialogue command.
- Added the item tag ``cobblemon:held/leaves_leftovers`` this can be used to flag apple-like items that can create leftovers when eaten.
- You can now add support for dynamic lighting implementations, Cobblemon ships with a default implementation for [LambDynamicLights](https://modrinth.com/mod/lambdynamiclights) and the [Dynamic Lights Reforged](https://www.curseforge.com/minecraft/mc-mods/dynamiclights-reforged), add the following to any species or form:
```JSON
{
"lightingData": {
"lightLevel": 14,
"_lightLevelCommentRemoveMe": "Above supports 0 to 15",
"liquidGlowMode": "LAND",
"_liquidGlowModeCommentRemoveMe": "Above supports LAND, UNDERWATER or BOTH"
}
}
```
- Following up on this change ``light_source`` was removed as a possible shoulder effect due to becoming unnecessary and never having had a default implementation.
## [1.4.1 (December 23rd, 2023)](#1-4-1)
@@ -81,6 +108,7 @@
- Added cries to Beldum, Metang and Metagross.
- Added a /bedrockparticle command to run Snowstorm-format particle effects.
- Added data for Dipplin, Fezandipiti, Munkidori, Ogerpon, Okidogi, Poltchageist and Sinistcha.
- Added additional nickname trigger "Grumm" for Inkay's evolution.
### Changes
- Using Potions, Status Heals, Ethers, and Antidotes will now return a glass bottle
@@ -998,4 +1026,4 @@ Thank you so much to all of our community translators that bring the mod to the
- Added species, ability, and move translations for `ko_ko`, `jp_jp`, `fr_fr`, `es_es`, `it_it`, and `zh_cn`.
## [1.0.0 (November 12th, 2022)](#1-0-0)
- Initial release.
- Initial release.
+2 -1
View File
@@ -1,6 +1,7 @@
plugins {
base
id("cobblemon.root-conventions")
id ("net.nemerosa.versioning") version "2.8.2"
}
group = "com.cobblemon.mod"
@@ -8,5 +9,5 @@ version = "${project.property("mod_version")}+${project.property("mc_version")}"
val isSnapshot = project.property("snapshot")?.equals("true") ?: false
if (isSnapshot) {
version = "$version-SNAPSHOT"
version = "$version-SNAPSHOT-${versioning.info.branch}-${versioning.info.build}"
}
+1
View File
@@ -33,6 +33,7 @@ dependencies {
modApi(libs.molang)
compileOnlyApi(libs.jeiApi)
modCompileOnly(libs.adornFabric)
modCompileOnly(libs.lambDynamicLights) { isTransitive = false }
// For Showdown
modCompileOnly(libs.graal)
@@ -0,0 +1,96 @@
/*
* 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.compat;
import com.cobblemon.mod.common.CobblemonEntities;
import com.cobblemon.mod.common.client.render.layer.PokemonOnShoulderRenderer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.pokemon.FormData;
import com.cobblemon.mod.common.pokemon.lighthing.LightingData;
import dev.lambdaurora.lambdynlights.api.DynamicLightHandler;
import dev.lambdaurora.lambdynlights.api.DynamicLightHandlers;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
// Java class due to relying on entrypoint on Fabric side
public class LambDynamicLightsCompat {
// This is all called on client side
// These things are checked every tick so try to keep it as free as possible
// That being said it's unlikely school Toshiba person is running dynamic lighting and shaders to enjoy this
// Make sure not to call anything Fabric specific :)
public static void hookCompat() {
// Pokémon entities
DynamicLightHandlers.registerDynamicLightHandler(
CobblemonEntities.POKEMON,
DynamicLightHandler.makeHandler(
pokemon -> resolvedPokemonLightLevel(pokemon, false),
pokemon -> true
)
);
DynamicLightHandlers.registerDynamicLightHandler(
CobblemonEntities.POKEMON,
DynamicLightHandler.makeHandler(
pokemon -> resolvedPokemonLightLevel(pokemon, true),
pokemon -> false
)
);
// Shouldered Pokémon
DynamicLightHandlers.registerDynamicLightHandler(
EntityType.PLAYER,
DynamicLightHandler.makeHandler(
player -> resolvedShoulderLightLevel(player, false),
player -> true
)
);
DynamicLightHandlers.registerDynamicLightHandler(
EntityType.PLAYER,
DynamicLightHandler.makeHandler(
player -> resolvedShoulderLightLevel(player, true),
player -> false
)
);
}
private static int resolvedPokemonLightLevel(PokemonEntity pokemon, boolean underwater) {
return extractFormLightLevel(pokemon.getForm(), underwater).orElse(0);
}
private static int resolvedShoulderLightLevel(PlayerEntity player, boolean underwater) {
final Pair<PokemonOnShoulderRenderer.ShoulderData, PokemonOnShoulderRenderer.ShoulderData> shoulderDataPair = PokemonOnShoulderRenderer.shoulderDataOf(player);
final Optional<Integer> leftLightLevel = extractShoulderLightLevel(shoulderDataPair.getLeft(), underwater);
final Optional<Integer> rightLightLevel = extractShoulderLightLevel(shoulderDataPair.getRight(), underwater);
return Math.max(leftLightLevel.orElse(0), rightLightLevel.orElse(0));
}
private static Optional<Integer> extractFormLightLevel(@NotNull FormData form, boolean underwater) {
if (form.getLightingData() == null || !liquidGlowModeSupport(form.getLightingData().getLiquidGlowMode(), underwater)) {
return Optional.empty();
}
return Optional.of(form.getLightingData().getLightLevel());
}
private static Optional<Integer> extractShoulderLightLevel(@Nullable PokemonOnShoulderRenderer.ShoulderData shoulderData, boolean underwater) {
if (shoulderData == null) {
return Optional.empty();
}
return extractFormLightLevel(shoulderData.getForm(), underwater);
}
private static boolean liquidGlowModeSupport(@NotNull LightingData.LiquidGlowMode liquidGlowMode, boolean underwater) {
return underwater ? liquidGlowMode.getGlowsUnderwater() : liquidGlowMode.getGlowsInLand();
}
}
@@ -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.mixin;
import com.cobblemon.mod.common.sherds.CobblemonSherds;
import net.minecraft.block.DecoratedPotPatterns;
import net.minecraft.item.Item;
import net.minecraft.registry.RegistryKey;
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(DecoratedPotPatterns.class)
public abstract class DecoratedPotPatternsMixin {
@Inject(method = "fromSherd", at=@At("HEAD"), cancellable = true)
private static void cobblemon$getCobblemonSherdTexture(Item sherd,
CallbackInfoReturnable<RegistryKey<String>> cir) {
if (CobblemonSherds.INSTANCE.getSherdToPattern().containsKey(sherd)) {
cir.setReturnValue(CobblemonSherds.INSTANCE.getSherdToPattern().get(sherd));
return;
}
}
}
@@ -40,4 +40,5 @@ public class GameRendererMixin {
}
lastTime = newTime;
}
}
@@ -0,0 +1,37 @@
/*
* 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.api.snowstorm.ParticleMaterials;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.particle.ParticleManager;
import net.minecraft.client.particle.ParticleTextureSheet;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
@Mixin(ParticleManager.class)
final class ParticleManagerMixin {
@Mutable
@Final
@Shadow
private static List<ParticleTextureSheet> PARTICLE_TEXTURE_SHEETS;
@Inject(at = @At("RETURN"), method = "<clinit>")
private static void lodestone$addTypes(CallbackInfo ci) {
PARTICLE_TEXTURE_SHEETS = ImmutableList.<ParticleTextureSheet>builder().addAll(PARTICLE_TEXTURE_SHEETS)
.add(ParticleMaterials.INSTANCE.getADD(), ParticleMaterials.INSTANCE.getALPHA(), ParticleMaterials.INSTANCE.getBLEND(), ParticleMaterials.INSTANCE.getOPAQUE()).build();
}
}
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents;
import com.cobblemon.mod.common.api.events.item.LeftoversCreatedEvent;
import com.cobblemon.mod.common.api.storage.NoPokemonStoreException;
import com.cobblemon.mod.common.api.storage.party.PartyStore;
import com.cobblemon.mod.common.api.tags.CobblemonItemTags;
import com.cobblemon.mod.common.pokemon.Pokemon;
import com.cobblemon.mod.common.util.CompoundTagExtensionsKt;
import com.cobblemon.mod.common.util.DataKeys;
@@ -22,11 +23,8 @@ import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.sound.SoundEvent;
import net.minecraft.sound.SoundEvents;
import net.minecraft.text.Text;
import net.minecraft.world.World;
import org.objectweb.asm.Opcodes;
@@ -40,8 +38,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Objects;
import java.util.UUID;
import static com.cobblemon.mod.common.util.LocalizationUtilsKt.lang;
@Mixin(PlayerEntity.class)
public abstract class PlayerMixin extends LivingEntity {
@@ -144,7 +140,7 @@ public abstract class PlayerMixin extends LivingEntity {
)
public void onEatFood(World world, ItemStack stack, CallbackInfoReturnable<ItemStack> cir) {
if (!getWorld().isClient) {
if (stack.getItem() == Items.APPLE && getWorld().random.nextDouble() < Cobblemon.config.getAppleLeftoversChance()) {
if (stack.isIn(CobblemonItemTags.LEAVES_LEFTOVERS) && getWorld().random.nextDouble() < Cobblemon.config.getAppleLeftoversChance()) {
ItemStack leftovers = new ItemStack(CobblemonItems.LEFTOVERS);
ServerPlayerEntity player = Objects.requireNonNull(getServer()).getPlayerManager().getPlayer(uuid);
assert player != null;
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents.EVOLUTION_COMPLETE
import com.cobblemon.mod.common.api.events.CobblemonEvents.LEVEL_UP_EVENT
import com.cobblemon.mod.common.api.events.CobblemonEvents.POKEMON_CAPTURED
import com.cobblemon.mod.common.api.events.CobblemonEvents.TRADE_COMPLETED
import com.cobblemon.mod.common.api.net.serializers.IdentifierDataSerializer
import com.cobblemon.mod.common.api.net.serializers.PoseTypeDataSerializer
import com.cobblemon.mod.common.api.net.serializers.StringSetDataSerializer
import com.cobblemon.mod.common.api.net.serializers.Vec3DataSerializer
@@ -102,6 +103,7 @@ import com.cobblemon.mod.common.pokemon.properties.HiddenAbilityPropertyType
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.pokemon.properties.tags.PokemonFlagProperty
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
import com.cobblemon.mod.common.sherds.CobblemonSherds
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
import com.cobblemon.mod.common.trade.TradeManager
import com.cobblemon.mod.common.util.*
@@ -235,6 +237,7 @@ object Cobblemon {
TrackedDataHandlerRegistry.register(Vec3DataSerializer)
TrackedDataHandlerRegistry.register(StringSetDataSerializer)
TrackedDataHandlerRegistry.register(PoseTypeDataSerializer)
TrackedDataHandlerRegistry.register(IdentifierDataSerializer)
HeldItemProvider.register(CobblemonHeldItemManager, Priority.LOWEST)
}
@@ -385,6 +388,8 @@ object Cobblemon {
true
).ifSuccessful { it.mute = true }
}
CobblemonSherds.registerSherds()
}
fun getLevel(dimension: RegistryKey<World>): World? {
@@ -12,9 +12,9 @@ import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import com.cobblemon.mod.common.block.entity.HealingMachineBlockEntity
import com.cobblemon.mod.common.block.entity.PCBlockEntity
import com.cobblemon.mod.common.block.entity.PokemonPastureBlockEntity
import com.cobblemon.mod.common.block.entity.fossil.FossilCompartmentBlockEntity
import com.cobblemon.mod.common.block.entity.fossil.FossilMultiblockEntity
import com.cobblemon.mod.common.block.entity.fossil.FossilTubeBlockEntity
import com.cobblemon.mod.common.block.entity.FossilAnalyzerBlockEntity
import com.cobblemon.mod.common.block.entity.FossilMultiblockEntity
import com.cobblemon.mod.common.block.entity.RestorationTankBlockEntity
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockBuilder
import com.cobblemon.mod.common.block.entity.*
import com.cobblemon.mod.common.platform.PlatformRegistry
@@ -48,21 +48,21 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
@JvmField
val FOSSIL_MULTIBLOCK: BlockEntityType<FossilMultiblockEntity> = this.create("fossil_multiblock",
BlockEntityType.Builder.create({ pos, state -> FossilMultiblockEntity(pos, state, FossilMultiblockBuilder(pos)) },
CobblemonBlocks.FOSSIL_MONITOR
CobblemonBlocks.MONITOR
).build(null)
)
@JvmField
val FOSSIL_TUBE: BlockEntityType<FossilTubeBlockEntity> = this.create("fossil_tube",
BlockEntityType.Builder.create({ pos, state -> FossilTubeBlockEntity(pos, state, FossilMultiblockBuilder(pos)) },
CobblemonBlocks.FOSSIL_TUBE
val RESTORATION_TANK: BlockEntityType<RestorationTankBlockEntity> = this.create("restoration_tank",
BlockEntityType.Builder.create({ pos, state -> RestorationTankBlockEntity(pos, state, FossilMultiblockBuilder(pos)) },
CobblemonBlocks.RESTORATION_TANK
).build(null)
)
@JvmField
val FOSSIL_COMPARTMENT: BlockEntityType<FossilCompartmentBlockEntity> = this.create("fossil_compartment",
BlockEntityType.Builder.create({ pos, state -> FossilCompartmentBlockEntity(pos, state, FossilMultiblockBuilder(pos)) },
CobblemonBlocks.FOSSIL_COMPARTMENT
val FOSSIL_ANALYZER: BlockEntityType<FossilAnalyzerBlockEntity> = this.create("fossil_analyzer",
BlockEntityType.Builder.create({ pos, state -> FossilAnalyzerBlockEntity(pos, state, FossilMultiblockBuilder(pos)) },
CobblemonBlocks.FOSSIL_ANALYZER
).build(null)
)
}
@@ -11,9 +11,9 @@ package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.apricorn.Apricorn
import com.cobblemon.mod.common.block.*
import com.cobblemon.mod.common.block.MintBlock.MintType
import com.cobblemon.mod.common.block.fossilmachine.FossilCompartmentBlock
import com.cobblemon.mod.common.block.fossilmachine.FossilMonitorBlock
import com.cobblemon.mod.common.block.fossilmachine.FossilTubeBlock
import com.cobblemon.mod.common.block.FossilAnalyzerBlock
import com.cobblemon.mod.common.block.MonitorBlock
import com.cobblemon.mod.common.block.RestorationTankBlock
import com.cobblemon.mod.common.mixin.invoker.DoorBlockInvoker
import com.cobblemon.mod.common.mixin.invoker.PressurePlateBlockInvoker
import com.cobblemon.mod.common.mixin.invoker.StairsBlockInvoker
@@ -57,6 +57,8 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, RegistryKey<Registry<
@JvmField
val FIRE_STONE_ORE = evolutionStoneOre("fire_stone_ore")
@JvmField
val NETHER_FIRE_STONE_ORE = evolutionStoneOre("nether_fire_stone_ore")
@JvmField
val ICE_STONE_ORE = evolutionStoneOre("ice_stone_ore")
@JvmField
val LEAF_STONE_ORE = evolutionStoneOre("leaf_stone_ore")
@@ -158,31 +160,31 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, RegistryKey<Registry<
val REVIVAL_HERB = this.create("revival_herb", RevivalHerbBlock(AbstractBlock.Settings.create().pistonBehavior(PistonBehavior.DESTROY).mapColor(MapColor.DARK_GREEN).burnable().noCollision().breakInstantly().sounds(BlockSoundGroup.CROP)))
@JvmField
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null)
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
@JvmField
val LARGE_BUDDING_TUMBLESTONE = tumblestoneBlock("large_budding_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, TUMBLESTONE_CLUSTER)
val LARGE_BUDDING_TUMBLESTONE = tumblestoneBlock("large_budding_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, TUMBLESTONE_CLUSTER)
@JvmField
val MEDIUM_BUDDING_TUMBLESTONE = tumblestoneBlock("medium_budding_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_TUMBLESTONE)
val MEDIUM_BUDDING_TUMBLESTONE = tumblestoneBlock("medium_budding_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_TUMBLESTONE)
@JvmField
val SMALL_BUDDING_TUMBLESTONE = tumblestoneBlock("small_budding_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_TUMBLESTONE)
val SMALL_BUDDING_TUMBLESTONE = tumblestoneBlock("small_budding_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_TUMBLESTONE)
@JvmField
val SKY_TUMBLESTONE_CLUSTER = tumblestoneBlock("sky_tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null)
val SKY_TUMBLESTONE_CLUSTER = tumblestoneBlock("sky_tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
@JvmField
val LARGE_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("large_budding_sky_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, SKY_TUMBLESTONE_CLUSTER)
val LARGE_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("large_budding_sky_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, SKY_TUMBLESTONE_CLUSTER)
@JvmField
val MEDIUM_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("medium_budding_sky_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_SKY_TUMBLESTONE)
val MEDIUM_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("medium_budding_sky_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_SKY_TUMBLESTONE)
@JvmField
val SMALL_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("small_budding_sky_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_SKY_TUMBLESTONE)
val SMALL_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("small_budding_sky_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_SKY_TUMBLESTONE)
@JvmField
val BLACK_TUMBLESTONE_CLUSTER = tumblestoneBlock("black_tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null)
val BLACK_TUMBLESTONE_CLUSTER = tumblestoneBlock("black_tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
@JvmField
val LARGE_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("large_budding_black_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, BLACK_TUMBLESTONE_CLUSTER)
val LARGE_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("large_budding_black_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, BLACK_TUMBLESTONE_CLUSTER)
@JvmField
val MEDIUM_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("medium_budding_black_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_BLACK_TUMBLESTONE)
val MEDIUM_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("medium_budding_black_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_BLACK_TUMBLESTONE)
@JvmField
val SMALL_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("small_budding_black_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_BLACK_TUMBLESTONE)
val SMALL_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("small_budding_black_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_BLACK_TUMBLESTONE)
@JvmField
val TUMBLESTONE_BLOCK = this.create("tumblestone_block", Block(AbstractBlock.Settings.create()
@@ -222,38 +224,41 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, RegistryKey<Registry<
val YELLOW_APRICORN = apricornBlock("yellow_apricorn", Apricorn.YELLOW)
@JvmField
val FOSSIL_MONITOR = create(
"fossil_monitor",
FossilMonitorBlock(
val MONITOR = create(
"monitor",
MonitorBlock(
AbstractBlock.Settings.create()
.mapColor(MapColor.IRON_GRAY)
.sounds(BlockSoundGroup.METAL)
.pistonBehavior(PistonBehavior.BLOCK)
.strength(2f)
.luminance { if (it.get(FossilMonitorBlock.PROGRESS) > 0) 15 else 0 }
.requiresTool()
.strength(5.0F, 6.0F)
.luminance { if (it.get(MonitorBlock.SCREEN) != MonitorBlock.MonitorScreen.OFF) 15 else 0 }
)
)
@JvmField
val FOSSIL_COMPARTMENT = create(
"fossil_compartment",
FossilCompartmentBlock(
val FOSSIL_ANALYZER = create(
"fossil_analyzer",
FossilAnalyzerBlock(
AbstractBlock.Settings.create()
.mapColor(MapColor.IRON_GRAY)
.sounds(BlockSoundGroup.METAL)
.pistonBehavior(PistonBehavior.BLOCK)
.strength(2f)
.requiresTool()
.strength(5.0F, 6.0F)
.nonOpaque()
)
)
@JvmField
val FOSSIL_TUBE = create(
"fossil_tube",
FossilTubeBlock(
val RESTORATION_TANK = create(
"restoration_tank",
RestorationTankBlock(
AbstractBlock.Settings.create()
.mapColor(MapColor.IRON_GRAY)
.sounds(BlockSoundGroup.METAL)
.pistonBehavior(PistonBehavior.BLOCK)
.strength(2f)
.requiresTool()
.strength(5.0F, 6.0F)
.nonOpaque()
)
)
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.entity.boat.CobblemonBoatEntity
import com.cobblemon.mod.common.entity.boat.CobblemonChestBoatEntity
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.entity.generic.GenericBedrockEntity
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.platform.PlatformRegistry
@@ -75,6 +76,15 @@ object CobblemonEntities : PlatformRegistry<Registry<EntityType<*>>, RegistryKey
.build(POKE_BOBBER_KEY.toString())
)
@JvmField
val GENERIC_BEDROCK_ENTITY_KEY = cobblemonResource("generic_bedrock")
@JvmField
val GENERIC_BEDROCK_ENTITY: EntityType<GenericBedrockEntity> = this.create(
GENERIC_BEDROCK_ENTITY_KEY.path,
EntityType.Builder.create({ _, world -> GenericBedrockEntity(world) }, SpawnGroup.MISC)
.build(GENERIC_BEDROCK_ENTITY_KEY.toString())
)
fun registerAttributes(consumer: (EntityType<out LivingEntity>, DefaultAttributeContainer.Builder) -> Unit) {
consumer(POKEMON, PokemonEntity.createAttributes())
}
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.block.MintBlock
import com.cobblemon.mod.common.block.MintBlock.MintType
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
import com.cobblemon.mod.common.item.*
import com.cobblemon.mod.common.item.armor.CobblemonArmorTrims
import com.cobblemon.mod.common.item.battle.DireHitItem
import com.cobblemon.mod.common.item.battle.GuardSpecItem
import com.cobblemon.mod.common.item.battle.XStatItem
@@ -38,6 +39,14 @@ import net.minecraft.entity.LivingEntity
import net.minecraft.entity.effect.StatusEffectInstance
import net.minecraft.entity.effect.StatusEffects
import net.minecraft.item.*
import net.minecraft.item.BlockItem
import net.minecraft.item.FoodComponent
import net.minecraft.item.HangingSignItem
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.item.SignItem
import net.minecraft.item.SmithingTemplateItem
import net.minecraft.item.StewItem
import net.minecraft.registry.Registries
import net.minecraft.registry.Registry
import net.minecraft.registry.RegistryKey
@@ -171,19 +180,19 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
val WHITE_APRICORN = apricornItem("white", ApricornItem(CobblemonBlocks.WHITE_APRICORN))
@JvmField
val RED_APRICORN_SEED = apricornSeedItem("red", ApricornSeedItem(CobblemonBlocks.RED_APRICORN_SAPLING))
val RED_APRICORN_SEED = apricornSeedItem("red", ApricornSeedItem(CobblemonBlocks.RED_APRICORN_SAPLING, CobblemonBlocks.RED_APRICORN))
@JvmField
val YELLOW_APRICORN_SEED = apricornSeedItem("yellow", ApricornSeedItem(CobblemonBlocks.YELLOW_APRICORN_SAPLING))
val YELLOW_APRICORN_SEED = apricornSeedItem("yellow", ApricornSeedItem(CobblemonBlocks.YELLOW_APRICORN_SAPLING, CobblemonBlocks.YELLOW_APRICORN))
@JvmField
val GREEN_APRICORN_SEED = apricornSeedItem("green", ApricornSeedItem(CobblemonBlocks.GREEN_APRICORN_SAPLING))
val GREEN_APRICORN_SEED = apricornSeedItem("green", ApricornSeedItem(CobblemonBlocks.GREEN_APRICORN_SAPLING, CobblemonBlocks.GREEN_APRICORN))
@JvmField
val BLUE_APRICORN_SEED = apricornSeedItem("blue", ApricornSeedItem(CobblemonBlocks.BLUE_APRICORN_SAPLING))
val BLUE_APRICORN_SEED = apricornSeedItem("blue", ApricornSeedItem(CobblemonBlocks.BLUE_APRICORN_SAPLING, CobblemonBlocks.BLUE_APRICORN))
@JvmField
val PINK_APRICORN_SEED = apricornSeedItem("pink", ApricornSeedItem(CobblemonBlocks.PINK_APRICORN_SAPLING))
val PINK_APRICORN_SEED = apricornSeedItem("pink", ApricornSeedItem(CobblemonBlocks.PINK_APRICORN_SAPLING, CobblemonBlocks.PINK_APRICORN))
@JvmField
val BLACK_APRICORN_SEED = apricornSeedItem("black", ApricornSeedItem(CobblemonBlocks.BLACK_APRICORN_SAPLING))
val BLACK_APRICORN_SEED = apricornSeedItem("black", ApricornSeedItem(CobblemonBlocks.BLACK_APRICORN_SAPLING, CobblemonBlocks.BLACK_APRICORN))
@JvmField
val WHITE_APRICORN_SEED = apricornSeedItem("white", ApricornSeedItem(CobblemonBlocks.WHITE_APRICORN_SAPLING))
val WHITE_APRICORN_SEED = apricornSeedItem("white", ApricornSeedItem(CobblemonBlocks.WHITE_APRICORN_SAPLING, CobblemonBlocks.WHITE_APRICORN))
@JvmField
val APRICORN_LOG = blockItem("apricorn_log", CobblemonBlocks.APRICORN_LOG)
@@ -224,11 +233,11 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
val APRICORN_HANGING_SIGN = this.create("apricorn_hanging_sign", HangingSignItem(CobblemonBlocks.APRICORN_HANGING_SIGN, CobblemonBlocks.APRICORN_WALL_HANGING_SIGN, Item.Settings().maxCount(16)))
@JvmField
val FOSSIL_TUBE = blockItem("fossil_tube", CobblemonBlocks.FOSSIL_TUBE)
val RESTORATION_TANK = blockItem("restoration_tank", CobblemonBlocks.RESTORATION_TANK)
@JvmField
val FOSSIL_COMPARTMENT = blockItem("fossil_compartment", CobblemonBlocks.FOSSIL_COMPARTMENT)
val FOSSIL_ANALYZER = blockItem("fossil_analyzer", CobblemonBlocks.FOSSIL_ANALYZER)
@JvmField
val FOSSIL_MONITOR = blockItem("fossil_monitor", CobblemonBlocks.FOSSIL_MONITOR)
val MONITOR = blockItem("monitor", CobblemonBlocks.MONITOR)
@JvmField
val HEALING_MACHINE = blockItem("healing_machine", CobblemonBlocks.HEALING_MACHINE)
@JvmField
@@ -568,6 +577,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
val DEEPSLATE_WATER_STONE_ORE = blockItem("deepslate_water_stone_ore", CobblemonBlocks.DEEPSLATE_WATER_STONE_ORE)
@JvmField
val DRIPSTONE_MOON_STONE_ORE = blockItem("dripstone_moon_stone_ore", CobblemonBlocks.DRIPSTONE_MOON_STONE_ORE)
@JvmField
val NETHER_FIRE_STONE_ORE = blockItem("nether_fire_stone_ore", CobblemonBlocks.NETHER_FIRE_STONE_ORE)
@JvmField
val DAWN_STONE = noSettingsItem("dawn_stone")
@JvmField
@@ -761,6 +773,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
val HELIX_SHERD = noSettingsItem("helix_sherd")
@JvmField
val NOSTALGIC_SHERD = noSettingsItem("nostalgic_sherd")
@JvmField
val SUSPICIOUS_SHERD = noSettingsItem("suspicious_sherd")
@JvmField
val TUMBLESTONE = this.create("tumblestone", TumblestoneItem(Item.Settings(), CobblemonBlocks.SMALL_BUDDING_TUMBLESTONE))
@@ -803,6 +817,12 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
@JvmField
val BLACK_TUMBLESTONE_BLOCK = blockItem("black_tumblestone_block", CobblemonBlocks.BLACK_TUMBLESTONE_BLOCK)
@JvmField
val AUTOMATON_ARMOR_TRIM_SMITHING_TEMPLATE: SmithingTemplateItem = this.create(
"automaton_armor_trim_smithing_template",
SmithingTemplateItem.of(CobblemonArmorTrims.AUTOMATON)
)
@JvmField
val POKEROD = pokerodItem("pokerod")
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.client.net.PlayerInteractOptionsHandler
import com.cobblemon.mod.common.client.net.SetClientPlayerDataHandler
import com.cobblemon.mod.common.client.net.animation.PlayPoseableAnimationHandler
import com.cobblemon.mod.common.client.net.battle.BattleApplyPassResponseHandler
import com.cobblemon.mod.common.client.net.battle.BattleCaptureEndHandler
import com.cobblemon.mod.common.client.net.battle.BattleCaptureShakeHandler
@@ -37,8 +38,10 @@ import com.cobblemon.mod.common.client.net.callback.party.OpenPartyCallbackHandl
import com.cobblemon.mod.common.client.net.callback.partymove.OpenPartyMoveCallbackHandler
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
import com.cobblemon.mod.common.client.net.data.UnlockReloadPacketHandler
import com.cobblemon.mod.common.client.net.effect.RunPosableMoLangHandler
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.SpawnSnowstormEntityParticleHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormParticleHandler
import com.cobblemon.mod.common.client.net.gui.InteractPokemonUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
@@ -48,7 +51,6 @@ import com.cobblemon.mod.common.client.net.pasture.PokemonPasturedHandler
import com.cobblemon.mod.common.client.net.pasture.PokemonUnpasturedHandler
import com.cobblemon.mod.common.client.net.pokemon.update.PokemonUpdatePacketHandler
import com.cobblemon.mod.common.client.net.settings.ServerSettingsPacketHandler
import com.cobblemon.mod.common.client.net.sound.PokemonCryHandler
import com.cobblemon.mod.common.client.net.sound.UnvalidatedPlaySoundS2CPacketHandler
import com.cobblemon.mod.common.client.net.spawn.SpawnExtraDataEntityHandler
import com.cobblemon.mod.common.client.net.starter.StarterUIPacketHandler
@@ -73,6 +75,7 @@ import com.cobblemon.mod.common.client.net.trade.TradeOfferNotificationHandler
import com.cobblemon.mod.common.client.net.trade.TradeStartedHandler
import com.cobblemon.mod.common.client.net.trade.TradeUpdatedHandler
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
import com.cobblemon.mod.common.net.messages.client.animation.PlayPoseableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.battle.*
import com.cobblemon.mod.common.net.messages.client.callback.OpenMoveCallbackPacket
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyCallbackPacket
@@ -85,6 +88,8 @@ import com.cobblemon.mod.common.net.messages.client.data.SpeciesRegistrySyncPack
import com.cobblemon.mod.common.net.messages.client.data.UnlockReloadPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.net.messages.client.fossil.FossilRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.fossil.NaturalMaterialRegistrySyncPacket
@@ -97,8 +102,8 @@ import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.Add
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.ClearEvolutionsPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.RemoveEvolutionPacket
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
import com.cobblemon.mod.common.net.messages.client.sound.PokemonCryPacket
import com.cobblemon.mod.common.net.messages.client.sound.UnvalidatedPlaySoundS2CPacket
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnGenericBedrockPacket
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnPokeballPacket
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnPokemonPacket
import com.cobblemon.mod.common.net.messages.client.starter.OpenStarterUIPacket
@@ -314,12 +319,15 @@ object CobblemonNetwork : NetworkManager {
// Effects
this.createClientBound(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler)
this.createClientBound(SpawnSnowstormEntityParticlePacket.ID, SpawnSnowstormEntityParticlePacket::decode, SpawnSnowstormEntityParticleHandler)
this.createClientBound(RunPosableMoLangPacket.ID, RunPosableMoLangPacket::decode, RunPosableMoLangHandler)
// Hax
this.createClientBound(UnvalidatedPlaySoundS2CPacket.ID, UnvalidatedPlaySoundS2CPacket::decode, UnvalidatedPlaySoundS2CPacketHandler)
this.createClientBound(SpawnPokemonPacket.ID, SpawnPokemonPacket::decode, SpawnExtraDataEntityHandler())
this.createClientBound(SpawnPokeballPacket.ID, SpawnPokeballPacket::decode, SpawnExtraDataEntityHandler())
this.createClientBound(ToastPacket.ID, ToastPacket::decode, ToastPacketHandler)
this.createClientBound(SpawnGenericBedrockPacket.ID, SpawnGenericBedrockPacket::decode, SpawnExtraDataEntityHandler())
// Trade packets
this.createClientBound(TradeAcceptanceChangedPacket.ID, TradeAcceptanceChangedPacket::decode, TradeAcceptanceChangedHandler)
@@ -337,7 +345,7 @@ object CobblemonNetwork : NetworkManager {
this.createClientBound(PokemonUnpasturedPacket.ID, PokemonUnpasturedPacket::decode, PokemonUnpasturedHandler)
// Behaviours
this.createClientBound(PokemonCryPacket.ID, PokemonCryPacket::decode, PokemonCryHandler)
this.createClientBound(PlayPoseableAnimationPacket.ID, PlayPoseableAnimationPacket::decode, PlayPoseableAnimationHandler)
// Move select packets
this.createClientBound(OpenMoveCallbackPacket.ID, OpenMoveCallbackPacket::decode, OpenMoveCallbackHandler)
@@ -55,6 +55,10 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, RegistryKey<Regi
val POKE_BALL_SEND_OUT = this.create("poke_ball.send_out")
@JvmField
val POKE_BALL_RECALL = this.create("poke_ball.recall")
@JvmField
val POKE_BALL_THROW = this.create("poke_ball.throw")
@JvmField
val POKE_BALL_TRAIL = this.create("poke_ball.trail")
@JvmField
val ITEM_USE = this.create("item.use")
@@ -62,6 +66,8 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, RegistryKey<Regi
val CAN_EVOLVE = this.create("pokemon.can_evolve")
@JvmField
val EVOLVING = this.create("pokemon.evolving")
@JvmField
val EVOLVE = this.create("evolution.evolve")
@JvmField
val PVN_BATTLE = this.create("battle.pvn.default")
@@ -123,6 +129,43 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, RegistryKey<Regi
@JvmField
val TUMBLESTONE_STEP = this.create("tumblestone.step")
@JvmField
val IMPACT_NORMAL = this.create("impact.normal")
@JvmField
val IMPACT_BUG = this.create("impact.bug")
@JvmField
val IMPACT_DARK = this.create("impact.dark")
@JvmField
val IMPACT_DRAGON = this.create("impact.dragon")
@JvmField
val IMPACT_ELECTRIC = this.create("impact.electric")
@JvmField
val IMPACT_FAIRY = this.create("impact.fairy")
@JvmField
val IMPACT_FIGHTING = this.create("impact.fighting")
@JvmField
val IMPACT_FIRE = this.create("impact.fire")
@JvmField
val IMPACT_FLYING = this.create("impact.flying")
@JvmField
val IMPACT_GHOST = this.create("impact.ghost")
@JvmField
val IMPACT_GRASS = this.create("impact.grass")
@JvmField
val IMPACT_GROUND = this.create("impact.ground")
@JvmField
val IMPACT_ICE = this.create("impact.ice")
@JvmField
val IMPACT_POISON = this.create("impact.poison")
@JvmField
val IMPACT_PSYCHIC = this.create("impact.psychic")
@JvmField
val IMPACT_ROCK = this.create("impact.rock")
@JvmField
val IMPACT_STEEL = this.create("impact.steel")
@JvmField
val IMPACT_WATER = this.create("impact.water")
@JvmField
val TUMBLESTONE_SOUNDS = BlockSoundGroup(1f, 1f,
TUMBLESTONE_BREAK,
@@ -8,9 +8,13 @@
package com.cobblemon.mod.common.api.battles.model
import com.bedrockk.molang.runtime.MoLangEnvironment
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.Environment
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
@@ -18,6 +22,8 @@ import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
import com.cobblemon.mod.common.api.battles.model.actor.FleeableBattleActor
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunction
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.api.text.red
@@ -464,4 +470,13 @@ open class PokemonBattle(
LOGGER.error("Missing interpretation on '{}' action: \nPublic » {}\nPrivate » {}", publicMessage.id, publicMessage.rawMessage, privateMessage.rawMessage)
return Text.literal("Missing interpretation on '${publicMessage.id}' action please report to the developers").red()
}
fun addQueryFunctions(queryStruct: QueryStruct): QueryStruct {
queryStruct.addFunction("pvp") { DoubleValue(isPvP) }
queryStruct.addFunction("pvn") { DoubleValue(isPvN) }
queryStruct.addFunction("pvw") { DoubleValue(isPvW) }
queryStruct.addFunction("has_rule") { params -> DoubleValue(params.getString(0) in format.ruleSet) }
return queryStruct
}
}
@@ -33,6 +33,7 @@ import com.cobblemon.mod.common.api.events.pokemon.evolution.EvolutionCompleteEv
import com.cobblemon.mod.common.api.events.pokemon.evolution.EvolutionDisplayEvent
import com.cobblemon.mod.common.api.events.pokemon.evolution.EvolutionTestedEvent
import com.cobblemon.mod.common.api.events.pokemon.interaction.ExperienceCandyUseEvent
import com.cobblemon.mod.common.api.events.pokemon.interaction.PokemonInteractionGUICreationEvent
import com.cobblemon.mod.common.api.events.starter.StarterChosenEvent
import com.cobblemon.mod.common.api.events.storage.ReleasePokemonEvent
import com.cobblemon.mod.common.api.events.world.BigRootPropagatedEvent
@@ -100,6 +101,9 @@ object CobblemonEvents {
@JvmField
val LEVEL_UP_EVENT = EventObservable<LevelUpEvent>()
@JvmField
/** CLIENT ONLY! */
val POKEMON_INTERACTION_GUI_CREATION = EventObservable<PokemonInteractionGUICreationEvent>()
@JvmField
val POKEMON_ENTITY_SAVE = EventObservable<PokemonEntitySaveEvent>()
@JvmField
@@ -153,4 +157,8 @@ object CobblemonEvents {
val LEFTOVERS_CREATED = CancelableObservable<LeftoversCreatedEvent>()
@JvmField
val BIG_ROOT_PROPAGATED = CancelableObservable<BigRootPropagatedEvent>()
@JvmField
val HELD_ITEM_PRE = CancelableObservable<HeldItemEvent.Pre>()
@JvmField
val HELD_ITEM_POST = EventObservable<HeldItemEvent.Post>()
}
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.pokemon
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.item.ItemStack
/**
* The base for all the events related to held items.
*
* @see [Pre]
* @see [Post]
*/
interface HeldItemEvent {
/**
* The [Pokemon] triggering this event.
*/
val pokemon: Pokemon
/**
* Fired at the start of [Pokemon.swapHeldItem].
*
* This event should be used to mutate the results of this transaction.
*
* Canceling this event will prevent the operation and causes the return to be item attempted to be given originally.
*
* @property pokemon The [Pokemon] triggering this event.
* @property receiving The [ItemStack] being sent received from the transaction. By default, this is the item that triggered the interaction.
* @property returning The [ItemStack] being sent back from the transaction. By default, this is the currently held item.
* @property decrement If the operation should decrement the [receiving] [ItemStack.count], this is handled in the implementation.
*
* @see [Post]
*/
data class Pre(override val pokemon: Pokemon, var receiving: ItemStack, var returning: ItemStack, var decrement: Boolean) : HeldItemEvent, Cancelable()
/**
* Fired at the end of [Pokemon.swapHeldItem].
*
* @property pokemon The [Pokemon] triggering this event.
* @property received The [ItemStack] considered as received and set to the [pokemon]. This is a copy and mutation will not be taken into account.
* @property returned The [ItemStack] returned by the method call. This is a copy and mutation will not be taken into account.
* @property decremented If the [received] [ItemStack] got decremented.
*
* @see [Pre]
*/
data class Post(override val pokemon: Pokemon, val received: ItemStack, val returned: ItemStack, val decremented: Boolean) : HeldItemEvent
}
@@ -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.events.pokemon.interaction
import com.cobblemon.mod.common.client.gui.interact.wheel.InteractWheelOption
import com.cobblemon.mod.common.client.gui.interact.wheel.Orientation
import com.google.common.collect.Multimap
import java.util.UUID
data class PokemonInteractionGUICreationEvent(
val pokemonID: UUID,
val mountShoulder: Boolean,
val options: Multimap<Orientation, InteractWheelOption>
) {
fun addFillingOption(option: InteractWheelOption) {
options.put(getNextFreeOrientation(), option)
}
fun addOption(orientation: Orientation, option: InteractWheelOption) {
options.put(orientation, option)
}
private fun getNextFreeOrientation(): Orientation {
var largest = Orientation.TOP_LEFT
for(orientation in Orientation.values()) {
if(!options.containsKey(orientation)) {
return orientation
} else {
if(options.get(orientation).size < options.get(largest).size) {
largest = orientation
}
}
}
return largest
}
}
@@ -19,7 +19,8 @@ import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.dialogue.PlayerDialogueFaceProvider
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunction
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunctions
import com.cobblemon.mod.common.util.isInt
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.block.Block
@@ -51,7 +52,30 @@ object MoLangFunctions {
"is_number" to java.util.function.Function { params -> DoubleValue(params.get<MoValue>(0).asString().toDoubleOrNull() != null) },
"to_number" to java.util.function.Function { params -> DoubleValue(params.get<MoValue>(0).asString().toDoubleOrNull() ?: 0.0) },
"to_int" to java.util.function.Function { params -> DoubleValue(params.get<MoValue>(0).asString().toIntOrNull() ?: 0) },
"to_string" to java.util.function.Function { params -> StringValue(params.get<MoValue>(0).asString()) }
"to_string" to java.util.function.Function { params -> StringValue(params.get<MoValue>(0).asString()) },
"do_effect_walks" to java.util.function.Function { _ ->
DoubleValue(Cobblemon.config.walkingInBattleAnimations)
},
"random" to java.util.function.Function { params ->
val options = mutableListOf<MoValue>()
var index = 0
while (params.contains(index)) {
options.add(params.get(index))
index++
}
return@Function options.random() // Can throw an exception if they specified no args. They'd be idiots though.
},
"curve" to java.util.function.Function { params ->
val curveName = params.getString(0)
val curve = WaveFunctions.functions[curveName] ?: throw IllegalArgumentException("Unknown curve: $curveName")
return@Function ObjectValue(curve)
},
"array" to java.util.function.Function { params ->
val values = params.params
val array = ArrayStruct(hashMapOf())
values.forEachIndexed { index, moValue -> array.setDirectly("$index", moValue) }
return@Function array
}
)
val biomeFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
val worldFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
@@ -110,6 +134,11 @@ object MoLangFunctions {
return this
}
fun <T : QueryStruct> T.addFunction(name: String, function: java.util.function.Function<MoParams, Any>): T {
this.functions[name] = function
return this
}
fun MoLangEnvironment.getQueryStruct(name: String = "query") = structs.getOrPut(name) { QueryStruct(hashMapOf()) } as QueryStruct
fun MoLangRuntime.setup(): MoLangRuntime {
@@ -8,6 +8,12 @@
package com.cobblemon.mod.common.api.moves
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.molang.MoLangFunctions.addFunction
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
@@ -45,8 +51,22 @@ open class MoveTemplate(
val pp: Int,
val priority: Int,
val critRatio: Double,
val effectChances: Array<Double>
val effectChances: Array<Double>,
val actionEffect: ActionEffectTimeline?
) {
val struct: MoStruct by lazy {
QueryStruct(hashMapOf())
.addFunction("name") { StringValue(name) }
.addFunction("type") { StringValue(elementalType.name) }
.addFunction("damage_category") { StringValue(damageCategory.name) }
.addFunction("power") { DoubleValue(power) }
.addFunction("target") { StringValue(target.name) }
.addFunction("accuracy") { DoubleValue(accuracy) }
.addFunction("pp") { DoubleValue(pp) }
.addFunction("priority") { DoubleValue(priority) }
.addFunction("crit_ratio") { DoubleValue(critRatio) }
}
val displayName: MutableText
get() = lang("move.$name")
@@ -65,7 +85,8 @@ open class MoveTemplate(
pp = 5,
priority = 0,
critRatio = 0.0,
effectChances = emptyArray()
effectChances = emptyArray(),
actionEffect = null
)
companion object {
@@ -10,12 +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.ActionEffectTimeline
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.google.gson.JsonArray
import com.google.gson.JsonObject
@@ -72,7 +75,18 @@ object Moves : DataRegistry {
effectChances += secondaryMember.get("chance").asDouble
}
}
val move = MoveTemplate(id, num, elementalType, damageCategory, power, target, accuracy, pp, priority, critRatio, effectChances.toTypedArray())
val actionEffect = ActionEffects.actionEffects[id.asIdentifierDefaultingNamespace()]
?: run {
ActionEffects.actionEffects["generic_move".asIdentifierDefaultingNamespace()]
// if (damageCategory == DamageCategories.STATUS) {
// ActionEffects.actionEffects[cobblemonResource("status")]
// } else {
// val type = elementalType.name.lowercase()
// val category = damageCategory.name.lowercase()
// ActionEffects.actionEffects["${category}_$type".asIdentifierDefaultingNamespace()]
// }
}
val move = MoveTemplate(id, num, elementalType, damageCategory, power, target, accuracy, pp, priority, critRatio, effectChances.toTypedArray(), actionEffect)
this.register(move)
} catch (e: Exception) {
Cobblemon.LOGGER.error("Caught exception trying to resolve the move '{}'", id, e)
@@ -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.moves.animations
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.struct.VariableStruct
import com.cobblemon.mod.common.api.moves.animations.keyframes.ActionEffectKeyframe
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolveBoolean
import java.util.concurrent.CompletableFuture
import net.minecraft.entity.Entity
/**
* An action effect will run and execute a series of 'keyframes', with each running once
* the previous has completed. The entire effect is considered complete for the given context
* once all the 'holds' provided in the context have been cleared or once every keyframe
* has finished.
*
* @author Hiroku
* @since October 20th, 2023
*/
class ActionEffectTimeline(
val timeline: List<ActionEffectKeyframe> = mutableListOf(),
val condition: Expression = "true".asExpression()
) {
companion object {
val NONE = ActionEffectTimeline()
}
fun run(context: ActionEffectContext): CompletableFuture<Unit> {
return if (timeline.isEmpty() || !context.runtime.resolveBoolean(condition)) {
CompletableFuture.completedFuture(Unit)
} else {
val finalFuture = CompletableFuture<Unit>()
// .toList copy because I'm paranoid about iterators being trying to share between identical effects playing
chainKeyframes(context, timeline.toList().iterator(), finalFuture)
finalFuture
}
}
fun chainKeyframes(context: ActionEffectContext, iterator: Iterator<ActionEffectKeyframe>, finalFuture: CompletableFuture<Unit>) {
if (!iterator.hasNext()) {
finalFuture.complete(Unit)
} else {
val keyframe = iterator.next()
context.currentKeyframes.add(keyframe)
keyframe.play(context)
.thenRun { context.currentKeyframes.remove(keyframe) }
.thenApply { chainKeyframes(context, iterator, finalFuture) }
}
}
}
class ActionEffectContext(
val actionEffect: ActionEffectTimeline,
val holds: MutableSet<String> = mutableSetOf(),
val providers: MutableList<Any> = mutableListOf(),
val runtime: MoLangRuntime,
var canBeInterrupted: Boolean = false,
var interrupted: Boolean = false,
var currentKeyframes: MutableList<ActionEffectKeyframe> = mutableListOf()
) {
inline fun <reified T> findOneProvider() = providers.filterIsInstance<T>().firstOrNull()
}
class UsersProvider(users: List<Entity>): EntityProvider {
override val entities = users
constructor(vararg users: Entity): this(users.toList())
}
class TargetsProvider(targets: List<Entity>): EntityProvider {
override val entities = targets
constructor(vararg targets: Entity): this(targets.toList())
}
interface EntityProvider {
val entities: List<Entity>
}
//class MoveAnimationKeyframe(
// val sound: Identifier? = null,
// val animation: List<AnimationWithMoments> = listOf(),
// val effects: List<ActionEffect> = listOf()
//)
@@ -0,0 +1,95 @@
/*
* 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.animations
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.animations.keyframes.*
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.pokemon.adapters.CobblemonStatTypeAdapter
import com.cobblemon.mod.common.util.adapters.*
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import java.awt.Color
import net.minecraft.predicate.NumberRange
import net.minecraft.resource.ResourceType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.minecraft.util.math.Box
import net.minecraft.util.math.Vec3d
/**
* Data registry containing all of the [ActionEffectTimeline]s that can be triggered from various actions.
*
* @author Hiroku
* @since October 21st, 2023
*/
object ActionEffects : JsonDataRegistry<ActionEffectTimeline> {
override val id: Identifier = cobblemonResource("action_effects")
override val type: ResourceType = ResourceType.SERVER_DATA
override val observable = SimpleObservable<ActionEffects>()
init {
ActionEffectKeyframe.register<AnimationActionEffectKeyframe>("animation")
ActionEffectKeyframe.register<EntityMoLangActionEffectKeyframe>("entity_molang")
ActionEffectKeyframe.register<MoLangActionEffectKeyframe>("molang")
ActionEffectKeyframe.register<ParallelActionEffectKeyframe>("parallel")
ActionEffectKeyframe.register<CanInterruptActionEffectKeyframe>("can_interrupt")
ActionEffectKeyframe.register<CannotInterruptActionEffectKeyframe>("cannot_interrupt")
ActionEffectKeyframe.register<RemoveHoldsActionEffectKeyframe>("remove_holds")
ActionEffectKeyframe.register<AddHoldsActionEffectKeyframe>("add_holds")
ActionEffectKeyframe.register<MoveToTargetActionEffectKeyframe>("move_to_target")
ActionEffectKeyframe.register<ReturnToPositionActionEffectKeyframe>("return_to_position")
ActionEffectKeyframe.register<PauseActionEffectKeyframe>("pause")
ActionEffectKeyframe.register<SavePositionActionEffectKeyframe>("save_position")
ActionEffectKeyframe.register<ForkActionEffectKeyframe>("fork")
ActionEffectKeyframe.register<SequenceActionEffectKeyframe>("sequence")
ActionEffectKeyframe.register<RunActionEffectKeyframe>("run_action_effect")
ActionEffectKeyframe.register<EntityParticlesActionEffectKeyframe>("entity_particles")
ActionEffectKeyframe.register<EntitySoundActionEffectKeyframe>("entity_sound")
}
override val gson = GsonBuilder()
.disableHtmlEscaping()
.setPrettyPrinting()
.registerTypeAdapter(ActionEffectKeyframe::class.java, ActionEffectKeyframeAdapter)
.registerTypeAdapter(NumberRange.FloatRange::class.java, FloatNumberRangeAdapter)
.registerTypeAdapter(TypeToken.getParameterized(Collection::class.java, Box::class.java).type, BoxCollectionAdapter)
.registerTypeAdapter(Box::class.java, BoxAdapter)
.registerTypeAdapter(Vec3d::class.java, VerboseVec3dAdapter)
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
.registerTypeAdapter(IntRange::class.java, VerboseIntRangeAdapter)
.registerTypeAdapter(Color::class.java, LiteralHexColorAdapter)
.registerTypeAdapter(Stat::class.java, CobblemonStatTypeAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(
TypeToken.getParameterized(
TypeToken.get(List::class.java).type,
TypeToken.get(ActionEffectKeyframe::class.java).type
).type,
SingleToPluralAdapter(ActionEffectKeyframe::class.java) { it }
)
.create()
override val typeToken: TypeToken<ActionEffectTimeline> = TypeToken.get(ActionEffectTimeline::class.java)
override val resourcePath = "action_effects"
val actionEffects = mutableMapOf<Identifier, ActionEffectTimeline>()
override fun reload(data: Map<Identifier, ActionEffectTimeline>) {
actionEffects.clear()
actionEffects.putAll(data)
observable.emit(this)
}
override fun sync(player: ServerPlayerEntity) {}
}
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.moves.animations.keyframes
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import java.util.concurrent.CompletableFuture
/**
* A single component of an action effect. When played it must return a completable future that completes
* when the action this keyframe represents is finished. [interrupt] is provided for when something successfully
* interrupts the effect so that if any cancellation logic is necessary, it can be provided.
*
* @author Hiroku
* @since October 26th, 2023
*/
interface ActionEffectKeyframe {
companion object {
val types = mutableMapOf<String, Class<out ActionEffectKeyframe>>()
inline fun <reified T : ActionEffectKeyframe> register(type: String) {
types[type] = T::class.java
}
}
fun play(context: ActionEffectContext): CompletableFuture<Unit>
fun interrupt(context: ActionEffectContext) {}
fun skip(): CompletableFuture<Unit> = CompletableFuture.completedFuture(Unit)
}
@@ -0,0 +1,27 @@
/*
* 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.animations.keyframes
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import java.util.concurrent.CompletableFuture
/**
* An [ActionEffectKeyframe] that adds some holds from the context for future code to recognize as blocking some
* functionality from proceeding.
*
* @author Hiroku
* @since October 27th, 2023
*/
class AddHoldsActionEffectKeyframe : ActionEffectKeyframe {
val holds = mutableSetOf<String>()
override fun play(context: ActionEffectContext): CompletableFuture<Unit> {
context.holds.addAll(holds)
return CompletableFuture.completedFuture(Unit)
}
}
@@ -0,0 +1,54 @@
/*
* 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.animations.keyframes
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.EntityProvider
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.scheduling.delayedFuture
import com.cobblemon.mod.common.net.messages.client.animation.PlayPoseableAnimationPacket
import com.cobblemon.mod.common.util.asExpressionLike
import java.util.concurrent.CompletableFuture
import net.minecraft.server.world.ServerWorld
class AnimationActionEffectKeyframe : ConditionalActionEffectKeyframe(), EntityConditionalActionEffectKeyframe {
var delay = "0".asExpressionLike()
var visibilityRange = 200
var animation: Set<String> = setOf("physical")
var variables: List<Expression> = listOf()
override val entityCondition = "q.entity.is_user".asExpressionLike()
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
val entities = context.providers
.filterIsInstance<EntityProvider>()
.flatMap { prov -> prov.entities.filter { test(context, it, isUser = prov is UsersProvider) } }
val expressions = variables.map { it.originalString }.toSet()
// Treat them as expressions if possible but otherwise yeah just send them as strings
val animation = animation.map {
try {
it.asExpressionLike().resolveString(context.runtime).takeIf { it != "0.0" } ?: it
} catch (e: Exception) {
it
}
}.toSet()
for (entity in entities) {
val world = entity.world as ServerWorld
val players = world.getPlayers { it.distanceTo(entity) <= visibilityRange }
val pkt = PlayPoseableAnimationPacket(entity.id, animation = animation, expressions = expressions)
players.forEach { it.sendPacket(pkt) }
}
return delayedFuture(seconds = delay.resolveFloat(context.runtime), serverThread = true)
}
}
@@ -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.moves.animations.keyframes
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import java.util.concurrent.CompletableFuture
class CanInterruptActionEffectKeyframe : ConditionalActionEffectKeyframe() {
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
context.canBeInterrupted = true
return CompletableFuture.completedFuture(Unit)
}
}
@@ -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.moves.animations.keyframes
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import java.util.concurrent.CompletableFuture
class CannotInterruptActionEffectKeyframe : ConditionalActionEffectKeyframe() {
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
context.canBeInterrupted = true
return CompletableFuture.completedFuture(Unit)
}
}
@@ -0,0 +1,27 @@
/*
* 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.animations.keyframes
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.util.asExpressionLike
import java.util.concurrent.CompletableFuture
abstract class ConditionalActionEffectKeyframe : ActionEffectKeyframe {
var condition: ExpressionLike = "true".asExpressionLike()
override fun play(context: ActionEffectContext): CompletableFuture<Unit> {
return if (condition.resolveBoolean(context.runtime)) {
playWhenTrue(context)
} else {
skip()
}
}
abstract fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit>
}
@@ -0,0 +1,36 @@
/*
* 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.animations.keyframes
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.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import net.minecraft.entity.Entity
/**
* An action effect keyframe that plays for all entities for which the condition is true.
*
* @author Hiroku
* @since January 21st, 2024
*/
interface EntityConditionalActionEffectKeyframe {
val entityCondition: ExpressionLike
fun test(context: ActionEffectContext, entity: Entity, isUser: Boolean): Boolean {
// TODO this should use the entity's own query struct
context.runtime.environment.getQueryStruct().addFunction("entity") {
QueryStruct(hashMapOf())
.addFunction("uuid") { StringValue(entity.uuidAsString) }
.addFunction("is_user") { DoubleValue(isUser) }
}
return entityCondition.resolveBoolean(context.runtime)
}
}
@@ -0,0 +1,51 @@
/*
* 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.animations.keyframes
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.EntityProvider
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.scheduling.delayedFuture
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.util.asExpressionLike
import java.util.concurrent.CompletableFuture
import net.minecraft.server.world.ServerWorld
/**
* An action effect keyframe that will run client-side MoLang for all entities that fit the entity condition.
*
* This MoLang executes within that entity's runtime, meaning you can trigger animations or locational particles
* or sounds.
*
* @author Hiroku
* @since October 30th, 2023
*/
class EntityMoLangActionEffectKeyframe : ConditionalActionEffectKeyframe(), EntityConditionalActionEffectKeyframe {
var delay = "0".asExpressionLike()
val expressions = mutableSetOf<String>()
val visibilityRange = 200
override val entityCondition: ExpressionLike = "q.entity.is_user".asExpressionLike()
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
val entities = context.providers
.filterIsInstance<EntityProvider>()
.flatMap { prov -> prov.entities.filter { test(context, it, isUser = prov is UsersProvider) } }
for (entity in entities) {
val world = entity.world as ServerWorld
val players = world.getPlayers { it.distanceTo(entity) <= visibilityRange }
val pkt = RunPosableMoLangPacket(entityId = entity.id, expressions = expressions)
players.forEach { it.sendPacket(pkt) }
}
return delayedFuture(seconds = delay.resolveFloat(context.runtime), serverThread = true)
}
}
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.moves.animations.keyframes
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.EntityProvider
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.scheduling.delayedFuture
import com.cobblemon.mod.common.entity.Poseable
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import java.util.concurrent.CompletableFuture
import net.minecraft.server.world.ServerWorld
/**
* Spawns particles on the entity based on the given particle effect and locator.
*
* @author Hiroku
* @since January 21st, 2024
*/
class EntityParticlesActionEffectKeyframe : ConditionalActionEffectKeyframe(), EntityConditionalActionEffectKeyframe {
override val entityCondition = "q.entity.is_user".asExpressionLike()
var effect: String? = null
var locator: String = "root"
val delay: ExpressionLike = "0".asExpressionLike()
val visibilityRange = 200
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
val entities = context.providers
.filterIsInstance<EntityProvider>()
.flatMap { prov -> prov.entities.filter { test(context, it, isUser = prov is UsersProvider) } }
val effectIdentifier = try {
effect?.asExpressionLike()?.resolveString(context.runtime)
} catch (e: Exception) {
effect
}?.asIdentifierDefaultingNamespace() ?: return skip()
entities.filter { it is Poseable }.forEach { entity ->
val packet = SpawnSnowstormEntityParticlePacket(effectIdentifier, entity.id, locator)
val players = (entity.world as ServerWorld).getPlayers { it.distanceTo(entity) <= visibilityRange }
packet.sendToPlayers(players)
}
return delayedFuture(seconds = delay.resolveFloat(context.runtime), serverThread = true)
}
}
@@ -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.moves.animations.keyframes
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.EntityProvider
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.scheduling.delayedFuture
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import java.util.concurrent.CompletableFuture
import net.minecraft.registry.RegistryKeys
/**
* An action effect keyframe that plays a positioned sound for all matching entities.
*
* @author Hiroku
* @since January 21st, 2024
*/
class EntitySoundActionEffectKeyframe : ConditionalActionEffectKeyframe(), EntityConditionalActionEffectKeyframe {
override val entityCondition = "q.entity.is_user".asExpressionLike()
var sound: String? = null
val delay: ExpressionLike = "0".asExpressionLike()
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
val entities = context.providers
.filterIsInstance<EntityProvider>()
.flatMap { prov -> prov.entities.filter { test(context, it, isUser = prov is UsersProvider) } }
val soundIdentifier = try {
sound?.asExpressionLike()?.resolveString(context.runtime)?.takeIf { it != "0.0" } ?: sound
} catch (e: Exception) {
sound
}?.asIdentifierDefaultingNamespace() ?: return skip()
entities.forEach { entity ->
val soundEvent = entity.world.registryManager.get(RegistryKeys.SOUND_EVENT).get(soundIdentifier) ?: return skip()
entity.playSound(soundEvent, 1f, 1f)
}
return delayedFuture(seconds = delay.resolveFloat(context.runtime), serverThread = true)
}
}
@@ -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.moves.animations.keyframes
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.util.asExpressionLike
import java.util.concurrent.CompletableFuture
class ForkActionEffectKeyframe(
val condition: ExpressionLike = "true".asExpressionLike(),
val ifTrue: List<ActionEffectKeyframe> = listOf(),
val ifFalse: List<ActionEffectKeyframe> = listOf()
) : ActionEffectKeyframe {
override fun play(context: ActionEffectContext): CompletableFuture<Unit> {
val future = CompletableFuture<Unit>()
/*
* Using .toList() before running chainKeyframes because iterators can be weird about sharing,
* .toList() creates a copy of the list and then grabs its iterator so much safer (three parachutes)
*/
if (!condition.resolveBoolean(context.runtime)) {
context.actionEffect.chainKeyframes(context, ifFalse.toList().iterator(), future)
} else {
context.actionEffect.chainKeyframes(context, ifTrue.toList().iterator(), future)
}
return future
}
}
@@ -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.moves.animations.keyframes
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.scheduling.delayedFuture
import com.cobblemon.mod.common.util.asExpressionLike
import java.util.concurrent.CompletableFuture
/**
* An action effect keyframe that simply executes some MoLang on the effect context's runtime.
*
* @author Hiroku
* @since January 21st, 2024
*/
class MoLangActionEffectKeyframe : ActionEffectKeyframe {
val expressions: ExpressionLike = "0".asExpressionLike()
val delay: ExpressionLike = "0".asExpressionLike()
override fun play(context: ActionEffectContext): CompletableFuture<Unit> {
expressions.resolve(context.runtime)
return delayedFuture(seconds = delay.resolveFloat(context.runtime), serverThread = true)
}
}
@@ -0,0 +1,75 @@
/*
* 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.animations.keyframes
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.TargetsProvider
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.scheduling.afterOnServer
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonNavigation
import com.cobblemon.mod.common.util.asExpressionLike
import java.util.concurrent.CompletableFuture
import kotlin.math.pow
import kotlin.math.sqrt
import net.minecraft.util.Identifier
class MoveToTargetActionEffectKeyframe : ActionEffectKeyframe {
val speed = 1F
val timeout = "4".asExpressionLike()
var proximity = -1F
val timeoutActionEffect: Identifier? = null
override fun play(context: ActionEffectContext): CompletableFuture<Unit> {
val user = context.findOneProvider<UsersProvider>()?.entities?.firstOrNull() as? PokemonEntity ?: return CompletableFuture.completedFuture(Unit)
val target = context.findOneProvider<TargetsProvider>()?.entities?.firstOrNull() ?: return CompletableFuture.completedFuture(Unit)
val future = CompletableFuture<Unit>()
var timedOut = false
val proximity = this.proximity.takeIf { it != -1F } ?: (sqrt(2 * user.boundingBox.xLength.pow(2)) + 1.5F + sqrt(2 * target.boundingBox.xLength.pow(2))).toFloat()
if (target.distanceTo(user) !in proximity..20F) {
future.complete(Unit)
return future
}
val timeoutEffect = timeoutActionEffect?.let { ActionEffects.actionEffects[it] } ?: ActionEffectTimeline.NONE
val nav = user.navigation
val navContext = PokemonNavigation.NavigationContext(
destinationProximity = proximity,
onArrival = {
if (!future.isDone && !timedOut) {
future.complete(Unit)
}
},
onCannotReach = {
if (!future.isDone && !timedOut) {
timedOut = true
timeoutEffect.run(context).thenApply { future.complete(Unit) }
}
}
)
afterOnServer(seconds = timeout.resolveFloat(context.runtime)) {
if (!future.isDone && !timedOut) {
timedOut = true
timeoutEffect.run(context).thenApply { future.complete(Unit) }
nav.stop()
}
}
nav.startMovingTo(target.x, target.y, target.z, speed.toDouble(), navContext)
return future
}
}
@@ -0,0 +1,25 @@
/*
* 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.animations.keyframes
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import java.util.concurrent.CompletableFuture
class ParallelActionEffectKeyframe : ConditionalActionEffectKeyframe() {
var keyframes: MutableList<ActionEffectKeyframe> = mutableListOf()
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
return CompletableFuture.allOf(
*keyframes.map {
context.currentKeyframes.add(it)
it.play(context).thenRun { context.currentKeyframes.remove(it) }
}.toTypedArray()
).thenApply {} // Map Void -> Unit at the end
}
}
@@ -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.moves.animations.keyframes
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.scheduling.delayedFuture
import com.cobblemon.mod.common.util.asExpressionLike
class PauseActionEffectKeyframe : ConditionalActionEffectKeyframe() {
val pause = "1".asExpressionLike()
override fun playWhenTrue(context: ActionEffectContext) = delayedFuture(seconds = pause.resolveFloat(context.runtime), serverThread = true)
}
@@ -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.moves.animations.keyframes
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.scheduling.delayedFuture
import java.util.concurrent.CompletableFuture
/**
* An [ActionEffectKeyframe] that clears some holds from the context, potentially marking the effect as
* complete as far as any blocked actions are concerned. If no holds are specified, then all holds are
* removed.
*
* @author Hiroku
* @since October 27th, 2023
*/
class RemoveHoldsActionEffectKeyframe : ActionEffectKeyframe {
val delay = 0F
val holds = mutableSetOf<String>()
override fun play(context: ActionEffectContext): CompletableFuture<Unit> {
return delayedFuture(seconds = delay, serverThread = true).thenApply {
if (holds.isEmpty()) {
context.holds.clear()
} else {
context.holds.removeAll(holds)
}
}
}
}
@@ -0,0 +1,70 @@
/*
* 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.animations.keyframes
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.scheduling.after
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonNavigation
import com.cobblemon.mod.common.util.asExpressionLike
import java.util.concurrent.CompletableFuture
import net.minecraft.util.Identifier
import net.minecraft.util.math.Vec3d
class ReturnToPositionActionEffectKeyframe : ActionEffectKeyframe {
val speed = 1F
val timeout = "4".asExpressionLike()
val timeoutActionEffect: Identifier? = null
override fun play(context: ActionEffectContext): CompletableFuture<Unit> {
val user = context.findOneProvider<UsersProvider>()?.entities?.firstOrNull() as? PokemonEntity ?: return skip()
val future = CompletableFuture<Unit>()
val pos = (context.runtime.environment.getValue(setOf("${user.uuidAsString}-pos").iterator())?.value() as? ObjectValue<*>)?.obj as? Vec3d ?: return skip()
var timedOut = false
if (pos.distanceTo(user.pos) > 20) {
future.complete(Unit)
return future
}
val timeoutEffect = timeoutActionEffect?.let { ActionEffects.actionEffects[it] } ?: ActionEffectTimeline.NONE
val nav = user.navigation
val navContext = PokemonNavigation.NavigationContext(
onArrival = {
if (!future.isDone && !timedOut) {
future.complete(Unit)
}
},
onCannotReach = {
if (!future.isDone && !timedOut) {
timedOut = true
timeoutEffect.run(context).thenApply { future.complete(Unit) }
}
}
)
after(seconds = timeout.resolveFloat(context.runtime), serverThread = true) {
if (!future.isDone && !timedOut) {
timedOut = true
timeoutEffect.run(context).thenApply { future.complete(Unit) }
nav.stop()
}
}
nav.startMovingTo(pos.x, pos.y, pos.z, speed.toDouble(), navContext)
return future
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.moves.animations.keyframes
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import java.util.concurrent.CompletableFuture
import net.minecraft.util.Identifier
/**
* An action effect keyframe that simply runs another action effect based on an identifier. This action
* effect is marked complete only when the action effect it runs is complete unless [waitForActionEffect] is
* false.
*
* @author Hiroku
* @since January 21st, 2024
*/
class RunActionEffectKeyframe : ConditionalActionEffectKeyframe() {
val actionEffect: Identifier? = null
val waitForActionEffect = true
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
return if (actionEffect != null) {
ActionEffects.actionEffects[actionEffect]?.run(context)?.takeIf { waitForActionEffect } ?: skip()
} else {
skip()
}
}
}
@@ -0,0 +1,22 @@
/*
* 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.animations.keyframes
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import java.util.concurrent.CompletableFuture
class SavePositionActionEffectKeyframe: ConditionalActionEffectKeyframe() {
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
val user = context.findOneProvider<UsersProvider>()?.entities?.firstOrNull() ?: return skip()
context.runtime.environment.setSimpleVariable("${user.uuidAsString}-pos", ObjectValue(user.pos))
return skip()
}
}
@@ -0,0 +1,26 @@
/*
* 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.animations.keyframes
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import java.util.concurrent.CompletableFuture
/**
* A sequence of action effects that are dependent on a condition.
*
* @author Hiroku
* @since October 29th, 2023
*/
class SequenceActionEffectKeyframe(val keyframes: List<ActionEffectKeyframe> = listOf()) : ConditionalActionEffectKeyframe() {
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
val future = CompletableFuture<Unit>()
context.actionEffect.chainKeyframes(context, keyframes.toList().iterator(), future)
return future
}
}
@@ -8,7 +8,7 @@
package com.cobblemon.mod.common.api.multiblock
import com.cobblemon.mod.common.block.entity.fossil.FossilMultiblockEntity
import com.cobblemon.mod.common.block.entity.FossilMultiblockEntity
import net.minecraft.block.*
import net.minecraft.block.entity.BlockEntity
import net.minecraft.entity.LivingEntity
@@ -42,5 +42,7 @@ interface MultiblockStructure {
fun getComparatorOutput(state: BlockState, world: World?, pos: BlockPos?): Int {
return 0
}
fun markRemoved(world: World):Unit
fun onTriggerEvent(state: BlockState?, world: ServerWorld?, pos: BlockPos?, random: Random?)
}
@@ -0,0 +1,28 @@
/*
* 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.net.serializers
import net.minecraft.entity.data.TrackedDataHandler
import net.minecraft.network.PacketByteBuf
import net.minecraft.util.Identifier
/**
* Data serializer of [Identifier] for DataTracker things.
*
* @author Hiroku
* @since May 22nd, 2023
*/
object IdentifierDataSerializer : TrackedDataHandler<Identifier> {
override fun copy(value: Identifier) = Identifier(value.namespace, value.path)
override fun read(buf: PacketByteBuf) = Identifier(buf.readString(), buf.readString())
override fun write(buf: PacketByteBuf, value: Identifier) {
buf.writeString(value.namespace)
buf.writeString(value.path)
}
}
@@ -204,7 +204,7 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
createDefault("ancient_feather_ball", throwPower = 2.5f, ancient = true)
createDefault("ancient_wing_ball", throwPower = 2.5f, ancient = true)
createDefault("ancient_jet_ball", throwPower = 2.5f, ancient = true)
createDefault("ancient_origin_ball", ancient = true)
createDefault("ancient_origin_ball", GuaranteedModifier(), ancient = true)
// Luxury ball effect
CobblemonEvents.FRIENDSHIP_UPDATED.subscribe(priority = Priority.LOWEST) { event ->
var increment = (event.newFriendship - event.pokemon.friendship).toFloat()
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.pokemon.effect
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokemon.activestate.ShoulderedState
import com.cobblemon.mod.common.pokemon.effects.LightSourceEffect
import com.cobblemon.mod.common.pokemon.effects.PotionBaseEffect
import com.cobblemon.mod.common.util.party
import net.minecraft.server.network.ServerPlayerEntity
@@ -29,7 +28,6 @@ object ShoulderEffectRegistry {
private val effects = mutableMapOf<String, Class<out ShoulderEffect>>()
// Effects - START
val LIGHT_SOURCE = register("light_source", LightSourceEffect::class.java)
val POTION_EFFECT = register("potion_effect", PotionBaseEffect::class.java)
// Effects - END
@@ -16,11 +16,14 @@ import com.cobblemon.mod.common.api.moves.BenchedMove
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.evolution.requirement.EvolutionRequirement
import com.cobblemon.mod.common.api.scheduling.afterOnServer
import com.cobblemon.mod.common.entity.generic.GenericBedrockEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.activestate.ShoulderedState
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.LevelUpEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.item.ItemStack
import net.minecraft.sound.SoundCategory
@@ -92,6 +95,7 @@ interface Evolution : EvolutionLike {
return true
}
/**
* Starts this evolution as soon as possible.
* This will not present a choice to the client regardless of [optional].
@@ -99,25 +103,57 @@ interface Evolution : EvolutionLike {
* @param pokemon The [Pokemon] being evolved.
*/
fun forceEvolve(pokemon: Pokemon) {
// This is a switch to enable/disable the evolution effect while we get particles improved
val useEvolutionEffect = false
if (pokemon.state is ShoulderedState) {
pokemon.tryRecallWithAnimation()
}
// ToDo Once implemented queue evolution for a pokemon state that is not in battle, start animation instead of instantly doing all of this
val pokemonEntity = pokemon.entity
if (pokemonEntity == null || !useEvolutionEffect) {
evolutionMethod(pokemon)
} else {
pokemonEntity.evolutionEntity = pokemon.getOwnerPlayer()?.let { GenericBedrockEntity(world = it.world) }
val evolutionEntity = pokemon.entity!!.evolutionEntity
evolutionEntity?.apply {
category = cobblemonResource("evolution")
colliderHeight = pokemonEntity.height
colliderWidth = pokemonEntity.width
scale = pokemonEntity.scaleFactor
syncAge = true // Otherwise particle animation will be starting from zero even if you come along partway through
setPosition(pokemonEntity.x, pokemonEntity.y, pokemonEntity.z)
}
pokemon.getOwnerPlayer()?.world?.spawnEntity(evolutionEntity)
afterOnServer(seconds = 9F) {
if (!pokemonEntity.isRemoved) {
evolutionMethod(pokemon)
afterOnServer(seconds = 1.5F) { pokemonEntity.cry() }
afterOnServer(seconds = 3F) {
if (evolutionEntity != null) {
evolutionEntity.kill()
if (!pokemonEntity.isRemoved) {
pokemonEntity.evolutionEntity = null
}
}
}
}
}
}
}
fun evolutionMethod(pokemon: Pokemon) {
this.result.apply(pokemon)
this.learnableMoves.forEach { move ->
if (pokemon.moveSet.hasSpace()) {
pokemon.moveSet.add(move.create())
}
else {
} else {
pokemon.benchedMoves.add(BenchedMove(move, 0))
}
pokemon.getOwnerPlayer()?.sendMessage(lang("experience.learned_move", pokemon.getDisplayName(), move.displayName))
}
// we want to instantly tick for example you might only evolve your Bulbasaur at level 34 so Venusaur should be immediately available
pokemon.evolutions.filterIsInstance<PassiveEvolution>()
.forEach { evolution ->
evolution.attemptEvolution(pokemon)
}
pokemon.evolutions.filterIsInstance<PassiveEvolution>().forEach { evolution -> evolution.attemptEvolution(pokemon) }
pokemon.getOwnerPlayer()?.playSound(CobblemonSounds.EVOLVING, SoundCategory.NEUTRAL, 1F, 1F)
CobblemonEvents.EVOLUTION_COMPLETE.post(EvolutionCompleteEvent(pokemon, this))
}
@@ -8,13 +8,27 @@
package com.cobblemon.mod.common.api.scheduling
import java.util.concurrent.CompletableFuture
@Deprecated("Use afterOnServer or afterOnClient; ambiguous side is not good for your health")
@JvmOverloads
fun after(ticks: Int = 0, seconds: Float = 0F, serverThread: Boolean = false, action: () -> Unit) {
val scheduler = if (serverThread) ServerTaskTracker else ClientTaskTracker
scheduler.after(seconds = seconds + ticks / 20F, action)
}
fun delayedFuture(ticks: Int = 0, seconds: Float = 0F, serverThread: Boolean = false): CompletableFuture<Unit> {
val future = CompletableFuture<Unit>()
if (ticks == 0 && seconds == 0F) {
future.complete(Unit)
} else {
after(ticks = ticks, seconds = seconds, serverThread = serverThread) { future.complete(Unit) }
}
return future
}
/**
* Delayed task is created to run on the main thread. This is for when the task
* being completed after the delay does things like entity removal or other thread-unsafe actions.
@@ -25,6 +25,7 @@ import com.mojang.serialization.DynamicOps
import com.mojang.serialization.codecs.ListCodec
import com.mojang.serialization.codecs.PrimitiveCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import com.mojang.serialization.codecs.UnboundedMapCodec
import kotlin.math.floor
import net.minecraft.network.PacketByteBuf
@@ -44,6 +45,7 @@ interface MoLangCurve : CodecMapped {
registerSubtype(CurveType.LINEAR, LinearMoLangCurve::class.java, LinearMoLangCurve.CODEC)
registerSubtype(CurveType.CATMULL_ROM, CatmullRomMoLangCurve::class.java, CatmullRomMoLangCurve.CODEC)
registerSubtype(CurveType.BEZIER, BezierMoLangCurve::class.java, BezierMoLangCurve.CODEC)
registerSubtype(CurveType.BEZIER_CHAIN, BezierChainMoLangCurve::class.java, BezierChainMoLangCurve.CODEC)
}
}
@@ -52,8 +54,7 @@ interface MoLangCurve : CodecMapped {
var input: Expression
fun resolve(runtime: MoLangRuntime, inputValue: Double): Double
fun apply(runtime: MoLangRuntime) {
val inputValue = runtime.resolveDouble(input)
runtime.environment.setSimpleVariable(name, DoubleValue(resolve(runtime, inputValue)))
runtime.environment.setSimpleVariable(name, DoubleValue(resolve(runtime, runtime.resolveDouble(input))))
}
}
@@ -165,6 +166,103 @@ class CatmullRomMoLangCurve(
}
}
class BezierChainMoLangCurve(
override var name: String = "variable",
override var input: Expression = NumberExpression(0.5),
var nodes: Map<Double, BezierChainNode> = emptyMap()
) : MoLangCurve {
var nodePairs: List<BezierChainNodePair> = emptyList()
fun deriveNodePairs() {
val nodePairs = mutableListOf<BezierChainNodePair>()
val sortedNodes = nodes.entries.sortedBy { it.key }
for (i in 1 until sortedNodes.size) {
val node1 = sortedNodes[i - 1]
val node2 = sortedNodes[i]
nodePairs.add(BezierChainNodePair(node1.key, node2.key, node1.value, node2.value))
}
this.nodePairs = nodePairs
}
init {
deriveNodePairs()
}
class BezierChainNode(var value: Double, var slope: Double) {
fun writeToBuffer(buffer: PacketByteBuf) {
buffer.writeDouble(value)
buffer.writeDouble(slope)
}
fun readFromBuffer(buffer: PacketByteBuf) {
value = buffer.readDouble()
slope = buffer.readDouble()
}
companion object {
val CODEC = RecordCodecBuilder.create<BezierChainNode> { instance ->
instance.group(
PrimitiveCodec.DOUBLE.fieldOf("value").forGetter { it.value },
PrimitiveCodec.DOUBLE.fieldOf("slope").forGetter { it.slope }
).apply(instance, ::BezierChainNode)
}
}
}
class BezierChainNodePair(
val startTime: Double,
val endTime: Double,
node1: BezierChainNode,
node2: BezierChainNode
) {
val curve: CubedBezierCurve
init {
val v1 = node1.value
val v2 = node1.value + node1.slope / 3
val v3 = node2.value - node2.slope / 3
val v4 = node2.value
curve = CubedBezierCurve(v1, v2, v3, v4)
}
}
class BezierChainPointData(val time: Double, val value: Double, val slope: Double)
companion object {
val CODEC: Codec<BezierChainMoLangCurve> = RecordCodecBuilder.create { instance ->
instance.group(
PrimitiveCodec.STRING.fieldOf("type").forGetter { it.type.name },
PrimitiveCodec.STRING.fieldOf("name").forGetter { it.name },
EXPRESSION_CODEC.fieldOf("input").forGetter { it.input },
UnboundedMapCodec(PrimitiveCodec.DOUBLE, BezierChainNode.CODEC).fieldOf("nodes").forGetter { it.nodes }
).apply(instance) { _, name, input, nodes -> BezierChainMoLangCurve(name, input, nodes) }
}
}
override val type: CurveType = CurveType.BEZIER_CHAIN
override fun resolve(runtime: MoLangRuntime, inputValue: Double): Double {
val position = inputValue.coerceIn(0.0, 1.0)
val nodePair = nodePairs.last { position >= it.startTime }
val curve = nodePair.curve
val t = (position - nodePair.startTime) / (nodePair.endTime - nodePair.startTime)
return curve.getY(t)
}
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
override fun readFromBuffer(buffer: PacketByteBuf) {
name = buffer.readString()
input = MoLang.createParser(buffer.readString()).parseExpression()
nodes = buffer.readMap({ buffer.readDouble() }, { BezierChainNode(0.0, 0.0).also { it.readFromBuffer(buffer) } })
deriveNodePairs()
}
override fun writeToBuffer(buffer: PacketByteBuf) {
buffer.writeString(name)
buffer.writeString(input.getString())
buffer.writeMap(nodes, { pb, key -> pb.writeDouble(key) }, { pb, value -> value.writeToBuffer(pb) })
}
}
class BezierMoLangCurve(
override var name: String = "variable",
override var input: Expression = NumberExpression(0.5),
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.codec.CodecMapped
import com.cobblemon.mod.common.api.data.ArbitrarilyMappedSerializableCompanion
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.util.math.hamiltonProduct
import com.cobblemon.mod.common.util.set
import com.mojang.serialization.Codec
import com.mojang.serialization.DynamicOps
import com.mojang.serialization.codecs.PrimitiveCodec
@@ -26,6 +27,7 @@ import net.minecraft.util.math.RotationAxis
import net.minecraft.util.math.Vec3d
import org.joml.AxisAngle4d
import org.joml.Quaternionf
import org.joml.Vector3f
interface ParticleCameraMode : CodecMapped {
companion object : ArbitrarilyMappedSerializableCompanion<ParticleCameraMode, ParticleCameraModeType>(
@@ -54,6 +56,8 @@ interface ParticleCameraMode : CodecMapped {
prevAngle: Float,
angle: Float,
deltaTicks: Float,
particlePosition: Vec3d,
cameraPosition: Vec3d,
cameraAngle: Quaternionf,
cameraYaw: Float,
cameraPitch: Float,
@@ -77,6 +81,8 @@ class RotateXYZCameraMode : ParticleCameraMode {
prevAngle: Float,
angle: Float,
deltaTicks: Float,
particlePosition: Vec3d,
cameraPosition: Vec3d,
cameraAngle: Quaternionf,
cameraYaw: Float,
cameraPitch: Float,
@@ -109,6 +115,8 @@ class RotateYCameraMode : ParticleCameraMode {
prevAngle: Float,
angle: Float,
deltaTicks: Float,
particlePosition: Vec3d,
cameraPosition: Vec3d,
cameraAngle: Quaternionf,
cameraYaw: Float,
cameraPitch: Float,
@@ -146,13 +154,15 @@ class LookAtXYZ : ParticleCameraMode {
prevAngle: Float,
angle: Float,
deltaTicks: Float,
particlePosition: Vec3d,
cameraPosition: Vec3d,
cameraAngle: Quaternionf,
cameraYaw: Float,
cameraPitch: Float,
viewDirection: Vec3d
): Quaternionf {
val i = if (angle == 0F) 0F else MathHelper.lerp(deltaTicks, prevAngle, angle)
val rotation = Quaternionf(0F, 0F, 0F, 1F)
val rotation = Quaternionf()
rotation.hamiltonProduct(RotationAxis.POSITIVE_Y.rotationDegrees(-cameraYaw))
rotation.hamiltonProduct(RotationAxis.POSITIVE_X.rotationDegrees(cameraPitch))
rotation.hamiltonProduct(RotationAxis.POSITIVE_Z.rotationDegrees(i))
@@ -174,6 +184,8 @@ class LookAtY : ParticleCameraMode {
prevAngle: Float,
angle: Float,
deltaTicks: Float,
particlePosition: Vec3d,
cameraPosition: Vec3d,
cameraAngle: Quaternionf,
cameraYaw: Float,
cameraPitch: Float,
@@ -203,6 +215,8 @@ class DirectionZ : ParticleCameraMode {
prevAngle: Float,
angle: Float,
deltaTicks: Float,
particlePosition: Vec3d,
cameraPosition: Vec3d,
cameraAngle: Quaternionf,
cameraYaw: Float,
cameraPitch: Float,
@@ -236,6 +250,8 @@ class EmitterYZPlane : ParticleCameraMode {
prevAngle: Float,
angle: Float,
deltaTicks: Float,
particlePosition: Vec3d,
cameraPosition: Vec3d,
cameraAngle: Quaternionf,
cameraYaw: Float,
cameraPitch: Float,
@@ -274,6 +290,8 @@ class EmitterXZPlane : ParticleCameraMode {
prevAngle: Float,
angle: Float,
deltaTicks: Float,
particlePosition: Vec3d,
cameraPosition: Vec3d,
cameraAngle: Quaternionf,
cameraYaw: Float,
cameraPitch: Float,
@@ -311,6 +329,8 @@ class EmitterXYPlane : ParticleCameraMode {
prevAngle: Float,
angle: Float,
deltaTicks: Float,
particlePosition: Vec3d,
cameraPosition: Vec3d,
cameraAngle: Quaternionf,
cameraYaw: Float,
cameraPitch: Float,
@@ -347,6 +367,8 @@ class DirectionY : ParticleCameraMode {
prevAngle: Float,
angle: Float,
deltaTicks: Float,
particlePosition: Vec3d,
cameraPosition: Vec3d,
cameraAngle: Quaternionf,
cameraYaw: Float,
cameraPitch: Float,
@@ -380,6 +402,8 @@ class DirectionX : ParticleCameraMode {
prevAngle: Float,
angle: Float,
deltaTicks: Float,
particlePosition: Vec3d,
cameraPosition: Vec3d,
cameraAngle: Quaternionf,
cameraYaw: Float,
cameraPitch: Float,
@@ -398,6 +422,29 @@ class DirectionX : ParticleCameraMode {
override fun writeToBuffer(buffer: PacketByteBuf) {}
}
/**
* You can think of this camera mode as the one that does the following:
*
* - Uses the direction of motion as the particle X axis
* - Uses a vector parallel to the camera plane and perpendicular to the direction of motion as the particle Y axis.
*
* The second requirement is difficult to accomplish. The way it was eventually solved is by
* thinking of the plane generated by two specific vectors. One is the camera to the particle,
* the other is the camera to where the particle will be after it continues moving on its
* trajectory for some amount of time.
*
* That plane slices right through the camera, and so the normal vector of that plane is parallel
* lies on the camera plane (hard to explain that one). Since the plane includes the direction vector,
* the normal vector of that plane is perpendicular to the direction vector. Since the direction
* vector is being used for the X axis, and the normal vector is perpendicular, we can use that as
* the Y axis safely.
*
* The chicanery of this look direction is all simply to calculate the correct Y axis, use it,
* and then use the direction vector for the X axis.
*
* @author Hiroku
* @since June 29th, 2023
*/
class LookAtDirection : ParticleCameraMode {
override val type: ParticleCameraModeType = ParticleCameraModeType.LOOK_AT_DIRECTION
companion object {
@@ -408,21 +455,50 @@ class LookAtDirection : ParticleCameraMode {
}
}
// Reusable shit so fewer instantiations during rendering.
val viewDirectionF = Vector3f(0F, 0F, 0F)
val particlePositionF = Vector3f(0F, 0F, 0F)
val cameraPositionF = Vector3f(0F, 0F, 0F)
val axisAngle = AxisAngle4d()
override fun getRotation(
matrixWrapper: MatrixWrapper,
prevAngle: Float,
angle: Float,
deltaTicks: Float,
particlePosition: Vec3d,
cameraPosition: Vec3d,
cameraAngle: Quaternionf,
cameraYaw: Float,
cameraPitch: Float,
viewDirection: Vec3d
): Quaternionf {
val i = if (angle == 0F) 0F else MathHelper.lerp(deltaTicks, prevAngle, angle)
val rotation = Quaternionf(0F, 0F, 0F, 1F)
rotation.hamiltonProduct(RotationAxis.POSITIVE_Y.rotationDegrees((viewDirection.x * -cameraYaw).toFloat()))
rotation.hamiltonProduct(RotationAxis.POSITIVE_X.rotationDegrees((viewDirection.y * cameraPitch).toFloat()))
rotation.hamiltonProduct(RotationAxis.POSITIVE_Z.rotationDegrees((viewDirection.z * i).toFloat()))
viewDirectionF.set(viewDirection)
particlePositionF.set(particlePosition)
cameraPositionF.set(cameraPosition)
// Perform a rotation around the axis that should later become the particle Y axis
Quaternionf().rotateTo(
// Camera -> Particle
particlePositionF.sub(cameraPositionF, Vector3f()),
// Camera -> (Particle + Direction)
particlePositionF.add(viewDirectionF, Vector3f()).sub(cameraPositionF)
).get(axisAngle) // Extract the rotation into axis + angle, so we can pluck the axis out.
// Extract the axis of rotation, it is what we want the particle's local Y axis to be.
val correctY = Vector3f(axisAngle.x.toFloat(), axisAngle.y.toFloat(), axisAngle.z.toFloat())
// First make X look along the direction vector
val rotation = Quaternionf().rotateTo(Vector3f(1F, 0F, 0F), viewDirectionF)
// Now figure out what the local Y axis is
val currentY = Vector3f(0F, 1F, 0F).rotate(rotation)
// Move that local Y to the correct local Y
// Pre-multiply is because the previous transform won't affect the correct Y, but vice versa absolutely would; we don't want that.
rotation.premul(Quaternionf().rotateTo(currentY, correctY))
// Do the regular rotation around Z to spin the particle, same as all other modes.
val particleAngle = if (angle == 0.0f) 0F else MathHelper.lerp(deltaTicks, prevAngle, angle)
rotation.hamiltonProduct(RotationAxis.POSITIVE_Z.rotationDegrees(particleAngle))
return rotation
}
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.data.ArbitrarilyMappedSerializableCompanion
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.codec.EXPRESSION_CODEC
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.resolve
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.mojang.serialization.Codec
@@ -55,7 +56,9 @@ class OnceEmitterLifetime(var activeTime: Expression = 1.0.asExpression()) : Par
override val type = ParticleEmitterLifetimeType.ONCE
override fun getAction(runtime: MoLangRuntime, started: Boolean, emitterAge: Double): ParticleEmitterAction {
return if (emitterAge > runtime.resolveDouble(activeTime)) {
val activeTime = runtime.resolve(activeTime)
runtime.environment.setSimpleVariable("emitter_lifetime", activeTime)
return if (emitterAge > activeTime.asDouble()) {
ParticleEmitterAction.STOP
} else {
ParticleEmitterAction.GO
@@ -127,11 +130,13 @@ class LoopingEmitterLifetime(var activeTime: Expression = 1.0.asExpression(), va
override val type = ParticleEmitterLifetimeType.LOOPING
override fun getAction(runtime: MoLangRuntime, started: Boolean, emitterAge: Double): ParticleEmitterAction {
val activeTime = runtime.resolveDouble(activeTime)
val activeTime = runtime.resolve(activeTime)
val activeTimeValue = activeTime.asDouble()
val sleepTime = runtime.resolveDouble(sleepTime)
val interval = activeTime + sleepTime
val interval = activeTimeValue + sleepTime
val displacement = emitterAge % interval
return if (displacement < activeTime) {
runtime.environment.setSimpleVariable("emitter_lifetime", activeTime)
return if (displacement < activeTimeValue) {
ParticleEmitterAction.GO
} else {
ParticleEmitterAction.RESET
@@ -145,7 +150,6 @@ class LoopingEmitterLifetime(var activeTime: Expression = 1.0.asExpression(), va
}
override fun writeToBuffer(buffer: PacketByteBuf) {
val activeTimeString =
buffer.writeString(activeTime.getString())
buffer.writeString(sleepTime.getString())
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.snowstorm
import com.bedrockk.molang.Expression
import com.bedrockk.molang.MoLang
import com.bedrockk.molang.ast.NumberExpression
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.struct.VariableStruct
@@ -17,9 +16,11 @@ import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.codec.CodecMapped
import com.cobblemon.mod.common.api.data.ArbitrarilyMappedSerializableCompanion
import com.cobblemon.mod.common.api.snowstorm.ParticleEmitterRate.Companion.OVERFLOW_VARIABLE
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.codec.EXPRESSION_CODEC
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveInt
import com.mojang.serialization.Codec
import com.mojang.serialization.DynamicOps
import com.mojang.serialization.codecs.PrimitiveCodec
@@ -49,12 +50,12 @@ enum class ParticleEmitterRateType {
INSTANT
}
class InstantParticleEmitterRate(var amount: Int = 1) : ParticleEmitterRate {
class InstantParticleEmitterRate(var amount: Expression = "1".asExpression()) : ParticleEmitterRate {
companion object {
val CODEC: Codec<InstantParticleEmitterRate> = RecordCodecBuilder.create { instance ->
instance.group(
PrimitiveCodec.STRING.fieldOf("type").forGetter { it.type.name },
PrimitiveCodec.INT.fieldOf("amount").forGetter { it.amount }
EXPRESSION_CODEC.fieldOf("amount").forGetter { it.amount }
).apply(instance) { _, amount -> InstantParticleEmitterRate(amount) }
}
}
@@ -65,17 +66,17 @@ class InstantParticleEmitterRate(var amount: Int = 1) : ParticleEmitterRate {
if (started) {
return 0
} else {
return amount
return runtime.resolveInt(amount)
}
}
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
override fun readFromBuffer(buffer: PacketByteBuf) {
amount = buffer.readInt()
amount = buffer.readString().asExpression()
}
override fun writeToBuffer(buffer: PacketByteBuf) {
buffer.writeInt(amount)
buffer.writeString(amount.getString())
}
}
@@ -99,29 +100,38 @@ class SteadyParticleEmitterRate(
override val type = ParticleEmitterRateType.STEADY
override fun getEmitCount(runtime: MoLangRuntime, started: Boolean, currentlyActive: Int): Int {
// The emitting rates are all per second, but this runs every tick. Presents some difficulties.
val max = runtime.resolveDouble(maximum).toInt()
val variables = runtime.environment.structs["variable"] as VariableStruct
var currentOverflow = variables.map[OVERFLOW_VARIABLE]?.asDouble() ?: 0.0
/*
* The strategy is that per tick it might be calculated to be 1.2 particles. We can't spawn a fifth
* of a particle, so we consider the .2 to be overflow which will be added to the next total. If the
* next tick we end up with 1.9, the overflow from last time will bring it to 2.1, so 2 particles with
* 0.1 overflow for the next tick.
*/
val currentOverflow = variables.map[OVERFLOW_VARIABLE]?.asDouble() ?: 0.0
if (currentlyActive >= max) {
return 0
}
val perSecond = runtime.resolveDouble(rate)
val trySpawn = perSecond / 20.0 + currentOverflow
val intComponent = trySpawn.toInt()
val doubleComponent = trySpawn - intComponent
val intComponent = trySpawn.toInt() // round down
val doubleComponent = trySpawn - intComponent // decimal portion (the overflow)
currentOverflow = doubleComponent
variables.map[OVERFLOW_VARIABLE] = DoubleValue(currentOverflow)
variables.map[OVERFLOW_VARIABLE] = DoubleValue(doubleComponent)
return min(intComponent, max - currentlyActive)
}
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
override fun readFromBuffer(buffer: PacketByteBuf) {
rate = MoLang.createParser(buffer.readString()).parseExpression()
maximum = MoLang.createParser(buffer.readString()).parseExpression()
rate = buffer.readString().asExpression()
maximum = buffer.readString().asExpression()
}
override fun writeToBuffer(buffer: PacketByteBuf) {
@@ -11,5 +11,6 @@ package com.cobblemon.mod.common.api.snowstorm
enum class ParticleMaterial {
ALPHA,
OPAQUE,
BLEND
BLEND,
ADD
}
@@ -0,0 +1,103 @@
/*
* 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.snowstorm
import com.cobblemon.mod.common.client.render.shader.CobblemonShaders
import com.mojang.blaze3d.platform.GlStateManager
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.particle.ParticleTextureSheet
import net.minecraft.client.render.*
import net.minecraft.client.texture.SpriteAtlasTexture
import net.minecraft.client.texture.TextureManager
object ParticleMaterials {
val ALPHA = object : ParticleTextureSheet {
override fun begin(builder: BufferBuilder, textureManager: TextureManager) {
RenderSystem.disableBlend()
RenderSystem.depthMask(true)
RenderSystem.setShader { CobblemonShaders.PARTICLE_CUTOUT }
RenderSystem.setShaderTexture(0, SpriteAtlasTexture.PARTICLE_ATLAS_TEXTURE)
builder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR_LIGHT)
}
override fun draw(tessellator: Tessellator) {
tessellator.draw()
}
override fun toString(): String {
return "ALPHA"
}
}
val ADD = object : ParticleTextureSheet {
override fun begin(builder: BufferBuilder, textureManager: TextureManager) {
RenderSystem.enableBlend()
RenderSystem.depthMask(true)
RenderSystem.setShader { CobblemonShaders.PARTICLE_BLEND }
RenderSystem.setShaderTexture(0, SpriteAtlasTexture.PARTICLE_ATLAS_TEXTURE)
RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA)
builder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR_LIGHT)
}
override fun draw(tessellator: Tessellator) {
tessellator.draw()
}
override fun toString(): String {
return "ADD"
}
}
val BLEND = object : ParticleTextureSheet {
override fun begin(builder: BufferBuilder, textureManager: TextureManager) {
RenderSystem.enableBlend()
RenderSystem.depthMask(true)
RenderSystem.setShader { CobblemonShaders.PARTICLE_BLEND }
RenderSystem.setShaderTexture(0, SpriteAtlasTexture.PARTICLE_ATLAS_TEXTURE)
RenderSystem.blendFunc(GlStateManager.SrcFactor.DST_COLOR, GlStateManager.DstFactor.ZERO)
builder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR_LIGHT)
}
override fun draw(tessellator: Tessellator) {
tessellator.draw()
}
override fun toString(): String {
return "BLEND"
}
}
val OPAQUE = object : ParticleTextureSheet {
override fun begin(builder: BufferBuilder, textureManager: TextureManager) {
RenderSystem.enableBlend()
RenderSystem.depthMask(true)
RenderSystem.setShader { CobblemonShaders.PARTICLE_BLEND }
RenderSystem.setShaderTexture(0, SpriteAtlasTexture.PARTICLE_ATLAS_TEXTURE)
RenderSystem.blendFunc(GlStateManager.SrcFactor.ONE, GlStateManager.DstFactor.ONE)
builder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR_LIGHT)
}
override fun draw(tessellator: Tessellator) {
tessellator.draw()
}
override fun toString(): String {
return "OPAQUE"
}
}
}
@@ -14,8 +14,10 @@ import com.bedrockk.molang.ast.NumberExpression
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.codec.CodecMapped
import com.cobblemon.mod.common.api.data.ArbitrarilyMappedSerializableCompanion
import com.cobblemon.mod.common.client.particle.ParticleStorm
import com.cobblemon.mod.common.util.codec.EXPRESSION_CODEC
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.math.geometry.transformDirection
import com.cobblemon.mod.common.util.resolveDouble
import com.mojang.serialization.Codec
import com.mojang.serialization.DynamicOps
@@ -37,7 +39,7 @@ interface ParticleMotion : CodecMapped {
}
val type: ParticleMotionType
fun getInitialVelocity(runtime: MoLangRuntime, particlePos: Vec3d, emitterPos: Vec3d): Vec3d
fun getInitialVelocity(runtime: MoLangRuntime, storm: ParticleStorm, particlePos: Vec3d, emitterPos: Vec3d): Vec3d
fun getAcceleration(runtime: MoLangRuntime, velocity: Vec3d): Vec3d
}
@@ -73,8 +75,8 @@ class DynamicParticleMotion(
}
}
override fun getInitialVelocity(runtime: MoLangRuntime, particlePos: Vec3d, emitterPos: Vec3d): Vec3d {
return direction.getDirectionVector(runtime, emitterPos, particlePos).multiply(runtime.resolveDouble(speed))
override fun getInitialVelocity(runtime: MoLangRuntime, storm: ParticleStorm, particlePos: Vec3d, emitterPos: Vec3d): Vec3d {
return direction.getDirectionVector(runtime, storm, emitterPos, particlePos).normalize().multiply(runtime.resolveDouble(speed))
}
override fun getAcceleration(runtime: MoLangRuntime, velocity: Vec3d): Vec3d {
@@ -82,7 +84,7 @@ class DynamicParticleMotion(
runtime.resolveDouble(acceleration.first),
runtime.resolveDouble(acceleration.second),
runtime.resolveDouble(acceleration.third)
).subtract(velocity.multiply(runtime.resolveDouble(drag) / 20)/* Drag is measured in blocks per tick per tick, I know it sounds dumb. */)
).subtract(velocity.multiply(runtime.resolveDouble(drag)))
}
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
@@ -122,7 +124,7 @@ interface ParticleMotionDirection : CodecMapped {
}
}
val type: ParticleMotionDirectionType
fun getDirectionVector(runtime: MoLangRuntime, emitterPos: Vec3d, particlePos: Vec3d): Vec3d
fun getDirectionVector(runtime: MoLangRuntime, storm: ParticleStorm, emitterPos: Vec3d, particlePos: Vec3d): Vec3d
}
class InwardsMotionDirection : ParticleMotionDirection {
@@ -134,7 +136,13 @@ class InwardsMotionDirection : ParticleMotionDirection {
}
override val type = ParticleMotionDirectionType.INWARDS
override fun getDirectionVector(runtime: MoLangRuntime, emitterPos: Vec3d, particlePos: Vec3d) = emitterPos.subtract(particlePos).normalize()
override fun getDirectionVector(runtime: MoLangRuntime, storm: ParticleStorm, emitterPos: Vec3d, particlePos: Vec3d): Vec3d {
return if (particlePos == emitterPos) {
Vec3d(storm.world.random.nextDouble() - 0.5, storm.world.random.nextDouble() - 0.5, storm.world.random.nextDouble() - 0.5)
} else {
particlePos.subtract(emitterPos)
}.normalize()
}
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
override fun readFromBuffer(buffer: PacketByteBuf) {}
override fun writeToBuffer(buffer: PacketByteBuf) {}
@@ -149,8 +157,12 @@ class OutwardsMotionDirection : ParticleMotionDirection {
}
override val type = ParticleMotionDirectionType.OUTWARDS
override fun getDirectionVector(runtime: MoLangRuntime, emitterPos: Vec3d, particlePos: Vec3d): Vec3d {
return particlePos.subtract(emitterPos).normalize()
override fun getDirectionVector(runtime: MoLangRuntime, storm: ParticleStorm, emitterPos: Vec3d, particlePos: Vec3d): Vec3d {
return if (particlePos == emitterPos) {
Vec3d(storm.world.random.nextDouble() - 0.5, storm.world.random.nextDouble() - 0.5, storm.world.random.nextDouble() - 0.5)
} else {
particlePos.subtract(emitterPos)
}.normalize()
}
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
@@ -178,12 +190,13 @@ class CustomMotionDirection(
override val type = ParticleMotionDirectionType.CUSTOM
override fun getDirectionVector(runtime: MoLangRuntime, emitterPos: Vec3d, particlePos: Vec3d): Vec3d {
return Vec3d(
override fun getDirectionVector(runtime: MoLangRuntime, storm: ParticleStorm, emitterPos: Vec3d, particlePos: Vec3d): Vec3d {
val v = Vec3d(
runtime.resolveDouble(direction.first),
runtime.resolveDouble(direction.second),
runtime.resolveDouble(direction.third)
)
return storm.matrixWrapper.matrix.transformDirection(v)
}
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
@@ -220,7 +233,7 @@ class StaticParticleMotion : ParticleMotion {
@Transient
override val type = ParticleMotionType.STATIC
override fun getInitialVelocity(runtime: MoLangRuntime, particlePos: Vec3d, emitterPos: Vec3d) = Vec3d.ZERO
override fun getInitialVelocity(runtime: MoLangRuntime, storm: ParticleStorm, particlePos: Vec3d, emitterPos: Vec3d) = Vec3d.ZERO
override fun getAcceleration(runtime: MoLangRuntime, velocity: Vec3d) = Vec3d.ZERO
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
override fun readFromBuffer(buffer: PacketByteBuf) {}
@@ -34,6 +34,9 @@ class ParticleSpace(
}
}
val isLocalSpace: Boolean
get() = localPosition || localRotation
fun readFromBuffer(buffer: PacketByteBuf) {
localPosition = buffer.readBoolean()
localRotation = buffer.readBoolean()
@@ -146,7 +146,7 @@ class AnimatedParticleUVMode(
} else {
val fps = runtime.resolveDouble(fps)
val effectiveFrame = ((age * fps) % maxFrame).toInt()
val frame = if (!loop && age * fps > maxFrame) {
val frame = if (!loop && age * fps >= maxFrame) {
maxFrame
} else {
effectiveFrame
@@ -9,17 +9,13 @@
package com.cobblemon.mod.common.api.snowstorm
import com.bedrockk.molang.Expression
import com.bedrockk.molang.MoLang
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.codec.CodecMapped
import com.cobblemon.mod.common.api.data.ArbitrarilyMappedSerializableCompanion
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.codec.EXPRESSION_CODEC
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.readVec3d
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveVec3d
import com.cobblemon.mod.common.util.writeVec3d
import com.mojang.serialization.Codec
import com.mojang.serialization.DynamicOps
import com.mojang.serialization.codecs.PrimitiveCodec
@@ -70,7 +66,7 @@ class FromMotionViewDirection(var minSpeed: Double = 0.01) : ParticleViewDirecti
}
override fun getDirection(runtime: MoLangRuntime, lastDirection: Vec3d, currentVelocity: Vec3d): Vec3d {
return if (currentVelocity.length() / 20 >= minSpeed) {
return if (currentVelocity.length() * 20 >= minSpeed) {
currentVelocity.normalize()
} else {
lastDirection
@@ -32,11 +32,10 @@ open class NBTStoreAdapter(
override fun save(file: File, serialized: NbtCompound) = NbtIo.writeCompressed(serialized, file)
override fun <E, T : PokemonStore<E>> load(file: File, storeClass: Class<out T>, uuid: UUID): T? {
val store = try {
storeClass.getConstructor(UUID::class.java, UUID::class.java).newInstance(uuid, uuid)
storeClass.getConstructor(UUID::class.java).newInstance(uuid)
} catch (exception: NoSuchMethodException) {
storeClass.getConstructor(UUID::class.java).newInstance(uuid)
}
return try {
val nbt = NbtIo.readCompressed(file)
store.loadFromNBT(nbt)
@@ -43,6 +43,8 @@ open class PlayerPartyStore(
private var secondsSinceFriendshipUpdate = 0
constructor(playerUUID: UUID): this(playerUUID, playerUUID)
override fun initialize() {
super.initialize()
observerUUIDs.add(playerUUID)
@@ -148,6 +148,8 @@ object CobblemonBiomeTags {
@JvmField
val HAS_FIRE_STONE_ORE_RARE = create("has_ore/ore_fire_stone_rare")
@JvmField
val HAS_FIRE_STONE_ORE_NETHER = create("has_ore/ore_fire_stone_nether")
@JvmField
val HAS_ICE_STONE_ORE = create("has_ore/ore_ice_stone_normal")
@JvmField
val HAS_ICE_STONE_ORE_RARE = create("has_ore/ore_ice_stone_rare")
@@ -45,6 +45,7 @@ object CobblemonBlockTags {
@JvmField val SNOW_BLOCK = createTag("snow_block")
@JvmField val ROOTS = createTag("roots")
@JvmField val STANDING_SIGNS = createTag("standing_signs")
@JvmField val TUMBLESTONE_HEAT_SOURCE = createTag("tumblestone_heat_source")
@JvmField val WALL_HANGING_SIGNS = createTag("wall_hanging_signs")
@JvmField val WALL_SIGNS = createTag("wall_signs")
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.tags
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.registry.Registries
import net.minecraft.registry.RegistryKeys
@@ -21,11 +22,12 @@ import net.minecraft.registry.tag.TagKey
*/
@Suppress("unused", "HasPlatformType")
object CobblemonItemTags {
@JvmField
val ANCIENT_POKE_BALLS = create("ancient_poke_balls")
@JvmField
val APRICORN_LOGS = create("apricorn_logs")
@JvmField
val APRICORN_SEEDS = create("apricorn_seeds")
val APRICORN_SPROUTS = create("apricorn_sprouts")
@JvmField
val APRICORNS = create("apricorns")
/**
@@ -40,6 +42,15 @@ object CobblemonItemTags {
val BOATS = create("boats")
@JvmField
val COBBLEMON_SEEDS = create("cobblemon_seeds")
/**
* This tag is used for Fossil Machine natural materials
*/
@JvmField
val COOKED_MEAT = create("cooked_meat")
@JvmField
val DAWN_STONE_ORES = create("dawn_stone_ores")
@JvmField
val DUSK_STONE_ORES = create("dusk_stone_ores")
@JvmField
val EVOLUTION_ITEMS = create("evolution_items")
@JvmField
@@ -47,27 +58,54 @@ object CobblemonItemTags {
@JvmField
val EXPERIENCE_CANDIES = create("experience_candies")
@JvmField
val FIRE_STONE_ORES = create("fire_stone_ores")
@JvmField
val FOSSILS = create("fossils")
@JvmField
val HANGING_SIGNS = create("hanging_signs")
@JvmField
val HERBS = create("herbs")
@JvmField
val ICE_STONE_ORES = create("ice_stone_ores")
@JvmField
val LEAF_STONE_ORES = create("leaf_stone_ores")
@JvmField
val MINT_LEAF = create("mint_leaf")
@JvmField
val MINT_SEEDS = create("mint_seeds")
@JvmField
val MINTS = create("mints")
@JvmField
val MOON_STONE_ORES = create("moon_stone_ores")
@JvmField
val MUTATED_BERRIES = create("mutated_berries")
@JvmField
val PLANTS = create("plants")
@JvmField
val POKEBALLS = create("poke_balls")
val POKE_BALLS = create("poke_balls")
@JvmField
val PROTEIN_INGREDIENTS = create("protein_ingredients")
/** See [COOKED_MEAT] */
@JvmField
val RAW_MEAT = create("raw_meat")
@JvmField
val SEEDS = create("seeds")
@JvmField
val SHINY_STONE_ORES = create("shiny_stone_ores")
@JvmField
val SIGNS = create("signs")
@JvmField
val SUN_STONE_ORES = create("sun_stone_ores")
@JvmField
val THUNDER_STONE_ORES = create("thunder_stone_ores")
@JvmField
val TUMBLESTONES = create("tumblestones")
@JvmField
val WATER_STONE_ORES = create("water_stone_ores")
@JvmField
val ZINC_INGREDIENTS = create("zinc_ingredients")
// Held Item Tags
@JvmField
val ANY_HELD_ITEM = create("held/is_held_item")
@JvmField
@@ -97,6 +135,15 @@ object CobblemonItemTags {
@JvmField
val CONSUMED_IN_WILD_BATTLE = create("held/consumed_in_wild_battle")
/**
* Tag that flags items as being able to "create" [CobblemonItems.LEFTOVERS].
*/
@JvmField
val LEAVES_LEFTOVERS = create("held/leaves_leftovers")
@JvmField
val POTTERY_SHERDS = create("decorated_pot_sherds")
private fun create(path: String) = TagKey.of(RegistryKeys.ITEM, cobblemonResource(path))
}
@@ -10,7 +10,11 @@ package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.battles.interpreter.*
import com.cobblemon.mod.common.api.battles.interpreter.BasicContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.interpreter.Effect
import com.cobblemon.mod.common.api.battles.interpreter.MissingContext
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
@@ -20,7 +24,11 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.BattleVictoryEvent
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.text.*
import com.cobblemon.mod.common.api.text.gold
import com.cobblemon.mod.common.api.text.plus
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.dispatch.BattleDispatch
@@ -31,24 +39,46 @@ import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.battles.interpreter.ContextManager
import com.cobblemon.mod.common.battles.interpreter.instructions.DamageInstruction
import com.cobblemon.mod.common.battles.interpreter.instructions.DeprecatedInstruction
import com.cobblemon.mod.common.battles.interpreter.instructions.DeprecatedSplitInstruction
import com.cobblemon.mod.common.battles.interpreter.instructions.FaintInstruction
import com.cobblemon.mod.common.battles.interpreter.instructions.IgnoredInstruction
import com.cobblemon.mod.common.battles.interpreter.instructions.MoveInstruction
import com.cobblemon.mod.common.battles.interpreter.instructions.TurnInstruction
import com.cobblemon.mod.common.battles.interpreter.instructions.UnknownInstruction
import com.cobblemon.mod.common.battles.interpreter.instructions.UpkeepInstruction
import com.cobblemon.mod.common.battles.interpreter.instructions.*
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.net.messages.client.battle.*
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMadeInvalidChoicePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattlePersistentStatusPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleQueueRequestPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleSwitchPokemonPacket
import com.cobblemon.mod.common.pokemon.evolution.progress.DamageTakenEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.progress.RecoilEvolutionProgress
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.runOnServer
import com.cobblemon.mod.common.util.swap
import java.util.UUID
import java.util.concurrent.CompletableFuture
import kotlin.collections.Iterator
import kotlin.collections.MutableList
import kotlin.collections.any
import kotlin.collections.emptySet
import kotlin.collections.filter
import kotlin.collections.filterIsInstance
import kotlin.collections.find
import kotlin.collections.first
import kotlin.collections.firstOrNull
import kotlin.collections.forEach
import kotlin.collections.getOrNull
import kotlin.collections.listOf
import kotlin.collections.map
import kotlin.collections.mapNotNull
import kotlin.collections.mutableListOf
import kotlin.collections.mutableMapOf
import kotlin.collections.reduce
import kotlin.collections.set
import kotlin.collections.setOf
import kotlin.collections.toMutableList
import kotlin.collections.toTypedArray
import kotlin.math.roundToInt
import net.minecraft.entity.LivingEntity
import net.minecraft.server.world.ServerWorld
@@ -86,12 +116,13 @@ object ShowdownInterpreter {
updateInstructionParser["upkeep"] = { _, _, _, _ -> UpkeepInstruction() }
updateInstructionParser["faint"] = { battle, _, message, _ -> FaintInstruction(battle, message) }
updateInstructionParser["move"] = { _, instructionSet, message, _ -> MoveInstruction(instructionSet, message) }
splitInstructionParser["-damage"] = { _, targetActor, _, publicMessage, privateMessage, _ ->
DamageInstruction(targetActor, publicMessage, privateMessage)
updateInstructionParser["-activate"] = { _, instructionSet, message, _ -> ActivateInstruction(instructionSet, message) }
updateInstructionParser["-ability"] = { _, instructionSet, message, _ -> AbilityInstruction(instructionSet, message) }
updateInstructionParser["-miss"] = { battle, instructionSet, message, _ -> MissInstruction(battle, instructionSet, message) }
splitInstructionParser["-damage"] = { _, targetActor, instructionSet, publicMessage, privateMessage, _ ->
DamageInstruction(instructionSet, targetActor, publicMessage, privateMessage)
}
// Note '-cureteam' is a legacy thing that is only used in generation 2 and 4 mods for heal bell and aromatherapy respectively as such we can just ignore that
updateInstructions["|win|"] = this::handleWinInstruction
@@ -105,11 +136,9 @@ object ShowdownInterpreter {
updateInstructions["|-fail|"] = this::handleFailInstruction
updateInstructions["|-start|"] = this::handleStartInstructions
updateInstructions["|-block|"] = this::handleBlockInstructions
updateInstructions["|-activate|"] = this::handleActivateInstructions
updateInstructions["|-curestatus|"] = this::handleCureStatusInstruction
updateInstructions["|-fieldstart|"] = this::handleFieldStartInstructions
updateInstructions["|-fieldend|"] = this::handleFieldEndInstructions
updateInstructions["|-ability|"] = this::handleAbilityInstructions
updateInstructions["|-endability|"] = this::handleEndAbilityInstruction
updateInstructions["|-nothing"] = { battle, _, _ ->
battle.dispatchGo { battle.broadcastChatMessage(battleLang("nothing")) }
@@ -131,7 +160,6 @@ object ShowdownInterpreter {
updateInstructions["|-invertboost|"] = this::handleInvertBoostInstruction
updateInstructions["|-status|"] = this::handleStatusInstruction
updateInstructions["|-end|"] = this::handleEndInstruction
updateInstructions["|-miss|"] = this::handleMissInstruction
updateInstructions["|-hitcount|"] = this::handleHitCountInstruction
updateInstructions["|-item|"] = this::handleItemInstruction
updateInstructions["|-enditem|"] = this::handleEndItemInstruction
@@ -556,20 +584,6 @@ object ShowdownInterpreter {
}
}
/**
* Format:
* |-miss|SOURCE|TARGET
*
* The move used by the SOURCE Pokémon missed (maybe absent) the TARGET Pokémon.
*/
private fun handleMissInstruction(battle: PokemonBattle, message: BattleMessage, remainingLines: MutableList<String>) {
battle.dispatchWaiting(1.5F) {
val pokemon = message.getBattlePokemon(0, battle) ?: return@dispatchWaiting
battle.broadcastChatMessage(battleLang("missed").red())
battle.minorBattleActions[pokemon.uuid] = message
}
}
/**
* Format:
* |-immune|POKEMON
@@ -625,7 +639,7 @@ object ShowdownInterpreter {
val effectID = message.effectAt(1)?.id ?: return@dispatchWaiting
val name = pokemon.getName()
// Move may be null as it's not always given
val moveName = message.moveAt(2)?.displayName ?: run { println(message.argumentAt(2)); "(Unrecognized: ${message.argumentAt(2)})".text() }
val moveName = message.moveAt(2)?.displayName ?: run { "(Unrecognized: ${message.argumentAt(2)})".text() }
val lang = when (effectID) {
// TODO: in the games they use a generic image because there is a popup of the ability and the sprite of the mon, it may be good to have a similar system here
@@ -957,46 +971,6 @@ object ShowdownInterpreter {
}
}
/**
* Format:
* |-activate|POKEMON|EFFECT
*
* A miscellaneous effect has activated.This is triggered whenever an effect could not be better described by one of the other minor messages.
*/
private fun handleActivateInstructions(battle: PokemonBattle, message: BattleMessage, remainingLines: MutableList<String>){
val pokemon = message.getBattlePokemon(0, battle) ?: return
val pokemonName = pokemon.getName()
val sourceName = message.getSourceBattlePokemon(battle)?.getName() ?: Text.literal("UNKNOWN")
val effect = message.effectAt(1) ?: return
val extraEffect = message.effectAt(2)?.typelessData ?: Text.literal("UNKNOWN")
broadcastOptionalAbility(battle, effect, pokemonName)
battle.dispatch{
this.lastCauser[battle.battleId] = message
battle.minorBattleActions[pokemon.uuid] = message
val lang = when (effect.id) {
// Includes a 3rd argument being the magnitude level as a number
"magnitude" -> battleLang("activate.magnitude", message.argumentAt(2)?.toIntOrNull() ?: 1)
// Includes spited move and the PP it was reduced by
"spite", "eeriespell" -> battleLang("activate.spite", pokemonName, extraEffect, message.argumentAt(3)!!)
// Don't need additional lang, announced elsewhere
"toxicdebris", "shedskin" -> return@dispatch GO
// Add activation to each Pokemon's history
"destinybond" -> {
battle.activePokemon.mapNotNull { it.battlePokemon?.uuid }.forEach { battle.minorBattleActions[it] = message }
battleLang("activate.destinybond", pokemonName)
}
"focussash", "focusband" -> battleLang("activate.focusband", pokemonName, effect.typelessData)
"maxguard", "protect" -> battleLang("activate.protect", pokemonName)
"shadowforce", "hyperspacefury", "hyperspacehole" -> battleLang("activate.phantomforce", pokemonName)
else -> battleLang("activate.${effect.id}", pokemonName, sourceName, extraEffect)
}
battle.broadcastChatMessage(lang)
WaitDispatch(1F)
}
}
/**
* Format:
* |-fieldstart|CONDITION
@@ -1054,54 +1028,6 @@ object ShowdownInterpreter {
}
}
/**
* Format:
* |-ability|POKEMON|ABILITY|(from)EFFECT
*
* The ABILITY of the POKEMON has been changed due to a move/ability EFFECT.
*
* Format:
* |-ability|POKEMON|ABILITY
*
* POKEMON has just switched-in, and its ability ABILITY is being announced to have a long-term effect.
*/
private fun handleAbilityInstructions(battle: PokemonBattle, message: BattleMessage, remainingLines: MutableList<String>) {
val pokemon = message.getBattlePokemon(0, battle) ?: return
val pokemonName = pokemon.getName()
val effect = message.effectAt(1) ?: return
val optionalEffect = message.effect()
val optionalPokemon = message.getSourceBattlePokemon(battle)
val optionalPokemonName = optionalPokemon?.getName()
// If there is an optional effect causing the activation, broadcast that instead of the standard effect
if (optionalEffect != null) {
broadcastAbility(battle, optionalEffect, pokemonName)
} else {
broadcastAbility(battle, effect, pokemonName)
}
battle.dispatch {
this.lastCauser[battle.battleId] = message
val lang = when (optionalEffect?.id) {
"trace" -> optionalPokemonName?.let { battleLang("ability.trace", pokemonName, it, effect.typelessData) }
"receiver", "powerofalchemy" -> optionalPokemonName?.let { battleLang("ability.receiver", it, effect.typelessData) } // Receiver and Power of Alchemy share the same text
else -> when (effect.id) {
"sturdy", "unnerve", "anticipation" -> battleLang("ability.${effect.id}", pokemonName) // Unique message
"airlock", "cloudnine" -> battleLang("ability.airlock") // Cloud Nine shares the same text as Air Lock
else -> null // Effect broadcasted by a succeeding instruction
}
}
battle.minorBattleActions[pokemon.uuid] = message
if (lang != null) {
battle.broadcastChatMessage(lang)
return@dispatch WaitDispatch(1F)
}
else return@dispatch GO
}
}
fun broadcastOptionalAbility(battle: PokemonBattle, effect: Effect?, pokemonName: MutableText) {
if (effect != null && effect.type == Effect.Type.ABILITY)
broadcastAbility(battle, effect, pokemonName)
@@ -0,0 +1,11 @@
/*
* 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.battles.dispatch
interface CauserInstruction
@@ -9,9 +9,11 @@
package com.cobblemon.mod.common.battles.dispatch
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import org.objectweb.asm.tree.analysis.Interpreter
class InstructionSet {
val instructions: MutableList<InterpreterInstruction> = mutableListOf()
fun getSubsequentInstructions(instruction: InterpreterInstruction): List<InterpreterInstruction> {
val index = instructions.indexOf(instruction)
return instructions.subList(index + 1, instructions.size).toList()
@@ -22,6 +24,31 @@ class InstructionSet {
return instructions.subList(0, index).toList()
}
inline fun <reified T> getMostRecentInstruction(comparedTo: InterpreterInstruction, predicate: (T) -> Boolean = { true }): T? {
val index = instructions.indexOf(comparedTo)
return instructions.subList(0, index).filterIsInstance<T>().lastOrNull(predicate)
}
fun findInstructionsCausedBy(causerInstruction: CauserInstruction): List<InterpreterInstruction> {
causerInstruction as InterpreterInstruction
val thisCauseIndex = instructions.indexOf(causerInstruction)
if (thisCauseIndex == instructions.size - 1) {
return emptyList()
}
val nextCauseIndex = getNextInstruction<CauserInstruction>(causerInstruction)?.let { instructions.indexOf(it as InterpreterInstruction) }
return instructions.subList(thisCauseIndex + 1, nextCauseIndex ?: instructions.size)
}
inline fun <reified T> getNextInstruction(comparedTo: InterpreterInstruction, predicate: (T) -> Boolean = { true }): T? {
val index = instructions.indexOf(comparedTo)
if (instructions.last() == comparedTo) {
return null
}
return instructions.subList(index + 1, instructions.size).filterIsInstance<T>().firstOrNull(predicate)
}
fun getMostRecentCauser(comparedTo: InterpreterInstruction) = getMostRecentInstruction<CauserInstruction>(comparedTo)
fun execute(battle: PokemonBattle) {
instructions.forEach { it(battle) }
}
@@ -0,0 +1,69 @@
/*
* 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.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.CauserInstruction
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.util.battleLang
/**
* Format:
* |-ability|POKEMON|ABILITY|(from)EFFECT
*
* The ABILITY of the POKEMON has been changed due to a move/ability EFFECT.
*
* Format:
* |-ability|POKEMON|ABILITY
*
* POKEMON has just switched-in, and its ability ABILITY is being announced to have a long-term effect.
*/
class AbilityInstruction(val instructionSet: InstructionSet, val message: BattleMessage) : InterpreterInstruction, CauserInstruction {
override fun invoke(battle: PokemonBattle) {
val pokemon = message.getBattlePokemon(0, battle) ?: return
val pokemonName = pokemon.getName()
val effect = message.effectAt(1) ?: return
val optionalEffect = message.effect()
val optionalPokemon = message.getSourceBattlePokemon(battle)
val optionalPokemonName = optionalPokemon?.getName()
// If there is an optional effect causing the activation, broadcast that instead of the standard effect
if (optionalEffect != null) {
ShowdownInterpreter.broadcastAbility(battle, optionalEffect, pokemonName)
} else {
ShowdownInterpreter.broadcastAbility(battle, effect, pokemonName)
}
battle.dispatch {
ShowdownInterpreter.lastCauser[battle.battleId] = message
val lang = when (optionalEffect?.id) {
"trace" -> optionalPokemonName?.let { battleLang("ability.trace", pokemonName, it, effect.typelessData) }
"receiver", "powerofalchemy" -> optionalPokemonName?.let { battleLang("ability.receiver", it, effect.typelessData) } // Receiver and Power of Alchemy share the same text
else -> when (effect.id) {
"sturdy", "unnerve", "anticipation" -> battleLang("ability.${effect.id}", pokemonName) // Unique message
"airlock", "cloudnine" -> battleLang("ability.airlock") // Cloud Nine shares the same text as Air Lock
else -> null // Effect broadcasted by a succeeding instruction
}
}
battle.minorBattleActions[pokemon.uuid] = message
if (lang != null) {
battle.broadcastChatMessage(lang)
return@dispatch WaitDispatch(1F)
}
else return@dispatch GO
}
}
}
@@ -0,0 +1,62 @@
/*
* 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.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.CauserInstruction
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.util.battleLang
import net.minecraft.text.Text
/**
* Format:
* |-activate|POKEMON|EFFECT
*
* A miscellaneous effect has activated.This is triggered whenever an effect could not be better described by one of the other minor messages.
*/
class ActivateInstruction(val instructionSet: InstructionSet, val message: BattleMessage) : InterpreterInstruction, CauserInstruction {
override fun invoke(battle: PokemonBattle) {
val pokemon = message.getBattlePokemon(0, battle) ?: return
val pokemonName = pokemon.getName()
val sourceName = message.getSourceBattlePokemon(battle)?.getName() ?: Text.literal("UNKNOWN")
val effect = message.effectAt(1) ?: return
val extraEffect = message.effectAt(2)?.typelessData ?: Text.literal("UNKNOWN")
ShowdownInterpreter.broadcastOptionalAbility(battle, effect, pokemonName)
battle.dispatch{
ShowdownInterpreter.lastCauser[battle.battleId] = message
battle.minorBattleActions[pokemon.uuid] = message
val lang = when (effect.id) {
// Includes a 3rd argument being the magnitude level as a number
"magnitude" -> battleLang("activate.magnitude", message.argumentAt(2)?.toIntOrNull() ?: 1)
// Includes spited move and the PP it was reduced by
"spite", "eeriespell" -> battleLang("activate.spite", pokemonName, extraEffect, message.argumentAt(3)!!)
// Don't need additional lang, announced elsewhere
"toxicdebris", "shedskin" -> return@dispatch GO
// Add activation to each Pokemon's history
"destinybond" -> {
battle.activePokemon.mapNotNull { it.battlePokemon?.uuid }.forEach { battle.minorBattleActions[it] = message }
battleLang("activate.destinybond", pokemonName)
}
"focussash", "focusband" -> battleLang("activate.focusband", pokemonName, effect.typelessData)
"maxguard", "protect" -> battleLang("activate.protect", pokemonName)
"shadowforce", "hyperspacefury", "hyperspacehole" -> battleLang("activate.phantomforce", pokemonName)
else -> battleLang("activate.${effect.id}", pokemonName, sourceName, extraEffect)
}
battle.broadcastChatMessage(lang)
WaitDispatch(1F)
}
}
}
@@ -15,24 +15,34 @@ import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.net.messages.client.animation.PlayPoseableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
import com.cobblemon.mod.common.pokemon.evolution.progress.DamageTakenEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.progress.RecoilEvolutionProgress
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.lang
import java.util.concurrent.CompletableFuture
import kotlin.math.roundToInt
import net.minecraft.text.Text
class DamageInstruction(
val instructionSet: InstructionSet,
val actor: BattleActor,
val publicMessage: BattleMessage,
val privateMessage: BattleMessage
) : InterpreterInstruction {
var future = CompletableFuture.completedFuture(Unit)
val battlePokemon = publicMessage.getBattlePokemon(0, actor.battle)
override fun invoke(battle: PokemonBattle) {
val battlePokemon = publicMessage.getBattlePokemon(0, battle) ?: return
if (privateMessage.optionalArgument("from")?.equals("recoil", true) == true) {
battlePokemon ?: return
val recoiling = privateMessage.optionalArgument("from")?.equals("recoil", true) == true
val lastCauser = instructionSet.getMostRecentCauser(comparedTo = this)
if (recoiling) {
battlePokemon.effectedPokemon.let { pokemon ->
if (RecoilEvolutionProgress.supports(pokemon)) {
val newPercentage = privateMessage.argumentAt(1)?.split("/")?.getOrNull(0)?.toIntOrNull() ?: 0
@@ -48,10 +58,34 @@ class DamageInstruction(
val newHealth = privateMessage.argumentAt(1)?.split(" ")?.get(0) ?: return
val effect = privateMessage.effect()
val pokemonName = battlePokemon.getName()
val sourceName = privateMessage.getSourceBattlePokemon(battle)?.getName() ?: Text.literal("UNKOWN")
ShowdownInterpreter.broadcastOptionalAbility(battle, effect, sourceName)
val sourceName = privateMessage.getSourceBattlePokemon(battle)?.getName() ?: Text.literal("UNKNOWN")
var causedFaint = newHealth == "0"
battle.dispatch {
if (lastCauser is MoveInstruction && recoiling) {
UntilDispatch { "recoil" !in lastCauser.holds }
} else {
GO
}
}
battle.dispatch {
if (!causedFaint) {
battlePokemon.entity?.let { pokemonEntity ->
val pkt = PlayPoseableAnimationPacket(pokemonEntity.id, setOf("recoil"), emptySet())
pkt.sendToPlayersAround(
x = pokemonEntity.x,
y = pokemonEntity.y,
z = pokemonEntity.z,
worldKey = pokemonEntity.world.registryKey,
distance = 50.0
)
}
}
ShowdownInterpreter.broadcastOptionalAbility(battle, effect, sourceName)
val newHealthRatio: Float
val remainingHealth = newHealth.split("/")[0].toInt()
@@ -70,8 +104,7 @@ class DamageInstruction(
battle.broadcastChatMessage(lang.red())
}
var causedFaint = false
if (newHealth == "0") {
if (causedFaint) {
newHealthRatio = 0F
battle.dispatch {
battlePokemon.effectedPokemon.currentHealth = 0
@@ -102,7 +135,9 @@ class DamageInstruction(
battle.minorBattleActions[battlePokemon.uuid] = privateMessage
// If they faint from this damage then don't bother waiting for the damage to be applied.
if (causedFaint) {
if (lastCauser is MoveInstruction && lastCauser.actionEffect != null && !causedFaint) {
UntilDispatch { lastCauser.future.isDone }
} else if (causedFaint) {
GO
} else {
WaitDispatch(1F)
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format:
* |-miss|SOURCE|TARGET
*
* The move used by the SOURCE Pokémon missed (maybe absent) the TARGET Pokémon.
*/
class MissInstruction(val battle: PokemonBattle, val instructionSet: InstructionSet, val message: BattleMessage): InterpreterInstruction {
val target = message.getBattlePokemon(1, battle)
override fun invoke(battle: PokemonBattle) {
battle.dispatchGo {
val pokemon = message.getBattlePokemon(0, battle) ?: return@dispatchGo
battle.broadcastChatMessage(battleLang("missed").red())
battle.minorBattleActions[pokemon.uuid] = message
}
}
}
@@ -8,30 +8,55 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.interpreter.Effect
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunction
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.TargetsProvider
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.CauserInstruction
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.pokemon.evolution.progress.UseMoveEvolutionProgress
import com.cobblemon.mod.common.util.battleLang
import java.util.concurrent.CompletableFuture
class MoveInstruction(
val instructionSet: InstructionSet,
val message: BattleMessage
) : InterpreterInstruction {
) : InterpreterInstruction, CauserInstruction {
val effect = message.effectAt(1) ?: Effect.pure("", "")
val move = Moves.getByNameOrDummy(effect.id)
val actionEffect = move.actionEffect
var future = CompletableFuture.completedFuture(Unit)
var holds = mutableSetOf<String>()
lateinit var userPokemon: BattlePokemon
var targetPokemon: BattlePokemon? = null
override fun invoke(battle: PokemonBattle) {
val userPokemon = message.getBattlePokemon(0, battle) ?: return
val targetPokemon = message.getBattlePokemon(2, battle)
val effect = message.effectAt(1) ?: return
userPokemon = message.getBattlePokemon(0, battle)!!
targetPokemon = message.getBattlePokemon(2, battle)
val targetPokemon = targetPokemon // So smart non-null casts can happen
val optionalEffect = message.effect()
val move = Moves.getByNameOrDummy(effect.id)
val pokemonName = userPokemon.getName()
ShowdownInterpreter.broadcastOptionalAbility(battle, optionalEffect, pokemonName)
battle.dispatch { UntilDispatch { instructionSet.getMostRecentInstruction<MoveInstruction>(this)?.future?.isDone != false } }
battle.dispatchGo {
battle.dispatch {
ShowdownInterpreter.lastCauser[battle.battleId] = message
userPokemon.effectedPokemon.let { pokemon ->
@@ -50,8 +75,50 @@ class MoveInstruction(
battleLang("used_move", pokemonName, move.displayName)
}
battle.broadcastChatMessage(lang)
battle.majorBattleActions[userPokemon.uuid] = message
val providers = mutableListOf<Any>(battle)
userPokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
targetPokemon?.effectedPokemon?.entity?.let { TargetsProvider(it) }?.let(providers::add)
val runtime = MoLangRuntime().also {
battle.addQueryFunctions(it.environment.getQueryStruct()).addStandardFunctions()
}
actionEffect ?: return@dispatch GO
val context = ActionEffectContext(
actionEffect = actionEffect,
runtime = runtime,
providers = providers
)
val subsequentInstructions = instructionSet.findInstructionsCausedBy(this)
val missedTargets = subsequentInstructions.filterIsInstance<MissInstruction>().mapNotNull { it.target }
runtime.environment.getQueryStruct().addFunction("missed") { params ->
if (params.params.size == 0) {
return@addFunction DoubleValue(missedTargets.isNotEmpty())
} else {
val entityUUID = params.getString(0)
return@addFunction DoubleValue(missedTargets.any { it.entity?.uuidAsString == entityUUID })
}
}
val hurtTargets = subsequentInstructions.filterIsInstance<DamageInstruction>().mapNotNull { it.battlePokemon }
runtime.environment.getQueryStruct().addFunction("hurt") { params ->
if (params.params.size == 0) {
return@addFunction DoubleValue(hurtTargets.isNotEmpty())
} else {
val entityUUID = params.getString(0)
return@addFunction DoubleValue(hurtTargets.any { it.entity?.uuidAsString == entityUUID })
}
}
runtime.environment.getQueryStruct().addFunction("move") { move.struct }
this.future = actionEffect.run(context)
holds = context.holds // Reference so future things can check on this action effect's holds
future.thenApply { holds.clear() }
return@dispatch UntilDispatch { "effects" !in holds }
}
}
}
@@ -6,12 +6,12 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.fossilmachine
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.api.multiblock.MultiblockBlock
import com.cobblemon.mod.common.block.entity.fossil.FossilCompartmentBlockEntity
import com.cobblemon.mod.common.block.entity.fossil.FossilMultiblockEntity
import com.cobblemon.mod.common.block.entity.FossilAnalyzerBlockEntity
import com.cobblemon.mod.common.block.entity.FossilMultiblockEntity
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockStructure
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockBuilder
import net.minecraft.block.Block
@@ -30,7 +30,7 @@ import net.minecraft.util.math.Direction
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
class FossilCompartmentBlock(properties: Settings) : MultiblockBlock(properties), InventoryProvider {
class FossilAnalyzerBlock(properties: Settings) : MultiblockBlock(properties), InventoryProvider {
init {
defaultState = defaultState
.with(HorizontalFacingBlock.FACING, Direction.NORTH)
@@ -38,14 +38,14 @@ class FossilCompartmentBlock(properties: Settings) : MultiblockBlock(properties)
}
override fun createMultiBlockEntity(pos: BlockPos, state: BlockState): FossilMultiblockEntity {
return FossilCompartmentBlockEntity(pos, state, FossilMultiblockBuilder(pos))
return FossilAnalyzerBlockEntity(pos, state, FossilMultiblockBuilder(pos))
}
override fun <T : BlockEntity?> getTicker(
world: World?,
state: BlockState?,
type: BlockEntityType<T>?
): BlockEntityTicker<T>? = checkType(type, CobblemonBlockEntities.FOSSIL_COMPARTMENT, FossilMultiblockStructure.TICKER::tick)
): BlockEntityTicker<T>? = checkType(type, CobblemonBlockEntities.FOSSIL_ANALYZER, FossilMultiblockStructure.TICKER::tick)
override fun getPlacementState(blockPlaceContext: ItemPlacementContext): BlockState? {
return defaultState.with(HorizontalFacingBlock.FACING, blockPlaceContext.horizontalPlayerFacing)
@@ -56,9 +56,9 @@ class FossilCompartmentBlock(properties: Settings) : MultiblockBlock(properties)
world: WorldAccess,
pos: BlockPos
): SidedInventory {
val compartmentEntity = world.getBlockEntity(pos) as FossilCompartmentBlockEntity
val analyzerEntity = world.getBlockEntity(pos) as FossilAnalyzerBlockEntity
return compartmentEntity.inv
return analyzerEntity.inv
}
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
@@ -0,0 +1,156 @@
/*
* 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 net.minecraft.block.*
import net.minecraft.item.ItemPlacementContext
import net.minecraft.server.world.ServerWorld
import net.minecraft.state.StateManager
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.util.math.random.Random
import net.minecraft.util.shape.VoxelShape
import net.minecraft.world.BlockView
import net.minecraft.world.WorldAccess
import net.minecraft.world.WorldView
/**
* Used for blocks that grow similarly to Amethyst.
*
* @param settings Block settings
* @param stage The stage of growth of the block
* @param height The height of this block's hitbox
* @param nextStage The next stage of this block's growth. Null if final stage
*
* @author whatsy
*/
abstract class GrowableStoneBlock(
settings: Settings,
val stage: Int,
val height: Int,
xzOffset: Int,
val nextStage: Block?
) : FacingBlock(settings) {
private val upShape: VoxelShape = Block.createCuboidShape(
xzOffset.toDouble(),
0.0,
xzOffset.toDouble(),
(16 - xzOffset).toDouble(),
height.toDouble(),
(16 - xzOffset).toDouble())
private val downShape: VoxelShape = createCuboidShape(
xzOffset.toDouble(),
(16 - height).toDouble(),
xzOffset.toDouble(),
(16 - xzOffset).toDouble(),
16.0,
(16 - xzOffset).toDouble())
private val northShape: VoxelShape = createCuboidShape(
xzOffset.toDouble(),
xzOffset.toDouble(),
(16 - height).toDouble(),
(16 - xzOffset).toDouble(),
(16 - xzOffset).toDouble(),
16.0)
private val southShape: VoxelShape = createCuboidShape(
xzOffset.toDouble(),
xzOffset.toDouble(),
0.0,
(16 - xzOffset).toDouble(),
(16 - xzOffset).toDouble(),
height.toDouble())
private val eastShape: VoxelShape = createCuboidShape(
0.0,
xzOffset.toDouble(),
xzOffset.toDouble(),
height.toDouble(),
(16 - xzOffset).toDouble(),
(16 - xzOffset).toDouble())
private val westShape: VoxelShape = createCuboidShape(
(16 - height).toDouble(),
xzOffset.toDouble(),
xzOffset.toDouble(),
16.0,
(16 - xzOffset).toDouble(),
(16 - xzOffset).toDouble())
init {
this.defaultState = this.stateManager.defaultState
.with(FACING, Direction.DOWN)
}
abstract fun canGrow(pos: BlockPos, world: BlockView): Boolean
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
builder.add(FACING)
}
override fun hasRandomTicks(state: BlockState?) = stage < MAX_STAGE
override fun randomTick(state: BlockState, world: ServerWorld, pos: BlockPos, random: Random) {
if (world.random.nextInt(5) == 0 && canGrow(pos, world)) {
val block = nextStage
if (block != null) {
val newState = block.defaultState.with(FACING, state.get(FACING)) as BlockState
world.setBlockState(pos, newState)
}
}
}
override fun getPlacementState(ctx: ItemPlacementContext): BlockState? {
var blockState = defaultState
val worldView = ctx.world
val blockPos = ctx.blockPos
blockState = blockState.with(FACING, ctx.side) as BlockState
if (blockState.canPlaceAt(worldView, blockPos)) {
return blockState
}
return null
}
override fun canPlaceAt(state: BlockState, world: WorldView, pos: BlockPos): Boolean {
val direction = state.get(FACING) as Direction
val blockState = world.getBlockState(pos.offset(direction.opposite))
return blockState.isSideSolidFullSquare(world, pos, direction)
}
override fun getStateForNeighborUpdate(state: BlockState, direction: Direction, neighborState: BlockState, world: WorldAccess, pos: BlockPos, neighborPos: BlockPos): BlockState? {
return if (direction == state.get(FACING).opposite && !state.canPlaceAt(world, pos)) Blocks.AIR.defaultState
else super.getStateForNeighborUpdate(state, direction, neighborState, world, pos, neighborPos)
}
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape {
val direction = state.get(FACING) as Direction
return when (direction) {
Direction.NORTH -> northShape
Direction.SOUTH -> southShape
Direction.EAST -> eastShape
Direction.WEST -> westShape
Direction.DOWN -> downShape
Direction.UP -> upShape
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
}
}
@@ -6,19 +6,21 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.fossilmachine
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.block.entity.fossil.FossilMultiblockEntity
import com.cobblemon.mod.common.api.multiblock.MultiblockBlock
import com.cobblemon.mod.common.api.multiblock.MultiblockEntity
import com.cobblemon.mod.common.block.entity.FossilMultiblockEntity
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockBuilder
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.HorizontalFacingBlock
import net.minecraft.block.ShapeContext
import net.minecraft.entity.ai.pathing.NavigationType
import net.minecraft.item.ItemPlacementContext
import net.minecraft.state.StateManager
import net.minecraft.state.property.IntProperty
import net.minecraft.state.property.EnumProperty
import net.minecraft.util.StringIdentifiable
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.util.shape.VoxelShape
@@ -26,7 +28,7 @@ import net.minecraft.util.shape.VoxelShapes
import net.minecraft.world.BlockView
import net.minecraft.world.World
class FossilMonitorBlock(properties: Settings) : MultiblockBlock(properties) {
class MonitorBlock(properties: Settings) : MultiblockBlock(properties) {
init {
defaultState = defaultState.with(HorizontalFacingBlock.FACING, Direction.NORTH)
}
@@ -46,7 +48,7 @@ class FossilMonitorBlock(properties: Settings) : MultiblockBlock(properties) {
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
builder.add(HorizontalFacingBlock.FACING)
builder.add(PROGRESS)
builder.add(SCREEN)
}
@Deprecated("Deprecated in Java")
@@ -76,10 +78,14 @@ class FossilMonitorBlock(properties: Settings) : MultiblockBlock(properties) {
): VoxelShape {
return HITBOX
}
@Deprecated("Deprecated in Java")
override fun canPathfindThrough(state: BlockState?, world: BlockView?, pos: BlockPos?, type: NavigationType?): Boolean {
return false
}
companion object {
//0 is off
val PROGRESS = IntProperty.of("progress", 0, 9)
val SCREEN = EnumProperty.of("screen", MonitorScreen::class.java)
val HITBOX = VoxelShapes.union(
VoxelShapes.cuboid(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375),
VoxelShapes.cuboid(0.0625, 0.875, 0.0625, 0.9375, 1.0, 0.9375),
@@ -88,4 +94,19 @@ class FossilMonitorBlock(properties: Settings) : MultiblockBlock(properties) {
VoxelShapes.cuboid(0.0625, 0.375, 0.0625, 0.1875, 0.875, 0.9375)
)
}
}
enum class MonitorScreen : StringIdentifiable {
OFF,
BLUE_PROGRESS_1,
BLUE_PROGRESS_2,
BLUE_PROGRESS_3,
BLUE_PROGRESS_4,
BLUE_PROGRESS_5,
BLUE_PROGRESS_6,
BLUE_PROGRESS_7,
BLUE_PROGRESS_8,
BLUE_PROGRESS_9,
GREEN_PROGRESS_9;
override fun asString(): String = this.name.lowercase()
}
}
@@ -6,13 +6,13 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.fossilmachine
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.api.multiblock.MultiblockBlock
import com.cobblemon.mod.common.api.multiblock.MultiblockEntity
import com.cobblemon.mod.common.block.entity.fossil.FossilMultiblockEntity
import com.cobblemon.mod.common.block.entity.fossil.FossilTubeBlockEntity
import com.cobblemon.mod.common.block.entity.FossilMultiblockEntity
import com.cobblemon.mod.common.block.entity.RestorationTankBlockEntity
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockBuilder
import net.minecraft.block.*
import net.minecraft.entity.LivingEntity
@@ -22,6 +22,7 @@ import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemStack
import net.minecraft.server.world.ServerWorld
import net.minecraft.state.StateManager
import net.minecraft.state.property.BooleanProperty
import net.minecraft.state.property.EnumProperty
import net.minecraft.state.property.Properties
import net.minecraft.util.ActionResult
@@ -39,17 +40,19 @@ import net.minecraft.world.WorldAccess
import net.minecraft.world.WorldView
class FossilTubeBlock(properties: Settings) : MultiblockBlock(properties), InventoryProvider {
class RestorationTankBlock(properties: Settings) : MultiblockBlock(properties), InventoryProvider {
init {
defaultState = defaultState
.with(HorizontalFacingBlock.FACING, Direction.NORTH)
.with(PART, TubePart.BOTTOM)
.with(PART, TankPart.BOTTOM)
.with(TRIGGERED, false)
.with(ON, false)
}
fun getPositionOfOtherPart(state: BlockState, pos: BlockPos): BlockPos {
return if (state.get(PART) == TubePart.BOTTOM) {
return if (state.get(PART) == TankPart.BOTTOM) {
pos.up()
} else {
pos.down()
@@ -64,12 +67,12 @@ class FossilTubeBlock(properties: Settings) : MultiblockBlock(properties), Inven
}
}
private fun isBase(state: BlockState): Boolean = state.get(PART) == TubePart.BOTTOM
private fun isBase(state: BlockState): Boolean = state.get(PART) == TankPart.BOTTOM
override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity?) {
super.onBreak(world, pos, state, player)
val otherPart = world.getBlockState(getPositionOfOtherPart(state, pos))
if (otherPart.block is FossilTubeBlock) {
if (otherPart.block is RestorationTankBlock) {
world.setBlockState(getPositionOfOtherPart(state, pos), Blocks.AIR.defaultState, 35)
world.syncWorldEvent(player, 2001, getPositionOfOtherPart(state, pos), getRawIdFromState(otherPart))
}
@@ -83,7 +86,7 @@ class FossilTubeBlock(properties: Settings) : MultiblockBlock(properties), Inven
itemStack: ItemStack?
) {
//Place the full block before we call to super to validate the multiblock
world.setBlockState(pos.up(), state.with(PART, TubePart.TOP) as BlockState, 3)
world.setBlockState(pos.up(), state.with(PART, TankPart.TOP) as BlockState, 3)
world.updateNeighbors(pos, Blocks.AIR)
state.updateNeighbors(world, pos, 3)
super.onPlaced(world, pos, state, placer, itemStack)
@@ -91,20 +94,20 @@ class FossilTubeBlock(properties: Settings) : MultiblockBlock(properties), Inven
@Deprecated("Deprecated in Java")
override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hit: BlockHitResult): ActionResult {
if(state.get(PART) == TubePart.TOP) {
// TubeTop isn't reliably up to date on clients, need to look at the bottom half
val tubeBottomPos = pos.down()
val tubeBottomState = world.getBlockState(tubeBottomPos)
if(tubeBottomState.block.equals(CobblemonBlocks.FOSSIL_TUBE.asBlock()) && tubeBottomState.get(PART) == TubePart.BOTTOM) {
return super.onUse(tubeBottomState, world, tubeBottomPos, player, hand, hit)
if(state.get(PART) == TankPart.TOP) {
// TankTop isn't reliably up to date on clients, need to look at the bottom half
val tankBottomPos = pos.down()
val tankBottomState = world.getBlockState(tankBottomPos)
if(tankBottomState.block.equals(CobblemonBlocks.RESTORATION_TANK.asBlock()) && tankBottomState.get(PART) == TankPart.BOTTOM) {
return super.onUse(tankBottomState, world, tankBottomPos, player, hand, hit)
}
}
return super.onUse(state, world, pos, player, hand, hit)
}
override fun createMultiBlockEntity(pos: BlockPos, state: BlockState): FossilMultiblockEntity {
return if (state.get(PART) == TubePart.BOTTOM) {
FossilTubeBlockEntity(pos, state, FossilMultiblockBuilder(pos))
return if (state.get(PART) == TankPart.BOTTOM) {
RestorationTankBlockEntity(pos, state, FossilMultiblockBuilder(pos))
} else {
FossilMultiblockEntity(pos, state, FossilMultiblockBuilder(pos))
}
@@ -116,7 +119,7 @@ class FossilTubeBlock(properties: Settings) : MultiblockBlock(properties), Inven
if (world.getBlockState(abovePosition).canReplace(blockPlaceContext) && !world.isOutOfHeightLimit(abovePosition)) {
return defaultState
.with(HorizontalFacingBlock.FACING, blockPlaceContext.horizontalPlayerFacing)
.with(PART, TubePart.BOTTOM)
.with(PART, TankPart.BOTTOM)
}
return null
@@ -125,13 +128,13 @@ class FossilTubeBlock(properties: Settings) : MultiblockBlock(properties), Inven
override fun canPlaceAt(state: BlockState, world: WorldView, pos: BlockPos): Boolean {
val blockPos = pos.down()
val blockState = world.getBlockState(blockPos)
return if (state.get(PART) == TubePart.BOTTOM) blockState.isSideSolidFullSquare(world, blockPos, Direction.UP) else blockState.isOf(this)
return if (state.get(PART) == TankPart.BOTTOM) true else blockState.isOf(this)
}
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
builder.add(HorizontalFacingBlock.FACING)
builder.add(PART)
builder.add(TRIGGERED)
builder.add(ON)
}
@Deprecated("Deprecated in Java")
@@ -145,8 +148,8 @@ class FossilTubeBlock(properties: Settings) : MultiblockBlock(properties), Inven
if(world == null || pos == null) {
return 0
}
val tubeEntity = world.getBlockEntity(pos) as MultiblockEntity
val multiBlockEntity = tubeEntity.multiblockStructure
val tankEntity = world.getBlockEntity(pos) as MultiblockEntity
val multiBlockEntity = tankEntity.multiblockStructure
if(multiBlockEntity != null) {
return multiBlockEntity.getComparatorOutput(state, world, pos)
}
@@ -175,10 +178,8 @@ class FossilTubeBlock(properties: Settings) : MultiblockBlock(properties), Inven
if(world == null || pos == null) {
return
}
val tubeEntity = world.getBlockEntity(pos) as MultiblockEntity
if (tubeEntity.multiblockStructure != null) {
tubeEntity.multiblockStructure!!.onTriggerEvent(state, world, pos, random)
}
val tankEntity = world.getBlockEntity(pos) as? MultiblockEntity
tankEntity?.multiblockStructure!!.onTriggerEvent(state, world, pos, random)
}
override fun getOutlineShape(
@@ -187,7 +188,7 @@ class FossilTubeBlock(properties: Settings) : MultiblockBlock(properties), Inven
pos: BlockPos?,
context: ShapeContext?
): VoxelShape {
return if (state.get(PART) == TubePart.TOP) {
return if (state.get(PART) == TankPart.TOP) {
var shape = VoxelShapes.cuboid(0.0625, 0.0, 0.0625, 0.9375, 0.8125, 0.9375)
shape = VoxelShapes.union(shape, VoxelShapes.cuboid(0.0, 0.8125, 0.0, 1.0, 1.0, 1.0))
shape
@@ -202,21 +203,22 @@ class FossilTubeBlock(properties: Settings) : MultiblockBlock(properties), Inven
world: WorldAccess,
pos: BlockPos
): SidedInventory {
val tubeEntity =
(if (state.get(PART) == TubePart.TOP) world.getBlockEntity(pos.down())
else world.getBlockEntity(pos)) as FossilTubeBlockEntity
val tankEntity =
(if (state.get(PART) == TankPart.TOP) world.getBlockEntity(pos.down())
else world.getBlockEntity(pos)) as RestorationTankBlockEntity
return tubeEntity.inv
return tankEntity.inv
}
enum class TubePart(private val label: String) : StringIdentifiable {
enum class TankPart(private val label: String) : StringIdentifiable {
TOP("top"),
BOTTOM("bottom");
override fun asString() = label
}
companion object {
val PART = EnumProperty.of("part", TubePart::class.java)
val PART = EnumProperty.of("part", TankPart::class.java)
val TRIGGERED = Properties.TRIGGERED
val ON = BooleanProperty.of("on")
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import net.minecraft.block.*
import net.minecraft.item.ItemPlacementContext
import net.minecraft.server.world.ServerWorld
@@ -20,125 +21,15 @@ import net.minecraft.world.BlockView
import net.minecraft.world.WorldAccess
import net.minecraft.world.WorldView
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
class TumblestoneBlock(
settings: Settings,
val stage: Int,
val height: Int,
stage: Int,
height: Int,
xzOffset: Int,
val nextStage: Block?
) : FacingBlock(settings) {
private val upShape: VoxelShape = Block.createCuboidShape(
xzOffset.toDouble(),
0.0,
xzOffset.toDouble(),
(16 - xzOffset).toDouble(),
height.toDouble(),
(16 - xzOffset).toDouble())
private val downShape: VoxelShape = createCuboidShape(
xzOffset.toDouble(),
(16 - height).toDouble(),
xzOffset.toDouble(),
(16 - xzOffset).toDouble(),
16.0,
(16 - xzOffset).toDouble())
private val northShape: VoxelShape = createCuboidShape(
xzOffset.toDouble(),
xzOffset.toDouble(),
(16 - height).toDouble(),
(16 - xzOffset).toDouble(),
(16 - xzOffset).toDouble(),
16.0)
private val southShape: VoxelShape = createCuboidShape(
xzOffset.toDouble(),
xzOffset.toDouble(),
0.0,
(16 - xzOffset).toDouble(),
(16 - xzOffset).toDouble(),
height.toDouble())
private val eastShape: VoxelShape = createCuboidShape(
0.0,
xzOffset.toDouble(),
xzOffset.toDouble(),
height.toDouble(),
(16 - xzOffset).toDouble(),
(16 - xzOffset).toDouble())
private val westShape: VoxelShape = createCuboidShape(
(16 - height).toDouble(),
xzOffset.toDouble(),
xzOffset.toDouble(),
16.0,
(16 - xzOffset).toDouble(),
(16 - xzOffset).toDouble())
init {
this.defaultState = this.stateManager.defaultState
.with(FACING, Direction.DOWN)
}
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
builder.add(FACING)
}
override fun hasRandomTicks(state: BlockState?) = stage < MAX_STAGE
override fun randomTick(state: BlockState, world: ServerWorld, pos: BlockPos, random: Random) {
if (world.random.nextInt(5) == 0 && canGrow(pos, world)) {
val block = nextStage
if (block != null) {
val newState = block.defaultState.with(FACING, state.get(FACING)) as BlockState
world.setBlockState(pos, newState)
}
}
}
override fun getPlacementState(ctx: ItemPlacementContext): BlockState? {
var blockState = defaultState
val worldView = ctx.world
val blockPos = ctx.blockPos
blockState = blockState.with(FACING, ctx.side) as BlockState
if (blockState.canPlaceAt(worldView, blockPos)) {
return blockState
}
return null
}
override fun canPlaceAt(state: BlockState, world: WorldView, pos: BlockPos): Boolean {
val direction = state.get(FACING) as Direction
val blockState = world.getBlockState(pos.offset(direction.opposite))
return blockState.isSideSolidFullSquare(world, pos, direction)
}
override fun getStateForNeighborUpdate(state: BlockState, direction: Direction, neighborState: BlockState, world: WorldAccess, pos: BlockPos, neighborPos: BlockPos): BlockState? {
return if (direction == state.get(FACING).opposite && !state.canPlaceAt(world, pos)) Blocks.AIR.defaultState
else super.getStateForNeighborUpdate(state, direction, neighborState, world, pos, neighborPos)
}
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape {
val direction = state.get(FACING) as Direction
return when (direction) {
Direction.NORTH -> northShape
Direction.SOUTH -> southShape
Direction.EAST -> eastShape
Direction.WEST -> westShape
Direction.DOWN -> downShape
Direction.UP -> upShape
else -> upShape
}
}
private fun isValidGrowthBlock(block: Block): Boolean {
return block == Blocks.MAGMA_BLOCK || block == Blocks.LAVA
}
private fun canGrow(pos: BlockPos, world: BlockView): Boolean {
nextStage: Block?
) : GrowableStoneBlock(settings, stage, height, xzOffset, nextStage) {
override fun canGrow(pos: BlockPos, world: BlockView): Boolean {
if (stage == MAX_STAGE) return false
val iterator: Iterator<BlockPos> =
BlockPos.iterate(pos.add(-1, -1, -1), pos.add(1, 1, 1))
@@ -147,18 +38,9 @@ class TumblestoneBlock(
var blockPos: BlockPos
do { if (!iterator.hasNext()) { return false }
blockPos = iterator.next()
} while (!isValidGrowthBlock(world.getBlockState(blockPos).block))
} while (!world.getBlockState(blockPos).isIn(CobblemonBlockTags.TUMBLESTONE_HEAT_SOURCE))
return true
}
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
}
}
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity.fossil
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonBlockEntities
@@ -20,14 +20,14 @@ import net.minecraft.item.ItemStack
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
class FossilCompartmentBlockEntity(
class FossilAnalyzerBlockEntity(
pos: BlockPos, state: BlockState,
multiblockBuilder: MultiblockStructureBuilder
): FossilMultiblockEntity(pos, state, multiblockBuilder, CobblemonBlockEntities.FOSSIL_COMPARTMENT) {
): FossilMultiblockEntity(pos, state, multiblockBuilder, CobblemonBlockEntities.FOSSIL_ANALYZER) {
val inv = FossilCompartmentInventory(this)
val inv = FossilAnalyzerInventory(this)
class FossilCompartmentInventory(val compartmentEntity: FossilCompartmentBlockEntity) : SidedInventory {
class FossilAnalyzerInventory(val analyzerEntity: FossilAnalyzerBlockEntity) : SidedInventory {
override fun clear() {
TODO("Not yet implemented")
}
@@ -53,17 +53,17 @@ class FossilCompartmentBlockEntity(
}
override fun setStack(slot: Int, stack: ItemStack) {
if (compartmentEntity.multiblockStructure != null) {
val struct = compartmentEntity.multiblockStructure as FossilMultiblockStructure
compartmentEntity.world?.let {
if (analyzerEntity.multiblockStructure != null) {
val struct = analyzerEntity.multiblockStructure as FossilMultiblockStructure
analyzerEntity.world?.let {
struct.insertFossil(stack, it)
}
}
}
override fun markDirty() {
if (compartmentEntity.world != null) {
compartmentEntity.multiblockStructure?.markDirty(compartmentEntity.world!!)
if (analyzerEntity.world != null) {
analyzerEntity.multiblockStructure?.markDirty(analyzerEntity.world!!)
}
}
@@ -76,8 +76,8 @@ class FossilCompartmentBlockEntity(
}
override fun canInsert(slot: Int, stack: ItemStack?, dir: Direction?): Boolean {
if (compartmentEntity.multiblockStructure is FossilMultiblockStructure) {
val structure = compartmentEntity.multiblockStructure as FossilMultiblockStructure
if (analyzerEntity.multiblockStructure is FossilMultiblockStructure) {
val structure = analyzerEntity.multiblockStructure as FossilMultiblockStructure
return stack?.let { Fossils.isFossilIngredient(it) } == true
&& structure.fossilInventory.size < Cobblemon.config.maxInsertedFossilItems
&& structure.isRunning() == false && structure.resultingFossil == null
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity.fossil
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.api.multiblock.MultiblockEntity
@@ -47,9 +47,25 @@ open class FossilMultiblockEntity(
}
}
override fun markRemoved() {
super.markRemoved()
if(this.multiblockStructure != null && world != null) {
this.multiblockStructure!!.markRemoved(world!!)
}
}
override fun readNbt(nbt: NbtCompound) {
val oldMultiblockStructure = this.multiblockStructure as? FossilMultiblockStructure
multiblockStructure = if (nbt.contains(DataKeys.MULTIBLOCK_STORAGE)) {
FossilMultiblockStructure.fromNbt(nbt.getCompound(DataKeys.MULTIBLOCK_STORAGE))
if(oldMultiblockStructure?.fossilState != null) {
// Copy the fossilState's previous animation time to the new instance
// Otherwise the fetus animation gets interrupted on every block update
val animAge = oldMultiblockStructure.fossilState.peekAge() // If someone knows a better way to fetch the age, please do.
val partialTicks = oldMultiblockStructure.fossilState.getPartialTicks()
FossilMultiblockStructure.fromNbt(nbt.getCompound(DataKeys.MULTIBLOCK_STORAGE), animAge, partialTicks )
} else {
FossilMultiblockStructure.fromNbt(nbt.getCompound(DataKeys.MULTIBLOCK_STORAGE))
}
} else {
null
}
@@ -0,0 +1,152 @@
/*
* 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.api.fossil.NaturalMaterials
import com.cobblemon.mod.common.api.multiblock.builder.MultiblockStructureBuilder
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockStructure
import net.minecraft.block.BlockState
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.inventory.Inventories
import net.minecraft.inventory.SidedInventory
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound
import net.minecraft.registry.Registries
import net.minecraft.util.collection.DefaultedList
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
class RestorationTankBlockEntity(
pos: BlockPos, state: BlockState,
multiblockBuilder: MultiblockStructureBuilder
) : FossilMultiblockEntity(pos, state, multiblockBuilder, CobblemonBlockEntities.RESTORATION_TANK) {
val inv = RestorationTankInventory(this)
override fun writeNbt(nbt: NbtCompound) {
super.writeNbt(nbt)
Inventories.writeNbt(nbt, inv.items)
}
override fun readNbt(nbt: NbtCompound) {
super.readNbt(nbt)
Inventories.readNbt(nbt, inv.items)
}
class RestorationTankInventory(val tankEntity: RestorationTankBlockEntity) : SidedInventory {
val items: DefaultedList<ItemStack> = DefaultedList.ofSize(8, ItemStack.EMPTY)
override fun clear() {
for (i in items.indices) {
items[i] = ItemStack.EMPTY
}
}
override fun size(): Int {
return 8
}
override fun isEmpty(): Boolean {
return items.all { it == ItemStack.EMPTY }
}
override fun getStack(slot: Int): ItemStack {
if(slot == -1 || slot >= size()) {
return ItemStack.EMPTY
}
return items[slot]
}
override fun removeStack(slot: Int, amount: Int): ItemStack {
if(slot < 0 || slot >= size()) {
return ItemStack.EMPTY
}
val result = items[slot].split(amount)
if(items[slot].count == 0) {
items[slot] = ItemStack.EMPTY
}
return result
}
override fun removeStack(slot: Int): ItemStack {
return removeStack(slot, 1)
}
override fun setStack(slot: Int, stack: ItemStack) {
val struct = tankEntity.multiblockStructure as? FossilMultiblockStructure
tankEntity.world?.let {
struct?.insertOrganicMaterial(stack, it)
val returnIdentifier = NaturalMaterials.getReturnItem((stack))
if(returnIdentifier != null ) {
// Store the return item
val returnItem = Registries.ITEM.get(returnIdentifier)
storeReturnItem(returnItem, stack.count)
}
}
}
private fun storeReturnItem(returnItem: Item, count: Int) {
val destStack = items.withIndex().firstOrNull {
it.value == ItemStack.EMPTY || (it.value.count < it.value.maxCount && it.value.item == returnItem)
}
if (destStack != null) {
if(destStack.value == ItemStack.EMPTY) {
items[destStack.index] = ItemStack(returnItem, 1)
} else {
destStack.value.increment(count)
}
}
}
override fun markDirty() {
tankEntity.world?.let {
tankEntity.multiblockStructure?.markDirty(it)
}
}
override fun canPlayerUse(player: PlayerEntity?): Boolean {
return false
}
override fun getAvailableSlots(side: Direction?): IntArray {
return if(side == Direction.DOWN) intArrayOf(0,1,2,3,4,5,6,7) else intArrayOf(-1)
}
override fun canInsert(slot: Int, stack: ItemStack?, dir: Direction?): Boolean {
if (tankEntity.multiblockStructure is FossilMultiblockStructure) {
if(dir != Direction.DOWN) {
val structure = tankEntity.multiblockStructure as FossilMultiblockStructure
val canUtilize = stack?.let { NaturalMaterials.isNaturalMaterial(it) } == true
&& structure.organicMaterialInside < FossilMultiblockStructure.MATERIAL_TO_START
&& structure.createdPokemon == null
val returnItem = NaturalMaterials.getReturnItem(stack!!) ?: return false
if(canUtilize) {
// See if there's room
for (i in items.indices ) {
if(items[i] == ItemStack.EMPTY || (items[i].count < items[i].maxCount
&& items[i].item == Registries.ITEM.get(returnItem))) {
return true
}
}
return false
}
}
}
return false
}
override fun canExtract(slot: Int, stack: ItemStack?, dir: Direction?): Boolean {
return dir == Direction.DOWN && slot >= 0 && slot < size()
}
}
}
@@ -1,97 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity.fossil
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.api.fossil.NaturalMaterials
import com.cobblemon.mod.common.api.multiblock.builder.MultiblockStructureBuilder
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockStructure
import com.cobblemon.mod.common.client.render.models.blockbench.fossil.FossilState
import com.cobblemon.mod.common.util.DataKeys
import net.minecraft.block.BlockState
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.inventory.SidedInventory
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound
import net.minecraft.network.listener.ClientPlayPacketListener
import net.minecraft.network.packet.Packet
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
class FossilTubeBlockEntity(
pos: BlockPos, state: BlockState,
multiblockBuilder: MultiblockStructureBuilder
) : FossilMultiblockEntity(pos, state, multiblockBuilder, CobblemonBlockEntities.FOSSIL_TUBE) {
val inv = FossilTubeInventory(this)
class FossilTubeInventory(val tubeEntity: FossilTubeBlockEntity) : SidedInventory {
override fun clear() {
TODO("Not yet implemented")
}
override fun size(): Int {
TODO("Not yet implemented")
}
override fun isEmpty(): Boolean {
return false
}
override fun getStack(slot: Int): ItemStack {
return ItemStack.EMPTY
}
override fun removeStack(slot: Int, amount: Int): ItemStack {
return ItemStack.EMPTY
}
override fun removeStack(slot: Int): ItemStack {
return ItemStack.EMPTY
}
override fun setStack(slot: Int, stack: ItemStack) {
if (tubeEntity.multiblockStructure != null) {
val struct = tubeEntity.multiblockStructure as FossilMultiblockStructure
tubeEntity.world?.let {
struct.insertOrganicMaterial(stack, it)
}
}
}
override fun markDirty() {
if (tubeEntity.world != null) {
tubeEntity.multiblockStructure?.markDirty(tubeEntity.world!!)
}
}
override fun canPlayerUse(player: PlayerEntity?): Boolean {
return false
}
override fun getAvailableSlots(side: Direction?): IntArray {
return IntArray(1)
}
override fun canInsert(slot: Int, stack: ItemStack?, dir: Direction?): Boolean {
if (tubeEntity.multiblockStructure is FossilMultiblockStructure) {
val structure = tubeEntity.multiblockStructure as FossilMultiblockStructure
return stack?.let { NaturalMaterials.isNaturalMaterial(it) } == true
&& structure.organicMaterialInside < FossilMultiblockStructure.MATERIAL_TO_START
&& structure.createdPokemon == null
}
return false
}
override fun canExtract(slot: Int, stack: ItemStack?, dir: Direction?): Boolean {
return false
}
}
}
@@ -13,11 +13,8 @@ import com.cobblemon.mod.common.CobblemonBlocks
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.block.entity.fossil.FossilTubeBlockEntity
import com.cobblemon.mod.common.block.fossilmachine.FossilCompartmentBlock
import com.cobblemon.mod.common.block.fossilmachine.FossilTubeBlock
import com.cobblemon.mod.common.block.RestorationTankBlock
import com.cobblemon.mod.common.api.multiblock.condition.BlockRelativeCondition
import com.cobblemon.mod.common.block.entity.fossil.FossilCompartmentBlockEntity
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.blockPositionsAsList
import net.minecraft.nbt.NbtCompound
@@ -53,13 +50,13 @@ class FossilMultiblockBuilder(val centerPos: BlockPos) : MultiblockStructureBuil
override val conditions = listOf(
BlockRelativeCondition(
FOSSIL_COMPARTMENT_PRED,
FOSSIL_MONITOR_PRED,
FOSSIL_ANALYZER_PRED,
MONITOR_PRED,
arrayOf(Direction.UP)
),
BlockRelativeCondition(
FOSSIL_COMPARTMENT_PRED,
FOSSIL_TUBE_PRED,
FOSSIL_ANALYZER_PRED,
RESTORATION_TANK_PRED,
arrayOf(Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST)
)
)
@@ -72,68 +69,68 @@ class FossilMultiblockBuilder(val centerPos: BlockPos) : MultiblockStructureBuil
The process of identifying the constituent blocks is a bit of a clusterfuck so here's an explanation
1. We find all of the monitors in the boundingBox
2. We map those positions to a new list representing valid compartment positions
(This is where it starts getting complicated) We want valid compartment positions, but we
need to remember the index in the monitor position list that each compartment is valid for.
So basically we take the monitor positions, check if they have a compartment underneath.
If they do, set the element in the NEW list to be the position of the compartment.
If they dont, set the element in the new list to be null
3. Do a similar process for tubes.
(Make a copy of the compartment positions, set them to the tube position if theres a tube, null if not)
2. We map those positions to a new list representing valid analyzer positions
(This is where it starts getting complicated) We want valid analyzer positions, but we
need to remember the index in the monitor position list that each analyzer is valid for.
So basically we take the monitor positions, check if they have a analyzer underneath.
If they do, set the element in the NEW list to be the position of the analyzer.
If they don't, set the element in the new list to be null
3. Do a similar process for tanks.
(Make a copy of the analyzer positions, set them to the tank position if there's a tank, null if not)
What this ends up doing is giving us a list at the end filled with either nulls or BlockPositions
of tubes that have a valid structure. The index of any tube in the list corresponds to the index
of the Monitor/Compartment in their respective lists. So if you have a fossilTubePositions list of
[null BlockPos null], the fossilCompartment BlockPos is fossilCompPositions[1] and the monitor BlockPos
is fossilMonitorPositions[1]
of tanks that have a valid structure. The index of any tank in the list corresponds to the index
of the Monitor/Analyzer in their respective lists. So if you have a fossilTankPositions list of
[null BlockPos null], the fossilAnalyzer BlockPos is fossilCompPositions[1] and the monitor BlockPos
is monitorPositions[1]
- Apion
*/
var fossilMonitorPositions = blocks.filter {
FOSSIL_MONITOR_PRED.test(world, it)
var monitorPositions = blocks.filter {
MONITOR_PRED.test(world, it)
}
var fossilCompPositions = fossilMonitorPositions.map {
if (FOSSIL_COMPARTMENT_PRED.test(world, it.down())) it.down()
var fossilAnalyzerPositions = monitorPositions.map {
if (FOSSIL_ANALYZER_PRED.test(world, it.down())) it.down()
else null
}
var fossilTubePositions = fossilCompPositions.map { compPosition ->
if (compPosition == null) {
var restorationTankPositions = fossilAnalyzerPositions.map { analyzerPosition ->
if (analyzerPosition == null) {
return@map null
}
dirsToCheck.forEach {
if (FOSSIL_TUBE_PRED.test(world, compPosition.offset(it))) {
return@map compPosition.offset(it)
if (RESTORATION_TANK_PRED.test(world, analyzerPosition.offset(it))) {
return@map analyzerPosition.offset(it)
}
}
return@map null
}
val fossilTubeIndex = fossilTubePositions.indexOfFirst {
val fossilTankIndex = restorationTankPositions.indexOfFirst {
it != null
}
if (fossilTubeIndex == -1) {
if (fossilTankIndex == -1) {
Cobblemon.LOGGER.error("FossilMultiblockBuilder form called on invalid structure! This should never happen!")
return
}
val fossilMonitorPos = fossilMonitorPositions[fossilTubeIndex]
val fossilCompPos = fossilCompPositions[fossilTubeIndex]!!
val fossilTubePos = fossilTubePositions[fossilTubeIndex]!!
val monitorEntity = world.getBlockEntity(fossilMonitorPos) as MultiblockEntity
val compEntity = world.getBlockEntity(fossilCompPos) as MultiblockEntity
val tubeBaseEntity = world.getBlockEntity(fossilTubePos) as MultiblockEntity
val tubeTopEntity = world.getBlockEntity(fossilTubePos.up()) as MultiblockEntity
val structure = FossilMultiblockStructure(fossilMonitorPos, fossilCompPos, fossilTubePos)
val monitorPos = monitorPositions[fossilTankIndex]
val fossilAnalyzerPos = fossilAnalyzerPositions[fossilTankIndex]!!
val restorationTankPos = restorationTankPositions[fossilTankIndex]!!
val monitorEntity = world.getBlockEntity(monitorPos) as MultiblockEntity
val analyzerEntity = world.getBlockEntity(fossilAnalyzerPos) as MultiblockEntity
val tankBaseEntity = world.getBlockEntity(restorationTankPos) as MultiblockEntity
val tankTopEntity = world.getBlockEntity(restorationTankPos.up()) as MultiblockEntity
val structure = FossilMultiblockStructure(monitorPos, fossilAnalyzerPos, restorationTankPos)
structure.tubeConnectorDirection = dirsToCheck.filter {
val adjPos = fossilTubePos.offset(it)
return@filter adjPos == fossilCompPos
structure.tankConnectorDirection = dirsToCheck.filter {
val adjPos = restorationTankPos.offset(it)
return@filter adjPos == fossilAnalyzerPos
}.first()
compEntity.multiblockStructure = structure
tubeBaseEntity.multiblockStructure = structure
tubeTopEntity.multiblockStructure = structure
analyzerEntity.multiblockStructure = structure
tankBaseEntity.multiblockStructure = structure
tankTopEntity.multiblockStructure = structure
monitorEntity.multiblockStructure = structure
structure.syncToClient(world)
structure.markDirty(world)
@@ -141,9 +138,9 @@ class FossilMultiblockBuilder(val centerPos: BlockPos) : MultiblockStructureBuil
world.playSound(null, centerPos, CobblemonSounds.FOSSIL_MACHINE_ASSEMBLE, SoundCategory.BLOCKS)
//Set these to null so the builders can be freed
compEntity.multiblockBuilder = null
tubeBaseEntity.multiblockBuilder = null
tubeTopEntity.multiblockBuilder = null
analyzerEntity.multiblockBuilder = null
tankBaseEntity.multiblockBuilder = null
tankTopEntity.multiblockBuilder = null
monitorEntity.multiblockBuilder = null
}
@@ -153,18 +150,18 @@ class FossilMultiblockBuilder(val centerPos: BlockPos) : MultiblockStructureBuil
nbt.putBoolean(DataKeys.FORMED, false)
return@run nbt
}
val FOSSIL_MONITOR_PRED = BlockPredicate.Builder.create().blocks(CobblemonBlocks.FOSSIL_MONITOR)
val MONITOR_PRED = BlockPredicate.Builder.create().blocks(CobblemonBlocks.MONITOR)
.nbt(NBT_TO_CHECK)
.build()
val FOSSIL_COMPARTMENT_PRED = BlockPredicate.Builder.create()
.blocks(CobblemonBlocks.FOSSIL_COMPARTMENT)
val FOSSIL_ANALYZER_PRED = BlockPredicate.Builder.create()
.blocks(CobblemonBlocks.FOSSIL_ANALYZER)
.nbt(NBT_TO_CHECK)
.build()
val FOSSIL_TUBE_PRED = BlockPredicate.Builder.create()
val RESTORATION_TANK_PRED = BlockPredicate.Builder.create()
.nbt(NBT_TO_CHECK)
.blocks(CobblemonBlocks.FOSSIL_TUBE)
.state(StatePredicate.Builder.create().exactMatch(FossilTubeBlock.PART, FossilTubeBlock.TubePart.BOTTOM).build())
.blocks(CobblemonBlocks.RESTORATION_TANK)
.state(StatePredicate.Builder.create().exactMatch(RestorationTankBlock.PART, RestorationTankBlock.TankPart.BOTTOM).build())
.build()
//lol thanks mojang for not allowing nbt puts to be chained
}
@@ -17,22 +17,22 @@ import com.cobblemon.mod.common.api.fossil.NaturalMaterials
import com.cobblemon.mod.common.api.multiblock.MultiblockEntity
import com.cobblemon.mod.common.api.multiblock.MultiblockStructure
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.block.entity.fossil.FossilMultiblockEntity
import com.cobblemon.mod.common.block.fossilmachine.FossilCompartmentBlock
import com.cobblemon.mod.common.block.fossilmachine.FossilMonitorBlock
import com.cobblemon.mod.common.block.entity.FossilMultiblockEntity
import com.cobblemon.mod.common.block.FossilAnalyzerBlock
import com.cobblemon.mod.common.block.MonitorBlock
import com.cobblemon.mod.common.block.RestorationTankBlock
import com.cobblemon.mod.common.block.entity.RestorationTankBlockEntity
import com.cobblemon.mod.common.client.render.models.blockbench.fossil.FossilState
import com.cobblemon.mod.common.client.sound.CancellableSoundController
import com.cobblemon.mod.common.client.sound.CancellableSoundInstance
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.PokeBallItem
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.giveOrDropItemStack
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.util.*
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.HorizontalFacingBlock
import net.minecraft.block.entity.BlockEntityTicker
import net.minecraft.client.MinecraftClient
import net.minecraft.entity.EntityPose
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.ItemStack
@@ -54,15 +54,18 @@ import net.minecraft.util.math.Box
import net.minecraft.util.math.Direction
import net.minecraft.util.math.random.Random
import net.minecraft.world.World
import java.util.UUID
import kotlin.math.ceil
class FossilMultiblockStructure (
val monitorPos: BlockPos,
val compartmentPos: BlockPos,
val tubeBasePos: BlockPos
val analyzerPos: BlockPos,
val tankBasePos: BlockPos,
animAge: Int = -1,
animPartialTicks: Float = 0F
) : MultiblockStructure {
override val controllerBlockPos = compartmentPos
override val controllerBlockPos = analyzerPos
// TODO: API method for this
@@ -78,10 +81,10 @@ class FossilMultiblockStructure (
private var lastInteraction: Long = 0
private var machineStartTime: Long = 0
private var protectionTime: Int = -1
private var fossilOwner: PlayerEntity? = null
val fossilState = FossilState()
private var fossilOwnerUUID: UUID? = null
val fossilState = FossilState(animAge, animPartialTicks)
var fossilInventory: MutableList<ItemStack> = mutableListOf<ItemStack>()
var tubeConnectorDirection: Direction? = null
var tankConnectorDirection: Direction? = null
//Only updated clientside
var fillLevel = 0
@@ -97,7 +100,7 @@ class FossilMultiblockStructure (
if (this.createdPokemon != null) {
if (!stack.isIn(CobblemonItemTags.POKEBALLS) || stack.item !is PokeBallItem) {
if (!stack.isIn(CobblemonItemTags.POKE_BALLS) || stack.item !is PokeBallItem) {
return ActionResult.PASS
}
@@ -105,8 +108,12 @@ class FossilMultiblockStructure (
return ActionResult.CONSUME
}
if (this.fossilOwner != null && player != this.fossilOwner) {
player.sendMessage(lang("fossilmachine.protected", this.fossilOwner!!.name), true)
if (this.fossilOwnerUUID != null && player.uuid != this.fossilOwnerUUID) {
var ownerName : String = "UNKNOWN_USER" // TODO: lang agnostic fallback
server()?.userCache?.getByUuid(this.fossilOwnerUUID)?.orElse(null)?.name?.let {
ownerName = it
}
player.sendMessage(lang("fossilmachine.protected", ownerName), true)
return ActionResult.FAIL
}
@@ -123,8 +130,14 @@ class FossilMultiblockStructure (
player.playSound(CobblemonSounds.FOSSIL_MACHINE_RETRIEVE_POKEMON, SoundCategory.BLOCKS, 1.0F, 1.0F)
CobblemonCriteria.RESURRECT_POKEMON.trigger(player, createdPokemon!!)
// Turn the monitor off
val monitorState = world.getBlockState(monitorPos)
if(!monitorState.equals(MonitorBlock.MonitorScreen.OFF)) {
world.setBlockState(monitorPos, monitorState.with(MonitorBlock.SCREEN, MonitorBlock.MonitorScreen.OFF))
}
this.createdPokemon = null
this.fossilOwner = null
this.fossilOwnerUUID = null
this.protectionTime = -1
this.updateFossilType(world)
this.syncToClient(world)
@@ -147,7 +160,7 @@ class FossilMultiblockStructure (
// remove last fossil in the fossil machine stack when grabbed out of the machine
this.fossilInventory.removeAt(fossilInventory.size - 1)
if(!world.isClient) {
world.playSound(null, compartmentPos, CobblemonSounds.FOSSIL_MACHINE_RETRIEVE_FOSSIL, SoundCategory.BLOCKS)
world.playSound(null, analyzerPos, CobblemonSounds.FOSSIL_MACHINE_RETRIEVE_FOSSIL, SoundCategory.BLOCKS)
this.updateFossilType(world)
this.syncToClient(world)
this.markDirty(world)
@@ -166,11 +179,11 @@ class FossilMultiblockStructure (
stack?.decrement(1)
}
fossilOwner = player
fossilOwnerUUID = player.uuid
fossilInventory.add(copyFossilStack)
if(!world.isClient) {
this.updateFossilType(world)
world.playSound(null, compartmentPos, CobblemonSounds.FOSSIL_MACHINE_INSERT_FOSSIL, SoundCategory.BLOCKS)
world.playSound(null, analyzerPos, CobblemonSounds.FOSSIL_MACHINE_INSERT_FOSSIL, SoundCategory.BLOCKS)
this.syncToClient(world)
this.markDirty(world)
}
@@ -191,16 +204,18 @@ class FossilMultiblockStructure (
return ActionResult.success(world.isClient)
}
// pure client instances dont know what a valid fossil is so this is my janky workaround
if (stack.isIn(CobblemonItemTags.FOSSILS)) return ActionResult.SUCCESS
return ActionResult.PASS
}
public fun spawn(world: World, pos: BlockPos, directionToBehind: Direction, pokemon: Pokemon) : Boolean {
val world = world ?: return false
val entity = PokemonEntity(world, pokemon = pokemon)
entity.calculateDimensions()
val width = entity.boundingBox.xLength
val idealPlace = pos.add(directionToBehind.vector.multiply(ceil(width).toInt() + 1))
val idealPlace = pos.add(directionToBehind.vector.multiply(ceil(width / 2.0).toInt() + 1))
var box = entity.getDimensions(EntityPose.STANDING).getBoxAt(idealPlace.toCenterPos().subtract(0.0, 0.5, 0.0))
for (i in 0..5) {
@@ -269,7 +284,7 @@ class FossilMultiblockStructure (
}
return Math.max(15 - timeRemaining * 15 / TIME_TO_TAKE, 1)
}
if(tubeBasePos == pos || tubeBasePos.up() == pos) {
if(tankBasePos == pos || tankBasePos.up() == pos) {
return organicMaterialInside * 15 / MATERIAL_TO_START
}
return 0
@@ -284,9 +299,9 @@ class FossilMultiblockStructure (
if(success) {
this.fossilState.growthState = "Taken"
this.createdPokemon = null
this.fossilOwner = null
this.fossilOwnerUUID = null
this.protectionTime = -1
world.playSound(null, tubeBasePos, CobblemonSounds.FOSSIL_MACHINE_RETRIEVE_POKEMON, SoundCategory.BLOCKS)
world.playSound(null, tankBasePos, CobblemonSounds.FOSSIL_MACHINE_RETRIEVE_POKEMON, SoundCategory.BLOCKS)
this.updateFossilType(world)
this.syncToClient(world)
this.markDirty(world)
@@ -297,22 +312,21 @@ class FossilMultiblockStructure (
override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity?) {
val monitorEntity = world.getBlockEntity(monitorPos) as MultiblockEntity
val compartmentEntity = world.getBlockEntity(compartmentPos) as MultiblockEntity
val tubeBaseEntity = world.getBlockEntity(tubeBasePos) as MultiblockEntity
val tubeTopEntity = world.getBlockEntity(tubeBasePos.up()) as MultiblockEntity
val state = world.getBlockState(monitorEntity.pos)
val analyzerEntity = world.getBlockEntity(analyzerPos) as MultiblockEntity
val tankBaseEntity = world.getBlockEntity(tankBasePos) as MultiblockEntity
val tankTopEntity = world.getBlockEntity(tankBasePos.up()) as MultiblockEntity
val state = world.getBlockState(tankBaseEntity.pos)
val direction = state.get(HorizontalFacingBlock.FACING).getOpposite()
val wildPokemon: Pokemon? = this.createdPokemon
monitorEntity.multiblockStructure = null
compartmentEntity.multiblockStructure = null
tubeBaseEntity.multiblockStructure = null
tubeTopEntity.multiblockStructure = null
analyzerEntity.multiblockStructure = null
tankBaseEntity.multiblockStructure = null
tankTopEntity.multiblockStructure = null
monitorEntity.masterBlockPos = null
compartmentEntity.masterBlockPos = null
tubeBaseEntity.masterBlockPos = null
tubeTopEntity.masterBlockPos = null
analyzerEntity.masterBlockPos = null
tankBaseEntity.masterBlockPos = null
tankTopEntity.masterBlockPos = null
// Drop fossils from machine as long as the machine is not running
if (this.timeRemaining == TIME_TO_TAKE || this.timeRemaining == -1) {
@@ -321,10 +335,17 @@ class FossilMultiblockStructure (
ItemScatterer.spawn(world, pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble(), stack)
}
}
if(tankBaseEntity is RestorationTankBlockEntity) {
tankBaseEntity.inv.items.forEach {
val stack = ItemStack(it.item, 1)
ItemScatterer.spawn(world, pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble(), stack)
}
tankBaseEntity.inv.clear()
}
// if the machine is broken while the pokemon is done then spawn the pokemon at the location and make it a wild pokemon
if (this.fossilState.growthState == "Fully Grown" && wildPokemon != null) {
if (wildPokemon != null) {
//world.createExplosion(this.createdPokemon?.entity, pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble(), 5F, World.ExplosionSourceType.TNT)
// instantiate the pokemon as a new entity and spawn it at the location of the machine
@@ -334,29 +355,39 @@ class FossilMultiblockStructure (
this.spawn(world, pos, direction, wildPokemon)
}
MinecraftClient.getInstance().soundManager.stopSounds(CobblemonSounds.FOSSIL_MACHINE_ACTIVE_LOOP.id, SoundCategory.BLOCKS)
this.protectionTime = -1
this.updateFossilType(world)
this.stopMachine(world)
this.syncToClient(world)
this.markDirty(world)
}
override fun markRemoved(world: World) {
if(world.isClient) {
CancellableSoundController.stopSound(this.tankBasePos, CobblemonSounds.FOSSIL_MACHINE_ACTIVE_LOOP.id)
}
}
override fun tick(world: World) {
if (protectionTime > 0) protectionTime--
if (protectionTime == 0) {
protectionTime = -1
this.fossilOwner = null
world.playSound(null, tubeBasePos, CobblemonSounds.FOSSIL_MACHINE_UNPROTECTED, SoundCategory.BLOCKS)
this.fossilOwnerUUID = null
this.updateProgress(world)
this.syncToClient(world)
this.markDirty(world)
world.playSound(null, tankBasePos, CobblemonSounds.FOSSIL_MACHINE_UNPROTECTED, SoundCategory.BLOCKS)
}
if (this.createdPokemon != null) {
return
}
if (this.isRunning() && (world.time - this.machineStartTime) % 160L == 0L) {
world.playSound(null, this.tubeBasePos, CobblemonSounds.FOSSIL_MACHINE_ACTIVE_LOOP, SoundCategory.BLOCKS, 1.0F, 1.0F)
if (world.isClient && this.isRunning() && (world.time - this.machineStartTime) % 160L == 0L) {
if(world.isClient) {
CancellableSoundController.playSound(CancellableSoundInstance(CobblemonSounds.FOSSIL_MACHINE_ACTIVE_LOOP,
tankBasePos, true, 1.0f, 1.0f, ))
}
}
if (this.timeRemaining == -1 && this.organicMaterialInside >= MATERIAL_TO_START && this.resultingFossil != null) {
@@ -375,14 +406,13 @@ class FossilMultiblockStructure (
}
if (this.timeRemaining == 0) {
world.playSound(null, tubeBasePos, CobblemonSounds.FOSSIL_MACHINE_FINISHED, SoundCategory.BLOCKS)
MinecraftClient.getInstance().soundManager.stopSounds(CobblemonSounds.FOSSIL_MACHINE_ACTIVE_LOOP.id, SoundCategory.BLOCKS)
world.playSound(null, tankBasePos, CobblemonSounds.FOSSIL_MACHINE_FINISHED, SoundCategory.BLOCKS)
fossilInventory.clear()
this.resultingFossil?.let {
this.createdPokemon = it.result.create()
}
if(this.fossilOwner != null) {
if(this.fossilOwnerUUID != null) {
protectionTime = PROTECTION_TIME
}
@@ -391,20 +421,23 @@ class FossilMultiblockStructure (
}
override fun syncToClient(world: World) {
val tubeBaseEntity = world.getBlockEntity(tubeBasePos) as MultiblockEntity
val compartmentEntity = world.getBlockEntity(controllerBlockPos) as MultiblockEntity
val monitorEntity = world.getBlockEntity(monitorPos) as MultiblockEntity
val tankBaseEntity = world.getBlockEntity(tankBasePos) as? MultiblockEntity
val analyzerEntity = world.getBlockEntity(controllerBlockPos) as? MultiblockEntity
val monitorEntity = world.getBlockEntity(monitorPos) as? MultiblockEntity
world.updateListeners(tubeBasePos, tubeBaseEntity.cachedState, tubeBaseEntity.cachedState, Block.NOTIFY_LISTENERS)
world.updateListeners(compartmentPos, compartmentEntity.cachedState, compartmentEntity.cachedState, Block.NOTIFY_LISTENERS)
world.updateListeners(monitorPos, monitorEntity.cachedState, monitorEntity.cachedState, Block.NOTIFY_LISTENERS)
if(tankBaseEntity != null)
world.updateListeners(tankBasePos, tankBaseEntity.cachedState, tankBaseEntity.cachedState, Block.NOTIFY_LISTENERS)
if(analyzerEntity != null)
world.updateListeners(analyzerPos, analyzerEntity.cachedState, analyzerEntity.cachedState, Block.NOTIFY_LISTENERS)
if(monitorEntity != null)
world.updateListeners(monitorPos, monitorEntity.cachedState, monitorEntity.cachedState, Block.NOTIFY_LISTENERS)
}
override fun markDirty(world: World) {
val entities = listOf(
world.getBlockEntity(compartmentPos),
world.getBlockEntity(tubeBasePos),
world.getBlockEntity(tubeBasePos.up()),
world.getBlockEntity(analyzerPos),
world.getBlockEntity(tankBasePos),
world.getBlockEntity(tankBasePos.up()),
world.getBlockEntity(monitorPos)
)
entities.forEach {
@@ -416,8 +449,11 @@ class FossilMultiblockStructure (
this.timeRemaining = TIME_TO_TAKE
this.machineStartTime = world.time
world.playSound(null, tubeBasePos, CobblemonSounds.FOSSIL_MACHINE_ACTIVATE, SoundCategory.BLOCKS)
world.playSound(null, tubeBasePos, CobblemonSounds.FOSSIL_MACHINE_ACTIVE_LOOP, SoundCategory.BLOCKS)
world.playSound(null, tankBasePos, CobblemonSounds.FOSSIL_MACHINE_ACTIVATE, SoundCategory.BLOCKS)
if(world.isClient) {
CancellableSoundController.playSound(CancellableSoundInstance(CobblemonSounds.FOSSIL_MACHINE_ACTIVE_LOOP,
tankBasePos, true, 1.0f, 1.0f, ))
}
this.updateOnStatus(world)
this.updateProgress(world)
@@ -432,6 +468,10 @@ class FossilMultiblockStructure (
fossilInventory.clear()
if(world.isClient) {
CancellableSoundController.stopSound(tankBasePos, CobblemonSounds.FOSSIL_MACHINE_ACTIVE_LOOP.id)
}
this.updateOnStatus(world)
this.updateProgress(world)
this.syncToClient(world)
@@ -439,14 +479,38 @@ class FossilMultiblockStructure (
}
fun updateOnStatus(world: World) {
val compartmentState = world.getBlockState(compartmentPos)
world.setBlockState(compartmentPos, compartmentState.with(FossilCompartmentBlock.ON, timeRemaining >= 0))
val upperTankPos = tankBasePos.up()
val analyzerState = world.getBlockState(analyzerPos)
val tankState = world.getBlockState(tankBasePos.up())
world.setBlockState(analyzerPos, analyzerState.with(FossilAnalyzerBlock.ON, timeRemaining >= 0))
world.setBlockState(upperTankPos, tankState.with(RestorationTankBlock.ON, timeRemaining >= 0))
}
fun updateProgress(world: World) {
val progress = if (timeRemaining <= 0) 0 else ((TIME_TO_TAKE - timeRemaining) / TIME_PER_STAGE) + 1
val screenID = if(protectionTime > 0F ) {
MonitorBlock.MonitorScreen.GREEN_PROGRESS_9
} else if (timeRemaining <= 0) {
MonitorBlock.MonitorScreen.OFF
} else {
getProgressScreen((TIME_TO_TAKE - timeRemaining) / TIME_PER_STAGE)
}
val monitorState = world.getBlockState(monitorPos)
world.setBlockState(monitorPos, monitorState.with(FossilMonitorBlock.PROGRESS, progress))
world.setBlockState(monitorPos, monitorState.with(MonitorBlock.SCREEN, screenID))
}
fun getProgressScreen(progress:Int) : MonitorBlock.MonitorScreen {
return when (progress) {
0 -> MonitorBlock.MonitorScreen.BLUE_PROGRESS_1
1 -> MonitorBlock.MonitorScreen.BLUE_PROGRESS_2
2 -> MonitorBlock.MonitorScreen.BLUE_PROGRESS_3
3 -> MonitorBlock.MonitorScreen.BLUE_PROGRESS_4
4 -> MonitorBlock.MonitorScreen.BLUE_PROGRESS_5
5 -> MonitorBlock.MonitorScreen.BLUE_PROGRESS_6
6 -> MonitorBlock.MonitorScreen.BLUE_PROGRESS_7
7 -> MonitorBlock.MonitorScreen.BLUE_PROGRESS_8
8 -> MonitorBlock.MonitorScreen.BLUE_PROGRESS_9
else -> MonitorBlock.MonitorScreen.OFF
}
}
/**
@@ -491,11 +555,11 @@ class FossilMultiblockStructure (
organicMaterialInside += natureValue
}
if (this.organicMaterialInside >= MATERIAL_TO_START) {
world.playSound(null, this.tubeBasePos, CobblemonSounds.FOSSIL_MACHINE_DNA_FULL, SoundCategory.BLOCKS, 1.0F, 1.0F)
world.playSound(null, this.tankBasePos, CobblemonSounds.FOSSIL_MACHINE_DNA_FULL, SoundCategory.BLOCKS, 1.0F, 1.0F)
} else if (world.time - this.lastInteraction < 10) {
world.playSound(null, this.tubeBasePos, CobblemonSounds.FOSSIL_MACHINE_INSERT_DNA_SMALL, SoundCategory.BLOCKS, 1.0F, 1.0F)
world.playSound(null, this.tankBasePos, CobblemonSounds.FOSSIL_MACHINE_INSERT_DNA_SMALL, SoundCategory.BLOCKS, 1.0F, 1.0F)
} else {
world.playSound(null, this.tubeBasePos, CobblemonSounds.FOSSIL_MACHINE_INSERT_DNA, SoundCategory.BLOCKS, 1.0F, 1.0F)
world.playSound(null, this.tankBasePos, CobblemonSounds.FOSSIL_MACHINE_INSERT_DNA, SoundCategory.BLOCKS, 1.0F, 1.0F)
}
this.markDirty(world)
if (oldFillStage != (organicMaterialInside / 8)) {
@@ -514,7 +578,7 @@ class FossilMultiblockStructure (
//add fossil to the stack in the Compartment
this.fossilInventory.add(stack)
world.playSound(null, compartmentPos, CobblemonSounds.FOSSIL_MACHINE_INSERT_FOSSIL, SoundCategory.BLOCKS)
world.playSound(null, analyzerPos, CobblemonSounds.FOSSIL_MACHINE_INSERT_FOSSIL, SoundCategory.BLOCKS)
this.updateFossilType(world)
this.markDirty(world)
@@ -527,16 +591,19 @@ class FossilMultiblockStructure (
override fun writeToNbt(): NbtCompound {
val result = NbtCompound()
result.put(DataKeys.MONITOR_POS, NbtHelper.fromBlockPos(monitorPos))
result.put(DataKeys.COMPARTMENT_POS, NbtHelper.fromBlockPos(compartmentPos))
result.put(DataKeys.TUBE_BASE_POS, NbtHelper.fromBlockPos(tubeBasePos))
result.put(DataKeys.ANALYZER_POS, NbtHelper.fromBlockPos(analyzerPos))
result.put(DataKeys.TANK_BASE_POS, NbtHelper.fromBlockPos(tankBasePos))
result.putInt(DataKeys.TIME_LEFT, timeRemaining)
result.putInt(DataKeys.PROTECTED_TIME_LEFT, protectionTime)
if(fossilOwnerUUID != null)
result.putUuid(DataKeys.FOSSIL_OWNER, fossilOwnerUUID)
result.putInt(DataKeys.ORGANIC_MATERIAL, organicMaterialInside)
val fossilInv = NbtList()
fossilInventory.forEach{ item ->
fossilInv.add(item.writeNbt(NbtCompound()))
}
result.put(DataKeys.FOSSIL_INVENTORY, fossilInv)
result.putString(DataKeys.CONNECTOR_DIRECTION, tubeConnectorDirection?.toString())
result.putString(DataKeys.CONNECTOR_DIRECTION, tankConnectorDirection?.toString())
if (this.resultingFossil != null) {
result.putString(DataKeys.INSERTED_FOSSIL, this.resultingFossil!!.asString())
@@ -562,14 +629,16 @@ class FossilMultiblockStructure (
const val TIME_PER_STAGE = TIME_TO_TAKE / 8
const val PROTECTION_TIME = 6000
fun fromNbt(nbt: NbtCompound): FossilMultiblockStructure {
fun fromNbt(nbt: NbtCompound, animAge: Int = -1, partialTicks: Float = 0f): FossilMultiblockStructure {
val monitorPos = NbtHelper.toBlockPos(nbt.getCompound(DataKeys.MONITOR_POS))
val compartmentPos = NbtHelper.toBlockPos(nbt.getCompound(DataKeys.COMPARTMENT_POS))
val tubeBasePos = NbtHelper.toBlockPos(nbt.getCompound(DataKeys.TUBE_BASE_POS))
val compartmentPos = NbtHelper.toBlockPos(nbt.getCompound(DataKeys.ANALYZER_POS))
val tankBasePos = NbtHelper.toBlockPos(nbt.getCompound(DataKeys.TANK_BASE_POS))
val result = FossilMultiblockStructure(monitorPos, compartmentPos, tubeBasePos)
val result = FossilMultiblockStructure(monitorPos, compartmentPos, tankBasePos, animAge, partialTicks)
result.organicMaterialInside = nbt.getInt(DataKeys.ORGANIC_MATERIAL)
result.timeRemaining = nbt.getInt(DataKeys.TIME_LEFT)
result.protectionTime = if(nbt.contains(DataKeys.PROTECTED_TIME_LEFT)) nbt.getInt(DataKeys.PROTECTED_TIME_LEFT) else -1
result.fossilOwnerUUID = if(nbt.contains(DataKeys.FOSSIL_OWNER)) nbt.getUuid(DataKeys.FOSSIL_OWNER) else null
val fossilInv = (nbt.get(DataKeys.FOSSIL_INVENTORY) as NbtList)
val actualFossilList = mutableListOf<ItemStack>()
@@ -577,7 +646,7 @@ class FossilMultiblockStructure (
actualFossilList.add(ItemStack.fromNbt(it as NbtCompound))
}
result.fossilInventory = actualFossilList
result.tubeConnectorDirection = Direction.byName(nbt.getString(DataKeys.CONNECTOR_DIRECTION))
result.tankConnectorDirection = Direction.byName(nbt.getString(DataKeys.CONNECTOR_DIRECTION))
if (nbt.contains(DataKeys.INSERTED_FOSSIL)) {
val id = Identifier(nbt.getString(DataKeys.INSERTED_FOSSIL))
@@ -21,44 +21,44 @@ import net.minecraft.util.Identifier
object CobblemonBakingOverrides {
val models = mutableListOf<BakingOverride>()
val FOSSIL_FLUID_BUBBLING = registerOverride(
cobblemonResource("block/fossil/fossil_fluid_bubbling"),
cobblemonModel("fossil_fluid_bubbling", "none")
val RESTORATION_TANK_FLUID_BUBBLING = registerOverride(
cobblemonResource("block/restoration_tank_fluid_bubbling"),
cobblemonModel("restoration_tank_fluid_bubbling", "none")
)
val FOSSIL_FLUID_CHUNKED_1 = registerOverride(
cobblemonResource("block/fossil/fossil_fluid_chunked_1"),
cobblemonModel("fossil_fluid_chunked", "1")
val RESTORATION_TANK_FLUID_CHUNKED_1 = registerOverride(
cobblemonResource("block/restoration_tank_fluid_chunked_1"),
cobblemonModel("restoration_tank_fluid_chunked", "1")
)
val FOSSIL_FLUID_CHUNKED_2 = registerOverride(
cobblemonResource("block/fossil/fossil_fluid_chunked_2"),
cobblemonModel("fossil_fluid_chunked", "2")
val RESTORATION_TANK_FLUID_CHUNKED_2 = registerOverride(
cobblemonResource("block/restoration_tank_fluid_chunked_2"),
cobblemonModel("restoration_tank_fluid_chunked", "2")
)
val FOSSIL_FLUID_CHUNKED_3 = registerOverride(
cobblemonResource("block/fossil/fossil_fluid_chunked_3"),
cobblemonModel("fossil_fluid_chunked", "3")
val RESTORATION_TANK_FLUID_CHUNKED_3 = registerOverride(
cobblemonResource("block/restoration_tank_fluid_chunked_3"),
cobblemonModel("restoration_tank_fluid_chunked", "3")
)
val FOSSIL_FLUID_CHUNKED_4 = registerOverride(
cobblemonResource("block/fossil/fossil_fluid_chunked_4"),
cobblemonModel("fossil_fluid_chunked", "4")
val RESTORATION_TANK_FLUID_CHUNKED_4 = registerOverride(
cobblemonResource("block/restoration_tank_fluid_chunked_4"),
cobblemonModel("restoration_tank_fluid_chunked", "4")
)
val FOSSIL_FLUID_CHUNKED_5 = registerOverride(
cobblemonResource("block/fossil/fossil_fluid_chunked_5"),
cobblemonModel("fossil_fluid_chunked", "5")
val RESTORATION_TANK_FLUID_CHUNKED_5 = registerOverride(
cobblemonResource("block/restoration_tank_fluid_chunked_5"),
cobblemonModel("restoration_tank_fluid_chunked", "5")
)
val FOSSIL_FLUID_CHUNKED_6 = registerOverride(
cobblemonResource("block/fossil/fossil_fluid_chunked_6"),
cobblemonModel("fossil_fluid_chunked", "6")
val RESTORATION_TANK_FLUID_CHUNKED_6 = registerOverride(
cobblemonResource("block/restoration_tank_fluid_chunked_6"),
cobblemonModel("restoration_tank_fluid_chunked", "6")
)
val FOSSIL_FLUID_CHUNKED_7 = registerOverride(
cobblemonResource("block/fossil/fossil_fluid_chunked_7"),
cobblemonModel("fossil_fluid_chunked", "7")
val RESTORATION_TANK_FLUID_CHUNKED_7 = registerOverride(
cobblemonResource("block/restoration_tank_fluid_chunked_7"),
cobblemonModel("restoration_tank_fluid_chunked", "7")
)
val FOSSIL_FLUID_CHUNKED_8 = registerOverride(
cobblemonResource("block/fossil/fossil_fluid_chunked_8"),
cobblemonModel("fossil_fluid_chunked", "8")
val RESTORATION_TANK_FLUID_CHUNKED_8 = registerOverride(
cobblemonResource("block/restoration_tank_fluid_chunked_8"),
cobblemonModel("restoration_tank_fluid_chunked", "8")
)
val RESTORATION_TANK_CONNECTOR = registerOverride(
cobblemonResource("block/fossil/restoration_tank_connector"),
cobblemonResource("block/restoration_tank_connector"),
cobblemonModel("restoration_tank_connector", "none")
)
@@ -23,8 +23,8 @@ import com.cobblemon.mod.common.client.gui.PartyOverlay
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.particle.BedrockParticleEffectRepository
import com.cobblemon.mod.common.client.render.block.BerryBlockRenderer
import com.cobblemon.mod.common.client.render.block.FossilCompartmentRenderer
import com.cobblemon.mod.common.client.render.block.FossilTubeRenderer
import com.cobblemon.mod.common.client.render.block.FossilAnalyzerRenderer
import com.cobblemon.mod.common.client.render.block.RestorationTankRenderer
import com.cobblemon.mod.common.client.render.block.HealingMachineRenderer
import com.cobblemon.mod.common.client.render.boat.CobblemonBoatRenderer
import com.cobblemon.mod.common.client.render.entity.PokeBobberEntityRenderer
@@ -45,6 +45,7 @@ import com.cobblemon.mod.common.data.CobblemonDataProvider
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
import com.cobblemon.mod.common.item.PokeBallItem
import com.cobblemon.mod.common.client.render.models.blockbench.repository.FossilModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.MiscModelRepository
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.asTranslated
@@ -162,7 +163,7 @@ object CobblemonClient {
this.implementation.registerBlockRenderType(
RenderLayer.getCutout(),
CobblemonBlocks.FOSSIL_COMPARTMENT,
CobblemonBlocks.FOSSIL_ANALYZER,
CobblemonBlocks.APRICORN_DOOR,
CobblemonBlocks.APRICORN_TRAPDOOR,
CobblemonBlocks.APRICORN_SIGN,
@@ -202,7 +203,7 @@ object CobblemonClient {
CobblemonBlocks.REVIVAL_HERB,
*CobblemonBlocks.berries().values.toTypedArray(),
CobblemonBlocks.POTTED_PEP_UP_FLOWER,
CobblemonBlocks.FOSSIL_TUBE,
CobblemonBlocks.RESTORATION_TANK,
CobblemonBlocks.SMALL_BUDDING_TUMBLESTONE,
CobblemonBlocks.MEDIUM_BUDDING_TUMBLESTONE,
CobblemonBlocks.LARGE_BUDDING_TUMBLESTONE,
@@ -242,8 +243,8 @@ object CobblemonClient {
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.BERRY, ::BerryBlockRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.SIGN, ::SignBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.HANGING_SIGN, ::HangingSignBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.FOSSIL_COMPARTMENT, ::FossilCompartmentRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.FOSSIL_TUBE, ::FossilTubeRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.FOSSIL_ANALYZER, ::FossilAnalyzerRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.RESTORATION_TANK, ::RestorationTankRenderer)
}
private fun registerEntityRenderers() {
@@ -270,6 +271,7 @@ object CobblemonClient {
PokeBallModelRepository.reload(resourceManager)
BerryModelRepository.reload(resourceManager)
FossilModelRepository.reload(resourceManager)
MiscModelRepository.reload(resourceManager)
LOGGER.info("Loaded assets")
}
@@ -8,7 +8,10 @@
package com.cobblemon.mod.common.client.entity
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.entity.EntitySideDelegate
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.reactive.SettableObservable
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
@@ -37,6 +40,11 @@ class EmptyPokeBallClientDelegate : PokeBallPoseableState(), EntitySideDelegate<
this.currentEntity = entity
age = entity.age
initSubscriptions()
this.runtime.environment.getQueryStruct().addFunctions(mapOf(
"pokeball_type" to java.util.function.Function {
return@Function DoubleValue(currentEntity.pokeBall.name.toString())
}
))
}
override fun tick(entity: EmptyPokeBallEntity) {
@@ -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.client.entity
import com.cobblemon.mod.common.api.entity.EntitySideDelegate
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.GenericBedrockEntityModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.entity.generic.GenericBedrockEntity
class GenericBedrockClientDelegate : EntitySideDelegate<GenericBedrockEntity>, PoseableEntityState<GenericBedrockEntity>() {
lateinit var currentEntity: GenericBedrockEntity
override val schedulingTracker
get() = getEntity().schedulingTracker
override fun getEntity() = currentEntity
override fun initialize(entity: GenericBedrockEntity) {
super.initialize(entity)
this.currentEntity = entity
this.age = entity.age
this.currentModel = GenericBedrockEntityModelRepository.getPoser(entity.category, entity.aspects)
val model = currentModel!!
model.context.put(RenderContext.ENTITY, entity)
currentModel!!.updateLocators(this)
updateLocatorPosition(entity.pos)
val currentPoseType = entity.getCurrentPoseType()
val pose = this.currentModel!!.poses.values.firstOrNull { currentPoseType in it.poseTypes && (it.condition == null || it.condition.invoke(entity)) }
if (pose != null) {
doLater { setPose(pose.poseName) }
}
}
override fun tick(entity: GenericBedrockEntity) {
super.tick(entity)
updateLocatorPosition(entity.pos)
incrementAge(entity)
}
override fun updatePartialTicks(partialTicks: Float) {
this.currentPartialTicks = partialTicks
}
}
@@ -8,25 +8,48 @@
package com.cobblemon.mod.common.client.entity
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.entity.PokemonSideDelegate
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.api.scheduling.afterOnClient
import com.cobblemon.mod.common.api.scheduling.lerpOnClient
import com.cobblemon.mod.common.client.particle.BedrockParticleEffectRepository
import com.cobblemon.mod.common.client.particle.ParticleStorm
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.client.render.SnowstormParticle
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PrimaryAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.StatefulAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPoseableModel
import com.cobblemon.mod.common.client.render.pokemon.PokemonRenderer.Companion.ease
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.MovingSoundInstance
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.MinecraftClient
import net.minecraft.client.util.math.MatrixStack
import java.lang.Float.min
import kotlin.math.abs
import net.minecraft.entity.Entity
import net.minecraft.entity.data.TrackedData
import net.minecraft.particle.ParticleTypes
import net.minecraft.sound.SoundCategory
import net.minecraft.sound.SoundEvent
import net.minecraft.util.Hand
import net.minecraft.util.Identifier
import net.minecraft.util.math.Vec3d
class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideDelegate {
companion object {
const val BEAM_SHRINK_TIME = 0.8F
const val BEAM_SHRINK_TIME = 0.4F
const val BEAM_EXTEND_TIME = 0.2F
const val POKEBALL_AIR_TIME = 0.5F
}
override val schedulingTracker: SchedulingTracker
@@ -43,10 +66,21 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
}
var beamStartTime = System.currentTimeMillis()
var ballStartTime = System.currentTimeMillis()
var ballDone = false
var ballOffset = 0f
var ballRotOffset = 0f
var sendOutPosition: Vec3d? = null
var sendOutOffset: Vec3d? = null
var playedSendOutSound: Boolean = false
var playedThrowingSound: Boolean = false
val secondsSinceBeamEffectStarted: Float
get() = (System.currentTimeMillis() - beamStartTime) / 1000F
val secondsSinceBallThrown: Float
get() = (System.currentTimeMillis() - ballStartTime) / 1000F
private var cryAnimation: StatefulAnimation<PokemonEntity, *>? = null
override fun onTrackedDataSet(data: TrackedData<*>) {
@@ -61,7 +95,7 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
if (isDying) {
val model = (currentModel ?: return) as PokemonPoseableModel
val animation = try {
model.getFaintAnimation(currentEntity, this)
model.getAnimation(this, "faint", runtime)
} catch (e: Exception) {
e.printStackTrace()
null
@@ -76,21 +110,95 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
0 -> { /* Do nothing */ }
1 -> {
// Scaling up out of pokeball
playedSendOutSound = false
entityScaleModifier = 0F
beamStartTime = System.currentTimeMillis()
ballStartTime = System.currentTimeMillis()
currentEntity.isInvisible = true
afterOnClient(seconds = BEAM_EXTEND_TIME) {
lerpOnClient(BEAM_SHRINK_TIME) { entityScaleModifier = it }
currentEntity.isInvisible = false
ballDone = false
var soundPos = currentEntity.pos
currentEntity.pokemon.getOwnerUUID()?.let{
currentEntity.world.getPlayerByUuid(it)?.let {
val offset = it.pos.subtract(currentEntity.pos.add(0.0, 2.0 - (ballOffset.toDouble()/10f), 0.0)).normalize().multiply(-ease(ballOffset.toDouble()))
with(it.pos.subtract(currentEntity.pos)) {
var newOffset = offset.multiply(2.0)
val distance = it.pos.distanceTo(currentEntity.pos)
newOffset = newOffset.multiply((distance / 10.0) * 5)
soundPos = currentEntity.pos.add(newOffset)
}
it.swingHand(it.activeHand ?: Hand.MAIN_HAND)
}
}
val client = MinecraftClient.getInstance()
val sound = MovingSoundInstance(SoundEvent.of(CobblemonSounds.POKE_BALL_TRAIL.id), SoundCategory.PLAYERS, { sendOutPosition?.add(sendOutOffset) }, 0.1f, 1f, false, 20, 0)
if(!playedThrowingSound){
client.soundManager.play(sound)
playedThrowingSound = true
}
lerpOnClient(POKEBALL_AIR_TIME) { ballOffset = it }
ballRotOffset = ((Math.random()) * currentEntity.world.random.nextBetween(-15, 15)).toFloat()
currentEntity.after(seconds = POKEBALL_AIR_TIME){
beamStartTime = System.currentTimeMillis()
ballDone = true
if (client.soundManager.get(CobblemonSounds.POKE_BALL_OPEN.id) != null && !playedSendOutSound) {
currentEntity.owner?.let {
client.world?.playSound(client.player, soundPos.x, soundPos.y, soundPos.z, SoundEvent.of(CobblemonSounds.POKE_BALL_SEND_OUT.id), SoundCategory.PLAYERS, 0.6f, 1f)
playedSendOutSound = true
/// create end rod particles in a 0.1 radius around the soundPos with a count of 50 and a random velocity of 0.1
sendOutPosition?.let{
val newPos = it.add(sendOutOffset)
val ballType = currentEntity.pokemon.caughtBall.name.path.toLowerCase().replace("_","")
val mode = if(currentEntity.isBattling) "battle" else "casual"
val sendflash = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/${mode}/sendflash"))
sendflash?.let { effect ->
val wrapper = MatrixWrapper()
val matrix = MatrixStack()
matrix.translate(newPos.x, newPos.y, newPos.z)
wrapper.updateMatrix(matrix.peek().positionMatrix)
val world = MinecraftClient.getInstance().world ?: return@let
ParticleStorm(effect, wrapper, world).spawn()
val ballsparks = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/${mode}/ballsparks"))
val ballsendsparkle = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/${mode}/ballsendsparkle"))
// using afterOnClient because it's such a small timeframe that it's unlikely the entity has been removed & we'd like the precision
afterOnClient(seconds = 0.01667f) {
ballsparks?.let { effect ->
ParticleStorm(effect, wrapper, world).spawn()
}
ballsendsparkle?.let { effect ->
ParticleStorm(effect, wrapper, world).spawn()
}
currentEntity.after(seconds = 0.4f) {
val ballsparkle = BedrockParticleEffectRepository.getEffect(cobblemonResource("${ballType}/ballsparkle"))
ballsparkle?.let { effect ->
ParticleStorm(effect, wrapper, world).spawn()
}
}
}
}
}
}
}
currentEntity.after(seconds = BEAM_EXTEND_TIME) {
lerpOnClient(BEAM_SHRINK_TIME) { entityScaleModifier = it }
currentEntity.isInvisible = false
currentEntity.after(seconds = POKEBALL_AIR_TIME*2){
ballOffset = 0f
ballRotOffset = 0f
sendOutPosition = null
}
}
}
}
else -> {
// Scaling down into pokeball
entityScaleModifier = 1F
beamStartTime = System.currentTimeMillis()
ballOffset = 0f
ballRotOffset = 0f
sendOutPosition = null
afterOnClient(seconds = BEAM_EXTEND_TIME) {
val afterPoint2 = System.currentTimeMillis()
lerpOnClient(BEAM_SHRINK_TIME) {
lerpOnClient(BEAM_SHRINK_TIME) {
entityScaleModifier = (1 - it)
}
}
@@ -118,6 +226,27 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
override fun initialize(entity: PokemonEntity) {
this.currentEntity = entity
this.age = entity.age
this.runtime.environment.getQueryStruct().addFunctions(mapOf(
"in_battle" to java.util.function.Function {
return@Function DoubleValue(currentEntity.isBattling)
},
"shiny" to java.util.function.Function {
return@Function DoubleValue(currentEntity.pokemon.shiny)
},
"form" to java.util.function.Function {
return@Function StringValue(currentEntity.pokemon.form.name)
},
"width" to java.util.function.Function {
return@Function DoubleValue(currentEntity.boundingBox.xLength)
},
"height" to java.util.function.Function {
return@Function DoubleValue(currentEntity.boundingBox.yLength)
},
"weight" to java.util.function.Function {
return@Function DoubleValue(currentEntity.pokemon.species.weight.toDouble())
}
))
}
override fun tick(entity: PokemonEntity) {
@@ -144,12 +273,16 @@ class PokemonClientDelegate : PoseableEntityState<PokemonEntity>(), PokemonSideD
fun cry() {
val model = currentModel ?: return
if (model is PokemonPoseableModel) {
if (cryAnimation != null && cryAnimation in statefulAnimations) {
if (cryAnimation != null && (cryAnimation in statefulAnimations || cryAnimation == primaryAnimation)) {
return
}
val animation = model.cryAnimation(currentEntity, this) ?: return
statefulAnimations.add(animation)
if (animation is PrimaryAnimation) {
addPrimaryAnimation(animation)
} else {
statefulAnimations.add(animation)
}
cryAnimation = animation
}
}
@@ -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,
actionEffect = null
)
}
}
@@ -9,7 +9,9 @@
package com.cobblemon.mod.common.client.gui.interact.wheel
import com.cobblemon.mod.common.api.gui.blitk
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.tooltip.Tooltip
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.util.math.MatrixStack
@@ -21,6 +23,7 @@ import org.joml.Vector4f
class InteractWheelButton(
private val iconResource: Identifier?,
private val buttonResource: Identifier,
private val tooltipText: String?,
x: Int,
y: Int,
private val isEnabled: Boolean,
@@ -50,6 +53,12 @@ class InteractWheelButton(
alpha = if (isEnabled) 1f else 0.4f
)
if(isHovered(mouseX.toFloat(), mouseY.toFloat())){
tooltipText?.let {
context.drawTooltip(MinecraftClient.getInstance().textRenderer, Text.translatable(it), mouseX, mouseY)
}
}
if (iconResource != null) {
val (iconX, iconY) = getIconPosition()
val colour = this.colour() ?: Vector3f(1F, 1F, 1F)
@@ -86,4 +95,8 @@ class InteractWheelButton(
return mouseX in xMin..xMax && mouseY in yMin..yMax
}
override fun getTooltip(): Tooltip? {
tooltipText?.let { return Tooltip.of(Text.translatable(it)) } ?: return super.getTooltip()
}
}
@@ -9,15 +9,19 @@
package com.cobblemon.mod.common.client.gui.interact.wheel
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.startselection.widgets.preview.ArrowButton
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.common.collect.Multimap
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.Drawable
import net.minecraft.client.gui.Element
import net.minecraft.client.gui.Selectable
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
import kotlin.math.max
class InteractWheelGUI(private val options: Map<Orientation, InteractWheelOption>, title: Text) :
class InteractWheelGUI(private val options: Multimap<Orientation, InteractWheelOption>, title: Text) :
Screen(title) {
companion object {
const val SIZE = 138
const val OPTION_SIZE = 69
@@ -30,11 +34,59 @@ class InteractWheelGUI(private val options: Map<Orientation, InteractWheelOption
)
}
private val buttons = mutableListOf<InteractWheelButton>()
private var maxPage = 1
private var currentPage = 0
override fun init() {
addButton(Orientation.TOP_LEFT, options[Orientation.TOP_LEFT])
addButton(Orientation.TOP_RIGHT, options[Orientation.TOP_RIGHT])
addButton(Orientation.BOTTOM_LEFT, options[Orientation.BOTTOM_LEFT])
addButton(Orientation.BOTTOM_RIGHT, options[Orientation.BOTTOM_RIGHT])
calculateMaxPage()
addButton(Orientation.TOP_LEFT, options[Orientation.TOP_LEFT].toList().getOrNull(0))
addButton(Orientation.TOP_RIGHT, options[Orientation.TOP_RIGHT].toList().getOrNull(0))
addButton(Orientation.BOTTOM_LEFT, options[Orientation.BOTTOM_LEFT].toList().getOrNull(0))
addButton(Orientation.BOTTOM_RIGHT, options[Orientation.BOTTOM_RIGHT].toList().getOrNull(0))
if(maxPage > 1){
addDrawableChild(ArrowButton(
// x = left 3rd, y = center
pX = (width / 3) - 12,
pY = (height / 2) - 7,
pWidth = 9,
pHeight = 14,
right = false,
onPress = {
// loop to last page if on page 0, otherwise go to previous page
setPage(if(currentPage == 0) maxPage - 1 else currentPage - 1)
}
))
addDrawableChild(ArrowButton(
// x = right 3rd, y = center
pX = (width / 3) * 2,
pY = (height / 2) - 7,
pWidth = 9,
pHeight = 14,
right = true,
onPress = { setPage((currentPage + 1) % max(1, maxPage)) }
))
}
}
private fun calculateMaxPage() {
maxPage = max(
max(options[Orientation.TOP_LEFT].size,
options[Orientation.TOP_RIGHT].size),
max(options[Orientation.BOTTOM_LEFT].size,
options[Orientation.BOTTOM_RIGHT].size)
)
}
private fun setPage(page: Int) {
currentPage = page
buttons.forEach { remove(it) }
buttons.clear()
val orientations = Orientation.values()
orientations.forEach { orientation ->
val option = options[orientation].toList().getOrNull(page)
addButton(orientation, option)
}
}
private fun addButton(orientation: Orientation, option: InteractWheelOption?) {
@@ -42,6 +94,7 @@ class InteractWheelGUI(private val options: Map<Orientation, InteractWheelOption
addDrawableChild(InteractWheelButton(
iconResource = option?.iconResource,
buttonResource = buttonResources[orientation]!!,
tooltipText = option?.tooltipText,
x = x,
y = y,
isEnabled = option != null,
@@ -50,6 +103,13 @@ class InteractWheelGUI(private val options: Map<Orientation, InteractWheelOption
))
}
override fun <T> addDrawableChild(drawableElement: T): T where T : Element?, T : Drawable?, T : Selectable? {
if (drawableElement is InteractWheelButton) {
buttons.add(drawableElement)
}
return super.addDrawableChild(drawableElement)
}
override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
val (x, y) = getDimensions()
blitk(
@@ -9,6 +9,8 @@
package com.cobblemon.mod.common.client.gui.interact.wheel
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokemon.interaction.PokemonInteractionGUICreationEvent
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
import com.cobblemon.mod.common.net.messages.server.BattleChallengePacket
@@ -16,10 +18,10 @@ import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.interact.InteractPokemonPacket
import com.cobblemon.mod.common.net.messages.server.trade.AcceptTradeRequestPacket
import com.cobblemon.mod.common.net.messages.server.trade.OfferTradePacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.common.collect.ArrayListMultimap
import com.google.common.collect.Multimap
import net.minecraft.client.MinecraftClient
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.text.Text
import java.util.*
import org.joml.Vector3f
@@ -27,6 +29,7 @@ import org.joml.Vector3f
fun createPokemonInteractGui(pokemonID: UUID, canMountShoulder: Boolean): InteractWheelGUI {
val mountShoulder = InteractWheelOption(
iconResource = cobblemonResource("textures/gui/interact/icon_shoulder.png"),
tooltipText = "cobblemon.ui.interact.mount.shoulder",
onPress = {
if (canMountShoulder) {
InteractPokemonPacket(pokemonID, true).sendToServer()
@@ -36,17 +39,18 @@ fun createPokemonInteractGui(pokemonID: UUID, canMountShoulder: Boolean): Intera
)
val giveItem = InteractWheelOption(
iconResource = cobblemonResource("textures/gui/interact/icon_held_item.png"),
tooltipText = "cobblemon.ui.interact.give.item",
onPress = {
InteractPokemonPacket(pokemonID, false).sendToServer()
closeGUI()
}
)
val options = mutableMapOf(
Orientation.TOP_RIGHT to giveItem,
)
val options: Multimap<Orientation, InteractWheelOption> = ArrayListMultimap.create()
options.put(Orientation.TOP_RIGHT, giveItem)
if (canMountShoulder) {
options[Orientation.TOP_LEFT] = mountShoulder
options.put(Orientation.TOP_LEFT, mountShoulder)
}
CobblemonEvents.POKEMON_INTERACTION_GUI_CREATION.post(PokemonInteractionGUICreationEvent(pokemonID, canMountShoulder, options))
return InteractWheelGUI(options, Text.translatable("cobblemon.ui.interact.pokemon"))
}
@@ -54,6 +58,7 @@ fun createPlayerInteractGui(optionsPacket: PlayerInteractOptionsPacket): Interac
val trade = InteractWheelOption(
iconResource = cobblemonResource("textures/gui/interact/icon_trade.png"),
colour = { if (CobblemonClient.requests.tradeOffers.any { it.traderId == optionsPacket.targetId }) Vector3f(0F, 0.6F, 0F) else null },
tooltipText = "cobblemon.ui.interact.trade",
onPress = {
val tradeOffer = CobblemonClient.requests.tradeOffers.find { it.traderId == optionsPacket.targetId }
if (tradeOffer == null) {
@@ -68,6 +73,7 @@ fun createPlayerInteractGui(optionsPacket: PlayerInteractOptionsPacket): Interac
val battle = InteractWheelOption(
iconResource = cobblemonResource("textures/gui/interact/icon_battle.png"),
colour = { if (CobblemonClient.requests.battleChallenges.any { it.challengerId == optionsPacket.targetId }) Vector3f(0F, 0.6F, 0F) else null },
tooltipText = "cobblemon.ui.interact.battle",
onPress = {
val battleRequest = CobblemonClient.requests.battleChallenges.find { it.challengerId == optionsPacket.targetId }
// This can be improved in future with more detailed battle challenge data.
@@ -81,19 +87,20 @@ fun createPlayerInteractGui(optionsPacket: PlayerInteractOptionsPacket): Interac
onPress = {
SpectateBattlePacket(optionsPacket.targetId).sendToServer()
closeGUI()
}
)
val options = mutableMapOf<Orientation, InteractWheelOption>()
},
tooltipText = "cobblemon.ui.interact.spectate"
)
val options: Multimap<Orientation, InteractWheelOption> = ArrayListMultimap.create()
//The way things are positioned should probably be more thought out if more options are added
optionsPacket.options.map {
if (it.equals(PlayerInteractOptionsPacket.Options.TRADE)) {
options[Orientation.TOP_LEFT] = trade
options.put(Orientation.TOP_LEFT, trade)
}
if (it.equals(PlayerInteractOptionsPacket.Options.BATTLE)) {
options[Orientation.TOP_RIGHT] = battle
options.put(Orientation.TOP_RIGHT, battle)
}
if (it.equals(PlayerInteractOptionsPacket.Options.SPECTATE_BATTLE)) {
options[Orientation.TOP_RIGHT] = spectate
options.put(Orientation.TOP_RIGHT, spectate)
}
}
return InteractWheelGUI(options, Text.translatable("cobblemon.ui.interact.player"))
@@ -13,6 +13,7 @@ import org.joml.Vector3f
data class InteractWheelOption(
val iconResource: Identifier,
val tooltipText: String?,
val colour: () -> Vector3f? = { null },
val onPress: () -> Unit
)
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.animation
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.entity.Poseable
import com.cobblemon.mod.common.net.messages.client.animation.PlayPoseableAnimationPacket
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolve
import net.minecraft.client.MinecraftClient
object PlayPoseableAnimationHandler : ClientNetworkPacketHandler<PlayPoseableAnimationPacket> {
override fun handle(packet: PlayPoseableAnimationPacket, client: MinecraftClient) {
val entity = client.world?.getEntityById(packet.entityId) ?: return
if (entity is Poseable) {
val delegate = entity.delegate
if (delegate is PoseableEntityState<*>) {
for (expr in packet.expressions) {
delegate.runtime.resolve(expr.asExpression())
}
delegate.addFirstAnimation(packet.animation)
}
}
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.effect
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.entity.Poseable
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolve
import net.minecraft.client.MinecraftClient
object RunPosableMoLangHandler : ClientNetworkPacketHandler<RunPosableMoLangPacket> {
override fun handle(packet: RunPosableMoLangPacket, client: MinecraftClient) {
val entity = client.world?.getEntityById(packet.entityId) ?: return
if (entity is Poseable) {
val state = entity.delegate as? PoseableEntityState<*> ?: return
for (expression in packet.expressions.map { it.asExpression() }) {
state.runtime.resolve(expression)
}
}
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.effect
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.particle.BedrockParticleEffectRepository
import com.cobblemon.mod.common.client.particle.ParticleStorm
import com.cobblemon.mod.common.client.render.models.blockbench.PoseableEntityState
import com.cobblemon.mod.common.entity.Poseable
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
import net.minecraft.client.MinecraftClient
import net.minecraft.entity.Entity
object SpawnSnowstormEntityParticleHandler : ClientNetworkPacketHandler<SpawnSnowstormEntityParticlePacket> {
override fun handle(packet: SpawnSnowstormEntityParticlePacket, client: MinecraftClient) {
val world = MinecraftClient.getInstance().world ?: return
val effect = BedrockParticleEffectRepository.getEffect(packet.effectId) ?: return
val entity = world.getEntityById(packet.entityId) as? Poseable ?: return
entity as Entity
val state = entity.delegate as PoseableEntityState<*>
val matrixWrapper = state.locatorStates[packet.locator] ?: state.locatorStates["root"]!!
val particleRuntime = MoLangRuntime().setup().setupClient()
particleRuntime.environment.getQueryStruct().addFunction("entity") { state.runtime.environment.getQueryStruct() }
val storm = ParticleStorm(
effect = effect,
matrixWrapper = matrixWrapper,
world = world,
runtime = particleRuntime,
sourceVelocity = { entity.velocity },
sourceAlive = { !entity.isRemoved },
sourceVisible = { !entity.isInvisible }
)
storm.spawn()
}
}
@@ -1,25 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.sound
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.sound.PokemonCryPacket
import net.minecraft.client.MinecraftClient
object PokemonCryHandler : ClientNetworkPacketHandler<PokemonCryPacket> {
override fun handle(packet: PokemonCryPacket, client: MinecraftClient) {
val entity = client.world?.getEntityById(packet.entityId) ?: return
if (entity is PokemonEntity) {
val delegate = entity.delegate as PokemonClientDelegate
delegate.cry()
}
}
}

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