Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Визуальная часть Scoreboard rockstar 1.21.4

Начинающий
Начинающий
Статус
Оффлайн
Регистрация
26 Янв 2025
Сообщения
21
Реакции
0
Выберите загрузчик игры
  1. Fabric
Всем привет! Сливаю вам скорборд из своего клиента так как мне лень его дальше писать
LegacyTextHelper.java:
Expand Collapse Copy
package moscow.rockstar.utility.game;

import net.minecraft.text.MutableText;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.text.TextColor;
import net.minecraft.util.Formatting;
import java.util.ArrayList;
import java.util.List;

public final class LegacyTextHelper {
   private static final char LEGACY_PREFIX_SECTION = '\u00A7';
   private static final char LEGACY_PREFIX_AMP = '&';
   private static final char LEGACY_PREFIX_DOLLAR = '$';

   public static Text resolve(Text text, int defaultColor) {
      if (text == null) {
         return Text.empty();
      }

      int baseColor = defaultColor & 0xFFFFFF;
      String raw = extractRaw(text);
      if (containsLegacyCodes(raw)) {
         return parse(raw, baseColor);
      }

      if (hasExplicitColor(text)) {
         return text;
      }

      return applyDefaultColor(text, baseColor);
   }

   public static List<ColoredSegment> parseSegments(String input, int defaultColor) {
      List<ColoredSegment> segments = new ArrayList<>();
      if (input == null || input.isEmpty()) {
         return segments;
      }

      StringBuilder buffer = new StringBuilder();
      int currentColor = 0xFF000000 | (defaultColor & 0xFFFFFF);
      int baseColor = currentColor;
      int length = input.length();

      for (int i = 0; i < length; i++) {
         char c = input.charAt(i);
         if (c == '<' || c == '{') {
            char end = c == '<' ? '>' : '}';
            if (isWrappedHexSequence(input, i, end)) {
               flushSegment(buffer, segments, currentColor);
               int rgb = parseHexColorPlain(input, i + 2);
               currentColor = 0xFF000000 | rgb;
               i += 8;
               continue;
            }
         }

         if (isLegacyPrefix(c) && i + 1 < length) {
            char code = Character.toLowerCase(input.charAt(i + 1));
            if (code == 'x' && isValidHexSequence(input, i)) {
               flushSegment(buffer, segments, currentColor);
               int rgb = parseHexColorLegacy(input, i);
               currentColor = 0xFF000000 | rgb;
               i += 13;
               continue;
            }

            if (code == '#' && isHexSequence(input, i + 2, 6)) {
               flushSegment(buffer, segments, currentColor);
               int rgb = parseHexColorPlain(input, i + 2);
               currentColor = 0xFF000000 | rgb;
               i += 7;
               continue;
            }

            Formatting formatting = Formatting.byCode(code);
            if (formatting != null) {
               flushSegment(buffer, segments, currentColor);
               if (formatting == Formatting.RESET) {
                  currentColor = baseColor;
               } else if (formatting.isColor()) {
                  Integer rgb = formatting.getColorValue();
                  if (rgb != null) {
                     currentColor = 0xFF000000 | rgb;
                  }
               }
               i++;
               continue;
            }
         }

         buffer.append(c);
      }

      flushSegment(buffer, segments, currentColor);
      return segments;
   }

   public static String extractRaw(Text text) {
      if (text == null) {
         return "";
      }

      StringBuilder visited = new StringBuilder();
      text.visit((style, string) -> {
         if (style != null && style.getColor() != null) {
            int rgb = style.getColor().getRgb() & 0xFFFFFF;
            String hex = Integer.toHexString(rgb);
            visited.append("&#");
            for (int i = 0; i < 6 - hex.length(); i++) {
               visited.append('0');
            }
            visited.append(hex);
         }
         visited.append(string);
         return java.util.Optional.empty();
      }, Style.EMPTY);

      return visited.toString();
   }

   public static boolean containsLegacyCodes(String input) {
      if (input == null || input.length() < 2) {
         return false;
      }

      int length = input.length();
      for (int i = 0; i < length - 1; i++) {
         char c = input.charAt(i);
         if (c == '<' || c == '{') {
            if (isWrappedHexSequence(input, i, c == '<' ? '>' : '}')) {
               return true;
            }
            continue;
         }

         if (!isLegacyPrefix(c)) {
            continue;
         }

         char code = Character.toLowerCase(input.charAt(i + 1));
         if (isLegacyCode(code)) {
            return true;
         }

         if (code == 'x' && isValidHexSequence(input, i)) {
            return true;
         }

         if (code == '#' && isHexSequence(input, i + 2, 6)) {
            return true;
         }
      }

      return false;
   }

   public static Text parse(String input, int defaultColor) {
      if (input == null || input.isEmpty()) {
         return Text.empty();
      }

      MutableText result = Text.empty();
      StringBuilder buffer = new StringBuilder();
      int baseColor = defaultColor & 0xFFFFFF;
      Style current = Style.EMPTY.withColor(TextColor.fromRgb(baseColor));
      int length = input.length();

      for (int i = 0; i < length; i++) {
         char c = input.charAt(i);
         if (c == '<' || c == '{') {
            char end = c == '<' ? '>' : '}';
            if (isWrappedHexSequence(input, i, end)) {
               flush(buffer, result, current);
               int rgb = parseHexColorPlain(input, i + 2);
               current = current.withColor(TextColor.fromRgb(rgb));
               i += 8;
               continue;
            }
         }

         if (isLegacyPrefix(c) && i + 1 < length) {
            char code = Character.toLowerCase(input.charAt(i + 1));
            if (code == 'x' && isValidHexSequence(input, i)) {
               flush(buffer, result, current);
               int rgb = parseHexColorLegacy(input, i);
               current = current.withColor(TextColor.fromRgb(rgb));
               i += 13;
               continue;
            }

            if (code == '#' && isHexSequence(input, i + 2, 6)) {
               flush(buffer, result, current);
               int rgb = parseHexColorPlain(input, i + 2);
               current = current.withColor(TextColor.fromRgb(rgb));
               i += 7;
               continue;
            }

            Formatting formatting = Formatting.byCode(code);
            if (formatting != null) {
               flush(buffer, result, current);
               if (formatting == Formatting.RESET) {
                  current = Style.EMPTY.withColor(TextColor.fromRgb(baseColor));
               } else if (formatting.isColor()) {
                  current = Style.EMPTY.withFormatting(formatting);
               } else {
                  current = current.withFormatting(formatting);
               }
               i++;
               continue;
            }
         }

         buffer.append(c);
      }

      flush(buffer, result, current);
      return result;
   }

   private static void flush(StringBuilder buffer, MutableText result, Style style) {
      if (buffer.length() == 0) {
         return;
      }

      result.append(Text.literal(buffer.toString()).setStyle(style));
      buffer.setLength(0);
   }

   private static void flushSegment(StringBuilder buffer, List<ColoredSegment> segments, int color) {
      if (buffer.length() == 0) {
         return;
      }

      segments.add(new ColoredSegment(buffer.toString(), color));
      buffer.setLength(0);
   }

   private static boolean isLegacyPrefix(char c) {
      return c == LEGACY_PREFIX_SECTION || c == LEGACY_PREFIX_AMP || c == LEGACY_PREFIX_DOLLAR;
   }

   private static boolean isLegacyCode(char code) {
      return (code >= '0' && code <= '9')
            || (code >= 'a' && code <= 'f')
            || code == 'k'
            || code == 'l'
            || code == 'm'
            || code == 'n'
            || code == 'o'
            || code == 'r';
   }

   private static boolean isValidHexSequence(String input, int startIndex) {
      int length = input.length();
      if (startIndex + 13 >= length) {
         return false;
      }

      for (int i = 0; i < 6; i++) {
         int base = startIndex + 2 + i * 2;
         char prefix = input.charAt(base);
         char hex = input.charAt(base + 1);
         if (!isLegacyPrefix(prefix) || !isHexDigit(hex)) {
            return false;
         }
      }

      return true;
   }

   private static int parseHexColorLegacy(String input, int startIndex) {
      StringBuilder hex = new StringBuilder(6);
      for (int i = 0; i < 6; i++) {
         int base = startIndex + 2 + i * 2;
         hex.append(input.charAt(base + 1));
      }
      return Integer.parseInt(hex.toString(), 16);
   }

   private static int parseHexColorPlain(String input, int startIndex) {
      return Integer.parseInt(input.substring(startIndex, startIndex + 6), 16);
   }

   private static boolean isHexDigit(char c) {
      return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
   }

   private static boolean isHexSequence(String input, int start, int length) {
      if (start < 0 || start + length > input.length()) {
         return false;
      }
      for (int i = 0; i < length; i++) {
         if (!isHexDigit(input.charAt(start + i))) {
            return false;
         }
      }
      return true;
   }

   private static boolean isWrappedHexSequence(String input, int startIndex, char endChar) {
      if (startIndex + 8 >= input.length()) {
         return false;
      }
      if (input.charAt(startIndex + 1) != '#') {
         return false;
      }
      if (!isHexSequence(input, startIndex + 2, 6)) {
         return false;
      }
      return input.charAt(startIndex + 8) == endChar;
   }

   private static Text applyDefaultColor(Text text, int defaultColor) {
      if (hasExplicitColor(text))
         return text;
      Style base = Style.EMPTY.withColor(TextColor.fromRgb(defaultColor));
      return Text.literal("").setStyle(base).append(text);
   }

   private static boolean hasExplicitColor(Text text) {
      final boolean[] hasColor = new boolean[] { false };
      text.visit((style, string) -> {
         if (style.getColor() != null) {
            hasColor[0] = true;
            return java.util.Optional.of(true);
         }
         return java.util.Optional.empty();
      }, Style.EMPTY);
      return hasColor[0];
   }

   private LegacyTextHelper() {
      throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
   }

   public record ColoredSegment(String text, int color) {
   }
}
ScoreboardHud.java:
Expand Collapse Copy
package moscow.rockstar.ui.hud.impl;

import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.List;
import moscow.rockstar.framework.base.UIContext;
import moscow.rockstar.framework.msdf.Font;
import moscow.rockstar.framework.msdf.Fonts;
import moscow.rockstar.framework.msdf.FormattedTextProcessor;
import moscow.rockstar.framework.objects.BorderRadius;
import moscow.rockstar.Rockstar;
import moscow.rockstar.systems.modules.modules.visuals.Interface;
import moscow.rockstar.systems.theme.Theme;
import moscow.rockstar.ui.hud.HudElement;
import moscow.rockstar.utility.colors.ColorRGBA;
import moscow.rockstar.utility.colors.Colors;
import moscow.rockstar.framework.objects.gradient.impl.HorizontalGradient;
import moscow.rockstar.utility.game.LegacyTextHelper;
import moscow.rockstar.systems.setting.settings.SliderSetting;
import moscow.rockstar.utility.interfaces.IScaledResolution;
import net.minecraft.client.gui.screen.ChatScreen;
import net.minecraft.scoreboard.Scoreboard;
import net.minecraft.scoreboard.ScoreboardDisplaySlot;
import net.minecraft.scoreboard.ScoreboardEntry;
import net.minecraft.scoreboard.ScoreboardObjective;
import net.minecraft.scoreboard.Team;
import net.minecraft.scoreboard.number.StyledNumberFormat;
import net.minecraft.text.Text;

public class ScoreboardHud extends HudElement implements IScaledResolution {
   private static final float TITLE_SIZE = 8.8F;
   private static final float LINE_SIZE = 7.8F;
   private static final float PAD_X = 16.0F;
   private static final float PAD_Y = 7.0F;
   private static final float LINE_GAP = 5.0F;
   private static final float VALUE_GAP = 16.0F;
   private boolean initialPos;
   private float lastSavedX = Float.NaN;
   private float lastSavedY = Float.NaN;
   private final SliderSetting scaleSetting = new SliderSetting(this, "\u0420\u0430\u0437\u043c\u0435\u0440").min(0.7F)
         .max(1.4F).step(0.05F).currentValue(1.0F);

   public ScoreboardHud() {
      super("\u0421\u043a\u043e\u0440\u0431\u043e\u0440\u0434", "icons/hud/world.png");
   }

   @Override
   public void update(UIContext context) {
      Layout layout = buildLayout();
      this.width = layout.width;
      this.height = layout.height;
      if (!this.initialPos && this.x == 0.0F && this.y == 0.0F) {
         this.x = Math.max(0.0F, sr.getScaledWidth() - this.width - 40.0F);
         this.y = 40.0F;
         this.initialPos = true;
      }
      if (!this.isDragging() && (this.x != this.lastSavedX || this.y != this.lastSavedY)) {
         this.lastSavedX = this.x;
         this.lastSavedY = this.y;
         Rockstar.getInstance().getFileManager().writeFile("client");
      }
      super.update(context);
   }

   @Override
   protected void renderComponent(UIContext context) {
      Layout layout = buildLayout();
      float x = this.x;
      float y = this.y;
      Font titleFont = getTitleFont();
      Font lineFont = getLineFont();

      boolean dark = Rockstar.getInstance().getThemeManager().getCurrentTheme() == Theme.DARK;
      ColorRGBA bgColor = Colors.getBackgroundColor()
            .withAlpha(255.0F * (dark ? 0.8F - 0.6F * Interface.glass() : 0.7F));

      context.drawShadow(
            x - 5.0F,
            y - 5.0F,
            layout.width + 10.0F,
            layout.height + 10.0F,
            15.0F,
            BorderRadius.all(6.0F),
            ColorRGBA.BLACK.withAlpha(63.75F));

      if (Interface.showMinimalizm()) {
         context.drawBlurredRect(
               x,
               y,
               layout.width,
               layout.height,
               45.0F,
               7.0F,
               BorderRadius.all(6.0F),
               ColorRGBA.WHITE.withAlpha(255.0F * Interface.minimalizm()));
      }

      if (Interface.showGlass()) {
         context.drawLiquidGlass(
               x,
               y,
               layout.width,
               layout.height,
               7.0F,
               0.08F,
               BorderRadius.all(6.0F),
               ColorRGBA.WHITE.withAlpha(255.0F * Interface.glass()));
      }

      context.drawSquircle(x, y, layout.width, layout.height, 7.0F, BorderRadius.all(6.0F), bgColor);

      float lineH = Math.max(1.0F, 1.4F * layout.scale);
      ColorRGBA accent = Colors.ACCENT.withAlpha(170.0F);
      ColorRGBA secondary = Colors.ACCENT.withAlpha(50.0F);
      context.drawRoundedRect(x + layout.padX * 0.6F, y + layout.headerHeight - lineH - layout.padY * 0.3F,
            layout.width - layout.padX * 1.2F, lineH, BorderRadius.all(lineH / 2.0F),
            new HorizontalGradient(accent, secondary));

      float titleX = x + PAD_X;
      for (Segment segment : layout.title.segments) {
         context.drawText(titleFont, segment.text, titleX, y + PAD_Y, ColorRGBA.fromInt(segment.color));
         titleX += titleFont.width(segment.text);
      }

      float lineY = y + layout.headerHeight;
      for (EntryLine line : layout.lines) {
         float rowY = lineY + (layout.lineHeight - lineFont.height()) / 2.0F;
         float valueWidth = line.value.width;

         float nameX = x + PAD_X;
         for (Segment segment : line.name.segments) {
            context.drawText(lineFont, segment.text, nameX, rowY, ColorRGBA.fromInt(segment.color));
            nameX += lineFont.width(segment.text);
         }

         float valueX = x + layout.width - PAD_X - valueWidth;
         for (Segment segment : line.value.segments) {
            context.drawText(lineFont, segment.text, valueX, rowY, ColorRGBA.fromInt(segment.color));
            valueX += lineFont.width(segment.text);
         }

         lineY += layout.lineHeight;
      }
   }

   @Override
   public boolean show() {
      return getObjective() != null;
   }

   private Layout buildLayout() {
      int defaultTextColor = Colors.getTextColor().getRGB() & 0xFFFFFF;
      float scale = this.scaleSetting.getCurrentValue();
      Font titleFont = getTitleFont();
      Font lineFont = getLineFont();
      ScoreboardObjective objective = getObjective();

      if (objective == null) {
         return new Layout(new Segments(List.of(), 0), List.of(), 0, 0, 0, 0, scale, PAD_X, PAD_Y);
      }

      Text titleSource = objective.getDisplayName();
      Segments title = buildSegments(titleSource, titleFont, defaultTextColor);
      float maxWidth = title.width;

      List<EntryLine> lines = objective.getScoreboard().getScoreboardEntries(objective).stream()
            .filter(entry -> entry != null && !entry.hidden())
            .sorted(Comparator.comparingInt(ScoreboardEntry::value).reversed().thenComparing(ScoreboardEntry::owner,
                  String::compareToIgnoreCase))
            .limit(25)
            .map(entry -> {
               Segments name = buildSegments(resolveNameText(objective.getScoreboard(), entry), lineFont,
                     defaultTextColor);
               Segments value = buildSegments(resolveValueText(objective, entry), lineFont, defaultTextColor);
               return new EntryLine(name, value);
            })
            .toList();

      for (EntryLine line : lines) {
         float rowWidth = line.name.width + line.value.width + VALUE_GAP;
         maxWidth = Math.max(maxWidth, rowWidth);
      }

      float headerHeight = titleFont.height() + PAD_Y * 2.0F;
      float lineHeight = lineFont.height() + LINE_GAP;
      float totalHeight = headerHeight + lines.size() * lineHeight + PAD_Y;
      float extraWidth = 16.0F;
      float totalWidth = maxWidth + PAD_X * 2.0F + extraWidth;

      totalWidth *= scale;
      totalHeight *= scale;
      headerHeight *= scale;
      lineHeight *= scale;

      return new Layout(title, lines, headerHeight, lineHeight, totalWidth, totalHeight, scale, PAD_X, PAD_Y);
   }

