Implemented gamemode validation
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user