WIP animated gas

This commit is contained in:
Anuken
2021-11-26 21:35:58 -05:00
parent 616fa8748f
commit 77b3a83826
43 changed files with 194 additions and 18 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 626 B

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 B

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 776 B

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 308 B

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 B

After

Width:  |  Height:  |  Size: 322 B

View File

@@ -460,3 +460,4 @@
63246=sublimate|block-sublimate-ui 63246=sublimate|block-sublimate-ui
63245=reinforced-container|block-reinforced-container-ui 63245=reinforced-container|block-reinforced-container-ui
63244=reinforced-vault|block-reinforced-vault-ui 63244=reinforced-vault|block-reinforced-vault-ui
63243=nitrogen|liquid-nitrogen-ui

Binary file not shown.

View File

@@ -60,7 +60,9 @@ 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, oxidationChamber, slagHeater, slagIncinerator, heatReactor, carbideCrucible, slagCentrifuge, surgeCrucible, cyanogenSynthesizer, phaseSynthesizer,
//erekir
electrolyzer, oxidationChamber, atmosphericConcentrator, slagHeater, slagIncinerator, heatReactor, carbideCrucible, slagCentrifuge, surgeCrucible, cyanogenSynthesizer, phaseSynthesizer,
cellSynthesisChamber, cellSynthesisChamber,
//sandbox //sandbox
@@ -987,6 +989,20 @@ public class Blocks implements ContentList{
liquidOutputDirections = new int[]{1, 3}; liquidOutputDirections = new int[]{1, 3};
}}; }};
if(false)
atmosphericConcentrator = new HeatCrafter("atmospheric-concentrator"){{
requirements(Category.crafting, with(Items.tungsten, 60, Items.graphite, 30));
size = 3;
craftTime = 60f;
liquidCapacity = 50f;
consumes.power(2f);
heatRequirement = 5f;
outputLiquid = new LiquidStack(Liquids.nitrogen, 4f / 60f);
}};
oxidationChamber = new HeatProducer("oxidation-chamber"){{ 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;
@@ -1627,12 +1643,14 @@ public class Blocks implements ContentList{
liquidRouter = new LiquidRouter("liquid-router"){{ liquidRouter = new LiquidRouter("liquid-router"){{
requirements(Category.liquid, with(Items.graphite, 4, Items.metaglass, 2)); requirements(Category.liquid, with(Items.graphite, 4, Items.metaglass, 2));
liquidCapacity = 20f; liquidCapacity = 20f;
newDrawing = true;
}}; }};
liquidContainer = new LiquidRouter("liquid-container"){{ liquidContainer = new LiquidRouter("liquid-container"){{
requirements(Category.liquid, with(Items.titanium, 10, Items.metaglass, 15)); requirements(Category.liquid, with(Items.titanium, 10, Items.metaglass, 15));
liquidCapacity = 700f; liquidCapacity = 700f;
size = 2; size = 2;
newDrawing = true;
}}; }};
liquidTank = new LiquidRouter("liquid-tank"){{ liquidTank = new LiquidRouter("liquid-tank"){{
@@ -1640,6 +1658,7 @@ public class Blocks implements ContentList{
size = 3; size = 3;
liquidCapacity = 1800f; liquidCapacity = 1800f;
health = 500; health = 500;
newDrawing = true;
}}; }};
liquidJunction = new LiquidJunction("liquid-junction"){{ liquidJunction = new LiquidJunction("liquid-junction"){{
@@ -1705,18 +1724,24 @@ public class Blocks implements ContentList{
reinforcedLiquidRouter = new LiquidRouter("reinforced-liquid-router"){{ reinforcedLiquidRouter = new LiquidRouter("reinforced-liquid-router"){{
requirements(Category.liquid, with(Items.graphite, 4, Items.beryllium, 2)); requirements(Category.liquid, with(Items.graphite, 4, Items.beryllium, 2));
liquidCapacity = 30f; liquidCapacity = 30f;
newDrawing = true;
liquidPadding = 3f/4f;
}}; }};
reinforcedLiquidContainer = new LiquidRouter("reinforced-liquid-container"){{ reinforcedLiquidContainer = new LiquidRouter("reinforced-liquid-container"){{
requirements(Category.liquid, with(Items.graphite, 10, Items.beryllium, 15)); requirements(Category.liquid, with(Items.graphite, 10, Items.beryllium, 15));
liquidCapacity = 1000f; liquidCapacity = 1000f;
size = 2; size = 2;
newDrawing = true;
liquidPadding = 6f/4f;
}}; }};
reinforcedLiquidTank = new LiquidRouter("reinforced-liquid-tank"){{ reinforcedLiquidTank = new LiquidRouter("reinforced-liquid-tank"){{
requirements(Category.liquid, with(Items.tungsten, 30, Items.beryllium, 40)); requirements(Category.liquid, with(Items.tungsten, 30, Items.beryllium, 40));
size = 3; size = 3;
liquidCapacity = 2700f; liquidCapacity = 2700f;
newDrawing = true;
liquidPadding = 2f;
}}; }};
//endregion //endregion
@@ -1958,6 +1983,8 @@ public class Blocks implements ContentList{
consumes.liquid(Liquids.water, 0.2f); consumes.liquid(Liquids.water, 0.2f);
}}; }};
//TODO higher tier impact drill, 5x5
waterExtractor = new SolidPump("water-extractor"){{ waterExtractor = new SolidPump("water-extractor"){{
requirements(Category.production, with(Items.metaglass, 30, Items.graphite, 30, Items.lead, 30, Items.copper, 30)); requirements(Category.production, with(Items.metaglass, 30, Items.graphite, 30, Items.lead, 30, Items.copper, 30));
result = Liquids.water; result = Liquids.water;

View File

@@ -7,7 +7,7 @@ import mindustry.type.*;
public class Liquids implements ContentList{ public class Liquids implements ContentList{
public static Liquid water, slag, oil, cryofluid, neoplasm, public static Liquid water, slag, oil, cryofluid, neoplasm,
gallium, gallium,
ozone, hydrogen, cyanogen; ozone, hydrogen, nitrogen, cyanogen;
@Override @Override
public void load(){ public void load(){
@@ -64,7 +64,7 @@ public class Liquids implements ContentList{
}}; }};
//TODO reactivity, etc //TODO reactivity, etc
ozone = new Liquid("ozone", Color.valueOf("f099da")){{ ozone = new Liquid("ozone", Color.valueOf("fc81dd")){{
gas = true; gas = true;
barColor = Color.valueOf("d699f0"); barColor = Color.valueOf("d699f0");
explosiveness = 1f; explosiveness = 1f;
@@ -78,9 +78,12 @@ public class Liquids implements ContentList{
flammability = 1f; flammability = 1f;
}}; }};
nitrogen = new Liquid("nitrogen", Color.valueOf("e2cffa")){{
gas = true;
}};
cyanogen = new Liquid("cyanogen", Color.valueOf("89e8b6")){{ cyanogen = new Liquid("cyanogen", Color.valueOf("89e8b6")){{
gas = true; gas = true;
barColor = color;
flammability = 2f; flammability = 2f;
}}; }};

View File

@@ -16,6 +16,10 @@ import static mindustry.entities.Puddles.*;
/** A better name for this class would be "fluid", but it's too late for that. */ /** A better name for this class would be "fluid", but it's too late for that. */
public class Liquid extends UnlockableContent{ public class Liquid extends UnlockableContent{
//must be static and global so conduits don't conflict - DO NOT INTERACT WITH THESE IN MODS OR I WILL PERSONALLY YELL AT YOU
public static final int animationFrames = 40;
public static float animationScale = 190f;
protected static final Rand rand = new Rand(); protected static final Rand rand = new Rand();
/** TODO If true, this fluid is treated as a gas (and does not create puddles) */ /** TODO If true, this fluid is treated as a gas (and does not create puddles) */
@@ -74,6 +78,9 @@ public class Liquid extends UnlockableContent{
color.a = 0.6f; color.a = 0.6f;
//for gases, gas color is implicitly their color //for gases, gas color is implicitly their color
gasColor = color; gasColor = color;
if(barColor == null){
barColor = color.cpy().a(1f);
}
} }
} }

View File

@@ -20,6 +20,7 @@ import mindustry.world.blocks.*;
import mindustry.world.blocks.distribution.*; import mindustry.world.blocks.distribution.*;
import static mindustry.Vars.*; import static mindustry.Vars.*;
import static mindustry.type.Liquid.*;
public class Conduit extends LiquidBlock implements Autotiler{ public class Conduit extends LiquidBlock implements Autotiler{
public final int timerFlow = timers++; public final int timerFlow = timers++;
@@ -28,8 +29,13 @@ public class Conduit extends LiquidBlock implements Autotiler{
public @Load(value = "@-top-#", length = 5) TextureRegion[] topRegions; public @Load(value = "@-top-#", length = 5) TextureRegion[] topRegions;
public @Load(value = "@-bottom-#", length = 5, fallback = "conduit-bottom-#") TextureRegion[] botRegions; public @Load(value = "@-bottom-#", length = 5, fallback = "conduit-bottom-#") TextureRegion[] botRegions;
public @Load(value = "@-liquid-r#", length = 4, fallback = "conduit-liquid-r#") TextureRegion[] liquidRotateRegions;
public @Load(value = "@-liquid", fallback = "conduit-liquid") TextureRegion liquidBaseRegion;
public @Load("@-cap") TextureRegion capRegion; public @Load("@-cap") TextureRegion capRegion;
public @Load(value = "conduit-liquid-#", length = animationFrames) TextureRegion[] gasRegions;
public @Load(value = "conduit-liquid-r#1-#2", lengths = {4, animationFrames}) TextureRegion[][] rotateGasRegions;
public boolean leaks = true; public boolean leaks = true;
public @Nullable Block junctionReplacement, bridgeReplacement, rotBridgeReplacement; public @Nullable Block junctionReplacement, bridgeReplacement, rotBridgeReplacement;
@@ -106,7 +112,6 @@ public class Conduit extends LiquidBlock implements Autotiler{
@Override @Override
public void draw(){ public void draw(){
float rotation = rotdeg();
int r = this.rotation; int r = this.rotation;
//draw extra conduits facing this one for tiling purposes //draw extra conduits facing this one for tiling purposes
@@ -114,28 +119,40 @@ public class Conduit extends LiquidBlock implements Autotiler{
for(int i = 0; i < 4; i++){ for(int i = 0; i < 4; i++){
if((blending & (1 << i)) != 0){ if((blending & (1 << i)) != 0){
int dir = r - i; int dir = r - i;
float rot = i == 0 ? rotation : (dir)*90; drawAt(x + Geometry.d4x(dir) * tilesize*0.75f, y + Geometry.d4y(dir) * tilesize*0.75f, 0, i == 0 ? r : dir, i != 0 ? SliceMode.bottom : SliceMode.top);
drawAt(x + Geometry.d4x(dir) * tilesize*0.75f, y + Geometry.d4y(dir) * tilesize*0.75f, 0, rot, i != 0 ? SliceMode.bottom : SliceMode.top);
} }
} }
Draw.z(Layer.block); Draw.z(Layer.block);
Draw.scl(xscl, yscl); Draw.scl(xscl, yscl);
drawAt(x, y, blendbits, rotation, SliceMode.none); drawAt(x, y, blendbits, r, SliceMode.none);
Draw.reset(); Draw.reset();
if(capped && capRegion.found()) Draw.rect(capRegion, x, y, rotdeg()); if(capped && capRegion.found()) Draw.rect(capRegion, x, y, rotdeg());
if(backCapped && capRegion.found()) Draw.rect(capRegion, x, y, rotdeg() + 180); if(backCapped && capRegion.found()) Draw.rect(capRegion, x, y, rotdeg() + 180);
} }
protected void drawAt(float x, float y, int bits, float rotation, SliceMode slice){ protected void drawAt(float x, float y, int bits, int rotation, SliceMode slice){
float angle = rotation * 90f;
Draw.color(botColor); Draw.color(botColor);
Draw.rect(sliced(botRegions[bits], slice), x, y, rotation); Draw.rect(sliced(botRegions[bits], slice), x, y, angle);
Drawf.liquid(sliced(botRegions[bits], slice), x, y, smoothLiquid, liquids.current().color, rotation); int offset = yscl == -1 ? 3 : 0;
//TODO move out of conduit
int frame = (int)(Time.time / animationScale * animationFrames) % animationFrames;
TextureRegion liquidr =
liquids.current().gas ?
(bits == 1 ? rotateGasRegions[(rotation + offset) % 4][frame] : gasRegions[frame]) :
(bits == 1 ? liquidRotateRegions[(rotation + offset) % 4] : liquidBaseRegion);
Draw.rect(sliced(topRegions[bits], slice), x, y, rotation); //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, y, smoothLiquid, liquids.current().color.write(Tmp.c1).a(1f));
Draw.scl(xscl, yscl);
Draw.rect(sliced(topRegions[bits], slice), x, y, angle);
} }
@Override @Override

View File

@@ -1,12 +1,17 @@
package mindustry.world.blocks.liquid; package mindustry.world.blocks.liquid;
import arc.graphics.*;
import arc.graphics.g2d.*; import arc.graphics.g2d.*;
import arc.util.*;
import mindustry.annotations.Annotations.*; import mindustry.annotations.Annotations.*;
import mindustry.gen.*; import mindustry.gen.*;
import mindustry.graphics.*; import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*; import mindustry.world.*;
import mindustry.world.meta.*; import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class LiquidBlock extends Block{ public class LiquidBlock extends Block{
public @Load("@-liquid") TextureRegion liquidRegion; public @Load("@-liquid") TextureRegion liquidRegion;
public @Load("@-top") TextureRegion topRegion; public @Load("@-top") TextureRegion topRegion;
@@ -27,6 +32,39 @@ public class LiquidBlock extends Block{
return new TextureRegion[]{bottomRegion, topRegion}; return new TextureRegion[]{bottomRegion, topRegion};
} }
public static void drawTiledGas(TextureRegion[] gasRegions, int size, float x, float y, float padding, Color color, float alpha){
TextureRegion region = gasRegions[(int)(Time.time / Liquid.animationScale * Liquid.animationFrames) % Liquid.animationFrames];
TextureRegion toDraw = Tmp.tr1;
float bounds = size/2f * tilesize - padding;
for(int sx = 0; sx < size; sx++){
for(int sy = 0; sy < size; sy++){
float relx = sx - (size-1)/2f, rely = sy - (size-1)/2f;
toDraw.set(region);
//truncate region if at border
float rightBorder = relx*tilesize + padding, topBorder = rely*tilesize + padding;
float squishX = rightBorder + tilesize/2f - bounds, squishY = topBorder + tilesize/2f - bounds;
float ox = 0f, oy = 0f;
//cut out the parts that don't fit inside the padding
if(squishX > 0){
toDraw.setWidth(toDraw.width - squishX * 4f);
ox = -squishX/2f;
}
if(squishY > 0){
toDraw.setY(toDraw.getY() + squishY * 4f);
oy = -squishY/2f;
}
Drawf.liquid(toDraw, x + rightBorder + ox, y + topBorder + oy, alpha, Tmp.c1.set(color).a(1f));
}
}
}
public class LiquidBuild extends Building{ public class LiquidBuild extends Building{
@Override @Override
public void draw(){ public void draw(){

View File

@@ -1,9 +1,18 @@
package mindustry.world.blocks.liquid; package mindustry.world.blocks.liquid;
import arc.graphics.g2d.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*; import mindustry.gen.*;
import mindustry.type.*; import mindustry.type.*;
import static mindustry.Vars.*;
public class LiquidRouter extends LiquidBlock{ public class LiquidRouter extends LiquidBlock{
/** kept only for mod compatibility reasons; all vanilla blocks have this as true */
public boolean newDrawing = false;
public float liquidPadding = 0f;
public @Load(value = "conduit-liquid-#", length = Liquid.animationFrames) TextureRegion[] gasRegions;
public LiquidRouter(String name){ public LiquidRouter(String name){
super(name); super(name);
@@ -12,6 +21,11 @@ public class LiquidRouter extends LiquidBlock{
canOverdrive = false; canOverdrive = false;
} }
@Override
public TextureRegion[] icons(){
return newDrawing ? new TextureRegion[]{bottomRegion, region} : super.icons();
}
public class LiquidRouterBuild extends LiquidBuild{ public class LiquidRouterBuild extends LiquidBuild{
@Override @Override
public void updateTile(){ public void updateTile(){
@@ -20,6 +34,28 @@ public class LiquidRouter extends LiquidBlock{
} }
} }
@Override
public void draw(){
if(newDrawing){
Draw.rect(bottomRegion, x, y);
if(liquids.currentAmount() > 0.001f){
if(liquids.current().gas){
drawTiledGas(gasRegions, size, x, y, liquidPadding, liquids.current().color, liquids.currentAmount() / liquidCapacity);
}else{
Draw.color(liquids.current().color, liquids.currentAmount() / liquidCapacity);
Fill.square(x, y, size * tilesize/2f - liquidPadding);
Draw.color();
}
}
Draw.rect(region, x, y);
}else{
super.draw();
}
}
@Override @Override
public boolean acceptLiquid(Building source, Liquid liquid){ public boolean acceptLiquid(Building source, Liquid liquid){
return (liquids.current() == liquid || liquids.currentAmount() < 0.2f); return (liquids.current() == liquid || liquids.currentAmount() < 0.2f);

View File

@@ -24,4 +24,4 @@ android.useAndroidX=true
#used for slow jitpack builds; TODO see if this actually works #used for slow jitpack builds; TODO see if this actually works
org.gradle.internal.http.socketTimeout=100000 org.gradle.internal.http.socketTimeout=100000
org.gradle.internal.http.connectionTimeout=100000 org.gradle.internal.http.connectionTimeout=100000
archash=e98a7f21 archash=ee4906c1

View File

@@ -30,6 +30,24 @@ import static mindustry.tools.ImagePacker.*;
public class Generators{ public class Generators{
static final int logicIconSize = (int)iconMed, maxUiIcon = 128; static final int logicIconSize = (int)iconMed, maxUiIcon = 128;
private static final int keyframes = 4;
private static float gas(double x, double y, float frame){
int curFrame = (int)(frame * keyframes);
int nextFrame = (curFrame + 1) % keyframes;
float progress = (frame * keyframes) % 1f;
//interpolate between the current two keyframes
return Mathf.lerp((float)gasFrame(x, y, curFrame), (float)gasFrame(x, y, nextFrame), progress);
}
private static double gasFrame(double x, double y, int frame){
int s = 31;
//calculate random space offsets for the frame cutout
double ox = Mathf.randomSeed(frame, 200_000), oy = Mathf.randomSeed(frame, 200_000);
double scale = 21, second = 0.3;
return (Simplex.rawTiled(x, y, ox, oy, s, s, scale) + Simplex.rawTiled(x, y, ox, oy, s, s, scale / 1.5) * second) / (1.0 + second);
}
public static void run(){ public static void run(){
ObjectMap<Block, Pixmap> gens = new ObjectMap<>(); ObjectMap<Block, Pixmap> gens = new ObjectMap<>();
@@ -45,12 +63,14 @@ public class Generators{
Pixmap pixmap = new Pixmap(size, size); Pixmap pixmap = new Pixmap(size, size);
pixmap.each((x, y) -> { for(int x = 0; x < pixmap.width; x++){
float dst = Mathf.dst(x, y, size/2f, size/2f); for(int y = 0; y < pixmap.height; y++){
if(Math.abs(dst - radius) <= stroke){ float dst = Mathf.dst(x, y, size/2f, size/2f);
pixmap.set(x, y, Color.white); if(Math.abs(dst - radius) <= stroke){
pixmap.set(x, y, Color.white);
}
} }
}); }
Fi.get("splash-" + i + ".png").writePng(pixmap); Fi.get("splash-" + i + ".png").writePng(pixmap);
@@ -85,6 +105,33 @@ public class Generators{
} }
}); });
generate("gas-frames", () -> {
int frames = Liquid.animationFrames;
String[] stencils = {"conduit-liquid", "conduit-liquid-r0", "conduit-liquid-r1", "conduit-liquid-r2", "conduit-liquid-r3"};
for(String region : stencils){
Pixmap base = get(region);
float min = 0.56f, imin = 1f - min;
for(int i = 0; i < frames; i++){
float frame = i / (float)frames;
Pixmap copy = base.copy();
for(int x = 0; x < copy.width; x++){
for(int y = 0; y < copy.height; y++){
if(copy.getA(x, y) > 128){
double value = gas(x, y, frame);
float va = min + imin*(float)(value);
copy.setRaw(x, y, Color.rgba8888(1f, 1f, 1f, va));
}
}
}
save(copy, region + "-" + i);
}
}
});
generate("cliffs", () -> { generate("cliffs", () -> {
ExecutorService exec = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); ExecutorService exec = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
int size = 64; int size = 64;