/*******************************************************************************
* Copyright (c) 2015
*
* 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.list;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import jsettlers.common.CommonConstants;
import jsettlers.common.logging.MilliStopWatch;
import jsettlers.common.map.IMapData;
import jsettlers.common.utils.collections.ChangingList;
import jsettlers.input.PlayerState;
import jsettlers.logic.constants.MatchConstants;
import jsettlers.logic.map.loading.MapLoader;
import jsettlers.logic.map.grid.GameSerializer;
import jsettlers.logic.map.grid.MainGrid;
import jsettlers.logic.map.loading.list.IMapLister.IMapListerCallable;
import jsettlers.logic.map.loading.newmap.FreshMapSerializer;
import jsettlers.logic.map.loading.newmap.MapFileHeader;
import jsettlers.logic.map.loading.newmap.MapFileHeader.MapType;
import jsettlers.logic.map.loading.newmap.RemakeMapLoader;
import jsettlers.logic.timer.RescheduleTimer;
/**
* This is the main map list.
* <p>
* It lists all available maps, and it can be used to add maps to the game.
* <p>
* TODO: load maps before they are needed, to decrease startup time.
*
* @author michael
* @author Andreas Eberle
*/
public class MapList implements IMapListerCallable {
/**
* Gives the currently used map extension for saving a map.
*
* @return
*/
public static String getMapExtension() {
return CommonConstants.USE_SAVEGAME_COMPRESSION ? MapLoader.MAP_EXTENSION_COMPRESSED : MapLoader.MAP_EXTENSION;
}
private static IMapListFactory mapListFactory = new DefaultMapListFactory();
private static MapList defaultList;
private final ArrayList<IMapLister> mapDirectories;
private final IMapLister saveDirectory;
private final ChangingList<MapLoader> freshMaps = new ChangingList<>();
private final ChangingList<RemakeMapLoader> savedMaps = new ChangingList<>();
private boolean fileListLoaded = false;
public MapList(Collection<IMapLister> mapDirectories, IMapLister saveDirectory) {
this.mapDirectories = new ArrayList<>(mapDirectories);
this.saveDirectory = saveDirectory;
}
private void loadFileList() {
freshMaps.clear();
savedMaps.clear();
for (IMapLister dir : mapDirectories) {
dir.listMaps(this);
}
}
@Override
public synchronized void foundMap(IListedMap map) {
MapLoader loader;
try {
loader = MapLoader.getLoaderForListedMap(map);
} catch (Exception e) {
System.err.println("Cought exception while loading header for " + map.getFileName());
e.printStackTrace();
return;
}
MapFileHeader mapHead = loader.getFileHeader();
// - if the map can't be load (e.g. caused by wrong format) the mapHead gets NULL! -> hide/ignore this map from user
if (mapHead != null) {
MapType type = loader.getFileHeader().getType();
if ((type == MapType.SAVED_SINGLE)) {
savedMaps.add((RemakeMapLoader) loader);
} else {
freshMaps.add(loader);
}
}
}
public synchronized ChangingList<RemakeMapLoader> getSavedMaps() {
if (!fileListLoaded) {
loadFileList();
fileListLoaded = true;
}
return savedMaps;
}
public synchronized ChangingList<MapLoader> getFreshMaps() {
if (!fileListLoaded) {
loadFileList();
fileListLoaded = true;
}
return freshMaps;
}
/**
* Gives the {@link MapLoader} for the map with the given id.
*
* @param id
* The id of the map to be found.
* @return Returns the corresponding {@link MapLoader}<br>
* or null if no map with the given id has been found.
*/
public MapLoader getMapById(String id) {
ArrayList<MapLoader> maps = new ArrayList<MapLoader>();
maps.addAll(getFreshMaps().getItems());
maps.addAll(getSavedMaps().getItems());
for (MapLoader curr : maps) {
if (curr.getMapId().equals(id)) {
return curr;
}
}
return null;
}
public MapLoader getMapByName(String mapName) {
ArrayList<MapLoader> maps = new ArrayList<MapLoader>();
maps.addAll(getFreshMaps().getItems());
maps.addAll(getSavedMaps().getItems());
for (MapLoader curr : maps) {
if (curr.getMapName().equals(mapName)) {
return curr;
}
}
return null;
}
/**
* saves a static map to the given directory.
*
* @param header
* The header to use.
* @param data
* The data to save.
* @param out
* This parameter is optional. If it is not null, the stream is used to save the map to this location. If it is null, the map is saved
* in the default location.
* @throws IOException
* If any IO error occurred.
*/
public synchronized void saveNewMap(jsettlers.logic.map.loading.newmap.MapFileHeader header, IMapData data, OutputStream out) throws IOException {
try {
if (out == null) {
out = mapDirectories.iterator().next().getOutputStream(header);
}
header.writeTo(out);
FreshMapSerializer.serialize(data, out);
} finally {
if (out != null) {
out.close();
}
}
loadFileList();
}
/**
* Saves a map to disk. The map logic should be paused while calling this method.
*
* @param playerStates
* @param grid
* @throws IOException
*/
public synchronized void saveMap(PlayerState[] playerStates, MainGrid grid) throws IOException {
MilliStopWatch watch = new MilliStopWatch();
MapFileHeader header = grid.generateSaveHeader();
OutputStream outStream = saveDirectory.getOutputStream(header);
header.writeTo(outStream);
ObjectOutputStream oos = new ObjectOutputStream(outStream);
MatchConstants.serialize(oos);
oos.writeObject(playerStates);
GameSerializer gameSerializer = new GameSerializer();
gameSerializer.save(grid, oos);
RescheduleTimer.saveTo(oos);
oos.close();
watch.stop("Writing savegame required");
loadFileList();
}
public ArrayList<MapLoader> getSavedMultiplayerMaps() {
// TODO: save multiplayer maps, so that we can load them.
return null;
}
/**
* gets the list of the default directory.
*
* @return
*/
public static synchronized MapList getDefaultList() {
if (defaultList == null) {
defaultList = mapListFactory.getMapList();
}
return defaultList;
}
public static void setDefaultListFactory(IMapListFactory factory) {
mapListFactory = factory;
defaultList = null;
}
public static class DefaultMapListFactory implements IMapListFactory {
protected ArrayList<IMapLister> directories = new ArrayList<>();
protected IMapLister saveDirectory = null;
public void addMapDirectory(String directory, boolean create) {
directories.add(new DirectoryMapLister(new File(directory), create));
}
public void addSaveDirectory(IMapLister mapLister) {
saveDirectory = mapLister;
addMapDirectory(mapLister);
}
@Override
public MapList getMapList() {
IMapLister save = getSave();
if (saveDirectory == null) {
throw new RuntimeException("Savegame directory not set.");
}
return new MapList(getMapListers(), saveDirectory);
}
public void addResourcesDirectory(File resources) {
addMapDirectory(new DirectoryMapLister(new File(resources, "maps"), true));
saveDirectory = new DirectoryMapLister(new File(resources, "save"), true);
addMapDirectory(saveDirectory);
}
protected IMapLister getSave() {
return saveDirectory;
}
public Collection<IMapLister> getMapListers() {
return directories;
}
public void addMapDirectory(IMapLister dir) {
this.directories.add(dir);
}
}
public static class ListedResourceMap implements IListedMap {
private String path;
public ListedResourceMap(String path) {
super();
this.path = path;
}
@Override
public boolean isCompressed() {
return path.endsWith(MapLoader.MAP_EXTENSION_COMPRESSED);
}
@Override
public InputStream getInputStream() throws IOException {
InputStream stream = getClass().getResourceAsStream(path);
if (stream == null) {
throw new IOException("Map not found in " + path);
}
return stream;
}
@Override
public String getFileName() {
return path.replaceFirst(".*/", "");
}
@Override
public File getFile() {
throw new UnsupportedOperationException();
}
@Override
public void delete() {
throw new UnsupportedOperationException();
}
}
}