diff --git a/common/src/main/java/com/mrbysco/armorposer/Reference.java b/common/src/main/java/com/mrbysco/armorposer/Reference.java index 0d4bfff..2d14a17 100644 --- a/common/src/main/java/com/mrbysco/armorposer/Reference.java +++ b/common/src/main/java/com/mrbysco/armorposer/Reference.java @@ -22,6 +22,7 @@ public class Reference { public static final ResourceLocation SYNC_PACKET_ID = ResourceLocation.fromNamespaceAndPath(Reference.MOD_ID, "sync_packet"); public static final ResourceLocation SWAP_PACKET_ID = ResourceLocation.fromNamespaceAndPath(Reference.MOD_ID, "swap_packet"); + public static final ResourceLocation RENAME_PACKET_ID = ResourceLocation.fromNamespaceAndPath(Reference.MOD_ID, "rename_packet"); public static final ResourceLocation SCREEN_PACKET_ID = ResourceLocation.fromNamespaceAndPath(Reference.MOD_ID, "screen_packet"); public static final Map defaultPoseMap = initializePoseMap(); diff --git a/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorStandScreen.java b/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorStandScreen.java index dbad791..9f1d8c6 100644 --- a/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorStandScreen.java +++ b/common/src/main/java/com/mrbysco/armorposer/client/gui/ArmorStandScreen.java @@ -4,6 +4,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.math.Axis; import com.mrbysco.armorposer.Reference; +import com.mrbysco.armorposer.client.gui.widgets.NameBox; import com.mrbysco.armorposer.client.gui.widgets.NumberFieldBox; import com.mrbysco.armorposer.client.gui.widgets.SizeField; import com.mrbysco.armorposer.client.gui.widgets.ToggleButton; @@ -33,8 +34,6 @@ import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.phys.Vec3; -import java.util.Locale; - public class ArmorStandScreen extends Screen { private static final WidgetSprites MIRROR_POSE_SPRITES = new WidgetSprites( ResourceLocation.fromNamespaceAndPath(Reference.MOD_ID, "widget/mirror_pose"), ResourceLocation.fromNamespaceAndPath(Reference.MOD_ID, "widget/mirror_pose_highlighted") @@ -67,6 +66,11 @@ public class ArmorStandScreen extends Screen { private final String[] sliderLabels = new String[]{"head", "body", "left_leg", "right_leg", "left_arm", "right_arm", "position"}; private final String version; + private NameBox nameField; + private String oldName; + private String changedName; + private Button renameButton; + private NumberFieldBox rotationTextField; private final ToggleButton[] toggleButtons = new ToggleButton[6]; protected final NumberFieldBox[] poseTextFields = new NumberFieldBox[3 * 7]; @@ -85,6 +89,7 @@ public class ArmorStandScreen extends Screen { public ArmorStandScreen(ArmorStand entityArmorStand) { super(Component.translatable("armorposer.gui.title")); this.entityArmorStand = entityArmorStand; + this.oldName = entityArmorStand.hasCustomName() ? entityArmorStand.getName().getString() : this.getTitle().getString(); this.armorStandData = new ArmorStandData(); CompoundTag tag = entityArmorStand.saveWithoutId(new CompoundTag()); @@ -106,6 +111,32 @@ public boolean isPauseScreen() { public void init() { super.init(); + this.nameField = new NameBox(this.font, this.width / 2 - this.font.width(this.oldName) / 2, 10, 100, 20, Component.translatable("armorposer.gui.label.name")); + this.nameField.setValue(this.oldName); + this.nameField.setTextColor(whiteColor); + this.nameField.setTextColorUneditable(whiteColor); + this.nameField.setBordered(false); + this.nameField.setMaxLength(50); + this.nameField.setTextShadow(true); + this.nameField.setFocused(false); + this.nameField.setResponder((text) -> { + this.changedName = text; + this.updateRenameButton(); + }); + this.addWidget(this.nameField); + this.addRenderableWidget(this.renameButton = Button.builder(Component.translatable("armorposer.gui.label.rename"), (button) -> { + if (this.hasLevels() && !this.oldName.equals(this.changedName)) { + this.entityArmorStand.setCustomName(Component.literal(this.changedName)); + Services.PLATFORM.renameArmorStand(this.entityArmorStand, this.changedName); + this.oldName = this.changedName; + this.updateRenameButton(); + } + }) + .bounds(this.width / 2, 24, 40, 20) + .tooltip(Tooltip.create(Component.translatable("armorposer.gui.tooltip.rename"))).build()); + this.renameButton.visible = false; + this.renameButton.active = false; + int offsetX = 110; int offsetY = 20; @@ -544,6 +575,13 @@ public void init() { }).bounds(0, 0, 16, 16).build()); } + @Override + public void resize(Minecraft minecraft, int width, int height) { + String s = this.nameField.getValue(); + this.init(minecraft, width, height); + this.nameField.setValue(s); + } + /** * Get the desired offset to get the armor stand in the correct position * @@ -559,14 +597,38 @@ private double getDesiredOffset(double posValue, double desiredValue) { return desiredValue - value; } + private boolean hasLevels() { + if (this.minecraft == null || this.minecraft.player == null) return false; + if (this.minecraft.player.getAbilities().instabuild) return true; + return this.minecraft.player.experienceLevel >= 1; + } + + private void updateRenameButton() { + if (!this.oldName.equals(this.changedName)) { + this.renameButton.visible = true; + if (this.minecraft != null && this.minecraft.player != null) { + if (!this.hasLevels()) { + this.renameButton.active = false; + this.renameButton.setTooltip(Tooltip.create(Component.translatable("armorposer.gui.tooltip.rename.disabled").withStyle(ChatFormatting.RED))); + } else { + this.renameButton.active = true; + } + } + } else { + this.renameButton.visible = false; + this.renameButton.active = false; + this.renameButton.setTooltip(Tooltip.create(Component.translatable("armorposer.gui.tooltip.rename"))); + } + } + @Override public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) { super.render(guiGraphics, mouseX, mouseY, partialTicks); - // Draw gui title - guiGraphics.drawString(this.font, this.title, this.width / 2 - this.font.width(this.title) / 2, 10, whiteColor, true); - // Draw textboxes + // Name + this.nameField.render(guiGraphics, mouseX, mouseY, partialTicks); + this.rotationTextField.render(guiGraphics, mouseX, mouseY, partialTicks); for (EditBox textField : this.poseTextFields) textField.render(guiGraphics, mouseX, mouseY, partialTicks); @@ -651,7 +713,7 @@ public boolean mouseScrolled(double mouseX, double mouseY, double xScroll, doubl return true; } if (sizeField.canConsumeInput()) { - float nextValue = (float)(sizeField.getFloat() + (double)(multiplier * sizeField.scrollMultiplier)); + float nextValue = (float) (sizeField.getFloat() + (double) (multiplier * sizeField.scrollMultiplier)); nextValue = Math.clamp(nextValue, sizeField.minValue, sizeField.maxValue); sizeField.setValue(String.valueOf(nextValue)); sizeField.setCursorPosition(0); @@ -680,7 +742,7 @@ public boolean mouseScrolled(double mouseX, double mouseY, double xScroll, doubl return true; } if (sizeField.canConsumeInput()) { - float previousValue = (float)(sizeField.getFloat() - (double)(multiplier * sizeField.scrollMultiplier)); + float previousValue = (float) (sizeField.getFloat() - (double) (multiplier * sizeField.scrollMultiplier)); previousValue = Math.clamp(previousValue, sizeField.minValue, sizeField.maxValue); sizeField.setValue(String.valueOf(previousValue)); sizeField.setCursorPosition(0); @@ -718,7 +780,10 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { } } } else { - if (this.rotationTextField.keyPressed(keyCode, scanCode, modifiers)) { + if (this.nameField.keyPressed(keyCode, scanCode, modifiers)) { + this.textFieldUpdated(); + return true; + } else if (this.rotationTextField.keyPressed(keyCode, scanCode, modifiers)) { this.textFieldUpdated(); return true; } else if (this.sizeField.keyPressed(keyCode, scanCode, modifiers)) { diff --git a/common/src/main/java/com/mrbysco/armorposer/client/gui/widgets/NameBox.java b/common/src/main/java/com/mrbysco/armorposer/client/gui/widgets/NameBox.java new file mode 100644 index 0000000..8e82241 --- /dev/null +++ b/common/src/main/java/com/mrbysco/armorposer/client/gui/widgets/NameBox.java @@ -0,0 +1,562 @@ +package com.mrbysco.armorposer.client.gui.widgets; + +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.WidgetSprites; +import net.minecraft.client.gui.narration.NarratedElementType; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.FormattedCharSequence; +import net.minecraft.util.Mth; +import net.minecraft.util.StringUtil; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Predicate; + +public class NameBox extends AbstractWidget { + private static final WidgetSprites SPRITES = new WidgetSprites( + ResourceLocation.withDefaultNamespace("widget/text_field"), + ResourceLocation.withDefaultNamespace("widget/text_field_highlighted") + ); + private final Font font; + private String value; + private int maxLength; + private boolean bordered; + private boolean canLoseFocus; + private boolean isEditable; + private int displayPos; + private int cursorPos; + private int highlightPos; + private int textColor; + private int textColorUneditable; + @Nullable + private Consumer responder; + private Predicate filter; + private BiFunction formatter; + @Nullable + private Component hint; + private long focusedTime; + private boolean textShadow; + + public NameBox(Font font, int width, int height, Component message) { + this(font, 0, 0, width, height, message); + } + + public NameBox(Font font, int x, int y, int width, int height, Component message) { + this(font, x, y, width, height, (NameBox)null, message); + } + + public NameBox(Font font, int x, int y, int width, int height, @Nullable NameBox editBox, Component message) { + super(x, y, width, height, message); + this.value = ""; + this.maxLength = 32; + this.bordered = true; + this.canLoseFocus = true; + this.isEditable = true; + this.textColor = 14737632; + this.textColorUneditable = 7368816; + this.filter = Objects::nonNull; + this.formatter = (forward, p_94148_) -> FormattedCharSequence.forward(forward, Style.EMPTY); + this.focusedTime = Util.getMillis(); + this.textShadow = true; + this.font = font; + if (editBox != null) { + this.setValue(editBox.getValue()); + } + this.scrollTo(0); + + } + + public void setResponder(Consumer responder) { + this.responder = responder; + } + + public void setFormatter(BiFunction textFormatter) { + this.formatter = textFormatter; + } + + protected MutableComponent createNarrationMessage() { + Component component = this.getMessage(); + return Component.translatable("gui.narrate.editBox", new Object[]{component, this.value}); + } + + public void setValue(String text) { + if (this.filter.test(text)) { + if (text.length() > this.maxLength) { + this.value = text.substring(0, this.maxLength); + } else { + this.value = text; + } + + this.moveCursorToEnd(false); + this.setHighlightPos(this.cursorPos); + this.onValueChange(text); + } + + } + + public String getValue() { + return this.value; + } + + public String getHighlighted() { + int i = Math.min(this.cursorPos, this.highlightPos); + int j = Math.max(this.cursorPos, this.highlightPos); + return this.value.substring(i, j); + } + + public void setFilter(Predicate validator) { + this.filter = validator; + } + + public void insertText(String textToWrite) { + int i = Math.min(this.cursorPos, this.highlightPos); + int j = Math.max(this.cursorPos, this.highlightPos); + int k = this.maxLength - this.value.length() - (i - j); + if (k > 0) { + String s = StringUtil.filterText(textToWrite); + int l = s.length(); + if (k < l) { + if (Character.isHighSurrogate(s.charAt(k - 1))) { + --k; + } + + s = s.substring(0, k); + l = k; + } + + String s1 = (new StringBuilder(this.value)).replace(i, j, s).toString(); + if (this.filter.test(s1)) { + this.value = s1; + this.setCursorPosition(i + l); + this.setHighlightPos(this.cursorPos); + this.onValueChange(this.value); + } + } + + } + + private void onValueChange(String newText) { + if (this.responder != null) { + this.responder.accept(newText); + } + } + + private void deleteText(int count) { + if (Screen.hasControlDown()) { + this.deleteWords(count); + } else { + this.deleteChars(count); + } + + } + + public void deleteWords(int num) { + if (!this.value.isEmpty()) { + if (this.highlightPos != this.cursorPos) { + this.insertText(""); + } else { + this.deleteCharsToPos(this.getWordPosition(num)); + } + } + + } + + public void deleteChars(int num) { + this.deleteCharsToPos(this.getCursorPos(num)); + } + + public void deleteCharsToPos(int num) { + if (!this.value.isEmpty()) { + if (this.highlightPos != this.cursorPos) { + this.insertText(""); + } else { + int i = Math.min(num, this.cursorPos); + int j = Math.max(num, this.cursorPos); + if (i != j) { + String s = (new StringBuilder(this.value)).delete(i, j).toString(); + if (this.filter.test(s)) { + this.value = s; + this.moveCursorTo(i, false); + } + } + } + } + + } + + public int getWordPosition(int numWords) { + return this.getWordPosition(numWords, this.getCursorPosition()); + } + + private int getWordPosition(int numWords, int pos) { + return this.getWordPosition(numWords, pos, true); + } + + private int getWordPosition(int numWords, int pos, boolean skipConsecutiveSpaces) { + int i = pos; + boolean flag = numWords < 0; + int j = Math.abs(numWords); + + for(int k = 0; k < j; ++k) { + if (!flag) { + int l = this.value.length(); + i = this.value.indexOf(32, i); + if (i == -1) { + i = l; + } else { + while(skipConsecutiveSpaces && i < l && this.value.charAt(i) == ' ') { + ++i; + } + } + } else { + while(skipConsecutiveSpaces && i > 0 && this.value.charAt(i - 1) == ' ') { + --i; + } + + while(i > 0 && this.value.charAt(i - 1) != ' ') { + --i; + } + } + } + + return i; + } + + public void moveCursor(int delta, boolean select) { + this.moveCursorTo(this.getCursorPos(delta), select); + } + + private int getCursorPos(int delta) { + return Util.offsetByCodepoints(this.value, this.cursorPos, delta); + } + + public void moveCursorTo(int delta, boolean select) { + this.setCursorPosition(delta); + if (!select) { + this.setHighlightPos(this.cursorPos); + } + + this.onValueChange(this.value); + } + + public void setCursorPosition(int pos) { + this.cursorPos = Mth.clamp(pos, 0, this.value.length()); + this.scrollTo(this.cursorPos); + } + + public void moveCursorToStart(boolean select) { + this.moveCursorTo(0, select); + } + + public void moveCursorToEnd(boolean select) { + this.moveCursorTo(this.value.length(), select); + } + + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (this.isActive() && this.isFocused()) { + switch (keyCode) { + case 259: + if (this.isEditable) { + this.deleteText(-1); + } + + return true; + case 260: + case 264: + case 265: + case 266: + case 267: + default: + if (Screen.isSelectAll(keyCode)) { + this.moveCursorToEnd(false); + this.setHighlightPos(0); + return true; + } else if (Screen.isCopy(keyCode)) { + Minecraft.getInstance().keyboardHandler.setClipboard(this.getHighlighted()); + return true; + } else if (Screen.isPaste(keyCode)) { + if (this.isEditable()) { + this.insertText(Minecraft.getInstance().keyboardHandler.getClipboard()); + } + + return true; + } else { + if (Screen.isCut(keyCode)) { + Minecraft.getInstance().keyboardHandler.setClipboard(this.getHighlighted()); + if (this.isEditable()) { + this.insertText(""); + } + + return true; + } + + return false; + } + case 261: + if (this.isEditable) { + this.deleteText(1); + } + + return true; + case 262: + if (Screen.hasControlDown()) { + this.moveCursorTo(this.getWordPosition(1), Screen.hasShiftDown()); + } else { + this.moveCursor(1, Screen.hasShiftDown()); + } + + return true; + case 263: + if (Screen.hasControlDown()) { + this.moveCursorTo(this.getWordPosition(-1), Screen.hasShiftDown()); + } else { + this.moveCursor(-1, Screen.hasShiftDown()); + } + + return true; + case 268: + this.moveCursorToStart(Screen.hasShiftDown()); + return true; + case 269: + this.moveCursorToEnd(Screen.hasShiftDown()); + return true; + } + } else { + return false; + } + } + + public boolean canConsumeInput() { + return this.isActive() && this.isFocused() && this.isEditable(); + } + + public boolean charTyped(char codePoint, int modifiers) { + if (!this.canConsumeInput()) { + return false; + } else if (StringUtil.isAllowedChatCharacter(codePoint)) { + if (this.isEditable) { + this.insertText(Character.toString(codePoint)); + } + + return true; + } else { + return false; + } + } + + @Override + public void onClick(double mouseX, double mouseY) { + int i = Mth.floor(mouseX) - this.getX(); + if (this.bordered) { + i -= 4; + } + + String s = this.font.plainSubstrByWidth(this.value.substring(this.displayPos), this.getInnerWidth()); + this.moveCursorTo(this.font.plainSubstrByWidth(s, i).length() + this.displayPos, Screen.hasShiftDown()); + } + + @Override + public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + if (this.isVisible()) { + if (this.isBordered()) { + ResourceLocation resourcelocation = SPRITES.get(this.isActive(), this.isFocused()); + guiGraphics.blitSprite(RenderType::guiTextured, resourcelocation, this.getX(), this.getY(), this.getWidth(), this.getHeight()); + } + + int usedColor = this.isEditable ? this.textColor : this.textColorUneditable; + int i = this.cursorPos - this.displayPos; + String s = this.font.plainSubstrByWidth(this.value.substring(this.displayPos), this.getInnerWidth()); + boolean flag = i >= 0 && i <= s.length(); + boolean flag1 = this.isFocused() && (Util.getMillis() - this.focusedTime) / 300L % 2L == 0L && flag; + int j = this.bordered ? this.getX() + 4 : this.getX(); + int k = this.bordered ? this.getY() + (this.height - 8) / 2 : this.getY(); + int l = j; + int i1 = Mth.clamp(this.highlightPos - this.displayPos, 0, s.length()); + if (!s.isEmpty()) { + String s1 = flag ? s.substring(0, i) : s; + l = guiGraphics.drawString(this.font, (FormattedCharSequence)this.formatter.apply(s1, this.displayPos), j, k, usedColor, this.textShadow); + } + + boolean flag2 = this.cursorPos < this.value.length() || this.value.length() >= this.getMaxLength(); + int j1 = l; + if (!flag) { + j1 = i > 0 ? j + this.width : j; + } else if (flag2) { + j1 = l - 1; + --l; + } + + if (!s.isEmpty() && flag && i < s.length()) { + guiGraphics.drawString(this.font, (FormattedCharSequence)this.formatter.apply(s.substring(i), this.cursorPos), l, k, usedColor, this.textShadow); + } + + if (this.hint != null && s.isEmpty() && !this.isFocused()) { + guiGraphics.drawString(this.font, this.hint, l, k, usedColor, this.textShadow); + } + + if (flag1) { + if (flag2) { + guiGraphics.fill(RenderType.guiOverlay(), j1, k - 1, j1 + 1, k + 1 + 9, -3092272); + } else { + guiGraphics.drawString(this.font, "_", j1, k, usedColor, this.textShadow); + } + } + + if (i1 != i) { + int k1 = j + this.font.width(s.substring(0, i1)); + this.renderHighlight(guiGraphics, j1, k - 1, k1 - 1, k + 1 + 9); + } + } + + } + + private void renderHighlight(GuiGraphics guiGraphics, int minX, int minY, int maxX, int maxY) { + if (minX < maxX) { + int i = minX; + minX = maxX; + maxX = i; + } + + if (minY < maxY) { + int j = minY; + minY = maxY; + maxY = j; + } + + if (maxX > this.getX() + this.width) { + maxX = this.getX() + this.width; + } + + if (minX > this.getX() + this.width) { + minX = this.getX() + this.width; + } + + guiGraphics.fill(RenderType.guiTextHighlight(), minX, minY, maxX, maxY, -16776961); + } + + public void setMaxLength(int length) { + this.maxLength = length; + if (this.value.length() > length) { + this.value = this.value.substring(0, length); + this.onValueChange(this.value); + } + + } + + private int getMaxLength() { + return this.maxLength; + } + + public int getCursorPosition() { + return this.cursorPos; + } + + public boolean isBordered() { + return this.bordered; + } + + public void setBordered(boolean enableBackgroundDrawing) { + this.bordered = enableBackgroundDrawing; + } + + public void setTextColor(int color) { + this.textColor = color; + } + + public void setTextColorUneditable(int color) { + this.textColorUneditable = color; + } + + @Override + public void setFocused(boolean focused) { + if (this.canLoseFocus || focused) { + super.setFocused(focused); + if (focused) { + this.focusedTime = Util.getMillis(); + } + } + + } + + private boolean isEditable() { + return this.isEditable; + } + + public void setEditable(boolean enabled) { + this.isEditable = enabled; + } + + public int getInnerWidth() { + return this.isBordered() ? this.width - 8 : this.width; + } + + public void setHighlightPos(int position) { + this.highlightPos = Mth.clamp(position, 0, this.value.length()); + this.scrollTo(this.highlightPos); + } + + private void scrollTo(int position) { + if (this.font != null) { + this.displayPos = Math.min(this.displayPos, this.value.length()); + int i = this.getInnerWidth(); + String s = this.font.plainSubstrByWidth(this.value.substring(this.displayPos), i); + int j = s.length() + this.displayPos; + if (position == this.displayPos) { + this.displayPos -= this.font.plainSubstrByWidth(this.value, i, true).length(); + } + + if (position > j) { + this.displayPos += position - j; + } else if (position <= this.displayPos) { + this.displayPos -= this.displayPos - position; + } + + this.displayPos = Mth.clamp(this.displayPos, 0, this.value.length()); + } + + } + + public void setCanLoseFocus(boolean canLoseFocus) { + this.canLoseFocus = canLoseFocus; + } + + public boolean isVisible() { + return this.visible; + } + + public void setVisible(boolean isVisible) { + this.visible = isVisible; + } + + public int getScreenX(int charNum) { + return charNum > this.value.length() ? this.getX() : this.getX() + this.font.width(this.value.substring(0, charNum)); + } + + public void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { + narrationElementOutput.add(NarratedElementType.TITLE, this.createNarrationMessage()); + } + + public void setHint(Component hint) { + this.hint = hint; + } + + public void setTextShadow(boolean textShadow) { + this.textShadow = textShadow; + } + + public boolean getTextShadow() { + return this.textShadow; + } +} diff --git a/common/src/main/java/com/mrbysco/armorposer/data/RenameData.java b/common/src/main/java/com/mrbysco/armorposer/data/RenameData.java new file mode 100644 index 0000000..36b5068 --- /dev/null +++ b/common/src/main/java/com/mrbysco/armorposer/data/RenameData.java @@ -0,0 +1,26 @@ +package com.mrbysco.armorposer.data; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.decoration.ArmorStand; +import net.minecraft.world.entity.player.Player; + +import java.util.UUID; + +public record RenameData(UUID entityUUID, String name) { + public void encode(FriendlyByteBuf buf) { + buf.writeUUID(entityUUID); + buf.writeUtf(name); + } + + public static RenameData decode(final FriendlyByteBuf packetBuffer) { + return new RenameData(packetBuffer.readUUID(), packetBuffer.readUtf()); + } + + public void handleData(ArmorStand armorStand, Player player) { + if (!name.isEmpty() && (player.experienceLevel >= 1 || player.getAbilities().instabuild)) { + player.giveExperienceLevels(-1); + armorStand.setCustomName(Component.literal(name)); + } + } +} diff --git a/common/src/main/java/com/mrbysco/armorposer/packets/ArmorStandRenamePayload.java b/common/src/main/java/com/mrbysco/armorposer/packets/ArmorStandRenamePayload.java new file mode 100644 index 0000000..c042155 --- /dev/null +++ b/common/src/main/java/com/mrbysco/armorposer/packets/ArmorStandRenamePayload.java @@ -0,0 +1,29 @@ +package com.mrbysco.armorposer.packets; + +import com.mrbysco.armorposer.Reference; +import com.mrbysco.armorposer.data.RenameData; +import com.mrbysco.armorposer.data.SyncData; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; + + +public record ArmorStandRenamePayload(RenameData data) implements CustomPacketPayload { + public static final StreamCodec CODEC = CustomPacketPayload.codec( + ArmorStandRenamePayload::write, + ArmorStandRenamePayload::new); + public static final Type ID = new Type<>(Reference.RENAME_PACKET_ID); + + public ArmorStandRenamePayload(final FriendlyByteBuf packetBuffer) { + this(RenameData.decode(packetBuffer)); + } + + public void write(FriendlyByteBuf buf) { + data.encode(buf); + } + + @Override + public Type type() { + return ID; + } +} diff --git a/common/src/main/java/com/mrbysco/armorposer/platform/services/IPlatformHelper.java b/common/src/main/java/com/mrbysco/armorposer/platform/services/IPlatformHelper.java index 87e4ac8..aa20c92 100644 --- a/common/src/main/java/com/mrbysco/armorposer/platform/services/IPlatformHelper.java +++ b/common/src/main/java/com/mrbysco/armorposer/platform/services/IPlatformHelper.java @@ -18,6 +18,11 @@ public interface IPlatformHelper { */ void swapSlots(ArmorStand armorStand, SwapData.Action action); + /** + * Update Armor Stand Name + */ + void renameArmorStand(ArmorStand armorStand, String newName); + /** * Allow scrolling to increase/decrease the angle of text fields */ diff --git a/common/src/main/resources/assets/armorposer/lang/en_us.json b/common/src/main/resources/assets/armorposer/lang/en_us.json index 0360b8a..0cf1d4e 100644 --- a/common/src/main/resources/assets/armorposer/lang/en_us.json +++ b/common/src/main/resources/assets/armorposer/lang/en_us.json @@ -37,6 +37,7 @@ "armorposer.gui.label.scroll": "Click and scroll v%s", "armorposer.gui.label.show_arms": "Show Arms", "armorposer.gui.label.small": "Small", + "armorposer.gui.label.rename": "Rename", "armorposer.gui.pose.arabesque": "Arabesque", "armorposer.gui.pose.attention": "Attention", "armorposer.gui.pose.block": "Block", @@ -100,11 +101,14 @@ "armorposer.gui.tooltip.y_rotation": "Adjust Y rotation", "armorposer.gui.tooltip.z_position": "Adjust Z position", "armorposer.gui.tooltip.z_rotation": "Adjust Z rotation", + "armorposer.gui.tooltip.rename": "Rename the Armor Stand (Costs 1 level)", + "armorposer.gui.tooltip.rename.disabled": "Unable to rename. Requires 1 level.", "armorposer.key.categories.general": "Armor Stand Configurator", "armorposer.key.openArmorStandGui": "Open GUI", "armorposer.networking.screen.failed": "Armor poser networking screen packet failed", "armorposer.networking.swap.failed": "Armor poser networking swap packet failed", "armorposer.networking.sync.failed": "Armor poser networking sync packet failed", + "armorposer.networking.rename.failed": "Armor poser networking rename packet failed", "text.autoconfig.armorposer.option.general": "General", "text.autoconfig.armorposer.option.general.allowScrolling": "Allow Scrolling", "text.autoconfig.armorposer.option.general.allowScrolling.@Tooltip": "Allow scrolling to add / decrease an angle value in the posing screen", diff --git a/fabric/src/main/java/com/mrbysco/armorposer/ArmorPoser.java b/fabric/src/main/java/com/mrbysco/armorposer/ArmorPoser.java index 60c5d6b..54b5e48 100644 --- a/fabric/src/main/java/com/mrbysco/armorposer/ArmorPoser.java +++ b/fabric/src/main/java/com/mrbysco/armorposer/ArmorPoser.java @@ -1,9 +1,11 @@ package com.mrbysco.armorposer; import com.mrbysco.armorposer.config.PoserConfig; +import com.mrbysco.armorposer.data.RenameData; import com.mrbysco.armorposer.data.SwapData; import com.mrbysco.armorposer.data.SyncData; import com.mrbysco.armorposer.handlers.EventHandler; +import com.mrbysco.armorposer.packets.ArmorStandRenamePayload; import com.mrbysco.armorposer.packets.ArmorStandScreenPayload; import com.mrbysco.armorposer.packets.ArmorStandSwapPayload; import com.mrbysco.armorposer.packets.ArmorStandSyncPayload; @@ -53,5 +55,17 @@ public void onInitialize() { } }); }); + PayloadTypeRegistry.playC2S().register(ArmorStandRenamePayload.ID, ArmorStandRenamePayload.CODEC); + ServerPlayNetworking.registerGlobalReceiver(ArmorStandRenamePayload.ID, (payload, context) -> { + final ServerLevel serverLevel = context.player().serverLevel(); + + RenameData renameData = payload.data(); + context.player().server.execute(() -> { + Entity entity = serverLevel.getEntity(renameData.entityUUID()); + if (entity instanceof ArmorStand armorStandEntity) { + renameData.handleData(armorStandEntity, context.player()); + } + }); + }); } } diff --git a/fabric/src/main/java/com/mrbysco/armorposer/platform/FabricPlatformHelper.java b/fabric/src/main/java/com/mrbysco/armorposer/platform/FabricPlatformHelper.java index 8122362..fb1e642 100644 --- a/fabric/src/main/java/com/mrbysco/armorposer/platform/FabricPlatformHelper.java +++ b/fabric/src/main/java/com/mrbysco/armorposer/platform/FabricPlatformHelper.java @@ -2,8 +2,10 @@ import com.mrbysco.armorposer.Reference; import com.mrbysco.armorposer.config.PoserConfig; +import com.mrbysco.armorposer.data.RenameData; import com.mrbysco.armorposer.data.SwapData; import com.mrbysco.armorposer.data.SyncData; +import com.mrbysco.armorposer.packets.ArmorStandRenamePayload; import com.mrbysco.armorposer.packets.ArmorStandSwapPayload; import com.mrbysco.armorposer.packets.ArmorStandSyncPayload; import com.mrbysco.armorposer.platform.services.IPlatformHelper; @@ -33,6 +35,12 @@ public void swapSlots(ArmorStand armorStand, SwapData.Action action) { ClientPlayNetworking.send(new ArmorStandSwapPayload(data)); } + @Override + public void renameArmorStand(ArmorStand armorStand, String newName) { + RenameData data = new RenameData(armorStand.getUUID(), newName); + ClientPlayNetworking.send(new ArmorStandRenamePayload(data)); + } + @Override public boolean allowScrolling() { PoserConfig config = AutoConfig.getConfigHolder(PoserConfig.class).getConfig(); diff --git a/forge/src/main/java/com/mrbysco/armorposer/ArmorPoser.java b/forge/src/main/java/com/mrbysco/armorposer/ArmorPoser.java index 6288196..b26ccf0 100644 --- a/forge/src/main/java/com/mrbysco/armorposer/ArmorPoser.java +++ b/forge/src/main/java/com/mrbysco/armorposer/ArmorPoser.java @@ -1,6 +1,7 @@ package com.mrbysco.armorposer; import com.mrbysco.armorposer.config.PoserConfig; +import com.mrbysco.armorposer.packets.ArmorStandRenamePayload; import com.mrbysco.armorposer.packets.ArmorStandScreenPayload; import com.mrbysco.armorposer.packets.ArmorStandSwapPayload; import com.mrbysco.armorposer.packets.ArmorStandSyncPayload; @@ -35,5 +36,6 @@ private void setupPackets(final RegisterPayloadHandlersEvent event) { registrar.playToClient(ArmorStandScreenPayload.ID, ArmorStandScreenPayload.CODEC, ClientPayloadHandler.getInstance()::handleScreenData); registrar.playToServer(ArmorStandSwapPayload.ID, ArmorStandSwapPayload.CODEC, ServerPayloadHandler.getInstance()::handleSwapData); registrar.playToServer(ArmorStandSyncPayload.ID, ArmorStandSyncPayload.CODEC, ServerPayloadHandler.getInstance()::handleSyncData); + registrar.playToServer(ArmorStandRenamePayload.ID, ArmorStandRenamePayload.CODEC, ServerPayloadHandler.getInstance()::handleRenameData); } } \ No newline at end of file diff --git a/forge/src/main/java/com/mrbysco/armorposer/packets/handler/ServerPayloadHandler.java b/forge/src/main/java/com/mrbysco/armorposer/packets/handler/ServerPayloadHandler.java index 10967a2..730c640 100644 --- a/forge/src/main/java/com/mrbysco/armorposer/packets/handler/ServerPayloadHandler.java +++ b/forge/src/main/java/com/mrbysco/armorposer/packets/handler/ServerPayloadHandler.java @@ -1,5 +1,6 @@ package com.mrbysco.armorposer.packets.handler; +import com.mrbysco.armorposer.packets.ArmorStandRenamePayload; import com.mrbysco.armorposer.packets.ArmorStandSwapPayload; import com.mrbysco.armorposer.packets.ArmorStandSyncPayload; import net.minecraft.network.chat.Component; @@ -48,4 +49,21 @@ public void handleSyncData(final ArmorStandSyncPayload syncData, final IPayloadC return null; }); } + + public void handleRenameData(final ArmorStandRenamePayload renameData, final IPayloadContext context) { + // Do something with the pose, on the main thread + context.enqueueWork(() -> { + if (context.player() != null && context.player().level() instanceof ServerLevel serverLevel) { + Entity entity = serverLevel.getEntity(renameData.data().entityUUID()); + if (entity instanceof ArmorStand armorStandEntity) { + renameData.data().handleData(armorStandEntity, context.player()); + } + } + }) + .exceptionally(e -> { + // Handle exception + context.disconnect(Component.translatable("armorposer.networking.rename.failed", e.getMessage())); + return null; + }); + } } diff --git a/forge/src/main/java/com/mrbysco/armorposer/platform/NeoForgePlatformHelper.java b/forge/src/main/java/com/mrbysco/armorposer/platform/NeoForgePlatformHelper.java index 0aab0a7..2863aa2 100644 --- a/forge/src/main/java/com/mrbysco/armorposer/platform/NeoForgePlatformHelper.java +++ b/forge/src/main/java/com/mrbysco/armorposer/platform/NeoForgePlatformHelper.java @@ -2,8 +2,10 @@ import com.mrbysco.armorposer.Reference; import com.mrbysco.armorposer.config.PoserConfig; +import com.mrbysco.armorposer.data.RenameData; import com.mrbysco.armorposer.data.SwapData; import com.mrbysco.armorposer.data.SyncData; +import com.mrbysco.armorposer.packets.ArmorStandRenamePayload; import com.mrbysco.armorposer.packets.ArmorStandSwapPayload; import com.mrbysco.armorposer.packets.ArmorStandSyncPayload; import com.mrbysco.armorposer.platform.services.IPlatformHelper; @@ -31,6 +33,11 @@ public void swapSlots(ArmorStand armorStand, SwapData.Action action) { PacketDistributor.sendToServer(new ArmorStandSwapPayload(new SwapData(armorStand.getUUID(), action))); } + @Override + public void renameArmorStand(ArmorStand armorStand, String newName) { + PacketDistributor.sendToServer(new ArmorStandRenamePayload(new RenameData(armorStand.getUUID(), newName))); + } + @Override public boolean allowScrolling() { return PoserConfig.COMMON.allowScrolling.get();