Simple enemy rebuilding AI + fleeing

This commit is contained in:
Anuken
2022-02-18 17:29:06 -05:00
parent bc8842d0d7
commit 0c0adea2a4
10 changed files with 104 additions and 16 deletions

View File

@@ -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){

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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.*;

View File

@@ -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. */

View File

@@ -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. */

View File

@@ -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);