Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ public void teleport(Location location) {
Location l = new Location(location.getX(), y, location.getZ(), location.getYaw(), location.getPitch());
line.teleport(l, false);
}

}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,21 @@ public void hide() {
spawned = false;
}

private static final float LINE_SPACING = 0.28f;

@Override
public void teleport(Location location) {
this.location = location;
if (lines.isEmpty()) return;

if (parent != null) {
return;
}

WrapperEntity first = lines.get(0);
first.teleport(location);
for (WrapperEntity e : lines) {
if (e.getUuid().equals(first.getUuid())) continue;
first.addPassenger(e);

for (int i = 0; i < lines.size(); i++) {
double y = location.getY() + (lines.size() - 1 - i) * LINE_SPACING;
Location lineLoc = new Location(location.getX(), y, location.getZ(), location.getYaw(), location.getPitch());
lines.get(i).teleport(lineLoc);
}
}

Expand Down
59 changes: 36 additions & 23 deletions spaceNPC/src/main/java/me/tofaa/entitylib/npc/NPC.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import me.tofaa.entitylib.wrapper.WrapperPlayer;
import me.tofaa.entitylib.wrapper.hologram.Hologram;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
Expand Down Expand Up @@ -319,9 +320,32 @@ public void remove() {
NPCRegistry.unregister(this);
}

/**
* Splits a Component into multiple lines on {@code <br>} tags.
* MiniMessage keeps {@code <br>} as a tag token through serialize/deserialize round-trips,
* so we serialize back to a MiniMessage string and split on the literal {@code <br>} token
* before re-deserializing each fragment into its own Component. This allows multi-line NPC
* name tags to be rendered as separate hologram line entities, since armor stands and text
* displays do not visually break a single component on newlines.
*/
private List<Component> splitDisplayNameLines(Component component) {
MiniMessage mm = MiniMessage.miniMessage();
String serialized = mm.serialize(component);
// MiniMessage round-trips <br> as the literal tag string "<br>"
String[] parts = serialized.split("(?i)<br>", -1);
if (parts.length == 1) {
return Collections.singletonList(component);
}
List<Component> lines = new ArrayList<>(parts.length);
for (String part : parts) {
lines.add(mm.deserialize(part));
}
return lines;
}
Comment on lines +331 to +344

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

MiniMessage <br> newline tag deserialize serialize round trip literal

💡 Result:

In MiniMessage, the newline tag, which can be represented as or
, is used to insert a newline into formatted text [1][2]. Regarding serialization and deserialization round-tripping: 1. Deserialization: When you deserialize a MiniMessage string containing or
, the MiniMessage parser interprets these tags as instructions to include a newline character or structure within the resulting component hierarchy [3][1]. 2. Serialization: When serializing components back into a MiniMessage string, the behavior of how newlines are represented depends on your configuration. While MiniMessage supports these tags, serializing a component tree that contains literal newline characters (\n) or specialized newline components back into a MiniMessage string does not always automatically "round-trip" to the exact original tag (
or ). Often, literal newline characters in a component may be preserved as raw characters in the serialized string, or handled according to the specific serialization logic active in your environment [4][5]. 3. Literal Handling: MiniMessage does not treat literal newline characters (\n) in the input string as special tags; it generally parses them as part of the text content [6][7]. The
and tags are explicit additions provided by the library to allow for cleaner management of multiline text within the MiniMessage format [1][6]. If you are experiencing issues with round-tripping, ensure that your MiniMessage instance is configured correctly and consider whether the component tree itself contains the expected structure (e.g., text components containing \n vs. components relying on newline-specific logic) before serialization [8][7]. There is no automatic, guaranteed 1:1 conversion back to the original
tag for all component types, as the serializer interprets components based on their structure [4][8].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the file and the helper's call sites.
rg -n --hidden --glob '!**/target/**' --glob '!**/build/**' \
  'splitDisplayNameLines|createHologram|updateHologram' \
  spaceNPC/src/main/java/me/tofaa/entitylib/npc/NPC.java \
  spaceNPC/src/main/java || true

# Show the surrounding code for the helper and its callers.
python3 - <<'PY'
from pathlib import Path
p = Path("spaceNPC/src/main/java/me/tofaa/entitylib/npc/NPC.java")
if p.exists():
    lines = p.read_text().splitlines()
    for start in [300, 330, 360]:
        end = min(len(lines), start + 70)
        print(f"\n--- {p}:{start+1}-{end} ---")
        for i in range(start, end):
            print(f"{i+1:4d}: {lines[i]}")
PY

Repository: Tofaa2/EntityLib

Length of output: 11084


Split on actual newlines instead of re-serializing MiniMessage

MiniMessage.serialize(component) does not preserve <br> as a stable delimiter, so multiline names can collapse into one hologram line and literal <br> text can be mangled. Update this helper (and its doc comment) to split on real \n or accept pre-split lines.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spaceNPC/src/main/java/me/tofaa/entitylib/npc/NPC.java` around lines 331 -
344, The splitDisplayNameLines helper in NPC should stop using
MiniMessage.serialize(component) and splitting on the literal "<br>" tag, since
that is not a stable multiline delimiter. Update splitDisplayNameLines to split
on real newline characters (or accept already split lines) and then deserialize
each line with MiniMessage.miniMessage(), preserving multiline display names
without mangling literal "<br>" text. Also adjust the related doc comment to
describe newline-based splitting so the behavior is clear.


private void createHologram() {
Location loc = getPosition();
double yOffset = options.isSitting() ? 2.76 : 2.26;
double yOffset = options.isSitting() ? 1.1 : 1.0;
Location hologramLoc = new Location(
loc.getX(),
loc.getY() + yOffset,
Expand All @@ -330,24 +354,15 @@ private void createHologram() {
loc.getPitch()
);

int protocolVersion = EntityLib.getApi()
.getPacketEvents()
.getServerManager()
.getVersion()
.getProtocolVersion();
Hologram.Legacy hologram = Hologram.legacy(hologramLoc);
hologram.setLineOffset(-0.28f);

Hologram hologram;
if (protocolVersion >= 760) {
hologram = Hologram.modern(hologramLoc);
} else {
hologram = Hologram.legacy(hologramLoc);
Component displayName = options.getDisplayName() != null
? options.getDisplayName()
: Component.text(name);
for (Component line : splitDisplayNameLines(displayName)) {
hologram.addLine(line);
}

hologram.addLine(
options.getDisplayName() != null
? options.getDisplayName()
: Component.text(name)
);
hologram.show();
// if (hologram.getEntity().getEntityMeta() instanceof AbstractDisplayMeta displayMeta) {
//// displayMeta.setTranslation(new Vector3f(0, 0.5f, 0));
Expand All @@ -358,12 +373,10 @@ private void createHologram() {

private void updateHologram() {
if (hologram != null) {
hologram.setLine(
0,
options.getDisplayName() != null
? options.getDisplayName()
: Component.text(name)
);
Component displayName = options.getDisplayName() != null
? options.getDisplayName()
: Component.text(name);
hologram.setLines(splitDisplayNameLines(displayName));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@ private static void processViewerSync() {
Location npcLocation = entity.getLocation();

npc.getHologram().ifPresent(hologram -> {
hologram.setParent(npc.getEntity().get());
boolean isSittingNow = npc.getOptions().isSitting();
double yOff = isSittingNow ? 1.1 : 1.0;
Location npcLoc = npc.getEntity().get().getLocation();
hologram.teleport(new Location(npcLoc.getX(), npcLoc.getY() + yOff, npcLoc.getZ(), npcLoc.getYaw(), npcLoc.getPitch()));
});

boolean permanentlyVisible = npc.getOptions().isPermanentlyVisible();
Expand Down
Loading