1
0
mirror of https://github.com/Anuken/Mindustry.git synced 2024-09-11 08:15:35 +03:00

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

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

View 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}]}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

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

Binary file not shown.

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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