Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -19,13 +21,18 @@
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<EntityType<?>> TICK = TagKey.create(Registries.ENTITY_TYPE, Glowcase.id("tick_in_display"));

protected int entityId;
protected @Nullable Entity displayEntity = null;
protected @Nullable EntityType<?> entityType = null;
protected Map<String, PartPoseInfo> partPoses = new HashMap<>();

public EntityDisplayBlockEntity(BlockPos pos, BlockState state) {
super(Glowcase.ENTITY_DISPLAY_BLOCK_ENTITY.get(), pos, state);
Expand Down Expand Up @@ -53,6 +60,7 @@ public void setFromStack(ItemStack stack) {

@Override
public void unsetFromStack() {
clearPartPoses();
setDisplayEntity(null);
}

Expand Down Expand Up @@ -82,12 +90,39 @@ public static void tick(Level world, BlockPos blockPos, BlockState state, Entity
}
}

public Map<String,PartPoseInfo> getPartPoses(){
return partPoses;
}

public void loadPartPoses(Map<String, PartPoseInfo> newPartPoses) {
this.partPoses.putAll(newPartPoses);
}

public void clearPartPoses(){
this.partPoses.clear();
}

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
Expand All @@ -97,6 +132,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<Map<String, PartPoseInfo>> optionalStringPartPoseInfoMap = view.read("partPoses", Codec.unboundedMap(Codec.STRING, PartPoseInfo.CODEC));
optionalStringPartPoseInfoMap.ifPresent(stringPartPoseInfoMap -> this.partPoses.putAll(stringPartPoseInfoMap));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,106 @@
package dev.hephaestus.glowcase.client.gui.screen.ingame;

import dev.hephaestus.glowcase.block.entity.DisplayBlockEntity;
import dev.hephaestus.glowcase.block.entity.EntityDisplayBlockEntity;
import dev.hephaestus.glowcase.client.gui.widget.ingame.number.Vec3FieldsWidget;
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.Button;
import net.minecraft.client.gui.components.StringWidget;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class EntityDisplayEditScreen extends DisplayBlockEditScreen {

private final EntityDisplayBlockEntity entityDisplay;
private final Set<String> expandedParts = new HashSet<>();
private boolean expanded = false;
private final int STARTING_X = 10;
private final int STARTING_Y = 200;

public EntityDisplayEditScreen(DisplayBlockEntity displayBlock) {
super(displayBlock);
this.entityDisplay = (EntityDisplayBlockEntity) displayBlock;
}

@Override
public void init() {
super.init();

int bottom = STARTING_Y + 22;
Entity displayEntity = this.entityDisplay.getDisplayEntity();
if(displayEntity != null) {
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());

ModelPart root = ModelPartUtil.getModelRoot(displayEntity);
if (this.expanded && root != null) {
bottom = this.addPartRows("root", root, bottom, 0);
}
}

this.setMaxScroll(0, bottom + 10 - this.height, false);
}

private int addPartRows(String path, ModelPart parent, int y, int depth) {
int indent = Math.min(depth, 6) * 10;
for (Map.Entry<String, ModelPart> entry : parent.children.entrySet()) {
String name = entry.getKey();
String partPath = path + "." + name;
boolean partExpanded = this.expandedParts.contains(partPath);
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(partPath)) this.expandedParts.add(partPath);
this.rebuildWidgets();
}).bounds(STARTING_X + 10 + indent, y, 190 - indent, 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));

PartPoseInfo partPoseInfo = this.entityDisplay.getPartPostInfo(partPath);
this.addRenderableWidget(Vec3FieldsWidget.builder(this.font, new Vec3(isRotation ? partPoseInfo.rotation() : partPoseInfo.offset()))
.setPos(STARTING_X + 70, y).
setWidth(246).setHeight(18)
.setRotation(isRotation)
.setOnValueChange(vec3 -> {
PartPoseInfo current = this.entityDisplay.getPartPostInfo(partPath);
this.entityDisplay.setPartPoseInfo(
partPath,
isRotation ? current.withRotation(vec3.toVector3f()) : current.withOffset(vec3.toVector3f())
);
editDisplayBlock();
}).build());
y += 22;
}
y = this.addPartRows(partPath, entry.getValue(), y, depth + 1);
}
return y;

}

