mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 03:47:27 +00:00
Circular Magic Map
This commit is contained in:
1 parent
7a0c742833
commit
334eca1b40
2 files changed
+304
-300
No files matched your search
@@ -17,10 +17,6 @@ import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* Implementation of GuiNPCDiagram for displaying Magic nodes and their connections.
|
||||
* Magic objects are wrapped as DiagramIcons, and each Magic's weaknesses generate connections.
|
||||
*/
|
||||
public class GuiMagicMap extends GuiNPCDiagram {
|
||||
|
||||
private GuiScreen parent;
|
||||
@@ -35,6 +31,7 @@ public class GuiMagicMap extends GuiNPCDiagram {
|
||||
for (Magic magic : MagicController.getInstance().magics.values()) {
|
||||
magics.add(magic);
|
||||
}
|
||||
setLayout(DiagramLayout.CIRCULAR);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
@@ -18,7 +19,7 @@ import org.lwjgl.opengl.GL11;
|
||||
* Abstract GUI class for drawing diagrams that consist of icons (nodes) and connections (arrows).
|
||||
*
|
||||
* This class provides:
|
||||
* - A standard circular layout of nodes.
|
||||
* - Multiple layout algorithms for positioning nodes (Circular, Square, Tree, Generated).
|
||||
* - Panning and zooming functionality.
|
||||
* - Standard drawing of a node's "box": a background and border that can be customized.
|
||||
* - Connection drawing (including arrow heads).
|
||||
@@ -30,33 +31,66 @@ import org.lwjgl.opengl.GL11;
|
||||
*/
|
||||
public abstract class GuiNPCDiagram extends Gui {
|
||||
|
||||
// The area where the diagram is drawn.
|
||||
protected int x, y, width, height;
|
||||
// Panning offsets.
|
||||
protected float panX = 0, panY = 0;
|
||||
// Zoom factor.
|
||||
protected float zoom = 1.0f;
|
||||
// For mouse dragging.
|
||||
protected boolean dragging = false;
|
||||
protected int lastDragX, lastDragY;
|
||||
// ---------------
|
||||
// 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.
|
||||
|
||||
// The size of the icon image (e.g. 16 pixels square).
|
||||
protected int iconSize = 16;
|
||||
// Extra padding for the node background (box).
|
||||
protected int slotPadding = 4;
|
||||
// The overall size of the node's box (icon plus padding).
|
||||
protected int slotSize = iconSize + slotPadding;
|
||||
// ---------------
|
||||
// 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.
|
||||
|
||||
// Standard colors for the node background and border.
|
||||
protected int iconBackgroundColor = 0xFF999999;
|
||||
protected int iconBorderColor = 0xFF555555;
|
||||
// Thickness of the border (in pixels). We draw the border lines inside the box so nothing clips.
|
||||
protected int iconBorderThickness = 1;
|
||||
// ---------------
|
||||
// Node Box Appearance
|
||||
// ---------------
|
||||
protected int iconBackgroundColor = 0xFF999999; // Background color.
|
||||
protected int iconBorderColor = 0xFF555555; // Border color.
|
||||
protected int iconBorderThickness = 1; // Border thickness in pixels.
|
||||
|
||||
// Flags for drawing arrow heads and using color scaling on connections.
|
||||
protected boolean showArrowHeads = true;
|
||||
protected boolean useColorScaling = true;
|
||||
// ---------------
|
||||
// Connection Options
|
||||
// ---------------
|
||||
protected boolean showArrowHeads = true; // Whether to draw arrow heads.
|
||||
protected boolean useColorScaling = true; // Whether to scale connection color based on a percentage.
|
||||
|
||||
// ---------------
|
||||
// Layout Options
|
||||
// ---------------
|
||||
public enum DiagramLayout { CIRCULAR, SQUARE, TREE, GENERATED }
|
||||
protected DiagramLayout layout = DiagramLayout.CIRCULAR; // Default layout.
|
||||
|
||||
/**
|
||||
* Allows setting the desired layout type.
|
||||
* This will invalidate the cached positions.
|
||||
*/
|
||||
public void setLayout(DiagramLayout layout) {
|
||||
this.layout = layout;
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
// ---------------
|
||||
// Caching
|
||||
// ---------------
|
||||
// Cache for positions (computed once for any given icon set & layout).
|
||||
protected Map<Integer, Point> cachedPositions = null;
|
||||
|
||||
/**
|
||||
* Clears the cached positions.
|
||||
*/
|
||||
public void invalidateCache() {
|
||||
cachedPositions = null;
|
||||
}
|
||||
|
||||
// ---------------
|
||||
// Constructor
|
||||
// ---------------
|
||||
public GuiNPCDiagram(int x, int y, int width, int height) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
@@ -64,58 +98,21 @@ public abstract class GuiNPCDiagram extends Gui {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
// -------------
|
||||
// Data Providers
|
||||
// -------------
|
||||
/**
|
||||
* Must return the list of icons (nodes) to be drawn.
|
||||
*/
|
||||
// ---------------
|
||||
// Data Providers (abstract methods to be implemented by child classes)
|
||||
// ---------------
|
||||
protected abstract List<DiagramIcon> getIcons();
|
||||
|
||||
/**
|
||||
* Must return the list of connections (arrows) between nodes.
|
||||
*/
|
||||
protected abstract List<DiagramConnection> getConnections();
|
||||
|
||||
/**
|
||||
* Subclass must implement rendering of the icon image itself (without drawing the background box).
|
||||
*
|
||||
* @param icon The icon data.
|
||||
* @param posX The center X position where the icon is drawn.
|
||||
* @param posY The center Y position.
|
||||
* @param highlighted Whether this node is currently highlighted.
|
||||
*/
|
||||
protected abstract void renderIcon(DiagramIcon icon, int posX, int posY, boolean highlighted);
|
||||
|
||||
/**
|
||||
* Optionally supply tooltip text for an icon.
|
||||
*/
|
||||
protected List<String> getIconTooltip(DiagramIcon icon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default tooltip for a connection is based on its hoverText.
|
||||
* <p>
|
||||
* Also, if a reciprocal connection exists (i.e. A→B and B→A) then the tooltip will include two lines.
|
||||
* For example:
|
||||
* <pre>
|
||||
* A --> Hover Text
|
||||
* B --> Hover Text
|
||||
* </pre>
|
||||
* To display the names of the endpoints, getIconName() is used.
|
||||
*/
|
||||
protected List<String> getIconTooltip(DiagramIcon icon) { return null; }
|
||||
protected List<String> getConnectionTooltip(DiagramConnection conn) {
|
||||
List<String> tooltip = new ArrayList<>();
|
||||
// Get the display names for the endpoints.
|
||||
DiagramIcon iconFrom = getIconById(conn.idFrom);
|
||||
DiagramIcon iconTo = getIconById(conn.idTo);
|
||||
String nameFrom = iconFrom != null ? getIconName(iconFrom) : "Unknown";
|
||||
String nameTo = iconTo != null ? getIconName(iconTo) : "Unknown";
|
||||
// Add tooltip for the current connection.
|
||||
tooltip.add(nameFrom + " > " + nameTo + ":");
|
||||
tooltip.add(conn.hoverText);
|
||||
// Check if there is a reciprocal connection (B→A).
|
||||
DiagramConnection reverse = getConnectionByIds(conn.idTo, conn.idFrom);
|
||||
if (reverse != null) {
|
||||
tooltip.add(nameTo + " > " + nameFrom + ":");
|
||||
@@ -123,127 +120,237 @@ public abstract class GuiNPCDiagram extends Gui {
|
||||
}
|
||||
return tooltip;
|
||||
}
|
||||
protected String getIconName(DiagramIcon icon) { return "Icon " + icon.id; }
|
||||
|
||||
// ---------------
|
||||
// Layout Calculation – choose algorithm based on layout type.
|
||||
// ---------------
|
||||
/**
|
||||
* Returns a display name for the given icon.
|
||||
* Child classes may override this method to provide a more meaningful name.
|
||||
* By default, it returns "Icon <id>".
|
||||
*/
|
||||
protected String getIconName(DiagramIcon icon) {
|
||||
return "Icon " + icon.id;
|
||||
}
|
||||
|
||||
// -------------
|
||||
// Layout Calculation
|
||||
// -------------
|
||||
/**
|
||||
* Arranges icons in a circle centered in the diagram area.
|
||||
* Returns a mapping from an icon’s id to its (x,y) center position.
|
||||
* 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).
|
||||
*/
|
||||
protected Map<Integer, Point> calculatePositions() {
|
||||
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;
|
||||
}
|
||||
return cachedPositions;
|
||||
}
|
||||
|
||||
protected Map<Integer, Point> calculateCircularPositions() {
|
||||
List<DiagramIcon> icons = getIcons();
|
||||
Map<Integer, Point> positions = new HashMap<>();
|
||||
int count = icons.size();
|
||||
// Calculate the center of the diagram area.
|
||||
int centerX = x + width / 2;
|
||||
int centerY = y + height / 2;
|
||||
// Determine the radius of the circle, leaving room for icons.
|
||||
int radius = Math.min(width, height) / 2 - iconSize - 10;
|
||||
for (int i = 0; i < count; i++) {
|
||||
// Angle for this icon in radians.
|
||||
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;
|
||||
}
|
||||
|
||||
// -------------
|
||||
// Standard Icon Box (Background and Border)
|
||||
// -------------
|
||||
protected Map<Integer, Point> calculateSquarePositions() {
|
||||
List<DiagramIcon> icons = getIcons();
|
||||
Map<Integer, Point> 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;
|
||||
for (int i = 0; i < count; i++) {
|
||||
int row = i / n;
|
||||
int 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));
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
protected Map<Integer, Point> calculateTreePositions() {
|
||||
List<DiagramIcon> icons = getIcons();
|
||||
Map<Integer, Point> positions = new HashMap<>();
|
||||
int count = icons.size();
|
||||
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 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 posY = y + (level + 1) * levelHeight;
|
||||
positions.put(icons.get(index).id, new Point(posX, posY));
|
||||
}
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the background box (with optional highlighting) and border for an icon.
|
||||
* The box is drawn at (slotX, slotY) with the given slotSize.
|
||||
*
|
||||
* @param slotX The top-left x-coordinate of the box.
|
||||
* @param slotY The top-left y-coordinate of the box.
|
||||
* @param slotSize The width/height of the box.
|
||||
* @param highlighted Whether the box should appear highlighted.
|
||||
* Generated layout: uses a force-directed algorithm to position nodes.
|
||||
* Computed only once using a fixed seed for determinism.
|
||||
*/
|
||||
protected Map<Integer, Point> calculateGeneratedPositions() {
|
||||
List<DiagramIcon> icons = getIcons();
|
||||
Map<Integer, Point> positions = new HashMap<>();
|
||||
int count = icons.size();
|
||||
int margin = 20; // margin inside diagram area
|
||||
|
||||
// Fixed seed for deterministic initial placement.
|
||||
Random rand = new Random(100);
|
||||
// Initialize nodes at random positions (with margin).
|
||||
for (DiagramIcon icon : icons) {
|
||||
int posX = x + margin + rand.nextInt(Math.max(1, width - 2 * margin));
|
||||
int posY = y + margin + rand.nextInt(Math.max(1, height - 2 * margin));
|
||||
positions.put(icon.id, new Point(posX, posY));
|
||||
}
|
||||
|
||||
// Fruchterman–Reingold parameters.
|
||||
double area = width * height;
|
||||
// Ideal distance between nodes.
|
||||
double k = Math.sqrt(area / (double) count);
|
||||
int iterations = 100;
|
||||
double temperature = width / 10.0; // initial max displacement
|
||||
double cooling = temperature / (iterations + 1);
|
||||
double minDistance = iconSize + slotPadding; // enforce minimal separation
|
||||
|
||||
// Get the list of connections (only actual edges).
|
||||
List<DiagramConnection> connections = getConnections();
|
||||
|
||||
for (int iter = 0; iter < iterations; iter++) {
|
||||
// Initialize displacement for each node.
|
||||
Map<Integer, double[]> disp = new HashMap<>();
|
||||
for (DiagramIcon v : icons) {
|
||||
disp.put(v.id, new double[] {0.0, 0.0});
|
||||
}
|
||||
|
||||
// Repulsive forces: for every pair of nodes.
|
||||
for (int i = 0; i < icons.size(); i++) {
|
||||
DiagramIcon v = icons.get(i);
|
||||
Point posV = positions.get(v.id);
|
||||
for (int j = i + 1; j < icons.size(); j++) {
|
||||
DiagramIcon u = icons.get(j);
|
||||
Point posU = positions.get(u.id);
|
||||
double dx = posV.x - posU.x;
|
||||
double dy = posV.y - posU.y;
|
||||
double distance = Math.sqrt(dx * dx + dy * dy);
|
||||
if (distance < minDistance) distance = minDistance;
|
||||
double repForce = (k * k) / distance;
|
||||
double[] dispV = disp.get(v.id);
|
||||
double[] dispU = disp.get(u.id);
|
||||
dispV[0] += (dx / distance) * repForce;
|
||||
dispV[1] += (dy / distance) * repForce;
|
||||
dispU[0] -= (dx / distance) * repForce;
|
||||
dispU[1] -= (dy / distance) * repForce;
|
||||
}
|
||||
}
|
||||
|
||||
// Attractive forces: only for nodes that are connected.
|
||||
for (DiagramConnection conn : connections) {
|
||||
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 distance = Math.sqrt(dx * dx + dy * dy);
|
||||
if (distance < 0.01) distance = 0.01;
|
||||
double attrForce = (distance * distance) / k;
|
||||
double[] dispV = disp.get(conn.idFrom);
|
||||
double[] dispU = disp.get(conn.idTo);
|
||||
dispV[0] -= (dx / distance) * attrForce;
|
||||
dispV[1] -= (dy / distance) * attrForce;
|
||||
dispU[0] += (dx / distance) * attrForce;
|
||||
dispU[1] += (dy / distance) * attrForce;
|
||||
}
|
||||
|
||||
// Update positions based on displacement vectors.
|
||||
for (DiagramIcon v : icons) {
|
||||
double[] d = disp.get(v.id);
|
||||
double dispLength = Math.sqrt(d[0] * d[0] + d[1] * d[1]);
|
||||
if (dispLength < 0.01) dispLength = 0.01;
|
||||
int newX = positions.get(v.id).x + (int)((d[0] / dispLength) * Math.min(dispLength, temperature));
|
||||
int newY = positions.get(v.id).y + (int)((d[1] / dispLength) * Math.min(dispLength, temperature));
|
||||
newX = Math.max(x + margin, Math.min(x + width - margin, newX));
|
||||
newY = Math.max(y + margin, Math.min(y + height - margin, newY));
|
||||
positions.get(v.id).x = newX;
|
||||
positions.get(v.id).y = newY;
|
||||
}
|
||||
temperature -= cooling;
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
// ---------------
|
||||
// Standard Icon Box Drawing (Background + Border)
|
||||
// ---------------
|
||||
protected void drawIconBox(int slotX, int slotY, int slotSize, boolean highlighted) {
|
||||
// If highlighted, mix the background color with white a little.
|
||||
int bg = highlighted ? mixColors(iconBackgroundColor, 0xFFFFFFFF, 0.2f) : iconBackgroundColor;
|
||||
// Draw the box background.
|
||||
drawRect(slotX, slotY, slotX + slotSize, slotY + slotSize, bg);
|
||||
// Draw the border lines inside the box.
|
||||
for (int i = 0; i < iconBorderThickness; i++) {
|
||||
// Draw horizontal top and bottom borders.
|
||||
drawHorizontalLine(slotX + i, slotX + slotSize - i, slotY + i, iconBorderColor);
|
||||
drawHorizontalLine(slotX + i, slotX + slotSize - i, slotY + slotSize - 1 - i, iconBorderColor);
|
||||
// Draw vertical left and right borders.
|
||||
drawVerticalLine(slotX + i, slotY + i, slotY + slotSize - i, iconBorderColor);
|
||||
drawVerticalLine(slotX + slotSize - 1 - i, slotY + i, slotY + slotSize - i, iconBorderColor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to mix two ARGB colors by a given ratio.
|
||||
* If ratio is 0, returns color1; if 1, returns color2.
|
||||
*/
|
||||
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;
|
||||
int a = (int) (a1 + (a2 - a1) * ratio);
|
||||
int r = (int) (r1 + (r2 - r1) * ratio);
|
||||
int g = (int) (g1 + (g2 - g1) * ratio);
|
||||
int b = (int) (b1 + (b2 - b1) * ratio);
|
||||
int a = (int)(a1 + (a2 - a1) * ratio);
|
||||
int r = (int)(r1 + (r2 - r1) * ratio);
|
||||
int g = (int)(g1 + (g2 - g1) * ratio);
|
||||
int b = (int)(b1 + (b2 - b1) * ratio);
|
||||
return (a << 24) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
// -------------
|
||||
// ---------------
|
||||
// Main Draw Method
|
||||
// -------------
|
||||
/**
|
||||
* Draws the entire diagram: nodes (with their standard boxes) and connections (with arrow heads).
|
||||
* Also performs hit testing to determine which icon or connection is being hovered,
|
||||
* so that appropriate highlighting and tooltips can be shown.
|
||||
*
|
||||
* @param mouseX The current mouse x-coordinate.
|
||||
* @param mouseY The current mouse y-coordinate.
|
||||
*/
|
||||
// ---------------
|
||||
public void drawDiagram(int mouseX, int mouseY) {
|
||||
// Handle zoom changes from the scroll wheel.
|
||||
handleMouseScroll(Mouse.getDWheel());
|
||||
|
||||
Minecraft mc = Minecraft.getMinecraft();
|
||||
ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
int factor = sr.getScaleFactor();
|
||||
|
||||
// Calculate positions for all icons.
|
||||
Map<Integer, Point> positions = calculatePositions();
|
||||
// Determine the center of the diagram.
|
||||
int centerX = x + width / 2, centerY = y + height / 2;
|
||||
// Transform mouse coordinates to account for zoom (for hit testing).
|
||||
int tMouseX = centerX + (int) ((mouseX - centerX) / zoom);
|
||||
int tMouseY = centerY + (int) ((mouseY - centerY) / zoom);
|
||||
int tMouseX = centerX + (int)((mouseX - centerX) / zoom);
|
||||
int tMouseY = centerY + (int)((mouseY - centerY) / zoom);
|
||||
|
||||
// -------------
|
||||
// Hit Testing & Selection
|
||||
// -------------
|
||||
Integer hoveredIconId = null;
|
||||
int hoveredConnFrom = -1, hoveredConnTo = -1;
|
||||
// This set will contain IDs of icons that should be drawn highlighted.
|
||||
HashSet<Integer> selectedIconIds = new HashSet<>();
|
||||
|
||||
// Check if the mouse is over any icon.
|
||||
for (DiagramIcon icon : getIcons()) {
|
||||
Point pos = positions.get(icon.id);
|
||||
if (pos == null) continue;
|
||||
// Calculate the top-left corner of the icon box.
|
||||
int slotX = pos.x + (int) panX - slotSize / 2;
|
||||
int slotY = pos.y + (int) panY - slotSize / 2;
|
||||
// If the transformed mouse is within the box...
|
||||
int slotX = pos.x + (int)panX - slotSize / 2;
|
||||
int slotY = pos.y + (int)panY - slotSize / 2;
|
||||
if (tMouseX >= slotX && tMouseX < slotX + slotSize &&
|
||||
tMouseY >= slotY && tMouseY < slotY + slotSize) {
|
||||
hoveredIconId = icon.id;
|
||||
@@ -251,7 +358,6 @@ public abstract class GuiNPCDiagram extends Gui {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If an icon is hovered, also mark all icons connected to it.
|
||||
if (hoveredIconId != null) {
|
||||
for (DiagramConnection conn : getConnections()) {
|
||||
if (conn.idFrom == hoveredIconId || conn.idTo == hoveredIconId) {
|
||||
@@ -260,7 +366,6 @@ public abstract class GuiNPCDiagram extends Gui {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If no icon is hovered, check if the mouse is near any connection line.
|
||||
final double threshold = 5.0;
|
||||
outer:
|
||||
for (DiagramConnection conn : getConnections()) {
|
||||
@@ -279,60 +384,39 @@ public abstract class GuiNPCDiagram extends Gui {
|
||||
}
|
||||
}
|
||||
|
||||
// -------------
|
||||
// Clipping & Background
|
||||
// -------------
|
||||
GL11.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
// Set the scissor rectangle to the diagram area.
|
||||
GL11.glScissor(x * factor, (sr.getScaledHeight() - (y + height)) * factor, width * factor, height * factor);
|
||||
// Draw the overall diagram background.
|
||||
drawRect(x, y, x + width, y + height, 0xFF333333);
|
||||
|
||||
// -------------
|
||||
// Zoom Transformation
|
||||
// -------------
|
||||
GL11.glPushMatrix();
|
||||
GL11.glTranslatef(centerX, centerY, 0);
|
||||
GL11.glScalef(zoom, zoom, 1.0f);
|
||||
GL11.glTranslatef(-centerX, -centerY, 0);
|
||||
|
||||
// -------------
|
||||
// Draw Connections (Arrows)
|
||||
// -------------
|
||||
for (DiagramConnection conn : getConnections()) {
|
||||
Point pFrom = positions.get(conn.idFrom);
|
||||
Point pTo = positions.get(conn.idTo);
|
||||
if (pFrom == null || pTo == null) continue;
|
||||
int ax = pFrom.x + (int) panX;
|
||||
int ay = pFrom.y + (int) panY;
|
||||
int bx = pTo.x + (int) panX;
|
||||
int by = pTo.y + (int) panY;
|
||||
// Dim the connection if both endpoints are not selected.
|
||||
int ax = pFrom.x + (int)panX;
|
||||
int ay = pFrom.y + (int)panY;
|
||||
int bx = pTo.x + (int)panX;
|
||||
int by = pTo.y + (int)panY;
|
||||
boolean dim = !selectedIconIds.isEmpty() &&
|
||||
!(selectedIconIds.contains(conn.idFrom) && selectedIconIds.contains(conn.idTo));
|
||||
drawConnectionLine(ax, ay, bx, by, conn, dim);
|
||||
}
|
||||
|
||||
// -------------
|
||||
// Draw Icons (Nodes)
|
||||
// -------------
|
||||
for (DiagramIcon icon : getIcons()) {
|
||||
Point pos = positions.get(icon.id);
|
||||
if (pos == null) continue;
|
||||
int slotX = pos.x + (int) panX - slotSize / 2;
|
||||
int slotY = pos.y + (int) panY - slotSize / 2;
|
||||
// If this icon's id is in the selected set, consider it highlighted.
|
||||
int slotX = pos.x + (int)panX - slotSize / 2;
|
||||
int slotY = pos.y + (int)panY - slotSize / 2;
|
||||
boolean highlighted = !selectedIconIds.isEmpty() && selectedIconIds.contains(icon.id);
|
||||
// First draw the standardized icon box (background and border).
|
||||
drawIconBox(slotX, slotY, slotSize, highlighted);
|
||||
// Then let the subclass render the icon image.
|
||||
renderIcon(icon, pos.x + (int) panX, pos.y + (int) panY, highlighted);
|
||||
renderIcon(icon, pos.x + (int)panX, pos.y + (int)panY, highlighted);
|
||||
}
|
||||
GL11.glPopMatrix();
|
||||
|
||||
// -------------
|
||||
// Draw Arrow Heads (if enabled)
|
||||
// -------------
|
||||
if (showArrowHeads) {
|
||||
GL11.glPushMatrix();
|
||||
GL11.glTranslatef(centerX, centerY, 0);
|
||||
@@ -342,22 +426,18 @@ public abstract class GuiNPCDiagram 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) panX;
|
||||
int ay = pFrom.y + (int) panY;
|
||||
int bx = pTo.x + (int) panX;
|
||||
int by = pTo.y + (int) panY;
|
||||
int ax = pFrom.x + (int)panX;
|
||||
int ay = pFrom.y + (int)panY;
|
||||
int bx = pTo.x + (int)panX;
|
||||
int by = pTo.y + (int)panY;
|
||||
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);
|
||||
|
||||
// -------------
|
||||
// Draw Tooltips
|
||||
// -------------
|
||||
if (hoveredIconId != null) {
|
||||
DiagramIcon icon = getIconById(hoveredIconId);
|
||||
List<String> tooltip = getIconTooltip(icon);
|
||||
@@ -373,12 +453,6 @@ public abstract class GuiNPCDiagram extends Gui {
|
||||
}
|
||||
}
|
||||
|
||||
// -------------
|
||||
// Helper Methods for Hit Testing & Drawing
|
||||
// -------------
|
||||
/**
|
||||
* Returns the DiagramIcon with the given id, or null if not found.
|
||||
*/
|
||||
protected DiagramIcon getIconById(int id) {
|
||||
for (DiagramIcon icon : getIcons()) {
|
||||
if (icon.id == id)
|
||||
@@ -387,9 +461,6 @@ public abstract class GuiNPCDiagram extends Gui {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DiagramConnection connecting idFrom to idTo, or null if not found.
|
||||
*/
|
||||
protected DiagramConnection getConnectionByIds(int idFrom, int idTo) {
|
||||
for (DiagramConnection conn : getConnections()) {
|
||||
if (conn.idFrom == idFrom && conn.idTo == idTo)
|
||||
@@ -398,32 +469,16 @@ public abstract class GuiNPCDiagram extends Gui {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a connection line between two points.
|
||||
*
|
||||
* @param x1 Source x-coordinate.
|
||||
* @param y1 Source y-coordinate.
|
||||
* @param x2 Target x-coordinate.
|
||||
* @param y2 Target y-coordinate.
|
||||
* @param conn The connection data.
|
||||
* @param dim Whether to draw a dimmed (less bright) version.
|
||||
*/
|
||||
protected void drawConnectionLine(int x1, int y1, int x2, int y2, DiagramConnection conn, boolean dim) {
|
||||
int color = getConnectionColor(conn);
|
||||
if (!useColorScaling) {
|
||||
color = 0xFFFFFFFF;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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);
|
||||
@@ -433,49 +488,27 @@ public abstract class GuiNPCDiagram extends Gui {
|
||||
GL11.glPopAttrib();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws an arrow head at the target end of a connection.
|
||||
*
|
||||
* @param x1 Source x-coordinate.
|
||||
* @param y1 Source y-coordinate.
|
||||
* @param x2 Target x-coordinate.
|
||||
* @param y2 Target y-coordinate.
|
||||
* @param conn The connection data.
|
||||
* @param dim Whether to draw a dimmed version.
|
||||
*/
|
||||
protected void drawArrowHead(int x1, int y1, int x2, int y2, DiagramConnection conn, boolean dim) {
|
||||
float dx = x2 - x1;
|
||||
float dy = y2 - y1;
|
||||
float len = (float) Math.sqrt(dx * dx + dy * dy);
|
||||
if (len == 0) return;
|
||||
// Compute the unit vector from source to target.
|
||||
float ux = dx / len, uy = dy / len;
|
||||
// Offset from target center by half the slot size to get the edge of the icon box.
|
||||
float defenderEdgeX = x2 - ux * (slotSize / 2f);
|
||||
float defenderEdgeY = y2 - uy * (slotSize / 2f);
|
||||
|
||||
int arrowSize = 6; // Adjustable arrow size.
|
||||
double angle = Math.atan2(uy, ux);
|
||||
// Calculate the left and right vertices of the arrow head triangle.
|
||||
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 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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);
|
||||
@@ -488,55 +521,41 @@ public abstract class GuiNPCDiagram extends Gui {
|
||||
GL11.glPopAttrib();
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a color for a connection based on its percentage value.
|
||||
* Uses different thresholds to return a base color, and interpolates for higher percentages.
|
||||
*/
|
||||
protected int getConnectionColor(DiagramConnection conn) {
|
||||
float percent = conn.percent;
|
||||
if (percent <= 0.10f) return 0xFF80FF80; // light green
|
||||
else if (percent <= 0.40f) return 0xFFCCFF66; // green-yellow
|
||||
else if (percent <= 0.50f) return 0xFFFFA500; // orange
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method: returns the shortest distance from a point (px,py) to the line segment defined by (x1,y1)-(x2,y2).
|
||||
*/
|
||||
private double pointLineDistance(double px, double py, double x1, double y1, double x2, double 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) {
|
||||
if (mouseButton == 0 && isWithin(mouseX, mouseY)) {
|
||||
if (mouseButton==0 && isWithin(mouseX, mouseY)) {
|
||||
dragging = true;
|
||||
lastDragX = mouseX;
|
||||
lastDragY = mouseY;
|
||||
@@ -546,12 +565,11 @@ public abstract class GuiNPCDiagram extends Gui {
|
||||
}
|
||||
|
||||
public void mouseClickMove(int mouseX, int mouseY, int mouseButton, long timeSinceLastClick) {
|
||||
if (dragging) {
|
||||
int dx = mouseX - lastDragX;
|
||||
int dy = mouseY - lastDragY;
|
||||
// Adjust panning speed relative to zoom.
|
||||
panX += dx / zoom * 0.7f;
|
||||
panY += dy / zoom * 0.7f;
|
||||
if(dragging){
|
||||
int dx = mouseX-lastDragX;
|
||||
int dy = mouseY-lastDragY;
|
||||
panX += dx/zoom*0.7f;
|
||||
panY += dy/zoom*0.7f;
|
||||
lastDragX = mouseX;
|
||||
lastDragY = mouseY;
|
||||
}
|
||||
@@ -562,29 +580,28 @@ public abstract class GuiNPCDiagram extends Gui {
|
||||
}
|
||||
|
||||
protected boolean isWithin(int mouseX, int mouseY) {
|
||||
return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height;
|
||||
return mouseX >= x && mouseX <= x+width && mouseY >= y && mouseY <= y+height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles mouse wheel scrolling for zooming in/out.
|
||||
* Handles zoom in/out 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 want to render tooltips differently.
|
||||
* By default, it does nothing.
|
||||
* Override this method if you wish to render tooltips differently.
|
||||
*/
|
||||
protected void drawHoveringText(List<String> text, int mouseX, int mouseY, FontRenderer fontRenderer) {
|
||||
// Default implementation is empty.
|
||||
}
|
||||
|
||||
// -------------
|
||||
// ---------------
|
||||
// Flag Setters
|
||||
// -------------
|
||||
// ---------------
|
||||
public void setShowArrowHeads(boolean showArrowHeads) {
|
||||
this.showArrowHeads = showArrowHeads;
|
||||
}
|
||||
@@ -593,24 +610,14 @@ public abstract class GuiNPCDiagram extends Gui {
|
||||
this.useColorScaling = useColorScaling;
|
||||
}
|
||||
|
||||
// -------------
|
||||
// ---------------
|
||||
// Inner Classes for Data Wrapping
|
||||
// -------------
|
||||
/**
|
||||
* A simple wrapper class for a node/icon.
|
||||
*/
|
||||
// ---------------
|
||||
public static class DiagramIcon {
|
||||
public int id;
|
||||
public DiagramIcon(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
public DiagramIcon(int id) { this.id = id; }
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple wrapper class for a connection (arrow) between nodes.
|
||||
* Contains the source node id (idFrom), target node id (idTo), a percentage value (for color scaling),
|
||||
* and a hover text.
|
||||
*/
|
||||
public static class DiagramConnection {
|
||||
public int idFrom, idTo;
|
||||
public float percent;
|
||||
|
||||
Reference in new issue
Block a user