   private Segments buildSegments(Text text, Font font, int defaultColor) {
      String raw = LegacyTextHelper.extractRaw(text);
      Text source = LegacyTextHelper.containsLegacyCodes(raw) ? LegacyTextHelper.parse(raw, defaultColor) : text;
      boolean hasLegacy = LegacyTextHelper.containsLegacyCodes(raw);
      List<Segment> segments = hasLegacy
            ? LegacyTextHelper.parseSegments(raw, defaultColor).stream()
                  .map(segment -> new Segment(segment.text(), segment.color())).toList()
            : FormattedTextProcessor.processText(source, defaultColor).stream()
                  .map(segment -> new Segment(segment.text, segment.color)).toList();

      float width = 0.0F;
      List<Segment> sanitized = new java.util.ArrayList<>();
      for (Segment segment : segments) {
         String cleaned = segment.text;
         if (!cleaned.isEmpty()) {
            sanitized.add(new Segment(cleaned, segment.color));
            width += font.width(cleaned);
         }
      }

      return new Segments(sanitized, width);
   }

   private Font getTitleFont() {
      return Fonts.MEDIUM.getFont(TITLE_SIZE * this.scaleSetting.getCurrentValue());
   }

   private Font getLineFont() {
      return Fonts.REGULAR.getFont(LINE_SIZE * this.scaleSetting.getCurrentValue());
   }

   private ScoreboardObjective getObjective() {
      if (mc.world == null) {
         return null;
      }
      return mc.world.getScoreboard().getObjectiveForSlot(ScoreboardDisplaySlot.SIDEBAR);
   }

   private Text resolveNameText(Scoreboard scoreboard, ScoreboardEntry entry) {
      Text display = entry.display();
      if (display != null) {
         return display;
      }

      String owner = entry.owner();
      if (owner == null) {
         return Text.empty();
      }

      Team team = resolveTeam(scoreboard, owner);
      Text base = Text.literal(owner);
      return team != null ? Team.decorateName(team, base) : base;
   }

   private Text resolveValueText(ScoreboardObjective objective, ScoreboardEntry entry) {
      Text formatted = entry.formatted(objective.getNumberFormatOr(StyledNumberFormat.EMPTY));
      if (formatted != null) {
         return formatted;
      }

      return Text.literal(Integer.toString(entry.value()));
   }

   private Team resolveTeam(Scoreboard scoreboard, String owner) {
      if (scoreboard == null || owner == null) {
         return null;
      }
      return scoreboard.getScoreHolderTeam(owner);
   }

   private record Segment(String text, int color) {
   }

   private record Segments(List<Segment> segments, float width) {
   }

   private record EntryLine(Segments name, Segments value) {
   }

   private record Layout(Segments title, List<EntryLine> lines, float headerHeight, float lineHeight, float width,
         float height, float scale, float padX, float padY) {
   }
}

ScoreboardOverlay.java:
Expand Collapse Copy
package moscow.rockstar.ui.overlay;

import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.List;
import moscow.rockstar.framework.base.CustomDrawContext;
import moscow.rockstar.framework.msdf.Font;
import moscow.rockstar.framework.msdf.Fonts;
import moscow.rockstar.framework.objects.BorderRadius;
import moscow.rockstar.utility.colors.ColorRGBA;
import moscow.rockstar.utility.colors.Colors;
import moscow.rockstar.utility.game.LegacyTextHelper;
import moscow.rockstar.utility.gui.GuiUtility;
import moscow.rockstar.utility.interfaces.IMinecraft;
import moscow.rockstar.utility.interfaces.IScaledResolution;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.scoreboard.Scoreboard;
import net.minecraft.scoreboard.ScoreboardEntry;
import net.minecraft.scoreboard.ScoreboardObjective;
import net.minecraft.scoreboard.Team;
import net.minecraft.scoreboard.number.StyledNumberFormat;
import net.minecraft.text.Text;
import org.lwjgl.glfw.GLFW;

public class ScoreboardOverlay implements IMinecraft, IScaledResolution {
   private static final ScoreboardOverlay INSTANCE = new ScoreboardOverlay();
   private float x = -1.0F;
   private float y = -1.0F;
   private float width;
   private float height;
   private boolean dragging;
   private float dragOffsetX;
   private float dragOffsetY;
   private boolean wasMouseDown;

   public static ScoreboardOverlay getInstance() {
      return INSTANCE;
   }

   public void render(DrawContext context, ScoreboardObjective objective) {
      if (objective == null || mc.player == null || mc.world == null) {
         return;
      }

      Scoreboard scoreboard = objective.getScoreboard();
      List<ScoreboardEntry> entries = scoreboard.getScoreboardEntries(objective).stream()
         .filter(entry -> entry != null && !entry.hidden())
         .sorted(Comparator.comparingInt(ScoreboardEntry::value).reversed().thenComparing(ScoreboardEntry::owner, String::compareToIgnoreCase))
         .limit(15)
         .toList();

      Font titleFont = Fonts.MEDIUM.getFont(8.5F);
      Font lineFont = Fonts.REGULAR.getFont(7.5F);
      float padX = 10.0F;
      float padY = 6.0F;
      float lineGap = 4.0F;
      float valueGap = 10.0F;
      float shadowExpand = 5.0F;

      int defaultTextColor = Colors.getTextColor().getRGB() & 0xFFFFFF;
      Text title = LegacyTextHelper.resolve(objective.getDisplayName(), defaultTextColor);
      float maxWidth = titleFont.width(title);

      for (ScoreboardEntry entry : entries) {
         Text nameText = LegacyTextHelper.resolve(resolveNameText(scoreboard, entry), defaultTextColor);
         Text valueText = LegacyTextHelper.resolve(resolveValueText(objective, entry), defaultTextColor);
         float rowWidth = lineFont.width(nameText) + lineFont.width(valueText) + valueGap;
         maxWidth = Math.max(maxWidth, rowWidth);
      }

      float headerHeight = titleFont.height() + padY * 2.0F;
      float lineHeight = lineFont.height() + lineGap;
      float totalHeight = headerHeight + entries.size() * lineHeight + padY;
      float totalWidth = maxWidth + padX * 2.0F + 12.0F;

      updateDrag(totalWidth, totalHeight);
      float x = this.x;
      float y = this.y;
      this.width = totalWidth;
      this.height = totalHeight;

      CustomDrawContext drawContext = CustomDrawContext.of(context);
      drawContext.drawShadow(
         x - shadowExpand,
         y - shadowExpand,
         totalWidth + shadowExpand * 2.0F,
         totalHeight + shadowExpand * 2.0F,
         15.0F,
         BorderRadius.all(6.0F),
         ColorRGBA.BLACK.withAlpha(60.0F)
      );
      drawContext.drawClientRect(x, y, totalWidth, totalHeight, 1.0F, 0.0F, 7.0F);
      drawContext.drawRect(x, y + headerHeight - 1.0F, totalWidth, 1.0F, Colors.getSeparatorColor());

      float titleY = y + padY;
      drawContext.drawText(titleFont, title, x + padX, titleY);

      float lineY = y + headerHeight;
      for (ScoreboardEntry entry : entries) {
         Text nameText = LegacyTextHelper.resolve(resolveNameText(scoreboard, entry), defaultTextColor);
         Text valueText = LegacyTextHelper.resolve(resolveValueText(objective, entry), defaultTextColor);

         float rowY = lineY + (lineHeight - lineFont.height()) / 2.0F;
         float valueWidth = lineFont.width(valueText);

         drawContext.drawText(lineFont, nameText, x + padX, rowY);
         drawContext.drawText(lineFont, valueText, x + totalWidth - padX - valueWidth, rowY);

         lineY += lineHeight;
      }
   }

   private void updateDrag(float targetWidth, float targetHeight) {
      float maxX = Math.max(0.0F, sr.getScaledWidth() - targetWidth);
      float maxY = Math.max(0.0F, sr.getScaledHeight() - targetHeight);

      if (this.x < 0.0F || this.y < 0.0F) {
         this.x = maxX;
         this.y = 6.0F;
      }

      this.x = Math.max(0.0F, Math.min(this.x, maxX));
      this.y = Math.max(0.0F, Math.min(this.y, maxY));

      boolean mouseDown = GLFW.glfwGetMouseButton(mc.getWindow().getHandle(), 0) == 1;
      var mouse = GuiUtility.getMouse();
      float mouseX = mouse.getX();
      float mouseY = mouse.getY();
      if (mouseDown && !this.wasMouseDown && isHovered(mouseX, mouseY, targetWidth, targetHeight)) {
         this.dragging = true;
         this.dragOffsetX = mouseX - this.x;
         this.dragOffsetY = mouseY - this.y;
      }

      if (!mouseDown) {
         this.dragging = false;
      }

      if (this.dragging) {
         this.x = Math.max(0.0F, Math.min(mouseX - this.dragOffsetX, maxX));
         this.y = Math.max(0.0F, Math.min(mouseY - this.dragOffsetY, maxY));
      }

      this.wasMouseDown = mouseDown;
   }

   private boolean isHovered(float mouseX, float mouseY, float targetWidth, float targetHeight) {
      return mouseX >= this.x && mouseX < this.x + targetWidth && mouseY >= this.y && mouseY < this.y + targetHeight;
   }

   private Text resolveNameText(Scoreboard scoreboard, ScoreboardEntry entry) {
      Text display = entry.display();
      if (display != null) {
         return display;
      }

      String owner = entry.owner();
      if (owner == null) {
         return Text.empty();
      }

      Team team = resolveTeam(scoreboard, owner);
      Text base = Text.literal(owner);
      return team != null ? Team.decorateName(team, base) : base;
   }

   private Text resolveValueText(ScoreboardObjective objective, ScoreboardEntry entry) {
      Text formatted = entry.formatted(objective.getNumberFormatOr(StyledNumberFormat.EMPTY));
      if (formatted != null) {
         return formatted;
      }

      return Text.literal(Integer.toString(entry.value()));
   }

   private Team resolveTeam(Scoreboard scoreboard, String owner) {
      if (scoreboard == null || owner == null) {
         return null;
      }

      try {
         Method method = scoreboard.getClass().getMethod("getScoreHolderTeam", String.class);
         Object result = method.invoke(scoreboard, owner);
         if (result instanceof Team team) {
            return team;
         }
      } catch (Throwable ignored) {
      }

      try {
         Method method = scoreboard.getClass().getMethod("getPlayerTeam", String.class);
         Object result = method.invoke(scoreboard, owner);
         if (result instanceof Team team) {
            return team;
         }
      } catch (Throwable ignored) {
      }

      return null;
   }
}
ss
 

Вложения

  • Снимок экрана 2026-03-20 132205.png
    Снимок экрана 2026-03-20 132205.png
    54.4 KB · Просмотры: 332
Всем привет! Сливаю вам скорборд из своего клиента так как мне лень его дальше писать
LegacyTextHelper.java:
Expand Collapse Copy
package moscow.rockstar.utility.game;

import net.minecraft.text.MutableText;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.text.TextColor;
import net.minecraft.util.Formatting;
import java.util.ArrayList;
import java.util.List;

public final class LegacyTextHelper {
   private static final char LEGACY_PREFIX_SECTION = '\u00A7';
   private static final char LEGACY_PREFIX_AMP = '&';
   private static final char LEGACY_PREFIX_DOLLAR = '$';

   public static Text resolve(Text text, int defaultColor) {
      if (text == null) {
         return Text.empty();
      }

      int baseColor = defaultColor & 0xFFFFFF;
      String raw = extractRaw(text);
      if (containsLegacyCodes(raw)) {
         return parse(raw, baseColor);
      }

      if (hasExplicitColor(text)) {
         return text;
      }

      return applyDefaultColor(text, baseColor);
   }

   public static List<ColoredSegment> parseSegments(String input, int defaultColor) {
      List<ColoredSegment> segments = new ArrayList<>();
      if (input == null || input.isEmpty()) {
         return segments;
      }

      StringBuilder buffer = new StringBuilder();
      int currentColor = 0xFF000000 | (defaultColor & 0xFFFFFF);
      int baseColor = currentColor;
      int length = input.length();

      for (int i = 0; i < length; i++) {
         char c = input.charAt(i);
         if (c == '<' || c == '{') {
            char end = c == '<' ? '>' : '}';
            if (isWrappedHexSequence(input, i, end)) {
               flushSegment(buffer, segments, currentColor);
               int rgb = parseHexColorPlain(input, i + 2);
               currentColor = 0xFF000000 | rgb;
               i += 8;
               continue;
            }
         }

         if (isLegacyPrefix(c) && i + 1 < length) {
            char code = Character.toLowerCase(input.charAt(i + 1));
            if (code == 'x' && isValidHexSequence(input, i)) {
               flushSegment(buffer, segments, currentColor);
               int rgb = parseHexColorLegacy(input, i);
               currentColor = 0xFF000000 | rgb;
               i += 13;
               continue;
            }

            if (code == '#' && isHexSequence(input, i + 2, 6)) {
               flushSegment(buffer, segments, currentColor);
               int rgb = parseHexColorPlain(input, i + 2);
               currentColor = 0xFF000000 | rgb;
               i += 7;
               continue;
            }

            Formatting formatting = Formatting.byCode(code);
            if (formatting != null) {
               flushSegment(buffer, segments, currentColor);
               if (formatting == Formatting.RESET) {
                  currentColor = baseColor;
               } else if (formatting.isColor()) {
                  Integer rgb = formatting.getColorValue();
                  if (rgb != null) {
                     currentColor = 0xFF000000 | rgb;
                  }
               }
               i++;
               continue;
            }
         }

         buffer.append(c);
      }

      flushSegment(buffer, segments, currentColor);
      return segments;
   }

   public static String extractRaw(Text text) {
      if (text == null) {
         return "";
      }

      StringBuilder visited = new StringBuilder();
      text.visit((style, string) -> {
         if (style != null && style.getColor() != null) {
            int rgb = style.getColor().getRgb() & 0xFFFFFF;
            String hex = Integer.toHexString(rgb);
            visited.append("&#");
            for (int i = 0; i < 6 - hex.length(); i++) {
               visited.append('0');
            }
            visited.append(hex);
         }
         visited.append(string);
         return java.util.Optional.empty();
      }, Style.EMPTY);

      return visited.toString();
   }

   public static boolean containsLegacyCodes(String input) {
      if (input == null || input.length() < 2) {
         return false;
      }

      int length = input.length();
      for (int i = 0; i < length - 1; i++) {
         char c = input.charAt(i);
         if (c == '<' || c == '{') {
            if (isWrappedHexSequence(input, i, c == '<' ? '>' : '}')) {
               return true;
            }
            continue;
         }

         if (!isLegacyPrefix(c)) {
            continue;
         }

         char code = Character.toLowerCase(input.charAt(i + 1));
         if (isLegacyCode(code)) {
            return true;
         }

         if (code == 'x' && isValidHexSequence(input, i)) {
            return true;
         }

         if (code == '#' && isHexSequence(input, i + 2, 6)) {
            return true;
         }
      }

      return false;
   }

   public static Text parse(String input, int defaultColor) {
      if (input == null || input.isEmpty()) {
         return Text.empty();
      }

      MutableText result = Text.empty();
      StringBuilder buffer = new StringBuilder();
      int baseColor = defaultColor & 0xFFFFFF;
      Style current = Style.EMPTY.withColor(TextColor.fromRgb(baseColor));
      int length = input.length();

      for (int i = 0; i < length; i++) {
         char c = input.charAt(i);
         if (c == '<' || c == '{') {
            char end = c == '<' ? '>' : '}';
            if (isWrappedHexSequence(input, i, end)) {
               flush(buffer, result, current);
               int rgb = parseHexColorPlain(input, i + 2);
               current = current.withColor(TextColor.fromRgb(rgb));
               i += 8;
               continue;
            }
         }

         if (isLegacyPrefix(c) && i + 1 < length) {
            char code = Character.toLowerCase(input.charAt(i + 1));
            if (code == 'x' && isValidHexSequence(input, i)) {
               flush(buffer, result, current);
               int rgb = parseHexColorLegacy(input, i);
               current = current.withColor(TextColor.fromRgb(rgb));
               i += 13;
               continue;
            }

            if (code == '#' && isHexSequence(input, i + 2, 6)) {
               flush(buffer, result, current);
               int rgb = parseHexColorPlain(input, i + 2);
               current = current.withColor(TextColor.fromRgb(rgb));
               i += 7;
               continue;
            }

            Formatting formatting = Formatting.byCode(code);
            if (formatting != null) {
               flush(buffer, result, current);
               if (formatting == Formatting.RESET) {
                  current = Style.EMPTY.withColor(TextColor.fromRgb(baseColor));
               } else if (formatting.isColor()) {
                  current = Style.EMPTY.withFormatting(formatting);
               } else {
                  current = current.withFormatting(formatting);
               }
               i++;
               continue;
            }
         }

         buffer.append(c);
      }

      flush(buffer, result, current);
      return result;
   }

   private static void flush(StringBuilder buffer, MutableText result, Style style) {
      if (buffer.length() == 0) {
         return;
      }

      result.append(Text.literal(buffer.toString()).setStyle(style));
      buffer.setLength(0);
   }

   private static void flushSegment(StringBuilder buffer, List<ColoredSegment> segments, int color) {
      if (buffer.length() == 0) {
         return;
      }

      segments.add(new ColoredSegment(buffer.toString(), color));
      buffer.setLength(0);
   }