@Override
public CustomPacketPayload getUpdatePayload() {
return C2SEditEntityDisplayBlock.of(blockEntity);
}

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
package dev.hephaestus.glowcase.client.gui.screen.ingame;

import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth;
import net.minecraft.util.Util;

public abstract class GlowcaseScreen extends Screen {
// Scroll handler
private long lastScroll = Util.getMillis();
private int scrollOffsetX = 0;
private int scrollOffsetY = 0;
private int maxScrollX = 0;
private int maxScrollY = 0;

protected GlowcaseScreen() {
super(Component.empty());
}
Expand All @@ -23,4 +34,76 @@ public void extractBackground(GuiGraphicsExtractor graphics, int mouseX, int mou
public boolean isPauseScreen() {
return false;
}


/**
* Sets the max scroll of the UI, adjusting every element when necessary.
*
* @param maxScrollX The maximum the pane can be scrolled on X
* @param maxScrollY The maximum the pane can be scrolled on Y
* @param useDeltaOffset Whether to use the offset to adjust elements.
* If this is called from init, pass {@code false}.
*/
protected final void setMaxScroll(int maxScrollX, int maxScrollY, boolean useDeltaOffset) {
this.maxScrollX = Math.max(maxScrollX, 0);
this.maxScrollY = Math.max(maxScrollY, 0);

final int newOffsetX = Mth.clamp(this.scrollOffsetX, 0, this.maxScrollX);
final int newOffsetY = Mth.clamp(this.scrollOffsetY, 0, this.maxScrollY);

if (useDeltaOffset) {
this.offsetWidgets(
newOffsetX - this.scrollOffsetX,
newOffsetY - this.scrollOffsetY
);
} else {
this.offsetWidgets(
-newOffsetX,
-newOffsetY
);
}

this.scrollOffsetX = newOffsetX;
this.scrollOffsetY = newOffsetY;
}

@Override
public boolean mouseScrolled(double x, double y, double scrollX, double scrollY) {
return (!this.scrollPaneFirst() && super.mouseScrolled(x, y, scrollX, scrollY)) || scrollElements(
scrollX,
scrollY
);
}

private boolean scrollPaneFirst() {
return Util.getMillis() - this.lastScroll < 500L;
}

protected boolean scrollElements(double scrollX, double scrollY) {
int newOffsetX = Mth.clamp(this.scrollOffsetX - (int) (scrollX * 20), 0, this.maxScrollX);
int newOffsetY = Mth.clamp(this.scrollOffsetY - (int) (scrollY * 20), 0, this.maxScrollY);
if (newOffsetX != this.scrollOffsetX || newOffsetY != this.scrollOffsetY) {
// TODO: consider using absolutes rather than relative?
this.offsetWidgets(this.scrollOffsetX - newOffsetX, this.scrollOffsetY - newOffsetY);
this.scrollOffsetX = newOffsetX;
this.scrollOffsetY = newOffsetY;
this.lastScroll = Util.getMillis();
return true;
}
return false;
}

/**
* Adjusts every element to be scrolled. By default, this adjusts every abstract widget.
*/
protected void offsetWidgets(int deltaX, int deltaY) {
for (GuiEventListener child : this.children()) {
if (child instanceof AbstractWidget widget) {
widget.setPosition(
widget.getX() + deltaX,
widget.getY() + deltaY
);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<EntityDisplayBlockEntity, EntityDisplayBlockEntityRenderer.EntityDisplayRenderState> {
public static Identifier ITEM_TEXTURE = Glowcase.id("textures/item/entity_display_block.png");

Expand All @@ -30,6 +34,8 @@ public static class EntityDisplayRenderState extends BlockEntityRenderState {
public Vector3f scale;

public Entity entity;

public Map<String, PartPoseInfo> partPoseInfoMap;
}

@Override
Expand All @@ -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
Expand All @@ -62,6 +69,7 @@ public void submit(EntityDisplayRenderState state, PoseStack poseStack, SubmitNo
//noinspection unchecked
EntityRenderer<Entity, EntityRenderState> entityRenderer = (EntityRenderer<Entity, EntityRenderState>) 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);
}

Expand Down
Loading
Loading