Better multi-liquid support / Reinforced pump progress

This commit is contained in:
Anuken
2021-11-16 10:25:59 -05:00
parent bf1909295e
commit eefcce5cf1
16 changed files with 182 additions and 109 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

View File

@@ -443,3 +443,4 @@
63263=hydrogen|liquid-hydrogen-ui 63263=hydrogen|liquid-hydrogen-ui
63262=electrolyzer|block-electrolyzer-ui 63262=electrolyzer|block-electrolyzer-ui
63261=ozone|liquid-ozone-ui 63261=ozone|liquid-ozone-ui
63260=reinforced-liquid-junction|block-reinforced-liquid-junction-ui

Binary file not shown.

View File

@@ -60,7 +60,7 @@ public class Blocks implements ContentList{
//crafting //crafting
siliconSmelter, siliconCrucible, siliconArcFurnace, kiln, graphitePress, plastaniumCompressor, multiPress, phaseWeaver, surgeSmelter, pyratiteMixer, blastMixer, cryofluidMixer, siliconSmelter, siliconCrucible, siliconArcFurnace, kiln, graphitePress, plastaniumCompressor, multiPress, phaseWeaver, surgeSmelter, pyratiteMixer, blastMixer, cryofluidMixer,
melter, separator, disassembler, sporePress, pulverizer, incinerator, coalCentrifuge, melter, separator, disassembler, sporePress, pulverizer, incinerator, coalCentrifuge,
electrolyzer, oxidizer, heatReactor, carbideCrucible, electrolyzer, oxidationChamber, heatReactor, carbideCrucible,
cellSynthesisChamber, cellSynthesisChamber,
//sandbox //sandbox
@@ -84,7 +84,7 @@ public class Blocks implements ContentList{
mechanicalPump, rotaryPump, impulsePump, conduit, pulseConduit, platedConduit, liquidRouter, liquidContainer, liquidTank, liquidJunction, bridgeConduit, phaseConduit, mechanicalPump, rotaryPump, impulsePump, conduit, pulseConduit, platedConduit, liquidRouter, liquidContainer, liquidTank, liquidJunction, bridgeConduit, phaseConduit,
//liquid - reinforced //liquid - reinforced
reinforcedPump, reinforcedConduit, reinforcedBridgeConduit, reinforcedLiquidRouter, reinforcedLiquidContainer, reinforcedLiquidTank, reinforcedPump, reinforcedConduit, reinforcedLiquidJunction, reinforcedBridgeConduit, reinforcedLiquidRouter, reinforcedLiquidContainer, reinforcedLiquidTank,
//power //power
combustionGenerator, thermalGenerator, steamGenerator, differentialGenerator, rtgGenerator, solarPanel, largeSolarPanel, thoriumReactor, combustionGenerator, thermalGenerator, steamGenerator, differentialGenerator, rtgGenerator, solarPanel, largeSolarPanel, thoriumReactor,
@@ -980,17 +980,28 @@ public class Blocks implements ContentList{
glowScale = 6f; glowScale = 6f;
}} }}
); );
iconOverride = new String[]{"-bottom", "", "-top"};
iconOverride = new String[]{"-bottom", "", "-top"};
outputLiquids = LiquidStack.with(Liquids.ozone, 2f * craftTime / 60, Liquids.hydrogen, 3f * craftTime / 60); outputLiquids = LiquidStack.with(Liquids.ozone, 2f * craftTime / 60, Liquids.hydrogen, 3f * craftTime / 60);
liquidOutputDirections = new int[]{1, 3}; liquidOutputDirections = new int[]{1, 3};
}}; }};
oxidizer = new HeatProducer("oxidizer"){{ //TODO sprite
//TODO 'oxidation chamber'
if(false)
oxidationChamber = new HeatProducer("oxidation-chamber"){{
requirements(Category.crafting, with(Items.tungsten, 60, Items.graphite, 30)); requirements(Category.crafting, with(Items.tungsten, 60, Items.graphite, 30));
size = 3; size = 3;
//converts oxygen (?) + beryllium into heat + oxide outputItem = new ItemStack(Items.oxide, 1);
consumes.liquid(Liquids.ozone, 2f / 60f);
consumes.item(Items.beryllium);
consumes.power(1f);
craftTime = 60f * 3f;
liquidCapacity = 30f;
heatOutput = 8f;
}}; }};
heatReactor = new HeatProducer("heat-reactor"){{ heatReactor = new HeatProducer("heat-reactor"){{
@@ -1495,13 +1506,12 @@ public class Blocks implements ContentList{
//TODO different name //TODO different name
reinforcedPump = new Pump("reinforced-pump"){{ reinforcedPump = new Pump("reinforced-pump"){{
requirements(Category.liquid, with(Items.beryllium, 70, Items.tungsten, 20, Items.silicon, 20)); requirements(Category.liquid, with(Items.beryllium, 70, Items.tungsten, 20, Items.silicon, 20));
//TODO perhaps something else? //TODO CUSTOM DRAW ANIMATION + 3x3?
consumes.item(Items.beryllium); //TODO balance consumption
consumes.liquid(Liquids.hydrogen, 1.5f / 60f);
pumpAmount = 0.4f; pumpAmount = 0.4f;
consumes.power(0.5f);
liquidCapacity = 40f; liquidCapacity = 40f;
hasPower = true;
size = 2; size = 2;
}}; }};
@@ -1514,6 +1524,13 @@ public class Blocks implements ContentList{
health = 250; health = 250;
}}; }};
//TODO is this necessary? junctions are not good design
reinforcedLiquidJunction = new LiquidJunction("reinforced-liquid-junction"){{
requirements(Category.liquid, with(Items.graphite, 3, Items.beryllium, 3));
health = 260;
((Conduit)reinforcedConduit).junctionReplacement = this;
}};
reinforcedBridgeConduit = new DirectionLiquidBridge("reinforced-bridge-conduit"){{ reinforcedBridgeConduit = new DirectionLiquidBridge("reinforced-bridge-conduit"){{
requirements(Category.liquid, with(Items.graphite, 4, Items.beryllium, 8)); requirements(Category.liquid, with(Items.graphite, 4, Items.beryllium, 8));
range = 4; range = 4;
@@ -2742,23 +2759,6 @@ public class Blocks implements ContentList{
health = size * size * 80; health = size * size * 80;
}}; }};
nuclearWarhead = new NuclearWarhead("nuclear-warhead"){{
requirements(Category.crafting, BuildVisibility.debugOnly, with(Items.thorium, 40));
size = 2;
}};
warheadAssembler = new SingleBlockProducer("warhead-assembler"){{
requirements(Category.crafting, BuildVisibility.debugOnly, with(Items.thorium, 100));
result = nuclearWarhead;
size = 3;
buildSpeed = 0.3f;
}};
ballisticSilo = new BallisticSilo("ballistic-silo"){{
requirements(Category.crafting, BuildVisibility.debugOnly, with(Items.thorium, 100));
size = 5;
}};
//endregion campaign //endregion campaign
//region logic //region logic

View File

@@ -66,9 +66,9 @@ public class Liquids implements ContentList{
}}; }};
//TODO combustion //TODO combustion
hydrogen = new Liquid("hydrogen", Color.valueOf("97a5f7")){{ hydrogen = new Liquid("hydrogen", Color.valueOf("b8c2fc")){{
gas = true; gas = true;
barColor = Color.valueOf("7d8be0"); barColor = Color.valueOf("a3b0ff");
flammability = 1f; flammability = 1f;
}}; }};

