Unit cargo transport system
This commit is contained in:
@@ -15,8 +15,6 @@ import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
/** Class used for indexing special target blocks for AI. */
|
||||
@@ -37,7 +35,7 @@ public class BlockIndexer{
|
||||
/** Stores teams that are present here as tiles. */
|
||||
private Seq<Team> activeTeams = new Seq<>(Team.class);
|
||||
/** Maps teams to a map of flagged tiles by flag. */
|
||||
private TileArray[][] flagMap = new TileArray[Team.all.length][BlockFlag.all.length];
|
||||
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;
|
||||
|
||||
@@ -61,7 +59,7 @@ public class BlockIndexer{
|
||||
|
||||
Events.on(WorldLoadEvent.class, event -> {
|
||||
damagedTiles = new Seq[Team.all.length];
|
||||
flagMap = new TileArray[Team.all.length][BlockFlag.all.length];
|
||||
flagMap = new Seq[Team.all.length][BlockFlag.all.length];
|
||||
activeTeams = new Seq<>(Team.class);
|
||||
|
||||
clearFlags();
|
||||
@@ -105,9 +103,9 @@ public class BlockIndexer{
|
||||
var flags = tile.block().flags;
|
||||
var data = team.data();
|
||||
|
||||
if(flags.size() > 0){
|
||||
for(BlockFlag flag : flags){
|
||||
getFlagged(team)[flag.ordinal()].remove(tile);
|
||||
if(flags.size > 0){
|
||||
for(BlockFlag flag : flags.array){
|
||||
getFlagged(team)[flag.ordinal()].remove(build);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,12 +164,12 @@ public class BlockIndexer{
|
||||
private void clearFlags(){
|
||||
for(int i = 0; i < flagMap.length; i++){
|
||||
for(int j = 0; j < BlockFlag.all.length; j++){
|
||||
flagMap[i][j] = new TileArray();
|
||||
flagMap[i][j] = new Seq();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TileArray[] getFlagged(Team team){
|
||||
private Seq<Building>[] getFlagged(Team team){
|
||||
return flagMap[team.id];
|
||||
}
|
||||
|
||||
@@ -190,13 +188,13 @@ public class BlockIndexer{
|
||||
}
|
||||
|
||||
/** Get all allied blocks with a flag. */
|
||||
public TileArray getAllied(Team team, BlockFlag type){
|
||||
public Seq<Building> getFlagged(Team team, BlockFlag type){
|
||||
return flagMap[team.id][type.ordinal()];
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Tile findClosestFlag(float x, float y, Team team, BlockFlag flag){
|
||||
return Geometry.findClosest(x, y, getAllied(team, flag));
|
||||
public Building findClosestFlag(float x, float y, Team team, BlockFlag flag){
|
||||
return Geometry.findClosest(x, y, getFlagged(team, flag));
|
||||
}
|
||||
|
||||
public boolean eachBlock(Teamc team, float range, Boolf<Building> pred, Cons<Building> cons){
|
||||
@@ -239,34 +237,30 @@ public class BlockIndexer{
|
||||
}
|
||||
|
||||
/** Get all enemy blocks with a flag. */
|
||||
public Seq<Tile> getEnemy(Team team, BlockFlag type){
|
||||
returnArray.clear();
|
||||
public Seq<Building> getEnemy(Team team, BlockFlag type){
|
||||
breturnArray.clear();
|
||||
Seq<TeamData> data = state.teams.present;
|
||||
//when team data is not initialized, scan through every team. this is terrible
|
||||
if(data.isEmpty()){
|
||||
for(Team enemy : Team.all){
|
||||
if(enemy == team) continue;
|
||||
TileArray set = getFlagged(enemy)[type.ordinal()];
|
||||
var set = getFlagged(enemy)[type.ordinal()];
|
||||
if(set != null){
|
||||
for(Tile tile : set){
|
||||
returnArray.add(tile);
|
||||
}
|
||||
breturnArray.addAll(set);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
for(int i = 0; i < data.size; i++){
|
||||
Team enemy = data.items[i].team;
|
||||
if(enemy == team) continue;
|
||||
TileArray set = getFlagged(enemy)[type.ordinal()];
|
||||
var set = getFlagged(enemy)[type.ordinal()];
|
||||
if(set != null){
|
||||
for(Tile tile : set){
|
||||
returnArray.add(tile);
|
||||
}
|
||||
breturnArray.addAll(set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return returnArray;
|
||||
return breturnArray;
|
||||
}
|
||||
|
||||
public void notifyBuildHealed(Building build){
|
||||
@@ -400,16 +394,12 @@ public class BlockIndexer{
|
||||
//only process entity changes with centered tiles
|
||||
if(tile.isCenter() && tile.build != null){
|
||||
var data = team.data();
|
||||
if(tile.block().flags.size() > 0 && tile.isCenter()){
|
||||
TileArray[] map = getFlagged(team);
|
||||
|
||||
for(BlockFlag flag : tile.block().flags){
|
||||
if(tile.block().flags.size > 0 && tile.isCenter()){
|
||||
var map = getFlagged(team);
|
||||
|
||||
TileArray arr = map[flag.ordinal()];
|
||||
|
||||
arr.add(tile);
|
||||
|
||||
map[flag.ordinal()] = arr;
|
||||
for(BlockFlag flag : tile.block().flags.array){
|
||||
map[flag.ordinal()].add(tile.build);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,34 +426,4 @@ public class BlockIndexer{
|
||||
//bounds checks only needed in very specific scenarios
|
||||
if(tile.blockID() < blocksPresent.length) blocksPresent[tile.blockID()] = true;
|
||||
}
|
||||
|
||||
public static class TileArray implements Iterable<Tile>{
|
||||
Seq<Tile> tiles = new Seq<>(false, 16);
|
||||
IntSet contained = new IntSet();
|
||||
|
||||
public void add(Tile tile){
|
||||
if(contained.add(tile.pos())){
|
||||
tiles.add(tile);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(Tile tile){
|
||||
if(contained.remove(tile.pos())){
|
||||
tiles.remove(tile);
|
||||
}
|
||||
}
|
||||
|
||||
public int size(){
|
||||
return tiles.size;
|
||||
}
|
||||
|
||||
public Tile first(){
|
||||
return tiles.first();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Tile> iterator(){
|
||||
return tiles.iterator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,7 +394,7 @@ public class Pathfinder implements Runnable{
|
||||
public static class EnemyCoreField extends Flowfield{
|
||||
@Override
|
||||
protected void getPositions(IntSeq out){
|
||||
for(Tile other : indexer.getEnemy(team, BlockFlag.core)){
|
||||
for(Building other : indexer.getEnemy(team, BlockFlag.core)){
|
||||
out.add(other.pos());
|
||||
}
|
||||
|
||||
@@ -410,7 +410,7 @@ public class Pathfinder implements Runnable{
|
||||
public static class RallyField extends Flowfield{
|
||||
@Override
|
||||
protected void getPositions(IntSeq out){
|
||||
for(Tile other : indexer.getAllied(team, BlockFlag.rally)){
|
||||
for(Building other : indexer.getFlagged(team, BlockFlag.rally)){
|
||||
out.add(other.pos());
|
||||
}
|
||||
}
|
||||
|
||||
179
core/src/mindustry/ai/types/CargoAI.java
Normal file
179
core/src/mindustry/ai/types/CargoAI.java
Normal file
@@ -0,0 +1,179 @@
|
||||
package mindustry.ai.types;
|
||||
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.blocks.units.UnitCargoUnloadPoint.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class CargoAI extends AIController{
|
||||
static Seq<Item> orderedItems = new Seq<>();
|
||||
static Seq<UnitCargoUnloadPointBuild> targets = new Seq<>();
|
||||
|
||||
public static float emptyWaitTime = 60f * 2f, dropSpacing = 60f * 1.5f;
|
||||
public static float transferRange = 20f, moveRange = 6f, moveSmoothing = 20f;
|
||||
|
||||
public @Nullable UnitCargoUnloadPointBuild unloadTarget;
|
||||
public @Nullable Item itemTarget;
|
||||
public float noDestTimer = 0f;
|
||||
public int targetIndex = 0;
|
||||
|
||||
@Override
|
||||
public void updateMovement(){
|
||||
if(!(unit instanceof BuildingTetherc tether) || tether.building() == null) return;
|
||||
|
||||
var build = tether.building();
|
||||
|
||||
//empty, approach the loader, even if there's nothing to pick up (units hanging around doing nothing looks bad)
|
||||
if(!unit.hasItem()){
|
||||
moveTo(build, moveRange, moveSmoothing);
|
||||
|
||||
//check if ready to pick up
|
||||
if(build.items.any() && unit.within(build, transferRange)){
|
||||
if(retarget()){
|
||||
findAnyTarget(build);
|
||||
|
||||
//target has been found, grab items and go
|
||||
if(unloadTarget != null){
|
||||
Call.takeItems(build, itemTarget, Math.min(unit.type.itemCapacity, build.items.get(itemTarget)), unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{ //the unit has an item, deposit it somewhere.
|
||||
|
||||
//there may be no current target, try to find one
|
||||
if(unloadTarget == null){
|
||||
if(retarget()){
|
||||
findDropTarget(unit.item(), 0, null);
|
||||
|
||||
//if there is not even a single place to unload, dump items.
|
||||
if(unloadTarget == null){
|
||||
unit.clearItem();
|
||||
}
|
||||
}
|
||||
}else{
|
||||
moveTo(unloadTarget, moveRange, moveSmoothing);
|
||||
|
||||
//deposit in bursts, unloading can take a while
|
||||
if(unit.within(unloadTarget, transferRange) && timer.get(timerTarget2, dropSpacing)){
|
||||
int max = unloadTarget.acceptStack(unit.item(), unit.stack.amount, unit);
|
||||
|
||||
//deposit items when it's possible
|
||||
if(max > 0){
|
||||
noDestTimer = 0f;
|
||||
Call.transferItemTo(unit, unit.item(), max, unit.x, unit.y, unloadTarget);
|
||||
|
||||
//try the next target later
|
||||
if(!unit.hasItem()){
|
||||
targetIndex ++;
|
||||
}
|
||||
}else if((noDestTimer += dropSpacing) >= emptyWaitTime){
|
||||
//oh no, it's out of space - wait for a while, and if nothing changes, try the next destination
|
||||
|
||||
//next targeting attempt will try the next destination point
|
||||
targetIndex = findDropTarget(unit.item(), targetIndex, unloadTarget) + 1;
|
||||
|
||||
//nothing found at all, clear item
|
||||
if(unloadTarget == null){
|
||||
unit.clearItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** find target for the unit's current item */
|
||||
public int findDropTarget(Item item, int offset, UnitCargoUnloadPointBuild ignore){
|
||||
unloadTarget = null;
|
||||
itemTarget = item;
|
||||
|
||||
//autocast for convenience... I know all of these must be cargo unload points anyway
|
||||
targets.selectFrom((Seq<UnitCargoUnloadPointBuild>)(Seq)Vars.indexer.getFlagged(unit.team, BlockFlag.unitCargoUnloadPoint), u -> u.item == item);
|
||||
|
||||
if(targets.isEmpty()) return 0;
|
||||
|
||||
UnitCargoUnloadPointBuild lastStale = null;
|
||||
|
||||
offset %= targets.size;
|
||||
|
||||
int i = 0;
|
||||
|
||||
for(var target : targets){
|
||||
if(i >= offset && target != ignore){
|
||||
if(target.stale){
|
||||
lastStale = target;
|
||||
}else{
|
||||
unloadTarget = target;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
i ++;
|
||||
}
|
||||
|
||||
//it's still possible that the ignored target may become available at some point, try that, so it doesn't waste items
|
||||
if(ignore != null){
|
||||
unloadTarget = ignore;
|
||||
}else if(lastStale != null){ //a stale target is better than nothing
|
||||
unloadTarget = lastStale;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void findAnyTarget(Building build){
|
||||
unloadTarget = null;
|
||||
itemTarget = null;
|
||||
|
||||
//autocast for convenience... I know all of these must be cargo unload points anyway
|
||||
var baseTargets = (Seq<UnitCargoUnloadPointBuild>)(Seq)Vars.indexer.getFlagged(unit.team, BlockFlag.unitCargoUnloadPoint);
|
||||
|
||||
if(baseTargets.isEmpty()) return;
|
||||
|
||||
orderedItems.size = 0;
|
||||
for(Item item : content.items()){
|
||||
if(build.items.get(item) > 0){
|
||||
orderedItems.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
//sort by most items in descending order, and try each one.
|
||||
orderedItems.sort(i -> -build.items.get(i));
|
||||
|
||||
UnitCargoUnloadPointBuild lastStale = null;
|
||||
|
||||
outer:
|
||||
for(Item item : orderedItems){
|
||||
targets.selectFrom(baseTargets, u -> u.item == item);
|
||||
|
||||
if(targets.size > 0) itemTarget = item;
|
||||
|
||||
for(int i = 0; i < targets.size; i ++){
|
||||
var target = targets.get((i + targetIndex) % targets.size);
|
||||
|
||||
lastStale = target;
|
||||
|
||||
if(!target.stale){
|
||||
unloadTarget = target;
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if the only thing that was found was a "stale" target, at least try that...
|
||||
if(unloadTarget == null && lastStale != null){
|
||||
unloadTarget = lastStale;
|
||||
}
|
||||
}
|
||||
|
||||
void sortTargets(Seq<UnitCargoUnloadPointBuild> targets){
|
||||
//find sort by "most desirable" first
|
||||
targets.sort(Structs.comps(Structs.comparingInt(b -> b.items.total()), Structs.comparingFloat(b -> b.dst2(unit))));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user