2222import net .minecraft .world .entity .decoration .ArmorStand ;
2323import net .minecraft .world .entity .decoration .ItemFrame ;
2424import net .minecraft .world .entity .item .ItemEntity ;
25+ import net .minecraft .world .entity .ExperienceOrb ;
2526import net .minecraft .world .entity .npc .Villager ;
2627import net .minecraft .world .entity .player .Player ;
2728import net .minecraft .world .item .Item ;
@@ -55,6 +56,14 @@ private enum SpecialMode
5556 QUERY
5657 }
5758
59+ private enum XpMode
60+ {
61+ OFF ,
62+ NORMAL ,
63+ SPECIAL ,
64+ RAINBOW
65+ }
66+
5867 private static final int MAX_SPECIAL_TEXT_LENGTH = 256 ;
5968 private final EspStyleSetting style = new EspStyleSetting ();
6069
@@ -122,6 +131,11 @@ private enum SpecialMode
122131 new CheckboxSetting ("Ignore villagers" ,
123132 "Won't highlight equipped special items on villagers." , false );
124133
134+ // XP orb handling mode
135+ private final net .wurstclient .settings .EnumSetting <XpMode > xpMode =
136+ new net .wurstclient .settings .EnumSetting <>("XP orb mode" ,
137+ XpMode .values (), XpMode .NORMAL );
138+
125139 // New: include item frames holding special items
126140 private final CheckboxSetting includeItemFrames =
127141 new CheckboxSetting ("Highlight frames with special" ,
@@ -134,6 +148,7 @@ private enum SpecialMode
134148 false );
135149
136150 private final ArrayList <ItemEntity > items = new ArrayList <>();
151+ private final ArrayList <ExperienceOrb > xpOrbs = new ArrayList <>();
137152 // Above-ground filter
138153 private final CheckboxSetting onlyAboveGround =
139154 new CheckboxSetting ("Above ground only" ,
@@ -164,6 +179,7 @@ public ItemEspHack()
164179 addSetting (specialRainbow );
165180 addSetting (specialColor );
166181 addSetting (outlineOnly );
182+ addSetting (xpMode );
167183 // ignored items
168184 addSetting (useIgnoredItems );
169185 addSetting (ignoredList );
@@ -223,11 +239,16 @@ public boolean isIgnoredId(String id)
223239 public void onUpdate ()
224240 {
225241 items .clear ();
242+ xpOrbs .clear ();
226243 for (Entity entity : MC .level .entitiesForRendering ())
227- if (entity instanceof ItemEntity )
228- items .add ((ItemEntity )entity );
244+ {
245+ if (entity instanceof ItemEntity ie )
246+ items .add (ie );
247+ else if (entity instanceof ExperienceOrb xo )
248+ xpOrbs .add (xo );
249+ }
229250 // update count for HUD (clamped to 999)
230- foundCount = Math .min (items .size (), 999 );
251+ foundCount = Math .min (items .size () + xpOrbs . size () , 999 );
231252 }
232253
233254 @ Override
@@ -324,12 +345,22 @@ public void onRender(PoseStack matrixStack, float partialTicks)
324345 AABB box = EntityUtils .getLerpedBox (e , partialTicks )
325346 .move (0 , extraSize , 0 ).inflate (extraSize );
326347 boolean isSpecial = isSpecial (stack );
327- // check traced override from ItemHandlerHack
328- String id =
329- BuiltInRegistries . ITEM . getKey ( stack . getItem ()). toString ( );
348+ // check traced override from ItemHandlerHack (use synthetic id if
349+ // present)
350+ String id = net . wurstclient . util . ItemUtils . getStackId ( stack );
330351 net .wurstclient .hacks .itemhandler .ItemHandlerHack ih =
331352 net .wurstclient .WurstClient .INSTANCE .getHax ().itemHandlerHack ;
332- boolean isTraced = ih != null && ih .isTraced (id );
353+ boolean isTraced = false ;
354+ if (ih != null && id != null )
355+ {
356+ isTraced = ih .isTraced (id );
357+ if (!isTraced
358+ && net .wurstclient .util .ItemUtils .isSyntheticXp (stack ))
359+ {
360+ int xp = net .wurstclient .util .ItemUtils .getXpAmount (stack );
361+ isTraced = ih .isTraced (id + ":xp:" + xp );
362+ }
363+ }
333364 visibleDrops ++;
334365 if (isTraced )
335366 {
@@ -350,6 +381,63 @@ public void onRender(PoseStack matrixStack, float partialTicks)
350381 }
351382 foundCount = Math .min (visibleDrops , 999 );
352383
384+ // Integrate XP orbs into boxes/ends as synthetic items
385+ for (ExperienceOrb orb : xpOrbs )
386+ {
387+ if (onlyAboveGround .isChecked ()
388+ && orb .getY () < aboveGroundY .getValue ())
389+ continue ;
390+ // synthetic proxy stack for rendering and UI
391+ net .minecraft .world .item .ItemStack stack =
392+ net .wurstclient .util .ItemUtils .createSyntheticXpStack (orb );
393+ if (isIgnored (stack ))
394+ continue ;
395+ Vec3 center =
396+ EntityUtils .getLerpedBox (orb , partialTicks ).getCenter ();
397+ AABB box = smallBoxAt (center );
398+ // decide how to render XP orbs based on user setting and trace
399+ XpMode mode = xpMode .getSelected ();
400+ if (mode == XpMode .OFF )
401+ continue ;
402+ String id = "minecraft:experience_orb" ;
403+ net .wurstclient .hacks .itemhandler .ItemHandlerHack ih =
404+ net .wurstclient .WurstClient .INSTANCE .getHax ().itemHandlerHack ;
405+ boolean isTraced = false ;
406+ if (ih != null )
407+ {
408+ isTraced = ih .isTraced (id );
409+ int xp = net .wurstclient .util .ItemUtils .getXpAmount (stack );
410+ if (!isTraced )
411+ isTraced = ih .isTraced (id + ":xp:" + xp );
412+ }
413+ visibleDrops ++;
414+ if (isTraced )
415+ {
416+ tracedBoxes .add (box );
417+ tracedEnds .add (center );
418+ }else
419+ {
420+ switch (mode )
421+ {
422+ case RAINBOW ->
423+ {
424+ tracedBoxes .add (box );
425+ tracedEnds .add (center );
426+ }
427+ case SPECIAL ->
428+ {
429+ specialBoxes .add (box );
430+ specialEnds .add (center );
431+ }
432+ default ->
433+ {
434+ normalBoxes .add (box );
435+ normalEnds .add (center );
436+ }
437+ }
438+ }
439+ }
440+
353441 // Item frames holding special items
354442 if (includeItemFrames .isChecked ())
355443 {
@@ -520,20 +608,28 @@ private boolean isIgnored(ItemStack stack)
520608 return false ;
521609 if (ignoredExactIds == null || ignoredExactIds .isEmpty ())
522610 return false ;
523- ResourceLocation id = BuiltInRegistries . ITEM . getKey ( stack . getItem () );
611+ String id = net . wurstclient . util . ItemUtils . getStackId ( stack );
524612 if (id == null )
525613 return false ;
526- return ignoredExactIds .contains (id .toString ());
614+ if (ignoredExactIds != null && ignoredExactIds .contains (id ))
615+ return true ;
616+ // fallback: allow raw ignored list entries (useful for synthetic ids)
617+ for (String s : ignoredList .getItemNames ())
618+ if (id .equalsIgnoreCase (s .trim ()))
619+ return true ;
620+ return false ;
527621 }
528622
529623 private boolean isSpecial (ItemStack stack )
530624 {
531625 Item item = stack .getItem ();
626+ String stackId = net .wurstclient .util .ItemUtils .getStackId (stack );
532627 switch (specialMode .getSelected ())
533628 {
534629 case LIST :
535630 {
536- String id = BuiltInRegistries .ITEM .getKey (item ).toString ();
631+ String id = stackId != null ? stackId
632+ : BuiltInRegistries .ITEM .getKey (item ).toString ();
537633 if (specialExactIds != null && specialExactIds .contains (id ))
538634 return true ;
539635 String localId =
@@ -554,7 +650,13 @@ private boolean isSpecial(ItemStack stack)
554650 return false ;
555651 }
556652 case ITEM_ID :
557- return itemMatchesId (item , specialItemId .getValue ());
653+ {
654+ if (stackId != null && specialItemId .getValue () != null
655+ && stackId
656+ .equalsIgnoreCase (specialItemId .getValue ().trim ()))
657+ return true ;
658+ return itemMatchesId (item , specialItemId .getValue ());
659+ }
558660 case QUERY :
559661 return itemOrStackMatchesQuery (item , stack ,
560662 normalizeQuery (specialQuery .getValue ()));
@@ -590,7 +692,9 @@ private boolean itemOrStackMatchesQuery(Item item, ItemStack stack,
590692 {
591693 if (normalizedQuery .isEmpty ())
592694 return false ;
593- String fullId = BuiltInRegistries .ITEM .getKey (item ).toString ();
695+ String stackId = net .wurstclient .util .ItemUtils .getStackId (stack );
696+ String fullId = stackId != null ? stackId
697+ : BuiltInRegistries .ITEM .getKey (item ).toString ();
594698 String localId = fullId .contains (":" )
595699 ? fullId .substring (fullId .indexOf (":" ) + 1 ) : fullId ;
596700 String localSpaced = localId .replace ('_' , ' ' );
0 commit comments