Skip to content

Commit 5e21f37

Browse files
committed
Update ShowLvlHeadSystem.java
1 parent 1a7f140 commit 5e21f37

1 file changed

Lines changed: 167 additions & 80 deletions

File tree

src/main/java/com/azuredoom/levelingcore/systems/nameplate/ShowLvlHeadSystem.java

Lines changed: 167 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818

1919
import java.util.Arrays;
2020
import java.util.Objects;
21-
import java.util.concurrent.atomic.AtomicReference;
22-
import java.util.regex.Pattern;
2321

2422
import com.azuredoom.levelingcore.LevelingCore;
2523
import com.azuredoom.levelingcore.api.LevelingCoreApi;
@@ -32,6 +30,18 @@ public class ShowLvlHeadSystem implements Runnable {
3230

3331
private final Config<GUIConfig> config;
3432

33+
/**
34+
* A marker string used to identify and differentiate nameplate components in the system.
35+
* This marker is composed of zero-width Unicode characters (\u200B, \u200C, \u200D)
36+
* that are invisible in rendered output but can be used for internal processing
37+
* or as separators in nameplate configuration and formatting.
38+
* <p>
39+
* This constantly enables the system to embed or parse hidden metadata within
40+
* strings associated with nameplates, ensuring that visual elements remain
41+
* unaffected while still allowing for structured data handling.
42+
*/
43+
private static final String NAMEPLATE_MARKER = "\u200B\u200C\u200D";
44+
3545
public ShowLvlHeadSystem(Config<GUIConfig> config) {
3646
this.config = config;
3747
}
@@ -48,172 +58,249 @@ public void run() {
4858
}
4959
}
5060