   private static boolean isLegacyPrefix(char c) {
      return c == LEGACY_PREFIX_SECTION || c == LEGACY_PREFIX_AMP || c == LEGACY_PREFIX_DOLLAR;
   }

   private static boolean isLegacyCode(char code) {
      return (code >= '0' && code <= '9')
            || (code >= 'a' && code <= 'f')
            || code == 'k'
            || code == 'l'
            || code == 'm'
            || code == 'n'
            || code == 'o'
            || code == 'r';
   }

   private static boolean isValidHexSequence(String input, int startIndex) {
      int length = input.length();
      if (startIndex + 13 >= length) {
         return false;
      }

      for (int i = 0; i < 6; i++) {
         int base = startIndex + 2 + i * 2;
         char prefix = input.charAt(base);
         char hex = input.charAt(base + 1);
         if (!isLegacyPrefix(prefix) || !isHexDigit(hex)) {
            return false;
         }
      }

      return true;
   }

   private static int parseHexColorLegacy(String input, int startIndex) {
      StringBuilder hex = new StringBuilder(6);
      for (int i = 0; i < 6; i++) {
         int base = startIndex + 2 + i * 2;
         hex.append(input.charAt(base + 1));
      }
      return Integer.parseInt(hex.toString(), 16);
   }

   private static int parseHexColorPlain(String input, int startIndex) {
      return Integer.parseInt(input.substring(startIndex, startIndex + 6), 16);
   }

   private static boolean isHexDigit(char c) {
      return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
   }

   private static boolean isHexSequence(String input, int start, int length) {
      if (start < 0 || start + length > input.length()) {
         return false;
      }
      for (int i = 0; i < length; i++) {
         if (!isHexDigit(input.charAt(start + i))) {
            return false;
         }
      }
      return true;
   }

   private static boolean isWrappedHexSequence(String input, int startIndex, char endChar) {
      if (startIndex + 8 >= input.length()) {
         return false;
      }
      if (input.charAt(startIndex + 1) != '#') {
         return false;
      }
      if (!isHexSequence(input, startIndex + 2, 6)) {
         return false;
      }
      return input.charAt(startIndex + 8) == endChar;
   }

   private static Text applyDefaultColor(Text text, int defaultColor) {
      if (hasExplicitColor(text))
         return text;
      Style base = Style.EMPTY.withColor(TextColor.fromRgb(defaultColor));
      return Text.literal("").setStyle(base).append(text);
   }

   private static boolean hasExplicitColor(Text text) {
      final boolean[] hasColor = new boolean[] { false };
      text.visit((style, string) -> {
         if (style.getColor() != null) {
            hasColor[0] = true;
            return java.util.Optional.of(true);
         }
         return java.util.Optional.empty();
      }, Style.EMPTY);
      return hasColor[0];
   }

   private LegacyTextHelper() {
      throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
   }

   public record ColoredSegment(String text, int color) {
   }
}
ScoreboardHud.java:
Expand Collapse Copy
package moscow.rockstar.ui.hud.impl;

import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.List;
import moscow.rockstar.framework.base.UIContext;
import moscow.rockstar.framework.msdf.Font;
import moscow.rockstar.framework.msdf.Fonts;
import moscow.rockstar.framework.msdf.FormattedTextProcessor;
import moscow.rockstar.framework.objects.BorderRadius;
import moscow.rockstar.Rockstar;
import moscow.rockstar.systems.modules.modules.visuals.Interface;
import moscow.rockstar.systems.theme.Theme;
import moscow.rockstar.ui.hud.HudElement;
import moscow.rockstar.utility.colors.ColorRGBA;
import moscow.rockstar.utility.colors.Colors;
import moscow.rockstar.framework.objects.gradient.impl.HorizontalGradient;
import moscow.rockstar.utility.game.LegacyTextHelper;
import moscow.rockstar.systems.setting.settings.SliderSetting;
import moscow.rockstar.utility.interfaces.IScaledResolution;
import net.minecraft.client.gui.screen.ChatScreen;
import net.minecraft.scoreboard.Scoreboard;
import net.minecraft.scoreboard.ScoreboardDisplaySlot;
import net.minecraft.scoreboard.ScoreboardEntry;
import net.minecraft.scoreboard.ScoreboardObjective;
import net.minecraft.scoreboard.Team;
import net.minecraft.scoreboard.number.StyledNumberFormat;
import net.minecraft.text.Text;

public class ScoreboardHud extends HudElement implements IScaledResolution {
   private static final float TITLE_SIZE = 8.8F;
   private static final float LINE_SIZE = 7.8F;
   private static final float PAD_X = 16.0F;
   private static final float PAD_Y = 7.0F;
   private static final float LINE_GAP = 5.0F;
   private static final float VALUE_GAP = 16.0F;
   private boolean initialPos;
   private float lastSavedX = Float.NaN;
   private float lastSavedY = Float.NaN;
   private final SliderSetting scaleSetting = new SliderSetting(this, "\u0420\u0430\u0437\u043c\u0435\u0440").min(0.7F)
         .max(1.4F).step(0.05F).currentValue(1.0F);

   public ScoreboardHud() {
      super("\u0421\u043a\u043e\u0440\u0431\u043e\u0440\u0434", "icons/hud/world.png");
   }

   @Override
   public void update(UIContext context) {
      Layout layout = buildLayout();
      this.width = layout.width;
      this.height = layout.height;
      if (!this.initialPos && this.x == 0.0F && this.y == 0.0F) {
         this.x = Math.max(0.0F, sr.getScaledWidth() - this.width - 40.0F);
         this.y = 40.0F;
         this.initialPos = true;
      }
      if (!this.isDragging() && (this.x != this.lastSavedX || this.y != this.lastSavedY)) {
         this.lastSavedX = this.x;
         this.lastSavedY = this.y;
         Rockstar.getInstance().getFileManager().writeFile("client");
      }
      super.update(context);
   }

   @Override
   protected void renderComponent(UIContext context) {
      Layout layout = buildLayout();
      float x = this.x;
      float y = this.y;
      Font titleFont = getTitleFont();
      Font lineFont = getLineFont();

      boolean dark = Rockstar.getInstance().getThemeManager().getCurrentTheme() == Theme.DARK;
      ColorRGBA bgColor = Colors.getBackgroundColor()
            .withAlpha(255.0F * (dark ? 0.8F - 0.6F * Interface.glass() : 0.7F));

      context.drawShadow(
            x - 5.0F,
            y - 5.0F,
            layout.width + 10.0F,
            layout.height + 10.0F,
            15.0F,
            BorderRadius.all(6.0F),
            ColorRGBA.BLACK.withAlpha(63.75F));

      if (Interface.showMinimalizm()) {
         context.drawBlurredRect(
               x,
               y,
               layout.width,
               layout.height,
               45.0F,
               7.0F,
               BorderRadius.all(6.0F),
               ColorRGBA.WHITE.withAlpha(255.0F * Interface.minimalizm()));
      }

      if (Interface.showGlass()) {
         context.drawLiquidGlass(
               x,
               y,
               layout.width,
               layout.height,
               7.0F,
               0.08F,
               BorderRadius.all(6.0F),
               ColorRGBA.WHITE.withAlpha(255.0F * Interface.glass()));
      }

      context.drawSquircle(x, y, layout.width, layout.height, 7.0F, BorderRadius.all(6.0F), bgColor);

      float lineH = Math.max(1.0F, 1.4F * layout.scale);
      ColorRGBA accent = Colors.ACCENT.withAlpha(170.0F);
      ColorRGBA secondary = Colors.ACCENT.withAlpha(50.0F);
      context.drawRoundedRect(x + layout.padX * 0.6F, y + layout.headerHeight - lineH - layout.padY * 0.3F,
            layout.width - layout.padX * 1.2F, lineH, BorderRadius.all(lineH / 2.0F),
            new HorizontalGradient(accent, secondary));

      float titleX = x + PAD_X;
      for (Segment segment : layout.title.segments) {
         context.drawText(titleFont, segment.text, titleX, y + PAD_Y, ColorRGBA.fromInt(segment.color));
         titleX += titleFont.width(segment.text);
      }

      float lineY = y + layout.headerHeight;
      for (EntryLine line : layout.lines) {
         float rowY = lineY + (layout.lineHeight - lineFont.height()) / 2.0F;
         float valueWidth = line.value.width;

         float nameX = x + PAD_X;
         for (Segment segment : line.name.segments) {
            context.drawText(lineFont, segment.text, nameX, rowY, ColorRGBA.fromInt(segment.color));
            nameX += lineFont.width(segment.text);
         }

         float valueX = x + layout.width - PAD_X - valueWidth;
         for (Segment segment : line.value.segments) {
            context.drawText(lineFont, segment.text, valueX, rowY, ColorRGBA.fromInt(segment.color));
            valueX += lineFont.width(segment.text);
         }

         lineY += layout.lineHeight;
      }
   }

   @Override
   public boolean show() {
      return getObjective() != null;
   }

   private Layout buildLayout() {
      int defaultTextColor = Colors.getTextColor().getRGB() & 0xFFFFFF;
      float scale = this.scaleSetting.getCurrentValue();
      Font titleFont = getTitleFont();
      Font lineFont = getLineFont();
      ScoreboardObjective objective = getObjective();

      if (objective == null) {
         return new Layout(new Segments(List.of(), 0), List.of(), 0, 0, 0, 0, scale, PAD_X, PAD_Y);
      }

      Text titleSource = objective.getDisplayName();
      Segments title = buildSegments(titleSource, titleFont, defaultTextColor);
      float maxWidth = title.width;

      List<EntryLine> lines = objective.getScoreboard().getScoreboardEntries(objective).stream()
            .filter(entry -> entry != null && !entry.hidden())
            .sorted(Comparator.comparingInt(ScoreboardEntry::value).reversed().thenComparing(ScoreboardEntry::owner,
                  String::compareToIgnoreCase))
            .limit(25)
            .map(entry -> {
               Segments name = buildSegments(resolveNameText(objective.getScoreboard(), entry), lineFont,
                     defaultTextColor);
               Segments value = buildSegments(resolveValueText(objective, entry), lineFont, defaultTextColor);
               return new EntryLine(name, value);
            })
            .toList();

      for (EntryLine line : lines) {
         float rowWidth = line.name.width + line.value.width + VALUE_GAP;
         maxWidth = Math.max(maxWidth, rowWidth);
      }

      float headerHeight = titleFont.height() + PAD_Y * 2.0F;
      float lineHeight = lineFont.height() + LINE_GAP;
      float totalHeight = headerHeight + lines.size() * lineHeight + PAD_Y;
      float extraWidth = 16.0F;
      float totalWidth = maxWidth + PAD_X * 2.0F + extraWidth;

      totalWidth *= scale;
      totalHeight *= scale;
      headerHeight *= scale;
      lineHeight *= scale;

      return new Layout(title, lines, headerHeight, lineHeight, totalWidth, totalHeight, scale, PAD_X, PAD_Y);
   }

   private Segments buildSegments(Text text, Font font, int defaultColor) {
      String raw = LegacyTextHelper.extractRaw(text);
      Text source = LegacyTextHelper.containsLegacyCodes(raw) ? LegacyTextHelper.parse(raw, defaultColor) : text;
      boolean hasLegacy = LegacyTextHelper.containsLegacyCodes(raw);
      List<Segment> segments = hasLegacy
            ? LegacyTextHelper.parseSegments(raw, defaultColor).stream()
                  .map(segment -> new Segment(segment.text(), segment.color())).toList()
            : FormattedTextProcessor.processText(source, defaultColor).stream()
                  .map(segment -> new Segment(segment.text, segment.color)).toList();

      float width = 0.0F;
      List<Segment> sanitized = new java.util.ArrayList<>();
      for (Segment segment : segments) {
         String cleaned = segment.text;
         if (!cleaned.isEmpty()) {
            sanitized.add(new Segment(cleaned, segment.color));
            width += font.width(cleaned);
         }
      }

      return new Segments(sanitized, width);
   }

   private Font getTitleFont() {
      return Fonts.MEDIUM.getFont(TITLE_SIZE * this.scaleSetting.getCurrentValue());
   }

   private Font getLineFont() {
      return Fonts.REGULAR.getFont(LINE_SIZE * this.scaleSetting.getCurrentValue());
   }

   private ScoreboardObjective getObjective() {
      if (mc.world == null) {
         return null;
      }
      return mc.world.getScoreboard().getObjectiveForSlot(ScoreboardDisplaySlot.SIDEBAR);
   }

   private Text resolveNameText(Scoreboard scoreboard, ScoreboardEntry entry) {
      Text display = entry.display();
      if (display != null) {
         return display;
      }

      String owner = entry.owner();
      if (owner == null) {
         return Text.empty();
      }

      Team team = resolveTeam(scoreboard, owner);
      Text base = Text.literal(owner);
      return team != null ? Team.decorateName(team, base) : base;
   }

   private Text resolveValueText(ScoreboardObjective objective, ScoreboardEntry entry) {
      Text formatted = entry.formatted(objective.getNumberFormatOr(StyledNumberFormat.EMPTY));
      if (formatted != null) {
         return formatted;
      }

      return Text.literal(Integer.toString(entry.value()));
   }

   private Team resolveTeam(Scoreboard scoreboard, String owner) {
      if (scoreboard == null || owner == null) {
         return null;
      }
      return scoreboard.getScoreHolderTeam(owner);
   }

   private record Segment(String text, int color) {
   }

   private record Segments(List<Segment> segments, float width) {
   }

   private record EntryLine(Segments name, Segments value) {
   }

   private record Layout(Segments title, List<EntryLine> lines, float headerHeight, float lineHeight, float width,
         float height, float scale, float padX, float padY) {
   }
}

ScoreboardOverlay.java:
Expand Collapse Copy
package moscow.rockstar.ui.overlay;

import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.List;
import moscow.rockstar.framework.base.CustomDrawContext;
import moscow.rockstar.framework.msdf.Font;
import moscow.rockstar.framework.msdf.Fonts;
import moscow.rockstar.framework.objects.BorderRadius;
import moscow.rockstar.utility.colors.ColorRGBA;
import moscow.rockstar.utility.colors.Colors;
import moscow.rockstar.utility.game.LegacyTextHelper;
import moscow.rockstar.utility.gui.GuiUtility;
import moscow.rockstar.utility.interfaces.IMinecraft;
import moscow.rockstar.utility.interfaces.IScaledResolution;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.scoreboard.Scoreboard;
import net.minecraft.scoreboard.ScoreboardEntry;
import net.minecraft.scoreboard.ScoreboardObjective;
import net.minecraft.scoreboard.Team;
import net.minecraft.scoreboard.number.StyledNumberFormat;
import net.minecraft.text.Text;
import org.lwjgl.glfw.GLFW;

public class ScoreboardOverlay implements IMinecraft, IScaledResolution {
   private static final ScoreboardOverlay INSTANCE = new ScoreboardOverlay();
   private float x = -1.0F;
   private float y = -1.0F;
   private float width;
   private float height;
   private boolean dragging;
   private float dragOffsetX;
   private float dragOffsetY;
   private boolean wasMouseDown;

   public static ScoreboardOverlay getInstance() {
      return INSTANCE;
   }

   public void render(DrawContext context, ScoreboardObjective objective) {
      if (objective == null || mc.player == null || mc.world == null) {
         return;
      }

      Scoreboard scoreboard = objective.getScoreboard();
      List<ScoreboardEntry> entries = scoreboard.getScoreboardEntries(objective).stream()
         .filter(entry -> entry != null && !entry.hidden())
         .sorted(Comparator.comparingInt(ScoreboardEntry::value).reversed().thenComparing(ScoreboardEntry::owner, String::compareToIgnoreCase))
         .limit(15)
         .toList();

      Font titleFont = Fonts.MEDIUM.getFont(8.5F);
      Font lineFont = Fonts.REGULAR.getFont(7.5F);
      float padX = 10.0F;
      float padY = 6.0F;
      float lineGap = 4.0F;
      float valueGap = 10.0F;
      float shadowExpand = 5.0F;

      int defaultTextColor = Colors.getTextColor().getRGB() & 0xFFFFFF;
      Text title = LegacyTextHelper.resolve(objective.getDisplayName(), defaultTextColor);
      float maxWidth = titleFont.width(title);

      for (ScoreboardEntry entry : entries) {
         Text nameText = LegacyTextHelper.resolve(resolveNameText(scoreboard, entry), defaultTextColor);
         Text valueText = LegacyTextHelper.resolve(resolveValueText(objective, entry), defaultTextColor);
         float rowWidth = lineFont.width(nameText) + lineFont.width(valueText) + valueGap;
         maxWidth = Math.max(maxWidth, rowWidth);
      }

      float headerHeight = titleFont.height() + padY * 2.0F;
      float lineHeight = lineFont.height() + lineGap;
      float totalHeight = headerHeight + entries.size() * lineHeight + padY;
      float totalWidth = maxWidth + padX * 2.0F + 12.0F;

      updateDrag(totalWidth, totalHeight);
      float x = this.x;
      float y = this.y;
      this.width = totalWidth;
      this.height = totalHeight;

      CustomDrawContext drawContext = CustomDrawContext.of(context);
      drawContext.drawShadow(
         x - shadowExpand,
         y - shadowExpand,
         totalWidth + shadowExpand * 2.0F,
         totalHeight + shadowExpand * 2.0F,
         15.0F,
         BorderRadius.all(6.0F),
         ColorRGBA.BLACK.withAlpha(60.0F)
      );
      drawContext.drawClientRect(x, y, totalWidth, totalHeight, 1.0F, 0.0F, 7.0F);
      drawContext.drawRect(x, y + headerHeight - 1.0F, totalWidth, 1.0F, Colors.getSeparatorColor());

      float titleY = y + padY;
      drawContext.drawText(titleFont, title, x + padX, titleY);

      float lineY = y + headerHeight;
      for (ScoreboardEntry entry : entries) {
         Text nameText = LegacyTextHelper.resolve(resolveNameText(scoreboard, entry), defaultTextColor);
         Text valueText = LegacyTextHelper.resolve(resolveValueText(objective, entry), defaultTextColor);

         float rowY = lineY + (lineHeight - lineFont.height()) / 2.0F;
         float valueWidth = lineFont.width(valueText);

         drawContext.drawText(lineFont, nameText, x + padX, rowY);
         drawContext.drawText(lineFont, valueText, x + totalWidth - padX - valueWidth, rowY);

         lineY += lineHeight;
      }
   }

