diff --git a/src/main/java/dev/hephaestus/glowcase/block/entity/EntityDisplayBlockEntity.java b/src/main/java/dev/hephaestus/glowcase/block/entity/EntityDisplayBlockEntity.java index 6db86155..c000bd8f 100644 --- a/src/main/java/dev/hephaestus/glowcase/block/entity/EntityDisplayBlockEntity.java +++ b/src/main/java/dev/hephaestus/glowcase/block/entity/EntityDisplayBlockEntity.java @@ -1,6 +1,8 @@ package dev.hephaestus.glowcase.block.entity; +import com.mojang.serialization.Codec; import dev.hephaestus.glowcase.Glowcase; +import dev.hephaestus.glowcase.util.PartPoseInfo; import net.minecraft.core.BlockPos; import net.minecraft.core.HolderLookup; import net.minecraft.core.registries.Registries; @@ -19,6 +21,10 @@ import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + @NullMarked public class EntityDisplayBlockEntity extends DisplayBlockEntity implements StackInteractable { public static final TagKey> TICK = TagKey.create(Registries.ENTITY_TYPE, Glowcase.id("tick_in_display")); @@ -26,6 +32,7 @@ public class EntityDisplayBlockEntity extends DisplayBlockEntity implements Stac protected int entityId; protected @Nullable Entity displayEntity = null; protected @Nullable EntityType entityType = null; + protected Map partPoses = new HashMap<>(); public EntityDisplayBlockEntity(BlockPos pos, BlockState state) { super(Glowcase.ENTITY_DISPLAY_BLOCK_ENTITY.get(), pos, state); @@ -82,12 +89,35 @@ public static void tick(Level world, BlockPos blockPos, BlockState state, Entity } } + public Map getPartPoses(){ + return partPoses; + } + + public void loadPartPoses(Map newPartPoses) { + this.partPoses.putAll(newPartPoses); + } + + public PartPoseInfo getPartPostInfo(String name) { + if(partPoses.containsKey(name)) { + return partPoses.get(name); + } + return new PartPoseInfo(name,new Vector3f(0), new Vector3f(0)); + } + + public void setPartPoseInfo(String name, PartPoseInfo partPoseInfo) { + this.partPoses.put(name,partPoseInfo); + } + + @Override protected void saveAdditional(ValueOutput view) { super.saveAdditional(view); if (entityType != null) { view.store("type", EntityType.CODEC, entityType); } + if(!partPoses.isEmpty()) { + view.store("partPoses", Codec.unboundedMap(Codec.STRING, PartPoseInfo.CODEC), partPoses); + } } @Override @@ -97,6 +127,10 @@ protected void loadAdditional(ValueInput view) { this.entityId = view.getIntOr("entityId", 0); this.entityType = view.read("type", EntityType.CODEC).orElse(null); this.displayEntity = null; + + this.partPoses.clear(); + Optional> optionalStringPartPoseInfoMap = view.read("partPoses", Codec.unboundedMap(Codec.STRING, PartPoseInfo.CODEC)); + optionalStringPartPoseInfoMap.ifPresent(stringPartPoseInfoMap -> this.partPoses.putAll(stringPartPoseInfoMap)); } @Override diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/DisplayBlockEditScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/DisplayBlockEditScreen.java index 85c1429e..c80c5eef 100644 --- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/DisplayBlockEditScreen.java +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/DisplayBlockEditScreen.java @@ -197,12 +197,12 @@ public void init() { @Override public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { super.extractRenderState(graphics, mouseX, mouseY, delta); - graphics.text(minecraft.font, Component.translatable("gui.glowcase.scale_label"), 20, 17, 0xFFFFFFFF); - graphics.text(minecraft.font, Component.translatable("gui.glowcase.x_offset_label"), 20, 47, 0xFFFFFFFF); - graphics.text(minecraft.font, Component.translatable("gui.glowcase.y_offset_label"), 20, 77, 0xFFFFFFFF); - graphics.text(minecraft.font, Component.translatable("gui.glowcase.z_offset_label"), 20, 107, 0xFFFFFFFF); - graphics.text(minecraft.font, Component.translatable("gui.glowcase.pitch_value"), 20, 137, 0xFFFFFFFF); - graphics.text(minecraft.font, Component.translatable("gui.glowcase.yaw_value"), 20, 167, 0xFFFFFFFF); + graphics.text(minecraft.font, Component.translatable("gui.glowcase.scale_label"), 20, this.scaleField.getY() + 7, 0xFFFFFFFF); + graphics.text(minecraft.font, Component.translatable("gui.glowcase.x_offset_label"), 20, this.xOffsetField.getY() + 7, 0xFFFFFFFF); + graphics.text(minecraft.font, Component.translatable("gui.glowcase.y_offset_label"), 20, this.yOffsetField.getY() + 7, 0xFFFFFFFF); + graphics.text(minecraft.font, Component.translatable("gui.glowcase.z_offset_label"), 20, this.zOffsetField.getY() + 7, 0xFFFFFFFF); + graphics.text(minecraft.font, Component.translatable("gui.glowcase.pitch_value"), 20, this.pitchField.getY() + 7, 0xFFFFFFFF); + graphics.text(minecraft.font, Component.translatable("gui.glowcase.yaw_value"), 20, this.yawField.getY() + 7, 0xFFFFFFFF); } protected final void editDisplayBlock() { diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/EntityDisplayEditScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/EntityDisplayEditScreen.java index 21321f2d..6ba6e484 100644 --- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/EntityDisplayEditScreen.java +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/EntityDisplayEditScreen.java @@ -1,16 +1,166 @@ package dev.hephaestus.glowcase.client.gui.screen.ingame; +import com.google.common.primitives.Floats; import dev.hephaestus.glowcase.block.entity.DisplayBlockEntity; +import dev.hephaestus.glowcase.block.entity.EntityDisplayBlockEntity; +import dev.hephaestus.glowcase.client.util.ModelPartUtil; import dev.hephaestus.glowcase.packet.C2SEditEntityDisplayBlock; +import dev.hephaestus.glowcase.util.PartPoseInfo; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.StringWidget; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.EntityType; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; public class EntityDisplayEditScreen extends DisplayBlockEditScreen { + + private final EntityDisplayBlockEntity entityDisplay; + private final Set expandedParts = new HashSet<>(); + private boolean expanded = false; + private int STARTING_X = 10; + private int STARTING_Y = 200; + private int scrollOffset = 0; + private int maxScroll = 0; + + public EntityDisplayEditScreen(DisplayBlockEntity displayBlock) { super(displayBlock); + this.entityDisplay = (EntityDisplayBlockEntity) displayBlock; + } + + @Override + public void init() { + super.init(); + + this.addRenderableWidget(Button.builder( + Component.literal(this.expanded ? "▼ " : "▶ ").append(Component.translatable("gui.glowcase.model_parts")), + _ -> { + this.expanded = !this.expanded; + this.rebuildWidgets(); + } + ).bounds(STARTING_X, STARTING_Y, 200, 20).build()); + + int bottom = STARTING_Y + 22; + if(this.entityDisplay.getDisplayEntity() != null) { + EntityType type = this.entityDisplay.getDisplayEntity().getType(); + ModelPart root = ModelPartUtil.getModelRoot(type); + if (this.expanded && root != null) { + bottom = this.addPartRows(root); + } + } + + this.maxScroll = Math.max(0, bottom + 10 - this.height); + this.scrollOffset = Mth.clamp(this.scrollOffset, 0, this.maxScroll); + this.offsetWidgets(-this.scrollOffset); + + + } + + private int addPartRows(ModelPart parent) { + int y = STARTING_Y + 22; + for (Map.Entry entry : parent.children.entrySet()) { + String name = entry.getKey(); + boolean partExpanded = this.expandedParts.contains(name); + String displayName = name.replace("_", " "); + displayName = displayName.substring(0, 1).toUpperCase() + displayName.substring(1); + + this.addRenderableWidget(Button.builder(Component.literal((partExpanded ? "▼ " : "▶ ") + displayName), _ -> { + if (!this.expandedParts.remove(name)) this.expandedParts.add(name); + this.rebuildWidgets(); + }).bounds(STARTING_X + 10, y, 190, 20).build()); + + y += 22; + if (!partExpanded) continue; + + for (int i = 0; i <= 1; i++) { + boolean isRotation = i == 0; + Component label = Component.translatable(isRotation ? "gui.glowcase.rotation" : "gui.glowcase.offset"); + this.addRenderableWidget(new StringWidget(STARTING_X + 20, y + 5, this.font.width(label), 9, label, this.font)); + + float step = isRotation ? 5F : 0.25F; + for (int axis = 0; axis < 3; axis++) { + int column = STARTING_X + 70 + axis * 84; + int a = axis; + PartPoseInfo partPoseInfo = this.entityDisplay.getPartPostInfo(name); + + EditBox field = new EditBox(this.font, column, y, 44, 18, Component.empty()); + field.setValue(String.valueOf((isRotation ? partPoseInfo.rotation() : partPoseInfo.offset()).get(a))); + field.setResponder(string -> { + if (Floats.tryParse(string) instanceof Float parsed) { + if(isRotation) { + entityDisplay.setPartPoseInfo(name, entityDisplay.getPartPostInfo(name).updateRotationAtAxis(a,parsed)); + } + else { + entityDisplay.setPartPoseInfo(name, entityDisplay.getPartPostInfo(name).updateOffsetAtAxis(a,parsed)); + } + editDisplayBlock(); + } + }); + + this.addRenderableWidget(field); + this.addRenderableWidget(Button.builder(Component.literal("-"), _ -> { + PartPoseInfo newValue = this.entityDisplay.getPartPostInfo(name); + if(isRotation) { + newValue = newValue.updateRotationAtAxis(a,newValue.getRotationAtAxis(a)-step); + this.entityDisplay.setPartPoseInfo(name,newValue); + } + else { + newValue = newValue.updateOffsetAtAxis(a,newValue.getOffsetAtAxis(a)-step); + this.entityDisplay.setPartPoseInfo(name,newValue); + } + editDisplayBlock(); + field.setValue(String.valueOf((isRotation ? newValue.getRotationAtAxis(a) : newValue.getOffsetAtAxis(a)))); + }).bounds(column + 46, y, 18, 18).build()); + this.addRenderableWidget(Button.builder(Component.literal("+"), _ -> { + PartPoseInfo newValue = this.entityDisplay.getPartPostInfo(name); + if(isRotation) { + newValue = newValue.updateRotationAtAxis(a,newValue.getRotationAtAxis(a)+step); + this.entityDisplay.setPartPoseInfo(name,newValue); + } + else { + newValue = newValue.updateOffsetAtAxis(a,newValue.getOffsetAtAxis(a)+step); + this.entityDisplay.setPartPoseInfo(name,newValue); + } + editDisplayBlock(); + field.setValue(String.valueOf((isRotation ? newValue.getRotationAtAxis(a) : newValue.getOffsetAtAxis(a)))); + }).bounds(column + 64, y, 18, 18).build()); + } + y += 22; + } + } + return y; + + } + + @Override + public boolean mouseScrolled(double x, double y, double scrollX, double scrollY) { + int newOffset = Mth.clamp(this.scrollOffset - (int) (scrollY * 20), 0, this.maxScroll); + if (newOffset != this.scrollOffset) { + this.offsetWidgets(this.scrollOffset - newOffset); + this.scrollOffset = newOffset; + return true; + } + return super.mouseScrolled(x, y, scrollX, scrollY); + } + + private void offsetWidgets(int scrollOffset) { + for (GuiEventListener child : this.children()) { + if (child instanceof AbstractWidget widget) widget.setY(widget.getY() + scrollOffset); + } } @Override public CustomPacketPayload getUpdatePayload() { return C2SEditEntityDisplayBlock.of(blockEntity); } + } diff --git a/src/main/java/dev/hephaestus/glowcase/client/render/block/entity/EntityDisplayBlockEntityRenderer.java b/src/main/java/dev/hephaestus/glowcase/client/render/block/entity/EntityDisplayBlockEntityRenderer.java index 5d773373..55b8faef 100644 --- a/src/main/java/dev/hephaestus/glowcase/client/render/block/entity/EntityDisplayBlockEntityRenderer.java +++ b/src/main/java/dev/hephaestus/glowcase/client/render/block/entity/EntityDisplayBlockEntityRenderer.java @@ -5,6 +5,8 @@ import dev.hephaestus.glowcase.Glowcase; import dev.hephaestus.glowcase.block.entity.EntityDisplayBlockEntity; import dev.hephaestus.glowcase.client.util.BlockEntityRenderUtil; +import dev.hephaestus.glowcase.mixinsupport.PartPosedRenderState; +import dev.hephaestus.glowcase.util.PartPoseInfo; import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; @@ -19,6 +21,8 @@ import org.joml.Vector3f; import org.jspecify.annotations.Nullable; +import java.util.Map; + public record EntityDisplayBlockEntityRenderer(BlockEntityRendererProvider.Context context) implements BlockEntityRenderer { public static Identifier ITEM_TEXTURE = Glowcase.id("textures/item/entity_display_block.png"); @@ -30,6 +34,8 @@ public static class EntityDisplayRenderState extends BlockEntityRenderState { public Vector3f scale; public Entity entity; + + public Map partPoseInfoMap; } @Override @@ -47,6 +53,7 @@ public void extractRenderState(EntityDisplayBlockEntity blockEntity, EntityDispl // TODO: hmm, yes, shuttle the *entire entity* to the render state // Perhaps shuttle the entity renderer & its state instead? state.entity = blockEntity.getDisplayEntity(); + state.partPoseInfoMap = blockEntity.getPartPoses(); } @Override @@ -62,6 +69,7 @@ public void submit(EntityDisplayRenderState state, PoseStack poseStack, SubmitNo //noinspection unchecked EntityRenderer entityRenderer = (EntityRenderer) context.entityRenderer().getRenderer(renderEntity); EntityRenderState entityRenderState = entityRenderer.createRenderState(renderEntity, 0); + ((PartPosedRenderState) entityRenderState).glowcase$setPartPoses(state.partPoseInfoMap.isEmpty() ? null : state.partPoseInfoMap); entityRenderer.submit(entityRenderState, poseStack, submitNodeCollector, camera); } diff --git a/src/main/java/dev/hephaestus/glowcase/client/util/ModelPartUtil.java b/src/main/java/dev/hephaestus/glowcase/client/util/ModelPartUtil.java new file mode 100644 index 00000000..5fb0bc58 --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/client/util/ModelPartUtil.java @@ -0,0 +1,64 @@ +package dev.hephaestus.glowcase.client.util; + +import dev.hephaestus.glowcase.mixinsupport.PartPosedRenderState; +import dev.hephaestus.glowcase.util.PartPoseInfo; +import net.minecraft.client.Minecraft; +import net.minecraft.client.model.Model; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.client.renderer.entity.LivingEntityRenderer; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntitySpawnReason; +import net.minecraft.world.entity.EntityType; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3fc; + +import java.util.Map; + +public class ModelPartUtil { + + @Nullable + public static ModelPart getModelRoot(EntityType type) { + Minecraft minecraft = Minecraft.getInstance(); + ClientLevel level = minecraft.level; + if (level == null) return null; + + Entity entity = type.create(level, EntitySpawnReason.EVENT); + if (entity == null) return null; + + EntityRenderer renderer = minecraft.getEntityRenderDispatcher().getRenderer(entity); + if (renderer instanceof LivingEntityRenderer living) { + return living.getModel().root(); + } + return null; + } + + public static void applyPartPoses(Model model, Object state) { + if (!(state instanceof PartPosedRenderState posed)) { + return; + } + Map partPoses = posed.glowcase$getPartPoses(); + if (partPoses == null) { + return; + } + + Map children = model.root().children; + for (PartPoseInfo partPoseInfo : partPoses.values()) { + ModelPart part = children.get(partPoseInfo.name()); + if (part == null) { + continue; + } + + Vector3fc offset = partPoseInfo.offset(); + Vector3fc rotation = partPoseInfo.rotation(); + part.x += offset.x(); + part.y += offset.y(); + part.z += offset.z(); + part.xRot += rotation.x() * Mth.DEG_TO_RAD; + part.yRot += rotation.y() * Mth.DEG_TO_RAD; + part.zRot += rotation.z() * Mth.DEG_TO_RAD; + } + } +} diff --git a/src/main/java/dev/hephaestus/glowcase/mixin/client/EntityRenderStateMixin.java b/src/main/java/dev/hephaestus/glowcase/mixin/client/EntityRenderStateMixin.java new file mode 100644 index 00000000..af053f50 --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/mixin/client/EntityRenderStateMixin.java @@ -0,0 +1,25 @@ +package dev.hephaestus.glowcase.mixin.client; + +import dev.hephaestus.glowcase.mixinsupport.PartPosedRenderState; +import dev.hephaestus.glowcase.util.PartPoseInfo; +import net.minecraft.client.renderer.entity.state.EntityRenderState; +import org.jspecify.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +import java.util.Map; + +@Mixin(EntityRenderState.class) +public class EntityRenderStateMixin implements PartPosedRenderState { + @Unique private @Nullable Map glowcase$partPoses; + + @Override + public void glowcase$setPartPoses(@Nullable Map partPoses) { + this.glowcase$partPoses = partPoses; + } + + @Override + public @Nullable Map glowcase$getPartPoses() { + return glowcase$partPoses; + } +} diff --git a/src/main/java/dev/hephaestus/glowcase/mixin/client/LivingEntityRendererMixin.java b/src/main/java/dev/hephaestus/glowcase/mixin/client/LivingEntityRendererMixin.java new file mode 100644 index 00000000..ef66a627 --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/mixin/client/LivingEntityRendererMixin.java @@ -0,0 +1,18 @@ +package dev.hephaestus.glowcase.mixin.client; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import dev.hephaestus.glowcase.client.util.ModelPartUtil; +import net.minecraft.client.model.EntityModel; +import net.minecraft.client.renderer.entity.LivingEntityRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(LivingEntityRenderer.class) +public class LivingEntityRendererMixin { + @WrapOperation(method = "submit", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/EntityModel;setupAnim(Ljava/lang/Object;)V")) + private void applyPartPoses(EntityModel model, Object state, Operation original) { + original.call(model, state); + ModelPartUtil.applyPartPoses(model, state); + } +} diff --git a/src/main/java/dev/hephaestus/glowcase/mixin/client/ModelFeatureRendererMixin.java b/src/main/java/dev/hephaestus/glowcase/mixin/client/ModelFeatureRendererMixin.java new file mode 100644 index 00000000..d046507e --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/mixin/client/ModelFeatureRendererMixin.java @@ -0,0 +1,18 @@ +package dev.hephaestus.glowcase.mixin.client; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import dev.hephaestus.glowcase.client.util.ModelPartUtil; +import net.minecraft.client.model.Model; +import net.minecraft.client.renderer.feature.ModelFeatureRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(ModelFeatureRenderer.class) +public class ModelFeatureRendererMixin { + @WrapOperation(method = "prepareModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/Model;setupAnim(Ljava/lang/Object;)V")) + private void applyPartPoses(Model model, Object state, Operation original) { + original.call(model, state); + ModelPartUtil.applyPartPoses(model, state); + } +} diff --git a/src/main/java/dev/hephaestus/glowcase/mixinsupport/PartPosedRenderState.java b/src/main/java/dev/hephaestus/glowcase/mixinsupport/PartPosedRenderState.java new file mode 100644 index 00000000..4e6585b4 --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/mixinsupport/PartPosedRenderState.java @@ -0,0 +1,10 @@ +package dev.hephaestus.glowcase.mixinsupport; + +import dev.hephaestus.glowcase.util.PartPoseInfo; +import org.jspecify.annotations.Nullable; +import java.util.Map; + +public interface PartPosedRenderState { + void glowcase$setPartPoses(@Nullable Map partPoses); + @Nullable Map glowcase$getPartPoses(); +} diff --git a/src/main/java/dev/hephaestus/glowcase/packet/C2SEditEntityDisplayBlock.java b/src/main/java/dev/hephaestus/glowcase/packet/C2SEditEntityDisplayBlock.java index bce1d00d..750727a7 100644 --- a/src/main/java/dev/hephaestus/glowcase/packet/C2SEditEntityDisplayBlock.java +++ b/src/main/java/dev/hephaestus/glowcase/packet/C2SEditEntityDisplayBlock.java @@ -2,24 +2,38 @@ import dev.hephaestus.glowcase.Glowcase; import dev.hephaestus.glowcase.block.entity.DisplayBlockEntity; +import dev.hephaestus.glowcase.block.entity.EntityDisplayBlockEntity; import dev.hephaestus.glowcase.util.DisplayBlockSettings; +import dev.hephaestus.glowcase.util.PartPoseInfo; +import io.netty.buffer.ByteBuf; import net.minecraft.core.BlockPos; import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.entity.BlockEntity; -public record C2SEditEntityDisplayBlock(BlockPos pos, DisplayBlockSettings settings) implements C2SEditBlockEntity, C2SEditDisplayBlock { +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public record C2SEditEntityDisplayBlock(BlockPos pos, DisplayBlockSettings settings, Optional> partPoses) implements C2SEditBlockEntity, C2SEditDisplayBlock { public static final Type ID = new Type<>(Glowcase.id("channel.entity_display")); + + private static final StreamCodec> PART_POSES_CODEC = + ByteBufCodecs.map(HashMap::new, ByteBufCodecs.stringUtf8(64), PartPoseInfo.PACKET_CODEC, 256); + public static final StreamCodec PACKET_CODEC = StreamCodec.composite( BlockPos.STREAM_CODEC, C2SEditEntityDisplayBlock::pos, DisplayBlockSettings.PACKET_CODEC, C2SEditEntityDisplayBlock::settings, + ByteBufCodecs.optional(PART_POSES_CODEC), C2SEditEntityDisplayBlock::partPoses, C2SEditEntityDisplayBlock::new ); public static C2SEditEntityDisplayBlock of(DisplayBlockEntity be) { - return new C2SEditEntityDisplayBlock(be.getBlockPos(), be.toSettings()); + EntityDisplayBlockEntity entityDisplayBlock = (EntityDisplayBlockEntity)be; + return new C2SEditEntityDisplayBlock(be.getBlockPos(), be.toSettings(), Optional.of(entityDisplayBlock.getPartPoses())); } @Override @@ -29,6 +43,9 @@ public Type type() { @Override public void receive(ServerLevel world, BlockEntity blockEntity) { + if(blockEntity instanceof EntityDisplayBlockEntity entityDisplayBlock) { + partPoses().ifPresent(entityDisplayBlock::loadPartPoses); + } C2SEditDisplayBlock.super.receive(world, blockEntity); } } diff --git a/src/main/java/dev/hephaestus/glowcase/util/PartPoseInfo.java b/src/main/java/dev/hephaestus/glowcase/util/PartPoseInfo.java new file mode 100644 index 00000000..ad461ad5 --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/util/PartPoseInfo.java @@ -0,0 +1,90 @@ +package dev.hephaestus.glowcase.util; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.util.ExtraCodecs; +import org.joml.Vector3f; +import org.joml.Vector3fc; + +public record PartPoseInfo(String name, Vector3fc offset, Vector3fc rotation) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.STRING.fieldOf("name").forGetter(PartPoseInfo::name), + ExtraCodecs.VECTOR3F.lenientOptionalFieldOf("offset", new Vector3f()).forGetter(PartPoseInfo::offset), + ExtraCodecs.VECTOR3F.lenientOptionalFieldOf("rotation", new Vector3f()).forGetter(PartPoseInfo::rotation) + ).apply(instance, PartPoseInfo::new)); + + public static final StreamCodec PACKET_CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8, PartPoseInfo::name, + ByteBufCodecs.VECTOR3F, PartPoseInfo::offset, + ByteBufCodecs.VECTOR3F, PartPoseInfo::rotation, + PartPoseInfo::new + ); + + public float getRotationAtAxis(int axis){ + if(axis == 0) { + return rotation().x(); + } + else if (axis == 1) { + return rotation().y(); + } + else { + return rotation().z(); + } + } + + public float getOffsetAtAxis(int axis){ + if(axis == 0) { + return offset().x(); + } + else if (axis == 1) { + return offset().y(); + } + else { + return offset().z(); + } + } + + public PartPoseInfo updateRotationAtAxis(int axis, float newRotation) { + Vector3f newRotationVec; + if(axis == 0) { + newRotationVec = new Vector3f(newRotation, rotation.y(), rotation.z()); + return new PartPoseInfo(name(), offset(), newRotationVec); + } + else if (axis == 1) { + newRotationVec = new Vector3f(rotation.x(), newRotation, rotation.z()); + return new PartPoseInfo(name(), offset(), newRotationVec); + } + else { + newRotationVec = new Vector3f(rotation.x(), rotation().y(), newRotation); + return new PartPoseInfo(name(), offset(), newRotationVec); + } + } + + public PartPoseInfo updateOffsetAtAxis(int axis, float newOffset) { + Vector3f newOffsetVec; + if(axis == 0) { + newOffsetVec = new Vector3f(newOffset, offset().y(), offset().z()); + return new PartPoseInfo(name(), newOffsetVec, rotation()); + } + else if (axis == 1) { + newOffsetVec = new Vector3f(offset().x(), newOffset, offset().z()); + return new PartPoseInfo(name(), newOffsetVec, rotation()); + } + else { + newOffsetVec = new Vector3f(offset().x(), offset().y(), newOffset); + return new PartPoseInfo(name(), newOffsetVec, rotation()); + } + } + + public PartPoseInfo updateRotation(Vector3f newRotation) { + return new PartPoseInfo(name(), offset(), newRotation); + } + + public PartPoseInfo updateOffset(Vector3f newOffset) { + return new PartPoseInfo(name(), newOffset, rotation()); + } + +} diff --git a/src/main/resources/assets/glowcase/lang/en_us.json b/src/main/resources/assets/glowcase/lang/en_us.json index 6b0a901c..cb042823 100644 --- a/src/main/resources/assets/glowcase/lang/en_us.json +++ b/src/main/resources/assets/glowcase/lang/en_us.json @@ -198,5 +198,7 @@ "gui.glowcase.underline": "Underline (Ctrl+U)", "gui.glowcase.obfuscate": "Obfuscate (Ctrl+O)", "gui.glowcase.color_text": "Color Text (Via QuickText Tags)", - "gui.glowcase.insert_font": "Insert Font (Via QuickText Tags)" + "gui.glowcase.insert_font": "Insert Font (Via QuickText Tags)", + "gui.glowcase.model_parts": "Model Parts", + "gui.glowcase.rotation": "Rotation" } diff --git a/src/main/resources/glowcase.classtweaker b/src/main/resources/glowcase.classtweaker index 579e1447..fa8d2f40 100644 --- a/src/main/resources/glowcase.classtweaker +++ b/src/main/resources/glowcase.classtweaker @@ -40,3 +40,5 @@ accessible class net/minecraft/client/gui/components/MultilineTextField$StringVi accessible field net/minecraft/client/gui/components/EditBox displayPos I accessible field net/minecraft/client/gui/components/EditBox highlightPos I + +accessible field net/minecraft/client/model/geom/ModelPart children Ljava/util/Map; diff --git a/src/main/resources/glowcase.mixins.json b/src/main/resources/glowcase.mixins.json index e7cec271..00443509 100644 --- a/src/main/resources/glowcase.mixins.json +++ b/src/main/resources/glowcase.mixins.json @@ -13,12 +13,15 @@ ], "client": [ "AbstractContainerScreenInvoker", + "client.EntityRenderStateMixin", "client.FirstPersonHandsAndItemsRendererMixin", "client.FontAccessor", "client.GameRendererAccessor", "client.GuiRendererMixin", "client.HudMixin", "client.KeyboardHandlerMixin", + "client.LivingEntityRendererMixin", + "client.ModelFeatureRendererMixin", "client.TextureManagerAccessor", "client.bakedbe.BlockEntityRenderDispatcherMixin", "client.bakedbe.BlockEntityRenderStateMixin",