Submitted for your consideration: below patch file, and below-linked example map (SLBM being a renamed ZIP file; SLBM standing for Simple Logical Background Map).
https://www.dropbox.com/s/mokjpo5nhqig9 ... .slbm?dl=0
Patch file (below) includes:
* Change to PersistenceUtils.loadMap(File):PersistedMap to test for an SLBM extension, and to load using the SLBMLoader instead of the built-in PersistedMap loader if present.
* Old PersistenceUtils.loadMap(File):PersistedMap functionality moved to new method PersistenceUtils.loadMap(PackedFile):PersistedMap
* New class org.moonshot.maptool.util.SLBMLoader added. Code is offered freely for use, with the usual assumption of credit. If you need me to provide an explicit license, expect me to forward a Creative Commons CC-BY license on request.
Patch
Code: Select all
Index: src/net/rptools/maptool/util/PersistenceUtil.java
===================================================================
--- src/net/rptools/maptool/util/PersistenceUtil.java (revision 5987)
+++ src/net/rptools/maptool/util/PersistenceUtil.java (working copy)
@@ -65,6 +65,7 @@
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
+import org.moonshot.maptool.util.SLBMLoader;
import com.caucho.hessian.io.HessianInput;
import com.thoughtworks.xstream.XStream;
@@ -171,47 +172,65 @@
}
public static PersistedMap loadMap(File mapFile) throws IOException {
- PackedFile pakFile = null;
+
try {
- pakFile = new PackedFile(mapFile);
+ if (mapFile.getName().endsWith(".slbm"))
+ {
+ return new SLBMLoader().loadMap(mapFile);
+ }
+ else
+ {
+ PackedFile pakFile = null;
+ try {
+ return loadMap(new PackedFile(mapFile));
- // Sanity check
- String progVersion = (String) pakFile.getProperty(PROP_VERSION);
- if (!versionCheck(progVersion))
- return null;
+ } finally {
+ if (pakFile != null)
+ pakFile.close();
+ }
+ }
- PersistedMap persistedMap = (PersistedMap) pakFile.getContent();
-
- // Now load up any images that we need
- loadAssets(persistedMap.assetMap.keySet(), pakFile);
-
- // FJE We only want the token's graphical data, so loop through all tokens and
- // destroy all properties and macros. Keep some fields, though. Since that type
- // of object editing doesn't belong here, we just call Token.imported() and let
- // that method Do The Right Thing.
- for (Iterator<Token> iter = persistedMap.zone.getAllTokens().iterator(); iter.hasNext();) {
- Token token = iter.next();
- token.imported();
- }
- // XXX FJE This doesn't work the way I want it to. But doing this the Right Way
- // is too much work right now. :-}
- Zone z = persistedMap.zone;
- String n = fixupZoneName(z.getName());
- z.setName(n);
- z.imported(); // Resets creation timestamp and init panel, among other things
- z.optimize(); // Collapses overlaid or redundant drawables
- return persistedMap;
} catch (ConversionException ce) {
MapTool.showError("PersistenceUtil.error.mapVersion", ce);
} catch (IOException ioe) {
MapTool.showError("PersistenceUtil.error.mapRead", ioe);
- } finally {
- if (pakFile != null)
- pakFile.close();
+ } catch (Throwable t) {
+ MapTool.showError("PersistenceUtil.error.unknown", t);
}
return null;
}
+ protected static PersistedMap loadMap(PackedFile pakFile)
+ throws Exception
+ {
+ // Sanity check
+ String progVersion = (String) pakFile.getProperty(PROP_VERSION);
+ if (!versionCheck(progVersion))
+ return null;
+
+ PersistedMap persistedMap = (PersistedMap) pakFile.getContent();
+
+ // Now load up any images that we need
+ loadAssets(persistedMap.assetMap.keySet(), pakFile);
+
+ // FJE We only want the token's graphical data, so loop through all tokens and
+ // destroy all properties and macros. Keep some fields, though. Since that type
+ // of object editing doesn't belong here, we just call Token.imported() and let
+ // that method Do The Right Thing.
+ for (Iterator<Token> iter = persistedMap.zone.getAllTokens().iterator(); iter.hasNext();) {
+ Token token = iter.next();
+ token.imported();
+ }
+ // XXX FJE This doesn't work the way I want it to. But doing this the Right Way
+ // is too much work right now. :-}
+ Zone z = persistedMap.zone;
+ String n = fixupZoneName(z.getName());
+ z.setName(n);
+ z.imported(); // Resets creation timestamp and init panel, among other things
+ z.optimize(); // Collapses overlaid or redundant drawables
+ return persistedMap;
+ }
+
/**
* Determines whether the incoming map name is unique. If it is, it's
* returned as-is. If it's not unique, a newly generated name is returned.
@@ -220,7 +239,7 @@
* name from imported map
* @return new name to use for the map
*/
- private static String fixupZoneName(String n) {
+ public static String fixupZoneName(String n) {
List<Zone> zones = MapTool.getCampaign().getZones();
for (Zone zone : zones) {
if (zone.getName().equals(n)) {
Index: src/org/moonshot/maptool/util/SLBMLoader.java
===================================================================
--- src/org/moonshot/maptool/util/SLBMLoader.java (revision 0)
+++ src/org/moonshot/maptool/util/SLBMLoader.java (working copy)
@@ -0,0 +1,219 @@
+package org.moonshot.maptool.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import net.rptools.lib.MD5Key;
+import net.rptools.maptool.client.MapTool;
+import net.rptools.maptool.model.Asset;
+import net.rptools.maptool.model.AssetManager;
+import net.rptools.maptool.model.Grid;
+import net.rptools.maptool.model.SquareGrid;
+import net.rptools.maptool.model.Token;
+import net.rptools.maptool.model.Token.TokenShape;
+import net.rptools.maptool.model.Zone;
+import net.rptools.maptool.model.Zone.Layer;
+import net.rptools.maptool.model.ZoneFactory;
+import net.rptools.maptool.util.PersistenceUtil;
+import net.rptools.maptool.util.PersistenceUtil.PersistedMap;
+
+public class SLBMLoader
+{
+ private final HashMap<String, MD5Key> assetLookup = new HashMap<String, MD5Key>();
+
+ private final PersistedMap pm;
+ private final Zone z;
+
+ private int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE;
+
+ private ArrayList<TileData> data = new ArrayList<TileData>();
+ private int tileWidth = 0, tileHeight = 0;
+ private int grid = 0;
+
+
+ protected static final class TileData
+ {
+ public final String id, name;
+ public final int x, y, rot;
+ public final boolean fh, fv;
+
+ public TileData(String[] aParts) {
+ x = Integer.valueOf(aParts[1]);
+ y = Integer.valueOf(aParts[2]);
+ rot = Integer.valueOf(aParts[3]);
+ fh = aParts[4].indexOf('h') > -1 || aParts[4].equals("y");
+ fv = aParts[4].indexOf('v') > -1;
+ id = aParts[5];
+
+ if( aParts.length >= 7 ) {
+ name = aParts[6];
+ } else {
+ name = x+","+y;
+ }
+ }
+ }
+
+
+ public SLBMLoader()
+ {
+ pm = new PersistedMap();
+ pm.zone = z = ZoneFactory.createZone();
+ pm.assetMap = new HashMap<MD5Key, Asset>();
+ pm.mapToolVersion = MapTool.getVersion();
+ }
+
+ public PersistedMap loadMap(File mapFile)
+ throws Exception
+ {
+ ZipFile zf = new ZipFile(mapFile);
+ ZipEntry maptext = zf.getEntry("map.txt");
+
+ try {
+ readMapData(new InputStreamReader(zf.getInputStream(maptext)));
+ } catch (Throwable t) {
+ //TODO Report
+ return null;
+ }
+
+ if (data.size() == 0) {
+ //TODO Report
+ return null;
+ }
+
+ if (tileWidth <= 0 || tileHeight <= 0) {
+ //TODO Report
+ return null;
+ }
+
+ // Set grid size, if one was given.
+ if (grid > 1) {
+ // XXX Dear Fellow MapTools Devs: I've no idea why this doesn't work.
+ Grid g = new SquareGrid();
+ g.setFacings(true, true);
+ g.setOffset(0, 0);
+ g.setSize(grid);
+ z.setGrid(g);
+ }
+
+ for (TileData td : data)
+ {
+ MD5Key assetKey = assetLookup.get(td.id);
+ if (assetKey == null) {
+ assetKey = importAsset(zf, td.id);
+
+ if (assetKey == null) {
+ //TODO Report
+ return null;
+ }
+
+ assetLookup.put(td.id, assetKey);
+ }
+
+ // Make new Token
+ Token tile = new Token(td.name, assetKey);
+
+ // Standard config
+ tile.setLayer(Layer.BACKGROUND);
+ tile.setShape(TokenShape.TOP_DOWN);
+ tile.setSnapToGrid(true);
+ tile.setSnapToScale(false);
+ tile.setVisible(true);
+
+ // Plotted config
+ tile.setX((td.x - minX + 1) * tileWidth);
+ tile.setY((td.y - minY + 1) * tileHeight);
+ tile.setFlippedX(td.fh);
+ tile.setFlippedY(td.fv);
+ tile.setWidth(tileWidth);
+ tile.setHeight(tileHeight);
+ tile.setFacing(td.rot * -90 - 90);
+
+ // Add to Zone
+ z.putToken(tile);
+ }
+
+ z.setName(PersistenceUtil.fixupZoneName(mapFile.getName()));
+ z.imported(); // Resets creation timestamp and init panel, among other things
+ z.optimize(); // Collapses overlaid or redundant drawables
+ return pm;
+ }
+
+ private void readMapData(Reader aReader)
+ throws Throwable
+ {
+ BufferedReader br = new BufferedReader(aReader);
+ try {
+ String line;
+ while (null != (line = br.readLine()))
+ {
+ line = line.trim();
+ if (line.length() > 0 && line.charAt(0) != '#')
+ {
+ String[] parts = line.split("\t+");
+
+ if ("grid".equals(parts[0]))
+ {
+ grid = Integer.parseInt(parts[1]);
+ }
+ else if ("size".equals(parts[0]))
+ {
+ tileWidth = Integer.parseInt(parts[1]);
+ tileHeight = Integer.parseInt(parts[2]);
+ }
+ else if ("plot".equals(parts[0]))
+ {
+ TileData td = new TileData(parts);
+ data.add(td);
+
+ if (td.x < minX)
+ minX = td.x;
+ if (td.y < minY)
+ minY = td.y;
+ }
+ }
+ }
+
+ } finally {
+ br.close();
+ }
+ }
+
+ private MD5Key importAsset(ZipFile zf, String id)
+ throws IOException
+ {
+ ZipEntry ze = zf.getEntry("assets/" + id);
+ InputStream is = zf.getInputStream(ze);
+ try {
+ long size = ze.getSize();
+ byte[] buffer = new byte[(int) size];
+ int pos = 0;
+ while (pos < size) {
+ int num = is.read(buffer, pos, (int) (size - pos));
+ if (num < 0)
+ return null;
+ pos += num;
+ }
+
+ MD5Key key = new MD5Key(buffer);
+ Asset asset = new Asset(key.toString(), buffer);
+
+ // XXX Dear Fellow MapTools Devs: is it more efficient to do this in batches?
+ AssetManager.putAsset(asset);
+ MapTool.serverCommand().putAsset(asset);
+
+ return key;
+
+ } finally {
+ is.close();
+ }
+ }
+
+}
An SLBM file is a renamed ZIP, containing:
* map.txt - map description
* assets/ - folder containing images
Map content format follows (plain text file "map.txt" inside the SLBM)...
Code: Select all
# Parser ignores blank lines and lines STARTING with hash (#)
# All declarations (lines of tab-delimited fields) are case-sensitive.
# Size; declares pixel dimensions of tiles. Non-square tiles are supported.
size 900 900
# Grid; if present, sets map grid to given pixel size. If not present, leaves default.
grid 150
# Each PLOT places a tile.
# Parameters are X, Y, R, F, Asset.
# X,Y: coordinates, in whole tiles. The map will be centered on 0,0 during load.
# R: rotation in 90° clockwise steps. 0 is "original" rotation, 1,2,3 are other accepted values.
# F: 'n', 'h', 'v', 'hv', or 'vh'. Flips the tile not at all, horizontally, vertically, or both. Flips are applied before rotation, as normal for MapTool.
# Asset: the name of an image in the assets/ directory to use. Each one will be given a MapTool MD5 ID, and only loaded once no matter how many times it is mentioned in the map.
plot 0 0 0 n RT05.jpg
plot 1 0 2 h RT08.jpg
plot 0 1 3 h RT10.jpg
plot 1 1 0 n RT16.jpg
# Specifications to consider, but which are not yet supported:
# option degrees - enables 1-degree rotation steps, instead of 90°
# option pixels - coordinates are in pixels, not tiles
# option layer X - enables multi-layer support, and selects layer #X as current.
# Each layer has its own SIZE and PLOTS.
# Layers are sorted from lowest integer index to highest.