Implemented gamemode validation

This commit is contained in:
Anuken
2019-07-24 23:05:16 -04:00
parent c9bd253960
commit bf7803d554
8 changed files with 80 additions and 113 deletions

View File

@@ -530,13 +530,13 @@ keybind.drop_unit.name = Drop Unit
keybind.zoom_minimap.name = Zoom minimap keybind.zoom_minimap.name = Zoom minimap
mode.help.title = Description of modes mode.help.title = Description of modes
mode.survival.name = Survival mode.survival.name = Survival
mode.survival.description = The normal mode. Limited resources and automatic incoming waves. mode.survival.description = The normal mode. Limited resources and automatic incoming waves.\n[gray]Requires enemy spawns in the map to play.
mode.sandbox.name = Sandbox mode.sandbox.name = Sandbox
mode.sandbox.description = Infinite resources and no timer for waves. mode.sandbox.description = Infinite resources and no timer for waves.
mode.pvp.name = PvP mode.pvp.name = PvP
mode.pvp.description = Fight against other players locally. Requires at least 2 differently-colored cores in the map to play. mode.pvp.description = Fight against other players locally.\n[gray]Requires at least 2 differently-colored cores in the map to play.
mode.attack.name = Attack mode.attack.name = Attack
mode.attack.description = Destroy the enemy's base. No waves. Requires a red core in the map to play. mode.attack.description = Destroy the enemy's base. No waves.\n[gray]Requires a red core in the map to play.
mode.custom = Custom Rules mode.custom = Custom Rules
rules.infiniteresources = Infinite Resources rules.infiniteresources = Infinite Resources

View File

@@ -156,7 +156,7 @@
down: button-down, down: button-down,
up: button, up: button,
over: button-over, over: button-over,
disabled: button, disabled: button-disabled,
disabledFontColor: gray disabledFontColor: gray
} }
}, },

View File

