/******************************************************************************* * Copyright (c) 2015, 2016 * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *******************************************************************************/ package jsettlers.logic.map.loading.newmap; import jsettlers.logic.player.PlayerSetting; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Date; import java.util.UUID; /** * This is a map file header. * <p> * Each map file starts with such a header. * <p> * The header format is: * <ul> * <li>4 byte: MAP<space></li> * <li>2 Byte (short): Header version. must be '1'.</li> * <li>String: Type of the map.</li> * <li>String: name</li> * <li>String: mapId</li> * <li>String: baseMapId</li> * <li>String: description</li> * <li>16 bit: width</li> * <li>16 bit: height</li> * <li>16 bit: minplayer (for saved game: number of alive players)</li> * <li>16 bit: maxplayer (for saved game: number of start players)</li> * <li>128*128*2 bytes: image of the map.</li> * <li>Some more type-dependent settings</li> * </ul> * * @author michael * @author Andreas Eberle */ public class MapFileHeader { private static final short MIN_VERSION = 1; private static final short VERSION_MAP_ID_INTRODUCED = 2; private static final short VERSION_DATE_ALWAYS_SAVED = 3; private static final short VERSION_PLAYER_CONFIGURATIONS = 4; private static final short VERSION = 4; private static final byte[] START_BYTES = new byte[] { 'M', 'A', 'P', ' ' }; public static final int PREVIEW_IMAGE_SIZE = 128; private final String name; private final String mapId; private final String baseMapId; private final String description; private final MapType type; private final short width; private final short height; private final short minPlayers; private final PlayerSetting[] playerSettings; private final Date creationDate; private final short[] previewImage; /** * The content type of a map file. * * @author michael */ public enum MapType { NORMAL, SAVED_SINGLE } public MapFileHeader(MapType type, String name, String baseMapId, String description, short width, short height, short minPlayers, short maxPlayers, Date date, short[] previewImage) { this(type, name, UUID.randomUUID().toString(), baseMapId, description, width, height, minPlayers, PlayerSetting.getUnspecifiedPlayerSettings(maxPlayers), date, previewImage); } public MapFileHeader(MapType type, String name, String baseMapId, String description, short width, short height, short minPlayers, PlayerSetting[] playerConfigurations, Date date, short[] previewImage) { this(type, name, UUID.randomUUID().toString(), baseMapId, description, width, height, minPlayers, playerConfigurations, date, previewImage); } private MapFileHeader(MapType type, String name, String mapId, String baseMapId, String description, short width, short height, short minPlayers, PlayerSetting[] playerSettings, Date date, short[] previewImage) { if (previewImage.length != PREVIEW_IMAGE_SIZE * PREVIEW_IMAGE_SIZE) { throw new IllegalArgumentException("bg image has wrong size."); } this.type = type; this.name = name; this.mapId = mapId; this.baseMapId = baseMapId; this.description = description; this.width = width; this.height = height; this.minPlayers = minPlayers; this.playerSettings = playerSettings; this.creationDate = date; this.previewImage = previewImage; } public MapType getType() { return type; } public String getName() { return name; } public String getDescription() { return description; } public Date getCreationDate() { return creationDate; } public short getWidth() { return width; } public short getHeight() { return height; } public short getMinPlayers() { return minPlayers; } public short getMaxPlayers() { return (short) playerSettings.length; } public short[] getPreviewImage() { return previewImage; } public PlayerSetting[] getPlayerSettings() { return playerSettings; } public void writeTo(OutputStream stream) throws IOException { DataOutputStream out = new DataOutputStream(stream); out.write(START_BYTES); out.writeShort(VERSION); out.writeUTF(type.toString()); out.writeUTF(name); out.writeUTF(mapId); out.writeUTF(baseMapId == null ? "" : baseMapId); out.writeUTF(description); out.writeShort(width); out.writeShort(height); out.writeShort(minPlayers); out.writeShort(playerSettings.length); for (int i = 0; i < playerSettings.length; i++) { playerSettings[i].writeTo(out); } for (int i = 0; i < PREVIEW_IMAGE_SIZE * PREVIEW_IMAGE_SIZE; i++) { out.writeShort(previewImage[i]); } out.writeLong(creationDate.getTime()); out.flush(); } /** * Reads a new file header from the stream. * * @param stream * The stream to read from. * @return */ public static MapFileHeader readFromStream(InputStream stream) throws IOException { try { DataInputStream in = new DataInputStream(stream); for (byte b : START_BYTES) { if (in.readByte() != b) { throw new IOException("Map header start is invalid."); } } final int version = in.readShort(); if (version < MIN_VERSION) { throw new IOException("Map header version is invalid."); } String typeStr = in.readUTF(); MapType type = MapType.valueOf(typeStr); String mapName = in.readUTF(); String mapId = (version < VERSION_MAP_ID_INTRODUCED) ? mapName : in.readUTF(); String baseMapId = (version < VERSION_MAP_ID_INTRODUCED) ? null : in.readUTF(); String description = in.readUTF(); short width = in.readShort(); short height = in.readShort(); short minPlayers = in.readShort(); short maxPlayers = in.readShort(); PlayerSetting[] playerConfigurations; if (version < VERSION_PLAYER_CONFIGURATIONS) { playerConfigurations = PlayerSetting.getUnspecifiedPlayerSettings(maxPlayers); } else { playerConfigurations = new PlayerSetting[maxPlayers]; for (int i = 0; i < maxPlayers; i++) { playerConfigurations[i] = PlayerSetting.readFromStream(in); } } short[] bgImage = new short[PREVIEW_IMAGE_SIZE * PREVIEW_IMAGE_SIZE]; for (int i = 0; i < PREVIEW_IMAGE_SIZE * PREVIEW_IMAGE_SIZE; i++) { bgImage[i] = in.readShort(); } Date date = null; if (version < VERSION_DATE_ALWAYS_SAVED) { if (type == MapType.SAVED_SINGLE) { date = new Date(in.readLong()); } } else { date = new Date(in.readLong()); } return new MapFileHeader(type, mapName, mapId, baseMapId, description, width, height, minPlayers, playerConfigurations, date, bgImage); } catch (Throwable t) { if (t instanceof IOException) { throw (IOException) t; } else { throw new IOException(t); } } } public String getUniqueId() { return mapId; } public String getBaseMapId() { return baseMapId; } }