From 5ccadcf38f539451d613419190a535145f2114d5 Mon Sep 17 00:00:00 2001 From: Anuken Date: Tue, 9 May 2023 15:00:26 -0400 Subject: [PATCH] Semi-functioning implementation --- .../mindustry/entities/comp/BuildingComp.java | 43 +++-- core/src/mindustry/world/Block.java | 4 +- .../world/blocks/liquid/Conduit.java | 176 ++++++++++++++---- .../world/draw/DrawLiquidRegion.java | 2 +- .../mindustry/world/modules/LiquidModule.java | 41 ++++ 5 files changed, 219 insertions(+), 47 deletions(-) diff --git a/core/src/mindustry/entities/comp/BuildingComp.java b/core/src/mindustry/entities/comp/BuildingComp.java index 848cb37e67..5c20d3de46 100644 --- a/core/src/mindustry/entities/comp/BuildingComp.java +++ b/core/src/mindustry/entities/comp/BuildingComp.java @@ -99,7 +99,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, private transient boolean sleeping; private transient float sleepTime; - private transient boolean initialized; + private transient boolean initialized, wasAdded; /** Sets this tile entity data to this and adds it if necessary. */ public Building init(Tile tile, Team team, boolean shouldAdd, int rotation){ @@ -174,6 +174,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, //endregion //region io + /** certain blocks merge liquid graphs, so it is necessary to provide a fake one at write-time */ + public LiquidModule writeLiquids(){ + return liquids; + } + public final void writeBase(Writes write){ boolean writeVisibility = state.rules.fog && visibleFlags != 0; @@ -186,7 +191,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, write.b(moduleBitmask()); if(items != null) items.write(write); if(power != null) power.write(write); - if(liquids != null) liquids.write(write); + if(liquids != null) writeLiquids().write(write); //efficiency is written as two bytes to save space write.b((byte)(Mathf.clamp(efficiency) * 255f)); @@ -820,6 +825,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, if(!net.client() && state.isCampaign() && team == state.rules.defaultTeam) liquid.unlock(); + float selfCapacity = liquidCapacity(); + for(int i = 0; i < proximity.size; i++){ incrementDump(proximity.size); @@ -829,10 +836,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, other = other.getLiquidDestination(self(), liquid); if(other != null && other.team == team && other.block.hasLiquids && canDumpLiquid(other, liquid) && other.liquids != null){ - float ofract = other.liquids.get(liquid) / other.block.liquidCapacity; - float fract = liquids.get(liquid) / block.liquidCapacity; + float ofract = other.liquids.get(liquid) / other.liquidCapacity(); + float fract = liquids.get(liquid) / selfCapacity; - if(ofract < fract) transferLiquid(other, (fract - ofract) * block.liquidCapacity / scaling, liquid); + if(ofract < fract) transferLiquid(other, (fract - ofract) * selfCapacity / scaling, liquid); } } } @@ -842,7 +849,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, } public void transferLiquid(Building next, float amount, Liquid liquid){ - float flow = Math.min(next.block.liquidCapacity - next.liquids.get(liquid), amount); + float flow = Math.min(next.liquidCapacity() - next.liquids.get(liquid), amount); if(next.acceptLiquid(self(), liquid)){ next.handleLiquid(self(), liquid, flow); @@ -869,19 +876,20 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, if(next == null) return 0; next = next.getLiquidDestination(self(), liquid); + float selfCapacity = liquidCapacity(), nextCapacity = next.liquidCapacity(); if(next.team == team && next.block.hasLiquids && liquids.get(liquid) > 0f){ - float ofract = next.liquids.get(liquid) / next.block.liquidCapacity; - float fract = liquids.get(liquid) / block.liquidCapacity * block.liquidPressure; - float flow = Math.min(Mathf.clamp((fract - ofract)) * (block.liquidCapacity), liquids.get(liquid)); - flow = Math.min(flow, next.block.liquidCapacity - next.liquids.get(liquid)); + float ofract = next.liquids.get(liquid) / nextCapacity; + float fract = liquids.get(liquid) / selfCapacity * block.liquidPressure; + float flow = Math.min(Mathf.clamp((fract - ofract)) * selfCapacity, liquids.get(liquid)); + flow = Math.min(flow, nextCapacity - next.liquids.get(liquid)); if(flow > 0f && ofract <= fract && next.acceptLiquid(self(), liquid)){ next.handleLiquid(self(), liquid, flow); liquids.remove(liquid, flow); return flow; //handle reactions between different liquid types ▼ - }else if(!next.block.consumesLiquid(liquid) && next.liquids.currentAmount() / next.block.liquidCapacity > 0.1f && fract > 0.1f){ + }else if(!next.block.consumesLiquid(liquid) && next.liquids.currentAmount() / nextCapacity > 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 float fx = (x + next.x) / 2f, fy = (y + next.y) / 2f; @@ -907,6 +915,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, return 0; } + /** Override to set a custom liquid capacity, e.g. for blocks with shared inventories. */ + public float liquidCapacity(){ + return block.liquidCapacity; + } + public Building getLiquidDestination(Building from, Liquid liquid){ return self(); } @@ -1074,6 +1087,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, noSleep(); } + /** Like onProximityAdded, but only called once. Essentially does what onProximityAdded was supposed to. */ + public void onAdded(){} + public void updatePowerGraph(){ for(Building other : getPowerConnections(tempBuilds)){ if(other.power != null){ @@ -1679,6 +1695,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, other.onProximityUpdate(); } proximity.clear(); + wasAdded = false; } public void rotated(int prevRotation, int newRotation){ @@ -1705,6 +1722,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, proximity.add(tile); } + if(!wasAdded){ + onAdded(); + wasAdded = true; + } onProximityAdded(); onProximityUpdate(); diff --git a/core/src/mindustry/world/Block.java b/core/src/mindustry/world/Block.java index cb149be40a..605dc12714 100644 --- a/core/src/mindustry/world/Block.java +++ b/core/src/mindustry/world/Block.java @@ -558,7 +558,7 @@ public class Block extends UnlockableContent implements Senseable{ addBar("liquid-" + liq.name, entity -> !liq.unlockedNow() ? null : new Bar( () -> liq.localizedName, liq::barColor, - () -> entity.liquids.get(liq) / liquidCapacity + () -> entity.liquids.get(liq) / entity.liquidCapacity() )); } @@ -567,7 +567,7 @@ public class Block extends UnlockableContent implements Senseable{ addBar("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) + () -> current.get((T)entity) == null ? 0f : entity.liquids.get(current.get((T)entity)) / entity.liquidCapacity()) ); } diff --git a/core/src/mindustry/world/blocks/liquid/Conduit.java b/core/src/mindustry/world/blocks/liquid/Conduit.java index ab279b1fab..40d35c7a40 100644 --- a/core/src/mindustry/world/blocks/liquid/Conduit.java +++ b/core/src/mindustry/world/blocks/liquid/Conduit.java @@ -8,6 +8,7 @@ import arc.math.*; import arc.math.geom.*; import arc.struct.*; import arc.util.*; +import arc.util.io.*; import mindustry.annotations.Annotations.*; import mindustry.content.*; import mindustry.entities.*; @@ -15,17 +16,21 @@ import mindustry.entities.units.*; import mindustry.gen.*; import mindustry.graphics.*; import mindustry.input.*; +import mindustry.logic.*; import mindustry.type.*; import mindustry.world.*; import mindustry.world.blocks.*; import mindustry.world.blocks.distribution.*; +import mindustry.world.modules.*; import static mindustry.Vars.*; import static mindustry.type.Liquid.*; public class Conduit extends LiquidBlock implements Autotiler{ + static final boolean debugGraphs = false; static final float rotatePad = 6, hpad = rotatePad / 2f / 4f; static final float[][] rotateOffsets = {{hpad, hpad}, {-hpad, hpad}, {-hpad, -hpad}, {hpad, -hpad}}; + static final LiquidModule tempLiquids = new LiquidModule(); public final int timerFlow = timers++; @@ -51,6 +56,10 @@ public class Conduit extends LiquidBlock implements Autotiler{ noUpdateDisabled = true; canOverdrive = false; priority = TargetPriority.transport; + + //conduits don't need to update + update = false; + destructible = true; } @Override @@ -155,7 +164,6 @@ public class Conduit extends LiquidBlock implements Autotiler{ public boolean capped, backCapped = false; protected void addGraphs(){ - graph = null; //connect self to every nearby graph getConnections(other -> { if(other.graph != null){ @@ -163,6 +171,7 @@ public class Conduit extends LiquidBlock implements Autotiler{ } }); + //nothing to connect to if(graph == null){ new ConduitGraph().merge(this); } @@ -172,15 +181,35 @@ public class Conduit extends LiquidBlock implements Autotiler{ //graph is getting recalculated, no longer valid if(graph != null){ graph.checkRemove(); + graph.remove(this); + graph = null; //TODO ????? } getConnections(other -> new ConduitGraph().reflow(this, other)); } @Override - public void onProximityAdded(){ - super.onProximityAdded(); + public void control(LAccess type, double p1, double p2, double p3, double p4){ + if(type == LAccess.enabled){ + boolean shouldEnable = !Mathf.zero((float)p1); + if(enabled != shouldEnable){ + if(graph != null){ + //keep track of how many conduits are disabled, so the graph can stop working + if(shouldEnable){ + graph.disabledConduits --; + }else{ + graph.disabledConduits ++; + } + } + + enabled = shouldEnable; + } + } + } + + @Override + public void onAdded(){ addGraphs(); } @@ -222,14 +251,20 @@ public class Conduit extends LiquidBlock implements Autotiler{ if(capped && capRegion.found()) Draw.rect(capRegion, x, y, rotdeg()); if(backCapped && capRegion.found()) Draw.rect(capRegion, x, y, rotdeg() + 180); - //TODO this is for debuggig only - Mathf.rand.setSeed(graph == null ? -1 : graph.id); - Draw.color(Tmp.c1.rand()); - Draw.alpha(0.4f); + if(debugGraphs){ + //simple visualization that assigns random color to each graph + Mathf.rand.setSeed(graph == null ? -1 : graph.id); + Draw.color(Tmp.c1.rand()); - Fill.square(x, y, 4f); + Drawf.selected(tileX(), tileY(), block, Tmp.c1); + Draw.color(Pal.accent); - Draw.color(); + if(this == graph.head){ + Fill.poly(x, y, 3, 2f, rotdeg()); + } + + Draw.color(); + } } protected void drawAt(float x, float y, int bits, int rotation, SliceMode slice){ @@ -253,7 +288,7 @@ public class Conduit extends LiquidBlock implements Autotiler{ //the drawing state machine sure was a great design choice with no downsides or hidden behavior!!! float xscl = Draw.xscl, yscl = Draw.yscl; Draw.scl(1f, 1f); - Drawf.liquid(sliced(liquidr, slice), x + ox, y + oy, smoothLiquid, liquids.current().color.write(Tmp.c1).a(1f)); + Drawf.liquid(sliced(liquidr, slice), x + ox, y + oy, graph == null ? smoothLiquid : graph.smoothLiquid, liquids.current().color.write(Tmp.c1).a(1f)); Draw.scl(xscl, yscl); Draw.rect(sliced(topRegions[bits], slice), x, y, angle); @@ -282,15 +317,15 @@ public class Conduit extends LiquidBlock implements Autotiler{ } @Override - public void updateTile(){ - smoothLiquid = Mathf.lerpDelta(smoothLiquid, liquids.currentAmount() / liquidCapacity, 0.05f); + public LiquidModule writeLiquids(){ + //"saved" liquids are based on a fraction, essentially splitting apart and re-joining + tempLiquids.set(liquids, graph == null ? 1f : block.liquidCapacity / graph.totalCapacity); + return tempLiquids; + } - if(liquids.currentAmount() > 0.0001f && timer(timerFlow, 1)){ - moveLiquidForward(leaks, liquids.current()); - noSleep(); - }else{ - sleep(); - } + @Override + public float liquidCapacity(){ + return graph == null ? block.liquidCapacity : graph.totalCapacity; } @Nullable @@ -303,10 +338,15 @@ public class Conduit extends LiquidBlock implements Autotiler{ return null; } + @Override + public void writeAll(Writes write){ + super.writeAll(write); + } + /** Calls callback with every conduit that transfers fluids to this one. */ public void getConnections(Cons cons){ for(var other : proximity){ - if(other instanceof ConduitBuild conduit){ + if(other instanceof ConduitBuild conduit && other.team == team){ if( front() == conduit || other.front() == this @@ -320,25 +360,34 @@ public class Conduit extends LiquidBlock implements Autotiler{ /* TODO: - - [ ] liquids shared as one inventory - - [ ] liquids merged when placing - - [ ] liquids split when breaking - - [ ] liquids saved - - [ ] liquids accept input - - [ ] liquids transfer forward - - [ ] liquids leak - - [ ] liquids display properly (including flow rate) + - [X] liquids shared as one inventory + - [X] liquids merged when placing + - [X] liquids split when breaking + - [X] liquids saved + - [X] liquids accept input + - [X] liquids transfer forward + - [X] liquids leak + - [X] liquids display properly (including flow rate) + - [ ] liquids merge different types correctly - ????? + - [X] conduits can (or can't) be disabled */ public static class ConduitGraph{ - private static final IntSet closedSet = new IntSet(); + private static final IntSet closedSet = new IntSet(), headSet = new IntSet(); private static final Queue queue = new Queue<>(); - static int lastId = -1; + static int lastId = 0; public final int id = lastId ++; + public float smoothLiquid; + /** if any are disabled, does not update */ + private int disabledConduits; private Seq conduits = new Seq<>(); private final @Nullable ConduitGraphUpdater entity; + private LiquidModule liquids = new LiquidModule(); + private float totalCapacity; + + public @Nullable Building head; public ConduitGraph(){ entity = ConduitGraphUpdater.create(); @@ -346,7 +395,17 @@ public class Conduit extends LiquidBlock implements Autotiler{ } public void update(){ - //TODO + smoothLiquid = Mathf.lerpDelta(smoothLiquid, liquids.currentAmount() / totalCapacity, 0.05f); + + if(disabledConduits > 0) return; + + if(head != null){ + + //move forward as the head + if(liquids.currentAmount() > 0.0001f && head.timer(((Conduit)head.block).timerFlow, 1)){ + head.moveLiquidForward(((Conduit)head.block).leaks, liquids.current()); + } + } } public void checkAdd(){ @@ -357,6 +416,15 @@ public class Conduit extends LiquidBlock implements Autotiler{ if(entity != null) entity.remove(); } + public void remove(ConduitBuild build){ + float fraction = build.block.liquidCapacity / totalCapacity; + //remove fraction of liquids based on what part this conduit constituted + //e.g. 70% of capacity was made up by this conduit = multiply liquids by 0.3 (remove 70%) + liquids.mul(1f - fraction); + + totalCapacity -= build.block.liquidCapacity; + } + public void reflow(@Nullable ConduitBuild ignore, ConduitBuild conduit){ closedSet.clear(); queue.clear(); @@ -367,9 +435,10 @@ public class Conduit extends LiquidBlock implements Autotiler{ closedSet.add(conduit.id); queue.add(conduit); + while(queue.size > 0){ var parent = queue.removeFirst(); - assign(parent); + assign(parent, ignore); parent.getConnections(child -> { if(closedSet.add(child.id)){ @@ -387,7 +456,7 @@ public class Conduit extends LiquidBlock implements Autotiler{ if(other.graph != null){ - //merge graphs - TODO - flip if it is larger + //merge graphs - TODO - flip if it is larger, like power graphs? for(var cond : other.graph.conduits){ assign(cond); } @@ -397,16 +466,57 @@ public class Conduit extends LiquidBlock implements Autotiler{ } protected void assign(ConduitBuild build){ + assign(build, null); + } + + protected void assign(ConduitBuild build, @Nullable Building ignore){ if(build.graph != this){ - //invalidate older graph + //merge graph liquids - TODO - how does this react to different types if(build.graph != null){ build.graph.checkRemove(); + + //add liquids based on what fraction it made up + liquids.add(build.liquids, build.block.liquidCapacity / build.graph.totalCapacity); + }else{ + //simple direct liquid merge + liquids.add(build.liquids); } + totalCapacity += build.block.liquidCapacity; build.graph = this; + build.liquids = liquids; conduits.add(build); checkAdd(); + + //re-validate head + if(head == null){ + head = build; + } + + //find the best head block + headSet.clear(); + headSet.add(head.id); + + while(true){ + var next = head.front(); + + if(next instanceof ConduitBuild cond && cond.team == head.team && next != ignore){ + if(!headSet.add(next.id)){ + //there's a loop, which means a head does not exist + head = null; + break; + }else{ + head = next; + } + }else{ + //found the end + break; + } + } + + //snap smoothLiquid so it doesn't start at 0 + smoothLiquid = liquids.currentAmount() / totalCapacity; } } diff --git a/core/src/mindustry/world/draw/DrawLiquidRegion.java b/core/src/mindustry/world/draw/DrawLiquidRegion.java index e22daa7c9e..3b07e369d7 100644 --- a/core/src/mindustry/world/draw/DrawLiquidRegion.java +++ b/core/src/mindustry/world/draw/DrawLiquidRegion.java @@ -24,7 +24,7 @@ public class DrawLiquidRegion extends DrawBlock{ public void draw(Building build){ Liquid drawn = drawLiquid != null ? drawLiquid : build.liquids.current(); Drawf.liquid(liquid, build.x, build.y, - build.liquids.get(drawn) / build.block.liquidCapacity * alpha, + build.liquids.get(drawn) / build.liquidCapacity() * alpha, drawn.color ); } diff --git a/core/src/mindustry/world/modules/LiquidModule.java b/core/src/mindustry/world/modules/LiquidModule.java index 589c712840..6523a76163 100644 --- a/core/src/mindustry/world/modules/LiquidModule.java +++ b/core/src/mindustry/world/modules/LiquidModule.java @@ -64,6 +64,12 @@ public class LiquidModule extends BlockModule{ } } + public void mul(float amount){ + for(int i = 0; i < liquids.length; i ++){ + liquids[i] *= amount; + } + } + public void stopFlow(){ flow = null; } @@ -107,6 +113,27 @@ public class LiquidModule extends BlockModule{ Arrays.fill(liquids, 0); } + public void add(LiquidModule other){ + add(other, 1f); + } + + public void add(LiquidModule other, float mul){ + for(int i = 0; i < liquids.length; i ++){ + liquids[i] += other.liquids[i] * mul; + + if(liquids[i] > liquids[current.id]){ + current = content.liquid(i); + } + } + } + + public void set(LiquidModule other, float mul){ + current = other.current; + for(int i = 0; i < liquids.length; i ++){ + liquids[i] = other.liquids[i] * mul; + } + } + public void add(Liquid liquid, float amount){ liquids[liquid.id] += amount; current = liquid; @@ -180,6 +207,20 @@ public class LiquidModule extends BlockModule{ } } + @Override + public String toString(){ + var res = new StringBuilder(); + res.append("LiquidModule{ current=").append(current).append(", "); + for(int i = 0; i < liquids.length; i++){ + if(liquids[i] > 0){ + res.append(content.liquid(i).name).append(":").append(liquids[i]).append(", "); + } + } + res.setLength(res.length() - 2); + res.append("}"); + return res.toString(); + } + public interface LiquidConsumer{ void accept(Liquid liquid, float amount); }