diff --git a/core/assets-raw/sprites/blocks/turrets/salvo.png b/core/assets-raw/sprites/blocks/turrets/salvo.png index 6da02f7105..2af563f821 100644 Binary files a/core/assets-raw/sprites/blocks/turrets/salvo.png and b/core/assets-raw/sprites/blocks/turrets/salvo.png differ diff --git a/core/assets/maps/saltFlats.msav b/core/assets/maps/saltFlats.msav new file mode 100644 index 0000000000..8d56cbab72 Binary files /dev/null and b/core/assets/maps/saltFlats.msav differ diff --git a/core/assets/sprites/block_colors.png b/core/assets/sprites/block_colors.png index a8966c2d13..3e14b7eafc 100644 Binary files a/core/assets/sprites/block_colors.png and b/core/assets/sprites/block_colors.png differ diff --git a/core/assets/sprites/sprites.png b/core/assets/sprites/sprites.png index 54ebe91902..1d7928652f 100644 Binary files a/core/assets/sprites/sprites.png and b/core/assets/sprites/sprites.png differ diff --git a/core/assets/sprites/sprites2.png b/core/assets/sprites/sprites2.png index ac2e615ae8..62bd1775cc 100644 Binary files a/core/assets/sprites/sprites2.png and b/core/assets/sprites/sprites2.png differ diff --git a/core/assets/sprites/sprites_fallback2.png b/core/assets/sprites/sprites_fallback2.png index ddca19eac1..220994fc71 100644 Binary files a/core/assets/sprites/sprites_fallback2.png and b/core/assets/sprites/sprites_fallback2.png differ diff --git a/core/assets/sprites/sprites_fallback3.png b/core/assets/sprites/sprites_fallback3.png index 89a14bf2dc..29262b23ee 100644 Binary files a/core/assets/sprites/sprites_fallback3.png and b/core/assets/sprites/sprites_fallback3.png differ diff --git a/core/assets/sprites/sprites_fallback4.png b/core/assets/sprites/sprites_fallback4.png index b00e37bfcd..403c143919 100644 Binary files a/core/assets/sprites/sprites_fallback4.png and b/core/assets/sprites/sprites_fallback4.png differ diff --git a/core/assets/sprites/sprites_fallback5.png b/core/assets/sprites/sprites_fallback5.png index 23d0a95a14..6a7ee7bf03 100644 Binary files a/core/assets/sprites/sprites_fallback5.png and b/core/assets/sprites/sprites_fallback5.png differ diff --git a/core/src/io/anuke/mindustry/ai/WaveSpawner.java b/core/src/io/anuke/mindustry/ai/WaveSpawner.java index bba51232c8..f2848e9e0c 100644 --- a/core/src/io/anuke/mindustry/ai/WaveSpawner.java +++ b/core/src/io/anuke/mindustry/ai/WaveSpawner.java @@ -2,6 +2,7 @@ package io.anuke.mindustry.ai; import io.anuke.arc.Events; import io.anuke.arc.collection.Array; +import io.anuke.arc.function.PositionConsumer; import io.anuke.arc.math.Angles; import io.anuke.arc.math.Mathf; import io.anuke.arc.util.Time; @@ -19,6 +20,8 @@ import io.anuke.mindustry.world.Tile; import static io.anuke.mindustry.Vars.*; public class WaveSpawner{ + private static final float margin = 40f, coreMargin = tilesize * 3; //how far away from the edge flying units spawn + private Array flySpawns = new Array<>(); private Array groundSpawns = new Array<>(); private boolean spawning = false; @@ -46,28 +49,20 @@ public class WaveSpawner{ for(SpawnGroup group : state.rules.spawns){ int spawned = group.getUnitsSpawned(state.wave - 1); - float spawnX, spawnY; - float spread; - if(group.type.isFlying){ - for(FlyerSpawn spawn : flySpawns){ - float margin = 40f; //how far away from the edge flying units spawn - float trns = (world.width() + world.height()) * tilesize; - spawnX = Mathf.clamp(world.width() * tilesize / 2f + Angles.trnsx(spawn.angle, trns), -margin, world.width() * tilesize + margin); - spawnY = Mathf.clamp(world.height() * tilesize / 2f + Angles.trnsy(spawn.angle, trns), -margin, world.height() * tilesize + margin); - spread = margin / 1.5f; + float spread = margin / 1.5f; + eachFlyerSpawn((spawnX, spawnY) -> { for(int i = 0; i < spawned; i++){ BaseUnit unit = group.createUnit(waveTeam); unit.set(spawnX + Mathf.range(spread), spawnY + Mathf.range(spread)); unit.add(); } - } + }); }else{ - for(Tile spawn : groundSpawns){ - spawnX = spawn.worldx(); - spawnY = spawn.worldy(); - spread = tilesize * 2; + float spread = tilesize * 2; + + eachGroundSpawn((spawnX, spawnY, doShockwave) -> { for(int i = 0; i < spawned; i++){ Tmp.v1.rnd(spread); @@ -75,18 +70,49 @@ public class WaveSpawner{ BaseUnit unit = group.createUnit(waveTeam); unit.set(spawnX + Tmp.v1.x, spawnY + Tmp.v1.y); - Time.run(Math.min(i * 5, 60 * 2), () -> shockwave(unit)); + Time.run(Math.min(i * 5, 60 * 2), () -> spawnEffect(unit)); } - Time.run(20f, () -> Effects.effect(Fx.spawnShockwave, spawn.x * tilesize, spawn.y * tilesize, state.rules.dropZoneRadius)); - //would be interesting to see player structures survive this without hacks - Time.run(40f, () -> Damage.damage(waveTeam, spawn.x * tilesize, spawn.y * tilesize, state.rules.dropZoneRadius, 99999999f, true)); - } + + if(doShockwave){ + Time.run(20f, () -> Effects.effect(Fx.spawnShockwave, spawnX, spawnY, state.rules.dropZoneRadius)); + Time.run(40f, () -> Damage.damage(waveTeam, spawnX, spawnY, state.rules.dropZoneRadius, 99999999f, true)); + } + }); } } Time.runTask(121f, () -> spawning = false); } + private void eachGroundSpawn(SpawnConsumer cons){ + for(Tile spawn : groundSpawns){ + cons.accept(spawn.worldx(), spawn.worldy(), true); + } + + if(state.rules.attackMode && state.teams.isActive(waveTeam) && !state.teams.get(defaultTeam).cores.isEmpty()){ + Tile firstCore = state.teams.get(defaultTeam).cores.first(); + for(Tile core : state.teams.get(waveTeam).cores){ + Tmp.v1.set(firstCore).sub(core.worldx(), core.worldy()).limit(coreMargin + core.block().size*tilesize); + cons.accept(core.worldx() + Tmp.v1.x, core.worldy() + Tmp.v1.y, false); + } + } + } + + private void eachFlyerSpawn(PositionConsumer cons){ + for(FlyerSpawn spawn : flySpawns){ + float trns = (world.width() + world.height()) * tilesize; + float spawnX = Mathf.clamp(world.width() * tilesize / 2f + Angles.trnsx(spawn.angle, trns), -margin, world.width() * tilesize + margin); + float spawnY = Mathf.clamp(world.height() * tilesize / 2f + Angles.trnsy(spawn.angle, trns), -margin, world.height() * tilesize + margin); + cons.accept(spawnX, spawnY); + } + + if(state.rules.attackMode && state.teams.isActive(waveTeam)){ + for(Tile core : state.teams.get(waveTeam).cores){ + cons.accept(core.worldx(), core.worldy()); + } + } + } + public boolean isSpawning(){ return spawning && !Net.client(); } @@ -114,7 +140,7 @@ public class WaveSpawner{ flySpawns.add(fspawn); } - private void shockwave(BaseUnit unit){ + private void spawnEffect(BaseUnit unit){ Effects.effect(Fx.unitSpawn, unit.x, unit.y, 0f, unit); Time.run(30f, () -> { unit.add(); @@ -122,11 +148,11 @@ public class WaveSpawner{ }); } + private interface SpawnConsumer{ + void accept(float x, float y, boolean shockwave); + } + private class FlyerSpawn{ float angle; } - - private class GroundSpawn{ - int x, y; - } } diff --git a/core/src/io/anuke/mindustry/content/Zones.java b/core/src/io/anuke/mindustry/content/Zones.java index e8e3933150..eb5f694cd9 100644 --- a/core/src/io/anuke/mindustry/content/Zones.java +++ b/core/src/io/anuke/mindustry/content/Zones.java @@ -80,20 +80,24 @@ public class Zones implements ContentList{ }}; }}; - //to be implemented as an attack map - /* - saltFlats = new Zone("saltFlats", new DesertWastesGenerator(260, 260)){{ - startingItems = ItemStack.list(Items.copper, 200); - conditionWave = 10; - zoneRequirements = ZoneRequirement.with(desertWastes, 25); - blockRequirements = new Block[]{Blocks.router}; - resources = new Item[]{Items.copper, Items.lead, Items.coal, Items.sand}; + saltFlats = new Zone("saltFlats", new MapGenerator("saltFlats")){{ + baseLaunchCost = ItemStack.with(Items.copper, -100); + startingItems = ItemStack.list(Items.copper, 100); + alwaysUnlocked = true; + conditionWave = 5; + launchPeriod = 5; + zoneRequirements = ZoneRequirement.with(desertWastes, 60); + resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal}; rules = () -> new Rules(){{ waves = true; waveTimer = true; - waveSpacing = 60 * 60 * 1.5f; + attackMode = true; + waveSpacing = 60 * 60; + buildCostMultiplier = 0.5f; + unitBuildSpeedMultiplier = 0.5f; + enemyCheat = true; }}; - }};*/ + }}; craters = new Zone("craters", new MapGenerator("craters", 1).dist(0).decor(new Decoration(Blocks.snow, Blocks.sporeCluster, 0.01))){{ startingItems = ItemStack.list(Items.copper, 200); diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java index df834fb7f7..89c76b5126 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -21,7 +21,8 @@ import io.anuke.mindustry.Vars; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.core.Platform; import io.anuke.mindustry.game.*; -import io.anuke.mindustry.io.*; +import io.anuke.mindustry.io.JsonIO; +import io.anuke.mindustry.io.MapIO; import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.ui.dialogs.FileChooser; import io.anuke.mindustry.ui.dialogs.FloatingDialog; @@ -267,7 +268,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ }else{ Map map = world.maps.all().find(m -> m.name().equals(name)); if(map != null && !map.custom){ - ui.showError("$editor.save.overwrite"); + handleSaveBuiltin(map); }else{ world.maps.saveMap(editor.getTags()); ui.showInfoFade("$editor.saved"); @@ -278,6 +279,11 @@ public class MapEditorDialog extends Dialog implements Disposable{ saved = true; } + /** Called when a built-in map save is attempted.*/ + protected void handleSaveBuiltin(Map map){ + ui.showError("$editor.save.overwrite"); + } + /** * Argument format: * 0) button name diff --git a/core/src/io/anuke/mindustry/maps/Maps.java b/core/src/io/anuke/mindustry/maps/Maps.java index 03597baa48..bfaf23ff8f 100644 --- a/core/src/io/anuke/mindustry/maps/Maps.java +++ b/core/src/io/anuke/mindustry/maps/Maps.java @@ -18,7 +18,7 @@ import static io.anuke.mindustry.Vars.*; public class Maps implements Disposable{ /** List of all built-in maps. Filenames only. */ - private static final String[] defaultMapNames = {"fortress", "labyrinth", "islands"}; + private static String[] defaultMapNames = {"fortress", "labyrinth", "islands"}; /** All maps stored in an ordered array. */ private Array maps = new Array<>(); /** Serializer for meta. */ diff --git a/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java b/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java index 643f4c8b12..9592edc538 100644 --- a/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java +++ b/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java @@ -16,6 +16,7 @@ import io.anuke.mindustry.world.blocks.*; import io.anuke.mindustry.world.blocks.storage.CoreBlock; import io.anuke.mindustry.world.blocks.storage.StorageBlock; +import static io.anuke.mindustry.Vars.defaultTeam; import static io.anuke.mindustry.Vars.world; public class MapGenerator extends Generator{ @@ -79,7 +80,7 @@ public class MapGenerator extends Generator{ for(int x = 0; x < width; x++){ for(int y = 0; y < height; y++){ - if(tiles[x][y].block() instanceof CoreBlock){ + if(tiles[x][y].block() instanceof CoreBlock && tiles[x][y].getTeam() == defaultTeam){ players.add(new Point2(x, y)); tiles[x][y].setBlock(Blocks.air); } diff --git a/core/src/io/anuke/mindustry/ui/IntFormat.java b/core/src/io/anuke/mindustry/ui/IntFormat.java index cdf1e529ad..22d50ab495 100644 --- a/core/src/io/anuke/mindustry/ui/IntFormat.java +++ b/core/src/io/anuke/mindustry/ui/IntFormat.java @@ -2,6 +2,7 @@ package io.anuke.mindustry.ui; import io.anuke.arc.Core; +import io.anuke.arc.function.Function; /** * A low-garbage way to format bundle strings. @@ -10,15 +11,21 @@ public class IntFormat{ private final StringBuilder builder = new StringBuilder(); private final String text; private int lastValue = Integer.MIN_VALUE; + private Function converter = String::valueOf; public IntFormat(String text){ this.text = text; } + public IntFormat(String text, Function converter){ + this.text = text; + this.converter = converter; + } + public CharSequence get(int value){ if(lastValue != value){ builder.setLength(0); - builder.append(Core.bundle.format(text, value)); + builder.append(Core.bundle.format(text, converter.get(value))); } lastValue = value; return builder; diff --git a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java index 080374b051..61e7216799 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java @@ -534,11 +534,27 @@ public class HudFragment extends Fragment{ } private void addWaveTable(TextButton table){ + StringBuilder ibuild = new StringBuilder(); IntFormat wavef = new IntFormat("wave"); IntFormat enemyf = new IntFormat("wave.enemy"); IntFormat enemiesf = new IntFormat("wave.enemies"); - IntFormat waitingf = new IntFormat("wave.waiting"); + IntFormat waitingf = new IntFormat("wave.waiting", i -> { + ibuild.setLength(0); + int m = i/60; + int s = i % 60; + if(m <= 0){ + ibuild.append(s); + }else{ + ibuild.append(m); + ibuild.append(":"); + if(s < 10){ + ibuild.append("0"); + } + ibuild.append(s); + } + return ibuild.toString(); + }); table.clearChildren(); table.touchable(Touchable.enabled); diff --git a/tests/src/test/java/ZoneTests.java b/tests/src/test/java/ZoneTests.java index dd6f2580b9..c46974116e 100644 --- a/tests/src/test/java/ZoneTests.java +++ b/tests/src/test/java/ZoneTests.java @@ -45,14 +45,14 @@ public class ZoneTests{ if(tile.drop() != null){ resources.add(tile.drop()); } - if(tile.block() instanceof CoreBlock){ + if(tile.block() instanceof CoreBlock && tile.getTeam() == defaultTeam){ hasSpawnPoint = true; } } } assertTrue(hasSpawnPoint, "Zone \"" + zone.name + "\" has no spawn points."); - assertTrue(world.spawner.countSpawns() > 0, "Zone \"" + zone.name + "\" has no enemy spawn points: " + world.spawner.countSpawns()); + assertTrue(world.spawner.countSpawns() > 0 || (zone.rules.get().attackMode && !state.teams.get(waveTeam).cores.isEmpty()), "Zone \"" + zone.name + "\" has no enemy spawn points: " + world.spawner.countSpawns()); for(Item item : resources){ assertTrue(Structs.contains(zone.resources, item), "Zone \"" + zone.name + "\" is missing item in resource list: \"" + item.name + "\""); diff --git a/tools/src/io/anuke/mindustry/Generators.java b/tools/src/io/anuke/mindustry/Generators.java index 423e4eacbd..443f81a883 100644 --- a/tools/src/io/anuke/mindustry/Generators.java +++ b/tools/src/io/anuke/mindustry/Generators.java @@ -70,7 +70,7 @@ public class Generators{ ImagePacker.generate("block-icons", () -> { Image colors = new Image(content.blocks().size, 1); - Color outlineColor = Color.valueOf("404049"); + Color outlineColor = Color.valueOf("4d4e58"); for(Block block : content.blocks()){ TextureRegion[] regions = block.getGeneratedIcons();