View File

@@ -645,6 +645,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
liquids.remove(liquid, flow); liquids.remove(liquid, flow);
return flow; return flow;
}else if(next.liquids.currentAmount() / next.block.liquidCapacity > 0.1f && fract > 0.1f){ }else if(next.liquids.currentAmount() / next.block.liquidCapacity > 0.1f && fract > 0.1f){
//TODO !IMPORTANT! uses current(), which is 1) wrong for multi-liquid blocks and 2) causes unwanted reactions, e.g. hydrogen + slag in pump
//TODO these are incorrect effect positions //TODO these are incorrect effect positions
float fx = (x + next.x) / 2f, fy = (y + next.y) / 2f; float fx = (x + next.x) / 2f, fy = (y + next.y) / 2f;
@@ -1181,21 +1182,30 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(liquids != null){ if(liquids != null){
table.row(); table.row();
table.left();
table.table(l -> { table.table(l -> {
boolean[] had = {false}; Bits current = new Bits();
Runnable rebuild = () -> { Runnable rebuild = () -> {
l.clearChildren(); l.clearChildren();
l.left(); l.left();
l.image(() -> liquids.current().uiIcon).padRight(3f); for(var liquid : content.liquids()){
l.label(() -> liquids.getFlowRate() < 0 ? "..." : Strings.fixed(liquids.getFlowRate(), 2) + ps).color(Color.lightGray); if(liquids.hasFlowLiquid(liquid)){
l.image(liquid.uiIcon).padRight(3f);
l.label(() -> liquids.getFlowRate(liquid) < 0 ? "..." : Strings.fixed(liquids.getFlowRate(liquid), 1) + ps).color(Color.lightGray);
l.row();
}
}
}; };
rebuild.run();
l.update(() -> { l.update(() -> {
if(!had[0] && liquids.hadFlow()){ for(var liquid : content.liquids()){
had[0] = true; if(liquids.hasFlowLiquid(liquid) && !current.get(liquid.id)){
rebuild.run(); current.set(liquid.id);
} rebuild.run();
}
}
}); });
}).left(); }).left();
} }

View File

@@ -447,25 +447,26 @@ public class Block extends UnlockableContent{
if(hasItems && itemCapacity > 0) stats.add(Stat.itemCapacity, itemCapacity, StatUnit.items); if(hasItems && itemCapacity > 0) stats.add(Stat.itemCapacity, itemCapacity, StatUnit.items);
} }
public void addLiquidBar(Liquid liq){
bars.add("liquid-" + liq.name, entity -> new Bar(
() -> liq.localizedName,
liq::barColor,
() -> entity.liquids.get(liq) / liquidCapacity)
);
}
/** Adds a liquid bar that dynamically displays a liquid type. */
public <T extends Building> void addLiquidBar(Func<T, Liquid> current){
bars.add("liquid", entity -> new Bar(
() -> current.get((T)entity) == null || entity.liquids.get(current.get((T)entity)) <= 0.001f ? Core.bundle.get("bar.liquid") : current.get((T)entity).localizedName,
() -> current.get((T)entity) == null ? Color.clear : current.get((T)entity).barColor(),
() -> current.get((T)entity) == null ? 0f : entity.liquids.get(current.get((T)entity)) / liquidCapacity)
);
}
public void setBars(){ public void setBars(){
bars.add("health", entity -> new Bar("stat.health", Pal.health, entity::healthf).blink(Color.white)); bars.add("health", entity -> new Bar("stat.health", Pal.health, entity::healthf).blink(Color.white));
if(hasLiquids){
Func<Building, Liquid> current;
if(consumes.has(ConsumeType.liquid) && consumes.get(ConsumeType.liquid) instanceof ConsumeLiquid){
Liquid liquid = consumes.<ConsumeLiquid>get(ConsumeType.liquid).liquid;
current = entity -> liquid;
}else{
current = entity -> entity.liquids.current();
}
bars.add("liquid", entity -> new Bar(
() -> entity.liquids.get(current.get(entity)) <= 0.001f ? Core.bundle.get("bar.liquid") : current.get(entity).localizedName,
() -> current.get(entity).barColor(),
() -> entity.liquids.get(current.get(entity)) / liquidCapacity)
);
}
if(hasPower && consumes.hasPower()){ if(hasPower && consumes.hasPower()){
ConsumePower cons = consumes.getPower(); ConsumePower cons = consumes.getPower();
boolean buffered = cons.buffered; boolean buffered = cons.buffered;
@@ -490,6 +491,38 @@ public class Block extends UnlockableContent{
if(unitCapModifier != 0){ if(unitCapModifier != 0){
stats.add(Stat.maxUnits, (unitCapModifier < 0 ? "-" : "+") + Math.abs(unitCapModifier)); stats.add(Stat.maxUnits, (unitCapModifier < 0 ? "-" : "+") + Math.abs(unitCapModifier));
} }
//liquids added last
if(hasLiquids){
//TODO liquids need to be handled VERY carefully. there are several potential possibilities:
//1. no consumption or output (conduit/tank)
// - display current(), 1 bar
//2. static set of inputs and outputs
// - create bars for each input/output, straightforward
//3. TODO dynamic input/output combo???
// - confusion
boolean added = false;
//add bars for *specific* consumed liquids
if(consumes.has(ConsumeType.liquid)){
var consl = consumes.get(ConsumeType.liquid);
if(consl instanceof ConsumeLiquid liq){
added = true;
addLiquidBar(liq.liquid);
}else if(consl instanceof ConsumeLiquids multi){
added = true;
for(var stack : multi.liquids){
addLiquidBar(stack.liquid);
}
}
}
//nothing was added, so it's safe to add a dynamic liquid bar (probably?)
if(!added){
addLiquidBar(build -> build.liquids.current());
}
}
} }
public boolean canReplace(Block other){ public boolean canReplace(Block other){

View File

@@ -4,7 +4,6 @@ import arc.math.*;
import arc.util.io.*; import arc.util.io.*;
import mindustry.graphics.*; import mindustry.graphics.*;
import mindustry.ui.*; import mindustry.ui.*;
import mindustry.world.blocks.power.NuclearReactor.*;
import mindustry.world.blocks.production.*; import mindustry.world.blocks.production.*;
import mindustry.world.draw.*; import mindustry.world.draw.*;
@@ -30,7 +29,7 @@ public class HeatProducer extends GenericCrafter{
public void setBars(){ public void setBars(){
super.setBars(); super.setBars();
bars.add("heat", (NuclearReactorBuild entity) -> new Bar("bar.heat", Pal.lightOrange, () -> entity.heat)); bars.add("heat", (HeatProducerBuild entity) -> new Bar("bar.heat", Pal.lightOrange, () -> entity.heat));
} }
public class HeatProducerBuild extends GenericCrafterBuild implements HeatBlock{ public class HeatProducerBuild extends GenericCrafterBuild implements HeatBlock{

View File

@@ -5,6 +5,7 @@ import mindustry.gen.*;
import mindustry.type.*; import mindustry.type.*;
import mindustry.world.meta.*; import mindustry.world.meta.*;
//TODO should leak!
public class LiquidJunction extends LiquidBlock{ public class LiquidJunction extends LiquidBlock{
public LiquidJunction(String name){ public LiquidJunction(String name){

View File

@@ -12,9 +12,7 @@ import mindustry.entities.units.*;
import mindustry.gen.*; import mindustry.gen.*;
import mindustry.logic.*; import mindustry.logic.*;
import mindustry.type.*; import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*; import mindustry.world.*;
import mindustry.world.consumers.*;
import mindustry.world.draw.*; import mindustry.world.draw.*;
import mindustry.world.meta.*; import mindustry.world.meta.*;
@@ -74,41 +72,14 @@ public class GenericCrafter extends Block{
public void setBars(){ public void setBars(){
super.setBars(); super.setBars();
//set up liquid bars for multiple liquid outputs //set up liquid bars for liquid outputs
//TODO this will currently screw up input display if input liquids are filters - no good way to fix that yet
if(outputLiquids != null && outputLiquids.length > 0){ if(outputLiquids != null && outputLiquids.length > 0){
//no need for dynamic liquid bar
bars.remove("liquid"); bars.remove("liquid");
Seq<Liquid> consumed = new Seq<>();
//find list of liquids consumed
if(consumes.has(ConsumeType.liquid)){
var consl = consumes.get(ConsumeType.liquid);
if(consl instanceof ConsumeLiquid liq){
consumed.add(liq.liquid);
}else if(consl instanceof ConsumeLiquids multi){
for(var stack : multi.liquids){
consumed.add(stack.liquid);
}
}
}
//display consumed first
for(var liq : consumed){
bars.add("liquid-consume-" + liq.name, entity -> new Bar(
() -> liq.localizedName,
liq::barColor,
() -> entity.liquids.get(liq) / liquidCapacity)
);
}
//then display output buffer //then display output buffer
for(var stack : outputLiquids){ for(var stack : outputLiquids){
bars.add("liquid-output-" + stack.liquid.name, entity -> new Bar( addLiquidBar(stack.liquid);
() -> stack.liquid.localizedName,
() -> stack.liquid.barColor(),
() -> entity.liquids.get(stack.liquid) / liquidCapacity)
);
} }
} }
} }

View File

@@ -3,6 +3,7 @@ package mindustry.world.blocks.production;
import arc.*; import arc.*;
import arc.graphics.*; import arc.graphics.*;
import arc.graphics.g2d.*; import arc.graphics.g2d.*;
import arc.util.*;
import mindustry.game.*; import mindustry.game.*;
import mindustry.graphics.*; import mindustry.graphics.*;
import mindustry.type.*; import mindustry.type.*;
@@ -82,6 +83,14 @@ public class Pump extends LiquidBlock{
} }
} }
@Override
public void setBars(){
super.setBars();
//replace dynamic output bar with own custom bar
addLiquidBar((PumpBuild build) -> build.liquidDrop);
}
protected boolean canPump(Tile tile){ protected boolean canPump(Tile tile){
return tile != null && tile.floor().liquidDrop != null; return tile != null && tile.floor().liquidDrop != null;
} }
@@ -89,13 +98,15 @@ public class Pump extends LiquidBlock{
public class PumpBuild extends LiquidBuild{ public class PumpBuild extends LiquidBuild{
public float consTimer; public float consTimer;
public float amount = 0f; public float amount = 0f;
public Liquid liquidDrop = null; public @Nullable Liquid liquidDrop = null;
@Override @Override
public void draw(){ public void draw(){
Draw.rect(name, x, y); Draw.rect(region, x, y);
Drawf.liquid(liquidRegion, x, y, liquids.currentAmount() / liquidCapacity, liquids.current().color); if(liquidDrop == null) return;
Drawf.liquid(liquidRegion, x, y, liquids.get(liquidDrop) / liquidCapacity, liquidDrop.color);
} }
@Override @Override
@@ -126,17 +137,19 @@ public class Pump extends LiquidBlock{
@Override @Override
public void updateTile(){ public void updateTile(){
if(consValid() && liquidDrop != null){ if(consValid() && liquidDrop != null){
float maxPump = Math.min(liquidCapacity - liquids.total(), amount * pumpAmount * edelta()); float maxPump = Math.min(liquidCapacity - liquids.get(liquidDrop), amount * pumpAmount * edelta());
liquids.add(liquidDrop, maxPump); liquids.add(liquidDrop, maxPump);
//does nothing for most pumps, as those do not require items. //does nothing for most pumps, as those do not require items.
if((consTimer += delta()) >= consumeTime){ if((consTimer += delta()) >= consumeTime){
consume(); consume();
consumeTime = 0f; consTimer = 0f;
} }
} }
dumpLiquid(liquids.current()); if(liquidDrop != null){
dumpLiquid(liquidDrop);
}
} }
} }

View File

@@ -30,6 +30,7 @@ public class LiquidVoid extends Block{
@Override @Override
public void handleLiquid(Building source, Liquid liquid, float amount){ public void handleLiquid(Building source, Liquid liquid, float amount){
liquids.handleFlow(liquid, amount);
} }
} }

View File

@@ -13,8 +13,6 @@ public class BlockBars{
} }
public void remove(String name){ public void remove(String name){
if(!bars.containsKey(name))
throw new RuntimeException("No bar with name '" + name + "' found; current bars: " + bars.keys().toSeq());
bars.remove(name); bars.remove(name);
} }

View File

@@ -90,15 +90,11 @@ public class ItemModule extends BlockModule{
/** @return a specific item's flow rate in items/s; any value < 0 means not ready.*/ /** @return a specific item's flow rate in items/s; any value < 0 means not ready.*/
public float getFlowRate(Item item){ public float getFlowRate(Item item){
if(flow == null) return -1f; return flow == null ? -1f : displayFlow[item.id] * 60;
return displayFlow[item.id] * 60;
} }
public boolean hasFlowItem(Item item){ public boolean hasFlowItem(Item item){
if(flow == null) return false; return flow != null && cacheBits.get(item.id);
return cacheBits.get(item.id);
} }
public void each(ItemConsumer cons){ public void each(ItemConsumer cons){

View File

@@ -1,6 +1,7 @@
package mindustry.world.modules; package mindustry.world.modules;
import arc.math.*; import arc.math.*;
import arc.struct.*;
import arc.util.*; import arc.util.*;
import arc.util.io.*; import arc.util.io.*;
import mindustry.type.*; import mindustry.type.*;
@@ -14,18 +15,25 @@ public class LiquidModule extends BlockModule{
private static final Interval flowTimer = new Interval(2); private static final Interval flowTimer = new Interval(2);
private static final float pollScl = 20f; private static final float pollScl = 20f;
private static WindowedMean[] cacheFlow;
private static float[] cacheSums;
private static float[] displayFlow;
private static final Bits cacheBits = new Bits();
private float[] liquids = new float[content.liquids().size]; private float[] liquids = new float[content.liquids().size];
private float total; private float total;
private Liquid current = content.liquid(0); private Liquid current = content.liquid(0);
private float smoothLiquid; private float smoothLiquid;
private boolean hadFlow; //TODO broken for multi flow
private @Nullable WindowedMean flow; private @Nullable WindowedMean[] flow;
private float lastAdded, currentFlowRate;
public void update(boolean showFlow){ public void update(boolean showFlow){
smoothLiquid = Mathf.lerpDelta(smoothLiquid, currentAmount(), 0.1f); smoothLiquid = Mathf.lerpDelta(smoothLiquid, currentAmount(), 0.1f);
if(showFlow){ if(showFlow){
/*
if(flowTimer.get(1, pollScl)){ if(flowTimer.get(1, pollScl)){
if(flow == null) flow = new WindowedMean(windowSize); if(flow == null) flow = new WindowedMean(windowSize);
@@ -36,21 +44,57 @@ public class LiquidModule extends BlockModule{
if(currentFlowRate < 0 || flowTimer.get(updateInterval)){ if(currentFlowRate < 0 || flowTimer.get(updateInterval)){
currentFlowRate = flow.hasEnoughData() ? flow.mean() / pollScl : -1f; currentFlowRate = flow.hasEnoughData() ? flow.mean() / pollScl : -1f;
} }
}*/
if(flowTimer.get(1, pollScl)){
if(flow == null){
if(cacheFlow == null || cacheFlow.length != liquids.length){
cacheFlow = new WindowedMean[liquids.length];
for(int i = 0; i < liquids.length; i++){
cacheFlow[i] = new WindowedMean(windowSize);
}
cacheSums = new float[liquids.length];
displayFlow = new float[liquids.length];
}else{
for(int i = 0; i < liquids.length; i++){
cacheFlow[i].reset();
}
Arrays.fill(cacheSums, 0);
cacheBits.clear();
}
Arrays.fill(displayFlow, -1);
flow = cacheFlow;
}
boolean updateFlow = flowTimer.get(30);
for(int i = 0; i < liquids.length; i++){
flow[i].add(cacheSums[i]);
if(cacheSums[i] > 0){
cacheBits.set(i);
}
cacheSums[i] = 0;
if(updateFlow){
displayFlow[i] = flow[i].hasEnoughData() ? flow[i].mean() / pollScl : -1;
}
}
} }
}else{ }else{
currentFlowRate = -1f;
flow = null; flow = null;
hadFlow = false;
} }
} }
/** @return current liquid's flow rate in u/s; any value < 0 means 'not ready'. */ /** @return current liquid's flow rate in u/s; any value < 0 means 'not ready'. */
public float getFlowRate(){ public float getFlowRate(Liquid liquid){
return currentFlowRate * 60; return flow == null ? -1f : displayFlow[liquid.id] * 60;
} }
public boolean hadFlow(){ public boolean hasFlowLiquid(Liquid liquid){
return hadFlow; return flow != null && cacheBits.get(liquid.id);
} }
public float smoothAmount(){ public float smoothAmount(){
@@ -93,7 +137,13 @@ public class LiquidModule extends BlockModule{
current = liquid; current = liquid;
if(flow != null){ if(flow != null){
lastAdded += Math.max(amount, 0); cacheSums[liquid.id] += Math.max(amount, 0);
}
}
public void handleFlow(Liquid liquid, float amount){
if(flow != null){
cacheSums[liquid.id] += Math.max(amount, 0);
} }
} }