   private void updateDrag(float targetWidth, float targetHeight) {
      float maxX = Math.max(0.0F, sr.getScaledWidth() - targetWidth);
      float maxY = Math.max(0.0F, sr.getScaledHeight() - targetHeight);

      if (this.x < 0.0F || this.y < 0.0F) {
         this.x = maxX;
         this.y = 6.0F;
      }

      this.x = Math.max(0.0F, Math.min(this.x, maxX));
      this.y = Math.max(0.0F, Math.min(this.y, maxY));

      boolean mouseDown = GLFW.glfwGetMouseButton(mc.getWindow().getHandle(), 0) == 1;
      var mouse = GuiUtility.getMouse();
      float mouseX = mouse.getX();
      float mouseY = mouse.getY();
      if (mouseDown && !this.wasMouseDown && isHovered(mouseX, mouseY, targetWidth, targetHeight)) {
         this.dragging = true;
         this.dragOffsetX = mouseX - this.x;
         this.dragOffsetY = mouseY - this.y;
      }

      if (!mouseDown) {
         this.dragging = false;
      }

      if (this.dragging) {
         this.x = Math.max(0.0F, Math.min(mouseX - this.dragOffsetX, maxX));
         this.y = Math.max(0.0F, Math.min(mouseY - this.dragOffsetY, maxY));
      }

      this.wasMouseDown = mouseDown;
   }

   private boolean isHovered(float mouseX, float mouseY, float targetWidth, float targetHeight) {
      return mouseX >= this.x && mouseX < this.x + targetWidth && mouseY >= this.y && mouseY < this.y + targetHeight;
   }

   private Text resolveNameText(Scoreboard scoreboard, ScoreboardEntry entry) {
      Text display = entry.display();
      if (display != null) {
         return display;
      }

      String owner = entry.owner();
      if (owner == null) {
         return Text.empty();
      }

      Team team = resolveTeam(scoreboard, owner);
      Text base = Text.literal(owner);
      return team != null ? Team.decorateName(team, base) : base;
   }

   private Text resolveValueText(ScoreboardObjective objective, ScoreboardEntry entry) {
      Text formatted = entry.formatted(objective.getNumberFormatOr(StyledNumberFormat.EMPTY));
      if (formatted != null) {
         return formatted;
      }

      return Text.literal(Integer.toString(entry.value()));
   }

   private Team resolveTeam(Scoreboard scoreboard, String owner) {
      if (scoreboard == null || owner == null) {
         return null;
      }

      try {
         Method method = scoreboard.getClass().getMethod("getScoreHolderTeam", String.class);
         Object result = method.invoke(scoreboard, owner);
         if (result instanceof Team team) {
            return team;
         }
      } catch (Throwable ignored) {
      }

      try {
         Method method = scoreboard.getClass().getMethod("getPlayerTeam", String.class);
         Object result = method.invoke(scoreboard, owner);
         if (result instanceof Team team) {
            return team;
         }
      } catch (Throwable ignored) {
      }

      return null;
   }
}
ss
нармас
 
Всем привет! Сливаю вам скорборд из своего клиента так как мне лень его дальше писать
LegacyTextHelper.java:
Expand Collapse Copy
package moscow.rockstar.utility.game;

import net.minecraft.text.MutableText;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.text.TextColor;
import net.minecraft.util.Formatting;
import java.util.ArrayList;
import java.util.List;

public final class LegacyTextHelper {
   private static final char LEGACY_PREFIX_SECTION = '\u00A7';
   private static final char LEGACY_PREFIX_AMP = '&';
   private static final char LEGACY_PREFIX_DOLLAR = '$';

   public static Text resolve(Text text, int defaultColor) {
      if (text == null) {
         return Text.empty();
      }

      int baseColor = defaultColor & 0xFFFFFF;
      String raw = extractRaw(text);
      if (containsLegacyCodes(raw)) {
         return parse(raw, baseColor);
      }

      if (hasExplicitColor(text)) {
         return text;
      }

      return applyDefaultColor(text, baseColor);
   }

   public static List<ColoredSegment> parseSegments(String input, int defaultColor) {
      List<ColoredSegment> segments = new ArrayList<>();
      if (input == null || input.isEmpty()) {
         return segments;
      }

      StringBuilder buffer = new StringBuilder();
      int currentColor = 0xFF000000 | (defaultColor & 0xFFFFFF);
      int baseColor = currentColor;
      int length = input.length();

      for (int i = 0; i < length; i++) {
         char c = input.charAt(i);
         if (c == '<' || c == '{') {
            char end = c == '<' ? '>' : '}';
            if (isWrappedHexSequence(input, i, end)) {
               flushSegment(buffer, segments, currentColor);
               int rgb = parseHexColorPlain(input, i + 2);
               currentColor = 0xFF000000 | rgb;
               i += 8;
               continue;
            }
         }

         if (isLegacyPrefix(c) && i + 1 < length) {
            char code = Character.toLowerCase(input.charAt(i + 1));
            if (code == 'x' && isValidHexSequence(input, i)) {
               flushSegment(buffer, segments, currentColor);
               int rgb = parseHexColorLegacy(input, i);
               currentColor = 0xFF000000 | rgb;
               i += 13;
               continue;
            }

            if (code == '#' && isHexSequence(input, i + 2, 6)) {
               flushSegment(buffer, segments, currentColor);
               int rgb = parseHexColorPlain(input, i + 2);
               currentColor = 0xFF000000 | rgb;
               i += 7;
               continue;
            }

            Formatting formatting = Formatting.byCode(code);
            if (formatting != null) {
               flushSegment(buffer, segments, currentColor);
               if (formatting == Formatting.RESET) {
                  currentColor = baseColor;
               } else if (formatting.isColor()) {
                  Integer rgb = formatting.getColorValue();
                  if (rgb != null) {
                     currentColor = 0xFF000000 | rgb;
                  }
               }
               i++;
               continue;
            }
         }

         buffer.append(c);
      }

      flushSegment(buffer, segments, currentColor);
      return segments;
   }

   public static String extractRaw(Text text) {
      if (text == null) {
         return "";
      }

      StringBuilder visited = new StringBuilder();
      text.visit((style, string) -> {
         if (style != null && style.getColor() != null) {
            int rgb = style.getColor().getRgb() & 0xFFFFFF;
            String hex = Integer.toHexString(rgb);
            visited.append("&#");
            for (int i = 0; i < 6 - hex.length(); i++) {
               visited.append('0');
            }
            visited.append(hex);
         }
         visited.append(string);
         return java.util.Optional.empty();
      }, Style.EMPTY);

      return visited.toString();
   }

   public static boolean containsLegacyCodes(String input) {
      if (input == null || input.length() < 2) {
         return false;
      }

      int length = input.length();
      for (int i = 0; i < length - 1; i++) {
         char c = input.charAt(i);
         if (c == '<' || c == '{') {
            if (isWrappedHexSequence(input, i, c == '<' ? '>' : '}')) {
               return true;
            }
            continue;
         }

         if (!isLegacyPrefix(c)) {
            continue;
         }

         char code = Character.toLowerCase(input.charAt(i + 1));
         if (isLegacyCode(code)) {
            return true;
         }

         if (code == 'x' && isValidHexSequence(input, i)) {
            return true;
         }

         if (code == '#' && isHexSequence(input, i + 2, 6)) {
            return true;
         }
      }

      return false;
   }

   public static Text parse(String input, int defaultColor) {
      if (input == null || input.isEmpty()) {
         return Text.empty();
      }

      MutableText result = Text.empty();
      StringBuilder buffer = new StringBuilder();
      int baseColor = defaultColor & 0xFFFFFF;
      Style current = Style.EMPTY.withColor(TextColor.fromRgb(baseColor));
      int length = input.length();

      for (int i = 0; i < length; i++) {
         char c = input.charAt(i);
         if (c == '<' || c == '{') {
            char end = c == '<' ? '>' : '}';
            if (isWrappedHexSequence(input, i, end)) {
               flush(buffer, result, current);
               int rgb = parseHexColorPlain(input, i + 2);
               current = current.withColor(TextColor.fromRgb(rgb));
               i += 8;
               continue;
            }
         }

         if (isLegacyPrefix(c) && i + 1 < length) {
            char code = Character.toLowerCase(input.charAt(i + 1));
            if (code == 'x' && isValidHexSequence(input, i)) {
               flush(buffer, result, current);
               int rgb = parseHexColorLegacy(input, i);
               current = current.withColor(TextColor.fromRgb(rgb));
               i += 13;
               continue;
            }

            if (code == '#' && isHexSequence(input, i + 2, 6)) {
               flush(buffer, result, current);
               int rgb = parseHexColorPlain(input, i + 2);
               current = current.withColor(TextColor.fromRgb(rgb));
               i += 7;
               continue;
            }

            Formatting formatting = Formatting.byCode(code);
            if (formatting != null) {
               flush(buffer, result, current);
               if (formatting == Formatting.RESET) {
                  current = Style.EMPTY.withColor(TextColor.fromRgb(baseColor));
               } else if (formatting.isColor()) {
                  current = Style.EMPTY.withFormatting(formatting);
               } else {
                  current = current.withFormatting(formatting);
               }
               i++;
               continue;
            }
         }

         buffer.append(c);
      }

      flush(buffer, result, current);
      return result;
   }

   private static void flush(StringBuilder buffer, MutableText result, Style style) {
      if (buffer.length() == 0) {
         return;
      }

      result.append(Text.literal(buffer.toString()).setStyle(style));
      buffer.setLength(0);
   }

   private static void flushSegment(StringBuilder buffer, List<ColoredSegment> segments, int color) {
      if (buffer.length() == 0) {
         return;
      }

      segments.add(new ColoredSegment(buffer.toString(), color));
      buffer.setLength(0);
   }

   private static boolean isLegacyPrefix(char c) {
      return c == LEGACY_PREFIX_SECTION || c == LEGACY_PREFIX_AMP || c == LEGACY_PREFIX_DOLLAR;
   }

   private static boolean isLegacyCode(char code) {
      return (code >= '0' && code <= '9')
            || (code >= 'a' && code <= 'f')
            || code == 'k'
            || code == 'l'
            || code == 'm'
            || code == 'n'
            || code == 'o'
            || code == 'r';
   }

   private static boolean isValidHexSequence(String input, int startIndex) {
      int length = input.length();
      if (startIndex + 13 >= length) {
         return false;
      }

      for (int i = 0; i < 6; i++) {
         int base = startIndex + 2 + i * 2;
         char prefix = input.charAt(base);
         char hex = input.charAt(base + 1);
         if (!isLegacyPrefix(prefix) || !isHexDigit(hex)) {
            return false;
         }
      }

      return true;
   }

   private static int parseHexColorLegacy(String input, int startIndex) {
      StringBuilder hex = new StringBuilder(6);
      for (int i = 0; i < 6; i++) {
         int base = startIndex + 2 + i * 2;
         hex.append(input.charAt(base + 1));
      }
      return Integer.parseInt(hex.toString(), 16);
   }

   private static int parseHexColorPlain(String input, int startIndex) {
      return Integer.parseInt(input.substring(startIndex, startIndex + 6), 16);
   }

   private static boolean isHexDigit(char c) {
      return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
   }

   private static boolean isHexSequence(String input, int start, int length) {
      if (start < 0 || start + length > input.length()) {
         return false;
      }
      for (int i = 0; i < length; i++) {
         if (!isHexDigit(input.charAt(start + i))) {
            return false;
         }
      }
      return true;
   }

   private static boolean isWrappedHexSequence(String input, int startIndex, char endChar) {
      if (startIndex + 8 >= input.length()) {
         return false;
      }
      if (input.charAt(startIndex + 1) != '#') {
         return false;
      }
      if (!isHexSequence(input, startIndex + 2, 6)) {
         return false;
      }
      return input.charAt(startIndex + 8) == endChar;
   }

   private static Text applyDefaultColor(Text text, int defaultColor) {
      if (hasExplicitColor(text))
         return text;
      Style base = Style.EMPTY.withColor(TextColor.fromRgb(defaultColor));
      return Text.literal("").setStyle(base).append(text);
   }

   private static boolean hasExplicitColor(Text text) {
      final boolean[] hasColor = new boolean[] { false };
      text.visit((style, string) -> {
         if (style.getColor() != null) {
            hasColor[0] = true;
            return java.util.Optional.of(true);
         }
         return java.util.Optional.empty();
      }, Style.EMPTY);
      return hasColor[0];
   }

   private LegacyTextHelper() {
      throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
   }

   public record ColoredSegment(String text, int color) {
   }
}
ScoreboardHud.java:
Expand Collapse Copy
package moscow.rockstar.ui.hud.impl;

import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.List;
import moscow.rockstar.framework.base.UIContext;
import moscow.rockstar.framework.msdf.Font;
import moscow.rockstar.framework.msdf.Fonts;
import moscow.rockstar.framework.msdf.FormattedTextProcessor;
import moscow.rockstar.framework.objects.BorderRadius;
import moscow.rockstar.Rockstar;
import moscow.rockstar.systems.modules.modules.visuals.Interface;
import moscow.rockstar.systems.theme.Theme;
import moscow.rockstar.ui.hud.HudElement;
import moscow.rockstar.utility.colors.ColorRGBA;
import moscow.rockstar.utility.colors.Colors;
import moscow.rockstar.framework.objects.gradient.impl.HorizontalGradient;
import moscow.rockstar.utility.game.LegacyTextHelper;
import moscow.rockstar.systems.setting.settings.SliderSetting;
import moscow.rockstar.utility.interfaces.IScaledResolution;
import net.minecraft.client.gui.screen.ChatScreen;
import net.minecraft.scoreboard.Scoreboard;
import net.minecraft.scoreboard.ScoreboardDisplaySlot;
import net.minecraft.scoreboard.ScoreboardEntry;
import net.minecraft.scoreboard.ScoreboardObjective;
import net.minecraft.scoreboard.Team;
import net.minecraft.scoreboard.number.StyledNumberFormat;
import net.minecraft.text.Text;

public class ScoreboardHud extends HudElement implements IScaledResolution {
   private static final float TITLE_SIZE = 8.8F;
   private static final float LINE_SIZE = 7.8F;
   private static final float PAD_X = 16.0F;
   private static final float PAD_Y = 7.0F;
   private static final float LINE_GAP = 5.0F;
   private static final float VALUE_GAP = 16.0F;
   private boolean initialPos;
   private float lastSavedX = Float.NaN;
   private float lastSavedY = Float.NaN;
   private final SliderSetting scaleSetting = new SliderSetting(this, "\u0420\u0430\u0437\u043c\u0435\u0440").min(0.7F)
         .max(1.4F).step(0.05F).currentValue(1.0F);

   public ScoreboardHud() {
      super("\u0421\u043a\u043e\u0440\u0431\u043e\u0440\u0434", "icons/hud/world.png");
   }

   @Override
   public void update(UIContext context) {
      Layout layout = buildLayout();
      this.width = layout.width;
      this.height = layout.height;
      if (!this.initialPos && this.x == 0.0F && this.y == 0.0F) {
         this.x = Math.max(0.0F, sr.getScaledWidth() - this.width - 40.0F);
         this.y = 40.0F;
         this.initialPos = true;
      }
      if (!this.isDragging() && (this.x != this.lastSavedX || this.y != this.lastSavedY)) {
         this.lastSavedX = this.x;
         this.lastSavedY = this.y;
         Rockstar.getInstance().getFileManager().writeFile("client");
      }
      super.update(context);
   }

   @Override
   protected void renderComponent(UIContext context) {
      Layout layout = buildLayout();
      float x = this.x;
      float y = this.y;
      Font titleFont = getTitleFont();
      Font lineFont = getLineFont();

      boolean dark = Rockstar.getInstance().getThemeManager().getCurrentTheme() == Theme.DARK;
      ColorRGBA bgColor = Colors.getBackgroundColor()
            .withAlpha(255.0F * (dark ? 0.8F - 0.6F * Interface.glass() : 0.7F));

      context.drawShadow(
            x - 5.0F,
            y - 5.0F,
            layout.width + 10.0F,
            layout.height + 10.0F,
            15.0F,
            BorderRadius.all(6.0F),
            ColorRGBA.BLACK.withAlpha(63.75F));

      if (Interface.showMinimalizm()) {
         context.drawBlurredRect(
               x,
               y,
               layout.width,
               layout.height,
               45.0F,
               7.0F,
               BorderRadius.all(6.0F),
               ColorRGBA.WHITE.withAlpha(255.0F * Interface.minimalizm()));
      }

      if (Interface.showGlass()) {
         context.drawLiquidGlass(
               x,
               y,
               layout.width,
               layout.height,
               7.0F,
               0.08F,
               BorderRadius.all(6.0F),
               ColorRGBA.WHITE.withAlpha(255.0F * Interface.glass()));
      }

      context.drawSquircle(x, y, layout.width, layout.height, 7.0F, BorderRadius.all(6.0F), bgColor);

      float lineH = Math.max(1.0F, 1.4F * layout.scale);
      ColorRGBA accent = Colors.ACCENT.withAlpha(170.0F);
      ColorRGBA secondary = Colors.ACCENT.withAlpha(50.0F);
      context.drawRoundedRect(x + layout.padX * 0.6F, y + layout.headerHeight - lineH - layout.padY * 0.3F,
            layout.width - layout.padX * 1.2F, lineH, BorderRadius.all(lineH / 2.0F),
            new HorizontalGradient(accent, secondary));

      float titleX = x + PAD_X;
      for (Segment segment : layout.title.segments) {
         context.drawText(titleFont, segment.text, titleX, y + PAD_Y, ColorRGBA.fromInt(segment.color));
         titleX += titleFont.width(segment.text);
      }

      float lineY = y + layout.headerHeight;
      for (EntryLine line : layout.lines) {
         float rowY = lineY + (layout.lineHeight - lineFont.height()) / 2.0F;
         float valueWidth = line.value.width;

         float nameX = x + PAD_X;
         for (Segment segment : line.name.segments) {
            context.drawText(lineFont, segment.text, nameX, rowY, ColorRGBA.fromInt(segment.color));
            nameX += lineFont.width(segment.text);
         }

         float valueX = x + layout.width - PAD_X - valueWidth;
         for (Segment segment : line.value.segments) {
            context.drawText(lineFont, segment.text, valueX, rowY, ColorRGBA.fromInt(segment.color));
            valueX += lineFont.width(segment.text);
         }

         lineY += layout.lineHeight;
      }
   }

