Unit cargo transport system
@ -9,6 +9,7 @@ corvus=24
|
||||
flare=3
|
||||
gamma=31
|
||||
mace=4
|
||||
manifold=36
|
||||
mega=5
|
||||
mindustry.entities.comp.BuildingComp=6
|
||||
mindustry.entities.comp.BulletComp=7
|
||||
|
1
annotations/src/main/resources/revisions/manifold/0.json
Normal file
@ -0,0 +1 @@
|
||||
{fields:[{name:ammo,type:float},{name:building,type:Building},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}
|
Before Width: | Height: | Size: 710 B |
Before Width: | Height: | Size: 1.1 KiB |
BIN
core/assets-raw/sprites/blocks/units/unit-cargo-loader.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 193 B |
BIN
core/assets-raw/sprites/blocks/units/unit-cargo-unload-point.png
Normal file
After Width: | Height: | Size: 798 B |
BIN
core/assets-raw/sprites/units/manifold-cell.png
Normal file
After Width: | Height: | Size: 434 B |
BIN
core/assets-raw/sprites/units/manifold.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
@ -462,3 +462,6 @@
|
||||
63244=reinforced-vault|block-reinforced-vault-ui
|
||||
63243=nitrogen|liquid-nitrogen-ui
|
||||
63242=atmospheric-concentrator|block-atmospheric-concentrator-ui
|
||||
63241=unit-cargo-loader|block-unit-cargo-loader-ui
|
||||
63240=unit-cargo-unload-point|block-unit-cargo-unload-point-ui
|
||||
63239=manifold|unit-manifold-ui
|
||||
|
@ -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
@ -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))));
|
||||
}
|
||||
}
|
@ -83,6 +83,8 @@ public class Blocks{
|
||||
duct, ductRouter, overflowDuct, ductBridge, ductUnloader,
|
||||
surgeConveyor, surgeRouter,
|
||||
|
||||
unitCargoLoader, unitCargoUnloadPoint,
|
||||
|
||||
//liquid
|
||||
mechanicalPump, rotaryPump, impulsePump, conduit, pulseConduit, platedConduit, liquidRouter, liquidContainer, liquidTank, liquidJunction, bridgeConduit, phaseConduit,
|
||||
|
||||
@ -1554,7 +1556,7 @@ public class Blocks{
|
||||
consumes.power(1.75f);
|
||||
}};
|
||||
|
||||
//special transport blocks
|
||||
//erekir transport blocks
|
||||
|
||||
duct = new Duct("duct"){{
|
||||
requirements(Category.distribution, with(Items.graphite, 2));
|
||||
@ -1608,6 +1610,24 @@ public class Blocks{
|
||||
consumes.power(3f / 60f);
|
||||
}};
|
||||
|
||||
unitCargoLoader = new UnitCargoLoader("unit-cargo-loader"){{
|
||||
requirements(Category.distribution, with(Items.silicon, 80, Items.phaseFabric, 60, Items.carbide, 50, Items.oxide, 40));
|
||||
|
||||
size = 3;
|
||||
|
||||
consumes.power(4f / 60f);
|
||||
|
||||
itemCapacity = 200;
|
||||
}};
|
||||
|
||||
unitCargoUnloadPoint = new UnitCargoUnloadPoint("unit-cargo-unload-point"){{
|
||||
requirements(Category.distribution, with(Items.silicon, 60, Items.thorium, 80));
|
||||
|
||||
size = 2;
|
||||
|
||||
itemCapacity = 100;
|
||||
}};
|
||||
|
||||
//endregion
|
||||
//region liquid
|
||||
|
||||
|
@ -67,6 +67,9 @@ public class UnitTypes{
|
||||
//special block unit type
|
||||
public static @EntityDef({Unitc.class, BlockUnitc.class}) UnitType block;
|
||||
|
||||
//transport
|
||||
public static @EntityDef({Unitc.class, BuildingTetherc.class}) UnitType manifold;
|
||||
|
||||
//endregion
|
||||
|
||||
//region neoplasm
|
||||
@ -2601,7 +2604,7 @@ public class UnitTypes{
|
||||
//TODO emanate (+ better names)
|
||||
|
||||
//endregion
|
||||
//region internal
|
||||
//region internal + special
|
||||
|
||||
block = new UnitType("block"){{
|
||||
speed = 0f;
|
||||
@ -2613,6 +2616,32 @@ public class UnitTypes{
|
||||
hidden = true;
|
||||
}};
|
||||
|
||||
manifold = new UnitType("manifold"){{
|
||||
defaultController = CargoAI::new;
|
||||
isCounted = false;
|
||||
allowedInPayloads = false;
|
||||
logicControllable = false;
|
||||
envDisabled = 0;
|
||||
|
||||
outlineColor = Pal.darkOutline;
|
||||
lowAltitude = false;
|
||||
flying = true;
|
||||
drag = 0.06f;
|
||||
speed = 2f;
|
||||
rotateSpeed = 9f;
|
||||
accel = 0.1f;
|
||||
itemCapacity = 60;
|
||||
health = 200f;
|
||||
hitSize = 11f;
|
||||
commandLimit = 0;
|
||||
engineSize = 2.3f;
|
||||
engineOffset = 6.5f;
|
||||
|
||||
setEnginesMirror(
|
||||
new UnitEngine(24 / 4f, -24 / 4f, 2.3f, 315f)
|
||||
);
|
||||
}};
|
||||
|
||||
//endregion
|
||||
//region neoplasm
|
||||
|
||||
|
@ -9,10 +9,13 @@ import arc.audio.*;
|
||||
import arc.files.*;
|
||||
import arc.struct.*;
|
||||
import mindustry.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
/** Handles files in a modded context. */
|
||||
public class FileTree implements FileHandleResolver{
|
||||
private ObjectMap<String, Fi> files = new ObjectMap<>();
|
||||
private ObjectMap<String, Sound> loadedSounds = new ObjectMap<>();
|
||||
private ObjectMap<String, Music> loadedMusic = new ObjectMap<>();
|
||||
|
||||
public void addFile(String path, Fi f){
|
||||
files.put(path, f);
|
||||
@ -46,29 +49,41 @@ public class FileTree implements FileHandleResolver{
|
||||
return get(fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a sound by name from the sounds/ folder. OGG and MP3 are supported; the extension is automatically added to the end of the file name.
|
||||
* Results are cached; consecutive calls to this method with the same name will return the same sound instance.
|
||||
* */
|
||||
public Sound loadSound(String soundName){
|
||||
if(Vars.headless) return new Sound();
|
||||
if(Vars.headless) return Sounds.none;
|
||||
|
||||
String name = "sounds/" + soundName;
|
||||
String path = Vars.tree.get(name + ".ogg").exists() ? name + ".ogg" : name + ".mp3";
|
||||
return loadedSounds.get(soundName, () -> {
|
||||
String name = "sounds/" + soundName;
|
||||
String path = Vars.tree.get(name + ".ogg").exists() ? name + ".ogg" : name + ".mp3";
|
||||
|
||||
var sound = new Sound();
|
||||
AssetDescriptor<?> desc = Core.assets.load(path, Sound.class, new SoundParameter(sound));
|
||||
desc.errored = Throwable::printStackTrace;
|
||||
var sound = new Sound();
|
||||
AssetDescriptor<?> desc = Core.assets.load(path, Sound.class, new SoundParameter(sound));
|
||||
desc.errored = Throwable::printStackTrace;
|
||||
|
||||
return sound;
|
||||
return sound;
|
||||
});
|
||||
}
|
||||
|
||||
public Music loadMusic(String soundName){
|
||||
/**
|
||||
* Loads a music file by name from the music/ folder. OGG and MP3 are supported; the extension is automatically added to the end of the file name.
|
||||
* Results are cached; consecutive calls to this method with the same name will return the same music instance.
|
||||
* */
|
||||
public Music loadMusic(String musicName){
|
||||
if(Vars.headless) return new Music();
|
||||
|
||||
String name = "music/" + soundName;
|
||||
String path = Vars.tree.get(name + ".ogg").exists() ? name + ".ogg" : name + ".mp3";
|
||||
return loadedMusic.get(musicName, () -> {
|
||||
String name = "music/" + musicName;
|
||||
String path = Vars.tree.get(name + ".ogg").exists() ? name + ".ogg" : name + ".mp3";
|
||||
|
||||
var music = new Music();
|
||||
AssetDescriptor<?> desc = Core.assets.load(path, Music.class, new MusicParameter(music));
|
||||
desc.errored = Throwable::printStackTrace;
|
||||
var music = new Music();
|
||||
AssetDescriptor<?> desc = Core.assets.load(path, Music.class, new MusicParameter(music));
|
||||
desc.errored = Throwable::printStackTrace;
|
||||
|
||||
return music;
|
||||
return music;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -785,17 +785,19 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
/** Dumps any item with an accumulator. May dump multiple times per frame. Use with care. */
|
||||
public void dumpAccumulate(){
|
||||
dumpAccumulate(null);
|
||||
public boolean dumpAccumulate(){
|
||||
return dumpAccumulate(null);
|
||||
}
|
||||
|
||||
/** Dumps any item with an accumulator. May dump multiple times per frame. Use with care. */
|
||||
public void dumpAccumulate(Item item){
|
||||
public boolean dumpAccumulate(Item item){
|
||||
boolean res = false;
|
||||
dumpAccum += delta();
|
||||
while(dumpAccum >= 1f){
|
||||
dump(item);
|
||||
res |= dump(item);
|
||||
dumpAccum -=1f;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/** Try dumping any item near the building. */
|
||||
|
@ -1,5 +1,6 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
@ -11,7 +12,7 @@ abstract class BuildingTetherComp implements Unitc{
|
||||
@Import UnitType type;
|
||||
@Import Team team;
|
||||
|
||||
public Building building;
|
||||
public @Nullable Building building;
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
|
@ -173,8 +173,7 @@ public class AIController implements UnitController{
|
||||
|
||||
public Teamc targetFlag(float x, float y, BlockFlag flag, boolean enemy){
|
||||
if(unit.team == Team.derelict) return null;
|
||||
Tile target = Geometry.findClosest(x, y, enemy ? indexer.getEnemy(unit.team, flag) : indexer.getAllied(unit.team, flag));
|
||||
return target == null ? null : target.build;
|
||||
return Geometry.findClosest(x, y, enemy ? indexer.getEnemy(unit.team, flag) : indexer.getFlagged(unit.team, flag));
|
||||
}
|
||||
|
||||
public Teamc target(float x, float y, float range, boolean air, boolean ground){
|
||||
|
@ -193,10 +193,10 @@ public class SectorInfo{
|
||||
stat.mean = Math.min(stat.mean, rawProduction.get(item, ExportStat::new).mean);
|
||||
});
|
||||
|
||||
var pads = indexer.getAllied(state.rules.defaultTeam, BlockFlag.launchPad);
|
||||
var pads = indexer.getFlagged(state.rules.defaultTeam, BlockFlag.launchPad);
|
||||
|
||||
//disable export when launch pads are disabled, or there aren't any active ones
|
||||
if(pads.size() == 0 || !Seq.with(pads).contains(t -> t.build.consValid())){
|
||||
if(pads.size == 0 || !pads.contains(t -> t.consValid())){
|
||||
export.clear();
|
||||
}
|
||||
|
||||
|
@ -273,7 +273,8 @@ public class LExecutor{
|
||||
}
|
||||
}
|
||||
case building -> {
|
||||
res = Geometry.findClosest(unit.x, unit.y, exec.bool(enemy) ? indexer.getEnemy(unit.team, flag) : indexer.getAllied(unit.team, flag));
|
||||
Building b = Geometry.findClosest(unit.x, unit.y, exec.bool(enemy) ? indexer.getEnemy(unit.team, flag) : indexer.getFlagged(unit.team, flag));
|
||||
res = b == null ? null : b.tile;
|
||||
build = true;
|
||||
}
|
||||
case spawn -> {
|
||||
|
@ -82,14 +82,16 @@ public class Scripts implements Disposable{
|
||||
return Vars.tree.get(path, true).readBytes();
|
||||
}
|
||||
|
||||
//kept for backwards compatibility
|
||||
/** @deprecated use Vars.tree.loadSound(soundName) instead */
|
||||
@Deprecated
|
||||
public Sound loadSound(String soundName){
|
||||
return Vars.tree.loadSound(soundName);
|
||||
}
|
||||
|
||||
//kept for backwards compatibility
|
||||
public Music loadMusic(String soundName){
|
||||
return Vars.tree.loadMusic(soundName);
|
||||
/** @deprecated use Vars.tree.loadMusic(musicName) instead */
|
||||
@Deprecated
|
||||
public Music loadMusic(String musicName){
|
||||
return Vars.tree.loadMusic(musicName);
|
||||
}
|
||||
|
||||
/** Ask the user to select a file to read for a certain purpose like "Please upload a sprite" */
|
||||
|
@ -30,7 +30,7 @@ public class BlockInventoryFragment extends Fragment{
|
||||
Table table = new Table();
|
||||
Building tile;
|
||||
float holdTime = 0f, emptyTime;
|
||||
boolean holding;
|
||||
boolean holding, held;
|
||||
float[] shrinkHoldTimes = new float[content.items().size];
|
||||
Item lastItem;
|
||||
|
||||
@ -69,6 +69,20 @@ public class BlockInventoryFragment extends Fragment{
|
||||
tile = null;
|
||||
}
|
||||
|
||||
private void takeItem(int requested){
|
||||
//take everything
|
||||
int amount = Math.min(requested, player.unit().maxAccepted(lastItem));
|
||||
|
||||
if(amount > 0){
|
||||
Call.requestItem(player, tile, lastItem, amount);
|
||||
holding = false;
|
||||
holdTime = 0f;
|
||||
held = true;
|
||||
|
||||
if(net.client()) Events.fire(new WithdrawEvent(tile, player, lastItem, amount));
|
||||
}
|
||||
}
|
||||
|
||||
private void rebuild(boolean actions){
|
||||
IntSet container = new IntSet();
|
||||
|
||||
@ -90,17 +104,11 @@ public class BlockInventoryFragment extends Fragment{
|
||||
emptyTime = 0f;
|
||||
}
|
||||
|
||||
if(holding && lastItem != null){
|
||||
holdTime += Time.delta;
|
||||
if(holding && lastItem != null && (holdTime += Time.delta) >= holdWithdraw){
|
||||
holdTime = 0f;
|
||||
|
||||
if(holdTime >= holdWithdraw){
|
||||
int amount = Math.min(tile.items.get(lastItem), player.unit().maxAccepted(lastItem));
|
||||
Call.requestItem(player, tile, lastItem, amount);
|
||||
holding = false;
|
||||
holdTime = 0f;
|
||||
|
||||
if(net.client()) Events.fire(new WithdrawEvent(tile, player, lastItem, amount));
|
||||
}
|
||||
//take one when held
|
||||
takeItem(1);
|
||||
}
|
||||
|
||||
updateTablePosition();
|
||||
@ -155,23 +163,33 @@ public class BlockInventoryFragment extends Fragment{
|
||||
});
|
||||
image.addListener(l);
|
||||
|
||||
image.addListener(new InputListener(){
|
||||
Boolp validClick = () -> !(!canPick.get() || tile == null || !tile.isValid() || tile.items == null || !tile.items.has(item));
|
||||
|
||||
image.addListener(new ClickListener(){
|
||||
|
||||
@Override
|
||||
public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){
|
||||
if(!canPick.get() || tile == null || !tile.isValid() || tile.items == null || !tile.items.has(item)) return false;
|
||||
int amount = Math.min(1, player.unit().maxAccepted(item));
|
||||
if(amount > 0){
|
||||
Call.requestItem(player, tile, item, amount);
|
||||
held = false;
|
||||
if(validClick.get()){
|
||||
lastItem = item;
|
||||
holding = true;
|
||||
holdTime = 0f;
|
||||
if(net.client()) Events.fire(new WithdrawEvent(tile, player, item, amount));
|
||||
}
|
||||
return true;
|
||||
|
||||
return super.touchDown(event, x, y, pointer, button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clicked(InputEvent event, float x, float y){
|
||||
if(!validClick.get() || held) return;
|
||||
|
||||
//take all
|
||||
takeItem(tile.items.get(lastItem = item));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode button){
|
||||
super.touchUp(event, x, y, pointer, button);
|
||||
|
||||
holding = false;
|
||||
lastItem = null;
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ public class HintsFragment extends Fragment{
|
||||
command(() -> state.rules.defaultTeam.data().units.size > 3 && !net.active(), () -> player.unit().isCommanding()),
|
||||
payloadPickup(() -> !player.unit().dead && player.unit() instanceof Payloadc p && p.payloads().isEmpty(), () -> player.unit() instanceof Payloadc p && p.payloads().any()),
|
||||
payloadDrop(() -> !player.unit().dead && player.unit() instanceof Payloadc p && p.payloads().any(), () -> player.unit() instanceof Payloadc p && p.payloads().isEmpty()),
|
||||
waveFire(() -> Groups.fire.size() > 0 && Blocks.wave.unlockedNow(), () -> indexer.getAllied(state.rules.defaultTeam, BlockFlag.extinguisher).size() > 0),
|
||||
waveFire(() -> Groups.fire.size() > 0 && Blocks.wave.unlockedNow(), () -> indexer.getFlagged(state.rules.defaultTeam, BlockFlag.extinguisher).size > 0),
|
||||
generator(() -> control.input.block == Blocks.combustionGenerator, () -> ui.hints.placedBlocks.contains(Blocks.combustionGenerator)),
|
||||
guardian(() -> state.boss() != null && state.boss().armor >= 4, () -> state.boss() == null),
|
||||
coreUpgrade(() -> state.isCampaign() && Blocks.coreFoundation.unlocked()
|
||||
|
@ -3,6 +3,7 @@ package mindustry.world.blocks.distribution;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
@ -127,6 +128,20 @@ public class StackConveyor extends Block implements Autotiler{
|
||||
}
|
||||
}
|
||||
|
||||
//draw inputs
|
||||
if(state == stateLoad){
|
||||
for(int i = 0; i < 4; i++){
|
||||
if((blendprox & (1 << i)) != 0 && i != 0){
|
||||
int dir = rotation - i;
|
||||
Draw.rect(sliced(regions[0], SliceMode.bottom), x + Geometry.d4x(dir) * tilesize*0.75f, y + Geometry.d4y(dir) * tilesize*0.75f, (float)(dir*90));
|
||||
}
|
||||
}
|
||||
}else if(state == stateUnload){ //front unload
|
||||
if((blendprox & (1)) != 0){
|
||||
Draw.rect(sliced(regions[0], SliceMode.top), x + Geometry.d4x(rotation) * tilesize*0.75f, y + Geometry.d4y(rotation) * tilesize*0.75f, rotation * 90f);
|
||||
}
|
||||
}
|
||||
|
||||
Draw.z(Layer.block - 0.1f);
|
||||
|
||||
Tile from = world.tile(link);
|
||||
|
@ -525,12 +525,16 @@ public class LogicBlock extends Block{
|
||||
write.b(compressed);
|
||||
|
||||
//write only the non-constant variables
|
||||
int count = Structs.count(executor.vars, v -> !v.constant || v == executor.vars[LExecutor.varUnit]);
|
||||
int count = Structs.count(executor.vars, v -> (!v.constant || v == executor.vars[LExecutor.varUnit]) && !(v.isobj && v.objval == null));
|
||||
|
||||
write.i(count);
|
||||
for(int i = 0; i < executor.vars.length; i++){
|
||||
Var v = executor.vars[i];
|
||||
|
||||
//null is the default variable value, so waste no time serializing that
|
||||
if(v.isobj && v.objval == null) continue;
|
||||
|
||||
//skip constants
|
||||
if(v.constant && i != LExecutor.varUnit) continue;
|
||||
|
||||
//write the name and the object value
|
||||
|
@ -113,6 +113,12 @@ public class Pump extends LiquidBlock{
|
||||
draw.drawBase(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawLight(){
|
||||
super.drawLight();
|
||||
draw.drawLights(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pickedUp(){
|
||||
amount = 0f;
|
||||
|
@ -396,6 +396,9 @@ public class CoreBlock extends StorageBlock{
|
||||
|
||||
@Override
|
||||
public void drawSelect(){
|
||||
//do not draw a pointless single outline when there's no storage
|
||||
if(team.cores().size <= 1 && !proximity.contains(storage -> storage.items == items)) return;
|
||||
|
||||
Lines.stroke(1f, Pal.accent);
|
||||
Cons<Building> outline = b -> {
|
||||
for(int i = 0; i < 4; i++){
|
||||
|
146
core/src/mindustry/world/blocks/units/UnitCargoLoader.java
Normal file
@ -0,0 +1,146 @@
|
||||
package mindustry.world.blocks.units;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
public class UnitCargoLoader extends Block{
|
||||
public UnitType unitType = UnitTypes.manifold;
|
||||
public float buildTime = 60f * 8f;
|
||||
|
||||
public float polyStroke = 1.8f, polyRadius = 8f;
|
||||
public int polySides = 6;
|
||||
public float polyRotateSpeed = 1f;
|
||||
public Color polyColor = Pal.accent;
|
||||
|
||||
public UnitCargoLoader(String name){
|
||||
super(name);
|
||||
|
||||
solid = true;
|
||||
update = true;
|
||||
hasItems = true;
|
||||
itemCapacity = 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
|
||||
bars.add("units", (UnitTransportSourceBuild e) ->
|
||||
new Bar(
|
||||
() ->
|
||||
Core.bundle.format("bar.unitcap",
|
||||
Fonts.getUnicodeStr(unitType.name),
|
||||
e.team.data().countType(unitType),
|
||||
Units.getStringCap(e.team)
|
||||
),
|
||||
() -> Pal.power,
|
||||
() -> (float)e.team.data().countType(unitType) / Units.getCap(e.team)
|
||||
));
|
||||
}
|
||||
|
||||
public class UnitTransportSourceBuild extends Building{
|
||||
//needs to be "unboxed" after reading, since units are read after buildings.
|
||||
public int readUnitId = -1;
|
||||
public float buildProgress, totalProgress;
|
||||
public float warmup, readyness;
|
||||
public @Nullable Unit unit;
|
||||
|
||||
@Override
|
||||
public void updateTile(){
|
||||
//unit was lost/destroyed
|
||||
if(unit != null && (unit.dead || !unit.isAdded())){
|
||||
unit = null;
|
||||
}
|
||||
|
||||
if(readUnitId != -1){
|
||||
unit = Groups.unit.getByID(readUnitId);
|
||||
readUnitId = -1;
|
||||
}
|
||||
|
||||
warmup = Mathf.approachDelta(warmup, efficiency(), 1f / 60f);
|
||||
readyness = Mathf.approachDelta(readyness, unit != null ? 1f : 0f, 1f / 60f);
|
||||
|
||||
if(unit == null && Units.canCreate(team, unitType)){
|
||||
buildProgress += edelta() / buildTime;
|
||||
totalProgress += edelta();
|
||||
|
||||
if(buildProgress >= 1f){
|
||||
unit = unitType.create(team);
|
||||
if(unit instanceof BuildingTetherc bt){
|
||||
bt.building(this);
|
||||
}
|
||||
unit.set(x, y);
|
||||
unit.rotation = 90f;
|
||||
unit.add();
|
||||
|
||||
Fx.spawn.at(unit);
|
||||
|
||||
buildProgress = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptItem(Building source, Item item){
|
||||
return items.total() < itemCapacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldConsume(){
|
||||
return unit == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
Draw.rect(block.region, x, y);
|
||||
if(unit == null){
|
||||
Draw.draw(Layer.blockOver, () -> {
|
||||
//TODO make sure it looks proper
|
||||
Drawf.construct(this, unitType.fullIcon, 0f, buildProgress, warmup, totalProgress);
|
||||
});
|
||||
}else{
|
||||
Draw.z(Layer.bullet - 0.01f);
|
||||
Draw.color(polyColor);
|
||||
Lines.stroke(polyStroke * readyness);
|
||||
Lines.poly(x, y, polySides, polyRadius, Time.time * polyRotateSpeed);
|
||||
Draw.reset();
|
||||
Draw.z(Layer.block);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float totalProgress(){
|
||||
return totalProgress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float progress(){
|
||||
return buildProgress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Writes write){
|
||||
super.write(write);
|
||||
|
||||
write.i(unit == null ? -1 : unit.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(Reads read, byte revision){
|
||||
super.read(read, revision);
|
||||
|
||||
readUnitId = read.i();
|
||||
}
|
||||
}
|
||||
}
|
109
core/src/mindustry/world/blocks/units/UnitCargoUnloadPoint.java
Normal file
@ -0,0 +1,109 @@
|
||||
package mindustry.world.blocks.units;
|
||||
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class UnitCargoUnloadPoint extends Block{
|
||||
/** If a block is full for this amount of time, it will not be flown to anymore. */
|
||||
public float staleTimeDuration = 60f * 6f;
|
||||
|
||||
public @Load("@-top") TextureRegion topRegion;
|
||||
|
||||
public UnitCargoUnloadPoint(String name){
|
||||
super(name);
|
||||
update = solid = true;
|
||||
hasItems = true;
|
||||
configurable = true;
|
||||
saveConfig = true;
|
||||
flags = EnumSet.of(BlockFlag.unitCargoUnloadPoint);
|
||||
|
||||
config(Item.class, (UnitCargoUnloadPointBuild build, Item item) -> build.item = item);
|
||||
configClear((UnitCargoUnloadPointBuild build) -> build.item = null);
|
||||
}
|
||||
|
||||
public class UnitCargoUnloadPointBuild extends Building{
|
||||
public Item item;
|
||||
public float staleTimer;
|
||||
public boolean stale;
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
super.draw();
|
||||
|
||||
if(item != null){
|
||||
Draw.color(item.color);
|
||||
Draw.rect(topRegion, x, y);
|
||||
Draw.color();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTile(){
|
||||
super.updateTile();
|
||||
|
||||
if(items.total() < itemCapacity){
|
||||
staleTimer = 0f;
|
||||
stale = false;
|
||||
}
|
||||
|
||||
if(dumpAccumulate()){
|
||||
staleTimer = 0f;
|
||||
stale = false;
|
||||
}else if(items.total() >= itemCapacity && (staleTimer += Time.delta) >= staleTimeDuration){
|
||||
stale = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int acceptStack(Item item, int amount, Teamc source){
|
||||
return Math.min(itemCapacity - items.total(), amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildConfiguration(Table table){
|
||||
ItemSelection.buildTable(UnitCargoUnloadPoint.this, table, content.items(), () -> item, this::configure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onConfigureTileTapped(Building other){
|
||||
if(this == other){
|
||||
deselect();
|
||||
configure(null);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object config(){
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Writes write){
|
||||
super.write(write);
|
||||
write.s(item == null ? -1 : item.id);
|
||||
write.bool(stale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(Reads read, byte revision){
|
||||
super.read(read, revision);
|
||||
item = Vars.content.item(read.s());
|
||||
stale = read.bool();
|
||||
}
|
||||
}
|
||||
}
|
@ -23,7 +23,9 @@ public enum BlockFlag{
|
||||
/** Blocks that extinguishes fires. */
|
||||
extinguisher,
|
||||
/** Just a launch pad. */
|
||||
launchPad;
|
||||
launchPad,
|
||||
/** Destination for unit cargo. */
|
||||
unitCargoUnloadPoint;
|
||||
|
||||
public final static BlockFlag[] all = values();
|
||||
|
||||
|