From d1eae2d1a2385641c0a31a37e2752478bde043bc Mon Sep 17 00:00:00 2001 From: Anuken Date: Tue, 8 Feb 2022 19:29:23 -0500 Subject: [PATCH] Slightly more functional unit commands --- core/src/mindustry/ai/ControlPathfinder.java | 97 ++++++++++++++++++++ core/src/mindustry/content/Blocks.java | 2 +- core/src/mindustry/graphics/Drawf.java | 14 ++- core/src/mindustry/input/DesktopInput.java | 90 +++++++++++++----- core/src/mindustry/input/InputHandler.java | 14 ++- 5 files changed, 190 insertions(+), 27 deletions(-) create mode 100644 core/src/mindustry/ai/ControlPathfinder.java diff --git a/core/src/mindustry/ai/ControlPathfinder.java b/core/src/mindustry/ai/ControlPathfinder.java new file mode 100644 index 0000000000..a43e38f68c --- /dev/null +++ b/core/src/mindustry/ai/ControlPathfinder.java @@ -0,0 +1,97 @@ +package mindustry.ai; + +import arc.*; +import arc.struct.*; +import arc.util.*; +import arc.util.async.*; +import mindustry.game.EventType.*; + +import static mindustry.Vars.*; + +public class ControlPathfinder implements Runnable{ + private static final long maxUpdate = Time.millisToNanos(7); + private static final int updateFPS = 60; + private static final int updateInterval = 1000 / updateFPS; + + //MAIN THREAD DATA + /** Current pathfinding thread */ + @Nullable Thread thread; + /** for unique target IDs */ + int lastTargetId; + /** handles task scheduling on the update thread. */ + TaskQueue queue = new TaskQueue(); + + //PATHFINDING THREAD DATA + IntMap requests = new IntMap<>(); + + + /** @return the next target ID to use as a unique path identifier. */ + public int nextTargetId(){ + return lastTargetId ++; + } + + public ControlPathfinder(){ + Events.on(WorldLoadEvent.class, event -> { + stop(); + + + start(); + }); + + Events.on(ResetEvent.class, event -> stop()); + } + + /** Starts or restarts the pathfinding thread. */ + private void start(){ + stop(); + thread = Threads.daemon("ControlPathfinder", this); + } + + /** Stops the pathfinding thread. */ + private void stop(){ + if(thread != null){ + thread.interrupt(); + thread = null; + } + requests.clear(); + } + + @Override + public void run(){ + while(true){ + if(net.client()) return; + try{ + if(state.isPlaying()){ + queue.run(); + + //total update time no longer than maxUpdate + //for(Flowfield data : threadList){ + // updateFrontier(data, maxUpdate / threadList.size); + //} + + for(var entry : requests){ + + } + } + + try{ + Thread.sleep(updateInterval); + }catch(InterruptedException e){ + //stop looping when interrupted externally + return; + } + }catch(Throwable e){ + //do not crash the pathfinding thread + Log.err(e); + } + } + } + + class PathRequest{ + public int lastRequestFrame; + + public void update(long maxUpdateNs){ + + } + } +} diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index 43a8e1ee89..e817555b52 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -3507,7 +3507,7 @@ public class Blocks{ shipAssembler = new UnitAssembler("ship-assembler"){{ requirements(Category.units, with(Items.beryllium, 700, Items.oxide, 150, Items.tungsten, 500, Items.silicon, 800)); size = 5; - plans.add(new AssemblerUnitPlan(UnitTypes.quell, 60f * 25f, BlockStack.list(Blocks.tungstenWallLarge, 5, Blocks.plasmaBore, 2))); + plans.add(new AssemblerUnitPlan(UnitTypes.quell, 60f * 25f, BlockStack.list(Blocks.berylliumWallLarge, 4, Blocks.duct, 10, Blocks.plasmaBore, 4))); consumes.power(2f); areaSize = 13; diff --git a/core/src/mindustry/graphics/Drawf.java b/core/src/mindustry/graphics/Drawf.java index babe4a0388..499992dec0 100644 --- a/core/src/mindustry/graphics/Drawf.java +++ b/core/src/mindustry/graphics/Drawf.java @@ -119,7 +119,10 @@ public class Drawf{ } public static void dashLine(Color color, float x, float y, float x2, float y2){ - int segments = (int)(Math.max(Math.abs(x - x2), Math.abs(y - y2)) / tilesize * 2); + dashLine(color, x, y, x2, y2, (int)(Math.max(Math.abs(x - x2), Math.abs(y - y2)) / tilesize * 2)); + } + + public static void dashLine(Color color, float x, float y, float x2, float y2, int segments){ Lines.stroke(3f); Draw.color(Pal.gray, color.a); Lines.dashLine(x, y, x2, y2, segments); @@ -128,6 +131,15 @@ public class Drawf{ Draw.reset(); } + public static void line(Color color, float x, float y, float x2, float y2){ + Lines.stroke(3f); + Draw.color(Pal.gray, color.a); + Lines.line(x, y, x2, y2); + Lines.stroke(1f, color); + Lines.line(x, y, x2, y2); + Draw.reset(); + } + public static void dashLineBasic(float x, float y, float x2, float y2){ Lines.dashLine(x, y, x2, y2, (int)(Math.max(Math.abs(x - x2), Math.abs(y - y2)) / tilesize * 2)); } diff --git a/core/src/mindustry/input/DesktopInput.java b/core/src/mindustry/input/DesktopInput.java index de2abaac34..db98c3d2db 100644 --- a/core/src/mindustry/input/DesktopInput.java +++ b/core/src/mindustry/input/DesktopInput.java @@ -5,6 +5,7 @@ import arc.Graphics.*; import arc.Graphics.Cursor.*; import arc.graphics.*; import arc.graphics.g2d.*; +import arc.input.*; import arc.math.*; import arc.math.geom.*; import arc.scene.*; @@ -24,7 +25,6 @@ import mindustry.ui.*; import mindustry.world.*; import static arc.Core.*; -import static mindustry.Vars.net; import static mindustry.Vars.*; import static mindustry.input.PlaceMode.*; @@ -116,30 +116,47 @@ public class DesktopInput extends InputHandler{ drawSelection(schemX, schemY, cursorX, cursorY, Vars.maxSchematicSize); } + //draw command overlay UI + for(Unit unit : selectedUnits){ CommandAI ai = (CommandAI)unit.controller(); //draw target line if(ai.targetPos != null){ Tmp.v1.set(ai.targetPos).sub(unit).setLength(unit.hitSize / 2f); - Drawf.dashLine(Pal.accent, unit.x + Tmp.v1.x, unit.y + Tmp.v1.y, ai.targetPos.x, ai.targetPos.y); + Drawf.line(Pal.accent, unit.x + Tmp.v1.x, unit.y + Tmp.v1.y, ai.targetPos.x, ai.targetPos.y); } Drawf.square(unit.x, unit.y, unit.hitSize / 1.4f + 1f); } - //draw command overlay UI - if(commandMode){ + + if(commandMode && !commandRect){ Unit sel = selectedCommandUnit(input.mouseWorldX(), input.mouseWorldY()); if(sel != null){ - Drawf.square(sel.x, sel.y, sel.hitSize / 1.4f + Mathf.absin(4f, 1f), selectedUnits.contains(sel) ? Pal.remove : Pal.accent); + drawCommand(sel); } } + if(commandRect){ + float x2 = input.mouseWorldX(), y2 = input.mouseWorldY(); + var units = selectedCommandUnits(commandRectX, commandRectY, x2 - commandRectX, y2 - commandRectY); + for(var unit : units){ + drawCommand(unit); + } + + Draw.color(Pal.accent, 0.3f); + Fill.crect(commandRectX, commandRectY, x2 - commandRectX, y2 - commandRectY); + } + Draw.reset(); } + public void drawCommand(Unit sel){ + Drawf.square(sel.x, sel.y, sel.hitSize / 1.4f + Mathf.absin(4f, 1f), selectedUnits.contains(sel) ? Pal.remove : Pal.accent); + } + @Override public void drawBottom(){ int cursorX = tileX(Core.input.mouseX()); @@ -516,7 +533,19 @@ public class DesktopInput extends InputHandler{ lastLineY = cursorY; } + //select some units + if(Core.input.keyRelease(Binding.select) && commandRect){ + if(!tappedOne){ + var units = selectedCommandUnits(commandRectX, commandRectY, input.mouseWorldX() - commandRectX, input.mouseWorldY() - commandRectY); + //tiny brain method of unique addition + selectedUnits.removeAll(units); + selectedUnits.addAll(units); + } + commandRect = false; + } + if(Core.input.keyTap(Binding.select) && !Core.scene.hasMouse()){ + tappedOne = false; BuildPlan req = getRequest(cursorX, cursorY); if(Core.input.keyDown(Binding.break_block)){ @@ -535,25 +564,9 @@ public class DesktopInput extends InputHandler{ }else if(req != null && req.breaking){ deleting = true; }else if(commandMode){ - Unit unit = selectedCommandUnit(input.mouseWorldX(), input.mouseWorldY()); - if(unit != null){ - if(selectedUnits.contains(unit)){ - selectedUnits.remove(unit); - }else{ - selectedUnits.add(unit); - } - }else if(selectedUnits.size > 0){ - //move to location - TODO right click instead? - - //TODO all this needs to be synced, done with packets, etc - Vec2 target = input.mouseWorld().cpy(); - - for(var sel : selectedUnits){ - ((CommandAI)sel.controller()).commandPosition(target); - } - - Fx.moveCommand.at(target); - } + commandRect = true; + commandRectX = input.mouseWorldX(); + commandRectY = input.mouseWorldY(); }else if(selected != null){ //only begin shooting if there's no cursor event if(!tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && !tileTapped(selected.build) && !player.unit().activelyBuilding() && !droppingItem @@ -647,6 +660,35 @@ public class DesktopInput extends InputHandler{ } } + @Override + public boolean tap(float x, float y, int count, KeyCode button){ + if(scene.hasMouse()) return false; + + tappedOne = true; + + Unit unit = selectedCommandUnit(input.mouseWorldX(), input.mouseWorldY()); + if(unit != null){ + if(selectedUnits.contains(unit)){ + selectedUnits.remove(unit); + }else{ + selectedUnits.add(unit); + } + }else if(selectedUnits.size > 0){ + //move to location - TODO right click instead? + + //TODO all this needs to be synced, done with packets, etc + Vec2 target = input.mouseWorld().cpy(); + + for(var sel : selectedUnits){ + ((CommandAI)sel.controller()).commandPosition(target); + } + + Fx.moveCommand.at(target); + } + + return super.tap(x, y, count, button); + } + @Override public boolean selectedBlock(){ return isPlacing() && mode != breaking; diff --git a/core/src/mindustry/input/InputHandler.java b/core/src/mindustry/input/InputHandler.java index 1846074209..c8600a4482 100644 --- a/core/src/mindustry/input/InputHandler.java +++ b/core/src/mindustry/input/InputHandler.java @@ -48,7 +48,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ /** Maximum line length. */ final static int maxLength = 100; final static Rect r1 = new Rect(), r2 = new Rect(); - final static Seq tmpUnits = new Seq<>(); + final static Seq tmpUnits = new Seq<>(false); public final OverlayFragment frag = new OverlayFragment(); @@ -75,6 +75,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ //for RTS controls public Seq selectedUnits = new Seq<>(); public boolean commandMode = false; + public boolean commandRect = false; + public boolean tappedOne = false; + public float commandRectX, commandRectY; private Seq plansOut = new Seq<>(BuildPlan.class); private QuadTree playerPlanTree = new QuadTree<>(new Rect()); @@ -1196,6 +1199,15 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ return tmpUnits.min(u -> u.isCommandable(), u -> u.dst(x, y) - u.hitSize/2f); } + public Seq selectedCommandUnits(float x, float y, float w, float h){ + var tree = player.team().data().tree(); + tmpUnits.clear(); + float rad = 4f; + tree.intersect(Tmp.r1.set(x - rad/2f, y - rad/2f, rad*2f + w, rad*2f + h).normalize(), tmpUnits); + tmpUnits.removeAll(u -> !u.isCommandable()); + return tmpUnits; + } + public void remove(){ Core.input.removeProcessor(this); frag.remove();