   @Override
   public boolean show() {
      return getObjective() != null;
   }

   private Layout buildLayout() {
      int defaultTextColor = Colors.getTextColor().getRGB() & 0xFFFFFF;
      float scale = this.scaleSetting.getCurrentValue();
      Font titleFont = getTitleFont();
      Font lineFont = getLineFont();
      ScoreboardObjective objective = getObjective();

      if (objective == null) {
         return new Layout(new Segments(List.of(), 0), List.of(), 0, 0, 0, 0, scale, PAD_X, PAD_Y);
      }

      Text titleSource = objective.getDisplayName();
      Segments title = buildSegments(titleSource, titleFont, defaultTextColor);
      float maxWidth = title.width;

      List<EntryLine> lines = objective.getScoreboard().getScoreboardEntries(objective).stream()
            .filter(entry -> entry != null && !entry.hidden())
            .sorted(Comparator.comparingInt(ScoreboardEntry::value).reversed().thenComparing(ScoreboardEntry::owner,
                  String::compareToIgnoreCase))
            .limit(25)
            .map(entry -> {
               Segments name = buildSegments(resolveNameText(objective.getScoreboard(), entry), lineFont,
                     defaultTextColor);
               Segments value = buildSegments(resolveValueText(objective, entry), lineFont, defaultTextColor);
               return new EntryLine(name, value);
            })
            .toList();

      for (EntryLine line : lines) {
         float rowWidth = line.name.width + line.value.width + VALUE_GAP;
         maxWidth = Math.max(maxWidth, rowWidth);
      }

      float headerHeight = titleFont.height() + PAD_Y * 2.0F;
      float lineHeight = lineFont.height() + LINE_GAP;
      float totalHeight = headerHeight + lines.size() * lineHeight + PAD_Y;
      float extraWidth = 16.0F;
      float totalWidth = maxWidth + PAD_X * 2.0F + extraWidth;

      totalWidth *= scale;
      totalHeight *= scale;
      headerHeight *= scale;
      lineHeight *= scale;

      return new Layout(title, lines, headerHeight, lineHeight, totalWidth, totalHeight, scale, PAD_X, PAD_Y);
   }

   private Segments buildSegments(Text text, Font font, int defaultColor) {
      String raw = LegacyTextHelper.extractRaw(text);
      Text source = LegacyTextHelper.containsLegacyCodes(raw) ? LegacyTextHelper.parse(raw, defaultColor) : text;
      boolean hasLegacy = LegacyTextHelper.containsLegacyCodes(raw);
      List<Segment> segments = hasLegacy
            ? LegacyTextHelper.parseSegments(raw, defaultColor).stream()
                  .map(segment -> new Segment(segment.text(), segment.color())).toList()
            : FormattedTextProcessor.processText(source, defaultColor).stream()
                  .map(segment -> new Segment(segment.text, segment.color)).toList();

      float width = 0.0F;
      List<Segment> sanitized = new java.util.ArrayList<>();
      for (Segment segment : segments) {
         String cleaned = segment.text;
         if (!cleaned.isEmpty()) {
            sanitized.add(new Segment(cleaned, segment.color));
            width += font.width(cleaned);
         }
      }

      return new Segments(sanitized, width);
   }

   private Font getTitleFont() {
      return Fonts.MEDIUM.getFont(TITLE_SIZE * this.scaleSetting.getCurrentValue());
   }

   private Font getLineFont() {
      return Fonts.REGULAR.getFont(LINE_SIZE * this.scaleSetting.getCurrentValue());
   }

   private ScoreboardObjective getObjective() {
      if (mc.world == null) {
         return null;
      }
      return mc.world.getScoreboard().getObjectiveForSlot(ScoreboardDisplaySlot.SIDEBAR);
   }

   private Text resolveNameText(Scoreboard scoreboard, ScoreboardEntry entry) {
      Text display = entry.display();
      if (display != null) {
         return display;
      }

      String owner = entry.owner();
      if (owner == null) {
         return Text.empty();
      }

      Team team = resolveTeam(scoreboard, owner);
      Text base = Text.literal(owner);
      return team != null ? Team.decorateName(team, base) : base;
   }

   private Text resolveValueText(ScoreboardObjective objective, ScoreboardEntry entry) {
      Text formatted = entry.formatted(objective.getNumberFormatOr(StyledNumberFormat.EMPTY));
      if (formatted != null) {
         return formatted;
      }

      return Text.literal(Integer.toString(entry.value()));
   }

   private Team resolveTeam(Scoreboard scoreboard, String owner) {
      if (scoreboard == null || owner == null) {
         return null;
      }
      return scoreboard.getScoreHolderTeam(owner);
   }

   private record Segment(String text, int color) {
   }

   private record Segments(List<Segment> segments, float width) {
   }

   private record EntryLine(Segments name, Segments value) {
   }

   private record Layout(Segments title, List<EntryLine> lines, float headerHeight, float lineHeight, float width,
         float height, float scale, float padX, float padY) {
   }
}

ScoreboardOverlay.java:
Expand Collapse Copy
package moscow.rockstar.ui.overlay;

import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.List;
import moscow.rockstar.framework.base.CustomDrawContext;
import moscow.rockstar.framework.msdf.Font;
import moscow.rockstar.framework.msdf.Fonts;
import moscow.rockstar.framework.objects.BorderRadius;
import moscow.rockstar.utility.colors.ColorRGBA;
import moscow.rockstar.utility.colors.Colors;
import moscow.rockstar.utility.game.LegacyTextHelper;
import moscow.rockstar.utility.gui.GuiUtility;
import moscow.rockstar.utility.interfaces.IMinecraft;
import moscow.rockstar.utility.interfaces.IScaledResolution;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.scoreboard.Scoreboard;
import net.minecraft.scoreboard.ScoreboardEntry;
import net.minecraft.scoreboard.ScoreboardObjective;
import net.minecraft.scoreboard.Team;
import net.minecraft.scoreboard.number.StyledNumberFormat;
import net.minecraft.text.Text;
import org.lwjgl.glfw.GLFW;

public class ScoreboardOverlay implements IMinecraft, IScaledResolution {
   private static final ScoreboardOverlay INSTANCE = new ScoreboardOverlay();
   private float x = -1.0F;
   private float y = -1.0F;
   private float width;
   private float height;
   private boolean dragging;
   private float dragOffsetX;
   private float dragOffsetY;
   private boolean wasMouseDown;

   public static ScoreboardOverlay getInstance() {
      return INSTANCE;
   }

   public void render(DrawContext context, ScoreboardObjective objective) {
      if (objective == null || mc.player == null || mc.world == null) {
         return;
      }

      Scoreboard scoreboard = objective.getScoreboard();
      List<ScoreboardEntry> entries = scoreboard.getScoreboardEntries(objective).stream()
         .filter(entry -> entry != null && !entry.hidden())
         .sorted(Comparator.comparingInt(ScoreboardEntry::value).reversed().thenComparing(ScoreboardEntry::owner, String::compareToIgnoreCase))
         .limit(15)
         .toList();

      Font titleFont = Fonts.MEDIUM.getFont(8.5F);
      Font lineFont = Fonts.REGULAR.getFont(7.5F);
      float padX = 10.0F;
      float padY = 6.0F;
      float lineGap = 4.0F;
      float valueGap = 10.0F;
      float shadowExpand = 5.0F;

      int defaultTextColor = Colors.getTextColor().getRGB() & 0xFFFFFF;
      Text title = LegacyTextHelper.resolve(objective.getDisplayName(), defaultTextColor);
      float maxWidth = titleFont.width(title);

      for (ScoreboardEntry entry : entries) {
         Text nameText = LegacyTextHelper.resolve(resolveNameText(scoreboard, entry), defaultTextColor);
         Text valueText = LegacyTextHelper.resolve(resolveValueText(objective, entry), defaultTextColor);
         float rowWidth = lineFont.width(nameText) + lineFont.width(valueText) + valueGap;
         maxWidth = Math.max(maxWidth, rowWidth);
      }

      float headerHeight = titleFont.height() + padY * 2.0F;
      float lineHeight = lineFont.height() + lineGap;
      float totalHeight = headerHeight + entries.size() * lineHeight + padY;
      float totalWidth = maxWidth + padX * 2.0F + 12.0F;

      updateDrag(totalWidth, totalHeight);
      float x = this.x;
      float y = this.y;
      this.width = totalWidth;
      this.height = totalHeight;

      CustomDrawContext drawContext = CustomDrawContext.of(context);
      drawContext.drawShadow(
         x - shadowExpand,
         y - shadowExpand,
         totalWidth + shadowExpand * 2.0F,
         totalHeight + shadowExpand * 2.0F,
         15.0F,
         BorderRadius.all(6.0F),
         ColorRGBA.BLACK.withAlpha(60.0F)
      );
      drawContext.drawClientRect(x, y, totalWidth, totalHeight, 1.0F, 0.0F, 7.0F);
      drawContext.drawRect(x, y + headerHeight - 1.0F, totalWidth, 1.0F, Colors.getSeparatorColor());

      float titleY = y + padY;
      drawContext.drawText(titleFont, title, x + padX, titleY);

      float lineY = y + headerHeight;
      for (ScoreboardEntry entry : entries) {
         Text nameText = LegacyTextHelper.resolve(resolveNameText(scoreboard, entry), defaultTextColor);
         Text valueText = LegacyTextHelper.resolve(resolveValueText(objective, entry), defaultTextColor);

         float rowY = lineY + (lineHeight - lineFont.height()) / 2.0F;
         float valueWidth = lineFont.width(valueText);

         drawContext.drawText(lineFont, nameText, x + padX, rowY);
         drawContext.drawText(lineFont, valueText, x + totalWidth - padX - valueWidth, rowY);

         lineY += lineHeight;
      }
   }

   private void updateDrag(float targetWidth, float targetHeight) {
      float maxX = Math.max(0.0F, sr.getScaledWidth() - targetWidth);
      float maxY = Math.max(0.0F, sr.getScaledHeight() - targetHeight);

      if (this.x < 0.0F || this.y < 0.0F) {
         this.x = maxX;
         this.y = 6.0F;
      }

      this.x = Math.max(0.0F, Math.min(this.x, maxX));
      this.y = Math.max(0.0F, Math.min(this.y, maxY));

      boolean mouseDown = GLFW.glfwGetMouseButton(mc.getWindow().getHandle(), 0) == 1;
      var mouse = GuiUtility.getMouse();
      float mouseX = mouse.getX();
      float mouseY = mouse.getY();
      if (mouseDown && !this.wasMouseDown && isHovered(mouseX, mouseY, targetWidth, targetHeight)) {
         this.dragging = true;
         this.dragOffsetX = mouseX - this.x;
         this.dragOffsetY = mouseY - this.y;
      }

      if (!mouseDown) {
         this.dragging = false;
      }

      if (this.dragging) {
         this.x = Math.max(0.0F, Math.min(mouseX - this.dragOffsetX, maxX));
         this.y = Math.max(0.0F, Math.min(mouseY - this.dragOffsetY, maxY));
      }

      this.wasMouseDown = mouseDown;
   }

   private boolean isHovered(float mouseX, float mouseY, float targetWidth, float targetHeight) {
      return mouseX >= this.x && mouseX < this.x + targetWidth && mouseY >= this.y && mouseY < this.y + targetHeight;
   }

   private Text resolveNameText(Scoreboard scoreboard, ScoreboardEntry entry) {
      Text display = entry.display();
      if (display != null) {
         return display;
      }

      String owner = entry.owner();
      if (owner == null) {
         return Text.empty();
      }

      Team team = resolveTeam(scoreboard, owner);
      Text base = Text.literal(owner);
      return team != null ? Team.decorateName(team, base) : base;
   }

   private Text resolveValueText(ScoreboardObjective objective, ScoreboardEntry entry) {
      Text formatted = entry.formatted(objective.getNumberFormatOr(StyledNumberFormat.EMPTY));
      if (formatted != null) {
         return formatted;
      }

      return Text.literal(Integer.toString(entry.value()));
   }

   private Team resolveTeam(Scoreboard scoreboard, String owner) {
      if (scoreboard == null || owner == null) {
         return null;
      }

      try {
         Method method = scoreboard.getClass().getMethod("getScoreHolderTeam", String.class);
         Object result = method.invoke(scoreboard, owner);
         if (result instanceof Team team) {
            return team;
         }
      } catch (Throwable ignored) {
      }

      try {
         Method method = scoreboard.getClass().getMethod("getPlayerTeam", String.class);
         Object result = method.invoke(scoreboard, owner);
         if (result instanceof Team team) {
            return team;
         }
      } catch (Throwable ignored) {
      }

      return null;
   }
}
ss
Кривая хуйня без поддержки символов /del
 
Всем привет! Сливаю вам скорборд из своего клиента так как мне лень его дальше писать
LegacyTextHelper.java:
Expand Collapse Copy
package moscow.rockstar.utility.game;

import net.minecraft.text.MutableText;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.text.TextColor;
import net.minecraft.util.Formatting;
import java.util.ArrayList;
import java.util.List;

public final class LegacyTextHelper {
   private static final char LEGACY_PREFIX_SECTION = '\u00A7';
   private static final char LEGACY_PREFIX_AMP = '&';
   private static final char LEGACY_PREFIX_DOLLAR = '$';

   public static Text resolve(Text text, int defaultColor) {
      if (text == null) {
         return Text.empty();
      }

      int baseColor = defaultColor & 0xFFFFFF;
      String raw = extractRaw(text);
      if (containsLegacyCodes(raw)) {
         return parse(raw, baseColor);
      }

      if (hasExplicitColor(text)) {
         return text;
      }

      return applyDefaultColor(text, baseColor);
   }

   public static List<ColoredSegment> parseSegments(String input, int defaultColor) {
      List<ColoredSegment> segments = new ArrayList<>();
      if (input == null || input.isEmpty()) {
         return segments;
      }

      StringBuilder buffer = new StringBuilder();
      int currentColor = 0xFF000000 | (defaultColor & 0xFFFFFF);
      int baseColor = currentColor;
      int length = input.length();

      for (int i = 0; i < length; i++) {
         char c = input.charAt(i);
         if (c == '<' || c == '{') {
            char end = c == '<' ? '>' : '}';
            if (isWrappedHexSequence(input, i, end)) {
               flushSegment(buffer, segments, currentColor);
               int rgb = parseHexColorPlain(input, i + 2);
               currentColor = 0xFF000000 | rgb;
               i += 8;
               continue;
            }
         }

         if (isLegacyPrefix(c) && i + 1 < length) {
            char code = Character.toLowerCase(input.charAt(i + 1));
            if (code == 'x' && isValidHexSequence(input, i)) {
               flushSegment(buffer, segments, currentColor);
               int rgb = parseHexColorLegacy(input, i);
               currentColor = 0xFF000000 | rgb;
               i += 13;
               continue;
            }

            if (code == '#' && isHexSequence(input, i + 2, 6)) {
               flushSegment(buffer, segments, currentColor);
               int rgb = parseHexColorPlain(input, i + 2);
               currentColor = 0xFF000000 | rgb;
               i += 7;
               continue;
            }

            Formatting formatting = Formatting.byCode(code);
            if (formatting != null) {
               flushSegment(buffer, segments, currentColor);
               if (formatting == Formatting.RESET) {
                  currentColor = baseColor;
               } else if (formatting.isColor()) {
                  Integer rgb = formatting.getColorValue();
                  if (rgb != null) {
                     currentColor = 0xFF000000 | rgb;
                  }
               }
               i++;
               continue;
            }
         }

         buffer.append(c);
      }

      flushSegment(buffer, segments, currentColor);
      return segments;
   }

   public static String extractRaw(Text text) {
      if (text == null) {
         return "";
      }

      StringBuilder visited = new StringBuilder();
      text.visit((style, string) -> {
         if (style != null && style.getColor() != null) {
            int rgb = style.getColor().getRgb() & 0xFFFFFF;
            String hex = Integer.toHexString(rgb);
            visited.append("&#");
            for (int i = 0; i < 6 - hex.length(); i++) {
               visited.append('0');
            }
            visited.append(hex);
         }
         visited.append(string);
         return java.util.Optional.empty();
      }, Style.EMPTY);

      return visited.toString();
   }

