1818
1919import java .util .Arrays ;
2020import java .util .Objects ;
21- import java .util .concurrent .atomic .AtomicReference ;
22- import java .util .regex .Pattern ;
2321
2422import com .azuredoom .levelingcore .LevelingCore ;
2523import 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