61+
/**
62+
* Processes all entities within the world to update or insert nameplates for players and NPCs. The method iterates
63+
* through entity chunks, extracts relevant data, computes levels, and displays updated information based on the
64+
* game configuration and locale settings. Blacklisted entities are ignored. This method also accounts for optional
65+
* plugins and localization.
66+
*
67+
* @param world the world object containing entity data and stores; must not be null.
68+
*/
5169
private void tickWorld(World world) {
5270
var store = world.getEntityStore().getStore();
53-
54-
if (store == null)
71+
if (store == null) {
5572
return;
73+
}
5674

75+
var guiConfig = config.get();
5776
var levelingServiceOpt = LevelingCoreApi.getLevelServiceIfPresent();
58-
if (levelingServiceOpt.isEmpty())
77+
if (levelingServiceOpt.isEmpty()) {
5978
return;
79+
}
6080

6181
var levelingService = levelingServiceOpt.get();
82+
var hasClassesCore = PluginManager.get()
83+
.getPlugin(new PluginIdentifier("com.azuredoom", "classescore")) != null;
6284

63-
AtomicReference<String> locale = new AtomicReference<>();
85+
var blacklisted = new java.util.HashSet<>(Arrays.asList(guiConfig.getBlacklistedMobs()));
86+
final String[] localeHolder = { "en-US" };
6487

6588
store.forEachChunk(PlayerRef.getComponentType(), (chunk, commandBuffer) -> {
6689
var size = chunk.size();
6790
for (var i = 0; i < size; i++) {
6891
var ref = chunk.getReferenceTo(i);
69-
7092
var playerRef = commandBuffer.getComponent(ref, PlayerRef.getComponentType());
71-
if (playerRef == null)
93+
if (playerRef == null) {
7294
continue;
95+
}
7396

74-
locale.set(playerRef.getLanguage());
97+
localeHolder[0] = playerRef.getLanguage();
7598
var lvl = levelingService.getLevel(playerRef.getUuid());
76-
String className = null;
77-
if (PluginManager.get().getPlugin(new PluginIdentifier("com.azuredoom", "classescore")) != null) {
78-
className = ClassesCoreCompat.getPlayerClass(playerRef.getUuid());
79-
}
99+
var className = hasClassesCore ? ClassesCoreCompat.getPlayerClass(playerRef.getUuid()) : null;
100+
80101
insertNameplate(
81102
commandBuffer,
82103
ref,
83-
formatNameplate(playerRef.getUsername(), config.get().isShowPlayerLvls() ? lvl : 0, className)
104+
playerRef.getUsername(),
105+
formatNameplate(
106+
guiConfig.getMobNameplate(),
107+
playerRef.getUsername(),
108+
guiConfig.isShowPlayerLvls() ? lvl : 0,
109+
className,
110+
false
111+
)
84112
);
85113
}
86114
});
115+
87116
store.forEachChunk(NPCEntity.getComponentType(), (chunk, commandBuffer) -> {
88117
var size = chunk.size();
89118
for (var i = 0; i < size; i++) {
90119
var ref = chunk.getReferenceTo(i);
91-
92120
var npc = commandBuffer.getComponent(ref, Objects.requireNonNull(NPCEntity.getComponentType()));
93-
if (npc == null)
121+
if (npc == null) {
94122
continue;
123+
}
95124

96-
var blacklistedMobs = config.get().getBlacklistedMobs();
97-
var npcTypeId = npc.getNPCTypeId();
98-
if (Arrays.asList(blacklistedMobs).contains(npcTypeId)) {
125+
if (blacklisted.contains(npc.getNPCTypeId())) {
99126
continue;
100127
}
101-
final var entityId = npc.getUuid();
128+
102129
var npcRole = npc.getRole();
103130
if (npcRole == null) {
104131
continue;
105132
}
133+
134+
var finalLocale = localeHolder[0] == null ? "en-US" : localeHolder[0];
106135
var entityName = I18nModule.get()
107136
.getMessage(
108-
Boolean.parseBoolean(locale.get()) ? null : "en-US",
137+
finalLocale,
109138
npcRole.getNameTranslationKey()
110139
);
140+
111141
var lvl = LevelingCore.mobLevelRegistry.getOrCreateWithPersistence(
112-
entityId,
142+
npc.getUuid(),
113143
() -> MobLevelingUtil.computeSpawnLevel(npc),
114144
0,
115145
LevelingCore.mobLevelPersistence
116146
);
117-
if (lvl == null)
147+
if (lvl == null) {
118148
continue;
149+
}
119150

120-
var text = formatNameplate(entityName, config.get().isShowMobLvls() ? lvl.level : 0, null);
121-
insertNameplate(commandBuffer, ref, text);
151+
insertNameplate(
152+
commandBuffer,
153+
ref,
154+
entityName,
155+
formatNameplate(
156+
guiConfig.getMobNameplate(),
157+
entityName,
158+
guiConfig.isShowMobLvls() ? lvl.level : 0,
159+
null,
160+
true
161+
)
162+
);
122163
}
123164
});
124165
}
125166

167+
/**
168+
* Updates or inserts a nameplate component for an entity based on the provided base name and suffix. If the suffix
169+
* is blank or null, only the base name is considered. If the entity lacks sufficient health or other required
170+
* conditions are not met, the nameplate update is skipped.
171+
*
172+
* @param commandBuffer the buffer used to manipulate entity components; must not be null.
173+
* @param ref a reference to the target entity whose nameplate needs to be updated; must not be null.
174+
* @param baseName the base name to display on the nameplate; may be null, in which case it defaults to an
175+
* empty string.
176+
* @param desiredSuffix the suffix to append to the base name; if null or blank, only the base name is used.
177+
*/
126178
private void insertNameplate(
127179
CommandBuffer<EntityStore> commandBuffer,
128180
Ref<EntityStore> ref,
129-
String desiredText
181+
String baseName,
182+
String desiredSuffix
130183
) {
131-
if (desiredText == null || desiredText.isBlank()) {
132-
var current = commandBuffer.getComponent(ref, Nameplate.getComponentType());
184+
var current = commandBuffer.getComponent(ref, Nameplate.getComponentType());
185+
186+
var safeBaseName = baseName == null ? "" : baseName;
187+
188+
if (desiredSuffix == null || desiredSuffix.isBlank()) {
133189
if (current != null) {
134-
var strip = buildSuffixStripPattern();
135-
var base = strip.matcher(current.getText()).replaceAll("");
136-
current.setText(base);
137-
commandBuffer.putComponent(ref, Nameplate.getComponentType(), current);
190+
if (!safeBaseName.equals(current.getText())) {
191+
current.setText(safeBaseName);
192+
commandBuffer.putComponent(ref, Nameplate.getComponentType(), current);
193+
}
194+
} else if (!safeBaseName.isBlank()) {
195+
commandBuffer.putComponent(ref, Nameplate.getComponentType(), new Nameplate(safeBaseName));
138196
}
139197
return;
140198
}
199+
141200
var entityStatMap = commandBuffer.getComponent(ref, EntityStatMap.getComponentType());
142-
var healthStat = DefaultEntityStatTypes.getHealth();
143-
var healthValue = entityStatMap.get(healthStat);
201+
if (entityStatMap == null) {
202+
return;
203+
}
144204

145-
if (healthValue.get() <= 0)
205+
var healthValue = entityStatMap.get(DefaultEntityStatTypes.getHealth());
206+
if (healthValue == null || healthValue.get() <= 0) {
146207
return;
208+
}
209+
210+
var newText = safeBaseName + NAMEPLATE_MARKER + desiredSuffix;
147211

148-
var current = commandBuffer.getComponent(ref, Nameplate.getComponentType());
149212
if (current != null) {
150-
var oldText = current.getText();
151-
var strip = buildSuffixStripPattern();
152-
153-
if (strip.matcher(oldText).find()) {
154-
var base = strip.matcher(oldText).replaceAll("");
155-
var newText = base + desiredText;
156-
157-
if (oldText.equals(newText))
158-
return;
159-
current.setText(desiredText);
160-
} else {
161-
current.setText(oldText + desiredText);
213+
if (newText.equals(current.getText())) {
214+
return;
162215
}
163216

217+
current.setText(newText);
164218
commandBuffer.putComponent(ref, Nameplate.getComponentType(), current);
165219
} else {
166-
commandBuffer.putComponent(ref, Nameplate.getComponentType(), new Nameplate(desiredText));
220+
commandBuffer.putComponent(ref, Nameplate.getComponentType(), new Nameplate(newText));
167221
}
168222
}
169223

170-
private String formatNameplate(@NullableDecl String entityName, int level, String className) {
171-
if (level <= 0)
224+
/**
225+
* Formats a nameplate string by replacing placeholders with appropriate values such as entity name, level, and
226+
* class name. The formatting includes actions such as uncapping the input template, resolving placeholders, and
227+
* cleaning up unnecessary whitespace or empty lines.
228+
*
229+
* @param rawTemplate the raw template string containing placeholders such as {name}, {level}, and
230+
* {class}. May include escaped newline or tab characters that will be unescaped.
231+
* @param entityName the name of the entity to be included in the formatted output. If {@code null}, the
232+
* name placeholder will be replaced accordingly based on
233+
* {@code includeNameInTemplate}.
234+
* @param level the level to be injected into the template. Must be greater than 0; otherwise, the
235+
* method will return {@code null}.
236+
* @param className the class name to be included in the formatted output. If {@code null} or blank,
237+
* relevant placeholders in the template will be removed.
238+
* @param includeNameInTemplate a flag indicating whether the entity name should be included in the formatted
239+
* output. If {@code false}, the name placeholder will be omitted or replaced with an
240+
* empty string.
241+
* @return the formatted nameplate string with placeholders replaced by corresponding data, or {@code null} if the
242+
* input template is invalid, empty, or the level is lower than or equal to 0.
243+
*/
244+
private String formatNameplate(
245+
String rawTemplate,
246+
@NullableDecl String entityName,
247+
int level,
248+
String className,
249+
boolean includeNameInTemplate
250+
) {
251+
if (level <= 0) {
172252
return null;
253+
}
173254

174-
var rawTemplate = config.get().getMobNameplate();
175255
var template = unescape(rawTemplate);
176256

177-
if (template == null || template.isBlank())
257+
if (template == null || template.isBlank()) {
178258
return null;
259+
}
179260

180-
if ((entityName == null || entityName.isBlank()) && template.contains("{name}")) {
181-
return null;
261+
var resolvedName = includeNameInTemplate
262+
? (entityName == null ? "" : entityName)
263+
: "";
264+
265+
if (className == null || className.isBlank()) {
266+
template = template
267+
.replace("[{class}] - ", "")
268+
.replace("[{class}]- ", "")
269+
.replace("[{class}] ", "")
270+
.replace("[{class}]", "");
182271
}
183272

184273
var result = template
185-
.replace("{level}", Integer.toString(level))
186-
.replace("{name}", entityName == null ? "" : entityName)
187-
.replace("{class}", className == null ? "" : className);
274+
.replace("{level}", Integer.toString(level))
275+
.replace("{name}", resolvedName)
276+
.replace("{class}", className == null ? "" : className);
277+
278+
var cleaned = new StringBuilder();
279+
for (var line : result.split("\\R", -1)) {
280+
if (!line.isBlank()) {
281+
if (!cleaned.isEmpty()) {
282+
cleaned.append('\n');
283+
}
188284

189-
result = result
190-
.replaceAll("[ \\t]+\\n", "\n")
191-
.replaceAll("\\n[ \\t]+", "\n")
192-
.replaceAll(" {2,}", " ")
193-
.trim();
285+
var normalizedLine = line.replaceAll(" {2,}", " ");
286+
cleaned.append(normalizedLine);
287+
}
288+
}
194289

195-
return result.isBlank() ? null : result;
290+
return cleaned.isEmpty() ? null : cleaned.toString();
196291
}
197292

293+
/**
294+
* Replaces escaped newline and tab characters in the input string with their actual values.
295+
*
296+
* @param s the string to unescape; may contain escaped newline (\n) or tab (\t) characters. If the input string is
297+
* null, the method returns null.
298+
* @return a new string with escaped newline and tab characters replaced by their actual values, or null if the
299+
* input string is null.
300+
*/
198301
private static String unescape(String s) {
199302
if (s == null)
200303
return null;
201304
return s.replace("\\n", "\n").replace("\\t", "\t");
202305
}
203-
204-
private Pattern buildSuffixStripPattern() {
205-
var rawTemplate = config.get().getMobNameplate();
206-
if (rawTemplate == null || rawTemplate.isBlank()) {
207-
return Pattern.compile("(?!)");
208-
}
209-
210-
var regex = Pattern.quote(rawTemplate)
211-
.replace("{level}", "\\E\\d+\\Q")
212-
.replace("{name}", "\\E.*?\\Q")
213-
.replace("{class}", "\\E.*?\\Q")
214-
.replace(" \\\\n", "\\E\\s*\\Q")
215-
.replace("\\\\n", "\\E\\s*\\Q");
216-
217-
return Pattern.compile(regex + "$", Pattern.DOTALL);
218-
}
219306
}

0 commit comments

Comments
 (0)