   public static boolean containsLegacyCodes(String input) {
      if (input == null || input.length() < 2) {
         return false;
      }

      int length = input.length();
      for (int i = 0; i < length - 1; i++) {
         char c = input.charAt(i);
         if (c == '<' || c == '{') {
            if (isWrappedHexSequence(input, i, c == '<' ? '>' : '}')) {
               return true;
            }
            continue;
         }

         if (!isLegacyPrefix(c)) {
            continue;
         }

         char code = Character.toLowerCase(input.charAt(i + 1));
         if (isLegacyCode(code)) {
            return true;
         }

         if (code == 'x' && isValidHexSequence(input, i)) {
            return true;
         }

         if (code == '#' && isHexSequence(input, i + 2, 6)) {
            return true;
         }
      }

      return false;
   }

   public static Text parse(String input, int defaultColor) {
      if (input == null || input.isEmpty()) {
         return Text.empty();
      }

      MutableText result = Text.empty();
      StringBuilder buffer = new StringBuilder();
      int baseColor = defaultColor & 0xFFFFFF;
      Style current = Style.EMPTY.withColor(TextColor.fromRgb(baseColor));
      int length = input.length();

      for (int i = 0; i < length; i++) {
         char c = input.charAt(i);
         if (c == '<' || c == '{') {
            char end = c == '<' ? '>' : '}';
            if (isWrappedHexSequence(input, i, end)) {
               flush(buffer, result, current);
               int rgb = parseHexColorPlain(input, i + 2);
               current = current.withColor(TextColor.fromRgb(rgb));
               i += 8;
               continue;
            }
         }

         if (isLegacyPrefix(c) && i + 1 < length) {
            char code = Character.toLowerCase(input.charAt(i + 1));
            if (code == 'x' && isValidHexSequence(input, i)) {
               flush(buffer, result, current);
               int rgb = parseHexColorLegacy(input, i);
               current = current.withColor(TextColor.fromRgb(rgb));
               i += 13;
               continue;
            }

            if (code == '#' && isHexSequence(input, i + 2, 6)) {
               flush(buffer, result, current);
               int rgb = parseHexColorPlain(input, i + 2);
               current = current.withColor(TextColor.fromRgb(rgb));
               i += 7;
               continue;
            }

            Formatting formatting = Formatting.byCode(code);
            if (formatting != null) {
               flush(buffer, result, current);
               if (formatting == Formatting.RESET) {
                  current = Style.EMPTY.withColor(TextColor.fromRgb(baseColor));
               } else if (formatting.isColor()) {
                  current = Style.EMPTY.withFormatting(formatting);
               } else {
                  current = current.withFormatting(formatting);
               }
               i++;
               continue;
            }
         }

         buffer.append(c);
      }

      flush(buffer, result, current);
      return result;
   }

   private static void flush(StringBuilder buffer, MutableText result, Style style) {
      if (buffer.length() == 0) {
         return;
      }

      result.append(Text.literal(buffer.toString()).setStyle(style));
      buffer.setLength(0);
   }

   private static void flushSegment(StringBuilder buffer, List<ColoredSegment> segments, int color) {
      if (buffer.length() == 0) {
         return;
      }

      segments.add(new ColoredSegment(buffer.toString(), color));
      buffer.setLength(0);
   }

   private static boolean isLegacyPrefix(char c) {
      return c == LEGACY_PREFIX_SECTION || c == LEGACY_PREFIX_AMP || c == LEGACY_PREFIX_DOLLAR;
   }

   private static boolean isLegacyCode(char code) {
      return (code >= '0' && code <= '9')
            || (code >= 'a' && code <= 'f')
            || code == 'k'
            || code == 'l'
            || code == 'm'
            || code == 'n'
            || code == 'o'
            || code == 'r';
   }

   private static boolean isValidHexSequence(String input, int startIndex) {
      int length = input.length();
      if (startIndex + 13 >= length) {
         return false;
      }

      for (int i = 0; i < 6; i++) {
         int base = startIndex + 2 + i * 2;
         char prefix = input.charAt(base);
         char hex = input.charAt(base + 1);
         if (!isLegacyPrefix(prefix) || !isHexDigit(hex)) {
            return false;
         }
      }

      return true;
   }

   private static int parseHexColorLegacy(String input, int startIndex) {
      StringBuilder hex = new StringBuilder(6);
      for (int i = 0; i < 6; i++) {
         int base = startIndex + 2 + i * 2;
         hex.append(input.charAt(base + 1));
      }
      return Integer.parseInt(hex.toString(), 16);
   }

   private static int parseHexColorPlain(String input, int startIndex) {
      return Integer.parseInt(input.substring(startIndex, startIndex + 6), 16);
   }

   private static boolean isHexDigit(char c) {
      return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
   }

   private static boolean isHexSequence(String input, int start, int length) {
      if (start < 0 || start + length > input.length()) {
         return false;
      }
      for (int i = 0; i < length; i++) {
         if (!isHexDigit(input.charAt(start + i))) {
            return false;
         }
      }
      return true;
   }

   private static boolean isWrappedHexSequence(String input, int startIndex, char endChar) {
      if (startIndex + 8 >= input.length()) {
         return false;
      }
      if (input.charAt(startIndex + 1) != '#') {
         return false;
      }
      if (!isHexSequence(input, startIndex + 2, 6)) {
         return false;
      }
      return input.charAt(startIndex + 8) == endChar;
   }

   private static Text applyDefaultColor(Text text, int defaultColor) {
      if (hasExplicitColor(text))
         return text;
      Style base = Style.EMPTY.withColor(TextColor.fromRgb(defaultColor));
      return Text.literal("").setStyle(base).append(text);
   }

   private static boolean hasExplicitColor(Text text) {
      final boolean[] hasColor = new boolean[] { false };
      text.visit((style, string) -> {
         if (style.getColor() != null) {
            hasColor[0] = true;
            return java.util.Optional.of(true);
         }
         return java.util.Optional.empty();
      }, Style.EMPTY);
      return hasColor[0];
   }

   private LegacyTextHelper() {
      throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
   }

   public record ColoredSegment(String text, int color) {
   }
}
ScoreboardHud.java:
Expand Collapse Copy
package moscow.rockstar.ui.hud.impl;

import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.List;
import moscow.rockstar.framework.base.UIContext;
import moscow.rockstar.framework.msdf.Font;
import moscow.rockstar.framework.msdf.Fonts;
import moscow.rockstar.framework.msdf.FormattedTextProcessor;
import moscow.rockstar.framework.objects.BorderRadius;
import moscow.rockstar.Rockstar;
import moscow.rockstar.systems.modules.modules.visuals.Interface;
import moscow.rockstar.systems.theme.Theme;
import moscow.rockstar.ui.hud.HudElement;
import moscow.rockstar.utility.colors.ColorRGBA;
import moscow.rockstar.utility.colors.Colors;
import moscow.rockstar.framework.objects.gradient.impl.HorizontalGradient;
import moscow.rockstar.utility.game.LegacyTextHelper;
import moscow.rockstar.systems.setting.settings.SliderSetting;
import moscow.rockstar.utility.interfaces.IScaledResolution;
import net.minecraft.client.gui.screen.ChatScreen;
import net.minecraft.scoreboard.Scoreboard;
import net.minecraft.scoreboard.ScoreboardDisplaySlot;
import net.minecraft.scoreboard.ScoreboardEntry;
import net.minecraft.scoreboard.ScoreboardObjective;
import net.minecraft.scoreboard.Team;
import net.minecraft.scoreboard.number.StyledNumberFormat;
import net.minecraft.text.Text;

public class ScoreboardHud extends HudElement implements IScaledResolution {
   private static final float TITLE_SIZE = 8.8F;
   private static final float LINE_SIZE = 7.8F;
   private static final float PAD_X = 16.0F;
   private static final float PAD_Y = 7.0F;
   private static final float LINE_GAP = 5.0F;
   private static final float VALUE_GAP = 16.0F;
   private boolean initialPos;
   private float lastSavedX = Float.NaN;
   private float lastSavedY = Float.NaN;
   private final SliderSetting scaleSetting = new SliderSetting(this, "\u0420\u0430\u0437\u043c\u0435\u0440").min(0.7F)
         .max(1.4F).step(0.05F).currentValue(1.0F);

   public ScoreboardHud() {
      super("\u0421\u043a\u043e\u0440\u0431\u043e\u0440\u0434", "icons/hud/world.png");
   }

   @Override
   public void update(UIContext context) {
      Layout layout = buildLayout();
      this.width = layout.width;
      this.height = layout.height;
      if (!this.initialPos && this.x == 0.0F && this.y == 0.0F) {
         this.x = Math.max(0.0F, sr.getScaledWidth() - this.width - 40.0F);
         this.y = 40.0F;
         this.initialPos = true;
      }
      if (!this.isDragging() && (this.x != this.lastSavedX || this.y != this.lastSavedY)) {
         this.lastSavedX = this.x;
         this.lastSavedY = this.y;
         Rockstar.getInstance().getFileManager().writeFile("client");
      }
      super.update(context);
   }

   @Override
   protected void renderComponent(UIContext context) {
      Layout layout = buildLayout();
      float x = this.x;
      float y = this.y;
      Font titleFont = getTitleFont();
      Font lineFont = getLineFont();

      boolean dark = Rockstar.getInstance().getThemeManager().getCurrentTheme() == Theme.DARK;
      ColorRGBA bgColor = Colors.getBackgroundColor()
            .withAlpha(255.0F * (dark ? 0.8F - 0.6F * Interface.glass() : 0.7F));

      context.drawShadow(
            x - 5.0F,
            y - 5.0F,
            layout.width + 10.0F,
            layout.height + 10.0F,
            15.0F,
            BorderRadius.all(6.0F),
            ColorRGBA.BLACK.withAlpha(63.75F));

      if (Interface.showMinimalizm()) {
         context.drawBlurredRect(
               x,
               y,
               layout.width,
               layout.height,
               45.0F,
               7.0F,
               BorderRadius.all(6.0F),
               ColorRGBA.WHITE.withAlpha(255.0F * Interface.minimalizm()));
      }

      if (Interface.showGlass()) {
         context.drawLiquidGlass(
               x,
               y,
               layout.width,
               layout.height,
               7.0F,
               0.08F,
               BorderRadius.all(6.0F),
               ColorRGBA.WHITE.withAlpha(255.0F * Interface.glass()));
      }

      context.drawSquircle(x, y, layout.width, layout.height, 7.0F, BorderRadius.all(6.0F), bgColor);

      float lineH = Math.max(1.0F, 1.4F * layout.scale);
      ColorRGBA accent = Colors.ACCENT.withAlpha(170.0F);
      ColorRGBA secondary = Colors.ACCENT.withAlpha(50.0F);
      context.drawRoundedRect(x + layout.padX * 0.6F, y + layout.headerHeight - lineH - layout.padY * 0.3F,
            layout.width - layout.padX * 1.2F, lineH, BorderRadius.all(lineH / 2.0F),
            new HorizontalGradient(accent, secondary));

      float titleX = x + PAD_X;
      for (Segment segment : layout.title.segments) {
         context.drawText(titleFont, segment.text, titleX, y + PAD_Y, ColorRGBA.fromInt(segment.color));
         titleX += titleFont.width(segment.text);
      }

      float lineY = y + layout.headerHeight;
      for (EntryLine line : layout.lines) {
         float rowY = lineY + (layout.lineHeight - lineFont.height()) / 2.0F;
         float valueWidth = line.value.width;

         float nameX = x + PAD_X;
         for (Segment segment : line.name.segments) {
            context.drawText(lineFont, segment.text, nameX, rowY, ColorRGBA.fromInt(segment.color));
            nameX += lineFont.width(segment.text);
         }

         float valueX = x + layout.width - PAD_X - valueWidth;
         for (Segment segment : line.value.segments) {
            context.drawText(lineFont, segment.text, valueX, rowY, ColorRGBA.fromInt(segment.color));
            valueX += lineFont.width(segment.text);
         }

         lineY += layout.lineHeight;
      }
   }

   @Override
   public boolean show() {
      return getObjective() != null;
   }

   private Layout buildLayout() {
      int defaultTextColor = Colors.getTextColor().getRGB() & 0xFFFFFF;
      float scale = this.scaleSetting.getCurrentValue();
      Font titleFont = getTitleFont();
      Font lineFont = getLineFont();
      ScoreboardObjective objective = getObjective();

      if (objective == null) {
         return new Layout(new Segments(List.of(), 0), List.of(), 0, 0, 0, 0, scale, PAD_X, PAD_Y);
      }

      Text titleSource = objective.getDisplayName();
      Segments title = buildSegments(titleSource, titleFont, defaultTextColor);
      float maxWidth = title.width;

      List<EntryLine> lines = objective.getScoreboard().getScoreboardEntries(objective).stream()
            .filter(entry -> entry != null && !entry.hidden())
            .sorted(Comparator.comparingInt(ScoreboardEntry::value).reversed().thenComparing(ScoreboardEntry::owner,
                  String::compareToIgnoreCase))
            .limit(25)
            .map(entry -> {
               Segments name = buildSegments(resolveNameText(objective.getScoreboard(), entry), lineFont,
                     defaultTextColor);
               Segments value = buildSegments(resolveValueText(objective, entry), lineFont, defaultTextColor);
               return new EntryLine(name, value);
            })
            .toList();

      for (EntryLine line : lines) {
         float rowWidth = line.name.width + line.value.width + VALUE_GAP;
         maxWidth = Math.max(maxWidth, rowWidth);
      }

      float headerHeight = titleFont.height() + PAD_Y * 2.0F;
      float lineHeight = lineFont.height() + LINE_GAP;
      float totalHeight = headerHeight + lines.size() * lineHeight + PAD_Y;
      float extraWidth = 16.0F;
      float totalWidth = maxWidth + PAD_X * 2.0F + extraWidth;

      totalWidth *= scale;
      totalHeight *= scale;
      headerHeight *= scale;
      lineHeight *= scale;

      return new Layout(title, lines, headerHeight, lineHeight, totalWidth, totalHeight, scale, PAD_X, PAD_Y);
   }

   private Segments buildSegments(Text text, Font font, int defaultColor) {
      String raw = LegacyTextHelper.extractRaw(text);
      Text source = LegacyTextHelper.containsLegacyCodes(raw) ? LegacyTextHelper.parse(raw, defaultColor) : text;
      boolean hasLegacy = LegacyTextHelper.containsLegacyCodes(raw);
      List<Segment> segments = hasLegacy
            ? LegacyTextHelper.parseSegments(raw, defaultColor).stream()
                  .map(segment -> new Segment(segment.text(), segment.color())).toList()
            : FormattedTextProcessor.processText(source, defaultColor).stream()
                  .map(segment -> new Segment(segment.text, segment.color)).toList();

      float width = 0.0F;
      List<Segment> sanitized = new java.util.ArrayList<>();
      for (Segment segment : segments) {
         String cleaned = segment.text;
         if (!cleaned.isEmpty()) {
            sanitized.add(new Segment(cleaned, segment.color));
            width += font.width(cleaned);
         }
      }

      return new Segments(sanitized, width);
   }

   private Font getTitleFont() {
      return Fonts.MEDIUM.getFont(TITLE_SIZE * this.scaleSetting.getCurrentValue());
   }

   private Font getLineFont() {
      return Fonts.REGULAR.getFont(LINE_SIZE * this.scaleSetting.getCurrentValue());
   }

   private ScoreboardObjective getObjective() {
      if (mc.world == null) {
         return null;
      }
      return mc.world.getScoreboard().getObjectiveForSlot(ScoreboardDisplaySlot.SIDEBAR);
   }

   private Text resolveNameText(Scoreboard scoreboard, ScoreboardEntry entry) {
      Text display = entry.display();
      if (display != null) {
         return display;
      }

      String owner = entry.owner();
      if (owner == null) {
         return Text.empty();
      }

      Team team = resolveTeam(scoreboard, owner);
      Text base = Text.literal(owner);
      return team != null ? Team.decorateName(team, base) : base;
   }

   private Text resolveValueText(ScoreboardObjective objective, ScoreboardEntry entry) {
      Text formatted = entry.formatted(objective.getNumberFormatOr(StyledNumberFormat.EMPTY));
      if (formatted != null) {
         return formatted;
      }

      return Text.literal(Integer.toString(entry.value()));
   }

   private Team resolveTeam(Scoreboard scoreboard, String owner) {
      if (scoreboard == null || owner == null) {
         return null;
      }
      return scoreboard.getScoreHolderTeam(owner);
   }

   private record Segment(String text, int color) {
   }

   private record Segments(List<Segment> segments, float width) {
   }

   private record EntryLine(Segments name, Segments value) {
   }

   private record Layout(Segments title, List<EntryLine> lines, float headerHeight, float lineHeight, float width,
         float height, float scale, float padX, float padY) {
   }
}

ScoreboardOverlay.java:
Expand Collapse Copy
package moscow.rockstar.ui.overlay;

import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.List;
import moscow.rockstar.framework.base.CustomDrawContext;
import moscow.rockstar.framework.msdf.Font;
import moscow.rockstar.framework.msdf.Fonts;
import moscow.rockstar.framework.objects.BorderRadius;
import moscow.rockstar.utility.colors.ColorRGBA;
import moscow.rockstar.utility.colors.Colors;
import moscow.rockstar.utility.game.LegacyTextHelper;
import moscow.rockstar.utility.gui.GuiUtility;
import moscow.rockstar.utility.interfaces.IMinecraft;
import moscow.rockstar.utility.interfaces.IScaledResolution;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.scoreboard.Scoreboard;
import net.minecraft.scoreboard.ScoreboardEntry;
import net.minecraft.scoreboard.ScoreboardObjective;
import net.minecraft.scoreboard.Team;
import net.minecraft.scoreboard.number.StyledNumberFormat;
import net.minecraft.text.Text;
import org.lwjgl.glfw.GLFW;

