From d6016f1b04f150527ee6bebeed866ff770e179af Mon Sep 17 00:00:00 2001 From: Anuken Date: Sat, 12 Jun 2021 16:59:18 -0400 Subject: [PATCH 01/31] Support for hidden weather --- core/src/mindustry/content/Weathers.java | 1 + core/src/mindustry/type/Weather.java | 1 + core/src/mindustry/ui/dialogs/CustomRulesDialog.java | 1 + 3 files changed, 3 insertions(+) diff --git a/core/src/mindustry/content/Weathers.java b/core/src/mindustry/content/Weathers.java index 262c38475b..ab03e32eda 100644 --- a/core/src/mindustry/content/Weathers.java +++ b/core/src/mindustry/content/Weathers.java @@ -109,6 +109,7 @@ public class Weathers implements ContentList{ particleRegion = "particle"; statusGround = false; useWindVector = true; + hidden = true; sizeMax = 4f; sizeMin = 1.4f; minAlpha = 0.5f; diff --git a/core/src/mindustry/type/Weather.java b/core/src/mindustry/type/Weather.java index fe4c0d3aa6..86935bd8d1 100644 --- a/core/src/mindustry/type/Weather.java +++ b/core/src/mindustry/type/Weather.java @@ -30,6 +30,7 @@ public class Weather extends UnlockableContent{ public Sound sound = Sounds.none; public float soundVol = 0.1f, soundVolMin = 0f; public float soundVolOscMag = 0f, soundVolOscScl = 20f; + public boolean hidden = false; //internals public Prov type = WeatherState::create; diff --git a/core/src/mindustry/ui/dialogs/CustomRulesDialog.java b/core/src/mindustry/ui/dialogs/CustomRulesDialog.java index bf150bcd6a..6c763e840b 100644 --- a/core/src/mindustry/ui/dialogs/CustomRulesDialog.java +++ b/core/src/mindustry/ui/dialogs/CustomRulesDialog.java @@ -337,6 +337,7 @@ public class CustomRulesDialog extends BaseDialog{ t.background(Tex.button); int i = 0; for(Weather weather : content.getBy(ContentType.weather)){ + if(weather.hidden) continue; t.button(weather.localizedName, Styles.cleart, () -> { rules.weather.add(new WeatherEntry(weather)); From 347b38ba26884fe2ea3239058acd4f436d4d8b73 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sat, 12 Jun 2021 20:59:28 -0400 Subject: [PATCH 02/31] Added unit tests for Java, JavaScript and JSON mods --- build.gradle | 2 + core/src/mindustry/content/Blocks.java | 1 - core/src/mindustry/graphics/CacheLayer.java | 3 +- core/src/mindustry/mod/ClassMap.java | 11 +- .../world/blocks/experimental/BlockForge.java | 14 ++ .../blocks/experimental/BlockLoader.java | 142 +-------------- .../blocks/experimental/BlockUnloader.java | 61 +------ .../world/blocks/payloads/BlockLoader.java | 147 ++++++++++++++++ .../world/blocks/payloads/BlockUnloader.java | 67 +++++++ .../world/blocks/payloads/PayloadVoid.java | 2 +- .../src/mindustry/server/ServerLauncher.java | 10 -- tests/src/test/java/ApplicationTests.java | 165 +++++++++++++++++- tests/src/test/java/GenericModTest.java | 35 ++++ tests/src/test/java/IOTests.java | 63 ------- tests/src/test/java/ModTestBM.java | 39 +++++ tests/src/test/java/ModTestExotic.java | 40 +++++ tests/src/test/java/ModTestHAI.java | 39 +++++ tests/src/test/java/SectorTests.java | 105 ----------- .../test/java/power/DirectConsumerTests.java | 37 ++-- .../java/power/ItemLiquidGeneratorTests.java | 1 - 20 files changed, 587 insertions(+), 397 deletions(-) create mode 100644 core/src/mindustry/world/blocks/experimental/BlockForge.java create mode 100644 core/src/mindustry/world/blocks/payloads/BlockLoader.java create mode 100644 core/src/mindustry/world/blocks/payloads/BlockUnloader.java create mode 100644 tests/src/test/java/GenericModTest.java delete mode 100644 tests/src/test/java/IOTests.java create mode 100644 tests/src/test/java/ModTestBM.java create mode 100644 tests/src/test/java/ModTestExotic.java create mode 100644 tests/src/test/java/ModTestHAI.java delete mode 100644 tests/src/test/java/SectorTests.java diff --git a/build.gradle b/build.gradle index 950d2cda44..27969b5cb6 100644 --- a/build.gradle +++ b/build.gradle @@ -430,6 +430,8 @@ project(":tests"){ } test{ + //fork every test so mods don't interact with each other + forkEvery = 1 useJUnitPlatform() workingDir = new File("../core/assets") testLogging{ diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index 469eba9ce4..2e30852194 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -16,7 +16,6 @@ import mindustry.world.blocks.defense.*; import mindustry.world.blocks.defense.turrets.*; import mindustry.world.blocks.distribution.*; import mindustry.world.blocks.environment.*; -import mindustry.world.blocks.experimental.*; import mindustry.world.blocks.legacy.*; import mindustry.world.blocks.liquid.*; import mindustry.world.blocks.logic.*; diff --git a/core/src/mindustry/graphics/CacheLayer.java b/core/src/mindustry/graphics/CacheLayer.java index 31b8074196..a68b7f6e06 100644 --- a/core/src/mindustry/graphics/CacheLayer.java +++ b/core/src/mindustry/graphics/CacheLayer.java @@ -3,6 +3,7 @@ package mindustry.graphics; import arc.*; import arc.graphics.*; import arc.graphics.gl.*; +import arc.util.*; import static mindustry.Vars.*; @@ -53,7 +54,7 @@ public class CacheLayer{ } public static class ShaderLayer extends CacheLayer{ - public Shader shader; + public @Nullable Shader shader; public ShaderLayer(Shader shader){ //shader will be null on headless backend, but that's ok diff --git a/core/src/mindustry/mod/ClassMap.java b/core/src/mindustry/mod/ClassMap.java index 0961e12999..de033c0e9a 100644 --- a/core/src/mindustry/mod/ClassMap.java +++ b/core/src/mindustry/mod/ClassMap.java @@ -2,6 +2,7 @@ package mindustry.mod; import arc.struct.*; import mindustry.world.blocks.environment.*; +import mindustry.world.blocks.payloads.*; /** Generated class. Maps simple class names to concrete classes. For use in JSON mods. */ @SuppressWarnings("deprecation") @@ -183,10 +184,10 @@ public class ClassMap{ classes.put("TreeBlock", mindustry.world.blocks.environment.TreeBlock.class); classes.put("WallOreBlock", mindustry.world.blocks.environment.WallOreBlock.class); classes.put("WobbleProp", mindustry.world.blocks.environment.WobbleProp.class); - classes.put("BlockLoader", mindustry.world.blocks.experimental.BlockLoader.class); - classes.put("BlockLoaderBuild", mindustry.world.blocks.experimental.BlockLoader.BlockLoaderBuild.class); - classes.put("BlockUnloader", mindustry.world.blocks.experimental.BlockUnloader.class); - classes.put("BlockUnloaderBuild", mindustry.world.blocks.experimental.BlockUnloader.BlockUnloaderBuild.class); + classes.put("BlockLoader", BlockLoader.class); + classes.put("BlockLoaderBuild", BlockLoader.BlockLoaderBuild.class); + classes.put("BlockUnloader", BlockUnloader.class); + classes.put("BlockUnloaderBuild", BlockUnloader.BlockUnloaderBuild.class); classes.put("LegacyBlock", mindustry.world.blocks.legacy.LegacyBlock.class); classes.put("LegacyMechPad", mindustry.world.blocks.legacy.LegacyMechPad.class); classes.put("LegacyMechPadBuild", mindustry.world.blocks.legacy.LegacyMechPad.LegacyMechPadBuild.class); @@ -237,7 +238,7 @@ public class ClassMap{ classes.put("PayloadSource", mindustry.world.blocks.payloads.PayloadSource.class); classes.put("PayloadSourceBuild", mindustry.world.blocks.payloads.PayloadSource.PayloadSourceBuild.class); classes.put("PayloadVoid", mindustry.world.blocks.payloads.PayloadVoid.class); - classes.put("BlockLoaderBuild", mindustry.world.blocks.payloads.PayloadVoid.BlockLoaderBuild.class); + classes.put("BlockLoaderBuild", PayloadVoid.PayloadVoidBuild.class); classes.put("UnitPayload", mindustry.world.blocks.payloads.UnitPayload.class); classes.put("Battery", mindustry.world.blocks.power.Battery.class); classes.put("BatteryBuild", mindustry.world.blocks.power.Battery.BatteryBuild.class); diff --git a/core/src/mindustry/world/blocks/experimental/BlockForge.java b/core/src/mindustry/world/blocks/experimental/BlockForge.java new file mode 100644 index 0000000000..29adc64d00 --- /dev/null +++ b/core/src/mindustry/world/blocks/experimental/BlockForge.java @@ -0,0 +1,14 @@ +package mindustry.world.blocks.experimental; + +@Deprecated +public class BlockForge extends mindustry.world.blocks.payloads.BlockForge{ + + public BlockForge(String name){ + super(name); + } + + @Deprecated + public class BlockForgeBuild extends mindustry.world.blocks.payloads.BlockForge.BlockForgeBuild{ + + } +} diff --git a/core/src/mindustry/world/blocks/experimental/BlockLoader.java b/core/src/mindustry/world/blocks/experimental/BlockLoader.java index 41f2aa13f8..91a044d4f8 100644 --- a/core/src/mindustry/world/blocks/experimental/BlockLoader.java +++ b/core/src/mindustry/world/blocks/experimental/BlockLoader.java @@ -1,148 +1,14 @@ package mindustry.world.blocks.experimental; -import arc.*; -import arc.graphics.g2d.*; -import arc.util.*; -import mindustry.entities.units.*; -import mindustry.gen.*; -import mindustry.graphics.*; -import mindustry.type.*; -import mindustry.ui.*; -import mindustry.world.blocks.payloads.*; - -import static mindustry.Vars.*; - -public class BlockLoader extends PayloadBlock{ - public final int timerLoad = timers++; - - public float loadTime = 2f; - public int itemsLoaded = 5; - public float liquidsLoaded = 5f; - public int maxBlockSize = 2; +@Deprecated +public class BlockLoader extends mindustry.world.blocks.payloads.BlockLoader{ public BlockLoader(String name){ super(name); - - hasItems = true; - itemCapacity = 25; - //liquidCapacity = 25; - update = true; - outputsPayload = true; - size = 3; - rotate = true; } - @Override - public TextureRegion[] icons(){ - return new TextureRegion[]{region, inRegion, outRegion, topRegion}; - } + @Deprecated + public class BlockLoaderBuild extends mindustry.world.blocks.payloads.BlockLoader.BlockLoaderBuild{ - @Override - public boolean outputsItems(){ - return false; - } - - @Override - public void setBars(){ - super.setBars(); - - bars.add("progress", (BlockLoaderBuild entity) -> new Bar(() -> Core.bundle.format("bar.items", entity.payload == null ? 0 : entity.payload.build.items.total()), () -> Pal.items, entity::fraction)); - } - - @Override - public void drawRequestRegion(BuildPlan req, Eachable list){ - Draw.rect(region, req.drawx(), req.drawy()); - Draw.rect(inRegion, req.drawx(), req.drawy(), req.rotation * 90); - Draw.rect(outRegion, req.drawx(), req.drawy(), req.rotation * 90); - Draw.rect(topRegion, req.drawx(), req.drawy()); - } - - public class BlockLoaderBuild extends PayloadBlockBuild{ - - @Override - public boolean acceptPayload(Building source, Payload payload){ - return super.acceptPayload(source, payload) && - (payload instanceof BuildPayload build) && - ((build.build.block.hasItems && build.block().unloadable && build.block().itemCapacity >= 10 && build.block().size <= maxBlockSize)/* || - ((BlockPayload)payload).entity.block().hasLiquids && ((BlockPayload)payload).block().liquidCapacity >= 10f)*/); - } - - @Override - public boolean acceptItem(Building source, Item item){ - return items.total() < itemCapacity; - } - - @Override - public void draw(){ - Draw.rect(region, x, y); - - //draw input - boolean fallback = true; - for(int i = 0; i < 4; i++){ - if(blends(i) && i != rotation){ - Draw.rect(inRegion, x, y, (i * 90) - 180); - fallback = false; - } - } - if(fallback) Draw.rect(inRegion, x, y, rotation * 90); - - Draw.rect(outRegion, x, y, rotdeg()); - - Draw.z(Layer.blockOver); - drawPayload(); - - Draw.z(Layer.blockOver + 0.1f); - Draw.rect(topRegion, x, y); - } - - @Override - public void updateTile(){ - if(shouldExport()){ - moveOutPayload(); - }else if(moveInPayload()){ - - //load up items - if(payload.block().hasItems && items.any()){ - if(efficiency() > 0.01f && timer(timerLoad, loadTime / efficiency())){ - //load up items a set amount of times - for(int j = 0; j < itemsLoaded && items.any(); j++){ - - for(int i = 0; i < items.length(); i++){ - if(items.get(i) > 0){ - Item item = content.item(i); - if(payload.build.acceptItem(payload.build, item)){ - payload.build.handleItem(payload.build, item); - items.remove(item, 1); - break; - } - } - } - } - } - } - - //load up liquids (disabled) - /* - if(payload.block().hasLiquids && liquids.total() >= 0.001f){ - Liquid liq = liquids.current(); - float total = liquids.currentAmount(); - float flow = Math.min(Math.min(liquidsLoaded * delta(), payload.block().liquidCapacity - payload.entity.liquids.get(liq) - 0.0001f), total); - if(payload.entity.acceptLiquid(payload.entity, liq, flow)){ - payload.entity.liquids.add(liq, flow); - liquids.remove(liq, flow); - } - }*/ - } - } - - public float fraction(){ - return payload == null ? 0f : payload.build.items.total() / (float)payload.build.block.itemCapacity; - } - - public boolean shouldExport(){ - return payload != null && - ((payload.block().hasLiquids && payload.build.liquids.total() >= payload.block().liquidCapacity - 0.001f) || - (payload.block().hasItems && payload.build.items.total() >= payload.block().itemCapacity)); - } } } diff --git a/core/src/mindustry/world/blocks/experimental/BlockUnloader.java b/core/src/mindustry/world/blocks/experimental/BlockUnloader.java index 53d4df1b66..1c655318e3 100644 --- a/core/src/mindustry/world/blocks/experimental/BlockUnloader.java +++ b/core/src/mindustry/world/blocks/experimental/BlockUnloader.java @@ -1,67 +1,14 @@ package mindustry.world.blocks.experimental; -import mindustry.gen.*; -import mindustry.type.*; - -import static mindustry.Vars.*; - -public class BlockUnloader extends BlockLoader{ +@Deprecated +public class BlockUnloader extends mindustry.world.blocks.payloads.BlockUnloader{ public BlockUnloader(String name){ super(name); } - @Override - public boolean outputsItems(){ - return true; - } + @Deprecated + public class BlockUnloaderBuild extends mindustry.world.blocks.payloads.BlockUnloader.BlockUnloaderBuild{ - @Override - public boolean rotatedOutput(int x, int y){ - return false; - } - - public class BlockUnloaderBuild extends BlockLoaderBuild{ - - @Override - public boolean acceptItem(Building source, Item item){ - return false; - } - - @Override - public void updateTile(){ - if(shouldExport()){ - moveOutPayload(); - }else if(moveInPayload()){ - - //load up items - if(payload.block().hasItems && !full()){ - if(efficiency() > 0.01f && timer(timerLoad, loadTime / efficiency())){ - //load up items a set amount of times - for(int j = 0; j < itemsLoaded && !full(); j++){ - for(int i = 0; i < items.length(); i++){ - if(payload.build.items.get(i) > 0){ - Item item = content.item(i); - payload.build.items.remove(item, 1); - items.add(item, 1); - break; - } - } - } - } - } - } - - dump(); - } - - public boolean full(){ - return items.total() >= itemCapacity; - } - - @Override - public boolean shouldExport(){ - return payload != null && (payload.block().hasItems && payload.build.items.empty()); - } } } diff --git a/core/src/mindustry/world/blocks/payloads/BlockLoader.java b/core/src/mindustry/world/blocks/payloads/BlockLoader.java new file mode 100644 index 0000000000..001f93712a --- /dev/null +++ b/core/src/mindustry/world/blocks/payloads/BlockLoader.java @@ -0,0 +1,147 @@ +package mindustry.world.blocks.payloads; + +import arc.*; +import arc.graphics.g2d.*; +import arc.util.*; +import mindustry.entities.units.*; +import mindustry.gen.*; +import mindustry.graphics.*; +import mindustry.type.*; +import mindustry.ui.*; + +import static mindustry.Vars.*; + +public class BlockLoader extends PayloadBlock{ + public final int timerLoad = timers++; + + public float loadTime = 2f; + public int itemsLoaded = 5; + public float liquidsLoaded = 5f; + public int maxBlockSize = 2; + + public BlockLoader(String name){ + super(name); + + hasItems = true; + itemCapacity = 25; + //liquidCapacity = 25; + update = true; + outputsPayload = true; + size = 3; + rotate = true; + } + + @Override + public TextureRegion[] icons(){ + return new TextureRegion[]{region, inRegion, outRegion, topRegion}; + } + + @Override + public boolean outputsItems(){ + return false; + } + + @Override + public void setBars(){ + super.setBars(); + + bars.add("progress", (BlockLoaderBuild entity) -> new Bar(() -> Core.bundle.format("bar.items", entity.payload == null ? 0 : entity.payload.build.items.total()), () -> Pal.items, entity::fraction)); + } + + @Override + public void drawRequestRegion(BuildPlan req, Eachable list){ + Draw.rect(region, req.drawx(), req.drawy()); + Draw.rect(inRegion, req.drawx(), req.drawy(), req.rotation * 90); + Draw.rect(outRegion, req.drawx(), req.drawy(), req.rotation * 90); + Draw.rect(topRegion, req.drawx(), req.drawy()); + } + + public class BlockLoaderBuild extends PayloadBlockBuild{ + + @Override + public boolean acceptPayload(Building source, Payload payload){ + return super.acceptPayload(source, payload) && + (payload instanceof BuildPayload build) && + ((build.build.block.hasItems && build.block().unloadable && build.block().itemCapacity >= 10 && build.block().size <= maxBlockSize)/* || + ((BlockPayload)payload).entity.block().hasLiquids && ((BlockPayload)payload).block().liquidCapacity >= 10f)*/); + } + + @Override + public boolean acceptItem(Building source, Item item){ + return items.total() < itemCapacity; + } + + @Override + public void draw(){ + Draw.rect(region, x, y); + + //draw input + boolean fallback = true; + for(int i = 0; i < 4; i++){ + if(blends(i) && i != rotation){ + Draw.rect(inRegion, x, y, (i * 90) - 180); + fallback = false; + } + } + if(fallback) Draw.rect(inRegion, x, y, rotation * 90); + + Draw.rect(outRegion, x, y, rotdeg()); + + Draw.z(Layer.blockOver); + drawPayload(); + + Draw.z(Layer.blockOver + 0.1f); + Draw.rect(topRegion, x, y); + } + + @Override + public void updateTile(){ + if(shouldExport()){ + moveOutPayload(); + }else if(moveInPayload()){ + + //load up items + if(payload.block().hasItems && items.any()){ + if(efficiency() > 0.01f && timer(timerLoad, loadTime / efficiency())){ + //load up items a set amount of times + for(int j = 0; j < itemsLoaded && items.any(); j++){ + + for(int i = 0; i < items.length(); i++){ + if(items.get(i) > 0){ + Item item = content.item(i); + if(payload.build.acceptItem(payload.build, item)){ + payload.build.handleItem(payload.build, item); + items.remove(item, 1); + break; + } + } + } + } + } + } + + //load up liquids (disabled) + /* + if(payload.block().hasLiquids && liquids.total() >= 0.001f){ + Liquid liq = liquids.current(); + float total = liquids.currentAmount(); + float flow = Math.min(Math.min(liquidsLoaded * delta(), payload.block().liquidCapacity - payload.entity.liquids.get(liq) - 0.0001f), total); + if(payload.entity.acceptLiquid(payload.entity, liq, flow)){ + payload.entity.liquids.add(liq, flow); + liquids.remove(liq, flow); + } + }*/ + } + } + + public float fraction(){ + return payload == null ? 0f : payload.build.items.total() / (float)payload.build.block.itemCapacity; + } + + public boolean shouldExport(){ + return payload != null && + ((payload.block().hasLiquids && payload.build.liquids.total() >= payload.block().liquidCapacity - 0.001f) || + (payload.block().hasItems && payload.build.items.total() >= payload.block().itemCapacity)); + } + } +} diff --git a/core/src/mindustry/world/blocks/payloads/BlockUnloader.java b/core/src/mindustry/world/blocks/payloads/BlockUnloader.java new file mode 100644 index 0000000000..8f4a644fce --- /dev/null +++ b/core/src/mindustry/world/blocks/payloads/BlockUnloader.java @@ -0,0 +1,67 @@ +package mindustry.world.blocks.payloads; + +import mindustry.gen.*; +import mindustry.type.*; + +import static mindustry.Vars.*; + +public class BlockUnloader extends BlockLoader{ + + public BlockUnloader(String name){ + super(name); + } + + @Override + public boolean outputsItems(){ + return true; + } + + @Override + public boolean rotatedOutput(int x, int y){ + return false; + } + + public class BlockUnloaderBuild extends BlockLoaderBuild{ + + @Override + public boolean acceptItem(Building source, Item item){ + return false; + } + + @Override + public void updateTile(){ + if(shouldExport()){ + moveOutPayload(); + }else if(moveInPayload()){ + + //load up items + if(payload.block().hasItems && !full()){ + if(efficiency() > 0.01f && timer(timerLoad, loadTime / efficiency())){ + //load up items a set amount of times + for(int j = 0; j < itemsLoaded && !full(); j++){ + for(int i = 0; i < items.length(); i++){ + if(payload.build.items.get(i) > 0){ + Item item = content.item(i); + payload.build.items.remove(item, 1); + items.add(item, 1); + break; + } + } + } + } + } + } + + dump(); + } + + public boolean full(){ + return items.total() >= itemCapacity; + } + + @Override + public boolean shouldExport(){ + return payload != null && (payload.block().hasItems && payload.build.items.empty()); + } + } +} diff --git a/core/src/mindustry/world/blocks/payloads/PayloadVoid.java b/core/src/mindustry/world/blocks/payloads/PayloadVoid.java index e1cb184f48..fa41218e3c 100644 --- a/core/src/mindustry/world/blocks/payloads/PayloadVoid.java +++ b/core/src/mindustry/world/blocks/payloads/PayloadVoid.java @@ -29,7 +29,7 @@ public class PayloadVoid extends PayloadBlock{ return new TextureRegion[]{region, topRegion}; } - public class BlockLoaderBuild extends PayloadBlockBuild{ + public class PayloadVoidBuild extends PayloadBlockBuild{ @Override public void draw(){ diff --git a/server/src/mindustry/server/ServerLauncher.java b/server/src/mindustry/server/ServerLauncher.java index 8c20047022..4d4d3d9aa1 100644 --- a/server/src/mindustry/server/ServerLauncher.java +++ b/server/src/mindustry/server/ServerLauncher.java @@ -2,7 +2,6 @@ package mindustry.server; import arc.*; import arc.backend.headless.*; -import arc.files.*; import arc.util.*; import mindustry.*; import mindustry.core.*; @@ -44,15 +43,6 @@ public class ServerLauncher implements ApplicationListener{ loadLocales = false; headless = true; - Fi plugins = Core.settings.getDataDirectory().child("plugins"); - if(plugins.isDirectory() && plugins.list().length > 0 && !plugins.sibling("mods").exists()){ - warn("[IMPORTANT NOTICE] &lrPlugins have been detected.&ly Automatically moving all contents of the plugin folder into the 'mods' folder. The original folder will not be removed; please do so manually."); - plugins.sibling("mods").mkdirs(); - for(Fi file : plugins.list()){ - file.copyTo(plugins.sibling("mods")); - } - } - Vars.loadSettings(); Vars.init(); content.createBaseContent(); diff --git a/tests/src/test/java/ApplicationTests.java b/tests/src/test/java/ApplicationTests.java index 8bb1a1524e..2ea226ab7c 100644 --- a/tests/src/test/java/ApplicationTests.java +++ b/tests/src/test/java/ApplicationTests.java @@ -1,10 +1,12 @@ import arc.*; import arc.backend.headless.*; +import arc.files.*; import arc.func.*; import arc.math.*; import arc.math.geom.*; import arc.struct.*; import arc.util.*; +import arc.util.io.*; import arc.util.serialization.*; import arc.util.serialization.JsonValue.*; import mindustry.*; @@ -16,21 +18,36 @@ import mindustry.entities.units.*; import mindustry.game.*; import mindustry.gen.*; import mindustry.io.*; +import mindustry.io.SaveIO.*; import mindustry.maps.*; +import mindustry.mod.*; +import mindustry.mod.Mods.*; import mindustry.net.Net; import mindustry.type.*; import mindustry.world.*; +import mindustry.world.blocks.storage.*; import org.junit.jupiter.api.*; +import org.junit.jupiter.params.*; +import org.junit.jupiter.params.provider.*; + +import java.nio.*; import static mindustry.Vars.*; import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.DynamicTest.*; public class ApplicationTests{ static Map testMap; static boolean initialized; + //core/assets + static final Fi testDataFolder = new Fi("../../tests/build/test_data"); @BeforeAll - static void launchApplication(){ + public static void launchApplication(){ + launchApplication(true); + } + + public static void launchApplication(boolean clear){ //only gets called once if(initialized) return; initialized = true; @@ -43,6 +60,12 @@ public class ApplicationTests{ ApplicationCore core = new ApplicationCore(){ @Override public void setup(){ + //clear older data + if(clear){ + ApplicationTests.testDataFolder.deleteDirectory(); + } + + Core.settings.setDataDirectory(testDataFolder); headless = true; net = new Net(null); tree = new FileTree(); @@ -55,12 +78,26 @@ public class ApplicationTests{ } }; content.createBaseContent(); + mods.loadScripts(); + content.createModContent(); add(logic = new Logic()); add(netServer = new NetServer()); content.init(); + mods.eachClass(Mod::init); + + if(mods.hasContentErrors()){ + for(LoadedMod mod : mods.list()){ + if(mod.hasContentErrors()){ + for(Content cont : mod.erroredContent){ + throw new RuntimeException("error in file: " + cont.minfo.sourceFile.path(), cont.minfo.baseError); + } + } + } + } + } @Override @@ -96,6 +133,55 @@ public class ApplicationTests{ state.set(State.menu); } + @ParameterizedTest + @NullSource + @ValueSource(strings = { + "asd asd asd asd asdagagasasjakbgeah;jwrej 23424234", + "这个服务器可以用自己的语言说话" + }) + void writeStringTest(String string){ + ByteBuffer buffer = ByteBuffer.allocate(500); + TypeIO.writeString(buffer, string); + buffer.position(0); + assertEquals(TypeIO.readString(buffer), string); + } + + @Test + void writeRules(){ + ByteBuffer buffer = ByteBuffer.allocate(500); + + Rules rules = new Rules(); + rules.attackMode = true; + rules.buildSpeedMultiplier = 99f; + + TypeIO.writeRules(new Writes(new ByteBufferOutput(buffer)), rules); + buffer.position(0); + Rules res = TypeIO.readRules(new Reads(new ByteBufferInput(buffer))); + + assertEquals(rules.buildSpeedMultiplier, res.buildSpeedMultiplier); + assertEquals(rules.attackMode, res.attackMode); + } + + @Test + void writeRules2(){ + Rules rules = new Rules(); + rules.attackMode = true; + rules.tags.put("blah", "bleh"); + rules.buildSpeedMultiplier = 99.1f; + + String str = JsonIO.write(rules); + Rules res = JsonIO.read(Rules.class, str); + + assertEquals(rules.buildSpeedMultiplier, res.buildSpeedMultiplier); + assertEquals(rules.attackMode, res.attackMode); + assertEquals(rules.tags.get("blah"), res.tags.get("blah")); + + String str2 = JsonIO.write(new Rules(){{ + attackMode = true; + }}); + Log.info(str2); + } + @Test void serverListJson(){ String[] files = {"servers.json", "servers_be.json", "servers_v6.json"}; @@ -730,6 +816,83 @@ public class ApplicationTests{ } } + @TestFactory + DynamicTest[] testSectorValidity(){ + Seq out = new Seq<>(); + if(world == null) world = new World(); + + for(SectorPreset zone : content.sectors()){ + + out.add(dynamicTest(zone.name, () -> { + Time.setDeltaProvider(() -> 1f); + + logic.reset(); + try{ + world.loadGenerator(zone.generator.map.width, zone.generator.map.height, zone.generator::generate); + }catch(SaveException e){ + //fails randomly and I don't care about fixing it + e.printStackTrace(); + return; + } + zone.rules.get(state.rules); + ObjectSet resources = new ObjectSet<>(); + boolean hasSpawnPoint = false; + + for(Tile tile : world.tiles){ + if(tile.drop() != null){ + resources.add(tile.drop()); + } + if(tile.block() instanceof CoreBlock && tile.team() == state.rules.defaultTeam){ + hasSpawnPoint = true; + } + } + + Seq spawns = state.rules.spawns; + + int bossWave = 0; + if(state.rules.winWave > 0){ + bossWave = state.rules.winWave; + }else{ + outer: + for(int i = 1; i <= 1000; i++){ + for(SpawnGroup spawn : spawns){ + if(spawn.effect == StatusEffects.boss && spawn.getSpawned(i) > 0){ + bossWave = i; + break outer; + } + } + } + } + + if(state.rules.attackMode){ + bossWave = 100; + }else{ + assertNotEquals(0, bossWave, "Sector doesn't have a boss wave."); + } + + //TODO check for difficulty? + for(int i = 1; i <= bossWave; i++){ + int total = 0; + for(SpawnGroup spawn : spawns){ + total += spawn.getSpawned(i); + } + + assertNotEquals(0, total, "Sector " + zone + " has no spawned enemies at wave " + i); + //TODO this is flawed and needs to be changed later + //assertTrue(total < 75, "Sector spawns too many enemies at wave " + i + " (" + total + ")"); + } + + assertEquals(1, Team.sharded.cores().size, "Sector must have one core: " + zone); + assertTrue(Team.sharded.core().items.total() < 1000, "Sector must not have starting resources: " + zone); + + assertTrue(hasSpawnPoint, "Sector \"" + zone.name + "\" has no spawn points."); + assertTrue(spawner.countSpawns() > 0 || (state.rules.attackMode && state.rules.waveTeam.data().hasCore()), "Sector \"" + zone.name + "\" has no enemy spawn points: " + spawner.countSpawns()); + })); + } + + return out.toArray(DynamicTest.class); + } + void initBuilding(){ createMap(); diff --git a/tests/src/test/java/GenericModTest.java b/tests/src/test/java/GenericModTest.java new file mode 100644 index 0000000000..f703d53873 --- /dev/null +++ b/tests/src/test/java/GenericModTest.java @@ -0,0 +1,35 @@ +import arc.*; +import arc.Net.*; +import arc.util.io.*; +import mindustry.*; +import org.junit.jupiter.api.*; + +import java.io.*; + +import static org.junit.jupiter.api.Assertions.*; + +public class GenericModTest{ + + /** grabs a mod and puts it in the mod folder */ + static void grabMod(String url){ + //clear older mods + ApplicationTests.testDataFolder.deleteDirectory(); + Core.net = new Net(); + Core.net.setBlock(true); + Core.net.http(new HttpRequest().url(url).method(HttpMethod.GET), httpResponse -> { + try{ + ApplicationTests.testDataFolder.child("mods").child("test_mod." + (url.endsWith("jar") ? "jar" : "zip")).writeBytes(Streams.copyBytes(httpResponse.getResultAsStream())); + }catch(IOException e){ + Assertions.fail(e); + } + }, Assertions::fail); + + ApplicationTests.launchApplication(false); + } + + static void checkExistence(String modName){ + assertNotEquals(Vars.mods, null); + assertNotEquals(Vars.mods.list().size, 0, "At least one mod must be loaded."); + assertEquals(Vars.mods.list().first().name, modName, modName + " must be loaded."); + } +} diff --git a/tests/src/test/java/IOTests.java b/tests/src/test/java/IOTests.java deleted file mode 100644 index d10774439a..0000000000 --- a/tests/src/test/java/IOTests.java +++ /dev/null @@ -1,63 +0,0 @@ -import arc.util.*; -import arc.util.io.*; -import mindustry.game.*; -import mindustry.io.*; -import org.junit.jupiter.api.*; -import org.junit.jupiter.params.*; -import org.junit.jupiter.params.provider.*; - -import java.nio.*; - -import static org.junit.jupiter.api.Assertions.*; - -public class IOTests{ - - @ParameterizedTest - @NullSource - @ValueSource(strings = { - "asd asd asd asd asdagagasasjakbgeah;jwrej 23424234", - "这个服务器可以用自己的语言说话" - }) - void writeStringTest(String string){ - ByteBuffer buffer = ByteBuffer.allocate(500); - TypeIO.writeString(buffer, string); - buffer.position(0); - assertEquals(TypeIO.readString(buffer), string); - } - - @Test - void writeRules(){ - ByteBuffer buffer = ByteBuffer.allocate(500); - - Rules rules = new Rules(); - rules.attackMode = true; - rules.buildSpeedMultiplier = 99f; - - TypeIO.writeRules(new Writes(new ByteBufferOutput(buffer)), rules); - buffer.position(0); - Rules res = TypeIO.readRules(new Reads(new ByteBufferInput(buffer))); - - assertEquals(rules.buildSpeedMultiplier, res.buildSpeedMultiplier); - assertEquals(rules.attackMode, res.attackMode); - } - - @Test - void writeRules2(){ - Rules rules = new Rules(); - rules.attackMode = true; - rules.tags.put("blah", "bleh"); - rules.buildSpeedMultiplier = 99.1f; - - String str = JsonIO.write(rules); - Rules res = JsonIO.read(Rules.class, str); - - assertEquals(rules.buildSpeedMultiplier, res.buildSpeedMultiplier); - assertEquals(rules.attackMode, res.attackMode); - assertEquals(rules.tags.get("blah"), res.tags.get("blah")); - - String str2 = JsonIO.write(new Rules(){{ - attackMode = true; - }}); - Log.info(str2); - } -} diff --git a/tests/src/test/java/ModTestBM.java b/tests/src/test/java/ModTestBM.java new file mode 100644 index 0000000000..a99969a188 --- /dev/null +++ b/tests/src/test/java/ModTestBM.java @@ -0,0 +1,39 @@ +import mindustry.*; +import mindustry.world.*; +import mindustry.world.meta.*; +import org.junit.jupiter.api.*; + +import static mindustry.Vars.*; +import static org.junit.jupiter.api.Assertions.*; + +//grabs a betamindy release and makes sure it initializes correctly +//this mod was chosen because: +//- it is one of the top java mods on the browser +//- it uses a variety of mindustry classes +//- it is popular enough to cause significant amounts of crashes when something breaks +//- I have some familiarity with its codebase +public class ModTestBM extends GenericModTest{ + + @Test + public void begin(){ + grabMod("https://github.com/sk7725/BetaMindy/releases/download/v0.955/BetaMindy.jar"); + + checkExistence("betamindy"); + + Block type = Vars.content.blocks().find(u -> u.name.equals("betamindy-piston")); + assertNotNull(type, "A mod block must be loaded."); + assertSame(type.buildVisibility, BuildVisibility.shown, "A mod block must be buildable."); + + world.loadMap(ApplicationTests.testMap); + Tile t = world.tile(3, 3); + + t.setBlock(type); + + //check for crash + t.build.update(); + + assertTrue(t.build.health > 0, "Block must be spawned and alive."); + assertSame(t.build.block, type, "Block must be spawned and alive."); + } + +} diff --git a/tests/src/test/java/ModTestExotic.java b/tests/src/test/java/ModTestExotic.java new file mode 100644 index 0000000000..c9e401f811 --- /dev/null +++ b/tests/src/test/java/ModTestExotic.java @@ -0,0 +1,40 @@ +import arc.util.*; +import mindustry.*; +import mindustry.gen.*; +import mindustry.type.*; +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +//grabs a version-locked exotic-mod commit and makes sure its content is parsed correctly +//this mod was chosen because: +//- it is written solely in (h)json +//- it is probably the mod with the most json, and as such covers a lot of classes +//- it is popular enough in the mod browser +//- I am somewhat familiar with its files & the type of content it adds +public class ModTestExotic extends GenericModTest{ + + @Test + public void begin(){ + grabMod("https://github.com/BlueWolf3682/Exotic-Mod/archive/08c861398ac9c3d1292132f9a110e17e06294a90.zip"); + checkExistence("exotic-mod"); + + UnitType type = Vars.content.units().find(u -> u.name.equals("exotic-mod-luminance")); + assertNotNull(type, "A mod unit must be loaded."); + assertTrue(type.weapons.size > 0, "A mod unit must have a weapon."); + + Vars.world.loadMap(ApplicationTests.testMap); + + Unit unit = type.spawn(0, 0); + + //check for crash + unit.update(); + + assertTrue(unit.health > 0, "Unit must be spawned and alive."); + assertTrue(Groups.unit.size() > 0, "Unit must be spawned and alive."); + + //just an extra sanity check + Log.info("Modded units: @", Vars.content.units().select(u -> u.minfo.mod != null)); + } + +} diff --git a/tests/src/test/java/ModTestHAI.java b/tests/src/test/java/ModTestHAI.java new file mode 100644 index 0000000000..c33f9262c9 --- /dev/null +++ b/tests/src/test/java/ModTestHAI.java @@ -0,0 +1,39 @@ +import arc.util.*; +import mindustry.*; +import mindustry.gen.*; +import mindustry.type.*; +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +//grabs a version-locked Heavy Armaments Industries commit and makes sure it initializes correctly +//this mod was chosen because: +//- it is one of the top JS mods, based on stars +//- it contains both JS and JSON, which can be used to test compatibility of the two +//- it can be used server-side (unlike FactoryDustry, which is a client-side texture pack that cannot be tested here) +public class ModTestHAI extends GenericModTest{ + + @Test + public void begin(){ + grabMod("https://github.com/Eschatologue/Heavy-Armaments-Industries/archive/d996e92dcf9a30a6acb7b3bfdfb6522dddc3804c.zip"); + checkExistence("heavy-armaments"); + + UnitType type = Vars.content.units().find(u -> u.name.equals("heavy-armaments-t3A_copter")); + assertNotNull(type, "A mod unit must be loaded."); + assertTrue(type.weapons.size > 0, "A mod unit must have a weapon."); + + Vars.world.loadMap(ApplicationTests.testMap); + + Unit unit = type.spawn(0, 0); + + //check for crash + unit.update(); + + assertTrue(unit.health > 0, "Unit must be spawned and alive."); + assertTrue(Groups.unit.size() > 0, "Unit must be spawned and alive."); + + //just an extra sanity check + Log.info("Modded units: @", Vars.content.units().select(u -> u.minfo.mod != null)); + } + +} diff --git a/tests/src/test/java/SectorTests.java b/tests/src/test/java/SectorTests.java deleted file mode 100644 index 8df675c04f..0000000000 --- a/tests/src/test/java/SectorTests.java +++ /dev/null @@ -1,105 +0,0 @@ -import arc.struct.*; -import arc.util.*; -import mindustry.content.*; -import mindustry.core.*; -import mindustry.core.GameState.*; -import mindustry.game.*; -import mindustry.io.SaveIO.*; -import mindustry.type.*; -import mindustry.world.*; -import mindustry.world.blocks.storage.*; -import org.junit.jupiter.api.*; - -import static mindustry.Vars.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; - -public class SectorTests{ - - @BeforeAll - static void launchApplication(){ - ApplicationTests.launchApplication(); - } - - @BeforeEach - void resetWorld(){ - Time.setDeltaProvider(() -> 1f); - logic.reset(); - state.set(State.menu); - } - - @TestFactory - DynamicTest[] testZoneValidity(){ - Seq out = new Seq<>(); - if(world == null) world = new World(); - - for(SectorPreset zone : content.sectors()){ - - out.add(dynamicTest(zone.name, () -> { - logic.reset(); - try{ - world.loadGenerator(zone.generator.map.width, zone.generator.map.height, zone.generator::generate); - }catch(SaveException e){ - //fails randomly and I don't care about fixing it - e.printStackTrace(); - return; - } - zone.rules.get(state.rules); - ObjectSet resources = new ObjectSet<>(); - boolean hasSpawnPoint = false; - - for(Tile tile : world.tiles){ - if(tile.drop() != null){ - resources.add(tile.drop()); - } - if(tile.block() instanceof CoreBlock && tile.team() == state.rules.defaultTeam){ - hasSpawnPoint = true; - } - } - - Seq spawns = state.rules.spawns; - - int bossWave = 0; - if(state.rules.winWave > 0){ - bossWave = state.rules.winWave; - }else{ - outer: - for(int i = 1; i <= 1000; i++){ - for(SpawnGroup spawn : spawns){ - if(spawn.effect == StatusEffects.boss && spawn.getSpawned(i) > 0){ - bossWave = i; - break outer; - } - } - } - } - - if(state.rules.attackMode){ - bossWave = 100; - }else{ - assertNotEquals(0, bossWave, "Sector doesn't have a boss wave."); - } - - //TODO check for difficulty? - for(int i = 1; i <= bossWave; i++){ - int total = 0; - for(SpawnGroup spawn : spawns){ - total += spawn.getSpawned(i); - } - - assertNotEquals(0, total, "Sector " + zone + " has no spawned enemies at wave " + i); - //TODO this is flawed and needs to be changed later - //assertTrue(total < 75, "Sector spawns too many enemies at wave " + i + " (" + total + ")"); - } - - assertEquals(1, Team.sharded.cores().size, "Sector must have one core: " + zone); - assertTrue(Team.sharded.core().items.total() < 1000, "Sector must not have starting resources: " + zone); - - assertTrue(hasSpawnPoint, "Sector \"" + zone.name + "\" has no spawn points."); - assertTrue(spawner.countSpawns() > 0 || (state.rules.attackMode && state.rules.waveTeam.data().hasCore()), "Sector \"" + zone.name + "\" has no enemy spawn points: " + spawner.countSpawns()); - })); - } - - return out.toArray(DynamicTest.class); - } -} diff --git a/tests/src/test/java/power/DirectConsumerTests.java b/tests/src/test/java/power/DirectConsumerTests.java index 1b8aa04a8d..48110ee4e9 100644 --- a/tests/src/test/java/power/DirectConsumerTests.java +++ b/tests/src/test/java/power/DirectConsumerTests.java @@ -1,9 +1,18 @@ package power; +import mindustry.content.*; +import mindustry.type.*; +import mindustry.world.*; +import mindustry.world.blocks.power.PowerGenerator.*; +import mindustry.world.blocks.power.*; +import mindustry.world.blocks.production.*; +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + /** Tests for direct power consumers. */ public class DirectConsumerTests extends PowerTestFixture{ - //TODO reimplement -/* + @Test void noPowerRequestedWithNoItems(){ testUnitFactory(0, 0, 0.08f, 0.08f, 1f); @@ -21,26 +30,26 @@ public class DirectConsumerTests extends PowerTestFixture{ } void testUnitFactory(int siliconAmount, int leadAmount, float producedPower, float requestedPower, float expectedSatisfaction){ - Tile consumerTile = createFakeTile(0, 0, new UnitFactory("fakefactory"){{ - entityType = UnitFactoryEntity::new; - unitType = UnitTypes.spirit; - produceTime = 60; + Tile ct = createFakeTile(0, 0, new GenericCrafter("fakefactory"){{ + hasPower = true; + hasItems = true; consumes.power(requestedPower); consumes.items(new ItemStack(Items.silicon, 30), new ItemStack(Items.lead, 30)); }}); - consumerTile.entity.items.add(Items.silicon, siliconAmount); - consumerTile.entity.items.add(Items.lead, leadAmount); + ct.block().init(); + ct.build.items.add(Items.silicon, siliconAmount); + ct.build.items.add(Items.lead, leadAmount); Tile producerTile = createFakeTile(2, 0, createFakeProducerBlock(producedPower)); - producerTile.ent().productionEfficiency = 1f; + ((GeneratorBuild)producerTile.build).productionEfficiency = 1f; PowerGraph graph = new PowerGraph(); - graph.add(producerTile.entity); - graph.add(consumerTile.entity); + graph.add(producerTile.build); + graph.add(ct.build); - consumerTile.entity.update(); + ct.build.update(); graph.update(); - assertEquals(expectedSatisfaction, consumerTile.entity.power.status); - }*/ + assertEquals(expectedSatisfaction, ct.build.power.status); + } } diff --git a/tests/src/test/java/power/ItemLiquidGeneratorTests.java b/tests/src/test/java/power/ItemLiquidGeneratorTests.java index 71311cda10..2c58f974be 100644 --- a/tests/src/test/java/power/ItemLiquidGeneratorTests.java +++ b/tests/src/test/java/power/ItemLiquidGeneratorTests.java @@ -24,7 +24,6 @@ import static org.junit.jupiter.api.DynamicTest.dynamicTest; * Any expected power amount (produced, consumed, buffered) should be affected by FakeThreadHandler.fakeDelta but status should not! */ public class ItemLiquidGeneratorTests extends PowerTestFixture{ - private ItemLiquidGenerator generator; private Tile tile; private ItemLiquidGeneratorBuild entity; From 5601f972667be4d54c1db4d4b1b61a08ee238e93 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 13 Jun 2021 10:12:50 -0400 Subject: [PATCH 03/31] Fixed #5418 --- core/src/mindustry/core/Control.java | 3 ++- core/src/mindustry/world/blocks/power/ItemLiquidGenerator.java | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/mindustry/core/Control.java b/core/src/mindustry/core/Control.java index 93fa1e68a2..a416b5d2fd 100644 --- a/core/src/mindustry/core/Control.java +++ b/core/src/mindustry/core/Control.java @@ -323,13 +323,14 @@ public class Control implements ApplicationListener, Loadable{ if(slot != null && !clearSectors){ try{ + boolean hadNoCore = !sector.info.hasCore; reloader.begin(); slot.load(); slot.setAutosave(true); state.rules.sector = sector; //if there is no base, simulate a new game and place the right loadout at the spawn position - if(state.rules.defaultTeam.cores().isEmpty()){ + if(state.rules.defaultTeam.cores().isEmpty() || hadNoCore){ //no spawn set -> delete the sector save if(sector.info.spawnPosition == 0){ diff --git a/core/src/mindustry/world/blocks/power/ItemLiquidGenerator.java b/core/src/mindustry/world/blocks/power/ItemLiquidGenerator.java index 5295bf0703..1ef6010f2b 100644 --- a/core/src/mindustry/world/blocks/power/ItemLiquidGenerator.java +++ b/core/src/mindustry/world/blocks/power/ItemLiquidGenerator.java @@ -61,6 +61,8 @@ public class ItemLiquidGenerator extends PowerGenerator{ @Override public void init(){ + emitLight = true; + lightRadius = 65f * size; if(!defaults){ setDefaults(); } From a756dec379b849fcef8926b1534fc0eb2bc53339 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 13 Jun 2021 11:02:17 -0400 Subject: [PATCH 04/31] Fixed #5419 --- core/src/mindustry/world/blocks/ConstructBlock.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/mindustry/world/blocks/ConstructBlock.java b/core/src/mindustry/world/blocks/ConstructBlock.java index 5e31f6df00..4c2fc98b1c 100644 --- a/core/src/mindustry/world/blocks/ConstructBlock.java +++ b/core/src/mindustry/world/blocks/ConstructBlock.java @@ -92,6 +92,8 @@ public class ConstructBlock extends Block{ Fx.placeBlock.at(tile.drawx(), tile.drawy(), block.size); if(shouldPlay()) Sounds.place.at(tile, calcPitch(true)); + + Events.fire(new BlockBuildEndEvent(tile, builder, team, false, config)); } static boolean shouldPlay(){ @@ -123,8 +125,6 @@ public class ConstructBlock extends Block{ if(tile.build != null){ tile.build.placed(); } - - Events.fire(new BlockBuildEndEvent(tile, builder, team, false, config)); } @Override @@ -259,7 +259,9 @@ public class ConstructBlock extends Block{ if(progress >= 1f || state.rules.infiniteResources){ if(lastBuilder == null) lastBuilder = builder; - constructed(tile, current, lastBuilder, (byte)rotation, builder.team, config); + if(!net.client()){ + constructed(tile, current, lastBuilder, (byte)rotation, builder.team, config); + } } } From de9c82d1d2ab80344a0e9f32ecbbbe61dbf89d84 Mon Sep 17 00:00:00 2001 From: QmelZ <59574967+QmelZ@users.noreply.github.com> Date: Mon, 14 Jun 2021 02:22:33 +0300 Subject: [PATCH 05/31] Freedom for javascript mods (#5421) * remove blacklist and class shutter * no context factory --- android/src/mindustry/android/AndroidRhinoContext.java | 7 ------- core/src/mindustry/core/Platform.java | 9 --------- core/src/mindustry/mod/ContentParser.java | 4 ++-- core/src/mindustry/mod/Scripts.java | 10 ---------- 4 files changed, 2 insertions(+), 28 deletions(-) diff --git a/android/src/mindustry/android/AndroidRhinoContext.java b/android/src/mindustry/android/AndroidRhinoContext.java index 1b845d61cf..ab28532941 100644 --- a/android/src/mindustry/android/AndroidRhinoContext.java +++ b/android/src/mindustry/android/AndroidRhinoContext.java @@ -62,13 +62,6 @@ public class AndroidRhinoContext{ initApplicationClassLoader(createClassLoader(AndroidContextFactory.class.getClassLoader())); } - @Override - protected Context makeContext(){ - Context ctx = super.makeContext(); - ctx.setClassShutter(Scripts::allowClass); - return ctx; - } - /** * Create a ClassLoader which is able to deal with bytecode * @param parent the parent of the create classloader diff --git a/core/src/mindustry/core/Platform.java b/core/src/mindustry/core/Platform.java index a595668aa8..bbceee8a64 100644 --- a/core/src/mindustry/core/Platform.java +++ b/core/src/mindustry/core/Platform.java @@ -79,15 +79,6 @@ public interface Platform{ } default Context getScriptContext(){ - ContextFactory.getGlobalSetter().setContextFactoryGlobal(new ContextFactory(){ - @Override - protected Context makeContext(){ - Context ctx = super.makeContext(); - ctx.setClassShutter(Scripts::allowClass); - return ctx; - } - }); - Context c = Context.enter(); c.setOptimizationLevel(9); return c; diff --git a/core/src/mindustry/mod/ContentParser.java b/core/src/mindustry/mod/ContentParser.java index 6862b1a540..9c8c7f3b3a 100644 --- a/core/src/mindustry/mod/ContentParser.java +++ b/core/src/mindustry/mod/ContentParser.java @@ -752,8 +752,8 @@ public class ContentParser{ var out = ClassMap.classes.get(!base.isEmpty() && Character.isLowerCase(base.charAt(0)) ? Strings.capitalize(base) : base); if(out != null) return (Class)out; - //try to resolve it as a raw class name if it's allowed - if(base.indexOf('.') != -1 && Scripts.allowClass(base)){ + //try to resolve it as a raw class name + if(base.indexOf('.') != -1){ try{ return (Class)Class.forName(base); }catch(Exception ignored){ diff --git a/core/src/mindustry/mod/Scripts.java b/core/src/mindustry/mod/Scripts.java index 4f70c0c814..1aa69b9ab7 100644 --- a/core/src/mindustry/mod/Scripts.java +++ b/core/src/mindustry/mod/Scripts.java @@ -22,22 +22,12 @@ import java.util.*; import java.util.regex.*; public class Scripts implements Disposable{ - private static final Seq blacklist = Seq.with(".net.", "java.net", "files", "reflect", "javax", "rhino", "file", "channels", "jdk", - "runtime", "util.os", "rmi", "security", "org.", "sun.", "beans", "sql", "http", "exec", "compiler", "process", "system", - ".awt", "socket", "classloader", "oracle", "invoke", "java.util.function", "java.util.stream", "org.", "mod.classmap"); - private static final Seq whitelist = Seq.with("mindustry.net", "netserver", "netclient", "com.sun.proxy.$proxy", "jdk.proxy", "mindustry.gen.", - "mindustry.logic.", "mindustry.async.", "saveio", "systemcursor", "filetreeinitevent", "asyncexecutor"); - private final Context context; private final Scriptable scope; private boolean errored; LoadedMod currentMod = null; - public static boolean allowClass(String type){ - return !blacklist.contains(t -> type.toLowerCase(Locale.ROOT).contains(t)) || whitelist.contains(t -> type.toLowerCase(Locale.ROOT).contains(t)); - } - public Scripts(){ Time.mark(); From 2d9204d638dc12e0a4d7a31e3c8a9d347d2f319b Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 13 Jun 2021 19:27:28 -0400 Subject: [PATCH 06/31] I guess everything is unsafe now --- core/src/mindustry/ui/dialogs/ModsDialog.java | 26 +++++-------------- .../world/blocks/distribution/MassDriver.java | 2 +- .../blocks/payloads/PayloadMassDriver.java | 1 + 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/core/src/mindustry/ui/dialogs/ModsDialog.java b/core/src/mindustry/ui/dialogs/ModsDialog.java index 44ebebe8ae..ca5ff1fcf8 100644 --- a/core/src/mindustry/ui/dialogs/ModsDialog.java +++ b/core/src/mindustry/ui/dialogs/ModsDialog.java @@ -174,21 +174,12 @@ public class ModsDialog extends BaseDialog{ dialog.hide(); platform.showMultiFileChooser(file -> { - Runnable go = () -> { - try{ - mods.importMod(file); - setup(); - }catch(IOException e){ - ui.showException(e); - e.printStackTrace(); - } - }; - - //show unsafe jar file warning - if(file.extEquals("jar")){ - ui.showConfirm("@warning", "@mod.jarwarn", go); - }else{ - go.run(); + try{ + mods.importMod(file); + setup(); + }catch(IOException e){ + ui.showException(e); + Log.err(e); } }, "zip", "jar"); }).margin(12f); @@ -529,10 +520,7 @@ public class ModsDialog extends BaseDialog{ private void githubImportMod(String repo, boolean isJava){ if(isJava){ - ui.showConfirm("@warning", "@mod.jarwarn", () -> { - ui.loadfrag.show(); - githubImportJavaMod(repo); - }); + githubImportJavaMod(repo); }else{ ui.loadfrag.show(); Core.net.httpGet(ghApi + "/repos/" + repo, res -> { diff --git a/core/src/mindustry/world/blocks/distribution/MassDriver.java b/core/src/mindustry/world/blocks/distribution/MassDriver.java index 94b993aa7f..045d61736f 100644 --- a/core/src/mindustry/world/blocks/distribution/MassDriver.java +++ b/core/src/mindustry/world/blocks/distribution/MassDriver.java @@ -324,7 +324,7 @@ public class MassDriver extends Block{ } protected boolean shooterValid(Building other){ - return other instanceof MassDriverBuild entity && other.consValid() && entity.block == block && entity.link == pos() && within(other, range); + return other instanceof MassDriverBuild entity && other.isValid() && other.consValid() && entity.block == block && entity.link == pos() && within(other, range); } protected boolean linkValid(){ diff --git a/core/src/mindustry/world/blocks/payloads/PayloadMassDriver.java b/core/src/mindustry/world/blocks/payloads/PayloadMassDriver.java index 3bf83976ec..0d1821c2f6 100644 --- a/core/src/mindustry/world/blocks/payloads/PayloadMassDriver.java +++ b/core/src/mindustry/world/blocks/payloads/PayloadMassDriver.java @@ -184,6 +184,7 @@ public class PayloadMassDriver extends PayloadBlock{ if(current != null && !( current instanceof PayloadDriverBuild entity && + current.isValid() && entity.consValid() && entity.block == block && entity.link == pos() && within(current, range) )){ From b58e64ea16e7ab072218e365daa2a42ea400c1d0 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 14 Jun 2021 08:47:38 -0400 Subject: [PATCH 07/31] Fixed #5422 --- core/src/mindustry/ai/BaseAI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/mindustry/ai/BaseAI.java b/core/src/mindustry/ai/BaseAI.java index bebbdc1e8c..6ca2a5a751 100644 --- a/core/src/mindustry/ai/BaseAI.java +++ b/core/src/mindustry/ai/BaseAI.java @@ -289,7 +289,7 @@ public class BaseAI{ } Tile o = world.tile(tile.x + p.x, tile.y + p.y); - if(o != null && (o.block() instanceof PayloadBlock || o.block() instanceof PayloadConveyor)){ + if(o != null && (o.block() instanceof PayloadBlock || o.block() instanceof PayloadConveyor || o.block() instanceof ShockMine)){ continue outer; } From 3ad16ab8aa7a856f220758307d7788c3c56fbe24 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 14 Jun 2021 10:41:19 -0400 Subject: [PATCH 08/31] Upgraded to Gradle 7 & JDK 16 --- .github/workflows/deployment.yml | 4 +-- .github/workflows/pr.yml | 4 +-- .github/workflows/push.yml | 4 +-- README.md | 2 +- android/build.gradle | 4 +-- core/src/mindustry/content/UnitTypes.java | 4 +-- gradle.properties | 6 ++--- gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 31 ++++++++++------------ gradlew.bat | 25 +++++------------ jitpack.yml | 2 +- 12 files changed, 36 insertions(+), 52 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 29dad558e5..de6250d370 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -11,10 +11,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up JDK 14 + - name: Set up JDK 16 uses: actions/setup-java@v1 with: - java-version: 14 + java-version: 16 - name: Set env run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - name: Add Arc release diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index f769d87b6a..038db3ee59 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -8,10 +8,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up JDK 14 + - name: Set up JDK 16 uses: actions/setup-java@v1 with: - java-version: 14 + java-version: 16 - name: Run unit tests and build JAR run: ./gradlew test desktop:dist - name: Upload desktop JAR for testing diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index d300e57e24..925f9054b4 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -17,9 +17,9 @@ jobs: git tag ${BNUM} git config --global user.name "Build Uploader" git push https://Anuken:${{ secrets.API_TOKEN_GITHUB }}@github.com/Anuken/MindustryBuilds ${BNUM} - - name: Set up JDK 14 + - name: Set up JDK 16 uses: actions/setup-java@v1 with: - java-version: 14 + java-version: 16 - name: Run unit tests run: ./gradlew clean cleanTest test diff --git a/README.md b/README.md index b6b3e26597..575f421530 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ See [CONTRIBUTING](CONTRIBUTING.md). Bleeding-edge builds are generated automatically for every commit. You can see them [here](https://github.com/Anuken/MindustryBuilds/releases). If you'd rather compile on your own, follow these instructions. -First, make sure you have [JDK 14](https://adoptopenjdk.net/archive.html?variant=openjdk14&jvmVariant=hotspot) installed. **Other JDK versions will not work.** Open a terminal in the Mindustry directory and run the following commands: +First, make sure you have [JDK 16](https://adoptopenjdk.net/archive.html?variant=openjdk16&jvmVariant=hotspot) installed. **Other JDK versions will not work.** Open a terminal in the Mindustry directory and run the following commands: ### Windows diff --git a/android/build.gradle b/android/build.gradle index 0196805db9..a4de97915b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,9 +7,7 @@ buildscript{ } dependencies{ - //IMPORTANT NOTICE: any version of the plugin after 3.4.1 will break builds - //it appears abstract methods don't get desugared properly (if at all) - classpath 'com.android.tools.build:gradle:3.4.1' + classpath 'com.android.tools.build:gradle:7.1.0-alpha02' } } diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index e6bffdedd4..b5225e6b85 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -612,9 +612,9 @@ public class UnitTypes implements ContentList{ bullet = new LiquidBulletType(Liquids.slag){{ damage = 11; speed = 2.4f; - drag = 0.01f; + drag = 0.009f; shootEffect = Fx.shootSmall; - lifetime = 56f; + lifetime = 57f; collidesAir = false; }}; }}); diff --git a/gradle.properties b/gradle.properties index 34b13c8f1d..3a5e8befb4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,5 @@ org.gradle.daemon=true -#--illegal-access=permit -org.gradle.jvmargs=-Xms256m -Xmx1024m +org.gradle.jvmargs=-Xms256m -Xmx1024m --illegal-access=permit # Don't recompute annotations if sources haven't been changed kapt.incremental.apt = true # Multithreaded @@ -9,4 +8,5 @@ kapt.use.worker.api=true kapt.include.compile.classpath=false # I don't need to use the kotlin stdlib yet, so remove it to prevent extra bloat & method count issues kotlin.stdlib.default.dependency=false -archash=3926b785320fea0cd9ca597f6bfa9071263a5464 +#needed for android compilation +android.useAndroidX=truearchash=3926b785320fea0cd9ca597f6bfa9071263a5464 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch delta 23334 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 delta 19998 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 z zk-S^M@O;68<`~n&39tFzMymfz7G5IrOs-n#c>9hj`}EdggTU5LvsPC%Cbt-0XlHnL z)LkQUo{(La;kl%QJLVq@OkK^D*o016XRM00bc%XV9@=0Mec^qS)w|5!(_Dn2oRXL( z?>qZn$n$B9^~5y`c6K?a8HUum7N{A{S)){9w}H7d@*h|D#ZAp8ibU7OWQuNa?Js!2 zSFmEi-mbpS2V?Ip{#~;8cS&sKiaL!g&B>=b-zAi<=sNdymbEQMVbA5Mj$4!)g?nDH zv~B22yuQ_2?&YlP?S@A`YzX_nqh}=UYg0No>bb-;;SE22%}LHslQ`ur(WLNpOZ~$@ zFU71+sh^qypJ*MZRB&uND_Rm-ShMV1r+eKE<^8n^jp;LXY}VV}lD_y@?t!NZcU+n> z+lBYL!sDXsN$e-w>!&_{^2FoIk=Q$v6%)Rn5x7`YR#0T=;I=pPODRuC!TYi__Ki

X&!w{;_lT@BHFuYE93NwLRx$o>hJ` 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 z zO$_hk7q>KIP)6BN6pHvm6$($5cqlMA<+c=4UKm(td$KH8Xi0_u4@}FF#=2;*q~9I( z$>%bJC-dIXVycXT3fVJVNuS(*$Dhe0WAcMLp-iA5M{(9C6Q)sjd*rJXgmLR(S266w|#Tun^yUGbWYF$q%O} zPtLqAE(0I(;ACL1hPjJ@fniA_e-&8K{`)RW+iIaq*2()|z+8vrY?Dpqi-A>TJd$FPm!ydNuJ5~A6qdAEC6%uo>()bECMs@*UN!S0%xts7oLbQ&09M8;S{CGTu%*{er%uo z;fTiMh(`jGOP)$Gu^yN_;b{bu=HbbT7i_>G|NJ7dU4M^E{(8|GEHU?)6w`%clXpJz eW>PvnS?Y@VWZmZ+e8vpM42K077}Ae}!Uh0s16mIN diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 442d9132ea..0f80bbf516 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 83f2acfdc3..4f906e0c81 100755 --- a/gradlew +++ b/gradlew @@ -82,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -129,6 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -154,19 +156,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -175,14 +177,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 9618d8d960..107acd32c4 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -51,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -61,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/jitpack.yml b/jitpack.yml index c06db14182..e5bc069fcf 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,4 +1,4 @@ before_install: - wget https://github.com/sormuras/bach/raw/master/install-jdk.sh - - source install-jdk.sh --feature 14 + - source install-jdk.sh --feature 16 - jshell --version From 51040f9f3dd32a9febb9cc8ed85c6dc4e4db3d9e Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 14 Jun 2021 10:42:24 -0400 Subject: [PATCH 09/31] Hash fix --- gradle.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 3a5e8befb4..8198f3fd92 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,4 +9,5 @@ kapt.include.compile.classpath=false # I don't need to use the kotlin stdlib yet, so remove it to prevent extra bloat & method count issues kotlin.stdlib.default.dependency=false #needed for android compilation -android.useAndroidX=truearchash=3926b785320fea0cd9ca597f6bfa9071263a5464 +android.useAndroidX=true +archash=3926b785320fea0cd9ca597f6bfa9071263a5464 From 1a031519666434d624072e09f0067b8362cfd710 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 14 Jun 2021 10:55:28 -0400 Subject: [PATCH 10/31] Enforce JDK 16 as a minimum --- build.gradle | 7 +------ gradle.properties | 2 ++ settings.gradle | 4 ++-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index 27969b5cb6..09feb3183b 100644 --- a/build.gradle +++ b/build.gradle @@ -196,12 +196,7 @@ allprojects{ tasks.withType(JavaCompile){ targetCompatibility = 8 - //TODO fix dynamically, this is a hack - if(System.getProperty("user.name") == "anuke"){ - sourceCompatibility = JavaVersion.VERSION_15 - }else{ - sourceCompatibility = JavaVersion.VERSION_14 - } + sourceCompatibility = JavaVersion.VERSION_16 options.encoding = "UTF-8" options.compilerArgs += ["-Xlint:deprecation"] dependsOn clearCache diff --git a/gradle.properties b/gradle.properties index 8198f3fd92..30f7da1b8d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,6 @@ kapt.include.compile.classpath=false kotlin.stdlib.default.dependency=false #needed for android compilation android.useAndroidX=true +#suppresses a warning I don't care about +android.disableAutomaticComponentCreation=true archash=3926b785320fea0cd9ca597f6bfa9071263a5464 diff --git a/settings.gradle b/settings.gradle index ef9b57e54a..76eaa676a6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,5 @@ -if(JavaVersion.current().ordinal() < JavaVersion.VERSION_14.ordinal()){ - throw new GradleException("!!! YOU MUST USE JAVA 14 OR ABOVE TO COMPILE AND RUN MINDUSTRY !!! Read the README. Your version: ${System.properties["java.version"]}") +if(JavaVersion.current().ordinal() < JavaVersion.VERSION_16.ordinal()){ + throw new Exception("!!! YOU MUST USE JAVA 16 OR ABOVE TO COMPILE AND RUN MINDUSTRY !!! Read the README. Your version: ${System.properties["java.version"]}") } include 'desktop', 'core', 'server', 'ios', 'annotations', 'tools', 'tests' From 2a451aa41fb230212755c03c59a394b78f22802f Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 14 Jun 2021 10:59:38 -0400 Subject: [PATCH 11/31] Disable preview features --- build.gradle | 12 +++--------- gradle.properties | 2 -- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index 09feb3183b..f93082e0fa 100644 --- a/build.gradle +++ b/build.gradle @@ -219,18 +219,13 @@ configure(project(":annotations")){ //compile with java 8 compatibility for everything except the annotation project configure(subprojects - project(":annotations")){ tasks.withType(JavaCompile){ - options.compilerArgs.addAll(['--release', '8', '--enable-preview']) - - doFirst{ - options.compilerArgs = options.compilerArgs.findAll{it != '--enable-preview' } - } + options.compilerArgs.addAll(['--release', '8']) } tasks.withType(Javadoc){ options{ addStringOption('Xdoclint:none', '-quiet') - addBooleanOption('-enable-preview', true) - addStringOption('-release', '14') + addStringOption('-release', '16') } } } @@ -297,9 +292,8 @@ project(":core"){ kapt{ javacOptions{ - option("-source", "14") + option("-source", "16") option("-target", "1.8") - option("--enable-preview") } } diff --git a/gradle.properties b/gradle.properties index 30f7da1b8d..8198f3fd92 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,6 +10,4 @@ kapt.include.compile.classpath=false kotlin.stdlib.default.dependency=false #needed for android compilation android.useAndroidX=true -#suppresses a warning I don't care about -android.disableAutomaticComponentCreation=true archash=3926b785320fea0cd9ca597f6bfa9071263a5464 From e5b80c37eb787074ec260ecfe4b7025faa82b1b5 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 14 Jun 2021 11:05:37 -0400 Subject: [PATCH 12/31] Renamed misleading github jobs --- .github/workflows/deployment.yml | 2 +- .github/workflows/pr.yml | 2 +- .github/workflows/push.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index de6250d370..b64e8feca4 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -6,7 +6,7 @@ on: - 'v*' jobs: - buildJava14: + deploy: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 038db3ee59..5888db4507 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -3,7 +3,7 @@ name: Pull Request Tests on: [pull_request, workflow_dispatch] jobs: - buildJava14: + testPR: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 925f9054b4..1409aef4ff 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -3,7 +3,7 @@ name: Tests on: [push, workflow_dispatch] jobs: - buildJava14: + runPush: runs-on: ubuntu-latest steps: From 31c39e0148d35db04c052c0ccbeafa82ac46b5f7 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 14 Jun 2021 11:15:06 -0400 Subject: [PATCH 13/31] JITPack appears to be throwing a fit --- desktop/build.gradle | 2 +- jitpack.yml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/desktop/build.gradle b/desktop/build.gradle index 4c8a14f86f..347ffeee07 100644 --- a/desktop/build.gradle +++ b/desktop/build.gradle @@ -48,7 +48,7 @@ task dist(type: Jar, dependsOn: configurations.runtimeClasspath){ } } -if(!project.ext.hasSprites()){ +if(!project.ext.hasSprites() && System.getenv("JITPACK") != "true"){ println "Scheduling sprite packing." run.dependsOn ":tools:pack" dist.dependsOn ":tools:pack" diff --git a/jitpack.yml b/jitpack.yml index e5bc069fcf..0478674dfa 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,3 +1,5 @@ +jdk: + - openjdk16 before_install: - wget https://github.com/sormuras/bach/raw/master/install-jdk.sh - source install-jdk.sh --feature 16 From 22d1700fb28c0d8ba7e8d0134f622fdafab6ad49 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 14 Jun 2021 11:22:56 -0400 Subject: [PATCH 14/31] JITPack appears to be throwing a fit (2) --- jitpack.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jitpack.yml b/jitpack.yml index 0478674dfa..ee1051c8d3 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -4,3 +4,7 @@ before_install: - wget https://github.com/sormuras/bach/raw/master/install-jdk.sh - source install-jdk.sh --feature 16 - jshell --version +install: + - java --version + - javac --version + - ./gradlew build publishToMavenLocal \ No newline at end of file From 39f4e90fff45cb542efd6521ad68f18e4243c871 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 14 Jun 2021 11:23:47 -0400 Subject: [PATCH 15/31] s t o p --- jitpack.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/jitpack.yml b/jitpack.yml index ee1051c8d3..b5a194a046 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -7,4 +7,5 @@ before_install: install: - java --version - javac --version + - ./gradlew --stop - ./gradlew build publishToMavenLocal \ No newline at end of file From 2e733273a0d40992c2e2bcec47ae0f85e6ae1262 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 14 Jun 2021 11:28:28 -0400 Subject: [PATCH 16/31] confusion --- jitpack.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jitpack.yml b/jitpack.yml index b5a194a046..ff53fc84bb 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -7,5 +7,6 @@ before_install: install: - java --version - javac --version - - ./gradlew --stop - - ./gradlew build publishToMavenLocal \ No newline at end of file + - echo $JAVA_HOME + - echo $PATH + - ./gradlew publishToMavenLocal \ No newline at end of file From 28252173b45189d1abf15eb039cfcf77b3a752bf Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 14 Jun 2021 11:32:46 -0400 Subject: [PATCH 17/31] confusion 2 --- jitpack.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jitpack.yml b/jitpack.yml index ff53fc84bb..509954d8c1 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -9,4 +9,5 @@ install: - javac --version - echo $JAVA_HOME - echo $PATH - - ./gradlew publishToMavenLocal \ No newline at end of file + - ./gradlew publishToMavenLocal + - sed -i 's/ --illegal-access=permit//' dest.properties #remove extra flags after compilation \ No newline at end of file From 6b5a74358372807202fea8214ce63b00bca252ae Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 14 Jun 2021 11:33:00 -0400 Subject: [PATCH 18/31] confusion 3 --- jitpack.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jitpack.yml b/jitpack.yml index 509954d8c1..d3268d55fc 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -10,4 +10,4 @@ install: - echo $JAVA_HOME - echo $PATH - ./gradlew publishToMavenLocal - - sed -i 's/ --illegal-access=permit//' dest.properties #remove extra flags after compilation \ No newline at end of file + - sed -i 's/ --illegal-access=permit//' gradle.properties #remove extra flags after compilation \ No newline at end of file From a0f80a8865a136a20de6601a3b487abf66b35111 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 14 Jun 2021 11:35:53 -0400 Subject: [PATCH 19/31] it is solved --- jitpack.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/jitpack.yml b/jitpack.yml index d3268d55fc..c418d56017 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -5,9 +5,5 @@ before_install: - source install-jdk.sh --feature 16 - jshell --version install: - - java --version - - javac --version - - echo $JAVA_HOME - - echo $PATH - ./gradlew publishToMavenLocal - sed -i 's/ --illegal-access=permit//' gradle.properties #remove extra flags after compilation \ No newline at end of file From aef92fd3b0ba621a65cf3efd290d21a1136a52cf Mon Sep 17 00:00:00 2001 From: Volas171 Date: Mon, 14 Jun 2021 22:24:26 -0500 Subject: [PATCH 20/31] remove mindustrypvp.ml (#5423) we are leaving the list for the following reasons : we are renewing domain coming back later (having some stability issue in moderation) --- servers_be.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/servers_be.json b/servers_be.json index 6fcc7a4a4f..d22999dea0 100644 --- a/servers_be.json +++ b/servers_be.json @@ -13,8 +13,5 @@ }, { "address": "mindurka.tk:9999" - }, - { - "address": "mindustrypvp.ml:6000" } ] From 2d8f7cb680822f941973c7c29211147600a0fdb7 Mon Sep 17 00:00:00 2001 From: Anuken Date: Tue, 15 Jun 2021 09:39:23 -0400 Subject: [PATCH 21/31] Fixed #5425 --- core/src/mindustry/ClientLauncher.java | 2 +- tools/src/mindustry/tools/Generators.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/mindustry/ClientLauncher.java b/core/src/mindustry/ClientLauncher.java index 6042a38b19..71b04314a8 100644 --- a/core/src/mindustry/ClientLauncher.java +++ b/core/src/mindustry/ClientLauncher.java @@ -83,7 +83,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform Fonts.loadDefaultFont(); //load fallback atlas if max texture size is below 4096 - assets.load(new AssetDescriptor<>(maxTextureSize >= 4096 ? "sprites/sprites.aatls" : "sprites/fallback/sprites.aatls", TextureAtlas.class)).loaded = t -> atlas = (TextureAtlas)t; + assets.load(new AssetDescriptor<>(maxTextureSize >= 4096 ? "sprites/sprites.aatls" : "sprites/fallback/sprites.aatls", TextureAtlas.class)).loaded = t -> atlas = (TextureAtlas)t; assets.loadRun("maps", Map.class, () -> maps.loadPreviews()); Musics.load(); diff --git a/tools/src/mindustry/tools/Generators.java b/tools/src/mindustry/tools/Generators.java index 626986b5ce..caf411793b 100644 --- a/tools/src/mindustry/tools/Generators.java +++ b/tools/src/mindustry/tools/Generators.java @@ -220,7 +220,7 @@ public class Generators{ TextureRegion[] regions = block.getGeneratedIcons(); - if(block.variants > 0){ + if(block.variants > 0 || block instanceof Floor){ for(TextureRegion region : block.variantRegions()){ GenRegion gen = (GenRegion)region; if(gen.path == null) continue; From 183c922b6bf3db33af6173dd38e7f3c25a689718 Mon Sep 17 00:00:00 2001 From: Anuken Date: Tue, 15 Jun 2021 11:32:49 -0400 Subject: [PATCH 22/31] Do not save MirrorFilter temporary vectors --- core/src/mindustry/entities/effect/ParticleEffect.java | 4 ++-- core/src/mindustry/maps/filters/MirrorFilter.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/mindustry/entities/effect/ParticleEffect.java b/core/src/mindustry/entities/effect/ParticleEffect.java index a6e5df24a8..2b8fd4e52d 100644 --- a/core/src/mindustry/entities/effect/ParticleEffect.java +++ b/core/src/mindustry/entities/effect/ParticleEffect.java @@ -59,12 +59,12 @@ public class ParticleEffect extends Effect{ Angles.randLenVectors(e.id, particles, length * fin + baseLength, e.rotation, cone, (x, y) -> { Lines.lineAngle(ox + x, oy + y, Mathf.angle(x, y), len); - Drawf.light(ox + x, oy + y, len * lightScl, lightColor, lightOpacity); + Drawf.light(ox + x, oy + y, len * lightScl, lightColor, lightOpacity* Draw.getColor().a); }); }else{ Angles.randLenVectors(e.id, particles, length * fin + baseLength, e.rotation, cone, (x, y) -> { Draw.rect(tex, ox + x, oy + y, rad, rad, e.rotation + offset + e.time * spin); - Drawf.light(ox + x, oy + y, rad * lightScl, lightColor, lightOpacity); + Drawf.light(ox + x, oy + y, rad * lightScl, lightColor, lightOpacity * Draw.getColor().a); }); } } diff --git a/core/src/mindustry/maps/filters/MirrorFilter.java b/core/src/mindustry/maps/filters/MirrorFilter.java index cdb324b0ef..9e91fb4847 100644 --- a/core/src/mindustry/maps/filters/MirrorFilter.java +++ b/core/src/mindustry/maps/filters/MirrorFilter.java @@ -12,7 +12,7 @@ import mindustry.maps.filters.FilterOption.*; import mindustry.world.*; public class MirrorFilter extends GenerateFilter{ - private final Vec2 v1 = new Vec2(), v2 = new Vec2(), v3 = new Vec2(); + private static final Vec2 v1 = new Vec2(), v2 = new Vec2(), v3 = new Vec2(); int angle = 45; boolean rotate = false; From 69d8af7874c82164fd8a185bc0354527e33dc420 Mon Sep 17 00:00:00 2001 From: Anuken Date: Tue, 15 Jun 2021 11:58:26 -0400 Subject: [PATCH 23/31] Editor filter seed saving & cleanup --- .../mindustry/editor/MapGenerateDialog.java | 1 + core/src/mindustry/editor/MapInfoDialog.java | 9 ++++++-- .../mindustry/maps/filters/BlendFilter.java | 13 ++++++------ .../mindustry/maps/filters/ClearFilter.java | 5 +++-- .../maps/filters/CoreSpawnFilter.java | 4 +--- .../mindustry/maps/filters/DistortFilter.java | 11 +++++----- .../maps/filters/EnemySpawnFilter.java | 7 +++---- .../maps/filters/GenerateFilter.java | 14 ++++++------- .../mindustry/maps/filters/MedianFilter.java | 9 ++++---- .../mindustry/maps/filters/MirrorFilter.java | 8 +++---- .../mindustry/maps/filters/NoiseFilter.java | 19 ++++++++--------- .../src/mindustry/maps/filters/OreFilter.java | 17 +++++++-------- .../maps/filters/OreMedianFilter.java | 9 ++++---- .../maps/filters/RiverNoiseFilter.java | 21 +++++++++---------- .../mindustry/maps/filters/ScatterFilter.java | 13 ++++++------ .../maps/filters/SpawnPathFilter.java | 6 +++--- .../mindustry/maps/filters/TerrainFilter.java | 21 +++++++++---------- 17 files changed, 91 insertions(+), 96 deletions(-) diff --git a/core/src/mindustry/editor/MapGenerateDialog.java b/core/src/mindustry/editor/MapGenerateDialog.java index 71f4f60264..fc496f64a0 100644 --- a/core/src/mindustry/editor/MapGenerateDialog.java +++ b/core/src/mindustry/editor/MapGenerateDialog.java @@ -340,6 +340,7 @@ public class MapGenerateDialog extends BaseDialog{ if(filter.isPost() && applied) continue; p.button((icon == '\0' ? "" : icon + " ") + filter.name(), Styles.cleart, () -> { + filter.randomize(); filters.add(filter); rebuildFilters(); update(); diff --git a/core/src/mindustry/editor/MapInfoDialog.java b/core/src/mindustry/editor/MapInfoDialog.java index a18828f1f0..da4bf5a4b3 100644 --- a/core/src/mindustry/editor/MapInfoDialog.java +++ b/core/src/mindustry/editor/MapInfoDialog.java @@ -3,6 +3,7 @@ package mindustry.editor; import arc.*; import arc.scene.ui.*; import arc.struct.*; +import arc.util.*; import mindustry.*; import mindustry.game.*; import mindustry.io.*; @@ -73,8 +74,12 @@ public class MapInfoDialog extends BaseDialog{ t.row(); t.add("@editor.generation").padRight(8).left(); t.button("@edit", () -> { - generate.show(Vars.maps.readFilters(editor.tags.get("genfilters", "")), - filters -> editor.tags.put("genfilters", JsonIO.write(filters))); + generate.show(maps.readFilters(editor.tags.get("genfilters", "")), + filters -> { + //reset seed to 0 so it is not written + filters.each(f -> f.seed = 0); + editor.tags.put("genfilters", JsonIO.write(filters)); + }); hide(); }).left().width(200f); diff --git a/core/src/mindustry/maps/filters/BlendFilter.java b/core/src/mindustry/maps/filters/BlendFilter.java index d8d1ceda9e..ab5171b00f 100644 --- a/core/src/mindustry/maps/filters/BlendFilter.java +++ b/core/src/mindustry/maps/filters/BlendFilter.java @@ -1,6 +1,5 @@ package mindustry.maps.filters; -import arc.util.*; import mindustry.content.*; import mindustry.gen.*; import mindustry.world.*; @@ -13,12 +12,12 @@ public class BlendFilter extends GenerateFilter{ @Override public FilterOption[] options(){ - return Structs.arr( - new SliderOption("radius", () -> radius, f -> radius = f, 1f, 10f), - new BlockOption("block", () -> block, b -> block = b, anyOptional), - new BlockOption("floor", () -> floor, b -> floor = b, anyOptional), - new BlockOption("ignore", () -> ignore, b -> ignore = b, floorsOptional) - ); + return new FilterOption[]{ + new SliderOption("radius", () -> radius, f -> radius = f, 1f, 10f), + new BlockOption("block", () -> block, b -> block = b, anyOptional), + new BlockOption("floor", () -> floor, b -> floor = b, anyOptional), + new BlockOption("ignore", () -> ignore, b -> ignore = b, floorsOptional) + }; } @Override diff --git a/core/src/mindustry/maps/filters/ClearFilter.java b/core/src/mindustry/maps/filters/ClearFilter.java index 59e1a5b53c..1ce2ca6a25 100644 --- a/core/src/mindustry/maps/filters/ClearFilter.java +++ b/core/src/mindustry/maps/filters/ClearFilter.java @@ -1,6 +1,5 @@ package mindustry.maps.filters; -import arc.util.*; import mindustry.content.*; import mindustry.gen.*; import mindustry.world.*; @@ -12,7 +11,9 @@ public class ClearFilter extends GenerateFilter{ @Override public FilterOption[] options(){ - return Structs.arr(new BlockOption("block", () -> block, b -> block = b, b -> oresOnly.get(b) || wallsOnly.get(b))); + return new BlockOption[]{ + new BlockOption("block", () -> block, b -> block = b, b -> oresOnly.get(b) || wallsOnly.get(b)) + }; } @Override diff --git a/core/src/mindustry/maps/filters/CoreSpawnFilter.java b/core/src/mindustry/maps/filters/CoreSpawnFilter.java index 84447ea1f3..5e18726d45 100644 --- a/core/src/mindustry/maps/filters/CoreSpawnFilter.java +++ b/core/src/mindustry/maps/filters/CoreSpawnFilter.java @@ -1,7 +1,6 @@ package mindustry.maps.filters; import arc.struct.*; -import arc.util.*; import mindustry.gen.*; import mindustry.world.*; import mindustry.world.blocks.storage.*; @@ -14,10 +13,9 @@ public class CoreSpawnFilter extends GenerateFilter{ @Override public FilterOption[] options(){ - return Structs.arr( //disabled until necessary // SliderOption("amount", () -> amount, f -> amount = (int)f, 1, 10).display() - ); + return new FilterOption[]{}; } @Override diff --git a/core/src/mindustry/maps/filters/DistortFilter.java b/core/src/mindustry/maps/filters/DistortFilter.java index 4ae9c5a1d2..23f7f42a91 100644 --- a/core/src/mindustry/maps/filters/DistortFilter.java +++ b/core/src/mindustry/maps/filters/DistortFilter.java @@ -1,6 +1,5 @@ package mindustry.maps.filters; -import arc.util.*; import mindustry.gen.*; import mindustry.maps.filters.FilterOption.*; import mindustry.world.*; @@ -10,10 +9,10 @@ public class DistortFilter extends GenerateFilter{ @Override public FilterOption[] options(){ - return Structs.arr( - new SliderOption("scale", () -> scl, f -> scl = f, 1f, 200f), - new SliderOption("mag", () -> mag, f -> mag = f, 0.5f, 100f) - ); + return new SliderOption[]{ + new SliderOption("scale", () -> scl, f -> scl = f, 1f, 200f), + new SliderOption("mag", () -> mag, f -> mag = f, 0.5f, 100f) + }; } @Override @@ -28,7 +27,7 @@ public class DistortFilter extends GenerateFilter{ @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); + Tile tile = in.tile(in.x + noise(in.x, in.y, scl, mag) - mag / 2f, in.y + noise(in.x, in.y, scl, mag) - mag / 2f); in.floor = tile.floor(); if(!tile.block().synthetic() && !in.block.synthetic()) in.block = tile.block(); diff --git a/core/src/mindustry/maps/filters/EnemySpawnFilter.java b/core/src/mindustry/maps/filters/EnemySpawnFilter.java index 682d82debf..7d2a5d8d1d 100644 --- a/core/src/mindustry/maps/filters/EnemySpawnFilter.java +++ b/core/src/mindustry/maps/filters/EnemySpawnFilter.java @@ -1,7 +1,6 @@ package mindustry.maps.filters; import arc.struct.*; -import arc.util.*; import mindustry.content.*; import mindustry.gen.*; import mindustry.maps.filters.FilterOption.*; @@ -13,9 +12,9 @@ public class EnemySpawnFilter extends GenerateFilter{ @Override public FilterOption[] options(){ - return Structs.arr( - new SliderOption("amount", () -> amount, f -> amount = (int)f, 1, 10).display() - ); + return new SliderOption[]{ + new SliderOption("amount", () -> amount, f -> amount = (int)f, 1, 10).display() + }; } @Override diff --git a/core/src/mindustry/maps/filters/GenerateFilter.java b/core/src/mindustry/maps/filters/GenerateFilter.java index b938914528..51db9bf07b 100644 --- a/core/src/mindustry/maps/filters/GenerateFilter.java +++ b/core/src/mindustry/maps/filters/GenerateFilter.java @@ -12,8 +12,8 @@ import mindustry.gen.*; import mindustry.world.*; public abstract class GenerateFilter{ - protected transient float o = (float)(Math.random() * 10000000.0); - protected transient int seed; + public int seed = 0; + protected transient GenerateInput in; public void apply(Tiles tiles, GenerateInput in){ @@ -93,7 +93,7 @@ public abstract class GenerateFilter{ /** set the seed to a random number */ public void randomize(){ - seed = Mathf.random(99999999); + seed = Mathf.random(999999999); } /** @return whether this filter needs a read/write buffer (e.g. not a 1:1 tile mapping). */ @@ -109,19 +109,19 @@ public abstract class GenerateFilter{ //utility generation functions protected float noise(float x, float y, float scl, float mag){ - return (float)in.noise.octaveNoise2D(1f, 0f, 1f / scl, x + o, y + o) * mag; + return (float)in.noise.octaveNoise2D(1f, 0f, 1f / scl, x, y) * mag; } protected float noise(float x, float y, float scl, float mag, float octaves, float persistence){ - return (float)in.noise.octaveNoise2D(octaves, persistence, 1f / scl, x + o, y + o) * mag; + return (float)in.noise.octaveNoise2D(octaves, persistence, 1f / scl, x, y) * mag; } protected float rnoise(float x, float y, float scl, float mag){ - return RidgedPerlin.noise2d(seed + 1, (int)(x + o), (int)(y + o), 1f / scl) * mag; + return RidgedPerlin.noise2d(seed + 1, (int)(x), (int)(y), 1f / scl) * mag; } protected float rnoise(float x, float y, int octaves, float scl, float falloff, float mag){ - return RidgedPerlin.noise2d(seed + 1, (int)(x + o), (int)(y + o), octaves, falloff, 1f / scl) * mag; + return RidgedPerlin.noise2d(seed + 1, (int)(x), (int)(y), octaves, falloff, 1f / scl) * mag; } protected float chance(){ diff --git a/core/src/mindustry/maps/filters/MedianFilter.java b/core/src/mindustry/maps/filters/MedianFilter.java index 001d79bba5..d97d469952 100644 --- a/core/src/mindustry/maps/filters/MedianFilter.java +++ b/core/src/mindustry/maps/filters/MedianFilter.java @@ -2,7 +2,6 @@ package mindustry.maps.filters; import arc.math.*; import arc.struct.*; -import arc.util.*; import mindustry.gen.*; import mindustry.maps.filters.FilterOption.*; import mindustry.world.*; @@ -17,10 +16,10 @@ public class MedianFilter extends GenerateFilter{ @Override public FilterOption[] options(){ - return Structs.arr( - new SliderOption("radius", () -> radius, f -> radius = f, 1f, 10f), - new SliderOption("percentile", () -> percentile, f -> percentile = f, 0f, 1f) - ); + return new SliderOption[]{ + new SliderOption("radius", () -> radius, f -> radius = f, 1f, 10f), + new SliderOption("percentile", () -> percentile, f -> percentile = f, 0f, 1f) + }; } @Override diff --git a/core/src/mindustry/maps/filters/MirrorFilter.java b/core/src/mindustry/maps/filters/MirrorFilter.java index 9e91fb4847..33bd6ace94 100644 --- a/core/src/mindustry/maps/filters/MirrorFilter.java +++ b/core/src/mindustry/maps/filters/MirrorFilter.java @@ -19,10 +19,10 @@ public class MirrorFilter extends GenerateFilter{ @Override public FilterOption[] options(){ - return Structs.arr( - new SliderOption("angle", () -> angle, f -> angle = (int)f, 0, 360, 45), - new ToggleOption("rotate", () -> rotate, f -> rotate = f) - ); + return new FilterOption[]{ + new SliderOption("angle", () -> angle, f -> angle = (int)f, 0, 360, 45), + new ToggleOption("rotate", () -> rotate, f -> rotate = f) + }; } @Override diff --git a/core/src/mindustry/maps/filters/NoiseFilter.java b/core/src/mindustry/maps/filters/NoiseFilter.java index acbf8162c6..986ee077ce 100644 --- a/core/src/mindustry/maps/filters/NoiseFilter.java +++ b/core/src/mindustry/maps/filters/NoiseFilter.java @@ -1,6 +1,5 @@ package mindustry.maps.filters; -import arc.util.*; import mindustry.content.*; import mindustry.gen.*; import mindustry.world.*; @@ -13,15 +12,15 @@ public class NoiseFilter extends GenerateFilter{ @Override public FilterOption[] options(){ - return Structs.arr( - 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, 10f), - new SliderOption("falloff", () -> falloff, f -> falloff = f, 0f, 1f), - new BlockOption("target", () -> target, b -> target = b, anyOptional), - new BlockOption("floor", () -> floor, b -> floor = b, floorsOptional), - new BlockOption("wall", () -> block, b -> block = b, wallsOptional) - ); + return new FilterOption[]{ + 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, 10f), + new SliderOption("falloff", () -> falloff, f -> falloff = f, 0f, 1f), + new BlockOption("target", () -> target, b -> target = b, anyOptional), + new BlockOption("floor", () -> floor, b -> floor = b, floorsOptional), + new BlockOption("wall", () -> block, b -> block = b, wallsOptional) + }; } @Override diff --git a/core/src/mindustry/maps/filters/OreFilter.java b/core/src/mindustry/maps/filters/OreFilter.java index 5487ea0d59..67c79ab82c 100644 --- a/core/src/mindustry/maps/filters/OreFilter.java +++ b/core/src/mindustry/maps/filters/OreFilter.java @@ -1,6 +1,5 @@ package mindustry.maps.filters; -import arc.util.*; import mindustry.content.*; import mindustry.gen.*; import mindustry.world.*; @@ -13,14 +12,14 @@ public class OreFilter extends GenerateFilter{ @Override public FilterOption[] options(){ - return Structs.arr( - 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, 10f), - new SliderOption("falloff", () -> falloff, f -> falloff = f, 0f, 1f), - new BlockOption("ore", () -> ore, b -> ore = b, oresOnly), - new BlockOption("target", () -> target, b -> target = b, oresFloorsOptional) - ); + return new FilterOption[]{ + 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, 10f), + new SliderOption("falloff", () -> falloff, f -> falloff = f, 0f, 1f), + new BlockOption("ore", () -> ore, b -> ore = b, oresOnly), + new BlockOption("target", () -> target, b -> target = b, oresFloorsOptional) + }; } @Override diff --git a/core/src/mindustry/maps/filters/OreMedianFilter.java b/core/src/mindustry/maps/filters/OreMedianFilter.java index cba1d56936..97f5557925 100644 --- a/core/src/mindustry/maps/filters/OreMedianFilter.java +++ b/core/src/mindustry/maps/filters/OreMedianFilter.java @@ -2,7 +2,6 @@ package mindustry.maps.filters; import arc.math.*; import arc.struct.*; -import arc.util.*; import mindustry.*; import mindustry.content.*; import mindustry.gen.*; @@ -17,10 +16,10 @@ public class OreMedianFilter extends GenerateFilter{ @Override public FilterOption[] options(){ - return Structs.arr( - new SliderOption("radius", () -> radius, f -> radius = f, 1f, 12f), - new SliderOption("percentile", () -> percentile, f -> percentile = f, 0f, 1f) - ); + return new SliderOption[]{ + new SliderOption("radius", () -> radius, f -> radius = f, 1f, 12f), + new SliderOption("percentile", () -> percentile, f -> percentile = f, 0f, 1f) + }; } @Override diff --git a/core/src/mindustry/maps/filters/RiverNoiseFilter.java b/core/src/mindustry/maps/filters/RiverNoiseFilter.java index aeefe8422a..af82b91886 100644 --- a/core/src/mindustry/maps/filters/RiverNoiseFilter.java +++ b/core/src/mindustry/maps/filters/RiverNoiseFilter.java @@ -1,6 +1,5 @@ package mindustry.maps.filters; -import arc.util.*; import mindustry.content.*; import mindustry.gen.*; import mindustry.world.*; @@ -13,16 +12,16 @@ public class RiverNoiseFilter extends GenerateFilter{ @Override public FilterOption[] options(){ - return Structs.arr( - new SliderOption("scale", () -> scl, f -> scl = f, 1f, 500f), - new SliderOption("threshold", () -> threshold, f -> threshold = f, -1f, 1f), - new SliderOption("threshold2", () -> threshold2, f -> threshold2 = f, -1f, 1f), - new SliderOption("octaves", () -> octaves, f -> octaves = f, 1f, 10f), - new SliderOption("falloff", () -> falloff, f -> falloff = f, 0f, 1f), - new BlockOption("block", () -> block, b -> block = b, wallsOnly), - new BlockOption("floor", () -> floor, b -> floor = b, floorsOnly), - new BlockOption("floor2", () -> floor2, b -> floor2 = b, floorsOnly) - ); + return new FilterOption[]{ + new SliderOption("scale", () -> scl, f -> scl = f, 1f, 500f), + new SliderOption("threshold", () -> threshold, f -> threshold = f, -1f, 1f), + new SliderOption("threshold2", () -> threshold2, f -> threshold2 = f, -1f, 1f), + new SliderOption("octaves", () -> octaves, f -> octaves = f, 1f, 10f), + new SliderOption("falloff", () -> falloff, f -> falloff = f, 0f, 1f), + new BlockOption("block", () -> block, b -> block = b, wallsOnly), + new BlockOption("floor", () -> floor, b -> floor = b, floorsOnly), + new BlockOption("floor2", () -> floor2, b -> floor2 = b, floorsOnly) + }; } @Override diff --git a/core/src/mindustry/maps/filters/ScatterFilter.java b/core/src/mindustry/maps/filters/ScatterFilter.java index b766183087..5787648276 100644 --- a/core/src/mindustry/maps/filters/ScatterFilter.java +++ b/core/src/mindustry/maps/filters/ScatterFilter.java @@ -1,6 +1,5 @@ package mindustry.maps.filters; -import arc.util.*; import mindustry.content.*; import mindustry.gen.*; import mindustry.world.*; @@ -13,12 +12,12 @@ public class ScatterFilter extends GenerateFilter{ @Override public FilterOption[] options(){ - return Structs.arr( - new SliderOption("chance", () -> chance, f -> chance = f, 0f, 1f), - new BlockOption("flooronto", () -> flooronto, b -> flooronto = b, floorsOptional), - new BlockOption("floor", () -> floor, b -> floor = b, floorsOptional), - new BlockOption("block", () -> block, b -> block = b, wallsOresOptional) - ); + return new FilterOption[]{ + new SliderOption("chance", () -> chance, f -> chance = f, 0f, 1f), + new BlockOption("flooronto", () -> flooronto, b -> flooronto = b, floorsOptional), + new BlockOption("floor", () -> floor, b -> floor = b, floorsOptional), + new BlockOption("block", () -> block, b -> block = b, wallsOresOptional) + }; } @Override diff --git a/core/src/mindustry/maps/filters/SpawnPathFilter.java b/core/src/mindustry/maps/filters/SpawnPathFilter.java index ac49b13d31..09d453e513 100644 --- a/core/src/mindustry/maps/filters/SpawnPathFilter.java +++ b/core/src/mindustry/maps/filters/SpawnPathFilter.java @@ -19,9 +19,9 @@ public class SpawnPathFilter extends GenerateFilter{ @Override public FilterOption[] options(){ - return Structs.arr( - new SliderOption("radius", () -> radius, f -> radius = (int)f, 1, 20).display() - ); + return new SliderOption[]{ + new SliderOption("radius", () -> radius, f -> radius = (int)f, 1, 20).display() + }; } @Override diff --git a/core/src/mindustry/maps/filters/TerrainFilter.java b/core/src/mindustry/maps/filters/TerrainFilter.java index f494d0c143..2c4a9f48c3 100644 --- a/core/src/mindustry/maps/filters/TerrainFilter.java +++ b/core/src/mindustry/maps/filters/TerrainFilter.java @@ -1,7 +1,6 @@ package mindustry.maps.filters; import arc.math.*; -import arc.util.*; import mindustry.content.*; import mindustry.gen.*; import mindustry.world.*; @@ -14,16 +13,16 @@ public class TerrainFilter extends GenerateFilter{ @Override public FilterOption[] options(){ - return Structs.arr( - new SliderOption("scale", () -> scl, f -> scl = f, 1f, 500f), - new SliderOption("mag", () -> magnitude, f -> magnitude = f, 0f, 2f), - 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, floorsOptional), - new BlockOption("wall", () -> block, b -> block = b, wallsOnly) - ); + return new FilterOption[]{ + new SliderOption("scale", () -> scl, f -> scl = f, 1f, 500f), + new SliderOption("mag", () -> magnitude, f -> magnitude = f, 0f, 2f), + 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, floorsOptional), + new BlockOption("wall", () -> block, b -> block = b, wallsOnly) + }; } @Override From 210fb349de9807c41b17b3032f516e65e15dc63a Mon Sep 17 00:00:00 2001 From: Anuken Date: Tue, 15 Jun 2021 12:26:00 -0400 Subject: [PATCH 24/31] Less editor filter state --- .../mindustry/editor/MapGenerateDialog.java | 4 +-- .../mindustry/maps/filters/BlendFilter.java | 2 +- .../mindustry/maps/filters/ClearFilter.java | 2 +- .../mindustry/maps/filters/DistortFilter.java | 4 +-- .../maps/filters/GenerateFilter.java | 34 ++++++++----------- .../mindustry/maps/filters/MedianFilter.java | 2 +- .../mindustry/maps/filters/MirrorFilter.java | 12 +++---- .../mindustry/maps/filters/NoiseFilter.java | 4 +-- .../src/mindustry/maps/filters/OreFilter.java | 4 +-- .../maps/filters/OreMedianFilter.java | 2 +- .../maps/filters/RiverNoiseFilter.java | 2 +- .../mindustry/maps/filters/ScatterFilter.java | 6 ++-- .../mindustry/maps/filters/TerrainFilter.java | 4 +-- 13 files changed, 38 insertions(+), 44 deletions(-) diff --git a/core/src/mindustry/editor/MapGenerateDialog.java b/core/src/mindustry/editor/MapGenerateDialog.java index fc496f64a0..31af738182 100644 --- a/core/src/mindustry/editor/MapGenerateDialog.java +++ b/core/src/mindustry/editor/MapGenerateDialog.java @@ -164,7 +164,7 @@ public class MapGenerateDialog extends BaseDialog{ for(int x = 0; x < editor.width(); x++){ for(int y = 0; y < editor.height(); y++){ Tile tile = editor.tile(x, y); - input.apply(x, y, tile.block(), tile.floor(), tile.overlay()); + input.set(x, y, tile.block(), tile.floor(), tile.overlay()); filter.apply(input); writeTiles[x + y*world.width()] = PackTile.get(input.block.id, input.floor.id, input.overlay.id); } @@ -420,7 +420,7 @@ public class MapGenerateDialog extends BaseDialog{ pixmap.each((px, py) -> { int x = px * scaling, y = py * scaling; long tile = buffer1[px + py * w]; - input.apply(x, y, content.block(PackTile.block(tile)), content.block(PackTile.floor(tile)), content.block(PackTile.overlay(tile))); + input.set(x, y, content.block(PackTile.block(tile)), content.block(PackTile.floor(tile)), content.block(PackTile.overlay(tile))); filter.apply(input); buffer2[px + py * w] = PackTile.get(input.block.id, input.floor.id, input.overlay.id); }); diff --git a/core/src/mindustry/maps/filters/BlendFilter.java b/core/src/mindustry/maps/filters/BlendFilter.java index ab5171b00f..7f0d319df9 100644 --- a/core/src/mindustry/maps/filters/BlendFilter.java +++ b/core/src/mindustry/maps/filters/BlendFilter.java @@ -31,7 +31,7 @@ public class BlendFilter extends GenerateFilter{ } @Override - public void apply(){ + public void apply(GenerateInput in){ if(in.floor == block || block == Blocks.air || in.floor == ignore || (!floor.isFloor() && (in.block == block || in.block == ignore))) return; int rad = (int)radius; diff --git a/core/src/mindustry/maps/filters/ClearFilter.java b/core/src/mindustry/maps/filters/ClearFilter.java index 1ce2ca6a25..3b2684ef7e 100644 --- a/core/src/mindustry/maps/filters/ClearFilter.java +++ b/core/src/mindustry/maps/filters/ClearFilter.java @@ -22,7 +22,7 @@ public class ClearFilter extends GenerateFilter{ } @Override - public void apply(){ + public void apply(GenerateInput in){ if(in.block == block){ in.block = Blocks.air; diff --git a/core/src/mindustry/maps/filters/DistortFilter.java b/core/src/mindustry/maps/filters/DistortFilter.java index 23f7f42a91..8b6d80e8e1 100644 --- a/core/src/mindustry/maps/filters/DistortFilter.java +++ b/core/src/mindustry/maps/filters/DistortFilter.java @@ -26,8 +26,8 @@ public class DistortFilter extends GenerateFilter{ } @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, scl, mag) - mag / 2f); + public void apply(GenerateInput in){ + Tile tile = in.tile(in.x + noise(in, scl, mag) - mag / 2f, in.y + noise(in, scl, mag) - mag / 2f); in.floor = tile.floor(); if(!tile.block().synthetic() && !in.block.synthetic()) in.block = tile.block(); diff --git a/core/src/mindustry/maps/filters/GenerateFilter.java b/core/src/mindustry/maps/filters/GenerateFilter.java index 51db9bf07b..b4512c0699 100644 --- a/core/src/mindustry/maps/filters/GenerateFilter.java +++ b/core/src/mindustry/maps/filters/GenerateFilter.java @@ -14,10 +14,7 @@ import mindustry.world.*; public abstract class GenerateFilter{ public int seed = 0; - protected transient GenerateInput in; - public void apply(Tiles tiles, GenerateInput in){ - this.in = in; if(isBuffered()){ //buffer of tiles used, each tile packed into a long struct @@ -26,8 +23,8 @@ public abstract class GenerateFilter{ for(int i = 0; i < tiles.width * tiles.height; i++){ Tile tile = tiles.geti(i); - in.apply(tile.x, tile.y, tile.block(), tile.floor(), tile.overlay()); - apply(); + in.set(tile.x, tile.y, tile.block(), tile.floor(), tile.overlay()); + apply(in); buffer[i] = PackTile.get(in.block.id, in.floor.id, in.overlay.id); } @@ -48,8 +45,8 @@ public abstract class GenerateFilter{ } }else{ for(Tile tile : tiles){ - in.apply(tile.x, tile.y, tile.block(), tile.floor(), tile.overlay()); - apply(); + in.set(tile.x, tile.y, tile.block(), tile.floor(), tile.overlay()); + apply(in); tile.setFloor(in.floor.asFloor()); tile.setOverlay(!in.floor.asFloor().hasSurface() && in.overlay.asFloor().needsSurface ? Blocks.air : in.overlay); @@ -61,16 +58,11 @@ public abstract class GenerateFilter{ } } - public final void apply(GenerateInput in){ - this.in = in; - apply(); - } - /** @return a new array of options for configuring this filter */ public abstract FilterOption[] options(); /** apply the actual filter on the input */ - protected void apply(){} + public void apply(GenerateInput in){} /** draw any additional guides */ public void draw(Image image){} @@ -108,12 +100,14 @@ public abstract class GenerateFilter{ //utility generation functions - protected float noise(float x, float y, float scl, float mag){ - return (float)in.noise.octaveNoise2D(1f, 0f, 1f / scl, x, y) * mag; + //TODO would be nice if these functions used the seed and ditched "in" completely; simplex should be stateless + + protected float noise(GenerateInput in, float scl, float mag){ + return (float)in.noise.octaveNoise2D(1f, 0f, 1f / scl, in.x, in.y) * mag; } - protected float noise(float x, float y, float scl, float mag, float octaves, float persistence){ - return (float)in.noise.octaveNoise2D(octaves, persistence, 1f / scl, x, y) * mag; + protected float noise(GenerateInput in, float scl, float mag, float octaves, float persistence){ + return (float)in.noise.octaveNoise2D(octaves, persistence, 1f / scl, in.x, in.y) * mag; } protected float rnoise(float x, float y, float scl, float mag){ @@ -124,8 +118,8 @@ public abstract class GenerateFilter{ return RidgedPerlin.noise2d(seed + 1, (int)(x), (int)(y), octaves, falloff, 1f / scl) * mag; } - protected float chance(){ - return Mathf.randomSeed(Pack.longInt(in.x, in.y + seed)); + protected float chance(int x, int y){ + return Mathf.randomSeed(Pack.longInt(x, y + seed)); } /** an input for generating at a certain coordinate. should only be instantiated once. */ @@ -140,7 +134,7 @@ public abstract class GenerateFilter{ Simplex noise = new Simplex(); TileProvider buffer; - public void apply(int x, int y, Block block, Block floor, Block overlay){ + public void set(int x, int y, Block block, Block floor, Block overlay){ this.floor = floor; this.block = block; this.overlay = overlay; diff --git a/core/src/mindustry/maps/filters/MedianFilter.java b/core/src/mindustry/maps/filters/MedianFilter.java index d97d469952..35d08f814d 100644 --- a/core/src/mindustry/maps/filters/MedianFilter.java +++ b/core/src/mindustry/maps/filters/MedianFilter.java @@ -33,7 +33,7 @@ public class MedianFilter extends GenerateFilter{ } @Override - public void apply(){ + public void apply(GenerateInput in){ int rad = (int)radius; blocks.clear(); floors.clear(); diff --git a/core/src/mindustry/maps/filters/MirrorFilter.java b/core/src/mindustry/maps/filters/MirrorFilter.java index 33bd6ace94..d96c3a6b5f 100644 --- a/core/src/mindustry/maps/filters/MirrorFilter.java +++ b/core/src/mindustry/maps/filters/MirrorFilter.java @@ -31,7 +31,7 @@ public class MirrorFilter extends GenerateFilter{ } @Override - protected void apply(){ + public void apply(GenerateInput in){ v1.trnsExact(angle - 90, 1f); v2.set(v1).scl(-1f); @@ -41,7 +41,7 @@ public class MirrorFilter extends GenerateFilter{ v3.set(in.x, in.y); if(!left(v1, v2, v3)){ - mirror(v3, v1.x, v1.y, v2.x, v2.y); + mirror(in.width, in.height, v3, v1.x, v1.y, v2.x, v2.y); Tile tile = in.tile(v3.x, v3.y); in.floor = tile.floor(); if(!tile.block().synthetic()){ @@ -73,11 +73,11 @@ public class MirrorFilter extends GenerateFilter{ Draw.reset(); } - void mirror(Vec2 p, float x0, float y0, float x1, float y1){ + void mirror(int width, int height, Vec2 p, float x0, float y0, float x1, float y1){ //special case: uneven map mirrored at 45 degree angle (or someone might just want rotational symmetry) - if((in.width != in.height && angle % 90 != 0) || rotate){ - p.x = in.width - p.x - 1; - p.y = in.height - p.y - 1; + if((width != height && angle % 90 != 0) || rotate){ + p.x = width - p.x - 1; + p.y = height - p.y - 1; }else{ float dx = x1 - x0; float dy = y1 - y0; diff --git a/core/src/mindustry/maps/filters/NoiseFilter.java b/core/src/mindustry/maps/filters/NoiseFilter.java index 986ee077ce..b4aad342d2 100644 --- a/core/src/mindustry/maps/filters/NoiseFilter.java +++ b/core/src/mindustry/maps/filters/NoiseFilter.java @@ -29,8 +29,8 @@ public class NoiseFilter extends GenerateFilter{ } @Override - public void apply(){ - float noise = noise(in.x, in.y, scl, 1f, octaves, falloff); + public void apply(GenerateInput in){ + float noise = noise(in, scl, 1f, octaves, falloff); if(noise > threshold && (target == Blocks.air || in.floor == target || in.block == target)){ if(floor != Blocks.air) in.floor = floor; diff --git a/core/src/mindustry/maps/filters/OreFilter.java b/core/src/mindustry/maps/filters/OreFilter.java index 67c79ab82c..e5fd5f1344 100644 --- a/core/src/mindustry/maps/filters/OreFilter.java +++ b/core/src/mindustry/maps/filters/OreFilter.java @@ -28,8 +28,8 @@ public class OreFilter extends GenerateFilter{ } @Override - public void apply(){ - float noise = noise(in.x, in.y, scl, 1f, octaves, falloff); + public void apply(GenerateInput in){ + float noise = noise(in, scl, 1f, octaves, falloff); if(noise > threshold && in.overlay != Blocks.spawn && (target == Blocks.air || in.floor == target || in.overlay == target) && in.floor.asFloor().hasSurface()){ in.overlay = ore; diff --git a/core/src/mindustry/maps/filters/OreMedianFilter.java b/core/src/mindustry/maps/filters/OreMedianFilter.java index 97f5557925..9409604c23 100644 --- a/core/src/mindustry/maps/filters/OreMedianFilter.java +++ b/core/src/mindustry/maps/filters/OreMedianFilter.java @@ -33,7 +33,7 @@ public class OreMedianFilter extends GenerateFilter{ } @Override - public void apply(){ + public void apply(GenerateInput in){ if(in.overlay == Blocks.spawn) return; int cx = (in.x / 2) * 2; diff --git a/core/src/mindustry/maps/filters/RiverNoiseFilter.java b/core/src/mindustry/maps/filters/RiverNoiseFilter.java index af82b91886..3fb6efab5e 100644 --- a/core/src/mindustry/maps/filters/RiverNoiseFilter.java +++ b/core/src/mindustry/maps/filters/RiverNoiseFilter.java @@ -30,7 +30,7 @@ public class RiverNoiseFilter extends GenerateFilter{ } @Override - public void apply(){ + public void apply(GenerateInput in){ float noise = rnoise(in.x, in.y, (int)octaves, scl, falloff, 1f); if(noise >= threshold){ diff --git a/core/src/mindustry/maps/filters/ScatterFilter.java b/core/src/mindustry/maps/filters/ScatterFilter.java index 5787648276..ca094891b4 100644 --- a/core/src/mindustry/maps/filters/ScatterFilter.java +++ b/core/src/mindustry/maps/filters/ScatterFilter.java @@ -26,9 +26,9 @@ public class ScatterFilter extends GenerateFilter{ } @Override - public void apply(){ + public void apply(GenerateInput in){ - if(block != Blocks.air && (in.floor == flooronto || flooronto == Blocks.air) && in.block == Blocks.air && chance() <= chance){ + if(block != Blocks.air && (in.floor == flooronto || flooronto == Blocks.air) && in.block == Blocks.air && chance(in.x, in.y) <= chance){ if(!block.isOverlay()){ in.block = block; }else{ @@ -36,7 +36,7 @@ public class ScatterFilter extends GenerateFilter{ } } - if(floor != Blocks.air && (in.floor == flooronto || flooronto == Blocks.air) && chance() <= chance){ + if(floor != Blocks.air && (in.floor == flooronto || flooronto == Blocks.air) && chance(in.x, in.y) <= chance){ in.floor = floor; } } diff --git a/core/src/mindustry/maps/filters/TerrainFilter.java b/core/src/mindustry/maps/filters/TerrainFilter.java index 2c4a9f48c3..98fdc53d9d 100644 --- a/core/src/mindustry/maps/filters/TerrainFilter.java +++ b/core/src/mindustry/maps/filters/TerrainFilter.java @@ -31,8 +31,8 @@ public class TerrainFilter extends GenerateFilter{ } @Override - public void apply(){ - float noise = noise(in.x, in.y, scl, magnitude, octaves, falloff) + Mathf.dst((float)in.x / in.width, (float)in.y / in.height, 0.5f, 0.5f) * circleScl; + public void apply(GenerateInput in){ + float noise = noise(in, scl, magnitude, octaves, falloff) + Mathf.dst((float)in.x / in.width, (float)in.y / in.height, 0.5f, 0.5f) * circleScl; if(floor != Blocks.air){ in.floor = floor; From fe9ff212b24f7b2f0e1ac1a95fef4c19a4e21ce8 Mon Sep 17 00:00:00 2001 From: Anuken Date: Tue, 15 Jun 2021 19:28:54 -0400 Subject: [PATCH 25/31] Stateless simplex --- core/src/mindustry/graphics/MenuRenderer.java | 18 ++++++++---------- core/src/mindustry/graphics/g3d/SunMesh.java | 3 +-- core/src/mindustry/logic/LExecutor.java | 3 --- core/src/mindustry/logic/LogicOp.java | 3 ++- .../mindustry/maps/filters/GenerateFilter.java | 10 ++++------ .../maps/generators/PlanetGenerator.java | 3 +-- .../maps/planet/SerpuloPlanetGenerator.java | 18 ++++++++++-------- gradle.properties | 2 +- tools/src/mindustry/tools/Generators.java | 9 +++------ 9 files changed, 30 insertions(+), 39 deletions(-) diff --git a/core/src/mindustry/graphics/MenuRenderer.java b/core/src/mindustry/graphics/MenuRenderer.java index 307b337028..3bb562bde1 100644 --- a/core/src/mindustry/graphics/MenuRenderer.java +++ b/core/src/mindustry/graphics/MenuRenderer.java @@ -44,9 +44,7 @@ public class MenuRenderer implements Disposable{ Seq ores = content.blocks().select(b -> b instanceof OreBlock && !(b instanceof WallOreBlock)); shadows = new FrameBuffer(width, height); int offset = Mathf.random(100000); - Simplex s1 = new Simplex(offset); - Simplex s2 = new Simplex(offset + 1); - Simplex s3 = new Simplex(offset + 2); + int s1 = offset, s2 = offset + 1, s3 = offset + 2; Block[] selected = Structs.select( new Block[]{Blocks.sand, Blocks.sandWall}, new Block[]{Blocks.shale, Blocks.shaleWall}, @@ -85,27 +83,27 @@ public class MenuRenderer implements Disposable{ Block ore = Blocks.air; Block wall = Blocks.air; - if(s1.octaveNoise2D(3, 0.5, 1/20.0, x, y) > 0.5){ + if(Simplex.noise2d(s1, 3, 0.5, 1/20.0, x, y) > 0.5){ wall = walld; } - if(s3.octaveNoise2D(3, 0.5, 1/20.0, x, y) > 0.5){ + if(Simplex.noise2d(s3, 3, 0.5, 1/20.0, x, y) > 0.5){ floor = floord2; if(wall != Blocks.air){ wall = walld2; } } - if(s2.octaveNoise2D(3, 0.3, 1/30.0, x, y) > tr1){ + if(Simplex.noise2d(s2, 3, 0.3, 1/30.0, x, y) > tr1){ ore = ore1; } - if(s2.octaveNoise2D(2, 0.2, 1/15.0, x, y+99999) > tr2){ + if(Simplex.noise2d(s2, 2, 0.2, 1/15.0, x, y+99999) > tr2){ ore = ore2; } if(doheat){ - double heat = s3.octaveNoise2D(4, 0.6, 1 / 50.0, x, y + 9999); + double heat = Simplex.noise2d(s3, 4, 0.6, 1 / 50.0, x, y + 9999); double base = 0.65; if(heat > base){ @@ -126,7 +124,7 @@ public class MenuRenderer implements Disposable{ if(tech){ int mx = x % secSize, my = y % secSize; int sclx = x / secSize, scly = y / secSize; - if(s1.octaveNoise2D(2, 1f / 10f, 0.5f, sclx, scly) > 0.4f && (mx == 0 || my == 0 || mx == secSize - 1 || my == secSize - 1)){ + if(Simplex.noise2d(s1, 2, 1f / 10f, 0.5f, sclx, scly) > 0.4f && (mx == 0 || my == 0 || mx == secSize - 1 || my == secSize - 1)){ floor = Blocks.darkPanel3; if(Mathf.dst(mx, my, secSize/2, secSize/2) > secSize/2f + 1){ floor = Blocks.darkPanel4; @@ -140,7 +138,7 @@ public class MenuRenderer implements Disposable{ } if(tendrils){ - if(RidgedPerlin.noise2d(1 + offset, x, y, 1f / 17f) > 0f){ + if(Ridged.noise2d(1 + offset, x, y, 1f / 17f) > 0f){ floor = Mathf.chance(0.2) ? Blocks.sporeMoss : Blocks.moss; if(wall != Blocks.air){ diff --git a/core/src/mindustry/graphics/g3d/SunMesh.java b/core/src/mindustry/graphics/g3d/SunMesh.java index bef0761d5f..6b00aa3885 100644 --- a/core/src/mindustry/graphics/g3d/SunMesh.java +++ b/core/src/mindustry/graphics/g3d/SunMesh.java @@ -12,7 +12,6 @@ public class SunMesh extends HexMesh{ public SunMesh(Planet planet, int divisions, double octaves, double persistence, double scl, double pow, double mag, float colorScale, Color... colors){ super(planet, new HexMesher(){ - Simplex sim = new Simplex(); @Override public float getHeight(Vec3 position){ @@ -21,7 +20,7 @@ public class SunMesh extends HexMesh{ @Override public Color getColor(Vec3 position){ - double height = Math.pow(sim.octaveNoise3D(octaves, persistence, scl, position.x, position.y, position.z), pow) * mag; + double height = Math.pow(Simplex.noise3d(0, octaves, persistence, scl, position.x, position.y, position.z), pow) * mag; return Tmp.c1.set(colors[Mathf.clamp((int)(height * colors.length), 0, colors.length - 1)]).mul(colorScale); } }, divisions, Shaders.unlit); diff --git a/core/src/mindustry/logic/LExecutor.java b/core/src/mindustry/logic/LExecutor.java index 43a90298bc..b5695d9773 100644 --- a/core/src/mindustry/logic/LExecutor.java +++ b/core/src/mindustry/logic/LExecutor.java @@ -28,9 +28,6 @@ import static mindustry.Vars.*; public class LExecutor{ public static final int maxInstructions = 1000; - //for noise operations - public static final Simplex noise = new Simplex(); - //special variables public static final int varCounter = 0, diff --git a/core/src/mindustry/logic/LogicOp.java b/core/src/mindustry/logic/LogicOp.java index 2de33558b9..9604279584 100644 --- a/core/src/mindustry/logic/LogicOp.java +++ b/core/src/mindustry/logic/LogicOp.java @@ -2,6 +2,7 @@ package mindustry.logic; import arc.math.*; import arc.util.*; +import arc.util.noise.*; public enum LogicOp{ add("+", (a, b) -> a + b), @@ -32,7 +33,7 @@ public enum LogicOp{ min("min", true, Math::min), angle("angle", true, (x, y) -> Angles.angle((float)x, (float)y)), len("len", true, (x, y) -> Mathf.dst((float)x, (float)y)), - noise("noise", true, LExecutor.noise::rawNoise2D), + noise("noise", true, (x, y) -> Simplex.raw2d(0, x, y)), abs("abs", a -> Math.abs(a)), log("log", Math::log), log10("log10", Math::log10), diff --git a/core/src/mindustry/maps/filters/GenerateFilter.java b/core/src/mindustry/maps/filters/GenerateFilter.java index b4512c0699..cdddad9099 100644 --- a/core/src/mindustry/maps/filters/GenerateFilter.java +++ b/core/src/mindustry/maps/filters/GenerateFilter.java @@ -103,19 +103,19 @@ public abstract class GenerateFilter{ //TODO would be nice if these functions used the seed and ditched "in" completely; simplex should be stateless protected float noise(GenerateInput in, float scl, float mag){ - return (float)in.noise.octaveNoise2D(1f, 0f, 1f / scl, in.x, in.y) * mag; + return (float)Simplex.noise2d(seed, 1f, 0f, 1f / scl, in.x, in.y) * mag; } protected float noise(GenerateInput in, float scl, float mag, float octaves, float persistence){ - return (float)in.noise.octaveNoise2D(octaves, persistence, 1f / scl, in.x, in.y) * mag; + return (float)Simplex.noise2d(seed, octaves, persistence, 1f / scl, in.x, in.y) * mag; } protected float rnoise(float x, float y, float scl, float mag){ - return RidgedPerlin.noise2d(seed + 1, (int)(x), (int)(y), 1f / scl) * mag; + return Ridged.noise2d(seed + 1, (int)(x), (int)(y), 1f / scl) * mag; } protected float rnoise(float x, float y, int octaves, float scl, float falloff, float mag){ - return RidgedPerlin.noise2d(seed + 1, (int)(x), (int)(y), octaves, falloff, 1f / scl) * mag; + return Ridged.noise2d(seed + 1, (int)(x), (int)(y), octaves, falloff, 1f / scl) * mag; } protected float chance(int x, int y){ @@ -131,7 +131,6 @@ public abstract class GenerateFilter{ /** output parameters */ public Block floor, block, overlay; - Simplex noise = new Simplex(); TileProvider buffer; public void set(int x, int y, Block block, Block floor, Block overlay){ @@ -146,7 +145,6 @@ public abstract class GenerateFilter{ this.buffer = buffer; this.width = width; this.height = height; - noise.setSeed(filter.seed); } Tile tile(float x, float y){ diff --git a/core/src/mindustry/maps/generators/PlanetGenerator.java b/core/src/mindustry/maps/generators/PlanetGenerator.java index 830be018fb..059c8e2d31 100644 --- a/core/src/mindustry/maps/generators/PlanetGenerator.java +++ b/core/src/mindustry/maps/generators/PlanetGenerator.java @@ -18,7 +18,6 @@ import static mindustry.Vars.*; public abstract class PlanetGenerator extends BasicGenerator implements HexMesher{ protected IntSeq ints = new IntSeq(); protected Sector sector; - protected Simplex noise = new Simplex(); /** Should generate sector bases for a planet. */ public void generateSector(Sector sector){ @@ -116,7 +115,7 @@ public abstract class PlanetGenerator extends BasicGenerator implements HexMeshe @Override protected float noise(float x, float y, double octaves, double falloff, double scl, double mag){ Vec3 v = sector.rect.project(x, y); - return (float)noise.octaveNoise3D(octaves, falloff, 1f / scl, v.x, v.y, v.z) * (float)mag; + return (float)Simplex.noise3d(0, octaves, falloff, 1f / scl, v.x, v.y, v.z) * (float)mag; } /** @return the scaling factor for sector rects. */ diff --git a/core/src/mindustry/maps/planet/SerpuloPlanetGenerator.java b/core/src/mindustry/maps/planet/SerpuloPlanetGenerator.java index fb68343b1a..f69537ef44 100644 --- a/core/src/mindustry/maps/planet/SerpuloPlanetGenerator.java +++ b/core/src/mindustry/maps/planet/SerpuloPlanetGenerator.java @@ -18,6 +18,8 @@ import mindustry.world.*; import static mindustry.Vars.*; public class SerpuloPlanetGenerator extends PlanetGenerator{ + static final int seed = 0; + BaseGenerator basegen = new BaseGenerator(); float scl = 5f; float waterOffset = 0.07f; @@ -55,7 +57,7 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{ float rawHeight(Vec3 position){ position = Tmp.v33.set(position).scl(scl); - return (Mathf.pow((float)noise.octaveNoise3D(7, 0.5f, 1f/3f, position.x, position.y, position.z), 2.3f) + waterOffset) / (1f + waterOffset); + return (Mathf.pow((float)Simplex.noise3d(seed, 7, 0.5f, 1f/3f, position.x, position.y, position.z), 2.3f) + waterOffset) / (1f + waterOffset); } @Override @@ -114,7 +116,7 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{ tile.floor = getBlock(position); tile.block = tile.floor.asFloor().wall; - if(RidgedPerlin.noise3d(1, position.x, position.y, position.z, 2, 22) > 0.31){ + if(Ridged.noise3d(1, position.x, position.y, position.z, 2, 22) > 0.31){ tile.block = Blocks.air; } } @@ -125,12 +127,12 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{ position = Tmp.v33.set(position).scl(scl); float rad = scl; float temp = Mathf.clamp(Math.abs(position.y * 2f) / (rad)); - float tnoise = (float)noise.octaveNoise3D(7, 0.56, 1f/3f, position.x, position.y + 999f, position.z); + float tnoise = (float)Simplex.noise3d(seed, 7, 0.56, 1f/3f, position.x, position.y + 999f, position.z); temp = Mathf.lerp(temp, tnoise, 0.5f); height *= 1.2f; height = Mathf.clamp(height); - float tar = (float)noise.octaveNoise3D(4, 0.55f, 1f/2f, position.x, position.y + 999f, position.z) * 0.3f + Tmp.v31.dst(0, 0, 1f) * 0.2f; + float tar = (float)Simplex.noise3d(seed, 4, 0.55f, 1f/2f, position.x, position.y + 999f, position.z) * 0.3f + Tmp.v31.dst(0, 0, 1f) * 0.2f; Block res = arr[Mathf.clamp((int)(temp * arr.length), 0, arr[0].length - 1)][Mathf.clamp((int)(height * arr[0].length), 0, arr[0].length - 1)]; if(tar > 0.5f){ @@ -143,7 +145,7 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{ @Override protected float noise(float x, float y, double octaves, double falloff, double scl, double mag){ Vec3 v = sector.rect.project(x, y).scl(5f); - return (float)noise.octaveNoise3D(octaves, falloff, 1f / scl, v.x, v.y, v.z) * (float)mag; + return (float)Simplex.noise3d(seed, octaves, falloff, 1f / scl, v.x, v.y, v.z) * (float)mag; } @Override @@ -250,15 +252,15 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{ float scl = 1f; float addscl = 1.3f; - if(noise.octaveNoise3D(2, 0.5, scl, sector.tile.v.x, sector.tile.v.y, sector.tile.v.z)*nmag + poles > 0.25f*addscl){ + if(Simplex.noise3d(seed, 2, 0.5, scl, sector.tile.v.x, sector.tile.v.y, sector.tile.v.z)*nmag + poles > 0.25f*addscl){ ores.add(Blocks.oreCoal); } - if(noise.octaveNoise3D(2, 0.5, scl, sector.tile.v.x + 1, sector.tile.v.y, sector.tile.v.z)*nmag + poles > 0.5f*addscl){ + if(Simplex.noise3d(seed, 2, 0.5, scl, sector.tile.v.x + 1, sector.tile.v.y, sector.tile.v.z)*nmag + poles > 0.5f*addscl){ ores.add(Blocks.oreTitanium); } - if(noise.octaveNoise3D(2, 0.5, scl, sector.tile.v.x + 2, sector.tile.v.y, sector.tile.v.z)*nmag + poles > 0.7f*addscl){ + if(Simplex.noise3d(seed, 2, 0.5, scl, sector.tile.v.x + 2, sector.tile.v.y, sector.tile.v.z)*nmag + poles > 0.7f*addscl){ ores.add(Blocks.oreThorium); } diff --git a/gradle.properties b/gradle.properties index 8198f3fd92..15947e3eac 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ kapt.include.compile.classpath=false kotlin.stdlib.default.dependency=false #needed for android compilation android.useAndroidX=true -archash=3926b785320fea0cd9ca597f6bfa9071263a5464 +archash=07ced971f4c8b8b5a61aa3a84b29c90aa497cb48 diff --git a/tools/src/mindustry/tools/Generators.java b/tools/src/mindustry/tools/Generators.java index caf411793b..b97890fed3 100644 --- a/tools/src/mindustry/tools/Generators.java +++ b/tools/src/mindustry/tools/Generators.java @@ -176,7 +176,7 @@ public class Generators{ for(int x = 0; x < dim; x++){ for(int y = 0; y < dim; y++){ float dst = Mathf.dst((float)x/dim, (float)y/dim, 0.5f, 0.5f) * 2f; - if(dst < 1.2f && RidgedPerlin.noise2d(1, x, y, 3, 1f / 40f) - dst*(1f-fract) > 0.16f){ + if(dst < 1.2f && Ridged.noise2d(1, x, y, 3, 1f / 40f) - dst*(1f-fract) > 0.16f){ image.setRaw(x, y, Color.whiteRgba); } } @@ -488,7 +488,7 @@ public class Generators{ image.each((x, y) -> { //add darker cracks on top - boolean rValue = Math.max(RidgedPerlin.noise2d(1, x, y, 3, 1f / (20f + image.width/8f)), 0) > 0.16f; + boolean rValue = Math.max(Ridged.noise2d(1, x, y, 3, 1f / (20f + image.width/8f)), 0) > 0.16f; //cut out random chunks with voronoi boolean vval = vn.noise(x, y, 1f / (14f + image.width/40f)) > 0.47; @@ -601,14 +601,11 @@ public class Generators{ /** Generates a scorch pixmap based on parameters. Thread safe, unless multiple scorch generators are running in parallel. */ public static class ScorchGenerator{ - private static final Simplex sim = new Simplex(); - public int size = 80, seed = 0, color = Color.whiteRgba; public double scale = 18, pow = 2, octaves = 4, pers = 0.4, add = 2, nscl = 4.5f; public Pixmap generate(){ Pixmap pix = new Pixmap(size, size); - sim.setSeed(seed); pix.each((x, y) -> { double dst = Mathf.dst(x, y, size/2, size/2) / (size / 2f); @@ -621,7 +618,7 @@ public class Generators{ } private double noise(float angle){ - return Math.pow(sim.octaveNoise2D(octaves, pers, 1 / scale, Angles.trnsx(angle, size/2f) + size/2f, Angles.trnsy(angle, size/2f) + size/2f), pow); + return Math.pow(Simplex.noise2d(seed, octaves, pers, 1 / scale, Angles.trnsx(angle, size/2f) + size/2f, Angles.trnsy(angle, size/2f) + size/2f), pow); } } From efcae883fb871b3caab75f014a90a32e83df4aae Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 16 Jun 2021 11:09:18 -0400 Subject: [PATCH 26/31] Experimental core-capture PvP map / Editor filter fixes --- core/assets/bundles/bundle.properties | 1 + core/assets/maps/passage.msav | Bin 0 -> 13716 bytes core/src/mindustry/editor/MapInfoDialog.java | 8 ++++-- core/src/mindustry/maps/Maps.java | 4 +-- .../mindustry/maps/filters/ClearFilter.java | 27 ++++++++++++------ gradle.properties | 2 +- 6 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 core/assets/maps/passage.msav diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 8aff18d4b9..3e2304b765 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -492,6 +492,7 @@ filter.option.block = Block filter.option.floor = Floor filter.option.flooronto = Target Floor filter.option.target = Target +filter.option.replacement = Replacement filter.option.wall = Wall filter.option.ore = Ore filter.option.floor2 = Secondary Floor diff --git a/core/assets/maps/passage.msav b/core/assets/maps/passage.msav new file mode 100644 index 0000000000000000000000000000000000000000..9e9d8d11383a1ddc3bfe5854670abbf15136db3a GIT binary patch literal 13716 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 C5bwnR literal 0 HcmV?d00001 diff --git a/core/src/mindustry/editor/MapInfoDialog.java b/core/src/mindustry/editor/MapInfoDialog.java index da4bf5a4b3..63b9a053da 100644 --- a/core/src/mindustry/editor/MapInfoDialog.java +++ b/core/src/mindustry/editor/MapInfoDialog.java @@ -3,10 +3,10 @@ package mindustry.editor; import arc.*; import arc.scene.ui.*; import arc.struct.*; -import arc.util.*; import mindustry.*; import mindustry.game.*; import mindustry.io.*; +import mindustry.maps.filters.*; import mindustry.ui.*; import mindustry.ui.dialogs.*; @@ -74,7 +74,11 @@ public class MapInfoDialog extends BaseDialog{ t.row(); t.add("@editor.generation").padRight(8).left(); t.button("@edit", () -> { - generate.show(maps.readFilters(editor.tags.get("genfilters", "")), + //randomize so they're not all the same seed + var res = maps.readFilters(editor.tags.get("genfilters", "")); + res.each(GenerateFilter::randomize); + + generate.show(res, filters -> { //reset seed to 0 so it is not written filters.each(f -> f.seed = 0); diff --git a/core/src/mindustry/maps/Maps.java b/core/src/mindustry/maps/Maps.java index d7ea3b392d..72fcc09aa1 100644 --- a/core/src/mindustry/maps/Maps.java +++ b/core/src/mindustry/maps/Maps.java @@ -29,9 +29,9 @@ import static mindustry.Vars.*; public class Maps{ /** List of all built-in maps. Filenames only. */ - private static String[] defaultMapNames = {"maze", "fortress", "labyrinth", "islands", "tendrils", "caldera", "wasteland", "shattered", "fork", "triad", "mudFlats", "moltenLake", "archipelago", "debrisField", "veins", "glacier"}; + private static String[] defaultMapNames = {"maze", "fortress", "labyrinth", "islands", "tendrils", "caldera", "wasteland", "shattered", "fork", "triad", "mudFlats", "moltenLake", "archipelago", "debrisField", "veins", "glacier", "passage"}; /** Maps tagged as PvP */ - static final String[] pvpMaps = {"veins", "glacier"}; + static final String[] pvpMaps = {"veins", "glacier", "passage"}; /** All maps stored in an ordered array. */ private Seq maps = new Seq<>(); /** Serializer for meta. */ diff --git a/core/src/mindustry/maps/filters/ClearFilter.java b/core/src/mindustry/maps/filters/ClearFilter.java index 3b2684ef7e..59e4512a82 100644 --- a/core/src/mindustry/maps/filters/ClearFilter.java +++ b/core/src/mindustry/maps/filters/ClearFilter.java @@ -7,12 +7,14 @@ import mindustry.world.*; import static mindustry.maps.filters.FilterOption.*; public class ClearFilter extends GenerateFilter{ - protected Block block = Blocks.air; + protected Block target = Blocks.stone; + protected Block replace = Blocks.air; @Override public FilterOption[] options(){ return new BlockOption[]{ - new BlockOption("block", () -> block, b -> block = b, b -> oresOnly.get(b) || wallsOnly.get(b)) + new BlockOption("target", () -> target, b -> target = b, anyOptional), + new BlockOption("replacement", () -> replace, b -> replace = b, anyOptional) }; } @@ -24,12 +26,21 @@ public class ClearFilter extends GenerateFilter{ @Override public void apply(GenerateInput in){ - if(in.block == block){ - in.block = Blocks.air; - } - - if(in.overlay == block){ - in.overlay = Blocks.air; + if(in.block == target || in.floor == target || (target.isOverlay() && in.overlay == target)){ + //special case: when air is the result, replace only the overlay or wall + if(replace == Blocks.air){ + if(in.overlay == target){ + in.overlay = Blocks.air; + }else{ + in.block = Blocks.air; + } + }else if(replace.isOverlay()){ //replace the best match based on type + in.overlay = replace; + }else if(replace.isFloor()){ + in.floor = replace; + }else{ + in.block = replace; + } } } } diff --git a/gradle.properties b/gradle.properties index 15947e3eac..a70ad43723 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ kapt.include.compile.classpath=false kotlin.stdlib.default.dependency=false #needed for android compilation android.useAndroidX=true -archash=07ced971f4c8b8b5a61aa3a84b29c90aa497cb48 +archash=37fbff84ebd824be79586e1e13d05506446938b3 From 865ee952c9b03a55b1f4dc1610f4c9e9f8ef2ae2 Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 16 Jun 2021 13:26:18 -0400 Subject: [PATCH 27/31] WIP polygonal core protection --- core/assets/bundles/bundle.properties | 1 + core/src/mindustry/game/Rules.java | 2 + .../mindustry/graphics/OverlayRenderer.java | 96 ++- core/src/mindustry/graphics/Voronoi.java | 731 ++++++++++++++++++ .../ui/dialogs/CustomRulesDialog.java | 3 +- core/src/mindustry/world/Build.java | 20 +- 6 files changed, 842 insertions(+), 11 deletions(-) create mode 100755 core/src/mindustry/graphics/Voronoi.java diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 3e2304b765..280d6624e6 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -995,6 +995,7 @@ rules.waves = Waves rules.attack = Attack Mode rules.buildai = AI Building rules.corecapture = Capture Core On Destruction +rules.polygoncoreprotection = Polygonal Core Protection rules.enemyCheat = Infinite AI (Red Team) Resources rules.blockhealthmultiplier = Block Health Multiplier rules.blockdamagemultiplier = Block Damage Multiplier diff --git a/core/src/mindustry/game/Rules.java b/core/src/mindustry/game/Rules.java index 093a09f722..b0dd8d3b4f 100644 --- a/core/src/mindustry/game/Rules.java +++ b/core/src/mindustry/game/Rules.java @@ -68,6 +68,8 @@ public class Rules{ public float deconstructRefundMultiplier = 0.5f; /** No-build zone around enemy core radius. */ public float enemyCoreBuildRadius = 400f; + /** If true, no-build zones are calculated based on the closest core. */ + public boolean polygonCoreProtection = false; /** Radius around enemy wave drop zones.*/ public float dropZoneRadius = 300f; /** Time between waves in ticks. */ diff --git a/core/src/mindustry/graphics/OverlayRenderer.java b/core/src/mindustry/graphics/OverlayRenderer.java index 5279de9e4d..fe7309621e 100644 --- a/core/src/mindustry/graphics/OverlayRenderer.java +++ b/core/src/mindustry/graphics/OverlayRenderer.java @@ -5,14 +5,18 @@ import arc.graphics.*; import arc.graphics.g2d.*; import arc.math.*; import arc.math.geom.*; +import arc.struct.*; import arc.util.*; import mindustry.*; import mindustry.ai.types.*; import mindustry.entities.*; +import mindustry.game.EventType.*; +import mindustry.game.*; +import mindustry.game.Teams.*; import mindustry.gen.*; import mindustry.input.*; -import mindustry.ui.*; import mindustry.world.*; +import mindustry.world.blocks.storage.CoreBlock.*; import static mindustry.Vars.*; @@ -23,6 +27,39 @@ public class OverlayRenderer{ private float buildFade, unitFade; private Sized lastSelect; + private Seq cedges = new Seq<>(); + private int lastCores = -1; + + public OverlayRenderer(){ + Events.on(WorldLoadEvent.class, e -> { + cedges.clear(); + lastCores = -1; + }); + } + + private void updateCoreEdges(){ + int count = state.teams.active.sum(t -> t.cores.size); + if(count == lastCores){ + return; + } + + lastCores = count; + cedges.clear(); + + Seq pos = new Seq<>(); + Seq teams = new Seq<>(); + for(TeamData team : state.teams.active){ + for(CoreBuild b : team.cores){ + teams.add(b); + pos.add(new Vec2(b.x, b.y)); + } + } + + var result = Voronoi.generate(pos.toArray(Vec2.class), 0, world.unitWidth(), 0, world.unitHeight()); + for(var edge : result){ + cedges.add(new CoreEdge(edge.x1, edge.y1, edge.x2, edge.y2, teams.get(edge.site1).team, teams.get(edge.site2).team)); + } + } public void drawBottom(){ InputHandler input = control.input; @@ -117,15 +154,32 @@ public class OverlayRenderer{ Lines.stroke(buildFade * 2f); if(buildFade > 0.005f){ - state.teams.eachEnemyCore(player.team(), core -> { - float dst = core.dst(player); - if(dst < state.rules.enemyCoreBuildRadius * 2.2f){ - Draw.color(Color.darkGray); - Lines.circle(core.x, core.y - 2, state.rules.enemyCoreBuildRadius); - Draw.color(Pal.accent, core.team.color, 0.5f + Mathf.absin(Time.time, 10f, 0.5f)); - Lines.circle(core.x, core.y, state.rules.enemyCoreBuildRadius); + if(state.rules.polygonCoreProtection){ + updateCoreEdges(); + Draw.color(Pal.accent); + + for(int i = 0; i < 2; i++){ + float offset = (i == 0 ? -2f : 0f); + for(CoreEdge edge : cedges){ + Team displayed = edge.displayed(); + if(displayed != null){ + Draw.color(i == 0 ? Color.darkGray : Tmp.c1.set(displayed.color).lerp(Pal.accent, Mathf.absin(Time.time, 10f, 0.2f))); + Lines.line(edge.x1, edge.y1 + offset, edge.x2, edge.y2 + offset); + } + } } - }); + + Draw.color(); + }else{ + state.teams.eachEnemyCore(player.team(), core -> { + if(Core.camera.bounds(Tmp.r1).overlaps(Tmp.r2.setCentered(core.x, core.y, state.rules.enemyCoreBuildRadius * 2f))){ + Draw.color(Color.darkGray); + Lines.circle(core.x, core.y - 2, state.rules.enemyCoreBuildRadius); + Draw.color(Pal.accent, core.team.color, 0.5f + Mathf.absin(Time.time, 10f, 0.5f)); + Lines.circle(core.x, core.y, state.rules.enemyCoreBuildRadius); + } + }); + } } Lines.stroke(2f); @@ -192,4 +246,28 @@ public class OverlayRenderer{ } } } + + private static class CoreEdge{ + float x1, y1, x2, y2; + Team t1, t2; + + public CoreEdge(float x1, float y1, float x2, float y2, Team t1, Team t2){ + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + this.t1 = t1; + this.t2 = t2; + } + + @Nullable + Team displayed(){ + return + t1 == t2 ? null : + t1 == player.team() ? t2 : + t2 == player.team() ? t1 : + t2.id == 0 ? t1 : + t1.id < t2.id && t1.id != 0 ? t1 : t2; + } + } } diff --git a/core/src/mindustry/graphics/Voronoi.java b/core/src/mindustry/graphics/Voronoi.java new file mode 100755 index 0000000000..7053ac20ee --- /dev/null +++ b/core/src/mindustry/graphics/Voronoi.java @@ -0,0 +1,731 @@ +package mindustry.graphics; + +import arc.math.geom.*; +import arc.struct.*; + +import java.util.*; + +//TODO in dire need of cleanup +public class Voronoi{ + private final static int LE = 0; + private final static int RE = 1; + + //TODO make local + int siteidx; + Site[] sites; + int nsites; + float borderMinX, borderMaxX, borderMinY, borderMaxY; + float ymin; + float deltay; + int nvertices = 0; + int nedges; + Site bottomsite; + int PQcount; + int PQmin; + int PQhashsize; + Halfedge[] PQhash; + int ELhashsize; + Halfedge[] ELhash; + Seq allEdges; + float minDistanceBetweenSites = 1f; + + public static Seq generate(Vec2[] values, float minX, float maxX, float minY, float maxY){ + return new Voronoi().generateVoronoi(values, minX, maxX, minY, maxY); + } + + Seq generateVoronoi(Vec2[] values, float minX, float maxX, float minY, float maxY){ + allEdges = new Seq<>(); + + nsites = values.length; + + float sn = (float)nsites + 4; + int rtsites = (int)Math.sqrt(sn); + + sites = new Site[nsites]; + Vec2 first = values[0]; + float xmin = first.x; + ymin = first.y; + float xmax = first.x; + float ymax = first.y; + for(int i = 0; i < nsites; i++){ + sites[i] = new Site(); + sites[i].coord.set(values[i]); + sites[i].sitenbr = i; + + if(values[i].x < xmin){ + xmin = values[i].x; + }else if(values[i].x > xmax){ + xmax = values[i].x; + } + + if(values[i].y < ymin){ + ymin = values[i].y; + }else if(values[i].y > ymax){ + ymax = values[i].y; + } + } + + Arrays.sort(sites, (p1, p2) -> { + Vec2 s1 = p1.coord, s2 = p2.coord; + if(s1.y < s2.y){ + return (-1); + } + if(s1.y > s2.y){ + return (1); + } + return Float.compare(s1.x, s2.x); + }); + + deltay = ymax - ymin; + float deltax = xmax - xmin; + + // Check bounding box inputs - if mins are bigger than maxes, swap them + float temp; + if(minX > maxX){ + temp = minX; + minX = maxX; + maxX = temp; + } + if(minY > maxY){ + temp = minY; + minY = maxY; + maxY = temp; + } + borderMinX = minX; + borderMinY = minY; + borderMaxX = maxX; + borderMaxY = maxY; + + siteidx = 0; + Site newsite, bot, top, temp1, p; + Site v; + Vec2 newintstar = null; + int pm; + Halfedge lbnd, rbnd, llbnd, rrbnd, bisector; + Edge e; + + PQcount = 0; + PQmin = 0; + PQhashsize = 4 * rtsites; + PQhash = new Halfedge[PQhashsize]; + + for(int i2 = 0; i2 < PQhashsize; i2 += 1){ + PQhash[i2] = new Halfedge(); + } + int i1; + ELhashsize = 2 * rtsites; + ELhash = new Halfedge[ELhashsize]; + + for(i1 = 0; i1 < ELhashsize; i1 += 1){ + ELhash[i1] = null; + } + Halfedge ELleftend = newHe(null, 0); + Halfedge ELrightend = newHe(null, 0); + ELleftend.ELleft = null; + ELleftend.ELright = ELrightend; + ELrightend.ELleft = ELleftend; + ELrightend.ELright = null; + ELhash[0] = ELleftend; + ELhash[ELhashsize - 1] = ELrightend; + + bottomsite = next(); + newsite = next(); + while(true){ + if(PQcount != 0){ + Vec2 answer = new Vec2(); + + while(PQhash[PQmin].PQnext == null){ + PQmin += 1; + } + answer.x = PQhash[PQmin].PQnext.vertex.coord.x; + answer.y = PQhash[PQmin].PQnext.ystar; + newintstar = (answer); + } + + // if the lowest site has a smaller y value than the lowest vector + // intersection, + // process the site otherwise process the vector intersection + + if(newsite != null + && (PQcount == 0 || newsite.coord.y < newintstar.y || (newsite.coord.y == newintstar.y && newsite.coord.x < newintstar.x))){ + /* new site is smallest -this is a site event */ + // get the first HalfEdge to the LEFT of the new site + int i, bucket; + Halfedge he; + + /* Use hash table to get close to desired halfedge */ + // use the hash function to find the place in the hash map that this + // HalfEdge should be + bucket = (int)(((newsite.coord).x - xmin) / deltax * ELhashsize); + + // make sure that the bucket position in within the range of the hash + // array + if(bucket < 0){ + bucket = 0; + } + if(bucket >= ELhashsize){ + bucket = ELhashsize - 1; + } + + he = getHash(bucket); + if(he == null) + // if the HE isn't found, search backwards and forwards in the hash map + // for the first non-null entry + { + for(i = 1; i < ELhashsize; i += 1){ + if((he = getHash(bucket - i)) != null){ + break; + } + if((he = getHash(bucket + i)) != null){ + break; + } + } + } + /* Now search linear list of halfedges for the correct one */ + if(he == ELleftend || (he != ELrightend && right(he, (newsite.coord)))){ + // keep going right on the list until either the end is reached, or + // you find the 1st edge which the point isn't to the right of + do{ + he = he.ELright; + }while(he != ELrightend && right(he, (newsite.coord))); + he = he.ELleft; + }else + // if the point is to the left of the HalfEdge, then search left for + // the HE just to the left of the point + { + do{ + he = he.ELleft; + }while(he != ELleftend && !right(he, (newsite.coord))); + } + + /* Update hash table and reference counts */ + if(bucket > 0 && bucket < ELhashsize - 1){ + ELhash[bucket] = he; + } + lbnd = (he); + // get the first HalfEdge to the RIGHT of the new site + rbnd = (lbnd.ELright); + // if this halfedge has no edge,bot =bottom site (whatever that + // is) + bot = rightreg(lbnd); + // create a new edge that bisects + e = bisect(bot, newsite); + + // create a new HalfEdge, setting its ELpm field to 0 + bisector = newHe(e, LE); + // insert this new bisector edge between the left and right + // vectors in a linked list + insert(lbnd, bisector); + + // if the new bisector intersects with the left edge, + // remove the left edge's vertex, and put in the new one + if((p = intersect(lbnd, bisector)) != null){ + pqdelete(lbnd); + pqinsert(lbnd, p, p.coord.dst(newsite.coord)); + } + lbnd = bisector; + // create a new HalfEdge, setting its ELpm field to 1 + bisector = newHe(e, RE); + // insert the new HE to the right of the original bisector + // earlier in the IF stmt + insert(lbnd, bisector); + + // if this new bisector intersects with the new HalfEdge + if((p = intersect(bisector, rbnd)) != null){ + // push the HE into the ordered linked list of vertices + pqinsert(bisector, p, p.coord.dst(newsite.coord)); + } + newsite = next(); + }else if(!(PQcount == 0)) + /* intersection is smallest - this is a vector event */{ + // pop the HalfEdge with the lowest vector off the ordered list + // of vectors + Halfedge curr; + + curr = PQhash[PQmin].PQnext; + PQhash[PQmin].PQnext = curr.PQnext; + PQcount -= 1; + lbnd = (curr); + // get the HalfEdge to the left of the above HE + llbnd = (lbnd.ELleft); + // get the HalfEdge to the right of the above HE + rbnd = (lbnd.ELright); + // get the HalfEdge to the right of the HE to the right of the + // lowest HE + rrbnd = (rbnd.ELright); + // get the Site to the left of the left HE which it bisects + bot = leftReg(lbnd); + // get the Site to the right of the right HE which it bisects + top = rightreg(rbnd); + + v = lbnd.vertex; // get the vertex that caused this event + // set the vertex number - couldn't do this + v.sitenbr = nvertices; + nvertices += 1; + // earlier since we didn't know when it would be processed + endpoint(lbnd.ELedge, lbnd.ELpm, v); + // set the endpoint of + // the left HalfEdge to be this vector + endpoint(rbnd.ELedge, rbnd.ELpm, v); + // set the endpoint of the right HalfEdge to + // be this vector + delete(lbnd); // mark the lowest HE for + // deletion - can't delete yet because there might be pointers + // to it in Hash Map + pqdelete(rbnd); + // remove all vertex events to do with the right HE + delete(rbnd); // mark the right HE for + // deletion - can't delete yet because there might be pointers + // to it in Hash Map + pm = LE; // set the pm variable to zero + + if(bot.coord.y > top.coord.y) + // if the site to the left of the event is higher than the + // Site + { // to the right of it, then swap them and set the 'pm' + // variable to 1 + temp1 = bot; + bot = top; + top = temp1; + pm = RE; + } + e = bisect(bot, top); // create an Edge (or line) + // that is between the two Sites. This creates the formula of + // the line, and assigns a line number to it + bisector = newHe(e, pm); // create a HE from the Edge 'e', + // and make it point to that edge + // with its ELedge field + insert(llbnd, bisector); // insert the new bisector to the + // right of the left HE + endpoint(e, RE - pm, v); // set one endpoint to the new edge + // to be the vector point 'v'. + // If the site to the left of this bisector is higher than the + // right Site, then this endpoint + // is put in position 0; otherwise in pos 1 + + // if left HE and the new bisector intersect, then delete + // the left HE, and reinsert it + if((p = intersect(llbnd, bisector)) != null){ + pqdelete(llbnd); + pqinsert(llbnd, p, p.coord.dst(bot.coord)); + } + + // if right HE and the new bisector intersect, then + // reinsert it + if((p = intersect(bisector, rrbnd)) != null){ + pqinsert(bisector, p, p.coord.dst(bot.coord)); + } + }else{ + break; + } + } + + for(lbnd = (ELleftend.ELright); lbnd != ELrightend; lbnd = (lbnd.ELright)){ + e = lbnd.ELedge; + clipLine(e); + } + + return allEdges; + } + + private Site next(){ + return siteidx < nsites ? sites[siteidx ++] : null; + } + + private Edge bisect(Site s1, Site s2){ + float dx, dy, adx, ady; + Edge newedge; + + newedge = new Edge(); + + // store the sites that this edge is bisecting + newedge.reg[0] = s1; + newedge.reg[1] = s2; + // to begin with, there are no endpoints on the bisector - it goes to + // infinity + newedge.ep[0] = null; + newedge.ep[1] = null; + + // get the difference in x dist between the sites + dx = s2.coord.x - s1.coord.x; + dy = s2.coord.y - s1.coord.y; + // make sure that the difference in positive + adx = dx > 0 ? dx : -dx; + ady = dy > 0 ? dy : -dy; + newedge.c = s1.coord.x * dx + s1.coord.y * dy + (dx * dx + dy * dy) * 0.5f;// get the slope of the line + + if(adx > ady){ + newedge.a = 1.0f; + newedge.b = dy / dx; + newedge.c /= dx;// set formula of line, with x fixed to 1 + }else{ + newedge.b = 1.0f; + newedge.a = dx / dy; + newedge.c /= dy;// set formula of line, with y fixed to 1 + } + + newedge.edgenbr = nedges; + + nedges += 1; + return (newedge); + } + + private int pqbucket(Halfedge he){ + int bucket; + + bucket = (int)((he.ystar - ymin) / deltay * PQhashsize); + if(bucket < 0){ + bucket = 0; + } + if(bucket >= PQhashsize){ + bucket = PQhashsize - 1; + } + if(bucket < PQmin){ + PQmin = bucket; + } + return (bucket); + } + + // push the HalfEdge into the ordered linked list of vertices + private void pqinsert(Halfedge he, Site v, float offset){ + Halfedge last, next; + + he.vertex = v; + he.ystar = v.coord.y + offset; + last = PQhash[pqbucket(he)]; + while((next = last.PQnext) != null + && (he.ystar > next.ystar || (he.ystar == next.ystar && v.coord.x > next.vertex.coord.x))){ + last = next; + } + he.PQnext = last.PQnext; + last.PQnext = he; + PQcount += 1; + } + + // remove the HalfEdge from the list of vertices + private void pqdelete(Halfedge he){ + Halfedge last; + + if(he.vertex != null){ + last = PQhash[pqbucket(he)]; + while(last.PQnext != he){ + last = last.PQnext; + } + + last.PQnext = he.PQnext; + PQcount -= 1; + he.vertex = null; + } + } + + private Halfedge newHe(Edge e, int pm){ + Halfedge answer = new Halfedge(); + answer.ELedge = e; + answer.ELpm = pm; + answer.PQnext = null; + answer.vertex = null; + return (answer); + } + + private Site leftReg(Halfedge he){ + if(he.ELedge == null){ + return (bottomsite); + } + return (he.ELpm == LE ? he.ELedge.reg[LE] : he.ELedge.reg[RE]); + } + + private void insert(Halfedge lb, Halfedge newHe){ + newHe.ELleft = lb; + newHe.ELright = lb.ELright; + (lb.ELright).ELleft = newHe; + lb.ELright = newHe; + } + + /* + * This delete routine can't reclaim node, since pointers from hash table + * may be present. + */ + private void delete(Halfedge he){ + (he.ELleft).ELright = he.ELright; + (he.ELright).ELleft = he.ELleft; + he.deleted = true; + } + + /* Get entry from hash table, pruning any deleted nodes */ + private Halfedge getHash(int b){ + Halfedge he; + + if(b < 0 || b >= ELhashsize){ + return (null); + } + he = ELhash[b]; + if(he == null || !he.deleted){ + return (he); + } + + /* Hash table points to deleted half edge. Patch as necessary. */ + ELhash[b] = null; + return (null); + } + + private void clipLine(Edge e){ + float pxmin, pxmax, pymin, pymax; + Site s1, s2; + float x1 = 0, x2 = 0, y1 = 0, y2 = 0; + + x1 = e.reg[0].coord.x; + x2 = e.reg[1].coord.x; + y1 = e.reg[0].coord.y; + y2 = e.reg[1].coord.y; + + // if the distance between the two points this line was created from is + // less than the square root of 2, then ignore it + if(Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1))) < minDistanceBetweenSites){ + return; + } + pxmin = borderMinX; + pxmax = borderMaxX; + pymin = borderMinY; + pymax = borderMaxY; + + if(e.a == 1.0 && e.b >= 0.0){ + s1 = e.ep[1]; + s2 = e.ep[0]; + }else{ + s1 = e.ep[0]; + s2 = e.ep[1]; + } + + if(e.a == 1.0){ + y1 = pymin; + if(s1 != null && s1.coord.y > pymin){ + y1 = s1.coord.y; + } + if(y1 > pymax){ + y1 = pymax; + } + x1 = e.c - e.b * y1; + y2 = pymax; + if(s2 != null && s2.coord.y < pymax){ + y2 = s2.coord.y; + } + + if(y2 < pymin){ + y2 = pymin; + } + x2 = (e.c) - (e.b) * y2; + if(((x1 > pxmax) & (x2 > pxmax)) | ((x1 < pxmin) & (x2 < pxmin))){ + return; + } + if(x1 > pxmax){ + x1 = pxmax; + y1 = (e.c - x1) / e.b; + } + if(x1 < pxmin){ + x1 = pxmin; + y1 = (e.c - x1) / e.b; + } + if(x2 > pxmax){ + x2 = pxmax; + y2 = (e.c - x2) / e.b; + } + if(x2 < pxmin){ + x2 = pxmin; + y2 = (e.c - x2) / e.b; + } + }else{ + x1 = pxmin; + if(s1 != null && s1.coord.x > pxmin){ + x1 = s1.coord.x; + } + if(x1 > pxmax){ + x1 = pxmax; + } + y1 = e.c - e.a * x1; + x2 = pxmax; + if(s2 != null && s2.coord.x < pxmax){ + x2 = s2.coord.x; + } + if(x2 < pxmin){ + x2 = pxmin; + } + y2 = e.c - e.a * x2; + if(((y1 > pymax) & (y2 > pymax)) | ((y1 < pymin) & (y2 < pymin))){ + return; + } + if(y1 > pymax){ + y1 = pymax; + x1 = (e.c - y1) / e.a; + } + if(y1 < pymin){ + y1 = pymin; + x1 = (e.c - y1) / e.a; + } + if(y2 > pymax){ + y2 = pymax; + x2 = (e.c - y2) / e.a; + } + if(y2 < pymin){ + y2 = pymin; + x2 = (e.c - y2) / e.a; + } + } + + GraphEdge newEdge = new GraphEdge(); + allEdges.add(newEdge); + newEdge.x1 = x1; + newEdge.y1 = y1; + newEdge.x2 = x2; + newEdge.y2 = y2; + + newEdge.site1 = e.reg[0].sitenbr; + newEdge.site2 = e.reg[1].sitenbr; + } + + private void endpoint(Edge e, int lr, Site s){ + e.ep[lr] = s; + if(e.ep[RE - lr] == null){ + return; + } + clipLine(e); + } + + private boolean right(Halfedge el, Vec2 p){ + Edge e; + Site topsite; + boolean right_of_site; + boolean above, fast; + float dxp, dyp, dxs, t1, t2, t3, yl; + + e = el.ELedge; + topsite = e.reg[1]; + right_of_site = p.x > topsite.coord.x; + if(right_of_site && el.ELpm == LE){ + return (true); + } + if(!right_of_site && el.ELpm == RE){ + return (false); + } + + if(e.a == 1.0){ + dyp = p.y - topsite.coord.y; + dxp = p.x - topsite.coord.x; + fast = false; + if((!right_of_site & (e.b < 0.0)) | (right_of_site & (e.b >= 0.0))){ + above = dyp >= e.b * dxp; + fast = above; + }else{ + above = p.x + p.y * e.b > e.c; + if(e.b < 0.0){ + above = !above; + } + if(!above){ + fast = true; + } + } + if(!fast){ + dxs = topsite.coord.x - (e.reg[0]).coord.x; + above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp + * (1.0 + 2.0 * dxp / dxs + e.b * e.b); + if(e.b < 0.0){ + above = !above; + } + } + }else /* e.b==1.0 */{ + yl = e.c - e.a * p.x; + t1 = p.y - yl; + t2 = p.x - topsite.coord.x; + t3 = yl - topsite.coord.y; + above = t1 * t1 > t2 * t2 + t3 * t3; + } + return ((el.ELpm == LE) == above); + } + + private Site rightreg(Halfedge he){ + if(he.ELedge == null) return bottomsite; + + // if the ELpm field is zero, return the site 0 that this edge bisects, + // otherwise return site number 1 + return (he.ELpm == LE ? he.ELedge.reg[RE] : he.ELedge.reg[LE]); + } + + // create a new site where the HalfEdges el1 and el2 intersect - note that + // the Vec2 in the argument list is not used, don't know why it's there + private Site intersect(Halfedge el1, Halfedge el2){ + Edge e1, e2, e; + Halfedge el; + float d, xint, yint; + boolean right_of_site; + Site v; + + e1 = el1.ELedge; + e2 = el2.ELedge; + if(e1 == null || e2 == null){ + return null; + } + + // if the two edges bisect the same parent, return null + if(e1.reg[1] == e2.reg[1]){ + return null; + } + + d = e1.a * e2.b - e1.b * e2.a; + if(-1.0e-10 < d && d < 1.0e-10){ + return null; + } + + xint = (e1.c * e2.b - e2.c * e1.b) / d; + yint = (e2.c * e1.a - e1.c * e2.a) / d; + + if((e1.reg[1].coord.y < e2.reg[1].coord.y) + || (e1.reg[1].coord.y == e2.reg[1].coord.y && e1.reg[1].coord.x < e2.reg[1].coord.x)){ + el = el1; + e = e1; + }else{ + el = el2; + e = e2; + } + + right_of_site = xint >= e.reg[1].coord.x; + if((right_of_site && el.ELpm == LE) + || (!right_of_site && el.ELpm == RE)){ + return null; + } + + // create a new site at the point of intersection - this is a new vector + // event waiting to happen + v = new Site(); + v.coord.x = xint; + v.coord.y = yint; + return (v); + } + + // used both for sites and for vertices + static class Site{ + Vec2 coord = new Vec2(); + int sitenbr; + } + + static class Halfedge{ + Halfedge ELleft, ELright; + Edge ELedge; + boolean deleted; + int ELpm; + Site vertex; + float ystar; + Halfedge PQnext; + } + + public static class GraphEdge{ + public float x1, y1, x2, y2; + + public int site1, site2; + } + + static class Edge{ + float a = 0, b = 0, c = 0; + Site[] ep = new Site[2]; // JH: End points? + Site[] reg = new Site[2]; // JH: Sites this edge bisects? + int edgenbr; + } +} diff --git a/core/src/mindustry/ui/dialogs/CustomRulesDialog.java b/core/src/mindustry/ui/dialogs/CustomRulesDialog.java index 6c763e840b..aa19947d54 100644 --- a/core/src/mindustry/ui/dialogs/CustomRulesDialog.java +++ b/core/src/mindustry/ui/dialogs/CustomRulesDialog.java @@ -170,7 +170,8 @@ public class CustomRulesDialog extends BaseDialog{ check("@rules.attack", b -> rules.attackMode = b, () -> rules.attackMode); check("@rules.buildai", b -> rules.teams.get(rules.waveTeam).ai = rules.teams.get(rules.waveTeam).infiniteResources = b, () -> rules.teams.get(rules.waveTeam).ai); check("@rules.corecapture", b -> rules.coreCapture = b, () -> rules.coreCapture); - number("@rules.enemycorebuildradius", f -> rules.enemyCoreBuildRadius = f * tilesize, () -> Math.min(rules.enemyCoreBuildRadius / tilesize, 200)); + check("@rules.polygoncoreprotection", b -> rules.polygonCoreProtection = b, () -> rules.polygonCoreProtection); + number("@rules.enemycorebuildradius", f -> rules.enemyCoreBuildRadius = f * tilesize, () -> Math.min(rules.enemyCoreBuildRadius / tilesize, 200), () -> !rules.polygonCoreProtection); title("@rules.title.environment"); check("@rules.explosions", b -> rules.damageExplosions = b, () -> rules.damageExplosions); diff --git a/core/src/mindustry/world/Build.java b/core/src/mindustry/world/Build.java index b873546340..0069ea0f56 100644 --- a/core/src/mindustry/world/Build.java +++ b/core/src/mindustry/world/Build.java @@ -10,9 +10,11 @@ import mindustry.content.*; import mindustry.entities.*; import mindustry.game.EventType.*; import mindustry.game.*; +import mindustry.game.Teams.*; import mindustry.gen.*; import mindustry.world.blocks.*; import mindustry.world.blocks.ConstructBlock.*; +import mindustry.world.blocks.storage.CoreBlock.*; import static mindustry.Vars.*; @@ -132,7 +134,23 @@ public class Build{ return false; } - if(state.teams.eachEnemyCore(team, core -> Mathf.dst(x * tilesize + type.offset, y * tilesize + type.offset, core.x, core.y) < state.rules.enemyCoreBuildRadius + type.size * tilesize / 2f)){ + //find closest core, if it doesn't match the team, placing is not legal + if(state.rules.polygonCoreProtection){ + float mindst = Float.MAX_VALUE; + CoreBuild closest = null; + for(TeamData data : state.teams.active){ + for(CoreBuild tile : data.cores){ + float dst = tile.dst2(x * tilesize + type.offset, y * tilesize + type.offset); + if(dst < mindst){ + closest = tile; + mindst = dst; + } + } + } + if(closest != null && closest.team != team){ + return false; + } + }else if(state.teams.eachEnemyCore(team, core -> Mathf.dst(x * tilesize + type.offset, y * tilesize + type.offset, core.x, core.y) < state.rules.enemyCoreBuildRadius + type.size * tilesize / 2f)){ return false; } From f11f390c1f0fb983fc09f9c88e17138ed5e073cb Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 16 Jun 2021 14:51:37 -0400 Subject: [PATCH 28/31] Polygonal protection for built-in PvP maps --- core/assets/maps/glacier.msav | Bin 6998 -> 7198 bytes core/assets/maps/passage.msav | Bin 13716 -> 13732 bytes core/assets/maps/veins.msav | Bin 22875 -> 22989 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/core/assets/maps/glacier.msav b/core/assets/maps/glacier.msav index 73796f764351133dba65af629baba0efed5ef230..efe240108d7b7c17a5cb8355fdb667a36487b7d3 100644 GIT binary patch delta 7179 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 delta 6981 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 literal 13716 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 C5bwnR diff --git a/core/assets/maps/veins.msav b/core/assets/maps/veins.msav index 3273feb74bb068be036e59f9d8f3833277221a21..037d3f0b7596a5d6f329126d0b0a3d5d899a28f1 100644 GIT binary patch literal 22989 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 literal 22875 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 From 9088d9eb3986ef75cc4770deb476bd949fa83a4e Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 16 Jun 2021 15:07:14 -0400 Subject: [PATCH 29/31] Polygonal protection bugfixes --- core/src/mindustry/game/EventType.java | 10 ++++++++++ core/src/mindustry/graphics/OverlayRenderer.java | 15 +++++++++------ .../world/blocks/storage/CoreBlock.java | 16 ++++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/core/src/mindustry/game/EventType.java b/core/src/mindustry/game/EventType.java index 015eac0c97..9d590de52c 100644 --- a/core/src/mindustry/game/EventType.java +++ b/core/src/mindustry/game/EventType.java @@ -9,6 +9,7 @@ import mindustry.net.*; import mindustry.net.Packets.*; import mindustry.type.*; import mindustry.world.*; +import mindustry.world.blocks.storage.CoreBlock.*; public class EventType{ @@ -283,6 +284,15 @@ public class EventType{ } } + /** Called when a core block is placed/removed or its team is changed. */ + public static class CoreChangeEvent{ + public CoreBuild core; + + public CoreChangeEvent(CoreBuild core){ + this.core = core; + } + } + public static class StateChangeEvent{ public final State from, to; diff --git a/core/src/mindustry/graphics/OverlayRenderer.java b/core/src/mindustry/graphics/OverlayRenderer.java index fe7309621e..a75555b60b 100644 --- a/core/src/mindustry/graphics/OverlayRenderer.java +++ b/core/src/mindustry/graphics/OverlayRenderer.java @@ -28,22 +28,24 @@ public class OverlayRenderer{ private float buildFade, unitFade; private Sized lastSelect; private Seq cedges = new Seq<>(); - private int lastCores = -1; + private boolean updatedCores; public OverlayRenderer(){ Events.on(WorldLoadEvent.class, e -> { - cedges.clear(); - lastCores = -1; + updatedCores = true; + }); + + Events.on(CoreChangeEvent.class, e -> { + updatedCores = true; }); } private void updateCoreEdges(){ - int count = state.teams.active.sum(t -> t.cores.size); - if(count == lastCores){ + if(!updatedCores){ return; } - lastCores = count; + updatedCores = false; cedges.clear(); Seq pos = new Seq<>(); @@ -55,6 +57,7 @@ public class OverlayRenderer{ } } + //if this is laggy, it could be shoved in another thread. var result = Voronoi.generate(pos.toArray(Vec2.class), 0, world.unitWidth(), 0, world.unitHeight()); for(var edge : result){ cedges.add(new CoreEdge(edge.x1, edge.y1, edge.x2, edge.y2, teams.get(edge.site1).team, teams.get(edge.site2).team)); diff --git a/core/src/mindustry/world/blocks/storage/CoreBlock.java b/core/src/mindustry/world/blocks/storage/CoreBlock.java index a5d61237e3..52844719d7 100644 --- a/core/src/mindustry/world/blocks/storage/CoreBlock.java +++ b/core/src/mindustry/world/blocks/storage/CoreBlock.java @@ -194,6 +194,20 @@ public class CoreBlock extends StorageBlock{ super.damage(source, damage); } + @Override + public void created(){ + super.created(); + + Events.fire(new CoreChangeEvent(this)); + } + + @Override + public void changeTeam(Team next){ + super.changeTeam(next); + + Events.fire(new CoreChangeEvent(this)); + } + @Override public double sense(LAccess sensor){ if(sensor == LAccess.itemCapacity) return storageCapacity; @@ -260,6 +274,8 @@ public class CoreBlock extends StorageBlock{ spawner.getSpawns().add(tile); } } + + Events.fire(new CoreChangeEvent(this)); } @Override From a74bc0c0772a079ec3e7051b4be9dd8e35ffcf26 Mon Sep 17 00:00:00 2001 From: MEEP of Faith <54301439+MEEPofFaith@users.noreply.github.com> Date: Wed, 16 Jun 2021 12:55:08 -0700 Subject: [PATCH 30/31] block destroyEffect (#5427) --- core/src/mindustry/entities/Damage.java | 9 +++++++-- core/src/mindustry/entities/comp/BuildingComp.java | 2 +- core/src/mindustry/world/Block.java | 2 ++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/core/src/mindustry/entities/Damage.java b/core/src/mindustry/entities/Damage.java index 176761c349..7c69b06cb2 100644 --- a/core/src/mindustry/entities/Damage.java +++ b/core/src/mindustry/entities/Damage.java @@ -34,13 +34,18 @@ public class Damage{ dynamicExplosion(x, y, flammability, explosiveness, power, radius, damage, true, null, Fx.dynamicExplosion); } + /** Creates a dynamic explosion based on specified parameters. */ + public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, Effect explosionFx){ + dynamicExplosion(x, y, flammability, explosiveness, power, radius, damage, true, null, explosionFx); + } + /** Creates a dynamic explosion based on specified parameters. */ public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, boolean fire, @Nullable Team ignoreTeam){ dynamicExplosion(x, y, flammability, explosiveness, power, radius, damage, fire, ignoreTeam, Fx.dynamicExplosion); } /** Creates a dynamic explosion based on specified parameters. */ - public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, boolean fire, @Nullable Team ignoreTeam, Effect explosion){ + public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, boolean fire, @Nullable Team ignoreTeam, Effect explosionFx){ if(damage){ for(int i = 0; i < Mathf.clamp(power / 700, 0, 8); i++){ int length = 5 + Mathf.clamp((int)(power / 500), 1, 20); @@ -74,7 +79,7 @@ public class Damage{ float shake = Math.min(explosiveness / 4f + 3f, 9f); Effect.shake(shake, shake, x, y); - explosion.at(x, y, radius / 8f); + explosionFx.at(x, y, radius / 8f); } public static void createIncend(float x, float y, float range, int amount){ diff --git a/core/src/mindustry/entities/comp/BuildingComp.java b/core/src/mindustry/entities/comp/BuildingComp.java index 6c6bf1250d..83c20b40ab 100644 --- a/core/src/mindustry/entities/comp/BuildingComp.java +++ b/core/src/mindustry/entities/comp/BuildingComp.java @@ -1059,7 +1059,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, }); } - Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, state.rules.damageExplosions); + Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, state.rules.damageExplosions, block.destroyEffect); if(!floor().solid && !floor().isLiquid){ Effect.rubble(x, y, block.size); diff --git a/core/src/mindustry/world/Block.java b/core/src/mindustry/world/Block.java index 11eb6b3bef..6e2e2c1afc 100644 --- a/core/src/mindustry/world/Block.java +++ b/core/src/mindustry/world/Block.java @@ -229,6 +229,8 @@ public class Block extends UnlockableContent{ public boolean instantDeconstruct = false; /** Effect for breaking the block. Passes size as rotation. */ public Effect breakEffect = Fx.breakBlock; + /** Effect for destroying the block. */ + public Effect destroyEffect = Fx.dynamicExplosion; /** Multiplier for cost of research in tech tree. */ public float researchCostMultiplier = 1; /** Whether this block has instant transfer.*/ From 15affaad9b8edfdb5cb06a8526ced21cf2362095 Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 16 Jun 2021 16:53:09 -0400 Subject: [PATCH 31/31] Cleaner impact reactor explosion --- core/src/mindustry/content/Fx.java | 41 ++++ core/src/mindustry/graphics/Voronoi.java | 220 ++++++------------ .../world/blocks/power/ImpactReactor.java | 25 +- 3 files changed, 111 insertions(+), 175 deletions(-) diff --git a/core/src/mindustry/content/Fx.java b/core/src/mindustry/content/Fx.java index 56246bc02f..d5b9ffcf7b 100644 --- a/core/src/mindustry/content/Fx.java +++ b/core/src/mindustry/content/Fx.java @@ -1113,6 +1113,47 @@ public class Fx{ }); }), + impactReactorExplosion = new Effect(30, 500f, b -> { + float intensity = 8f; + float baseLifetime = 25f + intensity * 15f; + b.lifetime = 50f + intensity * 64f; + + color(Pal.lighterOrange); + alpha(0.8f); + for(int i = 0; i < 5; i++){ + rand.setSeed(b.id*2 + i); + float lenScl = rand.random(0.25f, 1f); + int fi = i; + b.scaled(b.lifetime * lenScl, e -> { + randLenVectors(e.id + fi - 1, e.fin(Interp.pow10Out), (int)(2.8f * intensity), 25f * intensity, (x, y, in, out) -> { + float fout = e.fout(Interp.pow5Out) * rand.random(0.5f, 1f); + float rad = fout * ((2f + intensity) * 2.35f); + + Fill.circle(e.x + x, e.y + y, rad); + Drawf.light(e.x + x, e.y + y, rad * 2.6f, Pal.lighterOrange, 0.7f); + }); + }); + } + + b.scaled(baseLifetime, e -> { + Draw.color(); + e.scaled(5 + intensity * 2f, i -> { + stroke((3.1f + intensity/5f) * i.fout()); + Lines.circle(e.x, e.y, (3f + i.fin() * 14f) * intensity); + Drawf.light(e.x, e.y, i.fin() * 14f * 2f * intensity, Color.white, 0.9f * e.fout()); + }); + + color(Color.white, Pal.lighterOrange, e.fin()); + stroke((2f * e.fout())); + + Draw.z(Layer.effect + 0.001f); + randLenVectors(e.id + 1, e.finpow() + 0.001f, (int)(8 * intensity), 30f * intensity, (x, y, in, out) -> { + lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + out * 4 * (4f + intensity)); + Drawf.light(e.x + x, e.y + y, (out * 4 * (3f + intensity)) * 3.5f, Draw.getColor(), 0.8f); + }); + }); + }), + blockExplosion = new Effect(30, e -> { e.scaled(7, i -> { stroke(3.1f * i.fout()); diff --git a/core/src/mindustry/graphics/Voronoi.java b/core/src/mindustry/graphics/Voronoi.java index 7053ac20ee..7b10e497e2 100755 --- a/core/src/mindustry/graphics/Voronoi.java +++ b/core/src/mindustry/graphics/Voronoi.java @@ -97,12 +97,6 @@ public class Voronoi{ borderMaxY = maxY; siteidx = 0; - Site newsite, bot, top, temp1, p; - Site v; - Vec2 newintstar = null; - int pm; - Halfedge lbnd, rbnd, llbnd, rrbnd, bisector; - Edge e; PQcount = 0; PQmin = 0; @@ -129,7 +123,10 @@ public class Voronoi{ ELhash[ELhashsize - 1] = ELrightend; bottomsite = next(); - newsite = next(); + Site newsite = next(); + Halfedge lbnd; + Vec2 newintstar = null; + Edge e; while(true){ if(PQcount != 0){ Vec2 answer = new Vec2(); @@ -142,24 +139,14 @@ public class Voronoi{ newintstar = (answer); } - // if the lowest site has a smaller y value than the lowest vector - // intersection, - // process the site otherwise process the vector intersection + Halfedge rbnd; + Halfedge bisector; + Site p; + Site bot; - if(newsite != null - && (PQcount == 0 || newsite.coord.y < newintstar.y || (newsite.coord.y == newintstar.y && newsite.coord.x < newintstar.x))){ - /* new site is smallest -this is a site event */ - // get the first HalfEdge to the LEFT of the new site - int i, bucket; - Halfedge he; + if(newsite != null && (PQcount == 0 || newsite.coord.y < newintstar.y || (newsite.coord.y == newintstar.y && newsite.coord.x < newintstar.x))){ + int bucket = (int)(((newsite.coord).x - xmin) / deltax * ELhashsize); - /* Use hash table to get close to desired halfedge */ - // use the hash function to find the place in the hash map that this - // HalfEdge should be - bucket = (int)(((newsite.coord).x - xmin) / deltax * ELhashsize); - - // make sure that the bucket position in within the range of the hash - // array if(bucket < 0){ bucket = 0; } @@ -167,12 +154,9 @@ public class Voronoi{ bucket = ELhashsize - 1; } - he = getHash(bucket); - if(he == null) - // if the HE isn't found, search backwards and forwards in the hash map - // for the first non-null entry - { - for(i = 1; i < ELhashsize; i += 1){ + Halfedge he = getHash(bucket); + if(he == null){ + for(int i = 1; i < ELhashsize; i += 1){ if((he = getHash(bucket - i)) != null){ break; } @@ -181,137 +165,81 @@ public class Voronoi{ } } } - /* Now search linear list of halfedges for the correct one */ if(he == ELleftend || (he != ELrightend && right(he, (newsite.coord)))){ - // keep going right on the list until either the end is reached, or - // you find the 1st edge which the point isn't to the right of do{ he = he.ELright; }while(he != ELrightend && right(he, (newsite.coord))); he = he.ELleft; - }else - // if the point is to the left of the HalfEdge, then search left for - // the HE just to the left of the point - { + }else{ do{ he = he.ELleft; }while(he != ELleftend && !right(he, (newsite.coord))); } - /* Update hash table and reference counts */ if(bucket > 0 && bucket < ELhashsize - 1){ ELhash[bucket] = he; } - lbnd = (he); - // get the first HalfEdge to the RIGHT of the new site - rbnd = (lbnd.ELright); - // if this halfedge has no edge,bot =bottom site (whatever that - // is) + lbnd = he; + rbnd = lbnd.ELright; + bot = rightreg(lbnd); - // create a new edge that bisects e = bisect(bot, newsite); - // create a new HalfEdge, setting its ELpm field to 0 bisector = newHe(e, LE); - // insert this new bisector edge between the left and right - // vectors in a linked list insert(lbnd, bisector); - // if the new bisector intersects with the left edge, - // remove the left edge's vertex, and put in the new one if((p = intersect(lbnd, bisector)) != null){ pqdelete(lbnd); pqinsert(lbnd, p, p.coord.dst(newsite.coord)); } lbnd = bisector; - // create a new HalfEdge, setting its ELpm field to 1 bisector = newHe(e, RE); - // insert the new HE to the right of the original bisector - // earlier in the IF stmt insert(lbnd, bisector); - // if this new bisector intersects with the new HalfEdge if((p = intersect(bisector, rbnd)) != null){ - // push the HE into the ordered linked list of vertices pqinsert(bisector, p, p.coord.dst(newsite.coord)); } newsite = next(); - }else if(!(PQcount == 0)) - /* intersection is smallest - this is a vector event */{ - // pop the HalfEdge with the lowest vector off the ordered list - // of vectors + }else if(!(PQcount == 0)){ Halfedge curr; curr = PQhash[PQmin].PQnext; PQhash[PQmin].PQnext = curr.PQnext; PQcount -= 1; lbnd = (curr); - // get the HalfEdge to the left of the above HE - llbnd = (lbnd.ELleft); - // get the HalfEdge to the right of the above HE - rbnd = (lbnd.ELright); - // get the HalfEdge to the right of the HE to the right of the - // lowest HE - rrbnd = (rbnd.ELright); - // get the Site to the left of the left HE which it bisects + Halfedge llbnd = lbnd.ELleft; + rbnd = lbnd.ELright; + Halfedge rrbnd = (rbnd.ELright); bot = leftReg(lbnd); - // get the Site to the right of the right HE which it bisects - top = rightreg(rbnd); + Site top = rightreg(rbnd); - v = lbnd.vertex; // get the vertex that caused this event - // set the vertex number - couldn't do this + Site v = lbnd.vertex; v.sitenbr = nvertices; nvertices += 1; - // earlier since we didn't know when it would be processed endpoint(lbnd.ELedge, lbnd.ELpm, v); - // set the endpoint of - // the left HalfEdge to be this vector endpoint(rbnd.ELedge, rbnd.ELpm, v); - // set the endpoint of the right HalfEdge to - // be this vector - delete(lbnd); // mark the lowest HE for - // deletion - can't delete yet because there might be pointers - // to it in Hash Map + delete(lbnd); pqdelete(rbnd); - // remove all vertex events to do with the right HE - delete(rbnd); // mark the right HE for - // deletion - can't delete yet because there might be pointers - // to it in Hash Map - pm = LE; // set the pm variable to zero + delete(rbnd); + int pm = LE; - if(bot.coord.y > top.coord.y) - // if the site to the left of the event is higher than the - // Site - { // to the right of it, then swap them and set the 'pm' - // variable to 1 - temp1 = bot; + if(bot.coord.y > top.coord.y){ + Site temp1 = bot; bot = top; top = temp1; pm = RE; } - e = bisect(bot, top); // create an Edge (or line) - // that is between the two Sites. This creates the formula of - // the line, and assigns a line number to it - bisector = newHe(e, pm); // create a HE from the Edge 'e', - // and make it point to that edge - // with its ELedge field - insert(llbnd, bisector); // insert the new bisector to the - // right of the left HE - endpoint(e, RE - pm, v); // set one endpoint to the new edge - // to be the vector point 'v'. - // If the site to the left of this bisector is higher than the - // right Site, then this endpoint - // is put in position 0; otherwise in pos 1 - // if left HE and the new bisector intersect, then delete - // the left HE, and reinsert it + e = bisect(bot, top); + bisector = newHe(e, pm); + insert(llbnd, bisector); + endpoint(e, RE - pm, v); + if((p = intersect(llbnd, bisector)) != null){ pqdelete(llbnd); pqinsert(llbnd, p, p.coord.dst(bot.coord)); } - // if right HE and the new bisector intersect, then - // reinsert it if((p = intersect(bisector, rrbnd)) != null){ pqinsert(bisector, p, p.coord.dst(bot.coord)); } @@ -333,10 +261,7 @@ public class Voronoi{ } private Edge bisect(Site s1, Site s2){ - float dx, dy, adx, ady; - Edge newedge; - - newedge = new Edge(); + Edge newedge = new Edge(); // store the sites that this edge is bisecting newedge.reg[0] = s1; @@ -347,11 +272,11 @@ public class Voronoi{ newedge.ep[1] = null; // get the difference in x dist between the sites - dx = s2.coord.x - s1.coord.x; - dy = s2.coord.y - s1.coord.y; + float dx = s2.coord.x - s1.coord.x; + float dy = s2.coord.y - s1.coord.y; // make sure that the difference in positive - adx = dx > 0 ? dx : -dx; - ady = dy > 0 ? dy : -dy; + float adx = dx > 0 ? dx : -dx; + float ady = dy > 0 ? dy : -dy; newedge.c = s1.coord.x * dx + s1.coord.y * dy + (dx * dx + dy * dy) * 0.5f;// get the slope of the line if(adx > ady){ @@ -367,7 +292,7 @@ public class Voronoi{ newedge.edgenbr = nedges; nedges += 1; - return (newedge); + return newedge; } private int pqbucket(Halfedge he){ @@ -383,7 +308,7 @@ public class Voronoi{ if(bucket < PQmin){ PQmin = bucket; } - return (bucket); + return bucket; } // push the HalfEdge into the ordered linked list of vertices @@ -424,20 +349,20 @@ public class Voronoi{ answer.ELpm = pm; answer.PQnext = null; answer.vertex = null; - return (answer); + return answer; } private Site leftReg(Halfedge he){ if(he.ELedge == null){ - return (bottomsite); + return bottomsite; } - return (he.ELpm == LE ? he.ELedge.reg[LE] : he.ELedge.reg[RE]); + return he.ELpm == LE ? he.ELedge.reg[LE] : he.ELedge.reg[RE]; } private void insert(Halfedge lb, Halfedge newHe){ newHe.ELleft = lb; newHe.ELright = lb.ELright; - (lb.ELright).ELleft = newHe; + lb.ELright.ELleft = newHe; lb.ELright = newHe; } @@ -446,8 +371,8 @@ public class Voronoi{ * may be present. */ private void delete(Halfedge he){ - (he.ELleft).ELright = he.ELright; - (he.ELright).ELleft = he.ELleft; + he.ELleft.ELright = he.ELright; + he.ELright.ELleft = he.ELleft; he.deleted = true; } @@ -591,27 +516,22 @@ public class Voronoi{ } private boolean right(Halfedge el, Vec2 p){ - Edge e; - Site topsite; - boolean right_of_site; - boolean above, fast; - float dxp, dyp, dxs, t1, t2, t3, yl; - - e = el.ELedge; - topsite = e.reg[1]; - right_of_site = p.x > topsite.coord.x; - if(right_of_site && el.ELpm == LE){ - return (true); + Edge e = el.ELedge; + Site topsite = e.reg[1]; + boolean rightOf = p.x > topsite.coord.x; + if(rightOf && el.ELpm == LE){ + return true; } - if(!right_of_site && el.ELpm == RE){ - return (false); + if(!rightOf && el.ELpm == RE){ + return false; } + boolean above; if(e.a == 1.0){ - dyp = p.y - topsite.coord.y; - dxp = p.x - topsite.coord.x; - fast = false; - if((!right_of_site & (e.b < 0.0)) | (right_of_site & (e.b >= 0.0))){ + float dyp = p.y - topsite.coord.y; + float dxp = p.x - topsite.coord.x; + boolean fast = false; + if((!rightOf & (e.b < 0.0)) | (rightOf & (e.b >= 0.0))){ above = dyp >= e.b * dxp; fast = above; }else{ @@ -624,18 +544,18 @@ public class Voronoi{ } } if(!fast){ - dxs = topsite.coord.x - (e.reg[0]).coord.x; + float dxs = topsite.coord.x - (e.reg[0]).coord.x; above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1.0 + 2.0 * dxp / dxs + e.b * e.b); if(e.b < 0.0){ above = !above; } } - }else /* e.b==1.0 */{ - yl = e.c - e.a * p.x; - t1 = p.y - yl; - t2 = p.x - topsite.coord.x; - t3 = yl - topsite.coord.y; + }else{ + float yl = e.c - e.a * p.x; + float t1 = p.y - yl; + float t2 = p.x - topsite.coord.x; + float t3 = yl - topsite.coord.y; above = t1 * t1 > t2 * t2 + t3 * t3; } return ((el.ELpm == LE) == above); @@ -644,13 +564,9 @@ public class Voronoi{ private Site rightreg(Halfedge he){ if(he.ELedge == null) return bottomsite; - // if the ELpm field is zero, return the site 0 that this edge bisects, - // otherwise return site number 1 return (he.ELpm == LE ? he.ELedge.reg[RE] : he.ELedge.reg[LE]); } - // create a new site where the HalfEdges el1 and el2 intersect - note that - // the Vec2 in the argument list is not used, don't know why it's there private Site intersect(Halfedge el1, Halfedge el2){ Edge e1, e2, e; Halfedge el; @@ -664,7 +580,6 @@ public class Voronoi{ return null; } - // if the two edges bisect the same parent, return null if(e1.reg[1] == e2.reg[1]){ return null; } @@ -692,15 +607,12 @@ public class Voronoi{ return null; } - // create a new site at the point of intersection - this is a new vector - // event waiting to happen v = new Site(); v.coord.x = xint; v.coord.y = yint; return (v); } - // used both for sites and for vertices static class Site{ Vec2 coord = new Vec2(); int sitenbr; @@ -724,8 +636,8 @@ public class Voronoi{ static class Edge{ float a = 0, b = 0, c = 0; - Site[] ep = new Site[2]; // JH: End points? - Site[] reg = new Site[2]; // JH: Sites this edge bisects? + Site[] ep = new Site[2]; + Site[] reg = new Site[2]; int edgenbr; } } diff --git a/core/src/mindustry/world/blocks/power/ImpactReactor.java b/core/src/mindustry/world/blocks/power/ImpactReactor.java index d692699100..64e94d8c14 100644 --- a/core/src/mindustry/world/blocks/power/ImpactReactor.java +++ b/core/src/mindustry/world/blocks/power/ImpactReactor.java @@ -26,6 +26,7 @@ public class ImpactReactor extends PowerGenerator{ public float itemDuration = 60f; public int explosionRadius = 23; public int explosionDamage = 1900; + public Effect explodeEffect = Fx.impactReactorExplosion; public Color plasma1 = Color.valueOf("ffd06b"), plasma2 = Color.valueOf("ff361b"); @@ -137,32 +138,14 @@ public class ImpactReactor extends PowerGenerator{ public void onDestroyed(){ super.onDestroyed(); - if(warmup < 0.4f || !state.rules.reactorExplosions) return; + if(warmup < 0.3f || !state.rules.reactorExplosions) return; Sounds.explosionbig.at(tile); - Effect.shake(6f, 16f, x, y); - Fx.impactShockwave.at(x, y); - for(int i = 0; i < 6; i++){ - Time.run(Mathf.random(80), () -> Fx.impactcloud.at(x, y)); - } - Damage.damage(x, y, explosionRadius * tilesize, explosionDamage * 4); - - for(int i = 0; i < 20; i++){ - Time.run(Mathf.random(80), () -> { - Tmp.v1.rnd(Mathf.random(40f)); - Fx.explosion.at(Tmp.v1.x + x, Tmp.v1.y + y); - }); - } - - for(int i = 0; i < 70; i++){ - Time.run(Mathf.random(90), () -> { - Tmp.v1.rnd(Mathf.random(120f)); - Fx.impactsmoke.at(Tmp.v1.x + x, Tmp.v1.y + y); - }); - } + Effect.shake(6f, 16f, x, y); + explodeEffect.at(x, y); } @Override