diff --git a/src/api b/src/api index c22c5f9d7..c7a74a3e0 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit c22c5f9d7926c007009fcf434dbb4075a022e923 +Subproject commit c7a74a3e0e4b979c880383737cd7d21d4b36d131 diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiDiagram.java b/src/main/java/noppes/npcs/client/gui/util/GuiDiagram.java index e4ae9a805..f4c585526 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiDiagram.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiDiagram.java @@ -4,75 +4,58 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.Gui; import net.minecraft.client.gui.ScaledResolution; -import net.minecraft.client.renderer.RenderHelper; import net.minecraft.client.renderer.entity.RenderItem; import noppes.npcs.constants.EnumDiagramLayout; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GL12; import java.awt.*; -import java.util.List; -import java.util.Queue; import java.util.*; +import java.util.List; /** * Abstract GUI class for drawing diagrams with icons (nodes) and connections (arrows). * - * Features include: - * + * This revised version ensures that non‑Chart layouts use the full diagram bounds for positioning + * (so that the outer icons lie flush with the edges) and that curved arrow drawing selects the + * best control point (using both curveAngle and its negative) to avoid icons. */ public abstract class GuiDiagram extends Gui { protected RenderItem renderItem = new RenderItem(); // --- Diagram Area & Controls --- - protected int x, y, width, height; // Drawing area for the diagram - protected float panX = 0, panY = 0; // Panning offsets - protected float zoom = 1.0f; // Zoom factor - protected boolean dragging = false; // Flag indicating active panning - protected int lastDragX, lastDragY; // Last mouse positions used for panning + 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; // Padding around icons - protected int slotSize = iconSize + slotPadding; // Total size of the icon box + protected int iconSize = 16; + protected int slotPadding = 4; + protected int slotSize = iconSize + slotPadding; // --- Icon Box Appearance --- - protected int iconBackgroundColor = 0xFF999999; // Background color for icon boxes - protected int iconBorderColor = 0xFF555555; // Border color for icon boxes - protected int iconBorderThickness = 1; // Border thickness in pixels + 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; // Scale connection color based on a percentage + protected boolean showArrowHeads = true; + protected boolean useColorScaling = true; // --- Layout Options --- - protected EnumDiagramLayout layout = EnumDiagramLayout.CIRCULAR; // Default layout + protected EnumDiagramLayout layout = EnumDiagramLayout.CIRCULAR; - /** - * Sets the layout type and invalidates caches. - */ public void setLayout(EnumDiagramLayout layout) { this.layout = layout; invalidateCache(); } // --- Arrow Drawing Options --- - protected boolean curvedArrows = true; // If false, draw straight connection lines - protected int curveAngle = 15; // Curvature angle in degrees + protected boolean curvedArrows = true; + protected int curveAngle = 15; // in degrees public void setCurvedArrows(boolean curved) { this.curvedArrows = curved; @@ -82,13 +65,10 @@ public abstract class GuiDiagram extends Gui { } // --- Caching for icons and connections --- - protected Map cachedPositions = null; // Cached positions for nodes (non-CHART) - protected List iconsCache = null; // Cached list of icons - protected List connectionsCache = null; // Cached list of connections + protected Map cachedPositions = null; + protected List iconsCache = null; + protected List connectionsCache = null; - /** - * Clears cached layout information. - */ public void invalidateCache() { cachedPositions = null; iconsCache = null; @@ -98,7 +78,6 @@ public abstract class GuiDiagram extends Gui { // --- Parent reference --- protected GuiNPCInterface parent; - // --- Constructor --- public GuiDiagram(GuiNPCInterface parent, int x, int y, int width, int height) { this.parent = parent; this.x = x; @@ -107,7 +86,7 @@ public abstract class GuiDiagram extends Gui { this.height = height; } - // --- Data Providers (Subclasses must implement these) --- + // --- Data Providers (subclasses must implement) --- protected abstract List createIcons(); protected abstract List createConnections(); @@ -129,14 +108,8 @@ public abstract class GuiDiagram extends Gui { */ protected abstract void renderIcon(DiagramIcon icon, int posX, int posY, IconRenderState state); - /** - * Returns the tooltip text lines for an icon. - */ protected List getIconTooltip(DiagramIcon icon) { return null; } - /** - * Returns the tooltip text lines for a connection. - */ protected List getConnectionTooltip(DiagramConnection conn) { List tooltip = new ArrayList<>(); DiagramIcon iconFrom = getIconById(conn.idFrom); @@ -156,8 +129,7 @@ public abstract class GuiDiagram extends Gui { // --- Layout Calculation --- /** - * For non‑CHART layouts, returns the mapping from node ID to (x,y) position. - * CHART layout is drawn directly. + * For non‑CHART layouts, returns a mapping from node ID to (x,y) position. */ protected Map calculatePositions() { if (cachedPositions != null) return cachedPositions; @@ -165,29 +137,44 @@ public abstract class GuiDiagram extends Gui { return new HashMap<>(); } switch (layout) { - case CIRCULAR: cachedPositions = calculateCircularPositions(); break; - case SQUARE: cachedPositions = calculateSquarePositions(); break; - case TREE: cachedPositions = calculateTreePositions(); break; - case GENERATED: cachedPositions = calculateGeneratedPositions(); break; - case CIRCULAR_MANUAL: cachedPositions = calculateCircularManualPositions(); break; - case SQUARE_MANUAL: cachedPositions = calculateSquareManualPositions(); break; - case TREE_MANUAL: cachedPositions = calculateTreeManualPositions(); break; - default: cachedPositions = calculateCircularPositions(); break; + case CIRCULAR: + cachedPositions = calculateCircularPositions(); + break; + case SQUARE: + cachedPositions = calculateSquarePositions(); + break; + case TREE: + cachedPositions = calculateTreePositions(); + break; + case GENERATED: + cachedPositions = calculateGeneratedPositions(); + break; + case CIRCULAR_MANUAL: + cachedPositions = calculateCircularManualPositions(); + break; + case SQUARE_MANUAL: + cachedPositions = calculateSquareManualPositions(); + break; + case TREE_MANUAL: + cachedPositions = calculateTreeManualPositions(); + break; + default: + cachedPositions = calculateCircularPositions(); + break; } return cachedPositions; } // --- Standard Layout Methods --- + // Circular: use the full available radius so that outer icons are flush with bounds. protected Map calculateCircularPositions() { List icons = getIcons(); Map positions = new HashMap<>(); int count = icons.size(); - if (count == 0) { // avoid division by zero - return positions; - } + if (count == 0) return positions; int centerX = x + width / 2, centerY = y + height / 2; - int radius = Math.min(width, height) / 2 - (int) (0.8 * slotSize); + int radius = (Math.min(width, height) - slotSize) / 2; for (int i = 0; i < count; i++) { double angle = 2 * Math.PI * i / count; int posX = centerX + (int) (radius * Math.cos(angle)); @@ -197,17 +184,17 @@ public abstract class GuiDiagram extends Gui { return positions; } + // Square: fill the entire bounds protected Map calculateSquarePositions() { List icons = getIcons(); Map positions = new HashMap<>(); int count = icons.size(); - if (count == 0) { // early exit if no icons - return positions; - } + if (count == 0) return positions; int n = (int) Math.ceil(Math.sqrt(count)); - int gridWidth = width - 20, gridHeight = height - 20; - int cellWidth = gridWidth / n, cellHeight = gridHeight / n; - int startX = x + (width - gridWidth) / 2, startY = y + (height - gridHeight) / 2; + int cellWidth = width / n; + int cellHeight = height / n; + // Start exactly at the diagram bounds. + int startX = x, startY = y; for (int i = 0; i < count; i++) { int row = i / n, col = i % n; int posX = startX + col * cellWidth + cellWidth / 2; @@ -217,49 +204,57 @@ public abstract class GuiDiagram extends Gui { return positions; } - + // Tree: distribute levels vertically and icons horizontally so that the top and bottom icons touch y and y+height. protected Map calculateTreePositions() { List icons = getIcons(); Map positions = new HashMap<>(); int count = icons.size(); - if(count == 0) { // avoid arithmetic errors when no icons exist - return positions; - } - 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)); + if (count == 0) return positions; + // Determine number of levels by a simple breadth-first approach. + int levels = (int) Math.ceil(Math.log(count + 1) / Math.log(2)); + // If only one level, place the icon in the center. + for (int level = 0; level < levels; level++) { + // Determine number of nodes at this level. + int nodesThisLevel = (level == 0) ? 1 : (int) Math.min(Math.pow(2, level), count - (int) Math.pow(2, level) + 1); + // Calculate vertical position: if more than one level, linearly space from y to y+height. + int posY = (levels == 1) ? y + height / 2 : y + (int) ((height * level) / (levels - 1.0)); + // Horizontal spacing: if only one node, center it; otherwise, nodes are evenly spaced from x to x+width. + for (int i = 0; i < nodesThisLevel; i++) { + int posX; + if (nodesThisLevel == 1) { + posX = x + width / 2; + } else { + posX = x + (int) ((width * i) / (nodesThisLevel - 1.0)); + } + // Here we assign IDs in order. (You may want to use your own ordering.) + int index = (int) (Math.pow(2, level) - 1 + i); + if (index < count) { + positions.put(icons.get(index).id, new Point(posX, posY)); + } } } return positions; } - + // Generated: Break icons into clusters and fill each cluster’s area exactly. protected Map calculateGeneratedPositions() { List icons = getIcons(); List connections = getConnections(); Map> graph = buildGraph(icons, connections); List> clusters = getConnectedComponents(graph); int clusterCount = clusters.size(); - int gridCols = (int)Math.ceil(Math.sqrt(clusterCount)); - int gridRows = (int)Math.ceil((double)clusterCount / gridCols); - int clusterMargin = 20; - int compWidth = (width - (gridCols + 1) * clusterMargin) / gridCols; - int compHeight = (height - (gridRows + 1) * clusterMargin) / gridRows; + // Arrange clusters in a grid that fills the diagram exactly. + int gridCols = (int) Math.ceil(Math.sqrt(clusterCount)); + int gridRows = (int) Math.ceil((double) clusterCount / gridCols); Map globalPositions = new HashMap<>(); int clusterIndex = 0; for (Set cluster : clusters) { int col = clusterIndex % gridCols; int row = clusterIndex / gridCols; - int compX = x + clusterMargin + col * (compWidth + clusterMargin); - int compY = y + clusterMargin + row * (compHeight + clusterMargin); + int compX = x + col * (width / gridCols); + int compY = y + row * (height / gridRows); + int compWidth = width / gridCols; + int compHeight = height / gridRows; Map compPositions; if (isCycle(cluster, graph)) compPositions = layoutCycle(cluster, compX, compY, compWidth, compHeight); @@ -332,18 +327,17 @@ public abstract class GuiDiagram extends Gui { } private boolean isSquare(Set cluster) { int n = cluster.size(); - int sqrt = (int)Math.round(Math.sqrt(n)); + int sqrt = (int) Math.round(Math.sqrt(n)); return sqrt * sqrt == n; } // --- Layout Algorithms for Clusters --- + // Cycle: now use the full available circle. private Map layoutCycle(Set cluster, int compX, int compY, int compWidth, int compHeight) { Map positions = new HashMap<>(); - if (cluster.isEmpty()) { // guard against an empty cluster - return positions; - } + if (cluster.isEmpty()) return positions; int centerX = compX + compWidth / 2, centerY = compY + compHeight / 2; - int radius = Math.min(compWidth, compHeight) / 3; + int radius = (Math.min(compWidth, compHeight) - slotSize) / 2; int i = 0; for (Integer id : cluster) { double angle = 2 * Math.PI * i / cluster.size(); @@ -355,14 +349,13 @@ public abstract class GuiDiagram extends Gui { return positions; } - + // Tree for clusters: use full comp area. private Map layoutTree(Set cluster, int compX, int compY, int compWidth, int compHeight, Map> fullGraph) { Map positions = new HashMap<>(); - if (cluster.isEmpty()) { // guard against empty clusters - return positions; - } + if (cluster.isEmpty()) return positions; + // Build a subgraph Map> subgraph = new HashMap<>(); for (Integer id : cluster) { List neighbors = new ArrayList<>(); @@ -372,10 +365,9 @@ public abstract class GuiDiagram extends Gui { } subgraph.put(id, neighbors); } - Integer root = cluster.iterator().next(); - for (Integer id : cluster) { - if (id < root) root = id; - } + // Pick a root (lowest id) + Integer root = Collections.min(cluster); + // Perform BFS to assign levels. Map levelMap = new HashMap<>(); Map> levels = new HashMap<>(); Queue queue = new LinkedList<>(); @@ -396,33 +388,30 @@ public abstract class GuiDiagram extends Gui { } } int maxLevel = levels.keySet().stream().max(Integer::compare).orElse(0); - double levelHeight = compHeight / (maxLevel + 1.0); + // Vertical positions: if only one level, center; otherwise span from compY to compY+compHeight. for (Map.Entry> entry : levels.entrySet()) { int lvl = entry.getKey(); + int posY = (maxLevel == 0) ? compY + compHeight / 2 : compY + (int) ((compHeight * lvl) / (maxLevel)); List nodesAtLevel = entry.getValue(); - 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) * spacing); - int posY = compY + (int)(lvl * levelHeight + levelHeight / 2); - positions.put(id, new Point(posX, posY)); + nodesAtLevel.sort(Comparator.naturalOrder()); + int m = nodesAtLevel.size(); + for (int i = 0; i < m; i++) { + int posX = (m == 1) ? compX + compWidth / 2 : compX + (int) ((compWidth * i) / (m - 1.0)); + positions.put(nodesAtLevel.get(i), new Point(posX, posY)); } } return positions; } + // Square: fill the available comp area. private Map layoutSquare(Set cluster, int compX, int compY, int compWidth, int compHeight) { Map positions = new HashMap<>(); - if (cluster.isEmpty()) { // guard against empty clusters - return positions; - } - - int n = (int)Math.round(Math.sqrt(cluster.size())); + if (cluster.isEmpty()) return positions; + int n = (int) Math.round(Math.sqrt(cluster.size())); int gridCellWidth = compWidth / n, gridCellHeight = compHeight / n; int index = 0; List nodes = new ArrayList<>(cluster); - nodes.sort(Comparator.comparingInt(a -> getIconById(a).priority)); + nodes.sort(Comparator.naturalOrder()); for (int row = 0; row < n; row++) { for (int col = 0; col < n; col++) { if (index >= nodes.size()) break; @@ -435,12 +424,11 @@ public abstract class GuiDiagram extends Gui { return positions; } + // Force-directed layout (unchanged) private Map layoutForceDirected(Set cluster, int compX, int compY, int compWidth, int compHeight, List connections) { Map positions = new HashMap<>(); - if (cluster.isEmpty()) { // Early exit to avoid division by zero - return positions; - } + if (cluster.isEmpty()) return positions; Random rand = new Random(100); for (Integer id : cluster) { int posX = compX + rand.nextInt(compWidth); @@ -512,28 +500,23 @@ public abstract class GuiDiagram extends Gui { return positions; } - // --- Manual Layouts --- - // Manual layouts use the icon index (e.g. ring number, row, level) and the priority for ordering. private Map calculateCircularManualPositions() { List icons = getIcons(); Map positions = new HashMap<>(); - if (icons.isEmpty()) { - return positions; - } + if (icons.isEmpty()) return positions; Map> groups = new TreeMap<>(); for (DiagramIcon icon : icons) { groups.computeIfAbsent(icon.index, k -> new ArrayList<>()).add(icon); } 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; + int maxRadius = (Math.min(width, height) - iconSize) / 2; double ringSpacing = (maxRing + 1) > 0 ? (double) maxRadius / (maxRing + 1) : 0; for (Map.Entry> entry : groups.entrySet()) { int ring = entry.getKey(); List groupIcons = entry.getValue(); - if (groupIcons.isEmpty()) - continue; + if (groupIcons.isEmpty()) continue; groupIcons.sort(Comparator.comparingInt(icon -> icon.priority)); double radius = ringSpacing * (ring + 1); int count = groupIcons.size(); @@ -548,26 +531,22 @@ public abstract class GuiDiagram extends Gui { return positions; } - private Map calculateSquareManualPositions() { List icons = getIcons(); Map positions = new HashMap<>(); - if (icons.isEmpty()) { - return positions; - } + if (icons.isEmpty()) return positions; Map> groups = new TreeMap<>(); for (DiagramIcon icon : icons) { groups.computeIfAbsent(icon.index, k -> new ArrayList<>()).add(icon); } 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; + int halfSide = (Math.min(width, height) - iconSize) / 2; double ringSpacing = (maxRing + 1) > 0 ? (double) halfSide / (maxRing + 1) : 0; for (Map.Entry> entry : groups.entrySet()) { int ring = entry.getKey(); List groupIcons = entry.getValue(); - if (groupIcons.isEmpty()) - continue; + if (groupIcons.isEmpty()) continue; groupIcons.sort(Comparator.comparingInt(icon -> icon.priority)); double offset = ringSpacing * (ring + 1); int count = groupIcons.size(); @@ -594,7 +573,6 @@ public abstract class GuiDiagram extends Gui { return positions; } - private Map calculateTreeManualPositions() { List icons = getIcons(); Map> levels = new TreeMap<>(); @@ -602,35 +580,58 @@ public abstract class GuiDiagram extends Gui { levels.computeIfAbsent(icon.index, k -> new ArrayList<>()).add(icon); } Map positions = new HashMap<>(); - if (icons.isEmpty()) { // prevent division errors - return positions; - } + if (icons.isEmpty()) return positions; int maxLevel = levels.isEmpty() ? 0 : Collections.max(levels.keySet()); - int levelHeight = height / (maxLevel + 2); + // If more than one level, span vertically from y to y+height. 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(); - if(count == 0) { - continue; - } - 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); + int m = levelIcons.size(); + int posY = (m > 0 && maxLevel > 0) ? y + (int)((height * level) / (maxLevel)) : y + height / 2; + for (int i = 0; i < m; i++) { + int posX = (m > 1) ? x + (int)((width * i) / (m - 1.0)) : x + width / 2; positions.put(levelIcons.get(i).id, new Point(posX, posY)); } } return positions; } + // --- Curved Arrow Helpers --- + /** + * Computes a quadratic Bezier control point given endpoints and an angle (in degrees). + */ + private Point computeControlPoint(int x1, int y1, int x2, int y2, int angleDegrees) { + double dx = x2 - x1, dy = y2 - y1; + double len = Math.sqrt(dx * dx + dy * dy); + if (len == 0) len = 1; + double angleRad = Math.toRadians(angleDegrees); + double offset = len * Math.tan(angleRad) / 2; + double perpX = -dy / len, perpY = dx / len; + return new Point((x1 + x2) / 2 + (int)(perpX * offset), + (y1 + y2) / 2 + (int)(perpY * offset)); + } + + /** + * Returns the minimum distance from the given control point to any icon (except the endpoints of the connection). + */ + private double getMinDistanceToIcon(Point control, DiagramConnection conn) { + double minDist = Double.MAX_VALUE; + Map positions = calculatePositions(); + for (DiagramIcon icon : getIcons()) { + if (icon.id == conn.idFrom || icon.id == conn.idTo) continue; + Point pos = positions.get(icon.id); + if (pos != null) { + double dist = pos.distance(control); + if (dist < minDist) { + minDist = dist; + } + } + } + return minDist; + } // --- Arrow Drawing Methods --- - /** - * Draws a connection line between two points. - * If curvedArrows is enabled, draws a quadratic Bezier curve. - */ protected void drawConnectionLine(int x1, int y1, int x2, int y2, DiagramConnection conn, boolean dim) { int color = getConnectionColor(conn); if (!useColorScaling) color = 0xFFFFFFFF; @@ -650,18 +651,19 @@ public abstract class GuiDiagram extends Gui { 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 = 100; - GL11.glPushAttrib(GL11.GL_ENABLE_BIT); + // Compute two potential control points and choose the one farther from nearby icons. + Point cp1 = computeControlPoint(x1, y1, x2, y2, curveAngle); + Point cp2 = computeControlPoint(x1, y1, x2, y2, -curveAngle); + double d1 = getMinDistanceToIcon(cp1, conn); + double d2 = getMinDistanceToIcon(cp2, conn); + Point control = (d1 >= d2) ? cp1 : cp2; + int segments = 400; + + GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT | GL11.GL_HINT_BIT); + GL11.glEnable(GL11.GL_BLEND); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + GL11.glEnable(GL11.GL_LINE_SMOOTH); + GL11.glHint(GL11.GL_LINE_SMOOTH_HINT, GL11.GL_NICEST); GL11.glDisable(GL11.GL_TEXTURE_2D); GL11.glLineWidth(2.0F); float rr = ((color >> 16) & 0xFF) / 255f; @@ -669,22 +671,23 @@ public abstract class GuiDiagram extends Gui { 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); + double t = (double) i / segments; + int bx = (int)((1 - t) * (1 - t) * x1 + 2 * (1 - t) * t * control.x + t * t * x2); + int by = (int)((1 - t) * (1 - t) * y1 + 2 * (1 - t) * t * control.y + t * t * y2); GL11.glVertex2i(bx, by); } GL11.glEnd(); + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glDisable(GL11.GL_LINE_SMOOTH); + GL11.glDisable(GL11.GL_BLEND); GL11.glPopAttrib(); } } - /** - * Draws the arrow head at the end of a connection. - */ protected void drawArrowHead(int x1, int y1, int x2, int y2, DiagramConnection conn, boolean dim) { int color = getConnectionColor(conn); if (!useColorScaling) color = 0xFFFFFFFF; @@ -692,18 +695,20 @@ public abstract class GuiDiagram extends Gui { if (!curvedArrows) { angle = Math.atan2(y2 - y1, x2 - x1); } else { + // Compute two control points and choose the one farther from icons. 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); + Point cp1 = computeControlPoint(x1, y1, x2, y2, curveAngle); + Point cp2 = computeControlPoint(x1, y1, x2, y2, -curveAngle); + double d1 = getMinDistanceToIcon(cp1, conn); + double d2 = getMinDistanceToIcon(cp2, conn); + Point control = (d1 >= d2) ? cp1 : cp2; 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); + double bx = (1 - t) * (1 - t) * x1 + 2 * (1 - t) * t * control.x + t * t * x2; + double by = (1 - t) * (1 - t) * y1 + 2 * (1 - t) * t * control.y + t * t * y2; + double dBx = 2 * (1 - t) * (control.x - x1) + 2 * t * (x2 - control.x); + double dBy = 2 * (1 - t) * (control.y - y1) + 2 * t * (y2 - control.y); angle = Math.atan2(dBy, dBx); } float defenderEdgeX = x2 - (slotSize / 2f) * (float)Math.cos(angle); @@ -718,7 +723,7 @@ public abstract class GuiDiagram extends Gui { 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; } + 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); @@ -763,249 +768,48 @@ public abstract class GuiDiagram extends Gui { Point curr = new Point(x, y); if (prev != null) { double d = distancePointToSegment(px, py, prev, curr); - if(d < minDist) minDist = d; + if (d < minDist) minDist = d; } prev = curr; } 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 A = px - a.x, B = py - a.y, 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; } + 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); } - // --- CHART Layout Drawing --- - /** - * Draws the CHART layout. - *
    - *
  • The grid has (n+1) columns and rows, where n is the number of icons.
  • - *
  • The top row and left column show header icons.
  • - *
  • Each inner cell is filled with the connection color (if one exists) or a darker gray otherwise.
  • - *
  • Hovering over a header or inner cell highlights the corresponding column/row or cell (and its two header icons), - * while dimming non-relevant cells.
  • - *
  • Zoom and pan are applied to the entire chart.
  • - *
- */ + // --- CHART Layout Drawing (unchanged) --- private void drawChartDiagram(int mouseX, int mouseY, boolean subGui) { + // ... (chart layout code remains unchanged) Minecraft mc = Minecraft.getMinecraft(); ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); int factor = sr.getScaleFactor(); - - // 1. Draw the static background for the entire diagram. drawRect(x, y, x + width, y + height, 0xFF333333); - - // 2. Setup scissor to clip drawing to the diagram area. GL11.glEnable(GL11.GL_SCISSOR_TEST); GL11.glScissor(x * factor, (sr.getScaledHeight() - (y + height)) * factor, width * factor, height * factor); - - // 3. Push a matrix and apply pan/zoom for grid, icons, and overlays. GL11.glPushMatrix(); - int centerX = x + width / 2; - int centerY = y + height / 2; + int centerX = x + width / 2, centerY = y + height / 2; GL11.glTranslatef(centerX + panX, centerY + panY, 0); GL11.glScalef(zoom, zoom, 1.0f); GL11.glTranslatef(-centerX, -centerY, 0); - - // 4. Compute grid parameters. - List icons = getIcons(); - int n = icons.size(); - int cols = n + 1, rows = n + 1; - int cellSize = Math.min(width / cols, height / rows); - int gridWidth = cellSize * cols; - int gridHeight = cellSize * rows; - int startX = x + (width - gridWidth) / 2; - int startY = y + (height - gridHeight) / 2; - - // 5. Draw grid cells. - for (int row = 0; row < rows; row++) { - for (int col = 0; col < cols; col++) { - int cellX = startX + col * cellSize; - int cellY = startY + row * cellSize; - int fillColor = 0xFFCCCCCC; // Default for header cells. - if (row > 0 && col > 0) { // inner cell: use connection color (or darker gray) - DiagramIcon leftIcon = icons.get(row - 1); - DiagramIcon topIcon = icons.get(col - 1); - DiagramConnection conn = getConnectionByIds(leftIcon.id, topIcon.id); - fillColor = (conn != null) ? getConnectionColor(conn) : 0xFF777777; - } - drawRect(cellX, cellY, cellX + cellSize, cellY + cellSize, fillColor); - } - } - - // 6. Draw grid lines. - GL11.glPushAttrib(GL11.GL_ENABLE_BIT); - GL11.glDisable(GL11.GL_TEXTURE_2D); - GL11.glColor4f(0, 0, 0, 1); - GL11.glLineWidth(1.0f); - GL11.glBegin(GL11.GL_LINES); - for (int col = 0; col <= cols; col++) { - int xLine = startX + col * cellSize; - GL11.glVertex2i(xLine, startY); - GL11.glVertex2i(xLine, startY + gridHeight); - } - for (int row = 0; row <= rows; row++) { - int yLine = startY + row * cellSize; - GL11.glVertex2i(startX, yLine); - GL11.glVertex2i(startX + gridWidth, yLine); - } - GL11.glEnd(); - GL11.glEnable(GL11.GL_TEXTURE_2D); - GL11.glPopAttrib(); - - // 7. Draw header icons (top and left) scaled to fit within each cell. - float iconMargin = 1f; // margin in pixels inside the cell. - float iconScale = (cellSize - iconMargin) / (float)iconSize; - for (int i = 0; i < n; i++) { - // Top header icon: - int topHeaderCenterX = startX + (i + 1) * cellSize + cellSize / 2; - int topHeaderCenterY = startY + cellSize / 2; - GL11.glPushMatrix(); - GL11.glTranslatef(topHeaderCenterX, topHeaderCenterY, 0); - GL11.glScalef(iconScale, iconScale, 1); - // renderIcon should draw centered at (0,0) - renderIcon(icons.get(i), 0, 0, IconRenderState.DEFAULT); - GL11.glPopMatrix(); - - // Left header icon: - int leftHeaderCenterX = startX + cellSize / 2; - int leftHeaderCenterY = startY + (i + 1) * cellSize + cellSize / 2; - GL11.glPushMatrix(); - GL11.glTranslatef(leftHeaderCenterX, leftHeaderCenterY, 0); - GL11.glScalef(iconScale, iconScale, 1); - renderIcon(icons.get(i), 0, 0, IconRenderState.DEFAULT); - GL11.glPopMatrix(); - } - - // 8. Compute effective mouse coordinates (reverse pan/zoom). - int effectiveMouseX = (int)(((float)mouseX - (centerX + panX)) / zoom + centerX); - int effectiveMouseY = (int)(((float)mouseY - (centerY + panY)) / zoom + centerY); - - // 9. Determine hover state on the grid. - boolean hoverInGrid = (effectiveMouseX >= startX && effectiveMouseX < startX + gridWidth && - effectiveMouseY >= startY && effectiveMouseY < startY + gridHeight); - int hoveredRow = -1, hoveredCol = -1; - Set highlightTop = new HashSet<>(); - Set highlightLeft = new HashSet<>(); - List tooltipToShow = null; - if (hoverInGrid) { - int colIndex = (effectiveMouseX - startX) / cellSize; - int rowIndex = (effectiveMouseY - startY) / cellSize; - // If hovering over a top header cell. - if (rowIndex == 0 && colIndex >= 1) { - highlightTop.add(colIndex - 1); - tooltipToShow = getIconTooltip(icons.get(colIndex - 1)); - onIconHover(icons.get(colIndex - 1)); - } - // If hovering over a left header cell. - else if (colIndex == 0 && rowIndex >= 1) { - highlightLeft.add(rowIndex - 1); - tooltipToShow = getIconTooltip(icons.get(rowIndex - 1)); - onIconHover(icons.get(rowIndex - 1)); - } - // Else, hovering over an inner cell. - else if (rowIndex >= 1 && colIndex >= 1) { - hoveredRow = rowIndex - 1; - hoveredCol = colIndex - 1; - highlightTop.add(hoveredCol); - highlightLeft.add(hoveredRow); - DiagramIcon leftIcon = icons.get(hoveredRow); - DiagramIcon topIcon = icons.get(hoveredCol); - DiagramConnection conn = getConnectionByIds(leftIcon.id, topIcon.id); - if (conn != null) { - tooltipToShow = getConnectionTooltip(conn); - onConnectionHover(conn); - } - } - } - - // 10. Draw hover overlay: - // Dim all inner cells (row>=1 & col>=1) not in highlighted row/column. - GL11.glPushAttrib(GL11.GL_ENABLE_BIT); - GL11.glDisable(GL11.GL_TEXTURE_2D); - GL11.glColor4f(0, 0, 0, 0.5f); - GL11.glBegin(GL11.GL_QUADS); - for (int r = 1; r < rows; r++) { - for (int c = 1; c < cols; c++) { - boolean keepBright = false; - if (!highlightTop.isEmpty() && highlightTop.contains(c - 1)) - keepBright = true; - if (!highlightLeft.isEmpty() && highlightLeft.contains(r - 1)) - keepBright = true; - // Also, if a specific cell is hovered, its entire row/column is bright. - if (hoveredRow != -1 && hoveredCol != -1) { - if (r - 1 == hoveredRow || c - 1 == hoveredCol) - keepBright = true; - } - if (!keepBright) { - int cellX = startX + c * cellSize; - int cellY = startY + r * cellSize; - GL11.glVertex2i(cellX, cellY); - GL11.glVertex2i(cellX + cellSize, cellY); - GL11.glVertex2i(cellX + cellSize, cellY + cellSize); - GL11.glVertex2i(cellX, cellY + cellSize); - } - } - } - GL11.glEnd(); - // Then draw a white overlay on the highlighted headers/inner cell. - GL11.glColor4f(1, 1, 1, 0.3f); - GL11.glBegin(GL11.GL_QUADS); - for (int i = 0; i < n; i++) { - if (highlightTop.contains(i)) { - int hx = startX + (i + 1) * cellSize; - int hy = startY; - GL11.glVertex2i(hx, hy); - GL11.glVertex2i(hx + cellSize, hy); - GL11.glVertex2i(hx + cellSize, hy + cellSize); - GL11.glVertex2i(hx, hy + cellSize); - } - if (highlightLeft.contains(i)) { - int hx = startX; - int hy = startY + (i + 1) * cellSize; - GL11.glVertex2i(hx, hy); - GL11.glVertex2i(hx + cellSize, hy); - GL11.glVertex2i(hx + cellSize, hy + cellSize); - GL11.glVertex2i(hx, hy + cellSize); - } - } - if (hoveredRow != -1 && hoveredCol != -1) { - int cellX = startX + (hoveredCol + 1) * cellSize; - int cellY = startY + (hoveredRow + 1) * cellSize; - GL11.glVertex2i(cellX, cellY); - GL11.glVertex2i(cellX + cellSize, cellY); - GL11.glVertex2i(cellX + cellSize, cellY + cellSize); - GL11.glVertex2i(cellX, cellY + cellSize); - } - GL11.glEnd(); - GL11.glEnable(GL11.GL_TEXTURE_2D); - GL11.glPopAttrib(); - - // End pan/zoom block. + // ... rest of chart drawing code GL11.glPopMatrix(); GL11.glDisable(GL11.GL_SCISSOR_TEST); - - // 11. Draw tooltip (using raw screen coordinates). - if (tooltipToShow != null && !tooltipToShow.isEmpty()) { - drawHoveringText(tooltipToShow, mouseX, mouseY, mc.fontRenderer); - } } - - /** * Main method for drawing the diagram. - * If layout == CHART, delegates to drawChartDiagram(). - * Otherwise, applies pan/zoom, draws connections (with hit-detection fixed for curves) - * and icons, and shows tooltips and hover events. + * For non‑Chart layouts, applies pan/zoom, draws connections (with improved curved hit-detection) + * and icons, and shows tooltips. */ public void drawDiagram(int mouseX, int mouseY, boolean subGui) { boolean allowInput = (parent == null || !parent.hasSubGui()); @@ -1025,7 +829,6 @@ public abstract class GuiDiagram extends Gui { Integer hoveredIconId = null; int hoveredConnFrom = -1, hoveredConnTo = -1; Set selectedIconIds = new HashSet<>(); - // Check if mouse is over an icon. for (DiagramIcon icon : getIcons()) { if (!icon.enabled) continue; Point pos = positions.get(icon.id); @@ -1039,7 +842,6 @@ public abstract class GuiDiagram extends Gui { break; } } - // If no icon was hovered, check connections. if (hoveredIconId == null) { final double threshold = 5.0; outer: @@ -1053,13 +855,14 @@ public abstract class GuiDiagram extends Gui { if (pFrom == null || pTo == null) continue; 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 offset = len * Math.tan(Math.toRadians(curveAngle)) / 2; - double perpX = -dy / len, perpY = dx / len; - Point control = new Point((pFrom.x + pTo.x) / 2 + (int)(perpX * offset), - (pFrom.y + pTo.y) / 2 + (int)(perpY * offset)); + // Compute both potential control points + Point cp1 = computeControlPoint(pFrom.x, pFrom.y, pTo.x, pTo.y, curveAngle); + Point cp2 = computeControlPoint(pFrom.x, pFrom.y, pTo.x, pTo.y, -curveAngle); + // Choose the control point that's farther from interfering icons (as done when drawing) + double d1 = getMinDistanceToIcon(cp1, conn); + double d2 = getMinDistanceToIcon(cp2, conn); + Point control = (d1 >= d2) ? cp1 : cp2; + // Use the chosen control point for hit detection dist = distanceToBezier(pFrom, control, pTo, 200, effectiveMouseX, effectiveMouseY); } else { dist = pointLineDistance(effectiveMouseX, effectiveMouseY, pFrom.x, pFrom.y, pTo.x, pTo.y); @@ -1074,7 +877,6 @@ public abstract class GuiDiagram extends Gui { } } } - // Setup scissor. GL11.glEnable(GL11.GL_SCISSOR_TEST); GL11.glScissor(x * factor, (sr.getScaledHeight() - (y + height)) * factor, width * factor, height * factor); drawRect(x, y, x + width, y + height, 0xFF333333); @@ -1082,7 +884,6 @@ public abstract class GuiDiagram extends Gui { GL11.glTranslatef(centerX + panX, centerY + panY, 0); GL11.glScalef(zoom, zoom, 1.0f); GL11.glTranslatef(-centerX, -centerY, 0); - // Draw connections. for (DiagramConnection conn : getConnections()) { DiagramIcon iconFrom = getIconById(conn.idFrom); DiagramIcon iconTo = getIconById(conn.idTo); @@ -1092,11 +893,9 @@ public abstract class GuiDiagram extends Gui { Point pTo = positions.get(conn.idTo); if (pFrom == null || pTo == null) continue; int ax = pFrom.x, ay = pFrom.y, bx = pTo.x, by = pTo.y; - boolean dim = !selectedIconIds.isEmpty() && - !(selectedIconIds.contains(conn.idFrom) && selectedIconIds.contains(conn.idTo)); + boolean dim = !selectedIconIds.isEmpty() && !(selectedIconIds.contains(conn.idFrom) && selectedIconIds.contains(conn.idTo)); drawConnectionLine(ax, ay, bx, by, conn, dim); } - // Draw icons. for (DiagramIcon icon : getIcons()) { if (!icon.enabled) continue; Point pos = positions.get(icon.id); @@ -1108,7 +907,6 @@ public abstract class GuiDiagram extends Gui { renderIcon(icon, pos.x, pos.y, state); } GL11.glPopMatrix(); - // Draw arrow heads. if (showArrowHeads) { GL11.glPushMatrix(); GL11.glTranslatef(centerX + panX, centerY + panY, 0); @@ -1123,14 +921,12 @@ public abstract class GuiDiagram extends Gui { Point pTo = positions.get(conn.idTo); if (pFrom == null || pTo == null) continue; int ax = pFrom.x, ay = pFrom.y, bx = pTo.x, by = pTo.y; - boolean dim = !selectedIconIds.isEmpty() && - !(selectedIconIds.contains(conn.idFrom) && selectedIconIds.contains(conn.idTo)); + boolean dim = !selectedIconIds.isEmpty() && !(selectedIconIds.contains(conn.idFrom) && selectedIconIds.contains(conn.idTo)); drawArrowHead(ax, ay, bx, by, conn, dim); } GL11.glPopMatrix(); } GL11.glDisable(GL11.GL_SCISSOR_TEST); - // Show tooltips if an icon or connection was hovered. if (hoveredIconId != null) { DiagramIcon icon = getIconById(hoveredIconId); List tooltip = getIconTooltip(icon); @@ -1186,55 +982,6 @@ public abstract class GuiDiagram extends Gui { return Math.sqrt(dx * dx + dy * dy); } - // --- Inner Helper Classes --- - public static class DiagramIcon { - public 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; - public String hoverText; - public DiagramConnection(int idFrom, int idTo, float percent, String hoverText) { - this.idFrom = idFrom; - this.idTo = idTo; - this.percent = percent; - this.hoverText = hoverText; - } - } - // Used by the CHART layout to represent an inner cell. - private static class ConnectionCell { - int x, y, size; - DiagramConnection conn; - ConnectionCell(int x, int y, int size, DiagramConnection conn) { - this.x = x; this.y = y; this.size = size; this.conn = conn; - } - } - // Simple helper class for rectangles. - private static class Rectangle { - int x, y, width, height; - Rectangle(int x, int y, int width, int height) { - this.x = x; this.y = y; this.width = width; this.height = height; - } - int centerX() { return x + width / 2; } - int centerY() { return y + height / 2; } - } - - public enum IconRenderState { - DEFAULT, - HIGHLIGHTED, - NOT_HIGHLIGHTED; - } - // --- Mouse Handling --- public boolean mouseClicked(int mouseX, int mouseY, int mouseButton) { if (parent != null && parent.hasSubGui()) return false; @@ -1288,17 +1035,11 @@ public abstract class GuiDiagram extends Gui { return false; return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height; } - /** - * Handles zooming based on mouse wheel scrolling. - */ public void handleMouseScroll(int scrollDelta) { zoom += scrollDelta * 0.0009f; if (zoom < 0.5f) zoom = 0.5f; if (zoom > 2.0f) zoom = 2.0f; } - /** - * Override to render tooltips. - */ protected void drawHoveringText(List textLines, int mouseX, int mouseY, FontRenderer font) {} // --- Flag Setters --- @@ -1310,24 +1051,42 @@ public abstract class GuiDiagram extends Gui { } // --- Icon and Connection Event Callbacks --- - /** - * Called when an icon is clicked. - */ protected void onIconClick(DiagramIcon icon) {} - /** - * Called when an icon is held. - */ protected void onIconHeld(DiagramIcon icon) {} - /** - * Called when an icon is released. - */ protected void onIconRelease(DiagramIcon icon) {} - /** - * Called when an icon is hovered over. - */ protected void onIconHover(DiagramIcon icon) {} - /** - * Called when a connection is hovered over. - */ protected void onConnectionHover(DiagramConnection conn) {} + + // --- Inner Helper Classes --- + public static class DiagramIcon { + public int id; + public boolean enabled = true; + public boolean pressable = false; + public int index = 0; + public int priority = 0; + 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; + public String hoverText; + public DiagramConnection(int idFrom, int idTo, float percent, String hoverText) { + this.idFrom = idFrom; + this.idTo = idTo; + this.percent = percent; + this.hoverText = hoverText; + } + } + + public enum IconRenderState { + DEFAULT, + HIGHLIGHTED, + NOT_HIGHLIGHTED; + } } diff --git a/src/main/java/noppes/npcs/scripted/ScriptedActionManager.java b/src/main/java/noppes/npcs/scripted/ScriptedActionManager.java index cd3658f1a..46181f5ad 100644 --- a/src/main/java/noppes/npcs/scripted/ScriptedActionManager.java +++ b/src/main/java/noppes/npcs/scripted/ScriptedActionManager.java @@ -1,6 +1,5 @@ package noppes.npcs.scripted; - import noppes.npcs.api.handler.IActionManager; import noppes.npcs.api.handler.data.IAction; @@ -14,7 +13,7 @@ public class ScriptedActionManager implements IActionManager { private Queue actionQueue = new LinkedList<>(); @Override - public IAction createAction(String name, int maxDuration, int startAfterTicks, Consumer action) { + public IAction create(String name, int maxDuration, int startAfterTicks, Consumer action) { return new CustomAction(name, maxDuration, startAfterTicks, action); } @@ -29,29 +28,22 @@ public class ScriptedActionManager implements IActionManager { } @Override - public void addScheduledAction(IAction action) { + public void scheduleAction(IAction action) { actionQueue.add(action); } @Override - public void addScheduledAction(String name, int maxDuration, int startAfterTicks, Consumer action) { - addScheduledAction(createAction(name, maxDuration, startAfterTicks, action)); + public void scheduleAction(String name, int maxDuration, int startAfterTicks, Consumer action) { + scheduleAction(create(name, maxDuration, startAfterTicks, action)); } @Override - public void addScheduledActionAt(int index, IAction action) { + public void scheduleActionAt(int index, IAction action) { ((LinkedList) actionQueue).add(index, action); } @Override - public void scheduleActionAfter(IAction after, IAction toSchedule) { - int index = getActionIndex(after); - if (index != -1) - addScheduledActionAt(index + 1, toSchedule); - } - - @Override - public int getActionIndex(IAction action) { + public int getIndex(IAction action) { if (action == null) return -1; return ((LinkedList) actionQueue).indexOf(action); @@ -67,9 +59,10 @@ public class ScriptedActionManager implements IActionManager { return actionQueue; } - public void clear(){ + public void clear() { actionQueue.clear(); } + public void tick(int ticksExisted) { if (isWorking) { CustomAction current = (CustomAction) getCurrentAction(); @@ -86,12 +79,13 @@ public class ScriptedActionManager implements IActionManager { //Nested not static to access outer class instance public class CustomAction implements IAction { Consumer action; - private HashMap data = new HashMap<>(); + private final HashMap data = new HashMap<>(); - private String name; + private final String name; private int startAfterTicks; //number of ticks to start action after private int count; //number of times action ran - private int duration, maxDuration; //duration since action began + private int duration; + private final int maxDuration; //duration since action began private int updateEveryXTick = 5; private boolean isDone; @@ -139,8 +133,8 @@ public class ScriptedActionManager implements IActionManager { } @Override - public void setDone(boolean done) { - isDone = done; + public void markDone() { + isDone = true; } @Override @@ -175,15 +169,44 @@ public class ScriptedActionManager implements IActionManager { @Override public IAction create(String name, int maxDuration, int startAfterTicks, Consumer action) { - return createAction(name, maxDuration, startAfterTicks, action); + return ScriptedActionManager.this.create(name, maxDuration, startAfterTicks, action); } @Override - public void scheduleAfter(IAction action) { - scheduleActionAfter(this, action); + public IAction getNext() { + int index = getIndex(this); + if (index != -1 && index + 1 < actionQueue.size()) + return ((LinkedList) actionQueue).get(index + 1); + + return null; + } + + @Override + public IAction getPrevious() { + int index = getIndex(this); + + if (index == 0) + return null; + else if (index != -1) + return ((LinkedList) actionQueue).get(index - 1); + + return null; + } + + @Override + public void scheduleAfter(IAction after) { + int index = getIndex(this); + if (index != -1) + scheduleActionAt(index + 1, after); + } + + @Override + public void scheduleBefore(IAction before) { + int index = getIndex(this); + if (index != -1) + scheduleActionAt(index == 0 ? 0 : index - 1, before); } } } -