@@ -1,15 +1,18 @@
package io.anuke.mindustry.game; package io.anuke.mindustry.game;
import io.anuke.arc.Core; import io.anuke.arc.*;
import io.anuke.arc.function.Consumer; import io.anuke.arc.function.*;
import io.anuke.mindustry.maps.*;
/** Defines preset rule sets.. */ import static io.anuke.mindustry.Vars.waveTeam;
/** Defines preset rule sets. */
public enum Gamemode{ public enum Gamemode{
survival(rules -> { survival(rules -> {
rules.waveTimer = true; rules.waveTimer = true;
rules.waves = true; rules.waves = true;
rules.unitDrops = true; rules.unitDrops = true;
}), }, map -> map.spawns > 0),
sandbox(rules -> { sandbox(rules -> {
rules.infiniteResources = true; rules.infiniteResources = true;
rules.waves = true; rules.waves = true;
@@ -21,7 +24,7 @@ public enum Gamemode{
rules.unitDrops = true; rules.unitDrops = true;
rules.waves = false; rules.waves = false;
rules.attackMode = true; rules.attackMode = true;
}), }, map -> map.teams.contains(waveTeam.ordinal())),
pvp(rules -> { pvp(rules -> {
rules.pvp = true; rules.pvp = true;
rules.enemyCoreBuildRadius = 600f; rules.enemyCoreBuildRadius = 600f;
@@ -33,7 +36,7 @@ public enum Gamemode{
rules.unitBuildSpeedMultiplier = 3f; rules.unitBuildSpeedMultiplier = 3f;
rules.unitHealthMultiplier = 3f; rules.unitHealthMultiplier = 3f;
rules.attackMode = true; rules.attackMode = true;
}), }, map -> map.teams.size > 1),
editor(true, rules -> { editor(true, rules -> {
rules.infiniteResources = true; rules.infiniteResources = true;
rules.editor = true; rules.editor = true;
@@ -44,15 +47,27 @@ public enum Gamemode{
}); });
private final Consumer<Rules> rules; private final Consumer<Rules> rules;
private final Predicate<Map> validator;
public final boolean hidden; public final boolean hidden;
public final static Gamemode[] all = values();
Gamemode(Consumer<Rules> rules){ Gamemode(Consumer<Rules> rules){
this(false, rules); this(false, rules);
} }
Gamemode(boolean hidden, Consumer<Rules> rules){ Gamemode(boolean hidden, Consumer<Rules> rules){
this(hidden, rules, m -> true);
}
Gamemode(Consumer<Rules> rules, Predicate<Map> validator){
this(false, rules, validator);
}
Gamemode(boolean hidden, Consumer<Rules> rules, Predicate<Map> validator){
this.rules = rules; this.rules = rules;
this.hidden = hidden; this.hidden = hidden;
this.validator = validator;
} }
/** Applies this preset to this ruleset. */ /** Applies this preset to this ruleset. */
@@ -61,6 +76,11 @@ public enum Gamemode{
return in; return in;
} }
/** @return whether this mode can be played on the specified map. */
public boolean valid(Map map){
return validator.test(map);
}
public String description(){ public String description(){
return Core.bundle.get("mode." + name() + ".description"); return Core.bundle.get("mode." + name() + ".description");
} }

View File

@@ -1,21 +1,18 @@
package io.anuke.mindustry.io; package io.anuke.mindustry.io;
import io.anuke.arc.collection.IntSet; import io.anuke.arc.collection.*;
import io.anuke.arc.collection.StringMap; import io.anuke.arc.files.*;
import io.anuke.arc.files.FileHandle; import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.Color; import io.anuke.arc.graphics.Pixmap.*;
import io.anuke.arc.graphics.Pixmap; import io.anuke.arc.util.io.*;
import io.anuke.arc.graphics.Pixmap.Format; import io.anuke.mindustry.content.*;
import io.anuke.arc.util.io.CounterInputStream; import io.anuke.mindustry.game.*;
import io.anuke.mindustry.content.Blocks; import io.anuke.mindustry.maps.*;
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.*;
import io.anuke.mindustry.world.blocks.storage.CoreBlock; import io.anuke.mindustry.world.blocks.storage.*;
import java.io.*; import java.io.*;
import java.util.zip.InflaterInputStream; import java.util.zip.*;
import static io.anuke.mindustry.Vars.*; import static io.anuke.mindustry.Vars.*;
@@ -65,9 +62,8 @@ public class MapIO{
} }
public static Pixmap generatePreview(Map map) throws IOException{ public static Pixmap generatePreview(Map map) throws IOException{
//by default, it does not have an enemy core or any other cores map.spawns = 0;
map.tags.put("enemycore", "false"); map.teams.clear();
map.tags.put("othercore", "false");
try(InputStream is = new InflaterInputStream(map.file.read(bufferSize)); CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){ try(InputStream is = new InflaterInputStream(map.file.read(bufferSize)); CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){
SaveIO.readHeader(stream); SaveIO.readHeader(stream);
@@ -79,7 +75,6 @@ public class MapIO{
Pixmap walls = new Pixmap(map.width, map.height, Format.RGBA8888); Pixmap walls = new Pixmap(map.width, map.height, Format.RGBA8888);
int black = Color.rgba8888(Color.BLACK); int black = Color.rgba8888(Color.BLACK);
int shade = Color.rgba8888(0f, 0f, 0f, 0.5f); int shade = Color.rgba8888(0f, 0f, 0f, 0.5f);
IntSet teams = new IntSet();
CachedTile tile = new CachedTile(){ CachedTile tile = new CachedTile(){
@Override @Override
public void setBlock(Block type){ public void setBlock(Block type){
@@ -95,7 +90,7 @@ public class MapIO{
public void setTeam(Team team){ public void setTeam(Team team){
super.setTeam(team); super.setTeam(team);
if(block instanceof CoreBlock){ if(block instanceof CoreBlock){
teams.add(team.ordinal()); map.teams.add(team.ordinal());
} }
} }
}; };
@@ -121,20 +116,13 @@ public class MapIO{
}else{ }else{
floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(content.block(floorID), Blocks.air, Blocks.air, Team.none)); floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(content.block(floorID), Blocks.air, Blocks.air, Team.none));
} }
if(content.block(overlayID) == Blocks.spawn){
map.spawns ++;
}
return tile; return tile;
} }
})); }));
if(teams.size > 1){
//map must have other team's cores
map.tags.put("othercore", "true");
}
if(teams.contains(waveTeam.ordinal())){
//map must have default enemy team's core
map.tags.put("enemycore", "true");
}
floors.drawPixmap(walls, 0, 0); floors.drawPixmap(walls, 0, 0);
walls.dispose(); walls.dispose();
return floors; return floors;

