1+ package com .nerds .addon .hud ;
2+
3+ import com .nerds .addon .NerdAddon ;
4+ import meteordevelopment .meteorclient .settings .BoolSetting ;
5+ import meteordevelopment .meteorclient .settings .ColorSetting ;
6+ import meteordevelopment .meteorclient .settings .EnumSetting ;
7+ import meteordevelopment .meteorclient .settings .Setting ;
8+ import meteordevelopment .meteorclient .settings .SettingGroup ;
9+ import meteordevelopment .meteorclient .settings .StringSetting ;
10+ import meteordevelopment .meteorclient .systems .hud .HudElement ;
11+ import meteordevelopment .meteorclient .systems .hud .HudElementInfo ;
12+ import meteordevelopment .meteorclient .systems .hud .HudRenderer ;
13+ import meteordevelopment .meteorclient .utils .render .color .SettingColor ;
14+ import net .minecraft .network .packet .c2s .play .ClientStatusC2SPacket ;
15+ import net .minecraft .stat .Stats ;
16+ import net .minecraft .util .Identifier ;
17+
18+ import static meteordevelopment .meteorclient .MeteorClient .mc ;
19+
20+ public class StatsHud extends HudElement {
21+ public static final HudElementInfo <StatsHud > INFO = new HudElementInfo <>(NerdAddon .HUD_GROUP , "stats" , "Displays account and vanilla stat values." , StatsHud ::new );
22+
23+ public enum Mode {
24+ Account ,
25+ PlayerKills ,
26+ MobKills ,
27+ Deaths ,
28+ Playtime ,
29+ DamageDealt ,
30+ DamageTaken
31+ }
32+
33+ static {
34+ INFO .addPreset ("Account" , hud -> hud .mode .set (Mode .Account ));
35+ INFO .addPreset ("Player Kills" , hud -> hud .mode .set (Mode .PlayerKills ));
36+ INFO .addPreset ("Mob Kills" , hud -> hud .mode .set (Mode .MobKills ));
37+ INFO .addPreset ("Deaths" , hud -> hud .mode .set (Mode .Deaths ));
38+ INFO .addPreset ("Playtime" , hud -> hud .mode .set (Mode .Playtime ));
39+ INFO .addPreset ("Damage Dealt" , hud -> hud .mode .set (Mode .DamageDealt ));
40+ INFO .addPreset ("Damage Taken" , hud -> hud .mode .set (Mode .DamageTaken ));
41+ }
42+
43+ private final SettingGroup sgGeneral = settings .getDefaultGroup ();
44+ private final SettingGroup sgColors = settings .createGroup ("Colors" );
45+
46+ private final Setting <Mode > mode = sgGeneral .add (new EnumSetting .Builder <Mode >()
47+ .name ("mode" )
48+ .description ("Which account/stat value to display." )
49+ .defaultValue (Mode .Account )
50+ .build ()
51+ );
52+
53+ private final Setting <Boolean > customLabel = sgGeneral .add (new BoolSetting .Builder ()
54+ .name ("custom-label" )
55+ .description ("Uses a custom label before the value." )
56+ .defaultValue (false )
57+ .build ()
58+ );
59+
60+ private final Setting <String > label = sgGeneral .add (new StringSetting .Builder ()
61+ .name ("label" )
62+ .description ("Custom label." )
63+ .defaultValue ("" )
64+ .visible (customLabel ::get )
65+ .build ()
66+ );
67+
68+ private final Setting <Boolean > shadow = sgGeneral .add (new BoolSetting .Builder ()
69+ .name ("shadow" )
70+ .description ("Renders shadow behind text." )
71+ .defaultValue (true )
72+ .build ()
73+ );
74+
75+ private final Setting <Boolean > syncStats = sgGeneral .add (new BoolSetting .Builder ()
76+ .name ("sync-stats" )
77+ .description ("Requests fresh vanilla stats from the server." )
78+ .defaultValue (true )
79+ .visible (() -> mode .get () != Mode .Account )
80+ .build ()
81+ );
82+
83+ private final Setting <SettingColor > labelColor = sgColors .add (new ColorSetting .Builder ()
84+ .name ("label-color" )
85+ .description ("Color of the label." )
86+ .defaultValue (new SettingColor (175 , 175 , 175 ))
87+ .build ()
88+ );
89+
90+ private final Setting <SettingColor > valueColor = sgColors .add (new ColorSetting .Builder ()
91+ .name ("value-color" )
92+ .description ("Color of the stat value." )
93+ .defaultValue (new SettingColor (255 , 255 , 255 ))
94+ .build ()
95+ );
96+
97+ public StatsHud () {
98+ super (INFO );
99+ }
100+
101+ @ Override
102+ public void tick (HudRenderer renderer ) {
103+ if (syncStats .get () && mode .get () != Mode .Account ) requestStats ();
104+ }
105+
106+ @ Override
107+ public void render (HudRenderer renderer ) {
108+ String left = getLabel ();
109+ String right = getValue ();
110+ boolean shadow = this .shadow .get ();
111+ double leftWidth = renderer .textWidth (left , shadow );
112+ double rightWidth = renderer .textWidth (right , shadow );
113+
114+ setSize (leftWidth + rightWidth , renderer .textHeight (shadow ));
115+
116+ double x = this .x ;
117+ x = renderer .text (left , x , y , labelColor .get (), shadow );
118+ renderer .text (right , x , y , valueColor .get (), shadow );
119+ }
120+
121+ private String getLabel () {
122+ if (customLabel .get () && !label .get ().isBlank ()) return label .get () + ": " ;
123+
124+ return switch (mode .get ()) {
125+ case Account -> "Account: " ;
126+ case PlayerKills -> "Kills: " ;
127+ case MobKills -> "Mob Kills: " ;
128+ case Deaths -> "Deaths: " ;
129+ case Playtime -> "Playtime: " ;
130+ case DamageDealt -> "Damage Dealt: " ;
131+ case DamageTaken -> "Damage Taken: " ;
132+ };
133+ }
134+
135+ private String getValue () {
136+ if (mc .player == null ) return mode .get () == Mode .Account ? "N/A" : "0" ;
137+
138+ return switch (mode .get ()) {
139+ case Account -> mc .getSession ().getUsername ();
140+ case PlayerKills -> Integer .toString (getCustomStat (Stats .PLAYER_KILLS ));
141+ case MobKills -> Integer .toString (getCustomStat (Stats .MOB_KILLS ));
142+ case Deaths -> Integer .toString (getCustomStat (Stats .DEATHS ));
143+ case Playtime -> formatPlaytime (getCustomStat (Stats .PLAY_TIME ));
144+ case DamageDealt -> formatDamage (getCustomStat (Stats .DAMAGE_DEALT ));
145+ case DamageTaken -> formatDamage (getCustomStat (Stats .DAMAGE_TAKEN ));
146+ };
147+ }
148+
149+ private int getCustomStat (Identifier id ) {
150+ return mc .player .getStatHandler ().getStat (Stats .CUSTOM .getOrCreateStat (id ));
151+ }
152+
153+ private void requestStats () {
154+ if (mc .getNetworkHandler () == null ) return ;
155+
156+ mc .getNetworkHandler ().sendPacket (new ClientStatusC2SPacket (ClientStatusC2SPacket .Mode .REQUEST_STATS ));
157+ }
158+
159+ private String formatDamage (int raw ) {
160+ return String .format ("%.1f" , raw / 10.0 );
161+ }
162+
163+ private String formatPlaytime (int ticks ) {
164+ long seconds = ticks / 20L ;
165+ long hours = seconds / 3600L ;
166+ long minutes = (seconds % 3600L ) / 60L ;
167+ long secs = seconds % 60L ;
168+
169+ if (hours > 0 ) return String .format ("%dh %02dm" , hours , minutes );
170+ if (minutes > 0 ) return String .format ("%dm %02ds" , minutes , secs );
171+ return secs + "s" ;
172+ }
173+ }
0 commit comments