diff --git a/core/src/mindustry/ai/types/CommandAI.java b/core/src/mindustry/ai/types/CommandAI.java index 2e59927e7b..f5a45d29ce 100644 --- a/core/src/mindustry/ai/types/CommandAI.java +++ b/core/src/mindustry/ai/types/CommandAI.java @@ -11,14 +11,16 @@ import mindustry.gen.*; import mindustry.world.*; public class CommandAI extends AIController{ - private static final float localInterval = 30f; - private static final Vec2 vecOut = new Vec2(); + private static final float localInterval = 40f; + private static final Vec2 vecOut = new Vec2(), flockVec = new Vec2(), separation = new Vec2(), cohesion = new Vec2(), massCenter = new Vec2(); public @Nullable Vec2 targetPos; public @Nullable Teamc attackTarget; + private Vec2 lastTargetPos; private int pathId = -1; private Seq local = new Seq<>(false); + private boolean flocked; @Override public void updateUnit(){ @@ -31,18 +33,28 @@ public class CommandAI extends AIController{ } if(targetPos != null){ - if(timer.get(timerTarget3, localInterval)){ + if(timer.get(timerTarget3, localInterval) || !flocked){ + if(!flocked){ + //make sure updates are staggered randomly + timer.reset(timerTarget3, Mathf.random(localInterval)); + } + local.clear(); + //TODO experiment with 2/3/4 float size = unit.hitSize * 3f; unit.team.data().tree().intersect(unit.x - size / 2f, unit.y - size/2f, size, size, local); + local.remove(unit); + flocked = true; } }else{ - //make sure updates are staggered randomly - timer.reset(timerTarget3, Mathf.random(localInterval)); + flocked = false; } if(attackTarget != null){ - if(targetPos == null) targetPos = new Vec2(); + if(targetPos == null){ + targetPos = new Vec2(); + lastTargetPos = targetPos; + } targetPos.set(attackTarget); if(unit.isGrounded() && attackTarget instanceof Building build && build.tile.solid() && unit.pathType() != Pathfinder.costLegs){ @@ -68,7 +80,9 @@ public class CommandAI extends AIController{ attackTarget != null && unit.within(attackTarget, engageRange) ? engageRange : unit.isGrounded() ? 0f : attackTarget != null ? engageRange : - 0f, 100f, false); + 0f, 100f, false, null); + + //calculateFlock().limit(unit.speed() * flockMult) } if(unit.isFlying()){ @@ -77,14 +91,75 @@ public class CommandAI extends AIController{ faceTarget(); } - if(attackTarget == null && unit.within(targetPos, Math.max(5f, unit.hitSize / 2.5f))){ - targetPos = null; + if(attackTarget == null){ + if(unit.within(targetPos, Math.max(5f, unit.hitSize / 2f))){ + targetPos = null; + }else if(local.size > 1){ + int count = 0; + for(var near : local){ + //has arrived + if(near.isCommandable() && !near.command().hasCommand() && targetPos.equals(near.command().lastTargetPos)){ + count ++; + } + } + + //others have arrived at destination, so this one will too + if(count >= Math.max(2, local.size / 3)){ + targetPos = null; + } + } } + }else if(target != null){ faceTarget(); } } + public static float cohesionScl = 0.3f; + public static float cohesionRad = 3f, separationRad = 1.1f, separationScl = 1f, flockMult = 0.5f; + + //TODO ひどい + Vec2 calculateFlock(){ + if(local.isEmpty()) return flockVec.setZero(); + + flockVec.setZero(); + separation.setZero(); + cohesion.setZero(); + massCenter.set(unit); + + float rad = unit.hitSize; + float sepDst = rad * separationRad, cohDst = rad * cohesionRad; + + //"cohesed" isn't even a word smh + int separated = 0, cohesed = 1; + + for(var other : local){ + float dst = other.dst(unit); + if(dst < sepDst){ + separation.add(Tmp.v1.set(unit).sub(other).scl(1f / sepDst)); + separated ++; + } + + if(dst < cohDst){ + massCenter.add(other); + cohesed ++; + } + } + + if(separated > 0){ + separation.scl(1f / separated); + flockVec.add(separation.scl(separationScl)); + } + + if(cohesed > 1){ + massCenter.scl(1f / cohesed); + flockVec.add(Tmp.v1.set(massCenter).sub(unit).limit(cohesionScl * unit.type.speed)); + //seek mass center? + } + + return flockVec; + } + @Override public boolean keepState(){ return true; @@ -107,6 +182,7 @@ public class CommandAI extends AIController{ public void commandPosition(Vec2 pos){ targetPos = pos; + lastTargetPos = pos; attackTarget = null; pathId = Vars.controlPath.nextTargetId(); } diff --git a/core/src/mindustry/entities/units/AIController.java b/core/src/mindustry/entities/units/AIController.java index 4d7f9e3894..cd79041957 100644 --- a/core/src/mindustry/entities/units/AIController.java +++ b/core/src/mindustry/entities/units/AIController.java @@ -256,10 +256,10 @@ public class AIController implements UnitController{ } public void moveTo(Position target, float circleLength, float smooth){ - moveTo(target, circleLength, smooth, unit.isFlying()); + moveTo(target, circleLength, smooth, unit.isFlying(), null); } - public void moveTo(Position target, float circleLength, float smooth, boolean keepDistance){ + public void moveTo(Position target, float circleLength, float smooth, boolean keepDistance, @Nullable Vec2 offset){ if(target == null) return; vec.set(target).sub(unit); @@ -267,6 +267,7 @@ public class AIController implements UnitController{ float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / smooth, -1f, 1f); vec.setLength(unit.speed() * length); + if(length < -0.5f){ if(keepDistance){ vec.rotate(180f); @@ -277,6 +278,11 @@ public class AIController implements UnitController{ vec.setZero(); } + if(offset != null){ + vec.add(offset); + vec.setLength(unit.speed() * length); + } + //do not move when infinite vectors are used. if(vec.isNaN() || vec.isInfinite()) return; diff --git a/gradle.properties b/gradle.properties index 1581b2309d..dcdee35506 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,4 +24,4 @@ android.useAndroidX=true #used for slow jitpack builds; TODO see if this actually works org.gradle.internal.http.socketTimeout=100000 org.gradle.internal.http.connectionTimeout=100000 -archash=6f8e68b7db +archash=ab33b65395