Merge branches 'master' and 'new-map-format' of https://github.com/Anuken/Mindustry

This commit is contained in:
Anuken
2019-03-12 13:50:48 -04:00
56 changed files with 2757 additions and 4253 deletions

View File

@@ -30,6 +30,8 @@ import java.util.Locale;
@SuppressWarnings("unchecked")
public class Vars{
/**IO buffer size.*/
public static final int bufferSize = 8192;
/**global charset*/
public static final Charset charset = Charset.forName("UTF-8");
/**main application name, capitalized*/

View File

@@ -203,7 +203,7 @@ public class BlockIndexer{
for(int x = Math.max(0, tile.x - oreQuadrantSize / 2); x < tile.x + oreQuadrantSize / 2 && x < world.width(); x++){
for(int y = Math.max(0, tile.y - oreQuadrantSize / 2); y < tile.y + oreQuadrantSize / 2 && y < world.height(); y++){
Tile res = world.tile(x, y);
if(res.block() == Blocks.air && res.floor().itemDrop == item){
if(res.block() == Blocks.air && res.drop() == item){
return res;
}
}
@@ -241,9 +241,9 @@ public class BlockIndexer{
for(int x = quadrantX * structQuadrantSize; x < world.width() && x < (quadrantX + 1) * structQuadrantSize; x++){
for(int y = quadrantY * structQuadrantSize; y < world.height() && y < (quadrantY + 1) * structQuadrantSize; y++){
Tile result = world.tile(x, y);
if( result == null || result.floor().itemDrop == null || !scanOres.contains(result.floor().itemDrop)) continue;
if( result == null || result.drop() == null || !scanOres.contains(result.drop())) continue;
itemSet.add(result.floor().itemDrop);
itemSet.add(result.drop());
}
}
@@ -320,8 +320,8 @@ public class BlockIndexer{
Tile tile = world.tile(x, y);
//add position of quadrant to list when an ore is found
if(tile.floor().itemDrop != null && scanOres.contains(tile.floor().itemDrop) && tile.block() == Blocks.air){
ores.get(tile.floor().itemDrop).add(world.tile(
if(tile.drop() != null && scanOres.contains(tile.drop()) && tile.block() == Blocks.air){
ores.get(tile.drop()).add(world.tile(
//make sure to clamp quadrant middle position, since it might go off bounds
Mathf.clamp(qx * oreQuadrantSize + oreQuadrantSize / 2, 0, world.width() - 1),
Mathf.clamp(qy * oreQuadrantSize + oreQuadrantSize / 2, 0, world.height() - 1)));

View File

@@ -7,7 +7,6 @@ import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.mindustry.game.ContentList;
import io.anuke.mindustry.graphics.CacheLayer;
import io.anuke.mindustry.type.Category;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.ItemStack;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
@@ -28,7 +27,8 @@ import io.anuke.mindustry.world.blocks.units.UnitFactory;
import io.anuke.mindustry.world.consumers.ConsumeItemFilter;
import io.anuke.mindustry.world.consumers.ConsumeLiquidFilter;
import static io.anuke.mindustry.Vars.*;
import static io.anuke.mindustry.Vars.state;
import static io.anuke.mindustry.Vars.world;
public class Blocks implements ContentList{
public static Block
@@ -39,6 +39,9 @@ public class Blocks implements ContentList{
iceSnow, sandWater, duneRocks, sandRocks, stainedRocks, moss, stainedRocksRed, stainedStoneRed, stainedRocksYellow, stainedStoneYellow, stainedBoulder, grass, salt,
metalFloor, metalFloorDamaged, metalFloor2, metalFloor3, metalFloor5, ignarock, magmarock, hotrock, snowrocks,
//ores
oreCopper, oreLead, oreScrap, oreCoal, oreTitanium, oreThorium,
//crafting
siliconSmelter, kiln, graphitePress, plastaniumCompressor, multiPress, phaseWeaver, surgeSmelter, pyratiteMixer, blastMixer, cryofluidMixer,
melter, separator, sporePress, pulverizer, incinerator,
@@ -145,7 +148,7 @@ public class Blocks implements ContentList{
}};
stone = new Floor("stone"){{
hasOres = true;
}};
craters = new Floor("craters"){{
@@ -170,12 +173,10 @@ public class Blocks implements ContentList{
sand = new Floor("sand"){{
itemDrop = Items.sand;
hasOres = true;
playerUnmineable = true;
}};
holostone = new Floor("holostone"){{
hasOres = true;
edgeStyle = "blocky";
}};
@@ -226,7 +227,6 @@ public class Blocks implements ContentList{
}};
pine = new StaticWall("pine"){{
//fillsTile = false;
variants = 0;
}};
@@ -253,7 +253,6 @@ public class Blocks implements ContentList{
}};
stainedStoneRed = new Floor("stained-stone-red"){{
hasOres = true;
variants = 1;
}};
@@ -308,6 +307,16 @@ public class Blocks implements ContentList{
blendGroup = ignarock;
}};
//endregion
//region ore
oreCopper = new OreBlock(Items.copper);
oreLead = new OreBlock(Items.lead);
oreScrap = new OreBlock(Items.scrap);
oreCoal = new OreBlock(Items.coal);
oreTitanium = new OreBlock(Items.titanium);
oreThorium = new OreBlock(Items.thorium);
//endregion
//region crafting
@@ -1418,23 +1427,6 @@ public class Blocks implements ContentList{
consumes.powerBuffered(120f);
}};
//endregion
//region ores
//create ores for every floor and item combination necessary
for(Item item : content.items()){
if(!item.genOre) continue;
for(Block block : content.blocks()){
if(block instanceof Floor && ((Floor) block).hasOres){
new OreBlock(item, (Floor) block);
}
}
}
//special variants
new OreBlock(Items.scrap, (Floor)snow);
//endregion
}
}

View File

@@ -15,7 +15,6 @@ public class Items implements ContentList{
type = ItemType.material;
hardness = 1;
cost = 0.6f;
genOre = true;
alwaysUnlocked = true;
}};
@@ -23,7 +22,6 @@ public class Items implements ContentList{
type = ItemType.material;
hardness = 1;
cost = 0.9f;
genOre = true;
}};
metaglass = new Item("metaglass", Color.valueOf("ebeef5")){{
@@ -40,14 +38,12 @@ public class Items implements ContentList{
explosiveness = 0.4f;
flammability = 1f;
hardness = 2;
genOre = true;
}};
titanium = new Item("titanium", Color.valueOf("8da1e3")){{
type = ItemType.material;
hardness = 3;
cost = 1.1f;
genOre = true;
}};
thorium = new Item("thorium", Color.valueOf("f9a3c7")){{
@@ -56,11 +52,10 @@ public class Items implements ContentList{
hardness = 4;
radioactivity = 1f;
cost = 1.4f;
genOre = true;
}};
scrap = new Item("scrap", Color.valueOf("777777")){{
genOre = true;
}};
silicon = new Item("silicon", Color.valueOf("53565c")){{

View File

@@ -23,8 +23,6 @@ import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.io.MapIO;
import io.anuke.mindustry.maps.Map;
import io.anuke.mindustry.maps.MapException;
import io.anuke.mindustry.maps.MapTileData;
import io.anuke.mindustry.maps.MapTileData.TileDataMarker;
import io.anuke.mindustry.maps.Maps;
import io.anuke.mindustry.maps.generators.Generator;
import io.anuke.mindustry.type.ContentType;
@@ -235,12 +233,15 @@ public class World implements ApplicationListener{
beginMapLoad();
this.currentMap = map;
int width = map.meta.width, height = map.meta.height;
createTiles(width, height);
try{
loadTileData(tiles, MapIO.readTileData(map, true));
createTiles(map.width, map.height);
for(int x = 0; x < map.width; x++){
for(int y = 0; y < map.height; y++){
tiles[x][y] = new Tile(x, y);
}
}
MapIO.readTiles(map, tiles);
prepareTiles(tiles);
}catch(Exception e){
Log.err(e);
if(!headless){
@@ -260,7 +261,7 @@ public class World implements ApplicationListener{
if(state.teams.get(players[0].getTeam()).cores.size == 0){
ui.showError("$map.nospawn");
invalidMap = true;
}else if(state.rules.pvp){ //pvp maps need two cores to be valid
}else if(state.rules.pvp){ //pvp maps need two cores to be valid
invalidMap = true;
for(Team team : Team.all){
if(state.teams.get(team).cores.size != 0 && team != players[0].getTeam()){
@@ -416,18 +417,7 @@ public class World implements ApplicationListener{
}
/**Loads raw map tile data into a Tile[][] array, setting up multiblocks, cliffs and ores. */
void loadTileData(Tile[][] tiles, MapTileData data){
data.position(0, 0);
TileDataMarker marker = data.newDataMarker();
for(int y = 0; y < data.height(); y++){
for(int x = 0; x < data.width(); x++){
data.read(marker);
tiles[x][y] = new Tile(x, y, marker.floor, marker.wall == Blocks.part.id ? 0 : marker.wall, marker.rotation, marker.team);
}
}
void loadTileData(Tile[][] tiles){
prepareTiles(tiles);
}

View File

@@ -1,72 +1,70 @@
package io.anuke.mindustry.editor;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.IntSet;
import io.anuke.arc.util.Pack;
import io.anuke.mindustry.maps.MapTileData;
import io.anuke.mindustry.maps.MapTileData.TileDataMarker;
import io.anuke.annotations.Annotations.Struct;
import io.anuke.arc.collection.LongArray;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.gen.TileOp;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Floor;
import static io.anuke.mindustry.Vars.content;
public class DrawOperation{
/**
* Data to apply operation to.
*/
private MapTileData data;
/**
* List of per-tile operations that occurred.
*/
private Array<TileOperation> operations = new Array<>();
/**
* Checks for duplicate operations, useful for brushes.
*/
private IntSet checks = new IntSet();
public DrawOperation(MapTileData data){
this.data = data;
}
private LongArray array = new LongArray();
public boolean isEmpty(){
return operations.size == 0;
return array.isEmpty();
}
public boolean checkDuplicate(short x, short y){
int i = Pack.shortInt(x, y);
if(checks.contains(i)) return true;
checks.add(i);
return false;
}
public void addOperation(TileOperation op){
operations.add(op);
public void addOperation(long op){
array.add(op);
}
public void undo(MapEditor editor){
for(int i = operations.size - 1; i >= 0; i--){
TileOperation op = operations.get(i);
data.position(op.x, op.y);
data.write(op.from);
editor.renderer().updatePoint(op.x, op.y);
for(int i = array.size - 1; i >= 0; i--){
long l = array.get(i);
set(editor, editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l), TileOp.from(l));
}
}
public void redo(MapEditor editor){
for(TileOperation op : operations){
data.position(op.x, op.y);
data.write(op.to);
editor.renderer().updatePoint(op.x, op.y);
for(int i = 0; i < array.size; i++){
long l = array.get(i);
set(editor, editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l), TileOp.to(l));
}
}
public static class TileOperation{
public short x, y;
public TileDataMarker from;
public TileDataMarker to;
void set(MapEditor editor, Tile tile, byte type, byte to){
editor.load(() -> {
if(type == OpType.floor.ordinal()){
tile.setFloor((Floor)content.block(to));
}else if(type == OpType.block.ordinal()){
tile.setBlock(content.block(to));
}else if(type == OpType.rotation.ordinal()){
tile.setRotation(to);
}else if(type == OpType.team.ordinal()){
tile.setTeam(Team.all[to]);
}else if(type == OpType.ore.ordinal()){
tile.setOreByte(to);
}
});
editor.renderer().updatePoint(tile.x, tile.y);
}
public TileOperation(short x, short y, TileDataMarker from, TileDataMarker to){
this.x = x;
this.y = y;
this.from = from;
this.to = to;
}
@Struct
class TileOpStruct{
short x;
short y;
byte type;
byte from;
byte to;
}
public enum OpType{
floor,
block,
rotation,
team,
ore
}
}

View File

@@ -0,0 +1,105 @@
package io.anuke.mindustry.editor;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.editor.DrawOperation.OpType;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.gen.TileOp;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Floor;
import io.anuke.mindustry.world.blocks.OreBlock;
import io.anuke.mindustry.world.modules.ConsumeModule;
import io.anuke.mindustry.world.modules.ItemModule;
import io.anuke.mindustry.world.modules.LiquidModule;
import io.anuke.mindustry.world.modules.PowerModule;
import static io.anuke.mindustry.Vars.ui;
public class EditorTile extends Tile{
public EditorTile(int x, int y, byte floor, byte wall){
super(x, y, floor, wall);
}
@Override
public Team getTeam(){
return Team.all[getTeamID()];
}
@Override
public void setFloor(Floor type){
if(type instanceof OreBlock){
//don't place on liquids
if(!floor().isLiquid) setOreByte(type.id);
return;
}
Block previous = floor();
Block ore = oreBlock();
if(previous == type && ore == Blocks.air) return;
super.setFloor(type);
//ore may get nullified so make sure to save editrs
if(oreBlock() != ore){
op(TileOp.get(x, y, (byte)OpType.ore.ordinal(), ore.id, oreBlock().id));
}
if(previous != type){
op(TileOp.get(x, y, (byte)OpType.floor.ordinal(), previous.id, type.id));
}
}
@Override
public void setBlock(Block type){
Block previous = block();
if(previous == type) return;
super.setBlock(type);
op(TileOp.get(x, y, (byte)OpType.block.ordinal(), previous.id, type.id));
}
@Override
public void setTeam(Team team){
byte previous = getTeamID();
if(previous == team.ordinal()) return;
super.setTeam(team);
op(TileOp.get(x, y, (byte)OpType.team.ordinal(), previous, (byte)team.ordinal()));
}
@Override
public void setRotation(byte rotation){
byte previous = getRotation();
if(previous == rotation) return;
super.setRotation(rotation);
op(TileOp.get(x, y, (byte)OpType.rotation.ordinal(), previous, rotation));
}
@Override
public void setOreByte(byte ore){
byte previous = getOreByte();
if(previous == ore) return;
super.setOreByte(ore);
op(TileOp.get(x, y, (byte)OpType.ore.ordinal(), previous, ore));
}
@Override
protected void preChanged(){
super.setTeam(Team.none);
}
@Override
protected void changed(){
entity = null;
Block block = block();
if(block.hasEntity()){
entity = block.newEntity();
entity.cons = new ConsumeModule(entity);
if(block.hasItems) entity.items = new ItemModule();
if(block.hasLiquids) entity.liquids = new LiquidModule();
if(block.hasPower) entity.power = new PowerModule();
}
}
private static void op(long op){
ui.editor.editor.addTileOp(op);
}
}

View File

@@ -7,34 +7,31 @@ import io.anuke.arc.input.KeyCode;
import io.anuke.arc.util.Pack;
import io.anuke.arc.util.Structs;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.maps.MapTileData;
import io.anuke.mindustry.maps.MapTileData.DataPosition;
import io.anuke.mindustry.maps.MapTileData.TileDataMarker;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Pos;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.BlockPart;
import io.anuke.mindustry.world.blocks.Floor;
import static io.anuke.mindustry.Vars.*;
public enum EditorTool{
pick{
public void touched(MapEditor editor, int x, int y){
if(!Structs.inBounds(x, y, editor.getMap().width(), editor.getMap().height())) return;
if(!Structs.inBounds(x, y, editor.width(), editor.height())) return;
byte bf = editor.getMap().read(x, y, DataPosition.floor);
byte bw = editor.getMap().read(x, y, DataPosition.wall);
byte link = editor.getMap().read(x, y, DataPosition.link);
Tile tile = editor.tile(x, y);
if(link != 0){
Block floor = tile.floor(), block = tile.block();
byte link = tile.getLinkByte();
if(block instanceof BlockPart && link != 0){
x -= (Pack.leftByte(link) - 8);
y -= (Pack.rightByte(link) - 8);
bf = editor.getMap().read(x, y, DataPosition.floor);
bw = editor.getMap().read(x, y, DataPosition.wall);
tile = editor.tile(x, y);
block = tile.block();
}
Block block = content.block(bw == 0 ? bf : bw);
editor.setDrawBlock(block);
ui.editor.updateSelectedBlock();
editor.drawBlock = block == Blocks.air ? floor : block;
}
},
pencil{
@@ -67,7 +64,7 @@ public enum EditorTool{
@Override
public void touched(MapEditor editor, int x, int y){
editor.draw(x, y, isPaint(), editor.getDrawBlock(), 0.012);
editor.draw(x, y, isPaint(), editor.drawBlock, 0.012);
}
},
line{
@@ -81,56 +78,57 @@ public enum EditorTool{
}
IntArray stack = new IntArray();
int width;
byte be, dest;
boolean floor;
MapTileData data;
Block dest;
boolean isfloor;
MapEditor data;
public void touched(MapEditor editor, int x, int y){
if(!Structs.inBounds(x, y, editor.getMap().width(), editor.getMap().height())) return;
if(!Structs.inBounds(x, y, editor.width(), editor.height())) return;
Tile tile = editor.tile(x, y);
if(editor.getDrawBlock().isMultiblock()){
if(editor.drawBlock.isMultiblock()){
//don't fill multiblocks, thanks
pencil.touched(editor, x, y);
return;
}
data = editor.getMap();
data = editor;
isfloor = editor.drawBlock instanceof Floor;
floor = editor.getDrawBlock() instanceof Floor;
Block floor = tile.floor();
Block block = tile.block();
boolean synth = editor.drawBlock.synthetic();
byte bf = data.read(x, y, DataPosition.floor);
byte bw = data.read(x, y, DataPosition.wall);
boolean synth = editor.getDrawBlock().synthetic();
byte brt = Pack.byteByte((byte) editor.getDrawRotation(), (byte) editor.getDrawTeam().ordinal());
dest = isfloor ? floor : block;
Block draw = editor.drawBlock;
dest = floor ? bf : bw;
byte draw = editor.getDrawBlock().id;
if(dest == draw || content.block(bw) instanceof BlockPart || content.block(bw).isMultiblock()){
if(dest == draw || block == Blocks.part || block.isMultiblock()){
return;
}
width = editor.getMap().width();
int height = editor.getMap().height();
int width = editor.width();
int height = editor.height();
IntPositionConsumer writer = (px, py) -> {
TileDataMarker prev = editor.getPrev(px, py, false);
Tile write = editor.tile(px, py);
if(floor){
data.write(px, py, DataPosition.floor, draw);
if(isfloor){
write.setFloor((Floor)draw);
}else{
data.write(px, py, DataPosition.wall, draw);
write.setBlock(draw);
}
if(synth){
data.write(px, py, DataPosition.rotationTeam, brt);
write.setTeam(editor.drawTeam);
}
editor.onWrite(px, py, prev);
if(draw.rotate){
write.setRotation((byte)editor.rotation);
}
};
if(isAlt()){
//fill all of the same type regardless of borders
for(int cx = 0; cx < width; cx++){
for(int cy = 0; cy < height; cy++){
if(eq(cx, cy)){
@@ -139,28 +137,28 @@ public enum EditorTool{
}
}
}else if(isAlt2()){
//fill all teams.
for(int cx = 0; cx < width; cx++){
for(int cy = 0; cy < height; cy++){
byte w = data.read(cx, cy, DataPosition.wall);
if(content.block(w).synthetic()){
TileDataMarker prev = editor.getPrev(cx, cy, false);
data.write(cx, cy, DataPosition.rotationTeam, (byte)editor.getDrawTeam().ordinal());
editor.onWrite(cx, cy, prev);
Tile write = editor.tile(cx, cy);
if(write.block().synthetic()){
write.setTeam(editor.drawTeam);
}
}
}
}else{
//normal fill
int x1;
boolean spanAbove, spanBelow;
stack.clear();
stack.add(asi(x, y));
stack.add(Pos.get(x, y));
while(stack.size > 0){
int popped = stack.pop();
x = popped % width;
y = popped / width;
x = Pos.x(popped);
y = Pos.y(popped);
x1 = x;
while(x1 >= 0 && eq(x1, y)) x1--;
@@ -170,14 +168,14 @@ public enum EditorTool{
writer.accept(x1, y);
if(!spanAbove && y > 0 && eq(x1, y - 1)){
stack.add(asi(x1, y - 1));
stack.add(Pos.get(x1, y - 1));
spanAbove = true;
}else if(spanAbove && y > 0 && eq(x1, y - 1)){
}else if(spanAbove && eq(x1, y - 1)){
spanAbove = false;
}
if(!spanBelow && y < height - 1 && eq(x1, y + 1)){
stack.add(asi(x1, y + 1));
stack.add(Pos.get(x1, y + 1));
spanBelow = true;
}else if(spanBelow && y < height - 1 && eq(x1, y + 1)){
spanBelow = false;
@@ -189,15 +187,9 @@ public enum EditorTool{
}
boolean eq(int px, int py){
byte nbf = data.read(px, py, DataPosition.floor);
byte nbw = data.read(px, py, DataPosition.wall);
byte nbe = data.read(px, py, DataPosition.elevation);
Tile tile = data.tile(px, py);
return (floor ? nbf : nbw) == dest && nbe == be;
}
int asi(int x, int y){
return x + y * width;
return (isfloor ? tile.floor() : tile.block()) == dest;
}
},
zoom;

View File

@@ -1,88 +1,150 @@
package io.anuke.mindustry.editor;
import io.anuke.arc.collection.ObjectMap;
import io.anuke.arc.files.FileHandle;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.util.Log;
import io.anuke.arc.util.Pack;
import io.anuke.arc.util.Structs;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.editor.DrawOperation.TileOperation;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.maps.MapTileData;
import io.anuke.mindustry.maps.MapTileData.DataPosition;
import io.anuke.mindustry.maps.MapTileData.TileDataMarker;
import io.anuke.mindustry.gen.TileOp;
import io.anuke.mindustry.io.MapIO;
import io.anuke.mindustry.maps.Map;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Floor;
import static io.anuke.mindustry.Vars.content;
import java.io.IOException;
public class MapEditor{
public static final int[] brushSizes = {1, 2, 3, 4, 5, 9, 15, 20};
private MapTileData map;
private ObjectMap<String, String> tags = new ObjectMap<>();
private MapRenderer renderer = new MapRenderer(this);
private Tile[][] tiles;
private int brushSize = 1;
private int rotation;
private Block drawBlock = Blocks.stone;
private Team drawTeam = Team.blue;
private OperationStack stack = new OperationStack();
private DrawOperation currentOp;
private boolean loading;
public MapTileData getMap(){
return map;
}
public int brushSize = 1;
public int rotation;
public Block drawBlock = Blocks.stone;
public Team drawTeam = Team.blue;
public ObjectMap<String, String> getTags(){
return tags;
}
public void beginEdit(MapTileData map, ObjectMap<String, String> tags, boolean clear){
this.map = map;
this.brushSize = 1;
this.tags = tags;
public void beginEdit(int width, int height){
reset();
if(clear){
for(int x = 0; x < map.width(); x++){
for(int y = 0; y < map.height(); y++){
map.write(x, y, DataPosition.floor, Blocks.stone.id);
loading = true;
tiles = createTiles(width, height);
renderer.resize(width(), height());
loading = false;
}
public void beginEdit(Map map) throws IOException{
reset();
loading = true;
tiles = createTiles(map.width, map.height);
tags.putAll(map.tags);
MapIO.readTiles(map, tiles);
checkTiles();
renderer.resize(width(), height());
loading = false;
}
public void beginEdit(Tile[][] tiles){
reset();
this.tiles = tiles;
checkTiles();
renderer.resize(width(), height());
}
//adds missing blockparts
void checkTiles(){
//clear block parts first
for(int x = 0; x < width(); x ++){
for(int y = 0; y < height(); y++){
if(tiles[x][y].block() == Blocks.part){
tiles[x][y].setBlock(Blocks.air);
tiles[x][y].setLinkByte((byte)0);
}
}
}
//set up missing blockparts
for(int x = 0; x < width(); x ++){
for(int y = 0; y < height(); y ++){
Block drawBlock = tiles[x][y].block();
if(drawBlock.isMultiblock()){
int offsetx = -(drawBlock.size - 1) / 2;
int offsety = -(drawBlock.size - 1) / 2;
for(int dx = 0; dx < drawBlock.size; dx++){
for(int dy = 0; dy < drawBlock.size; dy++){
int worldx = dx + offsetx + x;
int worldy = dy + offsety + y;
if(Structs.inBounds(worldx, worldy, width(), height()) && !(dx + offsetx == 0 && dy + offsety == 0)){
Tile tile = tiles[worldx][worldy];
tile.setBlock(Blocks.part);
tile.setLinkByte(Pack.byteByte((byte) (dx + offsetx + 8), (byte) (dy + offsety + 8)));
}
}
}
}
}
}
}
public void load(Runnable r){
loading = true;
r.run();
loading = false;
}
/**Creates a 2-D array of EditorTiles with stone as the floor block.*/
public Tile[][] createTiles(int width, int height){
tiles = new Tile[width][height];
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (byte)0);
}
}
return tiles;
}
public Map createMap(FileHandle file){
return new Map(file, width(), height(), new ObjectMap<>(tags), true);
}
private void reset(){
clearOp();
brushSize = 1;
drawBlock = Blocks.stone;
renderer.resize(map.width(), map.height());
tags = new ObjectMap<>();
}
public int getDrawRotation(){
return rotation;
public Tile[][] tiles(){
return tiles;
}
public void setDrawRotation(int rotation){
this.rotation = rotation;
public Tile tile(int x, int y){
return tiles[x][y];
}
public Team getDrawTeam(){
return drawTeam;
public int width(){
return tiles.length;
}
public void setDrawTeam(Team team){
this.drawTeam = team;
}
public Block getDrawBlock(){
return drawBlock;
}
public void setDrawBlock(Block block){
this.drawBlock = block;
}
public int getBrushSize(){
return brushSize;
}
public void setBrushSize(int size){
this.brushSize = size;
public int height(){
return tiles[0].length;
}
public void draw(int x, int y, boolean paint){
@@ -94,15 +156,12 @@ public class MapEditor{
}
public void draw(int x, int y, boolean paint, Block drawBlock, double chance){
byte writeID = drawBlock.id;
byte partID = Blocks.part.id;
byte rotationTeam = Pack.byteByte(drawBlock.rotate ? (byte)rotation : 0, drawBlock.synthetic() ? (byte)drawTeam.ordinal() : 0);
boolean isfloor = drawBlock instanceof Floor && drawBlock != Blocks.air;
if(drawBlock.isMultiblock()){
x = Mathf.clamp(x, (drawBlock.size-1)/2, map.width() - drawBlock.size/2 - 1);
y = Mathf.clamp(y, (drawBlock.size-1)/2, map.height() - drawBlock.size/2 - 1);
x = Mathf.clamp(x, (drawBlock.size-1)/2, width() - drawBlock.size/2 - 1);
y = Mathf.clamp(y, (drawBlock.size-1)/2, height() - drawBlock.size/2 - 1);
int offsetx = -(drawBlock.size - 1) / 2;
int offsety = -(drawBlock.size - 1) / 2;
@@ -113,68 +172,61 @@ public class MapEditor{
int worldx = dx + offsetx + x;
int worldy = dy + offsety + y;
if(Structs.inBounds(worldx, worldy, map.width(), map.height())){
TileDataMarker prev = getPrev(worldx, worldy, false);
if(Structs.inBounds(worldx, worldy, width(), height())){
Tile tile = tiles[worldx][worldy];
if(i == 1){
map.write(worldx, worldy, DataPosition.wall, partID);
map.write(worldx, worldy, DataPosition.rotationTeam, rotationTeam);
map.write(worldx, worldy, DataPosition.link, Pack.byteByte((byte) (dx + offsetx + 8), (byte) (dy + offsety + 8)));
tile.setBlock(Blocks.part);
tile.setLinked((byte)(dx + offsetx), (byte)(dy + offsety));
}else{
byte link = map.read(worldx, worldy, DataPosition.link);
byte block = map.read(worldx, worldy, DataPosition.wall);
byte link = tile.getLinkByte();
Block block = tile.block();
if(link != 0){
removeLinked(worldx - (Pack.leftByte(link) - 8), worldy - (Pack.rightByte(link) - 8));
}else if(content.block(block).isMultiblock()){
}else if(block.isMultiblock()){
removeLinked(worldx, worldy);
}
}
onWrite(worldx, worldy, prev);
}
}
}
}
TileDataMarker prev = getPrev(x, y, false);
map.write(x, y, DataPosition.wall, writeID);
map.write(x, y, DataPosition.link, (byte) 0);
map.write(x, y, DataPosition.rotationTeam, rotationTeam);
onWrite(x, y, prev);
Tile tile = tiles[x][y];
tile.setBlock(drawBlock);
tile.setTeam(drawTeam);
}else{
for(int rx = -brushSize; rx <= brushSize; rx++){
for(int ry = -brushSize; ry <= brushSize; ry++){
if(Mathf.dst(rx, ry) <= brushSize - 0.5f && (chance >= 0.999 || Mathf.chance(chance))){
int wx = x + rx, wy = y + ry;
if(wx < 0 || wy < 0 || wx >= map.width() || wy >= map.height() || (paint && !isfloor && content.block(map.read(wx, wy, DataPosition.wall)) == Blocks.air)){
if(wx < 0 || wy < 0 || wx >= width() || wy >= height() || (paint && !isfloor && tiles[wx][wy].block() == Blocks.air)){
continue;
}
TileDataMarker prev = getPrev(wx, wy, true);
Tile tile = tiles[wx][wy];
if(!isfloor){
byte link = map.read(wx, wy, DataPosition.link);
byte link = tile.getLinkByte();
Log.info("Remove linkd: " + tiles[x][y]);
if(content.block(map.read(wx, wy, DataPosition.wall)).isMultiblock()){
if(tile.block().isMultiblock()){
removeLinked(wx, wy);
}else if(link != 0){
}else if(link != 0 && tiles[x][y].block() == Blocks.part){
removeLinked(wx - (Pack.leftByte(link) - 8), wy - (Pack.rightByte(link) - 8));
}
}
if(isfloor){
map.write(wx, wy, DataPosition.floor, writeID);
tile.setFloor((Floor)drawBlock);
}else{
map.write(wx, wy, DataPosition.wall, writeID);
map.write(wx, wy, DataPosition.link, (byte) 0);
map.write(wx, wy, DataPosition.rotationTeam, rotationTeam);
tile.setBlock(drawBlock);
if(drawBlock.synthetic()){
tile.setTeam(drawTeam);
}
}
onWrite(x + rx, y + ry, prev);
}
}
}
@@ -182,77 +234,86 @@ public class MapEditor{
}
private void removeLinked(int x, int y){
Block block = content.block(map.read(x, y, DataPosition.wall));
Block block = tiles[x][y].block();
int offsetx = -(block.size - 1) / 2;
int offsety = -(block.size - 1) / 2;
for(int dx = 0; dx < block.size; dx++){
for(int dy = 0; dy < block.size; dy++){
int worldx = x + dx + offsetx, worldy = y + dy + offsety;
if(Structs.inBounds(worldx, worldy, map.width(), map.height())){
TileDataMarker prev = getPrev(worldx, worldy, false);
map.write(worldx, worldy, DataPosition.link, (byte) 0);
map.write(worldx, worldy, DataPosition.rotationTeam, (byte) 0);
map.write(worldx, worldy, DataPosition.wall, (byte) 0);
onWrite(worldx, worldy, prev);
if(Structs.inBounds(worldx, worldy, width(), height())){
tiles[worldx][worldy].setTeam(Team.none);
tiles[worldx][worldy].setBlock(Blocks.air);
}
}
}
}
boolean checkDupes(int x, int y){
return Vars.ui.editor.getView().checkForDuplicates((short) x, (short) y);
}
void onWrite(int x, int y, TileDataMarker previous){
if(previous == null){
renderer.updatePoint(x, y);
return;
}
TileDataMarker current = map.new TileDataMarker();
map.position(x, y);
map.read(current);
Vars.ui.editor.getView().addTileOp(new TileOperation((short) x, (short) y, previous, current));
renderer.updatePoint(x, y);
}
TileDataMarker getPrev(int x, int y, boolean checkDupes){
if(checkDupes && checkDupes(x, y)){
return null;
}else{
TileDataMarker marker = map.newDataMarker();
map.position(x, y);
map.read(marker);
return marker;
}
}
public MapRenderer renderer(){
return renderer;
}
public void resize(int width, int height){
MapTileData previous = map;
int offsetX = -(width - previous.width())/2, offsetY = -(height - previous.height())/2;
clearOp();
map = new MapTileData(width, height);
for(int x = 0; x < map.width(); x++){
for(int y = 0; y < map.height(); y++){
Tile[][] previous = tiles;
int offsetX = -(width - width())/2, offsetY = -(height - height())/2;
loading = true;
tiles = new Tile[width][height];
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
int px = offsetX + x, py = offsetY + y;
if(Structs.inBounds(px, py, previous.width(), previous.height())){
map.write(x, y, DataPosition.floor, previous.read(px, py, DataPosition.floor));
map.write(x, y, DataPosition.wall, previous.read(px, py, DataPosition.wall));
map.write(x, y, DataPosition.link, previous.read(px, py, DataPosition.link));
map.write(x, y, DataPosition.rotationTeam, previous.read(px, py, DataPosition.rotationTeam));
if(Structs.inBounds(px, py, previous.length, previous[0].length)){
tiles[x][y] = previous[px][py];
tiles[x][y].x = (short)x;
tiles[x][y].y = (short)y;
}else{
map.write(x, y, DataPosition.floor, Blocks.stone.id);
tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (byte)0);
}
}
}
renderer.resize(width, height);
loading = false;
}
public void clearOp(){
stack.clear();
}
public void undo(){
if(stack.canUndo()){
stack.undo(this);
}
}
public void redo(){
if(stack.canRedo()){
stack.redo(this);
}
}
public boolean canUndo(){
return stack.canUndo();
}
public boolean canRedo(){
return stack.canRedo();
}
public void flushOp(){
if(currentOp == null || currentOp.isEmpty()) return;
stack.add(currentOp);
currentOp = null;
}
public void addTileOp(long data){
if(loading) return;
if(currentOp == null) currentOp = new DrawOperation();
currentOp.addOperation(data);
renderer.updatePoint(TileOp.x(data), TileOp.y(data));
}
}

View File

@@ -2,7 +2,6 @@ package io.anuke.mindustry.editor;
import io.anuke.arc.Core;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.ObjectMap;
import io.anuke.arc.files.FileHandle;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.graphics.Color;
@@ -23,21 +22,17 @@ import io.anuke.mindustry.core.Platform;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.io.MapIO;
import io.anuke.mindustry.maps.Map;
import io.anuke.mindustry.maps.MapMeta;
import io.anuke.mindustry.maps.MapTileData;
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Block.Icon;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.OreBlock;
import io.anuke.mindustry.world.blocks.storage.CoreBlock;
import java.io.DataInputStream;
import java.io.InputStream;
import static io.anuke.mindustry.Vars.*;
public class MapEditorDialog extends Dialog implements Disposable{
private MapEditor editor;
public final MapEditor editor;
private MapView view;
private MapInfoDialog infoDialog;
private MapLoadDialog loadDialog;
@@ -48,8 +43,6 @@ public class MapEditorDialog extends Dialog implements Disposable{
private boolean shownWithMap = false;
private Array<Block> blocksOut = new Array<>();
private ButtonGroup<ImageButton> blockgroup;
public MapEditorDialog(){
super("", "dialog");
@@ -89,17 +82,12 @@ public class MapEditorDialog extends Dialog implements Disposable{
createDialog("$editor.import",
"$editor.importmap", "$editor.importmap.description", "icon-load-map", (Runnable) loadDialog::show,
"$editor.importfile", "$editor.importfile.description", "icon-file", (Runnable) () ->
Platform.instance.showFileChooser("$loadimage", "Map Files", file -> ui.loadAnd(() -> {
Platform.instance.showFileChooser("$editor.loadmap", "Map Files", file -> ui.loadAnd(() -> {
try{
DataInputStream stream = new DataInputStream(file.read());
MapMeta meta = MapIO.readMapMeta(stream);
MapTileData data = MapIO.readTileData(stream, meta, false);
editor.beginEdit(data, meta.tags, false);
view.clearStack();
//TODO what if it's an image? users should be warned for their stupidity
editor.beginEdit(MapIO.readMap(file, true));
}catch(Exception e){
ui.showError(Core.bundle.format("editor.errorimageload", Strings.parseException(e, false)));
ui.showError(Core.bundle.format("editor.errorload", Strings.parseException(e, false)));
Log.err(e);
}
}), true, mapExtension),
@@ -108,39 +96,32 @@ public class MapEditorDialog extends Dialog implements Disposable{
Platform.instance.showFileChooser("$loadimage", "Image Files", file ->
ui.loadAnd(() -> {
try{
MapTileData data = MapIO.readLegacyPixmap(new Pixmap(file));
editor.beginEdit(data, editor.getTags(), false);
view.clearStack();
Pixmap pixmap = new Pixmap(file);
Tile[][] tiles = editor.createTiles(pixmap.getWidth(), pixmap.getHeight());
editor.load(() -> MapIO.readLegacyPixmap(pixmap, tiles));
editor.beginEdit(tiles);
}catch (Exception e){
ui.showError(Core.bundle.format("editor.errorimageload", Strings.parseException(e, false)));
ui.showError(Core.bundle.format("editor.errorload", Strings.parseException(e, false)));
Log.err(e);
}
}), true, "png")
));
}), true, "png")));
t.addImageTextButton("$editor.export", "icon-save-map", isize, () -> createDialog("$editor.export",
"$editor.exportfile", "$editor.exportfile.description", "icon-file", (Runnable) () ->
Platform.instance.showFileChooser("$saveimage", "Map Files", file -> {
t.addImageTextButton("$editor.export", "icon-save-map", isize, () ->
Platform.instance.showFileChooser("$editor.savemap", "Map Files", file -> {
file = file.parent().child(file.nameWithoutExtension() + "." + mapExtension);
FileHandle result = file;
ui.loadAnd(() -> {
try{
if(!editor.getTags().containsKey("name")){
editor.getTags().put("name", result.nameWithoutExtension());
}
MapIO.writeMap(result.write(false), editor.getTags(), editor.getMap());
MapIO.writeMap(result, editor.createMap(result), editor.tiles());
}catch(Exception e){
ui.showError(Core.bundle.format("editor.errorimagesave", Strings.parseException(e, false)));
ui.showError(Core.bundle.format("editor.errorsave", Strings.parseException(e, false)));
Log.err(e);
}
});
}, false, mapExtension)));
t.row();
t.row();
}, false, mapExtension));
});
menu.cont.row();
@@ -151,24 +132,19 @@ public class MapEditorDialog extends Dialog implements Disposable{
}).padTop(-5).size(swidth * 2f + 10, 60f);
resizeDialog = new MapResizeDialog(editor, (x, y) -> {
if(!(editor.getMap().width() == x && editor.getMap().height() == y)){
if(!(editor.width() == x && editor.height() == y)){
ui.loadAnd(() -> {
editor.resize(x, y);
view.clearStack();
});
}
});
loadDialog = new MapLoadDialog(map ->
ui.loadAnd(() -> {
try(DataInputStream stream = new DataInputStream(map.stream.get())){
MapMeta meta = MapIO.readMapMeta(stream);
MapTileData data = MapIO.readTileData(stream, meta, false);
editor.beginEdit(data, meta.tags, false);
view.clearStack();
try{
editor.beginEdit(map);
}catch(Exception e){
ui.showError(Core.bundle.format("editor.errorimageload", Strings.parseException(e, false)));
ui.showError(Core.bundle.format("editor.errorload", Strings.parseException(e, false)));
Log.err(e);
}
}));
@@ -200,10 +176,10 @@ public class MapEditorDialog extends Dialog implements Disposable{
shown(() -> {
saved = true;
Platform.instance.beginForceLandscape();
view.clearStack();
editor.clearOp();
Core.scene.setScrollFocus(view);
if(!shownWithMap){
editor.beginEdit(new MapTileData(200, 200), new ObjectMap<>(), true);
editor.beginEdit(200, 200);
}
shownWithMap = false;
@@ -211,6 +187,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
});
hidden(() -> {
editor.clearOp();
Platform.instance.updateRPC();
Platform.instance.endForceLandscape();
});
@@ -225,13 +202,14 @@ public class MapEditorDialog extends Dialog implements Disposable{
String name = editor.getTags().get("name", "");
if(name.isEmpty()){
ui.showError("$editor.save.noname");
infoDialog.show();
Core.app.post(() -> ui.showError("$editor.save.noname"));
}else{
Map map = world.maps.getByName(name);
Map map = world.maps.all().find(m -> m.name().equals(name));
if(map != null && !map.custom){
ui.showError("$editor.save.overwrite");
}else{
world.maps.saveMap(name, editor.getMap(), editor.getTags());
world.maps.saveMap(editor.getTags(), editor.tiles());
ui.showInfoFade("$editor.saved");
}
}
@@ -293,18 +271,16 @@ public class MapEditorDialog extends Dialog implements Disposable{
editor.renderer().dispose();
}
public void beginEditMap(InputStream is){
public void beginEditMap(FileHandle file){
ui.loadAnd(() -> {
try{
Map map = MapIO.readMap(file, true);
shownWithMap = true;
DataInputStream stream = new DataInputStream(is);
MapMeta meta = MapIO.readMapMeta(stream);
editor.beginEdit(MapIO.readTileData(stream, meta, false), meta.tags, false);
is.close();
editor.beginEdit(map);
show();
}catch(Exception e){
Log.err(e);
ui.showError(Core.bundle.format("editor.errorimageload", Strings.parseException(e, false)));
ui.showError(Core.bundle.format("editor.errorload", Strings.parseException(e, false)));
}
});
}
@@ -317,16 +293,6 @@ public class MapEditorDialog extends Dialog implements Disposable{
saved = false;
}
public void updateSelectedBlock(){
Block block = editor.getDrawBlock();
for(int j = 0; j < Vars.content.blocks().size; j++){
if(block.id == j && j < blockgroup.getButtons().size){
blockgroup.getButtons().get(j).setChecked(true);
break;
}
}
}
public boolean hasPane(){
return Core.scene.getScrollFocus() == pane || Core.scene.getKeyboardFocus() != this;
}
@@ -370,15 +336,15 @@ public class MapEditorDialog extends Dialog implements Disposable{
tools.row();
ImageButton undo = tools.addImageButton("icon-undo", "clear", 16 * 2f, () -> view.undo()).get();
ImageButton redo = tools.addImageButton("icon-redo", "clear", 16 * 2f, () -> view.redo()).get();
ImageButton undo = tools.addImageButton("icon-undo", "clear", 16 * 2f, editor::undo).get();
ImageButton redo = tools.addImageButton("icon-redo", "clear", 16 * 2f, editor::redo).get();
addTool.accept(EditorTool.pick);
tools.row();
undo.setDisabled(() -> !view.getStack().canUndo());
redo.setDisabled(() -> !view.getStack().canRedo());
undo.setDisabled(() -> !editor.canUndo());
redo.setDisabled(() -> !editor.canRedo());
undo.update(() -> undo.getImage().setColor(undo.isDisabled() ? Color.GRAY : Color.WHITE));
redo.update(() -> redo.getImage().setColor(redo.isDisabled() ? Color.GRAY : Color.WHITE));
@@ -393,9 +359,9 @@ public class MapEditorDialog extends Dialog implements Disposable{
addTool.accept(EditorTool.fill);
addTool.accept(EditorTool.spray);
ImageButton rotate = tools.addImageButton("icon-arrow-16", "clear", 16 * 2f, () -> editor.setDrawRotation((editor.getDrawRotation() + 1) % 4)).get();
ImageButton rotate = tools.addImageButton("icon-arrow-16", "clear", 16 * 2f, () -> editor.rotation = (editor.rotation + 1) % 4).get();
rotate.getImage().update(() -> {
rotate.getImage().setRotation(editor.getDrawRotation() * 90);
rotate.getImage().setRotation(editor.rotation * 90);
rotate.getImage().setOrigin(Align.center);
});
@@ -415,8 +381,8 @@ public class MapEditorDialog extends Dialog implements Disposable{
button.margin(4f);
button.getImageCell().grow();
button.getStyle().imageUpColor = team.color;
button.clicked(() -> editor.setDrawTeam(team));
button.update(() -> button.setChecked(editor.getDrawTeam() == team));
button.clicked(() -> editor.drawTeam = team);
button.update(() -> button.setChecked(editor.drawTeam == team));
teamgroup.add(button);
tools.add(button);
@@ -429,7 +395,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
mid.table("underline", t -> {
Slider slider = new Slider(0, MapEditor.brushSizes.length - 1, 1, false);
slider.moved(f -> editor.setBrushSize(MapEditor.brushSizes[(int) (float) f]));
slider.moved(f -> editor.brushSize = MapEditor.brushSizes[(int) (float) f]);
t.top();
t.add("$editor.brush");
@@ -456,22 +422,28 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
}
if(Core.input.keyTap(KeyCode.ESCAPE)){
if(!menu.isShown()){
menu.show();
}
}
if(Core.input.keyTap(KeyCode.R)){
editor.setDrawRotation((editor.getDrawRotation() + 1) % 4);
editor.rotation = Mathf.mod(editor.rotation + 1, 4);
}
if(Core.input.keyTap(KeyCode.E)){
editor.setDrawRotation(Mathf.mod((editor.getDrawRotation() + 1), 4));
editor.rotation = Mathf.mod(editor.rotation - 1, 4);
}
//ctrl keys (undo, redo, save)
if(UIUtils.ctrl()){
if(Core.input.keyTap(KeyCode.Z)){
view.undo();
editor.undo();
}
if(Core.input.keyTap(KeyCode.Y)){
view.redo();
editor.redo();
}
if(Core.input.keyTap(KeyCode.S)){
@@ -498,7 +470,6 @@ public class MapEditorDialog extends Dialog implements Disposable{
pane.setFadeScrollBars(false);
pane.setOverscroll(true, false);
ButtonGroup<ImageButton> group = new ButtonGroup<>();
blockgroup = group;
int i = 0;
@@ -521,9 +492,9 @@ public class MapEditorDialog extends Dialog implements Disposable{
ImageButton button = new ImageButton("white", "clear-toggle");
button.getStyle().imageUp = new TextureRegionDrawable(region);
button.clicked(() -> editor.setDrawBlock(block));
button.clicked(() -> editor.drawBlock = block);
button.resizeImage(8 * 4f);
button.update(() -> button.setChecked(editor.getDrawBlock() == block));
button.update(() -> button.setChecked(editor.drawBlock == block));
group.add(button);
content.add(button).size(50f);
@@ -534,7 +505,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
group.getButtons().get(2).setChecked(true);
table.table("underline", extra -> extra.labelWrap(() -> editor.getDrawBlock().localizedName).width(200f).center()).growX();
table.table("underline", extra -> extra.labelWrap(() -> editor.drawBlock.localizedName).width(200f).center()).growX();
table.row();
table.add(pane).growY().fillX();
}

View File

@@ -9,14 +9,12 @@ import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.util.Disposable;
import io.anuke.arc.util.Pack;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.graphics.IndexedRenderer;
import io.anuke.mindustry.maps.MapTileData.DataPosition;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import static io.anuke.mindustry.Vars.content;
import static io.anuke.mindustry.Vars.tilesize;
public class MapRenderer implements Disposable{
@@ -102,27 +100,24 @@ public class MapRenderer implements Disposable{
private void render(int wx, int wy){
int x = wx / chunksize, y = wy / chunksize;
IndexedRenderer mesh = chunks[x][y];
byte bf = editor.getMap().read(wx, wy, DataPosition.floor);
byte bw = editor.getMap().read(wx, wy, DataPosition.wall);
byte btr = editor.getMap().read(wx, wy, DataPosition.rotationTeam);
byte rotation = Pack.leftByte(btr);
Team team = Team.all[Pack.rightByte(btr)];
Tile tile = editor.tiles()[wx][wy];
Block floor = content.block(bf);
Block wall = content.block(bw);
Team team = tile.getTeam();
Block floor = tile.floor();
Block wall = tile.block();
TextureRegion region;
int idxWall = (wx % chunksize) + (wy % chunksize) * chunksize;
int idxDecal = (wx % chunksize) + (wy % chunksize) * chunksize + chunksize * chunksize;
if(bw != 0 && (wall.synthetic() || wall == Blocks.part)){
if(wall != Blocks.air && (wall.synthetic() || wall == Blocks.part)){
region = !Core.atlas.isFound(wall.editorIcon()) ? Core.atlas.find("clear-editor") : wall.editorIcon();
if(wall.rotate){
mesh.draw(idxWall, region,
wx * tilesize + wall.offset(), wy * tilesize + wall.offset(),
region.getWidth() * Draw.scl, region.getHeight() * Draw.scl, rotation * 90 - 90);
region.getWidth() * Draw.scl, region.getHeight() * Draw.scl, tile.getRotation() * 90 - 90);
}else{
mesh.draw(idxWall, region,
wx * tilesize + wall.offset() + (tilesize - region.getWidth() * Draw.scl)/2f,
@@ -140,10 +135,12 @@ public class MapRenderer implements Disposable{
if(wall.update || wall.destructible){
mesh.setColor(team.color);
region = Core.atlas.find("block-border-editor");
}else if(!wall.synthetic() && bw != 0){
}else if(!wall.synthetic() && wall != Blocks.air){
region = !Core.atlas.isFound(wall.editorIcon()) ? Core.atlas.find("clear-editor") : wall.editorIcon();
offsetX = tilesize/2f - region.getWidth()/2f * Draw.scl;
offsetY = tilesize/2f - region.getHeight()/2f * Draw.scl;
}else if(wall == Blocks.air && tile.oreBlock() != null){
region = tile.oreBlock().editorVariantRegions()[Mathf.randomSeed(idxWall, 0, tile.oreBlock().editorVariantRegions().length-1)];;
}else{
region = Core.atlas.find("clear-editor");
}

View File

@@ -1,22 +1,20 @@
package io.anuke.mindustry.editor;
import io.anuke.arc.function.BiConsumer;
import io.anuke.arc.function.IntPositionConsumer;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.mindustry.maps.MapTileData;
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
public class MapResizeDialog extends FloatingDialog{
private static final int minSize = 50, maxSize = 500, increment = 50;
int width, height;
public MapResizeDialog(MapEditor editor, BiConsumer<Integer, Integer> cons){
public MapResizeDialog(MapEditor editor, IntPositionConsumer cons){
super("$editor.resizemap");
shown(() -> {
cont.clear();
MapTileData data = editor.getMap();
width = data.width();
height = data.height();
width = editor.width();
height = editor.height();
Table table = new Table();

View File

@@ -24,7 +24,7 @@ public class MapSaveDialog extends FloatingDialog{
shown(() -> {
cont.clear();
cont.label(() -> {
Map map = world.maps.getByName(field.getText());
Map map = world.maps.byName(field.getText());
if(map != null){
if(map.custom){
return "$editor.overwrite";
@@ -69,7 +69,7 @@ public class MapSaveDialog extends FloatingDialog{
if(field.getText().isEmpty()){
return true;
}
Map map = world.maps.getByName(field.getText());
Map map = world.maps.byName(field.getText());
return map != null && !map.custom;
}
}

View File

@@ -18,7 +18,6 @@ import io.anuke.arc.scene.event.Touchable;
import io.anuke.arc.scene.ui.TextField;
import io.anuke.arc.scene.ui.layout.Unit;
import io.anuke.arc.util.Tmp;
import io.anuke.mindustry.editor.DrawOperation.TileOperation;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.input.Binding;
import io.anuke.mindustry.ui.GridImage;
@@ -29,8 +28,9 @@ import static io.anuke.mindustry.Vars.ui;
public class MapView extends Element implements GestureListener{
private MapEditor editor;
private EditorTool tool = EditorTool.pencil;
private OperationStack stack = new OperationStack();
private DrawOperation op;
//private OperationStack stack = new OperationStack();
//private DrawOperation op;
//private GridBits used;
private Bresenham2 br = new Bresenham2();
private boolean updated = false;
private float offsetx, offsety;
@@ -87,8 +87,6 @@ public class MapView extends Element implements GestureListener{
mousex = x;
mousey = y;
op = new DrawOperation(editor.getMap());
updated = false;
Point2 p = project(x, y);
@@ -135,12 +133,7 @@ public class MapView extends Element implements GestureListener{
updated = true;
}
if(op != null && updated){
if(!op.isEmpty()){
stack.add(op);
}
op = null;
}
editor.flushOp();
if(button == KeyCode.MOUSE_MIDDLE && lastTool != null){
tool = lastTool;
@@ -190,14 +183,6 @@ public class MapView extends Element implements GestureListener{
this.tool = tool;
}
public void clearStack(){
stack.clear();
}
public OperationStack getStack(){
return stack;
}
public boolean isGrid(){
return grid;
}
@@ -206,26 +191,6 @@ public class MapView extends Element implements GestureListener{
this.grid = grid;
}
public void undo(){
if(stack.canUndo()){
stack.undo(editor);
}
}
public void redo(){
if(stack.canRedo()){
stack.redo(editor);
}
}
public void addTileOp(TileOperation t){
op.addOperation(t);
}
public boolean checkForDuplicates(short x, short y){
return op.checkDuplicate(x, y);
}
@Override
public void act(float delta){
super.act(delta);
@@ -259,14 +224,14 @@ public class MapView extends Element implements GestureListener{
}
private Point2 project(float x, float y){
float ratio = 1f / ((float) editor.getMap().width() / editor.getMap().height());
float ratio = 1f / ((float) editor.width() / editor.height());
float size = Math.min(width, height);
float sclwidth = size * zoom;
float sclheight = size * zoom * ratio;
x = (x - getWidth() / 2 + sclwidth / 2 - offsetx * zoom) / sclwidth * editor.getMap().width();
y = (y - getHeight() / 2 + sclheight / 2 - offsety * zoom) / sclheight * editor.getMap().height();
x = (x - getWidth() / 2 + sclwidth / 2 - offsetx * zoom) / sclwidth * editor.width();
y = (y - getHeight() / 2 + sclheight / 2 - offsety * zoom) / sclheight * editor.height();
if(editor.getDrawBlock().size % 2 == 0 && tool != EditorTool.eraser){
if(editor.drawBlock.size % 2 == 0 && tool != EditorTool.eraser){
return Tmp.g1.set((int) (x - 0.5f), (int) (y - 0.5f));
}else{
return Tmp.g1.set((int) x, (int) y);
@@ -274,26 +239,26 @@ public class MapView extends Element implements GestureListener{
}
private Vector2 unproject(int x, int y){
float ratio = 1f / ((float) editor.getMap().width() / editor.getMap().height());
float ratio = 1f / ((float) editor.width() / editor.height());
float size = Math.min(width, height);
float sclwidth = size * zoom;
float sclheight = size * zoom * ratio;
float px = ((float) x / editor.getMap().width()) * sclwidth + offsetx * zoom - sclwidth / 2 + getWidth() / 2;
float py = ((float) (y) / editor.getMap().height()) * sclheight
float px = ((float) x / editor.width()) * sclwidth + offsetx * zoom - sclwidth / 2 + getWidth() / 2;
float py = ((float) (y) / editor.height()) * sclheight
+ offsety * zoom - sclheight / 2 + getHeight() / 2;
return vec.set(px, py);
}
@Override
public void draw(){
float ratio = 1f / ((float) editor.getMap().width() / editor.getMap().height());
float ratio = 1f / ((float) editor.width() / editor.height());
float size = Math.min(width, height);
float sclwidth = size * zoom;
float sclheight = size * zoom * ratio;
float centerx = x + width / 2 + offsetx * zoom;
float centery = y + height / 2 + offsety * zoom;
image.setImageSize(editor.getMap().width(), editor.getMap().height());
image.setImageSize(editor.width(), editor.height());
if(!ScissorStack.pushScissors(rect.set(x, y, width, height))){
return;
@@ -318,18 +283,18 @@ public class MapView extends Element implements GestureListener{
int index = 0;
for(int i = 0; i < MapEditor.brushSizes.length; i++){
if(editor.getBrushSize() == MapEditor.brushSizes[i]){
if(editor.brushSize == MapEditor.brushSizes[i]){
index = i;
break;
}
}
float scaling = zoom * Math.min(width, height) / editor.getMap().width();
float scaling = zoom * Math.min(width, height) / editor.width();
Draw.color(Pal.accent);
Lines.stroke(Unit.dp.scl(2f));
if((!editor.getDrawBlock().isMultiblock() || tool == EditorTool.eraser) && tool != EditorTool.fill){
if((!editor.drawBlock.isMultiblock() || tool == EditorTool.eraser) && tool != EditorTool.fill){
if(tool == EditorTool.line && drawing){
Vector2 v1 = unproject(startx, starty).add(x, y);
float sx = v1.x, sy = v1.y;
@@ -348,11 +313,11 @@ public class MapView extends Element implements GestureListener{
if((tool.edit || tool == EditorTool.line) && (!mobile || drawing)){
Point2 p = project(mousex, mousey);
Vector2 v = unproject(p.x, p.y).add(x, y);
float offset = (editor.getDrawBlock().size % 2 == 0 ? scaling / 2f : 0f);
float offset = (editor.drawBlock.size % 2 == 0 ? scaling / 2f : 0f);
Lines.square(
v.x + scaling / 2f + offset,
v.y + scaling / 2f + offset,
scaling * editor.getDrawBlock().size / 2f);
scaling * editor.drawBlock.size / 2f);
}
}

View File

@@ -252,10 +252,10 @@ public interface BuilderTrait extends Entity, TeamTrait{
TileEntity core = unit.getClosestCore();
if(core == null || tile.block() != Blocks.air || dst(tile.worldx(), tile.worldy()) > mineDistance
|| tile.floor().itemDrop == null || !unit.acceptsItem(tile.floor().itemDrop) || !canMine(tile.floor().itemDrop)){
|| tile.drop() == null || !unit.acceptsItem(tile.drop()) || !canMine(tile.drop())){
setMineTile(null);
}else{
Item item = tile.floor().itemDrop;
Item item = tile.drop();
unit.rotation = Mathf.slerpDelta(unit.rotation, unit.angleTo(tile.worldx(), tile.worldy()), 0.4f);
if(Mathf.chance(Time.delta() * (0.06 - item.hardness * 0.01) * getMinePower())){

View File

@@ -269,7 +269,6 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
//TODO better smoke effect, this one is awful
if(health != 0 && health < tile.block().health && !(tile.block() instanceof Wall) &&
Mathf.chance(0.009f * Time.delta() * (1f - health / tile.block().health))){
Effects.effect(Fx.smoke, x + Mathf.range(4), y + Mathf.range(4));
}

View File

@@ -137,7 +137,7 @@ public class MinimapRenderer implements Disposable{
private int colorFor(Tile tile){
tile = tile.target();
return MapIO.colorFor(tile.floor(), tile.block(), tile.getTeam());
return MapIO.colorFor(tile.floor(), tile.block(), tile.oreBlock(), tile.getTeam());
}
@Override

View File

@@ -230,9 +230,9 @@ public abstract class InputHandler implements InputProcessor{
boolean canMine(Tile tile){
return !Core.scene.hasMouse()
&& tile.floor().itemDrop != null && tile.floor().itemDrop.hardness <= player.mech.drillPower
&& tile.drop() != null && tile.drop().hardness <= player.mech.drillPower
&& !tile.floor().playerUnmineable
&& player.acceptsItem(tile.floor().itemDrop)
&& player.acceptsItem(tile.drop())
&& tile.block() == Blocks.air && player.dst(tile.worldx(), tile.worldy()) <= Player.mineDistance;
}

View File

@@ -7,35 +7,50 @@ import io.anuke.arc.files.FileHandle;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.Pixmap;
import io.anuke.arc.graphics.Pixmap.Format;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.util.Pack;
import io.anuke.arc.util.Strings;
import io.anuke.arc.util.Structs;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.game.MappableContent;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.game.Version;
import io.anuke.mindustry.maps.Map;
import io.anuke.mindustry.maps.MapMeta;
import io.anuke.mindustry.maps.MapTileData;
import io.anuke.mindustry.maps.MapTileData.DataPosition;
import io.anuke.mindustry.maps.MapTileData.TileDataMarker;
import io.anuke.mindustry.type.ContentType;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.CachedTile;
import io.anuke.mindustry.world.LegacyColorMapper;
import io.anuke.mindustry.world.LegacyColorMapper.LegacyBlock;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.BlockPart;
import io.anuke.mindustry.world.blocks.Floor;
import java.io.*;
import java.util.Arrays;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import static io.anuke.mindustry.Vars.bufferSize;
import static io.anuke.mindustry.Vars.content;
/**
* Reads and writes map files.
*/
//TODO name mapping
/** Reads and writes map files.*/
public class MapIO{
public static final int version = 1;
private static final int[] pngHeader = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
private static final int version = 0;
private static IntIntMap defaultBlockMap = new IntIntMap();
private static ObjectMap<String, Block> missingBlocks;
private static void initBlocks(){
if(missingBlocks != null) return;
missingBlocks = ObjectMap.of(
"stained-stone", Blocks.moss
);
}
public static boolean isImage(FileHandle file){
try(InputStream stream = file.read()){
try(InputStream stream = file.read(32)){
for(int i1 : pngHeader){
if(stream.read() != i1){
return false;
@@ -47,44 +62,277 @@ public class MapIO{
}
}
private static void loadDefaultBlocks(){
for(Block block : content.blocks()){
defaultBlockMap.put(block.id, block.id);
}
public static Pixmap generatePreview(Map map) throws IOException{
Pixmap floor = new Pixmap(map.width, map.height, Format.RGBA8888);
Pixmap wall = new Pixmap(map.width, map.height, Format.RGBA8888);
int black = Color.rgba8888(Color.BLACK);
CachedTile tile = new CachedTile(){
@Override
public void setFloor(Floor type){
floor.drawPixel(x, floor.getHeight() - 1 - y, colorFor(type, Blocks.air, Blocks.air, getTeam()));
}
@Override
public void setOreByte(byte b){
if(b != 0)
floor.drawPixel(x, floor.getHeight() - 1 - y, colorFor(floor(), Blocks.air, content.block(b), getTeam()));
}
@Override
protected void changed(){
super.changed();
int c = colorFor(Blocks.air, block(), Blocks.air, getTeam());
if(c != black) wall.drawPixel(x, floor.getHeight() - 1 - y, c);
}
};
readTiles(map, (x, y) -> {
tile.x = (short)x;
tile.y = (short)y;
return tile;
});
floor.drawPixmap(wall, 0, 0);
wall.dispose();
return floor;
}
public static Pixmap generatePixmap(MapTileData data){
Pixmap pixmap = new Pixmap(data.width(), data.height(), Format.RGBA8888);
data.position(0, 0);
TileDataMarker marker = data.newDataMarker();
for(int y = 0; y < data.height(); y++){
for(int x = 0; x < data.width(); x++){
data.read(marker);
Block floor = content.block(marker.floor);
Block wall = content.block(marker.wall);
int color = colorFor(floor, wall, Team.all[marker.team]);
pixmap.drawPixel(x, pixmap.getHeight() - 1 - y, color);
public static Pixmap generatePreview(Tile[][] tiles){
Pixmap pixmap = new Pixmap(tiles.length, tiles[0].length, Format.RGBA8888);
for(int x = 0; x < pixmap.getWidth(); x++){
for(int y = 0; y < pixmap.getHeight(); y++){
Tile tile = tiles[x][y];
pixmap.drawPixel(x, pixmap.getHeight() - 1 - y, colorFor(tile.floor(), tile.block(), tile.oreBlock(), tile.getTeam()));
}
}
data.position(0, 0);
return pixmap;
}
/**Reads a pixmap in the old (3.5) map format.*/
public static MapTileData readLegacyPixmap(Pixmap pixmap){
MapTileData data = new MapTileData(pixmap.getWidth(), pixmap.getHeight());
public static int colorFor(Block floor, Block wall, Block ore, Team team){
if(wall.synthetic()){
return team.intColor;
}
return Color.rgba8888(wall.solid ? wall.color : ore == Blocks.air ? floor.color : ore.color);
}
for(int x = 0; x < data.width(); x++){
for(int y = 0; y < data.height(); y++){
public static void writeMap(FileHandle file, Map map, Tile[][] tiles) throws IOException{
OutputStream output = file.write(false, bufferSize);
{
DataOutputStream stream = new DataOutputStream(output);
stream.writeInt(version);
stream.writeInt(Version.build);
stream.writeShort(tiles.length);
stream.writeShort(tiles[0].length);
stream.writeByte((byte)map.tags.size);
for(Entry<String, String> entry : map.tags.entries()){
stream.writeUTF(entry.key);
stream.writeUTF(entry.value);
}
}
try(DataOutputStream stream = new DataOutputStream(new DeflaterOutputStream(output))){
int width = map.width, height = map.height;
SaveIO.getSaveWriter().writeContentHeader(stream);
//floor first
for(int i = 0; i < tiles.length * tiles[0].length; i++){
Tile tile = tiles[i % width][i / width];
stream.writeByte(tile.getFloorID());
stream.writeByte(tile.getOreByte());
int consecutives = 0;
for(int j = i + 1; j < width * height && consecutives < 255; j++){
Tile nextTile = tiles[j % width][j / width];
if(nextTile.getFloorID() != tile.getFloorID() || nextTile.block() != Blocks.air || nextTile.getOreByte() != tile.getOreByte()){
break;
}
consecutives++;
}
stream.writeByte(consecutives);
i += consecutives;
}
//then blocks
for(int i = 0; i < tiles.length * tiles[0].length; i++){
Tile tile = tiles[i % width][i / width];
stream.writeByte(tile.getBlockID());
if(tile.block() instanceof BlockPart){
stream.writeByte(tile.link);
}else if(tile.entity != null){
stream.writeByte(Pack.byteByte(tile.getTeamID(), tile.getRotation())); //team + rotation
stream.writeShort((short)tile.entity.health); //health
tile.entity.writeConfig(stream);
}else{
//write consecutive non-entity blocks
int consecutives = 0;
for(int j = i + 1; j < width * height && consecutives < 255; j++){
Tile nextTile = tiles[j % width][j / width];
if(nextTile.block() != tile.block()){
break;
}
consecutives++;
}
stream.writeByte(consecutives);
i += consecutives;
}
}
}
}
public static Map readMap(FileHandle file, boolean custom) throws IOException{
try(DataInputStream stream = new DataInputStream(file.read(1024))){
ObjectMap<String, String> tags = new ObjectMap<>();
//meta is uncompressed
int version = stream.readInt();
if(version == 0){
return readLegacyMap(file, custom);
}
int build = stream.readInt();
short width = stream.readShort(), height = stream.readShort();
byte tagAmount = stream.readByte();
for(int i = 0; i < tagAmount; i++){
String name = stream.readUTF();
String value = stream.readUTF();
tags.put(name, value);
}
return new Map(file, width, height, tags, custom, version, build);
}
}
/**Reads tiles from a map, version-agnostic.*/
public static void readTiles(Map map, Tile[][] tiles) throws IOException{
readTiles(map, (x, y) -> tiles[x][y]);
}
/**Reads tiles from a map, version-agnostic.*/
public static void readTiles(Map map, TileProvider tiles) throws IOException{
if(map.version == 0){
readLegacyMmapTiles(map.file, tiles);
}else if(map.version == version){
readTiles(map.file, map.width, map.height, tiles);
}else{
throw new IOException("Unknown map version. What?");
}
}
/**Reads tiles from a map in the new build-65 format.*/
private static void readTiles(FileHandle file, int width, int height, Tile[][] tiles) throws IOException{
readTiles(file, width, height, (x, y) -> tiles[x][y]);
}
/**Reads tiles from a map in the new build-65 format.*/
private static void readTiles(FileHandle file, int width, int height, TileProvider tiles) throws IOException{
try(BufferedInputStream input = file.read(bufferSize)){
//read map
{
DataInputStream stream = new DataInputStream(input);
stream.readInt(); //version
stream.readInt(); //build
stream.readInt(); //width + height
byte tagAmount = stream.readByte();
for(int i = 0; i < tagAmount; i++){
stream.readUTF(); //key
stream.readUTF(); //val
}
}
try(DataInputStream stream = new DataInputStream(new InflaterInputStream(input))){
MappableContent[][] c = SaveIO.getSaveWriter().readContentHeader(stream);
try{
content.setTemporaryMapper(c);
//read floor and create tiles first
for(int i = 0; i < width * height; i++){
int x = i % width, y = i / width;
byte floorid = stream.readByte();
byte oreid = stream.readByte();
int consecutives = stream.readUnsignedByte();
Tile tile = tiles.get(x, y);
tile.setFloor((Floor)content.block(floorid));
tile.setOreByte(oreid);
for(int j = i + 1; j < i + 1 + consecutives; j++){
int newx = j % width, newy = j / width;
Tile newTile = tiles.get(newx, newy);
newTile.setFloor((Floor)content.block(floorid));
newTile.setOreByte(oreid);
}
i += consecutives;
}
//read blocks
for(int i = 0; i < width * height; i++){
int x = i % width, y = i / width;
Block block = content.block(stream.readByte());
Tile tile = tiles.get(x, y);
tile.setBlock(block);
if(block == Blocks.part){
tile.link = stream.readByte();
}else if(tile.entity != null){
byte tr = stream.readByte();
short health = stream.readShort();
byte team = Pack.leftByte(tr);
byte rotation = Pack.rightByte(tr);
tile.setTeam(Team.all[team]);
tile.entity.health = health;
tile.setRotation(rotation);
tile.entity.readConfig(stream);
}else{ //no entity/part, read consecutives
int consecutives = stream.readUnsignedByte();
for(int j = i + 1; j < i + 1 + consecutives; j++){
int newx = j % width, newy = j / width;
tiles.get(newx, newy).setBlock(block);
}
i += consecutives;
}
}
}finally{
content.setTemporaryMapper(null);
}
}
}
}
//region legacy IO
/**Reads a pixmap in the 3.5 pixmap format.*/
public static void readLegacyPixmap(Pixmap pixmap, Tile[][] tiles){
for(int x = 0; x < pixmap.getWidth(); x++){
for(int y = 0; y < pixmap.getHeight(); y++){
int color = pixmap.getPixel(x, pixmap.getHeight() - 1 - y);
LegacyBlock block = LegacyColorMapper.get(color);
Tile tile = tiles[x][y];
data.write(x, y, DataPosition.floor, block.floor.id);
data.write(x, y, DataPosition.wall, block.wall.id);
tile.setFloor(block.floor);
tile.setBlock(block.wall);
if(block.ore != null) tile.setOre(block.ore);
//place core
if(color == Color.rgba8888(Color.GREEN)){
@@ -93,121 +341,133 @@ public class MapIO{
int worldx = dx - 1 + x;
int worldy = dy - 1 + y;
//multiblock parts
if(Structs.inBounds(worldx, worldy, pixmap.getWidth(), pixmap.getHeight())){
data.write(worldx, worldy, DataPosition.wall, Blocks.part.id);
data.write(worldx, worldy, DataPosition.rotationTeam, Pack.byteByte((byte)0, (byte)Team.blue.ordinal()));
data.write(worldx, worldy, DataPosition.link, Pack.byteByte((byte) (dx - 1 + 8), (byte) (dy - 1 + 8)));
Tile write = tiles[worldx][worldy];
write.setBlock(Blocks.part);
write.setTeam(Team.blue);
write.setLinkByte(Pack.byteByte((byte) (dx - 1 + 8), (byte) (dy - 1 + 8)));
}
}
}
data.write(x, y, DataPosition.wall, Blocks.coreShard.id);
data.write(x, y, DataPosition.rotationTeam, Pack.byteByte((byte)0, (byte)Team.blue.ordinal()));
//actual core parts
tile.setBlock(Blocks.coreShard);
tile.setTeam(Team.blue);
}
}
}
return data;
}
public static void writeMap(OutputStream stream, ObjectMap<String, String> tags, MapTileData data) throws IOException{
if(defaultBlockMap == null){
loadDefaultBlocks();
}
MapMeta meta = new MapMeta(version, tags, data.width(), data.height(), defaultBlockMap);
DataOutputStream ds = new DataOutputStream(stream);
writeMapMeta(ds, meta);
ds.write(data.toArray());
ds.close();
/**Reads a pixmap in the old 4.0 .mmap format.*/
private static void readLegacyMmapTiles(FileHandle file, Tile[][] tiles) throws IOException{
readLegacyMmapTiles(file, (x, y) -> tiles[x][y]);
}
/**
* Reads tile data, skipping meta.
*/
public static MapTileData readTileData(DataInputStream stream, boolean readOnly) throws IOException{
MapMeta meta = readMapMeta(stream);
return readTileData(stream, meta, readOnly);
}
/**Reads a mmap in the old 4.0 .mmap format.*/
private static void readLegacyMmapTiles(FileHandle file, TileProvider tiles) throws IOException{
try(DataInputStream stream = new DataInputStream(file.read(bufferSize))){
stream.readInt(); //version
byte tagAmount = stream.readByte();
for(int i = 0; i < tagAmount; i++){
stream.readUTF(); //key
stream.readUTF(); //val
}
/**
* Does not skip meta. Call after reading meta.
*/
public static MapTileData readTileData(DataInputStream stream, MapMeta meta, boolean readOnly) throws IOException{
byte[] bytes = new byte[stream.available()];
stream.readFully(bytes);
return new MapTileData(bytes, meta.width, meta.height, meta.blockMap, readOnly);
}
/**
* Reads tile data, skipping meta tags.
*/
public static MapTileData readTileData(Map map, boolean readOnly){
try(DataInputStream ds = new DataInputStream(map.stream.get())){
return MapIO.readTileData(ds, readOnly);
}catch(IOException e){
throw new RuntimeException(e);
}
}
public static MapMeta readMapMeta(DataInputStream stream) throws IOException{
ObjectMap<String, String> tags = new ObjectMap<>();
IntIntMap map = new IntIntMap();
int version = stream.readInt();
byte tagAmount = stream.readByte();
for(int i = 0; i < tagAmount; i++){
String name = stream.readUTF();
String value = stream.readUTF();
tags.put(name, value);
}
short blocks = stream.readShort();
for(int i = 0; i < blocks; i++){
short id = stream.readShort();
String name = stream.readUTF();
Block block = content.getByName(ContentType.block, name);
if(block == null){
block = Blocks.air;
initBlocks();
//block id -> real id map
IntIntMap map = new IntIntMap();
IntIntMap oreMap = new IntIntMap();
short blocks = stream.readShort();
for(int i = 0; i < blocks; i++){
short id = stream.readShort();
String name = stream.readUTF();
Block block = content.getByName(ContentType.block, name);
if(block == null){
//substitute for replacement in missingBlocks if possible
if(missingBlocks.containsKey(name)){
block = missingBlocks.get(name);
}else if(name.startsWith("ore-")){ //an ore floor combination
String[] split = name.split("-");
String itemName = split[1], floorName = Strings.join("-", Arrays.copyOfRange(split, 2, split.length));
Item item = content.getByName(ContentType.item, itemName);
Block oreBlock = item == null ? null : content.getByName(ContentType.block, "ore-" + item.name);
Block floor = missingBlocks.get(floorName, content.getByName(ContentType.block, floorName));
if(oreBlock != null && floor != null){
oreMap.put(id, oreBlock.id);
block = floor;
}else{
block = Blocks.air;
}
}else{
block = Blocks.air;
}
}
map.put(id, block.id);
}
short width = stream.readShort(), height = stream.readShort();
for(int y = 0; y < height; y++){
for(int x = 0; x < width; x++){
Tile tile = tiles.get(x, y);
byte floorb = stream.readByte();
byte blockb = stream.readByte();
byte link = stream.readByte();
byte rotTeamb = stream.readByte();
stream.readByte();//unused stuff
tile.setFloor((Floor)content.block(map.get(floorb, 0)));
tile.setBlock(content.block(map.get(blockb, 0)));
tile.setRotation(Pack.leftByte(rotTeamb));
if(tile.block().synthetic()){
tile.setTeam(Team.all[Mathf.clamp(Pack.rightByte(rotTeamb), 0, Team.all.length)]);
}
if(tile.block() == Blocks.part){
tile.setLinkByte(link);
}
if(oreMap.containsKey(floorb)){
tile.setOreByte((byte)oreMap.get(floorb, 0));
}
}
}
map.put(id, block.id);
}
int width = stream.readShort();
int height = stream.readShort();
return new MapMeta(version, tags, width, height, map);
}
public static void writeMapMeta(DataOutputStream stream, MapMeta meta) throws IOException{
stream.writeInt(meta.version);
stream.writeByte((byte) meta.tags.size);
private static Map readLegacyMap(FileHandle file, boolean custom) throws IOException{
try(DataInputStream stream = new DataInputStream(file.read(bufferSize))){
ObjectMap<String, String> tags = new ObjectMap<>();
for(Entry<String, String> entry : meta.tags.entries()){
stream.writeUTF(entry.key);
stream.writeUTF(entry.value);
int version = stream.readInt();
if(version != 0) throw new IOException("Attempted to read non-legacy map in legacy method!");
byte tagAmount = stream.readByte();
for(int i = 0; i < tagAmount; i++){
String name = stream.readUTF();
String value = stream.readUTF();
tags.put(name, value);
}
short blocks = stream.readShort();
for(int i = 0; i < blocks; i++){
stream.readShort();
stream.readUTF();
}
short width = stream.readShort(), height = stream.readShort();
//note that build 64 is the default build of all maps <65; while this can be inaccurate it's better than nothing
return new Map(file, width, height, tags, custom, 0, 64);
}
stream.writeShort(content.blocks().size);
for(Block block : content.blocks()){
stream.writeShort(block.id);
stream.writeUTF(block.name);
}
stream.writeShort(meta.width);
stream.writeShort(meta.height);
}
public static int colorFor(Block floor, Block wall, Team team){
if(wall.synthetic()){
return team.intColor;
}
return Color.rgba8888(wall.solid ? wall.color : floor.color);
//endregion
interface TileProvider{
Tile get(int x, int y);
}
}
}

View File

@@ -14,8 +14,8 @@ import io.anuke.mindustry.game.Rules;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.gen.Serialization;
import io.anuke.mindustry.type.ContentType;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.BlockPart;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -43,18 +43,37 @@ public abstract class SaveFileVersion{
}
public void writeMap(DataOutputStream stream) throws IOException{
//write world size
stream.writeShort(world.width());
stream.writeShort(world.height());
//floor first
for(int i = 0; i < world.width() * world.height(); i++){
Tile tile = world.tile(i % world.width(), i / world.width());
stream.writeByte(tile.getFloorID());
stream.writeByte(tile.getOreByte());
int consecutives = 0;
for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){
Tile nextTile = world.tile(j % world.width(), j / world.width());
if(nextTile.getFloorID() != tile.getFloorID() || nextTile.getOreByte() != tile.getOreByte()){
break;
}
consecutives++;
}
stream.writeByte(consecutives);
i += consecutives;
}
//blocks
for(int i = 0; i < world.width() * world.height(); i++){
Tile tile = world.tile(i % world.width(), i / world.width());
stream.writeByte(tile.getBlockID());
if(tile.block() instanceof BlockPart){
if(tile.block() == Blocks.part){
stream.writeByte(tile.link);
}else if(tile.entity != null){
stream.writeByte(Pack.byteByte(tile.getTeamID(), tile.getRotation())); //team + rotation
@@ -67,13 +86,14 @@ public abstract class SaveFileVersion{
tile.entity.writeConfig(stream);
tile.entity.write(stream);
}else if(tile.block() == Blocks.air){
}else{
//write consecutive non-entity blocks
int consecutives = 0;
for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){
Tile nextTile = world.tile(j % world.width(), j / world.width());
if(nextTile.getFloorID() != tile.getFloorID() || nextTile.block() != Blocks.air){
if(nextTile.block() != tile.block()){
break;
}
@@ -94,14 +114,34 @@ public abstract class SaveFileVersion{
Tile[][] tiles = world.createTiles(width, height);
//read floor and create tiles first
for(int i = 0; i < width * height; i++){
int x = i % width, y = i / width;
byte floorid = stream.readByte();
byte wallid = stream.readByte();
byte oreid = stream.readByte();
int consecutives = stream.readUnsignedByte();
Tile tile = new Tile(x, y, floorid, wallid);
tiles[x][y] = new Tile(x, y, floorid, (byte)0);
tiles[x][y].setOreByte(oreid);
if(wallid == Blocks.part.id){
for(int j = i + 1; j < i + 1 + consecutives; j++){
int newx = j % width, newy = j / width;
Tile newTile = new Tile(newx, newy, floorid, (byte)0);
newTile.setOreByte(oreid);
tiles[newx][newy] = newTile;
}
i += consecutives;
}
//read blocks
for(int i = 0; i < width * height; i++){
int x = i % width, y = i / width;
Block block = content.block(stream.readByte());
Tile tile = tiles[x][y];
tile.setBlock(block);
if(block == Blocks.part){
tile.link = stream.readByte();
}else if(tile.entity != null){
byte tr = stream.readByte();
@@ -121,19 +161,16 @@ public abstract class SaveFileVersion{
tile.entity.readConfig(stream);
tile.entity.read(stream);
}else if(wallid == 0){
}else{
int consecutives = stream.readUnsignedByte();
for(int j = i + 1; j < i + 1 + consecutives; j++){
int newx = j % width, newy = j / width;
Tile newTile = new Tile(newx, newy, floorid, wallid);
tiles[newx][newy] = newTile;
tiles[newx][newy].setBlock(block);
}
i += consecutives;
}
tiles[x][y] = tile;
}
content.setTemporaryMapper(null);

View File

@@ -48,7 +48,7 @@ public class SaveIO{
}
public static DataInputStream getSlotStream(int slot){
return new DataInputStream(new InflaterInputStream(fileFor(slot).read()));
return new DataInputStream(new InflaterInputStream(fileFor(slot).read(bufferSize)));
}
public static boolean isSaveValid(int slot){
@@ -60,7 +60,7 @@ public class SaveIO{
}
public static boolean isSaveValid(FileHandle file){
return isSaveValid(new DataInputStream(new InflaterInputStream(file.read())));
return isSaveValid(new DataInputStream(new InflaterInputStream(file.read(bufferSize))));
}
public static boolean isSaveValid(DataInputStream stream){
@@ -95,7 +95,7 @@ public class SaveIO{
}
public static void write(FileHandle file){
write(new DeflaterOutputStream(file.write(false)){
write(new DeflaterOutputStream(file.write(false, bufferSize)){
byte[] tmp = {0};
public void write(int var1) throws IOException {
@@ -120,12 +120,12 @@ public class SaveIO{
public static void load(FileHandle file) throws SaveException{
try{
//try and load; if any exception at all occurs
load(new InflaterInputStream(file.read()));
load(new InflaterInputStream(file.read(bufferSize)));
}catch(SaveException e){
e.printStackTrace();
FileHandle backup = file.sibling(file.name() + "-backup." + file.extension());
if(backup.exists()){
load(new InflaterInputStream(backup.read()));
load(new InflaterInputStream(backup.read(bufferSize)));
}else{
throw new SaveException(e.getCause());
}

View File

@@ -19,7 +19,7 @@ public class SaveMeta{
this.build = build;
this.timestamp = timestamp;
this.timePlayed = timePlayed;
this.map = world.maps.getByName(map);
this.map = world.maps.all().find(m -> m.fileName().equals(map));
this.wave = wave;
this.rules = rules;
}

View File

@@ -1,6 +1,6 @@
package io.anuke.mindustry.io.versions;
import io.anuke.arc.util.Strings;
import io.anuke.arc.collection.ObjectMap;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.game.Version;
import io.anuke.mindustry.gen.Serialization;
@@ -34,8 +34,8 @@ public class Save16 extends SaveFileVersion{
state.rules.spawns = content.<Zone>getByID(ContentType.zone, state.rules.zone).rules.get().spawns;
}
String mapname = stream.readUTF();
Map map = world.maps.getByName(mapname);
if(map == null) map = new Map(Strings.capitalize(mapname), 1, 1);
Map map = world.maps.all().find(m -> m.fileName().equals(mapname));
if(map == null) map = new Map(customMapDirectory.child(mapname), 1, 1, new ObjectMap<>(), true);
world.setMap(map);
int wave = stream.readInt();
@@ -62,7 +62,7 @@ public class Save16 extends SaveFileVersion{
//--GENERAL STATE--
Serialization.writeRules(stream, state.rules);
stream.writeUTF(world.getMap().name); //map name
stream.writeUTF(world.getMap().fileName()); //map name
stream.writeInt(state.wave); //wave
stream.writeFloat(state.wavetime); //wave countdown

View File

@@ -1,44 +1,75 @@
package io.anuke.mindustry.maps;
import io.anuke.arc.graphics.Texture;
import io.anuke.arc.Core;
import io.anuke.arc.collection.ObjectMap;
import io.anuke.arc.function.Supplier;
import java.io.InputStream;
import io.anuke.arc.files.FileHandle;
import io.anuke.arc.graphics.Texture;
import io.anuke.mindustry.io.MapIO;
public class Map{
/** Internal map name. This is the filename, without any extensions.*/
public final String name;
/** Whether this is a custom map.*/
public final boolean custom;
/** Metadata. Author description, display name, etc.*/
public final MapMeta meta;
/** Supplies a new input stream with the data of this map.*/
public final Supplier<InputStream> stream;
public final ObjectMap<String, String> tags;
/** Base file of this map.*/
public final FileHandle file;
/** Format version.*/
public final int version;
/** Map width/height, shorts.*/
public int width, height;
/** Preview texture.*/
public Texture texture;
/** Build that this map was created in. -1 = unknown or custom build.*/
public int build;
public Map(String name, MapMeta meta, boolean custom, Supplier<InputStream> streamSupplier){
this.name = name;
public Map(FileHandle file, int width, int height, ObjectMap<String, String> tags, boolean custom, int version, int build){
this.custom = custom;
this.meta = meta;
this.stream = streamSupplier;
this.tags = tags;
this.file = file;
this.width = width;
this.height = height;
this.version = version;
this.build = build;
}
public Map(String unknownName, int width, int height){
this(unknownName, new MapMeta(0, new ObjectMap<>(), width, height, null), true, () -> null);
public Map(FileHandle file, int width, int height, ObjectMap<String, String> tags, boolean custom, int version){
this(file, width, height, tags, custom, version, -1);
}
public Map(FileHandle file, int width, int height, ObjectMap<String, String> tags, boolean custom){
this(file, width, height, tags, custom, MapIO.version);
}
public String fileName(){
return file.nameWithoutExtension();
}
public String getDisplayName(){
return meta.tags.get("name", name);
return tags.get("name", fileName());
}
public String author(){
return tag("author");
}
public String description(){
return tag("description");
}
public String name(){
return tag("name");
}
public String tag(String name){
return tags.containsKey(name) && !tags.get(name).trim().isEmpty() ? tags.get(name): Core.bundle.get("unknown");
}
@Override
public String toString(){
return "Map{" +
"name='" + name + '\'' +
"file='" + file + '\'' +
", custom=" + custom +
", meta=" + meta +
", tags=" + tags +
'}';
}
}

View File

@@ -1,46 +0,0 @@
package io.anuke.mindustry.maps;
import io.anuke.arc.Core;
import io.anuke.arc.collection.IntIntMap;
import io.anuke.arc.collection.ObjectMap;
//todo: specify preferred game rules here; can be overriden
public class MapMeta{
public final int version;
public final ObjectMap<String, String> tags;
public final int width, height;
public final IntIntMap blockMap;
public MapMeta(int version, ObjectMap<String, String> tags, int width, int height, IntIntMap blockMap){
this.version = version;
this.tags = tags;
this.width = width;
this.height = height;
this.blockMap = blockMap;
}
public String author(){
return tag("author");
}
public String description(){
return tag("description");
}
public String name(){
return tag("name");
}
public String tag(String name){
return tags.containsKey(name) && !tags.get(name).trim().isEmpty() ? tags.get(name): Core.bundle.get("unknown");
}
@Override
public String toString(){
return "MapMeta{" +
"tags=" + tags +
", width=" + width +
", height=" + height +
'}';
}
}

View File

@@ -1,175 +0,0 @@
package io.anuke.mindustry.maps;
import io.anuke.arc.collection.IntIntMap;
import io.anuke.arc.util.Pack;
import io.anuke.arc.util.Structs;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.world.Block;
import java.nio.ByteBuffer;
public class MapTileData{
/**
* Tile size: 5 bytes. <br>
* 0: ground tile <br>
* 1: wall tile <br>
* 2: rotation + team <br>
* 3: link (x/y) <br>
* 4: elevation <br>
*/
private final static int TILE_SIZE = 5;
private final ByteBuffer buffer;
private final int width, height;
private final boolean readOnly;
private IntIntMap map;
public MapTileData(int width, int height){
this.width = width;
this.height = height;
this.map = null;
this.readOnly = false;
buffer = ByteBuffer.allocate(width * height * TILE_SIZE);
}
public MapTileData(byte[] bytes, int width, int height, IntIntMap mapping, boolean readOnly){
buffer = ByteBuffer.wrap(bytes);
this.width = width;
this.height = height;
this.map = mapping;
this.readOnly = readOnly;
if(mapping != null && !readOnly){
buffer.position(0);
TileDataMarker marker = new TileDataMarker();
for(int i = 0; i < width * height; i++){
read(marker);
//strip blockparts from map data, as they can be invalid
if(marker.wall == Blocks.part.id){
marker.wall = Blocks.air.id;
}
buffer.position(i * TILE_SIZE);
//write mapped marker
write(marker);
}
buffer.position(0);
for(int x = 0; x < width; x ++){
for(int y = 0; y < height; y ++){
//add missing blockparts
Block drawBlock = Vars.content.block(read(x, y, DataPosition.wall));
if(drawBlock.isMultiblock()){
int offsetx = -(drawBlock.size - 1) / 2;
int offsety = -(drawBlock.size - 1) / 2;
for(int dx = 0; dx < drawBlock.size; dx++){
for(int dy = 0; dy < drawBlock.size; dy++){
int worldx = dx + offsetx + x;
int worldy = dy + offsety + y;
if(Structs.inBounds(worldx, worldy, width, height) && !(dx + offsetx == 0 && dy + offsety == 0)){
write(worldx, worldy, DataPosition.wall, Blocks.part.id);
write(worldx, worldy, DataPosition.link, Pack.byteByte((byte) (dx + offsetx + 8), (byte) (dy + offsety + 8)));
}
}
}
}
}
}
buffer.position(0);
this.map = null;
}
}
public byte[] toArray(){
return buffer.array();
}
public int width(){
return width;
}
public int height(){
return height;
}
/**
* Write a byte to a specific position.
*/
public void write(int x, int y, DataPosition position, byte data){
buffer.put((x + width * y) * TILE_SIZE + position.ordinal(), data);
}
/**
* Gets a byte at a specific position.
*/
public byte read(int x, int y, DataPosition position){
return buffer.get((x + width * y) * TILE_SIZE + position.ordinal());
}
/**
* Reads and returns the next tile data.
*/
public TileDataMarker read(TileDataMarker marker){
marker.read(buffer);
return marker;
}
/**
* Writes this tile data marker.
*/
public void write(TileDataMarker marker){
marker.write(buffer);
}
/**
* Sets read position to the specified coordinates
*/
public void position(int x, int y){
buffer.position((x + width * y) * TILE_SIZE);
}
public TileDataMarker newDataMarker(){
return new TileDataMarker();
}
public enum DataPosition{
floor, wall, link, rotationTeam, elevation
}
public class TileDataMarker{
public byte floor, wall;
public byte link;
public byte rotation;
public byte team;
public byte elevation;
public void read(ByteBuffer buffer){
floor = buffer.get();
wall = buffer.get();
link = buffer.get();
byte rt = buffer.get();
elevation = buffer.get();
rotation = Pack.leftByte(rt);
team = Pack.rightByte(rt);
if(map != null){
floor = (byte) map.get(floor, Blocks.stone.id);
wall = (byte) map.get(wall, 0);
}
}
public void write(ByteBuffer buffer){
if(readOnly) throw new IllegalArgumentException("This data is read-only.");
buffer.put(floor);
buffer.put(wall);
buffer.put(link);
buffer.put(Pack.byteByte(rotation, team));
buffer.put(elevation);
}
}
}

View File

@@ -1,166 +1,155 @@
package io.anuke.mindustry.maps;
import io.anuke.arc.Core;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.ObjectMap;
import io.anuke.arc.files.FileHandle;
import io.anuke.arc.graphics.Texture;
import io.anuke.arc.collection.Array;
import io.anuke.arc.util.Disposable;
import io.anuke.arc.collection.ObjectMap;
import io.anuke.mindustry.io.MapIO;
import io.anuke.arc.function.Supplier;
import io.anuke.arc.util.Log;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.io.MapIO;
import io.anuke.mindustry.world.Tile;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import static io.anuke.mindustry.Vars.*;
public class Maps implements Disposable{
/**List of all built-in maps.*/
private static final String[] defaultMapNames = {};
/**Tile format version.*/
private static final int version = 0;
/** List of all built-in maps. */
private static final String[] defaultMapNames = {"impact0079"};
/** All maps stored in an ordered array. */
private Array<Map> maps = new Array<>();
/**Maps map names to the real maps.*/
private ObjectMap<String, Map> maps = new ObjectMap<>();
/**All maps stored in an ordered array.*/
private Array<Map> allMaps = new Array<>();
/**Temporary array used for returning things.*/
private Array<Map> returnArray = new Array<>();
/**Returns a list of all maps, including custom ones.*/
/** Returns a list of all maps, including custom ones. */
public Array<Map> all(){
return allMaps;
return maps;
}
/**Returns a list of only custom maps.*/
/** Returns a list of only custom maps. */
public Array<Map> customMaps(){
returnArray.clear();
for(Map map : allMaps){
if(map.custom) returnArray.add(map);
}
return returnArray;
return maps.select(m -> m.custom);
}
/**Returns a list of only default maps.*/
/** Returns a list of only default maps. */
public Array<Map> defaultMaps(){
returnArray.clear();
for(Map map : allMaps){
if(!map.custom) returnArray.add(map);
}
return returnArray;
return maps.select(m -> !m.custom);
}
/**Returns map by internal name.*/
public Map getByName(String name){
return maps.get(name);
public Map byName(String name){
return maps.find(m -> m.name().equals(name));
}
/**Loads a map from the map folder and returns it. Should only be used for zone maps.
* Does not add this map to the map list.*/
/**
* Loads a map from the map folder and returns it. Should only be used for zone maps.
* Does not add this map to the map list.
*/
public Map loadInternalMap(String name){
FileHandle file = Core.files.internal("maps/" + name + "." + mapExtension);
try(DataInputStream ds = new DataInputStream(file.read())) {
return new Map(name, MapIO.readMapMeta(ds), false, file::read);
try{
return MapIO.readMap(file, false);
}catch(IOException e){
throw new RuntimeException(e);
}
}
/**Load all maps. Should be called at application start.*/
/** Load all maps. Should be called at application start. */
public void load(){
Time.mark();
try{
for (String name : defaultMapNames) {
for(String name : defaultMapNames){
FileHandle file = Core.files.internal("maps/" + name + "." + mapExtension);
loadMap(file.nameWithoutExtension(), file::read, false);
loadMap(file, false);
}
}catch (IOException e){
}catch(IOException e){
throw new RuntimeException(e);
}
loadCustomMaps();
Log.info("Time to load maps: {0}", Time.elapsed());
}
/**Save a map. This updates all values and stored data necessary.*/
public void saveMap(String name, MapTileData data, ObjectMap<String, String> tags){
/** Save a custom map to the directory. This updates all values and stored data necessary.
* The tags are copied to prevent mutation later.*/
public void saveMap(ObjectMap<String, String> baseTags, Tile[][] data){
try{
//create copy of tags to prevent mutation later
ObjectMap<String, String> newTags = new ObjectMap<>();
newTags.putAll(tags);
tags = newTags;
ObjectMap<String, String> tags = new ObjectMap<>(baseTags);
String name = tags.get("name");
if(name == null) throw new IllegalArgumentException("Can't save a map with no name. How did this happen?");
FileHandle file = customMapDirectory.child(name + "." + mapExtension);
MapIO.writeMap(file.write(false), tags, data);
if(maps.containsKey(name)){
if(maps.get(name).texture != null) {
maps.get(name).texture.dispose();
maps.get(name).texture = null;
//find map with the same exact display name
Map other = maps.find(m -> m.getDisplayName().equals(name));
if(other != null){
//dispose of map if it's already there
if(other.texture != null){
other.texture.dispose();
other.texture = null;
}
allMaps.removeValue(maps.get(name), true);
maps.remove(other);
}
Map map = new Map(name, new MapMeta(version, tags, data.width(), data.height(), null), true, getStreamFor(name));
//create map, write it, etc etc etc
Map map = new Map(file, data.length, data[0].length, tags, true);
MapIO.writeMap(file, map, data);
if(!headless){
map.texture = new Texture(MapIO.generatePixmap(data));
map.texture = new Texture(MapIO.generatePreview(data));
}
allMaps.add(map);
maps.put(name, map);
}catch (IOException e){
maps.add(map);
}catch(IOException e){
throw new RuntimeException(e);
}
}
/**Removes a map completely.*/
/** Import a map, then save it. This updates all values and stored data necessary. */
public void importMap(FileHandle file, Map map) throws IOException{
file.copyTo(customMapDirectory.child(file.name()));
if(!headless){
map.texture = new Texture(MapIO.generatePreview(map));
}
maps.add(map);
}
/** Removes a map completely. */
public void removeMap(Map map){
if(map.texture != null){
map.texture.dispose();
map.texture = null;
}
maps.remove(map.name);
allMaps.removeValue(map, true);
customMapDirectory.child(map.name + "." + mapExtension).delete();
maps.remove(map);
map.file.delete();
}
private void loadMap(String name, Supplier<InputStream> supplier, boolean custom) throws IOException{
try(DataInputStream ds = new DataInputStream(supplier.get())) {
MapMeta meta = MapIO.readMapMeta(ds);
Map map = new Map(name, meta, custom, supplier);
private void loadMap(FileHandle file, boolean custom) throws IOException{
Map map = MapIO.readMap(file, custom);
if (!headless){
map.texture = new Texture(MapIO.generatePixmap(MapIO.readTileData(ds, meta, true)));
}
maps.put(map.name, map);
allMaps.add(map);
if(!headless){
map.texture = new Texture(MapIO.generatePreview(map));
}
maps.add(map);
}
private void loadCustomMaps(){
for(FileHandle file : customMapDirectory.list()){
try{
if(file.extension().equalsIgnoreCase(mapExtension)){
loadMap(file.nameWithoutExtension(), file::read, true);
loadMap(file, true);
}
}catch (Exception e){
}catch(Exception e){
Log.err("Failed to load custom map file '{0}'!", file);
Log.err(e);
}
}
}
/**Returns an input stream supplier for a given map name.*/
private Supplier<InputStream> getStreamFor(String name){
return customMapDirectory.child(name + "." + mapExtension)::read;
}
@Override
public void dispose() {
public void dispose(){
}
}

View File

@@ -6,8 +6,6 @@ import io.anuke.arc.util.noise.Simplex;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Floor;
import io.anuke.mindustry.world.blocks.OreBlock;
public class BasicGenerator extends RandomGenerator{
private Array<Item> ores;
@@ -31,13 +29,14 @@ public class BasicGenerator extends RandomGenerator{
public void generate(int x, int y){
floor = Blocks.stone;
if(ores != null && ((Floor) floor).hasOres){
if(ores != null){
int offsetX = x - 4, offsetY = y + 23;
for(int i = ores.size - 1; i >= 0; i--){
Item entry = ores.get(i);
if(Math.abs(0.5f - sim.octaveNoise2D(2, 0.7, 1f / (50 + i * 2), offsetX, offsetY)) > 0.23f &&
Math.abs(0.5f - sim2.octaveNoise2D(1, 1, 1f / (40 + i * 4), offsetX, offsetY)) > 0.32f){
floor = OreBlock.get(floor, entry);
//floor = OreBlock.get(floor, entry);
break;
}
}

View File

@@ -9,19 +9,17 @@ import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.content.Items;
import io.anuke.mindustry.io.MapIO;
import io.anuke.mindustry.maps.Map;
import io.anuke.mindustry.maps.MapTileData;
import io.anuke.mindustry.maps.MapTileData.TileDataMarker;
import io.anuke.mindustry.type.ItemStack;
import io.anuke.mindustry.type.Loadout;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Floor;
import io.anuke.mindustry.world.blocks.OreBlock;
import io.anuke.mindustry.world.blocks.StaticWall;
import io.anuke.mindustry.world.blocks.storage.CoreBlock;
import io.anuke.mindustry.world.blocks.storage.StorageBlock;
import static io.anuke.mindustry.Vars.content;
import java.io.IOException;
import static io.anuke.mindustry.Vars.world;
public class MapGenerator extends Generator{
@@ -73,112 +71,112 @@ public class MapGenerator extends Generator{
public void init(Loadout loadout){
this.loadout = loadout;
map = world.maps.loadInternalMap(mapName);
width = map.meta.width;
height = map.meta.height;
width = map.width;
height = map.height;
}
@Override
public void generate(Tile[][] tiles){
MapTileData data = MapIO.readTileData(map, true);
data.position(0, 0);
TileDataMarker marker = data.newDataMarker();
Array<Point2> players = new Array<>();
Array<Point2> enemies = new Array<>();
for(int y = 0; y < data.height(); y++){
for(int x = 0; x < data.width(); x++){
data.read(marker);
if(content.block(marker.wall) instanceof CoreBlock){
players.add(new Point2(x, y));
marker.wall = 0;
try{
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
tiles[x][y] = new Tile(x, y);
}
if(enemySpawns != -1 && content.block(marker.wall) == Blocks.spawn){
enemies.add(new Point2(x, y));
marker.wall = 0;
}
tiles[x][y] = new Tile(x, y, marker.floor, marker.wall == Blocks.part.id ? 0 : marker.wall, marker.rotation, marker.team);
}
}
Simplex simplex = new Simplex(Mathf.random(99999));
MapIO.readTiles(map, tiles);
Array<Point2> players = new Array<>();
Array<Point2> enemies = new Array<>();
for(int x = 0; x < data.width(); x++){
for(int y = 0; y < data.height(); y++){
final double scl = 10;
Tile tile = tiles[x][y];
int newX = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x, y) * distortion + x), 0, data.width()-1);
int newY = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x + 9999, y + 9999) * distortion + y), 0, data.height()-1);
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
if(tiles[x][y].block() instanceof CoreBlock){
players.add(new Point2(x, y));
tiles[x][y].setBlock(Blocks.air);
}
if((tile.block() instanceof StaticWall
if(tiles[x][y].block() == Blocks.spawn){
enemies.add(new Point2(x, y));
tiles[x][y].setBlock(Blocks.air);
}
}
}
Simplex simplex = new Simplex(Mathf.random(99999));
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
final double scl = 10;
Tile tile = tiles[x][y];
int newX = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x, y) * distortion + x), 0, width - 1);
int newY = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x + 9999, y + 9999) * distortion + y), 0, height - 1);
if((tile.block() instanceof StaticWall
&& tiles[newX][newY].block() instanceof StaticWall)
|| (tile.block() == Blocks.air && !tiles[newX][newY].block().synthetic())
|| (tiles[newX][newY].block() == Blocks.air && tile.block() instanceof StaticWall)){
tile.setBlock(tiles[newX][newY].block());
}
if(distortFloor){
tile.setFloor(tiles[newX][newY].floor());
}
for(Decoration decor : decorations){
if(tile.block() == Blocks.air && !(decor.wall instanceof Floor) && tile.floor() == decor.floor && Mathf.chance(decor.chance)){
tile.setBlock(decor.wall);
}else if(tile.floor() == decor.floor && decor.wall instanceof Floor && Mathf.chance(decor.chance)){
tile.setFloor((Floor)decor.wall);
tile.setBlock(tiles[newX][newY].block());
}
}
if(tile.block() instanceof StorageBlock && !(tile.block() instanceof CoreBlock)){
for(ItemStack stack : storageDrops){
if(Mathf.chance(0.3)){
tile.entity.items.add(stack.item, Math.min(Mathf.random(stack.amount), tile.block().itemCapacity));
if(distortFloor){
tile.setFloor(tiles[newX][newY].floor());
}
for(Decoration decor : decorations){
if(tile.block() == Blocks.air && !(decor.wall instanceof Floor) && tile.floor() == decor.floor && Mathf.chance(decor.chance)){
tile.setBlock(decor.wall);
}else if(tile.floor() == decor.floor && decor.wall instanceof Floor && Mathf.chance(decor.chance)){
tile.setFloor((Floor)decor.wall);
}
}
}
}
}
if(enemySpawns != -1){
if(enemySpawns > enemies.size){
throw new IllegalArgumentException("Enemy spawn pool greater than map spawn number.");
}
enemies.shuffle();
for(int i = 0; i < enemySpawns; i++){
Point2 point = enemies.get(i);
tiles[point.x][point.y].setBlock(Blocks.spawn);
int rad = 10, frad = 12;
for(int x = -rad; x <= rad; x++){
for(int y = -rad; y <= rad; y++){
int wx = x + point.x, wy = y + point.y;
double dst = Mathf.dst(x, y);
if(dst < frad && Structs.inBounds(wx, wy, tiles) && (dst <= rad || Mathf.chance(0.5))){
Tile tile = tiles[wx][wy];
if(tile.floor() instanceof OreBlock){
OreBlock block = (OreBlock)tile.floor();
tile.setFloor(block.base);
if(tile.block() instanceof StorageBlock && !(tile.block() instanceof CoreBlock)){
for(ItemStack stack : storageDrops){
if(Mathf.chance(0.3)){
tile.entity.items.add(stack.item, Math.min(Mathf.random(stack.amount), tile.block().itemCapacity));
}
}
}
}
}
if(enemySpawns != -1){
if(enemySpawns > enemies.size){
throw new IllegalArgumentException("Enemy spawn pool greater than map spawn number.");
}
enemies.shuffle();
for(int i = 0; i < enemySpawns; i++){
Point2 point = enemies.get(i);
tiles[point.x][point.y].setBlock(Blocks.spawn);
int rad = 10, frad = 12;
for(int x = -rad; x <= rad; x++){
for(int y = -rad; y <= rad; y++){
int wx = x + point.x, wy = y + point.y;
double dst = Mathf.dst(x, y);
if(dst < frad && Structs.inBounds(wx, wy, tiles) && (dst <= rad || Mathf.chance(0.5))){
Tile tile = tiles[wx][wy];
tile.clearOre();
}
}
}
}
}
Point2 core = players.random();
if(core == null){
throw new IllegalArgumentException("All zone maps must have a core.");
}
loadout.setup(core.x, core.y);
world.prepareTiles(tiles);
world.setMap(map);
}catch(IOException e){
throw new RuntimeException(e);
}
Point2 core = players.random();
if(core == null){
throw new IllegalArgumentException("All zone maps must have a core.");
}
loadout.setup(core.x, core.y);
world.prepareTiles(tiles);
world.setMap(map);
}
public static class Decoration{

View File

@@ -27,10 +27,10 @@ public class NetworkIO{
try(DataOutputStream stream = new DataOutputStream(os)){
//--GENERAL STATE--
Serialization.writeRules(stream, state.rules);
stream.writeUTF(world.getMap().name); //map name
stream.writeUTF(world.getMap().name()); //map name
//write tags
ObjectMap<String, String> tags = world.getMap().meta.tags;
ObjectMap<String, String> tags = world.getMap().tags;
stream.writeByte(tags.size);
for(Entry<String, String> entry : tags.entries()){
stream.writeUTF(entry.key);
@@ -105,7 +105,7 @@ public class NetworkIO{
//map
world.spawner.read(stream);
SaveIO.getSaveWriter().readMap(stream);
world.setMap(new Map(map, 0, 0));
world.setMap(new Map(customMapDirectory.child(map), 0, 0, new ObjectMap<>(), true));
state.teams = new Teams();
@@ -141,7 +141,7 @@ public class NetworkIO{
int maxlen = 32;
String host = (headless ? "Server" : players[0].name);
String map = world.getMap() == null ? "None" : world.getMap().name;
String map = world.getMap() == null ? "None" : world.getMap().name();
host = host.substring(0, Math.min(host.length(), maxlen));
map = map.substring(0, Math.min(map.length(), maxlen));

View File

@@ -5,9 +5,12 @@ import io.anuke.arc.collection.Array;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.game.UnlockableContent;
import io.anuke.mindustry.ui.ContentDisplay;
import io.anuke.mindustry.world.blocks.Floor;
import io.anuke.mindustry.world.blocks.OreBlock;
import static io.anuke.mindustry.Vars.content;
public class Item extends UnlockableContent implements Comparable<Item>{
public final Color color;
@@ -28,8 +31,6 @@ public class Item extends UnlockableContent implements Comparable<Item>{
* 1 cost = 1 tick added to build time
*/
public float cost = 3f;
/**Whether this item has ores generated for it.*/
public boolean genOre = false;
/**If true, item is always unlocked.*/
public boolean alwaysUnlocked = false;
@@ -101,6 +102,6 @@ public class Item extends UnlockableContent implements Comparable<Item>{
/**Allocates a new array containing all items the generate ores.*/
public static Array<Item> getAllOres(){
return Vars.content.items().select(i -> i.genOre);
return content.blocks().select(b -> b instanceof OreBlock).map(b -> ((Floor)b).itemDrop);
}
}

View File

@@ -3,13 +3,10 @@ package io.anuke.mindustry.type;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.IntMap;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.content.Items;
import io.anuke.mindustry.game.Content;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Pos;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Floor;
import io.anuke.mindustry.world.blocks.OreBlock;
import io.anuke.mindustry.world.blocks.storage.CoreBlock;
import static io.anuke.mindustry.Vars.defaultTeam;
@@ -27,7 +24,7 @@ public class Loadout extends Content{
put('2', new BlockEntry(Blocks.coreFoundation));
put('3', new BlockEntry(Blocks.coreNucleus));
put('C', new BlockEntry(Blocks.mechanicalDrill, Items.copper));
put('C', new BlockEntry(Blocks.mechanicalDrill, Blocks.oreCopper));
}};
private final IntMap<BlockEntry> blocks = new IntMap<>();
@@ -79,8 +76,7 @@ public class Loadout extends Content{
tile.setRotation((byte)entry.value.rotation);
if(entry.value.ore != null){
for(Tile t : tile.getLinkedTiles(outArray)){
Floor floor = t.floor();
t.setFloor(OreBlock.get(floor, entry.value.ore) == null ? OreBlock.get(Blocks.stone, entry.value.ore) : OreBlock.get(floor, entry.value.ore));
t.setOre(entry.value.ore);
}
}
}
@@ -93,10 +89,10 @@ public class Loadout extends Content{
static class BlockEntry{
final Block block;
final Item ore;
final Block ore;
final int rotation;
BlockEntry(Block block, Item ore){
BlockEntry(Block block, Block ore){
this.block = block;
this.ore = ore;
this.rotation = 0;

View File

@@ -106,7 +106,7 @@ public class CustomGameDialog extends FloatingDialog{
image.row();
image.add("[accent]" + map.getDisplayName()).pad(3f).growX().wrap().get().setAlignment(Align.center, Align.center);
image.row();
image.label((() -> Core.bundle.format("level.highscore", Core.settings.getInt("hiscore" + map.name, 0)))).pad(3f);
image.label((() -> Core.bundle.format("level.highscore", Core.settings.getInt("hiscore" + map.fileName(), 0)))).pad(3f);
BorderImage border = new BorderImage(map.texture, 3f);
border.setScaling(Scaling.fit);

View File

@@ -113,7 +113,7 @@ public class LoadDialog extends FloatingDialog{
button.defaults().padBottom(3);
button.row();
button.add(Core.bundle.format("save.map", color + (slot.getMap() == null ? Core.bundle.get("unknown") : slot.getMap().meta.name())));
button.add(Core.bundle.format("save.map", color + (slot.getMap() == null ? Core.bundle.get("unknown") : slot.getMap().getDisplayName())));
button.row();
button.add(Core.bundle.format("save.wave", color + slot.getWave()));
button.row();

View File

@@ -15,12 +15,8 @@ import io.anuke.mindustry.Vars;
import io.anuke.mindustry.core.Platform;
import io.anuke.mindustry.io.MapIO;
import io.anuke.mindustry.maps.Map;
import io.anuke.mindustry.maps.MapMeta;
import io.anuke.mindustry.maps.MapTileData;
import io.anuke.mindustry.ui.BorderImage;
import java.io.DataInputStream;
import static io.anuke.mindustry.Vars.*;
public class MapsDialog extends FloatingDialog{
@@ -33,22 +29,25 @@ public class MapsDialog extends FloatingDialog{
buttons.addImageTextButton("$editor.importmap", "icon-add", 14 * 2, () -> {
Platform.instance.showFileChooser("$editor.importmap", "Map File", file -> {
try{
DataInputStream stream = new DataInputStream(file.read());
MapMeta meta = MapIO.readMapMeta(stream);
MapTileData data = MapIO.readTileData(stream, meta, true);
stream.close();
Map map = MapIO.readMap(file, true);
String name = map.tags.get("name", file.nameWithoutExtension());
String name = meta.tags.get("name", file.nameWithoutExtension());
Map conflict = world.maps.all().find(m -> m.fileName().equals(file.nameWithoutExtension()) || m.name().equals(file.name()));
if(world.maps.getByName(name) != null && !world.maps.getByName(name).custom){
if(conflict != null && !conflict.custom){
ui.showError(Core.bundle.format("editor.import.exists", name));
}else if(world.maps.getByName(name) != null){
}else if(conflict != null){
ui.showConfirm("$confirm", "$editor.overwrite.confirm", () -> {
world.maps.saveMap(name, data, meta.tags);
setup();
try{
world.maps.importMap(file, map);
setup();
}catch(Exception e){
ui.showError(Core.bundle.format("editor.errorimageload", Strings.parseException(e, false)));
Log.err(e);
}
});
}else{
world.maps.saveMap(name, data, meta.tags);
world.maps.importMap(file, map);
setup();
}
@@ -89,7 +88,7 @@ public class MapsDialog extends FloatingDialog{
TextButton button = maps.addButton("", "clear", () -> showMapInfo(map)).width(mapsize).pad(8).get();
button.clearChildren();
button.margin(9);
button.add(map.meta.tags.get("name", map.name)).width(mapsize - 18f).center().get().setEllipsis(true);
button.add(map.getDisplayName()).width(mapsize - 18f).center().get().setEllipsis(true);
button.row();
button.addImage("white").growX().pad(4).color(Color.GRAY);
button.row();
@@ -129,15 +128,15 @@ public class MapsDialog extends FloatingDialog{
t.add("$editor.name").padRight(10).color(Color.GRAY).padTop(0);
t.row();
t.add(map.meta.tags.get("name", map.name)).growX().wrap().padTop(2);
t.add(map.getDisplayName()).growX().wrap().padTop(2);
t.row();
t.add("$editor.author").padRight(10).color(Color.GRAY);
t.row();
t.add(map.meta.author()).growX().wrap().padTop(2);
t.add(map.author()).growX().wrap().padTop(2);
t.row();
t.add("$editor.description").padRight(10).color(Color.GRAY).top();
t.row();
t.add(map.meta.description()).growX().wrap().padTop(2);
t.add(map.description()).growX().wrap().padTop(2);
t.row();
t.add("$editor.oregen.info").padRight(10).color(Color.GRAY);
}).height(mapsize).width(mapsize);
@@ -146,7 +145,7 @@ public class MapsDialog extends FloatingDialog{
table.addImageTextButton("$editor.openin", "icon-load-map", 16 * 2, () -> {
try{
Vars.ui.editor.beginEditMap(map.stream.get());
Vars.ui.editor.beginEditMap(map.file);
dialog.hide();
hide();
}catch(Exception e){
@@ -156,7 +155,7 @@ public class MapsDialog extends FloatingDialog{
}).fillX().height(54f).marginLeft(10);
table.addImageTextButton("$delete", "icon-trash-16", 16 * 2, () -> {
ui.showConfirm("$confirm", Core.bundle.format("map.delete", map.name), () -> {
ui.showConfirm("$confirm", Core.bundle.format("map.delete", map.name()), () -> {
world.maps.removeMap(map);
dialog.hide();
setup();

View File

@@ -13,6 +13,7 @@ import io.anuke.arc.scene.ui.ButtonGroup;
import io.anuke.arc.scene.ui.Image;
import io.anuke.arc.scene.ui.ImageButton;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.type.TileEntity;
import io.anuke.mindustry.game.EventType.UnlockEvent;
@@ -26,7 +27,6 @@ import io.anuke.mindustry.type.ItemStack;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Block.Icon;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.OreBlock;
import static io.anuke.mindustry.Vars.*;
@@ -340,6 +340,6 @@ public class PlacementFragment extends Fragment{
/** Returns the block currently being hovered over in the world. */
Block tileDisplayBlock(){
return hoverTile == null ? null : hoverTile.block().synthetic() ? hoverTile.block() : hoverTile.floor() instanceof OreBlock ? hoverTile.floor() : null;
return hoverTile == null ? null : hoverTile.block().synthetic() ? hoverTile.block() : hoverTile.oreBlock() != Blocks.air ? hoverTile.oreBlock() : null;
}
}

View File

@@ -0,0 +1,51 @@
package io.anuke.mindustry.world;
import io.anuke.arc.collection.IntMap;
import io.anuke.mindustry.entities.type.TileEntity;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.world.modules.ConsumeModule;
import io.anuke.mindustry.world.modules.ItemModule;
import io.anuke.mindustry.world.modules.LiquidModule;
import io.anuke.mindustry.world.modules.PowerModule;
/**A tile which does not trigger change events and whose entity types are cached.
* Prevents garbage when loading previews.*/
public class CachedTile extends Tile{
private static IntMap<TileEntity> entities = new IntMap<>();
public CachedTile(){
super(0, 0);
}
@Override
public Team getTeam(){
return Team.all[getTeamID()];
}
@Override
protected void preChanged(){
super.setTeam(Team.none);
}
@Override
protected void changed(){
entity = null;
Block block = block();
if(block.hasEntity()){
//cache all entity types so only one is ever created per block type. do not add it.
if(!entities.containsKey(block.id)){
TileEntity n = block.newEntity();
n.cons = new ConsumeModule(entity);
if(block.hasItems) n.items = new ItemModule();
if(block.hasLiquids) n.liquids = new LiquidModule();
if(block.hasPower) n.power = new PowerModule();
entities.put(block.id, n);
}
entity = entities.get(block.id);
}
}
}

View File

@@ -3,10 +3,8 @@ package io.anuke.mindustry.world;
import io.anuke.arc.collection.IntMap;
import io.anuke.arc.graphics.Color;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.content.Items;
import io.anuke.mindustry.game.ContentList;
import io.anuke.mindustry.world.blocks.Floor;
import io.anuke.mindustry.world.blocks.OreBlock;
public class LegacyColorMapper implements ContentList{
private static IntMap<LegacyBlock> blockMap = new IntMap<>();
@@ -38,10 +36,14 @@ public class LegacyColorMapper implements ContentList{
map("6e501e", Blocks.stainedStoneRed);
map("ed5334", Blocks.stainedStoneRed);
map("292929", Blocks.tar);
map("c3a490", OreBlock.get(Blocks.stone, Items.copper));
map("161616", OreBlock.get(Blocks.stone, Items.coal));
map("6277bc", OreBlock.get(Blocks.stone, Items.titanium));
map("83bc58", OreBlock.get(Blocks.stone, Items.thorium));
map("c3a490", Blocks.stone, Blocks.air, Blocks.oreCopper);
map("161616", Blocks.stone, Blocks.air, Blocks.oreCoal);
map("6277bc", Blocks.stone, Blocks.air, Blocks.oreTitanium);
map("83bc58", Blocks.stone, Blocks.air, Blocks.oreThorium);
}
private void map(String color, Block block, Block wall, Block ore){
blockMap.put(Color.rgba8888(Color.valueOf(color)), new LegacyBlock(block, wall, ore));
}
private void map(String color, Block block, Block wall){
@@ -55,10 +57,18 @@ public class LegacyColorMapper implements ContentList{
public static class LegacyBlock{
public final Floor floor;
public final Block wall;
public final Block ore;
public LegacyBlock(Block floor, Block wall){
this.floor = (Floor) floor;
this.wall = wall;
this.ore = null;
}
public LegacyBlock(Block floor, Block wall, Block ore){
this.floor = (Floor) floor;
this.wall = wall;
this.ore = ore;
}
}

View File

@@ -9,9 +9,10 @@ import io.anuke.arc.math.geom.Position;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.Pack;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.entities.type.TileEntity;
import io.anuke.mindustry.entities.traits.TargetTrait;
import io.anuke.mindustry.entities.type.TileEntity;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.world.blocks.BlockPart;
import io.anuke.mindustry.world.blocks.Floor;
import io.anuke.mindustry.world.modules.ConsumeModule;
@@ -39,10 +40,13 @@ public class Tile implements Position, TargetTrait{
private byte rotation;
/** Team ordinal. */
private byte team;
/**Ore that is on top of this (floor) block.*/
private byte ore = 0;
public Tile(int x, int y){
this.x = (short) x;
this.y = (short) y;
wall = floor = (Floor)Blocks.air;
}
public Tile(int x, int y, byte floor, byte wall){
@@ -159,6 +163,7 @@ public class Tile implements Position, TargetTrait{
public void setFloor(Floor type){
this.floor = type;
this.ore = 0;
}
public byte getRotation(){
@@ -213,6 +218,14 @@ public class Tile implements Position, TargetTrait{
return link != 0;
}
public byte getLinkByte(){
return link;
}
public void setLinkByte(byte b){
this.link = b;
}
/** Sets this to a linked tile, which sets the block to a part. dx and dy can only be -8-7. */
public void setLinked(byte dx, byte dy){
setBlock(Blocks.part);
@@ -316,6 +329,30 @@ public class Tile implements Position, TargetTrait{
return getTeam() == Team.none || team == getTeam();
}
public byte getOreByte(){
return ore;
}
public void setOreByte(byte ore){
this.ore = ore;
}
public void setOre(Block floor){
setOreByte(floor.id);
}
public void clearOre(){
this.ore = 0;
}
public Floor oreBlock(){
return (Floor)content.block(ore);
}
public Item drop(){
return ore == 0 ? floor.itemDrop : ((Floor)content.block(ore)).itemDrop;
}
public void updateOcclusion(){
cost = 1;
boolean occluded = false;
@@ -343,7 +380,7 @@ public class Tile implements Position, TargetTrait{
}
}
private void preChanged(){
protected void preChanged(){
block().removed(this);
if(entity != null){
entity.removeFromProximity();
@@ -351,7 +388,7 @@ public class Tile implements Position, TargetTrait{
team = 0;
}
private void changed(){
protected void changed(){
if(entity != null){
entity.remove();
entity = null;

View File

@@ -7,6 +7,7 @@ import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Geometry;
import io.anuke.arc.math.geom.Point2;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.content.StatusEffects;
import io.anuke.mindustry.entities.Effects.Effect;
@@ -45,8 +46,6 @@ public class Floor extends Block{
public Liquid liquidDrop = null;
/** item that drops from this block, used for drills */
public Item itemDrop = null;
/** Whether ores generate on this block. */
public boolean hasOres = false;
/** whether this block can be drowned in */
public boolean isLiquid;
/** Heat of this block, 0 at baseline. Used for calculating output of thermal generators.*/
@@ -110,6 +109,10 @@ public class Floor extends Block{
Mathf.random.setSeed(tile.pos());
Draw.rect(variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))], tile.worldx(), tile.worldy());
Floor floor = tile.oreBlock();
if(floor != Blocks.air){
floor.draw(tile);
}
drawEdges(tile);
}

View File

@@ -1,28 +1,18 @@
package io.anuke.mindustry.world.blocks;
import io.anuke.arc.collection.ObjectMap;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.math.Mathf;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
public class OreBlock extends Floor{
private static final ObjectMap<Item, ObjectMap<Block, Floor>> oreBlockMap = new ObjectMap<>();
public Floor base;
public OreBlock(Item ore, Floor base){
super("ore-" + ore.name + "-" + base.name);
this.localizedName = ore.localizedName() + " " + base.localizedName;
public OreBlock(Item ore){
super("ore-" + ore.name);
this.localizedName = ore.localizedName();
this.itemDrop = ore;
this.base = base;
this.variants = 3;
this.edge = base.name;
this.blendGroup = base.blendGroup;
this.color.set(ore.color);
oreBlockMap.getOr(ore, ObjectMap::new).put(base, this);
}
@Override
@@ -38,22 +28,5 @@ public class OreBlock extends Floor{
@Override
public void draw(Tile tile){
Draw.rect(variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))], tile.worldx(), tile.worldy());
drawEdges(tile);
}
@Override
public boolean doEdge(Floor floor, boolean f){
return floor != base && super.doEdge(floor, f);
}
@Override
protected boolean edgeOnto(Floor other){
return other != base;
}
public static Floor get(Block floor, Item item){
if(!oreBlockMap.containsKey(item) || !oreBlockMap.get(item).containsKey(floor)) return null;
return oreBlockMap.get(item).get(floor);
}
}

View File

@@ -18,6 +18,7 @@ import io.anuke.mindustry.entities.type.TileEntity;
import io.anuke.mindustry.graphics.Layer;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.ItemType;
import io.anuke.mindustry.ui.Bar;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
@@ -182,7 +183,7 @@ public class Drill extends Block{
}
itemArray.sort((item1, item2) -> Integer.compare(oreCount.get(item1, 0), oreCount.get(item2, 0)));
itemArray.sort((item1, item2) -> item1.genOre && !item2.genOre ? 1 : item1.genOre == item2.genOre ? 0 : -1);
itemArray.sort((item1, item2) -> Boolean.compare(item1.type == ItemType.material, item2.type == ItemType.material));
if(itemArray.size == 0){
return;
@@ -259,12 +260,12 @@ public class Drill extends Block{
}
public Item getDrop(Tile tile){
return tile.floor().itemDrop;
return tile.drop();
}
public boolean isValid(Tile tile){
if(tile == null) return false;
Item drops = tile.floor().itemDrop;
Item drops = tile.drop();
return drops != null && drops.hardness <= tier;
}