From a973eca54cdb42db402abc99cae01ab63eee92c5 Mon Sep 17 00:00:00 2001 From: Kam Date: Tue, 4 Feb 2025 14:38:13 -0500 Subject: [PATCH] Huge Chart Implementation --- .../npcs/client/gui/global/GuiDiagram.java | 713 ++++++++++++------ .../npcs/client/gui/global/GuiMagicMap.java | 4 +- .../npcs/controllers/MagicController.java | 20 +- 3 files changed, 511 insertions(+), 226 deletions(-) diff --git a/src/main/java/noppes/npcs/client/gui/global/GuiDiagram.java b/src/main/java/noppes/npcs/client/gui/global/GuiDiagram.java index 2c0ee2fac..404e3628d 100644 --- a/src/main/java/noppes/npcs/client/gui/global/GuiDiagram.java +++ b/src/main/java/noppes/npcs/client/gui/global/GuiDiagram.java @@ -14,56 +14,63 @@ import org.lwjgl.opengl.GL11; /** * Abstract GUI class for drawing diagrams with icons (nodes) and connections (arrows). * - * Features: - * - Uses a parent GuiNPCInterface to disable mouse interaction when a subgui is active. - * - Caches icons and connections (subclasses must implement createIcons() and createConnections()). - * - DiagramIcon now has an index (for ring, row, or level) and a priority (for ordering). - * - New manual layout types (CIRCULAR_MANUAL, SQUARE_MANUAL, TREE_MANUAL) use index and priority - * to order nodes. - * - Arrow-drawing options: curvedArrows (if false, straight lines) and curveAngle (in degrees). - * - For curved arrows, hit-detection is done by approximating the quadratic Bezier with 20 segments. + * Features include: + * */ public abstract class GuiDiagram extends Gui { - // Diagram Area & Controls - protected int x, y, width, height; - protected float panX = 0, panY = 0; - protected float zoom = 1.0f; - protected boolean dragging = false; - protected int lastDragX, lastDragY; + // --- Diagram Area & Controls --- + protected int x, y, width, height; // Drawing area for the diagram + protected float panX = 0, panY = 0; // Panning offsets + protected float zoom = 1.0f; // Zoom factor + protected boolean dragging = false; // Flag indicating active panning + protected int lastDragX, lastDragY; // Last mouse positions used for panning - // Node (Icon) Sizing - protected int iconSize = 16; - protected int slotPadding = 4; - protected int slotSize = iconSize + slotPadding; + // --- Node (Icon) Sizing --- + protected int iconSize = 16; // Icon image size (square) + protected int slotPadding = 4; // Padding around icons + protected int slotSize = iconSize + slotPadding; // Total size of the icon box - // Node Box Appearance - protected int iconBackgroundColor = 0xFF999999; - protected int iconBorderColor = 0xFF555555; - protected int iconBorderThickness = 1; + // --- Icon Box Appearance --- + protected int iconBackgroundColor = 0xFF999999; // Background color for icon boxes + protected int iconBorderColor = 0xFF555555; // Border color for icon boxes + protected int iconBorderThickness = 1; // Border thickness in pixels - // Connection Options - protected boolean showArrowHeads = true; - protected boolean useColorScaling = true; + // --- Connection Options --- + protected boolean showArrowHeads = true; // Whether to draw arrow heads + protected boolean useColorScaling = true; // Scale connection color based on a percentage - // Layout Options + // --- Layout Options --- public enum DiagramLayout { CIRCULAR, SQUARE, TREE, GENERATED, - CIRCULAR_MANUAL, SQUARE_MANUAL, TREE_MANUAL + CIRCULAR_MANUAL, SQUARE_MANUAL, TREE_MANUAL, + CHART } - protected DiagramLayout layout = DiagramLayout.CIRCULAR; + protected DiagramLayout layout = DiagramLayout.CIRCULAR; // Default layout /** - * Sets the layout and invalidates caches. + * Sets the layout type and invalidates caches. */ public void setLayout(DiagramLayout layout) { this.layout = layout; invalidateCache(); } - // Arrow Drawing Options - protected boolean curvedArrows = true; // if false, straight lines are drawn. - protected int curveAngle = 15; // in degrees; adjust for "hardness" of curve. + // --- Arrow Drawing Options --- + protected boolean curvedArrows = true; // If false, draw straight connection lines + protected int curveAngle = 15; // Curvature angle in degrees public void setCurvedArrows(boolean curved) { this.curvedArrows = curved; @@ -72,13 +79,13 @@ public abstract class GuiDiagram extends Gui { this.curveAngle = angle; } - // Caching for positions and for icons/connections. - protected Map cachedPositions = null; - protected List iconsCache = null; - protected List connectionsCache = null; + // --- Caching for icons and connections --- + protected Map cachedPositions = null; // Cached positions for nodes (non-CHART) + protected List iconsCache = null; // Cached list of icons + protected List connectionsCache = null; // Cached list of connections /** - * Clears all caches. + * Clears cached layout information. */ public void invalidateCache() { cachedPositions = null; @@ -86,10 +93,10 @@ public abstract class GuiDiagram extends Gui { connectionsCache = null; } - // Parent Reference + // --- Parent reference --- protected GuiNPCInterface parent; - // Constructor + // --- Constructor --- public GuiDiagram(GuiNPCInterface parent, int x, int y, int width, int height) { this.parent = parent; this.x = x; @@ -98,7 +105,7 @@ public abstract class GuiDiagram extends Gui { this.height = height; } - // Data Providers – Subclasses must implement these. + // --- Data Providers (Subclasses must implement these) --- protected abstract List createIcons(); protected abstract List createConnections(); @@ -120,7 +127,14 @@ public abstract class GuiDiagram extends Gui { */ protected abstract void renderIcon(DiagramIcon icon, int posX, int posY, IconRenderState state); + /** + * Returns the tooltip text lines for an icon. + */ protected List getIconTooltip(DiagramIcon icon) { return null; } + + /** + * Returns the tooltip text lines for a connection. + */ protected List getConnectionTooltip(DiagramConnection conn) { List tooltip = new ArrayList<>(); DiagramIcon iconFrom = getIconById(conn.idFrom); @@ -138,33 +152,41 @@ public abstract class GuiDiagram extends Gui { } protected String getIconName(DiagramIcon icon) { return "Icon " + icon.id; } - // Layout Calculation – Chooses algorithm based on layout type. + // --- Layout Calculation --- + /** + * For non‑CHART layouts, returns the mapping from node ID to (x,y) position. + * CHART layout is drawn directly. + */ protected Map calculatePositions() { if (cachedPositions != null) return cachedPositions; + if (layout == DiagramLayout.CHART) { + return new HashMap<>(); + } switch (layout) { - case CIRCULAR: cachedPositions = calculateCircularPositions(); break; - case SQUARE: cachedPositions = calculateSquarePositions(); break; - case TREE: cachedPositions = calculateTreePositions(); break; - case GENERATED: cachedPositions = calculateGeneratedPositions(); break; - case CIRCULAR_MANUAL: cachedPositions = calculateCircularManualPositions(); break; - case SQUARE_MANUAL: cachedPositions = calculateSquareManualPositions(); break; - case TREE_MANUAL: cachedPositions = calculateTreeManualPositions(); break; - default: cachedPositions = calculateCircularPositions(); break; + case CIRCULAR: cachedPositions = calculateCircularPositions(); break; + case SQUARE: cachedPositions = calculateSquarePositions(); break; + case TREE: cachedPositions = calculateTreePositions(); break; + case GENERATED: cachedPositions = calculateGeneratedPositions(); break; + case CIRCULAR_MANUAL: cachedPositions = calculateCircularManualPositions(); break; + case SQUARE_MANUAL: cachedPositions = calculateSquareManualPositions(); break; + case TREE_MANUAL: cachedPositions = calculateTreeManualPositions(); break; + default: cachedPositions = calculateCircularPositions(); break; } return cachedPositions; } - // Standard Layouts: + // --- Standard Layout Methods --- + protected Map calculateCircularPositions() { List icons = getIcons(); Map positions = new HashMap<>(); int count = icons.size(); - int centerX = x + width / 2, centerY = y + height / 2; - int radius = Math.min(width, height) / 2 - iconSize - 10; + int centerX = x + width/2, centerY = y + height/2; + int radius = Math.min(width, height)/2 - iconSize - 10; for (int i = 0; i < count; i++) { double angle = 2 * Math.PI * i / count; - int posX = centerX + (int) (radius * Math.cos(angle)); - int posY = centerY + (int) (radius * Math.sin(angle)); + int posX = centerX + (int)(radius * Math.cos(angle)); + int posY = centerY + (int)(radius * Math.sin(angle)); positions.put(icons.get(i).id, new Point(posX, posY)); } return positions; @@ -174,14 +196,14 @@ public abstract class GuiDiagram extends Gui { List icons = getIcons(); Map positions = new HashMap<>(); int count = icons.size(); - int n = (int) Math.ceil(Math.sqrt(count)); + int n = (int)Math.ceil(Math.sqrt(count)); int gridWidth = width - 20, gridHeight = height - 20; int cellWidth = gridWidth / n, cellHeight = gridHeight / n; - int startX = x + (width - gridWidth) / 2, startY = y + (height - gridHeight) / 2; + int startX = x + (width - gridWidth)/2, startY = y + (height - gridHeight)/2; for (int i = 0; i < count; i++) { int row = i / n, col = i % n; - int posX = startX + col * cellWidth + cellWidth / 2; - int posY = startY + row * cellHeight + cellHeight / 2; + int posX = startX + col * cellWidth + cellWidth/2; + int posY = startY + row * cellHeight + cellHeight/2; positions.put(icons.get(i).id, new Point(posX, posY)); } return positions; @@ -191,15 +213,15 @@ public abstract class GuiDiagram extends Gui { List icons = getIcons(); Map positions = new HashMap<>(); int count = icons.size(); - int levels = (int) Math.ceil(Math.log(count + 1) / Math.log(2)); + int levels = (int)Math.ceil(Math.log(count + 1) / Math.log(2)); int levelHeight = height / (levels + 1); int index = 0; for (int level = 0; level < levels && index < count; level++) { - int nodesThisLevel = (int) Math.min(Math.pow(2, level), count - index); + int nodesThisLevel = (int)Math.min(Math.pow(2, level), count - index); int levelWidth = width - 20; int cellWidth = levelWidth / nodesThisLevel; for (int i = 0; i < nodesThisLevel && index < count; i++, index++) { - int posX = x + 10 + i * cellWidth + cellWidth / 2; + int posX = x + 10 + i * cellWidth + cellWidth/2; int posY = y + (level + 1) * levelHeight; positions.put(icons.get(index).id, new Point(posX, posY)); } @@ -213,8 +235,8 @@ public abstract class GuiDiagram extends Gui { Map> graph = buildGraph(icons, connections); List> clusters = getConnectedComponents(graph); int clusterCount = clusters.size(); - int gridCols = (int) Math.ceil(Math.sqrt(clusterCount)); - int gridRows = (int) Math.ceil((double) clusterCount / gridCols); + int gridCols = (int)Math.ceil(Math.sqrt(clusterCount)); + int gridRows = (int)Math.ceil((double)clusterCount / gridCols); int clusterMargin = 20; int compWidth = (width - (gridCols + 1) * clusterMargin) / gridCols; int compHeight = (height - (gridRows + 1) * clusterMargin) / gridRows; @@ -240,7 +262,7 @@ public abstract class GuiDiagram extends Gui { return globalPositions; } - // Helpers for generated layout. + // --- Helpers for Generated Layout --- private Map> buildGraph(List icons, List connections) { Map> graph = new HashMap<>(); for (DiagramIcon icon : icons) { @@ -270,9 +292,8 @@ public abstract class GuiDiagram extends Gui { visited.add(current); comp.add(current); for (Integer neighbor : graph.get(current)) { - if (!visited.contains(neighbor)) { + if (!visited.contains(neighbor)) dfsComponent(neighbor, graph, visited, comp); - } } } private boolean isCycle(Set cluster, Map> graph) { @@ -298,11 +319,11 @@ public abstract class GuiDiagram extends Gui { } private boolean isSquare(Set cluster) { int n = cluster.size(); - int sqrt = (int) Math.round(Math.sqrt(n)); + int sqrt = (int)Math.round(Math.sqrt(n)); return sqrt * sqrt == n; } - // Layout algorithms for clusters: + // --- Layout Algorithms for Clusters --- private Map layoutCycle(Set cluster, int compX, int compY, int compWidth, int compHeight) { Map positions = new HashMap<>(); int centerX = compX + compWidth / 2, centerY = compY + compHeight / 2; @@ -310,8 +331,8 @@ public abstract class GuiDiagram extends Gui { int i = 0; for (Integer id : cluster) { double angle = 2 * Math.PI * i / cluster.size(); - int posX = centerX + (int) (radius * Math.cos(angle)); - int posY = centerY + (int) (radius * Math.sin(angle)); + int posX = centerX + (int)(radius * Math.cos(angle)); + int posY = centerY + (int)(radius * Math.sin(angle)); positions.put(id, new Point(posX, posY)); i++; } @@ -362,8 +383,8 @@ public abstract class GuiDiagram extends Gui { double spacing = compWidth / (nodesAtLevel.size() + 1.0); for (int i = 0; i < nodesAtLevel.size(); i++) { int id = nodesAtLevel.get(i); - int posX = compX + (int) ((i + 1) * spacing); - int posY = compY + (int) (lvl * levelHeight + levelHeight / 2); + int posX = compX + (int)((i + 1) * spacing); + int posY = compY + (int)(lvl * levelHeight + levelHeight / 2); positions.put(id, new Point(posX, posY)); } } @@ -372,7 +393,7 @@ public abstract class GuiDiagram extends Gui { private Map layoutSquare(Set cluster, int compX, int compY, int compWidth, int compHeight) { Map positions = new HashMap<>(); - int n = (int) Math.round(Math.sqrt(cluster.size())); + int n = (int)Math.round(Math.sqrt(cluster.size())); int gridCellWidth = compWidth / n, gridCellHeight = compHeight / n; int index = 0; List nodes = new ArrayList<>(cluster); @@ -463,7 +484,8 @@ public abstract class GuiDiagram extends Gui { return positions; } - // Manual Layouts – using index (ring) and priority. + // --- Manual Layouts --- + // Manual layouts use the icon index (e.g. ring number, row, level) and the priority for ordering. private Map calculateCircularManualPositions() { List icons = getIcons(); Map> groups = new TreeMap<>(); @@ -471,22 +493,21 @@ public abstract class GuiDiagram extends Gui { groups.computeIfAbsent(icon.index, k -> new ArrayList<>()).add(icon); } Map positions = new HashMap<>(); - int centerX = x + width / 2, centerY = y + height / 2; + int centerX = x + width/2, centerY = y + height/2; int maxRing = groups.isEmpty() ? 0 : Collections.max(groups.keySet()); - int maxRadius = Math.min(width, height) / 2 - iconSize - 10; - double ringSpacing = (maxRing + 1) > 0 ? (double) maxRadius / (maxRing + 1) : 0; - // For each ring, sort by priority. For ring 0, start at -90° (top); for higher rings, start at -135°. + int maxRadius = Math.min(width, height)/2 - iconSize - 10; + double ringSpacing = (maxRing + 1) > 0 ? (double)maxRadius/(maxRing + 1) : 0; for (Map.Entry> entry : groups.entrySet()) { int ring = entry.getKey(); List groupIcons = entry.getValue(); groupIcons.sort(Comparator.comparingInt(icon -> icon.priority)); double radius = ringSpacing * (ring + 1); int count = groupIcons.size(); - double startAngle = (ring == 0) ? -Math.PI / 2 : -3 * Math.PI / 4; + double startAngle = (ring == 0) ? -Math.PI/2 : -3 * Math.PI/4; for (int i = 0; i < count; i++) { double angle = startAngle + 2 * Math.PI * i / count; - int posX = centerX + (int) (radius * Math.cos(angle)); - int posY = centerY + (int) (radius * Math.sin(angle)); + int posX = centerX + (int)(radius * Math.cos(angle)); + int posY = centerY + (int)(radius * Math.sin(angle)); positions.put(groupIcons.get(i).id, new Point(posX, posY)); } } @@ -500,11 +521,10 @@ public abstract class GuiDiagram extends Gui { groups.computeIfAbsent(icon.index, k -> new ArrayList<>()).add(icon); } Map positions = new HashMap<>(); - int centerX = x + width / 2, centerY = y + height / 2; + int centerX = x + width/2, centerY = y + height/2; int maxRing = groups.isEmpty() ? 0 : Collections.max(groups.keySet()); - int halfSide = Math.min(width, height) / 2 - iconSize - 10; - double ringSpacing = (maxRing + 1) > 0 ? (double) halfSide / (maxRing + 1) : 0; - // Distribute each ring's icons along the square perimeter. + int halfSide = Math.min(width, height)/2 - iconSize - 10; + double ringSpacing = (maxRing + 1) > 0 ? (double)halfSide/(maxRing + 1) : 0; for (Map.Entry> entry : groups.entrySet()) { int ring = entry.getKey(); List groupIcons = entry.getValue(); @@ -516,17 +536,17 @@ public abstract class GuiDiagram extends Gui { double posAlong = (perimeter * i) / count; int posX, posY; if (posAlong < 2 * offset) { - posX = centerX - (int) offset + (int) posAlong; - posY = centerY - (int) offset; + posX = centerX - (int)offset + (int)posAlong; + posY = centerY - (int)offset; } else if (posAlong < 4 * offset) { - posX = centerX + (int) offset; - posY = centerY - (int) offset + (int) (posAlong - 2 * offset); + posX = centerX + (int)offset; + posY = centerY - (int)offset + (int)(posAlong - 2 * offset); } else if (posAlong < 6 * offset) { - posX = centerX + (int) offset - (int) (posAlong - 4 * offset); - posY = centerY + (int) offset; + posX = centerX + (int)offset - (int)(posAlong - 4 * offset); + posY = centerY + (int)offset; } else { - posX = centerX - (int) offset; - posY = centerY + (int) offset - (int) (posAlong - 6 * offset); + posX = centerX - (int)offset; + posY = centerY + (int)offset - (int)(posAlong - 6 * offset); } positions.put(groupIcons.get(i).id, new Point(posX, posY)); } @@ -558,11 +578,10 @@ public abstract class GuiDiagram extends Gui { return positions; } - // Arrow Drawing: + // --- Arrow Drawing Methods --- /** - * Draws a connection line. - * If curvedArrows is false, draws a straight line. - * Otherwise draws a quadratic Bezier curve using curveAngle for offset. + * Draws a connection line between two points. + * If curvedArrows is enabled, draws a quadratic Bezier curve. */ protected void drawConnectionLine(int x1, int y1, int x2, int y2, DiagramConnection conn, boolean dim) { int color = getConnectionColor(conn); @@ -593,7 +612,7 @@ public abstract class GuiDiagram extends Gui { double perpX = -dy / len, perpY = dx / len; Point control = new Point((x1 + x2) / 2 + (int)(perpX * offset), (y1 + y2) / 2 + (int)(perpY * offset)); - int segments = 300; + int segments = 100; GL11.glPushAttrib(GL11.GL_ENABLE_BIT); GL11.glDisable(GL11.GL_TEXTURE_2D); GL11.glLineWidth(2.0F); @@ -616,8 +635,7 @@ public abstract class GuiDiagram extends Gui { } /** - * Draws the arrow head. - * For curved arrows, the head is aligned with the derivative at t=0.95. + * Draws the arrow head at the end of a connection. */ protected void drawArrowHead(int x1, int y1, int x2, int y2, DiagramConnection conn, boolean dim) { int color = getConnectionColor(conn); @@ -643,16 +661,16 @@ public abstract class GuiDiagram extends Gui { float defenderEdgeX = x2 - (slotSize / 2f) * (float)Math.cos(angle); float defenderEdgeY = y2 - (slotSize / 2f) * (float)Math.sin(angle); int arrowSize = 6; - float leftX = (float)(defenderEdgeX - arrowSize * Math.cos(angle - Math.PI/6)); - float leftY = (float)(defenderEdgeY - arrowSize * Math.sin(angle - Math.PI/6)); - float rightX = (float)(defenderEdgeX - arrowSize * Math.cos(angle + Math.PI/6)); - float rightY = (float)(defenderEdgeY - arrowSize * Math.sin(angle + Math.PI/6)); + float leftX = defenderEdgeX - arrowSize * (float)Math.cos(angle - Math.PI / 6); + float leftY = defenderEdgeY - arrowSize * (float)Math.sin(angle - Math.PI / 6); + float rightX = defenderEdgeX - arrowSize * (float)Math.cos(angle + Math.PI / 6); + float rightY = defenderEdgeY - arrowSize * (float)Math.sin(angle + Math.PI / 6); GL11.glPushAttrib(GL11.GL_ENABLE_BIT); GL11.glDisable(GL11.GL_TEXTURE_2D); - float rr = ((color >> 16)&0xFF)/255f; - float gg = ((color >> 8)&0xFF)/255f; - float bb = (color&0xFF)/255f; - if(dim){ rr*=0.4f; gg*=0.4f; bb*=0.4f; } + float rr = ((color >> 16) & 0xFF) / 255f; + float gg = ((color >> 8) & 0xFF) / 255f; + float bb = (color & 0xFF) / 255f; + if(dim){ rr *= 0.4f; gg *= 0.4f; bb *= 0.4f; } GL11.glColor4f(rr, gg, bb, 1f); GL11.glDisable(GL11.GL_CULL_FACE); GL11.glBegin(GL11.GL_TRIANGLES); @@ -665,7 +683,7 @@ public abstract class GuiDiagram extends Gui { GL11.glPopAttrib(); } - // Standard Icon Box Drawing + // --- Standard Icon Box Drawing --- protected void drawIconBox(int slotX, int slotY, int slotSize, boolean highlighted) { int bg = highlighted ? mixColors(iconBackgroundColor, 0xFFFFFFFF, 0.2f) : iconBackgroundColor; drawRect(slotX, slotY, slotX + slotSize, slotY + slotSize, bg); @@ -686,7 +704,7 @@ public abstract class GuiDiagram extends Gui { return (a << 24) | (r << 16) | (g << 8) | b; } - // Hit-detection helper for curved arrows. + // --- Hit-detection helpers --- private double distanceToBezier(Point start, Point control, Point end, int segments, int px, int py) { double minDist = Double.MAX_VALUE; Point prev = null; @@ -697,7 +715,7 @@ public abstract class GuiDiagram extends Gui { Point curr = new Point(x, y); if (prev != null) { double d = distancePointToSegment(px, py, prev, curr); - if (d < minDist) minDist = d; + if(d < minDist) minDist = d; } prev = curr; } @@ -710,18 +728,245 @@ public abstract class GuiDiagram extends Gui { double lenSq = C * C + D * D; double param = (lenSq != 0) ? (dot / lenSq) : -1; double xx, yy; - if (param < 0) { xx = a.x; yy = a.y; } - else if (param > 1) { xx = b.x; yy = b.y; } + if(param < 0){ xx = a.x; yy = a.y; } + else if(param > 1){ xx = b.x; yy = b.y; } else { xx = a.x + param * C; yy = a.y + param * D; } double dx = px - xx, dy = py - yy; return Math.sqrt(dx * dx + dy * dy); } - // Main Draw Method + // --- CHART Layout Drawing --- + /** + * Draws the CHART layout. + *
    + *
  • The grid has (n+1) columns and rows, where n is the number of icons.
  • + *
  • The top row and left column show header icons.
  • + *
  • Each inner cell is filled with the connection color (if one exists) or a darker gray otherwise.
  • + *
  • Hovering over a header or inner cell highlights the corresponding column/row or cell (and its two header icons), + * while dimming non-relevant cells.
  • + *
  • Zoom and pan are applied to the entire chart.
  • + *
+ */ + private void drawChartDiagram(int mouseX, int mouseY, boolean subGui) { + Minecraft mc = Minecraft.getMinecraft(); + ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); + int factor = sr.getScaleFactor(); + + // 1. Draw the static background for the entire diagram. + drawRect(x, y, x + width, y + height, 0xFF333333); + + // 2. Setup scissor to clip drawing to the diagram area. + GL11.glEnable(GL11.GL_SCISSOR_TEST); + GL11.glScissor(x * factor, (sr.getScaledHeight() - (y + height)) * factor, width * factor, height * factor); + + // 3. Push a matrix and apply pan/zoom for grid, icons, and overlays. + GL11.glPushMatrix(); + int centerX = x + width / 2; + int centerY = y + height / 2; + GL11.glTranslatef(centerX + panX, centerY + panY, 0); + GL11.glScalef(zoom, zoom, 1.0f); + GL11.glTranslatef(-centerX, -centerY, 0); + + // 4. Compute grid parameters. + List icons = getIcons(); + int n = icons.size(); + int cols = n + 1, rows = n + 1; + int cellSize = Math.min(width / cols, height / rows); + int gridWidth = cellSize * cols; + int gridHeight = cellSize * rows; + int startX = x + (width - gridWidth) / 2; + int startY = y + (height - gridHeight) / 2; + + // 5. Draw grid cells. + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + int cellX = startX + col * cellSize; + int cellY = startY + row * cellSize; + int fillColor = 0xFFCCCCCC; // Default for header cells. + if (row > 0 && col > 0) { // inner cell: use connection color (or darker gray) + DiagramIcon leftIcon = icons.get(row - 1); + DiagramIcon topIcon = icons.get(col - 1); + DiagramConnection conn = getConnectionByIds(leftIcon.id, topIcon.id); + fillColor = (conn != null) ? getConnectionColor(conn) : 0xFF777777; + } + drawRect(cellX, cellY, cellX + cellSize, cellY + cellSize, fillColor); + } + } + + // 6. Draw grid lines. + GL11.glPushAttrib(GL11.GL_ENABLE_BIT); + GL11.glDisable(GL11.GL_TEXTURE_2D); + GL11.glColor4f(0, 0, 0, 1); + GL11.glLineWidth(1.0f); + GL11.glBegin(GL11.GL_LINES); + for (int col = 0; col <= cols; col++) { + int xLine = startX + col * cellSize; + GL11.glVertex2i(xLine, startY); + GL11.glVertex2i(xLine, startY + gridHeight); + } + for (int row = 0; row <= rows; row++) { + int yLine = startY + row * cellSize; + GL11.glVertex2i(startX, yLine); + GL11.glVertex2i(startX + gridWidth, yLine); + } + GL11.glEnd(); + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glPopAttrib(); + + // 7. Draw header icons (top and left) scaled to fit within each cell. + float iconMargin = 1f; // margin in pixels inside the cell. + float iconScale = (cellSize - iconMargin) / (float)iconSize; + for (int i = 0; i < n; i++) { + // Top header icon: + int topHeaderCenterX = startX + (i + 1) * cellSize + cellSize / 2; + int topHeaderCenterY = startY + cellSize / 2; + GL11.glPushMatrix(); + GL11.glTranslatef(topHeaderCenterX, topHeaderCenterY, 0); + GL11.glScalef(iconScale, iconScale, 1); + // renderIcon should draw centered at (0,0) + renderIcon(icons.get(i), 0, 0, IconRenderState.DEFAULT); + GL11.glPopMatrix(); + + // Left header icon: + int leftHeaderCenterX = startX + cellSize / 2; + int leftHeaderCenterY = startY + (i + 1) * cellSize + cellSize / 2; + GL11.glPushMatrix(); + GL11.glTranslatef(leftHeaderCenterX, leftHeaderCenterY, 0); + GL11.glScalef(iconScale, iconScale, 1); + renderIcon(icons.get(i), 0, 0, IconRenderState.DEFAULT); + GL11.glPopMatrix(); + } + + // 8. Compute effective mouse coordinates (reverse pan/zoom). + int effectiveMouseX = (int)(((float)mouseX - (centerX + panX)) / zoom + centerX); + int effectiveMouseY = (int)(((float)mouseY - (centerY + panY)) / zoom + centerY); + + // 9. Determine hover state on the grid. + boolean hoverInGrid = (effectiveMouseX >= startX && effectiveMouseX < startX + gridWidth && + effectiveMouseY >= startY && effectiveMouseY < startY + gridHeight); + int hoveredRow = -1, hoveredCol = -1; + Set highlightTop = new HashSet<>(); + Set highlightLeft = new HashSet<>(); + List tooltipToShow = null; + if (hoverInGrid) { + int colIndex = (effectiveMouseX - startX) / cellSize; + int rowIndex = (effectiveMouseY - startY) / cellSize; + // If hovering over a top header cell. + if (rowIndex == 0 && colIndex >= 1) { + highlightTop.add(colIndex - 1); + tooltipToShow = getIconTooltip(icons.get(colIndex - 1)); + onIconHover(icons.get(colIndex - 1)); + } + // If hovering over a left header cell. + else if (colIndex == 0 && rowIndex >= 1) { + highlightLeft.add(rowIndex - 1); + tooltipToShow = getIconTooltip(icons.get(rowIndex - 1)); + onIconHover(icons.get(rowIndex - 1)); + } + // Else, hovering over an inner cell. + else if (rowIndex >= 1 && colIndex >= 1) { + hoveredRow = rowIndex - 1; + hoveredCol = colIndex - 1; + highlightTop.add(hoveredCol); + highlightLeft.add(hoveredRow); + DiagramIcon leftIcon = icons.get(hoveredRow); + DiagramIcon topIcon = icons.get(hoveredCol); + DiagramConnection conn = getConnectionByIds(leftIcon.id, topIcon.id); + if (conn != null) { + tooltipToShow = getConnectionTooltip(conn); + onConnectionHover(conn); + } + } + } + + // 10. Draw hover overlay: + // Dim all inner cells (row>=1 & col>=1) not in highlighted row/column. + GL11.glPushAttrib(GL11.GL_ENABLE_BIT); + GL11.glDisable(GL11.GL_TEXTURE_2D); + GL11.glColor4f(0, 0, 0, 0.5f); + GL11.glBegin(GL11.GL_QUADS); + for (int r = 1; r < rows; r++) { + for (int c = 1; c < cols; c++) { + boolean keepBright = false; + if (!highlightTop.isEmpty() && highlightTop.contains(c - 1)) + keepBright = true; + if (!highlightLeft.isEmpty() && highlightLeft.contains(r - 1)) + keepBright = true; + // Also, if a specific cell is hovered, its entire row/column is bright. + if (hoveredRow != -1 && hoveredCol != -1) { + if (r - 1 == hoveredRow || c - 1 == hoveredCol) + keepBright = true; + } + if (!keepBright) { + int cellX = startX + c * cellSize; + int cellY = startY + r * cellSize; + GL11.glVertex2i(cellX, cellY); + GL11.glVertex2i(cellX + cellSize, cellY); + GL11.glVertex2i(cellX + cellSize, cellY + cellSize); + GL11.glVertex2i(cellX, cellY + cellSize); + } + } + } + GL11.glEnd(); + // Then draw a white overlay on the highlighted headers/inner cell. + GL11.glColor4f(1, 1, 1, 0.3f); + GL11.glBegin(GL11.GL_QUADS); + for (int i = 0; i < n; i++) { + if (highlightTop.contains(i)) { + int hx = startX + (i + 1) * cellSize; + int hy = startY; + GL11.glVertex2i(hx, hy); + GL11.glVertex2i(hx + cellSize, hy); + GL11.glVertex2i(hx + cellSize, hy + cellSize); + GL11.glVertex2i(hx, hy + cellSize); + } + if (highlightLeft.contains(i)) { + int hx = startX; + int hy = startY + (i + 1) * cellSize; + GL11.glVertex2i(hx, hy); + GL11.glVertex2i(hx + cellSize, hy); + GL11.glVertex2i(hx + cellSize, hy + cellSize); + GL11.glVertex2i(hx, hy + cellSize); + } + } + if (hoveredRow != -1 && hoveredCol != -1) { + int cellX = startX + (hoveredCol + 1) * cellSize; + int cellY = startY + (hoveredRow + 1) * cellSize; + GL11.glVertex2i(cellX, cellY); + GL11.glVertex2i(cellX + cellSize, cellY); + GL11.glVertex2i(cellX + cellSize, cellY + cellSize); + GL11.glVertex2i(cellX, cellY + cellSize); + } + GL11.glEnd(); + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glPopAttrib(); + + // End pan/zoom block. + GL11.glPopMatrix(); + GL11.glDisable(GL11.GL_SCISSOR_TEST); + + // 11. Draw tooltip (using raw screen coordinates). + if (tooltipToShow != null && !tooltipToShow.isEmpty()) { + drawHoveringText(tooltipToShow, mouseX, mouseY, mc.fontRenderer); + } + } + + + + /** + * Main method for drawing the diagram. + * If layout == CHART, delegates to drawChartDiagram(). + * Otherwise, applies pan/zoom, draws connections (with hit-detection fixed for curves) + * and icons, and shows tooltips and hover events. + */ public void drawDiagram(int mouseX, int mouseY, boolean subGui) { boolean allowInput = (parent == null || !parent.hasSubGui()); if (allowInput && isWithin(mouseX, mouseY) && !subGui) handleMouseScroll(Mouse.getDWheel()); + if (layout == DiagramLayout.CHART) { + drawChartDiagram(mouseX, mouseY, subGui); + return; + } Minecraft mc = Minecraft.getMinecraft(); ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); int factor = sr.getScaleFactor(); @@ -732,6 +977,7 @@ public abstract class GuiDiagram extends Gui { Integer hoveredIconId = null; int hoveredConnFrom = -1, hoveredConnTo = -1; Set selectedIconIds = new HashSet<>(); + // Check if mouse is over an icon. for (DiagramIcon icon : getIcons()) { if (!icon.enabled) continue; Point pos = positions.get(icon.id); @@ -741,21 +987,12 @@ public abstract class GuiDiagram extends Gui { effectiveMouseY >= slotY && effectiveMouseY < slotY + slotSize) { hoveredIconId = icon.id; selectedIconIds.add(icon.id); + onIconHover(icon); break; } } - if (hoveredIconId != null) { - for (DiagramConnection conn : getConnections()) { - DiagramIcon iconFrom = getIconById(conn.idFrom); - DiagramIcon iconTo = getIconById(conn.idTo); - if (iconFrom == null || iconTo == null || !iconFrom.enabled || !iconTo.enabled) - continue; - if (conn.idFrom == hoveredIconId || conn.idTo == hoveredIconId) { - selectedIconIds.add(conn.idFrom); - selectedIconIds.add(conn.idTo); - } - } - } else { + // If no icon was hovered, check connections. + if (hoveredIconId == null) { final double threshold = 5.0; outer: for (DiagramConnection conn : getConnections()) { @@ -771,12 +1008,11 @@ public abstract class GuiDiagram extends Gui { double dx = pTo.x - pFrom.x, dy = pTo.y - pFrom.y; double len = Math.sqrt(dx * dx + dy * dy); if (len == 0) len = 1; - double angleRad = Math.toRadians(curveAngle); - double offset = len * Math.tan(angleRad) / 2; + double offset = len * Math.tan(Math.toRadians(curveAngle)) / 2; double perpX = -dy / len, perpY = dx / len; - Point control = new Point((pFrom.x+pTo.x)/2 + (int)(perpX*offset), - (pFrom.y+pTo.y)/2 + (int)(perpY*offset)); - dist = distanceToBezier(pFrom, control, pTo, 20, effectiveMouseX, effectiveMouseY); + Point control = new Point((pFrom.x + pTo.x) / 2 + (int)(perpX * offset), + (pFrom.y + pTo.y) / 2 + (int)(perpY * offset)); + dist = distanceToBezier(pFrom, control, pTo, 200, effectiveMouseX, effectiveMouseY); } else { dist = pointLineDistance(effectiveMouseX, effectiveMouseY, pFrom.x, pFrom.y, pTo.x, pTo.y); } @@ -785,10 +1021,12 @@ public abstract class GuiDiagram extends Gui { hoveredConnTo = conn.idTo; selectedIconIds.add(conn.idFrom); selectedIconIds.add(conn.idTo); + onConnectionHover(conn); break outer; } } } + // Setup scissor. GL11.glEnable(GL11.GL_SCISSOR_TEST); GL11.glScissor(x * factor, (sr.getScaledHeight() - (y + height)) * factor, width * factor, height * factor); drawRect(x, y, x + width, y + height, 0xFF333333); @@ -796,6 +1034,7 @@ public abstract class GuiDiagram extends Gui { GL11.glTranslatef(centerX + panX, centerY + panY, 0); GL11.glScalef(zoom, zoom, 1.0f); GL11.glTranslatef(-centerX, -centerY, 0); + // Draw connections. for (DiagramConnection conn : getConnections()) { DiagramIcon iconFrom = getIconById(conn.idFrom); DiagramIcon iconTo = getIconById(conn.idTo); @@ -805,9 +1044,11 @@ public abstract class GuiDiagram extends Gui { Point pTo = positions.get(conn.idTo); if (pFrom == null || pTo == null) continue; int ax = pFrom.x, ay = pFrom.y, bx = pTo.x, by = pTo.y; - boolean dim = !selectedIconIds.isEmpty() && !(selectedIconIds.contains(conn.idFrom) && selectedIconIds.contains(conn.idTo)); + boolean dim = !selectedIconIds.isEmpty() && + !(selectedIconIds.contains(conn.idFrom) && selectedIconIds.contains(conn.idTo)); drawConnectionLine(ax, ay, bx, by, conn, dim); } + // Draw icons. for (DiagramIcon icon : getIcons()) { if (!icon.enabled) continue; Point pos = positions.get(icon.id); @@ -819,6 +1060,7 @@ public abstract class GuiDiagram extends Gui { renderIcon(icon, pos.x, pos.y, state); } GL11.glPopMatrix(); + // Draw arrow heads. if (showArrowHeads) { GL11.glPushMatrix(); GL11.glTranslatef(centerX + panX, centerY + panY, 0); @@ -833,12 +1075,14 @@ public abstract class GuiDiagram extends Gui { Point pTo = positions.get(conn.idTo); if (pFrom == null || pTo == null) continue; int ax = pFrom.x, ay = pFrom.y, bx = pTo.x, by = pTo.y; - boolean dim = !selectedIconIds.isEmpty() && !(selectedIconIds.contains(conn.idFrom) && selectedIconIds.contains(conn.idTo)); + boolean dim = !selectedIconIds.isEmpty() && + !(selectedIconIds.contains(conn.idFrom) && selectedIconIds.contains(conn.idTo)); drawArrowHead(ax, ay, bx, by, conn, dim); } GL11.glPopMatrix(); } GL11.glDisable(GL11.GL_SCISSOR_TEST); + // Show tooltips if an icon or connection was hovered. if (hoveredIconId != null) { DiagramIcon icon = getIconById(hoveredIconId); List tooltip = getIconTooltip(icon); @@ -852,6 +1096,7 @@ public abstract class GuiDiagram extends Gui { } } + // --- Utility Methods --- protected DiagramIcon getIconById(int id) { for (DiagramIcon icon : getIcons()) { if (icon.id == id) return icon; @@ -893,86 +1138,7 @@ public abstract class GuiDiagram extends Gui { return Math.sqrt(dx * dx + dy * dy); } - // Mouse Handling - public boolean mouseClicked(int mouseX, int mouseY, int mouseButton) { - if (parent != null && parent.hasSubGui()) return false; - if (mouseButton == 0) { - for (DiagramIcon icon : getIcons()) { - if (!icon.enabled || !icon.pressable) continue; - Point pos = calculatePositions().get(icon.id); - if (pos == null) continue; - int centerX = x + width/2, centerY = y + height/2; - int effectiveX = (int)(((float)mouseX - (centerX+panX))/zoom+centerX); - int effectiveY = (int)(((float)mouseY - (centerY+panY))/zoom+centerY); - int slotX = pos.x - slotSize/2, slotY = pos.y - slotSize/2; - if (effectiveX >= slotX && effectiveX < slotX+slotSize && - effectiveY >= slotY && effectiveY < slotY+slotSize) { - onIconClick(icon); - currentlyPressedIcon = icon; - return true; - } - } - if (isWithin(mouseX, mouseY)) { - dragging = true; - lastDragX = mouseX; - lastDragY = mouseY; - return true; - } - } - return false; - } - protected DiagramIcon currentlyPressedIcon = null; - public void mouseClickMove(int mouseX, int mouseY, int mouseButton, long timeSinceLastClick) { - if (parent != null && parent.hasSubGui()) return; - if (dragging) { - int dx = mouseX - lastDragX, dy = mouseY - lastDragY; - panX += dx/zoom*0.7f; - panY += dy/zoom*0.7f; - lastDragX = mouseX; - lastDragY = mouseY; - } - if (currentlyPressedIcon != null) onIconHeld(currentlyPressedIcon); - } - public void mouseReleased(int mouseX, int mouseY, int state) { - if (parent != null && parent.hasSubGui()) return; - if (currentlyPressedIcon != null) { - onIconRelease(currentlyPressedIcon); - currentlyPressedIcon = null; - } - dragging = false; - } - public boolean isWithin(int mouseX, int mouseY) { - if (parent.hasSubGui()) - return false; - return mouseX >= x && mouseX <= x+width && mouseY >= y && mouseY <= y+height; - } - /** - * Handles zoom based on mouse wheel scrolling. - */ - public void handleMouseScroll(int scrollDelta) { - zoom += scrollDelta * 0.0009f; - if (zoom < 0.5f) zoom = 0.5f; - if (zoom > 2.0f) zoom = 2.0f; - } - /** - * Override to render tooltips. - */ - protected void drawHoveringText(List text, int mouseX, int mouseY, FontRenderer fontRenderer) { - // Default implementation is empty. - } - // Flag Setters - public void setShowArrowHeads(boolean showArrowHeads) { - this.showArrowHeads = showArrowHeads; - } - public void setUseColorScaling(boolean useColorScaling) { - this.useColorScaling = useColorScaling; - } - public enum IconRenderState { - DEFAULT, - HIGHLIGHTED, - NOT_HIGHLIGHTED; - } - // Inner Classes + // --- Inner Helper Classes --- public static class DiagramIcon { public int id; public boolean enabled = true; @@ -997,14 +1163,133 @@ public abstract class GuiDiagram extends Gui { this.hoverText = hoverText; } } - // Icon Event Callbacks + // Used by the CHART layout to represent an inner cell. + private static class ConnectionCell { + int x, y, size; + DiagramConnection conn; + ConnectionCell(int x, int y, int size, DiagramConnection conn) { + this.x = x; this.y = y; this.size = size; this.conn = conn; + } + } + // Simple helper class for rectangles. + private static class Rectangle { + int x, y, width, height; + Rectangle(int x, int y, int width, int height) { + this.x = x; this.y = y; this.width = width; this.height = height; + } + int centerX() { return x + width / 2; } + int centerY() { return y + height / 2; } + } + + public enum IconRenderState { + DEFAULT, + HIGHLIGHTED, + NOT_HIGHLIGHTED; + } + + // --- Mouse Handling --- + public boolean mouseClicked(int mouseX, int mouseY, int mouseButton) { + if (parent != null && parent.hasSubGui()) return false; + if (mouseButton == 0) { + for (DiagramIcon icon : getIcons()) { + if (!icon.enabled || !icon.pressable) continue; + Point pos = calculatePositions().get(icon.id); + if (pos == null) continue; + int centerX = x + width / 2, centerY = y + height / 2; + int effectiveX = (int)(((float)mouseX - (centerX + panX)) / zoom + centerX); + int effectiveY = (int)(((float)mouseY - (centerY + panY)) / zoom + centerY); + int slotX = pos.x - slotSize / 2, slotY = pos.y - slotSize / 2; + if (effectiveX >= slotX && effectiveX < slotX + slotSize && + effectiveY >= slotY && effectiveY < slotY + slotSize) { + onIconClick(icon); + currentlyPressedIcon = icon; + return true; + } + } + if (isWithin(mouseX, mouseY)) { + dragging = true; + lastDragX = mouseX; + lastDragY = mouseY; + return true; + } + } + return false; + } + protected DiagramIcon currentlyPressedIcon = null; + public void mouseClickMove(int mouseX, int mouseY, int mouseButton, long timeSinceLastClick) { + if (parent != null && parent.hasSubGui()) return; + if (dragging) { + int dx = mouseX - lastDragX, dy = mouseY - lastDragY; + panX += dx / zoom * 0.7f; + panY += dy / zoom * 0.7f; + lastDragX = mouseX; + lastDragY = mouseY; + } + if (currentlyPressedIcon != null) onIconHeld(currentlyPressedIcon); + } + public void mouseReleased(int mouseX, int mouseY, int state) { + if (parent != null && parent.hasSubGui()) return; + if (currentlyPressedIcon != null) { + onIconRelease(currentlyPressedIcon); + currentlyPressedIcon = null; + } + dragging = false; + } + public boolean isWithin(int mouseX, int mouseY) { + if (parent.hasSubGui()) + return false; + return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height; + } + /** + * Handles zooming based on mouse wheel scrolling. + */ + public void handleMouseScroll(int scrollDelta) { + zoom += scrollDelta * 0.0009f; + if (zoom < 0.5f) zoom = 0.5f; + if (zoom > 2.0f) zoom = 2.0f; + } + /** + * Override to render tooltips. + */ + protected void drawHoveringText(List text, int mouseX, int mouseY, FontRenderer fontRenderer) {} + + // --- Flag Setters --- + public void setShowArrowHeads(boolean showArrowHeads) { + this.showArrowHeads = showArrowHeads; + } + public void setUseColorScaling(boolean useColorScaling) { + this.useColorScaling = useColorScaling; + } + + // --- Icon and Connection Event Callbacks --- + /** + * Called when an icon is clicked. + */ protected void onIconClick(DiagramIcon icon) { System.out.println("Icon " + icon.id + " clicked."); } + /** + * Called when an icon is held. + */ protected void onIconHeld(DiagramIcon icon) { System.out.println("Icon " + icon.id + " held."); } + /** + * Called when an icon is released. + */ protected void onIconRelease(DiagramIcon icon) { System.out.println("Icon " + icon.id + " released."); } + /** + * Called when an icon is hovered over. + */ + protected void onIconHover(DiagramIcon icon) { + // Override to handle icon hovering events. + } + /** + * Called when a connection is hovered over. + */ + protected void onConnectionHover(DiagramConnection conn) { + // Override to handle connection hovering events. + } } diff --git a/src/main/java/noppes/npcs/client/gui/global/GuiMagicMap.java b/src/main/java/noppes/npcs/client/gui/global/GuiMagicMap.java index 7954d5f56..01b9ef562 100644 --- a/src/main/java/noppes/npcs/client/gui/global/GuiMagicMap.java +++ b/src/main/java/noppes/npcs/client/gui/global/GuiMagicMap.java @@ -31,9 +31,9 @@ public class GuiMagicMap extends GuiDiagram { for (Magic magic : MagicController.getInstance().magics.values()) { magics.add(magic); } - setLayout(DiagramLayout.CIRCULAR_MANUAL); + setLayout(DiagramLayout.CHART); this.setCurvedArrows(true); - this.setCurveAngle(25); + this.setCurveAngle(-20); } @Override diff --git a/src/main/java/noppes/npcs/controllers/MagicController.java b/src/main/java/noppes/npcs/controllers/MagicController.java index b2c6385af..da72e80a2 100644 --- a/src/main/java/noppes/npcs/controllers/MagicController.java +++ b/src/main/java/noppes/npcs/controllers/MagicController.java @@ -57,7 +57,7 @@ public class MagicController { } catch (Exception ee) { } } magics.clear(); - if (false) { + if (true) { // Create default Pokémon types Magic normal = new Magic(0, "Normal", 0xA8A878); normal.setIconItem(new ItemStack(net.minecraft.init.Items.paper)); // using paper for Normal @@ -205,30 +205,30 @@ public class MagicController { magics.put(fairy.id, fairy); } - if (true || magics.isEmpty()) { + if (false) { // Create default magics - Magic earth = new Magic(0, "Earth", 0x00DD00, 0, 0); + Magic earth = new Magic(0, "Earth", 0x00DD00, 1, 1); earth.setIconItem(new ItemStack(CustomItems.earthElement)); - Magic water = new Magic(1, "Water", 0xF2DD00, 0, 1); + Magic water = new Magic(1, "Water", 0xF2DD00, 1, 2); water.setIconItem(new ItemStack(CustomItems.waterElement)); - Magic fire = new Magic(2, "Fire", 0xDD0000, 0, 2); + Magic fire = new Magic(2, "Fire", 0xDD0000, 1, 3); fire.setIconItem(new ItemStack(CustomItems.spellFire)); - Magic air = new Magic(3, "Air", 0xDD0000, 0, 3); + Magic air = new Magic(3, "Air", 0xDD0000, 1, 0); air.setIconItem(new ItemStack(CustomItems.airElement)); - Magic dark = new Magic(4, "Dark", 0xDD0000, 1, 3); + Magic dark = new Magic(4, "Dark", 0xDD0000, 0, 3); dark.setIconItem(new ItemStack(CustomItems.spellDark)); - Magic holy = new Magic(5, "Holy", 0xDD0000, 1, 1); + Magic holy = new Magic(5, "Holy", 0xDD0000, 0, 1); holy.setIconItem(new ItemStack(CustomItems.spellHoly)); - Magic nature = new Magic(6, "Nature", 0xDD0000, 1, 0); + Magic nature = new Magic(6, "Nature", 0xDD0000, 0, 0); nature.setIconItem(new ItemStack(CustomItems.spellNature)); - Magic arcane = new Magic(7, "Arcane", 0xDD0000, 1, 2); + Magic arcane = new Magic(7, "Arcane", 0xDD0000, 0, 2); arcane.setIconItem(new ItemStack(CustomItems.spellArcane)); // Insiders