Files
Mindustry/core/src/mindustry/io/MapIO.java
2025-07-18 11:22:11 -04:00

210 lines
8.1 KiB
Java

package mindustry.io;
import arc.files.*;
import arc.graphics.*;
import arc.struct.*;
import arc.util.io.*;
import mindustry.content.*;
import mindustry.core.*;
import mindustry.game.*;
import mindustry.maps.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import mindustry.world.blocks.storage.*;
import java.io.*;
import java.util.zip.*;
import static mindustry.Vars.*;
/** Reads and writes map files. */
public class MapIO{
private static final int[] pngHeader = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
public static boolean isImage(Fi file){
try(InputStream stream = file.read(32)){
for(int i1 : pngHeader){
if(stream.read() != i1){
return false;
}
}
return true;
}catch(IOException e){
return false;
}
}
public static Map createMap(Fi file, boolean custom) throws IOException{
try(InputStream is = new InflaterInputStream(file.read(bufferSize)); CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){
SaveIO.readHeader(stream);
int version = stream.readInt();
SaveVersion ver = SaveIO.getSaveWriter(version);
if(ver == null) throw new IOException("Unknown save version: " + version + ". Are you trying to load a save from a newer version?");
StringMap tags = new StringMap();
ver.region("meta", stream, counter, in -> tags.putAll(ver.readStringMap(in)));
return new Map(file, tags.getInt("width"), tags.getInt("height"), tags, custom, version, Version.build);
}
}
public static void writeMap(Fi file, Map map) throws IOException{
try{
SaveIO.write(file, map.tags);
}catch(Exception e){
throw new IOException(e);
}
}
public static void loadMap(Map map){
SaveIO.load(map.file);
}
public static void loadMap(Map map, WorldContext cons){
SaveIO.load(map.file, cons);
}
public static Pixmap generatePreview(Map map) throws IOException{
map.spawns = 0;
map.teams.clear();
try(InputStream is = new InflaterInputStream(map.file.read(bufferSize)); CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){
SaveIO.readHeader(stream);
int version = stream.readInt();
SaveVersion ver = SaveIO.getSaveWriter(version);
if(ver == null) throw new IOException("Unknown save version: " + version + ". Are you trying to load a save from a newer version?");
ver.region("meta", stream, counter, ver::readStringMap);
Pixmap floors = new Pixmap(map.width, map.height);
Pixmap walls = new Pixmap(map.width, map.height);
int black = 255;
int shade = Color.rgba8888(0f, 0f, 0f, 0.5f);
CachedTile tile = new CachedTile(){
@Override
public void setBlock(Block type){
super.setBlock(type);
int c = colorFor(block(), Blocks.air, Blocks.air, team());
if(c != black){
walls.setRaw(x, floors.height - 1 - y, c);
floors.set(x, floors.height - 1 - y + 1, shade);
}
}
};
ver.region("content", stream, counter, ver::readContentHeader);
ver.region("preview_map", stream, counter, in -> ver.readMap(in, new WorldContext(){
@Override public void resize(int width, int height){}
@Override public boolean isGenerating(){return false;}
@Override public void begin(){
world.setGenerating(true);
}
@Override public void end(){
world.setGenerating(false);
}
@Override
public void onReadBuilding(){
//read team colors
if(tile.build != null){
int c = tile.build.team.color.rgba8888();
int size = tile.block().size;
int offsetx = -(size - 1) / 2;
int offsety = -(size - 1) / 2;
for(int dx = 0; dx < size; dx++){
for(int dy = 0; dy < size; dy++){
int drawx = tile.x + dx + offsetx, drawy = tile.y + dy + offsety;
walls.set(drawx, floors.height - 1 - drawy, c);
}
}
if(tile.build.block instanceof CoreBlock){
map.teams.add(tile.build.team.id);
}
}
}
@Override
public Tile tile(int index){
tile.x = (short)(index % map.width);
tile.y = (short)(index / map.width);
return tile;
}
@Override
public Tile create(int x, int y, int floorID, int overlayID, int wallID){
if(overlayID != 0){
floors.set(x, floors.height - 1 - y, colorFor(Blocks.air, Blocks.air, content.block(overlayID), Team.derelict));
}else{
floors.set(x, floors.height - 1 - y, colorFor(Blocks.air, content.block(floorID), Blocks.air, Team.derelict));
}
if(content.block(overlayID) == Blocks.spawn){
map.spawns ++;
}
return tile;
}
}));
floors.draw(walls, true);
walls.dispose();
return floors;
}finally{
content.setTemporaryMapper(null);
}
}
public static Pixmap generatePreview(Tiles tiles){
Pixmap pixmap = new Pixmap(tiles.width, tiles.height);
for(int x = 0; x < pixmap.width; x++){
for(int y = 0; y < pixmap.height; y++){
Tile tile = tiles.getn(x, y);
pixmap.set(x, pixmap.height - 1 - y, colorFor(tile.block(), tile.floor(), tile.overlay(), tile.team()));
}
}
return pixmap;
}
public static int colorFor(Block wall, Block floor, Block overlay, Team team){
if(wall.synthetic()){
return team.color.rgba();
}
return (((Floor)overlay).wallOre ? overlay.mapColor : wall.solid ? wall.mapColor : !overlay.useColor ? floor.mapColor : overlay.mapColor).rgba();
}
public static Pixmap writeImage(Tiles tiles){
Pixmap pix = new Pixmap(tiles.width, tiles.height);
for(Tile tile : tiles){
//while synthetic blocks are possible, most of their data is lost, so in order to avoid questions like
//"why is there air under my drill" and "why are all my conveyors facing right", they are disabled
int color = tile.block().hasColor && !tile.block().hasBuilding() ? tile.block().mapColor.rgba() : tile.floor().mapColor.rgba();
pix.set(tile.x, tiles.height - 1 - tile.y, color);
}
return pix;
}
public static void readImage(Pixmap pixmap, Tiles tiles){
for(Tile tile : tiles){
int color = pixmap.get(tile.x, pixmap.height - 1 - tile.y);
Block block = ColorMapper.get(color);
//ignore buildings; reading images is only intended for environment tiles
if(block.hasBuilding()) continue;
if(block.isOverlay()){
tile.setOverlay(block.asFloor());
}else if(block.isFloor()){
tile.setFloor(block.asFloor());
}else if(block.isMultiblock()){
tile.setBlock(block, Team.derelict, 0);
}else{
tile.setBlock(block);
}
}
for(Tile tile : tiles){
//default to stone floor
if(tile.floor() == Blocks.air){
tile.setFloorUnder((Floor)Blocks.stone);
}
}
}
}