Split Nenya into proper Maven submodules (same treatment Narya got).

This commit is contained in:
Michael Bayne
2012-02-27 11:46:22 -08:00
parent 90146d517d
commit 5e2380eb24
645 changed files with 358 additions and 283 deletions
+76
View File
@@ -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 &lt;fileset&gt; 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 &lt;fileset&gt; 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>
* &lt;actions&gt;
* &lt;action&gt;
* // ...
* &lt;/action&gt;
* &lt;/actions&gt;
* </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>
* &lt;classes&gt;
* &lt;class .../&gt;
* &lt;/classes&gt;
* </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>
* &lt;bundler-config&gt;
* &lt;mapping&gt;
* &lt;path&gt;bundle.tilesets.uniform&lt;/path&gt;
* &lt;ruleset&gt;
* com.threerings.media.tile.tools.xml.UniformTileSetRuleSet
* &lt;/ruleset&gt;
* &lt;/mapping&gt;
* &lt;mapping&gt;
* &lt;path&gt;bundle.tilesets.object&lt;/path&gt;
* &lt;ruleset&gt;
* com.threerings.media.tile.tools.xml.ObjectTileSetRuleSet
* &lt;/ruleset&gt;
* &lt;/mapping&gt;
* &lt;/bundler-config&gt;
* </pre>
*
* This configuration would be used to parse a bundle description that
* looked something like the following:
*
* <pre>
* &lt;bundle&gt;
* &lt;tilesets&gt;
* &lt;uniform&gt;
* &lt;tileset&gt;
* &lt;!-- ... --&gt;
* &lt;/tileset&gt;
* &lt;/uniform&gt;
* &lt;object&gt;
* &lt;tileset&gt;
* &lt;!-- ... --&gt;
* &lt;/tileset&gt;
* &lt;/object&gt;
* &lt;/tilesets&gt;
* </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 &lt;fileset&gt; 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>
* &lt;tilesets&gt;
* &lt;objectsets&gt;
* &lt;tileset&gt;
* // ...
* &lt;/tileset&gt;
* &lt;/objectsets&gt;
* &lt;/tilesets&gt;
* </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 &lt;fileset&gt; 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>
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Some files were not shown because too many files have changed in this diff Show More