public class ScoreboardOverlay implements IMinecraft, IScaledResolution {
   private static final ScoreboardOverlay INSTANCE = new ScoreboardOverlay();
   private float x = -1.0F;
   private float y = -1.0F;
   private float width;
   private float height;
   private boolean dragging;
   private float dragOffsetX;
   private float dragOffsetY;
   private boolean wasMouseDown;

   public static ScoreboardOverlay getInstance() {
      return INSTANCE;
   }

   public void render(DrawContext context, ScoreboardObjective objective) {
      if (objective == null || mc.player == null || mc.world == null) {
         return;
      }

      Scoreboard scoreboard = objective.getScoreboard();
      List<ScoreboardEntry> entries = scoreboard.getScoreboardEntries(objective).stream()
         .filter(entry -> entry != null && !entry.hidden())
         .sorted(Comparator.comparingInt(ScoreboardEntry::value).reversed().thenComparing(ScoreboardEntry::owner, String::compareToIgnoreCase))
         .limit(15)
         .toList();

      Font titleFont = Fonts.MEDIUM.getFont(8.5F);
      Font lineFont = Fonts.REGULAR.getFont(7.5F);
      float padX = 10.0F;
      float padY = 6.0F;
      float lineGap = 4.0F;
      float valueGap = 10.0F;
      float shadowExpand = 5.0F;

      int defaultTextColor = Colors.getTextColor().getRGB() & 0xFFFFFF;
      Text title = LegacyTextHelper.resolve(objective.getDisplayName(), defaultTextColor);
      float maxWidth = titleFont.width(title);

      for (ScoreboardEntry entry : entries) {
         Text nameText = LegacyTextHelper.resolve(resolveNameText(scoreboard, entry), defaultTextColor);
         Text valueText = LegacyTextHelper.resolve(resolveValueText(objective, entry), defaultTextColor);
         float rowWidth = lineFont.width(nameText) + lineFont.width(valueText) + valueGap;
         maxWidth = Math.max(maxWidth, rowWidth);
      }

      float headerHeight = titleFont.height() + padY * 2.0F;
      float lineHeight = lineFont.height() + lineGap;
      float totalHeight = headerHeight + entries.size() * lineHeight + padY;
      float totalWidth = maxWidth + padX * 2.0F + 12.0F;

      updateDrag(totalWidth, totalHeight);
      float x = this.x;
      float y = this.y;
      this.width = totalWidth;
      this.height = totalHeight;

      CustomDrawContext drawContext = CustomDrawContext.of(context);
      drawContext.drawShadow(
         x - shadowExpand,
         y - shadowExpand,
         totalWidth + shadowExpand * 2.0F,
         totalHeight + shadowExpand * 2.0F,
         15.0F,
         BorderRadius.all(6.0F),
         ColorRGBA.BLACK.withAlpha(60.0F)
      );
      drawContext.drawClientRect(x, y, totalWidth, totalHeight, 1.0F, 0.0F, 7.0F);
      drawContext.drawRect(x, y + headerHeight - 1.0F, totalWidth, 1.0F, Colors.getSeparatorColor());

      float titleY = y + padY;
      drawContext.drawText(titleFont, title, x + padX, titleY);

      float lineY = y + headerHeight;
      for (ScoreboardEntry entry : entries) {
         Text nameText = LegacyTextHelper.resolve(resolveNameText(scoreboard, entry), defaultTextColor);
         Text valueText = LegacyTextHelper.resolve(resolveValueText(objective, entry), defaultTextColor);

         float rowY = lineY + (lineHeight - lineFont.height()) / 2.0F;
         float valueWidth = lineFont.width(valueText);

         drawContext.drawText(lineFont, nameText, x + padX, rowY);
         drawContext.drawText(lineFont, valueText, x + totalWidth - padX - valueWidth, rowY);

         lineY += lineHeight;
      }
   }

   private void updateDrag(float targetWidth, float targetHeight) {
      float maxX = Math.max(0.0F, sr.getScaledWidth() - targetWidth);
      float maxY = Math.max(0.0F, sr.getScaledHeight() - targetHeight);

      if (this.x < 0.0F || this.y < 0.0F) {
         this.x = maxX;
         this.y = 6.0F;
      }

      this.x = Math.max(0.0F, Math.min(this.x, maxX));
      this.y = Math.max(0.0F, Math.min(this.y, maxY));

      boolean mouseDown = GLFW.glfwGetMouseButton(mc.getWindow().getHandle(), 0) == 1;
      var mouse = GuiUtility.getMouse();
      float mouseX = mouse.getX();
      float mouseY = mouse.getY();
      if (mouseDown && !this.wasMouseDown && isHovered(mouseX, mouseY, targetWidth, targetHeight)) {
         this.dragging = true;
         this.dragOffsetX = mouseX - this.x;
         this.dragOffsetY = mouseY - this.y;
      }

      if (!mouseDown) {
         this.dragging = false;
      }

      if (this.dragging) {
         this.x = Math.max(0.0F, Math.min(mouseX - this.dragOffsetX, maxX));
         this.y = Math.max(0.0F, Math.min(mouseY - this.dragOffsetY, maxY));
      }

      this.wasMouseDown = mouseDown;
   }

   private boolean isHovered(float mouseX, float mouseY, float targetWidth, float targetHeight) {
      return mouseX >= this.x && mouseX < this.x + targetWidth && mouseY >= this.y && mouseY < this.y + targetHeight;
   }

   private Text resolveNameText(Scoreboard scoreboard, ScoreboardEntry entry) {
      Text display = entry.display();
      if (display != null) {
         return display;
      }

      String owner = entry.owner();
      if (owner == null) {
         return Text.empty();
      }

      Team team = resolveTeam(scoreboard, owner);
      Text base = Text.literal(owner);
      return team != null ? Team.decorateName(team, base) : base;
   }

   private Text resolveValueText(ScoreboardObjective objective, ScoreboardEntry entry) {
      Text formatted = entry.formatted(objective.getNumberFormatOr(StyledNumberFormat.EMPTY));
      if (formatted != null) {
         return formatted;
      }

      return Text.literal(Integer.toString(entry.value()));
   }

   private Team resolveTeam(Scoreboard scoreboard, String owner) {
      if (scoreboard == null || owner == null) {
         return null;
      }

      try {
         Method method = scoreboard.getClass().getMethod("getScoreHolderTeam", String.class);
         Object result = method.invoke(scoreboard, owner);
         if (result instanceof Team team) {
            return team;
         }
      } catch (Throwable ignored) {
      }

      try {
         Method method = scoreboard.getClass().getMethod("getPlayerTeam", String.class);
         Object result = method.invoke(scoreboard, owner);
         if (result instanceof Team team) {
            return team;
         }
      } catch (Throwable ignored) {
      }

      return null;
   }
}
ss
можно было разместить на 1 оверлей в 200 строк и 1 миксин, да и выглядит плохо.
 
можно было разместить на 1 оверлей в 200 строк и 1 миксин, да и выглядит плохо.
Это визуал рокстара темный режим без блюра ставь какой хочешь скорборд будет таким же
 
Всем привет! Сливаю вам скорборд из своего клиента так как мне лень его дальше писать
LegacyTextHelper.java:
Expand Collapse Copy
package moscow.rockstar.utility.game;

import net.minecraft.text.MutableText;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.text.TextColor;
import net.minecraft.util.Formatting;
import java.util.ArrayList;
import java.util.List;

public final class LegacyTextHelper {
   private static final char LEGACY_PREFIX_SECTION = '\u00A7';
   private static final char LEGACY_PREFIX_AMP = '&';
   private static final char LEGACY_PREFIX_DOLLAR = '$';

   public static Text resolve(Text text, int defaultColor) {
      if (text == null) {
         return Text.empty();
      }

      int baseColor = defaultColor & 0xFFFFFF;
      String raw = extractRaw(text);
      if (containsLegacyCodes(raw)) {
         return parse(raw, baseColor);
      }

      if (hasExplicitColor(text)) {
         return text;
      }

      return applyDefaultColor(text, baseColor);
   }

   public static List<ColoredSegment> parseSegments(String input, int defaultColor) {
      List<ColoredSegment> segments = new ArrayList<>();
      if (input == null || input.isEmpty()) {
         return segments;
      }

      StringBuilder buffer = new StringBuilder();
      int currentColor = 0xFF000000 | (defaultColor & 0xFFFFFF);
      int baseColor = currentColor;
      int length = input.length();

      for (int i = 0; i < length; i++) {
         char c = input.charAt(i);
         if (c == '<' || c == '{') {
            char end = c == '<' ? '>' : '}';
            if (isWrappedHexSequence(input, i, end)) {
               flushSegment(buffer, segments, currentColor);
               int rgb = parseHexColorPlain(input, i + 2);
               currentColor = 0xFF000000 | rgb;
               i += 8;
               continue;
            }
         }

         if (isLegacyPrefix(c) && i + 1 < length) {
            char code = Character.toLowerCase(input.charAt(i + 1));
            if (code == 'x' && isValidHexSequence(input, i)) {
               flushSegment(buffer, segments, currentColor);
               int rgb = parseHexColorLegacy(input, i);
               currentColor = 0xFF000000 | rgb;
               i += 13;
               continue;
            }

            if (code == '#' && isHexSequence(input, i + 2, 6)) {
               flushSegment(buffer, segments, currentColor);
               int rgb = parseHexColorPlain(input, i + 2);
               currentColor = 0xFF000000 | rgb;
               i += 7;
               continue;
            }

            Formatting formatting = Formatting.byCode(code);
            if (formatting != null) {
               flushSegment(buffer, segments, currentColor);
               if (formatting == Formatting.RESET) {
                  currentColor = baseColor;
               } else if (formatting.isColor()) {
                  Integer rgb = formatting.getColorValue();
                  if (rgb != null) {
                     currentColor = 0xFF000000 | rgb;
                  }
               }
               i++;
               continue;
            }
         }

         buffer.append(c);
      }

      flushSegment(buffer, segments, currentColor);
      return segments;
   }

   public static String extractRaw(Text text) {
      if (text == null) {
         return "";
      }

      StringBuilder visited = new StringBuilder();
      text.visit((style, string) -> {
         if (style != null && style.getColor() != null) {
            int rgb = style.getColor().getRgb() & 0xFFFFFF;
            String hex = Integer.toHexString(rgb);
            visited.append("&#");
            for (int i = 0; i < 6 - hex.length(); i++) {
               visited.append('0');
            }
            visited.append(hex);
         }
         visited.append(string);
         return java.util.Optional.empty();
      }, Style.EMPTY);

      return visited.toString();
   }

   public static boolean containsLegacyCodes(String input) {
      if (input == null || input.length() < 2) {
         return false;
      }

      int length = input.length();
      for (int i = 0; i < length - 1; i++) {
         char c = input.charAt(i);
         if (c == '<' || c == '{') {
            if (isWrappedHexSequence(input, i, c == '<' ? '>' : '}')) {
               return true;
            }
            continue;
         }

         if (!isLegacyPrefix(c)) {
            continue;
         }

         char code = Character.toLowerCase(input.charAt(i + 1));
         if (isLegacyCode(code)) {
            return true;
         }

         if (code == 'x' && isValidHexSequence(input, i)) {
            return true;
         }

         if (code == '#' && isHexSequence(input, i + 2, 6)) {
            return true;
         }
      }

      return false;
   }

   public static Text parse(String input, int defaultColor) {
      if (input == null || input.isEmpty()) {
         return Text.empty();
      }

      MutableText result = Text.empty();
      StringBuilder buffer = new StringBuilder();
      int baseColor = defaultColor & 0xFFFFFF;
      Style current = Style.EMPTY.withColor(TextColor.fromRgb(baseColor));
      int length = input.length();

      for (int i = 0; i < length; i++) {
         char c = input.charAt(i);
         if (c == '<' || c == '{') {
            char end = c == '<' ? '>' : '}';
            if (isWrappedHexSequence(input, i, end)) {
               flush(buffer, result, current);
               int rgb = parseHexColorPlain(input, i + 2);
               current = current.withColor(TextColor.fromRgb(rgb));
               i += 8;
               continue;
            }
         }

         if (isLegacyPrefix(c) && i + 1 < length) {
            char code = Character.toLowerCase(input.charAt(i + 1));
            if (code == 'x' && isValidHexSequence(input, i)) {
               flush(buffer, result, current);
               int rgb = parseHexColorLegacy(input, i);
               current = current.withColor(TextColor.fromRgb(rgb));
               i += 13;
               continue;
            }

            if (code == '#' && isHexSequence(input, i + 2, 6)) {
               flush(buffer, result, current);
               int rgb = parseHexColorPlain(input, i + 2);
               current = current.withColor(TextColor.fromRgb(rgb));
               i += 7;
               continue;
            }

            Formatting formatting = Formatting.byCode(code);
            if (formatting != null) {
               flush(buffer, result, current);
               if (formatting == Formatting.RESET) {
                  current = Style.EMPTY.withColor(TextColor.fromRgb(baseColor));
               } else if (formatting.isColor()) {
                  current = Style.EMPTY.withFormatting(formatting);
               } else {
                  current = current.withFormatting(formatting);
               }
               i++;
               continue;
            }
         }

         buffer.append(c);
      }

      flush(buffer, result, current);
      return result;
   }

   private static void flush(StringBuilder buffer, MutableText result, Style style) {
      if (buffer.length() == 0) {
         return;
      }

      result.append(Text.literal(buffer.toString()).setStyle(style));
      buffer.setLength(0);
   }

   private static void flushSegment(StringBuilder buffer, List<ColoredSegment> segments, int color) {
      if (buffer.length() == 0) {
         return;
      }

      segments.add(new ColoredSegment(buffer.toString(), color));
      buffer.setLength(0);
   }

   private static boolean isLegacyPrefix(char c) {
      return c == LEGACY_PREFIX_SECTION || c == LEGACY_PREFIX_AMP || c == LEGACY_PREFIX_DOLLAR;
   }

   private static boolean isLegacyCode(char code) {
      return (code >= '0' && code <= '9')
            || (code >= 'a' && code <= 'f')
            || code == 'k'
            || code == 'l'
            || code == 'm'
            || code == 'n'
            || code == 'o'
            || code == 'r';
   }

   private static boolean isValidHexSequence(String input, int startIndex) {
      int length = input.length();
      if (startIndex + 13 >= length) {
         return false;
      }

      for (int i = 0; i < 6; i++) {
         int base = startIndex + 2 + i * 2;
         char prefix = input.charAt(base);
         char hex = input.charAt(base + 1);
         if (!isLegacyPrefix(prefix) || !isHexDigit(hex)) {
            return false;
         }
      }

      return true;
   }

   private static int parseHexColorLegacy(String input, int startIndex) {
      StringBuilder hex = new StringBuilder(6);
      for (int i = 0; i < 6; i++) {
         int base = startIndex + 2 + i * 2;
         hex.append(input.charAt(base + 1));
      }
      return Integer.parseInt(hex.toString(), 16);
   }

   private static int parseHexColorPlain(String input, int startIndex) {
      return Integer.parseInt(input.substring(startIndex, startIndex + 6), 16);
   }

   private static boolean isHexDigit(char c) {
      return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
   }

   private static boolean isHexSequence(String input, int start, int length) {
      if (start < 0 || start + length > input.length()) {
         return false;
      }
      for (int i = 0; i < length; i++) {
         if (!isHexDigit(input.charAt(start + i))) {
            return false;
         }
      }
      return true;
   }

   private static boolean isWrappedHexSequence(String input, int startIndex, char endChar) {
      if (startIndex + 8 >= input.length()) {
         return false;
      }
      if (input.charAt(startIndex + 1) != '#') {
         return false;
      }
      if (!isHexSequence(input, startIndex + 2, 6)) {
         return false;
      }
      return input.charAt(startIndex + 8) == endChar;
   }

   private static Text applyDefaultColor(Text text, int defaultColor) {
      if (hasExplicitColor(text))
         return text;
      Style base = Style.EMPTY.withColor(TextColor.fromRgb(defaultColor));
      return Text.literal("").setStyle(base).append(text);
   }

   private static boolean hasExplicitColor(Text text) {
      final boolean[] hasColor = new boolean[] { false };
      text.visit((style, string) -> {
         if (style.getColor() != null) {
            hasColor[0] = true;
            return java.util.Optional.of(true);
         }
         return java.util.Optional.empty();
      }, Style.EMPTY);
      return hasColor[0];
   }

   private LegacyTextHelper() {
      throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
   }

   public record ColoredSegment(String text, int color) {
   }
}
ScoreboardHud.java:
Expand Collapse Copy
package moscow.rockstar.ui.hud.impl;

import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.List;
import moscow.rockstar.framework.base.UIContext;
import moscow.rockstar.framework.msdf.Font;
import moscow.rockstar.framework.msdf.Fonts;
import moscow.rockstar.framework.msdf.FormattedTextProcessor;
import moscow.rockstar.framework.objects.BorderRadius;
import moscow.rockstar.Rockstar;
import moscow.rockstar.systems.modules.modules.visuals.Interface;
import moscow.rockstar.systems.theme.Theme;
import moscow.rockstar.ui.hud.HudElement;
import moscow.rockstar.utility.colors.ColorRGBA;
import moscow.rockstar.utility.colors.Colors;
import moscow.rockstar.framework.objects.gradient.impl.HorizontalGradient;
import moscow.rockstar.utility.game.LegacyTextHelper;
import moscow.rockstar.systems.setting.settings.SliderSetting;
import moscow.rockstar.utility.interfaces.IScaledResolution;
import net.minecraft.client.gui.screen.ChatScreen;
import net.minecraft.scoreboard.Scoreboard;
import net.minecraft.scoreboard.ScoreboardDisplaySlot;
import net.minecraft.scoreboard.ScoreboardEntry;
import net.minecraft.scoreboard.ScoreboardObjective;
import net.minecraft.scoreboard.Team;
import net.minecraft.scoreboard.number.StyledNumberFormat;
import net.minecraft.text.Text;

