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 60b5def7b..2c0ee2fac 100644
--- a/src/main/java/noppes/npcs/client/gui/global/GuiDiagram.java
+++ b/src/main/java/noppes/npcs/client/gui/global/GuiDiagram.java
@@ -12,74 +12,73 @@ import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
/**
- * Abstract GUI class for drawing diagrams that consist of icons (nodes) and connections (arrows).
- *
- * This version adds:
- * - A parent GuiNPCInterface (passed in the constructor) so that if the parent has a subgui, no
- * mouse interactions are processed.
- * - Caching for icons and connections. The abstract methods createIcons() and createConnections() are
- * called only once (or when cache is invalidated).
- * - DiagramIcon now has fields for enabled and pressable. Disabled icons are not rendered or used in
- * connection validation.
- * - Basic mouse-event callbacks for pressable icons: click, held and released.
+ * 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.
*/
public abstract class GuiDiagram extends Gui {
- // ---------------
// Diagram Area & Controls
- // ---------------
- protected int x, y, width, height; // The drawing area.
- protected float panX = 0, panY = 0; // Panning offsets.
- protected float zoom = 1.0f; // Zoom factor.
- protected boolean dragging = false; // Drag flag.
- protected int lastDragX, lastDragY; // Last mouse positions.
+ 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;
- // ---------------
// Node (Icon) Sizing
- // ---------------
- protected int iconSize = 16; // Icon image size (square).
- protected int slotPadding = 4; // Extra padding for the node's box.
- protected int slotSize = iconSize + slotPadding; // Overall size of the node box.
+ protected int iconSize = 16;
+ protected int slotPadding = 4;
+ protected int slotSize = iconSize + slotPadding;
- // ---------------
// Node Box Appearance
- // ---------------
- protected int iconBackgroundColor = 0xFF999999; // Background color.
- protected int iconBorderColor = 0xFF555555; // Border color.
- protected int iconBorderThickness = 1; // Border thickness in pixels.
+ protected int iconBackgroundColor = 0xFF999999;
+ protected int iconBorderColor = 0xFF555555;
+ protected int iconBorderThickness = 1;
- // ---------------
// Connection Options
- // ---------------
- protected boolean showArrowHeads = true; // Whether to draw arrow heads.
- protected boolean useColorScaling = true; // Whether to scale connection color based on a percentage.
+ protected boolean showArrowHeads = true;
+ protected boolean useColorScaling = true;
- // ---------------
// Layout Options
- // ---------------
- public enum DiagramLayout { CIRCULAR, SQUARE, TREE, GENERATED }
- protected DiagramLayout layout = DiagramLayout.CIRCULAR; // Default layout.
+ public enum DiagramLayout {
+ CIRCULAR, SQUARE, TREE, GENERATED,
+ CIRCULAR_MANUAL, SQUARE_MANUAL, TREE_MANUAL
+ }
+ protected DiagramLayout layout = DiagramLayout.CIRCULAR;
/**
- * Allows setting the desired layout type.
- * This will invalidate the cached positions and cached icons/connections.
+ * Sets the layout and invalidates caches.
*/
public void setLayout(DiagramLayout layout) {
this.layout = layout;
invalidateCache();
}
- // ---------------
- // Caching
- // ---------------
- // Cache for positions (computed once for any given icon set & layout).
+ // Arrow Drawing Options
+ protected boolean curvedArrows = true; // if false, straight lines are drawn.
+ protected int curveAngle = 15; // in degrees; adjust for "hardness" of curve.
+
+ public void setCurvedArrows(boolean curved) {
+ this.curvedArrows = curved;
+ }
+ public void setCurveAngle(int angle) {
+ this.curveAngle = angle;
+ }
+
+ // Caching for positions and for icons/connections.
protected Map cachedPositions = null;
- // Cache for icons and connections.
protected List iconsCache = null;
protected List connectionsCache = null;
/**
- * Clears all caches: positions, icons, and connections.
+ * Clears all caches.
*/
public void invalidateCache() {
cachedPositions = null;
@@ -87,14 +86,10 @@ public abstract class GuiDiagram extends Gui {
connectionsCache = null;
}
- // ---------------
// Parent Reference
- // ---------------
protected GuiNPCInterface parent;
- // ---------------
// Constructor
- // ---------------
public GuiDiagram(GuiNPCInterface parent, int x, int y, int width, int height) {
this.parent = parent;
this.x = x;
@@ -103,34 +98,16 @@ public abstract class GuiDiagram extends Gui {
this.height = height;
}
- // ---------------
- // Data Providers (subclasses must implement the creation methods)
- // ---------------
- /**
- * Subclasses should override this method to create and return the list of DiagramIcons.
- * Note: the returned icons will be cached.
- */
+ // Data Providers – Subclasses must implement these.
protected abstract List createIcons();
-
- /**
- * Subclasses should override this method to create and return the list of DiagramConnections.
- * Note: the returned connections will be cached.
- */
protected abstract List createConnections();
- /**
- * Returns the cached icons.
- */
protected final List getIcons() {
if (iconsCache == null) {
iconsCache = createIcons();
}
return iconsCache;
}
-
- /**
- * Returns the cached connections.
- */
protected final List getConnections() {
if (connectionsCache == null) {
connectionsCache = createConnections();
@@ -139,15 +116,11 @@ public abstract class GuiDiagram extends Gui {
}
/**
- * Subclasses must implement how an icon’s image is rendered.
+ * Subclasses must implement how an icon is rendered.
*/
protected abstract void renderIcon(DiagramIcon icon, int posX, int posY, IconRenderState state);
- /**
- * Return a tooltip for an icon.
- */
protected List getIconTooltip(DiagramIcon icon) { return null; }
-
protected List getConnectionTooltip(DiagramConnection conn) {
List tooltip = new ArrayList<>();
DiagramIcon iconFrom = getIconById(conn.idFrom);
@@ -163,51 +136,35 @@ public abstract class GuiDiagram extends Gui {
}
return tooltip;
}
-
protected String getIconName(DiagramIcon icon) { return "Icon " + icon.id; }
- // ---------------
- // Layout Calculation – choose algorithm based on layout type.
- // ---------------
- /**
- * Returns a mapping from each icon's id to its (x,y) center position.
- * Uses caching so that positions are computed only once (unless invalidated).
- */
+ // Layout Calculation – Chooses algorithm based on layout type.
protected Map calculatePositions() {
- if (cachedPositions != null) {
- return cachedPositions;
- }
+ if (cachedPositions != null) return cachedPositions;
switch (layout) {
- case CIRCULAR:
- cachedPositions = calculateCircularPositions();
- break;
- case SQUARE:
- cachedPositions = calculateSquarePositions();
- break;
- case TREE:
- cachedPositions = calculateTreePositions();
- break;
- case GENERATED:
- cachedPositions = calculateGeneratedPositions();
- 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:
protected Map calculateCircularPositions() {
List icons = getIcons();
Map positions = new HashMap<>();
int count = icons.size();
- int centerX = x + width / 2;
- int centerY = y + height / 2;
+ 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;
@@ -217,16 +174,12 @@ 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 gridWidth = width - 20;
- int gridHeight = height - 20;
- int cellWidth = gridWidth / n;
- int cellHeight = gridHeight / n;
- int startX = x + (width - gridWidth) / 2;
- int startY = y + (height - gridHeight) / 2;
+ 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;
for (int i = 0; i < count; i++) {
- int row = i / n;
- int col = i % n;
+ int row = i / n, col = i % n;
int posX = startX + col * cellWidth + cellWidth / 2;
int posY = startY + row * cellHeight + cellHeight / 2;
positions.put(icons.get(i).id, new Point(posX, posY));
@@ -238,11 +191,11 @@ 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++) {
@@ -254,29 +207,17 @@ public abstract class GuiDiagram extends Gui {
return positions;
}
- /**
- * Generated layout: uses a force-directed algorithm to position nodes.
- * Computed only once using a fixed seed for determinism.
- */
protected Map calculateGeneratedPositions() {
List icons = getIcons();
List connections = getConnections();
-
- // Build a complete undirected graph from icons & connections.
Map> graph = buildGraph(icons, connections);
-
- // Partition the graph into clusters (connected components).
List> clusters = getConnectedComponents(graph);
-
- // Arrange clusters into a global layout. We’ll place each cluster
- // in its own bounding box on a grid.
int clusterCount = clusters.size();
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;
-
Map globalPositions = new HashMap<>();
int clusterIndex = 0;
for (Set cluster : clusters) {
@@ -284,31 +225,22 @@ public abstract class GuiDiagram extends Gui {
int row = clusterIndex / gridCols;
int compX = x + clusterMargin + col * (compWidth + clusterMargin);
int compY = y + clusterMargin + row * (compHeight + clusterMargin);
-
- // Look for a pattern in the cluster.
Map compPositions;
- if (isCycle(cluster, graph)) {
- // Layout nodes evenly on a circle.
+ if (isCycle(cluster, graph))
compPositions = layoutCycle(cluster, compX, compY, compWidth, compHeight);
- } else if (isTree(cluster, graph)) {
- // Layout nodes in a tree, minimizing edge crossings.
+ else if (isTree(cluster, graph))
compPositions = layoutTree(cluster, compX, compY, compWidth, compHeight, graph);
- } else if (isSquare(cluster)) {
- // If the number of nodes is a perfect square (or near enough)
- // you might try a grid layout.
+ else if (isSquare(cluster))
compPositions = layoutSquare(cluster, compX, compY, compWidth, compHeight);
- } else {
- // Otherwise, use a force-directed layout optimized for minimal overlap.
+ else
compPositions = layoutForceDirected(cluster, compX, compY, compWidth, compHeight, connections);
- }
globalPositions.putAll(compPositions);
clusterIndex++;
}
-
return globalPositions;
}
- // Build an undirected graph from icons and connections.
+ // Helpers for generated layout.
private Map> buildGraph(List icons, List connections) {
Map> graph = new HashMap<>();
for (DiagramIcon icon : icons) {
@@ -322,8 +254,6 @@ public abstract class GuiDiagram extends Gui {
}
return graph;
}
-
- // Extract connected components using DFS.
private List> getConnectedComponents(Map> graph) {
Set visited = new HashSet<>();
List> components = new ArrayList<>();
@@ -336,9 +266,7 @@ public abstract class GuiDiagram extends Gui {
}
return components;
}
-
- private void dfsComponent(Integer current, Map> graph,
- Set visited, Set comp) {
+ private void dfsComponent(Integer current, Map> graph, Set visited, Set comp) {
visited.add(current);
comp.add(current);
for (Integer neighbor : graph.get(current)) {
@@ -347,11 +275,6 @@ public abstract class GuiDiagram extends Gui {
}
}
}
-
- // Pattern detection helpers:
-
- // A cluster is considered a cycle if each node has exactly 2 neighbors (within the cluster)
- // and there are at least 3 nodes.
private boolean isCycle(Set cluster, Map> graph) {
if (cluster.size() < 3) return false;
for (Integer node : cluster) {
@@ -363,8 +286,6 @@ public abstract class GuiDiagram extends Gui {
}
return true;
}
-
- // A cluster is considered a tree if the number of edges equals nodes-1.
private boolean isTree(Set cluster, Map> graph) {
int edgeCount = 0;
for (Integer node : cluster) {
@@ -372,42 +293,34 @@ public abstract class GuiDiagram extends Gui {
if (cluster.contains(neighbor)) edgeCount++;
}
}
- edgeCount /= 2; // each edge counted twice.
+ edgeCount /= 2;
return edgeCount == cluster.size() - 1;
}
-
- // A simple heuristic: if the cluster size is a perfect square or nearly so, treat it as a square.
private boolean isSquare(Set cluster) {
int n = cluster.size();
int sqrt = (int) Math.round(Math.sqrt(n));
return sqrt * sqrt == n;
}
-// Layout algorithms:
-
- // Circular layout for cycles.
+ // 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;
- int centerY = compY + compHeight / 2;
+ int centerX = compX + compWidth / 2, centerY = compY + compHeight / 2;
int radius = Math.min(compWidth, compHeight) / 3;
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++;
}
return positions;
}
- // A simple top-down tree layout using BFS.
private Map layoutTree(Set cluster, int compX, int compY, int compWidth, int compHeight,
Map> fullGraph) {
Map positions = new HashMap<>();
-
- // Build subgraph for the cluster.
Map> subgraph = new HashMap<>();
for (Integer id : cluster) {
List neighbors = new ArrayList<>();
@@ -417,15 +330,10 @@ public abstract class GuiDiagram extends Gui {
}
subgraph.put(id, neighbors);
}
-
- // Choose a root (for example, the node with the smallest id).
Integer root = cluster.iterator().next();
for (Integer id : cluster) {
- if (id < root)
- root = id;
+ if (id < root) root = id;
}
-
- // BFS to determine levels.
Map levelMap = new HashMap<>();
Map> levels = new HashMap<>();
Queue queue = new LinkedList<>();
@@ -433,7 +341,6 @@ public abstract class GuiDiagram extends Gui {
queue.add(root);
visited.add(root);
levelMap.put(root, 0);
-
while (!queue.isEmpty()) {
Integer curr = queue.poll();
int lvl = levelMap.get(curr);
@@ -446,35 +353,30 @@ public abstract class GuiDiagram extends Gui {
}
}
}
-
- // Calculate vertical spacing.
int maxLevel = levels.keySet().stream().max(Integer::compare).orElse(0);
double levelHeight = compHeight / (maxLevel + 1.0);
-
- // For each level, assign horizontal positions evenly.
for (Map.Entry> entry : levels.entrySet()) {
int lvl = entry.getKey();
List nodesAtLevel = entry.getValue();
- double horizontalSpacing = compWidth / (nodesAtLevel.size() + 1.0);
+ nodesAtLevel.sort(Comparator.comparingInt(a -> getIconById(a).priority));
+ 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) * horizontalSpacing);
- 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));
}
}
-
return positions;
}
- // Grid layout for square-like clusters.
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 gridCellWidth = compWidth / n;
- int gridCellHeight = compHeight / n;
+ int gridCellWidth = compWidth / n, gridCellHeight = compHeight / n;
int index = 0;
List nodes = new ArrayList<>(cluster);
+ nodes.sort(Comparator.comparingInt(a -> getIconById(a).priority));
for (int row = 0; row < n; row++) {
for (int col = 0; col < n; col++) {
if (index >= nodes.size()) break;
@@ -487,7 +389,6 @@ public abstract class GuiDiagram extends Gui {
return positions;
}
- // Force-directed layout for generic clusters.
private Map layoutForceDirected(Set cluster, int compX, int compY, int compWidth, int compHeight,
List connections) {
Map positions = new HashMap<>();
@@ -497,29 +398,22 @@ public abstract class GuiDiagram extends Gui {
int posY = compY + rand.nextInt(compHeight);
positions.put(id, new Point(posX, posY));
}
-
double area = compWidth * compHeight;
double k = Math.sqrt(area / cluster.size());
int iterations = 100;
double temperature = compWidth / 10.0;
double cooling = temperature / (iterations + 1);
double minDistance = iconSize + slotPadding;
-
- // Consider only connections within the cluster.
List compConnections = new ArrayList<>();
for (DiagramConnection conn : connections) {
- if (cluster.contains(conn.idFrom) && cluster.contains(conn.idTo)) {
+ if (cluster.contains(conn.idFrom) && cluster.contains(conn.idTo))
compConnections.add(conn);
- }
}
-
for (int iter = 0; iter < iterations; iter++) {
Map disp = new HashMap<>();
for (Integer id : cluster) {
- disp.put(id, new double[] {0.0, 0.0});
+ disp.put(id, new double[]{0.0, 0.0});
}
-
- // Repulsive forces.
List nodes = new ArrayList<>(cluster);
for (int i = 0; i < nodes.size(); i++) {
Integer v = nodes.get(i);
@@ -527,8 +421,7 @@ public abstract class GuiDiagram extends Gui {
for (int j = i + 1; j < nodes.size(); j++) {
Integer u = nodes.get(j);
Point posU = positions.get(u);
- double dx = posV.x - posU.x;
- double dy = posV.y - posU.y;
+ double dx = posV.x - posU.x, dy = posV.y - posU.y;
double distance = Math.sqrt(dx * dx + dy * dy);
if (distance < minDistance) distance = minDistance;
double repForce = (k * k) / distance;
@@ -540,13 +433,10 @@ public abstract class GuiDiagram extends Gui {
dispU[1] -= (dy / distance) * repForce;
}
}
-
- // Attractive forces.
for (DiagramConnection conn : compConnections) {
Point posV = positions.get(conn.idFrom);
Point posU = positions.get(conn.idTo);
- double dx = posV.x - posU.x;
- double dy = posV.y - posU.y;
+ double dx = posV.x - posU.x, dy = posV.y - posU.y;
double distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 0.01) distance = 0.01;
double attrForce = (distance * distance) / k;
@@ -557,8 +447,6 @@ public abstract class GuiDiagram extends Gui {
dispU[0] += (dx / distance) * attrForce;
dispU[1] += (dy / distance) * attrForce;
}
-
- // Update positions.
for (Integer id : cluster) {
double[] d = disp.get(id);
double dispLength = Math.sqrt(d[0] * d[0] + d[1] * d[1]);
@@ -570,17 +458,214 @@ public abstract class GuiDiagram extends Gui {
positions.get(id).x = newX;
positions.get(id).y = newY;
}
-
temperature -= cooling;
}
-
return positions;
}
+ // Manual Layouts – using index (ring) and priority.
+ private Map calculateCircularManualPositions() {
+ List icons = getIcons();
+ Map> groups = new TreeMap<>();
+ for (DiagramIcon icon : icons) {
+ groups.computeIfAbsent(icon.index, k -> new ArrayList<>()).add(icon);
+ }
+ Map positions = new HashMap<>();
+ 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°.
+ 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;
+ 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));
+ positions.put(groupIcons.get(i).id, new Point(posX, posY));
+ }
+ }
+ return positions;
+ }
- // ---------------
- // Standard Icon Box Drawing (Background + Border)
- // ---------------
+ private Map calculateSquareManualPositions() {
+ List icons = getIcons();
+ Map> groups = new TreeMap<>();
+ for (DiagramIcon icon : icons) {
+ groups.computeIfAbsent(icon.index, k -> new ArrayList<>()).add(icon);
+ }
+ Map positions = new HashMap<>();
+ 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.
+ for (Map.Entry> entry : groups.entrySet()) {
+ int ring = entry.getKey();
+ List groupIcons = entry.getValue();
+ groupIcons.sort(Comparator.comparingInt(icon -> icon.priority));
+ double offset = ringSpacing * (ring + 1);
+ int count = groupIcons.size();
+ double perimeter = 8 * offset;
+ for (int i = 0; i < count; i++) {
+ double posAlong = (perimeter * i) / count;
+ int posX, posY;
+ if (posAlong < 2 * 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);
+ } else if (posAlong < 6 * 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);
+ }
+ positions.put(groupIcons.get(i).id, new Point(posX, posY));
+ }
+ }
+ return positions;
+ }
+
+ private Map calculateTreeManualPositions() {
+ List icons = getIcons();
+ Map> levels = new TreeMap<>();
+ for (DiagramIcon icon : icons) {
+ levels.computeIfAbsent(icon.index, k -> new ArrayList<>()).add(icon);
+ }
+ Map positions = new HashMap<>();
+ int maxLevel = levels.isEmpty() ? 0 : Collections.max(levels.keySet());
+ int levelHeight = height / (maxLevel + 2);
+ for (Map.Entry> entry : levels.entrySet()) {
+ int level = entry.getKey();
+ List levelIcons = entry.getValue();
+ levelIcons.sort(Comparator.comparingInt(icon -> icon.priority));
+ int count = levelIcons.size();
+ int horizontalSpacing = (width - 40) / (count + 1);
+ for (int i = 0; i < count; i++) {
+ int posX = x + 20 + (i + 1) * horizontalSpacing;
+ int posY = y + levelHeight * (level + 1);
+ positions.put(levelIcons.get(i).id, new Point(posX, posY));
+ }
+ }
+ return positions;
+ }
+
+ // Arrow Drawing:
+ /**
+ * Draws a connection line.
+ * If curvedArrows is false, draws a straight line.
+ * Otherwise draws a quadratic Bezier curve using curveAngle for offset.
+ */
+ protected void drawConnectionLine(int x1, int y1, int x2, int y2, DiagramConnection conn, boolean dim) {
+ int color = getConnectionColor(conn);
+ if (!useColorScaling) color = 0xFFFFFFFF;
+ if (!curvedArrows) {
+ GL11.glPushAttrib(GL11.GL_ENABLE_BIT);
+ GL11.glDisable(GL11.GL_TEXTURE_2D);
+ GL11.glLineWidth(2.0F);
+ float r = ((color >> 16) & 0xFF) / 255f;
+ float g = ((color >> 8) & 0xFF) / 255f;
+ float b = (color & 0xFF) / 255f;
+ if (dim) { r *= 0.4f; g *= 0.4f; b *= 0.4f; }
+ GL11.glColor4f(r, g, b, 1f);
+ GL11.glBegin(GL11.GL_LINES);
+ GL11.glVertex2i(x1, y1);
+ GL11.glVertex2i(x2, y2);
+ GL11.glEnd();
+ GL11.glEnable(GL11.GL_TEXTURE_2D);
+ GL11.glPopAttrib();
+ } else {
+ Point start = new Point(x1, y1);
+ Point end = new Point(x2, y2);
+ double dx = x2 - x1, dy = y2 - y1;
+ 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 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;
+ GL11.glPushAttrib(GL11.GL_ENABLE_BIT);
+ GL11.glDisable(GL11.GL_TEXTURE_2D);
+ GL11.glLineWidth(2.0F);
+ 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.glBegin(GL11.GL_LINE_STRIP);
+ for (int i = 0; i <= segments; i++) {
+ double t = (double)i / segments;
+ int bx = (int)((1 - t) * (1 - t) * start.x + 2 * (1 - t) * t * control.x + t * t * end.x);
+ int by = (int)((1 - t) * (1 - t) * start.y + 2 * (1 - t) * t * control.y + t * t * end.y);
+ GL11.glVertex2i(bx, by);
+ }
+ GL11.glEnd();
+ GL11.glEnable(GL11.GL_TEXTURE_2D);
+ GL11.glPopAttrib();
+ }
+ }
+
+ /**
+ * Draws the arrow head.
+ * For curved arrows, the head is aligned with the derivative at t=0.95.
+ */
+ protected void drawArrowHead(int x1, int y1, int x2, int y2, DiagramConnection conn, boolean dim) {
+ int color = getConnectionColor(conn);
+ if (!useColorScaling) color = 0xFFFFFFFF;
+ double angle;
+ if (!curvedArrows) {
+ angle = Math.atan2(y2 - y1, x2 - x1);
+ } else {
+ double dx = x2 - x1, dy = y2 - y1;
+ double len = Math.sqrt(dx * dx + dy * dy);
+ if (len == 0) len = 1;
+ double offset = len * Math.tan(Math.toRadians(curveAngle)) / 2;
+ double perpX = -dy / len, perpY = dx / len;
+ int cx = (x1 + x2) / 2 + (int)(perpX * offset);
+ int cy = (y1 + y2) / 2 + (int)(perpY * offset);
+ double t = 0.95;
+ double bx = (1 - t) * (1 - t) * x1 + 2 * (1 - t) * t * cx + t * t * x2;
+ double by = (1 - t) * (1 - t) * y1 + 2 * (1 - t) * t * cy + t * t * y2;
+ double dBx = 2 * (1 - t) * (cx - x1) + 2 * t * (x2 - cx);
+ double dBy = 2 * (1 - t) * (cy - y1) + 2 * t * (y2 - cy);
+ angle = Math.atan2(dBy, dBx);
+ }
+ 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));
+ 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; }
+ GL11.glColor4f(rr, gg, bb, 1f);
+ GL11.glDisable(GL11.GL_CULL_FACE);
+ GL11.glBegin(GL11.GL_TRIANGLES);
+ GL11.glVertex2f(defenderEdgeX, defenderEdgeY);
+ GL11.glVertex2f(leftX, leftY);
+ GL11.glVertex2f(rightX, rightY);
+ GL11.glEnd();
+ GL11.glEnable(GL11.GL_CULL_FACE);
+ GL11.glEnable(GL11.GL_TEXTURE_2D);
+ GL11.glPopAttrib();
+ }
+
+ // 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);
@@ -591,7 +676,6 @@ public abstract class GuiDiagram extends Gui {
drawVerticalLine(slotX + slotSize - 1 - i, slotY + i, slotY + slotSize - i, iconBorderColor);
}
}
-
protected int mixColors(int color1, int color2, float ratio) {
int a1 = (color1 >> 24) & 0xFF, r1 = (color1 >> 16) & 0xFF, g1 = (color1 >> 8) & 0xFF, b1 = color1 & 0xFF;
int a2 = (color2 >> 24) & 0xFF, r2 = (color2 >> 16) & 0xFF, g2 = (color2 >> 8) & 0xFF, b2 = color2 & 0xFF;
@@ -602,39 +686,57 @@ public abstract class GuiDiagram extends Gui {
return (a << 24) | (r << 16) | (g << 8) | b;
}
- // ---------------
- // Main Draw Method
- // ---------------
- public void drawDiagram(int mouseX, int mouseY, boolean subGui) {
- // Only allow mouse interaction if parent's not showing a subgui.
- boolean allowInput = (parent == null || !parent.hasSubGui());
- // Update zoom based on mouse scroll if allowed.
- if (allowInput && isWithin(mouseX, mouseY) && !subGui) {
- handleMouseScroll(Mouse.getDWheel());
+ // Hit-detection helper for curved arrows.
+ private double distanceToBezier(Point start, Point control, Point end, int segments, int px, int py) {
+ double minDist = Double.MAX_VALUE;
+ Point prev = null;
+ for (int i = 0; i <= segments; i++) {
+ double t = (double)i / segments;
+ int x = (int)((1 - t) * (1 - t) * start.x + 2 * (1 - t) * t * control.x + t * t * end.x);
+ int y = (int)((1 - t) * (1 - t) * start.y + 2 * (1 - t) * t * control.y + t * t * end.y);
+ Point curr = new Point(x, y);
+ if (prev != null) {
+ double d = distancePointToSegment(px, py, prev, curr);
+ if (d < minDist) minDist = d;
+ }
+ prev = curr;
}
+ return minDist;
+ }
+ private double distancePointToSegment(int px, int py, Point a, Point b) {
+ double A = px - a.x, B = py - a.y;
+ double C = b.x - a.x, D = b.y - a.y;
+ double dot = A * C + B * D;
+ 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; }
+ 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
+ public void drawDiagram(int mouseX, int mouseY, boolean subGui) {
+ boolean allowInput = (parent == null || !parent.hasSubGui());
+ if (allowInput && isWithin(mouseX, mouseY) && !subGui)
+ handleMouseScroll(Mouse.getDWheel());
Minecraft mc = Minecraft.getMinecraft();
ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
int factor = sr.getScaleFactor();
-
Map positions = calculatePositions();
- int centerX = x + width / 2;
- int centerY = y + height / 2;
-
+ int centerX = x + width / 2, centerY = y + height / 2;
int effectiveMouseX = (int)(((float)mouseX - (centerX + panX)) / zoom + centerX);
int effectiveMouseY = (int)(((float)mouseY - (centerY + panY)) / zoom + centerY);
-
Integer hoveredIconId = null;
int hoveredConnFrom = -1, hoveredConnTo = -1;
Set selectedIconIds = new HashSet<>();
-
- // Hit testing for icons (only consider enabled icons)
for (DiagramIcon icon : getIcons()) {
if (!icon.enabled) continue;
Point pos = positions.get(icon.id);
if (pos == null) continue;
- int slotX = pos.x - slotSize / 2;
- int slotY = pos.y - slotSize / 2;
+ int slotX = pos.x - slotSize / 2, slotY = pos.y - slotSize / 2;
if (effectiveMouseX >= slotX && effectiveMouseX < slotX + slotSize &&
effectiveMouseY >= slotY && effectiveMouseY < slotY + slotSize) {
hoveredIconId = icon.id;
@@ -644,7 +746,6 @@ public abstract class GuiDiagram extends Gui {
}
if (hoveredIconId != null) {
for (DiagramConnection conn : getConnections()) {
- // Validate connection: both endpoints must exist and be enabled.
DiagramIcon iconFrom = getIconById(conn.idFrom);
DiagramIcon iconTo = getIconById(conn.idTo);
if (iconFrom == null || iconTo == null || !iconFrom.enabled || !iconTo.enabled)
@@ -665,7 +766,20 @@ public abstract class GuiDiagram extends Gui {
Point pFrom = positions.get(conn.idFrom);
Point pTo = positions.get(conn.idTo);
if (pFrom == null || pTo == null) continue;
- double dist = pointLineDistance(effectiveMouseX, effectiveMouseY, pFrom.x, pFrom.y, pTo.x, pTo.y);
+ double dist;
+ if (curvedArrows) {
+ 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 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);
+ } else {
+ dist = pointLineDistance(effectiveMouseX, effectiveMouseY, pFrom.x, pFrom.y, pTo.x, pTo.y);
+ }
if (dist < threshold) {
hoveredConnFrom = conn.idFrom;
hoveredConnTo = conn.idTo;
@@ -675,17 +789,13 @@ public abstract class GuiDiagram extends Gui {
}
}
}
-
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);
-
GL11.glPushMatrix();
GL11.glTranslatef(centerX + panX, centerY + panY, 0);
GL11.glScalef(zoom, zoom, 1.0f);
GL11.glTranslatef(-centerX, -centerY, 0);
-
- // Draw connections (only for endpoints that are enabled).
for (DiagramConnection conn : getConnections()) {
DiagramIcon iconFrom = getIconById(conn.idFrom);
DiagramIcon iconTo = getIconById(conn.idTo);
@@ -694,30 +804,21 @@ public abstract class GuiDiagram extends Gui {
Point pFrom = positions.get(conn.idFrom);
Point pTo = positions.get(conn.idTo);
if (pFrom == null || pTo == null) continue;
- int ax = pFrom.x;
- int ay = pFrom.y;
- int bx = pTo.x;
- int by = pTo.y;
- boolean dim = !selectedIconIds.isEmpty() &&
- !(selectedIconIds.contains(conn.idFrom) && selectedIconIds.contains(conn.idTo));
+ 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));
drawConnectionLine(ax, ay, bx, by, conn, dim);
}
-
- // Draw icons.
for (DiagramIcon icon : getIcons()) {
if (!icon.enabled) continue;
Point pos = positions.get(icon.id);
if (pos == null) continue;
- int slotX = pos.x - slotSize / 2;
- int slotY = pos.y - slotSize / 2;
+ int slotX = pos.x - slotSize / 2, slotY = pos.y - slotSize / 2;
boolean highlighted = selectedIconIds.isEmpty() || selectedIconIds.contains(icon.id);
drawIconBox(slotX, slotY, slotSize, highlighted);
IconRenderState state = highlighted ? IconRenderState.HIGHLIGHTED : IconRenderState.NOT_HIGHLIGHTED;
renderIcon(icon, pos.x, pos.y, state);
}
GL11.glPopMatrix();
-
- // Draw arrow heads.
if (showArrowHeads) {
GL11.glPushMatrix();
GL11.glTranslatef(centerX + panX, centerY + panY, 0);
@@ -731,161 +832,86 @@ public abstract class GuiDiagram extends Gui {
Point pFrom = positions.get(conn.idFrom);
Point pTo = positions.get(conn.idTo);
if (pFrom == null || pTo == null) continue;
- int ax = pFrom.x;
- int ay = pFrom.y;
- int bx = pTo.x;
- int by = pTo.y;
- boolean dim = !selectedIconIds.isEmpty() &&
- !(selectedIconIds.contains(conn.idFrom) && selectedIconIds.contains(conn.idTo));
+ 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));
drawArrowHead(ax, ay, bx, by, conn, dim);
}
GL11.glPopMatrix();
}
GL11.glDisable(GL11.GL_SCISSOR_TEST);
-
- // Tooltips.
if (hoveredIconId != null) {
DiagramIcon icon = getIconById(hoveredIconId);
List tooltip = getIconTooltip(icon);
- if (tooltip != null && !tooltip.isEmpty()) {
+ if (tooltip != null && !tooltip.isEmpty())
drawHoveringText(tooltip, mouseX, mouseY, mc.fontRenderer);
- }
} else if (hoveredConnFrom != -1 && hoveredConnTo != -1) {
DiagramConnection conn = getConnectionByIds(hoveredConnFrom, hoveredConnTo);
List tooltip = getConnectionTooltip(conn);
- if (tooltip != null && !tooltip.isEmpty()) {
+ if (tooltip != null && !tooltip.isEmpty())
drawHoveringText(tooltip, mouseX, mouseY, mc.fontRenderer);
- }
}
}
protected DiagramIcon getIconById(int id) {
for (DiagramIcon icon : getIcons()) {
- if (icon.id == id)
- return icon;
+ if (icon.id == id) return icon;
}
return null;
}
-
protected DiagramConnection getConnectionByIds(int idFrom, int idTo) {
for (DiagramConnection conn : getConnections()) {
- if (conn.idFrom == idFrom && conn.idTo == idTo)
- return conn;
+ if (conn.idFrom == idFrom && conn.idTo == idTo) return conn;
}
return null;
}
-
- protected void drawConnectionLine(int x1, int y1, int x2, int y2, DiagramConnection conn, boolean dim) {
- int color = getConnectionColor(conn);
- if (!useColorScaling) { color = 0xFFFFFFFF; }
- GL11.glPushAttrib(GL11.GL_ENABLE_BIT);
- GL11.glDisable(GL11.GL_TEXTURE_2D);
- GL11.glLineWidth(2.0F);
- float r = ((color >> 16)&0xFF)/255f;
- float g = ((color >> 8)&0xFF)/255f;
- float b = (color&0xFF)/255f;
- if (dim) { r*=0.4f; g*=0.4f; b*=0.4f; }
- GL11.glColor4f(r, g, b, 1f);
- GL11.glBegin(GL11.GL_LINES);
- GL11.glVertex2f(x1, y1);
- GL11.glVertex2f(x2, y2);
- GL11.glEnd();
- GL11.glEnable(GL11.GL_TEXTURE_2D);
- GL11.glPopAttrib();
- }
-
- protected void drawArrowHead(int x1, int y1, int x2, int y2, DiagramConnection conn, boolean dim) {
- float dx = x2 - x1, dy = y2 - y1;
- float len = (float)Math.sqrt(dx*dx+dy*dy);
- if (len==0)return;
- float ux = dx/len, uy = dy/len;
- float defenderEdgeX = x2 - ux*(slotSize/2f);
- float defenderEdgeY = y2 - uy*(slotSize/2f);
- int arrowSize = 6;
- double angle = Math.atan2(uy,ux);
- 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));
- int color = getConnectionColor(conn);
- if (!useColorScaling) { color = 0xFFFFFFFF; }
- GL11.glPushAttrib(GL11.GL_ENABLE_BIT);
- GL11.glDisable(GL11.GL_TEXTURE_2D);
- float r = ((color >> 16)&0xFF)/255f;
- float g = ((color >> 8)&0xFF)/255f;
- float b = (color&0xFF)/255f;
- if(dim){ r*=0.4f; g*=0.4f; b*=0.4f; }
- GL11.glColor4f(r, g, b, 1f);
- GL11.glDisable(GL11.GL_CULL_FACE);
- GL11.glBegin(GL11.GL_TRIANGLES);
- GL11.glVertex2f(defenderEdgeX, defenderEdgeY);
- GL11.glVertex2f(leftX, leftY);
- GL11.glVertex2f(rightX, rightY);
- GL11.glEnd();
- GL11.glEnable(GL11.GL_CULL_FACE);
- GL11.glEnable(GL11.GL_TEXTURE_2D);
- GL11.glPopAttrib();
- }
-
protected int getConnectionColor(DiagramConnection conn) {
float percent = conn.percent;
- if (percent<=0.10f)return 0xFF80FF80;
- else if (percent<=0.40f)return 0xFFCCFF66;
- else if (percent<=0.50f)return 0xFFFFA500;
+ if (percent <= 0.10f) return 0xFF80FF80;
+ else if (percent <= 0.40f) return 0xFFCCFF66;
+ else if (percent <= 0.50f) return 0xFFFFA500;
else {
- float ratio = (percent-0.50f)/0.50f;
+ float ratio = (percent - 0.50f) / 0.50f;
ratio = Math.min(1f, Math.max(0f, ratio));
int r1 = 0xFF, g1 = 0xA5, b1 = 0x00;
int r2 = 0xFF, g2 = 0x00, b2 = 0x00;
- int r = r1 + (int)((r2-r1)*ratio);
- int g = g1 + (int)((g2-g1)*ratio);
- int b = b1 + (int)((b2-b1)*ratio);
+ int r = r1 + (int)((r2 - r1) * ratio);
+ int g = g1 + (int)((g2 - g1) * ratio);
+ int b = b1 + (int)((b2 - b1) * ratio);
return (0xFF << 24) | (r << 16) | (g << 8) | b;
}
}
-
- private double pointLineDistance(double px, double py, double x1, double y1, double x2, double y2) {
+ private double pointLineDistance(int px, int py, int x1, int y1, int x2, int y2) {
double A = px - x1, B = py - y1, C = x2 - x1, D = y2 - y1;
double dot = A * C + B * D;
double lenSq = C * C + D * D;
- double param = (lenSq!=0)?(dot/lenSq):-1;
+ double param = (lenSq != 0) ? (dot / lenSq) : -1;
double xx, yy;
- if(param<0){ xx = x1; yy = y1; }
- else if(param>1){ xx = x2; yy = y2; }
- else { xx = x1+param*C; yy = y1+param*D; }
- double dx = px-xx, dy = py-yy;
- return Math.sqrt(dx*dx+dy*dy);
+ if (param < 0) { xx = x1; yy = y1; }
+ else if (param > 1) { xx = x2; yy = y2; }
+ else { xx = x1 + param * C; yy = y1 + param * D; }
+ double dx = px - xx, dy = py - yy;
+ return Math.sqrt(dx * dx + dy * dy);
}
- // ---------------
// Mouse Handling
- // ---------------
public boolean mouseClicked(int mouseX, int mouseY, int mouseButton) {
- // Do not process if parent's subgui is active.
if (parent != null && parent.hasSubGui()) return false;
- // If left mouse button:
if (mouseButton == 0) {
- // First, try to dispatch icon events for pressable icons.
for (DiagramIcon icon : getIcons()) {
- if (!icon.enabled || !icon.pressable)
- continue;
+ if (!icon.enabled || !icon.pressable) continue;
Point pos = calculatePositions().get(icon.id);
if (pos == null) continue;
- int centerX = x + width / 2;
- int 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;
- int slotY = pos.y - slotSize / 2;
- if (effectiveX >= slotX && effectiveX < slotX + slotSize &&
- effectiveY >= slotY && effectiveY < slotY + slotSize) {
+ 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);
- // Mark as pressed for held/release events.
currentlyPressedIcon = icon;
return true;
}
}
- // Otherwise, start dragging for panning.
if (isWithin(mouseX, mouseY)) {
dragging = true;
lastDragX = mouseX;
@@ -895,25 +921,18 @@ public abstract class GuiDiagram extends Gui {
}
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;
- int dy = mouseY - lastDragY;
- panX += dx / zoom * 0.7f;
- panY += dy / zoom * 0.7f;
+ int dx = mouseX - lastDragX, dy = mouseY - lastDragY;
+ panX += dx/zoom*0.7f;
+ panY += dy/zoom*0.7f;
lastDragX = mouseX;
lastDragY = mouseY;
}
- // If an icon is pressed, dispatch held event.
- if (currentlyPressedIcon != null) {
- onIconHeld(currentlyPressedIcon);
- }
+ if (currentlyPressedIcon != null) onIconHeld(currentlyPressedIcon);
}
-
public void mouseReleased(int mouseX, int mouseY, int state) {
if (parent != null && parent.hasSubGui()) return;
if (currentlyPressedIcon != null) {
@@ -922,58 +941,51 @@ public abstract class GuiDiagram extends Gui {
}
dragging = false;
}
-
public boolean isWithin(int mouseX, int mouseY) {
- if(parent.hasSubGui())
+ if (parent.hasSubGui())
return false;
return mouseX >= x && mouseX <= x+width && mouseY >= y && mouseY <= y+height;
}
-
/**
- * Handles zoom in/out based on mouse wheel scrolling.
+ * 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;
+ if (zoom < 0.5f) zoom = 0.5f;
+ if (zoom > 2.0f) zoom = 2.0f;
}
-
/**
- * Override this method if you wish to render tooltips differently.
+ * 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 for Data Wrapping
- // ---------------
+ // Inner Classes
public static class DiagramIcon {
public int id;
- public boolean enabled = true; // If false, the icon is not rendered.
- public boolean pressable = false; // If true, it will receive mouse events.
- public DiagramIcon(int id) {
+ public boolean enabled = true;
+ public boolean pressable = false;
+ public int index = 0; // For row, ring, or level.
+ public int priority = 0; // For ordering.
+ public DiagramIcon(int id) { this.id = id; }
+ public DiagramIcon(int id, int index, int priority) {
this.id = id;
+ this.index = index;
+ this.priority = priority;
}
}
-
public static class DiagramConnection {
public int idFrom, idTo;
public float percent;
@@ -985,24 +997,14 @@ public abstract class GuiDiagram extends Gui {
this.hoverText = hoverText;
}
}
-
- // ---------------
- // Icon Event Callbacks (override these in subclasses as needed)
- // ---------------
+ // Icon Event Callbacks
protected void onIconClick(DiagramIcon icon) {
- // Called when a pressable icon is clicked.
System.out.println("Icon " + icon.id + " clicked.");
}
-
protected void onIconHeld(DiagramIcon icon) {
- // Called repeatedly if an icon remains pressed.
- // (You can add timing logic to fire this only after a threshold.)
- // For now, we simply print once.
System.out.println("Icon " + icon.id + " held.");
}
-
protected void onIconRelease(DiagramIcon icon) {
- // Called when a pressable icon is released.
System.out.println("Icon " + icon.id + " released.");
}
}
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 da8ac8217..7954d5f56 100644
--- a/src/main/java/noppes/npcs/client/gui/global/GuiMagicMap.java
+++ b/src/main/java/noppes/npcs/client/gui/global/GuiMagicMap.java
@@ -31,7 +31,9 @@ public class GuiMagicMap extends GuiDiagram {
for (Magic magic : MagicController.getInstance().magics.values()) {
magics.add(magic);
}
- setLayout(DiagramLayout.GENERATED);
+ setLayout(DiagramLayout.CIRCULAR_MANUAL);
+ this.setCurvedArrows(true);
+ this.setCurveAngle(25);
}
@Override
@@ -121,7 +123,7 @@ public class GuiMagicMap extends GuiDiagram {
private class MagicIcon extends DiagramIcon {
public Magic magic;
public MagicIcon(Magic magic) {
- super(magic.id);
+ super(magic.id, magic.index, magic.priority);
this.magic = magic;
}
}
diff --git a/src/main/java/noppes/npcs/controllers/MagicController.java b/src/main/java/noppes/npcs/controllers/MagicController.java
index 9aafa1820..b2c6385af 100644
--- a/src/main/java/noppes/npcs/controllers/MagicController.java
+++ b/src/main/java/noppes/npcs/controllers/MagicController.java
@@ -1,5 +1,6 @@
package noppes.npcs.controllers;
+import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompressedStreamTools;
import net.minecraft.nbt.NBTTagCompound;
@@ -55,31 +56,179 @@ public class MagicController {
}
} catch (Exception ee) { }
}
+ magics.clear();
+ if (false) {
+ // 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
- if (magics.isEmpty()) {
+ Magic fire = new Magic(1, "Fire", 0xF08030);
+ fire.setIconItem(new ItemStack(net.minecraft.init.Items.flint)); // flint for Fire
+
+ Magic water = new Magic(2, "Water", 0x6890F0);
+ water.setIconItem(new ItemStack(net.minecraft.init.Items.water_bucket)); // water bucket
+
+ Magic electric = new Magic(3, "Electric", 0xF8D030);
+ electric.setIconItem(new ItemStack(net.minecraft.init.Items.redstone)); // redstone
+
+ Magic grass = new Magic(4, "Grass", 0x78C850);
+ grass.setIconItem(new ItemStack(net.minecraft.init.Items.wheat_seeds)); // wheat seeds
+
+ Magic ice = new Magic(5, "Ice", 0x98D8D8);
+ ice.setIconItem(new ItemStack(net.minecraft.init.Items.snowball)); // snowball for Ice
+
+ Magic fighting = new Magic(6, "Fighting", 0xC03028);
+ fighting.setIconItem(new ItemStack(net.minecraft.init.Items.iron_sword)); // sword for Fighting
+
+ Magic poison = new Magic(7, "Poison", 0xA040A0);
+ poison.setIconItem(new ItemStack(net.minecraft.init.Items.potionitem)); // potion item for Poison
+
+ Magic ground = new Magic(8, "Ground", 0xE0C068);
+ ground.setIconItem(new ItemStack(Blocks.dirt)); // dirt for Ground
+
+ Magic flying = new Magic(9, "Flying", 0xA890F0);
+ flying.setIconItem(new ItemStack(net.minecraft.init.Items.feather)); // feather for Flying
+
+ Magic psychic = new Magic(10, "Psychic", 0xF85888);
+ psychic.setIconItem(new ItemStack(net.minecraft.init.Items.ender_pearl)); // ender pearl for Psychic
+
+ Magic bug = new Magic(11, "Bug", 0xA8B820);
+ bug.setIconItem(new ItemStack(net.minecraft.init.Items.spider_eye)); // spider eye for Bug
+
+ Magic rock = new Magic(12, "Rock", 0xB8A038);
+ rock.setIconItem(new ItemStack(Blocks.cobblestone)); // cobblestone for Rock
+
+ Magic ghost = new Magic(13, "Ghost", 0x705898);
+ ghost.setIconItem(new ItemStack(net.minecraft.init.Items.skull)); // skull for Ghost
+
+ Magic dragon = new Magic(14, "Dragon", 0x7038F8);
+ dragon.setIconItem(new ItemStack(Blocks.dragon_egg)); // dragon egg
+
+ Magic dark = new Magic(15, "Dark", 0x705848);
+ dark.setIconItem(new ItemStack(net.minecraft.init.Items.coal)); // coal for Dark
+
+ Magic steel = new Magic(16, "Steel", 0xB8B8D0);
+ steel.setIconItem(new ItemStack(net.minecraft.init.Items.iron_ingot)); // iron ingot for Steel
+
+ Magic fairy = new Magic(17, "Fairy", 0xEE99AC);
+ fairy.setIconItem(new ItemStack(net.minecraft.init.Items.sugar)); // sugar for Fairy
+
+ // Now set weaknesses (using a multiplier of 2.0f to indicate “double damage”).
+ // The following is based on a common Pokémon type chart.
+ normal.weaknesses.put(fighting.id, 2.0f);
+
+ fire.weaknesses.put(water.id, 2.0f);
+ fire.weaknesses.put(ground.id, 2.0f);
+ fire.weaknesses.put(rock.id, 2.0f);
+
+ water.weaknesses.put(electric.id, 2.0f);
+ water.weaknesses.put(grass.id, 2.0f);
+
+ electric.weaknesses.put(ground.id, 2.0f);
+
+ grass.weaknesses.put(fire.id, 2.0f);
+ grass.weaknesses.put(ice.id, 2.0f);
+ grass.weaknesses.put(poison.id, 2.0f);
+ grass.weaknesses.put(flying.id, 2.0f);
+ grass.weaknesses.put(bug.id, 2.0f);
+
+ ice.weaknesses.put(fire.id, 2.0f);
+ ice.weaknesses.put(fighting.id, 2.0f);
+ ice.weaknesses.put(rock.id, 2.0f);
+ ice.weaknesses.put(steel.id, 2.0f);
+
+ fighting.weaknesses.put(flying.id, 2.0f);
+ fighting.weaknesses.put(psychic.id, 2.0f);
+ fighting.weaknesses.put(fairy.id, 2.0f);
+
+ poison.weaknesses.put(ground.id, 2.0f);
+ poison.weaknesses.put(psychic.id, 2.0f);
+
+ ground.weaknesses.put(water.id, 2.0f);
+ ground.weaknesses.put(grass.id, 2.0f);
+ ground.weaknesses.put(ice.id, 2.0f);
+
+ flying.weaknesses.put(electric.id, 2.0f);
+ flying.weaknesses.put(ice.id, 2.0f);
+ flying.weaknesses.put(rock.id, 2.0f);
+
+ psychic.weaknesses.put(bug.id, 2.0f);
+ psychic.weaknesses.put(ghost.id, 2.0f);
+ psychic.weaknesses.put(dark.id, 2.0f);
+
+ bug.weaknesses.put(fire.id, 2.0f);
+ bug.weaknesses.put(flying.id, 2.0f);
+ bug.weaknesses.put(rock.id, 2.0f);
+
+ rock.weaknesses.put(water.id, 2.0f);
+ rock.weaknesses.put(grass.id, 2.0f);
+ rock.weaknesses.put(fighting.id, 2.0f);
+ rock.weaknesses.put(ground.id, 2.0f);
+ rock.weaknesses.put(steel.id, 2.0f);
+
+ ghost.weaknesses.put(ghost.id, 2.0f);
+ ghost.weaknesses.put(dark.id, 2.0f);
+
+ dragon.weaknesses.put(ice.id, 2.0f);
+ dragon.weaknesses.put(dragon.id, 2.0f);
+ dragon.weaknesses.put(fairy.id, 2.0f);
+
+ dark.weaknesses.put(fighting.id, 2.0f);
+ dark.weaknesses.put(bug.id, 2.0f);
+ dark.weaknesses.put(fairy.id, 2.0f);
+
+ steel.weaknesses.put(fire.id, 2.0f);
+ steel.weaknesses.put(fighting.id, 2.0f);
+ steel.weaknesses.put(ground.id, 2.0f);
+
+ fairy.weaknesses.put(poison.id, 2.0f);
+ fairy.weaknesses.put(steel.id, 2.0f);
+
+ // Register the types.
+ magics.put(normal.id, normal);
+ magics.put(fire.id, fire);
+ magics.put(water.id, water);
+ magics.put(electric.id, electric);
+ magics.put(grass.id, grass);
+ magics.put(ice.id, ice);
+ magics.put(fighting.id, fighting);
+ magics.put(poison.id, poison);
+ magics.put(ground.id, ground);
+ magics.put(flying.id, flying);
+ magics.put(psychic.id, psychic);
+ magics.put(bug.id, bug);
+ magics.put(rock.id, rock);
+ magics.put(ghost.id, ghost);
+ magics.put(dragon.id, dragon);
+ magics.put(dark.id, dark);
+ magics.put(steel.id, steel);
+ magics.put(fairy.id, fairy);
+ }
+
+ if (true || magics.isEmpty()) {
// Create default magics
- Magic earth = new Magic(0, "Earth", 0x00DD00);
+ Magic earth = new Magic(0, "Earth", 0x00DD00, 0, 0);
earth.setIconItem(new ItemStack(CustomItems.earthElement));
- Magic water = new Magic(1, "Water", 0xF2DD00);
+ Magic water = new Magic(1, "Water", 0xF2DD00, 0, 1);
water.setIconItem(new ItemStack(CustomItems.waterElement));
- Magic fire = new Magic(2, "Fire", 0xDD0000);
+ Magic fire = new Magic(2, "Fire", 0xDD0000, 0, 2);
fire.setIconItem(new ItemStack(CustomItems.spellFire));
- Magic air = new Magic(3, "Air", 0xDD0000);
+ Magic air = new Magic(3, "Air", 0xDD0000, 0, 3);
air.setIconItem(new ItemStack(CustomItems.airElement));
- Magic dark = new Magic(4, "Dark", 0xDD0000);
+ Magic dark = new Magic(4, "Dark", 0xDD0000, 1, 3);
dark.setIconItem(new ItemStack(CustomItems.spellDark));
- Magic holy = new Magic(5, "Holy", 0xDD0000);
+ Magic holy = new Magic(5, "Holy", 0xDD0000, 1, 1);
holy.setIconItem(new ItemStack(CustomItems.spellHoly));
- Magic nature = new Magic(6, "Nature", 0xDD0000);
+ Magic nature = new Magic(6, "Nature", 0xDD0000, 1, 0);
nature.setIconItem(new ItemStack(CustomItems.spellNature));
- Magic arcane = new Magic(7, "Arcane", 0xDD0000);
+ Magic arcane = new Magic(7, "Arcane", 0xDD0000, 1, 2);
arcane.setIconItem(new ItemStack(CustomItems.spellArcane));
// Insiders
diff --git a/src/main/java/noppes/npcs/controllers/data/Magic.java b/src/main/java/noppes/npcs/controllers/data/Magic.java
index eaea2608c..1b5636bc1 100644
--- a/src/main/java/noppes/npcs/controllers/data/Magic.java
+++ b/src/main/java/noppes/npcs/controllers/data/Magic.java
@@ -14,6 +14,9 @@ public class Magic implements IMagic {
public int color = Integer.parseInt("FF00", 16);
public int id = -1;
+ public int index;
+ public int priority;
+
// Icon as an ItemStack or as a texture file
public ItemStack iconItem = null;
public String iconTexture = "";
@@ -30,6 +33,14 @@ public class Magic implements IMagic {
this.id = id;
}
+ public Magic(int id, String name, int color, int index, int priority) {
+ this.name = name;
+ this.color = color;
+ this.id = id;
+ this.index = index;
+ this.priority = priority;
+ }
+
public static String formatName(String name) {
name = name.toLowerCase().trim();
return name.substring(0, 1).toUpperCase() + name.substring(1);
@@ -39,6 +50,8 @@ public class Magic implements IMagic {
name = compound.getString("Name");
color = compound.getInteger("Color");
id = compound.getInteger("Slot");
+ index = compound.getInteger("Index");
+ priority = compound.getInteger("Priority");
if(compound.hasKey("IconItem")){
NBTTagCompound itemTag = compound.getCompoundTag("IconItem");
@@ -63,6 +76,8 @@ public class Magic implements IMagic {
compound.setInteger("Slot", id);
compound.setString("Name", name);
compound.setInteger("Color", color);
+ compound.setInteger("Index", index);
+ compound.setInteger("Priority", priority);
if(iconItem != null) {
NBTTagCompound itemTag = new NBTTagCompound();
@@ -96,6 +111,22 @@ public class Magic implements IMagic {
this.name = name;
}
+ public int getPriority() {
+ return priority;
+ }
+
+ public void setPriority(int priority) {
+ this.priority = priority;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public void setIndex(int index) {
+ this.index = index;
+ }
+
public void setColor(int color) {
this.color = color;
}