Compare commits

...
Author SHA1 Message Date
Gertimoshka 2401985688 Stadium Block Rotation and Center defenitions 2025-05-14 13:31:55 +03:00
Gertimoshka 28e59f8bf9 Refactor Stadium Terminal (Block) to be Stadium Block 2025-04-30 08:38:04 +03:00
Gertimoshka 902f602354 Merge up-to-date branch 'main' into stadium-block-gert 2025-04-27 12:24:00 +03:00
Gertimoshka 8d8ac066f4 Merge Main into Stadium-Block-Gert 2025-04-27 12:12:32 +03:00
Gertimoshka bf53763443 Begginings of Stadium Block itself 2025-04-27 12:04:31 +03:00
Hiroku db573455ea Merge branch 'cross-dimensional-packets' into 'main'
Prevent Cross Dimensional Packets

See merge request cable-mc/cobblemon!1630
2025-04-27 00:06:22 +00:00
Hiroku 3af002a39c Changelog 2025-04-26 23:55:04 +00:00
Polymeta a15c44325d Merge branch 'riding-error-handling' into 'main'
Fail gracefully on riding deserialization errors

See merge request cable-mc/cobblemon!1639
2025-04-26 22:07:06 +00:00
Apion 33fc67ed58 Merge branch 'npc-gibberspeak' into 'main'
NPC gibberish

See merge request cable-mc/cobblemon!1616
2025-04-26 21:22:22 +00:00
Apion b9f2bcc3a3 Fix missing import causing compile error 2025-04-26 17:10:59 -04:00
Apion 8a5a88b4f1 Merge branch 'particle-reorg' into 'main'
𝕿𝖍𝖊 𝕲𝖗𝖊𝖆𝖙 𝕻𝖆𝖗𝖙𝖎𝖈𝖑𝖊 𝕽𝖊𝖔𝖗𝖌 𝖔𝖋 2025

See merge request cable-mc/cobblemon!1627
2025-04-26 21:01:01 +00:00
Apion af6b1190c1 Merge branch 'main' into npc-gibberspeak
# Conflicts:
#	common/src/main/resources/cobblemon-common.accesswidener
#	common/src/main/resources/mixins.cobblemon-common.json
2025-04-26 16:37:58 -04:00
Apion 7f0a6d4d48 Merge branch 'no-rolling' into 'main'
Option to disable rolling is now functional. I would not use this on anything...

See merge request cable-mc/cobblemon!1592
2025-04-26 20:22:15 +00:00
Apion d7e4298a91 Fix compile error from flashback MR 2025-04-26 15:42:44 -04:00
Landon Wills 923b4e4266 Change from throwing exception to logging warning when we fail to deserialize riding behaviour 2025-04-26 12:08:33 -04:00
Sterrezo 07bba37c03 Restored and relocated assets 2025-04-25 20:00:00 +02:00
Sterrezo 5896a74d1f Added generic gibber sound 2025-04-25 19:14:45 +02:00
Gertimoshka 7e553c440e Renamed a lot of classes as Stadium Cameras is just Battle Camera, and the system is not directly connected to Stadium Block 2025-04-22 14:18:44 +03:00
Gertimoshka be2e576df6 Battle Camera System can now be provided with center and yRot and all the Cameras applied will be offset by them 2025-04-22 13:55:10 +03:00
CeeJay da7dbeaaec add check for player dimension 2025-04-22 00:26:37 -04:00
321retrogamer dea2ad4b12 𝕿𝖍𝖊 𝕲𝖗𝖊𝖆𝖙 𝕻𝖆𝖗𝖙𝖎𝖈𝖑𝖊 𝕽𝖊𝖔𝖗𝖌 𝖔𝖋 2025
Completely reworks particle texture pathways and alters particles to direct to those pathways. All existing 1.6.1 particles (as well as the new Protect activation particles, which are also here) have been altered to fit this reorganization effort.

