Split Nenya into proper Maven submodules (same treatment Narya got).
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.threerings</groupId>
|
||||
<artifactId>nenya-parent</artifactId>
|
||||
<version>1.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>nenya-tools</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Nenya Tools</name>
|
||||
|
||||
<dependencies>
|
||||
<!-- exported dependencies -->
|
||||
<dependency>
|
||||
<groupId>com.threerings</groupId>
|
||||
<artifactId>nenya</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.threerings</groupId>
|
||||
<artifactId>narya</artifactId>
|
||||
<version>${narya.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-digester</groupId>
|
||||
<artifactId>commons-digester</artifactId>
|
||||
<version>1.8</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.megginson.sax</groupId>
|
||||
<artifactId>xml-writer</artifactId>
|
||||
<version>0.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- test/build dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.apache.ant</groupId>
|
||||
<artifactId>ant</artifactId>
|
||||
<version>1.7.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.10</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>com/threerings/**/*Test.java</include>
|
||||
</includes>
|
||||
<!-- we have to skip these tests as they depend on resources being -->
|
||||
<!-- prepared which are too fiddly to get working via Maven -->
|
||||
<excludes>
|
||||
<exclude>com/threerings/**/BundledComponentRepositoryTest.java</exclude>
|
||||
<exclude>com/threerings/**/BundledTileSetRepositoryTest.java</exclude>
|
||||
</excludes>
|
||||
<systemPropertyVariables>
|
||||
<resource_dir>target/test-classes/rsrc</resource_dir>
|
||||
<no_unpack_resources>true</no_unpack_resources>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,666 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast.bundle.tools;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.DirectoryScanner;
|
||||
import org.apache.tools.ant.Task;
|
||||
import org.apache.tools.ant.types.FileSet;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.util.ComparableArrayList;
|
||||
import com.samskivert.util.FileUtil;
|
||||
import com.samskivert.util.HashIntMap;
|
||||
import com.samskivert.util.Tuple;
|
||||
|
||||
import com.threerings.media.tile.ImageProvider;
|
||||
import com.threerings.media.tile.SimpleCachingImageProvider;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.TrimmedTileSet;
|
||||
|
||||
import com.threerings.cast.ComponentIDBroker;
|
||||
import com.threerings.cast.StandardActions;
|
||||
import com.threerings.cast.bundle.BundleUtil;
|
||||
|
||||
/**
|
||||
* Ant task for creating component bundles. This task must be configured
|
||||
* with a number of parameters:
|
||||
*
|
||||
* <pre>
|
||||
* target=[path to bundle file, which will be created]
|
||||
* mapfile=[path to the component map file which maintains a mapping from
|
||||
* component id to component class/name, it will be created the
|
||||
* first time and updated as new components are mapped]
|
||||
* </pre>
|
||||
*
|
||||
* It should also contain one or more nested <fileset> elements that
|
||||
* enumerate the action tileset images that should be included in the
|
||||
* component bundle.
|
||||
*/
|
||||
public class ComponentBundlerTask extends Task
|
||||
{
|
||||
/**
|
||||
* Sets the path to the bundle file that we'll be creating.
|
||||
*/
|
||||
public void setTarget (File target)
|
||||
{
|
||||
_target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path to the component map file that we'll use to obtain
|
||||
* component ids for the bundled components.
|
||||
*/
|
||||
public void setMapfile (File mapfile)
|
||||
{
|
||||
_mapfile = mapfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path to the action tilesets definition file.
|
||||
*/
|
||||
public void setActiondef (File actiondef)
|
||||
{
|
||||
_actionDef = actiondef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the root path which will be stripped from the image paths
|
||||
* prior to parsing them to obtain the component class, type and
|
||||
* action names.
|
||||
*/
|
||||
public void setRoot (File root)
|
||||
{
|
||||
_root = root.getPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a nested <fileset> element.
|
||||
*/
|
||||
public void addFileset (FileSet set)
|
||||
{
|
||||
_filesets.add(set);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note whether we are supposed to use the raw png files directly in the bundle or try to
|
||||
* re-encode them.
|
||||
*/
|
||||
public void setKeepRawPngs (boolean keep)
|
||||
{
|
||||
_keepRawPngs = keep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note whether we are supposed to leave the jar uncompressed rather than the normal process
|
||||
* of zipping it at maximum compression.
|
||||
*/
|
||||
public void setUncompressed (boolean uncompressed)
|
||||
{
|
||||
_uncompressed = uncompressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the actual work of the task.
|
||||
*/
|
||||
@Override
|
||||
public void execute () throws BuildException
|
||||
{
|
||||
// make sure everything was set up properly
|
||||
ensureSet(_target, "Must specify the path to the target bundle " +
|
||||
"file via the 'target' attribute.");
|
||||
ensureSet(_mapfile, "Must specify the path to the component map " +
|
||||
"file via the 'mapfile' attribute.");
|
||||
ensureSet(_actionDef, "Must specify the action sequence " +
|
||||
"definitions via the 'actiondef' attribute.");
|
||||
|
||||
// parse in the action tilesets
|
||||
Map<String, TileSet> actsets;
|
||||
try {
|
||||
actsets = ComponentBundlerUtil.parseActionTileSets(_actionDef);
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
throw new BuildException("Unable to load action definition file " +
|
||||
"[path=" + _actionDef.getPath() + "].", fnfe);
|
||||
} catch (Exception e) {
|
||||
throw new BuildException("Parsing error.", e);
|
||||
}
|
||||
|
||||
// load up our component ID broker
|
||||
ComponentIDBroker broker = loadBroker(_mapfile);
|
||||
|
||||
// check to see if any of the source files are newer than the
|
||||
// target file
|
||||
ArrayList<Object> sources = Lists.newArrayList();
|
||||
sources.addAll(_filesets);
|
||||
sources.add(_mapfile);
|
||||
sources.add(_actionDef);
|
||||
long newest = getNewestDate(sources);
|
||||
if (skipIfTargetNewer() && newest < _target.lastModified()) {
|
||||
System.out.println(_target.getPath() + " is up to date.");
|
||||
return;
|
||||
}
|
||||
|
||||
// create an image provider for loading our component images
|
||||
ImageProvider improv = new SimpleCachingImageProvider() {
|
||||
@Override
|
||||
protected BufferedImage loadImage (String path)
|
||||
throws IOException {
|
||||
return ImageIO.read(new File(path));
|
||||
}
|
||||
};
|
||||
|
||||
System.out.println("Generating " + _target + "...");
|
||||
|
||||
try {
|
||||
// make sure we can create our bundle file
|
||||
OutputStream fout = createOutputStream(_target);
|
||||
|
||||
// we'll fill this with component id to tuple mappings
|
||||
HashIntMap<Tuple<String, String>> mapping = new HashIntMap<Tuple<String, String>>();
|
||||
|
||||
// deal with the filesets
|
||||
for (FileSet fs : _filesets) {
|
||||
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
|
||||
File fromDir = fs.getDir(getProject());
|
||||
String[] srcFiles = ds.getIncludedFiles();
|
||||
|
||||
for (String srcFile : srcFiles) {
|
||||
File cfile = new File(fromDir, srcFile);
|
||||
// determine the [class, name, action] triplet
|
||||
String[] info = decomposePath(cfile.getPath());
|
||||
|
||||
// make sure we have an action tileset definition
|
||||
TileSet aset = actsets.get(info[2]);
|
||||
if (aset == null) {
|
||||
System.err.println(
|
||||
"No tileset definition for component action '" + info[2] +
|
||||
"' [class=" + info[0] + ", name=" + info[1] + "].");
|
||||
continue;
|
||||
}
|
||||
aset.setImageProvider(improv);
|
||||
|
||||
// obtain the component id from our id broker
|
||||
int cid = broker.getComponentID(info[0], info[1]);
|
||||
// add a mapping for this component
|
||||
mapping.put(cid, new Tuple<String, String>(info[0], info[1]));
|
||||
|
||||
// process and store the main component image
|
||||
processComponent(info, aset, cfile, fout, newest);
|
||||
|
||||
// pick up any auxiliary images as well like the shadow or
|
||||
// crop files
|
||||
String action = info[2];
|
||||
String ext = BundleUtil.IMAGE_EXTENSION;
|
||||
for (String element : AUX_EXTS) {
|
||||
File afile = new File(
|
||||
FileUtil.resuffix(cfile, ext, element + ext));
|
||||
if (afile.exists()) {
|
||||
info[2] = action + element;
|
||||
processComponent(info, aset, afile, fout, newest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write our mapping table to the jar file as well
|
||||
if (!skipEntry(BundleUtil.COMPONENTS_PATH, newest)) {
|
||||
fout = nextEntry(fout, BundleUtil.COMPONENTS_PATH);
|
||||
ObjectOutputStream oout = new ObjectOutputStream(fout);
|
||||
oout.writeObject(mapping);
|
||||
oout.flush();
|
||||
}
|
||||
|
||||
if (fout != null) {
|
||||
// seal up our jar file if we created one
|
||||
fout.close();
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
String errmsg = "Unable to create component bundle.";
|
||||
throw new BuildException(errmsg, ioe);
|
||||
|
||||
} catch (PersistenceException pe) {
|
||||
String errmsg = "Unable to obtain component ID mapping.";
|
||||
throw new BuildException(errmsg, pe);
|
||||
}
|
||||
|
||||
// save our updated component ID broker
|
||||
saveBroker(_mapfile, broker);
|
||||
}
|
||||
|
||||
protected void processComponent (
|
||||
String[] info, TileSet aset, File cfile, OutputStream fout, long newest)
|
||||
throws IOException, BuildException
|
||||
{
|
||||
// construct the path that'll go in the jar file
|
||||
String ipath = composePath(
|
||||
info, BundleUtil.IMAGE_EXTENSION);
|
||||
|
||||
// If we decide that the entry is up to date and we don't need to process it, bail out.
|
||||
if (skipEntry(ipath, newest)) {
|
||||
return;
|
||||
}
|
||||
|
||||
fout = nextEntry(fout, ipath);
|
||||
|
||||
aset.setImagePath(cfile.getPath());
|
||||
|
||||
TileSet tset;
|
||||
if (_keepRawPngs) {
|
||||
// We've elected to keep the pngs as they are and just stuff them into the jar.
|
||||
try {
|
||||
tset = aset;
|
||||
BufferedImage image = aset.getRawTileSetImage();
|
||||
ImageIO.write(image, "png", fout);
|
||||
} catch (Throwable t) {
|
||||
System.err.println(
|
||||
"Failure storing tileset in jar" +
|
||||
"[class=" + info[0] + ", name=" + info[1] +
|
||||
", action=" + info[2] +
|
||||
", srcimg=" + aset.getImagePath() + "].");
|
||||
t.printStackTrace(System.err);
|
||||
|
||||
String errmsg = "Failure trimming tileset.";
|
||||
throw new BuildException(errmsg, t);
|
||||
}
|
||||
} else {
|
||||
// create a trimmed tileset based on the source action tileset and
|
||||
// stuff the new trimmed image into the jar file at the same time
|
||||
try {
|
||||
tset = trim(aset, fout);
|
||||
tset.setImagePath(ipath);
|
||||
} catch (Throwable t) {
|
||||
System.err.println(
|
||||
"Failure trimming tileset " +
|
||||
"[class=" + info[0] + ", name=" + info[1] +
|
||||
", action=" + info[2] +
|
||||
", srcimg=" + aset.getImagePath() + "].");
|
||||
t.printStackTrace(System.err);
|
||||
|
||||
String errmsg = "Failure trimming tileset.";
|
||||
throw new BuildException(errmsg, t);
|
||||
}
|
||||
}
|
||||
// then write our trimmed tileset bundle data
|
||||
String tpath = composePath(info, BundleUtil.TILESET_EXTENSION);
|
||||
if (!skipEntry(tpath, newest) && !_keepRawPngs) {
|
||||
fout = nextEntry(fout, tpath);
|
||||
|
||||
ObjectOutputStream oout = new ObjectOutputStream(fout);
|
||||
oout.writeObject(tset);
|
||||
oout.flush();
|
||||
}
|
||||
}
|
||||
|
||||
protected long getNewestDate (ArrayList<Object> sources)
|
||||
{
|
||||
long newest = 0L;
|
||||
for (int ii = 0; ii < sources.size(); ii++) {
|
||||
newest = Math.max(newest, getNewestDate(sources.get(ii)));
|
||||
}
|
||||
return newest;
|
||||
}
|
||||
|
||||
protected long getNewestDate (Object source)
|
||||
{
|
||||
if (source instanceof FileSet) {
|
||||
FileSet fs = (FileSet)source;
|
||||
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
|
||||
File fromDir = fs.getDir(getProject());
|
||||
String[] srcFiles = ds.getIncludedFiles();
|
||||
long newest = 0L;
|
||||
for (String srcFile : srcFiles) {
|
||||
File cfile = new File(fromDir, srcFile);
|
||||
newest = Math.max(newest, cfile.lastModified());
|
||||
}
|
||||
return newest;
|
||||
|
||||
} else if (source instanceof File) {
|
||||
return ((File)source).lastModified();
|
||||
|
||||
} else {
|
||||
System.err.println("Can't get newest date for source: " + source + ".");
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we should skip updating the bundle if the target is newer than any component.
|
||||
*/
|
||||
protected boolean skipIfTargetNewer ()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decomposes the full path to a component image into a [class, name,
|
||||
* action] triplet.
|
||||
*/
|
||||
protected String[] decomposePath (String path)
|
||||
throws BuildException
|
||||
{
|
||||
// first strip off the root
|
||||
if (!path.startsWith(_root)) {
|
||||
throw new BuildException("Can't bundle images outside the root directory " +
|
||||
"[root=" + _root + ", path=" + path + "].");
|
||||
}
|
||||
path = path.substring(_root.length());
|
||||
|
||||
// strip off any preceding file separator
|
||||
if (path.startsWith(File.separator)) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
|
||||
// now strip off the file extension
|
||||
if (!path.endsWith(BundleUtil.IMAGE_EXTENSION)) {
|
||||
throw new BuildException("Can't bundle malformed image file [path=" + path + "].");
|
||||
}
|
||||
path = path.substring(0, path.length() - BundleUtil.IMAGE_EXTENSION.length());
|
||||
|
||||
// now decompose the path; the component type and action must always be a single string but
|
||||
// the class can span multiple directories for easier component organization; thus
|
||||
// "male/head/goatee/standing" will be parsed as
|
||||
// [class=male/head, type=goatee, action=standing]
|
||||
String malmsg = "Can't decode malformed image path: '" + path + "'";
|
||||
String[] info = new String[3];
|
||||
int lsidx = path.lastIndexOf(File.separator);
|
||||
if (lsidx == -1) {
|
||||
throw new BuildException(malmsg);
|
||||
}
|
||||
info[2] = path.substring(lsidx+1);
|
||||
int slsidx = path.lastIndexOf(File.separator, lsidx-1);
|
||||
if (slsidx == -1) {
|
||||
throw new BuildException(malmsg);
|
||||
}
|
||||
info[1] = path.substring(slsidx+1, lsidx);
|
||||
info[0] = path.substring(0, slsidx);
|
||||
// we need to turn file separator characters (platform dependent) into jar path separator
|
||||
// characters (always forward slash)
|
||||
info[0].replace(File.separatorChar, '/');
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes a triplet of [class, name, action] into the path that should be supplied to the
|
||||
* JarEntry that contains the associated image data.
|
||||
*/
|
||||
protected String composePath (String[] info, String extension)
|
||||
{
|
||||
return (info[0] + "/" + info[1] + "/" + info[2] + extension);
|
||||
}
|
||||
|
||||
protected void ensureSet (Object value, String errmsg)
|
||||
throws BuildException
|
||||
{
|
||||
if (value == null) {
|
||||
throw new BuildException(errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the base output stream to which to write our bundle's files.
|
||||
*/
|
||||
protected OutputStream createOutputStream (File target)
|
||||
throws IOException
|
||||
{
|
||||
JarOutputStream jout = new JarOutputStream(new FileOutputStream(target));
|
||||
jout.setLevel(_uncompressed ? Deflater.NO_COMPRESSION : Deflater.BEST_COMPRESSION);
|
||||
return jout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances to the next named entry in the bundle and returns the stream to which to write
|
||||
* that entry.
|
||||
*/
|
||||
protected OutputStream nextEntry (OutputStream lastEntry, String path)
|
||||
throws IOException
|
||||
{
|
||||
((JarOutputStream)lastEntry).putNextEntry(new JarEntry(path));
|
||||
return lastEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we should skip the specified entry in the bundle, presumably if it was
|
||||
* already created and up to date.
|
||||
*/
|
||||
protected boolean skipEntry (String path, long newest)
|
||||
{
|
||||
// If we're making the bundle, by default, we don't skip anything.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the tileset to a trimmed tile set and saves it at the specified location.
|
||||
*/
|
||||
protected TrimmedTileSet trim (TileSet aset, OutputStream fout)
|
||||
throws IOException
|
||||
{
|
||||
return TrimmedTileSet.trimTileSet(aset, fout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the hashmap ID broker from its persistent representation in
|
||||
* the specified file.
|
||||
*/
|
||||
protected HashMapIDBroker loadBroker (File mapfile)
|
||||
throws BuildException
|
||||
{
|
||||
HashMapIDBroker broker = new HashMapIDBroker();
|
||||
|
||||
try {
|
||||
BufferedReader bin = new BufferedReader(new FileReader(mapfile));
|
||||
broker.readFrom(bin);
|
||||
bin.close();
|
||||
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
// if the file doesn't yet exist, start with a blank broker
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new BuildException("Error loading component ID map " +
|
||||
"[mapfile=" + mapfile + "]", e);
|
||||
}
|
||||
|
||||
return broker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a persistent representation of the supplied hashmap ID
|
||||
* broker in the specified file.
|
||||
*/
|
||||
protected void saveBroker (File mapfile, ComponentIDBroker broker)
|
||||
throws BuildException
|
||||
{
|
||||
HashMapIDBroker hbroker = (HashMapIDBroker)broker;
|
||||
|
||||
// bail if the broker wasn't modified
|
||||
if (!hbroker.isModified()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
BufferedWriter bout = new BufferedWriter(new FileWriter(mapfile));
|
||||
hbroker.writeTo(bout);
|
||||
bout.close();
|
||||
} catch (IOException ioe) {
|
||||
throw new BuildException("Unable to store component ID map " +
|
||||
"[mapfile=" + mapfile + "]", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class HashMapIDBroker
|
||||
extends HashMap<Tuple<String, String>, Integer> implements ComponentIDBroker
|
||||
{
|
||||
public int getComponentID (String cclass, String cname)
|
||||
throws PersistenceException
|
||||
{
|
||||
Tuple<String, String> key = new Tuple<String, String>(cclass, cname);
|
||||
Integer cid = get(key);
|
||||
if (cid == null) {
|
||||
cid = Integer.valueOf(++_nextCID);
|
||||
put(key, cid);
|
||||
}
|
||||
return cid.intValue();
|
||||
}
|
||||
|
||||
public void commit ()
|
||||
throws PersistenceException
|
||||
{
|
||||
// nothing doing
|
||||
}
|
||||
|
||||
public boolean isModified ()
|
||||
{
|
||||
return _nextCID != _startCID;
|
||||
}
|
||||
|
||||
public void writeTo (BufferedWriter bout)
|
||||
throws IOException
|
||||
{
|
||||
// write out our most recently assigned component id
|
||||
String cidline = "" + _nextCID;
|
||||
bout.write(cidline, 0, cidline.length());
|
||||
bout.newLine();
|
||||
|
||||
// write out the keys and values
|
||||
ComparableArrayList<String> lines = new ComparableArrayList<String>();
|
||||
Iterator<Tuple<String, String>> keys = keySet().iterator();
|
||||
while (keys.hasNext()) {
|
||||
Tuple<String, String> key = keys.next();
|
||||
Integer value = get(key);
|
||||
String line = key.left + SEP_STR + key.right + SEP_STR + value;
|
||||
lines.add(line);
|
||||
}
|
||||
|
||||
// sort the output
|
||||
lines.sort();
|
||||
|
||||
// now write it to the file
|
||||
int lcount = lines.size();
|
||||
for (int ii = 0; ii < lcount; ii++) {
|
||||
String line = lines.get(ii);
|
||||
bout.write(line, 0, line.length());
|
||||
bout.newLine();
|
||||
}
|
||||
}
|
||||
|
||||
public void readFrom (BufferedReader bin)
|
||||
throws IOException
|
||||
{
|
||||
// read in our most recently assigned component id
|
||||
_nextCID = readInt(bin);
|
||||
// keep track of this so that we can tell if we were modified
|
||||
_startCID = _nextCID;
|
||||
|
||||
// now read in our keys and values
|
||||
String line;
|
||||
while ((line = bin.readLine()) != null) {
|
||||
String orig = line;
|
||||
int sidx = line.indexOf(SEP_STR);
|
||||
if (sidx == -1) {
|
||||
throw new IOException("Malformed line '" + orig + "'");
|
||||
}
|
||||
String cclass = line.substring(0, sidx);
|
||||
line = line.substring(sidx + SEP_STR.length());
|
||||
sidx = line.indexOf(SEP_STR);
|
||||
if (sidx == -1) {
|
||||
throw new IOException("Malformed line '" + orig + "'");
|
||||
}
|
||||
String cname = line.substring(0, sidx);
|
||||
line = line.substring(sidx + SEP_STR.length());
|
||||
try {
|
||||
put(new Tuple<String, String>(cclass, cname), Integer.valueOf(line));
|
||||
} catch (NumberFormatException nfe) {
|
||||
String err = "Malformed line, invalid code '" + orig + "'";
|
||||
throw new IOException(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected int readInt (BufferedReader bin)
|
||||
throws IOException
|
||||
{
|
||||
String line = bin.readLine();
|
||||
try {
|
||||
return Integer.parseInt(line);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new IOException("Expected number, got '" + line + "'");
|
||||
}
|
||||
}
|
||||
|
||||
protected int _nextCID = 0;
|
||||
protected int _startCID = 0;
|
||||
}
|
||||
|
||||
/** The path to our component bundle file. */
|
||||
protected File _target;
|
||||
|
||||
/** The path to our component map file. */
|
||||
protected File _mapfile;
|
||||
|
||||
/** The path to our action tilesets definition file. */
|
||||
protected File _actionDef;
|
||||
|
||||
/** The component directory root. */
|
||||
protected String _root;
|
||||
|
||||
/** A list of filesets that contain tile images. */
|
||||
protected ArrayList<FileSet> _filesets = Lists.newArrayList();
|
||||
|
||||
/** Whether we should keep raw pngs rather than reencoding them in the bundle. */
|
||||
protected boolean _keepRawPngs;
|
||||
|
||||
/** Whether we should keep the bundle jars uncompressed rather than zipped. */
|
||||
protected boolean _uncompressed;
|
||||
|
||||
/** Used to separate keys and values in the map file. */
|
||||
protected static final String SEP_STR = " := ";
|
||||
|
||||
/** Used to process auxilliary tilesets. */
|
||||
protected static final String[] AUX_EXTS = {
|
||||
"_" + StandardActions.SHADOW_TYPE,
|
||||
"_" + StandardActions.CROP_TYPE };
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast.bundle.tools;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
import org.apache.commons.digester.Digester;
|
||||
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.tools.xml.SwissArmyTileSetRuleSet;
|
||||
import com.threerings.media.tile.tools.xml.TileSetRuleSet;
|
||||
import com.threerings.media.tile.tools.xml.UniformTileSetRuleSet;
|
||||
|
||||
import com.threerings.cast.tools.xml.ActionRuleSet;
|
||||
|
||||
/**
|
||||
* Utilities needed when building component bundles.
|
||||
*/
|
||||
public class ComponentBundlerUtil
|
||||
{
|
||||
/**
|
||||
* Parses the action tileset definitions in the supplied file and puts them into a hash map,
|
||||
* keyed on action name.
|
||||
*/
|
||||
public static Map<String, TileSet> parseActionTileSets (File file)
|
||||
throws IOException, SAXException
|
||||
{
|
||||
return parseActionTileSets(new BufferedInputStream(new FileInputStream(file)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the action tileset definitions in the supplied input stream, and puts them into a
|
||||
* hash map, keyed on action name.
|
||||
*/
|
||||
public static Map<String, TileSet> parseActionTileSets (InputStream in)
|
||||
throws IOException, SAXException
|
||||
{
|
||||
Digester digester = new Digester();
|
||||
digester.addSetProperties("actions" + ActionRuleSet.ACTION_PATH);
|
||||
addTileSetRuleSet(digester, new SwissArmyTileSetRuleSet());
|
||||
addTileSetRuleSet(digester, new UniformTileSetRuleSet("/uniformTileset"));
|
||||
|
||||
Map<String, TileSet> actsets = new ActionMap();
|
||||
digester.push(actsets);
|
||||
digester.parse(in);
|
||||
return actsets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures <code>ruleSet</code> and hooks it into <code>digester</code>.
|
||||
*/
|
||||
protected static void addTileSetRuleSet (Digester digester, TileSetRuleSet ruleSet)
|
||||
{
|
||||
ruleSet.setPrefix("actions" + ActionRuleSet.ACTION_PATH);
|
||||
digester.addRuleSet(ruleSet);
|
||||
digester.addSetNext(ruleSet.getPath(), "addTileSet", TileSet.class.getName());
|
||||
}
|
||||
|
||||
/** Used when parsing action tilesets. (This class must be public for Digester to work.) */
|
||||
public static class ActionMap extends HashMap<String, TileSet>
|
||||
{
|
||||
public void setName (String name) {
|
||||
_name = name;
|
||||
}
|
||||
public void addTileSet (TileSet set) {
|
||||
set.setName(_name);
|
||||
put(_name, set);
|
||||
}
|
||||
protected String _name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast.bundle.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.TrimmedTileSet;
|
||||
|
||||
/**
|
||||
* Creates all the information for a component bundle but places it into a specified directory
|
||||
* rather than a bundle jar file.
|
||||
*/
|
||||
public class DirectoryComponentBundlerTask extends ComponentBundlerTask
|
||||
{
|
||||
@Override
|
||||
protected OutputStream createOutputStream (File target)
|
||||
throws IOException
|
||||
{
|
||||
// Since we recreate our output stream on every entry, we don't need one to start with.
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected OutputStream nextEntry (OutputStream lastEntry, String path)
|
||||
throws IOException
|
||||
{
|
||||
File file = new File(_target, path);
|
||||
file.getParentFile().mkdirs();
|
||||
if (!file.getParentFile().isDirectory()) {
|
||||
throw new IOException("Unable to make component directory.[dir=" +
|
||||
file.getParentFile() + "]");
|
||||
}
|
||||
return new FileOutputStream(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean skipEntry (String path, long newest)
|
||||
{
|
||||
File file = new File(_target, path);
|
||||
return (file.lastModified() > newest);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TrimmedTileSet trim (TileSet aset, OutputStream fout)
|
||||
throws IOException
|
||||
{
|
||||
return TrimmedTileSet.trimTileSet(aset, fout, "png");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean skipIfTargetNewer ()
|
||||
{
|
||||
// We have to check modification later on a file-by-file basis, so cannot skip.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast.bundle.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Creates all the information that would be in a metadata bundle and place it in a directory.
|
||||
*/
|
||||
public class DirectoryMetadataBundlerTask extends MetadataBundlerTask
|
||||
{
|
||||
@Override
|
||||
protected OutputStream createOutputStream (File target)
|
||||
throws IOException
|
||||
{
|
||||
// Since we recreate our output stream on every entry, we don't need one to start with.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OutputStream nextEntry (OutputStream lastEntry, String path)
|
||||
throws IOException
|
||||
{
|
||||
File file = new File(_target, path);
|
||||
file.getParentFile().mkdirs();
|
||||
return new FileOutputStream(file);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast.bundle.tools;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import com.samskivert.util.HashIntMap;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.resource.FileResourceBundle;
|
||||
import com.threerings.resource.ResourceBundle;
|
||||
|
||||
import com.threerings.cast.bundle.BundleUtil;
|
||||
|
||||
/**
|
||||
* Dumps the contents of a component bundle to stdout.
|
||||
*/
|
||||
public class DumpBundle
|
||||
{
|
||||
public static void main (String[] args)
|
||||
{
|
||||
if (args.length < 1) {
|
||||
String usage = "Usage: DumpBundle bundle.jar [bundle.jar ...]";
|
||||
System.err.println(usage);
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
for (String arg : args) {
|
||||
File file = new File(arg);
|
||||
try {
|
||||
ResourceBundle bundle = new FileResourceBundle(file);
|
||||
|
||||
HashMap<?, ?> actions = (HashMap<?, ?>)BundleUtil.loadObject(
|
||||
bundle, BundleUtil.ACTIONS_PATH, false);
|
||||
dumpTable("actions: ", actions);
|
||||
|
||||
HashMap<?, ?> actionSets = (HashMap<?, ?>)BundleUtil.loadObject(
|
||||
bundle, BundleUtil.ACTION_SETS_PATH, false);
|
||||
dumpTable("actionSets: ", actionSets);
|
||||
|
||||
HashMap<?, ?> classes = (HashMap<?, ?>)BundleUtil.loadObject(
|
||||
bundle, BundleUtil.CLASSES_PATH, false);
|
||||
dumpTable("classes: ", classes);
|
||||
|
||||
HashIntMap<?> comps = (HashIntMap<?>)BundleUtil.loadObject(
|
||||
bundle, BundleUtil.COMPONENTS_PATH, false);
|
||||
dumpTable("components: ", comps);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error dumping bundle [path=" + arg +
|
||||
", error=" + e + "].");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static void dumpTable (String prefix, Map<?, ?> table)
|
||||
{
|
||||
if (table != null) {
|
||||
System.out.println(prefix + StringUtil.toString(table.entrySet().iterator()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast.bundle.tools;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.Task;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.samskivert.util.Tuple;
|
||||
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.tools.xml.SwissArmyTileSetRuleSet;
|
||||
import com.threerings.media.tile.tools.xml.TileSetRuleSet;
|
||||
import com.threerings.media.tile.tools.xml.UniformTileSetRuleSet;
|
||||
|
||||
import com.threerings.cast.ActionSequence;
|
||||
import com.threerings.cast.ComponentClass;
|
||||
import com.threerings.cast.bundle.BundleUtil;
|
||||
import com.threerings.cast.tools.xml.ActionRuleSet;
|
||||
import com.threerings.cast.tools.xml.ClassRuleSet;
|
||||
|
||||
/**
|
||||
* Ant task for creating metadata bundles, which contain action sequence
|
||||
* and component class definition information. This task must be
|
||||
* configured with a number of parameters:
|
||||
*
|
||||
* <pre>
|
||||
* actiondef=[path to actions.xml]
|
||||
* classdef=[path to classes.xml]
|
||||
* file=[path to metadata bundle, which will be created]
|
||||
* </pre>
|
||||
*/
|
||||
public class MetadataBundlerTask extends Task
|
||||
{
|
||||
public void setActiondef (String actiondef)
|
||||
{
|
||||
_actionDef = actiondef;
|
||||
}
|
||||
|
||||
public void setClassdef (String classdef)
|
||||
{
|
||||
_classDef = classdef;
|
||||
}
|
||||
|
||||
public void setTarget (File target)
|
||||
{
|
||||
_target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the actual work of the task.
|
||||
*/
|
||||
@Override
|
||||
public void execute ()
|
||||
throws BuildException
|
||||
{
|
||||
// make sure everythign was set up properly
|
||||
ensureSet(_actionDef, "Must specify the action sequence " +
|
||||
"definitions via the 'actiondef' attribute.");
|
||||
ensureSet(_classDef, "Must specify the component class definitions " +
|
||||
"via the 'classdef' attribute.");
|
||||
ensureSet(_target, "Must specify the path to the target bundle " +
|
||||
"file via the 'target' attribute.");
|
||||
|
||||
// make sure we can write to the target bundle file
|
||||
OutputStream fout = null;
|
||||
try {
|
||||
|
||||
// parse our metadata
|
||||
Tuple<Map<String, ActionSequence>, Map<String, TileSet>> tuple = parseActions();
|
||||
Map<String, ActionSequence> actions = tuple.left;
|
||||
Map<String, TileSet> actionSets = tuple.right;
|
||||
Map<String, ComponentClass> classes = parseClasses();
|
||||
|
||||
fout = createOutputStream(_target);
|
||||
|
||||
// throw the serialized actions table in there
|
||||
fout = nextEntry(fout, BundleUtil.ACTIONS_PATH);
|
||||
ObjectOutputStream oout = new ObjectOutputStream(fout);
|
||||
oout.writeObject(actions);
|
||||
oout.flush();
|
||||
|
||||
// throw the serialized action tilesets table in there
|
||||
fout = nextEntry(fout, BundleUtil.ACTION_SETS_PATH);
|
||||
oout = new ObjectOutputStream(fout);
|
||||
oout.writeObject(actionSets);
|
||||
oout.flush();
|
||||
|
||||
// throw the serialized classes table in there
|
||||
fout = nextEntry(fout, BundleUtil.CLASSES_PATH);
|
||||
oout = new ObjectOutputStream(fout);
|
||||
oout.writeObject(classes);
|
||||
oout.flush();
|
||||
|
||||
// close it up and we're done
|
||||
fout.close();
|
||||
|
||||
} catch (IOException ioe) {
|
||||
String errmsg = "Unable to output to target bundle " +
|
||||
"[path=" + _target.getPath() + "].";
|
||||
throw new BuildException(errmsg, ioe);
|
||||
|
||||
} finally {
|
||||
if (fout != null) {
|
||||
try {
|
||||
fout.close();
|
||||
} catch (IOException ioe) {
|
||||
// nothing to complain about here
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the base output stream to which to write our bundle's files.
|
||||
*/
|
||||
protected OutputStream createOutputStream (File target)
|
||||
throws IOException
|
||||
{
|
||||
JarOutputStream jout = new JarOutputStream(new FileOutputStream(target));
|
||||
jout.setLevel(Deflater.BEST_COMPRESSION);
|
||||
return jout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances to the next named entry in the bundle and returns the stream to which to write
|
||||
* that entry.
|
||||
*/
|
||||
protected OutputStream nextEntry (OutputStream lastEntry, String path)
|
||||
throws IOException
|
||||
{
|
||||
((JarOutputStream)lastEntry).putNextEntry(new JarEntry(path));
|
||||
return lastEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures <code>ruleSet</code> and hooks it into <code>digester</code>.
|
||||
*/
|
||||
protected static void addTileSetRuleSet (Digester digester, TileSetRuleSet ruleSet)
|
||||
{
|
||||
ruleSet.setPrefix("actions" + ActionRuleSet.ACTION_PATH);
|
||||
digester.addRuleSet(ruleSet);
|
||||
digester.addSetNext(ruleSet.getPath(), "add", Object.class.getName());
|
||||
}
|
||||
|
||||
protected Tuple<Map<String, ActionSequence>, Map<String, TileSet>> parseActions ()
|
||||
throws BuildException
|
||||
{
|
||||
// scan through the XML once to read the actions
|
||||
Digester digester = new Digester();
|
||||
ActionRuleSet arules = new ActionRuleSet();
|
||||
arules.setPrefix("actions");
|
||||
digester.addRuleSet(arules);
|
||||
digester.addSetNext("actions" + ActionRuleSet.ACTION_PATH, "add", Object.class.getName());
|
||||
ArrayList<?> actlist = parseList(digester, _actionDef);
|
||||
|
||||
// now go through a second time reading the tileset info
|
||||
digester = new Digester();
|
||||
addTileSetRuleSet(digester, new SwissArmyTileSetRuleSet());
|
||||
addTileSetRuleSet(digester, new UniformTileSetRuleSet("/uniformTileset"));
|
||||
ArrayList<?> setlist = parseList(digester, _actionDef);
|
||||
|
||||
// sanity check
|
||||
if (actlist.size() != setlist.size()) {
|
||||
String errmsg = "An action is missing its tileset " +
|
||||
"definition, or something even wackier is going on.";
|
||||
throw new BuildException(errmsg);
|
||||
}
|
||||
|
||||
// now create our mappings
|
||||
Map<String, ActionSequence> actmap = Maps.newHashMap();
|
||||
Map<String, TileSet> setmap = Maps.newHashMap();
|
||||
|
||||
// create the action map
|
||||
for (int ii = 0; ii < setlist.size(); ii++) {
|
||||
TileSet set = (TileSet)setlist.get(ii);
|
||||
ActionSequence act = (ActionSequence)actlist.get(ii);
|
||||
// make sure nothing was missing in the action sequence
|
||||
// definition parsed from XML
|
||||
String errmsg = ActionRuleSet.validate(act);
|
||||
if (errmsg != null) {
|
||||
errmsg = "Action sequence invalid [seq=" + act +
|
||||
", error=" + errmsg + "].";
|
||||
throw new BuildException(errmsg);
|
||||
}
|
||||
actmap.put(act.name, act);
|
||||
setmap.put(act.name, set);
|
||||
}
|
||||
|
||||
return new Tuple<Map<String, ActionSequence>, Map<String, TileSet>>(actmap, setmap);
|
||||
}
|
||||
|
||||
protected Map<String, ComponentClass> parseClasses ()
|
||||
throws BuildException
|
||||
{
|
||||
// load up our action and class info
|
||||
Digester digester = new Digester();
|
||||
|
||||
// add our action rule set and a a rule to grab parsed actions
|
||||
ClassRuleSet crules = new ClassRuleSet();
|
||||
crules.setPrefix("classes");
|
||||
digester.addRuleSet(crules);
|
||||
digester.addSetNext("classes" + ClassRuleSet.CLASS_PATH,
|
||||
"add", Object.class.getName());
|
||||
|
||||
ArrayList<?> setlist = parseList(digester, _classDef);
|
||||
Map<String, ComponentClass> clmap = Maps.newHashMap();
|
||||
|
||||
// create the action map
|
||||
for (int ii = 0; ii < setlist.size(); ii++) {
|
||||
ComponentClass cl = (ComponentClass)setlist.get(ii);
|
||||
clmap.put(cl.name, cl);
|
||||
}
|
||||
|
||||
return clmap;
|
||||
}
|
||||
|
||||
protected ArrayList<?> parseList (Digester digester, String path)
|
||||
throws BuildException
|
||||
{
|
||||
try {
|
||||
FileInputStream fin = new FileInputStream(path);
|
||||
BufferedInputStream bin = new BufferedInputStream(fin);
|
||||
|
||||
ArrayList<Object> setlist = Lists.newArrayList();
|
||||
digester.push(setlist);
|
||||
|
||||
// now fire up the digester to parse the stream
|
||||
try {
|
||||
digester.parse(bin);
|
||||
} catch (Exception e) {
|
||||
throw new BuildException("Parsing error.", e);
|
||||
}
|
||||
|
||||
return setlist;
|
||||
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
String errmsg = "Unable to load metadata definition file " +
|
||||
"[path=" + path + "].";
|
||||
throw new BuildException(errmsg, fnfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected void ensureSet (Object value, String errmsg)
|
||||
throws BuildException
|
||||
{
|
||||
if (value == null) {
|
||||
throw new BuildException(errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
protected String _actionDef;
|
||||
protected String _classDef;
|
||||
protected File _target;
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast.tools.xml;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
import org.apache.commons.digester.RuleSetBase;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.samskivert.xml.CallMethodSpecialRule;
|
||||
import com.samskivert.xml.SetFieldRule;
|
||||
import com.samskivert.xml.SetPropertyFieldsRule;
|
||||
|
||||
import com.threerings.util.DirectionCodes;
|
||||
import com.threerings.util.DirectionUtil;
|
||||
|
||||
import com.threerings.cast.ActionSequence;
|
||||
|
||||
/**
|
||||
* The action rule set is used to parse the attributes of an action
|
||||
* sequence instance.
|
||||
*/
|
||||
public class ActionRuleSet extends RuleSetBase
|
||||
{
|
||||
/** The component of the digester path that is appended by the action
|
||||
* rule set to match a action. This is appended to whatever prefix is
|
||||
* provided to the action rule set to obtain the complete XML path to
|
||||
* a matched action. */
|
||||
public static final String ACTION_PATH = "/action";
|
||||
|
||||
/**
|
||||
* Instructs the action rule set to match actions with the supplied
|
||||
* prefix. For example, passing a prefix of <code>actions</code> will
|
||||
* match actions in the following XML file:
|
||||
*
|
||||
* <pre>
|
||||
* <actions>
|
||||
* <action>
|
||||
* // ...
|
||||
* </action>
|
||||
* </actions>
|
||||
* </pre>
|
||||
*
|
||||
* This must be called before adding the ruleset to a digester.
|
||||
*/
|
||||
public void setPrefix (String prefix)
|
||||
{
|
||||
_prefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the necessary rules to the digester to parse our actions.
|
||||
*/
|
||||
@Override
|
||||
public void addRuleInstances (Digester digester)
|
||||
{
|
||||
// this creates the appropriate instance when we encounter a
|
||||
// <action> tag
|
||||
digester.addObjectCreate(_prefix + ACTION_PATH,
|
||||
ActionSequence.class.getName());
|
||||
|
||||
// grab the name attribute from the <action> tag
|
||||
digester.addRule(_prefix + ACTION_PATH, new SetPropertyFieldsRule());
|
||||
|
||||
// grab the other attributes from their respective tags
|
||||
digester.addRule(_prefix + ACTION_PATH + "/framesPerSecond",
|
||||
new SetFieldRule("framesPerSecond"));
|
||||
|
||||
CallMethodSpecialRule origin = new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
throws Exception {
|
||||
int[] coords = StringUtil.parseIntArray(bodyText);
|
||||
if (coords.length != 2) {
|
||||
String errmsg = "Invalid <origin> specification '" +
|
||||
bodyText + "'.";
|
||||
throw new Exception(errmsg);
|
||||
}
|
||||
((ActionSequence)target).origin.setLocation(
|
||||
coords[0], coords[1]);
|
||||
}
|
||||
};
|
||||
digester.addRule(_prefix + ACTION_PATH + "/origin", origin);
|
||||
|
||||
CallMethodSpecialRule orient = new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
throws Exception {
|
||||
ActionSequence seq = ((ActionSequence)target);
|
||||
String[] ostrs = StringUtil.parseStringArray(bodyText);
|
||||
seq.orients = new int[ostrs.length];
|
||||
for (int ii = 0; ii < ostrs.length; ii++) {
|
||||
int orient = DirectionUtil.fromShortString(ostrs[ii]);
|
||||
if (orient != DirectionCodes.NONE) {
|
||||
seq.orients[ii] = orient;
|
||||
} else {
|
||||
String errmsg = "Invalid orientation specification " +
|
||||
"[index=" + ii + ", orient=" + ostrs[ii] + "].";
|
||||
throw new Exception(errmsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
digester.addRule(_prefix + ACTION_PATH + "/orients", orient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that all necessary fields have been parsed and set in
|
||||
* this action sequence object and are valid.
|
||||
*
|
||||
* @return null if the sequence is valid, a string explaining the
|
||||
* invalidity if it is not.
|
||||
*/
|
||||
public static String validate (ActionSequence seq)
|
||||
{
|
||||
if (StringUtil.isBlank(seq.name)) {
|
||||
return "Missing 'name' definition.";
|
||||
}
|
||||
if (seq.framesPerSecond == 0) {
|
||||
return "Missing 'framesPerSecond' definition.";
|
||||
}
|
||||
if (seq.orients == null) {
|
||||
return "Missing 'orients' definition.";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** The prefix at which me match our actions. */
|
||||
protected String _prefix;
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast.tools.xml;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
import org.apache.commons.digester.RuleSetBase;
|
||||
|
||||
import com.samskivert.util.ArrayIntSet;
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.samskivert.xml.SetPropertyFieldsRule;
|
||||
import com.samskivert.xml.SetPropertyFieldsRule.FieldParser;
|
||||
|
||||
import com.threerings.util.DirectionUtil;
|
||||
|
||||
import com.threerings.cast.ComponentClass;
|
||||
import com.threerings.cast.ComponentClass.PriorityOverride;
|
||||
|
||||
/**
|
||||
* The class rule set is used to parse the attributes of a component class
|
||||
* instance.
|
||||
*/
|
||||
public class ClassRuleSet extends RuleSetBase
|
||||
{
|
||||
/** The component of the digester path that is appended by the class
|
||||
* rule set to match a component class. This is appended to whatever
|
||||
* prefix is provided to the class rule set to obtain the complete XML
|
||||
* path to a matched class. */
|
||||
public static final String CLASS_PATH = "/class";
|
||||
|
||||
/**
|
||||
* Instructs the class rule set to match component classes with the
|
||||
* supplied prefix. For example, passing a prefix of
|
||||
* <code>classes</code> will match classes in the following XML file:
|
||||
*
|
||||
* <pre>
|
||||
* <classes>
|
||||
* <class .../>
|
||||
* </classes>
|
||||
* </pre>
|
||||
*
|
||||
* This must be called before adding the ruleset to a digester.
|
||||
*/
|
||||
public void setPrefix (String prefix)
|
||||
{
|
||||
_prefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the necessary rules to the digester to parse our classes.
|
||||
*/
|
||||
@Override
|
||||
public void addRuleInstances (Digester digester)
|
||||
{
|
||||
// this creates the appropriate instance when we encounter a
|
||||
// <class> tag
|
||||
digester.addObjectCreate(_prefix + CLASS_PATH,
|
||||
ComponentClass.class.getName());
|
||||
|
||||
// grab the attributes from the <class> tag
|
||||
SetPropertyFieldsRule rule = new SetPropertyFieldsRule();
|
||||
rule.addFieldParser("shadowColor", new FieldParser() {
|
||||
public Object parse (String text) {
|
||||
int[] values = StringUtil.parseIntArray(text);
|
||||
return new Color(values[0], values[1], values[2], values[3]);
|
||||
}
|
||||
});
|
||||
digester.addRule(_prefix + CLASS_PATH, rule);
|
||||
|
||||
// parse render priority overrides
|
||||
String opath = _prefix + CLASS_PATH + "/override";
|
||||
digester.addObjectCreate(opath, PriorityOverride.class.getName());
|
||||
rule = new SetPropertyFieldsRule();
|
||||
rule.addFieldParser("orients", new FieldParser() {
|
||||
public Object parse (String text) {
|
||||
String[] orients = StringUtil.parseStringArray(text);
|
||||
ArrayIntSet oset = new ArrayIntSet();
|
||||
for (String orient : orients) {
|
||||
oset.add(DirectionUtil.fromShortString(orient));
|
||||
}
|
||||
return oset;
|
||||
}
|
||||
});
|
||||
digester.addRule(opath, rule);
|
||||
|
||||
digester.addSetNext(opath, "addPriorityOverride",
|
||||
PriorityOverride.class.getName());
|
||||
}
|
||||
|
||||
/** The prefix at which me match our component classes. */
|
||||
protected String _prefix;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.image.tools;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.threerings.media.image.ColorPository;
|
||||
|
||||
/**
|
||||
* Simple tool for dumping a serialized color pository.
|
||||
*/
|
||||
public class DumpColorPository
|
||||
{
|
||||
public static void main (String[] args)
|
||||
{
|
||||
if (args.length == 0) {
|
||||
System.err.println("Usage: DumpColorPository colorpos.dat");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
try {
|
||||
ColorPository pos = ColorPository.loadColorPository(
|
||||
new FileInputStream(args[0]));
|
||||
Iterator<ColorPository.ClassRecord> iter = pos.enumerateClasses();
|
||||
while (iter.hasNext()) {
|
||||
System.out.println(iter.next());
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.image.tools.xml;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
import org.apache.commons.digester.Rule;
|
||||
|
||||
import com.samskivert.xml.SetPropertyFieldsRule;
|
||||
|
||||
import com.threerings.media.image.ColorPository;
|
||||
import com.threerings.media.image.ColorPository.ClassRecord;
|
||||
import com.threerings.media.image.ColorPository.ColorRecord;
|
||||
|
||||
import com.threerings.tools.xml.CompiledConfigParser;
|
||||
|
||||
/**
|
||||
* Parses the XML color repository definition and creates a {@link ColorPository} instance that
|
||||
* reflects its contents.
|
||||
*/
|
||||
public class ColorPositoryParser extends CompiledConfigParser
|
||||
{
|
||||
@Override
|
||||
protected Serializable createConfigObject ()
|
||||
{
|
||||
return new ColorPository();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addRules (Digester digest)
|
||||
{
|
||||
// create and configure class record instances
|
||||
String prefix = "colors/class";
|
||||
digest.addObjectCreate(prefix, ClassRecord.class.getName());
|
||||
digest.addRule(prefix, new SetPropertyFieldsRule());
|
||||
digest.addSetNext(prefix, "addClass", ClassRecord.class.getName());
|
||||
|
||||
// create and configure color record instances
|
||||
prefix += "/color";
|
||||
digest.addRule(prefix, new Rule() {
|
||||
@Override
|
||||
public void begin (String namespace, String name,
|
||||
Attributes attributes) throws Exception {
|
||||
// we want to inherit settings from the color class when
|
||||
// creating the record, so we do some custom stuff
|
||||
ColorRecord record = new ColorRecord();
|
||||
ClassRecord clrec = (ClassRecord)digester.peek();
|
||||
record.starter = clrec.starter;
|
||||
digester.push(record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end (String namespace, String name) throws Exception {
|
||||
digester.pop();
|
||||
}
|
||||
});
|
||||
digest.addRule(prefix, new SetPropertyFieldsRule());
|
||||
digest.addSetNext(prefix, "addColor", ColorRecord.class.getName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tile.bundle.tools;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import com.samskivert.io.StreamUtil;
|
||||
|
||||
import com.threerings.media.tile.ImageProvider;
|
||||
import com.threerings.media.tile.ObjectTileSet;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.TrimmedObjectTileSet;
|
||||
import com.threerings.media.tile.bundle.BundleUtil;
|
||||
import com.threerings.media.tile.bundle.TileSetBundle;
|
||||
|
||||
import static com.threerings.media.Log.log;
|
||||
|
||||
public class DirectoryTileSetBundler extends TileSetBundler
|
||||
{
|
||||
public DirectoryTileSetBundler (File configFile)
|
||||
throws IOException
|
||||
{
|
||||
super(configFile);
|
||||
}
|
||||
|
||||
public DirectoryTileSetBundler (String configPath)
|
||||
throws IOException
|
||||
{
|
||||
super(configPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createBundle (
|
||||
File target, TileSetBundle bundle, ImageProvider improv, String imageBase, long newestMod)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
// write all of the image files to the bundle's target path, converting the
|
||||
// tilesets to trimmed tilesets in the process
|
||||
Iterator<Integer> iditer = bundle.enumerateTileSetIds();
|
||||
while (iditer.hasNext()) {
|
||||
int tileSetId = iditer.next().intValue();
|
||||
TileSet set = bundle.getTileSet(tileSetId);
|
||||
String imagePath = set.getImagePath();
|
||||
|
||||
// sanity checks
|
||||
if (imagePath == null) {
|
||||
log.warning("Tileset contains no image path " +
|
||||
"[set=" + set + "]. It ain't gonna work.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// if this is an object tileset, trim it
|
||||
if (set instanceof ObjectTileSet) {
|
||||
// set the tileset up with an image provider; we
|
||||
// need to do this so that we can trim it!
|
||||
set.setImageProvider(improv);
|
||||
|
||||
try {
|
||||
// create a trimmed object tileset, which will
|
||||
// write the trimmed tileset image to the destination output stream
|
||||
File outFile = new File(target, imagePath);
|
||||
FileOutputStream fout = null;
|
||||
|
||||
if (outFile.lastModified() > newestMod) {
|
||||
// Our file's newer than the newest bundle mod - up to date.
|
||||
// So don't actually do anything
|
||||
|
||||
// TODO: Ideally, we'd like to skip re-trimming altogether, since that's
|
||||
// expensive, but for the moment, we're at least doing half as much by
|
||||
// not writing out the trimmed image.
|
||||
|
||||
} else {
|
||||
// It's changed, so let's open the file & do all that jazz
|
||||
outFile.getParentFile().mkdirs();
|
||||
fout = new FileOutputStream(outFile);
|
||||
}
|
||||
|
||||
TrimmedObjectTileSet tset =
|
||||
TrimmedObjectTileSet.trimObjectTileSet((ObjectTileSet)set, fout, "png");
|
||||
tset.setImagePath(imagePath);
|
||||
// replace the original set with the trimmed tileset in the tileset bundle
|
||||
bundle.addTileSet(tileSetId, tset);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
|
||||
String msg = "Error adding tileset to bundle " + imagePath +
|
||||
", " + set.getName() + ": " + e;
|
||||
throw (IOException) new IOException(msg).initCause(e);
|
||||
}
|
||||
|
||||
} else {
|
||||
// read the image file and write it to the proper place
|
||||
File ifile = new File(imageBase, imagePath);
|
||||
if (ifile.lastModified() > newestMod) {
|
||||
// Our file's newer than the newest bundle mod - up to date.
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// We read the image to ensure it is a valid image.
|
||||
ImageIO.read(ifile);
|
||||
|
||||
File outFile = new File(target, imagePath);
|
||||
if (outFile.lastModified() > newestMod) {
|
||||
// Our file's newer than the newest bundle mod - up to date.
|
||||
continue;
|
||||
}
|
||||
outFile.getParentFile().mkdirs();
|
||||
FileOutputStream fout = new FileOutputStream(outFile);
|
||||
FileInputStream imgin = new FileInputStream(ifile);
|
||||
StreamUtil.copy(imgin, fout);
|
||||
} catch (Exception e) {
|
||||
String msg = "Failure bundling image " + ifile + ": " + e;
|
||||
throw (IOException) new IOException(msg).initCause(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now write a serialized representation of the tileset bundle
|
||||
// object to the bundle jar file
|
||||
File outFile = new File(target, BundleUtil.METADATA_PATH);
|
||||
|
||||
outFile.getParentFile().mkdirs();
|
||||
FileOutputStream fout = new FileOutputStream(outFile);
|
||||
ObjectOutputStream oout = new ObjectOutputStream(fout);
|
||||
oout.writeObject(bundle);
|
||||
oout.flush();
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
String errmsg = "Failed to create bundle " + target + ": " + e;
|
||||
throw (IOException) new IOException(errmsg).initCause(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean skipIfTargetNewer ()
|
||||
{
|
||||
// We have to check modification later on a file-by-file basis, so cannot skip.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tile.bundle.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
|
||||
/**
|
||||
* Ant task for creating tileset bundles that are placed in a specified directory instead
|
||||
* of wrapped up in a fancy jar file.
|
||||
*/
|
||||
public class DirectoryTileSetBundlerTask extends TileSetBundlerTask
|
||||
{
|
||||
/**
|
||||
* Sets the path to the directory in which we'll be putting our bundle's files.
|
||||
*/
|
||||
public void setDeployDir (File deployDir)
|
||||
{
|
||||
_deployDir = deployDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute () throws BuildException
|
||||
{
|
||||
ensureSet(_deployDir, "Must specify the path to which we want to deploy tileset files.");
|
||||
super.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TileSetBundler createBundler ()
|
||||
throws IOException
|
||||
{
|
||||
return new DirectoryTileSetBundler(_config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTargetPath (File fromDir, String path)
|
||||
{
|
||||
File xmlFile = new File(path.replace(fromDir.getPath(), _deployDir.getPath()));
|
||||
return xmlFile.getParent();
|
||||
}
|
||||
|
||||
/** The directory in which we want to place our tile set files for deployment. */
|
||||
protected File _deployDir;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tile.bundle.tools;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import com.threerings.resource.FileResourceBundle;
|
||||
import com.threerings.resource.ResourceBundle;
|
||||
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.bundle.BundleUtil;
|
||||
import com.threerings.media.tile.bundle.TileSetBundle;
|
||||
|
||||
/**
|
||||
* Dumps the contents of a tileset bundle to stdout (just the serialized
|
||||
* object info, not the image data).
|
||||
*/
|
||||
public class DumpBundle
|
||||
{
|
||||
public static void main (String[] args)
|
||||
{
|
||||
boolean dumpTiles = false;
|
||||
|
||||
if (args.length < 1) {
|
||||
String usage = "Usage: DumpBundle [-tiles] " +
|
||||
"(bundle.jar|tsbundle.dat) [...]";
|
||||
System.err.println(usage);
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
for (String arg : args) {
|
||||
// oh the hackery
|
||||
if (arg.equals("-tiles")) {
|
||||
dumpTiles = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
File file = new File(arg);
|
||||
try {
|
||||
TileSetBundle tsb = null;
|
||||
if (arg.endsWith(".jar")) {
|
||||
ResourceBundle bundle = new FileResourceBundle(file);
|
||||
tsb = BundleUtil.extractBundle(bundle);
|
||||
tsb.init(bundle);
|
||||
} else {
|
||||
tsb = BundleUtil.extractBundle(file);
|
||||
}
|
||||
|
||||
Iterator<Integer> tsids = tsb.enumerateTileSetIds();
|
||||
while (tsids.hasNext()) {
|
||||
Integer tsid = tsids.next();
|
||||
TileSet set = tsb.getTileSet(tsid.intValue());
|
||||
System.out.println(tsid + " => " + set);
|
||||
if (dumpTiles) {
|
||||
for (int t = 0, nn = set.getTileCount(); t < nn; t++) {
|
||||
System.out.println(" " + t + " => " +
|
||||
set.getTile(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error dumping bundle [path=" + arg +
|
||||
", error=" + e + "].");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tile.bundle.tools;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.commons.digester.Digester;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.io.StreamUtil;
|
||||
import com.samskivert.util.HashIntMap;
|
||||
|
||||
import com.threerings.resource.FastImageIO;
|
||||
|
||||
import com.threerings.media.tile.ImageProvider;
|
||||
import com.threerings.media.tile.ObjectTileSet;
|
||||
import com.threerings.media.tile.SimpleCachingImageProvider;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.TileSetIDBroker;
|
||||
import com.threerings.media.tile.TrimmedObjectTileSet;
|
||||
import com.threerings.media.tile.bundle.BundleUtil;
|
||||
import com.threerings.media.tile.bundle.TileSetBundle;
|
||||
import com.threerings.media.tile.tools.xml.TileSetRuleSet;
|
||||
|
||||
import static com.threerings.media.Log.log;
|
||||
|
||||
/**
|
||||
* The tileset bundler is used to create tileset bundles from a set of XML
|
||||
* tileset descriptions in a bundle description file. The bundles contain
|
||||
* a serialized representation of the tileset objects along with the
|
||||
* actual image files referenced by those tilesets.
|
||||
*
|
||||
* <p> The organization of the bundle description file is customizable
|
||||
* based on the an XML configuration file provided to the tileset bundler
|
||||
* when constructed. The bundler configuration maps XML paths to tileset
|
||||
* parsers. An example configuration follows:
|
||||
*
|
||||
* <pre>
|
||||
* <bundler-config>
|
||||
* <mapping>
|
||||
* <path>bundle.tilesets.uniform</path>
|
||||
* <ruleset>
|
||||
* com.threerings.media.tile.tools.xml.UniformTileSetRuleSet
|
||||
* </ruleset>
|
||||
* </mapping>
|
||||
* <mapping>
|
||||
* <path>bundle.tilesets.object</path>
|
||||
* <ruleset>
|
||||
* com.threerings.media.tile.tools.xml.ObjectTileSetRuleSet
|
||||
* </ruleset>
|
||||
* </mapping>
|
||||
* </bundler-config>
|
||||
* </pre>
|
||||
*
|
||||
* This configuration would be used to parse a bundle description that
|
||||
* looked something like the following:
|
||||
*
|
||||
* <pre>
|
||||
* <bundle>
|
||||
* <tilesets>
|
||||
* <uniform>
|
||||
* <tileset>
|
||||
* <!-- ... -->
|
||||
* </tileset>
|
||||
* </uniform>
|
||||
* <object>
|
||||
* <tileset>
|
||||
* <!-- ... -->
|
||||
* </tileset>
|
||||
* </object>
|
||||
* </tilesets>
|
||||
* </pre>
|
||||
*
|
||||
* The class specified in the <code>ruleset</code> element must derive
|
||||
* from {@link TileSetRuleSet}. The images that will be included in the
|
||||
* bundle must be in the same directory as the bundle description file and
|
||||
* the tileset descriptions must reference the images without a preceding
|
||||
* path.
|
||||
*/
|
||||
public class TileSetBundler
|
||||
{
|
||||
/**
|
||||
* Constructs a tileset bundler with the specified path to a bundler
|
||||
* configuration file. The configuration file will be loaded and used
|
||||
* to configure this tileset bundler.
|
||||
*/
|
||||
public TileSetBundler (String configPath)
|
||||
throws IOException
|
||||
{
|
||||
this(new File(configPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a tileset bundler with the specified bundler config
|
||||
* file.
|
||||
*/
|
||||
public TileSetBundler (File configFile)
|
||||
throws IOException
|
||||
{
|
||||
this(configFile, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a tileset bundler with the specified bundler config
|
||||
* file and whether to keep pngs as-is or if not, re-encode them.
|
||||
*/
|
||||
public TileSetBundler (File configFile, boolean keepRawPngs, boolean uncompressed)
|
||||
throws IOException
|
||||
{
|
||||
_keepRawPngs = keepRawPngs;
|
||||
_uncompressed = uncompressed;
|
||||
|
||||
// we parse our configuration with a digester
|
||||
Digester digester = new Digester();
|
||||
|
||||
// push our mappings array onto the stack
|
||||
ArrayList<Mapping> mappings = Lists.newArrayList();
|
||||
digester.push(mappings);
|
||||
|
||||
// create a mapping object for each mapping entry and append it to
|
||||
// our mapping list
|
||||
digester.addObjectCreate("bundler-config/mapping", Mapping.class.getName());
|
||||
digester.addSetNext("bundler-config/mapping", "add", "java.lang.Object");
|
||||
|
||||
// configure each mapping object with the path and ruleset
|
||||
digester.addCallMethod("bundler-config/mapping", "init", 2);
|
||||
digester.addCallParam("bundler-config/mapping/path", 0);
|
||||
digester.addCallParam("bundler-config/mapping/ruleset", 1);
|
||||
|
||||
// now go like the wind
|
||||
FileInputStream fin = new FileInputStream(configFile);
|
||||
try {
|
||||
digester.parse(fin);
|
||||
} catch (SAXException saxe) {
|
||||
String errmsg = "Failure parsing bundler config file " +
|
||||
"[file=" + configFile.getPath() + "]";
|
||||
throw (IOException) new IOException(errmsg).initCause(saxe);
|
||||
}
|
||||
fin.close();
|
||||
|
||||
// create our digester
|
||||
_digester = new Digester();
|
||||
|
||||
// use the mappings we parsed to configure our actual digester
|
||||
int msize = mappings.size();
|
||||
for (int ii = 0; ii < msize; ii++) {
|
||||
Mapping map = mappings.get(ii);
|
||||
try {
|
||||
TileSetRuleSet ruleset = (TileSetRuleSet)Class.forName(map.ruleset).newInstance();
|
||||
|
||||
// configure the ruleset
|
||||
ruleset.setPrefix(map.path);
|
||||
// add it to the digester
|
||||
_digester.addRuleSet(ruleset);
|
||||
// and add a rule to stick the parsed tilesets onto the
|
||||
// end of an array list that we'll put on the stack
|
||||
_digester.addSetNext(ruleset.getPath(), "add", "java.lang.Object");
|
||||
|
||||
} catch (Exception e) {
|
||||
String errmsg = "Unable to create tileset rule set " +
|
||||
"instance [mapping=" + map + "].";
|
||||
throw (IOException) new IOException(errmsg).initCause(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tileset bundle at the location specified by the
|
||||
* <code>targetPath</code> parameter, based on the description
|
||||
* provided via the <code>bundleDesc</code> parameter.
|
||||
*
|
||||
* @param idBroker the tileset id broker that will be used to map
|
||||
* tileset names to tileset ids.
|
||||
* @param bundleDesc a file object pointing to the bundle description
|
||||
* file.
|
||||
* @param targetPath the path of the tileset bundle file that will be
|
||||
* created.
|
||||
*
|
||||
* @exception IOException thrown if an error occurs reading, writing
|
||||
* or processing anything.
|
||||
*/
|
||||
public void createBundle (
|
||||
TileSetIDBroker idBroker, File bundleDesc, String targetPath)
|
||||
throws IOException
|
||||
{
|
||||
createBundle(idBroker, bundleDesc, new File(targetPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tileset bundle at the location specified by the
|
||||
* <code>targetPath</code> parameter, based on the description
|
||||
* provided via the <code>bundleDesc</code> parameter.
|
||||
*
|
||||
* @param idBroker the tileset id broker that will be used to map
|
||||
* tileset names to tileset ids.
|
||||
* @param bundleDesc a file object pointing to the bundle description
|
||||
* file.
|
||||
* @param target the tileset bundle file that will be created.
|
||||
*
|
||||
* @return true if the bundle was rebuilt, false if it was not because
|
||||
* the bundle file was newer than all involved source files.
|
||||
*
|
||||
* @exception IOException thrown if an error occurs reading, writing
|
||||
* or processing anything.
|
||||
*/
|
||||
public boolean createBundle (
|
||||
TileSetIDBroker idBroker, final File bundleDesc, File target)
|
||||
throws IOException
|
||||
{
|
||||
// stick an array list on the top of the stack into which we will
|
||||
// collect parsed tilesets
|
||||
ArrayList<TileSet> sets = Lists.newArrayList();
|
||||
_digester.push(sets);
|
||||
|
||||
// parse the tilesets
|
||||
FileInputStream fin = new FileInputStream(bundleDesc);
|
||||
try {
|
||||
_digester.parse(fin);
|
||||
} catch (SAXException saxe) {
|
||||
String errmsg = "Failure parsing bundle description file " +
|
||||
"[path=" + bundleDesc.getPath() + "]";
|
||||
throw (IOException) new IOException(errmsg).initCause(saxe);
|
||||
} finally {
|
||||
fin.close();
|
||||
}
|
||||
|
||||
// we want to make sure that at least one of the tileset image
|
||||
// files or the bundle definition file is newer than the bundle
|
||||
// file, otherwise consider the bundle up to date
|
||||
long newest = bundleDesc.lastModified();
|
||||
|
||||
// create a tileset bundle to hold our tilesets
|
||||
TileSetBundle bundle = new TileSetBundle();
|
||||
|
||||
// add all of the parsed tilesets to the tileset bundle
|
||||
try {
|
||||
for (int ii = 0; ii < sets.size(); ii++) {
|
||||
TileSet set = sets.get(ii);
|
||||
String name = set.getName();
|
||||
|
||||
// let's be robust
|
||||
if (name == null) {
|
||||
log.warning("Tileset was parsed, but received no name " +
|
||||
"[set=" + set + "]. Skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// make sure this tileset's image file exists and check its last modified date
|
||||
File tsfile = new File(bundleDesc.getParent(),
|
||||
set.getImagePath());
|
||||
if (!tsfile.exists()) {
|
||||
System.err.println("Tile set missing image file " +
|
||||
"[bundle=" + bundleDesc.getPath() +
|
||||
", name=" + set.getName() +
|
||||
", imgpath=" + tsfile.getPath() + "].");
|
||||
continue;
|
||||
}
|
||||
if (tsfile.lastModified() > newest) {
|
||||
newest = tsfile.lastModified();
|
||||
}
|
||||
|
||||
// assign a tilset id to the tileset and bundle it
|
||||
try {
|
||||
int tileSetId = idBroker.getTileSetID(name);
|
||||
bundle.addTileSet(tileSetId, set);
|
||||
} catch (PersistenceException pe) {
|
||||
String errmsg = "Failure obtaining a tileset id for " +
|
||||
"tileset [set=" + set + "].";
|
||||
throw (IOException) new IOException(errmsg).initCause(pe);
|
||||
}
|
||||
}
|
||||
|
||||
// clear out our array list in preparation for another go
|
||||
sets.clear();
|
||||
|
||||
} finally {
|
||||
// before we go, we have to commit our brokered tileset ids
|
||||
// back to the broker's persistent store
|
||||
try {
|
||||
idBroker.commit();
|
||||
} catch (PersistenceException pe) {
|
||||
log.warning("Failure committing brokered tileset ids " +
|
||||
"back to broker's persistent store " +
|
||||
"[error=" + pe + "].");
|
||||
}
|
||||
}
|
||||
|
||||
// see if our newest file is newer than the tileset bundle
|
||||
if (skipIfTargetNewer() && newest < target.lastModified()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// create an image provider for loading our tileset images
|
||||
SimpleCachingImageProvider improv = new SimpleCachingImageProvider() {
|
||||
@Override
|
||||
protected BufferedImage loadImage (String path)
|
||||
throws IOException {
|
||||
return ImageIO.read(new File(bundleDesc.getParent(), path));
|
||||
}
|
||||
};
|
||||
|
||||
return createBundle(target, bundle, improv, bundleDesc.getParent(), newest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish the creation of a tileset bundle jar file.
|
||||
*
|
||||
* @param target the tileset bundle file that will be created.
|
||||
* @param bundle contains the tilesets we'd like to save out to the bundle.
|
||||
* @param improv the image provider.
|
||||
* @param imageBase the base directory for getting images for non
|
||||
* @param newestMod the most recent modification to any part of the bundle. By default we
|
||||
* ignore this since we normally duck out if we're up to date.
|
||||
* ObjectTileSet tilesets.
|
||||
*/
|
||||
public boolean createBundle (
|
||||
File target, TileSetBundle bundle, ImageProvider improv, String imageBase, long newestMod)
|
||||
throws IOException
|
||||
{
|
||||
return createBundleJar(target, bundle, improv, imageBase, _keepRawPngs, _uncompressed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tileset bundle jar file.
|
||||
*
|
||||
* @param target the tileset bundle file that will be created.
|
||||
* @param bundle contains the tilesets we'd like to save out to the bundle.
|
||||
* @param improv the image provider.
|
||||
* @param imageBase the base directory for getting images for non-ObjectTileSet tilesets.
|
||||
* @param keepOriginalPngs bundle up the original PNGs as PNGs instead of converting to the
|
||||
* FastImageIO raw format
|
||||
*/
|
||||
public static boolean createBundleJar (
|
||||
File target, TileSetBundle bundle, ImageProvider improv, String imageBase,
|
||||
boolean keepOriginalPngs, boolean uncompressed)
|
||||
throws IOException
|
||||
{
|
||||
// now we have to create the actual bundle file
|
||||
FileOutputStream fout = new FileOutputStream(target);
|
||||
Manifest manifest = new Manifest();
|
||||
JarOutputStream jar = new JarOutputStream(fout, manifest);
|
||||
jar.setLevel(uncompressed ? Deflater.NO_COMPRESSION : Deflater.BEST_COMPRESSION);
|
||||
|
||||
try {
|
||||
// write all of the image files to the bundle, converting the
|
||||
// tilesets to trimmed tilesets in the process
|
||||
Iterator<Integer> iditer = bundle.enumerateTileSetIds();
|
||||
|
||||
// Store off the updated TileSets in a separate Map so we can wait to change the
|
||||
// bundle till we're done iterating.
|
||||
HashIntMap<TileSet> toUpdate = new HashIntMap<TileSet>();
|
||||
while (iditer.hasNext()) {
|
||||
int tileSetId = iditer.next().intValue();
|
||||
TileSet set = bundle.getTileSet(tileSetId);
|
||||
String imagePath = set.getImagePath();
|
||||
|
||||
// sanity checks
|
||||
if (imagePath == null) {
|
||||
log.warning("Tileset contains no image path " +
|
||||
"[set=" + set + "]. It ain't gonna work.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// if this is an object tileset, trim it
|
||||
if (!keepOriginalPngs && (set instanceof ObjectTileSet)) {
|
||||
// set the tileset up with an image provider; we
|
||||
// need to do this so that we can trim it!
|
||||
set.setImageProvider(improv);
|
||||
|
||||
// we're going to trim it, so adjust the path
|
||||
imagePath = adjustImagePath(imagePath);
|
||||
jar.putNextEntry(new JarEntry(imagePath));
|
||||
|
||||
try {
|
||||
// create a trimmed object tileset, which will
|
||||
// write the trimmed tileset image to the jar
|
||||
// output stream
|
||||
TrimmedObjectTileSet tset =
|
||||
TrimmedObjectTileSet.trimObjectTileSet(
|
||||
(ObjectTileSet)set, jar);
|
||||
tset.setImagePath(imagePath);
|
||||
// replace the original set with the trimmed
|
||||
// tileset in the tileset bundle
|
||||
toUpdate.put(tileSetId, tset);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
|
||||
String msg = "Error adding tileset to bundle " + imagePath +
|
||||
", " + set.getName() + ": " + e;
|
||||
throw (IOException) new IOException(msg).initCause(e);
|
||||
}
|
||||
|
||||
} else {
|
||||
// read the image file and convert it to our custom
|
||||
// format in the bundle
|
||||
File ifile = new File(imageBase, imagePath);
|
||||
try {
|
||||
BufferedImage image = ImageIO.read(ifile);
|
||||
if (!keepOriginalPngs && FastImageIO.canWrite(image)) {
|
||||
imagePath = adjustImagePath(imagePath);
|
||||
jar.putNextEntry(new JarEntry(imagePath));
|
||||
set.setImagePath(imagePath);
|
||||
FastImageIO.write(image, jar);
|
||||
} else {
|
||||
jar.putNextEntry(new JarEntry(imagePath));
|
||||
FileInputStream imgin = new FileInputStream(ifile);
|
||||
StreamUtil.copy(imgin, jar);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String msg = "Failure bundling image " + ifile +
|
||||
": " + e;
|
||||
throw (IOException) new IOException(msg).initCause(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
bundle.putAll(toUpdate);
|
||||
|
||||
// now write a serialized representation of the tileset bundle
|
||||
// object to the bundle jar file
|
||||
JarEntry entry = new JarEntry(BundleUtil.METADATA_PATH);
|
||||
jar.putNextEntry(entry);
|
||||
ObjectOutputStream oout = new ObjectOutputStream(jar);
|
||||
oout.writeObject(bundle);
|
||||
oout.flush();
|
||||
|
||||
// finally close up the jar file and call ourself done
|
||||
jar.close();
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
// remove the incomplete jar file and rethrow the exception
|
||||
jar.close();
|
||||
if (!target.delete()) {
|
||||
log.warning("Failed to close botched bundle '" + target + "'.");
|
||||
}
|
||||
String errmsg = "Failed to create bundle " + target + ": " + e;
|
||||
throw (IOException) new IOException(errmsg).initCause(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we should skip updating the bundle if the target is newer than any component.
|
||||
*/
|
||||
protected boolean skipIfTargetNewer ()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Replaces the image suffix with <code>.raw</code>. */
|
||||
protected static String adjustImagePath (String imagePath)
|
||||
{
|
||||
int didx = imagePath.lastIndexOf(".");
|
||||
return ((didx == -1) ? imagePath :
|
||||
imagePath.substring(0, didx)) + ".raw";
|
||||
|
||||
}
|
||||
|
||||
/** Used to parse our configuration. */
|
||||
public static class Mapping
|
||||
{
|
||||
public String path;
|
||||
public String ruleset;
|
||||
|
||||
public void init (String path, String ruleset)
|
||||
{
|
||||
this.path = path;
|
||||
this.ruleset = ruleset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString ()
|
||||
{
|
||||
return "[path=" + path + ", ruleset=" + ruleset + "]";
|
||||
}
|
||||
}
|
||||
|
||||
/** The digester we use to parse bundle descriptions. */
|
||||
protected Digester _digester;
|
||||
|
||||
/** Whether we should keep pngs as-is rather than re-encoding. */
|
||||
protected boolean _keepRawPngs;
|
||||
|
||||
/** Normally we compress the jar, but if we want to leave them uncompressed, we set this. */
|
||||
protected boolean _uncompressed;
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tile.bundle.tools;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.DirectoryScanner;
|
||||
import org.apache.tools.ant.Task;
|
||||
import org.apache.tools.ant.types.FileSet;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.threerings.media.tile.tools.MapFileTileSetIDBroker;
|
||||
|
||||
/**
|
||||
* Ant task for creating tilset bundles.
|
||||
*/
|
||||
public class TileSetBundlerTask extends Task
|
||||
{
|
||||
/**
|
||||
* Sets the path to the bundler configuration file that we'll use when
|
||||
* creating the bundle.
|
||||
*/
|
||||
public void setConfig (File config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path to the tileset id mapping file we'll use when
|
||||
* creating the bundle.
|
||||
*/
|
||||
public void setMapfile (File mapfile)
|
||||
{
|
||||
_mapfile = mapfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a nested <fileset> element.
|
||||
*/
|
||||
public void addFileset (FileSet set)
|
||||
{
|
||||
_filesets.add(set);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note whether we are supposed to use the raw png files directly in the bundle or try to
|
||||
* re-encode them.
|
||||
*/
|
||||
public void setKeepRawPngs (boolean keep)
|
||||
{
|
||||
_keepRawPngs = keep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note whether we are supposed to leave the jar uncompressed rather than the normal process
|
||||
* of zipping it at maximum compression.
|
||||
*/
|
||||
public void setUncompressed (boolean uncompressed)
|
||||
{
|
||||
_uncompressed = uncompressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the actual work of the task.
|
||||
*/
|
||||
@Override
|
||||
public void execute () throws BuildException
|
||||
{
|
||||
// make sure everything was set up properly
|
||||
ensureSet(_config, "Must specify the path to the bundler config " +
|
||||
"file via the 'config' attribute.");
|
||||
ensureSet(_mapfile, "Must specify the path to the tileset id map " +
|
||||
"file via the 'mapfile' attribute.");
|
||||
|
||||
File cfile = null;
|
||||
try {
|
||||
// create a tileset bundler
|
||||
TileSetBundler bundler = createBundler();
|
||||
|
||||
// create our tileset id broker
|
||||
MapFileTileSetIDBroker broker =
|
||||
new MapFileTileSetIDBroker(_mapfile);
|
||||
|
||||
// deal with the filesets
|
||||
for (int ii = 0; ii < _filesets.size(); ii++) {
|
||||
FileSet fs = _filesets.get(ii);
|
||||
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
|
||||
File fromDir = fs.getDir(getProject());
|
||||
String[] srcFiles = ds.getIncludedFiles();
|
||||
|
||||
for (String srcFile : srcFiles) {
|
||||
cfile = new File(fromDir, srcFile);
|
||||
|
||||
// figure out the bundle file based on the definition
|
||||
// file
|
||||
String cpath = cfile.getPath();
|
||||
if (!cpath.endsWith(".xml")) {
|
||||
System.err.println("Can't infer bundle name from " +
|
||||
"bundle config name " +
|
||||
"[path=" + cpath + "].\n" +
|
||||
"Config file should end with .xml.");
|
||||
continue;
|
||||
}
|
||||
String bpath = getTargetPath(fromDir, cpath);
|
||||
File bfile = new File(bpath);
|
||||
|
||||
// create the bundle
|
||||
if (bundler.createBundle(broker, cfile, bfile)) {
|
||||
System.out.println(
|
||||
"Created bundle from '" + cpath + "'...");
|
||||
} else {
|
||||
System.out.println(
|
||||
"Tileset bundle up to date '" + bpath + "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// commit changes to the tileset id mapping
|
||||
broker.commit();
|
||||
|
||||
} catch (Exception e) {
|
||||
String errmsg = "Failure creating tileset bundle [source=" + cfile +
|
||||
"]: " + e.getMessage();
|
||||
throw new BuildException(errmsg, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the bundler to use during creation.
|
||||
*/
|
||||
protected TileSetBundler createBundler ()
|
||||
throws IOException
|
||||
{
|
||||
return new TileSetBundler(_config, _keepRawPngs, _uncompressed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the target path in which our bundler will write the tile set.
|
||||
*/
|
||||
protected String getTargetPath (File fromDir, String path)
|
||||
{
|
||||
return path.substring(0, path.length()-4) + ".jar";
|
||||
}
|
||||
|
||||
protected void ensureSet (Object value, String errmsg)
|
||||
throws BuildException
|
||||
{
|
||||
if (value == null) {
|
||||
throw new BuildException(errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
protected File _config;
|
||||
protected File _mapfile;
|
||||
|
||||
/** A list of filesets that contain tileset bundle definitions. */
|
||||
protected ArrayList<FileSet> _filesets = Lists.newArrayList();
|
||||
|
||||
/** Whether we should keep raw pngs rather than reencoding them in the bundle. */
|
||||
protected boolean _keepRawPngs;
|
||||
|
||||
/** Whether we should keep the bundle jars uncompressed rather than zipped. */
|
||||
protected boolean _uncompressed;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tile.tools;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
|
||||
/**
|
||||
* Prints out the tileset mappings in a {@link MapFileTileSetIDBroker}.
|
||||
*/
|
||||
public class DumpTileSetMap
|
||||
{
|
||||
public static void main (String[] args)
|
||||
{
|
||||
if (args.length < 1) {
|
||||
System.err.println("Usage: DumpTileSetMap tileset.map");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
try {
|
||||
MapFileTileSetIDBroker broker = new MapFileTileSetIDBroker(new File(args[0]));
|
||||
Iterator<String> iter = broker.enumerateMappings();
|
||||
while (iter.hasNext()) {
|
||||
String tsname = iter.next().toString();
|
||||
System.out.println(tsname + " => " + broker.getTileSetID(tsname));
|
||||
}
|
||||
|
||||
} catch (PersistenceException pe) {
|
||||
System.err.println("Unable to dump mapping: " + pe);
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tile.tools;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.util.QuickSort;
|
||||
|
||||
import com.threerings.media.tile.TileSetIDBroker;
|
||||
|
||||
/**
|
||||
* Stores a set of tileset name to id mappings in a map file.
|
||||
*/
|
||||
public class MapFileTileSetIDBroker implements TileSetIDBroker
|
||||
{
|
||||
/**
|
||||
* Creates a broker that will use the specified file as its persistent
|
||||
* store. The persistent store will be created if it does not yet
|
||||
* exist.
|
||||
*/
|
||||
public MapFileTileSetIDBroker (File mapfile)
|
||||
throws PersistenceException
|
||||
{
|
||||
// keep this for later
|
||||
_mapfile = mapfile;
|
||||
|
||||
// load up our map data
|
||||
try {
|
||||
BufferedReader bin = new BufferedReader(new FileReader(mapfile));
|
||||
// read in our metadata
|
||||
_nextTileSetID = readInt(bin);
|
||||
_storedTileSetID = _nextTileSetID;
|
||||
// read in our mappings
|
||||
_map = Maps.newHashMap();
|
||||
readMapFile(bin, _map);
|
||||
|
||||
bin.close();
|
||||
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
// create a blank map if our map file doesn't exist
|
||||
_map = Maps.newHashMap();
|
||||
|
||||
} catch (Exception e) {
|
||||
// other errors are more fatal
|
||||
String errmsg = "Failure reading map file.";
|
||||
throw new PersistenceException(errmsg, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected int readInt (BufferedReader bin)
|
||||
throws IOException
|
||||
{
|
||||
String line = bin.readLine();
|
||||
try {
|
||||
return Integer.parseInt(line);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new IOException("Expected number, got '" + line + "'");
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public int getTileSetID (String tileSetName)
|
||||
throws PersistenceException
|
||||
{
|
||||
Integer tsid = _map.get(tileSetName);
|
||||
if (tsid == null) {
|
||||
tsid = Integer.valueOf(++_nextTileSetID);
|
||||
_map.put(tileSetName, tsid);
|
||||
}
|
||||
return tsid.intValue();
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public boolean tileSetMapped (String tileSetName)
|
||||
throws PersistenceException
|
||||
{
|
||||
return _map.containsKey(tileSetName);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void commit ()
|
||||
throws PersistenceException
|
||||
{
|
||||
// only write ourselves out if we've changed
|
||||
if (_storedTileSetID == _nextTileSetID) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
BufferedWriter bout = new BufferedWriter(new FileWriter(_mapfile));
|
||||
// write out our metadata
|
||||
String tline = "" + _nextTileSetID;
|
||||
bout.write(tline, 0, tline.length());
|
||||
bout.newLine();
|
||||
// write out our mappings
|
||||
writeMapFile(bout, _map);
|
||||
bout.close();
|
||||
|
||||
} catch (IOException ioe) {
|
||||
String errmsg = "Failure writing map file.";
|
||||
throw new PersistenceException(errmsg, ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads in a mapping from strings to integers, which should have been
|
||||
* written via {@link #writeMapFile}.
|
||||
*/
|
||||
public static void readMapFile (BufferedReader bin, HashMap<String, Integer> map)
|
||||
throws IOException
|
||||
{
|
||||
String line;
|
||||
while ((line = bin.readLine()) != null) {
|
||||
int eidx = line.indexOf(SEP_STR);
|
||||
if (eidx == -1) {
|
||||
throw new IOException("Malformed line, no '" + SEP_STR +
|
||||
"': '" + line + "'");
|
||||
}
|
||||
try {
|
||||
String code = line.substring(eidx+SEP_STR.length());
|
||||
map.put(line.substring(0, eidx), Integer.valueOf(code));
|
||||
} catch (NumberFormatException nfe) {
|
||||
String errmsg = "Malformed line, invalid code: '" + line + "'";
|
||||
throw new IOException(errmsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes out a mapping from strings to integers in a manner that can
|
||||
* be read back in via {@link #readMapFile}.
|
||||
*/
|
||||
public static void writeMapFile (BufferedWriter bout, HashMap<String, Integer> map)
|
||||
throws IOException
|
||||
{
|
||||
String[] lines = new String[map.size()];
|
||||
Iterator<String> iter = map.keySet().iterator();
|
||||
for (int ii = 0; iter.hasNext(); ii++) {
|
||||
String key = iter.next();
|
||||
Integer value = map.get(key);
|
||||
lines[ii] = key + SEP_STR + value;
|
||||
}
|
||||
QuickSort.sort(lines);
|
||||
for (String line : lines) {
|
||||
bout.write(line, 0, line.length());
|
||||
bout.newLine();
|
||||
}
|
||||
bout.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the ID from the old tileset to the new tileset which is
|
||||
* useful when a tileset is renamed. This is called by the {@link
|
||||
* RenameTileSet} utility.
|
||||
*/
|
||||
protected boolean renameTileSet (String oldName, String newName)
|
||||
{
|
||||
Integer tsid = _map.get(oldName);
|
||||
if (tsid != null) {
|
||||
_map.put(newName, tsid);
|
||||
// fudge our stored tileset ID so that we flush ourselves when
|
||||
// the rename tool requests that we commit the changes
|
||||
_storedTileSetID--;
|
||||
return true;
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by {@link DumpTileSetMap} to enumerate our tileset ID
|
||||
* mappings.
|
||||
*/
|
||||
protected Iterator<String> enumerateMappings ()
|
||||
{
|
||||
return _map.keySet().iterator();
|
||||
}
|
||||
|
||||
/** Our persistent map file. */
|
||||
protected File _mapfile;
|
||||
|
||||
/** The next tileset id that we'll assign. */
|
||||
protected int _nextTileSetID;
|
||||
|
||||
/** The last tileset id assigned when we were unserialized. */
|
||||
protected int _storedTileSetID;
|
||||
|
||||
/** Our mapping from tileset names to ids. */
|
||||
protected HashMap<String, Integer> _map;
|
||||
|
||||
/** The character we use to separate tileset name from code in the map
|
||||
* file. */
|
||||
protected static final String SEP_STR = " := ";
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tile.tools;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
|
||||
/**
|
||||
* Used to map a tileset name to the same ID as a pre-existing tileset.
|
||||
* This only works for tileset mappings stored using a {@link
|
||||
* MapFileTileSetIDBroker}. If a tileset is renamed, this utility must be
|
||||
* used to map the new name to the old ID, otherwise scenes created with
|
||||
* the renamed tileset will cease to work as the tileset will live under a
|
||||
* new ID.
|
||||
*/
|
||||
public class RenameTileSet
|
||||
{
|
||||
/**
|
||||
* Loads up the tileset map file with the specified path and copies
|
||||
* the tileset ID from the old tileset name to the new tileset name.
|
||||
* This is necessary when a tileset is renamed so that the new name
|
||||
* does not cause the tileset to be assigned a new tileset ID. Bear in
|
||||
* mind that the old name should never again be used as it will
|
||||
* conflict with a tileset provided under the new name.
|
||||
*/
|
||||
public static void renameTileSet (
|
||||
String mapPath, String oldName, String newName)
|
||||
throws PersistenceException
|
||||
{
|
||||
MapFileTileSetIDBroker broker =
|
||||
new MapFileTileSetIDBroker(new File(mapPath));
|
||||
if (!broker.renameTileSet(oldName, newName)) {
|
||||
throw new PersistenceException(
|
||||
"No such old tileset '" + oldName + "'.");
|
||||
}
|
||||
broker.commit();
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
if (args.length < 3) {
|
||||
System.err.println("Usage: RenameTileSet tileset.map " +
|
||||
"old_name new_name");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
try {
|
||||
renameTileSet(args[0], args[1], args[2]);
|
||||
} catch (PersistenceException pe) {
|
||||
System.err.println("Unable to rename tileset: " + pe);
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tile.tools.xml;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.samskivert.xml.CallMethodSpecialRule;
|
||||
|
||||
import com.threerings.util.DirectionUtil;
|
||||
|
||||
import com.threerings.media.tile.ObjectTileSet;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
|
||||
/**
|
||||
* Parses {@link ObjectTileSet} instances from a tileset description. An
|
||||
* object tileset description looks like so:
|
||||
*
|
||||
* {@code
|
||||
* <tileset name="Sample Object Tileset">
|
||||
* <imagePath>path/to/image.png</imagePath>
|
||||
* <!-- the widths (per row) of each tile in pixels -->
|
||||
* <widths>265</widths>
|
||||
* <!-- the heights (per row) of each tile in pixels -->
|
||||
* <heights>224</heights>
|
||||
* <!-- the number of tiles in each row -->
|
||||
* <tileCounts>4</tileCounts>
|
||||
* <!-- the offset in pixels to the upper left tile -->
|
||||
* <offsetPos>0, 0</offsetPos>
|
||||
* <!-- the gap between tiles in pixels -->
|
||||
* <gapSize>0, 0</gapSize>
|
||||
* <!-- the widths (in unit tile count) of the objects -->
|
||||
* <objectWidths>4, 3, 4, 3</objectWidths>
|
||||
* <!-- the heights (in unit tile count) of the objects -->
|
||||
* <objectHeights>3, 4, 3, 4</objectHeights>
|
||||
* <!-- the default render priorities for these object tiles -->
|
||||
* <priorities>0, 0, -1, 0</priorities>
|
||||
* <!-- the constraints for these object tiles -->
|
||||
* <constraints>ATTACH_N, ATTACH_E, ATTACH_S, ATTACH_W</constraints>
|
||||
* </tileset>
|
||||
* }
|
||||
*/
|
||||
public class ObjectTileSetRuleSet extends SwissArmyTileSetRuleSet
|
||||
{
|
||||
@Override
|
||||
public void addRuleInstances (Digester digester)
|
||||
{
|
||||
super.addRuleInstances(digester);
|
||||
|
||||
digester.addRule(
|
||||
_path + "/objectWidths",
|
||||
new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
{
|
||||
int[] widths = StringUtil.parseIntArray(bodyText);
|
||||
((ObjectTileSet)target).setObjectWidths(widths);
|
||||
}
|
||||
});
|
||||
|
||||
digester.addRule(
|
||||
_path + "/objectHeights",
|
||||
new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
{
|
||||
int[] heights = StringUtil.parseIntArray(bodyText);
|
||||
((ObjectTileSet)target).setObjectHeights(heights);
|
||||
}
|
||||
});
|
||||
|
||||
digester.addRule(
|
||||
_path + "/xOrigins", new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
{
|
||||
int[] xorigins = StringUtil.parseIntArray(bodyText);
|
||||
((ObjectTileSet)target).setXOrigins(xorigins);
|
||||
}
|
||||
});
|
||||
|
||||
digester.addRule(
|
||||
_path + "/yOrigins", new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
{
|
||||
int[] yorigins = StringUtil.parseIntArray(bodyText);
|
||||
((ObjectTileSet)target).setYOrigins(yorigins);
|
||||
}
|
||||
});
|
||||
|
||||
digester.addRule(
|
||||
_path + "/priorities",
|
||||
new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
{
|
||||
byte[] prios = StringUtil.parseByteArray(bodyText);
|
||||
((ObjectTileSet)target).setPriorities(prios);
|
||||
}
|
||||
});
|
||||
|
||||
digester.addRule(
|
||||
_path + "/zations", new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
{
|
||||
String[] zations = StringUtil.parseStringArray(bodyText);
|
||||
((ObjectTileSet)target).setColorizations(zations);
|
||||
}
|
||||
});
|
||||
|
||||
digester.addRule(
|
||||
_path + "/xspots", new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
{
|
||||
short[] xspots = StringUtil.parseShortArray(bodyText);
|
||||
((ObjectTileSet)target).setXSpots(xspots);
|
||||
}
|
||||
});
|
||||
|
||||
digester.addRule(
|
||||
_path + "/yspots", new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
{
|
||||
short[] yspots = StringUtil.parseShortArray(bodyText);
|
||||
((ObjectTileSet)target).setYSpots(yspots);
|
||||
}
|
||||
});
|
||||
|
||||
digester.addRule(
|
||||
_path + "/sorients", new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
{
|
||||
ObjectTileSet set = (ObjectTileSet)target;
|
||||
String[] ostrs = StringUtil.parseStringArray(bodyText);
|
||||
byte[] sorients = new byte[ostrs.length];
|
||||
for (int ii = 0; ii < sorients.length; ii++) {
|
||||
sorients[ii] = (byte)
|
||||
DirectionUtil.fromShortString(ostrs[ii]);
|
||||
if ((sorients[ii] == DirectionUtil.NONE) &&
|
||||
// don't complain if they didn't even try to
|
||||
// specify a valid direction
|
||||
(! ostrs[ii].equals("-1"))) {
|
||||
System.err.println("Invalid spot orientation " +
|
||||
"[set=" + set.getName() +
|
||||
", idx=" + ii +
|
||||
", orient=" + ostrs[ii] + "].");
|
||||
}
|
||||
}
|
||||
set.setSpotOrients(sorients);
|
||||
}
|
||||
});
|
||||
|
||||
digester.addRule(
|
||||
_path + "/constraints",
|
||||
new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
{
|
||||
String[] constrs = StringUtil.parseStringArray(
|
||||
bodyText);
|
||||
String[][] constraints = new String[constrs.length][];
|
||||
for (int ii = 0; ii < constrs.length; ii++) {
|
||||
constraints[ii] = constrs[ii].split("\\s*\\|\\s*");
|
||||
}
|
||||
((ObjectTileSet)target).setConstraints(constraints);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<? extends TileSet> getTileSetClass ()
|
||||
{
|
||||
return ObjectTileSet.class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tile.tools.xml;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Point;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.samskivert.xml.CallMethodSpecialRule;
|
||||
|
||||
import com.threerings.media.tile.SwissArmyTileSet;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
|
||||
import static com.threerings.media.Log.log;
|
||||
|
||||
/**
|
||||
* Parses {@link SwissArmyTileSet} instances from a tileset description. A
|
||||
* swiss army tileset description looks like so:
|
||||
*
|
||||
* {@code
|
||||
* <tileset name="Sample Swiss Army Tileset">
|
||||
* <imagePath>path/to/image.png</imagePath>
|
||||
* <!-- the widths (per row) of each tile in pixels -->
|
||||
* <widths>64, 64, 64, 64</widths>
|
||||
* <!-- the heights (per row) of each tile in pixels -->
|
||||
* <heights>48, 48, 48, 64</heights>
|
||||
* <!-- the number of tiles in each row -->
|
||||
* <tileCounts>16, 5, 3, 10</tileCounts>
|
||||
* <!-- the offset in pixels to the upper left tile -->
|
||||
* <offsetPos>8, 8</offsetPos>
|
||||
* <!-- the gap between tiles in pixels -->
|
||||
* <gapSize>12, 12</gapSize>
|
||||
* </tileset>
|
||||
* }
|
||||
*/
|
||||
public class SwissArmyTileSetRuleSet extends TileSetRuleSet
|
||||
{
|
||||
@Override
|
||||
public void addRuleInstances (Digester digester)
|
||||
{
|
||||
super.addRuleInstances(digester);
|
||||
|
||||
digester.addRule(
|
||||
_path + "/widths", new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
{
|
||||
int[] widths = StringUtil.parseIntArray(bodyText);
|
||||
((SwissArmyTileSet)target).setWidths(widths);
|
||||
}
|
||||
});
|
||||
|
||||
digester.addRule(
|
||||
_path + "/heights", new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
{
|
||||
int[] heights = StringUtil.parseIntArray(bodyText);
|
||||
((SwissArmyTileSet)target).setHeights(heights);
|
||||
}
|
||||
});
|
||||
|
||||
digester.addRule(
|
||||
_path + "/tileCounts",
|
||||
new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
{
|
||||
int[] tileCounts = StringUtil.parseIntArray(bodyText);
|
||||
((SwissArmyTileSet)target).setTileCounts(tileCounts);
|
||||
}
|
||||
});
|
||||
|
||||
digester.addRule(
|
||||
_path + "/offsetPos", new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
{
|
||||
int[] values = StringUtil.parseIntArray(bodyText);
|
||||
SwissArmyTileSet starget = (SwissArmyTileSet)target;
|
||||
if (values.length == 2) {
|
||||
starget.setOffsetPos(new Point(values[0], values[1]));
|
||||
} else {
|
||||
log.warning("Invalid 'offsetPos' definition '" +
|
||||
bodyText + "'.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
digester.addRule(
|
||||
_path + "/gapSize", new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
{
|
||||
int[] values = StringUtil.parseIntArray(bodyText);
|
||||
SwissArmyTileSet starget = (SwissArmyTileSet)target;
|
||||
if (values.length == 2) {
|
||||
starget.setGapSize(new Dimension(values[0], values[1]));
|
||||
} else {
|
||||
log.warning("Invalid 'gapSize' definition '" +
|
||||
bodyText + "'.");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid (Object target)
|
||||
{
|
||||
SwissArmyTileSet set = (SwissArmyTileSet)target;
|
||||
boolean valid = super.isValid(target);
|
||||
|
||||
// check for a <widths> element
|
||||
if (set.getWidths() == null) {
|
||||
log.warning("Tile set definition missing valid <widths> " +
|
||||
"element [set=" + set + "].");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// check for a <heights> element
|
||||
if (set.getHeights() == null) {
|
||||
log.warning("Tile set definition missing valid <heights> " +
|
||||
"element [set=" + set + "].");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// check for a <tileCounts> element
|
||||
if (set.getTileCounts() == null) {
|
||||
log.warning("Tile set definition missing valid <tileCounts> " +
|
||||
"element [set=" + set + "].");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<? extends TileSet> getTileSetClass ()
|
||||
{
|
||||
return SwissArmyTileSet.class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tile.tools.xml;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
import org.apache.commons.digester.RuleSetBase;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.samskivert.xml.ValidatedSetNextRule;
|
||||
import com.samskivert.xml.ValidatedSetNextRule.Validator;
|
||||
|
||||
import com.threerings.media.tile.TileSet;
|
||||
|
||||
import static com.threerings.media.Log.log;
|
||||
|
||||
/**
|
||||
* The tileset rule set is used to parse the base attributes of a tileset
|
||||
* instance. Derived classes would extend this and add rules for their own
|
||||
* special tilesets.
|
||||
*/
|
||||
public abstract class TileSetRuleSet
|
||||
extends RuleSetBase implements Validator
|
||||
{
|
||||
/** The component of the digester path that is appended by the tileset
|
||||
* rule set to match a tileset. This is appended to whatever prefix is
|
||||
* provided to the tileset rule set to obtain the complete XML path to
|
||||
* a matched tileset. */
|
||||
public static final String TILESET_PATH = "/tileset";
|
||||
|
||||
/**
|
||||
* @return The full path used to match tilesets. Consists of the prefile plus _tilesetPath.
|
||||
*/
|
||||
public String getPath () {
|
||||
return _path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the tileset rule set to match tilesets with the supplied
|
||||
* prefix. For example, passing a prefix of
|
||||
* <code>tilesets.objectsets</code> will match tilesets in the
|
||||
* following XML file:
|
||||
*
|
||||
* <pre>
|
||||
* <tilesets>
|
||||
* <objectsets>
|
||||
* <tileset>
|
||||
* // ...
|
||||
* </tileset>
|
||||
* </objectsets>
|
||||
* </tilesets>
|
||||
* </pre>
|
||||
*
|
||||
* This must be called before adding the ruleset to a digester.
|
||||
*/
|
||||
public void setPrefix (String prefix)
|
||||
{
|
||||
_path = prefix + _tilesetPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the necessary rules to the digester to parse our tilesets.
|
||||
* Derived classes should override this method, being sure to call the
|
||||
* superclass method and then adding their own rule instances (which
|
||||
* should register themselves relative to the <code>_prefix</code>
|
||||
* member).
|
||||
*/
|
||||
@Override
|
||||
public void addRuleInstances (Digester digester)
|
||||
{
|
||||
// this creates the appropriate instance when we encounter a
|
||||
// <tileset> tag
|
||||
digester.addObjectCreate(_path, getTileSetClass().getName());
|
||||
|
||||
// grab the name attribute from the <tileset> tag
|
||||
digester.addSetProperties(_path);
|
||||
|
||||
// grab the image path from an element
|
||||
digester.addCallMethod(_path + "/imagePath", "setImagePath", 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* The ruleset can be provided to a {@link ValidatedSetNextRule} to ensure that the tileset
|
||||
* was fully parsed before doing something with it.
|
||||
*/
|
||||
public boolean isValid (Object target)
|
||||
{
|
||||
TileSet set = (TileSet)target;
|
||||
boolean valid = true;
|
||||
|
||||
// check for the 'name' attribute
|
||||
if (StringUtil.isBlank(set.getName())) {
|
||||
log.warning("Tile set definition missing 'name' attribute " +
|
||||
"[set=" + set + "].");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// check for an <imagePath> element
|
||||
if (StringUtil.isBlank(set.getImagePath())) {
|
||||
log.warning("Tile set definition missing <imagePath> element " +
|
||||
"[set=" + set + "].");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* A tileset rule set will create tilesets of a particular class,
|
||||
* which must be provided by the derived class via this method.
|
||||
*/
|
||||
protected abstract Class<? extends TileSet> getTileSetClass ();
|
||||
|
||||
/** The tileset path we append to the prefix to get the full path. */
|
||||
protected String _tilesetPath = TILESET_PATH;
|
||||
|
||||
/** The full path at which me match our tilesets. */
|
||||
protected String _path;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tile.tools.xml;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.UniformTileSet;
|
||||
|
||||
import static com.threerings.media.Log.log;
|
||||
|
||||
/**
|
||||
* Parses {@link UniformTileSet} instances from a tileset description. A
|
||||
* uniform tileset description looks like so:
|
||||
*
|
||||
* {@code
|
||||
* <tileset name="Sample Uniform Tileset">
|
||||
* <imagePath>path/to/image.png</imagePath>
|
||||
* <!-- the width of each tile in pixels -->
|
||||
* <width>64</width>
|
||||
* <!-- the height of each tile in pixels -->
|
||||
* <height>48</height>
|
||||
* </tileset>
|
||||
* }
|
||||
*/
|
||||
public class UniformTileSetRuleSet extends TileSetRuleSet
|
||||
{
|
||||
public UniformTileSetRuleSet(){
|
||||
this(TILESET_PATH);
|
||||
}
|
||||
|
||||
public UniformTileSetRuleSet(String tilesetPath){
|
||||
_tilesetPath = tilesetPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRuleInstances (Digester digester)
|
||||
{
|
||||
super.addRuleInstances(digester);
|
||||
|
||||
digester.addCallMethod(_path + "/width", "setWidth", 0,
|
||||
new Class<?>[] { java.lang.Integer.TYPE });
|
||||
digester.addCallMethod(_path + "/height", "setHeight", 0,
|
||||
new Class<?>[] { java.lang.Integer.TYPE });
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid (Object target)
|
||||
{
|
||||
UniformTileSet set = (UniformTileSet)target;
|
||||
boolean valid = super.isValid(target);
|
||||
|
||||
// check for a <width> element
|
||||
if (set.getWidth() == 0) {
|
||||
log.warning("Tile set definition missing valid <width> " +
|
||||
"element [set=" + set + "].");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// check for a <height> element
|
||||
if (set.getHeight() == 0) {
|
||||
log.warning("Tile set definition missing valid <height> " +
|
||||
"element [set=" + set + "].");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<? extends TileSet> getTileSetClass ()
|
||||
{
|
||||
return UniformTileSet.class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tile.tools.xml;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.samskivert.util.ConfigUtil;
|
||||
import com.samskivert.xml.ValidatedSetNextRule;
|
||||
|
||||
import com.threerings.media.tile.TileSet;
|
||||
|
||||
import static com.threerings.media.Log.log;
|
||||
|
||||
/**
|
||||
* Parse an XML tileset description file and construct tileset objects for
|
||||
* each valid description. Does not currently perform validation on the
|
||||
* input XML stream, though the parsing code assumes the XML document is
|
||||
* well-formed.
|
||||
*/
|
||||
public class XMLTileSetParser
|
||||
{
|
||||
/**
|
||||
* Constructs an xml tile set parser.
|
||||
*/
|
||||
public XMLTileSetParser ()
|
||||
{
|
||||
// create our digester
|
||||
_digester = new Digester();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a ruleset to be used when parsing tiles. This should be an
|
||||
* instance of a class derived from {@link TileSetRuleSet}. The prefix
|
||||
* will be used to configure the ruleset so that it matches elements
|
||||
* at a particular point in the XML hierarchy. For example:
|
||||
*
|
||||
* <pre>
|
||||
* _parser.addRuleSet("tilesets", new UniformTileSetRuleSet());
|
||||
* </pre>
|
||||
*/
|
||||
public void addRuleSet (String prefix, TileSetRuleSet ruleset)
|
||||
{
|
||||
// configure the ruleset with the appropriate prefix
|
||||
ruleset.setPrefix(prefix);
|
||||
|
||||
// and have it set itself up with the digester
|
||||
_digester.addRuleSet(ruleset);
|
||||
|
||||
// add a set next rule which will put tilesets with this prefix
|
||||
// into the array list that'll be on the top of the stack
|
||||
_digester.addRule(ruleset.getPath(),
|
||||
new ValidatedSetNextRule("add", Object.class,
|
||||
ruleset));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all of the tilesets specified in the supplied XML tileset
|
||||
* description file and places them into the supplied map indexed
|
||||
* by tileset name. This method is not reentrant, so don't go calling
|
||||
* it from multiple threads.
|
||||
*
|
||||
* @param path a path, relative to the classpath, at which the tileset
|
||||
* definition file can be found.
|
||||
* @param tilesets the map into which the tilesets will be placed,
|
||||
* indexed by tileset name.
|
||||
*/
|
||||
public void loadTileSets (String path, Map<String, TileSet> tilesets)
|
||||
throws IOException
|
||||
{
|
||||
// get an input stream for this XML file
|
||||
InputStream is = ConfigUtil.getStream(path);
|
||||
if (is == null) {
|
||||
String errmsg = "Can't load tileset description file from " +
|
||||
"classpath [path=" + path + "].";
|
||||
throw new FileNotFoundException(errmsg);
|
||||
}
|
||||
|
||||
// load up the tilesets
|
||||
loadTileSets(is, tilesets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all of the tilesets specified in the supplied XML tileset
|
||||
* description file and places them into the supplied map indexed
|
||||
* by tileset name. This method is not reentrant, so don't go calling
|
||||
* it from multiple threads.
|
||||
*
|
||||
* @param file the file in which the tileset definition file can be
|
||||
* found.
|
||||
* @param tilesets the map into which the tilesets will be placed,
|
||||
* indexed by tileset name.
|
||||
*/
|
||||
public void loadTileSets (File file, Map<String, TileSet> tilesets)
|
||||
throws IOException
|
||||
{
|
||||
// load up the tilesets
|
||||
loadTileSets(new FileInputStream(file), tilesets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all of the tilesets specified in the supplied XML tileset
|
||||
* description file and places them into the supplied map indexed
|
||||
* by tileset name. This method is not reentrant, so don't go calling
|
||||
* it from multiple threads.
|
||||
*
|
||||
* @param source an input stream from which the tileset definition
|
||||
* file can be read.
|
||||
* @param tilesets the map into which the tilesets will be placed,
|
||||
* indexed by tileset name.
|
||||
*/
|
||||
public void loadTileSets (InputStream source, Map<String, TileSet> tilesets)
|
||||
throws IOException
|
||||
{
|
||||
// stick an array list on the top of the stack for collecting
|
||||
// parsed tilesets
|
||||
List<TileSet> setlist = Lists.newArrayList();
|
||||
_digester.push(setlist);
|
||||
|
||||
// now fire up the digester to parse the stream
|
||||
try {
|
||||
_digester.parse(source);
|
||||
} catch (SAXException saxe) {
|
||||
log.warning("Exception parsing tile set descriptions.", saxe);
|
||||
}
|
||||
|
||||
// stick the tilesets from the list into the hashtable
|
||||
for (int ii = 0; ii < setlist.size(); ii++) {
|
||||
TileSet set = setlist.get(ii);
|
||||
if (set.getName() == null) {
|
||||
log.warning("Tileset did not receive name during " +
|
||||
"parsing process [set=" + set + "].");
|
||||
} else {
|
||||
tilesets.put(set.getName(), set);
|
||||
}
|
||||
}
|
||||
|
||||
// and clear out the list for next time
|
||||
setlist.clear();
|
||||
}
|
||||
|
||||
/** Our XML digester. */
|
||||
protected Digester _digester;
|
||||
}
|
||||
@@ -0,0 +1,640 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tools;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.GridLayout;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSlider;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.filechooser.FileFilter;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.samskivert.util.PrefsConfig;
|
||||
import com.samskivert.util.QuickSort;
|
||||
|
||||
import com.samskivert.swing.HGroupLayout;
|
||||
import com.samskivert.swing.VGroupLayout;
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
|
||||
import com.threerings.media.image.ColorPository;
|
||||
import com.threerings.media.image.Colorization;
|
||||
import com.threerings.media.image.ImageUtil;
|
||||
import com.threerings.media.image.ColorPository.ClassRecord;
|
||||
import com.threerings.media.image.ColorPository.ColorRecord;
|
||||
import com.threerings.media.image.tools.xml.ColorPositoryParser;
|
||||
|
||||
/**
|
||||
* Tests the image recoloring code.
|
||||
*/
|
||||
public class RecolorImage extends JPanel
|
||||
implements ActionListener
|
||||
{
|
||||
public RecolorImage ()
|
||||
{
|
||||
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
|
||||
VGroupLayout vlay = new VGroupLayout(VGroupLayout.STRETCH);
|
||||
vlay.setOffAxisPolicy(VGroupLayout.STRETCH);
|
||||
setLayout(vlay);
|
||||
|
||||
JPanel images = new JPanel(new HGroupLayout());
|
||||
images.add(_oldImage = new JLabel());
|
||||
images.add(_newImage = new JLabel());
|
||||
add(new JScrollPane(images));
|
||||
|
||||
// Image file
|
||||
JPanel file = new JPanel(new HGroupLayout(HGroupLayout.STRETCH));
|
||||
file.add(new JLabel("Image file:"), HGroupLayout.FIXED);
|
||||
file.add(_imagePath = new JTextField());
|
||||
_imagePath.setEditable(false);
|
||||
JButton browse = new JButton("Browse...");
|
||||
browse.setActionCommand(BROWSE_FOR_IMAGE_FILE);
|
||||
browse.addActionListener(this);
|
||||
file.add(browse, HGroupLayout.FIXED);
|
||||
JButton reload = new JButton("Reload");
|
||||
reload.setActionCommand(RELOAD_IMAGE);
|
||||
reload.addActionListener(this);
|
||||
file.add(reload, HGroupLayout.FIXED);
|
||||
add(file, VGroupLayout.FIXED);
|
||||
|
||||
JPanel colFile = new JPanel(new HGroupLayout(HGroupLayout.STRETCH));
|
||||
colFile.add(new JLabel("Colorize file:"), HGroupLayout.FIXED);
|
||||
colFile.add(_colFilePath = new JTextField());
|
||||
_colFilePath.setEditable(false);
|
||||
browse = new JButton("Browse...");
|
||||
browse.setActionCommand(BROWSE_FOR_COLORIZATION_FILE);
|
||||
browse.addActionListener(this);
|
||||
colFile.add(browse, HGroupLayout.FIXED);
|
||||
add(colFile, VGroupLayout.FIXED);
|
||||
|
||||
_tabs = new JTabbedPane();
|
||||
_tabs.addChangeListener(new ChangeListener() {
|
||||
public void stateChanged (ChangeEvent event) {
|
||||
convert();
|
||||
}
|
||||
});
|
||||
add(_tabs, VGroupLayout.FIXED);
|
||||
|
||||
// Colorization file
|
||||
JPanel byFile = new JPanel(new VGroupLayout(VGroupLayout.STRETCH));
|
||||
_tabs.addTab("Using Color Class", byFile);
|
||||
|
||||
byFile.add(_classList = new JComboBox());
|
||||
byFile.add(_labelColors = new JCheckBox("Label Colorizations"), VGroupLayout.FIXED);
|
||||
ActionListener al = new ActionListener() {
|
||||
public void actionPerformed (ActionEvent ae) {
|
||||
convert();
|
||||
}
|
||||
};
|
||||
_classList.addActionListener(al);
|
||||
|
||||
// Specific colors
|
||||
JPanel multiColor = new JPanel(new GridLayout(4, 2));
|
||||
_tabs.addTab("Multi-Color", multiColor);
|
||||
multiColor.add(_classList1 = new JComboBox());
|
||||
multiColor.add(_colorList1 = new JComboBox());
|
||||
multiColor.add(_classList2 = new JComboBox());
|
||||
multiColor.add(_colorList2 = new JComboBox());
|
||||
multiColor.add(_classList3 = new JComboBox());
|
||||
multiColor.add(_colorList3 = new JComboBox());
|
||||
multiColor.add(_classList4 = new JComboBox());
|
||||
multiColor.add(_colorList4 = new JComboBox());
|
||||
|
||||
_colorList1.addActionListener(al);
|
||||
_colorList2.addActionListener(al);
|
||||
_colorList3.addActionListener(al);
|
||||
_colorList4.addActionListener(al);
|
||||
|
||||
_labelColors.addActionListener(al);
|
||||
JPanel specRecolor = new JPanel(new VGroupLayout(VGroupLayout.STRETCH));
|
||||
_tabs.addTab("Manual Recolor", specRecolor);
|
||||
JPanel controls = new JPanel(new HGroupLayout(HGroupLayout.STRETCH));
|
||||
controls.add(new JLabel("Source color:"), HGroupLayout.FIXED);
|
||||
controls.add(_source = new JTextField("FF0000"));
|
||||
_colorLabel = new JPanel();
|
||||
_colorLabel.setSize(48, 48);
|
||||
_colorLabel.setOpaque(true);
|
||||
controls.add(_colorLabel, HGroupLayout.FIXED);
|
||||
controls.add(new JLabel("Target color:"), HGroupLayout.FIXED);
|
||||
controls.add(_target = new JTextField());
|
||||
JButton update = new JButton("Update");
|
||||
update.setActionCommand(UPDATE_TARGET_COLOR);
|
||||
update.addActionListener(this);
|
||||
controls.add(update, HGroupLayout.FIXED);
|
||||
specRecolor.add(controls, VGroupLayout.FIXED);
|
||||
|
||||
HGroupLayout hlay = new HGroupLayout(HGroupLayout.STRETCH);
|
||||
JPanel dists = new JPanel(hlay);
|
||||
dists.add(new JLabel("HSV distances:"), HGroupLayout.FIXED);
|
||||
dists.add(_hueD = new SliderAndLabel(0.0f, 1.0f, 0.05f));
|
||||
dists.add(_saturationD = new SliderAndLabel(0.0f, 1.0f, 0.8f));
|
||||
dists.add(_valueD = new SliderAndLabel(0.0f, 1.0f, 0.6f));
|
||||
specRecolor.add(dists, VGroupLayout.FIXED);
|
||||
|
||||
hlay = new HGroupLayout(HGroupLayout.STRETCH);
|
||||
JPanel offsets = new JPanel(hlay);
|
||||
offsets.add(new JLabel("HSV offsets:"), HGroupLayout.FIXED);
|
||||
offsets.add(_hueO = new SliderAndLabel(-1.0f, 1.0f, 0.1f));
|
||||
offsets.add(_saturationO = new SliderAndLabel(-1.0f, 1.0f, 0.0f));
|
||||
offsets.add(_valueO = new SliderAndLabel(-1.0f, 1.0f, 0.0f));
|
||||
specRecolor.add(offsets, VGroupLayout.FIXED);
|
||||
|
||||
add(_status = new JTextField(), VGroupLayout.FIXED);
|
||||
_status.setEditable(false);
|
||||
|
||||
hlay = new HGroupLayout();
|
||||
hlay.setJustification(HGroupLayout.CENTER);
|
||||
JPanel buttons = new JPanel(hlay);
|
||||
JButton button = new JButton("Convert");
|
||||
button.setActionCommand(CONVERT);
|
||||
button.addActionListener(this);
|
||||
buttons.add(button);
|
||||
button = new JButton("Save Snapshot");
|
||||
button.setActionCommand(SAVE_COLORIZED_IMAGE);
|
||||
button.addActionListener(this);
|
||||
buttons.add(button);
|
||||
add(buttons, VGroupLayout.FIXED);
|
||||
|
||||
// listen for mouse clicks
|
||||
images.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed (MouseEvent event) {
|
||||
RecolorImage.this.mousePressed(event);
|
||||
}
|
||||
});
|
||||
|
||||
// we'll be using a file chooser
|
||||
String cwd = System.getProperty("user.dir");
|
||||
|
||||
String image = CONFIG.getValue(LAST_IMAGE_KEY, cwd);
|
||||
String colorization = CONFIG.getValue(LAST_COLORIZATION_KEY, cwd);
|
||||
_chooser = new JFileChooser(image);
|
||||
_colChooser = new JFileChooser(colorization);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs colorizations.
|
||||
*/
|
||||
protected void convert ()
|
||||
{
|
||||
if (_image == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// obtain the target color and offset
|
||||
try {
|
||||
BufferedImage image;
|
||||
if (_tabs.getSelectedIndex() == 0) {
|
||||
// All recolorings from file.
|
||||
image = getAllRecolors(_labelColors.isSelected());
|
||||
if (image == null) {
|
||||
return;
|
||||
}
|
||||
} else if (_tabs.getSelectedIndex() == 1) {
|
||||
ArrayList<Colorization> zations = Lists.newArrayList();
|
||||
if (_classList1.getSelectedItem() != NONE) {
|
||||
zations.add(_colRepo.getColorization((String)_classList1.getSelectedItem(),
|
||||
(String)_colorList1.getSelectedItem()));
|
||||
}
|
||||
if (_classList2.getSelectedItem() != NONE) {
|
||||
zations.add(_colRepo.getColorization((String)_classList2.getSelectedItem(),
|
||||
(String)_colorList2.getSelectedItem()));
|
||||
}
|
||||
if (_classList3.getSelectedItem() != NONE) {
|
||||
zations.add(_colRepo.getColorization((String)_classList3.getSelectedItem(),
|
||||
(String)_colorList3.getSelectedItem()));
|
||||
}
|
||||
if (_classList4.getSelectedItem() != NONE) {
|
||||
zations.add(_colRepo.getColorization((String)_classList4.getSelectedItem(),
|
||||
(String)_colorList4.getSelectedItem()));
|
||||
}
|
||||
|
||||
image = ImageUtil.recolorImage(_image,
|
||||
zations.toArray(new Colorization[zations.size()]));
|
||||
} else {
|
||||
// Manual recoloring
|
||||
int color = Integer.parseInt(_source.getText(), 16);
|
||||
|
||||
float hueD = _hueD.getValue();
|
||||
float satD = _saturationD.getValue();
|
||||
float valD = _valueD.getValue();
|
||||
float[] dists = new float[] { hueD, satD, valD };
|
||||
|
||||
float hueO = _hueO.getValue();
|
||||
float satO = _saturationO.getValue();
|
||||
float valO = _valueO.getValue();
|
||||
float[] offsets = new float[] { hueO, satO, valO };
|
||||
|
||||
image = ImageUtil.recolorImage(_image, new Color(color), dists, offsets);
|
||||
}
|
||||
_newImage.setIcon(new ImageIcon(image));
|
||||
_status.setText("Recolored image.");
|
||||
repaint();
|
||||
|
||||
} catch (NumberFormatException nfe) {
|
||||
_status.setText("Invalid value: " + nfe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an image with all recolorings of the selection colorization class.
|
||||
*/
|
||||
public BufferedImage getAllRecolors (boolean label)
|
||||
{
|
||||
if (_colRepo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ColorPository.ClassRecord colClass =
|
||||
_colRepo.getClassRecord((String)_classList.getSelectedItem());
|
||||
int classId = colClass.classId;
|
||||
|
||||
BufferedImage img = new BufferedImage(_image.getWidth(),
|
||||
_image.getHeight()*colClass.colors.size(), BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics gfx = img.getGraphics();
|
||||
gfx.setColor(Color.BLACK);
|
||||
int y = 0;
|
||||
|
||||
|
||||
Integer[] sortedKeys =
|
||||
colClass.colors.keySet().toArray(new Integer[colClass.colors.size()]);
|
||||
Arrays.sort(sortedKeys);
|
||||
|
||||
for (int key : sortedKeys) {
|
||||
Colorization coloriz = _colRepo.getColorization(classId, key);
|
||||
BufferedImage subImg = ImageUtil.recolorImage(
|
||||
_image, coloriz.rootColor, coloriz.range, coloriz.offsets);
|
||||
|
||||
gfx.drawImage(subImg, 0, y, null, null);
|
||||
|
||||
if (label) {
|
||||
ColorRecord crec = _colRepo.getColorRecord(classId, key);
|
||||
gfx.drawString(crec.name, 2, y + gfx.getFontMetrics().getHeight() + 2);
|
||||
gfx.drawRect(0, y, _image.getWidth() - 1, _image.getHeight());
|
||||
}
|
||||
|
||||
y += subImg.getHeight();
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
public void actionPerformed (ActionEvent event)
|
||||
{
|
||||
String cmd = event.getActionCommand();
|
||||
|
||||
if (cmd.equals(CONVERT)) {
|
||||
convert();
|
||||
} else if (cmd.equals(UPDATE_TARGET_COLOR)) {
|
||||
// obtain the target color and offset
|
||||
try {
|
||||
int source = Integer.parseInt(_source.getText(), 16);
|
||||
int target = Integer.parseInt(_target.getText(), 16);
|
||||
float[] shsv = rgbToHSV(source);
|
||||
float[] thsv = rgbToHSV(target);
|
||||
|
||||
// set the offsets based on the differences
|
||||
_hueO.setValue(thsv[0] - shsv[0]);
|
||||
_saturationO.setValue(thsv[1] - shsv[1]);
|
||||
_valueO.setValue(thsv[2] - shsv[2]);
|
||||
|
||||
} catch (NumberFormatException nfe) {
|
||||
_status.setText("Invalid value: " + nfe.getMessage());
|
||||
}
|
||||
|
||||
} else if (cmd.equals(BROWSE_FOR_IMAGE_FILE)) {
|
||||
int result = _chooser.showOpenDialog(this);
|
||||
if (result == JFileChooser.APPROVE_OPTION) {
|
||||
setImage(_chooser.getSelectedFile());
|
||||
}
|
||||
} else if (cmd.equals(RELOAD_IMAGE)) {
|
||||
setImage(_chooser.getSelectedFile());
|
||||
} else if (cmd.equals(BROWSE_FOR_COLORIZATION_FILE)) {
|
||||
int result = _colChooser.showOpenDialog(this);
|
||||
if (result == JFileChooser.APPROVE_OPTION) {
|
||||
setColorizeFile(_colChooser.getSelectedFile());
|
||||
}
|
||||
} else if (cmd.equals(SAVE_COLORIZED_IMAGE)) {
|
||||
JFileChooser chooser = new JFileChooser(_chooser.getSelectedFile());
|
||||
chooser.setFileFilter(new FileFilter() {
|
||||
@Override public boolean accept (File f) {
|
||||
return (f.isDirectory() || f.getName().endsWith(".png"));
|
||||
}
|
||||
@Override public String getDescription () {
|
||||
return "PNG Files";
|
||||
}
|
||||
});
|
||||
int result = chooser.showSaveDialog(this);
|
||||
if (result == JFileChooser.APPROVE_OPTION) {
|
||||
try {
|
||||
ImageIO.write((BufferedImage)((ImageIcon)_newImage.getIcon()).getImage(),
|
||||
"png", chooser.getSelectedFile());
|
||||
} catch (Exception e) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"Error while saving image: " + e.getMessage(),
|
||||
"Error Saving Image",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setImage (File path)
|
||||
{
|
||||
try {
|
||||
_image = ImageIO.read(path);
|
||||
_imagePath.setText(path.getPath());
|
||||
_oldImage.setIcon(new ImageIcon(_image));
|
||||
|
||||
CONFIG.setValue(LAST_IMAGE_KEY, path.getAbsolutePath());
|
||||
} catch (IOException ioe) {
|
||||
_status.setText("Error opening image file: " + ioe);
|
||||
}
|
||||
}
|
||||
|
||||
public void setupColors (JComboBox box, String className)
|
||||
{
|
||||
box.removeAllItems();
|
||||
if (className != null && className != NONE) {
|
||||
ClassRecord classRec = _colRepo.getClassRecord(className);
|
||||
|
||||
ArrayList<String> colorNames = Lists.newArrayList();
|
||||
for (ColorRecord color : classRec.colors.values()) {
|
||||
colorNames.add(color.name);
|
||||
}
|
||||
|
||||
QuickSort.sort(colorNames);
|
||||
|
||||
for (String colorName : colorNames) {
|
||||
box.addItem(colorName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads up the colorization classes from the specified file.
|
||||
*/
|
||||
public void setColorizeFile (File path)
|
||||
{
|
||||
try {
|
||||
if (path.getName().endsWith("xml")) {
|
||||
ColorPositoryParser parser = new ColorPositoryParser();
|
||||
_colRepo = (ColorPository)(parser.parseConfig(path));
|
||||
} else {
|
||||
_colRepo = ColorPository.loadColorPository(new FileInputStream(path));
|
||||
}
|
||||
|
||||
_classList.removeAllItems();
|
||||
_classList1.removeAllItems();
|
||||
_classList1.addActionListener(new ActionListener() {
|
||||
public void actionPerformed (ActionEvent event) {
|
||||
setupColors(_colorList1, (String)_classList1.getSelectedItem());
|
||||
}
|
||||
});
|
||||
_classList2.removeAllItems();
|
||||
_classList2.addActionListener(new ActionListener() {
|
||||
public void actionPerformed (ActionEvent event) {
|
||||
setupColors(_colorList2, (String)_classList2.getSelectedItem());
|
||||
}
|
||||
});
|
||||
_classList3.removeAllItems();
|
||||
_classList3.addActionListener(new ActionListener() {
|
||||
public void actionPerformed (ActionEvent event) {
|
||||
setupColors(_colorList3, (String)_classList3.getSelectedItem());
|
||||
}
|
||||
});
|
||||
_classList4.removeAllItems();
|
||||
_classList4.addActionListener(new ActionListener() {
|
||||
public void actionPerformed (ActionEvent event) {
|
||||
setupColors(_colorList4, (String)_classList4.getSelectedItem());
|
||||
}
|
||||
});
|
||||
Iterator<ColorPository.ClassRecord> iter = _colRepo.enumerateClasses();
|
||||
ArrayList<String> names = Lists.newArrayList();
|
||||
while (iter.hasNext()) {
|
||||
String str = iter.next().name;
|
||||
names.add(str);
|
||||
}
|
||||
|
||||
_classList1.addItem(NONE);
|
||||
_classList2.addItem(NONE);
|
||||
_classList3.addItem(NONE);
|
||||
_classList4.addItem(NONE);
|
||||
|
||||
Collections.sort(names);
|
||||
for (String name : names) {
|
||||
_classList.addItem(name);
|
||||
_classList1.addItem(name);
|
||||
_classList2.addItem(name);
|
||||
_classList3.addItem(name);
|
||||
_classList4.addItem(name);
|
||||
}
|
||||
|
||||
_classList.setSelectedIndex(0);
|
||||
_classList1.setSelectedIndex(0);
|
||||
_classList2.setSelectedIndex(0);
|
||||
_classList3.setSelectedIndex(0);
|
||||
_classList4.setSelectedIndex(0);
|
||||
_colFilePath.setText(path.getPath());
|
||||
|
||||
CONFIG.setValue(LAST_COLORIZATION_KEY, path.getAbsolutePath());
|
||||
} catch (Exception ex) {
|
||||
_status.setText("Error opening colorization file: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected static float[] rgbToHSV (int rgb)
|
||||
{
|
||||
float[] hsv = new float[3];
|
||||
Color color = new Color(rgb);
|
||||
Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), hsv);
|
||||
return hsv;
|
||||
}
|
||||
|
||||
public void mousePressed (MouseEvent event)
|
||||
{
|
||||
// if the click was in the bounds of the source image, grab the
|
||||
// pixel color and use that to set the "source" color
|
||||
int x = event.getX(), y = event.getY();
|
||||
Rectangle ibounds = _oldImage.getBounds();
|
||||
if (ibounds.contains(x, y)) {
|
||||
int argb = _image.getRGB(x - ibounds.x, y - ibounds.y);
|
||||
String cstr = Integer.toString(argb & 0xFFFFFF, 16);
|
||||
_source.setText(cstr.toUpperCase());
|
||||
_colorLabel.setBackground(new Color(argb));
|
||||
_colorLabel.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class with linked slider and label arranged vertically.
|
||||
*/
|
||||
protected class SliderAndLabel extends JPanel
|
||||
{
|
||||
public SliderAndLabel (float minf, float maxf, float valuef) {
|
||||
int min = (int)(minf*CONVERSION);
|
||||
int max = (int)(maxf*CONVERSION);
|
||||
int value = (int)(valuef*CONVERSION);
|
||||
setLayout(new VGroupLayout(VGroupLayout.STRETCH));
|
||||
_intField = new JLabel(String.valueOf(value/CONVERSION));
|
||||
_slider = new JSlider(min, max, value);
|
||||
|
||||
_slider.addChangeListener(new ChangeListener() {
|
||||
public void stateChanged (ChangeEvent ce) {
|
||||
_intField.setText(String.valueOf((_slider.getValue())/CONVERSION));
|
||||
|
||||
convert();
|
||||
}
|
||||
});
|
||||
add(_intField);
|
||||
add(_slider);
|
||||
}
|
||||
|
||||
public float getValue () {
|
||||
return _slider.getValue()/CONVERSION;
|
||||
}
|
||||
|
||||
public void setValue (float val) {
|
||||
_slider.setValue((int)(val*CONVERSION));
|
||||
}
|
||||
|
||||
protected JSlider _slider;
|
||||
protected JLabel _intField;
|
||||
|
||||
protected final static float CONVERSION = 1000.0f;
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
try {
|
||||
JFrame frame = new JFrame("Image recoloring test");
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
|
||||
RecolorImage panel = new RecolorImage();
|
||||
|
||||
// load up the image from the command line if one was
|
||||
// specified
|
||||
if (args.length > 0) {
|
||||
panel.setImage(new File(args[0]));
|
||||
}
|
||||
|
||||
frame.getContentPane().add(panel, BorderLayout.CENTER);
|
||||
frame.setSize(600, 600);
|
||||
SwingUtil.centerWindow(frame);
|
||||
frame.setVisible(true);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
protected BufferedImage _image;
|
||||
protected JFileChooser _chooser;
|
||||
protected JFileChooser _colChooser;
|
||||
protected JTextField _imagePath;
|
||||
protected JTextField _colFilePath;
|
||||
|
||||
protected JLabel _oldImage;
|
||||
protected JLabel _newImage;
|
||||
protected JPanel _colorLabel;
|
||||
|
||||
protected JTextField _source;
|
||||
protected JTextField _target;
|
||||
|
||||
protected SliderAndLabel _hueO;
|
||||
protected SliderAndLabel _saturationO;
|
||||
protected SliderAndLabel _valueO;
|
||||
|
||||
protected SliderAndLabel _hueD;
|
||||
protected SliderAndLabel _saturationD;
|
||||
protected SliderAndLabel _valueD;
|
||||
|
||||
protected JTextField _status;
|
||||
|
||||
protected JComboBox _classList;
|
||||
protected JComboBox _classList1;
|
||||
protected JComboBox _classList2;
|
||||
protected JComboBox _classList3;
|
||||
protected JComboBox _classList4;
|
||||
protected JComboBox _colorList1;
|
||||
protected JComboBox _colorList2;
|
||||
protected JComboBox _colorList3;
|
||||
protected JComboBox _colorList4;
|
||||
protected JCheckBox _labelColors;
|
||||
|
||||
protected JTabbedPane _tabs;
|
||||
|
||||
protected ColorPository _colRepo;
|
||||
|
||||
protected static final String IMAGE_PATH =
|
||||
"bundles/components/pirate/head/regular/standing.png";
|
||||
|
||||
/** The actions for our various buttons. */
|
||||
protected static final String BROWSE_FOR_IMAGE_FILE = "browse_image";
|
||||
protected static final String RELOAD_IMAGE = "reload_image";
|
||||
protected static final String BROWSE_FOR_COLORIZATION_FILE = "browse_colorize";
|
||||
protected static final String SAVE_COLORIZED_IMAGE = "save_colorized";
|
||||
protected static final String UPDATE_TARGET_COLOR = "update_target";
|
||||
protected static final String CONVERT = "convert";
|
||||
|
||||
/** Where we can stash our preferences. */
|
||||
protected static final PrefsConfig CONFIG =
|
||||
new PrefsConfig("rsrc/config/threerings/recolorimage");
|
||||
|
||||
protected static final String NONE = "<none>";
|
||||
|
||||
/** Keys for our preferences. */
|
||||
protected static final String LAST_IMAGE_KEY = "last_image";
|
||||
protected static final String LAST_COLORIZATION_KEY = "last_colorization";
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tools;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.DirectoryScanner;
|
||||
import org.apache.tools.ant.Task;
|
||||
import org.apache.tools.ant.types.FileSet;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* Creates a file that lists all the resources in a fileset out to an index file.
|
||||
*/
|
||||
public class ResourceIndexerTask extends Task
|
||||
{
|
||||
/**
|
||||
* Adds a nested <fileset> element.
|
||||
*/
|
||||
public void addFileset (FileSet set)
|
||||
{
|
||||
_filesets.add(set);
|
||||
}
|
||||
|
||||
public void setIndexFile (String file)
|
||||
{
|
||||
_indexFile = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute () throws BuildException
|
||||
{
|
||||
PrintWriter fout = null;
|
||||
try {
|
||||
fout = new PrintWriter(new FileWriter(getProject().getBaseDir() + "/" + _indexFile));
|
||||
|
||||
for (FileSet fs : _filesets) {
|
||||
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
|
||||
String[] srcFiles = ds.getIncludedFiles();
|
||||
for (String filename : srcFiles) {
|
||||
fout.println(filename);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
throw new BuildException(ioe);
|
||||
} finally {
|
||||
if (fout != null) {
|
||||
fout.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A list of filesets that contain files to include in the index. */
|
||||
protected ArrayList<FileSet> _filesets = Lists.newArrayList();
|
||||
|
||||
/** The name of the file to which we should write the index. */
|
||||
protected String _indexFile;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.miso.tile.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.Task;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
|
||||
import com.threerings.util.CompiledConfig;
|
||||
|
||||
import com.threerings.media.tile.tools.MapFileTileSetIDBroker;
|
||||
|
||||
import com.threerings.miso.tile.tools.xml.FringeConfigurationParser;
|
||||
|
||||
/**
|
||||
* Compile fringe configuration.
|
||||
*/
|
||||
public class CompileFringeConfigurationTask extends Task
|
||||
{
|
||||
public void setTileSetMap (File tsetmap)
|
||||
{
|
||||
_tsetmap = tsetmap;
|
||||
}
|
||||
|
||||
public void setFringeDef (File fringedef)
|
||||
{
|
||||
_fringedef = fringedef;
|
||||
}
|
||||
|
||||
public void setTarget (File target)
|
||||
{
|
||||
_target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute () throws BuildException
|
||||
{
|
||||
// make sure the source file exists
|
||||
if (!_fringedef.exists()) {
|
||||
throw new BuildException("Fringe definition file not found " +
|
||||
"[path=" + _fringedef.getPath() + "].");
|
||||
}
|
||||
|
||||
// set up the tileid broker
|
||||
MapFileTileSetIDBroker broker;
|
||||
try {
|
||||
broker = new MapFileTileSetIDBroker(_tsetmap);
|
||||
} catch (PersistenceException pe) {
|
||||
throw new BuildException("Couldn't set up tileset mapping " +
|
||||
"[path=" + _tsetmap.getPath() +
|
||||
", error=" + pe.getCause() + "].");
|
||||
}
|
||||
|
||||
FringeConfigurationParser parser = new FringeConfigurationParser(
|
||||
broker);
|
||||
Serializable config;
|
||||
try {
|
||||
config = parser.parseConfig(_fringedef);
|
||||
} catch (Exception e) {
|
||||
throw new BuildException("Failure parsing config definition", e);
|
||||
}
|
||||
|
||||
try {
|
||||
// and write it on out
|
||||
CompiledConfig.saveConfig(_target, config);
|
||||
} catch (Exception e) {
|
||||
throw new BuildException("Failure writing serialized config", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected File _tsetmap;
|
||||
protected File _fringedef;
|
||||
protected File _target;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.miso.tile.tools.xml;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.samskivert.xml.CallMethodSpecialRule;
|
||||
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.tools.xml.SwissArmyTileSetRuleSet;
|
||||
|
||||
import com.threerings.miso.tile.BaseTileSet;
|
||||
|
||||
import static com.threerings.miso.Log.log;
|
||||
|
||||
/**
|
||||
* Parses {@link BaseTileSet} instances from a tileset description. Base
|
||||
* tilesets extend swiss army tilesets with the addition of a passability
|
||||
* flag for each tile.
|
||||
*
|
||||
* @see SwissArmyTileSetRuleSet
|
||||
*/
|
||||
public class BaseTileSetRuleSet extends SwissArmyTileSetRuleSet
|
||||
{
|
||||
@Override
|
||||
public void addRuleInstances (Digester digester)
|
||||
{
|
||||
super.addRuleInstances(digester);
|
||||
|
||||
digester.addRule(_path + "/passable", new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target) {
|
||||
int[] values = StringUtil.parseIntArray(bodyText);
|
||||
boolean[] passable = new boolean[values.length];
|
||||
for (int ii = 0; ii < values.length; ii++) {
|
||||
passable[ii] = (values[ii] != 0);
|
||||
}
|
||||
BaseTileSet starget = (BaseTileSet)target;
|
||||
starget.setPassability(passable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid (Object target)
|
||||
{
|
||||
BaseTileSet set = (BaseTileSet)target;
|
||||
boolean valid = super.isValid(target);
|
||||
|
||||
// check for a <passable> element
|
||||
if (set.getPassability() == null) {
|
||||
log.warning("Tile set definition missing valid <passable> " +
|
||||
"element [set=" + set + "].");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<? extends TileSet> getTileSetClass ()
|
||||
{
|
||||
return BaseTileSet.class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.miso.tile.tools.xml;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.samskivert.xml.SetPropertyFieldsRule;
|
||||
import com.samskivert.xml.ValidatedSetNextRule;
|
||||
|
||||
import com.threerings.media.tile.TileSetIDBroker;
|
||||
|
||||
import com.threerings.miso.tile.FringeConfiguration;
|
||||
import com.threerings.miso.tile.FringeConfiguration.FringeRecord;
|
||||
import com.threerings.miso.tile.FringeConfiguration.FringeTileSetRecord;
|
||||
|
||||
import com.threerings.tools.xml.CompiledConfigParser;
|
||||
|
||||
import static com.threerings.miso.Log.log;
|
||||
|
||||
/**
|
||||
* Parses fringe config definitions.
|
||||
*/
|
||||
public class FringeConfigurationParser extends CompiledConfigParser
|
||||
{
|
||||
public FringeConfigurationParser (TileSetIDBroker broker)
|
||||
{
|
||||
_idBroker = broker;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Serializable createConfigObject ()
|
||||
{
|
||||
return new FringeConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addRules (Digester digest)
|
||||
{
|
||||
// configure top-level constraints
|
||||
String prefix = "fringe";
|
||||
digest.addRule(prefix, new SetPropertyFieldsRule());
|
||||
|
||||
// create and configure fringe config instances
|
||||
prefix += "/base";
|
||||
digest.addObjectCreate(prefix, FringeRecord.class.getName());
|
||||
|
||||
ValidatedSetNextRule.Validator val;
|
||||
val = new ValidatedSetNextRule.Validator() {
|
||||
public boolean isValid (Object target) {
|
||||
if (((FringeRecord) target).isValid()) {
|
||||
return true;
|
||||
} else {
|
||||
log.warning("A FringeRecord was not added because it was " +
|
||||
"improperly specified [rec=" + target + "].");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
ValidatedSetNextRule vrule;
|
||||
vrule = new ValidatedSetNextRule("addFringeRecord", val) {
|
||||
// parse the fringe record, converting tileset names to
|
||||
// tileset ids
|
||||
@Override
|
||||
public void begin (String namespace, String lname, Attributes attrs)
|
||||
throws Exception
|
||||
{
|
||||
FringeRecord frec = (FringeRecord) digester.peek();
|
||||
|
||||
for (int ii=0; ii < attrs.getLength(); ii++) {
|
||||
String name = attrs.getLocalName(ii);
|
||||
if (StringUtil.isBlank(name)) {
|
||||
name = attrs.getQName(ii);
|
||||
}
|
||||
String value = attrs.getValue(ii);
|
||||
|
||||
if ("name".equals(name)) {
|
||||
if (_idBroker.tileSetMapped(value)) {
|
||||
frec.base_tsid = _idBroker.getTileSetID(value);
|
||||
} else {
|
||||
log.warning("Skipping unknown base " +
|
||||
"tileset [name=" + value + "].");
|
||||
}
|
||||
|
||||
} else if ("priority".equals(name)) {
|
||||
frec.priority = Integer.parseInt(value);
|
||||
} else {
|
||||
log.warning("Skipping unknown attribute " +
|
||||
"[name=" + name + "].");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
digest.addRule(prefix, vrule);
|
||||
|
||||
// create the tileset records in each fringe record
|
||||
prefix += "/tileset";
|
||||
digest.addObjectCreate(prefix, FringeTileSetRecord.class.getName());
|
||||
|
||||
val = new ValidatedSetNextRule.Validator() {
|
||||
public boolean isValid (Object target) {
|
||||
if (((FringeTileSetRecord) target).isValid()) {
|
||||
return true;
|
||||
} else {
|
||||
log.warning("A FringeTileSetRecord was not added because " +
|
||||
"it was improperly specified " +
|
||||
"[rec=" + target + "].");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
vrule = new ValidatedSetNextRule("addTileset", val) {
|
||||
// parse the fringe tilesetrecord, converting tileset names to ids
|
||||
@Override
|
||||
public void begin (String namespace, String lname, Attributes attrs)
|
||||
throws Exception
|
||||
{
|
||||
FringeTileSetRecord f = (FringeTileSetRecord) digester.peek();
|
||||
|
||||
for (int ii=0; ii < attrs.getLength(); ii++) {
|
||||
String name = attrs.getLocalName(ii);
|
||||
if (StringUtil.isBlank(name)) {
|
||||
name = attrs.getQName(ii);
|
||||
}
|
||||
String value = attrs.getValue(ii);
|
||||
|
||||
if ("name".equals(name)) {
|
||||
if (_idBroker.tileSetMapped(value)) {
|
||||
f.fringe_tsid = _idBroker.getTileSetID(value);
|
||||
} else {
|
||||
log.warning("Skipping unknown fringe " +
|
||||
"tileset [name=" + value + "].");
|
||||
}
|
||||
|
||||
} else if ("mask".equals(name)) {
|
||||
f.mask = Boolean.valueOf(value).booleanValue();
|
||||
} else {
|
||||
log.warning("Skipping unknown attribute " +
|
||||
"[name=" + name + "].");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
digest.addRule(prefix, vrule);
|
||||
}
|
||||
|
||||
protected TileSetIDBroker _idBroker;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.miso.tools.xml;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.miso.data.SimpleMisoSceneModel;
|
||||
|
||||
/**
|
||||
* A simple class for parsing simple miso scene models.
|
||||
*/
|
||||
public class SimpleMisoSceneParser
|
||||
{
|
||||
/**
|
||||
* Constructs a scene parser that parses scenes with the specified XML
|
||||
* path prefix.
|
||||
*/
|
||||
public SimpleMisoSceneParser (String prefix)
|
||||
{
|
||||
// create and configure our digester
|
||||
_digester = new Digester();
|
||||
|
||||
// create our scene rule set
|
||||
SimpleMisoSceneRuleSet set = new SimpleMisoSceneRuleSet();
|
||||
|
||||
// configure our top-level path prefix
|
||||
if (StringUtil.isBlank(prefix)) {
|
||||
_prefix = set.getOuterElement();
|
||||
} else {
|
||||
_prefix = prefix + "/" + set.getOuterElement();
|
||||
}
|
||||
|
||||
// add the scene rules
|
||||
set.addRuleInstances(_prefix, _digester);
|
||||
|
||||
// add a rule to grab the finished scene model
|
||||
_digester.addSetNext(
|
||||
_prefix, "setScene", SimpleMisoSceneModel.class.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the XML file at the specified path into a scene model
|
||||
* instance.
|
||||
*/
|
||||
public SimpleMisoSceneModel parseScene (String path)
|
||||
throws IOException, SAXException
|
||||
{
|
||||
_model = null;
|
||||
_digester.push(this);
|
||||
_digester.parse(new FileInputStream(path));
|
||||
return _model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the parser once the scene is parsed.
|
||||
*/
|
||||
public void setScene (SimpleMisoSceneModel model)
|
||||
{
|
||||
_model = model;
|
||||
}
|
||||
|
||||
protected String _prefix;
|
||||
protected Digester _digester;
|
||||
protected SimpleMisoSceneModel _model;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.miso.tools.xml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
import org.apache.commons.digester.Rule;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.samskivert.xml.CallMethodSpecialRule;
|
||||
import com.samskivert.xml.SetFieldRule;
|
||||
import com.samskivert.xml.SetPropertyFieldsRule;
|
||||
|
||||
import com.threerings.miso.data.ObjectInfo;
|
||||
import com.threerings.miso.data.SimpleMisoSceneModel;
|
||||
|
||||
import com.threerings.tools.xml.NestableRuleSet;
|
||||
|
||||
/**
|
||||
* Used to parse a {@link SimpleMisoSceneModel} from XML.
|
||||
*/
|
||||
public class SimpleMisoSceneRuleSet implements NestableRuleSet
|
||||
{
|
||||
// documentation inherited from interface
|
||||
public String getOuterElement ()
|
||||
{
|
||||
return SimpleMisoSceneWriter.OUTER_ELEMENT;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void addRuleInstances (String prefix, Digester dig)
|
||||
{
|
||||
// this creates the appropriate instance when we encounter our
|
||||
// prefix tag
|
||||
dig.addRule(prefix, new Rule() {
|
||||
@Override
|
||||
public void begin (String namespace, String name,
|
||||
Attributes attributes) throws Exception {
|
||||
digester.push(createMisoSceneModel());
|
||||
}
|
||||
@Override
|
||||
public void end (String namespace, String name) throws Exception {
|
||||
digester.pop();
|
||||
}
|
||||
});
|
||||
|
||||
// set up rules to parse and set our fields
|
||||
dig.addRule(prefix + "/width", new SetFieldRule("width"));
|
||||
dig.addRule(prefix + "/height", new SetFieldRule("height"));
|
||||
dig.addRule(prefix + "/viewwidth", new SetFieldRule("vwidth"));
|
||||
dig.addRule(prefix + "/viewheight", new SetFieldRule("vheight"));
|
||||
dig.addRule(prefix + "/base", new SetFieldRule("baseTileIds"));
|
||||
|
||||
dig.addObjectCreate(prefix + "/objects", ArrayList.class.getName());
|
||||
dig.addObjectCreate(prefix + "/objects/object",
|
||||
ObjectInfo.class.getName());
|
||||
dig.addSetNext(prefix + "/objects/object", "add",
|
||||
Object.class.getName());
|
||||
|
||||
dig.addRule(prefix + "/objects/object", new SetPropertyFieldsRule());
|
||||
|
||||
dig.addRule(prefix + "/objects", new CallMethodSpecialRule() {
|
||||
@Override
|
||||
public void parseAndSet (String bodyText, Object target)
|
||||
throws Exception
|
||||
{
|
||||
@SuppressWarnings("unchecked") ArrayList<ObjectInfo> ilist =
|
||||
(ArrayList<ObjectInfo>)target;
|
||||
ArrayList<ObjectInfo> ulist = Lists.newArrayList();
|
||||
SimpleMisoSceneModel model = (SimpleMisoSceneModel)
|
||||
digester.peek(1);
|
||||
|
||||
// filter interesting and uninteresting into two lists
|
||||
for (int ii = 0; ii < ilist.size(); ii++) {
|
||||
ObjectInfo info = ilist.get(ii);
|
||||
if (!info.isInteresting()) {
|
||||
ilist.remove(ii--);
|
||||
ulist.add(info);
|
||||
}
|
||||
}
|
||||
|
||||
// now populate the model
|
||||
SimpleMisoSceneModel.populateObjects(model, ilist, ulist);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected SimpleMisoSceneModel createMisoSceneModel ()
|
||||
{
|
||||
return new SimpleMisoSceneModel(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.miso.tools.xml;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.AttributesImpl;
|
||||
|
||||
import com.megginson.sax.DataWriter;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.miso.data.ObjectInfo;
|
||||
import com.threerings.miso.data.SimpleMisoSceneModel;
|
||||
|
||||
import com.threerings.tools.xml.NestableWriter;
|
||||
|
||||
/**
|
||||
* Generates an XML representation of a {@link SimpleMisoSceneModel}.
|
||||
*/
|
||||
public class SimpleMisoSceneWriter implements NestableWriter
|
||||
{
|
||||
/** The element used to enclose scene models written with this
|
||||
* writer. */
|
||||
public static final String OUTER_ELEMENT = "miso";
|
||||
|
||||
// documentation inherited from interface
|
||||
public void write (Object object, DataWriter writer)
|
||||
throws SAXException
|
||||
{
|
||||
SimpleMisoSceneModel model = (SimpleMisoSceneModel)object;
|
||||
writer.startElement(OUTER_ELEMENT);
|
||||
writeSceneData(model, writer);
|
||||
writer.endElement(OUTER_ELEMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes just the scene data which is handy for derived classes which
|
||||
* may wish to add their own scene data to the scene output.
|
||||
*/
|
||||
protected void writeSceneData (SimpleMisoSceneModel model,
|
||||
DataWriter writer)
|
||||
throws SAXException
|
||||
{
|
||||
writer.dataElement("width", Integer.toString(model.width));
|
||||
writer.dataElement("height", Integer.toString(model.height));
|
||||
writer.dataElement("viewwidth", Integer.toString(model.vwidth));
|
||||
writer.dataElement("viewheight", Integer.toString(model.vheight));
|
||||
writer.dataElement("base",
|
||||
StringUtil.toString(model.baseTileIds, "", ""));
|
||||
|
||||
// write our uninteresting object tile information
|
||||
writer.startElement("objects");
|
||||
for (int ii = 0; ii < model.objectTileIds.length; ii++) {
|
||||
AttributesImpl attrs = new AttributesImpl();
|
||||
attrs.addAttribute("", "tileId", "", "",
|
||||
String.valueOf(model.objectTileIds[ii]));
|
||||
attrs.addAttribute("", "x", "", "",
|
||||
String.valueOf(model.objectXs[ii]));
|
||||
attrs.addAttribute("", "y", "", "",
|
||||
String.valueOf(model.objectYs[ii]));
|
||||
writer.emptyElement("", "object", "", attrs);
|
||||
}
|
||||
|
||||
// write our uninteresting object tile information
|
||||
for (ObjectInfo info : model.objectInfo) {
|
||||
AttributesImpl attrs = new AttributesImpl();
|
||||
attrs.addAttribute("", "tileId", "", "",
|
||||
String.valueOf(info.tileId));
|
||||
attrs.addAttribute("", "x", "", "", String.valueOf(info.x));
|
||||
attrs.addAttribute("", "y", "", "", String.valueOf(info.y));
|
||||
|
||||
if (!StringUtil.isBlank(info.action)) {
|
||||
attrs.addAttribute("", "action", "", "", info.action);
|
||||
}
|
||||
|
||||
if (info.priority != 0) {
|
||||
attrs.addAttribute("", "priority", "", "",
|
||||
String.valueOf(info.priority));
|
||||
}
|
||||
|
||||
if (info.sx != 0 || info.sy != 0) {
|
||||
attrs.addAttribute("", "sx", "", "",
|
||||
String.valueOf(info.sx));
|
||||
attrs.addAttribute("", "sy", "", "",
|
||||
String.valueOf(info.sy));
|
||||
attrs.addAttribute("", "sorient", "", "",
|
||||
String.valueOf(info.sorient));
|
||||
}
|
||||
|
||||
if (info.zations != 0) {
|
||||
attrs.addAttribute("", "zations", "", "",
|
||||
String.valueOf(info.zations));
|
||||
}
|
||||
writer.emptyElement("", "object", "", attrs);
|
||||
}
|
||||
writer.endElement("objects");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.miso.tools.xml;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
|
||||
import com.samskivert.io.StreamUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.miso.data.SparseMisoSceneModel;
|
||||
|
||||
/**
|
||||
* A simple class for parsing simple miso scene models.
|
||||
*/
|
||||
public class SparseMisoSceneParser
|
||||
{
|
||||
/**
|
||||
* Constructs a scene parser that parses scenes with the specified XML
|
||||
* path prefix.
|
||||
*/
|
||||
public SparseMisoSceneParser (String prefix)
|
||||
{
|
||||
// create and configure our digester
|
||||
_digester = new Digester();
|
||||
|
||||
// create our scene rule set
|
||||
SparseMisoSceneRuleSet set = new SparseMisoSceneRuleSet();
|
||||
|
||||
// configure our top-level path prefix
|
||||
if (StringUtil.isBlank(prefix)) {
|
||||
_prefix = set.getOuterElement();
|
||||
} else {
|
||||
_prefix = prefix + "/" + set.getOuterElement();
|
||||
}
|
||||
|
||||
// add the scene rules
|
||||
set.addRuleInstances(_prefix, _digester);
|
||||
|
||||
// add a rule to grab the finished scene model
|
||||
_digester.addSetNext(
|
||||
_prefix, "setScene", SparseMisoSceneModel.class.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the XML file at the specified path into a scene model
|
||||
* instance.
|
||||
*/
|
||||
public SparseMisoSceneModel parseScene (String path)
|
||||
throws IOException, SAXException
|
||||
{
|
||||
_model = null;
|
||||
_digester.push(this);
|
||||
FileInputStream stream = null;
|
||||
try {
|
||||
stream = new FileInputStream(path);
|
||||
_digester.parse(stream);
|
||||
} finally {
|
||||
StreamUtil.close(stream);
|
||||
}
|
||||
return _model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the parser once the scene is parsed.
|
||||
*/
|
||||
public void setScene (SparseMisoSceneModel model)
|
||||
{
|
||||
_model = model;
|
||||
}
|
||||
|
||||
protected String _prefix;
|
||||
protected Digester _digester;
|
||||
protected SparseMisoSceneModel _model;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.miso.tools.xml;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
import org.apache.commons.digester.Rule;
|
||||
|
||||
import com.samskivert.xml.SetFieldRule;
|
||||
import com.samskivert.xml.SetPropertyFieldsRule;
|
||||
|
||||
import com.threerings.miso.data.ObjectInfo;
|
||||
import com.threerings.miso.data.SparseMisoSceneModel;
|
||||
import com.threerings.miso.data.SparseMisoSceneModel.Section;
|
||||
|
||||
import com.threerings.tools.xml.NestableRuleSet;
|
||||
|
||||
/**
|
||||
* Used to parse a {@link SparseMisoSceneModel} from XML.
|
||||
*/
|
||||
public class SparseMisoSceneRuleSet implements NestableRuleSet
|
||||
{
|
||||
// documentation inherited from interface
|
||||
public String getOuterElement ()
|
||||
{
|
||||
return SparseMisoSceneWriter.OUTER_ELEMENT;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void addRuleInstances (String prefix, Digester dig)
|
||||
{
|
||||
// this creates the appropriate instance when we encounter our
|
||||
// prefix tag
|
||||
dig.addRule(prefix, new Rule() {
|
||||
@Override public void begin (String namespace, String name,
|
||||
Attributes attributes) throws Exception {
|
||||
digester.push(createMisoSceneModel());
|
||||
}
|
||||
@Override public void end (String namespace, String name) throws Exception {
|
||||
digester.pop();
|
||||
}
|
||||
});
|
||||
|
||||
// set up rules to parse and set our fields
|
||||
dig.addRule(prefix + "/swidth", new SetFieldRule("swidth"));
|
||||
dig.addRule(prefix + "/sheight", new SetFieldRule("sheight"));
|
||||
dig.addRule(prefix + "/defTileSet", new SetFieldRule("defTileSet"));
|
||||
|
||||
String sprefix = prefix + "/sections/section";
|
||||
dig.addObjectCreate(sprefix, Section.class.getName());
|
||||
dig.addRule(sprefix, new SetPropertyFieldsRule());
|
||||
dig.addRule(sprefix + "/base", new SetFieldRule("baseTileIds"));
|
||||
addObjectExtractor(dig, "objects", sprefix, "addObject");
|
||||
dig.addSetNext(sprefix, "setSection", Section.class.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a set of rules to <code>dig</code> to create an Object info from the element at
|
||||
* base/type/object and calls <code>methodName</code> on the object on dig's stack.
|
||||
*/
|
||||
public static void addObjectExtractor (Digester dig, String type, String base,
|
||||
String methodName)
|
||||
{
|
||||
String prefix = base + "/" + type + "/object";
|
||||
dig.addObjectCreate(prefix, ObjectInfo.class);
|
||||
dig.addRule(prefix, new SetPropertyFieldsRule());
|
||||
dig.addSetNext(prefix, methodName, ObjectInfo.class.getName());
|
||||
}
|
||||
|
||||
protected SparseMisoSceneModel createMisoSceneModel ()
|
||||
{
|
||||
return new SparseMisoSceneModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.miso.tools.xml;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.AttributesImpl;
|
||||
|
||||
import com.megginson.sax.DataWriter;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.miso.data.ObjectInfo;
|
||||
import com.threerings.miso.data.SparseMisoSceneModel;
|
||||
import com.threerings.miso.data.SparseMisoSceneModel.Section;
|
||||
|
||||
import com.threerings.tools.xml.NestableWriter;
|
||||
|
||||
/**
|
||||
* Generates an XML representation of a {@link SparseMisoSceneModel}.
|
||||
*/
|
||||
public class SparseMisoSceneWriter implements NestableWriter
|
||||
{
|
||||
/** The element used to enclose scene models written with this
|
||||
* writer. */
|
||||
public static final String OUTER_ELEMENT = "miso";
|
||||
|
||||
// documentation inherited from interface
|
||||
public void write (Object object, DataWriter writer)
|
||||
throws SAXException
|
||||
{
|
||||
SparseMisoSceneModel model = (SparseMisoSceneModel)object;
|
||||
writer.startElement(OUTER_ELEMENT);
|
||||
writeSceneData(model, writer);
|
||||
writer.endElement(OUTER_ELEMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes just the scene data which is handy for derived classes which
|
||||
* may wish to add their own scene data to the scene output.
|
||||
*/
|
||||
protected void writeSceneData (SparseMisoSceneModel model,
|
||||
DataWriter writer)
|
||||
throws SAXException
|
||||
{
|
||||
writer.dataElement("swidth", Integer.toString(model.swidth));
|
||||
writer.dataElement("sheight", Integer.toString(model.sheight));
|
||||
writer.dataElement("defTileSet", Integer.toString(model.defTileSet));
|
||||
|
||||
writer.startElement("sections");
|
||||
for (Iterator<Section> iter = model.getSections(); iter.hasNext(); ) {
|
||||
Section sect = iter.next();
|
||||
if (sect.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
AttributesImpl attrs = new AttributesImpl();
|
||||
attrs.addAttribute("", "x", "", "", String.valueOf(sect.x));
|
||||
attrs.addAttribute("", "y", "", "", String.valueOf(sect.y));
|
||||
attrs.addAttribute("", "width", "", "", String.valueOf(sect.width));
|
||||
writer.startElement("", "section", "", attrs);
|
||||
|
||||
writer.dataElement(
|
||||
"base", StringUtil.toString(sect.baseTileIds, "", ""));
|
||||
|
||||
// write our uninteresting object tile information
|
||||
writer.startElement("objects");
|
||||
for (int ii = 0; ii < sect.objectTileIds.length; ii++) {
|
||||
attrs = new AttributesImpl();
|
||||
attrs.addAttribute("", "tileId", "", "",
|
||||
String.valueOf(sect.objectTileIds[ii]));
|
||||
attrs.addAttribute("", "x", "", "",
|
||||
String.valueOf(sect.objectXs[ii]));
|
||||
attrs.addAttribute("", "y", "", "",
|
||||
String.valueOf(sect.objectYs[ii]));
|
||||
writer.emptyElement("", "object", "", attrs);
|
||||
}
|
||||
|
||||
// write our interesting object tile information
|
||||
for (ObjectInfo element : sect.objectInfo) {
|
||||
writeInterestingObject(element, writer);
|
||||
}
|
||||
writer.endElement("objects");
|
||||
writer.endElement("section");
|
||||
}
|
||||
writer.endElement("sections");
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes <code>info</code> out to <code>writer</code>.
|
||||
*/
|
||||
public static void writeInterestingObject (ObjectInfo info, DataWriter writer)
|
||||
throws SAXException
|
||||
{
|
||||
AttributesImpl attrs = new AttributesImpl();
|
||||
attrs.addAttribute("", "tileId", "", "", String.valueOf(info.tileId));
|
||||
attrs.addAttribute("", "x", "", "", String.valueOf(info.x));
|
||||
attrs.addAttribute("", "y", "", "", String.valueOf(info.y));
|
||||
|
||||
if (!StringUtil.isBlank(info.action)) {
|
||||
attrs.addAttribute("", "action", "", "", info.action);
|
||||
}
|
||||
if (info.priority != 0) {
|
||||
attrs.addAttribute("", "priority", "", "", String.valueOf(info.priority));
|
||||
}
|
||||
if (info.sx != 0 || info.sy != 0) {
|
||||
attrs.addAttribute("", "sx", "", "", String.valueOf(info.sx));
|
||||
attrs.addAttribute("", "sy", "", "", String.valueOf(info.sy));
|
||||
attrs.addAttribute("", "sorient", "", "", String.valueOf(info.sorient));
|
||||
}
|
||||
if (info.zations != 0) {
|
||||
attrs.addAttribute("", "zations", "", "", String.valueOf(info.zations));
|
||||
}
|
||||
writer.emptyElement("", "object", "", attrs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.tools;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.DirectoryScanner;
|
||||
import org.apache.tools.ant.Task;
|
||||
import org.apache.tools.ant.types.FileSet;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.samskivert.util.FileUtil;
|
||||
|
||||
import com.threerings.util.CompiledConfig;
|
||||
|
||||
import com.threerings.tools.xml.CompiledConfigParser;
|
||||
|
||||
/**
|
||||
* Used to parse configuration information from an XML file and create the
|
||||
* serialized representation that is used by the client and server.
|
||||
*/
|
||||
public class CompiledConfigTask extends Task
|
||||
{
|
||||
public void setParser (String parser)
|
||||
{
|
||||
_parser = parser;
|
||||
}
|
||||
|
||||
public void setConfigdef (File configdef)
|
||||
{
|
||||
_configdef = configdef;
|
||||
}
|
||||
|
||||
public void setTarget (File target)
|
||||
{
|
||||
_target = target;
|
||||
}
|
||||
|
||||
public void setDest (File dest)
|
||||
{
|
||||
_dest = dest;
|
||||
}
|
||||
|
||||
public void addFileset (FileSet set)
|
||||
{
|
||||
_filesets.add(set);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute () throws BuildException
|
||||
{
|
||||
// instantiate and sanity check the parser class
|
||||
Object pobj = null;
|
||||
try {
|
||||
Class<?> pclass = Class.forName(_parser);
|
||||
pobj = pclass.newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new BuildException("Error instantiating config parser", e);
|
||||
}
|
||||
if (!(pobj instanceof CompiledConfigParser)) {
|
||||
throw new BuildException("Invalid parser class: " + _parser);
|
||||
}
|
||||
CompiledConfigParser parser = (CompiledConfigParser)pobj;
|
||||
|
||||
// if we have a single file and target specified, do those
|
||||
if (_configdef != null) {
|
||||
parse(parser, _configdef, _target == null ? getTarget(_configdef) : _target);
|
||||
}
|
||||
|
||||
// deal with the filesets
|
||||
for (FileSet fs : _filesets) {
|
||||
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
|
||||
File fromDir = fs.getDir(getProject());
|
||||
for (String file : ds.getIncludedFiles()) {
|
||||
File source = new File(fromDir, file);
|
||||
parse(parser, source, getTarget(source));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected File getTarget (File source)
|
||||
{
|
||||
if (_dest == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String baseDir = getProject().getBaseDir().getPath();
|
||||
File target = new File(source.getPath().replace(baseDir, _dest.getPath()));
|
||||
target = new File(FileUtil.resuffix(target, ".xml", ".dat"));
|
||||
return target;
|
||||
}
|
||||
|
||||
protected void parse (CompiledConfigParser parser, File confdef, File target)
|
||||
throws BuildException
|
||||
{
|
||||
// make sure the source file exists
|
||||
if (!confdef.exists()) {
|
||||
String errmsg = "Config definition file not found: " + confdef;
|
||||
throw new BuildException(errmsg);
|
||||
}
|
||||
|
||||
// if no target was specified, resuffix the source file as to .dat
|
||||
if (target == null) {
|
||||
target = new File(FileUtil.resuffix(confdef, ".xml", ".dat"));
|
||||
}
|
||||
|
||||
System.out.println("Compiling " + target + "...");
|
||||
Serializable config = null;
|
||||
try {
|
||||
// parse it on up
|
||||
config = parser.parseConfig(confdef);
|
||||
} catch (Exception e) {
|
||||
throw new BuildException("Failure parsing config definition", e);
|
||||
}
|
||||
|
||||
// create the target directory if necessary
|
||||
File parent = target.getParentFile();
|
||||
if (!parent.isDirectory() && !parent.mkdirs()) {
|
||||
throw new BuildException("Failed to create parent directory '" + parent + "'.");
|
||||
}
|
||||
|
||||
try {
|
||||
// and write it on out
|
||||
CompiledConfig.saveConfig(target, config);
|
||||
} catch (Exception e) {
|
||||
throw new BuildException("Failure writing serialized config", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected File _configdef;
|
||||
protected File _target;
|
||||
protected File _dest;
|
||||
protected String _parser;
|
||||
protected ArrayList<FileSet> _filesets = Lists.newArrayList();
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.tools.xml;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
|
||||
import com.threerings.util.CompiledConfig;
|
||||
|
||||
import com.threerings.tools.CompiledConfigTask;
|
||||
|
||||
/**
|
||||
* An abstract base implementation of a parser that is used to compile configuration definitions
|
||||
* into config objects for use by the client and server.
|
||||
*
|
||||
* @see CompiledConfig
|
||||
* @see CompiledConfigTask
|
||||
*/
|
||||
public abstract class CompiledConfigParser
|
||||
{
|
||||
/**
|
||||
* Parses the supplied configuration file into a serializable configuration object.
|
||||
*/
|
||||
public Serializable parseConfig (File source)
|
||||
throws IOException, SAXException
|
||||
{
|
||||
Digester digester = new Digester();
|
||||
Serializable config = createConfigObject();
|
||||
addRules(digester);
|
||||
digester.push(config);
|
||||
digester.parse(new FileInputStream(source));
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the config object instance that will be populated during the parsing process.
|
||||
*/
|
||||
protected abstract Serializable createConfigObject ();
|
||||
|
||||
/**
|
||||
* Adds the necessary digester rules for parsing the config object.
|
||||
*/
|
||||
protected abstract void addRules (Digester digester);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.tools.xml;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
|
||||
/**
|
||||
* Used to define rule sets that can be nested within other rule sets. For
|
||||
* example, say you have a "scene" object definition like so:
|
||||
*
|
||||
* <p> (Note that in the examples square brackets are used instead of
|
||||
* angle brackets to simplify my life when composing the documentation.)
|
||||
*
|
||||
* <pre>
|
||||
* [scene name="Foo" version=5]
|
||||
* [/scene]
|
||||
* </pre>
|
||||
*
|
||||
* This scene is extended with some auxiliary data defined by libraries
|
||||
* which can parse and generate XML for their auxiliary objects:
|
||||
*
|
||||
* <pre>
|
||||
* [scene sceneId=1 name="Foo" version=5]
|
||||
* [spot]
|
||||
* [portal portalId=1 x=1 y=1 targetSceneId=2/]
|
||||
* [portal portalId=2 x=15 y=3 targetSceneId=3/]
|
||||
* [portal portalId=3 x=9 y=6 targetSceneId=4/]
|
||||
* [/spot]
|
||||
* [miso]
|
||||
* [object tileId=878172 x=4 y=13 action="cluck"/]
|
||||
* [object tileId=123843 x=18 y=23 action="bark"/]
|
||||
* [/miso]
|
||||
* [/scene]
|
||||
* </pre>
|
||||
*
|
||||
* The spot and miso services can define nestable rule sets which will be
|
||||
* handed to the scene services who will instruct them to add their rule
|
||||
* instances with a prefix of <code>scene.spot</code> and
|
||||
* <code>scene.miso</code> respectively. They then happily parse their
|
||||
* auxiliary objects without knowing that they have been nested inside
|
||||
* some larger structure.
|
||||
*
|
||||
* <p> The nestable ruleset should then leave a single object on the
|
||||
* digester stack that the enclosing entity can grab.
|
||||
*
|
||||
* <p> This isn't proper use of XML, but it solves the problem at hand in
|
||||
* an easily extensible manner.
|
||||
*/
|
||||
public interface NestableRuleSet
|
||||
{
|
||||
/**
|
||||
* Returns the name of the nested object's outer element so that the
|
||||
* parent parser can use it to compose the total path prefix.
|
||||
*/
|
||||
public String getOuterElement ();
|
||||
|
||||
/**
|
||||
* Instructs this ruleset to add its rules such that it parses its
|
||||
* object from the specified path prefix. The outer element returned
|
||||
* by {@link #getOuterElement} will have been included in the path
|
||||
* prefix.
|
||||
*/
|
||||
public void addRuleInstances (String prefix, Digester digester);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.tools.xml;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import com.megginson.sax.DataWriter;
|
||||
|
||||
/**
|
||||
* Provides the writing component of the nestable parsing system described
|
||||
* by {@link NestableRuleSet}.
|
||||
*/
|
||||
public interface NestableWriter
|
||||
{
|
||||
/**
|
||||
* Called to generate XML for the supplied object to the supplied data
|
||||
* writer.
|
||||
*/
|
||||
public void write (Object object, DataWriter writer)
|
||||
throws SAXException;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast.bundle;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import com.threerings.resource.ResourceManager;
|
||||
|
||||
import com.threerings.media.image.ClientImageManager;
|
||||
|
||||
import com.threerings.cast.ComponentClass;
|
||||
|
||||
public class BundledComponentRepositoryTest extends TestCase
|
||||
{
|
||||
public BundledComponentRepositoryTest ()
|
||||
{
|
||||
super(BundledComponentRepositoryTest.class.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runTest ()
|
||||
{
|
||||
try {
|
||||
ResourceManager rmgr = new ResourceManager("rsrc");
|
||||
rmgr.initBundles(
|
||||
null, "config/resource/manager.properties", null);
|
||||
ClientImageManager imgr = new ClientImageManager(rmgr, (Component)null);
|
||||
BundledComponentRepository repo =
|
||||
new BundledComponentRepository(rmgr, imgr, "components");
|
||||
|
||||
// System.out.println("Classes: " + StringUtil.toString(
|
||||
// repo.enumerateComponentClasses()));
|
||||
|
||||
// System.out.println("Actions: " + StringUtil.toString(
|
||||
// repo.enumerateActionSequences()));
|
||||
|
||||
// System.out.println("Action sets: " + StringUtil.toString(
|
||||
// repo._actionSets.values().iterator()));
|
||||
|
||||
Iterator<ComponentClass> iter = repo.enumerateComponentClasses();
|
||||
while (iter.hasNext()) {
|
||||
// ComponentClass cclass = (ComponentClass)
|
||||
iter.next();
|
||||
// System.out.println("IDs [" + cclass + "]: " +
|
||||
// StringUtil.toString(
|
||||
// repo.enumerateComponentIds(cclass)));
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
BundledComponentRepositoryTest test =
|
||||
new BundledComponentRepositoryTest();
|
||||
test.runTest();
|
||||
}
|
||||
|
||||
public static Test suite ()
|
||||
{
|
||||
return new BundledComponentRepositoryTest();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast.bundle.tools;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import com.threerings.media.tile.SwissArmyTileSet;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
|
||||
/**
|
||||
* Tests the component bundler utilities.
|
||||
*/
|
||||
public class ComponentBundlerUtilTest
|
||||
{
|
||||
@Test
|
||||
public void testParseActionTileSets ()
|
||||
throws Exception
|
||||
{
|
||||
Map<String, TileSet> map = ComponentBundlerUtil.parseActionTileSets(
|
||||
new ByteArrayInputStream(ACTION_DATA.getBytes()));
|
||||
|
||||
SwissArmyTileSet defset = (SwissArmyTileSet)map.get("default");
|
||||
assertNotNull(defset);
|
||||
assertEquals("default", defset.getName());
|
||||
assertEquals(1, defset.getTileCount());
|
||||
assertArrayEquals(new int[] { 1 }, defset.getTileCounts());
|
||||
assertEquals(new Rectangle(0, 0, 540, 640), defset.computeTileBounds(0, new Rectangle()));
|
||||
|
||||
SwissArmyTileSet statset = (SwissArmyTileSet)map.get("static");
|
||||
assertNotNull(statset);
|
||||
assertEquals("static", statset.getName());
|
||||
assertEquals(1, statset.getTileCount());
|
||||
assertArrayEquals(new int[] { 1 }, statset.getTileCounts());
|
||||
assertEquals(new Rectangle(0, 0, 312, 240), statset.computeTileBounds(0, new Rectangle()));
|
||||
}
|
||||
|
||||
protected static final String ACTION_DATA =
|
||||
"<actions>\n" +
|
||||
" <!-- actions relating to the face shot components -->\n" +
|
||||
" <action name=\"default\">\n" +
|
||||
" <framesPerSecond>1</framesPerSecond>\n" +
|
||||
" <origin>270,640</origin>\n" +
|
||||
" <!-- TODO: fix code that requires one array entry for each orientation -->\n" +
|
||||
" <orients>SW</orients>\n" +
|
||||
" <tileset>\n" +
|
||||
" <widths>540</widths>\n" +
|
||||
" <heights>640</heights>\n" +
|
||||
" <tileCounts>1</tileCounts>\n" +
|
||||
" <offsetPos>0, 0</offsetPos>\n" +
|
||||
" <gapSize>0, 0</gapSize>\n" +
|
||||
" </tileset>\n" +
|
||||
" </action>\n" +
|
||||
"\n" +
|
||||
" <!-- actions relating to the gang buckle components -->\n" +
|
||||
" <action name=\"static\">\n" +
|
||||
" <framesPerSecond>1</framesPerSecond>\n" +
|
||||
" <origin>156,240</origin>\n" +
|
||||
" <orients>SW</orients>\n" +
|
||||
" <tileset>\n" +
|
||||
" <widths>312</widths>\n" +
|
||||
" <heights>240</heights>\n" +
|
||||
" <tileCounts>1</tileCounts>\n" +
|
||||
" <offsetPos>0, 0</offsetPos>\n" +
|
||||
" <gapSize>0, 0</gapSize>\n" +
|
||||
" </tileset>\n" +
|
||||
" </action>\n" +
|
||||
"</actions>";
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tile.bundle;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import com.threerings.resource.ResourceManager;
|
||||
|
||||
import com.threerings.media.image.ClientImageManager;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
|
||||
public class BundledTileSetRepositoryTest extends TestCase
|
||||
{
|
||||
public BundledTileSetRepositoryTest ()
|
||||
{
|
||||
super(BundledTileSetRepositoryTest.class.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runTest ()
|
||||
{
|
||||
try {
|
||||
ResourceManager rmgr = new ResourceManager("rsrc");
|
||||
rmgr.initBundles(
|
||||
null, "config/resource/manager.properties", null);
|
||||
BundledTileSetRepository repo = new BundledTileSetRepository(
|
||||
rmgr, new ClientImageManager(rmgr, (Component)null), "tilesets");
|
||||
Iterator<TileSet> sets = repo.enumerateTileSets();
|
||||
while (sets.hasNext()) {
|
||||
sets.next();
|
||||
// System.out.println(sets.next());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static Test suite ()
|
||||
{
|
||||
return new BundledTileSetRepositoryTest();
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
BundledTileSetRepositoryTest test =
|
||||
new BundledTileSetRepositoryTest();
|
||||
test.runTest();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tile.bundle.tools;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.test.TestUtil;
|
||||
|
||||
import com.threerings.media.tile.TileSetIDBroker;
|
||||
|
||||
public class BuildTestTileSetBundle
|
||||
{
|
||||
public static void main (String[] args)
|
||||
{
|
||||
try {
|
||||
TileSetIDBroker broker = new DummyTileSetIDBroker();
|
||||
|
||||
// sort out some paths
|
||||
String configPath = TestUtil.getResourcePath(CONFIG_PATH);
|
||||
String descPath = TestUtil.getResourcePath(BUNDLE_DESC_PATH);
|
||||
String targetPath = TestUtil.getResourcePath(TARGET_PATH);
|
||||
|
||||
// create our bundler and get going
|
||||
TileSetBundler bundler = new TileSetBundler(configPath);
|
||||
File descFile = new File(descPath);
|
||||
bundler.createBundle(broker, descFile, targetPath);
|
||||
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/** Dummy tileset id broker that makes up tileset ids (which are consistent in the course of
|
||||
* execution of the application, but not between invocations). */
|
||||
protected static class DummyTileSetIDBroker extends HashMap<String,Integer>
|
||||
implements TileSetIDBroker
|
||||
{
|
||||
public int getTileSetID (String tileSetName)
|
||||
throws PersistenceException
|
||||
{
|
||||
Integer id = get(tileSetName);
|
||||
if (id == null) {
|
||||
id = new Integer(++_nextId);
|
||||
put(tileSetName, id);
|
||||
}
|
||||
return id.intValue();
|
||||
}
|
||||
|
||||
public boolean tileSetMapped (String tileSetName)
|
||||
{
|
||||
return containsKey(tileSetName);
|
||||
}
|
||||
|
||||
public void commit ()
|
||||
throws PersistenceException
|
||||
{
|
||||
}
|
||||
|
||||
protected int _nextId;
|
||||
}
|
||||
|
||||
protected static final String CONFIG_PATH = "rsrc/media/tile/bundle/tools/bundler-config.xml";
|
||||
protected static final String BUNDLE_DESC_PATH = "rsrc/media/tile/bundle/tools/bundle.xml";
|
||||
protected static final String TARGET_PATH = "rsrc/media/tile/bundle/tools/bundle.jar";
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.tile.tools.xml;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.threerings.media.tile.ObjectTileSet;
|
||||
import com.threerings.media.tile.SwissArmyTileSet;
|
||||
import com.threerings.media.tile.UniformTileSet;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import com.threerings.media.tile.TileSet;
|
||||
|
||||
public class XMLTileSetParserTest
|
||||
{
|
||||
@Test
|
||||
public void testRuleSets ()
|
||||
throws IOException
|
||||
{
|
||||
Map<String, TileSet> sets = new HashMap<String, TileSet>();
|
||||
|
||||
XMLTileSetParser parser = new XMLTileSetParser();
|
||||
parser.addRuleSet("tilesets/uniform", new UniformTileSetRuleSet());
|
||||
parser.addRuleSet("tilesets/swissarmy", new SwissArmyTileSetRuleSet());
|
||||
parser.addRuleSet("tilesets/object", new ObjectTileSetRuleSet());
|
||||
|
||||
// load up the tilesets
|
||||
parser.loadTileSets(TILESET_PATH, sets);
|
||||
|
||||
// make sure they were properly parsed
|
||||
SwissArmyTileSet fset = (SwissArmyTileSet)sets.remove("Fringe");
|
||||
// System.out.println(fset);
|
||||
assertEquals("Fringe", fset.getName());
|
||||
assertEquals("fringe.png", fset.getImagePath());
|
||||
assertEquals(40, fset.getTileCount());
|
||||
assertArrayEquals(repeat(64, 5), fset.getWidths());
|
||||
assertArrayEquals(repeat(48, 5), fset.getHeights());
|
||||
assertArrayEquals(repeat(8, 5), fset.getTileCounts());
|
||||
|
||||
ObjectTileSet bset = (ObjectTileSet)sets.remove("Building");
|
||||
// System.out.println(bset);
|
||||
assertEquals("Building", bset.getName());
|
||||
assertEquals("building.png", bset.getImagePath());
|
||||
assertEquals(4, bset.getTileCount());
|
||||
assertArrayEquals(repeat(224, 1), bset.getWidths());
|
||||
assertArrayEquals(repeat(293, 1), bset.getHeights());
|
||||
assertArrayEquals(repeat(4, 1), bset.getTileCounts());
|
||||
int[] owidths = { 4, 3, 4, 3 }, oheights = { 3, 4, 3, 4 };
|
||||
for (int ii = 0; ii < 4; ii++) {
|
||||
assertEquals(owidths[ii], bset.getBaseWidth(ii));
|
||||
assertEquals(oheights[ii], bset.getBaseHeight(ii));
|
||||
}
|
||||
// TODO: test offset pos and gap size
|
||||
|
||||
UniformTileSet iset = (UniformTileSet)sets.remove("Node Icons");
|
||||
// System.out.println(iset);
|
||||
assertEquals("Node Icons", iset.getName());
|
||||
assertEquals("node-icons.png", iset.getImagePath());
|
||||
assertEquals(16, iset.getWidth());
|
||||
assertEquals(16, iset.getHeight());
|
||||
// TODO: can't test getTileCount() since that requires real image data
|
||||
|
||||
assertEquals(0, sets.size());
|
||||
}
|
||||
|
||||
protected int[] repeat (int value, int count)
|
||||
{
|
||||
int[] ints = new int[count];
|
||||
Arrays.fill(ints, value);
|
||||
return ints;
|
||||
}
|
||||
|
||||
protected static final String TILESET_PATH = "rsrc/media/tile/tools/xml/tilesets.xml";
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.miso.viewer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.awt.DisplayMode;
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.GraphicsDevice;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
|
||||
import com.threerings.resource.ResourceManager;
|
||||
|
||||
import com.threerings.media.FrameManager;
|
||||
import com.threerings.media.image.ClientImageManager;
|
||||
import com.threerings.media.image.ColorPository;
|
||||
import com.threerings.media.tile.bundle.BundledTileSetRepository;
|
||||
|
||||
import com.threerings.miso.data.SimpleMisoSceneModel;
|
||||
import com.threerings.miso.tile.MisoTileManager;
|
||||
import com.threerings.miso.tools.xml.SimpleMisoSceneParser;
|
||||
import com.threerings.miso.util.MisoContext;
|
||||
|
||||
import com.threerings.cast.CharacterManager;
|
||||
import com.threerings.cast.bundle.BundledComponentRepository;
|
||||
|
||||
import static com.threerings.miso.Log.log;
|
||||
|
||||
/**
|
||||
* The ViewerApp is a scene viewing application that allows for trying out game scenes in a
|
||||
* pseudo-runtime environment.
|
||||
*/
|
||||
public class ViewerApp
|
||||
{
|
||||
/**
|
||||
* Construct and initialize the ViewerApp object.
|
||||
*/
|
||||
public ViewerApp (String[] args)
|
||||
throws IOException
|
||||
{
|
||||
if (args.length < 1) {
|
||||
System.err.println("Usage: ViewerApp scene_file.xml");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
// get the graphics environment
|
||||
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
|
||||
// get the target graphics device
|
||||
GraphicsDevice gd = env.getDefaultScreenDevice();
|
||||
log.info("Graphics device", "dev", gd, "mem", gd.getAvailableAcceleratedMemory(),
|
||||
"displayChange", gd.isDisplayChangeSupported(),
|
||||
"fullScreen", gd.isFullScreenSupported());
|
||||
|
||||
// get the graphics configuration and display mode information
|
||||
GraphicsConfiguration gc = gd.getDefaultConfiguration();
|
||||
DisplayMode dm = gd.getDisplayMode();
|
||||
log.info("Display mode", "bits", dm.getBitDepth(), "wid", dm.getWidth(),
|
||||
"hei", dm.getHeight(), "refresh", dm.getRefreshRate());
|
||||
|
||||
// create the window
|
||||
_frame = new ViewerFrame(gc);
|
||||
_framemgr = FrameManager.newInstance(_frame);
|
||||
|
||||
// we don't need to configure anything
|
||||
ResourceManager rmgr = new ResourceManager("rsrc");
|
||||
rmgr.initBundles(null, "config/resource/manager.properties", null);
|
||||
ClientImageManager imgr = new ClientImageManager(rmgr, _frame);
|
||||
_tilemgr = new MisoTileManager(rmgr, imgr);
|
||||
_tilemgr.setTileSetRepository(new BundledTileSetRepository(rmgr, imgr, "tilesets"));
|
||||
|
||||
// create the context object
|
||||
MisoContext ctx = new ContextImpl();
|
||||
|
||||
// create the various managers
|
||||
BundledComponentRepository crepo =
|
||||
new BundledComponentRepository(rmgr, imgr, "components");
|
||||
CharacterManager charmgr = new CharacterManager(imgr, crepo);
|
||||
ColorPository cpos = ColorPository.loadColorPository(rmgr);
|
||||
|
||||
// create our scene view panel
|
||||
_panel = new ViewerSceneViewPanel(ctx, charmgr, crepo, cpos);
|
||||
_frame.setPanel(_panel);
|
||||
|
||||
// load up the scene specified by the user
|
||||
try {
|
||||
SimpleMisoSceneParser parser = new SimpleMisoSceneParser("");
|
||||
SimpleMisoSceneModel model = parser.parseScene(args[0]);
|
||||
if (model == null) {
|
||||
log.warning("No miso scene found in scene file", "path", args[0]);
|
||||
System.exit(-1);
|
||||
}
|
||||
_panel.setSceneModel(model);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warning("Unable to parse scene", "path", args[0], e);
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
// size and position the window, entering full-screen exclusive
|
||||
// mode if available
|
||||
if (gd.isFullScreenSupported()) {
|
||||
log.info("Entering full-screen exclusive mode.");
|
||||
gd.setFullScreenWindow(_frame);
|
||||
|
||||
} else {
|
||||
log.warning("Full-screen exclusive mode not available.");
|
||||
// _frame.pack();
|
||||
_frame.setSize(600, 400);
|
||||
SwingUtil.centerWindow(_frame);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation of the MisoContext interface that provides
|
||||
* handles to the config and manager objects that offer commonly used
|
||||
* services.
|
||||
*/
|
||||
protected class ContextImpl implements MisoContext
|
||||
{
|
||||
public MisoTileManager getTileManager () {
|
||||
return _tilemgr;
|
||||
}
|
||||
|
||||
public FrameManager getFrameManager () {
|
||||
return _framemgr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the application.
|
||||
*/
|
||||
public void run ()
|
||||
{
|
||||
// show the window
|
||||
_frame.setVisible(true);
|
||||
_framemgr.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate the application object and start it running.
|
||||
*/
|
||||
public static void main (String[] args)
|
||||
{
|
||||
try {
|
||||
ViewerApp app = new ViewerApp(args);
|
||||
app.run();
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Error initializing viewer app.");
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/** The tile manager object. */
|
||||
protected MisoTileManager _tilemgr;
|
||||
|
||||
/** The frame manager. */
|
||||
protected FrameManager _framemgr;
|
||||
|
||||
/** The main application window. */
|
||||
protected ViewerFrame _frame;
|
||||
|
||||
/** The main panel. */
|
||||
protected ViewerSceneViewPanel _panel;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.miso.viewer;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
|
||||
import com.samskivert.swing.util.MenuUtil;
|
||||
|
||||
import com.threerings.media.ManagedJFrame;
|
||||
|
||||
/**
|
||||
* The viewer frame is the main application window.
|
||||
*/
|
||||
public class ViewerFrame extends ManagedJFrame
|
||||
{
|
||||
/**
|
||||
* Creates a frame in which the viewer application can operate.
|
||||
*/
|
||||
public ViewerFrame (GraphicsConfiguration gc)
|
||||
{
|
||||
super(gc);
|
||||
|
||||
// set up the frame options
|
||||
setTitle("Scene Viewer");
|
||||
setResizable(false);
|
||||
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
||||
|
||||
// set the frame and content panel background to black
|
||||
setBackground(Color.black);
|
||||
getContentPane().setBackground(Color.black);
|
||||
|
||||
// create the "Settings" menu
|
||||
JMenu menuSettings = new JMenu("Settings");
|
||||
MenuUtil.addMenuItem(menuSettings, "Preferences", this, "handlePreferences");
|
||||
|
||||
// create the menu bar
|
||||
JMenuBar bar = new JMenuBar();
|
||||
bar.add(menuSettings);
|
||||
|
||||
// add the menu bar to the frame
|
||||
setJMenuBar(bar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the panel displayed by this frame.
|
||||
*/
|
||||
public void setPanel (Component panel)
|
||||
{
|
||||
// if we had an old panel, remove it
|
||||
if (_panel != null) {
|
||||
getContentPane().remove(_panel);
|
||||
}
|
||||
|
||||
// now add the new one
|
||||
_panel = panel;
|
||||
getContentPane().add(_panel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dummy callback method.
|
||||
*/
|
||||
public void handlePreferences (ActionEvent event)
|
||||
{
|
||||
System.err.println("Nothing doing!");
|
||||
}
|
||||
|
||||
protected Component _panel;
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.miso.viewer;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import com.samskivert.util.RandomUtil;
|
||||
|
||||
import com.threerings.media.image.ColorPository;
|
||||
import com.threerings.media.sprite.PathObserver;
|
||||
import com.threerings.media.sprite.Sprite;
|
||||
import com.threerings.media.sprite.SpriteManager;
|
||||
import com.threerings.media.util.LineSegmentPath;
|
||||
import com.threerings.media.util.Path;
|
||||
import com.threerings.media.util.PerformanceMonitor;
|
||||
import com.threerings.media.util.PerformanceObserver;
|
||||
|
||||
import com.threerings.miso.MisoConfig;
|
||||
import com.threerings.miso.client.MisoScenePanel;
|
||||
import com.threerings.miso.data.MisoSceneModel;
|
||||
import com.threerings.miso.util.MisoContext;
|
||||
|
||||
import com.threerings.cast.CharacterDescriptor;
|
||||
import com.threerings.cast.CharacterManager;
|
||||
import com.threerings.cast.CharacterSprite;
|
||||
import com.threerings.cast.ComponentRepository;
|
||||
import com.threerings.cast.util.CastUtil;
|
||||
|
||||
import static com.threerings.miso.Log.log;
|
||||
|
||||
public class ViewerSceneViewPanel extends MisoScenePanel
|
||||
implements PerformanceObserver, PathObserver
|
||||
{
|
||||
/**
|
||||
* Construct the panel and initialize it with a context.
|
||||
*/
|
||||
public ViewerSceneViewPanel (
|
||||
MisoContext ctx, CharacterManager charmgr, ComponentRepository crepo, ColorPository cpos)
|
||||
{
|
||||
super(ctx, MisoConfig.getSceneMetrics());
|
||||
|
||||
// create the character descriptors
|
||||
_descUser = CastUtil.getRandomDescriptor(
|
||||
crepo, "female", COMP_CLASSES, cpos, COLOR_CLASSES);
|
||||
_descDecoy = CastUtil.getRandomDescriptor(crepo, "male", COMP_CLASSES, cpos, COLOR_CLASSES);
|
||||
|
||||
// create the manipulable sprite
|
||||
_sprite = createSprite(_spritemgr, charmgr, _descUser);
|
||||
setFollowsPathable(_sprite, CENTER_ON_PATHABLE);
|
||||
|
||||
// create the decoy sprites
|
||||
createDecoys(_spritemgr, charmgr);
|
||||
|
||||
PerformanceMonitor.register(this, "paint", 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSceneModel (MisoSceneModel model)
|
||||
{
|
||||
super.setSceneModel(model);
|
||||
log.info("Using " + model + ".");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doLayout ()
|
||||
{
|
||||
super.doLayout();
|
||||
|
||||
// now that we have a scene, we can create valid paths for our
|
||||
// decoy sprites
|
||||
createDecoyPaths();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new sprite.
|
||||
*/
|
||||
protected CharacterSprite createSprite (
|
||||
SpriteManager spritemgr, CharacterManager charmgr,
|
||||
CharacterDescriptor desc)
|
||||
{
|
||||
CharacterSprite s = charmgr.getCharacter(desc);
|
||||
if (s != null) {
|
||||
// start 'em out standing
|
||||
s.setActionSequence(CharacterSprite.STANDING);
|
||||
s.setLocation(300, 300);
|
||||
s.addSpriteObserver(this);
|
||||
spritemgr.addSprite(s);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the decoy sprites.
|
||||
*/
|
||||
protected void createDecoys (
|
||||
SpriteManager spritemgr, CharacterManager charmgr)
|
||||
{
|
||||
_decoys = new CharacterSprite[NUM_DECOYS];
|
||||
for (int ii = 0; ii < NUM_DECOYS; ii++) {
|
||||
_decoys[ii] = createSprite(spritemgr, charmgr, _descDecoy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates paths for the decoy sprites.
|
||||
*/
|
||||
protected void createDecoyPaths ()
|
||||
{
|
||||
for (int ii = 0; ii < NUM_DECOYS; ii++) {
|
||||
if (_decoys[ii] != null) {
|
||||
createRandomPath(_decoys[ii]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint (Graphics g)
|
||||
{
|
||||
super.paint(g);
|
||||
PerformanceMonitor.tick(this, "paint");
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void checkpoint (String name, int ticks)
|
||||
{
|
||||
log.info(name + " [ticks=" + ticks + "].");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed (MouseEvent e)
|
||||
{
|
||||
super.mousePressed(e);
|
||||
|
||||
int x = e.getX(), y = e.getY();
|
||||
log.info("Mouse pressed +" + x + "+" + y);
|
||||
|
||||
switch (e.getModifiers()) {
|
||||
case MouseEvent.BUTTON1_MASK:
|
||||
createPath(_sprite, x, y);
|
||||
break;
|
||||
|
||||
case MouseEvent.BUTTON2_MASK:
|
||||
for (int ii = 0; ii < NUM_DECOYS; ii++) {
|
||||
createPath(_decoys[ii], x, y);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the sprite a path leading to the given destination
|
||||
* screen coordinates. Returns whether a path was successfully
|
||||
* assigned.
|
||||
*/
|
||||
protected boolean createPath (CharacterSprite s, int x, int y)
|
||||
{
|
||||
// get the path from here to there
|
||||
LineSegmentPath path = (LineSegmentPath)getPath(s, x, y, true);
|
||||
if (path == null) {
|
||||
s.cancelMove();
|
||||
return false;
|
||||
}
|
||||
|
||||
// start the sprite moving along the path
|
||||
path.setVelocity(100f/1000f);
|
||||
s.move(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a new random path to the given sprite.
|
||||
*/
|
||||
protected void createRandomPath (CharacterSprite s)
|
||||
{
|
||||
Dimension d = _vbounds.getSize();
|
||||
if (d.width <= 0 || d.height <= 0) {
|
||||
return;
|
||||
}
|
||||
int x, y;
|
||||
do {
|
||||
x = RandomUtil.getInt(d.width);
|
||||
y = RandomUtil.getInt(d.height);
|
||||
} while (!createPath(s, x, y));
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void pathCompleted (Sprite sprite, Path path, long when)
|
||||
{
|
||||
CharacterSprite s = (CharacterSprite)sprite;
|
||||
if (s != _sprite) {
|
||||
// move the sprite to a new random location
|
||||
createRandomPath(s);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void pathCancelled (Sprite sprite, Path path)
|
||||
{
|
||||
// nothing doing
|
||||
}
|
||||
|
||||
/** The number of decoy characters milling about. */
|
||||
protected static final int NUM_DECOYS = 5;
|
||||
|
||||
/** The character descriptor for the user character. */
|
||||
protected CharacterDescriptor _descUser;
|
||||
|
||||
/** The character descriptor for the decoy characters. */
|
||||
protected CharacterDescriptor _descDecoy;
|
||||
|
||||
/** The sprite we're manipulating within the view. */
|
||||
protected CharacterSprite _sprite;
|
||||
|
||||
/** The test sprites that meander about aimlessly. */
|
||||
protected CharacterSprite _decoys[];
|
||||
|
||||
/** Defines our various character component classes. */
|
||||
protected static final String[] COMP_CLASSES = {
|
||||
"legs", "feet", "hand_left", "hand_right", "torso",
|
||||
"head", "hair", "hat", "eyepatch" };
|
||||
|
||||
/** Defines the colorization classes used in the character component images. */
|
||||
protected static final String[] COLOR_CLASSES = {
|
||||
"skin", "hair", "textile_p", "textile_s" };
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" standalone="yes"?>
|
||||
<!-- $Id: actions.xml 1548 2002-06-26 23:53:07Z mdb $ -->
|
||||
|
||||
<!-- test component action definitions -->
|
||||
<actions>
|
||||
<!-- these are isometrically offset (SOUTH became SOUTHWEST, etc.) -->
|
||||
<action name="standing">
|
||||
<framesPerSecond>5</framesPerSecond>
|
||||
<origin>50,112</origin>
|
||||
<orients>SW, W, NW, N, NE, E, SE, S</orients>
|
||||
<tileset>
|
||||
<heights>160, 160, 160, 160, 160, 160, 160, 160</heights>
|
||||
<widths>100, 100, 100, 100, 100, 100, 100, 100</widths>
|
||||
<tileCounts>1, 1, 1, 1, 1, 1, 1, 1</tileCounts>
|
||||
<offsetPos>0, 0</offsetPos>
|
||||
<gapSize>0, 0</gapSize>
|
||||
</tileset>
|
||||
</action>
|
||||
|
||||
<action name="walking">
|
||||
<framesPerSecond>7.3</framesPerSecond>
|
||||
<origin>50,112</origin>
|
||||
<orients>SW, W, NW, N, NE, E, SE, S</orients>
|
||||
<tileset>
|
||||
<heights>160, 160, 160, 160, 160, 160, 160, 160</heights>
|
||||
<widths>100, 100, 100, 100, 100, 100, 100, 100</widths>
|
||||
<tileCounts>6, 6, 6, 6, 6, 6, 6, 6</tileCounts>
|
||||
<offsetPos>0, 0</offsetPos>
|
||||
<gapSize>0, 0</gapSize>
|
||||
</tileset>
|
||||
</action>
|
||||
|
||||
<action name="behind_back">
|
||||
<framesPerSecond>5</framesPerSecond>
|
||||
<origin>50,112</origin>
|
||||
<orients>SW, W, NW, N, NE, E, SE, S</orients>
|
||||
<tileset>
|
||||
<heights>160, 160, 160, 160, 160, 160, 160, 160</heights>
|
||||
<widths>100, 100, 100, 100, 100, 100, 100, 100</widths>
|
||||
<tileCounts>1, 1, 1, 1, 1, 1, 1, 1</tileCounts>
|
||||
<offsetPos>0, 0</offsetPos>
|
||||
<gapSize>0, 0</gapSize>
|
||||
</tileset>
|
||||
</action>
|
||||
|
||||
<!-- these are not isometrically offset -->
|
||||
<action name="sailing">
|
||||
<framesPerSecond>8</framesPerSecond>
|
||||
<origin>87,80</origin>
|
||||
<orients>
|
||||
S, SSW, SW, WSW, W, WNW, NW, NNW, N, NNE, NE, ENE, E, ESE, SE, SSE
|
||||
</orients>
|
||||
<tileset>
|
||||
<widths>168, 168, 168, 168, 168, 168, 168, 168</widths>
|
||||
<heights>128, 128, 128, 128, 128, 128, 128, 128</heights>
|
||||
<tileCounts>12, 12, 12, 12, 12, 12, 12, 12</tileCounts>
|
||||
<offsetPos>0, 0</offsetPos>
|
||||
<gapSize>0, 0</gapSize>
|
||||
</tileset>
|
||||
</action>
|
||||
|
||||
<action name="sailing_small">
|
||||
<framesPerSecond>8</framesPerSecond>
|
||||
<origin>42,34</origin>
|
||||
<orients>
|
||||
S, SSW, SW, WSW, W, WNW, NW, NNW, N, NNE, NE, ENE, E, ESE, SE, SSE
|
||||
</orients>
|
||||
<tileset>
|
||||
<widths>84, 84, 84, 84, 84, 84, 84, 84</widths>
|
||||
<heights>64, 64, 64, 64, 64, 64, 64, 64</heights>
|
||||
<tileCounts>2, 2, 2, 2, 2, 2, 2, 2</tileCounts>
|
||||
<offsetPos>0, 0</offsetPos>
|
||||
<gapSize>0, 0</gapSize>
|
||||
</tileset>
|
||||
</action>
|
||||
</actions>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" standalone="yes"?>
|
||||
<!-- $Id: classes.xml 1160 2002-03-27 21:49:40Z mdb $ -->
|
||||
|
||||
<!-- test component class definitions -->
|
||||
<classes>
|
||||
<!-- male character component classes -->
|
||||
<class name="male/feet" renderPriority="0"/>
|
||||
<class name="male/legs" renderPriority="1"/>
|
||||
<class name="male/hand_left" renderPriority="2"/>
|
||||
<class name="male/hand_right" renderPriority="2"/>
|
||||
<class name="male/torso" renderPriority="3"/>
|
||||
<class name="male/head" renderPriority="4"/>
|
||||
<class name="male/eyepatch" renderPriority="5"/>
|
||||
<class name="male/hair" renderPriority="6"/>
|
||||
<class name="male/hat" renderPriority="7"/>
|
||||
<class name="male/familiars" renderPriority="8"/>
|
||||
|
||||
<!-- female character component classes -->
|
||||
<class name="female/feet" renderPriority="0"/>
|
||||
<class name="female/legs" renderPriority="1"/>
|
||||
<class name="female/hand_left" renderPriority="2"/>
|
||||
<class name="female/hand_right" renderPriority="2"/>
|
||||
<class name="female/torso" renderPriority="3"/>
|
||||
<class name="female/head" renderPriority="4"/>
|
||||
<class name="female/eyepatch" renderPriority="5"/>
|
||||
<class name="female/hair" renderPriority="6"/>
|
||||
<class name="female/hat" renderPriority="7"/>
|
||||
<class name="female/familiars" renderPriority="8"/>
|
||||
|
||||
<!-- vessel classes -->
|
||||
<class name="navsail" renderPriority="0"/>
|
||||
</classes>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 26 KiB |