Skip to content

Instantly share code, notes, and snippets.

@Ensamisten
Created April 12, 2026 10:09
Show Gist options
  • Select an option

  • Save Ensamisten/4eeaf98dace21b264fe8c8c17bb415b0 to your computer and use it in GitHub Desktop.

Select an option

Save Ensamisten/4eeaf98dace21b264fe8c8c17bb415b0 to your computer and use it in GitHub Desktop.
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