All particle textures have also been renamed to match the guidelines of the reorg, as they are now seen in the assets repo.
2025-04-20 23:28:39 -05:00
Paul 7dd38bf043 Ratified dialogue gibberish with cool toys 2025-04-19 14:29:47 +10:00
Test 4caba2fbeb gibber sounds 2025-04-19 03:58:20 +00:00
Paul e6b1529f27 Gradual reveal of text while gibbering 2025-04-18 22:16:53 +10:00
Landon Wills a42ba8de43 Make dialogues play gibberish as a NPC POC 2025-04-18 06:07:18 -04:00
Gertimoshka 5bc056d28e Breaking my head working on this, but steady progress finalizing the camera system itself 2025-04-15 11:54:29 +03:00
Hiroku 2e05b25061 Merge branch 'main' into 'no-rolling'
# Conflicts:
#   common/src/main/java/com/cobblemon/mod/common/mixin/client/CameraMixin.java
2025-04-12 02:01:46 +00:00
JPAK 8aeb7a43c8 Option to disable rolling is now functional. I would not use this on anything and do flips as the camera does not handle it well.
Also fixed issue where camera Forward and Left would be improperly set in third person reverse.
2025-04-09 22:52:00 -05:00
Gertimoshka 3033adcc2f IT MOVES 2025-04-05 11:55:07 +03:00
Gertimoshka 252634bfde Setup the code that will handle the camera data and apply it 2025-04-05 08:04:24 +03:00
Gertimoshka 0f38c6b00e Setup Data Handling and Syncing for Stadium Cameras 2025-04-01 15:27:22 +03:00
Gertimoshka f711f748a8 A lot of Stadium Camera Data for the Data Gods before implementation 2025-03-29 10:27:34 +02:00
Gertimoshka c2fb914e08 Working on Networking for Stadium 2025-03-22 23:33:35 +02:00
Gertimoshka 7947e19337 Fixed Errors with Mergin Main into branch 2025-03-21 21:56:49 +02:00
Gertimoshka 5bde172731 Detached the Camera from the player 2025-03-21 21:43:59 +02:00
Gertimoshka 496e04a779 Merging Main into stadium-block-gert 2025-03-21 14:50:22 +02:00
Gertimoshka ff4192b43c Preparing to handle starting Stadium Battles 2025-03-21 14:47:27 +02:00
Gertimoshka 415591a443 Stadium Block now can innitate a 1v1 battle 2025-03-21 14:47:26 +02:00
Gertimoshka efb1eaf3e1 Setup to prepare to develop Stadium Block Functionality 2025-03-21 14:47:26 +02:00
Plastered_CrabandGertimoshka 7ebd2bdb94 Render Rotom drone camera to start orbiting around + try to avoid collisions using raycast point cloud 2025-03-21 14:47:24 +02:00
FlarkinaterandGertimoshka e07579bf0a Fix drone animation 2025-03-21 14:47:23 +02:00
FlarkinaterandGertimoshka 0695117549 Revert debug change 2025-03-21 14:47:22 +02:00
FlarkinaterandGertimoshka d6cd04e2a4 Attempt to debug the animation issue 2025-03-21 14:47:21 +02:00
FlarkinaterandGertimoshka 8240cd10e2 Attempt to make the drone animated (WIP) 2025-03-21 14:47:19 +02:00
FlarkinaterandGertimoshka 2a1e749787 Refactor - move most drone assets into bedrock/drone 2025-03-21 14:47:18 +02:00
FlarkinaterandGertimoshka 6d9daf692d Fix upside-down drone 2025-03-21 14:47:17 +02:00
FlarkinaterandGertimoshka 1f7df20e77 Render the drone in-game 2025-03-21 14:47:16 +02:00
FlarkinaterandGertimoshka 02a4b91778 Add Rotom drone model/texture/animation placeholders 2025-03-21 14:47:15 +02:00
FlarkinaterandGertimoshka a37da4d4f2 Remove placeholder model, we'll just use some "dev art" for now 2025-03-21 14:47:14 +02:00
FlarkinaterandGertimoshka 66817b5e9b Update deprecated methods, imports, and formatting 2025-03-21 14:47:10 +02:00
FlarkinaterandGertimoshka fd54e3a7e0 Rebase old stadium camera test branch 2025-03-21 14:47:08 +02:00
Gertimoshka 79f2b67bf9 Preparing to send client reqired info about Stadium Battle 2025-03-21 14:44:34 +02:00
Gertimoshka 2352dae6e0 Preparing to handle starting Stadium Battles 2025-03-20 18:35:46 +02:00
Gertimoshka 9ec100f9ea Stadium Block now can innitate a 1v1 battle 2025-03-20 12:36:12 +02:00
Gertimoshka 17c524ab9e Setup to prepare to develop Stadium Block Functionality 2025-03-17 22:30:28 +02:00
Plastered_Crab 86d48df9fe Render Rotom drone camera to start orbiting around + try to avoid collisions using raycast point cloud 2025-02-28 16:59:03 -08:00
Flarkinater 3401ba9cbb Fix drone animation 2025-02-15 21:48:29 -05:00
Flarkinater dac9ccb461 Revert debug change 2025-02-15 21:47:59 -05:00
Flarkinater 191a62f693 Attempt to debug the animation issue 2025-02-15 16:50:52 -05:00
Flarkinater d8d410e539 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into stadium-block 2025-02-15 15:37:37 -05:00
Flarkinater b7c6e1ea77 Attempt to make the drone animated (WIP) 2025-02-15 15:35:48 -05:00
Flarkinater 186812d6fd Refactor - move most drone assets into bedrock/drone 2025-02-14 12:23:27 -05:00
Flarkinater 1c0bdc766a Fix upside-down drone 2025-02-12 14:55:41 -05:00
Flarkinater c697bcd2e9 Render the drone in-game 2025-02-12 01:17:42 -05:00
Flarkinater 9d6ff01d80 Add Rotom drone model/texture/animation placeholders 2025-02-10 20:20:24 -05:00
Flarkinater 1691853a19 Remove placeholder model, we'll just use some "dev art" for now 2025-02-10 20:20:12 -05:00
Flarkinater 4e9a4e1e5a Update deprecated methods, imports, and formatting 2025-02-10 20:20:03 -05:00
Flarkinater 6450255a0b Rebase old stadium camera test branch 2025-02-10 20:19:13 -05:00
562 changed files with 4221 additions and 1903 deletions
+1
View File
@@ -165,6 +165,7 @@
- Fixed certain Pokémon with forms not having appropriate stock Pokédex entries.
- Fixed issue with Pokédex Scanner that caused the open/close overlay to have the wrong opacity values
- Fixed dragon's breath not being usable on the restoration tank when it should be
- Fixed some effects like particles from fishing rods appearing for players in the same coordinates in another world.
### Developer
- A finished battle now has winners and losers set inside of `PokemonBattle` instead of them always being empty.
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.riding.Rideable;
import com.cobblemon.mod.common.client.MountedPokemonAnimationRenderController;
import com.cobblemon.mod.common.client.render.camera.MountedCameraRenderer;
@@ -51,11 +52,10 @@ public abstract class CameraMixin {
@Unique private float rollAngleStart = 0;
@Unique Minecraft minecraft = Minecraft.getInstance();
@Unique boolean disableRollableCameraDebug = false;
@Inject(method = "setRotation", at = @At("HEAD"), cancellable = true)
public void cobblemon$setRotation(float f, float g, CallbackInfo ci) {
if (!(this.entity instanceof OrientationControllable controllable) || disableRollableCameraDebug) return;
if (!(this.entity instanceof OrientationControllable controllable) || Cobblemon.config.getDisableRoll()) return;
var controller = controllable.getOrientationController();
if (!controller.isActive() && controller.getOrientation() != null) {
if(this.returnTimer < 1) {
@@ -128,9 +128,9 @@ public abstract class CameraMixin {
this.rotation.set(newRotation);
this.xRot = controller.getPitch();
this.yRot = controller.getYaw();
this.forwards.set(controller.getForwardVector());
this.up.set(controller.getUpVector());
this.left.set(controller.getLeftVector());
OrientationController.Companion.getFORWARDS().rotate(this.rotation, this.forwards);
OrientationController.Companion.getUP().rotate(this.rotation, this.up);
OrientationController.Companion.getLEFT().rotate(this.rotation, this.left);
}
@WrapOperation(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;move(FFF)V", ordinal = 0))
@@ -9,10 +9,20 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker;
import com.cobblemon.mod.common.stadium.BattleCameraManagerClient;
import com.cobblemon.mod.common.util.CameraExtensionsKt;
import net.minecraft.client.Camera;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.world.entity.Entity;
import org.joml.Matrix4f;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -20,6 +30,17 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(GameRenderer.class)
public class GameRendererMixin {
@Shadow @Final Minecraft minecraft;
@Shadow float renderDistance;
@Shadow @Final private LightTexture lightTexture;
@Shadow private boolean renderHand;
@Shadow private double getFov(Camera activeRenderInfo, float partialTicks, boolean useFOVSetting) { return 0.0; }
@Shadow public void resetProjectionMatrix(Matrix4f matrix) {}
@Shadow public Matrix4f getProjectionMatrix(double fov) { return new Matrix4f(); }
@Shadow private void renderItemInHand(Camera camera, float partialTick, Matrix4f projectionMatrix) {}
@Shadow public void pick(float partialTicks) {}
@Unique
long lastTime = -1;
@@ -42,4 +63,44 @@ public class GameRendererMixin {
lastTime = newTime;
}
@Inject(method = "renderLevel", at = @At(value = "HEAD"), cancellable = true)
public void renderLevel(DeltaTracker deltaTracker, CallbackInfo ci) {
if (BattleCameraManagerClient.INSTANCE.shouldUseStadiumCamera()) {
float f = deltaTracker.getGameTimeDeltaPartialTick(true);
this.lightTexture.updateLightTexture(f);
if (this.minecraft.getCameraEntity() == null) {
this.minecraft.setCameraEntity(this.minecraft.player);
}
this.minecraft.getProfiler().push("camera");
Camera stadiumCamera = new Camera();
Entity entity = (Entity) this.minecraft.player;
stadiumCamera.setup(this.minecraft.level, entity, true, false, 1.0F);
//adjust camera
BattleCameraManagerClient.INSTANCE.updateCameraData(f);
stadiumCamera.setPosition(BattleCameraManagerClient.INSTANCE.getCameraPosition());
Vector3f rotations = BattleCameraManagerClient.INSTANCE.getCameraRotation();
CameraExtensionsKt.setRotation(stadiumCamera, rotations.x, rotations.y, rotations.z);
this.renderDistance = (float)(this.minecraft.options.getEffectiveRenderDistance() * 16);
double d = this.getFov(stadiumCamera, f, true);
Matrix4f matrix4f = this.getProjectionMatrix(d);
this.resetProjectionMatrix(matrix4f);
Quaternionf quaternionf = stadiumCamera.rotation().conjugate(new Quaternionf());
Matrix4f matrix4f2 = (new Matrix4f()).rotation(quaternionf);
this.minecraft.levelRenderer.prepareCullFrustum(stadiumCamera.getPosition(), matrix4f2, this.getProjectionMatrix(Math.max(d, (double)(Integer)this.minecraft.options.fov().get())));
this.minecraft.levelRenderer.renderLevel(deltaTracker, false, stadiumCamera, (GameRenderer) (Object) this, this.lightTexture, matrix4f2, matrix4f);
this.minecraft.getProfiler().pop();
ci.cancel();
}
}
@Inject(method = "tick", at = @At(value = "TAIL"))
public void tick(CallbackInfo ci) {
BattleCameraManagerClient.INSTANCE.tickCameraManager();
}
}
@@ -0,0 +1,46 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.client.render.TextClipping;
import net.minecraft.client.gui.Font;
import net.minecraft.network.chat.Style;
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;
/**
* This mixin facilitates the gradual reveal of text with NPC dialogue. If there is a better way
* to do this, I'd love to hear it.
*
* The idea is that somewhere static in the mod we have a control for how many characters can be drawn
* and how many have been drawn so far, and this mixin will add to the latter and stop drawing characters
* once it reaches the former.
*
* This relies STRONGLY on the code that sets the character cap to reset it to -1 after the text is rendered,
* otherwise all text in Minecraft will stop drawing.
*
* Sites that take advantage of this mixin will do the following:
* 1: Set the character cap, which resets the drawn counter to zero.
* 2: Call whatever Minecraft function draws your text.
* 3: Reset the character cap to -1, which will allow regular text to draw again.
*
* @author Hiroku
* @since April 18th, 2025
*/
@Mixin(Font.StringRenderOutput.class)
public class StringRenderOutputMixin {
@Inject(method = "accept", at = @At(value = "HEAD"), cancellable = true)
public void onAccept(int i, Style style, int j, CallbackInfoReturnable<Boolean> cir) {
if (!TextClipping.canDrawAnotherCharacter()) {
cir.setReturnValue(true);
}
}
}
@@ -37,6 +37,9 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
@JvmField
val PC: BlockEntityType<PCBlockEntity> = this.create("pc", BlockEntityType.Builder.of(::PCBlockEntity, CobblemonBlocks.PC).build(null))
@JvmField
val STADIUM: BlockEntityType<StadiumBlockEntity> = this.create("stadium_block", BlockEntityType.Builder.of(::StadiumBlockEntity, CobblemonBlocks.STADIUM_BLOCK).build(null))
@JvmField
val LECTERN: BlockEntityType<LecternBlockEntity> = this.create("lectern", BlockEntityType.Builder.of(::LecternBlockEntity, CobblemonBlocks.LECTERN).build(null))
@@ -482,6 +482,9 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val PC = create("pc", PCBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F).noOcclusion().lightLevel { if ((it.getValue(PCBlock.ON) as Boolean) && (it.getValue(PCBlock.PART) == PCBlock.PCPart.TOP)) 10 else 0 }))
@JvmField
val STADIUM_BLOCK = create("stadium_block", StadiumBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F)))
@JvmField
val LECTERN = create("lectern", LecternBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).sound(SoundType.WOOD).strength(2.5F).ignitedByLava()))
@@ -619,6 +619,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val PC = blockItem("pc", CobblemonBlocks.PC)
@JvmField
val STADIUM_BLOCK = blockItem("stadium_block", CobblemonBlocks.STADIUM_BLOCK)
@JvmField
val PASTURE = blockItem("pasture", CobblemonBlocks.PASTURE)
@JvmField
val DISPLAY_CASE = blockItem("display_case", CobblemonBlocks.DISPLAY_CASE)
@@ -225,6 +225,10 @@ import com.cobblemon.mod.common.net.serverhandling.trade.ChangeTradeAcceptanceHa
import com.cobblemon.mod.common.net.serverhandling.trade.OfferTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.PerformTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.UpdateTradeOfferHandler
import com.cobblemon.mod.common.stadium.BattleCamerasSyncPacket
import com.cobblemon.mod.common.stadium.BattleCameraSetupHandler
import com.cobblemon.mod.common.stadium.BattleCameraPacket
import com.cobblemon.mod.common.stadium.BattleCameraManagersSyncPacket
import com.cobblemon.mod.common.util.server
import net.minecraft.server.level.ServerPlayer
@@ -382,6 +386,8 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(SeasoningRegistrySyncPacket.ID, SeasoningRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(FlowRegistrySyncPacket.ID, FlowRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(CosmeticItemAssignmentSyncPacket.ID, CosmeticItemAssignmentSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BattleCamerasSyncPacket.ID, BattleCamerasSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BattleCameraManagersSyncPacket.ID, BattleCameraManagersSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(MarkRegistrySyncPacket.ID, MarkRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
// Effects
@@ -442,6 +448,9 @@ object CobblemonNetwork {
// Debug / cheats
list.add(PacketRegisterInfo(CalculateSeatPositionsPacket.ID, CalculateSeatPositionsPacket::decode, CalculateSeatPositionsHandler))
// Stadium packets
list.add(PacketRegisterInfo(BattleCameraPacket.ID, BattleCameraPacket::decode, BattleCameraSetupHandler))
// Riding
list.add(PacketRegisterInfo(ClientboundUpdateRidingStatePacket.ID, ClientboundUpdateRidingStatePacket::decode, ClientboundUpdateRidingStateHandler))
@@ -39,6 +39,10 @@ object CobblemonPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Regist
val INCENSE_SWEET_KEY: ResourceKey<PoiType> = createKey("saccharine_honey_log")
@JvmField
val INCENSE_SWEET = create(INCENSE_SWEET_KEY.location().path, PoiType(getBlockStates(CobblemonBlocks.INCENSE_SWEET), 0, 1))
@JvmField
val STADIUM_BLOCK_KEY: ResourceKey<PoiType> = createKey("stadium_block")
@JvmField
val STADIUM_BLOCK = create(STADIUM_BLOCK_KEY.location().path, PoiType(getBlockStates(CobblemonBlocks.STADIUM_BLOCK), 0, 1))
private fun createKey(string: String): ResourceKey<PoiType> = ResourceKey.create(Registries.POINT_OF_INTEREST_TYPE, cobblemonResource(string))
@@ -43,8 +43,18 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val PC_WALLPAPER_UNLOCK = this.create("pc.wallpaper.unlock")
@JvmField
val NPC_GIBBER_GENERIC = this.create("entity.npc.gibber.generic")
@JvmField
val NPC_GIBBER_SPEECH_TYPE_1_1 = this.create("entity.npc.gibber.speech_type_1_1")
@JvmField
val NPC_GIBBER_SPEECH_TYPE_1_2 = this.create("entity.npc.gibber.speech_type_1_2")
@JvmField
val NPC_GIBBER_SPEECH_TYPE_1_3 = this.create("entity.npc.gibber.speech_type_1_3")
@JvmField
val VILLAGER_WORK_NURSE = this.create("entity.villager.work_nurse")
@JvmField
val HEALING_MACHINE_ACTIVE = this.create("block.healing_machine.active")
@@ -460,4 +470,4 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
)
private fun create(name: String): SoundEvent = this.create(name, SoundEvent.createVariableRangeEvent(cobblemonResource(name)))
}
}
@@ -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.api.dialogue
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.resources.ResourceLocation
/**
* Settings for the gibbering sound effect that plays gradually as dialogue appears.
*
* @author Hiroku, landonjw
* @since April 19th, 2025
*/
class DialogueGibber(
/** Whether text will gradually appear while the gibbering occurs. */
val graduallyShowText: Boolean = true,
/** Whether clicking will make the gibber skip to the end. */
val allowSkip: Boolean = true,
/** The space between gibbers in terms of characters. 'Holy cow' would play 2 sounds if step = 4. */
val step: Int = 4,
/** The time (in seconds) between gibbers. The smaller this is, the faster the text will progress. */
val interval: Double = 0.1,
val minPitch: Float = 0.9F,
val maxPitch: Float = 1.1F,
val minVolume: Float = 0.9F,
val maxVolume: Float = 1.0F,
/** The possible sounds to play when gibbering. */
val sounds: List<ResourceLocation> = listOf(
cobblemonResource("entity.npc.gibber.generic")
)
)
@@ -31,6 +31,7 @@ class DialoguePage(
var lines: MutableList<DialogueText> = mutableListOf(),
var input: DialogueInput = DialogueNoInput(),
var background: ResourceLocation? = null,
var gibber: DialogueGibber? = null,
var clientActions: MutableList<Expression> = mutableListOf(),
var escapeAction: DialogueAction? = null,
) {
@@ -42,6 +43,7 @@ class DialoguePage(
speaker: String? = null,
lines: Iterable<MutableComponent>,
input: DialogueInput = DialogueNoInput(),
gibber: DialogueGibber? = null,
background: ResourceLocation? = null,
clientActions: Iterable<Expression> = emptyList(),
/** The thing to do when the player presses ESC while on this page. If null, falls back to the same property on [Dialogue]. */
@@ -52,6 +54,7 @@ class DialoguePage(
speaker = speaker,
lines = lines.map { WrappedDialogueText(it) }.toMutableList(),
input = input,
gibber = gibber,
background = background,
clientActions = clientActions.toMutableList(),
escapeAction = escapeAction?.let { func -> FunctionDialogueAction { activeDialogue, _ -> func(activeDialogue) } }
@@ -19,7 +19,16 @@ import net.minecraft.network.chat.MutableComponent
*/
class DialogueSpeaker(
val name: DialogueText? = null,
val face: DialogueFaceProvider? = null
val face: DialogueFaceProvider? = null,
val gibber: DialogueGibber? = null
) {
fun of(name: MutableComponent = "".text(), face: DialogueFaceProvider? = null) = DialogueSpeaker(WrappedDialogueText(name), face)
fun of(
name: MutableComponent = "".text(),
face: DialogueFaceProvider? = null,
gibber: DialogueGibber? = null
) = DialogueSpeaker(
name = WrappedDialogueText(name),
face = face,
gibber = gibber
)
}
@@ -76,6 +76,8 @@ interface NetworkPacket<T: NetworkPacket<T>> : CustomPacketPayload, Encodable {
server.playerList.players.filter { player ->
if (exclusionCondition.invoke(player))
return@filter false
if (player.level().dimension() != worldKey)
return@filter false
val xDiff = x - player.x
val yDiff = y - player.y
val zDiff = z - player.z
@@ -0,0 +1,132 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.block.entity.StadiumBlockEntity
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.Mirror
import net.minecraft.world.level.block.RenderShape
import net.minecraft.world.level.block.Rotation
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.phys.BlockHitResult
class StadiumBlock(properties: Properties): BaseEntityBlock(properties) {
companion object {
val CODEC = simpleCodec(::StadiumBlock)
}
init {
registerDefaultState(stateDefinition.any().setValue(HorizontalDirectionalBlock.FACING, Direction.NORTH))
}
override fun codec() = CODEC
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
return StadiumBlockEntity(pos, state)
}
override fun getStateForPlacement(blockPlaceContext: BlockPlaceContext): BlockState {
return this.defaultBlockState().setValue(HorizontalDirectionalBlock.FACING, blockPlaceContext.horizontalDirection)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(HorizontalDirectionalBlock.FACING)
}
override fun rotate(blockState: BlockState, rotation: Rotation): BlockState {
return blockState.setValue(
HorizontalDirectionalBlock.FACING, rotation.rotate(blockState.getValue(
HorizontalDirectionalBlock.FACING)))
}
override fun mirror(blockState: BlockState, mirror: Mirror): BlockState {
return blockState.rotate(mirror.getRotation(blockState.getValue(HorizontalDirectionalBlock.FACING)))
}
override fun onRemove(state: BlockState, world: Level, pos: BlockPos, newState: BlockState, moved: Boolean) {
if (!state.`is`(newState.block)) super.onRemove(state, world, pos, newState, moved)
}
//TODO: override getTicker
override fun getRenderShape(state: BlockState) = RenderShape.MODEL
override fun useWithoutItem(
state: BlockState,
world: Level,
pos: BlockPos,
player: Player,
hitResult: BlockHitResult
): InteractionResult {
if (player !is ServerPlayer) return InteractionResult.SUCCESS
val blockEntity = world.getBlockEntity(pos)
if (blockEntity !is StadiumBlockEntity) return InteractionResult.SUCCESS
player.sendSystemMessage(text("Adding you to Battle").red())
return InteractionResult.SUCCESS
}
/*
override fun useWithoutItem(
blockState: BlockState,
world: Level,
blockPos: BlockPos,
player: Player,
blockHitResult: BlockHitResult
): InteractionResult {
if (player !is ServerPlayer) return InteractionResult.SUCCESS
val blockEntity = world.getBlockEntity(blockPos)
if (blockEntity !is StadiumBlockEntity) return InteractionResult.SUCCESS
// Toggle isInUse state
blockEntity.isInUse = !blockEntity.isInUse
// debug
val activateMessage = if (blockEntity.isInUse) "Stadium Activated!" else "Stadium Deactivated!"
player.sendSystemMessage(text(activateMessage).red())
if (blockEntity.isInUse) {
// Generate flight path when activated
blockEntity.generateFlightPath(world, radiusX = 5.0, radiusZ = 3.0, height = 10.0, steps = 20)
} else {
// Clear path when deactivated
blockEntity.pathPoints.clear()
}
val message = "Stadium Pos: " + blockEntity.blockPos +
"\nPlayer 1 Pos: " + blockEntity.getPlayer1Pos() +
"\nPoke 1 Pos: " + blockEntity.getPoke1Pos() +
"\nPlayer 2 Pos: " + blockEntity.getPlayer2Pos() +
"\nPoke 2 Pos: " + blockEntity.getPoke2Pos()
player.sendSystemMessage(text(message).red())
blockEntity.setChanged()
world.sendBlockUpdated(blockPos, blockState, blockState, Block.UPDATE_CLIENTS)
return InteractionResult.SUCCESS
}
*/
}
@@ -0,0 +1,149 @@
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.client.render.models.blockbench.drone.DroneState
import com.cobblemon.mod.common.stadium.StadiumSingleBattle
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.network.protocol.game.ClientboundPlayerLookAtPacket
import net.minecraft.network.protocol.game.ClientboundSetCarriedItemPacket
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
class StadiumBlockEntity(
pos: BlockPos,
blockState: BlockState
): BlockEntity(
CobblemonBlockEntities.STADIUM,
pos,
blockState
) {
/** Battle area center is bottom center */
var battleArea = Vector3f(10f,10f,10f) // Width, Height, Length
var battleCenter = Vec3.ZERO // Local
var battleRot = 70F
fun positionInBattleArea(targetPos: Vector3f) : Boolean {
val adjustedPos = targetPos.sub(blockPos.bottomCenter.toVector3f()).sub(battleCenter.toVector3f())
if (targetPos.y < 0f || targetPos.y >= battleArea.y) return false
adjustedPos.rotateY(-battleRot)
if (targetPos.x < -battleArea.x || targetPos.x > battleArea.x) return false
if (targetPos.z < -battleArea.z || targetPos.z > battleArea.z) return false
return true
}
/*
var isInUse = false
val droneState = DroneState()
val pathPoints: MutableList<Vec3> = mutableListOf()
fun generateFlightPath(world: Level, radiusX: Double, radiusZ: Double, height: Double, steps: Int) {
pathPoints.clear()
for (i in 0 until steps) {
val angle = (i.toDouble() / steps) * Math.PI * 2
val xOffset = radiusX * Math.cos(angle)
val zOffset = radiusZ * Math.sin(angle)
val basePos = Vec3(blockPos.x + xOffset, blockPos.y + height, blockPos.z + zOffset)
val adjustedPos = getValidPoint(world, basePos)
pathPoints.add(adjustedPos)
}
}
private fun getValidPoint(world: Level, start: Vec3): Vec3 {
val maxHeight = 40 // Max height for drone path
val stepHeight = 1.0 // Step size for raycast
var adjustedHeight = start.y
for (h in 0..maxHeight step stepHeight.toInt()) {
val checkPos = BlockPos(start.x.toInt(), start.y.toInt() - h, start.z.toInt()) // Convert to BlockPos
if (!world.getBlockState(checkPos).isAir) { // Check if block is solid
return Vec3(start.x, start.y - h - 1, start.z) // Adjust position
}
}
return start // No obstruction found
}
fun getPlayer1Pos(): BlockPos {
return blockPos.north(10)
}
fun getPlayer2Pos(): BlockPos {
return blockPos.south(10)
}
fun getPoke1Pos(): BlockPos {
return blockPos.north(5)
}
fun getPoke2Pos(): BlockPos {
return blockPos.south(5)
}
/**
* Saves data to NBT.
*/
override fun saveAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.saveAdditional(tag, registries)
// Save the isInUse state
tag.putBoolean("IsInUse", isInUse)
// Save path points
val pathTag = ListTag()
for (point in pathPoints) {
val pointTag = CompoundTag()
pointTag.putDouble("x", point.x)
pointTag.putDouble("y", point.y)
pointTag.putDouble("z", point.z)
pathTag.add(pointTag)
}
tag.put("PathPoints", pathTag)
}
/**
* Loads data from NBT.
*/
override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.loadAdditional(tag, registries)
// Load the isInUse state
isInUse = tag.getBoolean("IsInUse")
// Load path points
pathPoints.clear()
val pathTag = tag.getList("PathPoints", 10) // 10 is the ID for CompoundTag
for (i in 0 until pathTag.size) {
val pointTag = pathTag.getCompound(i)
val point = Vec3(pointTag.getDouble("x"), pointTag.getDouble("y"), pointTag.getDouble("z"))
pathPoints.add(point)
}
}
override fun getUpdatePacket(): Packet<ClientGamePacketListener>? {
//println("getUpdatePacket called for NestBlockEntity at $blockPos with egg: $eggComponent")
return ClientboundBlockEntityDataPacket.create(this)
}
override fun getUpdateTag(registryLookup: HolderLookup.Provider): CompoundTag {
val tag = saveWithoutMetadata(registryLookup)
//println("getUpdateTag called for NestBlockEntity at $blockPos with tag: $tag")
return tag
}
*/
}
@@ -16,7 +16,6 @@ import com.cobblemon.mod.common.CobblemonEntities
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonMenuType.COOKING_POT
import com.cobblemon.mod.common.api.berry.Berries
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker
import com.cobblemon.mod.common.api.storage.player.client.ClientGeneralPlayerData
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
@@ -54,6 +53,7 @@ import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext
import com.cobblemon.mod.common.stadium.BattleCameraManagerClient
import com.cobblemon.mod.common.util.isLookingAt
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
@@ -68,14 +68,10 @@ import net.minecraft.client.renderer.blockentity.SignRenderer
import net.minecraft.client.renderer.entity.EntityRenderer
import net.minecraft.client.renderer.entity.LivingEntityRenderer
import net.minecraft.client.resources.PlayerSkin
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.resources.ResourceManager
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.AABB
import java.util.UUID
import net.minecraft.client.player.AbstractClientPlayer
import net.minecraft.server.level.ServerPlayer
object CobblemonClient {
@@ -111,6 +107,7 @@ object CobblemonClient {
CobblemonDataProvider.canReload = true
DeferredRenderer.clearAll()
ClientPlayerIcon.clear()
BattleCameraManagerClient.onLogout()
}
fun initialize(implementation: CobblemonClientImplementation) {
@@ -260,6 +257,8 @@ object CobblemonClient {
CobblemonBlocks.SKY_TUMBLESTONE_CLUSTER,
CobblemonBlocks.GIMMIGHOUL_CHEST,
CobblemonBlocks.DISPLAY_CASE,
CobblemonBlocks.LECTERN,
CobblemonBlocks.STADIUM_BLOCK,
CobblemonBlocks.SACCHARINE_DOOR,
CobblemonBlocks.SACCHARINE_TRAPDOOR,
CobblemonBlocks.SACCHARINE_SIGN,
@@ -278,7 +277,6 @@ object CobblemonClient {
CobblemonBlocks.RED_CAMPFIRE_POT,
CobblemonBlocks.WHITE_CAMPFIRE_POT,
CobblemonBlocks.YELLOW_CAMPFIRE_POT
)
this.createBoatModelLayers()
@@ -316,6 +314,7 @@ object CobblemonClient {
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.GILDED_CHEST, ::GildedChestBlockRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.DISPLAY_CASE, ::DisplayCaseRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.LECTERN, ::LecternBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.STADIUM, ::StadiumBlockRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.CAMPFIRE, ::CampfireBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.LURE_CAKE, ::CakeBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.POKE_CAKE, ::CakeBlockEntityRenderer)
@@ -8,6 +8,11 @@
package com.cobblemon.mod.common.client.gui.dialogue
import com.cobblemon.mod.common.net.messages.client.dialogue.dto.DialogueGibberDTO
import net.minecraft.network.chat.MutableComponent
class DialogueRenderableSpeaker(val name: MutableComponent?, val face: RenderableFace?)
class DialogueRenderableSpeaker(
val name: MutableComponent?,
val face: RenderableFace?,
val gibber: DialogueGibberDTO? = null
)
@@ -10,43 +10,71 @@ package com.cobblemon.mod.common.client.gui.dialogue
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.MoParams
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.dialogue.ArtificialDialogueFaceProvider
import com.cobblemon.mod.common.api.dialogue.PlayerDialogueFaceProvider
import com.cobblemon.mod.common.api.dialogue.ReferenceDialogueFaceProvider
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueBox
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialoguePortraitWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueNameWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueOptionWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialoguePortraitWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueTextInputWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueTimerWidget
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.net.messages.client.dialogue.dto.DialogueDTO
import com.cobblemon.mod.common.net.messages.client.dialogue.dto.DialogueGibberDTO
import com.cobblemon.mod.common.net.messages.client.dialogue.dto.DialogueInputDTO
import com.cobblemon.mod.common.net.messages.server.dialogue.EscapeDialoguePacket
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.asExpressions
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.isInventoryKeyPressed
import com.cobblemon.mod.common.util.nextBetween
import com.cobblemon.mod.common.util.resolve
import kotlin.random.Random
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.EditBox
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.sounds.SoundEvent
class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTranslated()), CobblemonRenderable {
val speakers = dialogueDTO.speakers?.mapNotNull { (key, value) ->
val name = value.name
when (val face = value.face) {
is ArtificialDialogueFaceProvider -> key to DialogueRenderableSpeaker(name, ArtificialRenderableFace(face.modelType, face.identifier, face.aspects, face.isLeftSide))
is PlayerDialogueFaceProvider -> key to DialogueRenderableSpeaker(name, PlayerRenderableFace(face.playerId, face.isLeftSide))
is ArtificialDialogueFaceProvider -> key to DialogueRenderableSpeaker(
name = name,
face = ArtificialRenderableFace(face.modelType, face.identifier, face.aspects, face.isLeftSide),
gibber = value.gibber
)
is PlayerDialogueFaceProvider -> key to DialogueRenderableSpeaker(
name = name,
face = PlayerRenderableFace(
playerId = face.playerId,
isLeftSide = face.isLeftSide
),
gibber = value.gibber
)
is ReferenceDialogueFaceProvider -> {
key to DialogueRenderableSpeaker(
name = name,
face = ReferenceRenderableFace(Minecraft.getInstance().level?.getEntity(face.entityId) as? PosableEntity ?: return@mapNotNull null, face.isLeftSide)
face = ReferenceRenderableFace(
entity = Minecraft.getInstance().level?.getEntity(face.entityId) as? PosableEntity ?: return@mapNotNull null,
isLeftSide = face.isLeftSide
),
gibber = value.gibber
)
}
else -> key to DialogueRenderableSpeaker(name, null)
else -> key to DialogueRenderableSpeaker(name = name, face = null, gibber = value.gibber)
}
}?.toMap() ?: emptyMap()
val runtime = MoLangRuntime().setup().setupClient()
@@ -63,11 +91,18 @@ class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTra
lateinit var dialogueNameWidget: DialogueNameWidget
lateinit var dialoguePortraitWidget: DialoguePortraitWidget
var dialogueStartTick = 0
val scaledWidth
get() = this.minecraft!!.window.guiScaledWidth
val scaledHeight
get() = this.minecraft!!.window.guiScaledHeight
var expressionsToRun = listOf<ExpressionLike>()
var gibberDone = false
var gibber: DialogueGibberDTO? = null
var gibberSounds = listOf<SoundEvent>()
companion object {
const val BOX_WIDTH = 196
const val BOX_HEIGHT = 74
@@ -94,13 +129,15 @@ class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTra
private val buttonResource = cobblemonResource("textures/gui/dialogue/dialogue_button.png")
private val buttonFullWidthResource = cobblemonResource("textures/gui/dialogue/dialogue_button_full.png")
val dialogueMolangFunctions = mutableListOf<(DialogueScreen) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
val dialogueMoLangFunctions = mutableListOf<(DialogueScreen) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ dialogueScreen ->
return@mutableListOf hashMapOf(
"face" to java.util.function.Function { Unit },
"face" to java.util.function.Function {
val face = dialogueScreen.dialogueDTO.currentPageDTO.speaker?.let { dialogueScreen.speakers[it] }?.face ?: return@Function DoubleValue.ZERO
return@Function face.struct
},
)
}
// idk something maybe, stuff for accessing the current 'face' to modify it?
)
}
@@ -111,14 +148,15 @@ class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTra
override fun init() {
super.init()
runtime.environment.query
.addFunctions(
dialogueMolangFunctions
.flatMap { it(this@DialogueScreen).entries }
.associate { it.key to it.value }
runtime.environment.query.addFunctions(
functions = dialogueMoLangFunctions.flatMap { it(this@DialogueScreen).entries }.associate { it.key to it.value }
)
dialogueDTO.currentPageDTO.clientActions.forEach { runtime.resolve(it.asExpressionLike()) }
val currentSpeaker = dialogueDTO.currentPageDTO.speaker
gibber = dialogueDTO.currentPageDTO.gibber ?: currentSpeaker?.let { speakers[it]?.gibber }
gibberSounds = gibber?.sounds?.mapNotNull { BuiltInRegistries.SOUND_EVENT.get(it) } ?: emptyList()
expressionsToRun = dialogueDTO.currentPageDTO.clientActions.map { it.asExpressionLike() }
val centerX = scaledWidth / 2F
val boxMinY = (scaledHeight / 2F) - (BOX_HEIGHT / 2F) - 30
val boxMaxY = boxMinY + BOX_HEIGHT
@@ -201,18 +239,34 @@ class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTra
setInitialFocus(dialogueTextInputWidget)
}
this.dialogueStartTick = minecraft!!.player!!.tickCount
dialogueDTO.currentPageDTO.clientActions.flatMap(String::asExpressions).resolve(runtime)
}
var gibberIndex = 0
var timeElapsedSinceGibber = 0.0
override fun render(GuiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val gibber = gibber
if (gibber != null) {
playGibberSpeak(
delta = delta,
text = dialogueDTO.currentPageDTO.lines.joinToString { it.string },
gibber = gibber
)
}
remainingSeconds -= delta / 20F
dialogueTimerWidget.ratio = if (remainingSeconds <= 0) -1F else remainingSeconds / dialogueDTO.dialogueInput.deadline
super.render(GuiGraphics, mouseX, mouseY, delta)
expressionsToRun.forEach { runtime.resolve(it) }
expressionsToRun = emptyList()
}
override fun isPauseScreen() = false
fun update(dialogueDTO: DialogueDTO) {
gibberIndex = 0
gibberDone = false
this.dialogueDTO = dialogueDTO
this.remainingSeconds = dialogueDTO.dialogueInput.deadline
waitingForServerUpdate = false
@@ -236,4 +290,31 @@ class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTra
return super.keyPressed(keyCode, scanCode, modifiers)
}
}
fun playGibberSpeak(delta: Float, text: String, gibber: DialogueGibberDTO) {
timeElapsedSinceGibber += delta / 20
if (timeElapsedSinceGibber < gibber.interval) {
return
}
timeElapsedSinceGibber = 0.0
if (gibberIndex >= text.length) {
gibberDone = true
return
}
val gibberChar = text[gibberIndex]
gibberIndex += gibber.step
if (gibberSounds.isNotEmpty()) {
val pitchRange = gibber.maxPitch * 100 - gibber.minPitch * 100
val gibberPitch = (gibberChar.hashCode() % pitchRange + gibber.minPitch * 100) / 100
val volume = Random.nextBetween(gibber.minVolume, gibber.maxVolume)
val gibberSound = gibberSounds[gibberChar.hashCode() % gibberSounds.size]
minecraft?.soundManager?.play(SimpleSoundInstance.forUI(gibberSound, gibberPitch, volume))
}
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.client.gui.dialogue
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.gui.drawPosablePortrait
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
@@ -34,10 +35,12 @@ import org.joml.Vector3f
*/
sealed interface RenderableFace {
val isLeftSide: Boolean
val struct: QueryStruct
fun render(GuiGraphics: GuiGraphics, partialTicks: Float)
}
class PlayerRenderableFace(val playerId: UUID, override val isLeftSide: Boolean) : RenderableFace {
override val struct: QueryStruct = QueryStruct(hashMapOf())
override fun render(GuiGraphics: GuiGraphics, partialTicks: Float) {
val entity = Minecraft.getInstance().level?.getPlayerByUUID(playerId) ?: return
// All of the maths below is shamelessly stolen from InventoryScreen.drawEntity.
@@ -74,6 +77,8 @@ class PlayerRenderableFace(val playerId: UUID, override val isLeftSide: Boolean)
class ReferenceRenderableFace(val entity: PosableEntity, override val isLeftSide: Boolean): RenderableFace {
val state = entity.delegate as PosableState
override val struct: QueryStruct
get() = state.runtime.environment.query
override fun render(GuiGraphics: GuiGraphics, partialTicks: Float) {
val state = this.state
if (state is PokemonClientDelegate) {
@@ -112,6 +117,8 @@ class ArtificialRenderableFace(
override val isLeftSide: Boolean
): RenderableFace {
val state = FloatingState()
override val struct: QueryStruct
get() = state.runtime.environment.query
override fun render(GuiGraphics: GuiGraphics, partialTicks: Float) {
val state = this.state
@@ -10,8 +10,10 @@ package com.cobblemon.mod.common.client.gui.dialogue.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.gui.ScrollingWidget
import com.cobblemon.mod.common.client.gui.dialogue.DialogueScreen
import com.cobblemon.mod.common.client.render.TextClipping
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.client.dialogue.dto.DialogueInputDTO
import com.cobblemon.mod.common.net.messages.server.dialogue.InputToDialoguePacket
@@ -127,6 +129,7 @@ class DialogueBox(
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
val gibber = dialogueScreen.gibber
correctSize()
blitk(
matrixStack = context.pose(),
@@ -137,8 +140,9 @@ class DialogueBox(
width = frameWidth,
textureWidth = frameWidth + DialoguePortraitWidget.DIALOGUE_ARROW_WIDTH + SCROLL_BAR_WIDTH + SCROLL_TRACK_WIDTH
)
super.renderWidget(context, mouseX, mouseY, partialTicks)
TextClipping.doWithMaxCharacters(if (gibber?.graduallyShowText == true) dialogueScreen.gibberIndex else -1) {
super.renderWidget(context, mouseX, mouseY, partialTicks)
}
}
override fun enableScissor(context: GuiGraphics) {
@@ -158,6 +162,14 @@ class DialogueBox(
mouseY > this.y && mouseY < this.bottom
) {
if (dialogue.dialogueInput.allowSkip && dialogue.dialogueInput.inputType in listOf(DialogueInputDTO.InputType.NONE, DialogueInputDTO.InputType.AUTO_CONTINUE)) {
val gibber = dialogueScreen.gibber
if (gibber != null && gibber.graduallyShowText && !dialogueScreen.gibberDone) {
if (gibber.allowSkip) {
dialogueScreen.gibberDone = true
}
return true
}
dialogueScreen.sendToServer(InputToDialoguePacket(dialogue.dialogueInput.inputId, "skip!"))
return true
}
@@ -63,4 +63,4 @@ class DialogueOptionWidget(
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
return true
}
}
}
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render
/**
* A utility class for limiting the number of characters that can be rendered by whatever draw operation
* is about to occur.
*
* Usage: Run [doWithMaxCharacters] with the maximum number of characters that will be allowed to render,
* or -1 for unlimited, and a mixin will ensure that a string renderer will only visit that many characters
* and cancel all those after.
*
* @author Hiroku
* @since April 19th, 2025
*/
object TextClipping {
private var acceptableRenderCount = -1
private var renderedCount = 0
@JvmStatic
fun doWithMaxCharacters(maxCharacters: Int, action: Runnable) {
acceptableRenderCount = maxCharacters
action.run()
acceptableRenderCount = -1
renderedCount = 0
}
@JvmStatic
fun canDrawAnotherCharacter(): Boolean {
if (acceptableRenderCount == -1) {
return true
}
if (renderedCount >= acceptableRenderCount) {
return false
}
renderedCount++
return true
}
}
@@ -0,0 +1,238 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.block
import com.cobblemon.mod.common.block.entity.StadiumBlockEntity
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.util.Mth
import org.joml.Vector3f
import org.joml.Vector3i
class StadiumBlockRenderer(ctx: BlockEntityRendererProvider.Context) : BlockEntityRenderer<StadiumBlockEntity> {
val context = RenderContext().also {
it.put(RenderContext.DO_QUIRKS, false)
it.put(RenderContext.RENDER_STATE, RenderContext.RenderState.BLOCK)
}
private var pathIndex = 0
private var animationTime = 0.0
private val droneSpeed = 0.10
override fun render(
blockEntity: StadiumBlockEntity,
partialTick: Float,
poseStack: PoseStack,
bufferSource: MultiBufferSource,
packedLight: Int,
packedOverlay: Int
) {
val vertexConsumer = bufferSource.getBuffer(RenderType.LINES)
val pose = poseStack.last()
val center = Vector3f(0.5f,0f,0.5f).add(blockEntity.battleCenter.toVector3f()) // 0,0,0 is corner of block, we make center be bottom center
val rotation = blockEntity.battleRot / (180f * Mth.PI)
val widthOffset = blockEntity.battleArea.x / 2f
val lengthOffset = blockEntity.battleArea.z / 2f
val height = blockEntity.battleArea.y
fun drawLine(point1: Vector3i, point2: Vector3i) {
val f = if (point1.x == 0) -widthOffset else widthOffset
val g = if (point1.y == 0) 0f else height
val h = if (point1.z == 0) -lengthOffset else lengthOffset
val i = if (point2.x == 0) -widthOffset else widthOffset
val j = if (point2.y == 0) 0f else height
val k = if (point2.z == 0) -lengthOffset else lengthOffset
val position1 = Vector3f(f, g, h).rotateY(rotation).add(center)
val position2 = Vector3f(i, j, k).rotateY(rotation).add(center)
val normal = Vector3f(position2).sub(position1).normalize()
vertexConsumer.addVertex(pose, position1).setColor(0.9f, 0.9f, 0.9f, 1f).setNormal(normal.x, normal.y, normal.z)
vertexConsumer.addVertex(pose, position2).setColor(0.9f, 0.9f, 0.9f, 1f).setNormal(normal.x, normal.y, normal.z)
}
drawLine(Vector3i(0,0,0), Vector3i(1,0,0))
drawLine(Vector3i(0,0,0), Vector3i(0,1,0))
drawLine(Vector3i(0,0,0), Vector3i(0,0,1))
drawLine(Vector3i(1,0,0), Vector3i(1,1,0))
drawLine(Vector3i(1,0,0), Vector3i(1,0,1))
drawLine(Vector3i(0,0,1), Vector3i(1,0,1))
drawLine(Vector3i(0,0,1), Vector3i(0,1,1))
drawLine(Vector3i(0,0,0), Vector3i(0,0,0))
drawLine(Vector3i(0,1,0), Vector3i(1,1,0))
drawLine(Vector3i(0,1,0), Vector3i(0,1,1))
drawLine(Vector3i(1,1,0), Vector3i(1,1,1))
drawLine(Vector3i(0,1,1), Vector3i(1,1,1))
drawLine(Vector3i(1,0,1), Vector3i(1,1,1))
}
/*
override fun render(
blockEntity: StadiumBlockEntity,
tickDelta: Float,
poseStack: PoseStack,
multiBufferSource: MultiBufferSource,
light: Int,
overlay: Int
) {
val blockState = blockEntity.blockState
val yRot = blockState.getValue(HorizontalDirectionalBlock.FACING).toYRot()
val isClientSide = blockEntity.level?.isClientSide == true
if (!isClientSide) return
val stadiumBlockState = blockEntity.level?.getBlockState(blockEntity.blockPos) ?: return
if (stadiumBlockState.block != CobblemonBlocks.STADIUM) {
return
}
val droneState = blockEntity.droneState
poseStack.pushPose()
if (blockEntity.isInUse) {
if (blockEntity.pathPoints.isEmpty()) {
poseStack.popPose()
return
}
spawnDebugParticles(blockEntity) // debug particles
// Center the drone at the block's center
poseStack.translate(0.5, -3.0, 0.5)
//poseStack.mulPose(Axis.XP.rotationDegrees(180F))
//poseStack.mulPose(Axis.YP.rotationDegrees(180F))
val points = blockEntity.pathPoints
val nextIndex = (pathIndex + 1) % points.size
val currentPoint = points[pathIndex]
val nextPoint = points[nextIndex]
animationTime += tickDelta * droneSpeed
if (animationTime >= 1.0) {
animationTime = 0.0
pathIndex = nextIndex
}
val interpolatedPos = currentPoint.lerp(nextPoint, animationTime.toDouble())
println("DEBUG: Drone moving between points:")
println(" - Current Point: ${currentPoint}")
println(" - Next Point: ${nextPoint}")
println(" - Interpolated Position: $interpolatedPos")
poseStack.translate(interpolatedPos.x - blockEntity.blockPos.x,
interpolatedPos.y - blockEntity.blockPos.y,
interpolatedPos.z - blockEntity.blockPos.z)
println("DEBUG: Drone position interpolated to: $interpolatedPos")
poseStack.mulPose(Axis.YP.rotationDegrees(-yRot))
poseStack.mulPose(Axis.ZP.rotationDegrees(180F)) // flip the drone right side up
renderDrone(droneState, "sleep", tickDelta, poseStack, 1.0F, multiBufferSource, light)
} else {
val yRot = blockState.getValue(HorizontalDirectionalBlock.FACING).toYRot()
val poseEntry = poseStack.last()
val matrix = poseEntry.pose()
poseStack.translate(0.5, 0.0, 0.5)
poseStack.mulPose(Axis.YP.rotationDegrees(yRot))
val posX = matrix.m03()
val posY = matrix.m13()
val posZ = matrix.m23()
println("DEBUG: Drone is NOT in use. Rendering default position at: x=$posX, y=$posY, z=$posZ")
poseStack.mulPose(Axis.ZP.rotationDegrees(180F))
renderDrone(droneState, "sleep", tickDelta, poseStack, 1.0F, multiBufferSource, light)
}
poseStack.popPose()
}
fun renderDrone(
state: DroneState,
pose: String,
tickDelta: Float,
poseStack: PoseStack,
scale: Float = 1F,
multiBufferSource: MultiBufferSource,
light: Int,
) {
state.updatePartialTicks(tickDelta)
val droneResource: ResourceLocation = cobblemonResource("rotom_drone")
val model = VaryingModelRepository.getPoser(droneResource, state) as DroneModel
val texture = VaryingModelRepository.getTexture(droneResource, state)
val buffer = multiBufferSource.getBuffer(RenderType.entityCutout(texture))
model.bufferProvider = multiBufferSource
state.currentModel = model
state.setPose(pose)
model.context = context
context.put(RenderContext.TEXTURE, texture)
context.put(RenderContext.RENDER_STATE, RenderContext.RenderState.BLOCK)
context.put(RenderContext.POSABLE_STATE, state)
model.applyAnimations(
entity = null,
state = state,
headYaw = 0F,
headPitch = 0F,
limbSwing = 0F,
limbSwingAmount = 0F,
ageInTicks = state.animationSeconds * 20
)
poseStack.scale(scale, scale, scale)
// TODO: Make drone lighting dynamic (simply using the light arg makes the drone always appear dark)
model.withLayerContext(multiBufferSource, state, VaryingModelRepository.getLayers(droneResource, state)) {
model.render(context, poseStack, buffer, 200, OverlayTexture.NO_OVERLAY, -0x1)
}
model.setDefault()
}
private fun spawnDebugParticles(blockEntity: StadiumBlockEntity) {
val world = blockEntity.level ?: return
if (!world.isClientSide) return // Only run on the client-side
for (point in blockEntity.pathPoints) {
world.addParticle(
net.minecraft.core.particles.ParticleTypes.FLAME, // Test particle (changeable)
point.x, point.y, point.z, // Position of the particle
0.0, 0.02, 0.0 // Slight upward motion
)
}
// Spawn a special particle at the next interpolation point
if (blockEntity.pathPoints.isNotEmpty()) {
val nextPoint = blockEntity.pathPoints[(pathIndex + 1) % blockEntity.pathPoints.size]
world.addParticle(
net.minecraft.core.particles.ParticleTypes.END_ROD, // Different effect for next point
nextPoint.x, nextPoint.y, nextPoint.z,
0.0, 0.05, 0.0
)
}
}
*/
}
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2025 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.drone
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PoseAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.client.render.models.blockbench.quirk.ModelQuirk
import com.cobblemon.mod.common.entity.PoseType
import com.google.gson.annotations.SerializedName
import net.minecraft.client.model.geom.ModelPart
/**
* A model for rendering the Rotom drone used by the stadium block.
*
* @author Flarkinater
* @since February 14th, 2025
*/
class DroneModel(root: Bone) : PosableModel(root) {
@Transient
@SerializedName("So this name can be literally anything, huh?")
override val rootPart = (root as ModelPart).registerChildWithAllChildren(root.children.entries.first().key)
var droneAnimations: Array<PoseAnimation> = emptyArray()
var droneQuirks: Array<ModelQuirk<*>> = emptyArray()
override fun registerPoses() {
registerPose(
poseType = PoseType.SLEEP,
animations = droneAnimations,
quirks = droneQuirks
)
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2025 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.drone
import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
/**
* State for a drone Rotom attached to a stadium block.
*
* @author Flarkinater
* @since February 12th, 2025
*/
class DroneState() : PosableState() {
init {
setPose("sleep")
age = 0
currentPartialTicks = 0F
}
override fun getEntity() = null
override fun updatePartialTicks(partialTicks: Float) {
currentPartialTicks += partialTicks / 2
}
override val schedulingTracker = SchedulingTracker()
}
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.client.render.models.blockbench.*
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockAnimationRepository
import com.cobblemon.mod.common.client.render.models.blockbench.blockentity.BlockEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.fossil.FossilModel
import com.cobblemon.mod.common.client.render.models.blockbench.drone.DroneModel
import com.cobblemon.mod.common.client.render.models.blockbench.pokeball.AncientPokeBallModel
import com.cobblemon.mod.common.client.render.models.blockbench.pokeball.BeastBallModel
import com.cobblemon.mod.common.client.render.models.blockbench.pokeball.PokeBallModel
@@ -79,12 +80,14 @@ object VaryingModelRepository {
"poke_balls",
"generic",
"block_entities",
"drone"
)
val poserDirectories: List<Pair<String, Class<out PosableModel>>> = listOf(
"bedrock/posers" to PosableModel::class.java,
"bedrock/pokemon/posers" to PokemonPosableModel::class.java,
"bedrock/fossils/posers" to FossilModel::class.java,
"bedrock/drone/posers" to DroneModel::class.java,
"bedrock/block_entities/posers" to BlockEntityModel::class.java,
"bedrock/npcs/posers" to PosableModel::class.java,
"bedrock/poke_balls/posers" to PosableModel::class.java,
@@ -116,6 +119,7 @@ object VaryingModelRepository {
.registerTypeAdapter(PosableModel::class.java, JsonModelAdapter(::PosableModel))
.registerTypeAdapter(PokemonPosableModel::class.java, JsonModelAdapter(::PokemonPosableModel))
.registerTypeAdapter(FossilModel::class.java, JsonModelAdapter(::FossilModel))
.registerTypeAdapter(DroneModel::class.java, JsonModelAdapter(::DroneModel))
.registerTypeAdapter(BlockEntityModel::class.java, JsonModelAdapter(::BlockEntityModel))
.registerTypeAdapter(Pose::class.java, PoseAdapter { JsonModelAdapter.model!! })
.registerTypeAdapter(ModelPartTransformation::class.java, ModelPartTransformationAdapter)
@@ -50,6 +50,8 @@ import com.cobblemon.mod.common.net.messages.client.data.UnlockReloadPacket
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokemon.SpeciesAdditions
import com.cobblemon.mod.common.pokemon.properties.PropertiesCompletionProvider
import com.cobblemon.mod.common.stadium.BattleCameraManagers
import com.cobblemon.mod.common.stadium.BattleCameras
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.ifClient
import com.cobblemon.mod.common.util.server
@@ -98,6 +100,8 @@ object CobblemonDataProvider : DataProvider {
this.register(CobblemonCosmeticItems)
this.register(CobblemonFlows)
this.register(CobblemonUnlockableWallpapers)
this.register(BattleCameras)
this.register(BattleCameraManagers)
this.register(Marks)
CobblemonSpawnPools.load()
@@ -242,6 +242,7 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.PC)
entries.accept(CobblemonItems.HEALING_MACHINE)
entries.accept(CobblemonItems.PASTURE)
entries.accept(CobblemonItems.STADIUM_BLOCK)
entries.accept(CobblemonItems.GILDED_CHEST)
entries.accept(CobblemonItems.YELLOW_GILDED_CHEST)
@@ -36,16 +36,24 @@ class DialogueDTO : Encodable, Decodable {
constructor(activeDialogue: ActiveDialogue, includeFaces: Boolean) {
this.dialogueId = activeDialogue.dialogueId
this.speakers = if (includeFaces) activeDialogue.dialogueReference.speakers.mapNotNull { (key, value) ->
if (value.face is ExpressionLikeDialogueFaceProvider) {
val name = value.name?.invoke(activeDialogue)
val gibber = value.gibber?.let(::DialogueGibberDTO)
val face = if (value.face is ExpressionLikeDialogueFaceProvider) {
val resolved = activeDialogue.runtime.resolve(value.face.providerExpression)
if (resolved is ObjectValue<*> && resolved.obj is DialogueFaceProvider) {
key to DialogueSpeakerDTO(value.name?.invoke(activeDialogue), resolved.obj as DialogueFaceProvider)
resolved.obj as DialogueFaceProvider
} else {
null
return@mapNotNull null
}
} else {
key to DialogueSpeakerDTO(value.name?.invoke(activeDialogue), value.face)
value.face
}
key to DialogueSpeakerDTO(
name = name,
face = face,
gibber = gibber
)
}.toMap() else null
this.currentPageDTO = DialoguePageDTO(activeDialogue.currentPage, activeDialogue)
val input = activeDialogue.activeInput.dialogueInput
@@ -88,6 +96,8 @@ class DialogueDTO : Encodable, Decodable {
buffer.writeUUID(value.face.playerId)
buffer.writeBoolean(value.face.isLeftSide)
}
buffer.writeNullable(value.gibber) { _, gibber -> gibber.encode(buffer) }
}
}
}
@@ -105,16 +115,50 @@ class DialogueDTO : Encodable, Decodable {
val name = buffer.readNullable { buffer.readText().copy() }
val faceType = buffer.readNullable { buffer.readString() }
when (faceType) {
"reference" -> key to DialogueSpeakerDTO(name, ReferenceDialogueFaceProvider(buffer.readInt(), buffer.readBoolean()))
"reference" -> {
val entityId = buffer.readInt()
val isLeftSide = buffer.readBoolean()
val gibber = buffer.readNullable { DialogueGibberDTO.decode(buffer) }
key to DialogueSpeakerDTO(
name = name,
face = ReferenceDialogueFaceProvider(
entityId = entityId,
isLeftSide = isLeftSide
),
gibber = gibber
)
}
"artificial" -> {
val modelType = buffer.readString()
val identifier = buffer.readIdentifier()
val aspects = buffer.readList { buffer.readString() }.toSet()
val isLeftSide = buffer.readBoolean()
key to DialogueSpeakerDTO(name, ArtificialDialogueFaceProvider(modelType, identifier, aspects, isLeftSide))
key to DialogueSpeakerDTO(
name = name,
face = ArtificialDialogueFaceProvider(modelType, identifier, aspects, isLeftSide),
gibber = DialogueGibberDTO.decode(buffer)
)
}
"player" -> {
val playerId = buffer.readUUID()
val isLeftSide = buffer.readBoolean()
val gibber = buffer.readNullable { DialogueGibberDTO.decode(buffer) }
key to DialogueSpeakerDTO(
name = name,
face = PlayerDialogueFaceProvider(
playerId = playerId,
isLeftSide = isLeftSide
),
gibber = gibber
)
}
else -> {
key to DialogueSpeakerDTO(
name = name,
face = null,
gibber = DialogueGibberDTO.decode(buffer)
)
}
"player" -> key to DialogueSpeakerDTO(name, PlayerDialogueFaceProvider(buffer.readUUID(), buffer.readBoolean()))
else -> key to DialogueSpeakerDTO(name, null)
}
}
}
@@ -0,0 +1,66 @@
/*
* 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.net.messages.client.dialogue.dto
import com.cobblemon.mod.common.api.dialogue.DialogueGibber
import com.cobblemon.mod.common.util.readIdentifier
import com.cobblemon.mod.common.util.writeIdentifier
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
class DialogueGibberDTO(
val graduallyShowText: Boolean,
val allowSkip: Boolean,
val step: Int,
val interval: Float,
val minPitch: Float,
val maxPitch: Float,
val minVolume: Float,
val maxVolume: Float,
val sounds: List<ResourceLocation>
) {
constructor(gibber: DialogueGibber): this(
graduallyShowText = gibber.graduallyShowText,
allowSkip = gibber.allowSkip,
step = gibber.step,
interval = gibber.interval.toFloat(),
minPitch = gibber.minPitch,
maxPitch = gibber.maxPitch,
minVolume = gibber.minVolume,
maxVolume = gibber.maxVolume,
sounds = gibber.sounds
)
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeBoolean(graduallyShowText)
buffer.writeBoolean(allowSkip)
buffer.writeInt(step)
buffer.writeFloat(interval)
buffer.writeFloat(minPitch)
buffer.writeFloat(maxPitch)
buffer.writeFloat(minVolume)
buffer.writeFloat(maxVolume)
buffer.writeCollection(sounds) { _, value -> buffer.writeIdentifier(value) }
}
companion object {
fun decode(buffer: RegistryFriendlyByteBuf): DialogueGibberDTO {
val graduallyShowText = buffer.readBoolean()
val allowSkip = buffer.readBoolean()
val step = buffer.readInt()
val interval = buffer.readFloat()
val minPitch = buffer.readFloat()
val maxPitch = buffer.readFloat()
val minVolume = buffer.readFloat()
val maxVolume = buffer.readFloat()
val sounds = buffer.readList { it.readIdentifier() }
return DialogueGibberDTO(graduallyShowText, allowSkip, step, interval, minPitch, maxPitch, minVolume, maxVolume, sounds)
}
}
}
@@ -13,13 +13,9 @@ import com.cobblemon.mod.common.api.dialogue.DialoguePage
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.util.readIdentifier
import com.cobblemon.mod.common.util.readList
import com.cobblemon.mod.common.util.readNullable
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.readText
import com.cobblemon.mod.common.util.writeCollection
import com.cobblemon.mod.common.util.writeIdentifier
import com.cobblemon.mod.common.util.writeNullable
import com.cobblemon.mod.common.util.writeString
import com.cobblemon.mod.common.util.writeText
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -30,6 +26,7 @@ class DialoguePageDTO : Encodable, Decodable {
var speaker: String? = null
lateinit var background: ResourceLocation
var lines: MutableList<MutableComponent> = mutableListOf()
var gibber: DialogueGibberDTO? = null
// Later can include some face data probably
var clientActions = mutableListOf<String>()
@@ -45,6 +42,7 @@ class DialoguePageDTO : Encodable, Decodable {
buffer.writeNullable(speaker) { _, value -> buffer.writeString(value)}
buffer.writeIdentifier(background)
buffer.writeCollection(lines) { _, value -> buffer.writeText(value) }
buffer.writeNullable(gibber) { _, value -> value.encode(buffer) }
buffer.writeInt(clientActions.size)
clientActions.forEach { buffer.writeString(it) }
}
@@ -53,6 +51,7 @@ class DialoguePageDTO : Encodable, Decodable {
speaker = buffer.readNullable { buffer.readString() }
background = buffer.readIdentifier()
lines = buffer.readList { (it as RegistryFriendlyByteBuf).readText().copy() }.toMutableList()
gibber = buffer.readNullable { DialogueGibberDTO.decode(buffer) }
val clientActionsSize = buffer.readInt()
for (i in 0 until clientActionsSize) {
clientActions.add(buffer.readString())
@@ -13,5 +13,6 @@ import net.minecraft.network.chat.MutableComponent
class DialogueSpeakerDTO(
val name: MutableComponent? = null,
val face: DialogueFaceProvider?
val face: DialogueFaceProvider?,
val gibber: DialogueGibberDTO? = null
)
@@ -15,7 +15,7 @@ import com.cobblemon.mod.common.util.readEnumConstant
import com.cobblemon.mod.common.util.writeEnumConstant
import net.minecraft.network.RegistryFriendlyByteBuf
class RideBoostsUpdatePacket(pokemon: () -> Pokemon, rideBoosts: Map<RidingStat, Float>) : SingleUpdatePacket<Map<RidingStat, Float>, RideBoostsUpdatePacket>(pokemon, rideBoosts) {
class RideBoostsUpdatePacket(pokemon: () -> Pokemon?, rideBoosts: Map<RidingStat, Float>) : SingleUpdatePacket<Map<RidingStat, Float>, RideBoostsUpdatePacket>(pokemon, rideBoosts) {
companion object {
val ID = cobblemonResource("ride_boosts_update")
fun decode(buffer: RegistryFriendlyByteBuf): RideBoostsUpdatePacket {
@@ -37,4 +37,4 @@ class RideBoostsUpdatePacket(pokemon: () -> Pokemon, rideBoosts: Map<RidingStat,
override fun set(pokemon: Pokemon, value: Map<RidingStat, Float>) {
pokemon.setRideBoosts(value)
}
}
}
@@ -0,0 +1,192 @@
package com.cobblemon.mod.common.stadium
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.util.*
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
import java.lang.reflect.Type
import kotlin.math.abs
import kotlin.math.atan
import kotlin.math.atan2
class BattleCameraEntry(
var id: ResourceLocation,
val length: Double = 0.0,
val positionTimeline: MutableMap<String, CameraKeyFrame> = mutableMapOf(),
val targetPositionTimeline: MutableMap<String, CameraKeyFrame> = mutableMapOf(),
val move: String = ""
) {
fun resolvePosition(time: Double, yRot: Float): Vec3 {
return resolvePosition(time, positionTimeline).yRot(yRot * (Mth.PI/180))
}
private fun resolvePosition(time: Double, map: MutableMap<String, CameraKeyFrame>): Vec3 {
val runtime = BattleCameraManagerClient.runtime
runtime.environment.setSimpleVariable("anim_time", DoubleValue(time))
var frame1: CameraKeyFrame? = null
var frame2: CameraKeyFrame? = null
var time1 = -1.0
var time2 = -1.0
for (keyframe in map) {
if (time >= keyframe.key.toDouble()) {
if (time1 == -1.0 || abs(time - keyframe.key.toDouble()) < abs(time - time1)) {
frame1 = keyframe.value
time1 = keyframe.key.toDouble()
}
}
if (time < keyframe.key.toDouble()) {
if (time2 == -1.0 || abs(time - keyframe.key.toDouble()) < abs(time - time2)) {
frame2 = keyframe.value
time2 = keyframe.key.toDouble()
}
}
}
val difference = (time - time1) / (time2 - time1)
if (frame1 != null && frame1.interpolation == CameraInterpolation.STEP) {
frame2 = null
}
if (frame1 == null && frame2 == null) {
return Vec3.ZERO
}
else if (frame1 == null) {
frame2!!
val x = runtime.resolveDouble(frame2.x)
val y = runtime.resolveDouble(frame2.y)
val z = runtime.resolveDouble(frame2.z)
return Vec3(x,y,z)
}
else if (frame2 == null) {
val x = runtime.resolveDouble(frame1.x)
val y = runtime.resolveDouble(frame1.y)
val z = runtime.resolveDouble(frame1.z)
return Vec3(x,y,z)
}
else {
val x1 = runtime.resolveDouble(frame1.x)
val y1 = runtime.resolveDouble(frame1.y)
val z1 = runtime.resolveDouble(frame1.z)
val vec1 = Vec3(x1,y1,z1)
val x2 = runtime.resolveDouble(frame2.x)
val y2 = runtime.resolveDouble(frame2.y)
val z2 = runtime.resolveDouble(frame2.z)
val vec2 = Vec3(x2,y2,z2)
return vec1.lerp(vec2, difference)
}
}
fun resolveRotation(time: Double, yRot: Float): Vector3f {
val position = resolvePosition(time, positionTimeline).yRot(yRot * (Mth.PI/180))
val target = resolvePosition(time, targetPositionTimeline).yRot(yRot * (Mth.PI/180))
val offset = target.subtract(position)
val xRot = -atan(offset.y.toFloat()/offset.horizontalDistance().toFloat()) * (180F / Math.PI.toFloat())
val yRot = -atan2(offset.x.toFloat(),offset.z.toFloat()) * (180F / Math.PI.toFloat())
return Vector3f(xRot, yRot, 0.0F)
}
fun encode(buf: RegistryFriendlyByteBuf) {
buf.writeIdentifier(id)
buf.writeDouble(length)
buf.writeMap(positionTimeline, {_, key -> buf.writeString(key)}, {_, value ->
buf.writeString(value.x.getString())
buf.writeString(value.y.getString())
buf.writeString(value.z.getString())
buf.writeEnum(value.interpolation)
})
buf.writeMap(targetPositionTimeline, {_, key -> buf.writeString(key)}, {_, value ->
buf.writeString(value.x.getString())
buf.writeString(value.y.getString())
buf.writeString(value.z.getString())
buf.writeEnum(value.interpolation)
})
buf.writeString(move)
}
companion object {
fun decode(buf: RegistryFriendlyByteBuf) : BattleCameraEntry {
val id = buf.readIdentifier()
val length = buf.readDouble()
val positionTimeline = buf.readMap({_ -> buf.readString()}, {_ ->
CameraKeyFrame(buf.readString().asExpression(), buf.readString().asExpression(), buf.readString().asExpression(), buf.readEnum(CameraInterpolation::class.java))
}).toMutableMap()
val targetPositionTimeline = buf.readMap({_ -> buf.readString()}, {_ ->
CameraKeyFrame(buf.readString().asExpression(), buf.readString().asExpression(), buf.readString().asExpression(), buf.readEnum(CameraInterpolation::class.java))
}).toMutableMap()
val move = buf.readString()
return BattleCameraEntry(id, length, positionTimeline, targetPositionTimeline, move)
}
}
data class CameraKeyFrame (
val x: Expression,
val y: Expression,
val z: Expression,
val interpolation: CameraInterpolation
)
enum class CameraInterpolation {
LINEAR,
STEP
}
object CameraEntryAdapter: JsonDeserializer<BattleCameraEntry> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): BattleCameraEntry {
if (json is JsonObject) {
val length = json["length"]?.asDouble ?: 0.0
val positionTimeline = mutableMapOf<String, CameraKeyFrame>()
json["positions"]?.asJsonObject?.entrySet()?.forEach { (name, position) ->
val xyz = position.asJsonObject["xyz"]
var x = "0.0".asExpression()
var y = "0.0".asExpression()
var z = "0.0".asExpression()
if (xyz != null && xyz.isJsonArray && xyz.asJsonArray.size() == 3) {
x = xyz.asJsonArray[0].asString.asExpression()
y = xyz.asJsonArray[1].asString.asExpression()
z = xyz.asJsonArray[2].asString.asExpression()
}
val interpolation = when (position.asJsonObject["interpolation"]?.asString) {
"linear" -> CameraInterpolation.LINEAR
"step" -> CameraInterpolation.STEP
else -> CameraInterpolation.LINEAR
}
positionTimeline[name] = CameraKeyFrame(x, y, z, interpolation)
}
val targetPositionTimeline = mutableMapOf<String, CameraKeyFrame>()
json["targetPositions"]?.asJsonObject?.entrySet()?.forEach { (name, position) ->
val xyz = position.asJsonObject["xyz"]
var x = "0.0".asExpression()
var y = "0.0".asExpression()
var z = "0.0".asExpression()
if (xyz != null && xyz.isJsonArray && xyz.asJsonArray.size() == 3) {
x = xyz.asJsonArray[0].asString.asExpression()
y = xyz.asJsonArray[1].asString.asExpression()
z = xyz.asJsonArray[2].asString.asExpression()
}
val interpolation = when (position.asJsonObject["interpolation"]?.asString) {
"linear" -> CameraInterpolation.LINEAR
"step" -> CameraInterpolation.STEP
else -> CameraInterpolation.LINEAR
}
targetPositionTimeline[name] = CameraKeyFrame(x, y, z, interpolation)
}
val move = json["move"]?.asString ?: ""
return BattleCameraEntry(cobblemonResource(""), length, positionTimeline, targetPositionTimeline, move)
}
throw IllegalStateException("Stadium Camera could not be parsed")
}
}
}
@@ -0,0 +1,215 @@
package com.cobblemon.mod.common.stadium
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.util.resolveBoolean
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
import java.util.UUID
import java.util.function.Function
object BattleCameraManagerClient {
//Molang
fun MoLangRuntime.setupCameraFunctions(): MoLangRuntime {
environment.query.addFunctions(
hashMapOf(
"moveCamera" to Function { params ->
if (params.get<MoValue>(0) !is StringValue) {
return@Function Unit
}
val camera = BattleCameras.getMoveCamera(params.get<MoValue>(0).asString())
return@Function camera?.id?.toString() ?: ""
}
)
)
return this
}
val runtime = MoLangRuntime().setup().setupClient().setupCameraFunctions()
var currentStadiumBattleUUID: UUID? = null
var Instruction = "DEFAULT"
var InstructionChangeTime = 0.0
var InstructionJustChanged = false
fun updateState(newState: String) {
Instruction = newState
InstructionChangeTime = 0.0
InstructionJustChanged = true
}
fun onLogout() {
currentStadiumBattleUUID = null
}
fun handleJoin(battleUUID: UUID, cameraManager: ResourceLocation, center: Vec3, yRot: Float) {
currentStadiumBattleUUID = battleUUID
activeCameraManager = cameraManager
battleCenter = center
battleYRot = yRot
cameraTimer = 0.0
currentCameraState = BattleCameraManagers.managers[cameraManager]!!.startingState
cameraPosition = Vec3.ZERO
cameraRotation = Vector3f()
updateCameraData(0.0)
}
fun inStadiumBattle(): Boolean {
return currentStadiumBattleUUID != null
}
fun shouldUseStadiumCamera(): Boolean {
return inStadiumBattle()
}
//Molang Data
fun updateMolangVar() {
val environment = runtime.environment
environment.setSimpleVariable("instruction", StringValue(Instruction))
//environment.setSimpleVariable("instructionChangeTime", DoubleValue(InstructionChangeTime))
environment.setSimpleVariable("instructionJustChanged", DoubleValue(InstructionJustChanged))
val isSpectating = CobblemonClient.battle?.spectating ?: false
environment.setSimpleVariable("isBattling", DoubleValue(!isSpectating))
environment.setSimpleVariable("isSpectating", DoubleValue(isSpectating))
}
fun instructionUpdate(newState: String, additionalValues: MutableMap<String, MoValue>) {
val environment = runtime.environment
Instruction = newState
InstructionChangeTime = 0.0
InstructionJustChanged = true
for (value in additionalValues) {
environment.setSimpleVariable(value.key, value.value)
}
}
//Camera Manager
var battleYRot = 0.0F
var battleCenter = Vec3.ZERO
var activeCameraManager: ResourceLocation? = null
var cameraTimer: Double = 0.0
var currentCameraState: String = ""
var nextCameraState: String = ""
var calculateNextState: Boolean = true
fun tickCameraManager() {
if (!shouldUseStadiumCamera()) return
//Molang Variables
val environment = runtime.environment
if (CobblemonClient.battle != null) {
environment.setSimpleVariable("selectingMove", DoubleValue(CobblemonClient.battle!!.mustChoose))
}
else {
environment.setSimpleVariable("selectingMove", DoubleValue(0.0))
}
val currentManager = BattleCameraManagers.managers[activeCameraManager]!!
val currentState = currentManager.states[currentCameraState]!!
val currentCamera = BattleCameras.cameras[currentState.camera]
InstructionChangeTime += 0.05
if (currentCamera != null) {
cameraTimer += 0.05
if (cameraTimer > currentCamera.length && currentCamera.length != 0.0) {
if (currentState.loop) {
cameraTimer -= currentCamera.length
}
else {
cameraTimer = currentCamera.length
}
}
}
if (!calculateNextState) {
currentCameraState = nextCameraState
calculateNextState = true
cameraTimer = if (currentCamera != null && currentCamera.length != 0.0) cameraTimer - currentCamera.length else 0.0
}
else {
updateMolangVar()
environment.setSimpleVariable("instructionChangeTime", DoubleValue(InstructionChangeTime))
val endOfAnim = if (currentCamera != null) cameraTimer + 1/20 >= currentCamera.length else true
val nextState = evaluateTransitions(currentState, endOfAnim)
if (nextState != null) {
nextCameraState = nextState.targetState
while (true) {
val newState = currentManager.states[nextCameraState]!!
val newCamera = BattleCameras.cameras[newState.camera]
val newEndOfAnim = if (newCamera != null) newCamera.length == 0.0 else true
val newNextState = evaluateTransitions(newState, newEndOfAnim)
if (newNextState == null) {
break
}
else {
nextCameraState = newNextState.targetState
}
}
calculateNextState = false
}
}
if (InstructionJustChanged) {
InstructionJustChanged = false
Instruction = "NONE"
}
}
private fun evaluateTransitions(state: BattleCameraState, checkEnd: Boolean) : BattleCameraTransition? {
var nextState = state.listOfTransitions.firstOrNull { !it.checkOnEnd && runtime.resolveBoolean(it.condition) }
if (nextState == null && checkEnd) {
nextState = state.listOfTransitions.firstOrNull { it.checkOnEnd && runtime.resolveBoolean(it.condition) }
}
return nextState
}
var cameraPosition: Vec3 = Vec3.ZERO
var cameraRotation: Vector3f = Vector3f()
fun updateCameraData(delta: Double) {
if (!shouldUseStadiumCamera()) return
val environment = runtime.environment
updateMolangVar()
environment.setSimpleVariable("instructionChangeTime", DoubleValue(InstructionChangeTime + delta/20))
val currentManager = BattleCameraManagers.managers[activeCameraManager]!!
val currentState = currentManager.states[currentCameraState]!!
val currentCamera = BattleCameras.cameras[currentState.camera]
if (currentCamera != null) {
var renderTime = cameraTimer + delta/20
if (renderTime > currentCamera.length && currentCamera.length != 0.0 && !calculateNextState) {
val newState = currentManager.states[nextCameraState]!!
val newCamera = BattleCameras.cameras[newState.camera] ?: return
environment.setSimpleVariable("anim_time", DoubleValue(renderTime - currentCamera.length))
cameraPosition = newCamera.resolvePosition(renderTime - currentCamera.length, battleYRot)
cameraRotation = newCamera.resolveRotation(renderTime - currentCamera.length, battleYRot)
}
else {
if (renderTime > currentCamera.length && currentCamera.length != 0.0) renderTime = currentCamera.length
environment.setSimpleVariable("anim_time", DoubleValue(renderTime))
cameraPosition = currentCamera.resolvePosition(renderTime, battleYRot)
cameraRotation = currentCamera.resolveRotation(renderTime, battleYRot)
}
cameraPosition = cameraPosition.add(battleCenter)
}
}
}
@@ -0,0 +1,119 @@
package com.cobblemon.mod.common.stadium
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.util.*
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import java.lang.reflect.Type
class BattleCameraManagerEntry(
var id: ResourceLocation,
val states: MutableMap<String, BattleCameraState>,
val startingState: String
) {
fun encode(buf: RegistryFriendlyByteBuf) {
buf.writeIdentifier(id)
buf.writeMap(states, {_, key -> buf.writeString(key)}, {_, value -> value.encode(buf)})
buf.writeString(startingState)
}
companion object {
fun decode(buf: RegistryFriendlyByteBuf): BattleCameraManagerEntry {
val id = buf.readIdentifier()
val states = buf.readMap({buf.readString()}, {BattleCameraState.decode(buf)}).toMutableMap()
val startState = buf.readString()
return BattleCameraManagerEntry(id, states, startState)
}
}
}
class BattleCameraState(
val camera: ResourceLocation,
val target: StadiumCameraTarget,
val listOfTransitions: MutableList<BattleCameraTransition>,
val loop: Boolean
) {
fun encode(buf: RegistryFriendlyByteBuf) {
buf.writeIdentifier(camera)
buf.writeEnum(target)
buf.writeCollection(listOfTransitions) {_, transition -> transition.encode(buf)}
buf.writeBoolean(loop)
}
companion object {
fun decode(buf: RegistryFriendlyByteBuf): BattleCameraState {
val camera = buf.readIdentifier()
val target = buf.readEnum(StadiumCameraTarget::class.java)
val listOfTransitions = buf.readList { BattleCameraTransition.decode(buf) }.toMutableList()
val loop = buf.readBoolean()
return BattleCameraState(camera, target, listOfTransitions, loop)
}
}
}
class BattleCameraTransition(
val targetState: String,
val checkOnEnd: Boolean,
val condition: Expression
) {
fun encode(buf: RegistryFriendlyByteBuf) {
buf.writeString(targetState)
buf.writeBoolean(checkOnEnd)
buf.writeString(condition.originalString)
}
companion object {
fun decode(buf: RegistryFriendlyByteBuf): BattleCameraTransition {
val targetState = buf.readString()
val checkOnEnd = buf.readBoolean()
val condition = buf.readString().asExpression()
return BattleCameraTransition(targetState, checkOnEnd, condition)
}
}
}
enum class StadiumCameraTarget {
CENTRE,
ACTIVE_POKEMON,
ENEMY_POKEMON,
TRAINER
}
object BattleCameraManagerAdapter: JsonDeserializer<BattleCameraManagerEntry> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): BattleCameraManagerEntry {
if (json is JsonObject) {
val states = mutableMapOf<String, BattleCameraState>()
json["states"]?.asJsonObject?.entrySet()?.forEach { (name, state) ->
val camera = state.asJsonObject["camera"]?.asString?.asResource() ?: "".asResource()
val target = when (state.asJsonObject["target"]?.asString) {
"CENTRE" -> StadiumCameraTarget.CENTRE
"ACTIVE_POKEMON" -> StadiumCameraTarget.ACTIVE_POKEMON
"ENEMY_POKEMON" -> StadiumCameraTarget.ENEMY_POKEMON
"TRAINER" -> StadiumCameraTarget.TRAINER
else -> StadiumCameraTarget.CENTRE
}
val listOfTransitions = mutableListOf<BattleCameraTransition>()
state.asJsonObject["transitions"]?.asJsonArray?.forEach {
val targetState = it.asJsonObject["targetState"]?.asString ?: ""
val checkOnEnd = it.asJsonObject["checkOnEnd"]?.asBoolean ?: false
val condition = it.asJsonObject["condition"]?.asString?.asExpression() ?: "0.0".asExpression()
listOfTransitions.add(BattleCameraTransition(targetState, checkOnEnd, condition))
}
val loop = state.asJsonObject["loop"]?.asBoolean ?: false
states[name] = BattleCameraState(camera, target, listOfTransitions, loop)
}
val startingState = json["startingState"].asString ?: ""
return BattleCameraManagerEntry(cobblemonResource(""), states, startingState)
}
throw IllegalStateException("StadiumCameraManager could not be parsed")
}
}
@@ -0,0 +1,44 @@
package com.cobblemon.mod.common.stadium
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object BattleCameraManagers: JsonDataRegistry<BattleCameraManagerEntry> {
override val id = cobblemonResource("stadium_managers")
override val type = PackType.SERVER_DATA
override val gson: Gson = GsonBuilder()
.disableHtmlEscaping()
.setPrettyPrinting()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(BattleCameraManagerEntry::class.java, BattleCameraManagerAdapter)
.create()
override val typeToken: TypeToken<BattleCameraManagerEntry> = TypeToken.get(BattleCameraManagerEntry::class.java)
override val resourcePath = "stadium_managers"
val managers = mutableMapOf<ResourceLocation, BattleCameraManagerEntry>()
override fun reload(data: Map<ResourceLocation, BattleCameraManagerEntry>) {
managers.clear()
data.forEach { location, manager ->
manager.id = location
managers[location] = manager
}
observable.emit(this)
}
override val observable = SimpleObservable<BattleCameraManagers>()
override fun sync(player: ServerPlayer) {
BattleCameraManagersSyncPacket(managers.values).sendToPlayer(player)
}
}
@@ -0,0 +1,27 @@
package com.cobblemon.mod.common.stadium
import com.cobblemon.mod.common.net.messages.client.data.DataRegistrySyncPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
class BattleCameraManagersSyncPacket(managers: Collection<BattleCameraManagerEntry>) :
DataRegistrySyncPacket<BattleCameraManagerEntry, BattleCameraManagersSyncPacket>(managers) {
override fun encodeEntry(buffer: RegistryFriendlyByteBuf, entry: BattleCameraManagerEntry) {
entry.encode(buffer)
}
override fun decodeEntry(buffer: RegistryFriendlyByteBuf): BattleCameraManagerEntry? {
return BattleCameraManagerEntry.decode(buffer)
}
override fun synchronizeDecoded(entries: Collection<BattleCameraManagerEntry>) {
BattleCameraManagers.reload(entries.associateBy { it.id })
}
override val id = ID
companion object {
val ID = cobblemonResource("stadium_camera_managers_sync")
fun decode(buffer: RegistryFriendlyByteBuf) = BattleCameraManagersSyncPacket(emptyList()).apply { decodeBuffer(buffer) }
}
}
@@ -0,0 +1,29 @@
package com.cobblemon.mod.common.stadium
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readIdentifier
import com.cobblemon.mod.common.util.writeIdentifier
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.phys.Vec3
import java.util.*
class BattleCameraPacket(
val joiningBattle: UUID,
val cameraManager: ResourceLocation,
val center: Vec3,
val yRot: Float
): NetworkPacket<BattleCameraPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeUUID(this.joiningBattle)
buffer.writeIdentifier(cameraManager)
buffer.writeVec3(center)
buffer.writeFloat(yRot)
}
companion object {
val ID = cobblemonResource("stadium_join")
fun decode(buffer: RegistryFriendlyByteBuf) = BattleCameraPacket(buffer.readUUID(), buffer.readIdentifier(), buffer.readVec3(), buffer.readFloat())
}
}
@@ -0,0 +1,10 @@
package com.cobblemon.mod.common.stadium
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import net.minecraft.client.Minecraft
object BattleCameraSetupHandler: ClientNetworkPacketHandler<BattleCameraPacket> {
override fun handle(packet: BattleCameraPacket, client: Minecraft) {
BattleCameraManagerClient.handleJoin(packet.joiningBattle, packet.cameraManager, packet.center, packet.yRot)
}
}
@@ -0,0 +1,48 @@
package com.cobblemon.mod.common.stadium
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object BattleCameras: JsonDataRegistry<BattleCameraEntry> {
override val id = cobblemonResource("stadium_cameras")
override val type = PackType.SERVER_DATA
override val gson: Gson = GsonBuilder()
.disableHtmlEscaping()
.setPrettyPrinting()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(BattleCameraEntry::class.java, BattleCameraEntry.CameraEntryAdapter)
.create()
override val typeToken: TypeToken<BattleCameraEntry> = TypeToken.get(BattleCameraEntry::class.java)
override val resourcePath = "stadium_cameras"
val cameras = mutableMapOf<ResourceLocation, BattleCameraEntry>()
override fun reload(data: Map<ResourceLocation, BattleCameraEntry>) {
cameras.clear()
data.forEach { location, camera ->
camera.id = location
cameras[location] = camera
}
observable.emit(this)
}
override val observable = SimpleObservable<BattleCameras>()
override fun sync(player: ServerPlayer) {
BattleCamerasSyncPacket(cameras.values).sendToPlayer(player)
}
fun getMoveCamera(move: String): BattleCameraEntry? {
return cameras.values.firstOrNull { it.move == move }
}
}
@@ -0,0 +1,27 @@
package com.cobblemon.mod.common.stadium
import com.cobblemon.mod.common.net.messages.client.data.DataRegistrySyncPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
class BattleCamerasSyncPacket(cameras: Collection<BattleCameraEntry>) :
DataRegistrySyncPacket<BattleCameraEntry, BattleCamerasSyncPacket>(cameras) {
override fun encodeEntry(buffer: RegistryFriendlyByteBuf, entry: BattleCameraEntry) {
entry.encode(buffer)
}
override fun decodeEntry(buffer: RegistryFriendlyByteBuf): BattleCameraEntry? {
return BattleCameraEntry.decode(buffer)
}
override fun synchronizeDecoded(entries: Collection<BattleCameraEntry>) {
BattleCameras.reload(entries.associateBy { it.id })
}
override val id = ID
companion object {
val ID = cobblemonResource("stadium_cameras_sync")
fun decode(buffer: RegistryFriendlyByteBuf) = BattleCamerasSyncPacket(emptyList()).apply { decodeBuffer(buffer) }
}
}
@@ -0,0 +1,12 @@
package com.cobblemon.mod.common.stadium
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.phys.Vec3
import java.util.*
abstract class StadiumBattleDefenition {
abstract fun playerPositions(): MutableList<Pair<Int, Vec3>>
abstract fun pokemonPositions(): MutableList<Pair<Int, Vec3>>
}
@@ -0,0 +1,4 @@
package com.cobblemon.mod.common.stadium
class StadiumFormat {
}
@@ -0,0 +1,34 @@
package com.cobblemon.mod.common.stadium
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import java.util.UUID
object StadiumServerManager {
val activeStadiumBattles = mutableMapOf<UUID, StadiumBattleInstance>()
fun createNewStadiumBattle(battle: UUID, managerResource: ResourceLocation, participants: MutableList<UUID> = mutableListOf<UUID>()) {
val newInstance = StadiumBattleInstance(battle, managerResource)
newInstance.participants = participants
activeStadiumBattles[battle] = newInstance
}
fun terminateNewStadiumBattle(battle: UUID) {
activeStadiumBattles.remove(battle)
}
fun addPlayerToStadiumBattle(battle: UUID, player: UUID) {
val instance = activeStadiumBattles[battle]
if (instance != null) {
if (!instance.participants.contains(player)) instance.participants.add(player)
}
}
}
class StadiumBattleInstance(
val stadiumBattleId: UUID,
val managerResource: ResourceLocation
){
var participants = mutableListOf<UUID>()
}
@@ -0,0 +1,73 @@
package com.cobblemon.mod.common.stadium
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.BattleBuilder
import com.cobblemon.mod.common.battles.SuccessfulBattleStart
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.party
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.phys.Vec3
class StadiumSingleBattle: StadiumBattleDefenition() {
override fun playerPositions(): MutableList<Pair<Int, Vec3>> {
return mutableListOf()
}
override fun pokemonPositions(): MutableList<Pair<Int, Vec3>> {
return mutableListOf()
}
/*
@JvmField
val MAX_PLAYERS = 2
var player1Position = Vec3(0.0,0.0,0.0)
var player2Position = Vec3(0.0,0.0,0.0)
var player1PokemonPosition = Vec3(0.0,0.0,0.0)
var player2PokemonPosition = Vec3(0.0,0.0,0.0)
var activeBattle: PokemonBattle? = null
override fun readyForBattle(): Boolean {
return this.getPlayers().count() == MAX_PLAYERS
}
override fun canAddPlayer(player: ServerPlayer): Boolean {
val party = player.party()
return party.occupied() > 0
}
override fun addPlayer(player: ServerPlayer) {
if (canAddPlayer(player)) {
participants.add(player.uuid)
player.sendPacket(BattleCameraPacket(player.uuid, BattleCameraManagers.managers.keys.first(), Vec3.ZERO, 0F))
}
}
override fun getPlayers(): MutableList<ServerPlayer> {
return participants.mapNotNull { it.getPlayer() }.toMutableList()
}
override fun initializeBattle() {
if (!readyForBattle()) return
val players = getPlayers()
val player1 = players[0]
val player2 = players[1]
val battleResult = BattleBuilder.pvp1v1(player1, player2)
var errored = false
battleResult.ifErrored { errored = true }
if (errored) return
val battle = (battleResult as SuccessfulBattleStart).battle
}
override fun getCurrentCamera(): BattleCameraEntry {
TODO("Not yet implemented")
}
*/
}
@@ -0,0 +1,14 @@
package com.cobblemon.mod.common.util
import com.cobblemon.mod.common.util.math.toEulerXYZ
import net.minecraft.client.Camera
import org.joml.Quaternionf
fun Camera.setRotation(yaw: Float, pitch: Float, roll: Float) {
this.xRot = yaw
this.yRot = pitch
this.rotation.rotationYXZ(Math.PI.toFloat() - pitch * (Math.PI.toFloat() / 180F),-yaw * (Math.PI.toFloat() / 180F),roll * (Math.PI.toFloat() / 180F))
Camera.FORWARDS.rotate(this.rotation, this.forwards)
Camera.UP.rotate(this.rotation, this.up)
Camera.LEFT.rotate(this.rotation, this.left)
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.util.adapters
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
@@ -28,7 +29,7 @@ import java.lang.reflect.Type
*
* @author landonjw
*/
object RidingBehaviourSettingsAdapter : JsonDeserializer<RidingBehaviourSettings> {
object RidingBehaviourSettingsAdapter : JsonDeserializer<RidingBehaviourSettings?> {
val types: MutableMap<ResourceLocation, Class<out RidingBehaviourSettings>> = mutableMapOf(BirdAirBehaviour.KEY to BirdAirSettings::class.java,
DolphinBehaviour.KEY to DolphinSettings::class.java,
GenericLandBehaviour.KEY to GenericLandSettings::class.java,
@@ -50,21 +51,27 @@ object RidingBehaviourSettingsAdapter : JsonDeserializer<RidingBehaviourSettings
RunStrategy.key to CompositeSettings::class.java,
)
override fun deserialize(element: JsonElement, type: Type, context: JsonDeserializationContext): RidingBehaviourSettings {
override fun deserialize(element: JsonElement, type: Type, context: JsonDeserializationContext): RidingBehaviourSettings? {
val root = element.asJsonObject
val key = root.get("key").asString
val keyIdentifier = key.asIdentifierDefaultingNamespace()
if (keyIdentifier == CompositeBehaviour.KEY) {
val strategy = root.get("transitionStrategy").asString
val strategyIdentifier = strategy.asIdentifierDefaultingNamespace()
val behaviourType = types[strategyIdentifier] ?: throw IllegalArgumentException("Unknown strategy for composite behaviour: $strategy")
val settings: RidingBehaviourSettings = context.deserialize(root, behaviourType)
return settings
val behaviourType = types[strategyIdentifier]
if (behaviourType == null) {
Cobblemon.LOGGER.warn("Unknown strategy: $strategyIdentifier for composite behaviour: $key. Skipping.")
return null
}
return context.deserialize(root, behaviourType)
}
else {
val behaviourType = types[keyIdentifier] ?: throw IllegalArgumentException("Unknown behaviour: $key")
val settings: RidingBehaviourSettings = context.deserialize(element, behaviourType)
return settings
val behaviourType = types[keyIdentifier]
if (behaviourType == null) {
Cobblemon.LOGGER.warn("Unknown riding behaviour encountered: $key. Skipping.")
return null
}
return context.deserialize(element, behaviourType)
}
}
}
@@ -0,0 +1,41 @@
{
"format_version": "1.8.0",
"animations": {
"animation.rotom_drone.sleep": {
"loop": true,
"bones": {
"body": {
"rotation": [
"math.sin(query.anim_time * 120) * 6",
0,
"math.cos(query.anim_time * 100) * 4"
],
"position": [
0,
"18 + math.sin(query.anim_time * 90) * 1",
0
]
},
"head": {
"relative_to": {
"rotation": "entity"
},
"rotation": [
0,
0,
0.01
]
}
}
},
"animation.rotom_drone.circle": {
"loop": true,
"bones": {
"rotom_drone": {
"rotation": [30, "90 - q.anim_time*90", 0],
"position": ["48*math.cos(q.anim_time*90)", 64, "48*math.sin(q.anim_time*90)"]
}
}
}
}
}
@@ -0,0 +1,208 @@
{
"format_version": "1.12.0",
"minecraft:geometry": [
{
"description": {
"identifier": "geometry.drone_rotom",
"texture_width": 64,
"texture_height": 32,
"visible_bounds_width": 8,
"visible_bounds_height": 6.5,
"visible_bounds_offset": [0, 2.75, 0]
},
"bones": [
{
"name": "rotom_drone",
"pivot": [0, 0, 0]
},
{
"name": "body",
"parent": "rotom_drone",
"pivot": [0, 8, 0]
},
{
"name": "head",
"parent": "body",
"pivot": [0, 6.5, 0],
"cubes": [
{"origin": [-5.5, 6.5, -3.5], "size": [11, 6, 7], "uv": [0, 0]}
]
},
{
"name": "eyes",
"parent": "head",
"pivot": [0.5, 6.5, -2.5]
},
{
"name": "eye_right",
"parent": "eyes",
"pivot": [-2.9, 9.5, -3.5],
"rotation": [0, 0, -7.5],
"cubes": [
{"origin": [-3.9, 7.5, -3.5], "size": [2, 4, 0], "inflate": 0.01, "uv": [0, 0], "mirror": true},
{"origin": [-3.9, 9, -3.5], "size": [2, 1, 0], "inflate": 0.02, "uv": [0, 6], "mirror": true}
]
},
{
"name": "pupil_right",
"parent": "eye_right",
"pivot": [-2.9, 9.5, -3.5],
"cubes": [
{"origin": [-3.4, 8, -3.5], "size": [1, 3, 0], "inflate": 0.03, "uv": [5, 0], "mirror": true}
]
},
{
"name": "eye_left",
"parent": "eyes",
"pivot": [2.9, 9.5, -3.5],
"rotation": [0, 0, 7.5],
"cubes": [
{"origin": [1.9, 7.5, -3.5], "size": [2, 4, 0], "inflate": 0.01, "uv": [0, 13]},
{"origin": [1.9, 9, -3.5], "size": [2, 1, 0], "inflate": 0.02, "uv": [0, 5]}
]
},
{
"name": "pupil_left",
"parent": "eye_left",
"pivot": [2.9, 9.5, -3.5],
"cubes": [
{"origin": [2.4, 8, -3.5], "size": [1, 3, 0], "inflate": 0.03, "uv": [6, 13]}
]
},
{
"name": "mouth_smile",
"parent": "head",
"pivot": [0, 7.55, -3.5],
"cubes": [
{"origin": [-1, 6.95, -3.5], "size": [2, 1, 0], "inflate": 0.02, "uv": [36, 7]},
{"origin": [-1.5, 7.25, -3.5], "size": [1, 1, 0], "inflate": 0.01, "uv": [40, 7]},
{"origin": [0.5, 7.25, -3.5], "size": [1, 1, 0], "inflate": 0.01, "uv": [42, 7]}
]
},
{
"name": "mouth_closed",
"parent": "head",
"pivot": [0, 7.45, -3.4],
"cubes": [
{"origin": [-0.5, 6.95, -3.4], "size": [1, 1, 0], "inflate": 0.02, "uv": [38, 8]}
]
},
{
"name": "mouth_open",
"parent": "head",
"pivot": [0, 7.45, -3.4],
"cubes": [
{"origin": [-0.5, 6.95, -3.4], "size": [1, 1, 0], "inflate": 0.02, "uv": [36, 8]}
]
},
{
"name": "mouth_sad",
"parent": "head",
"pivot": [0, 7.6, -3.3],
"cubes": [
{"origin": [-1, 7.3, -3.3], "size": [2, 1, 0], "inflate": 0.02, "uv": [36, 9]},
{"origin": [-1.5, 7, -3.3], "size": [1, 1, 0], "inflate": 0.01, "uv": [40, 9]},
{"origin": [0.5, 7, -3.3], "size": [1, 1, 0], "inflate": 0.01, "uv": [42, 9]}
]
},
{
"name": "head_spike1",
"parent": "head",
"pivot": [0, 12.5, 0],
"cubes": [
{"origin": [-1.5, 11.5, -1.5], "size": [3, 4, 3], "uv": [29, 0]}
]
},
{
"name": "head_spike2",
"parent": "head_spike1",
"pivot": [0, 15.5, 0],
"cubes": [
{"origin": [-1, 14.5, -1], "size": [2, 4, 2], "uv": [8, 13]}
]
},
{
"name": "head_spike3",
"parent": "head_spike2",
"pivot": [0, 18.5, 0],
"cubes": [
{"origin": [-0.5, 17.5, -0.5], "size": [1, 3, 1], "uv": [44, 6]}
]
},
{
"name": "torso",
"parent": "body",
"pivot": [0, 7, 0],
"cubes": [
{"origin": [-2, 3, -1.5], "size": [4, 4, 3], "uv": [34, 10]}
]
},
{
"name": "camera",
"parent": "torso",
"pivot": [0, 4.65, -1.25],
"cubes": [
{"origin": [-1, 3.65, -2.75], "size": [2, 2, 2], "uv": [24, 13]},
{"origin": [-0.5, 4.15, -2.75], "size": [1, 1, 0], "inflate": 0.01, "uv": [38, 0]}
]
},
{
"name": "propeller_front_right",
"parent": "torso",
"pivot": [-1.5, 5, -1],
"rotation": [0, 45, 0],
"cubes": [
{"origin": [-2, 5, -3.5], "size": [1, 0, 3], "uv": [37, 0]},
{"origin": [-5.5, 5, -11.5], "size": [8, 0, 8], "uv": [40, 0]}
]
},
{
"name": "propeller_front_left",
"parent": "torso",
"pivot": [1.5, 5, -1],
"rotation": [0, -45, 0],
"cubes": [
{"origin": [1, 5, -3.5], "size": [1, 0, 3], "uv": [27, 0]},
{"origin": [-2.5, 5, -11.5], "size": [8, 0, 8], "uv": [40, 0]}
]
},
{
"name": "propeller_back_right",
"parent": "torso",
"pivot": [-1.5, 5, 1],
"rotation": [0, -45, 0],
"cubes": [
{"origin": [-2, 5, 0.5], "size": [1, 0, 3], "uv": [39, 0]},
{"origin": [-5.5, 5, 3.5], "size": [8, 0, 8], "uv": [40, 0]}
]
},
{
"name": "propeller_back_left",
"parent": "torso",
"pivot": [1.5, 5, 1],
"rotation": [0, 45, 0],
"cubes": [
{"origin": [1, 5, 0.5], "size": [1, 0, 3], "uv": [42, 3]},
{"origin": [-2.5, 5, 3.5], "size": [8, 0, 8], "uv": [40, 0]}
]
},
{
"name": "tail1",
"parent": "torso",
"pivot": [0, 3, 0],
"cubes": [
{"origin": [-1, 1, -1], "size": [2, 3, 2], "uv": [16, 13]}
]
},
{
"name": "tail2",
"parent": "tail1",
"pivot": [0, 1, 0],
"cubes": [
{"origin": [-0.5, -1, -0.5], "size": [1, 3, 1], "uv": [41, 3]}
]
}
]
}
]
}
@@ -0,0 +1,8 @@
{
"poses": {
"sleep": {
"poseTypes": ["SLEEP"],
"animations": ["q.bedrock('rotom_drone', 'sleep')"]
}
}
}
@@ -0,0 +1,13 @@
{
"name": "cobblemon:rotom_drone",
"order": 0,
"variations": [
{
"aspects": [],
"poser": "cobblemon:rotom_drone",
"model": "cobblemon:rotom_drone.geo",
"texture": "cobblemon:textures/drone/rotom_drone.png",
"layers": []
}
]
}
@@ -5,7 +5,7 @@
"identifier": "cobblemon:protect-block",
"basic_render_parameters": {
"material": "particles_add",
"texture": "textures/particles/protect-block"
"texture": "textures/particles/moves/protect_block"
}
},
"events": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:protect-blockchip",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/xs-fadeorb-lite"
"texture": "textures/particles/generic/orb/xsfadeorblite"
}
},
"curves": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:afterspark",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/afterspark"
"texture": "textures/particles/balls/afterspark"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:capturesparks",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/capturesparks"
"texture": "textures/particles/balls/capturesparks"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:capturestar",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/capturestar"
"texture": "textures/particles/balls/capturestar"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:hisuiafterspark",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/hisuiafterspark"
"texture": "textures/particles/balls/hisuiafterspark"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:hisuicapturestar",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/hisuicapturestar"
"texture": "textures/particles/balls/hisuicapturestar"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:hisuipuff",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/hisuipuff"
"texture": "textures/particles/balls/hisuipuff"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:hisuisendspark",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/hisuisendspark"
"texture": "textures/particles/balls/hisuisendspark"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:hisuispark",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/hisuispark"
"texture": "textures/particles/balls/hisuispark"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:hisuitrail",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/hisuitrail"
"texture": "textures/particles/balls/hisuitrail"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:ancient_pokeball_smoke",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/white_vanilla_smoke"
"texture": "textures/particles/vanilla/big_smoke_white"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:cherry_leaves_particle",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/cherry_petal"
"texture": "textures/particles/vanilla/cherry_petal"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:cherry_leaves_faint_particle",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/cherry_petal"
"texture": "textures/particles/vanilla/cherry_petal"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:confetti",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/confetti"
"texture": "textures/particles/generic/confetti"
}
},
"curves": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:evo_bigsparkle",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/bigsparkle"
"texture": "textures/particles/generic/sparkle/bigsparkle"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:evo_buildup",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/glowing_dots_cyan"
"texture": "textures/particles/generic/orb/glowing_dots_cyan"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:evo_flyingsparkles",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/glowing_sparkle_yellow"
"texture": "textures/particles/generic/sparkle/glowingsparkle_yellow"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:evo_godrays",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/lightbeam"
"texture": "textures/particles/generic/lightbeam"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:evo_godrays_small",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/lightbeam"
"texture": "textures/particles/generic/lightbeam"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:evo_implode",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/glowing_dots_cyan"
"texture": "textures/particles/generic/orb/glowing_dots_cyan"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:evo_obscuring",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/glowing_smoke_cyan"
"texture": "textures/particles/generic/smoke/glowingsmoke_cyan"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:evo_smokeburst",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/glowing_smoke_cyan"
"texture": "textures/particles/generic/smoke/glowingsmoke_cyan"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:evo_sparkle",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/glowing_sparkle_yellow"
"texture": "textures/particles/generic/sparkle/glowingsparkle_yellow"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:evo_sparkleburst",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/glowing_sparkle_yellow"
"texture": "textures/particles/generic/sparkle/glowingsparkle_yellow"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:evo_sparklefetti",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/star"
"texture": "textures/particles/generic/star"
}
},
"curves": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:accessory_fish_splash",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "cobblemon:fish_splash"
"texture": "textures/particles/generic/water/fishsplash"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:big_fish_splash",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "cobblemon:fish_splash"
"texture": "textures/particles/generic/water/fishsplash"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:bob_splash",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "cobblemon:fish_splash"
"texture": "textures/particles/generic/water/fishsplash"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:fishing_bobber_big_ripple",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/water_ripple"
"texture": "textures/particles/generic/water/water_ripple"
}
},
"curves": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:fishing_bobber_ripple",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/water_ripple"
"texture": "textures/particles/generic/water/water_ripple"
}
},
"curves": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:fishing_surface_ripple",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/water_ripple"
"texture": "textures/particles/generic/water/water_ripple"
}
},
"curves": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:fishing_wake",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/white"
"texture": "textures/particles/generic/white"
}
},
"events": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:small_fish_splash",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "cobblemon:fish_splash"
"texture": "textures/particles/generic/water/fishsplash"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:splash",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/rain_splash"
"texture": "textures/particles/generic/water/rainsplash"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:burn_actor",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/large-flame"
"texture": "textures/particles/generic/fire/flame"
}
},
"events": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:burn_actorresidual",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/ember"
"texture": "textures/particles/generic/fire/ember"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:confused_actor",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/chirp"
"texture": "textures/particles/generic/status/confusion_bird"
}
},
"curves": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:confused_bird2",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/chirp"
"texture": "textures/particles/generic/status/confusion_bird"
}
},
"curves": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:confused_bird3",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/chirp"
"texture": "textures/particles/generic/status/confusion_bird"
}
},
"curves": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:confused_bird4",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/chirp"
"texture": "textures/particles/generic/status/confusion_bird"
}
},
"curves": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:confused_bird5",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/chirp"
"texture": "textures/particles/generic/status/confusion_bird"
}
},
"curves": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:confused_bird6",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/chirp"
"texture": "textures/particles/generic/status/confusion_bird"
}
},
"curves": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:infatuate_actor",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/attract_heart"
"texture": "textures/particles/generic/status/infatuation_heart"
}
},
"events": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:infatuate_actorresidual",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particles/attract_heart"
"texture": "textures/particles/generic/status/infatuation_heart"
}
},
"components": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:paralysis_actor",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/paralysis-spark"
"texture": "textures/particles/generic/status/paralysis_spark"
}
},
"events": {
@@ -5,7 +5,7 @@
"identifier": "cobblemon:paralysis_actorresidual",
"basic_render_parameters": {
"material": "particles_blend",
"texture": "textures/particles/accessory-spark"
"texture": "textures/particles/generic/status/accessory_spark"
}
},
"components": {

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