diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index e5152e403f..649d55e624 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -438,11 +438,11 @@ setting.fpscap.name = Max FPS setting.fpscap.none = None setting.fpscap.text = {0} FPS setting.swapdiagonal.name = Always Diagonal Placement -setting.difficulty.training = training -setting.difficulty.easy = easy -setting.difficulty.normal = normal -setting.difficulty.hard = hard -setting.difficulty.insane = insane +setting.difficulty.training = Training +setting.difficulty.easy = Easy +setting.difficulty.normal = Normal +setting.difficulty.hard = Hard +setting.difficulty.insane = Insane setting.difficulty.name = Difficulty: setting.screenshake.name = Screen Shake setting.effects.name = Display Effects diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java index facc12ab17..2e34d0d464 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -20,7 +20,6 @@ import io.anuke.arc.util.*; import io.anuke.mindustry.Vars; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.core.Platform; -import io.anuke.mindustry.game.Gamemode; import io.anuke.mindustry.game.Team; import io.anuke.mindustry.io.MapIO; import io.anuke.mindustry.maps.Map; @@ -226,7 +225,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ hide(); //only reset the player; logic.reset() will clear entities, which we do not want player.reset(); - state.rules = Gamemode.editor.get(); +// state.rules = Gamemode.editor.get(); world.setMap(new Map(StringMap.of( "name", "Editor Playtesting", "width", editor.width(), diff --git a/core/src/io/anuke/mindustry/game/Gamemode.java b/core/src/io/anuke/mindustry/game/Gamemode.java index 91915aa639..41a77c1949 100644 --- a/core/src/io/anuke/mindustry/game/Gamemode.java +++ b/core/src/io/anuke/mindustry/game/Gamemode.java @@ -1,63 +1,64 @@ package io.anuke.mindustry.game; import io.anuke.arc.Core; -import io.anuke.arc.function.Supplier; +import io.anuke.arc.function.Consumer; /** Defines preset rule sets.. */ public enum Gamemode{ - survival(() -> new Rules(){{ - waveTimer = true; - waves = true; - unitDrops = true; - spawns = DefaultWaves.get(); - }}), - sandbox(() -> new Rules(){{ - infiniteResources = true; - waves = true; - waveTimer = false; - respawnTime = 0f; - }}), - attack(() -> new Rules(){{ - enemyCheat = true; - unitDrops = true; - waves = false; - attackMode = true; - }}), - pvp(() -> new Rules(){{ - pvp = true; - enemyCoreBuildRadius = 600f; - respawnTime = 60 * 10; - buildCostMultiplier = 0.5f; - buildSpeedMultiplier = 2f; - playerDamageMultiplier = 0.45f; - playerHealthMultiplier = 0.8f; - unitBuildSpeedMultiplier = 3f; - unitHealthMultiplier = 2f; - attackMode = true; - }}), - editor(true, () -> new Rules(){{ - infiniteResources = true; - editor = true; - waves = false; - enemyCoreBuildRadius = 0f; - waveTimer = false; - respawnTime = 0f; - }}),; + survival(rules -> { + rules.waveTimer = true; + rules.waves = true; + rules.unitDrops = true; + }), + sandbox(rules -> { + rules.infiniteResources = true; + rules.waves = true; + rules.waveTimer = false; + rules.respawnTime = 0f; + }), + attack(rules -> { + rules.enemyCheat = true; + rules.unitDrops = true; + rules.waves = false; + rules.attackMode = true; + }), + pvp(rules -> { + rules.pvp = true; + rules.enemyCoreBuildRadius = 600f; + rules.respawnTime = 60 * 10; + rules.buildCostMultiplier = 0.5f; + rules.buildSpeedMultiplier = 2f; + rules.playerDamageMultiplier = 0.45f; + rules.playerHealthMultiplier = 0.8f; + rules.unitBuildSpeedMultiplier = 3f; + rules.unitHealthMultiplier = 2f; + rules.attackMode = true; + }), + editor(true, rules -> { + rules.infiniteResources = true; + rules.editor = true; + rules.waves = false; + rules.enemyCoreBuildRadius = 0f; + rules.waveTimer = false; + rules.respawnTime = 0f; + }); - private final Supplier rules; + private final Consumer rules; public final boolean hidden; - Gamemode(Supplier rules){ + Gamemode(Consumer rules){ this(false, rules); } - Gamemode(boolean hidden, Supplier rules){ + Gamemode(boolean hidden, Consumer rules){ this.rules = rules; this.hidden = hidden; } - public Rules get(){ - return rules.get(); + /** Applies this preset to this ruleset. */ + public Rules apply(Rules in){ + rules.accept(in); + return in; } public String description(){ diff --git a/core/src/io/anuke/mindustry/game/Rules.java b/core/src/io/anuke/mindustry/game/Rules.java index 2a12a804be..63babc963b 100644 --- a/core/src/io/anuke/mindustry/game/Rules.java +++ b/core/src/io/anuke/mindustry/game/Rules.java @@ -2,6 +2,7 @@ package io.anuke.mindustry.game; import io.anuke.annotations.Annotations.Serialize; import io.anuke.arc.collection.Array; +import io.anuke.mindustry.io.JsonIO; import io.anuke.mindustry.type.Zone; /** @@ -62,4 +63,9 @@ public class Rules{ public boolean attackMode = false; /** Whether this is the editor gamemode. */ public boolean editor = false; + + /** Copies this ruleset exactly. Not very efficient at all, do not use often. */ + public Rules copy(){ + return JsonIO.read(Rules.class, JsonIO.write(this)); + } } diff --git a/core/src/io/anuke/mindustry/io/MapIO.java b/core/src/io/anuke/mindustry/io/MapIO.java index fa419655bf..8daf1a7e2a 100644 --- a/core/src/io/anuke/mindustry/io/MapIO.java +++ b/core/src/io/anuke/mindustry/io/MapIO.java @@ -11,12 +11,12 @@ import io.anuke.mindustry.game.Team; import io.anuke.mindustry.game.Version; import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.world.*; +import io.anuke.mindustry.world.blocks.storage.CoreBlock; import java.io.*; import java.util.zip.InflaterInputStream; -import static io.anuke.mindustry.Vars.bufferSize; -import static io.anuke.mindustry.Vars.content; +import static io.anuke.mindustry.Vars.*; /** Reads and writes map files. */ //TODO does this class even need to exist??? move to Maps? @@ -64,6 +64,10 @@ public class MapIO{ } public static Pixmap generatePreview(Map map) throws IOException{ + //by default, it does not have an enemy core or any other cores + map.tags.put("enemycore", "false"); + map.tags.put("othercore", "false"); + try(InputStream is = new InflaterInputStream(map.file.read(bufferSize)); CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){ SaveIO.readHeader(stream); int version = stream.readInt(); @@ -84,6 +88,22 @@ public class MapIO{ floors.drawPixel(x, floors.getHeight() - 1 - y + 1, shade); } } + + @Override + public void setTeam(Team team){ + super.setTeam(team); + if(block instanceof CoreBlock){ + if(team != defaultTeam){ + //map must have other team's cores + map.tags.put("othercore", "true"); + } + + if(team == waveTeam){ + //map must have default enemy team's core + map.tags.put("enemycore", "true"); + } + } + } }; ver.region("content", stream, counter, ver::readContentHeader); diff --git a/core/src/io/anuke/mindustry/maps/Map.java b/core/src/io/anuke/mindustry/maps/Map.java index 920adc89ca..ea8828b3a9 100644 --- a/core/src/io/anuke/mindustry/maps/Map.java +++ b/core/src/io/anuke/mindustry/maps/Map.java @@ -5,6 +5,8 @@ import io.anuke.arc.collection.StringMap; import io.anuke.arc.files.FileHandle; import io.anuke.arc.graphics.Texture; import io.anuke.mindustry.Vars; +import io.anuke.mindustry.game.Rules; +import io.anuke.mindustry.io.JsonIO; public class Map implements Comparable{ /** Whether this is a custom map. */ @@ -53,6 +55,22 @@ public class Map implements Comparable{ Vars.data.modified(); } + public Rules rules(){ + return JsonIO.read(Rules.class, tags.get("rules", "{}")); + } + + /** Whether this map has a core of the enemy 'wave' team. Default: true. + * Used for checking Attack mode validity.*/ + public boolean hasEnemyCore(){ + return tags.get("enemycore", "true").equals("true"); + } + + /** Whether this map has a core of any team except the default player team. Default: true. + * Used for checking PvP mode validity.*/ + public boolean hasOtherCores(){ + return tags.get("othercore", "true").equals("true"); + } + public String author(){ return tag("author"); } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/CustomGameDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/CustomGameDialog.java index 76b0f97638..9fbf739020 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/CustomGameDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/CustomGameDialog.java @@ -2,28 +2,18 @@ package io.anuke.mindustry.ui.dialogs; import io.anuke.arc.Core; import io.anuke.arc.graphics.g2d.TextureRegion; -import io.anuke.arc.math.Mathf; -import io.anuke.arc.scene.event.Touchable; -import io.anuke.arc.scene.ui.ButtonGroup; import io.anuke.arc.scene.ui.ImageButton; import io.anuke.arc.scene.ui.ScrollPane; -import io.anuke.arc.scene.ui.TextButton; import io.anuke.arc.scene.ui.layout.Table; import io.anuke.arc.util.Align; import io.anuke.arc.util.Scaling; -import io.anuke.mindustry.game.Difficulty; -import io.anuke.mindustry.game.Gamemode; -import io.anuke.mindustry.game.Rules; import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.ui.BorderImage; -import static io.anuke.mindustry.Vars.*; +import static io.anuke.mindustry.Vars.world; public class CustomGameDialog extends FloatingDialog{ - Difficulty difficulty = Difficulty.normal; - CustomRulesDialog dialog = new CustomRulesDialog(); - Rules rules; - Gamemode selectedGamemode; + private MapPlayDialog dialog = new MapPlayDialog(); public CustomGameDialog(){ super("$customgame"); @@ -33,9 +23,9 @@ public class CustomGameDialog extends FloatingDialog{ } void setup(){ - selectedGamemode = Gamemode.survival; - rules = selectedGamemode.get(); - + clearChildren(); + stack(cont, buttons).grow(); + buttons.bottom(); cont.clear(); Table maps = new Table(); @@ -44,64 +34,9 @@ public class CustomGameDialog extends FloatingDialog{ pane.setFadeScrollBars(false); int maxwidth = (Core.graphics.isPortrait() ? 2 : 4); - - Table selmode = new Table(); - ButtonGroup group = new ButtonGroup<>(); - selmode.add("$level.mode").colspan(4); - selmode.row(); - int i = 0; - - Table modes = new Table(); - - for(Gamemode mode : Gamemode.values()){ - if(mode.hidden) continue; - modes.addButton(mode.toString(), "toggle", () -> { - selectedGamemode = mode; - rules = mode.get(); - dialog.selectedGamemode = null; - dialog.rules = null; - }).update(b -> b.setChecked(selectedGamemode == mode)).group(group).size(140f, 54f); - if(i++ % 2 == 1) modes.row(); - } - selmode.add(modes); - selmode.addButton("?", this::displayGameModeHelp).width(50f).fillY().padLeft(18f); - - cont.add(selmode); - cont.row(); - - Difficulty[] ds = Difficulty.values(); - - float s = 50f; - - Table sdif = new Table(); - - sdif.add("$setting.difficulty.name").colspan(3); - sdif.row(); - sdif.defaults().height(s + 4); - sdif.addImageButton("icon-arrow-left", 10 * 3, () -> { - difficulty = (ds[Mathf.mod(difficulty.ordinal() - 1, ds.length)]); - state.wavetime = difficulty.waveTime; - }).width(s); - - sdif.addButton("", () -> { - }) - .update(t -> { - t.setText(difficulty.toString()); - t.touchable(Touchable.disabled); - }).width(180f); - - sdif.addImageButton("icon-arrow-right", 10 * 3, () -> { - difficulty = (ds[Mathf.mod(difficulty.ordinal() + 1, ds.length)]); - state.wavetime = difficulty.waveTime; - }).width(s); - sdif.addButton("$customize", () -> dialog.show(rules, selectedGamemode)).width(140).padLeft(10); - - cont.add(sdif); - cont.row(); - float images = 146f; - i = 0; + int i = 0; maps.defaults().width(170).fillY().top().pad(4f); for(Map map : world.maps.all()){ @@ -122,10 +57,7 @@ public class CustomGameDialog extends FloatingDialog{ border.setScaling(Scaling.fit); image.replaceImage(border); - image.clicked(() -> { - hide(); - control.playMap(map, (dialog.rules == null) ? rules : dialog.rules); - }); + image.clicked(() -> dialog.show(map)); maps.add(image); @@ -138,23 +70,4 @@ public class CustomGameDialog extends FloatingDialog{ cont.add(pane).uniformX(); } - - private void displayGameModeHelp(){ - FloatingDialog d = new FloatingDialog(Core.bundle.get("mode.help.title")); - d.setFillParent(false); - Table table = new Table(); - table.defaults().pad(1f); - ScrollPane pane = new ScrollPane(table); - pane.setFadeScrollBars(false); - table.row(); - for(Gamemode mode : Gamemode.values()){ - if(mode.hidden) continue; - table.labelWrap("[accent]" + mode.toString() + ":[] [lightgray]" + mode.description()).width(400f); - table.row(); - } - - d.cont.add(pane); - d.buttons.addButton("$ok", d::hide).size(110, 50).pad(10f); - d.show(); - } } \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/ui/dialogs/CustomRulesDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/CustomRulesDialog.java index 0fb415686c..25670cb4bc 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/CustomRulesDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/CustomRulesDialog.java @@ -5,7 +5,6 @@ import io.anuke.arc.graphics.Color; import io.anuke.arc.scene.ui.layout.Table; import io.anuke.arc.util.Strings; import io.anuke.mindustry.core.Platform; -import io.anuke.mindustry.game.Gamemode; import io.anuke.mindustry.game.Rules; import io.anuke.mindustry.graphics.Pal; @@ -14,7 +13,6 @@ import static io.anuke.mindustry.Vars.tilesize; public class CustomRulesDialog extends FloatingDialog{ private Table main; public Rules rules; - public Gamemode selectedGamemode; public CustomRulesDialog(){ super("$mode.custom"); @@ -24,9 +22,9 @@ public class CustomRulesDialog extends FloatingDialog{ addCloseButton(); } - public void show(Rules rules, Gamemode gamemode){ + public void show(Rules rules){ this.rules = rules; - this.selectedGamemode = gamemode; + // this.selectedGamemode = gamemode; show(); } @@ -35,7 +33,7 @@ public class CustomRulesDialog extends FloatingDialog{ cont.pane(m -> main = m); main.margin(10f); main.addButton("$settings.reset", () -> { - rules = selectedGamemode.get(); + //rules = selectedGamemode.get(); setup(); }).size(300f, 50f); main.left().defaults().fillX().left().pad(5); @@ -63,7 +61,7 @@ public class CustomRulesDialog extends FloatingDialog{ number("$rules.playerhealthmultiplier", f -> rules.playerHealthMultiplier = f, () -> rules.playerHealthMultiplier); title("$rules.title.unit"); - check("$rules.unitdrops", b -> rules.unitDrops = b, () -> rules.unitDrops, ()->true); + check("$rules.unitdrops", b -> rules.unitDrops = b, () -> rules.unitDrops, () -> true); number("$rules.unitbuildspeedmultiplier", f -> rules.unitBuildSpeedMultiplier = f, () -> rules.unitBuildSpeedMultiplier); number("$rules.unithealthmultiplier", f -> rules.unitHealthMultiplier = f, () -> rules.unitHealthMultiplier); number("$rules.unitdamagemultiplier", f -> rules.unitDamageMultiplier = f, () -> rules.unitDamageMultiplier); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/MapPlayDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/MapPlayDialog.java new file mode 100644 index 0000000000..d3f6cb0dd7 --- /dev/null +++ b/core/src/io/anuke/mindustry/ui/dialogs/MapPlayDialog.java @@ -0,0 +1,117 @@ +package io.anuke.mindustry.ui.dialogs; + +import io.anuke.arc.Core; +import io.anuke.arc.graphics.Color; +import io.anuke.arc.math.Mathf; +import io.anuke.arc.scene.event.Touchable; +import io.anuke.arc.scene.ui.*; +import io.anuke.arc.scene.ui.layout.Table; +import io.anuke.arc.util.Scaling; +import io.anuke.mindustry.game.*; +import io.anuke.mindustry.maps.Map; +import io.anuke.mindustry.ui.BorderImage; + +import static io.anuke.mindustry.Vars.state; + +public class MapPlayDialog extends FloatingDialog{ + Difficulty difficulty = Difficulty.normal; + CustomRulesDialog dialog = new CustomRulesDialog(); + Rules rules; + Gamemode selectedGamemode; + + public MapPlayDialog(){ + super(""); + addCloseButton(); + } + + public void show(Map map){ + title.setText(map.name()); + cont.clearChildren(); + + selectedGamemode = Gamemode.survival; + rules = selectedGamemode.apply(new Rules()); + + Table selmode = new Table(); + ButtonGroup group = new ButtonGroup<>(); + selmode.add("$level.mode").colspan(4); + selmode.row(); + int i = 0; + + Table modes = new Table(); + + for(Gamemode mode : Gamemode.values()){ + if(mode.hidden) continue; + + if((mode == Gamemode.attack && !map.hasEnemyCore()) || (mode == Gamemode.pvp && !map.hasOtherCores())){ + continue; + } + + modes.addButton(mode.toString(), "toggle", () -> { + selectedGamemode = mode; + //rules = mode.get(); + //dialog.selectedGamemode = null; + dialog.rules = null; + }).update(b -> b.setChecked(selectedGamemode == mode)).group(group).size(140f, 54f); + if(i++ % 2 == 1) modes.row(); + } + selmode.add(modes); + selmode.addButton("?", this::displayGameModeHelp).width(50f).fillY().padLeft(18f); + + cont.add(selmode); + cont.row(); + + Difficulty[] ds = Difficulty.values(); + + float s = 50f; + + Table sdif = new Table(); + + sdif.add("$setting.difficulty.name").colspan(3); + sdif.row(); + sdif.defaults().height(s + 4); + sdif.addImageButton("icon-arrow-left", 10 * 3, () -> { + difficulty = (ds[Mathf.mod(difficulty.ordinal() - 1, ds.length)]); + state.wavetime = difficulty.waveTime; + }).width(s); + + sdif.addButton("", () -> {}).update(t -> { + t.setText(difficulty.toString()); + t.touchable(Touchable.disabled); + }).width(180f); + + sdif.addImageButton("icon-arrow-right", 10 * 3, () -> { + difficulty = (ds[Mathf.mod(difficulty.ordinal() + 1, ds.length)]); + state.wavetime = difficulty.waveTime; + }).width(s); + sdif.addButton("$customize", () -> dialog.show(rules)).width(140).padLeft(10); + + cont.add(sdif); + cont.row(); + if(map.hasTag("description")){ + cont.add(map.description()).color(Color.LIGHT_GRAY); + cont.row(); + } + cont.add(new BorderImage(map.texture, 3f)).grow().get().setScaling(Scaling.fit); + + show(); + } + + private void displayGameModeHelp(){ + FloatingDialog d = new FloatingDialog(Core.bundle.get("mode.help.title")); + d.setFillParent(false); + Table table = new Table(); + table.defaults().pad(1f); + ScrollPane pane = new ScrollPane(table); + pane.setFadeScrollBars(false); + table.row(); + for(Gamemode mode : Gamemode.values()){ + if(mode.hidden) continue; + table.labelWrap("[accent]" + mode.toString() + ":[] [lightgray]" + mode.description()).width(400f); + table.row(); + } + + d.cont.add(pane); + d.buttons.addButton("$ok", d::hide).size(110, 50).pad(10f); + d.show(); + } +}