public class ScoreboardHud extends HudElement implements IScaledResolution {
   private static final float TITLE_SIZE = 8.8F;
   private static final float LINE_SIZE = 7.8F;
   private static final float PAD_X = 16.0F;
   private static final float PAD_Y = 7.0F;
   private static final float LINE_GAP = 5.0F;
   private static final float VALUE_GAP = 16.0F;
   private boolean initialPos;
   private float lastSavedX = Float.NaN;
   private float lastSavedY = Float.NaN;
   private final SliderSetting scaleSetting = new SliderSetting(this, "\u0420\u0430\u0437\u043c\u0435\u0440").min(0.7F)
         .max(1.4F).step(0.05F).currentValue(1.0F);

   public ScoreboardHud() {
      super("\u0421\u043a\u043e\u0440\u0431\u043e\u0440\u0434", "icons/hud/world.png");
   }

   @Override
   public void update(UIContext context) {
      Layout layout = buildLayout();
      this.width = layout.width;
      this.height = layout.height;
      if (!this.initialPos && this.x == 0.0F && this.y == 0.0F) {
         this.x = Math.max(0.0F, sr.getScaledWidth() - this.width - 40.0F);
         this.y = 40.0F;
         this.initialPos = true;
      }
      if (!this.isDragging() && (this.x != this.lastSavedX || this.y != this.lastSavedY)) {
         this.lastSavedX = this.x;
         this.lastSavedY = this.y;
         Rockstar.getInstance().getFileManager().writeFile("client");
      }
      super.update(context);
   }

   @Override
   protected void renderComponent(UIContext context) {
      Layout layout = buildLayout();
      float x = this.x;
      float y = this.y;
      Font titleFont = getTitleFont();
      Font lineFont = getLineFont();

      boolean dark = Rockstar.getInstance().getThemeManager().getCurrentTheme() == Theme.DARK;
      ColorRGBA bgColor = Colors.getBackgroundColor()
            .withAlpha(255.0F * (dark ? 0.8F - 0.6F * Interface.glass() : 0.7F));

      context.drawShadow(
            x - 5.0F,
            y - 5.0F,
            layout.width + 10.0F,
            layout.height + 10.0F,
            15.0F,
            BorderRadius.all(6.0F),
            ColorRGBA.BLACK.withAlpha(63.75F));

      if (Interface.showMinimalizm()) {
         context.drawBlurredRect(
               x,
               y,
               layout.width,
               layout.height,
               45.0F,
               7.0F,
               BorderRadius.all(6.0F),
               ColorRGBA.WHITE.withAlpha(255.0F * Interface.minimalizm()));
      }

      if (Interface.showGlass()) {
         context.drawLiquidGlass(
               x,
               y,
               layout.width,
               layout.height,
               7.0F,
               0.08F,
               BorderRadius.all(6.0F),
               ColorRGBA.WHITE.withAlpha(255.0F * Interface.glass()));
      }

      context.drawSquircle(x, y, layout.width, layout.height, 7.0F, BorderRadius.all(6.0F), bgColor);

      float lineH = Math.max(1.0F, 1.4F * layout.scale);
      ColorRGBA accent = Colors.ACCENT.withAlpha(170.0F);
      ColorRGBA secondary = Colors.ACCENT.withAlpha(50.0F);
      context.drawRoundedRect(x + layout.padX * 0.6F, y + layout.headerHeight - lineH - layout.padY * 0.3F,
            layout.width - layout.padX * 1.2F, lineH, BorderRadius.all(lineH / 2.0F),
            new HorizontalGradient(accent, secondary));

      float titleX = x + PAD_X;
      for (Segment segment : layout.title.segments) {
         context.drawText(titleFont, segment.text, titleX, y + PAD_Y, ColorRGBA.fromInt(segment.color));
         titleX += titleFont.width(segment.text);
      }

      float lineY = y + layout.headerHeight;
      for (EntryLine line : layout.lines) {
         float rowY = lineY + (layout.lineHeight - lineFont.height()) / 2.0F;
         float valueWidth = line.value.width;

         float nameX = x + PAD_X;
         for (Segment segment : line.name.segments) {
            context.drawText(lineFont, segment.text, nameX, rowY, ColorRGBA.fromInt(segment.color));
            nameX += lineFont.width(segment.text);
         }

         float valueX = x + layout.width - PAD_X - valueWidth;
         for (Segment segment : line.value.segments) {
            context.drawText(lineFont, segment.text, valueX, rowY, ColorRGBA.fromInt(segment.color));
            valueX += lineFont.width(segment.text);
         }

         lineY += layout.lineHeight;
      }
   }

   @Override
   public boolean show() {
      return getObjective() != null;
   }

   private Layout buildLayout() {
      int defaultTextColor = Colors.getTextColor().getRGB() & 0xFFFFFF;
      float scale = this.scaleSetting.getCurrentValue();
      Font titleFont = getTitleFont();
      Font lineFont = getLineFont();
      ScoreboardObjective objective = getObjective();

      if (objective == null) {
         return new Layout(new Segments(List.of(), 0), List.of(), 0, 0, 0, 0, scale, PAD_X, PAD_Y);
      }

      Text titleSource = objective.getDisplayName();
      Segments title = buildSegments(titleSource, titleFont, defaultTextColor);
      float maxWidth = title.width;

      List<EntryLine> lines = objective.getScoreboard().getScoreboardEntries(objective).stream()
            .filter(entry -> entry != null && !entry.hidden())
            .sorted(Comparator.comparingInt(ScoreboardEntry::value).reversed().thenComparing(ScoreboardEntry::owner,
                  String::compareToIgnoreCase))
            .limit(25)
            .map(entry -> {
               Segments name = buildSegments(resolveNameText(objective.getScoreboard(), entry), lineFont,
                     defaultTextColor);
               Segments value = buildSegments(resolveValueText(objective, entry), lineFont, defaultTextColor);
               return new EntryLine(name, value);
            })
            .toList();

      for (EntryLine line : lines) {
         float rowWidth = line.name.width + line.value.width + VALUE_GAP;
         maxWidth = Math.max(maxWidth, rowWidth);
      }

      float headerHeight = titleFont.height() + PAD_Y * 2.0F;
      float lineHeight = lineFont.height() + LINE_GAP;
      float totalHeight = headerHeight + lines.size() * lineHeight + PAD_Y;
      float extraWidth = 16.0F;
      float totalWidth = maxWidth + PAD_X * 2.0F + extraWidth;

      totalWidth *= scale;
      totalHeight *= scale;
      headerHeight *= scale;
      lineHeight *= scale;

      return new Layout(title, lines, headerHeight, lineHeight, totalWidth, totalHeight, scale, PAD_X, PAD_Y);
   }

   private Segments buildSegments(Text text, Font font, int defaultColor) {
      String raw = LegacyTextHelper.extractRaw(text);
      Text source = LegacyTextHelper.containsLegacyCodes(raw) ? LegacyTextHelper.parse(raw, defaultColor) : text;
      boolean hasLegacy = LegacyTextHelper.containsLegacyCodes(raw);
      List<Segment> segments = hasLegacy
            ? LegacyTextHelper.parseSegments(raw, defaultColor).stream()
                  .map(segment -> new Segment(segment.text(), segment.color())).toList()
            : FormattedTextProcessor.processText(source, defaultColor).stream()
                  .map(segment -> new Segment(segment.text, segment.color)).toList();

      float width = 0.0F;
      List<Segment> sanitized = new java.util.ArrayList<>();
      for (Segment segment : segments) {
         String cleaned = segment.text;
         if (!cleaned.isEmpty()) {
            sanitized.add(new Segment(cleaned, segment.color));
            width += font.width(cleaned);
         }
      }

      return new Segments(sanitized, width);
   }

   private Font getTitleFont() {
      return Fonts.MEDIUM.getFont(TITLE_SIZE * this.scaleSetting.getCurrentValue());
   }

   private Font getLineFont() {
      return Fonts.REGULAR.getFont(LINE_SIZE * this.scaleSetting.getCurrentValue());
   }

   private ScoreboardObjective getObjective() {
      if (mc.world == null) {
         return null;
      }
      return mc.world.getScoreboard().getObjectiveForSlot(ScoreboardDisplaySlot.SIDEBAR);
   }

   private Text resolveNameText(Scoreboard scoreboard, ScoreboardEntry entry) {
      Text display = entry.display();
      if (display != null) {
         return display;
      }

      String owner = entry.owner();
      if (owner == null) {
         return Text.empty();
      }

      Team team = resolveTeam(scoreboard, owner);
      Text base = Text.literal(owner);
      return team != null ? Team.decorateName(team, base) : base;
   }

   private Text resolveValueText(ScoreboardObjective objective, ScoreboardEntry entry) {
      Text formatted = entry.formatted(objective.getNumberFormatOr(StyledNumberFormat.EMPTY));
      if (formatted != null) {
         return formatted;
      }

      return Text.literal(Integer.toString(entry.value()));
   }

   private Team resolveTeam(Scoreboard scoreboard, String owner) {
      if (scoreboard == null || owner == null) {
         return null;
      }
      return scoreboard.getScoreHolderTeam(owner);
   }

   private record Segment(String text, int color) {
   }

   private record Segments(List<Segment> segments, float width) {
   }

   private record EntryLine(Segments name, Segments value) {
   }

   private record Layout(Segments title, List<EntryLine> lines, float headerHeight, float lineHeight, float width,
         float height, float scale, float padX, float padY) {
   }
}

ScoreboardOverlay.java:
Expand Collapse Copy
package moscow.rockstar.ui.overlay;

import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.List;
import moscow.rockstar.framework.base.CustomDrawContext;
import moscow.rockstar.framework.msdf.Font;
import moscow.rockstar.framework.msdf.Fonts;
import moscow.rockstar.framework.objects.BorderRadius;
import moscow.rockstar.utility.colors.ColorRGBA;
import moscow.rockstar.utility.colors.Colors;
import moscow.rockstar.utility.game.LegacyTextHelper;
import moscow.rockstar.utility.gui.GuiUtility;
import moscow.rockstar.utility.interfaces.IMinecraft;
import moscow.rockstar.utility.interfaces.IScaledResolution;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.scoreboard.Scoreboard;
import net.minecraft.scoreboard.ScoreboardEntry;
import net.minecraft.scoreboard.ScoreboardObjective;
import net.minecraft.scoreboard.Team;
import net.minecraft.scoreboard.number.StyledNumberFormat;
import net.minecraft.text.Text;
import org.lwjgl.glfw.GLFW;

public class ScoreboardOverlay implements IMinecraft, IScaledResolution {
   private static final ScoreboardOverlay INSTANCE = new ScoreboardOverlay();
   private float x = -1.0F;
   private float y = -1.0F;
   private float width;
   private float height;
   private boolean dragging;
   private float dragOffsetX;
   private float dragOffsetY;
   private boolean wasMouseDown;

   public static ScoreboardOverlay getInstance() {
      return INSTANCE;
   }

   public void render(DrawContext context, ScoreboardObjective objective) {
      if (objective == null || mc.player == null || mc.world == null) {
         return;
      }

      Scoreboard scoreboard = objective.getScoreboard();
      List<ScoreboardEntry> entries = scoreboard.getScoreboardEntries(objective).stream()
         .filter(entry -> entry != null && !entry.hidden())
         .sorted(Comparator.comparingInt(ScoreboardEntry::value).reversed().thenComparing(ScoreboardEntry::owner, String::compareToIgnoreCase))
         .limit(15)
         .toList();

      Font titleFont = Fonts.MEDIUM.getFont(8.5F);
      Font lineFont = Fonts.REGULAR.getFont(7.5F);
      float padX = 10.0F;
      float padY = 6.0F;
      float lineGap = 4.0F;
      float valueGap = 10.0F;
      float shadowExpand = 5.0F;

      int defaultTextColor = Colors.getTextColor().getRGB() & 0xFFFFFF;
      Text title = LegacyTextHelper.resolve(objective.getDisplayName(), defaultTextColor);
      float maxWidth = titleFont.width(title);

      for (ScoreboardEntry entry : entries) {
         Text nameText = LegacyTextHelper.resolve(resolveNameText(scoreboard, entry), defaultTextColor);
         Text valueText = LegacyTextHelper.resolve(resolveValueText(objective, entry), defaultTextColor);
         float rowWidth = lineFont.width(nameText) + lineFont.width(valueText) + valueGap;
         maxWidth = Math.max(maxWidth, rowWidth);
      }

      float headerHeight = titleFont.height() + padY * 2.0F;
      float lineHeight = lineFont.height() + lineGap;
      float totalHeight = headerHeight + entries.size() * lineHeight + padY;
      float totalWidth = maxWidth + padX * 2.0F + 12.0F;

      updateDrag(totalWidth, totalHeight);
      float x = this.x;
      float y = this.y;
      this.width = totalWidth;
      this.height = totalHeight;

      CustomDrawContext drawContext = CustomDrawContext.of(context);
      drawContext.drawShadow(
         x - shadowExpand,
         y - shadowExpand,
         totalWidth + shadowExpand * 2.0F,
         totalHeight + shadowExpand * 2.0F,
         15.0F,
         BorderRadius.all(6.0F),
         ColorRGBA.BLACK.withAlpha(60.0F)
      );
      drawContext.drawClientRect(x, y, totalWidth, totalHeight, 1.0F, 0.0F, 7.0F);
      drawContext.drawRect(x, y + headerHeight - 1.0F, totalWidth, 1.0F, Colors.getSeparatorColor());

      float titleY = y + padY;
      drawContext.drawText(titleFont, title, x + padX, titleY);

      float lineY = y + headerHeight;
      for (ScoreboardEntry entry : entries) {
         Text nameText = LegacyTextHelper.resolve(resolveNameText(scoreboard, entry), defaultTextColor);
         Text valueText = LegacyTextHelper.resolve(resolveValueText(objective, entry), defaultTextColor);

         float rowY = lineY + (lineHeight - lineFont.height()) / 2.0F;
         float valueWidth = lineFont.width(valueText);

         drawContext.drawText(lineFont, nameText, x + padX, rowY);
         drawContext.drawText(lineFont, valueText, x + totalWidth - padX - valueWidth, rowY);

         lineY += lineHeight;
      }
   }

   private void updateDrag(float targetWidth, float targetHeight) {
      float maxX = Math.max(0.0F, sr.getScaledWidth() - targetWidth);
      float maxY = Math.max(0.0F, sr.getScaledHeight() - targetHeight);

      if (this.x < 0.0F || this.y < 0.0F) {
         this.x = maxX;
         this.y = 6.0F;
      }

      this.x = Math.max(0.0F, Math.min(this.x, maxX));
      this.y = Math.max(0.0F, Math.min(this.y, maxY));

      boolean mouseDown = GLFW.glfwGetMouseButton(mc.getWindow().getHandle(), 0) == 1;
      var mouse = GuiUtility.getMouse();
      float mouseX = mouse.getX();
      float mouseY = mouse.getY();
      if (mouseDown && !this.wasMouseDown && isHovered(mouseX, mouseY, targetWidth, targetHeight)) {
         this.dragging = true;
         this.dragOffsetX = mouseX - this.x;
         this.dragOffsetY = mouseY - this.y;
      }

      if (!mouseDown) {
         this.dragging = false;
      }

      if (this.dragging) {
         this.x = Math.max(0.0F, Math.min(mouseX - this.dragOffsetX, maxX));
         this.y = Math.max(0.0F, Math.min(mouseY - this.dragOffsetY, maxY));
      }

      this.wasMouseDown = mouseDown;
   }

   private boolean isHovered(float mouseX, float mouseY, float targetWidth, float targetHeight) {
      return mouseX >= this.x && mouseX < this.x + targetWidth && mouseY >= this.y && mouseY < this.y + targetHeight;
   }

   private Text resolveNameText(Scoreboard scoreboard, ScoreboardEntry entry) {
      Text display = entry.display();
      if (display != null) {
         return display;
      }

      String owner = entry.owner();
      if (owner == null) {
         return Text.empty();
      }

      Team team = resolveTeam(scoreboard, owner);
      Text base = Text.literal(owner);
      return team != null ? Team.decorateName(team, base) : base;
   }

   private Text resolveValueText(ScoreboardObjective objective, ScoreboardEntry entry) {
      Text formatted = entry.formatted(objective.getNumberFormatOr(StyledNumberFormat.EMPTY));
      if (formatted != null) {
         return formatted;
      }

      return Text.literal(Integer.toString(entry.value()));
   }

   private Team resolveTeam(Scoreboard scoreboard, String owner) {
      if (scoreboard == null || owner == null) {
         return null;
      }

      try {
         Method method = scoreboard.getClass().getMethod("getScoreHolderTeam", String.class);
         Object result = method.invoke(scoreboard, owner);
         if (result instanceof Team team) {
            return team;
         }
      } catch (Throwable ignored) {
      }

      try {
         Method method = scoreboard.getClass().getMethod("getPlayerTeam", String.class);
         Object result = method.invoke(scoreboard, owner);
         if (result instanceof Team team) {
            return team;
         }
      } catch (Throwable ignored) {
      }

      return null;
   }
}
ss
ебанное говно
 
Назад
Сверху Снизу