Simple enemy rebuilding AI + fleeing
This commit is contained in:
@@ -57,9 +57,9 @@ public class BaseAI{
|
||||
wallType = BaseGenerator.getDifficultyWall(1, data.team.rules().aiTier / 0.8f);
|
||||
}
|
||||
|
||||
if(data.team.rules().aiCoreSpawn && timer.get(timerSpawn, 60 * 2.5f) && data.hasCore()){
|
||||
if(data.team.rules().aiCoreSpawn && timer.get(timerSpawn, 60 * 6f) && data.hasCore()){
|
||||
CoreBlock block = (CoreBlock)data.core().block;
|
||||
int coreUnits = Groups.unit.count(u -> u.team == data.team && u.type == block.unitType);
|
||||
int coreUnits = data.countType(block.unitType);
|
||||
|
||||
//create AI core unit(s)
|
||||
if(!state.isEditor() && coreUnits < data.cores.size){
|
||||
|
||||
@@ -11,6 +11,7 @@ import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Teams.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.meta.*;
|
||||
@@ -38,9 +39,6 @@ public class BlockIndexer{
|
||||
private Seq<Building>[][] flagMap = new Seq[Team.all.length][BlockFlag.all.length];
|
||||
/** Counts whether a certain floor is present in the world upon load. */
|
||||
private boolean[] blocksPresent;
|
||||
|
||||
/** Array used for returning and reusing. */
|
||||
private Seq<Tile> returnArray = new Seq<>();
|
||||
/** Array used for returning and reusing. */
|
||||
private Seq<Building> breturnArray = new Seq<>(Building.class);
|
||||
|
||||
@@ -114,6 +112,11 @@ public class BlockIndexer{
|
||||
data.buildings.remove(build);
|
||||
}
|
||||
|
||||
//remove indexed turret
|
||||
if(data.turrets != null && build.block.attacks){
|
||||
data.turrets.remove(build);
|
||||
}
|
||||
|
||||
//is no longer registered
|
||||
build.wasDamaged = false;
|
||||
|
||||
@@ -442,6 +445,14 @@ public class BlockIndexer{
|
||||
}
|
||||
data.buildings.insert(tile.build);
|
||||
|
||||
if(tile.block().attacks && tile.build instanceof Ranged){
|
||||
if(data.turrets == null){
|
||||
data.turrets = new TurretQuadtree(new Rect(0, 0, world.unitWidth(), world.unitHeight()));
|
||||
}
|
||||
|
||||
data.turrets.insert(tile.build);
|
||||
}
|
||||
|
||||
notifyBuildDamaged(tile.build);
|
||||
}
|
||||
|
||||
@@ -452,4 +463,21 @@ public class BlockIndexer{
|
||||
//bounds checks only needed in very specific scenarios
|
||||
if(tile.blockID() < blocksPresent.length) blocksPresent[tile.blockID()] = true;
|
||||
}
|
||||
|
||||
static class TurretQuadtree extends QuadTree<Building>{
|
||||
|
||||
public TurretQuadtree(Rect bounds){
|
||||
super(bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hitbox(Building build){
|
||||
tmp.setCentered(build.x, build.y, ((Ranged)build).range() * 2f);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QuadTree<Building> newChild(Rect rect){
|
||||
return new TurretQuadtree(rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.Teams.*;
|
||||
@@ -15,6 +16,7 @@ import mindustry.graphics.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.defense.turrets.Turret.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
@@ -24,7 +26,7 @@ public class RtsAI{
|
||||
static final IntSet used = new IntSet();
|
||||
static final IntSet assignedTargets = new IntSet();
|
||||
static final float squadRadius = 120f;
|
||||
static final int timeUpdate = 0;
|
||||
static final int timeUpdate = 0, timerSpawn = 1;
|
||||
static final float minWeight = 0.9f;
|
||||
|
||||
//in order of priority??
|
||||
@@ -32,7 +34,7 @@ public class RtsAI{
|
||||
static final ObjectFloatMap<Building> weights = new ObjectFloatMap<>();
|
||||
static final int minSquadSize = 4;
|
||||
//TODO max squad size
|
||||
static final boolean debug = true;
|
||||
static final boolean debug = OS.hasProp("mindustry.debug");
|
||||
|
||||
final Interval timer = new Interval(10);
|
||||
final TeamData data;
|
||||
@@ -77,6 +79,22 @@ public class RtsAI{
|
||||
public void update(){
|
||||
if(timer.get(timeUpdate, 60f * 2f)){
|
||||
assignSquads();
|
||||
checkBuilding();
|
||||
}
|
||||
}
|
||||
|
||||
void checkBuilding(){
|
||||
if(data.team.rules().aiCoreSpawn && timer.get(timerSpawn, 60 * 7f) && data.hasCore()){
|
||||
CoreBlock block = (CoreBlock)data.core().block;
|
||||
int coreUnits = data.countType(block.unitType);
|
||||
|
||||
//create AI core unit(s) at random cores
|
||||
if(coreUnits < data.cores.size){
|
||||
Unit unit = block.unitType.create(data.team);
|
||||
unit.set(data.cores.random());
|
||||
unit.add();
|
||||
Fx.spawn.at(unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,15 +13,26 @@ import mindustry.world.blocks.ConstructBlock.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class BuilderAI extends AIController{
|
||||
public static float buildRadius = 1500, retreatDst = 110f, fleeRange = 370f, retreatDelay = Time.toSeconds * 2f;
|
||||
public static float buildRadius = 1500, retreatDst = 110f, retreatDelay = Time.toSeconds * 2f;
|
||||
|
||||
public @Nullable Unit following;
|
||||
public @Nullable Teamc enemy;
|
||||
public @Nullable BlockPlan lastPlan;
|
||||
|
||||
public float fleeRange = 370f;
|
||||
public boolean alwaysFlee;
|
||||
|
||||
boolean found = false;
|
||||
float retreatTimer;
|
||||
|
||||
public BuilderAI(boolean alwaysFlee, float fleeRange){
|
||||
this.alwaysFlee = alwaysFlee;
|
||||
this.fleeRange = fleeRange;
|
||||
}
|
||||
|
||||
public BuilderAI(){
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMovement(){
|
||||
|
||||
@@ -46,15 +57,16 @@ public class BuilderAI extends AIController{
|
||||
unit.plans.clear();
|
||||
unit.plans.addFirst(following.buildPlan());
|
||||
lastPlan = null;
|
||||
}else if(unit.buildPlan() == null){
|
||||
}else if(unit.buildPlan() == null || alwaysFlee){
|
||||
//not following anyone or building
|
||||
if(timer.get(timerTarget4, 40)){
|
||||
enemy = target(unit.x, unit.y, fleeRange, true, true);
|
||||
}
|
||||
|
||||
//fly away from enemy when not doing anything, but only after a delay
|
||||
if((retreatTimer += Time.delta) >= retreatDelay){
|
||||
if((retreatTimer += Time.delta) >= retreatDelay || alwaysFlee){
|
||||
if(enemy != null){
|
||||
unit.clearBuilding();
|
||||
var core = unit.closestCore();
|
||||
if(core != null && !unit.within(core, retreatDst)){
|
||||
moveTo(core, retreatDst);
|
||||
@@ -64,7 +76,7 @@ public class BuilderAI extends AIController{
|
||||
}
|
||||
|
||||
if(unit.buildPlan() != null){
|
||||
retreatTimer = 0f;
|
||||
if(!alwaysFlee) retreatTimer = 0f;
|
||||
//approach request if building
|
||||
BuildPlan req = unit.buildPlan();
|
||||
|
||||
@@ -131,7 +143,7 @@ public class BuilderAI extends AIController{
|
||||
//check if it's already been placed
|
||||
if(world.tile(block.x, block.y) != null && world.tile(block.x, block.y).block().id == block.block){
|
||||
blocks.removeFirst();
|
||||
}else if(Build.validPlace(content.block(block.block), unit.team(), block.x, block.y, block.rotation)){ //it's valid
|
||||
}else if(Build.validPlace(content.block(block.block), unit.team(), block.x, block.y, block.rotation) && (!alwaysFlee || !nearEnemy(block.x, block.y))){ //it's valid
|
||||
lastPlan = block;
|
||||
//add build request
|
||||
unit.addBuild(new BuildPlan(block.x, block.y, block.rotation, content.block(block.block), block.config));
|
||||
@@ -145,6 +157,10 @@ public class BuilderAI extends AIController{
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean nearEnemy(int x, int y){
|
||||
return Units.nearEnemy(unit.team, x * tilesize - fleeRange/2f, y * tilesize - fleeRange/2f, fleeRange, fleeRange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AIController fallback(){
|
||||
return unit.type.flying ? new FlyingAI() : new GroundAI();
|
||||
@@ -152,7 +168,7 @@ public class BuilderAI extends AIController{
|
||||
|
||||
@Override
|
||||
public boolean useFallback(){
|
||||
return state.rules.waves && unit.team == state.rules.waveTeam && !unit.team.rules().ai;
|
||||
return state.rules.waves && unit.team == state.rules.waveTeam && !unit.team.rules().ai && !unit.team.rules().rtsAi;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3156,10 +3156,12 @@ public class UnitTypes{
|
||||
//endregion
|
||||
//region erekir - core
|
||||
|
||||
float coreFleeRange = 500f;
|
||||
|
||||
//TODO bad name
|
||||
evoke = new ErekirUnitType("evoke"){{
|
||||
coreUnitDock = true;
|
||||
aiController = BuilderAI::new;
|
||||
defaultController = u -> new BuilderAI(true, coreFleeRange);
|
||||
isCounted = false;
|
||||
envDisabled = 0;
|
||||
|
||||
@@ -3216,7 +3218,7 @@ public class UnitTypes{
|
||||
|
||||
incite = new ErekirUnitType("incite"){{
|
||||
coreUnitDock = true;
|
||||
aiController = BuilderAI::new;
|
||||
defaultController = u -> new BuilderAI(true, coreFleeRange);
|
||||
isCounted = false;
|
||||
envDisabled = 0;
|
||||
|
||||
@@ -3285,7 +3287,7 @@ public class UnitTypes{
|
||||
|
||||
emanate = new ErekirUnitType("emanate"){{
|
||||
coreUnitDock = true;
|
||||
aiController = BuilderAI::new;
|
||||
defaultController = u -> new BuilderAI(true, coreFleeRange);
|
||||
isCounted = false;
|
||||
envDisabled = 0;
|
||||
|
||||
|
||||
@@ -457,6 +457,23 @@ public class Units{
|
||||
nearbyEnemies(team, rect.x, rect.y, rect.width, rect.height, cons);
|
||||
}
|
||||
|
||||
/** @return whether there is an enemy in this rectangle. */
|
||||
public static boolean nearEnemy(Team team, float x, float y, float width, float height){
|
||||
Seq<TeamData> data = state.teams.present;
|
||||
for(int i = 0; i < data.size; i++){
|
||||
var other = data.items[i];
|
||||
if(other.team != team){
|
||||
if(other.tree().any(x, y, width, height)){
|
||||
return true;
|
||||
}
|
||||
if(other.turrets != null && other.turrets.any(x, y, width, height)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public interface Sortf{
|
||||
float cost(Unit unit, float x, float y);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ package mindustry.entities.units;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Teams.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
@@ -237,6 +237,8 @@ public class Teams{
|
||||
|
||||
/** Quadtree for all buildings of this team. Null if not active. */
|
||||
public @Nullable QuadTree<Building> buildings;
|
||||
/** Turrets by range. Null if not active. */
|
||||
public @Nullable QuadTree<Building> turrets;
|
||||
/** Current unit cap. Do not modify externally. */
|
||||
public int unitCap;
|
||||
/** Total unit count. */
|
||||
|
||||
@@ -210,6 +210,8 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
public boolean hasColor = false;
|
||||
/** Whether units target this block. */
|
||||
public boolean targetable = true;
|
||||
/** If true, this block attacks and is considered a turret in the indexer. Building must implement Ranged. */
|
||||
public boolean attacks = false;
|
||||
/** If true, this block is mending-related and can be suppressed with special units/missiles. */
|
||||
public boolean suppressable = false;
|
||||
/** Whether the overdrive core has any effect on this block. */
|
||||
|
||||
@@ -32,6 +32,7 @@ public class BaseTurret extends Block{
|
||||
update = true;
|
||||
solid = true;
|
||||
outlineIcon = true;
|
||||
attacks = true;
|
||||
priority = TargetPriority.turret;
|
||||
group = BlockGroup.turrets;
|
||||
flags = EnumSet.of(BlockFlag.turret);
|
||||
|
||||
Reference in New Issue
Block a user