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;