Finish map editor core functionality

This commit is contained in:
Anuken
2017-12-19 00:05:41 -05:00
parent 7953a21073
commit a6e72c93f0
38 changed files with 712 additions and 335 deletions

View File

@@ -30,7 +30,8 @@ public class Mindustry extends ModuleCore {
DefenseBlocks.compositewall,
DistributionBlocks.conduit,
ProductionBlocks.coaldrill,
WeaponBlocks.chainturret
WeaponBlocks.chainturret,
SpecialBlocks.enemySpawn
};
@Override

View File

@@ -160,6 +160,8 @@ public class Control extends Module{
}
public void play(){
if(core == null) return;
renderer.clearTiles();
player.x = core.worldx();
@@ -435,7 +437,7 @@ public class Control extends Module{
@Override
public void update(){
if(debug){
if(debug && GameState.is(State.playing)){
//debug actions
if(Inputs.keyUp(Keys.P)){
Effects.effect(Fx.shellsmoke, player);

View File

@@ -449,10 +449,18 @@ public class UI extends SceneModule{
editorDialog.show();
}
public MapEditorDialog getEditor(){
public MapEditorDialog getEditorDialog(){
return editorDialog;
}
public MapEditor getEditor(){
return editor;
}
public void reloadLevels(){
((LevelDialog)levels).reload();
}
public boolean isEditing(){
return editorDialog.getScene() != null;
}

View File

@@ -158,6 +158,8 @@ public class World extends Module{
this.seed = seed;
Generator.generate(map.pixmap, tiles);
if(control.getCore() == null) return;
//TODO multiblock core
control.getInput().placeBlock(control.getCore().x, control.getCore().y, ProductionBlocks.core, 0, false, false);

View File

@@ -59,7 +59,7 @@ public enum EditorTool{
public void touched(MapEditor editor, int x, int y){
Block block = ColorMapper.get(editor.pixmap().getPixel(x, y)).dominant();
editor.setDrawBlock(block);
Vars.ui.getEditor().updateSelectedBlock();
Vars.ui.getEditorDialog().updateSelectedBlock();
}
},
zoom;

View File

@@ -13,8 +13,9 @@ import io.anuke.mindustry.world.blocks.Blocks;
import io.anuke.ucore.graphics.Pixmaps;
public class MapEditor{
public static final int[] validMapSizes = {64, 128, 256};
public static final int[] validMapSizes = {128, 256, 512};
public static final int[] brushSizes = {1, 2, 3, 4, 5, 9, 15};
public static final int maxSpawnpoints = 15;
private Pixmap[] brushPixmaps = new Pixmap[brushSizes.length];
@@ -36,6 +37,10 @@ public class MapEditor{
}
}
public Map getMap(){
return map;
}
public void updateTexture(){
texture.draw(pixmap, 0, 0);
}
@@ -156,10 +161,18 @@ public class MapEditor{
return pixmap;
}
public void resize(int mapSize){
Pixmap out = Pixmaps.resize(pixmap, mapSize, mapSize);
public void resize(int width, int height){
Pixmap out = Pixmaps.resize(pixmap, width, height);
pixmap.dispose();
pixmap = out;
texture.draw(pixmap, 0, 0);
texture.dispose();
texture = new Texture(out);
if(filterPixmap != null){
filterPixmap.dispose();
filterTexture.dispose();
filterPixmap = null;
filterTexture = null;
}
}
}

View File

@@ -1,12 +1,17 @@
package io.anuke.mindustry.mapeditor;
import com.badlogic.gdx.graphics.Pixmap;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.ColorMapper;
import io.anuke.mindustry.world.ColorMapper.BlockPair;
import io.anuke.mindustry.world.Map;
import io.anuke.mindustry.world.blocks.Blocks;
import io.anuke.mindustry.world.blocks.SpecialBlocks;
import io.anuke.ucore.core.Core;
import io.anuke.ucore.core.Draw;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.scene.builders.*;
import io.anuke.ucore.scene.ui.*;
import io.anuke.ucore.scene.ui.layout.Table;
@@ -16,6 +21,10 @@ public class MapEditorDialog extends Dialog{
private MapEditor editor;
private MapView view;
private MapGenerateDialog dialog;
private MapLoadDialog loadDialog;
private MapSaveDialog saveDialog;
private MapResizeDialog resizeDialog;
private ButtonGroup<ImageButton> blockgroup;
public MapEditorDialog(MapEditor editor){
@@ -24,6 +33,34 @@ public class MapEditorDialog extends Dialog{
dialog = new MapGenerateDialog(editor);
view = new MapView(editor);
loadDialog = new MapLoadDialog(map -> {
editor.beginEdit(map);
});
resizeDialog = new MapResizeDialog(editor, (x, y) -> {
Pixmap pix = editor.pixmap();
if(!(pix.getWidth() == x && pix.getHeight() == y)){
Vars.ui.showLoading();
Timers.run(10f, ()->{
editor.resize(x, y);
Vars.ui.hideLoading();
});
}
});
saveDialog = new MapSaveDialog(name -> {
Vars.ui.showLoading();
if(verifyMap()){
editor.getMap().name = name;
Timers.run(10f, () -> {
Vars.world.maps().saveAndReload(editor.getMap(), editor.pixmap());
Vars.ui.hideLoading();
});
}else{
Vars.ui.hideLoading();
}
});
setFillParent(true);
clearChildren();
@@ -48,7 +85,6 @@ public class MapEditorDialog extends Dialog{
}
i++;
}
}
public void build(){
@@ -68,13 +104,13 @@ public class MapEditorDialog extends Dialog{
row();
new imagebutton("icon-load", isize, () -> {
loadDialog.show();
}).text("load map");
row();
new imagebutton("icon-save", isize, ()->{
saveDialog.show();
}).text("save map");
row();
@@ -92,7 +128,7 @@ public class MapEditorDialog extends Dialog{
row();
new imagebutton("icon-cursor", 10f*3f, () -> {
resizeDialog.show();
}).text("resize").padTop(4f);
row();
@@ -150,6 +186,38 @@ public class MapEditorDialog extends Dialog{
}}.grow().end();
}
private boolean verifyMap(){
int psc = ColorMapper.getColor(SpecialBlocks.playerSpawn);
int esc = ColorMapper.getColor(SpecialBlocks.enemySpawn);
int playerSpawns = 0;
int enemySpawns = 0;
Pixmap pix = editor.pixmap();
for(int x = 0; x < pix.getWidth(); x ++){
for(int y = 0; y < pix.getHeight(); y ++){
int i = pix.getPixel(x, y);
if(i == psc) playerSpawns ++;
if(i == esc) enemySpawns ++;
}
}
if(playerSpawns == 0){
Vars.ui.showError("This map has no player spawnpoint!");
return false;
}else if(playerSpawns > 1){
Vars.ui.showError("Maps cannot have more than one\nplayer spawnpoint!");
return false;
}
if(enemySpawns > MapEditor.maxSpawnpoints){
Vars.ui.showError("Cannot have more than\n" + MapEditor.maxSpawnpoints + " enemy spawnpoints!");
return false;
}
return true;
}
private void addBlockSelection(Table table){
Table content = new Table();
ScrollPane pane = new ScrollPane(content, "volume");
@@ -174,6 +242,8 @@ public class MapEditorDialog extends Dialog{
}
}
group.getButtons().get(3).setChecked(true);
Table extra = new Table("button");
extra.addWrap(() -> editor.getDrawBlock().name).width(120f).center();
table.add(extra).growX();

View File

@@ -0,0 +1,63 @@
package io.anuke.mindustry.mapeditor;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.ui.BorderImage;
import io.anuke.mindustry.ui.FloatingDialog;
import io.anuke.mindustry.world.Map;
import io.anuke.ucore.function.Consumer;
import io.anuke.ucore.scene.ui.ButtonGroup;
import io.anuke.ucore.scene.ui.ScrollPane;
import io.anuke.ucore.scene.ui.TextButton;
import io.anuke.ucore.scene.ui.layout.Table;
import io.anuke.ucore.scene.ui.layout.Unit;
public class MapLoadDialog extends FloatingDialog{
Map selected = Vars.world.maps().getMap(0);
public MapLoadDialog(Consumer<Map> loader) {
super("load map");
ButtonGroup<TextButton> group = new ButtonGroup<>();
int maxcol = 3;
int i = 0;
Table table = new Table();
table.defaults().size(200f, 90f).units(Unit.dp).pad(4f);
table.pad(Unit.dp.inPixels(10f));
ScrollPane pane = new ScrollPane(table, "horizontal");
pane.setFadeScrollBars(false);
for(Map map : Vars.world.maps().list()){
if(!map.visible) continue;
TextButton button = new TextButton(map.name, "toggle");
button.add(new BorderImage(map.texture, 2f)).size(Unit.dp.inPixels(16*4f));
button.getCells().reverse();
button.clicked(() -> selected = map);
button.getLabelCell().grow().left().padLeft(5f).units(Unit.dp);
group.add(button);
table.add(button);
if(++i % maxcol == 0) table.row();
}
content().add("Select a map to load:");
content().row();
content().add(pane);
TextButton button = new TextButton("Load");
button.setDisabled(() -> selected == null);
button.clicked(() -> {
if(selected != null){
loader.accept(selected);
hide();
}
});
buttons().defaults().size(200f, 50f).units(Unit.dp);
buttons().addButton("Cancel", this::hide);
buttons().add(button);
}
}

View File

@@ -0,0 +1,67 @@
package io.anuke.mindustry.mapeditor;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.utils.Align;
import io.anuke.mindustry.ui.FloatingDialog;
import io.anuke.ucore.function.BiConsumer;
import io.anuke.ucore.scene.ui.ButtonGroup;
import io.anuke.ucore.scene.ui.TextButton;
import io.anuke.ucore.scene.ui.layout.Table;
import io.anuke.ucore.scene.ui.layout.Unit;
public class MapResizeDialog extends FloatingDialog{
int width, height;
public MapResizeDialog(MapEditor editor, BiConsumer<Integer, Integer> cons){
super("resize map");
shown(() -> {
Pixmap pix = editor.pixmap();
width = pix.getWidth();
height = pix.getHeight();
Table table = new Table();
for(int d = 0; d < 2; d ++){
boolean w = d == 0;
int curr = d == 0 ? pix.getWidth() : pix.getHeight();
int idx = 0;
for(int i = 0; i < MapEditor.validMapSizes.length; i ++)
if(MapEditor.validMapSizes[i] == curr) idx = i;
table.add(d == 0 ? "Width: ": "Height: ");
ButtonGroup<TextButton> group = new ButtonGroup<>();
for(int i = 0; i < MapEditor.validMapSizes.length; i ++){
int size = MapEditor.validMapSizes[i];
TextButton button = new TextButton(size + "", "toggle");
button.clicked(() -> {
if(w)
width = size;
else
height = size;
});
group.add(button);
if(i == idx) button.setChecked(true);
table.add(button).size(100f, 54f).pad(2f).units(Unit.dp);
}
table.row();
}
content().add(() ->
width + height > 512 ? "[scarlet]Warning!\n[]Maps larger than 256 units may be laggy and unstable." : ""
).get().setAlignment(Align.center, Align.center);
content().row();
content().add(table);
});
buttons().defaults().size(200f, 50f).units(Unit.dp);
buttons().addButton("Cancel", this::hide);
buttons().addButton("Resize", () -> {
cons.accept(width, height);
hide();
});
}
}

View File

@@ -0,0 +1,57 @@
package io.anuke.mindustry.mapeditor;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.ui.FloatingDialog;
import io.anuke.mindustry.world.Map;
import io.anuke.ucore.function.Consumer;
import io.anuke.ucore.scene.ui.TextButton;
import io.anuke.ucore.scene.ui.TextField;
import io.anuke.ucore.scene.ui.layout.Unit;
public class MapSaveDialog extends FloatingDialog{
private TextField field;
public MapSaveDialog(Consumer<String> cons){
super("Save Map");
field = new TextField();
shown(() -> {
content().clear();
content().add(() ->{
Map map = Vars.world.maps().getMap(field.getText());
if(map != null){
if(map.custom){
return "[accent]Warning!\nThis overwrites an existing map.";
}else{
return "[crimson]Cannot overwrite default map!";
}
}
return "";
}).colspan(2);
content().row();
content().add("Map Name: ");
content().add(field).size(220f, 48f);
});
buttons().defaults().size(200f, 50f).pad(2f).units(Unit.dp);
buttons().addButton("Cancel", this::hide);
TextButton button = new TextButton("Save");
button.clicked(() -> {
if(!invalid()){
cons.accept(field.getText());
hide();
}
});
button.setDisabled(this::invalid);
buttons().add(button);
}
private boolean invalid(){
if(field.getText().isEmpty()){
return true;
}
Map map = Vars.world.maps().getMap(field.getText());
return map != null && !map.custom;
}
}

View File

@@ -120,12 +120,12 @@ public class MapView extends Element implements GestureListener{
float centery = y + height/2 + offsety * zoom;
batch.flush();
ScissorStack.pushScissors(Tmp.r1.set(x + width/2 - size/2, y + height/2 - size/2, size, size));
boolean pop = ScissorStack.pushScissors(Tmp.r1.set(x + width/2 - size/2, y + height/2 - size/2, size, size));
batch.draw(editor.texture(), centerx - sclsize/2, centery - sclsize/2, sclsize, sclsize);
batch.flush();
ScissorStack.popScissors();
if(pop) ScissorStack.popScissors();
Draw.color(Colors.get("accent"));
Draw.thick(Unit.dp.inPixels(3f));
@@ -134,7 +134,7 @@ public class MapView extends Element implements GestureListener{
}
private boolean active(){
return Core.scene.getKeyboardFocus().isDescendantOf(Vars.ui.getEditor()) && Vars.ui.isEditing() && tool == EditorTool.zoom;
return Core.scene.getKeyboardFocus() != null && Core.scene.getKeyboardFocus().isDescendantOf(Vars.ui.getEditorDialog()) && Vars.ui.isEditing() && tool == EditorTool.zoom;
}
@Override

View File

@@ -1,6 +1,7 @@
package io.anuke.mindustry.ui;
import com.badlogic.gdx.graphics.Colors;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import io.anuke.ucore.core.Draw;
@@ -8,6 +9,18 @@ import io.anuke.ucore.scene.ui.Image;
import io.anuke.ucore.scene.ui.layout.Unit;
public class BorderImage extends Image{
private float thickness = 3f;
public BorderImage(){}
public BorderImage(Texture texture){
super(texture);
}
public BorderImage(Texture texture, float thick){
super(texture);
thickness = thick;
}
@Override
public void draw(Batch batch, float alpha){
@@ -17,7 +30,7 @@ public class BorderImage extends Image{
float scaleY = getScaleY();
Draw.color(Colors.get("accent"));
Draw.thick(Unit.dp.inPixels(3f));
Draw.thick(Unit.dp.inPixels(thickness));
Draw.linerect(x + imageX, y + imageY, imageWidth * scaleX, imageHeight * scaleY);
Draw.reset();
}

View File

@@ -23,11 +23,16 @@ public class LevelDialog extends FloatingDialog{
super("Level Select");
getTitleTable().getCell(title()).growX().center();
getTitleTable().center();
addCloseButton();
setup();
}
public void reload(){
content().clear();
setup();
}
void setup(){
addCloseButton();
Table maps = new Table();
ScrollPane pane = new ScrollPane(maps);
pane.setFadeScrollBars(false);

View File

@@ -7,11 +7,14 @@ import com.badlogic.gdx.utils.IntMap.Entry;
import com.badlogic.gdx.utils.ObjectIntMap;
import io.anuke.mindustry.world.blocks.Blocks;
import io.anuke.mindustry.world.blocks.SpecialBlocks;
public class ColorMapper{
private static ObjectIntMap<Block> reverseColors = new ObjectIntMap<>();
private static Array<BlockPair> pairs = new Array<>();
private static IntMap<BlockPair> colors = map(
"ff0000", pair(Blocks.dirt, SpecialBlocks.enemySpawn),
"00ff00", pair(Blocks.stone, SpecialBlocks.playerSpawn),
"323232", pair(Blocks.stone),
"646464", pair(Blocks.stone, Blocks.stoneblock),
"50965a", pair(Blocks.grass),

View File

@@ -6,16 +6,17 @@ import com.badlogic.gdx.utils.IntMap;
import com.badlogic.gdx.utils.ObjectMap;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.core.GameState;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.enemies.TargetEnemy;
import io.anuke.mindustry.world.ColorMapper.BlockPair;
import io.anuke.mindustry.world.blocks.Blocks;
import io.anuke.mindustry.world.blocks.SpecialBlocks;
import io.anuke.ucore.graphics.Hue;
import io.anuke.ucore.noise.Noise;
import io.anuke.ucore.util.Mathf;
public class Generator{
static final int spawn = Color.rgba8888(Color.RED);
static final int start = Color.rgba8888(Color.GREEN);
static final ObjectMap<Block, Block> rocks = new ObjectMap(){{
put(Blocks.stone, Blocks.rock);
put(Blocks.snow, Blocks.icerock);
@@ -25,6 +26,7 @@ public class Generator{
/**Returns world size.*/
public static void generate(Pixmap pixmap, Tile[][] tiles){
boolean hasenemies = true, hascore = false;;
Noise.setSeed(Vars.world.getSeed());
@@ -39,11 +41,16 @@ public class Generator{
if(pair != null){
block = pair.wall;
floor = pair.floor;
}else if(color == start){
}
if(block == SpecialBlocks.playerSpawn){
block = Blocks.air;
Vars.control.setCore(Vars.world.tile(x, y));
}else if(color == spawn){
hascore = true;
}else if(block == SpecialBlocks.enemySpawn){
block = Blocks.air;
Vars.control.addSpawnPoint(Vars.world.tile(x, y));
floor = Blocks.dirt;
hasenemies = true;
}
if(block == Blocks.air && Mathf.chance(0.025) && rocks.containsKey(floor)){
@@ -78,6 +85,16 @@ public class Generator{
tiles[x][y].setFloor(floor);
}
}
if(!hascore){
GameState.set(State.menu);
Vars.ui.showError("[orange]Invalid map:[] this map has no core!");
}
if(!hasenemies){
GameState.set(State.menu);
Vars.ui.showError("[orange]Invalid map:[] this map has no enemy spawnpoints!");
}
}
private static IntMap<Block> map(Object...objects){

View File

@@ -5,7 +5,7 @@ import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
public class Map{
public int id;
public int id = -1;
public String name;
public boolean visible = true;
public boolean flipBase = false;

View File

@@ -10,6 +10,7 @@ import com.badlogic.gdx.utils.Json.Serializer;
import com.badlogic.gdx.utils.JsonWriter.OutputType;
import io.anuke.mindustry.Vars;
import io.anuke.ucore.core.Settings;
import io.anuke.ucore.graphics.Pixmaps;
public class Maps implements Disposable{
@@ -49,17 +50,59 @@ public class Maps implements Disposable{
}
}
}
public void saveAndReload(Map map, Pixmap out){
if(map.pixmap != null && out != map.pixmap && map.texture != null){
map.texture.dispose();
map.texture = new Texture(out);
}else if (out == map.pixmap){
map.texture.draw(out, 0, 0);
}
map.pixmap = out;
if(map.texture == null) map.texture = new Texture(map.pixmap);
if(map.id == -1){
if(mapNames.containsKey(map.name)){
map.id = mapNames.get(map.name).id;
}else{
map.id = ++lastID;
}
}
if(!Settings.has("hiscore" + map.name)){
Settings.defaults("hiscore" + map.name, 0);
}
saveCustomMap(map);
Vars.ui.reloadLevels();
//TODO reload map dialog
}
public void saveMaps(Array<Map> array, FileHandle file){
json.toJson(new ArrayContainer(array), file);
}
public void saveCustomMap(Map toSave){
toSave.custom = true;
Array<Map> out = new Array<>();
boolean added = false;
for(Map map : maps.values()){
if(map.custom)
out.add(map);
if(map.custom){
if(map.name.equals(toSave.name)){
out.add(toSave);
added = true;
}else{
out.add(map);
}
}
}
if(!added){
out.add(toSave);
}
maps.remove(toSave.id);
mapNames.remove(toSave.name);
maps.put(toSave.id, toSave);
mapNames.put(toSave.name, toSave);
Pixmaps.write(toSave.pixmap, Vars.customMapDirectory.child(toSave.name + ".png"));
saveMaps(out, Vars.customMapDirectory.child("maps.json"));
}
@@ -73,6 +116,7 @@ public class Maps implements Disposable{
map.texture = new Texture(map.pixmap);
maps.put(map.id, map);
mapNames.put(map.name, map);
lastID = Math.max(lastID, map.id);
}
}
return true;

View File

@@ -0,0 +1,9 @@
package io.anuke.mindustry.world.blocks;
import io.anuke.mindustry.world.Block;
public class SpecialBlocks{
public static final Block
playerSpawn = new Block("playerspawn"),
enemySpawn = new Block("enemyspawn");
}

View File

@@ -19,10 +19,9 @@ import io.anuke.ucore.scene.ui.layout.Table;
import io.anuke.ucore.scene.ui.layout.Unit;
import io.anuke.ucore.util.Mathf;
//TODO
public class Teleporter extends Block implements Configurable{
public static final int colors = 4;
public static final Color[] colorArray = {Color.ROYAL, Color.ORANGE, Color.SCARLET, Color.FOREST, Color.PURPLE, Color.GOLD, Color.PINK};
public static final int colors = colorArray.length;
private static Array<Tile> removal = new Array<>();
private static Array<Tile> returns = new Array<>();
@@ -71,16 +70,13 @@ public class Teleporter extends Block implements Configurable{
TeleporterEntity entity = tile.entity();
table.addIButton("icon-arrow-left", Unit.dp.inPixels(10*3), ()->{
entity.color --;
if(entity.color < 0)
entity.color += 4;
entity.color = (byte)Mathf.mod(entity.color - 1, colors);
});
table.add().size(40f).units(Unit.dp);
table.addIButton("icon-arrow-right", Unit.dp.inPixels(10*3), ()->{
entity.color ++;
entity.color %= 4;
entity.color = (byte)Mathf.mod(entity.color + 1, colors);
});
}