View File

@@ -1,11 +1,11 @@
package io.anuke.mindustry.maps; package io.anuke.mindustry.maps;
import io.anuke.arc.Core; import io.anuke.arc.Core;
import io.anuke.arc.collection.StringMap; import io.anuke.arc.collection.*;
import io.anuke.arc.files.FileHandle; import io.anuke.arc.files.FileHandle;
import io.anuke.arc.graphics.Texture; import io.anuke.arc.graphics.Texture;
import io.anuke.mindustry.Vars; import io.anuke.mindustry.Vars;
import io.anuke.mindustry.game.Rules; import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.JsonIO; import io.anuke.mindustry.io.JsonIO;
public class Map implements Comparable<Map>{ public class Map implements Comparable<Map>{
@@ -23,6 +23,10 @@ public class Map implements Comparable<Map>{
public Texture texture; public Texture texture;
/** Build that this map was created in. -1 = unknown or custom build. */ /** Build that this map was created in. -1 = unknown or custom build. */
public int build; public int build;
/** All teams present on this map.*/
public IntSet teams = new IntSet();
/** Number of enemy spawns on this map.*/
public int spawns = 0;
public Map(FileHandle file, int width, int height, StringMap tags, boolean custom, int version, int build){ public Map(FileHandle file, int width, int height, StringMap tags, boolean custom, int version, int build){
this.custom = custom; this.custom = custom;
@@ -63,20 +67,16 @@ public class Map implements Comparable<Map>{
} }
/** Whether this map has a core of the enemy 'wave' team. Default: true. /** Whether this map has a core of the enemy 'wave' team. Default: true.
* Used for checking Attack mode validity.*/ * Used for checking Attack mode validity.
public boolean hasEnemyCore(){ public boolean hasEnemyCore(){
return tags.get("enemycore", "true").equals("true"); return tags.get("enemycore", "true").equals("true");
} }
/** Whether this map has a core of any team except the default player team. Default: true. /** Whether this map has a core of any team except the default player team. Default: true.
* Used for checking PvP mode validity.*/ * Used for checking PvP mode validity.
public boolean hasOtherCores(){ public boolean hasOtherCores(){
return tags.get("othercore", "true").equals("true"); return tags.get("othercore", "true").equals("true");
} }*/
public boolean attribute(MapAttribute attr){
return tags.getBool(attr.name());
}
public String author(){ public String author(){
return tag("author"); return tag("author");

View File

@@ -1,29 +0,0 @@
package io.anuke.mindustry.maps;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import static io.anuke.mindustry.Vars.*;
/** Defines a specific type of attribute for a map, usually whether or not it supports a certain type of mode.*/
public enum MapAttribute{
/** Whether a map has a player spawnpoint in it.*/
spawnpoint(teams -> teams.contains(defaultTeam.ordinal())),
/** Whether a map has a wave team core to attack.*/
attack(teams -> teams.contains(waveTeam.ordinal())),
/** Whether this map supports PvP.*/
pvp(teams -> teams.size > 1);
private final Predicate<IntSet> validator;
public static final MapAttribute[] all = values();
MapAttribute(Predicate<IntSet> set){
this.validator = set;
}
//todo also take into account enemy spawnpoints
public boolean validate(IntSet teams){
return validator.test(teams);
}
}

View File

@@ -1,26 +1,25 @@
package io.anuke.mindustry.maps; package io.anuke.mindustry.maps;
import io.anuke.arc.Core; import io.anuke.arc.*;
import io.anuke.arc.collection.*; import io.anuke.arc.collection.*;
import io.anuke.arc.files.FileHandle; import io.anuke.arc.files.*;
import io.anuke.arc.function.ExceptionRunnable; import io.anuke.arc.function.*;
import io.anuke.arc.graphics.Texture; import io.anuke.arc.graphics.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.serialization.Json; import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.game.SpawnGroup; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.io.LegacyMapIO; import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.MapIO; import io.anuke.mindustry.io.*;
import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.storage.CoreBlock; import io.anuke.mindustry.world.blocks.storage.*;
import java.io.IOException; import java.io.*;
import java.io.StringWriter;
import static io.anuke.mindustry.Vars.*; import static io.anuke.mindustry.Vars.*;
public class Maps implements Disposable{ public class Maps implements Disposable{
/** List of all built-in maps. Filenames only. */ /** List of all built-in maps. Filenames only. */
private static String[] defaultMapNames = {"fortress", "labyrinth", "islands", "tendrils", "caldera", "glacier", "vein"}; private static String[] defaultMapNames = {"fortress", "labyrinth", "islands", "tendrils", "caldera", "glacier", "veins"};
/** All maps stored in an ordered array. */ /** All maps stored in an ordered array. */
private Array<Map> maps = new Array<>(); private Array<Map> maps = new Array<>();
/** Serializer for meta. */ /** Serializer for meta. */
@@ -110,31 +109,24 @@ public class Maps implements Disposable{
MapIO.writeMap(file, map); MapIO.writeMap(file, map);
if(!headless){ if(!headless){
//by default, it does not have an enemy core or any other cores //reset attributes
map.tags.put("enemycore", "false"); map.teams.clear();
map.tags.put("othercore", "false"); map.spawns = 0;
IntSet teams = new IntSet();
for(int x = 0; x < map.width; x++){ for(int x = 0; x < map.width; x++){
for(int y = 0; y < map.height; y++){ for(int y = 0; y < map.height; y++){
Tile tile = world.getTiles()[x][y]; Tile tile = world.getTiles()[x][y];
if(tile.block() instanceof CoreBlock){ if(tile.block() instanceof CoreBlock){
teams.add(tile.getTeamID()); map.teams.add(tile.getTeamID());
}
if(tile.overlay() == Blocks.spawn){
map.spawns ++;
} }
} }
} }
if(teams.size > 1){
//map must have other team's cores
map.tags.put("othercore", "true");
}
if(teams.contains(waveTeam.ordinal())){
//map must have default enemy team's core
map.tags.put("enemycore", "true");
}
map.texture = new Texture(MapIO.generatePreview(world.getTiles())); map.texture = new Texture(MapIO.generatePreview(world.getTiles()));
} }
maps.add(map); maps.add(map);

View File

@@ -3,7 +3,7 @@ package io.anuke.mindustry.ui.dialogs;
import io.anuke.arc.Core; import io.anuke.arc.Core;
import io.anuke.arc.scene.ui.ScrollPane; import io.anuke.arc.scene.ui.ScrollPane;
import io.anuke.arc.scene.ui.layout.Table; import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.arc.util.Scaling; import io.anuke.arc.util.*;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;
import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.maps.Map;
import io.anuke.mindustry.ui.BorderImage; import io.anuke.mindustry.ui.BorderImage;
@@ -32,9 +32,9 @@ public class MapPlayDialog extends FloatingDialog{
title.setText(map.name()); title.setText(map.name());
cont.clearChildren(); cont.clearChildren();
//reset to a valid mode after switching to attack //reset to any valid mode after switching to attack (one must exist)
if((selectedGamemode == Gamemode.attack && !map.hasEnemyCore()) || (selectedGamemode == Gamemode.pvp && !map.hasOtherCores())){ if(!selectedGamemode.valid(map)){
selectedGamemode = Gamemode.survival; selectedGamemode = Structs.find(Gamemode.all, m -> m.valid(map));
} }
rules = map.rules(); rules = map.rules();
@@ -50,14 +50,10 @@ public class MapPlayDialog extends FloatingDialog{
for(Gamemode mode : Gamemode.values()){ for(Gamemode mode : Gamemode.values()){
if(mode.hidden) continue; if(mode.hidden) continue;
if((mode == Gamemode.attack && !map.hasEnemyCore()) || (mode == Gamemode.pvp && !map.hasOtherCores())){
continue;
}
modes.addButton(mode.toString(), "toggle", () -> { modes.addButton(mode.toString(), "toggle", () -> {
selectedGamemode = mode; selectedGamemode = mode;
rules = mode.apply(map.rules()); rules = mode.apply(map.rules());
}).update(b -> b.setChecked(selectedGamemode == mode)).size(140f, 54f); }).update(b -> b.setChecked(selectedGamemode == mode)).size(140f, 54f).disabled(!mode.valid(map));
if(i++ % 2 == 1) modes.row(); if(i++ % 2 == 1) modes.row();
} }
selmode.add(modes); selmode.add(modes);