Unit cargo transport system

This commit is contained in:
Anuken
2021-11-29 20:06:49 -05:00
parent 635027bb81
commit 6483d924af
32 changed files with 631 additions and 115 deletions

View File

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

View File

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

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