Improved generation tool

This commit is contained in:
Anuken 2019-03-15 15:06:55 -04:00
parent 97067a6ee2
commit e7ffbd817c
13 changed files with 388 additions and 126 deletions

View file

@ -212,6 +212,7 @@ editor.errorload = Error loading file:\n[accent]{0}
editor.errorsave = Error saving file:\n[accent]{0}
editor.errorname = Map has no name defined.
editor.update = Update
editor.randomize = Randomize
editor.apply = Apply
editor.generate = Generate
editor.resize = Resize

View file

@ -13,7 +13,6 @@ import io.anuke.arc.graphics.glutils.FrameBuffer;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Rectangle;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.ScreenRecorder;
import io.anuke.arc.util.Time;
import io.anuke.arc.util.Tmp;
import io.anuke.arc.util.pooling.Pools;
@ -131,10 +130,6 @@ public class Renderer implements ApplicationListener{
draw();
}
if(!ui.chatfrag.chatOpen()){
ScreenRecorder.record(); //this only does something if CoreGifRecorder is on the class path, which it usually isn't
}
}
void updateShake(float scale){

View file

@ -52,7 +52,7 @@ public class MapEditor{
tiles = createTiles(map.width, map.height);
tags.putAll(map.tags);
MapIO.readTiles(map, tiles);
checkTiles();
checkLinkedTiles();
renderer.resize(width(), height());
loading = false;
}
@ -61,12 +61,12 @@ public class MapEditor{
reset();
this.tiles = tiles;
checkTiles();
checkLinkedTiles();
renderer.resize(width(), height());
}
//adds missing blockparts
void checkTiles(){
public void checkLinkedTiles(){
//clear block parts first
for(int x = 0; x < width(); x ++){
for(int y = 0; y < height(); y++){

View file

@ -1,32 +1,49 @@
package io.anuke.mindustry.editor;
import io.anuke.arc.Core;
import io.anuke.arc.collection.Array;
import io.anuke.arc.function.Supplier;
import io.anuke.arc.graphics.Pixmap;
import io.anuke.arc.graphics.Pixmap.Format;
import io.anuke.arc.graphics.Texture;
import io.anuke.arc.scene.Element;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.mindustry.editor.generation.FilterOption;
import io.anuke.mindustry.editor.generation.GenerateFilter;
import io.anuke.arc.util.Scaling;
import io.anuke.arc.util.async.AsyncExecutor;
import io.anuke.arc.util.async.AsyncResult;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.editor.generation.*;
import io.anuke.mindustry.editor.generation.GenerateFilter.GenerateInput;
import io.anuke.mindustry.editor.generation.NoiseFilter;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.io.MapIO;
import io.anuke.mindustry.ui.BorderImage;
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
import io.anuke.mindustry.world.DummyTile;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Floor;
import static io.anuke.mindustry.Vars.mobile;
@SuppressWarnings("unchecked")
public class MapGenerateDialog extends FloatingDialog{
private final MapEditor editor;
private Pixmap pixmap;
private Texture texture;
private GenerateInput input = new GenerateInput();
private Array<GenerateFilter> filters = new Array<>();
private int scaling = mobile ? 3 : 1;
private Supplier<GenerateFilter>[] filterTypes = new Supplier[]{NoiseFilter::new, ScatterFilter::new, TerrainFilter::new, DistortFilter::new, RiverNoiseFilter::new};
private Table filterTable;
private AsyncExecutor executor = new AsyncExecutor(1);
private AsyncResult<Void> result;
private boolean generating;
private DummyTile returnTile = new DummyTile();
private DummyTile[][] buffer1, buffer2;
public MapGenerateDialog(MapEditor editor){
super("editor.generate");
super("$editor.generate");
this.editor = editor;
shown(this::setup);
@ -35,9 +52,17 @@ public class MapGenerateDialog extends FloatingDialog{
apply();
hide();
}).size(180f, 64f);
buttons.addButton("$editor.randomize", () -> {
for(GenerateFilter filter : filters){
filter.randomize();
}
update();
}).size(180f, 64f);
}
void setup(){
filters.clear();
if(pixmap != null){
pixmap.dispose();
texture.dispose();
@ -45,123 +70,246 @@ public class MapGenerateDialog extends FloatingDialog{
texture = null;
}
pixmap = new Pixmap(editor.width(), editor.height(), Format.RGBA8888);
pixmap = new Pixmap(editor.width() / scaling, editor.height() / scaling, Format.RGBA8888);
texture = new Texture(pixmap);
cont.clear();
cont.table("flat", t -> {
t.margin(8f);
t.add(new BorderImage(texture)).size(500f).padRight(6);
t.add(new BorderImage(texture)).grow().padRight(6).top().get().setScaling(Scaling.fit);
t.table(right -> {
Table[] fs = {null};
Runnable rebuild = () -> {
Table p = fs[0];
p.clearChildren();
for(GenerateFilter filter : filters){
p.table("button", f -> {
f.left();
for(FilterOption option : filter.options){
option.build(f);
f.row();
}
for(Element e : f.getChildren()){
e.changed(this::update);
}
}).pad(3).width(280f);
p.row();
}
};
right.pane(p -> {
p.top();
fs[0] = p;
}).fill().width(300f).growY().get().setScrollingDisabled(true, false);
right.pane(p -> filterTable = p).grow().get().setScrollingDisabled(true, false);
right.row();
right.addButton("$add", () -> {
filters.add(new NoiseFilter());
rebuild.run();
}).fillX().height(50f);
right.addButton("$add", this::showAdd).fillX().height(50f);
}).grow();
t.row();
t.addButton("$editor.update", () -> {
input.randomize();
update();
}).size(300f, 66f).colspan(2).pad(6);
});
}).grow();
update();
buffer1 = create();
buffer2 = create();
}
DummyTile[][] create(){
DummyTile[][] out = new DummyTile[editor.width() / scaling][editor.height() / scaling];
for(int x = 0; x < out.length; x++){
for(int y = 0; y < out[0].length; y++){
out[x][y] = new DummyTile();
}
}
return out;
}
void rebuildFilters(){
filterTable.clearChildren();
filterTable.top();
for(GenerateFilter filter : filters){
filterTable.table(t -> {
t.add(filter.name()).padTop(5).color(Pal.accent).growX().left();
t.row();
t.table(b -> {
b.left();
b.defaults().size(50f);
b.addImageButton("icon-refresh", 14*2, () -> {
filter.randomize();
update();
});
b.addImageButton("icon-arrow-up", 10*2, () -> {
int idx = filters.indexOf(filter);
filters.swap(idx, Math.max(0, idx - 1));
rebuildFilters();
update();
});
b.addImageButton("icon-arrow-down", 10*2, () -> {
int idx = filters.indexOf(filter);
filters.swap(idx, Math.min(filters.size-1, idx + 1));
rebuildFilters();
update();
});
b.addImageButton("icon-trash", 14*2, () -> {
filters.remove(filter);
rebuildFilters();
update();
});
}).growX();
}).fillX();
filterTable.row();
filterTable.table("underline", f -> {
f.left();
for(FilterOption option : filter.options){
option.changed = this::update;
f.table(t -> {
t.left();
option.build(t);
}).growX().left();
f.row();
}
}).pad(3).padTop(0).width(280f);
filterTable.row();
}
}
void showAdd(){
FloatingDialog selection = new FloatingDialog("");
selection.setFillParent(false);
selection.cont.defaults().size(210f, 60f);
for(Supplier<GenerateFilter> gen : filterTypes){
GenerateFilter filter = gen.get();
selection.cont.addButton(filter.name(), () -> {
filters.add(filter);
rebuildFilters();
update();
selection.hide();
});
selection.cont.row();
}
selection.addCloseButton();
selection.show();
}
DummyTile dset(Tile tile){
returnTile.set(tile);
return returnTile;
}
void apply(){
Tile[][] writeTiles = new Tile[editor.width()][editor.height()];
if(result != null){
result.get();
}
//writeback buffer
DummyTile[][] writeTiles = new DummyTile[editor.width()][editor.height()];
for(int x = 0; x < editor.width(); x++){
for(int y = 0; y < editor.height(); y++){
writeTiles[x][y] = new DummyTile(x, y);
writeTiles[x][y] = new DummyTile();
}
}
for(GenerateFilter filter : filters){
input.setFilter(filter, (x, y) -> dset(editor.tile(x, y)));
//write to buffer
for(int x = 0; x < editor.width(); x++){
for(int y = 0; y < editor.height(); y++){
Tile tile = editor.tile(x, y);
Tile write = writeTiles[x][y];
input.begin(editor, x, y, tile.floor(), tile.block(), tile.ore());
filter.apply(input);
write.setRotation(tile.getRotation());
write.setOre(input.ore);
write.setFloor((Floor)input.floor);
write.setBlock(input.block);
write.setTeam(tile.getTeam());
writeTiles[x][y].set(input.floor, input.block, input.ore, tile.getTeam(), tile.getRotation());
}
}
editor.load(() -> {
//read from buffer back into tiles
for(int x = 0; x < editor.width(); x++){
for(int y = 0; y < editor.height(); y++){
Tile tile = editor.tile(x, y);
Tile write = writeTiles[x][y];
DummyTile write = writeTiles[x][y];
tile.setRotation(write.getRotation());
tile.setOre(write.ore());
tile.setFloor(write.floor());
tile.setBlock(write.block());
tile.setTeam(write.getTeam());
tile.setRotation((byte)write.rotation);
tile.setFloor((Floor)write.floor);
tile.setBlock(write.block);
tile.setTeam(write.team);
tile.setOre(write.ore);
}
}
});
}
//reset undo stack as generation... messes things up
editor.load(editor::checkLinkedTiles);
editor.renderer().updateAll();
editor.clearOp();
}
void update(){
boolean modified = false;
for(GenerateFilter filter : filters){
modified = true;
for(int x = 0; x < editor.width(); x++){
for(int y = 0; y < editor.height(); y++){
Tile tile = editor.tile(x, y);
input.begin(editor, x, y, tile.floor(), tile.block(), tile.ore());
filter.apply(input);
pixmap.drawPixel(x, pixmap.getHeight() - 1 - y, MapIO.colorFor(input.floor, input.block, input.ore, Team.none));
}
}
if(generating){
return;
}
if(!modified){
for(int x = 0; x < editor.width(); x++){
for(int y = 0; y < editor.height(); y++){
Tile tile = editor.tile(x, y);
pixmap.drawPixel(x, pixmap.getHeight() - 1 - y, MapIO.colorFor(tile.floor(), tile.block(), tile.ore(), Team.none));
Array<GenerateFilter> copy = new Array<>(filters);
result = executor.submit(() -> {
generating = true;
if(!filters.isEmpty()){
//write to buffer1 for reading
for(int px = 0; px < pixmap.getWidth(); px++){
for(int py = 0; py < pixmap.getHeight(); py++){
buffer1[px][py].set(editor.tile(px * scaling, py * scaling));
}
}
}
for(GenerateFilter filter : copy){
input.setFilter(filter, (x, y) -> buffer1[x][y]);
//read from buffer1 and write to buffer2
for(int px = 0; px < pixmap.getWidth(); px++){
for(int py = 0; py < pixmap.getHeight(); py++){
int x = px*scaling, y = py*scaling;
DummyTile tile = buffer1[px][py];
input.begin(editor, x, y, tile.floor, tile.block, tile.ore);
filter.apply(input);
buffer2[px][py].set(input.floor, input.block, input.ore, tile.team, tile.rotation);
}
}
for(int px = 0; px < pixmap.getWidth(); px++){
for(int py = 0; py < pixmap.getHeight(); py++){
buffer1[px][py].set(buffer2[px][py]);
}
}
}
for(int px = 0; px < pixmap.getWidth(); px++){
for(int py = 0; py < pixmap.getHeight(); py++){
int color;
//get result from buffer1 if there's filters left, otherwise get from editor directly
if(filters.isEmpty()){
Tile tile = editor.tile(px * scaling, py * scaling);
color = MapIO.colorFor(tile.floor(), tile.block(), tile.ore(), Team.none);
}else{
DummyTile tile = buffer1[px][py];
color = MapIO.colorFor(tile.floor, tile.block, tile.ore, Team.none);
}
pixmap.drawPixel(px, pixmap.getHeight() - 1 - py, color);
}
}
Core.app.post(() -> {
texture.draw(pixmap, 0, 0);
generating = false;
});
return null;
});
}
public static class DummyTile{
public Block block = Blocks.air, ore = Blocks.air, floor = Blocks.air;
public Team team = Team.none;
public int rotation;
void set(Block floor, Block wall, Block ore, Team team, int rotation){
this.floor = floor;
this.block = wall;
this.ore = ore;
this.team = team;
this.rotation = rotation;
}
void set(DummyTile other){
set(other.floor, other.block, other.ore, other.team, other.rotation);
}
void set(Tile other){
set(other.floor(), other.block(), other.ore(), other.getTeam(), other.getRotation());
}
texture.draw(pixmap, 0, 0);
}
}

View file

@ -1,24 +1,25 @@
package io.anuke.mindustry.editor.generation;
import io.anuke.mindustry.editor.MapGenerateDialog.DummyTile;
import io.anuke.mindustry.editor.generation.FilterOption.SliderOption;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Floor;
public class DistortFilter extends GenerateFilter{
float scl = 40, mag = 5;
{
options(
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 1000f),
new SliderOption("mag", () -> mag, f -> mag = f, 0.5f, 10f)
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 400f),
new SliderOption("mag", () -> mag, f -> mag = f, 0.5f, 100f)
);
}
@Override
public void apply(){
Tile tile = in.tile(in.x + noise(in.x, in.y, scl, mag)-mag/2f, in.y + noise(in.x, in.y+o, scl, mag)-mag/2f);
DummyTile tile = in.tile(in.x + noise(in.x, in.y, scl, mag)-mag/2f, in.y + noise(in.x, in.y+o, scl, mag)-mag/2f);
in.floor = tile.floor();
if(!tile.block().synthetic() && !in.block.synthetic()) in.block = tile.block();
in.ore = tile.ore();
in.floor = tile.floor;
if(!tile.block.synthetic() && !in.block.synthetic()) in.block = tile.block;
if(!((Floor)in.floor).isLiquid) in.ore = tile.ore;
}
}

View file

@ -3,6 +3,7 @@ package io.anuke.mindustry.editor.generation;
import io.anuke.arc.Core;
import io.anuke.arc.function.*;
import io.anuke.arc.scene.style.TextureRegionDrawable;
import io.anuke.arc.scene.ui.Slider;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
@ -16,6 +17,7 @@ public abstract class FilterOption{
public static final Predicate<Block> wallsOnly = b -> (!b.synthetic() && !(b instanceof Floor)) && Core.atlas.isFound(b.icon(Icon.full));
public abstract void build(Table table);
public Runnable changed = () -> {};
static class SliderOption extends FilterOption{
final String name;
@ -35,7 +37,9 @@ public abstract class FilterOption{
public void build(Table table){
table.add(name);
table.row();
table.addSlider(min, max, (max-min)/100f, setter).get().setValue(getter.get());
Slider slider = table.addSlider(min, max, (max-min)/200f, setter).growX().get();
slider.setValue(getter.get());
slider.changed(changed);
}
}
@ -54,7 +58,6 @@ public abstract class FilterOption{
@Override
public void build(Table table){
table.add(name + ": ");
table.addButton(b -> {
b.addImage(supplier.get().icon(Icon.small)).update(i -> ((TextureRegionDrawable)i.getDrawable()).setRegion(supplier.get().icon(Icon.small))).size(8*3);
}, () -> {
@ -65,14 +68,17 @@ public abstract class FilterOption{
if(!filter.test(block)) continue;
dialog.cont.addImage(block.icon(Icon.medium)).size(8*4).pad(3).get().clicked(() -> {
consumer.accept(block);
dialog.hide();
consumer.accept(block);
dialog.hide();
changed.run();
});
if(++i % 10 == 0) dialog.cont.row();
}
dialog.show();
});
}).pad(4).margin(12f);
table.add(name);
}
}
}

View file

@ -1,14 +1,18 @@
package io.anuke.mindustry.editor.generation;
import io.anuke.arc.Core;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.util.Pack;
import io.anuke.arc.util.noise.RidgedPerlin;
import io.anuke.arc.util.noise.Simplex;
import io.anuke.mindustry.editor.MapEditor;
import io.anuke.mindustry.editor.MapGenerateDialog.DummyTile;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Floor;
public abstract class GenerateFilter{
protected float o = (float)(Math.random()*10000000.0);
protected long seed;
protected GenerateInput in;
public FilterOption[] options;
@ -23,10 +27,26 @@ public abstract class GenerateFilter{
return (float)in.noise.octaveNoise2D(octaves, persistence, 1f/scl, x + o, y + o)*mag;
}
protected float rnoise(float x, float y, float scl, float mag){
return in.pnoise.getValue((int)(x + o), (int)(y + o), 1f/scl)*mag;
}
public void randomize(){
seed = Mathf.random(99999999);
}
protected float chance(){
return Mathf.randomSeed(Pack.longInt(in.x, in.y + (int)o));
}
public void options(FilterOption... options){
this.options = options;
}
public String name(){
return Core.bundle.get("filter." + getClass().getSimpleName().toLowerCase().replace("filter", ""), getClass().getSimpleName().replace("Filter", ""));
}
public final void apply(GenerateInput in){
this.in = in;
apply();
@ -42,6 +62,8 @@ public abstract class GenerateFilter{
public Block floor, block, ore;
Simplex noise = new Simplex();
RidgedPerlin pnoise = new RidgedPerlin(0, 1);
TileProvider buffer;
public void begin(MapEditor editor, int x, int y, Block floor, Block block, Block ore){
this.editor = editor;
@ -52,12 +74,18 @@ public abstract class GenerateFilter{
this.y = y;
}
public void randomize(){
noise.setSeed(Mathf.random(99999999));
public void setFilter(GenerateFilter filter, TileProvider buffer){
this.buffer = buffer;
noise.setSeed(filter.seed);
pnoise.setSeed((int)(filter.seed + 1));
}
Tile tile(double x, double y){
return editor.tile((int)Mathf.clamp(x, 0, editor.width() - 1), (int)Mathf.clamp(y, 0, editor.height() - 1));
DummyTile tile(float x, float y){
return buffer.get(Mathf.clamp((int)x, 0, editor.width() - 1), Mathf.clamp((int)y, 0, editor.height() - 1));
}
public interface TileProvider{
DummyTile get(int x, int y);
}
}
}

View file

@ -14,9 +14,9 @@ public class NoiseFilter extends GenerateFilter{
{
options(
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 100f),
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 500f),
new SliderOption("threshold", () -> threshold, f -> threshold = f, 0f, 1f),
new SliderOption("octaves", () -> octaves, f -> octaves = f, 1f, 20f),
new SliderOption("octaves", () -> octaves, f -> octaves = f, 1f, 10f),
new SliderOption("falloff", () -> falloff, f -> falloff = f, 0f, 1f),
new BlockOption("floor", () -> floor, b -> floor = b, floorsOnly),
new BlockOption("wall", () -> block, b -> block = b, wallsOnly)

View file

@ -0,0 +1,36 @@
package io.anuke.mindustry.editor.generation;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.editor.generation.FilterOption.BlockOption;
import io.anuke.mindustry.editor.generation.FilterOption.SliderOption;
import io.anuke.mindustry.world.Block;
import static io.anuke.mindustry.editor.generation.FilterOption.floorsOnly;
public class RiverNoiseFilter extends GenerateFilter{
float scl = 40, threshold = 0f, threshold2 = 0.1f;
Block floor = Blocks.water, floor2 = Blocks.deepwater;
{
options(
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 500f),
new SliderOption("threshold", () -> threshold, f -> threshold = f, 0f, 1f),
new SliderOption("threshold2", () -> threshold2, f -> threshold2 = f, 0f, 1f),
new BlockOption("floor", () -> floor, b -> floor = b, floorsOnly),
new BlockOption("floor2", () -> floor2, b -> floor2 = b, floorsOnly)
);
}
@Override
public void apply(){
float noise = rnoise(in.x, in.y, scl, 1f);
if(noise >= threshold){
in.floor = floor;
if(noise >= threshold2){
in.floor = floor2;
}
}
}
}

View file

@ -0,0 +1,30 @@
package io.anuke.mindustry.editor.generation;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.editor.generation.FilterOption.BlockOption;
import io.anuke.mindustry.editor.generation.FilterOption.SliderOption;
import io.anuke.mindustry.world.Block;
import static io.anuke.mindustry.editor.generation.FilterOption.floorsOnly;
import static io.anuke.mindustry.editor.generation.FilterOption.wallsOnly;
public class ScatterFilter extends GenerateFilter{
float chance = 0.1f;
Block floor = Blocks.ice, block = Blocks.icerocks;
{
options(
new SliderOption("chance", () -> chance, f -> chance = f, 0f, 1f),
new BlockOption("floor", () -> floor, b -> floor = b, floorsOnly),
new BlockOption("block", () -> block, b -> block = b, wallsOnly)
);
}
@Override
public void apply(){
if(in.srcfloor == floor && in.srcblock == Blocks.air && chance() <= chance){
in.block = block;
}
}
}

View file

@ -0,0 +1,41 @@
package io.anuke.mindustry.editor.generation;
import io.anuke.arc.math.Mathf;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.editor.generation.FilterOption.BlockOption;
import io.anuke.mindustry.editor.generation.FilterOption.SliderOption;
import io.anuke.mindustry.world.Block;
import static io.anuke.mindustry.editor.generation.FilterOption.floorsOnly;
import static io.anuke.mindustry.editor.generation.FilterOption.wallsOnly;
public class TerrainFilter extends GenerateFilter{
float scl = 40, threshold = 0.9f, octaves = 3f, falloff = 0.5f, circleScl = 2.1f;
Block floor = Blocks.stone, block = Blocks.rocks;
{
options(
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 500f),
new SliderOption("threshold", () -> threshold, f -> threshold = f, 0f, 1f),
new SliderOption("circle scale", () -> circleScl, f -> circleScl = f, 0f, 3f),
new SliderOption("octaves", () -> octaves, f -> octaves = f, 1f, 10f),
new SliderOption("falloff", () -> falloff, f -> falloff = f, 0f, 1f),
new BlockOption("floor", () -> floor, b -> floor = b, floorsOnly),
new BlockOption("wall", () -> block, b -> block = b, wallsOnly)
);
}
@Override
public void apply(){
float noise = noise(in.x, in.y, scl, 1f, octaves, falloff) + Mathf.dst((float) in.x / in.editor.width(), (float) in.y / in.editor.height(), 0.5f, 0.5f) * circleScl;
in.floor = floor;
in.ore = Blocks.air;
if(noise >= threshold){
in.block = block;
}else{
in.block = Blocks.air;
}
}
}

View file

@ -1,25 +0,0 @@
package io.anuke.mindustry.world;
import io.anuke.mindustry.game.Team;
public class DummyTile extends Tile{
public DummyTile(int x, int y){
super(x, y);
}
@Override
public Team getTeam(){
return Team.all[getTeamID()];
}
@Override
protected void changed(){
//nothing matters
}
@Override
protected void preChanged(){
//it really doesn't
}
}

View file

@ -144,6 +144,7 @@ public class PowerGraph{
public void update(){
if(Core.graphics.getFrameId() == lastFrameUpdated || (consumers.size == 0 && producers.size == 0 && batteries.size == 0)){
powerBalance.addValue(0f);
return;
}