Slightly more functional unit commands
This commit is contained in:
97
core/src/mindustry/ai/ControlPathfinder.java
Normal file
97
core/src/mindustry/ai/ControlPathfinder.java
Normal file
@@ -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<PathRequest> 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){
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3507,7 +3507,7 @@ public class Blocks{
|
|||||||
shipAssembler = new UnitAssembler("ship-assembler"){{
|
shipAssembler = new UnitAssembler("ship-assembler"){{
|
||||||
requirements(Category.units, with(Items.beryllium, 700, Items.oxide, 150, Items.tungsten, 500, Items.silicon, 800));
|
requirements(Category.units, with(Items.beryllium, 700, Items.oxide, 150, Items.tungsten, 500, Items.silicon, 800));
|
||||||
size = 5;
|
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);
|
consumes.power(2f);
|
||||||
areaSize = 13;
|
areaSize = 13;
|
||||||
|
|
||||||
|
|||||||
@@ -119,7 +119,10 @@ public class Drawf{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void dashLine(Color color, float x, float y, float x2, float y2){
|
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);
|
Lines.stroke(3f);
|
||||||
Draw.color(Pal.gray, color.a);
|
Draw.color(Pal.gray, color.a);
|
||||||
Lines.dashLine(x, y, x2, y2, segments);
|
Lines.dashLine(x, y, x2, y2, segments);
|
||||||
@@ -128,6 +131,15 @@ public class Drawf{
|
|||||||
Draw.reset();
|
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){
|
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));
|
Lines.dashLine(x, y, x2, y2, (int)(Math.max(Math.abs(x - x2), Math.abs(y - y2)) / tilesize * 2));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import arc.Graphics.*;
|
|||||||
import arc.Graphics.Cursor.*;
|
import arc.Graphics.Cursor.*;
|
||||||
import arc.graphics.*;
|
import arc.graphics.*;
|
||||||
import arc.graphics.g2d.*;
|
import arc.graphics.g2d.*;
|
||||||
|
import arc.input.*;
|
||||||
import arc.math.*;
|
import arc.math.*;
|
||||||
import arc.math.geom.*;
|
import arc.math.geom.*;
|
||||||
import arc.scene.*;
|
import arc.scene.*;
|
||||||
@@ -24,7 +25,6 @@ import mindustry.ui.*;
|
|||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
|
|
||||||
import static arc.Core.*;
|
import static arc.Core.*;
|
||||||
import static mindustry.Vars.net;
|
|
||||||
import static mindustry.Vars.*;
|
import static mindustry.Vars.*;
|
||||||
import static mindustry.input.PlaceMode.*;
|
import static mindustry.input.PlaceMode.*;
|
||||||
|
|
||||||
@@ -116,30 +116,47 @@ public class DesktopInput extends InputHandler{
|
|||||||
drawSelection(schemX, schemY, cursorX, cursorY, Vars.maxSchematicSize);
|
drawSelection(schemX, schemY, cursorX, cursorY, Vars.maxSchematicSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//draw command overlay UI
|
||||||
|
|
||||||
for(Unit unit : selectedUnits){
|
for(Unit unit : selectedUnits){
|
||||||
CommandAI ai = (CommandAI)unit.controller();
|
CommandAI ai = (CommandAI)unit.controller();
|
||||||
//draw target line
|
//draw target line
|
||||||
if(ai.targetPos != null){
|
if(ai.targetPos != null){
|
||||||
Tmp.v1.set(ai.targetPos).sub(unit).setLength(unit.hitSize / 2f);
|
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);
|
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());
|
Unit sel = selectedCommandUnit(input.mouseWorldX(), input.mouseWorldY());
|
||||||
|
|
||||||
if(sel != null){
|
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();
|
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
|
@Override
|
||||||
public void drawBottom(){
|
public void drawBottom(){
|
||||||
int cursorX = tileX(Core.input.mouseX());
|
int cursorX = tileX(Core.input.mouseX());
|
||||||
@@ -516,7 +533,19 @@ public class DesktopInput extends InputHandler{
|
|||||||
lastLineY = cursorY;
|
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()){
|
if(Core.input.keyTap(Binding.select) && !Core.scene.hasMouse()){
|
||||||
|
tappedOne = false;
|
||||||
BuildPlan req = getRequest(cursorX, cursorY);
|
BuildPlan req = getRequest(cursorX, cursorY);
|
||||||
|
|
||||||
if(Core.input.keyDown(Binding.break_block)){
|
if(Core.input.keyDown(Binding.break_block)){
|
||||||
@@ -535,25 +564,9 @@ public class DesktopInput extends InputHandler{
|
|||||||
}else if(req != null && req.breaking){
|
}else if(req != null && req.breaking){
|
||||||
deleting = true;
|
deleting = true;
|
||||||
}else if(commandMode){
|
}else if(commandMode){
|
||||||
Unit unit = selectedCommandUnit(input.mouseWorldX(), input.mouseWorldY());
|
commandRect = true;
|
||||||
if(unit != null){
|
commandRectX = input.mouseWorldX();
|
||||||
if(selectedUnits.contains(unit)){
|
commandRectY = input.mouseWorldY();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}else if(selected != null){
|
}else if(selected != null){
|
||||||
//only begin shooting if there's no cursor event
|
//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
|
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
|
@Override
|
||||||
public boolean selectedBlock(){
|
public boolean selectedBlock(){
|
||||||
return isPlacing() && mode != breaking;
|
return isPlacing() && mode != breaking;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
/** Maximum line length. */
|
/** Maximum line length. */
|
||||||
final static int maxLength = 100;
|
final static int maxLength = 100;
|
||||||
final static Rect r1 = new Rect(), r2 = new Rect();
|
final static Rect r1 = new Rect(), r2 = new Rect();
|
||||||
final static Seq<Unit> tmpUnits = new Seq<>();
|
final static Seq<Unit> tmpUnits = new Seq<>(false);
|
||||||
|
|
||||||
public final OverlayFragment frag = new OverlayFragment();
|
public final OverlayFragment frag = new OverlayFragment();
|
||||||
|
|
||||||
@@ -75,6 +75,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
//for RTS controls
|
//for RTS controls
|
||||||
public Seq<Unit> selectedUnits = new Seq<>();
|
public Seq<Unit> selectedUnits = new Seq<>();
|
||||||
public boolean commandMode = false;
|
public boolean commandMode = false;
|
||||||
|
public boolean commandRect = false;
|
||||||
|
public boolean tappedOne = false;
|
||||||
|
public float commandRectX, commandRectY;
|
||||||
|
|
||||||
private Seq<BuildPlan> plansOut = new Seq<>(BuildPlan.class);
|
private Seq<BuildPlan> plansOut = new Seq<>(BuildPlan.class);
|
||||||
private QuadTree<BuildPlan> playerPlanTree = new QuadTree<>(new Rect());
|
private QuadTree<BuildPlan> 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);
|
return tmpUnits.min(u -> u.isCommandable(), u -> u.dst(x, y) - u.hitSize/2f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Seq<Unit> 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(){
|
public void remove(){
|
||||||
Core.input.removeProcessor(this);
|
Core.input.removeProcessor(this);
|
||||||
frag.remove();
|
frag.remove();
|
||||||
|
|||||||
Reference in New Issue
Block a user