Created
April 12, 2026 10:09
-
-
Save Ensamisten/4eeaf98dace21b264fe8c8c17bb415b0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package io.github.ensamisten.client.gui; | |
| import io.github.ensamisten.client.module.Extension; | |
| import io.github.ensamisten.client.module.ModuleManager; | |
| import io.github.ensamisten.client.module.player.CopyChat; | |
| import io.github.ensamisten.client.module.screen.*; | |
| import net.fabricmc.api.EnvType; | |
| import net.fabricmc.api.Environment; | |
| import net.minecraft.client.Minecraft; | |
| import net.minecraft.client.Options; | |
| import net.minecraft.client.gui.GuiGraphicsExtractor; | |
| import net.minecraft.client.gui.components.EditBox; | |
| import net.minecraft.client.gui.screens.Screen; | |
| import net.minecraft.client.gui.screens.options.OptionsScreen; | |
| import net.minecraft.client.input.CharacterEvent; | |
| import net.minecraft.client.input.KeyEvent; | |
| import net.minecraft.client.input.MouseButtonEvent; | |
| import net.minecraft.network.chat.Component; | |
| import org.lwjgl.glfw.GLFW; | |
| import java.util.*; | |
| @Environment(EnvType.CLIENT) | |
| public class AllyshipOptionsScreen extends OptionsScreen { | |
| // ── Sections ────────────────────────────────────────────────── | |
| private enum Section { MODULES, TOOLS } | |
| private static final List<String> MODULE_CATEGORIES = List.of( | |
| "Combat", "Movement", "Render", "Exploit", "World", "Player" | |
| ); | |
| // ── Tool card definitions ───────────────────────────────────── | |
| private record ToolCard(String name, String description, Screen screen) {} | |
| private List<ToolCard> buildToolCards() { | |
| return List.of( | |
| new ToolCard("Navigation", "Navigate anywhere", new NavigationScreen(this)), | |
| new ToolCard("Entity List", "List nearby entities", new EntityListScreen(Component.empty(), this)) | |
| ); | |
| } | |
| // ── Colors ──────────────────────────────────────────────────── | |
| private static final int COL_BG = 0xFF0D0D0D; | |
| private static final int COL_PANEL = 0xFF141414; | |
| private static final int COL_PANEL_BORDER = 0xFF2A2A2A; | |
| private static final int COL_GREEN = 0xFF33CC55; | |
| private static final int COL_GREEN_BG = 0xFF0D2B15; | |
| private static final int COL_GREEN_BORDER = 0xFF33CC55; | |
| private static final int COL_DARK_BTN = 0xFF1C1C1C; | |
| private static final int COL_DARK_SELECTED = 0xFF252525; | |
| private static final int COL_TEXT_DIM = 0xFF888888; | |
| private static final int COL_TEXT_WHITE = 0xFFDDDDDD; | |
| private static final int COL_TAB_INACTIVE_BG = 0xFF1A1A1A; | |
| private static final int COL_TAB_INACTIVE_TEXT = 0xFF888888; | |
| private static final int COL_ROW_DARK = 0xFF181818; | |
| private static final int COL_ROW_BORDER = 0xFF2A2A2A; | |
| private static final int COL_STAT_BG = 0xFF141414; | |
| private static final int COL_SCROLLBAR_BG = 0xFF111111; | |
| private static final int COL_SCROLLBAR_THUMB = 0xFF444444; | |
| private static final int COL_SCROLLBAR_ACTIVE = 0xFF33CC55; | |
| // ── Layout — modules ────────────────────────────────────────── | |
| private static final int LEFT_PANEL_X = 20; | |
| private static final int LEFT_PANEL_W = 160; | |
| private static final int CAT_LABEL_H = 24; | |
| private static final int CAT_BTN_H = 36; | |
| private static final int CAT_BTN_SPACING = 4; | |
| private static final int RIGHT_PANEL_X = LEFT_PANEL_X + LEFT_PANEL_W + 14; | |
| private static final int SEARCH_H = 24; | |
| private static final int ROW_H = 52; | |
| private static final int ROW_SPACING = 6; | |
| private static final int ROW_PADDING = 10; | |
| private static final int SCROLLBAR_W = 4; | |
| // ── Layout — tools ──────────────────────────────────────────── | |
| private static final int TOOLS_PADDING = 20; | |
| private static final int CARD_COLS = 3; | |
| private static final int CARD_H = 50; | |
| private static final int CARD_SPACING = 6; | |
| private static final int SECTION_HDR_H = 44; | |
| private static final int STAT_SECTION_H = 70; | |
| // ── Layout — shared ─────────────────────────────────────────── | |
| private static final int TAB_BAR_H = 24; | |
| private static final int TAB_BAR_Y = 4; | |
| private static final int CONTENT_TOP = TAB_BAR_Y + TAB_BAR_H + 6; | |
| // ── State ─────────────────────────────────────────��─────────── | |
| private Section activeSection = Section.MODULES; | |
| private String selectedCategory = "Combat"; | |
| private int moduleScrollOffset = 0; | |
| // ── Category scroll state ───────────────────────────────────── | |
| private int catScrollOffset = 0; // px scrolled in category panel | |
| private boolean catScrollDragging = false; | |
| private int catDragStartY = 0; | |
| private int catDragStartOffset = 0; | |
| private EditBox searchBox; | |
| private String searchQuery = ""; | |
| private final Minecraft client; | |
| public AllyshipOptionsScreen(Screen parent, Options options) { | |
| super(parent, options, true); | |
| this.client = Minecraft.getInstance(); | |
| } | |
| // ── Init ────────────────────────────────────────────────────── | |
| @Override | |
| protected void init() { | |
| int rightPanelW = width - RIGHT_PANEL_X - 20; | |
| int searchX = RIGHT_PANEL_X + 22; | |
| int searchY = CONTENT_TOP + 4; | |
| int searchW = rightPanelW - 26; | |
| searchBox = new EditBox(font, searchX, searchY, searchW, SEARCH_H, | |
| Component.literal("")); | |
| searchBox.setMaxLength(64); | |
| searchBox.setHint(Component.literal("SEARCH MODULES...")); | |
| searchBox.setBordered(false); | |
| searchBox.setResponder(q -> { | |
| searchQuery = q.trim().toLowerCase(); | |
| moduleScrollOffset = 0; | |
| }); | |
| addRenderableWidget(searchBox); | |
| searchBox.visible = (activeSection == Section.MODULES); | |
| } | |
| // ── Category scroll helpers ─────────────────────────────────── | |
| /** Total pixel height of all category buttons */ | |
| private int catTotalH() { | |
| int n = MODULE_CATEGORIES.size(); | |
| return n * CAT_BTN_H + Math.max(0, n - 1) * CAT_BTN_SPACING; | |
| } | |
| /** Visible height available for category buttons */ | |
| private int catVisibleH() { | |
| return (height - 10) - (CONTENT_TOP + CAT_LABEL_H); | |
| } | |
| /** Max scroll offset for category list */ | |
| private int catMaxScroll() { | |
| return Math.max(0, catTotalH() - catVisibleH()); | |
| } | |
| // ── Helpers ─────────────────────────────────────────────────── | |
| private List<Extension> visibleModules() { | |
| List<Extension> result = new ArrayList<>(); | |
| for (Extension m : ModuleManager.getAll()) { | |
| boolean catMatch = searchQuery.isEmpty() | |
| ? m.getCategory().equalsIgnoreCase(selectedCategory) | |
| : MODULE_CATEGORIES.stream() | |
| .anyMatch(c -> c.equalsIgnoreCase(m.getCategory())); | |
| boolean searchMatch = searchQuery.isEmpty() | |
| || m.getName().toLowerCase().contains(searchQuery); | |
| if (catMatch && searchMatch) result.add(m); | |
| } | |
| return result; | |
| } | |
| // ── Statistics ──────────────────────────────────────────────── | |
| private int countEnabled() { return (int) ModuleManager.getAll().stream().filter(Extension::isEnabled).count(); } | |
| private int countTotal() { return ModuleManager.getAll().size(); } | |
| private int countCategories() { return (int) ModuleManager.getAll().stream().map(Extension::getCategory).distinct().count(); } | |
| private int countTools() { return buildToolCards().size(); } | |
| // ── Mouse ───────────────────────────────────────────────────── | |
| private double lastMouseY = 0; | |
| @Override | |
| public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) { | |
| double mx = event.x(); | |
| double my = event.y(); | |
| int btn = event.button(); | |
| // Tab bar | |
| int tabW = (width - 10) / 2; | |
| if (my >= TAB_BAR_Y && my <= TAB_BAR_Y + TAB_BAR_H) { | |
| if (mx >= 5 && mx <= 5 + tabW) { setSection(Section.MODULES); return true; } | |
| if (mx >= 5 + tabW + 2 && mx <= width - 5) { setSection(Section.TOOLS); return true; } | |
| } | |
| if (activeSection == Section.MODULES) { | |
| int scrollbarX = LEFT_PANEL_X + LEFT_PANEL_W - SCROLLBAR_W - 2; | |
| if (btn == GLFW.GLFW_MOUSE_BUTTON_LEFT && catMaxScroll() > 0 | |
| && mx >= scrollbarX && mx <= scrollbarX + SCROLLBAR_W) { | |
| catScrollDragging = true; | |
| catDragStartY = (int) my; | |
| catDragStartOffset = catScrollOffset; | |
| lastMouseY = my; | |
| return true; | |
| } | |
| return handleModulesClick(mx, my, btn, event, doubleClick); | |
| } else { | |
| return handleToolsClick(mx, my, btn, event, doubleClick); | |
| } | |
| } | |
| @Override | |
| public boolean mouseReleased(final MouseButtonEvent event) { | |
| if (event.button() == GLFW.GLFW_MOUSE_BUTTON_LEFT) catScrollDragging = false; | |
| return super.mouseReleased(event); | |
| } | |
| @Override | |
| public boolean mouseDragged(final MouseButtonEvent event, final double dx, final double dy) { | |
| if (catScrollDragging && event.button() == GLFW.GLFW_MOUSE_BUTTON_LEFT && catMaxScroll() > 0) { | |
| // dy is a delta — accumulate it into an absolute position | |
| lastMouseY += dy; | |
| int catListH = catVisibleH(); | |
| int thumbH = Math.max(20, catListH * catListH / Math.max(1, catTotalH())); | |
| int trackH = catListH - thumbH; | |
| if (trackH > 0) { | |
| // Use accumulated absolute Y relative to drag start | |
| int delta = (int)(lastMouseY - catDragStartY); | |
| catScrollOffset = Math.clamp( | |
| catDragStartOffset + delta * catMaxScroll() / trackH, | |
| 0, catMaxScroll()); | |
| } | |
| return true; | |
| } | |
| return super.mouseDragged(event, dx, dy); | |
| } | |
| private void setSection(Section s) { | |
| activeSection = s; | |
| moduleScrollOffset = 0; | |
| searchBox.visible = (s == Section.MODULES); | |
| if (s == Section.MODULES) selectedCategory = MODULE_CATEGORIES.get(0); | |
| } | |
| private boolean handleModulesClick(double mx, double my, int btn, | |
| MouseButtonEvent event, boolean doubleClick) { | |
| if (btn == GLFW.GLFW_MOUSE_BUTTON_LEFT) { | |
| int panelBottom = height - 10; | |
| int listTop = CONTENT_TOP + CAT_LABEL_H; | |
| int listBottom = panelBottom; | |
| int catY = listTop - catScrollOffset; | |
| for (String cat : MODULE_CATEGORIES) { | |
| int visY0 = catY; | |
| int visY1 = catY + CAT_BTN_H; | |
| // Only clickable if within the visible clipped area | |
| if (visY1 > listTop && visY0 < listBottom) { | |
| if (mx >= LEFT_PANEL_X && mx <= LEFT_PANEL_X + LEFT_PANEL_W | |
| && my >= Math.max(visY0, listTop) | |
| && my <= Math.min(visY1, listBottom)) { | |
| selectedCategory = cat; | |
| moduleScrollOffset = 0; | |
| return true; | |
| } | |
| } | |
| catY += CAT_BTN_H + CAT_BTN_SPACING; | |
| } | |
| } | |
| // Module rows | |
| int rightPanelW = width - RIGHT_PANEL_X - 20; | |
| int listTop = CONTENT_TOP + SEARCH_H + 12; | |
| int rowY = listTop - moduleScrollOffset; | |
| for (Extension m : visibleModules()) { | |
| if (rowY + ROW_H >= listTop && rowY <= height - 10) { | |
| if (mx >= RIGHT_PANEL_X && mx <= RIGHT_PANEL_X + rightPanelW | |
| && my >= rowY && my <= rowY + ROW_H) { | |
| if (btn == GLFW.GLFW_MOUSE_BUTTON_RIGHT) { | |
| Screen cfg = getConfigScreen(m.getName()); | |
| if (cfg != null) { client.setScreen(cfg); return true; } | |
| } | |
| m.toggle(); return true; | |
| } | |
| } | |
| rowY += ROW_H + ROW_SPACING; | |
| } | |
| return super.mouseClicked(event, doubleClick); | |
| } | |
| private boolean handleToolsClick(double mx, double my, int btn, | |
| MouseButtonEvent event, boolean doubleClick) { | |
| if (btn != GLFW.GLFW_MOUSE_BUTTON_LEFT) return super.mouseClicked(event, doubleClick); | |
| int contentX = TOOLS_PADDING; | |
| int contentW = width - TOOLS_PADDING * 2; | |
| List<ToolCard> cards = buildToolCards(); | |
| int cardW = (contentW - CARD_SPACING * (CARD_COLS - 1) - 20) / CARD_COLS; | |
| int rows = (int) Math.ceil((double) cards.size() / CARD_COLS); | |
| int headerH = 14 + font.lineHeight + 4 + font.lineHeight + 12; | |
| int cardStartY = CONTENT_TOP + headerH; | |
| for (int row = 0; row < rows; row++) { | |
| for (int col = 0; col < CARD_COLS; col++) { | |
| int idx = row * CARD_COLS + col; | |
| if (idx >= cards.size()) break; | |
| int cx = contentX + 10 + col * (cardW + CARD_SPACING); | |
| int cy = cardStartY + row * (CARD_H + CARD_SPACING); | |
| if (mx >= cx && mx <= cx + cardW && my >= cy && my <= cy + CARD_H) { | |
| ToolCard card = cards.get(idx); | |
| if (card.screen() != null) { client.setScreen(card.screen()); return true; } | |
| } | |
| } | |
| } | |
| return super.mouseClicked(event, doubleClick); | |
| } | |
| @Override | |
| public boolean mouseScrolled(double mx, double my, double hAmt, double vAmt) { | |
| if (activeSection == Section.MODULES) { | |
| // Scroll category list when hovering left panel | |
| if (mx >= LEFT_PANEL_X && mx <= LEFT_PANEL_X + LEFT_PANEL_W) { | |
| catScrollOffset = Math.clamp( | |
| catScrollOffset - (int)(vAmt * (CAT_BTN_H + CAT_BTN_SPACING)), | |
| 0, catMaxScroll()); | |
| return true; | |
| } | |
| // Scroll module list when hovering right panel | |
| int rightPanelW = width - RIGHT_PANEL_X - 20; | |
| if (mx >= RIGHT_PANEL_X && mx <= RIGHT_PANEL_X + rightPanelW) { | |
| int maxScroll = Math.max(0, | |
| visibleModules().size() * (ROW_H + ROW_SPACING) | |
| - (height - CONTENT_TOP - SEARCH_H - 20)); | |
| moduleScrollOffset = Math.clamp( | |
| moduleScrollOffset - (int)(vAmt * (ROW_H + ROW_SPACING)), | |
| 0, maxScroll); | |
| return true; | |
| } | |
| } | |
| return super.mouseScrolled(mx, my, hAmt, vAmt); | |
| } | |
| // ── Keyboard ─────────────────────��──────────────────────────── | |
| @Override | |
| public boolean keyPressed(KeyEvent event) { | |
| if (event.key() == GLFW.GLFW_KEY_ESCAPE && !searchQuery.isEmpty()) { | |
| searchBox.setValue(""); searchQuery = ""; return true; | |
| } | |
| if (activeSection == Section.MODULES | |
| && searchBox.isFocused() && searchBox.keyPressed(event)) return true; | |
| return super.keyPressed(event); | |
| } | |
| @Override | |
| public boolean charTyped(CharacterEvent event) { | |
| if (activeSection == Section.MODULES | |
| && searchBox.isFocused() && searchBox.charTyped(event)) return true; | |
| return super.charTyped(event); | |
| } | |
| // ── Background ──────────────────────────────────────────────── | |
| @Override | |
| public void extractBackground(GuiGraphicsExtractor g, int mx, int my, float a) { | |
| g.fill(0, 0, width, height, COL_BG); | |
| } | |
| // ── Main render ─────────────────────────────────────────────── | |
| @Override | |
| public void extractRenderState(GuiGraphicsExtractor g, int mx, int my, float a) { | |
| g.fill(0, 0, width, height, COL_BG); | |
| renderTabBar(g, mx, my); | |
| if (activeSection == Section.MODULES) { | |
| renderModulesSection(g, mx, my, a); | |
| } else { | |
| renderToolsSection(g, mx, my); | |
| } | |
| super.extractRenderState(g, mx, my, a); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // TAB BAR | |
| // ───────────────────────────────────────────────────────────── | |
| private void renderTabBar(GuiGraphicsExtractor g, int mx, int my) { | |
| int tabW = (width - 10) / 2; | |
| boolean modActive = activeSection == Section.MODULES; | |
| g.fill(5, TAB_BAR_Y, 5 + tabW, TAB_BAR_Y + TAB_BAR_H, | |
| modActive ? COL_GREEN : COL_TAB_INACTIVE_BG); | |
| drawBorder(g, 5, TAB_BAR_Y, 5 + tabW, TAB_BAR_Y + TAB_BAR_H, COL_PANEL_BORDER); | |
| g.centeredText(font, "MODULES", | |
| 5 + tabW / 2, TAB_BAR_Y + TAB_BAR_H / 2 - font.lineHeight / 2, | |
| modActive ? COL_BG : COL_TAB_INACTIVE_TEXT); | |
| boolean toolActive = activeSection == Section.TOOLS; | |
| int toolX0 = 5 + tabW + 2, toolX1 = width - 5; | |
| g.fill(toolX0, TAB_BAR_Y, toolX1, TAB_BAR_Y + TAB_BAR_H, | |
| toolActive ? COL_GREEN : COL_TAB_INACTIVE_BG); | |
| drawBorder(g, toolX0, TAB_BAR_Y, toolX1, TAB_BAR_Y + TAB_BAR_H, COL_PANEL_BORDER); | |
| g.centeredText(font, "TOOLS", | |
| toolX0 + (toolX1 - toolX0) / 2, TAB_BAR_Y + TAB_BAR_H / 2 - font.lineHeight / 2, | |
| toolActive ? COL_BG : COL_TAB_INACTIVE_TEXT); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // MODULES SECTION | |
| // ───────────────────────────────────────────────────────────── | |
| private void renderModulesSection(GuiGraphicsExtractor g, int mx, int my, float a) { | |
| int panelBottom = height - 10; | |
| // ── Left panel ──────────────────────────────────────────── | |
| g.fill(LEFT_PANEL_X, CONTENT_TOP, LEFT_PANEL_X + LEFT_PANEL_W, panelBottom, COL_PANEL); | |
| drawBorder(g, LEFT_PANEL_X, CONTENT_TOP, LEFT_PANEL_X + LEFT_PANEL_W, panelBottom, COL_PANEL_BORDER); | |
| g.text(font, "CATEGORIES", LEFT_PANEL_X + 10, CONTENT_TOP + 8, COL_TEXT_DIM); | |
| // ── Category list (scissored) ───────────────────────────── | |
| int listTop = CONTENT_TOP + CAT_LABEL_H; | |
| int listBottom = panelBottom - 4; | |
| g.enableScissor(LEFT_PANEL_X + 2, listTop, LEFT_PANEL_X + LEFT_PANEL_W - 2, listBottom); | |
| int catY = listTop - catScrollOffset; | |
| for (String cat : MODULE_CATEGORIES) { | |
| boolean selected = cat.equalsIgnoreCase(selectedCategory); | |
| boolean hovered = mx >= LEFT_PANEL_X && mx <= LEFT_PANEL_X + LEFT_PANEL_W | |
| && my >= catY && my <= catY + CAT_BTN_H | |
| && catY >= listTop && catY + CAT_BTN_H <= listBottom; | |
| g.fill(LEFT_PANEL_X + 2, catY, | |
| LEFT_PANEL_X + LEFT_PANEL_W - 2, catY + CAT_BTN_H, | |
| selected ? COL_GREEN : (hovered ? COL_DARK_SELECTED : COL_DARK_BTN)); | |
| if (selected) | |
| g.fill(LEFT_PANEL_X + 2, catY, LEFT_PANEL_X + 5, catY + CAT_BTN_H, COL_GREEN); | |
| g.text(font, cat, | |
| LEFT_PANEL_X + 14, catY + CAT_BTN_H / 2 - font.lineHeight / 2, | |
| selected ? COL_BG : COL_TEXT_WHITE); | |
| catY += CAT_BTN_H + CAT_BTN_SPACING; | |
| } | |
| g.disableScissor(); | |
| // ── Category scrollbar ────────────────��─────────────────── | |
| if (catMaxScroll() > 0) { | |
| int catListH = listBottom - listTop; | |
| int scrollbarX = LEFT_PANEL_X + LEFT_PANEL_W - SCROLLBAR_W - 2; | |
| int thumbH = Math.max(20, catListH * catListH / Math.max(1, catTotalH())); | |
| int trackH = catListH - thumbH; | |
| int thumbY = listTop + (trackH > 0 | |
| ? catScrollOffset * trackH / catMaxScroll() : 0); | |
| // Track | |
| g.fill(scrollbarX, listTop, scrollbarX + SCROLLBAR_W, listBottom, COL_SCROLLBAR_BG); | |
| // Thumb | |
| g.fill(scrollbarX, thumbY, scrollbarX + SCROLLBAR_W, thumbY + thumbH, | |
| catScrollDragging ? COL_SCROLLBAR_ACTIVE : COL_SCROLLBAR_THUMB); | |
| } | |
| // ── Right panel ─────────────────────────────────────────── | |
| int rightPanelW = width - RIGHT_PANEL_X - 20; | |
| g.fill(RIGHT_PANEL_X, CONTENT_TOP, RIGHT_PANEL_X + rightPanelW, panelBottom, COL_PANEL); | |
| drawBorder(g, RIGHT_PANEL_X, CONTENT_TOP, RIGHT_PANEL_X + rightPanelW, panelBottom, COL_PANEL_BORDER); | |
| int searchRowY = CONTENT_TOP + 4; | |
| g.fill(RIGHT_PANEL_X + 4, searchRowY, | |
| RIGHT_PANEL_X + rightPanelW - 4, searchRowY + SEARCH_H, COL_DARK_BTN); | |
| drawBorder(g, RIGHT_PANEL_X + 4, searchRowY, | |
| RIGHT_PANEL_X + rightPanelW - 4, searchRowY + SEARCH_H, COL_PANEL_BORDER); | |
| g.text(font, "Q", RIGHT_PANEL_X + 8, searchRowY + SEARCH_H / 2 - font.lineHeight / 2, COL_TEXT_DIM); | |
| int modListTop = searchRowY + SEARCH_H + 8; | |
| g.enableScissor(RIGHT_PANEL_X + 4, modListTop, | |
| RIGHT_PANEL_X + rightPanelW - 4, panelBottom - 4); | |
| int rowY = modListTop - moduleScrollOffset; | |
| for (Extension m : visibleModules()) { | |
| renderModuleRow(g, mx, my, m, rowY, rightPanelW); | |
| rowY += ROW_H + ROW_SPACING; | |
| } | |
| g.disableScissor(); | |
| } | |
| private void renderModuleRow(GuiGraphicsExtractor g, int mx, int my, | |
| Extension m, int rowY, int rightPanelW) { | |
| boolean enabled = m.isEnabled(); | |
| boolean hovered = mx >= RIGHT_PANEL_X + 4 | |
| && mx <= RIGHT_PANEL_X + rightPanelW - 4 | |
| && my >= rowY && my <= rowY + ROW_H; | |
| int rowX = RIGHT_PANEL_X + 4, rowX2 = rowX + rightPanelW - 8, rowY2 = rowY + ROW_H; | |
| g.fill(rowX, rowY, rowX2, rowY2, enabled ? COL_GREEN_BG : COL_ROW_DARK); | |
| drawBorder(g, rowX, rowY, rowX2, rowY2, | |
| enabled ? COL_GREEN_BORDER : (hovered ? 0xFF3A3A3A : COL_ROW_BORDER)); | |
| if (enabled) g.fill(rowX, rowY, rowX + 3, rowY2, COL_GREEN); | |
| g.text(font, m.getName(), rowX + ROW_PADDING, rowY + 10, | |
| enabled ? COL_GREEN : COL_TEXT_WHITE); | |
| g.text(font, getModuleDescription(m), | |
| rowX + ROW_PADDING, rowY + 10 + font.lineHeight + 4, COL_TEXT_DIM); | |
| int checkX = rowX2 - 18, checkY = rowY + ROW_H / 2 - font.lineHeight / 2; | |
| if (enabled) { | |
| g.text(font, "✔", checkX, checkY, COL_GREEN); | |
| } else { | |
| drawBorder(g, checkX, checkY, checkX + 10, checkY + 10, COL_TEXT_DIM); | |
| } | |
| } | |
| // ───────────────────────────────────���───────────────────────── | |
| // TOOLS SECTION | |
| // ───────────────────────────────────────────────────────────── | |
| private void renderToolsSection(GuiGraphicsExtractor g, int mx, int my) { | |
| int contentX = TOOLS_PADDING; | |
| int contentW = width - TOOLS_PADDING * 2; | |
| List<ToolCard> cards = buildToolCards(); | |
| int cardW = (contentW - CARD_SPACING * (CARD_COLS - 1) - 20) / CARD_COLS; | |
| int rows = (int) Math.ceil((double) cards.size() / CARD_COLS); | |
| int headerH = 14 + font.lineHeight + 4 + font.lineHeight + 12; | |
| int gridH = rows * (CARD_H + CARD_SPACING); | |
| int statsH = 10 + font.lineHeight + 10 + font.lineHeight + font.lineHeight + 4 + 10; | |
| int totalInnerH = headerH + gridH + 16 + statsH + 10; | |
| int panelBottom = Math.min(height - 10, CONTENT_TOP + totalInnerH); | |
| g.fill(contentX, CONTENT_TOP, contentX + contentW, panelBottom, COL_PANEL); | |
| drawBorder(g, contentX, CONTENT_TOP, contentX + contentW, panelBottom, COL_PANEL_BORDER); | |
| int y = CONTENT_TOP + 14; | |
| g.text(font, "✦", contentX + 10, y, COL_GREEN); | |
| g.text(font, " UTILITIES", contentX + 22, y, COL_TEXT_WHITE); | |
| y += font.lineHeight + 4; | |
| g.text(font, "Manage client settings and configurations", contentX + 10, y, COL_TEXT_DIM); | |
| y += font.lineHeight + 12; | |
| int gridX = contentX + 10, cardStartY = y; | |
| for (int row = 0; row < rows; row++) { | |
| for (int col = 0; col < CARD_COLS; col++) { | |
| int idx = row * CARD_COLS + col; | |
| if (idx >= cards.size()) break; | |
| int cx = gridX + col * (cardW + CARD_SPACING); | |
| int cy = cardStartY + row * (CARD_H + CARD_SPACING); | |
| renderToolCard(g, mx, my, cards.get(idx), cx, cy, cardW); | |
| } | |
| } | |
| y = cardStartY + rows * (CARD_H + CARD_SPACING) + 16; | |
| int statsTop = y, statsBottom = statsTop + statsH; | |
| g.fill(contentX + 6, statsTop, contentX + contentW - 6, statsBottom, COL_STAT_BG); | |
| drawBorder(g, contentX + 6, statsTop, contentX + contentW - 6, statsBottom, COL_PANEL_BORDER); | |
| int statY = statsTop + 10; | |
| g.text(font, "▐ STATISTICS", contentX + 16, statY, COL_TEXT_WHITE); | |
| statY += font.lineHeight + 10; | |
| int[] statValues = { countEnabled(), countTotal(), countCategories(), countTools() }; | |
| String[] statLabels = { "ENABLED", "TOTAL MODULES", "CATEGORIES", "TOOLS" }; | |
| int colW = (contentW - 32) / statLabels.length; | |
| for (int i = 0; i < statLabels.length; i++) { | |
| int sx = contentX + 16 + i * colW; | |
| g.text(font, statLabels[i], sx, statY, COL_TEXT_DIM); | |
| g.text(font, String.valueOf(statValues[i]), sx, statY + font.lineHeight + 4, COL_GREEN); | |
| } | |
| } | |
| private void renderToolCard(GuiGraphicsExtractor g, int mx, int my, | |
| ToolCard card, int cx, int cy, int cardW) { | |
| boolean hovered = mx >= cx && mx <= cx + cardW && my >= cy && my <= cy + CARD_H; | |
| boolean hasScreen = card.screen() != null; | |
| g.fill(cx, cy, cx + cardW, cy + CARD_H, hovered ? COL_DARK_SELECTED : COL_DARK_BTN); | |
| drawBorder(g, cx, cy, cx + cardW, cy + CARD_H, | |
| (hovered && hasScreen) ? COL_GREEN_BORDER : COL_PANEL_BORDER); | |
| if (hovered && hasScreen) g.fill(cx, cy, cx + 3, cy + CARD_H, COL_GREEN); | |
| int totalTextH = font.lineHeight + 4 + font.lineHeight; | |
| int textY = cy + (CARD_H - totalTextH) / 2; | |
| g.text(font, "⚙", cx + 10, textY, COL_GREEN); | |
| g.text(font, card.name(), cx + 24, textY, COL_TEXT_WHITE); | |
| g.text(font, card.description(), cx + 10, textY + font.lineHeight + 4, COL_TEXT_DIM); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // SHARED UTILITIES | |
| // ───────────────────────────────────────────────────────────── | |
| private String getModuleDescription(Extension m) { return m.getCategory(); } | |
| CopyChat copyChat = (CopyChat) ModuleManager.getAll().stream() | |
| .filter(m -> m instanceof CopyChat).findFirst().orElse(null); | |
| private Screen getConfigScreen(String name) { | |
| return switch (name) { | |
| case "FriendGuard" -> new FriendGuardConfigScreen(this); | |
| case "KillAura" -> new KillAuraConfigScreen(this); | |
| case "WhomStruckMeLast" -> new WhomStruckMeLastScreen(this); | |
| case "CopyChat" -> new CopyChatScreen(this, copyChat); | |
| default -> null; | |
| }; | |
| } | |
| private void drawBorder(GuiGraphicsExtractor g, | |
| int x0, int y0, int x1, int y1, int color) { | |
| g.fill(x0, y0, x1, y0 + 1, color); | |
| g.fill(x0, y1 - 1, x1, y1, color); | |
| g.fill(x0, y0, x0 + 1, y1, color); | |
| g.fill(x1 - 1, y0, x1, y1, color); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment