commit 59d8ad1cdba96c25de089489ba29e930371111ef Author: Zukero Date: Mon Feb 23 22:43:19 2015 +0100 Initial commit diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..2709d4f --- /dev/null +++ b/.classpath @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a5e85c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/ATCS_v*.jar +/Project +/bin diff --git a/.project b/.project new file mode 100644 index 0000000..6321547 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + ATContentStudio + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..54e493c --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/arrows.xcf b/arrows.xcf new file mode 100644 index 0000000..d4fea55 Binary files /dev/null and b/arrows.xcf differ diff --git a/fileIconBase.xcf b/fileIconBase.xcf new file mode 100644 index 0000000..4324e8a Binary files /dev/null and b/fileIconBase.xcf differ diff --git a/folderIconBase.xcf b/folderIconBase.xcf new file mode 100644 index 0000000..6ee67cf Binary files /dev/null and b/folderIconBase.xcf differ diff --git a/hacked-libtiled/build.xml b/hacked-libtiled/build.xml new file mode 100644 index 0000000..ff5d952 --- /dev/null +++ b/hacked-libtiled/build.xml @@ -0,0 +1,111 @@ + + + + + + + + RSyntaxTextArea build file + + + + + + + + + + + + + + + + Compiling with java: ${java.runtime.version} + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+ + + + +
+
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/hacked-libtiled/tiled/core/AnimatedTile.java b/hacked-libtiled/tiled/core/AnimatedTile.java new file mode 100644 index 0000000..b769a47 --- /dev/null +++ b/hacked-libtiled/tiled/core/AnimatedTile.java @@ -0,0 +1,73 @@ +/* + * Copyright 2004-2006, Thorbjørn Lindeijer + * Copyright 2004-2006, Adam Turk + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.core; + +/** + * Animated tiles take advantage of the Sprite class internally to handle + * animation using an array of tiles. + * + * @see tiled.core.Sprite + */ +public class AnimatedTile extends Tile +{ + private Sprite sprite; + + public AnimatedTile() { + } + + public AnimatedTile(TileSet set) { + super(set); + } + + public AnimatedTile(Tile[] frames) { + this(); + sprite = new Sprite(frames); + } + + public AnimatedTile(Sprite s) { + this(); + setSprite(s); + } + + public void setSprite(Sprite s) { + sprite = s; + } + + public int countAnimationFrames() { + return sprite.getTotalFrames(); + } + + public int countKeys() { + return sprite.getTotalKeys(); + } + + public Sprite getSprite() { + return sprite; + } +} diff --git a/hacked-libtiled/tiled/core/Map.java b/hacked-libtiled/tiled/core/Map.java new file mode 100644 index 0000000..f796e5c --- /dev/null +++ b/hacked-libtiled/tiled/core/Map.java @@ -0,0 +1,469 @@ +/* + * Copyright 2004-2010, Thorbjørn Lindeijer + * Copyright 2004-2006, Adam Turk + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.core; + +import java.awt.Rectangle; +import java.util.*; + +/** + * The Map class is the focal point of the tiled.core package. + */ +public class Map implements Iterable +{ + public static final int ORIENTATION_ORTHOGONAL = 1; + public static final int ORIENTATION_ISOMETRIC = 2; + public static final int ORIENTATION_HEXAGONAL = 4; + /** Shifted (used for iso and hex). */ + public static final int ORIENTATION_SHIFTED = 5; + + private Vector layers; + private Vector objectGroups; + private Vector tileLayers; + private Vector tileSets; + + private int tileWidth, tileHeight; + private int orientation = ORIENTATION_ORTHOGONAL; + private Properties properties; + private String filename; + protected Rectangle bounds; // in tiles + + /** + * @param width the map width in tiles. + * @param height the map height in tiles. + */ + public Map(int width, int height) { + layers = new Vector(); + bounds = new Rectangle(width, height); + properties = new Properties(); + tileSets = new Vector(); + objectGroups = new Vector(); + tileLayers = new Vector(); + } + + /** + * Returns the total number of layers. + * + * @return the size of the layer vector + */ + public int getLayerCount() { + return layers.size(); + } + + /** + * Changes the bounds of this plane to include all layers completely. + */ + public void fitBoundsToLayers() { + int width = 0; + int height = 0; + + Rectangle layerBounds = new Rectangle(); + + for (int i = 0; i < layers.size(); i++) { + getLayer(i).getBounds(layerBounds); + if (width < layerBounds.width) width = layerBounds.width; + if (height < layerBounds.height) height = layerBounds.height; + } + + setBounds(width, height); + } + + /** + * Returns a Rectangle representing the maximum bounds in + * tiles. + * @return a new rectangle containing the maximum bounds of this plane + */ + public Rectangle getBounds() { + return new Rectangle(bounds); + } + + public void setBounds(int width, int height) { + bounds.width = width; + bounds.height = height; + } + + public MapLayer addLayer(MapLayer layer) { + layer.setMap(this); + layers.add(layer); + if (layer instanceof TileLayer) { + tileLayers.add((TileLayer) layer); + } else if (layer instanceof ObjectGroup) { + objectGroups.add((ObjectGroup) layer); + } + return layer; + } + + public void setLayer(int index, MapLayer layer) { + layer.setMap(this); + layers.set(index, layer); + if (layer instanceof TileLayer) { + tileLayers.set(index - objectGroups.size(), (TileLayer) layer); + } else if (layer instanceof ObjectGroup) { + objectGroups.set(index, (ObjectGroup) layer); + } + } + + public void insertLayer(int index, MapLayer layer) { + layer.setMap(this); + layers.add(index, layer); + if (layer instanceof TileLayer) { + tileLayers.add(index - objectGroups.size(), (TileLayer) layer); + } else if (layer instanceof ObjectGroup) { + objectGroups.add(index, (ObjectGroup) layer); + } + } + + /** + * Removes the layer at the specified index. Layers above this layer will + * move down to fill the gap. + * + * @param index the index of the layer to be removed + * @return the layer that was removed from the list + */ + public MapLayer removeLayer(int index) { + if (index < objectGroups.size()) { + objectGroups.remove(index); + } else if (index - objectGroups.size() < tileLayers.size()){ + tileLayers.remove(index - objectGroups.size()); + } + return layers.remove(index); + } + + /** + * Removes all layers from the plane. + */ + public void removeAllLayers() { + layers.removeAllElements(); + objectGroups.removeAllElements(); + tileLayers.removeAllElements(); + } + + /** + * Returns the layer vector. + * + * @return Vector the layer vector + */ + public Vector getLayers() { + return layers; + } + + /** + * Returns the tile layer vector. + * + * @return Vector the tile layer vector + */ + public Vector getTileLayers() { + return tileLayers; + } + + /** + * Returns the object group vector. + * + * @return Vector the object group vector + */ + public Vector getObjectGroup() { + return objectGroups; + } + + /** + * Sets the layer vector to the given java.util.Vector. + * + * @param layers the new set of layers + */ + public void setLayers(Vector layers) { + this.layers = layers; + } + + /** + * Returns the layer at the specified vector index. + * + * @param i the index of the layer to return + * @return the layer at the specified index, or null if the index is out of + * bounds + */ + public MapLayer getLayer(int i) { + try { + return layers.get(i); + } catch (ArrayIndexOutOfBoundsException e) { + } + return null; + } + + /** + * Resizes this plane. The (dx, dy) pair determines where the original + * plane should be positioned on the new area. Only layers that exactly + * match the bounds of the map are resized, any other layers are moved by + * the given shift. + * + * @see tiled.core.MapLayer#resize + * + * @param width The new width of the map. + * @param height The new height of the map. + * @param dx The shift in x direction in tiles. + * @param dy The shift in y direction in tiles. + */ + public void resize(int width, int height, int dx, int dy) { + for (MapLayer layer : this) { + if (layer.bounds.equals(bounds)) { + layer.resize(width, height, dx, dy); + } else { + layer.setOffset(layer.bounds.x + dx, layer.bounds.y + dy); + } + } + + bounds.width = width; + bounds.height = height; + } + + /** + * Determines whether the point (x,y) falls within the plane. + * + * @param x + * @param y + * @return true if the point is within the plane, + * false otherwise + */ + public boolean inBounds(int x, int y) { + return x >= 0 && y >= 0 && x < bounds.width && y < bounds.height; + } + + public Iterator iterator() { + return layers.iterator(); + } + + /** + * Adds a Tileset to this Map. If the set is already attached to this map, + * addTileset simply returns. + * + * @param tileset a tileset to add + */ + public void addTileset(TileSet tileset) { + if (tileset == null || tileSets.indexOf(tileset) > -1) { + return; + } + + Tile t = tileset.getTile(0); + + if (t != null) { + int tw = t.getWidth(); + int th = t.getHeight(); + if (tw != tileWidth) { + if (tileWidth == 0) { + tileWidth = tw; + tileHeight = th; + } + } + } + + tileSets.add(tileset); + } + + /** + * Removes a {@link TileSet} from the map, and removes any tiles in the set + * from the map layers. + * + * @param tileset TileSet to remove + */ + public void removeTileset(TileSet tileset) { + // Sanity check + final int tilesetIndex = tileSets.indexOf(tileset); + if (tilesetIndex == -1) + return; + + // Go through the map and remove any instances of the tiles in the set + for (Tile tile : tileset) { + for (MapLayer ml : this) { + if (ml instanceof TileLayer) { + ((TileLayer) ml).removeTile(tile); + } + } + } + + tileSets.remove(tileset); + } + + /** + * @return the map properties + */ + public Properties getProperties() { + return properties; + } + + public void setProperties(Properties prop) { + properties = prop; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + /** + * Sets a new tile width. + * + * @param width the new tile width + */ + public void setTileWidth(int width) { + tileWidth = width; + } + + /** + * Sets a new tile height. + * + * @param height the new tile height + */ + public void setTileHeight(int height) { + tileHeight = height; + } + + public void setOrientation(int orientation) { + this.orientation = orientation; + } + + public String getFilename() { + return filename; + } + + /** + * Returns a vector with the currently loaded tileSets. + * + * @return Vector + */ + public Vector getTileSets() { + return tileSets; + } + + /** + * Returns width of map in tiles. + * + * @return int + */ + public int getWidth() { + return bounds.width; + } + + /** + * Returns height of map in tiles. + * + * @return int + */ + public int getHeight() { + return bounds.height; + } + + /** + * Returns default tile width for this map. + * + * @return the default tile width + */ + public int getTileWidth() { + return tileWidth; + } + + /** + * Returns default tile height for this map. + * + * @return the default tile height + */ + public int getTileHeight() { + return tileHeight; + } + + /** + * Returns wether the given tile coordinates fall within the map + * boundaries. + * + * @param x The tile-space x-coordinate + * @param y The tile-space y-coordinate + * @return true if the point is within the map boundaries, + * false otherwise + */ + public boolean contains(int x, int y) { + return x >= 0 && y >= 0 && x < bounds.width && y < bounds.height; + } + + /** + * Returns the maximum tile height. This is the height of the highest tile + * in all tileSets or the tile height used by this map if it's smaller. + * + * @return int The maximum tile height + */ + public int getTileHeightMax() { + int maxHeight = tileHeight; + + for (TileSet tileset : tileSets) { + int height = tileset.getTileHeight(); + if (height > maxHeight) { + maxHeight = height; + } + } + + return maxHeight; + } + + /** + * Swaps the tile sets at the given indices. + */ + public void swapTileSets(int index0, int index1) { + if (index0 == index1) return; + TileSet set = tileSets.get(index0); + tileSets.set(index0, tileSets.get(index1)); + tileSets.set(index1, set); + } + + /** + * Returns the orientation of this map. Orientation will be one of + * {@link Map#ORIENTATION_ISOMETRIC}, {@link Map#ORIENTATION_ORTHOGONAL}, + * {@link Map#ORIENTATION_HEXAGONAL} and {@link Map#ORIENTATION_SHIFTED}. + * + * @return The orientation from the enumerated set + */ + public int getOrientation() { + return orientation; + } + + /** + * Returns string describing the map. The form is Map[width x height + * x layers][tileWidth x tileHeight], for example + * Map[64x64x2][24x24]. + * + * @return string describing map + */ + public String toString() { + return "Map[" + bounds.width + "x" + bounds.height + "x" + + getLayerCount() + "][" + tileWidth + "x" + + tileHeight + "]"; + } + + public boolean containsLayer(MapLayer layer) { + return layers.contains(layer); + } + + public int getLayerIndex(MapLayer layer) { + return layers.indexOf(layer); + } +} diff --git a/hacked-libtiled/tiled/core/MapLayer.java b/hacked-libtiled/tiled/core/MapLayer.java new file mode 100644 index 0000000..d768f5e --- /dev/null +++ b/hacked-libtiled/tiled/core/MapLayer.java @@ -0,0 +1,310 @@ +/* + * Copyright 2004-2010, Thorbjørn Lindeijer + * Copyright 2004-2006, Adam Turk + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.core; + +import java.awt.Rectangle; +import java.awt.geom.Area; +import java.util.Properties; + +/** + * A layer of a map. + * + * @see Map + */ +public abstract class MapLayer implements Cloneable +{ + /** MIRROR_HORIZONTAL */ + public static final int MIRROR_HORIZONTAL = 1; + /** MIRROR_VERTICAL */ + public static final int MIRROR_VERTICAL = 2; + + /** ROTATE_90 */ + public static final int ROTATE_90 = 90; + /** ROTATE_180 */ + public static final int ROTATE_180 = 180; + /** ROTATE_270 */ + public static final int ROTATE_270 = 270; + + protected String name; + protected boolean isVisible = true; + protected Map myMap; + protected float opacity = 1.0f; + protected Rectangle bounds; + private Properties properties = new Properties(); + + public MapLayer() { + bounds = new Rectangle(); + setMap(null); + } + + /** + * @param w width in tiles + * @param h height in tiles + */ + public MapLayer(int w, int h) { + this(new Rectangle(0, 0, w, h)); + } + + public MapLayer(Rectangle r) { + this(); + setBounds(r); + } + + /** + * @param map the map this layer is part of + */ + MapLayer(Map map) { + this(); + setMap(map); + } + + /** + * @param map the map this layer is part of + * @param w width in tiles + * @param h height in tiles + */ + public MapLayer(Map map, int w, int h) { + this(w, h); + setMap(map); + } + + /** + * Performs a linear translation of this layer by (dx, dy). + * + * @param dx distance over x axis + * @param dy distance over y axis + */ + public void translate(int dx, int dy) { + bounds.x += dx; + bounds.y += dy; + } + + public abstract void rotate(int angle); + + public abstract void mirror(int dir); + + /** + * Sets the bounds (in tiles) to the specified Rectangle. + * + * @param bounds + */ + protected void setBounds(Rectangle bounds) { + this.bounds = new Rectangle(bounds); + } + + /** + * Sets the name of this layer. + * + * @param name the new name + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the map this layer is part of. + * + * @param map the Map object + */ + public void setMap(Map map) { + myMap = map; + } + + public Map getMap() { + return myMap; + } + + /** + * Sets layer opacity. If it is different from the previous value and the + * layer is visible, a MapChangedEvent is fired. + * + * @param opacity the new opacity for this layer + */ + public void setOpacity(float opacity) { + this.opacity = opacity; + } + + /** + * Sets the visibility of this map layer. If it changes from its current + * value, a MapChangedEvent is fired. + * + * @param visible true to make the layer visible; + * false to make it invisible + */ + public void setVisible(boolean visible) { + isVisible = visible; + } + + /** + * Sets the offset of this map layer. The offset is a distance by which to + * shift this layer from the origin of the map. + * + * @param xOff x offset in tiles + * @param yOff y offset in tiles + */ + public void setOffset(int xOff, int yOff) { + bounds.x = xOff; + bounds.y = yOff; + } + + /** + * Returns the name of this layer. + * @return the name of the layer + */ + public String getName() { + return name; + } + + /** + * Returns layer width in tiles. + * @return layer width in tiles. + */ + public int getWidth() { + return bounds.width; + } + + /** + * Returns layer height in tiles. + * @return layer height in tiles. + */ + public int getHeight() { + return bounds.height; + } + + /** + * Returns the layer bounds in tiles. + * @return the layer bounds in tiles + */ + public Rectangle getBounds() { + return new Rectangle(bounds); + } + + /** + * Assigns the layer bounds in tiles to the given rectangle. + * @param rect the rectangle to which the layer bounds are assigned + */ + public void getBounds(Rectangle rect) { + rect.setBounds(bounds); + } + + /** + * A convenience method to check if a point in tile-space is within + * the layer boundaries. + * + * @param x the x-coordinate of the point + * @param y the y-coordinate of the point + * @return true if the point (x,y) is within the layer + * boundaries, false otherwise. + */ + public boolean contains(int x, int y) { + return bounds.contains(x, y); + } + + /** + * Returns layer opacity. + * + * @return layer opacity, ranging from 0.0 to 1.0 + */ + public float getOpacity() { + return opacity; + } + + /** + * Returns whether this layer is visible. + * + * @return true if the layer is visible, false + * otherwise. + */ + public boolean isVisible() { + return isVisible; + } + + /** + * Merges the tile data of this layer with the specified layer. The calling + * layer is considered the significant layer, and will overwrite the data + * of the argument layer. At cells where the calling layer has no data, the + * argument layer data is preserved. + * + * @param other the insignificant layer to merge with + */ + public abstract void mergeOnto(MapLayer other); + + public abstract void maskedMergeOnto(MapLayer other, Area mask); + + public abstract void copyFrom(MapLayer other); + + public abstract void maskedCopyFrom(MapLayer other, Area mask); + + public abstract MapLayer createDiff(MapLayer ml); + + /** + * Unlike mergeOnto, copyTo includes the null tile when merging + * + * @see MapLayer#copyFrom + * @see MapLayer#mergeOnto + * @param other the layer to copy this layer to + */ + public abstract void copyTo(MapLayer other); + + public abstract boolean isEmpty(); + + /** + * Creates a copy of this layer. + * + * @see Object#clone + * @return a clone of this layer, as complete as possible + * @exception CloneNotSupportedException + */ + public Object clone() throws CloneNotSupportedException { + MapLayer clone = (MapLayer) super.clone(); + + // Create a new bounds object + clone.bounds = new Rectangle(bounds); + clone.properties = (Properties) properties.clone(); + + return clone; + } + + /** + * @param width the new width of the layer + * @param height the new height of the layer + * @param dx the shift in x direction + * @param dy the shift in y direction + */ + public abstract void resize(int width, int height, int dx, int dy); + + public void setProperties(Properties p) { + properties.clear(); + properties.putAll(p); + } + + public Properties getProperties() { + return properties; + } +} diff --git a/hacked-libtiled/tiled/core/MapObject.java b/hacked-libtiled/tiled/core/MapObject.java new file mode 100644 index 0000000..2904758 --- /dev/null +++ b/hacked-libtiled/tiled/core/MapObject.java @@ -0,0 +1,198 @@ +/* + * Copyright 2004-2008, Thorbjørn Lindeijer + * Copyright 2004-2008, Adam Turk + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.core; + +import java.awt.*; +import java.util.Properties; +import java.io.File; +import java.io.IOException; +import javax.imageio.ImageIO; + +/** + * An object occupying an {@link ObjectGroup}. + */ +public class MapObject implements Cloneable +{ + private Properties properties = new Properties(); + private ObjectGroup objectGroup; + private Rectangle bounds = new Rectangle(); + private String name = "Object"; + private String type = ""; + private String imageSource = ""; + private Image image; + private Image scaledImage; + + public MapObject(int x, int y, int width, int height) { + bounds = new Rectangle(x, y, width, height); + } + + public Object clone() throws CloneNotSupportedException { + MapObject clone = (MapObject) super.clone(); + clone.bounds = new Rectangle(bounds); + clone.properties = (Properties) properties.clone(); + return clone; + } + + /** + * @return the object group this object is part of + */ + public ObjectGroup getObjectGroup() { + return objectGroup; + } + + /** + * Sets the object group this object is part of. Should only be called by + * the object group. + * + * @param objectGroup the object group this object is part of + */ + public void setObjectGroup(ObjectGroup objectGroup) { + this.objectGroup = objectGroup; + } + + public Rectangle getBounds() { + return bounds; + } + + public void setBounds(Rectangle bounds) { + this.bounds = bounds; + } + + public String getImageSource() { + return imageSource; + } + + public void setImageSource(String source) { + if (imageSource.equals(source)) + return; + + imageSource = source; + + // Attempt to read the image + if (imageSource.length() > 0) { + try { + image = ImageIO.read(new File(imageSource)); + } catch (IOException e) { + image = null; + } + } else { + image = null; + } + + scaledImage = null; + } + + /** + * Returns the image to be used when drawing this object. This image is + * scaled to the size of the object. + * + * @param zoom the requested zoom level of the image + * @return the image to be used when drawing this object + */ + public Image getImage(double zoom) { + if (image == null) + return null; + + final int zoomedWidth = (int) (getWidth() * zoom); + final int zoomedHeight = (int) (getHeight() * zoom); + + if (scaledImage == null || scaledImage.getWidth(null) != zoomedWidth + || scaledImage.getHeight(null) != zoomedHeight) + { + scaledImage = image.getScaledInstance(zoomedWidth, zoomedHeight, + Image.SCALE_SMOOTH); + } + + return scaledImage; + } + + public int getX() { + return bounds.x; + } + + public void setX(int x) { + bounds.x = x; + } + + public int getY() { + return bounds.y; + } + + public void setY(int y) { + bounds.y = y; + } + + public void translate(int dx, int dy) { + bounds.translate(dx, dy); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public int getWidth() { + return bounds.width; + } + + public void setWidth(int width) { + bounds.width = width; + } + + public void setHeight(int height) { + bounds.height = height; + } + + public int getHeight() { + return bounds.height; + } + + public Properties getProperties() { + return properties; + } + + public void setProperties(Properties p) { + properties = p; + } + + public String toString() { + return type + " (" + getX() + "," + getY() + ")"; + } +} diff --git a/hacked-libtiled/tiled/core/ObjectGroup.java b/hacked-libtiled/tiled/core/ObjectGroup.java new file mode 100644 index 0000000..67f1b47 --- /dev/null +++ b/hacked-libtiled/tiled/core/ObjectGroup.java @@ -0,0 +1,213 @@ +/* + * Copyright 2004-2006, Thorbjørn Lindeijer + * Copyright 2004-2006, Adam Turk + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.core; + +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Rectangle2D; +import java.util.LinkedList; +import java.util.Iterator; +import java.util.List; + +/** + * A layer containing {@link MapObject map objects}. + */ +public class ObjectGroup extends MapLayer +{ + private LinkedList objects = new LinkedList(); + + /** + * Default constructor. + */ + public ObjectGroup() { + } + + /** + * @param map the map this object group is part of + */ + public ObjectGroup(Map map) { + super(map); + } + + /** + * Creates an object group that is part of the given map and has the given + * origin. + * + * @param map the map this object group is part of + * @param origX the x origin of this layer + * @param origY the y origin of this layer + */ + public ObjectGroup(Map map, int origX, int origY) { + super(map); + setBounds(new Rectangle(origX, origY, 0, 0)); + } + + /** + * Creates an object group with a given area. The size of area is + * irrelevant, just its origin. + * + * @param area the area of the object group + */ + public ObjectGroup(Rectangle area) { + super(area); + } + + /** + * @see MapLayer#rotate(int) + */ + public void rotate(int angle) { + // TODO: Implement rotating an object group + } + + /** + * @see MapLayer#mirror(int) + */ + public void mirror(int dir) { + // TODO: Implement mirroring an object group + } + + public void mergeOnto(MapLayer other) { + // TODO: Implement merging with another object group + } + + public void maskedMergeOnto(MapLayer other, Area mask) { + // TODO: Figure out what object group should do with this method + } + + public void copyFrom(MapLayer other) { + // TODO: Implement copying from another object group (same as merging) + } + + public void maskedCopyFrom(MapLayer other, Area mask) { + // TODO: Figure out what object group should do with this method + } + + public void copyTo(MapLayer other) { + // TODO: Implement copying to another object group (same as merging) + } + + /** + * @see MapLayer#resize(int,int,int,int) + */ + public void resize(int width, int height, int dx, int dy) { + // TODO: Translate contained objects by the change of origin + } + + public boolean isEmpty() { + return objects.isEmpty(); + } + + public Object clone() throws CloneNotSupportedException { + ObjectGroup clone = (ObjectGroup) super.clone(); + clone.objects = new LinkedList(); + for (MapObject object : objects) { + final MapObject objectClone = (MapObject) object.clone(); + clone.objects.add(objectClone); + objectClone.setObjectGroup(clone); + } + return clone; + } + + /** + * @deprecated + */ + public MapLayer createDiff(MapLayer ml) { + return null; + } + + public void addObject(MapObject o) { + objects.add(o); + o.setObjectGroup(this); + } + + public void removeObject(MapObject o) { + objects.remove(o); + o.setObjectGroup(null); + } + + public Iterator getObjects() { + return objects.iterator(); + } + + + public List getObjectsList() { + return objects; + } + + public MapObject getObjectAt(int x, int y) { + for (MapObject obj : objects) { + // Attempt to get an object bordering the point that has no width + if (obj.getWidth() == 0 && obj.getX() + bounds.x == x) { + return obj; + } + + // Attempt to get an object bordering the point that has no height + if (obj.getHeight() == 0 && obj.getY() + bounds.y == y) { + return obj; + } + + Rectangle rect = new Rectangle(obj.getX() + bounds.x * myMap.getTileWidth(), + obj.getY() + bounds.y * myMap.getTileHeight(), + obj.getWidth(), obj.getHeight()); + if (rect.contains(x, y)) { + return obj; + } + } + return null; + } + + // This method will work at any zoom level, provided you provide the correct zoom factor. It also adds a one pixel buffer (that doesn't change with zoom). + public MapObject getObjectNear(int x, int y, double zoom) { + Rectangle2D mouse = new Rectangle2D.Double(x - zoom - 1, y - zoom - 1, 2 * zoom + 1, 2 * zoom + 1); + Shape shape; + + for (MapObject obj : objects) { + if (obj.getWidth() == 0 && obj.getHeight() == 0) { + shape = new Ellipse2D.Double(obj.getX() * zoom, obj.getY() * zoom, 10 * zoom, 10 * zoom); + } else { + shape = new Rectangle2D.Double(obj.getX() + bounds.x * myMap.getTileWidth(), + obj.getY() + bounds.y * myMap.getTileHeight(), + obj.getWidth() > 0 ? obj.getWidth() : zoom, + obj.getHeight() > 0 ? obj.getHeight() : zoom); + } + + if (shape.intersects(mouse)) { + return obj; + } + } + + return null; + } + + public void clear() { + objects.clear(); + } +} diff --git a/hacked-libtiled/tiled/core/Sprite.java b/hacked-libtiled/tiled/core/Sprite.java new file mode 100644 index 0000000..670bc4c --- /dev/null +++ b/hacked-libtiled/tiled/core/Sprite.java @@ -0,0 +1,378 @@ +/* + * Copyright 2004-2006, Thorbjørn Lindeijer + * Copyright 2004-2006, Adam Turk + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.core; + +import java.awt.Image; +import java.awt.Rectangle; +import java.util.Iterator; +import java.util.Vector; + +public class Sprite +{ + private Vector keys; + private int borderWidth = 0; + private int fpl = 0; + private int totalKeys = -1; + + private float currentFrame = 0; + private Rectangle frameSize; + private boolean bPlaying = true; + + public class KeyFrame + { + public static final int MASK_ANIMATION = 0x0000000F; + + public static final int KEY_LOOP = 0x01; + public static final int KEY_STOP = 0x02; + public static final int KEY_AUTO = 0x04; + public static final int KEY_REVERSE = 0x08; + + public static final int KEY_NAME_LENGTH_MAX = 32; + + private String name = null; + private int id = -1; + private int flags = KEY_LOOP; + private float frameRate = 1.0f; //one fps + private Tile[] frames; + + public KeyFrame() { + flags = KEY_LOOP; + } + + public KeyFrame(String name) { + this(); + this.name = name; + } + + public KeyFrame(String name, Tile[] tile) { + this(name); + frames = tile; + } + + public void setName(String name) { + this.name = name; + } + + public void setFrameRate(float r) { + frameRate = r; + } + + public void setId(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public int getLastFrame() { + return frames.length - 1; + } + + public boolean isFrameLast(int frame) { + return frames.length - 1 == frame; + } + + public void setFlags(int f) { + flags = f; + } + + public int getFlags() { + return flags; + } + + public String getName() { + return name; + } + + public Tile getFrame(int f) { + if (f > 0 && f < frames.length) { + return frames[f]; + } + return null; + } + + public float getFrameRate() { + return frameRate; + } + + public int getTotalFrames() { + return frames.length; + } + + public boolean equalsIgnoreCase(String n) { + return name != null && name.equalsIgnoreCase(n); + } + + public String toString() { + return "(" + name + ")" + id + ": @ " + frameRate; + } + } + + private KeyFrame currentKey = null; + + public Sprite() { + frameSize = new Rectangle(); + keys = new Vector(); + } + + public Sprite(Tile[] frames) { + setFrames(frames); + } + + public Sprite(Image image, int fpl, int border, int totalFrames) { + Tile[] frames = null; + this.fpl = fpl; + borderWidth = border; + + //TODO: break up the image into tiles + + //given this information, extrapolate the rest... + + frameSize.width = image.getWidth(null) / (fpl + borderWidth * fpl); + frameSize.height = (int) (image.getHeight(null) / (Math.ceil(totalFrames / fpl) + Math.ceil(totalFrames / fpl) * borderWidth)); + createKey("", frames, KeyFrame.KEY_LOOP); + } + + public void setFrames(Tile[] frames) { + frameSize = new Rectangle(0, 0, frames[0].getWidth(), frames[0].getHeight()); + + createKey("", frames, KeyFrame.KEY_LOOP); + } + + public void setFrameSize(int w, int h) { + frameSize.width = w; + frameSize.height = h; + } + + public void setBorderWidth(int b) { + borderWidth = b; + } + + public void setFpl(int f) { + fpl = f; + } + + public void setCurrentFrame(float c) { + if (c < 0) { + switch (currentKey.flags & KeyFrame.MASK_ANIMATION) { + case KeyFrame.KEY_LOOP: + currentFrame = currentKey.getLastFrame(); + break; + case KeyFrame.KEY_AUTO: + currentKey = getPreviousKey(); + currentFrame = currentKey.getLastFrame(); + break; + case KeyFrame.KEY_REVERSE: + currentKey.setFrameRate(-currentKey.getFrameRate()); + currentFrame = 0; + break; + case KeyFrame.KEY_STOP: + bPlaying = false; + currentFrame = 0; + break; + } + } else if (c > currentKey.getLastFrame()) { + switch (currentKey.flags & KeyFrame.MASK_ANIMATION) { + case KeyFrame.KEY_LOOP: + currentFrame = 0; + break; + case KeyFrame.KEY_AUTO: + currentFrame = 0; + currentKey = getNextKey(); + break; + case KeyFrame.KEY_REVERSE: + currentKey.setFrameRate(-currentKey.getFrameRate()); + currentFrame = currentKey.getLastFrame(); + break; + case KeyFrame.KEY_STOP: + bPlaying = false; + currentFrame = currentKey.getLastFrame(); + break; + } + } else { + currentFrame = c; + } + } + + public void setTotalKeys(int t) { + totalKeys = t; + } + + public Rectangle getFrameSize() { + return frameSize; + } + + public int getTotalFrames() { + int total = 0; + for (KeyFrame key : keys) { + total += key.getTotalFrames(); + } + + return total; + } + + public int getBorderWidth() { + return borderWidth; + } + + public Tile getCurrentFrame() { + return currentKey.getFrame((int) currentFrame); + } + + public KeyFrame getNextKey() { + Iterator itr = keys.iterator(); + while (itr.hasNext()) { + KeyFrame k = itr.next(); + if (k == currentKey) { + if (itr.hasNext()) + return itr.next(); + } + } + + return keys.get(0); + } + + public KeyFrame getPreviousKey() { + //TODO: this + return null; + } + + public KeyFrame getCurrentKey() { + return currentKey; + } + + public int getFPL() { + return fpl; + } + + public int getTotalKeys() { + return keys.size(); + } + + public void setKeyFrameTo(String name) { + for (KeyFrame k : keys) { + if (k.equalsIgnoreCase(name)) { + currentKey = k; + break; + } + } + } + + + public void addKey(KeyFrame k) { + keys.add(k); + } + + public void removeKey(String name) { + keys.remove(getKey(name)); + } + + public void createKey(String name, Tile[] frames, int flags) { + KeyFrame kf = new KeyFrame(name, frames); + kf.setName(name); + kf.setFlags(flags); + addKey(kf); + } + + public void iterateFrame() { + + if (currentKey != null) { + if (bPlaying) { + setCurrentFrame(currentFrame + currentKey.getFrameRate()); + } + } + } + + /** + * Sets the current frame relative to the starting frame of the + * current key. + * + * @param c + */ + public void keySetFrame(int c) { + setCurrentFrame(c); + } + + public void play() { + bPlaying = true; + } + + public void stop() { + bPlaying = false; + } + + public void keyStepBack(int amt) { + setCurrentFrame(currentFrame - amt); + } + + public void keyStepForward(int amt) { + setCurrentFrame(currentFrame + amt); + } + + public KeyFrame getKey(String keyName) { + for (KeyFrame k : keys) { + if (k != null && k.equalsIgnoreCase(keyName)) { + return k; + } + } + return null; + } + + public KeyFrame getKey(int i) { + return keys.get(i); + } + + public Iterator getKeys() throws Exception { + return keys.iterator(); + } + + public Rectangle getCurrentFrameRect() { + int x = 0, y = 0; + + if (frameSize.height > 0 && frameSize.width > 0) { + y = ((int) currentFrame / fpl) * (frameSize.height + borderWidth); + x = ((int) currentFrame % fpl) * (frameSize.width + borderWidth); + } + + return new Rectangle(x, y, frameSize.width, frameSize.height); + } + + /** + * @see Object#toString() + */ + public String toString() { + return "Frame: (" + frameSize.width + "x" + frameSize.height + ")\n" + + "Border: " + borderWidth + "\n" + + "FPL: " + fpl + "\n" + + "Total Frames: " + getTotalFrames() + "\n" + + "Total keys: " + totalKeys; + } +} + diff --git a/hacked-libtiled/tiled/core/Tile.java b/hacked-libtiled/tiled/core/Tile.java new file mode 100644 index 0000000..a335a2f --- /dev/null +++ b/hacked-libtiled/tiled/core/Tile.java @@ -0,0 +1,147 @@ +/* + * Copyright 2004-2010, Thorbjørn Lindeijer + * Copyright 2004-2006, Adam Turk + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.core; + +import java.awt.*; +import java.util.Properties; + +/** + * The core class for our tiles. + */ +public class Tile +{ + private Image image; + private int id = -1; + private Properties properties; + private TileSet tileset; + + public Tile() { +// properties = new Properties(); + } + + public Tile(TileSet set) { + this(); + setTileSet(set); + } + + /** + * Copy constructor + * + * @param t tile to copy + */ + public Tile(Tile t) { + if (t.properties != null) properties = (Properties) t.properties.clone(); + tileset = t.tileset; + } + + /** + * Sets the id of the tile as long as it is at least 0. + * + * @param i The id of the tile + */ + public void setId(int i) { + if (i >= 0) { + id = i; + } + } + + /** + * Sets the image of the tile. + * + * @param i the new image of the tile + */ + public void setImage(Image i) { + image = i; + } + + /** + * Sets the parent tileset for a tile. + * + * @param set + */ + public void setTileSet(TileSet set) { + tileset = set; + } + + public void setProperties(Properties p) { + properties = p; + } + + public Properties getProperties() { + if (properties == null) return new Properties(); + return properties; + } + + /** + * Returns the tile id of this tile, relative to tileset. + * + * @return id + */ + public int getId() { + return id; + } + + /** + * Returns the {@link tiled.core.TileSet} that this tile is part of. + * + * @return TileSet + */ + public TileSet getTileSet() { + return tileset; + } + + public int getWidth() { + if (image != null) + return image.getWidth(null); + return 0; + } + + public int getHeight() { + if (image != null) + return image.getHeight(null); + return 0; + } + + /** + * Returns the tile image for this Tile. + * + * @return Image + */ + public Image getImage() { + if (tileset != null && tileset.sheet != null) return tileset.sheet.getImage(getId()); + return image; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + return "Tile " + id + " (" + getWidth() + "x" + getHeight() + ")"; + } +} diff --git a/hacked-libtiled/tiled/core/TileLayer.java b/hacked-libtiled/tiled/core/TileLayer.java new file mode 100644 index 0000000..5158e45 --- /dev/null +++ b/hacked-libtiled/tiled/core/TileLayer.java @@ -0,0 +1,482 @@ +/* + * Copyright 2004-2010, Thorbjørn Lindeijer + * Copyright 2004-2006, Adam Turk + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.core; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.geom.Area; +import java.util.HashMap; +import java.util.Properties; + +/** + * A TileLayer is a specialized MapLayer, used for tracking two dimensional + * tile data. + */ +public class TileLayer extends MapLayer +{ + protected Tile[][] map; + protected HashMap tileInstanceProperties = new HashMap(); + + public Properties getTileInstancePropertiesAt(int x, int y) { + if (!bounds.contains(x, y)) { + return null; + } + Object key = new Point(x, y); + return tileInstanceProperties.get(key); + } + + public void setTileInstancePropertiesAt(int x, int y, Properties tip) { + if (bounds.contains(x, y)) { + Object key = new Point(x, y); + tileInstanceProperties.put(key, tip); + } + } + + /** + * Default constructor. + */ + public TileLayer() { + } + + /** + * Construct a TileLayer from the given width and height. + * + * @param w width in tiles + * @param h height in tiles + */ + public TileLayer(int w, int h) { + super(w, h); + } + + /** + * Create a tile layer using the given bounds. + * + * @param r the bounds of the tile layer. + */ + public TileLayer(Rectangle r) { + super(r); + } + + /** + * @param m the map this layer is part of + */ + TileLayer(Map m) { + super(m); + } + + /** + * @param m the map this layer is part of + * @param w width in tiles + * @param h height in tiles + */ + public TileLayer(Map m, int w, int h) { + super(w, h); + setMap(m); + } + + /** + * Rotates the layer by the given Euler angle. + * + * @param angle The Euler angle (0-360) to rotate the layer array data by. + * @see MapLayer#rotate(int) + */ + public void rotate(int angle) { + Tile[][] trans; + int xtrans = 0, ytrans = 0; + + switch (angle) { + case ROTATE_90: + trans = new Tile[bounds.width][bounds.height]; + xtrans = bounds.height - 1; + break; + case ROTATE_180: + trans = new Tile[bounds.height][bounds.width]; + xtrans = bounds.width - 1; + ytrans = bounds.height - 1; + break; + case ROTATE_270: + trans = new Tile[bounds.width][bounds.height]; + ytrans = bounds.width - 1; + break; + default: +// System.out.println("Unsupported rotation (" + angle + ")"); + return; + } + + double ra = Math.toRadians(angle); + int cos_angle = (int)Math.round(Math.cos(ra)); + int sin_angle = (int)Math.round(Math.sin(ra)); + + for (int y = 0; y < bounds.height; y++) { + for (int x = 0; x < bounds.width; x++) { + int xrot = x * cos_angle - y * sin_angle; + int yrot = x * sin_angle + y * cos_angle; + trans[yrot + ytrans][xrot + xtrans] = getTileAt(x+bounds.x, y+bounds.y); + } + } + + bounds.width = trans[0].length; + bounds.height = trans.length; + map = trans; + } + + /** + * Performs a mirroring function on the layer data. Two orientations are + * allowed: vertical and horizontal. + * + * Example: layer.mirror(MapLayer.MIRROR_VERTICAL); will + * mirror the layer data around a horizontal axis. + * + * @param dir the axial orientation to mirror around + */ + public void mirror(int dir) { + Tile[][] mirror = new Tile[bounds.height][bounds.width]; + for (int y = 0; y < bounds.height; y++) { + for (int x = 0; x < bounds.width; x++) { + if (dir == MIRROR_VERTICAL) { + mirror[y][x] = map[bounds.height - 1 - y][x]; + } else { + mirror[y][x] = map[y][bounds.width - 1 - x]; + } + } + } + map = mirror; + } + + /** + * Checks to see if the given Tile is used anywhere in the layer. + * + * @param t a Tile object to check for + * @return true if the Tile is used at least once, + * false otherwise. + */ + public boolean isUsed(Tile t) { + for (int y = 0; y < bounds.height; y++) { + for (int x = 0; x < bounds.width; x++) { + if (map[y][x] == t) { + return true; + } + } + } + return false; + } + + public boolean isEmpty() { + for (int p = 0; p < 2; p++) { + for (int y = 0; y < bounds.height; y++) { + for (int x = p; x < bounds.width; x += 2) { + if (map[y][x] != null) + return false; + } + } + } + return true; + } + + /** + * Sets the bounds (in tiles) to the specified Rectangle. Caution: + * this causes a reallocation of the data array, and all previous data is + * lost. + * + * @param bounds new new bounds of this tile layer (in tiles) + * @see MapLayer#setBounds + */ + protected void setBounds(Rectangle bounds) { + super.setBounds(bounds); + map = new Tile[bounds.height][bounds.width]; + + // Tile instance properties is null when this method is called from + // the constructor of MapLayer + if (tileInstanceProperties != null) { + tileInstanceProperties.clear(); + } + } + + /** + * Creates a diff of the two layers, ml is considered the + * significant difference. + * + * @param ml + * @return A new MapLayer that represents the difference between this + * layer, and the argument, or null if no difference exists. + */ + public MapLayer createDiff(MapLayer ml) { + if (ml == null) { return null; } + + if (ml instanceof TileLayer) { + Rectangle r = null; + + for (int y = bounds.y; y < bounds.height + bounds.y; y++) { + for (int x = bounds.x; x < bounds.width + bounds.x; x++) { + if (((TileLayer)ml).getTileAt(x, y) != getTileAt(x, y)) { + if (r != null) { + r.add(x, y); + } else { + r = new Rectangle(new Point(x, y)); + } + } + } + } + + if (r != null) { + MapLayer diff = new TileLayer( + new Rectangle(r.x, r.y, r.width + 1, r.height + 1)); + diff.copyFrom(ml); + return diff; + } else { + return new TileLayer(); + } + } else { + return null; + } + } + + /** + * Removes any occurences of the given tile from this map layer. If layer + * is locked, an exception is thrown. + * + * @param tile the Tile to be removed + */ + public void removeTile(Tile tile) { + for (int y = 0; y < bounds.height; y++) { + for (int x = 0; x < bounds.width; x++) { + if (map[y][x] == tile) { + setTileAt(x + bounds.x, y + bounds.y, null); + } + } + } + } + + /** + * Sets the tile at the specified position. Does nothing if (tx, ty) falls + * outside of this layer. + * + * @param tx x position of tile + * @param ty y position of tile + * @param ti the tile object to place + */ + public void setTileAt(int tx, int ty, Tile ti) { + if (bounds.contains(tx, ty)) { + map[ty - bounds.y][tx - bounds.x] = ti; + } + } + + /** + * Returns the tile at the specified position. + * + * @param tx Tile-space x coordinate + * @param ty Tile-space y coordinate + * @return tile at position (tx, ty) or null when (tx, ty) is + * outside this layer + */ + public Tile getTileAt(int tx, int ty) { + return (bounds.contains(tx, ty)) ? + map[ty - bounds.y][tx - bounds.x] : null; + } + + /** + * Returns the first occurrence (using top down, left to right search) of + * the given tile. + * + * @param t the {@link Tile} to look for + * @return A java.awt.Point instance of the first instance of t, or + * null if it is not found + */ + public Point locationOf(Tile t) { + for (int y = bounds.y; y < bounds.height + bounds.y; y++) { + for (int x = bounds.x; x < bounds.width + bounds.x; x++) { + if (getTileAt(x, y) == t) { + return new Point(x, y); + } + } + } + return null; + } + + /** + * Replaces all occurrences of the Tile find with the Tile + * replace in the entire layer + * + * @param find the tile to replace + * @param replace the replacement tile + */ + public void replaceTile(Tile find, Tile replace) { + for (int y = bounds.y; y < bounds.y + bounds.height; y++) { + for (int x = bounds.x; x < bounds.x + bounds.width; x++) { + if(getTileAt(x,y) == find) { + setTileAt(x, y, replace); + } + } + } + } + + /** + * @inheritDoc MapLayer#mergeOnto(MapLayer) + */ + public void mergeOnto(MapLayer other) { + for (int y = bounds.y; y < bounds.y + bounds.height; y++) { + for (int x = bounds.x; x < bounds.x + bounds.width; x++) { + Tile tile = getTileAt(x, y); + if (tile != null) { + ((TileLayer) other).setTileAt(x, y, tile); + } + } + } + } + + /** + * Like mergeOnto, but will only copy the area specified. + * + * @see TileLayer#mergeOnto(MapLayer) + * @param other + * @param mask + */ + public void maskedMergeOnto(MapLayer other, Area mask) { + Rectangle boundBox = mask.getBounds(); + + for (int y = boundBox.y; y < boundBox.y + boundBox.height; y++) { + for (int x = boundBox.x; x < boundBox.x + boundBox.width; x++) { + Tile tile = ((TileLayer) other).getTileAt(x, y); + if (mask.contains(x, y) && tile != null) { + setTileAt(x, y, tile); + } + } + } + } + + /** + * Copy data from another layer onto this layer. Unlike mergeOnto, + * copyFrom() copies the empty cells as well. + * + * @see MapLayer#mergeOnto + * @param other + */ + public void copyFrom(MapLayer other) { + for (int y = bounds.y; y < bounds.y + bounds.height; y++) { + for (int x = bounds.x; x < bounds.x + bounds.width; x++) { + setTileAt(x, y, ((TileLayer) other).getTileAt(x, y)); + } + } + } + + /** + * Like copyFrom, but will only copy the area specified. + * + * @see TileLayer#copyFrom(MapLayer) + * @param other + * @param mask + */ + public void maskedCopyFrom(MapLayer other, Area mask) { + Rectangle boundBox = mask.getBounds(); + + for (int y = boundBox.y; y < boundBox.y + boundBox.height; y++) { + for (int x = boundBox.x; x < boundBox.x + boundBox.width; x++) { + if (mask.contains(x,y)) { + setTileAt(x, y, ((TileLayer) other).getTileAt(x, y)); + } + } + } + } + + /** + * Unlike mergeOnto, copyTo includes the null tile when merging. + * + * @see MapLayer#copyFrom + * @see MapLayer#mergeOnto + * @param other the layer to copy this layer to + */ + public void copyTo(MapLayer other) { + for (int y = bounds.y; y < bounds.y + bounds.height; y++) { + for (int x = bounds.x; x < bounds.x + bounds.width; x++) { + ((TileLayer) other).setTileAt(x, y, getTileAt(x, y)); + } + } + } + + /** + * Creates a copy of this layer. + * + * @see Object#clone + * @return a clone of this layer, as complete as possible + * @exception CloneNotSupportedException + */ + public Object clone() throws CloneNotSupportedException { + TileLayer clone = (TileLayer) super.clone(); + + // Clone the layer data + clone.map = new Tile[map.length][]; + clone.tileInstanceProperties = new HashMap(); + + for (int i = 0; i < map.length; i++) { + clone.map[i] = new Tile[map[i].length]; + System.arraycopy(map[i], 0, clone.map[i], 0, map[i].length); + + for (int j = 0; j < map[i].length; j++) { + Properties p = getTileInstancePropertiesAt(i, j); + + if (p != null) { + Integer key = i + j * bounds.width; + clone.tileInstanceProperties.put(key, (Properties) p.clone()); + } + } + } + + return clone; + } + + /** + * @param width the new width of the layer + * @param height the new height of the layer + * @param dx the shift in x direction + * @param dy the shift in y direction + */ + public void resize(int width, int height, int dx, int dy) { + Tile[][] newMap = new Tile[height][width]; + HashMap newTileInstanceProperties = new HashMap(); + + int maxX = Math.min(width, bounds.width + dx); + int maxY = Math.min(height, bounds.height + dy); + + for (int x = Math.max(0, dx); x < maxX; x++) { + for (int y = Math.max(0, dy); y < maxY; y++) { + newMap[y][x] = getTileAt(x - dx, y - dy); + + Properties tip = getTileInstancePropertiesAt(x - dx, y - dy); + if (tip != null) { + newTileInstanceProperties.put(new Point(x, y), tip); + } + } + } + + map = newMap; + tileInstanceProperties = newTileInstanceProperties; + bounds.width = width; + bounds.height = height; + } +} diff --git a/hacked-libtiled/tiled/core/TileSet.java b/hacked-libtiled/tiled/core/TileSet.java new file mode 100644 index 0000000..0690220 --- /dev/null +++ b/hacked-libtiled/tiled/core/TileSet.java @@ -0,0 +1,523 @@ +/* + * Copyright 2004-2010, Thorbjørn Lindeijer + * Copyright 2004-2006, Adam Turk + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.core; + +import java.awt.Color; +import java.awt.Image; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.image.BufferedImage; +import java.awt.image.FilteredImageSource; +import java.io.File; +import java.io.IOException; +import java.util.Iterator; +import java.util.Vector; + +import javax.imageio.ImageIO; + +import tiled.util.BasicTileCutter; +import tiled.util.TileCutter; +import tiled.util.TransparentImageFilter; + +import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; +import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet; + +/** + * todo: Update documentation + *

TileSet handles operations on tiles as a set, or group. It has several + * advanced internal functions aimed at reducing unnecessary data replication. + * A 'tile' is represented internally as two distinct pieces of data. The + * first and most important is a {@link Tile} object, and these are held in + * a {@link Vector}.

+ * + *

The other is the tile image.

+ */ +public class TileSet implements Iterable +{ + private String base; + final private Vector tiles = new Vector(); + private long tilebmpFileLastModified; + private TileCutter tileCutter; + private Rectangle tileDimensions; + private int tileSpacing; + private int tileMargin; + private int tilesPerRow; + private String externalSource; + private File tilebmpFile; + private String name; + private Color transparentColor; + private Image tileSetImage; + public Spritesheet sheet = null; + + /** + * Default constructor + */ + public TileSet() { + tileDimensions = new Rectangle(); + } + + /** + * Creates a tileset from a tileset image file. + * + * @param imgFilename + * @param cutter + * @throws IOException + * @see TileSet#importTileBitmap(BufferedImage, TileCutter) + */ + public void importTileBitmap(String imgFilename, TileCutter cutter) + throws IOException + { + setTilesetImageFilename(imgFilename); + + File f = new File(imgFilename); + + Image image = ImageIO.read(f.getCanonicalFile()); + if (image == null) { + throw new IOException("Failed to load " + tilebmpFile); + } + + Toolkit tk = Toolkit.getDefaultToolkit(); + + if (transparentColor != null) { + int rgb = transparentColor.getRGB(); + image = tk.createImage( + new FilteredImageSource(image.getSource(), + new TransparentImageFilter(rgb))); + } + + BufferedImage buffered = new BufferedImage( + image.getWidth(null), + image.getHeight(null), + BufferedImage.TYPE_INT_ARGB); + buffered.getGraphics().drawImage(image, 0, 0, null); + + importTileBitmap(buffered, cutter); + } + + public void loadFromProject(String name, TMXMap tmxMap) { + sheet = tmxMap.getProject().getSpritesheet(name); + int i = 0; + Image tileImage = sheet.getImage(i); + while (tileImage != null) { + Tile tile = new Tile(); +// tile.setImage(tileImage); + addNewTile(tile); + i++; + tileImage = sheet.getImage(i); + } + } + + /** + * Creates a tileset from a buffered image. Tiles are cut by the passed + * cutter. + * + * @param tileBitmap the image to be used, must not be null + * @param cutter the tile cutter, must not be null + */ + private void importTileBitmap(BufferedImage tileBitmap, TileCutter cutter) + { + assert tileBitmap != null; + assert cutter != null; + + tileCutter = cutter; + tileSetImage = tileBitmap; + + cutter.setImage(tileBitmap); + + tileDimensions = new Rectangle(cutter.getTileDimensions()); + if (cutter instanceof BasicTileCutter) { + BasicTileCutter basicTileCutter = (BasicTileCutter) cutter; + tileSpacing = basicTileCutter.getTileSpacing(); + tileMargin = basicTileCutter.getTileMargin(); + tilesPerRow = basicTileCutter.getTilesPerRow(); + } + + Image tileImage = cutter.getNextTile(); + while (tileImage != null) { + Tile tile = new Tile(); + tile.setImage(tileImage); + addNewTile(tile); + tileImage = cutter.getNextTile(); + } + } + + /** + * Refreshes a tileset from a tileset image file. + * + * @throws IOException + * @see TileSet#importTileBitmap(BufferedImage,TileCutter) + */ + private void refreshImportedTileBitmap() + throws IOException + { + String imgFilename = tilebmpFile.getPath(); + + Image image = ImageIO.read(new File(imgFilename)); + if (image == null) { + throw new IOException("Failed to load " + tilebmpFile); + } + + Toolkit tk = Toolkit.getDefaultToolkit(); + + if (transparentColor != null) { + int rgb = transparentColor.getRGB(); + image = tk.createImage( + new FilteredImageSource(image.getSource(), + new TransparentImageFilter(rgb))); + } + + BufferedImage buffered = new BufferedImage( + image.getWidth(null), + image.getHeight(null), + BufferedImage.TYPE_INT_ARGB); + buffered.getGraphics().drawImage(image, 0, 0, null); + + refreshImportedTileBitmap(buffered); + } + + /** + * Refreshes a tileset from a buffered image. Tiles are cut by the passed + * cutter. + * + * @param tileBitmap the image to be used, must not be null + */ + private void refreshImportedTileBitmap(BufferedImage tileBitmap) { + assert tileBitmap != null; + + tileCutter.reset(); + tileCutter.setImage(tileBitmap); + + tileSetImage = tileBitmap; + tileDimensions = new Rectangle(tileCutter.getTileDimensions()); + + int id = 0; + Image tileImage = tileCutter.getNextTile(); + while (tileImage != null) { + Tile tile = getTile(id); + tile.setImage(tileImage); + tileImage = tileCutter.getNextTile(); + id++; + } + } + + public void checkUpdate() throws IOException { + if (tilebmpFile != null && + tilebmpFile.lastModified() > tilebmpFileLastModified) + { + refreshImportedTileBitmap(); + tilebmpFileLastModified = tilebmpFile.lastModified(); + } + } + + /** + * Sets the URI path of the external source of this tile set. By setting + * this, the set is implied to be external in all other operations. + * + * @param source a URI of the tileset image file + */ + public void setSource(String source) { + externalSource = source; + } + + /** + * Sets the base directory for the tileset + * + * @param base a String containing the native format directory + */ + public void setBaseDir(String base) { + this.base = base; + } + + /** + * Sets the filename of the tileset image. Doesn't change the tileset in + * any other way. + * + * @param name + */ + public void setTilesetImageFilename(String name) { + if (name != null) { + tilebmpFile = new File(name); + tilebmpFileLastModified = tilebmpFile.lastModified(); + } + else { + tilebmpFile = null; + } + } + + /** + * Sets the name of this tileset. + * + * @param name the new name for this tileset + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the transparent color in the tileset image. + * + * @param color + */ + public void setTransparentColor(Color color) { + transparentColor = color; + } + + /** + * Adds the tile to the set, setting the id of the tile only if the current + * value of id is -1. + * + * @param t the tile to add + * @return int The local id of the tile + */ + public int addTile(Tile t) { + if (t.getId() < 0) + t.setId(tiles.size()); + + if (tileDimensions.width < t.getWidth()) + tileDimensions.width = t.getWidth(); + + if (tileDimensions.height < t.getHeight()) + tileDimensions.height = t.getHeight(); + + tiles.add(t); + t.setTileSet(this); + + return t.getId(); + } + + /** + * This method takes a new Tile object as argument, and in addition to + * the functionality of addTile(), sets the id of the tile + * to -1. + * + * @see TileSet#addTile(Tile) + * @param t the new tile to add. + */ + public void addNewTile(Tile t) { + t.setId(-1); + addTile(t); + } + + /** + * Removes a tile from this tileset. Does not invalidate other tile + * indices. Removal is simply setting the reference at the specified + * index to null. + * + * @param i the index to remove + */ + public void removeTile(int i) { + tiles.set(i, null); + } + + /** + * Returns the amount of tiles in this tileset. + * + * @return the amount of tiles in this tileset + */ + public int size() { + return tiles.size(); + } + + /** + * Returns the maximum tile id. + * + * @return the maximum tile id, or -1 when there are no tiles + */ + public int getMaxTileId() { + return tiles.size() - 1; + } + + /** + * Returns an iterator over the tiles in this tileset. + * + * @return an iterator over the tiles in this tileset. + */ + public Iterator iterator() { + return tiles.iterator(); + } + + /** + * Returns the width of tiles in this tileset. All tiles in a tileset + * should be the same width, and the same as the tile width of the map the + * tileset is used with. + * + * @return int - The maximum tile width + */ + public int getTileWidth() { + return tileDimensions.width; + } + + /** + * Returns the tile height of tiles in this tileset. Not all tiles in a + * tileset are required to have the same height, but the height should be + * at least the tile height of the map the tileset is used with. + * + * If there are tiles with varying heights in this tileset, the returned + * height will be the maximum. + * + * @return the max height of the tiles in the set + */ + public int getTileHeight() { + return tileDimensions.height; + } + + /** + * Returns the spacing between the tiles on the tileset image. + * @return the spacing in pixels between the tiles on the tileset image + */ + public int getTileSpacing() { + return tileSpacing; + } + + /** + * Returns the margin around the tiles on the tileset image. + * @return the margin in pixels around the tiles on the tileset image + */ + public int getTileMargin() { + return tileMargin; + } + + /** + * Returns the number of tiles per row in the original tileset image. + * @return the number of tiles per row in the original tileset image. + */ + public int getTilesPerRow() { + return tilesPerRow; + } + + /** + * Gets the tile with local id i. + * + * @param i local id of tile + * @return A tile with local id i or null if no + * tile exists with that id + */ + public Tile getTile(int i) { + try { + return tiles.get(i); + } catch (ArrayIndexOutOfBoundsException a) {} + return null; + } + + /** + * Returns the first non-null tile in the set. + * + * @return The first tile in this tileset, or null if none + * exists. + */ + public Tile getFirstTile() { + Tile ret = null; + int i = 0; + while (ret == null && i <= getMaxTileId()) { + ret = getTile(i); + i++; + } + return ret; + } + + /** + * Returns the source of this tileset. + * + * @return a filename if tileset is external or null if + * tileset is internal. + */ + public String getSource() { + return externalSource; + } + + /** + * Returns the base directory for the tileset + * + * @return a directory in native format as given in the tileset file or tag + */ + public String getBaseDir() { + return base; + } + + /** + * Returns the filename of the tileset image. + * + * @return the filename of the tileset image, or null if this + * tileset doesn't reference a tileset image + */ + public String getTilebmpFile() { + if (tilebmpFile != null) { + try { + return tilebmpFile.getCanonicalPath(); + } catch (IOException e) { + } + } else if (sheet != null) { + try { + return sheet.spritesheetFile.getCanonicalPath(); + } catch (IOException e) {} + } + + return null; + } + + /** + * @return the name of this tileset. + */ + public String getName() { + return name; + } + + /** + * Returns the transparent color of the tileset image, or null + * if none is set. + * + * @return Color - The transparent color of the set + */ + public Color getTransparentColor() { + return transparentColor; + } + + /** + * @return the name of the tileset, and the total tiles + */ + public String toString() { + return getName() + " [" + size() + "]"; + } + + + // TILE IMAGE CODE + + /** + * Returns whether the tileset is derived from a tileset image. + * + * @return tileSetImage != null + */ + public boolean isSetFromImage() { + return tileSetImage != null; + } + + public Spritesheet getSpritesheet() { + return this.sheet; + } +} diff --git a/hacked-libtiled/tiled/io/TMXMapReader.java b/hacked-libtiled/tiled/io/TMXMapReader.java new file mode 100644 index 0000000..203c15d --- /dev/null +++ b/hacked-libtiled/tiled/io/TMXMapReader.java @@ -0,0 +1,963 @@ +/* + * Copyright 2004-2010, Thorbjørn Lindeijer + * Copyright 2004-2006, Adam Turk + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.io; + +import java.awt.Color; +import java.awt.Image; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Properties; +import java.util.TreeMap; +import java.util.zip.GZIPInputStream; +import java.util.zip.InflaterInputStream; + +import javax.imageio.ImageIO; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import tiled.core.AnimatedTile; +import tiled.core.Map; +import tiled.core.MapLayer; +import tiled.core.MapObject; +import tiled.core.ObjectGroup; +import tiled.core.Tile; +import tiled.core.TileLayer; +import tiled.core.TileSet; +import tiled.util.Base64; +import tiled.util.BasicTileCutter; +import tiled.util.ImageHelper; + +import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; + +/** + * The standard map reader for TMX files. Supports reading .tmx, .tmx.gz and *.tsx files. + */ +public class TMXMapReader +{ + private Map map; + private String xmlPath; + private String error; + private final EntityResolver entityResolver = new MapEntityResolver(); + private TreeMap tilesetPerFirstGid; + public final TMXMapReaderSettings settings = new TMXMapReaderSettings(); + private final HashMap cachedTilesets = new HashMap(); + + public static final class TMXMapReaderSettings { + public boolean reuseCachedTilesets = false; + } + + public TMXMapReader() { + } + + String getError() { + return error; + } + + private static String makeUrl(String filename) throws MalformedURLException { + final String url; + if (filename.indexOf("://") > 0 || filename.startsWith("file:")) { + url = filename; + } else { + url = new File(filename).toURI().toString(); + } + return url; + } + + private static int reflectFindMethodByName(Class c, String methodName) { + Method[] methods = c.getMethods(); + for (int i = 0; i < methods.length; i++) { + if (methods[i].getName().equalsIgnoreCase(methodName)) { + return i; + } + } + return -1; + } + + private void reflectInvokeMethod(Object invokeVictim, Method method, + String[] args) throws Exception + { + Class[] parameterTypes = method.getParameterTypes(); + Object[] conformingArguments = new Object[parameterTypes.length]; + + if (args.length < parameterTypes.length) { + throw new Exception("Insufficient arguments were supplied"); + } + + for (int i = 0; i < parameterTypes.length; i++) { + if ("int".equalsIgnoreCase(parameterTypes[i].getName())) { + conformingArguments[i] = new Integer(args[i]); + } else if ("float".equalsIgnoreCase(parameterTypes[i].getName())) { + conformingArguments[i] = new Float(args[i]); + } else if (parameterTypes[i].getName().endsWith("String")) { + conformingArguments[i] = args[i]; + } else if ("boolean".equalsIgnoreCase(parameterTypes[i].getName())) { + conformingArguments[i] = Boolean.valueOf(args[i]); + } else { + // Unsupported argument type, defaulting to String + conformingArguments[i] = args[i]; + } + } + + method.invoke(invokeVictim,conformingArguments); + } + + private void setOrientation(String o) { + if ("isometric".equalsIgnoreCase(o)) { + map.setOrientation(Map.ORIENTATION_ISOMETRIC); + } else if ("orthogonal".equalsIgnoreCase(o)) { + map.setOrientation(Map.ORIENTATION_ORTHOGONAL); + } else if ("hexagonal".equalsIgnoreCase(o)) { + map.setOrientation(Map.ORIENTATION_HEXAGONAL); + } else if ("shifted".equalsIgnoreCase(o)) { + map.setOrientation(Map.ORIENTATION_SHIFTED); + } else { +// System.out.println("Unknown orientation '" + o + "'"); + } + } + + private static String getAttributeValue(Node node, String attribname) { + final NamedNodeMap attributes = node.getAttributes(); + String value = null; + if (attributes != null) { + Node attribute = attributes.getNamedItem(attribname); + if (attribute != null) { + value = attribute.getNodeValue(); + } + } + return value; + } + + private static int getAttribute(Node node, String attribname, int def) { + final String attr = getAttributeValue(node, attribname); + if (attr != null) { + return Integer.parseInt(attr); + } else { + return def; + } + } + + private Object unmarshalClass(Class reflector, Node node) + throws InstantiationException, IllegalAccessException, + InvocationTargetException { + Constructor cons = null; + try { + cons = reflector.getConstructor((Class[]) null); + } catch (SecurityException e1) { + e1.printStackTrace(); + } catch (NoSuchMethodException e1) { + e1.printStackTrace(); + return null; + } + Object o = cons.newInstance((Object[]) null); + Node n; + + Method[] methods = reflector.getMethods(); + NamedNodeMap nnm = node.getAttributes(); + + if (nnm != null) { + for (int i = 0; i < nnm.getLength(); i++) { + n = nnm.item(i); + + try { + int j = reflectFindMethodByName(reflector, + "set" + n.getNodeName()); + if (j >= 0) { + reflectInvokeMethod(o,methods[j], + new String [] {n.getNodeValue()}); + } else { +// System.out.println("Unsupported attribute '" + +// n.getNodeName() + "' on <" + +// node.getNodeName() + "> tag"); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + return o; + } + + private Image unmarshalImage(Node t, String baseDir) throws IOException + { + Image img = null; + + String source = getAttributeValue(t, "source"); + + if (source != null) { + if (checkRoot(source)) { + source = makeUrl(source); + } else { + source = makeUrl(baseDir + source); + } + img = ImageIO.read(new URL(source)); + } else { + NodeList nl = t.getChildNodes(); + + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if ("data".equals(node.getNodeName())) { + Node cdata = node.getFirstChild(); + if (cdata != null) { + String sdata = cdata.getNodeValue(); + char[] charArray = sdata.trim().toCharArray(); + byte[] imageData = Base64.decode(charArray); + img = ImageHelper.bytesToImage(imageData); + + // Deriving a scaled instance, even if it has the same + // size, somehow makes drawing of the tiles a lot + // faster on various systems (seen on Linux, Windows + // and MacOS X). + img = img.getScaledInstance( + img.getWidth(null), img.getHeight(null), + Image.SCALE_FAST); + } + break; + } + } + } + + return img; + } + + private TileSet unmarshalTilesetFile(InputStream in, String filename, TMXMap tmxMap) + throws Exception + { + TileSet set = null; + Node tsNode; + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + try { + DocumentBuilder builder = factory.newDocumentBuilder(); + //builder.setErrorHandler(new XMLErrorHandler()); + Document tsDoc = builder.parse(in, "."); + + String xmlPathSave = xmlPath; + if (filename.indexOf(File.separatorChar) >= 0) { + xmlPath = filename.substring(0, + filename.lastIndexOf(File.separatorChar) + 1); + } + + NodeList tsNodeList = tsDoc.getElementsByTagName("tileset"); + + // There can be only one tileset in a .tsx file. + tsNode = tsNodeList.item(0); + if (tsNode != null) { + set = unmarshalTileset(tsNode, tmxMap); + if (set.getSource() != null) { +// System.out.println("Recursive external tilesets are not supported."); + } + set.setSource(filename); + } + + xmlPath = xmlPathSave; + } catch (SAXException e) { + error = "Failed while loading " + filename + ": " + + e.getLocalizedMessage(); + } + + return set; + } + + private TileSet unmarshalTileset(Node t, TMXMap tmxMap) throws Exception { + String source = getAttributeValue(t, "source"); + String basedir = getAttributeValue(t, "basedir"); + int firstGid = getAttribute(t, "firstgid", 1); + + String tilesetBaseDir = xmlPath; + + if (basedir != null) { + tilesetBaseDir = basedir; //makeUrl(basedir); + } + + if (source != null) { + String filename = tilesetBaseDir + source; + //if (checkRoot(source)) { + // filename = makeUrl(source); + //} + + TileSet ext = null; + + try { + InputStream in = new URL(makeUrl(filename)).openStream(); + ext = unmarshalTilesetFile(in, filename, tmxMap); + setFirstGidForTileset(ext, firstGid); + } catch (FileNotFoundException fnf) { + error = "Could not find external tileset file " + filename; + } + + if (ext == null) { + error = "Tileset " + source + " was not loaded correctly!"; + } + + return ext; + } + else { + final int tileWidth = getAttribute(t, "tilewidth", map != null ? map.getTileWidth() : 0); + final int tileHeight = getAttribute(t, "tileheight", map != null ? map.getTileHeight() : 0); + final int tileSpacing = getAttribute(t, "spacing", 0); + final int tileMargin = getAttribute(t, "margin", 0); + + final String name = getAttributeValue(t, "name"); + + TileSet set; + if (settings.reuseCachedTilesets) { + set = cachedTilesets.get(name); + if (set != null) { + setFirstGidForTileset(set, firstGid); + return set; + } + set = new TileSet(); + cachedTilesets.put(name, set); + } else { + set = new TileSet(); + } + + set.setName(name); + set.setBaseDir(basedir); + setFirstGidForTileset(set, firstGid); + + boolean hasTilesetImage = false; + NodeList children = t.getChildNodes(); + + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + + if (child.getNodeName().equalsIgnoreCase("image")) { + if (hasTilesetImage) { +// System.out.println("Ignoring illegal image element after tileset image."); + continue; + } + + String imgSource = getAttributeValue(child, "source"); + String transStr = getAttributeValue(child, "trans"); + + if (imgSource != null) { + // Not a shared image, but an entire set in one image + // file. There should be only one image element in this + // case. + + if (tmxMap.getProject().getSpritesheet(name) != null) { + set.loadFromProject(name, tmxMap); + } else { + + + hasTilesetImage = true; + + // FIXME: importTileBitmap does not fully support URLs + String sourcePath = imgSource; + if (! new File(imgSource).isAbsolute()) { + sourcePath = tilesetBaseDir + imgSource; + } + + if (transStr != null) { + if (transStr.startsWith("#")) + transStr = transStr.substring(1); + + int colorInt = Integer.parseInt(transStr, 16); + Color color = new Color(colorInt); + set.setTransparentColor(color); + } + + set.importTileBitmap(sourcePath, new BasicTileCutter( + tileWidth, tileHeight, tileSpacing, tileMargin)); + } + } + } + else if (child.getNodeName().equalsIgnoreCase("tile")) { + Tile tile = unmarshalTile(set, child, tilesetBaseDir); + if (!hasTilesetImage || tile.getId() > set.getMaxTileId()) { + set.addTile(tile); + } else { + Tile myTile = set.getTile(tile.getId()); + myTile.setProperties(tile.getProperties()); + //TODO: there is the possibility here of overlaying images, + // which some people may want + } + } + } + + return set; + } + } + + private MapObject readMapObject(Node t) throws Exception { + final String name = getAttributeValue(t, "name"); + final String type = getAttributeValue(t, "type"); + final int x = getAttribute(t, "x", 0); + final int y = getAttribute(t, "y", 0); + final int width = getAttribute(t, "width", 0); + final int height = getAttribute(t, "height", 0); + + MapObject obj = new MapObject(x, y, width, height); + if (name != null) + obj.setName(name); + if (type != null) + obj.setType(type); + + NodeList children = t.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + if ("image".equalsIgnoreCase(child.getNodeName())) { + String source = getAttributeValue(child, "source"); + if (source != null) { + if (! new File(source).isAbsolute()) { + source = xmlPath + source; + } + obj.setImageSource(source); + } + break; + } + } + + Properties props = new Properties(); + readProperties(children, props); + + obj.setProperties(props); + return obj; + } + + /** + * Reads properties from amongst the given children. When a "properties" + * element is encountered, it recursively calls itself with the children + * of this node. This function ensures backward compatibility with tmx + * version 0.99a. + * + * Support for reading property values stored as character data was added + * in Tiled 0.7.0 (tmx version 0.99c). + * + * @param children the children amongst which to find properties + * @param props the properties object to set the properties of + */ + private static void readProperties(NodeList children, Properties props) { + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + if ("property".equalsIgnoreCase(child.getNodeName())) { + final String key = getAttributeValue(child, "name"); + String value = getAttributeValue(child, "value"); + if (value == null) { + Node grandChild = child.getFirstChild(); + if (grandChild != null) { + value = grandChild.getNodeValue(); + if (value != null) + value = value.trim(); + } + } + if (value != null) + props.setProperty(key, value); + } + else if ("properties".equals(child.getNodeName())) { + readProperties(child.getChildNodes(), props); + } + } + } + + private Tile unmarshalTile(TileSet set, Node t, String baseDir) + throws Exception + { + Tile tile = null; + NodeList children = t.getChildNodes(); + boolean isAnimated = false; + + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + if ("animation".equalsIgnoreCase(child.getNodeName())) { + isAnimated = true; + break; + } + } + + try { + if (isAnimated) { + tile = (Tile) unmarshalClass(AnimatedTile.class, t); + } else { + tile = (Tile) unmarshalClass(Tile.class, t); + } + } catch (Exception e) { + error = "Failed creating tile: " + e.getLocalizedMessage(); + return tile; + } + + tile.setTileSet(set); + + readProperties(children, tile.getProperties()); + + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + if ("image".equalsIgnoreCase(child.getNodeName())) { + int id = getAttribute(child, "id", -1); + Image img = unmarshalImage(child, baseDir); + tile.setImage(img); + } else if ("animation".equalsIgnoreCase(child.getNodeName())) { + // TODO: fill this in once TMXMapWriter is complete + } + } + + return tile; + } + + private MapLayer unmarshalObjectGroup(Node t) throws Exception { + ObjectGroup og = null; + try { + og = (ObjectGroup)unmarshalClass(ObjectGroup.class, t); + } catch (Exception e) { + e.printStackTrace(); + return og; + } + + final int offsetX = getAttribute(t, "x", 0); + final int offsetY = getAttribute(t, "y", 0); + og.setOffset(offsetX, offsetY); + + // Add all objects from the objects group + NodeList children = t.getChildNodes(); + + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + if ("object".equalsIgnoreCase(child.getNodeName())) { + og.addObject(readMapObject(child)); + } + } + + Properties props = new Properties(); + readProperties(children, props); + og.setProperties(props); + + return og; + } + + /** + * Loads a map layer from a layer node. + * @param t the node representing the "layer" element + * @return the loaded map layer + * @throws Exception + */ + private MapLayer readLayer(Node t, TMXMap tmxMap) throws Exception { + final int layerWidth = getAttribute(t, "width", map.getWidth()); + final int layerHeight = getAttribute(t, "height", map.getHeight()); + + TileLayer ml = new TileLayer(layerWidth, layerHeight); + + final int offsetX = getAttribute(t, "x", 0); + final int offsetY = getAttribute(t, "y", 0); + final int visible = getAttribute(t, "visible", 1); + String opacity = getAttributeValue(t, "opacity"); + + ml.setName(getAttributeValue(t, "name")); + + if (opacity != null) { + ml.setOpacity(Float.parseFloat(opacity)); + } + + readProperties(t.getChildNodes(), ml.getProperties()); + + for (Node child = t.getFirstChild(); child != null; + child = child.getNextSibling()) + { + String nodeName = child.getNodeName(); + if ("data".equalsIgnoreCase(nodeName)) { + String encoding = getAttributeValue(child, "encoding"); + + if (encoding != null && "base64".equalsIgnoreCase(encoding)) { + Node cdata = child.getFirstChild(); + if (cdata != null) { + char[] enc = cdata.getNodeValue().trim().toCharArray(); + byte[] dec = Base64.decode(enc); + ByteArrayInputStream bais = new ByteArrayInputStream(dec); + InputStream is; + + String comp = getAttributeValue(child, "compression"); + + if ("gzip".equalsIgnoreCase(comp)) { + final int len = layerWidth * layerHeight * 4; + is = new GZIPInputStream(bais, len); + } else if ("zlib".equalsIgnoreCase(comp)) { + is = new InflaterInputStream(bais); + } else if (comp != null && !comp.isEmpty()) { + throw new IOException("Unrecognized compression method \"" + comp + "\" for map layer " + ml.getName()); + } else { + is = bais; + } + + for (int y = 0; y < ml.getHeight(); y++) { + for (int x = 0; x < ml.getWidth(); x++) { + int tileId = 0; + tileId |= is.read(); + tileId |= is.read() << 8; + tileId |= is.read() << 16; + tileId |= is.read() << 24; + + java.util.Map.Entry ts = findTileSetForTileGID(tileId); + if (ts != null) { + ml.setTileAt(x, y, + ts.getValue().getTile(tileId - ts.getKey())); + if (ts.getValue().getSpritesheet() != null) { + tmxMap.usedSpritesheets.add(ts.getValue().getSpritesheet()); + ts.getValue().getSpritesheet().addBacklink(tmxMap); + } + } else { + ml.setTileAt(x, y, null); + } + } + } + } + } else { + int x = 0, y = 0; + for (Node dataChild = child.getFirstChild(); + dataChild != null; + dataChild = dataChild.getNextSibling()) + { + if ("tile".equalsIgnoreCase(dataChild.getNodeName())) { + int tileId = getAttribute(dataChild, "gid", -1); + java.util.Map.Entry ts = findTileSetForTileGID(tileId); + if (ts != null) { + ml.setTileAt(x, y, + ts.getValue().getTile(tileId - ts.getKey())); + if (ts.getValue().getSpritesheet() != null) { + tmxMap.usedSpritesheets.add(ts.getValue().getSpritesheet()); + ts.getValue().getSpritesheet().addBacklink(tmxMap); + } + } else { + ml.setTileAt(x, y, null); + } + + x++; + if (x == ml.getWidth()) { + x = 0; y++; + } + if (y == ml.getHeight()) { break; } + } + } + } + } else if ("tileproperties".equalsIgnoreCase(nodeName)) { + for (Node tpn = child.getFirstChild(); + tpn != null; + tpn = tpn.getNextSibling()) + { + if ("tile".equalsIgnoreCase(tpn.getNodeName())) { + int x = getAttribute(tpn, "x", -1); + int y = getAttribute(tpn, "y", -1); + + Properties tip = new Properties(); + + readProperties(tpn.getChildNodes(), tip); + ml.setTileInstancePropertiesAt(x, y, tip); + } + } + } + } + + // This is done at the end, otherwise the offset is applied during + // the loading of the tiles. + ml.setOffset(offsetX, offsetY); + + // Invisible layers are automatically locked, so it is important to + // set the layer to potentially invisible _after_ the layer data is + // loaded. + // todo: Shouldn't this be just a user interface feature, rather than + // todo: something to keep in mind at this level? + ml.setVisible(visible == 1); + + return ml; + } + + private void buildMap(Document doc, TMXMap tmxMap) throws Exception { + Node item, mapNode; + + mapNode = doc.getDocumentElement(); + + if (!"map".equals(mapNode.getNodeName())) { + throw new Exception("Not a valid tmx map file."); + } + + // Get the map dimensions and create the map + int mapWidth = getAttribute(mapNode, "width", 0); + int mapHeight = getAttribute(mapNode, "height", 0); + + if (mapWidth > 0 && mapHeight > 0) { + map = new Map(mapWidth, mapHeight); + } else { + // Maybe this map is still using the dimensions element + NodeList l = doc.getElementsByTagName("dimensions"); + for (int i = 0; (item = l.item(i)) != null; i++) { + if (item.getParentNode() == mapNode) { + mapWidth = getAttribute(item, "width", 0); + mapHeight = getAttribute(item, "height", 0); + + if (mapWidth > 0 && mapHeight > 0) { + map = new Map(mapWidth, mapHeight); + } + } + } + } + + if (map == null) { + throw new Exception("Couldn't locate map dimensions."); + } + + // Load other map attributes + String orientation = getAttributeValue(mapNode, "orientation"); + int tileWidth = getAttribute(mapNode, "tilewidth", 0); + int tileHeight = getAttribute(mapNode, "tileheight", 0); + + if (tileWidth > 0) { + map.setTileWidth(tileWidth); + } + if (tileHeight > 0) { + map.setTileHeight(tileHeight); + } + + if (orientation != null) { + setOrientation(orientation); + } else { + setOrientation("orthogonal"); + } + + // Load properties + readProperties(mapNode.getChildNodes(), map.getProperties()); + + // Load tilesets first, in case order is munged + tilesetPerFirstGid = new TreeMap(); + NodeList l = doc.getElementsByTagName("tileset"); + for (int i = 0; (item = l.item(i)) != null; i++) { + map.addTileset(unmarshalTileset(item, tmxMap)); + } + + // Load the layers and objectgroups + for (Node sibs = mapNode.getFirstChild(); sibs != null; + sibs = sibs.getNextSibling()) + { + if ("layer".equals(sibs.getNodeName())) { + MapLayer layer = readLayer(sibs, tmxMap); + if (layer != null) { + map.addLayer(layer); + } + } + else if ("objectgroup".equals(sibs.getNodeName())) { + MapLayer layer = unmarshalObjectGroup(sibs); + if (layer != null) { + map.addLayer(layer); + } + } + } + tilesetPerFirstGid = null; + } + + private Map unmarshal(Reader reader, TMXMap tmxMap) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + Document doc; + try { + factory.setIgnoringComments(true); + factory.setIgnoringElementContentWhitespace(true); + factory.setExpandEntityReferences(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + builder.setEntityResolver(entityResolver); + InputSource insrc = new InputSource(reader); + insrc.setSystemId(xmlPath); + insrc.setEncoding("UTF-8"); + doc = builder.parse(insrc); + } catch (SAXException e) { + e.printStackTrace(); + throw new Exception("Error while parsing map file: " + + e.toString()); + } + + buildMap(doc, tmxMap); + + return map; + } + private Map unmarshal(InputStream in, TMXMap tmxMap) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + Document doc; + try { + factory.setIgnoringComments(true); + factory.setIgnoringElementContentWhitespace(true); + factory.setExpandEntityReferences(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + builder.setEntityResolver(entityResolver); + InputSource insrc = new InputSource(in); + insrc.setSystemId(xmlPath); + insrc.setEncoding("UTF-8"); + doc = builder.parse(insrc); + } catch (SAXException e) { + e.printStackTrace(); + throw new Exception("Error while parsing map file: " + + e.toString()); + } + + buildMap(doc, tmxMap); + + return map; + } + + + public Map readMap(String filename, TMXMap tmxMap) throws Exception { + xmlPath = filename.substring(0, + filename.lastIndexOf(File.separatorChar) + 1); + + String xmlFile = makeUrl(filename); + //xmlPath = makeUrl(xmlPath); + + URL url = new URL(xmlFile); + InputStream is = url.openStream(); + + // Wrap with GZIP decoder for .tmx.gz files + if (filename.endsWith(".gz")) { + is = new GZIPInputStream(is); + } + + Map unmarshalledMap = unmarshal(is, tmxMap); + unmarshalledMap.setFilename(filename); + + map = null; + + return unmarshalledMap; + } + + public Map readMap(InputStream in, TMXMap tmxMap) throws Exception { + xmlPath = makeUrl("."); + + Map unmarshalledMap = unmarshal(in, tmxMap); + + //unmarshalledMap.setFilename(xmlFile) + // + return unmarshalledMap; + } + + public Map readMap(Reader reader, TMXMap tmxMap) throws Exception { + xmlPath = makeUrl("."); + + Map unmarshalledMap = unmarshal(reader, tmxMap); + + //unmarshalledMap.setFilename(xmlFile) + // + return unmarshalledMap; + } + + public TileSet readTileset(String filename, TMXMap tmxMap) throws Exception { + String xmlFile = filename; + + xmlPath = filename.substring(0, + filename.lastIndexOf(File.separatorChar) + 1); + + xmlFile = makeUrl(xmlFile); + xmlPath = makeUrl(xmlPath); + + URL url = new URL(xmlFile); + return unmarshalTilesetFile(url.openStream(), filename, tmxMap); + } + + public TileSet readTileset(InputStream in, TMXMap tmxMap) throws Exception { + return unmarshalTilesetFile(in, ".", tmxMap); + } + + public boolean accept(File pathName) { + try { + String path = pathName.getCanonicalPath(); + if (path.endsWith(".tmx") || path.endsWith(".tsx") || + path.endsWith(".tmx.gz")) { + return true; + } + } catch (IOException e) {} + return false; + } + + private class MapEntityResolver implements EntityResolver + { + public InputSource resolveEntity(String publicId, String systemId) { + if (systemId.equals("http://mapeditor.org/dtd/1.0/map.dtd")) { + return new InputSource(TMXMapReader.class.getResourceAsStream( + "resources/map.dtd")); + } + return null; + } + } + + /** + * This utility function will check the specified string to see if it + * starts with one of the OS root designations. (Ex.: '/' on Unix, 'C:' on + * Windows) + * + * @param filename a filename to check for absolute or relative path + * @return true if the specified filename starts with a + * filesystem root, false otherwise. + */ + public static boolean checkRoot(String filename) { + File[] roots = File.listRoots(); + + for (File root : roots) { + try { + String canonicalRoot = root.getCanonicalPath().toLowerCase(); + if (filename.toLowerCase().startsWith(canonicalRoot)) { + return true; + } + } catch (IOException e) { + // Do we care? + } + } + + return false; + } + + /** + * Get the tile set and its corresponding firstgid that matches the given + * global tile id. + * + * + * @param gid a global tile id + * @return the tileset containing the tile with the given global tile id, + * or null when no such tileset exists + */ + private java.util.Map.Entry findTileSetForTileGID(int gid) { + return tilesetPerFirstGid.floorEntry(gid); + } + + private void setFirstGidForTileset(TileSet tileset, int firstGid) { + tilesetPerFirstGid.put(firstGid, tileset); + } + +} diff --git a/hacked-libtiled/tiled/io/TMXMapWriter.java b/hacked-libtiled/tiled/io/TMXMapWriter.java new file mode 100644 index 0000000..31cc20d --- /dev/null +++ b/hacked-libtiled/tiled/io/TMXMapWriter.java @@ -0,0 +1,613 @@ +/* + * Copyright 2004-2010, Thorbjørn Lindeijer + * Copyright 2004-2008, Adam Turk + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.io; + +import java.awt.Color; +import java.awt.Rectangle; +import java.io.*; +import java.nio.charset.Charset; +import java.util.*; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPOutputStream; + +import tiled.core.*; +import tiled.core.Map; +import tiled.io.xml.XMLWriter; +import tiled.util.Base64; + +/** + * A writer for Tiled's TMX map format. + */ +public class TMXMapWriter +{ + private static final int LAST_BYTE = 0x000000FF; + + private static final boolean encodeLayerData = true; + private static final boolean compressLayerData = encodeLayerData; + + private HashMap firstGidPerTileset; + + public static class Settings { + public static final String LAYER_COMPRESSION_METHOD_GZIP = "gzip"; + public static final String LAYER_COMPRESSION_METHOD_ZLIB = "zlib"; + + public String layerCompressionMethod = LAYER_COMPRESSION_METHOD_GZIP; + } + public Settings settings = new Settings(); + + /** + * Saves a map to an XML file. + * + * @param filename the filename of the map file + */ + public void writeMap(Map map, String filename) throws Exception { + OutputStream os = new FileOutputStream(filename); + + if (filename.endsWith(".tmx.gz")) { + os = new GZIPOutputStream(os); + } + + Writer writer = new OutputStreamWriter(os, Charset.forName("UTF-8")); + XMLWriter xmlWriter = new XMLWriter(writer); + + xmlWriter.startDocument(); + writeMap(map, xmlWriter, filename); + xmlWriter.endDocument(); + + writer.flush(); + + if (os instanceof GZIPOutputStream) { + ((GZIPOutputStream)os).finish(); + } + } + + /** + * Saves a tileset to an XML file. + * + * @param filename the filename of the tileset file + */ + public void writeTileset(TileSet set, String filename) throws Exception { + OutputStream os = new FileOutputStream(filename); + Writer writer = new OutputStreamWriter(os, Charset.forName("UTF-8")); + XMLWriter xmlWriter = new XMLWriter(writer); + + xmlWriter.startDocument(); + writeTileset(set, xmlWriter, filename); + xmlWriter.endDocument(); + + writer.flush(); + } + + + public void writeMap(Map map, OutputStream out) throws Exception { + writeMap(map, out, "/."); + } + + public void writeMap(Map map, OutputStream out, String workingDirectory) throws Exception { + Writer writer = new OutputStreamWriter(out,Charset.forName("UTF-8")); + XMLWriter xmlWriter = new XMLWriter(writer); + + xmlWriter.startDocument(); + writeMap(map, xmlWriter, workingDirectory); + xmlWriter.endDocument(); + + writer.flush(); + } + + public void writeTileset(TileSet set, OutputStream out) throws Exception { + Writer writer = new OutputStreamWriter(out, Charset.forName("UTF-8")); + XMLWriter xmlWriter = new XMLWriter(writer); + + xmlWriter.startDocument(); + writeTileset(set, xmlWriter, "/."); + xmlWriter.endDocument(); + + writer.flush(); + } + + private void writeMap(Map map, XMLWriter w, String wp) throws IOException { + w.writeDocType("map", null, "http://mapeditor.org/dtd/1.0/map.dtd"); + w.startElement("map"); + + w.writeAttribute("version", "1.0"); + + switch (map.getOrientation()) { + case Map.ORIENTATION_ORTHOGONAL: + w.writeAttribute("orientation", "orthogonal"); break; + case Map.ORIENTATION_ISOMETRIC: + w.writeAttribute("orientation", "isometric"); break; + case Map.ORIENTATION_HEXAGONAL: + w.writeAttribute("orientation", "hexagonal"); break; + case Map.ORIENTATION_SHIFTED: + w.writeAttribute("orientation", "shifted"); break; + } + + w.writeAttribute("width", map.getWidth()); + w.writeAttribute("height", map.getHeight()); + w.writeAttribute("tilewidth", map.getTileWidth()); + w.writeAttribute("tileheight", map.getTileHeight()); + + writeProperties(map.getProperties(), w); + + firstGidPerTileset = new HashMap(); + int firstgid = 1; + for (TileSet tileset : map.getTileSets()) { + setFirstGidForTileset(tileset, firstgid); + writeTilesetReference(tileset, w, wp); + firstgid += tileset.getMaxTileId() + 1; + } + + for (MapLayer layer : map) { + writeMapLayer(layer, w, wp); + } + firstGidPerTileset = null; + + w.endElement(); + } + + private static void writeProperties(Properties props, XMLWriter w) throws + IOException + { + if (!props.isEmpty()) { + final SortedSet propertyKeys = new TreeSet(); + propertyKeys.addAll(props.keySet()); + w.startElement("properties"); + for (Object propertyKey : propertyKeys) { + final String key = (String) propertyKey; + final String property = props.getProperty(key); + w.startElement("property"); + w.writeAttribute("name", key); + if (property.indexOf('\n') == -1) { + w.writeAttribute("value", property); + } else { + // Save multiline values as character data + w.writeCDATA(property); + } + w.endElement(); + } + w.endElement(); + } + } + + /** + * Writes a reference to an external tileset into a XML document. In the + * case where the tileset is not stored in an external file, writes the + * contents of the tileset instead. + * + * @param set the tileset to write a reference to + * @param w the XML writer to write to + * @param wp the working directory of the map + * @throws java.io.IOException + */ + private void writeTilesetReference(TileSet set, XMLWriter w, String wp) + throws IOException { + + String source = set.getSource(); + + if (source == null) { + writeTileset(set, w, wp); + } else { + w.startElement("tileset"); + w.writeAttribute("firstgid", getFirstGidForTileset(set)); + w.writeAttribute("source", getRelativePath(wp, source)); + if (set.getBaseDir() != null) { + w.writeAttribute("basedir", set.getBaseDir()); + } + w.endElement(); + } + } + + private void writeTileset(TileSet set, XMLWriter w, String wp) + throws IOException { + + String tileBitmapFile = set.getTilebmpFile(); + String name = set.getName(); + + w.startElement("tileset"); + w.writeAttribute("firstgid", getFirstGidForTileset(set)); + + if (name != null) { + w.writeAttribute("name", name); + } + + if (tileBitmapFile != null) { + w.writeAttribute("tilewidth", set.getTileWidth()); + w.writeAttribute("tileheight", set.getTileHeight()); + + final int tileSpacing = set.getTileSpacing(); + final int tileMargin = set.getTileMargin(); + if (tileSpacing != 0) { + w.writeAttribute("spacing", tileSpacing); + } + if (tileMargin != 0) { + w.writeAttribute("margin", tileMargin); + } + } + + if (set.getBaseDir() != null) { + w.writeAttribute("basedir", set.getBaseDir()); + } + + if (tileBitmapFile != null) { + w.startElement("image"); + w.writeAttribute("source", getRelativePath(wp, tileBitmapFile)); + + Color trans = set.getTransparentColor(); + if (trans != null) { + w.writeAttribute("trans", Integer.toHexString( + trans.getRGB()).substring(2)); + } + w.endElement(); + + // Write tile properties when necessary. + for (Tile tile : set) { + // todo: move the null check back into the iterator? + if (tile != null && !tile.getProperties().isEmpty()) { + w.startElement("tile"); + w.writeAttribute("id", tile.getId()); + writeProperties(tile.getProperties(), w); + w.endElement(); + } + } + } else { + // Check to see if there is a need to write tile elements + boolean needWrite = false; + + // As long as one has properties, they all need to be written. + // TODO: This shouldn't be necessary + for (Tile tile : set) { + if (!tile.getProperties().isEmpty()) { + needWrite = true; + break; + } + } + + if (needWrite) { + for (Tile tile : set) { + // todo: move this check back into the iterator? + if (tile != null) { + writeTile(tile, w); + } + } + } + } + w.endElement(); + } + + private static void writeObjectGroup(ObjectGroup o, XMLWriter w, String wp) + throws IOException + { + Iterator itr = o.getObjects(); + while (itr.hasNext()) { + writeMapObject(itr.next(), w, wp); + } + } + + /** + * Writes this layer to an XMLWriter. This should be done after the + * first global ids for the tilesets are determined, in order for the right + * gids to be written to the layer data. + */ + private void writeMapLayer(MapLayer l, XMLWriter w, String wp) throws IOException { + Rectangle bounds = l.getBounds(); + + if (l instanceof ObjectGroup) { + w.startElement("objectgroup"); + } else { + w.startElement("layer"); + } + + w.writeAttribute("name", l.getName()); + if (bounds.width != 0) { + w.writeAttribute("width", bounds.width); + } + if (bounds.height != 0) { + w.writeAttribute("height", bounds.height); + } + if (bounds.x != 0) { + w.writeAttribute("x", bounds.x); + } + if (bounds.y != 0) { + w.writeAttribute("y", bounds.y); + } + + if (!l.isVisible()) { + w.writeAttribute("visible", "0"); + } + if (l.getOpacity() < 1.0f) { + w.writeAttribute("opacity", l.getOpacity()); + } + + writeProperties(l.getProperties(), w); + + if (l instanceof ObjectGroup){ + writeObjectGroup((ObjectGroup) l, w, wp); + } else if (l instanceof TileLayer) { + final TileLayer tl = (TileLayer) l; + w.startElement("data"); + if (encodeLayerData) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + OutputStream out; + + w.writeAttribute("encoding", "base64"); + + DeflaterOutputStream dos; + if (compressLayerData) { + if (Settings.LAYER_COMPRESSION_METHOD_ZLIB.equalsIgnoreCase(settings.layerCompressionMethod)) { + dos = new DeflaterOutputStream(baos); + } else if (Settings.LAYER_COMPRESSION_METHOD_GZIP.equalsIgnoreCase(settings.layerCompressionMethod)) { + dos = new GZIPOutputStream(baos); + } else { + throw new IOException("Unrecognized compression method \"" + settings.layerCompressionMethod + "\" for map layer " + l.getName()); + } + out = dos; + w.writeAttribute("compression", settings.layerCompressionMethod); + } else { + out = baos; + } + + for (int y = 0; y < l.getHeight(); y++) { + for (int x = 0; x < l.getWidth(); x++) { + Tile tile = tl.getTileAt(x + bounds.x, + y + bounds.y); + int gid = 0; + + if (tile != null) { + gid = getGid(tile); + } + + out.write(gid & LAST_BYTE); + out.write(gid >> 8 & LAST_BYTE); + out.write(gid >> 16 & LAST_BYTE); + out.write(gid >> 24 & LAST_BYTE); + } + } + + if (compressLayerData && dos != null) { + dos.finish(); + } + + w.writeCDATA(Base64.encodeToString(baos.toByteArray(), false)); + } else { + for (int y = 0; y < l.getHeight(); y++) { + for (int x = 0; x < l.getWidth(); x++) { + Tile tile = tl.getTileAt(x + bounds.x, y + bounds.y); + int gid = 0; + + if (tile != null) { + gid = getGid(tile); + } + + w.startElement("tile"); + w.writeAttribute("gid", gid); + w.endElement(); + } + } + } + w.endElement(); + + boolean tilePropertiesElementStarted = false; + + for (int y = 0; y < l.getHeight(); y++) { + for (int x = 0; x < l.getWidth(); x++) { + Properties tip = tl.getTileInstancePropertiesAt(x, y); + + if (tip != null && !tip.isEmpty()) { + if (!tilePropertiesElementStarted) { + w.startElement("tileproperties"); + tilePropertiesElementStarted = true; + } + w.startElement("tile"); + + w.writeAttribute("x", x); + w.writeAttribute("y", y); + + writeProperties(tip, w); + + w.endElement(); + } + } + } + + if (tilePropertiesElementStarted) + w.endElement(); + } + w.endElement(); + } + + /** + * Used to write tile elements for tilesets not based on a tileset image. + * + * @param tile the tile instance that should be written + * @param w the writer to write to + * @throws IOException when an io error occurs + */ + private void writeTile(Tile tile, XMLWriter w) throws IOException { + w.startElement("tile"); + w.writeAttribute("id", tile.getId()); + + writeProperties(tile.getProperties(), w); + + if (tile instanceof AnimatedTile) + writeAnimation(((AnimatedTile)tile).getSprite(), w); + + w.endElement(); + } + + private void writeAnimation(Sprite s, XMLWriter w) throws IOException { + w.startElement("animation"); + for (int k = 0; k < s.getTotalKeys(); k++) { + Sprite.KeyFrame key = s.getKey(k); + w.startElement("keyframe"); + w.writeAttribute("name", key.getName()); + for (int it = 0; it < key.getTotalFrames(); it++) { + Tile stile = key.getFrame(it); + w.startElement("tile"); + w.writeAttribute("gid", getGid(stile)); + w.endElement(); + } + w.endElement(); + } + w.endElement(); + } + + private static void writeMapObject(MapObject mapObject, XMLWriter w, String wp) + throws IOException + { + w.startElement("object"); + w.writeAttribute("name", mapObject.getName()); + + if (mapObject.getType().length() != 0) + w.writeAttribute("type", mapObject.getType()); + + w.writeAttribute("x", mapObject.getX()); + w.writeAttribute("y", mapObject.getY()); + + if (mapObject.getWidth() != 0) + w.writeAttribute("width", mapObject.getWidth()); + if (mapObject.getHeight() != 0) + w.writeAttribute("height", mapObject.getHeight()); + + writeProperties(mapObject.getProperties(), w); + + if (mapObject.getImageSource().length() > 0) { + w.startElement("image"); + w.writeAttribute("source", + getRelativePath(wp, mapObject.getImageSource())); + w.endElement(); + } + + w.endElement(); + } + + /** + * Returns the relative path from one file to the other. The function + * expects absolute paths, relative paths will be converted to absolute + * using the working directory. + * + * @param from the path of the origin file + * @param to the path of the destination file + * @return the relative path from origin to destination + */ + public static String getRelativePath(String from, String to) { + if(!(new File(to)).isAbsolute()) + return to; + + // Make the two paths absolute and unique + try { + from = new File(from).getCanonicalPath(); + to = new File(to).getCanonicalPath(); + } catch (IOException e) { + } + + File fromFile = new File(from); + File toFile = new File(to); + Vector fromParents = new Vector(); + Vector toParents = new Vector(); + + // Iterate to find both parent lists + while (fromFile != null) { + fromParents.add(0, fromFile.getName()); + fromFile = fromFile.getParentFile(); + } + while (toFile != null) { + toParents.add(0, toFile.getName()); + toFile = toFile.getParentFile(); + } + + // Iterate while parents are the same + int shared = 0; + int maxShared = Math.min(fromParents.size(), toParents.size()); + for (shared = 0; shared < maxShared; shared++) { + String fromParent = fromParents.get(shared); + String toParent = toParents.get(shared); + if (!fromParent.equals(toParent)) { + break; + } + } + + // Append .. for each remaining parent in fromParents + StringBuffer relPathBuf = new StringBuffer(); + for (int i = shared; i < fromParents.size() - 1; i++) { + relPathBuf.append(".." + File.separator); + } + + // Add the remaining part in toParents + for (int i = shared; i < toParents.size() - 1; i++) { + relPathBuf.append(toParents.get(i) + File.separator); + } + relPathBuf.append(new File(to).getName()); + String relPath = relPathBuf.toString(); + + // Turn around the slashes when path is relative + try { + String absPath = new File(relPath).getCanonicalPath(); + + if (!absPath.equals(relPath)) { + // Path is not absolute, turn slashes around + // Assumes: \ does not occur in file names + relPath = relPath.replace('\\', '/'); + } + } catch (IOException e) { + } + + return relPath; + } + + public boolean accept(File pathName) { + try { + String path = pathName.getCanonicalPath(); + if (path.endsWith(".tmx") || path.endsWith(".tsx") || path.endsWith(".tmx.gz")) { + return true; + } + } catch (IOException e) {} + return false; + } + + /** + * Returns the global tile id of the given tile. + * + * @return global tile id of the given tile + */ + private int getGid(Tile tile) { + TileSet tileset = tile.getTileSet(); + if (tileset != null) { + return tile.getId() + getFirstGidForTileset(tileset); + } + return tile.getId(); + } + + private void setFirstGidForTileset(TileSet tileset, int firstGid) { + firstGidPerTileset.put(tileset.getName(), firstGid); + } + + private int getFirstGidForTileset(TileSet tileset) { + return firstGidPerTileset.get(tileset.getName()); + } +} diff --git a/hacked-libtiled/tiled/io/resources/map.dtd b/hacked-libtiled/tiled/io/resources/map.dtd new file mode 100644 index 0000000..de97939 --- /dev/null +++ b/hacked-libtiled/tiled/io/resources/map.dtd @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hacked-libtiled/tiled/io/xml/XMLWriter.java b/hacked-libtiled/tiled/io/xml/XMLWriter.java new file mode 100644 index 0000000..f47905e --- /dev/null +++ b/hacked-libtiled/tiled/io/xml/XMLWriter.java @@ -0,0 +1,206 @@ +/* + * Copyright 2004-2006, Thorbjørn Lindeijer + * Copyright 2004-2006, Adam Turk + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.io.xml; + +import java.lang.String; +import java.io.Writer; +import java.io.IOException; +import java.util.Stack; + +/** + * A simple helper class to write an XML file, based on + * http://www.xmlsoft.org/html/libxml-xmlwriter.html + */ +public class XMLWriter +{ + private boolean bIndent = true; + private String indentString = " "; + private String newLine = "\n"; + private final Writer w; + + private final Stack openElements; + private boolean bStartTagOpen; + private boolean bDocumentOpen; + + + public XMLWriter(Writer writer) { + openElements = new Stack(); + w = writer; + } + + + public void setIndent(boolean bIndent) { + this.bIndent = bIndent; + newLine = bIndent ? "\n" : ""; + } + + public void setIndentString(String indentString) { + this.indentString = indentString; + } + + + public void startDocument() throws IOException { + startDocument("1.0"); + } + + public void startDocument(String version) throws IOException { + w.write("" + + newLine); + bDocumentOpen = true; + } + + public void writeDocType(String name, String pubId, String sysId) + throws IOException, XMLWriterException { + if (!bDocumentOpen) { + throw new XMLWriterException( + "Can't write DocType, no open document."); + } else if (!openElements.isEmpty()) { + throw new XMLWriterException( + "Can't write DocType, open elements exist."); + } + + w.write("" + newLine); + } + + public void startElement(String name) + throws IOException, XMLWriterException { + if (!bDocumentOpen) { + throw new XMLWriterException( + "Can't start new element, no open document."); + } + + if (bStartTagOpen) { + w.write(">" + newLine); + } + + writeIndent(); + w.write("<" + name); + + openElements.push(name); + bStartTagOpen = true; + } + + + public void endDocument() throws IOException { + // End all open elements. + while (!openElements.isEmpty()) { + endElement(); + } + + w.flush(); //writers do not always flush automatically... + } + + public void endElement() throws IOException { + String name = openElements.pop(); + + // If start tag still open, end with />, else with . + if (bStartTagOpen) { + w.write("/>" + newLine); + bStartTagOpen = false; + } else { + writeIndent(); + w.write("" + newLine); + } + + // Set document closed when last element is closed + if (openElements.isEmpty()) { + bDocumentOpen = false; + } + } + + + public void writeAttribute(String name, String content) + throws IOException, XMLWriterException { + if (bStartTagOpen) { + String escapedContent = (content != null) ? + content.replaceAll("\"", """) : ""; + w.write(" " + name + "=\"" + escapedContent + "\""); + } else { + throw new XMLWriterException( + "Can't write attribute without open start tag."); + } + } + + public void writeAttribute(String name, int content) + throws IOException, XMLWriterException { + writeAttribute(name, String.valueOf(content)); + } + + public void writeAttribute(String name, float content) + throws IOException, XMLWriterException { + writeAttribute(name, String.valueOf(content)); + } + + public void writeCDATA(String content) throws IOException { + if (bStartTagOpen) { + w.write(">" + newLine); + bStartTagOpen = false; + } + + writeIndent(); + w.write(content + newLine); + } + + public void writeComment(String content) throws IOException { + if (bStartTagOpen) { + w.write(">" + newLine); + bStartTagOpen = false; + } + + writeIndent(); + w.write("" + newLine); + } + + public void writeElement(String name, String content) + throws IOException, XMLWriterException { + startElement(name); + writeCDATA(content); + endElement(); + } + + + private void writeIndent() throws IOException { + if (bIndent) { + for (int i = 0; i < openElements.size(); i++) { + w.write(indentString); + } + } + } +} diff --git a/hacked-libtiled/tiled/io/xml/XMLWriterException.java b/hacked-libtiled/tiled/io/xml/XMLWriterException.java new file mode 100644 index 0000000..0112095 --- /dev/null +++ b/hacked-libtiled/tiled/io/xml/XMLWriterException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2004-2006, Thorbjørn Lindeijer + * Copyright 2004-2006, Adam Turk + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.io.xml; + +public class XMLWriterException extends RuntimeException +{ + public XMLWriterException(String error) { + super(error); + } +} diff --git a/hacked-libtiled/tiled/util/Base64.java b/hacked-libtiled/tiled/util/Base64.java new file mode 100644 index 0000000..5b08cb2 --- /dev/null +++ b/hacked-libtiled/tiled/util/Base64.java @@ -0,0 +1,575 @@ +package tiled.util; + +import java.util.Arrays; + +/** A very fast and memory efficient class to encode and decode to and from BASE64 in full accordance + * with RFC 2045.

+ * On Windows XP sp1 with 1.4.2_04 and later ;), this encoder and decoder is about 10 times faster + * on small arrays (10 - 1000 bytes) and 2-3 times as fast on larger arrays (10000 - 1000000 bytes) + * compared to sun.misc.Encoder()/Decoder().

+ * + * On byte arrays the encoder is about 20% faster than Jakarta Commons Base64 Codec for encode and + * about 50% faster for decoding large arrays. This implementation is about twice as fast on very small + * arrays (< 30 bytes). If source/destination is a String this + * version is about three times as fast due to the fact that the Commons Codec result has to be recoded + * to a String from byte[], which is very expensive.

+ * + * This encode/decode algorithm doesn't create any temporary arrays as many other codecs do, it only + * allocates the resulting array. This produces less garbage and it is possible to handle arrays twice + * as large as algorithms that create a temporary array. (E.g. Jakarta Commons Codec). It is unknown + * whether Sun's sun.misc.Encoder()/Decoder() produce temporary arrays but since performance + * is quite low it probably does.

+ * + * The encoder produces the same output as the Sun one except that the Sun's encoder appends + * a trailing line separator if the last character isn't a pad. Unclear why but it only adds to the + * length and is probably a side effect. Both are in conformance with RFC 2045 though.
+ * Commons codec seem to always att a trailing line separator.

+ * + * Note! + * The encode/decode method pairs (types) come in three versions with the exact same algorithm and + * thus a lot of code redundancy. This is to not create any temporary arrays for transcoding to/from different + * format types. The methods not used can simply be commented out.

+ * + * There is also a "fast" version of all decode methods that works the same way as the normal ones, but + * har a few demands on the decoded input. Normally though, these fast verions should be used if the source if + * the input is known and it hasn't bee tampered with.

+ * + * If you find the code useful or you find a bug, please send me a note at base64 @ miginfocom . com. + * + * Licence (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 2.2 + * @author Mikael Grev + * Date: 2004-aug-02 + * Time: 11:31:11 + */ + +public class Base64 +{ + private static final char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); + private static final int[] IA = new int[256]; + static { + Arrays.fill(IA, -1); + for (int i = 0, iS = CA.length; i < iS; i++) + IA[CA[i]] = i; + IA['='] = 0; + } + + // **************************************************************************************** + // * char[] version + // **************************************************************************************** + + /** Encodes a raw byte array into a BASE64 char[] representation i accordance with RFC 2045. + * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. + * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a + * little faster. + * @return A BASE64 encoded array. Never null. + */ + public final static char[] encodeToChar(byte[] sArr, boolean lineSep) + { + // Check special case + int sLen = sArr != null ? sArr.length : 0; + if (sLen == 0) + return new char[0]; + + int eLen = (sLen / 3) * 3; // Length of even 24-bits. + int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count + int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array + char[] dArr = new char[dLen]; + + // Encode even 24-bits + for (int s = 0, d = 0, cc = 0; s < eLen;) { + // Copy next three bytes into lower 24 bits of int, paying attension to sign. + int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); + + // Encode the int into four chars + dArr[d++] = CA[(i >>> 18) & 0x3f]; + dArr[d++] = CA[(i >>> 12) & 0x3f]; + dArr[d++] = CA[(i >>> 6) & 0x3f]; + dArr[d++] = CA[i & 0x3f]; + + // Add optional line separator + if (lineSep && ++cc == 19 && d < dLen - 2) { + dArr[d++] = '\r'; + dArr[d++] = '\n'; + cc = 0; + } + } + + // Pad and encode last bits if source isn't even 24 bits. + int left = sLen - eLen; // 0 - 2. + if (left > 0) { + // Prepare the int + int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0); + + // Set last four chars + dArr[dLen - 4] = CA[i >> 12]; + dArr[dLen - 3] = CA[(i >>> 6) & 0x3f]; + dArr[dLen - 2] = left == 2 ? CA[i & 0x3f] : '='; + dArr[dLen - 1] = '='; + } + return dArr; + } + + /** Decodes a BASE64 encoded char array. All illegal characters will be ignored and can handle both arrays with + * and without line separators. + * @param sArr The source array. null or length 0 will return an empty array. + * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters + * (including '=') isn't divideable by 4. (I.e. definitely corrupted). + */ + public final static byte[] decode(char[] sArr) + { + // Check special case + int sLen = sArr != null ? sArr.length : 0; + if (sLen == 0) + return new byte[0]; + + // Count illegal characters (including '\r', '\n') to know what size the returned array will be, + // so we don't have to reallocate & copy it later. + int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) + for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. + if (IA[sArr[i]] < 0) + sepCnt++; + + // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. + if ((sLen - sepCnt) % 4 != 0) + return null; + + int pad = 0; + for (int i = sLen; i > 1 && IA[sArr[--i]] <= 0;) + if (sArr[i] == '=') + pad++; + + int len = ((sLen - sepCnt) * 6 >> 3) - pad; + + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + for (int s = 0, d = 0; d < len;) { + // Assemble three bytes into an int from four "valid" characters. + int i = 0; + for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. + int c = IA[sArr[s++]]; + if (c >= 0) + i |= c << (18 - j * 6); + else + j--; + } + // Add the bytes + dArr[d++] = (byte) (i >> 16); + if (d < len) { + dArr[d++]= (byte) (i >> 8); + if (d < len) + dArr[d++] = (byte) i; + } + } + return dArr; + } + + /** Decodes a BASE64 encoded char array that is known to be resonably well formatted. The method is about twice as + * fast as {@link #decode(char[])}. The preconditions are:
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045 + * + The array must not contain illegal characters within the encoded string
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. + */ + public final static byte[] decodeFast(char[] sArr) + { + // Check special case + int sLen = sArr.length; + if (sLen == 0) + return new byte[0]; + + int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. + + // Trim illegal chars from start + while (sIx < eIx && IA[sArr[sIx]] < 0) + sIx++; + + // Trim illegal chars from end + while (eIx > 0 && IA[sArr[eIx]] < 0) + eIx--; + + // get the padding count (=) (0, 1 or 2) + int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end. + int cCnt = eIx - sIx + 1; // Content count including possible separators + int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; + + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + // Decode all but the last 0 - 2 bytes. + int d = 0; + for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) { + // Assemble three bytes into an int from four "valid" characters. + int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]]; + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + + // If line separator, jump over it. + if (sepCnt > 0 && ++cc == 19) { + sIx += 2; + cc = 0; + } + } + + if (d < len) { + // Decode last 1-3 bytes (incl '=') into 1-3 bytes + int i = 0; + for (int j = 0; sIx <= eIx - pad; j++) + i |= IA[sArr[sIx++]] << (18 - j * 6); + + for (int r = 16; d < len; r -= 8) + dArr[d++] = (byte) (i >> r); + } + + return dArr; + } + + // **************************************************************************************** + // * byte[] version + // **************************************************************************************** + + /** Encodes a raw byte array into a BASE64 byte[] representation i accordance with RFC 2045. + * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. + * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a + * little faster. + * @return A BASE64 encoded array. Never null. + */ + public final static byte[] encodeToByte(byte[] sArr, boolean lineSep) + { + // Check special case + int sLen = sArr != null ? sArr.length : 0; + if (sLen == 0) + return new byte[0]; + + int eLen = (sLen / 3) * 3; // Length of even 24-bits. + int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count + int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array + byte[] dArr = new byte[dLen]; + + // Encode even 24-bits + for (int s = 0, d = 0, cc = 0; s < eLen;) { + // Copy next three bytes into lower 24 bits of int, paying attension to sign. + int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); + + // Encode the int into four chars + dArr[d++] = (byte) CA[(i >>> 18) & 0x3f]; + dArr[d++] = (byte) CA[(i >>> 12) & 0x3f]; + dArr[d++] = (byte) CA[(i >>> 6) & 0x3f]; + dArr[d++] = (byte) CA[i & 0x3f]; + + // Add optional line separator + if (lineSep && ++cc == 19 && d < dLen - 2) { + dArr[d++] = '\r'; + dArr[d++] = '\n'; + cc = 0; + } + } + + // Pad and encode last bits if source isn't an even 24 bits. + int left = sLen - eLen; // 0 - 2. + if (left > 0) { + // Prepare the int + int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0); + + // Set last four chars + dArr[dLen - 4] = (byte) CA[i >> 12]; + dArr[dLen - 3] = (byte) CA[(i >>> 6) & 0x3f]; + dArr[dLen - 2] = left == 2 ? (byte) CA[i & 0x3f] : (byte) '='; + dArr[dLen - 1] = '='; + } + return dArr; + } + + /** Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with + * and without line separators. + * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters + * (including '=') isn't divideable by 4. (I.e. definitely corrupted). + */ + public final static byte[] decode(byte[] sArr) + { + // Check special case + int sLen = sArr.length; + + // Count illegal characters (including '\r', '\n') to know what size the returned array will be, + // so we don't have to reallocate & copy it later. + int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) + for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. + if (IA[sArr[i] & 0xff] < 0) + sepCnt++; + + // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. + if ((sLen - sepCnt) % 4 != 0) + return null; + + int pad = 0; + for (int i = sLen; i > 1 && IA[sArr[--i] & 0xff] <= 0;) + if (sArr[i] == '=') + pad++; + + int len = ((sLen - sepCnt) * 6 >> 3) - pad; + + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + for (int s = 0, d = 0; d < len;) { + // Assemble three bytes into an int from four "valid" characters. + int i = 0; + for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. + int c = IA[sArr[s++] & 0xff]; + if (c >= 0) + i |= c << (18 - j * 6); + else + j--; + } + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + if (d < len) { + dArr[d++]= (byte) (i >> 8); + if (d < len) + dArr[d++] = (byte) i; + } + } + + return dArr; + } + + + /** Decodes a BASE64 encoded byte array that is known to be resonably well formatted. The method is about twice as + * fast as {@link #decode(byte[])}. The preconditions are:
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045 + * + The array must not contain illegal characters within the encoded string
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. + */ + public final static byte[] decodeFast(byte[] sArr) + { + // Check special case + int sLen = sArr.length; + if (sLen == 0) + return new byte[0]; + + int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. + + // Trim illegal chars from start + while (sIx < eIx && IA[sArr[sIx] & 0xff] < 0) + sIx++; + + // Trim illegal chars from end + while (eIx > 0 && IA[sArr[eIx] & 0xff] < 0) + eIx--; + + // get the padding count (=) (0, 1 or 2) + int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end. + int cCnt = eIx - sIx + 1; // Content count including possible separators + int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; + + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + // Decode all but the last 0 - 2 bytes. + int d = 0; + for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) { + // Assemble three bytes into an int from four "valid" characters. + int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]]; + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + + // If line separator, jump over it. + if (sepCnt > 0 && ++cc == 19) { + sIx += 2; + cc = 0; + } + } + + if (d < len) { + // Decode last 1-3 bytes (incl '=') into 1-3 bytes + int i = 0; + for (int j = 0; sIx <= eIx - pad; j++) + i |= IA[sArr[sIx++]] << (18 - j * 6); + + for (int r = 16; d < len; r -= 8) + dArr[d++] = (byte) (i >> r); + } + + return dArr; + } + + // **************************************************************************************** + // * String version + // **************************************************************************************** + + /** Encodes a raw byte array into a BASE64 String representation i accordance with RFC 2045. + * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. + * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a + * little faster. + * @return A BASE64 encoded array. Never null. + */ + public final static String encodeToString(byte[] sArr, boolean lineSep) + { + // Reuse char[] since we can't create a String incrementally anyway and StringBuffer/Builder would be slower. + return new String(encodeToChar(sArr, lineSep)); + } + + /** Decodes a BASE64 encoded String. All illegal characters will be ignored and can handle both strings with + * and without line separators.
+ * Note! It can be up to about 2x the speed to call decode(str.toCharArray()) instead. That + * will create a temporary array though. This version will use str.charAt(i) to iterate the string. + * @param str The source string. null or length 0 will return an empty array. + * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters + * (including '=') isn't divideable by 4. (I.e. definitely corrupted). + */ + public final static byte[] decode(String str) + { + // Check special case + int sLen = str != null ? str.length() : 0; + if (sLen == 0) + return new byte[0]; + + // Count illegal characters (including '\r', '\n') to know what size the returned array will be, + // so we don't have to reallocate & copy it later. + int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) + for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. + if (IA[str.charAt(i)] < 0) + sepCnt++; + + // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. + if ((sLen - sepCnt) % 4 != 0) + return null; + + // Count '=' at end + int pad = 0; + for (int i = sLen; i > 1 && IA[str.charAt(--i)] <= 0;) + if (str.charAt(i) == '=') + pad++; + + int len = ((sLen - sepCnt) * 6 >> 3) - pad; + + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + for (int s = 0, d = 0; d < len;) { + // Assemble three bytes into an int from four "valid" characters. + int i = 0; + for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. + int c = IA[str.charAt(s++)]; + if (c >= 0) + i |= c << (18 - j * 6); + else + j--; + } + // Add the bytes + dArr[d++] = (byte) (i >> 16); + if (d < len) { + dArr[d++]= (byte) (i >> 8); + if (d < len) + dArr[d++] = (byte) i; + } + } + return dArr; + } + + /** Decodes a BASE64 encoded string that is known to be resonably well formatted. The method is about twice as + * fast as {@link #decode(String)}. The preconditions are:
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045 + * + The array must not contain illegal characters within the encoded string
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ * @param s The source string. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. + */ + public final static byte[] decodeFast(String s) + { + // Check special case + int sLen = s.length(); + if (sLen == 0) + return new byte[0]; + + int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. + + // Trim illegal chars from start + while (sIx < eIx && IA[s.charAt(sIx) & 0xff] < 0) + sIx++; + + // Trim illegal chars from end + while (eIx > 0 && IA[s.charAt(eIx) & 0xff] < 0) + eIx--; + + // get the padding count (=) (0, 1 or 2) + int pad = s.charAt(eIx) == '=' ? (s.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end. + int cCnt = eIx - sIx + 1; // Content count including possible separators + int sepCnt = sLen > 76 ? (s.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0; + + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + // Decode all but the last 0 - 2 bytes. + int d = 0; + for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) { + // Assemble three bytes into an int from four "valid" characters. + int i = IA[s.charAt(sIx++)] << 18 | IA[s.charAt(sIx++)] << 12 | IA[s.charAt(sIx++)] << 6 | IA[s.charAt(sIx++)]; + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + + // If line separator, jump over it. + if (sepCnt > 0 && ++cc == 19) { + sIx += 2; + cc = 0; + } + } + + if (d < len) { + // Decode last 1-3 bytes (incl '=') into 1-3 bytes + int i = 0; + for (int j = 0; sIx <= eIx - pad; j++) + i |= IA[s.charAt(sIx++)] << (18 - j * 6); + + for (int r = 16; d < len; r -= 8) + dArr[d++] = (byte) (i >> r); + } + + return dArr; + } +} diff --git a/hacked-libtiled/tiled/util/BasicTileCutter.java b/hacked-libtiled/tiled/util/BasicTileCutter.java new file mode 100644 index 0000000..3ba6793 --- /dev/null +++ b/hacked-libtiled/tiled/util/BasicTileCutter.java @@ -0,0 +1,117 @@ +/* + * Copyright 2004-2006, Thorbjørn Lindeijer + * Copyright 2004-2006, Adam Turk + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.util; + +import java.awt.Dimension; +import java.awt.Image; +import java.awt.image.BufferedImage; + +/** + * Cuts tiles from a tileset image according to a regular rectangular pattern. + * Supports a variable spacing between tiles and a margin around them. + */ +public class BasicTileCutter implements TileCutter +{ + private int nextX, nextY; + private BufferedImage image; + private final int tileWidth; + private final int tileHeight; + private final int tileSpacing; + private final int tileMargin; + + public BasicTileCutter(int tileWidth, int tileHeight, int tileSpacing, + int tileMargin) + { + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + this.tileSpacing = tileSpacing; + this.tileMargin = tileMargin; + + reset(); + } + + public String getName() { + return "Basic"; + } + + public void setImage(BufferedImage image) { + this.image = image; + } + + public Image getNextTile() { + if (nextY + tileHeight + tileMargin <= image.getHeight()) { + BufferedImage tile = + image.getSubimage(nextX, nextY, tileWidth, tileHeight); + nextX += tileWidth + tileSpacing; + + if (nextX + tileWidth + tileMargin > image.getWidth()) { + nextX = tileMargin; + nextY += tileHeight + tileSpacing; + } + + return tile; + } + + return null; + } + + public void reset() { + nextX = tileMargin; + nextY = tileMargin; + } + + public Dimension getTileDimensions() { + return new Dimension(tileWidth, tileHeight); + } + + /** + * Returns the spacing between tile images. + * @return the spacing between tile images. + */ + public int getTileSpacing() { + return tileSpacing; + } + + /** + * Returns the margin around the tile images. + * @return the margin around the tile images. + */ + public int getTileMargin() { + return tileMargin; + } + + /** + * Returns the number of tiles per row in the tileset image. + * @return the number of tiles per row in the tileset image. + */ + public int getTilesPerRow() { + return (image.getWidth() - 2 * tileMargin + tileSpacing) / + (tileWidth + tileSpacing); + } +} diff --git a/hacked-libtiled/tiled/util/ImageHelper.java b/hacked-libtiled/tiled/util/ImageHelper.java new file mode 100644 index 0000000..55a63e8 --- /dev/null +++ b/hacked-libtiled/tiled/util/ImageHelper.java @@ -0,0 +1,79 @@ +/* + * Copyright 2004-2008, Thorbjørn Lindeijer + * Copyright 2004-2008, Adam Turk + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.util; + +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import javax.imageio.ImageIO; + +/** + * This class provides functions to help out with saving/loading images. + */ +public class ImageHelper +{ + /** + * Converts an image to a PNG stored in a byte array. + * + * @param image + * @return a byte array with the PNG data + */ + static public byte[] imageToPNG(Image image) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + try { + BufferedImage buffer = new BufferedImage( + image.getWidth(null), image.getHeight(null), + BufferedImage.TYPE_INT_ARGB); + + buffer.createGraphics().drawImage(image, 0, 0, null); + ImageIO.write(buffer, "PNG", baos); + baos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + return baos.toByteArray(); + } + + /** + * Converts a byte array into an image. The byte array must include the + * image header, so that a decision about format can be made. + * + * @param imageData The byte array of the data to convert. + * @return Image The image instance created from the byte array + * @throws IOException + * @see java.awt.Toolkit#createImage(byte[] imagedata) + */ + static public BufferedImage bytesToImage(byte[] imageData) throws IOException { + return ImageIO.read(new ByteArrayInputStream(imageData)); + } +} diff --git a/hacked-libtiled/tiled/util/TileCutter.java b/hacked-libtiled/tiled/util/TileCutter.java new file mode 100644 index 0000000..3fe60e7 --- /dev/null +++ b/hacked-libtiled/tiled/util/TileCutter.java @@ -0,0 +1,70 @@ +/* + * Copyright 2004-2006, Thorbjørn Lindeijer + * Copyright 2004-2006, Adam Turk + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.util; + +import java.awt.Dimension; +import java.awt.Image; +import java.awt.image.BufferedImage; + +/** + * A generic interface to a class that implements tile cutting behavior. + */ +public interface TileCutter +{ + /** + * Sets the image that this cutter should cut in tile images. + * @param image the image that this cutter should cut + */ + public void setImage(BufferedImage image); + + /** + * Retrieves the next tile image. + * @return the next tile image, or null when no more tile + * images are available + */ + public Image getNextTile(); + + /** + * Resets the tile cutter so that the next call to getNextTile + * will return the first tile. + */ + void reset(); + + /** + * Returns the default tile dimensions of tiles cut by this cutter. + * @return the default tile dimensions of tiles cut by this cutter. + */ + public Dimension getTileDimensions(); + + /** + * Returns the name of this tile cutter. + * @return the name of this tile cutter + */ + public String getName(); +} diff --git a/hacked-libtiled/tiled/util/TransparentImageFilter.java b/hacked-libtiled/tiled/util/TransparentImageFilter.java new file mode 100644 index 0000000..bff24a7 --- /dev/null +++ b/hacked-libtiled/tiled/util/TransparentImageFilter.java @@ -0,0 +1,62 @@ +/* + * Copyright 2004-2006, Thorbjørn Lindeijer + * Copyright 2004-2006, Adam Turk + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.util; + +import java.awt.image.RGBImageFilter; + +/** + * This filter is used for filtering out a given "transparent" color from an + * image. Sometimes known as magic pink. + */ +public class TransparentImageFilter extends RGBImageFilter +{ + int trans; + + /** + * @param col the color to make transparent + */ + public TransparentImageFilter(int col) { + trans = col; + + // The filter doesn't depend on pixel location + canFilterIndexColorModel = true; + } + + /** + * Filters the given pixel. It returns a transparent pixel for pixels that + * match the transparency color, or the existing pixel for anything else. + */ + public int filterRGB(int x, int y, int rgb) { + if (rgb == trans) { + return 0; + } else { + return rgb; + } + } +} diff --git a/hacked-libtiled/tiled/view/MapRenderer.java b/hacked-libtiled/tiled/view/MapRenderer.java new file mode 100644 index 0000000..16139e8 --- /dev/null +++ b/hacked-libtiled/tiled/view/MapRenderer.java @@ -0,0 +1,54 @@ +/* + * Copyright 2010, Thorbjørn Lindeijer + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.view; + +import tiled.core.TileLayer; + +import java.awt.*; + +/** + * An interface defining methods to render a map. + */ +public interface MapRenderer +{ + /** + * Calculates the dimensions of the map this renderer applies to. + * + * @return the dimensions of the given map in pixels + */ + public Dimension getMapSize(); + + /** + * Paints the given tile layer, taking into account the clip rect of the + * given graphics context. + * + * @param g the graphics context to paint to + * @param layer the layer to paint + */ + public void paintTileLayer(Graphics2D g, TileLayer layer); +} diff --git a/hacked-libtiled/tiled/view/OrthogonalRenderer.java b/hacked-libtiled/tiled/view/OrthogonalRenderer.java new file mode 100644 index 0000000..2f94444 --- /dev/null +++ b/hacked-libtiled/tiled/view/OrthogonalRenderer.java @@ -0,0 +1,93 @@ +/* + * Copyright 2010, Thorbjørn Lindeijer + * + * This file is part of libtiled-java. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package tiled.view; + +import tiled.core.Map; +import tiled.core.Tile; +import tiled.core.TileLayer; + +import java.awt.*; + +/** + * The orthogonal map renderer. This is the most basic map renderer, dealing + * with maps that use rectangular tiles. + */ +public class OrthogonalRenderer implements MapRenderer +{ + private final Map map; + + public OrthogonalRenderer(Map map) { + this.map = map; + } + + public Dimension getMapSize() { + return new Dimension( + map.getWidth() * map.getTileWidth(), + map.getHeight() * map.getTileHeight()); + } + + public void paintTileLayer(Graphics2D g, TileLayer layer) { + final Rectangle clip = g.getClipBounds(); + final int tileWidth = map.getTileWidth(); + final int tileHeight = map.getTileHeight(); + final Rectangle bounds = layer.getBounds(); + + g.translate(bounds.x * tileWidth, bounds.y * tileHeight); + clip.translate(-bounds.x * tileWidth, -bounds.y * tileHeight); + + clip.height += map.getTileHeightMax(); + + final int startX = Math.max(0, clip.x / tileWidth); + final int startY = Math.max(0, clip.y / tileHeight); + final int endX = Math.min(layer.getWidth(), + (int) Math.ceil(clip.getMaxX() / tileWidth)); + final int endY = Math.min(layer.getHeight(), + (int) Math.ceil(clip.getMaxY() / tileHeight)); + + for (int x = startX; x < endX; ++x) { + for (int y = startY; y < endY; ++y) { + final Tile tile = layer.getTileAt(x, y); + if (tile == null) + continue; + final Image image = tile.getImage(); + if (image == null) + continue; + + g.drawImage( + image, + x * tileWidth, + (y + 1) * tileHeight - image.getHeight(null), + null); + } + } + + + + g.translate(-bounds.x * tileWidth, -bounds.y * tileHeight); + } +} diff --git a/lib/AndorsTrainer_v0.1.1.jar b/lib/AndorsTrainer_v0.1.1.jar new file mode 100644 index 0000000..130917d Binary files /dev/null and b/lib/AndorsTrainer_v0.1.1.jar differ diff --git a/lib/jide-oss.jar b/lib/jide-oss.jar new file mode 100644 index 0000000..4c991f6 Binary files /dev/null and b/lib/jide-oss.jar differ diff --git a/lib/json_simple-1.1.jar b/lib/json_simple-1.1.jar new file mode 100644 index 0000000..f395f41 Binary files /dev/null and b/lib/json_simple-1.1.jar differ diff --git a/lib/junit-4.10.jar b/lib/junit-4.10.jar new file mode 100644 index 0000000..bf5c0b9 Binary files /dev/null and b/lib/junit-4.10.jar differ diff --git a/lib/prefuse.jar b/lib/prefuse.jar new file mode 100644 index 0000000..256ffee Binary files /dev/null and b/lib/prefuse.jar differ diff --git a/lib/rsyntaxtextarea.jar b/lib/rsyntaxtextarea.jar new file mode 100644 index 0000000..16a31ab Binary files /dev/null and b/lib/rsyntaxtextarea.jar differ diff --git a/lib/ui.jar b/lib/ui.jar new file mode 100644 index 0000000..920dc48 Binary files /dev/null and b/lib/ui.jar differ diff --git a/nsisBorderBannerBase.xcf b/nsisBorderBannerBase.xcf new file mode 100644 index 0000000..d211eb2 Binary files /dev/null and b/nsisBorderBannerBase.xcf differ diff --git a/nsisHeaderBase2.xcf b/nsisHeaderBase2.xcf new file mode 100644 index 0000000..bd4276f Binary files /dev/null and b/nsisHeaderBase2.xcf differ diff --git a/packaging/Linux/.gitignore b/packaging/Linux/.gitignore new file mode 100644 index 0000000..7f160db --- /dev/null +++ b/packaging/Linux/.gitignore @@ -0,0 +1,2 @@ +/ATCS_v*.zip +/ATCS_v*.zip.rename diff --git a/packaging/Linux/ATCS/ATCS.cmd b/packaging/Linux/ATCS/ATCS.cmd new file mode 100644 index 0000000..5cae4ba --- /dev/null +++ b/packaging/Linux/ATCS/ATCS.cmd @@ -0,0 +1 @@ +start "" "javaw.exe" -Xmx512M -cp "lib\jide-oss.jar;lib\ui.jar;lib\junit-4.10.jar;lib\json_simple-1.1.jar;lib\rsyntaxtextarea.jar;lib\prefuse.jar;lib\ATCS_v0.3.3.jar;lib\AndorsTrainer_v0.1.1.jar" com.gpl.rpg.atcontentstudio.ATContentStudio \ No newline at end of file diff --git a/packaging/Linux/ATCS/ATCS.ico b/packaging/Linux/ATCS/ATCS.ico new file mode 100644 index 0000000..4f829e4 Binary files /dev/null and b/packaging/Linux/ATCS/ATCS.ico differ diff --git a/packaging/Linux/ATCS/ATCS.sh b/packaging/Linux/ATCS/ATCS.sh new file mode 100644 index 0000000..a200387 --- /dev/null +++ b/packaging/Linux/ATCS/ATCS.sh @@ -0,0 +1,2 @@ +#!/bin/bash +java -Xmx512M -cp lib/AndorsTrainer_v0.1.1.jar:lib/ATCS_v0.3.3.jar:lib/prefuse.jar:lib/json_simple-1.1.jar:lib/jide-oss.jar:lib/ui.jar:lib/junit-4.10.jar:lib/rsyntaxtextarea.jar com.gpl.rpg.atcontentstudio.ATContentStudio \ No newline at end of file diff --git a/packaging/Linux/ATCS/lib/.gitignore b/packaging/Linux/ATCS/lib/.gitignore new file mode 100644 index 0000000..9503d0f --- /dev/null +++ b/packaging/Linux/ATCS/lib/.gitignore @@ -0,0 +1,8 @@ +/AndorsTrainer_v0.1.1.jar +/ATCS_v0.3.3.jar +/jide-oss.jar +/json_simple-1.1.jar +/junit-4.10.jar +/prefuse.jar +/rsyntaxtextarea.jar +/ui.jar diff --git a/packaging/Windows/.gitignore b/packaging/Windows/.gitignore new file mode 100644 index 0000000..f5bd428 --- /dev/null +++ b/packaging/Windows/.gitignore @@ -0,0 +1,10 @@ +/prefuse.jar +/rsyntaxtextarea.jar +/ui.jar +/jide-oss.jar +/json_simple-1.1.jar +/junit-4.10.jar +/ATCS_v*_Setup.exe +/ATCS_v*_Setup.exe.rename +/ATCS_v*.jar +/AndorsTrainer_v0.1.1.jar diff --git a/packaging/Windows/ATCS.cmd b/packaging/Windows/ATCS.cmd new file mode 100644 index 0000000..1fec81e --- /dev/null +++ b/packaging/Windows/ATCS.cmd @@ -0,0 +1 @@ +start "" "D:\Programs\jdk1.6.0_24\\bin\javaw.exe" -Xmx512M -cp "lib\jide-oss.jar;lib\ui.jar;lib\junit-4.10.jar;lib\json_simple-1.1.jar;lib\rsyntaxtextarea.jar;lib\ATCS_v0.0.4.jar" com.gpl.rpg.atcontentstudio.ATContentStudio \ No newline at end of file diff --git a/packaging/Windows/ATCS.ico b/packaging/Windows/ATCS.ico new file mode 100644 index 0000000..4f829e4 Binary files /dev/null and b/packaging/Windows/ATCS.ico differ diff --git a/packaging/Windows/ATCS_Installer.nsi b/packaging/Windows/ATCS_Installer.nsi new file mode 100644 index 0000000..e4ed0d5 --- /dev/null +++ b/packaging/Windows/ATCS_Installer.nsi @@ -0,0 +1,148 @@ +!include MUI2.nsh + +!define VERSION "0.3.3" +!define JAVA_BIN "java" + +Name "Andor's Trail Content Studio v${VERSION}" +OutFile "ATCS_v${VERSION}_Setup.exe" +InstallDir "$PROGRAMFILES\ATCS\" + +;SetCompressor /SOLID /FINAL lzma + +Var StartMenuFolder + +!define MUI_WELCOMEPAGE_TITLE "Welcome to Andor's Trail Content Studio installer" +!define MUI_WELCOMEPAGE_TEXT "This will install Andor's Trail Content Studio v${VERSION} installer" +!define MUI_FINISHPAGE_TEXT "Andor's Trail Content Studio v${VERSION} install completed !" +!define MUI_STARTMENUPAGE_DEFAULTFOLDER "Andor's Trail Content Studio" +!define MUI_PAGE_HEADER_TEXT "Installing Andor's Trail Content Studio v${VERSION}" + + +;Start Menu Folder Page Configuration +!define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU" +!define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\ATCS" +!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "ATCS" + +!define MUI_HEADERIMAGE +;!define MUI_HEADER_TRANSPARENT_TEXT +!define MUI_HEADERIMAGE_BITMAP nsisHeader.bmp +!define MUI_HEADERIMAGE_BITMAP_NOSTRETCH +;!define MUI_HEADERIMAGE_RIGHT +;!define MUI_HEADERIMAGE_BITMAP_STRETCH "AspectFitHeight" +!define MUI_HEADERIMAGE_UNBITMAP nsisHeader.bmp +;!define MUI_HEADERIMAGE_UNBITMAP_STRETCH "AspectFitHeight" +!define MUI_WELCOMEFINISHPAGE_BITMAP nsisBorderBanner.bmp +!define MUI_UNWELCOMEFINISHPAGE_BITMAP nsisBorderBanner.bmp +;!define MUI_BGCOLOR "E3E3E3" +!define MUI_ABORTWARNING + +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_STARTMENU "ATCS" $StartMenuFolder +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES + + +!insertmacro MUI_LANGUAGE "English" + +Section install + + + SetOutPath $INSTDIR + file "ATCS.ico" + + Call GetJRE + Pop $R0 + FileOpen $9 "ATCS.cmd" w + FileWrite $9 'start "" "$R0" -Xmx512M -cp "lib\AndorsTrainer_v0.1.1.jar;lib\jide-oss.jar;lib\ui.jar;lib\junit-4.10.jar;lib\json_simple-1.1.jar;lib\rsyntaxtextarea.jar;lib\prefuse.jar;lib\ATCS_v${VERSION}.jar" com.gpl.rpg.atcontentstudio.ATContentStudio' + FileClose $9 + + SetOutPath "$INSTDIR\lib\" + file "jide-oss.jar" + file "ui.jar" + file "AndorsTrainer_v0.1.1.jar" + file "junit-4.10.jar" + file "json_simple-1.1.jar" + file "ATCS_v${VERSION}.jar" + file "rsyntaxtextarea.jar" + file "prefuse.jar" + + SetOutPath $INSTDIR + + WriteUninstaller "$INSTDIR\Uninstall.exe" + + + !insertmacro MUI_STARTMENU_WRITE_BEGIN "ATCS" + + ;Create shortcuts + CreateDirectory "$SMPROGRAMS\$StartMenuFolder" + CreateShortcut "$SMPROGRAMS\$StartMenuFolder\Andor's Trail Content Studio.lnk" "$INSTDIR\ATCS.cmd" "" "$INSTDIR\ATCS.ico" + CreateShortcut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\Uninstall.exe" + + !insertmacro MUI_STARTMENU_WRITE_END + +SectionEnd + +Section uninstall + + Delete "$INSTDIR\lib\jide-oss.jar" + Delete "$INSTDIR\lib\ui.jar" + Delete "$INSTDIR\lib\junit-4.10.jar" + Delete "$INSTDIR\lib\json_simple-1.1.jar" + Delete "$INSTDIR\lib\AndorsTrainer_v0.1.1.jar" + Delete "$INSTDIR\lib\ATCS_v${VERSION}.jar" + Delete "$INSTDIR\lib\rsyntaxtextarea.jar" + Delete "$INSTDIR\lib\prefuse.jar" + RMDir "$INSTDIR\lib\" + Delete "$INSTDIR\ATCS.ico" + Delete "$INSTDIR\ATCS.cmd" + + Delete "$INSTDIR\Uninstall.exe" + + RMDir "$INSTDIR" + + !insertmacro MUI_STARTMENU_GETFOLDER "ATCS" $StartMenuFolder + + Delete "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" + Delete "$SMPROGRAMS\$StartMenuFolder\Andor's Trail Content Studio.lnk" + RMDir "$SMPROGRAMS\$StartMenuFolder" + +SectionEnd + + +Function GetJRE +; +; Find JRE (javaw.exe) +; DISABLED 1 - in .\jre directory (JRE Installed with application) +; 2 - in JAVA_HOME environment variable +; 3 - in the registry +; 4 - assume javaw.exe in current dir or PATH + + Push $R0 + Push $R1 + + ;ClearErrors + ;StrCpy $R0 "$EXEDIR\jre\bin\javaw.exe" + ;IfFileExists $R0 JreFound + ;StrCpy $R0 "" + + ClearErrors + ReadEnvStr $R0 "JAVA_HOME" + StrCpy $R0 "$R0\bin\${JAVA_BIN}.exe" + IfErrors 0 JreFound + + ClearErrors + ReadRegStr $R1 HKLM "SOFTWARE\JavaSoft\Java Runtime Environment" "CurrentVersion" + ReadRegStr $R0 HKLM "SOFTWARE\JavaSoft\Java Runtime Environment\$R1" "JavaHome" + StrCpy $R0 "$R0\bin\${JAVA_BIN}.exe" + + IfErrors 0 JreFound + StrCpy $R0 "${JAVA_BIN}.exe" + + JreFound: + Pop $R1 + Exch $R0 +FunctionEnd \ No newline at end of file diff --git a/packaging/Windows/nsisBorderBanner.bmp b/packaging/Windows/nsisBorderBanner.bmp new file mode 100644 index 0000000..4981f69 Binary files /dev/null and b/packaging/Windows/nsisBorderBanner.bmp differ diff --git a/packaging/Windows/nsisHeader.bmp b/packaging/Windows/nsisHeader.bmp new file mode 100644 index 0000000..199d4c4 Binary files /dev/null and b/packaging/Windows/nsisHeader.bmp differ diff --git a/res/LICENSE.GPLv3.txt b/res/LICENSE.GPLv3.txt new file mode 100644 index 0000000..d96f36b --- /dev/null +++ b/res/LICENSE.GPLv3.txt @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/res/LICENSE.JIDE.txt b/res/LICENSE.JIDE.txt new file mode 100644 index 0000000..d3a21fa --- /dev/null +++ b/res/LICENSE.JIDE.txt @@ -0,0 +1,31 @@ +(Please enable word-wrap when viewing this file) + +JIDE Common Layer is a dual-licensed. The two licenses are GPL with classpath exception and free commercial license. + +The first license is GPL with classpath exception. This is the same license under which the Java platform is released. You can read the license agreement from the link below. + +GPL v2 with Classpath Exception: http://openjdk.java.net/legal/gplv2+ce.html + +The second license is the original commercial license under which all other JIDE products are released, except it is free of charge in this case. You can read the license agreement from the link below. + +Commercial License: http://www.jidesoft.com/purchase/SLA.htm + +FAQ: + +1. Why do you choose dual-licensing? + +We want to find a license agreement that allows as many people to use this project as possible but still protects our copyright and commercial interests. None of the license agreement can satisfy both conditions by itself. GPL is used by a large percent of open source projects but proprietary projects are not allowed to use it. BSD, Apache or public domain license allows almost anybody to use the project but does not prevent other 3rd party taking our source code and commercially marketing their enhanced version to compete with our own commercial offer. That's why we decided to choose dual-licensing. For most open source projects, as long as GPL-compatible, you can choose the GPL license. For all commercial companies, you can choose the free commercial license as long as you follow the license agreement. + +2. If I modify your source code to fix a bug or add an enhancement, should I contribute the change back to you? + +As a matter of fact, you must contribute the changes back to us either you choose GPL or free commercial license to comply with the license agreements. It is for your own benefit too because you don't want to maintain a different version of source code yourself which will cause trouble when you want to upgrade to a new version of JIDE. This is actually one reason we don't want to go with BSD or Apache license. Since the project is open sourced on https://jide-oss.dev.java.net/, you can easily use Subversion to check in the changes you made. You may need to ask us for the permission to check in. Our developers will review the changes ahd commit them. + +3. I have some cool Swing components I developed myself. I would like to open source it. Should I start a new project or should I contribute it to your project? + +It is totally up to you. If you decide to contribute it to us and we agree to accept those components, we will maintain them for you in the future, even you might grow out of it. But we do require you to give the copyright of the components to JIDE so that we can release them under the same license agreement as all other components in JIDE Common Layer. + +4. I am a paid JIDE customer. What does this open source project mean to me? + +Pretty much business as usual. The JIDE Common Layer (the jide-common.jar) is free for all paid JIDE customers since its initial release. If you are making commercial products, the same commercial license agreement still applies. The only benefit I can think of is now you can see the source code of all the classes in jide-common.jar even you didn't purchase source code license from us. + +If you have any questions, please feel free to email sales@jidesoft.com. diff --git a/res/LICENSE.JSON.simple.txt b/res/LICENSE.JSON.simple.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/res/LICENSE.JSON.simple.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/res/LICENSE.libtiled.txt b/res/LICENSE.libtiled.txt new file mode 100644 index 0000000..0762ae8 --- /dev/null +++ b/res/LICENSE.libtiled.txt @@ -0,0 +1,20 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/res/RSyntaxTextArea.License.txt b/res/RSyntaxTextArea.License.txt new file mode 100644 index 0000000..260c1c8 --- /dev/null +++ b/res/RSyntaxTextArea.License.txt @@ -0,0 +1,24 @@ +Copyright (c) 2012, Robert Futrell +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/res/license-prefuse.txt b/res/license-prefuse.txt new file mode 100644 index 0000000..647671d --- /dev/null +++ b/res/license-prefuse.txt @@ -0,0 +1,30 @@ + + Copyright (c) 2004-2007 Regents of the University of California. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. diff --git a/res/spritesheets.properties b/res/spritesheets.properties new file mode 100644 index 0000000..8d2e7a2 --- /dev/null +++ b/res/spritesheets.properties @@ -0,0 +1,85 @@ +atcs.spritesheet.actorconditions_1.category=actorcondition +atcs.spritesheet.actorconditions_2.category=actorcondition +atcs.spritesheet.items_armours.category=item +atcs.spritesheet.items_armours_2.category=item +atcs.spritesheet.items_armours_3.category=item +atcs.spritesheet.items_weapons.category=item +atcs.spritesheet.items_weapons_2.category=item +atcs.spritesheet.items_weapons_3.category=item +atcs.spritesheet.items_jewelry.category=item +atcs.spritesheet.items_rings_1.category=item +atcs.spritesheet.items_necklaces_1.category=item +atcs.spritesheet.items_consumables.category=item +atcs.spritesheet.items_books.category=item +atcs.spritesheet.items_misc.category=item +atcs.spritesheet.items_misc_2.category=item +atcs.spritesheet.items_misc_3.category=item +atcs.spritesheet.items_misc_4.category=item +atcs.spritesheet.items_misc_5.category=item +atcs.spritesheet.items_misc_6.category=item +atcs.spritesheet.items_reterski_1.category=item +atcs.spritesheet.items_tometik1.category=item +atcs.spritesheet.items_tometik2.category=item +atcs.spritesheet.items_tometik3.category=item +atcs.spritesheet.monsters_armor1.category=monster +atcs.spritesheet.monsters_dogs.category=monster +atcs.spritesheet.monsters_eye1.category=monster +atcs.spritesheet.monsters_eye2.category=monster +atcs.spritesheet.monsters_eye3.category=monster +atcs.spritesheet.monsters_eye4.category=monster +atcs.spritesheet.monsters_ghost1.category=monster +atcs.spritesheet.monsters_hydra1.category=monster +atcs.spritesheet.monsters_hydra1.sizex=64 +atcs.spritesheet.monsters_hydra1.sizey=64 +atcs.spritesheet.monsters_insects.category=monster +atcs.spritesheet.monsters_liches.category=monster +atcs.spritesheet.monsters_mage.category=monster +atcs.spritesheet.monsters_mage2.category=monster +atcs.spritesheet.monsters_man1.category=monster +atcs.spritesheet.monsters_men.category=monster +atcs.spritesheet.monsters_men2.category=monster +atcs.spritesheet.monsters_misc.category=monster +atcs.spritesheet.monsters_rats.category=monster +atcs.spritesheet.monsters_rogue1.category=monster +atcs.spritesheet.monsters_skeleton1.category=monster +atcs.spritesheet.monsters_skeleton2.category=monster +atcs.spritesheet.monsters_snakes.category=monster +atcs.spritesheet.monsters_warrior1.category=monster +atcs.spritesheet.monsters_wraiths.category=monster +atcs.spritesheet.monsters_zombie1.category=monster +atcs.spritesheet.monsters_zombie2.category=monster +atcs.spritesheet.monsters_karvis1.category=monster +atcs.spritesheet.monsters_karvis2.category=monster +atcs.spritesheet.monsters_rltiles1.category=monster +atcs.spritesheet.monsters_rltiles2.category=monster +atcs.spritesheet.monsters_rltiles3.category=monster +atcs.spritesheet.monsters_rltiles4.category=monster +atcs.spritesheet.monsters_redshrike1.category=monster +atcs.spritesheet.monsters_ld1.category=monster +atcs.spritesheet.monsters_ld2.category=monster +atcs.spritesheet.monsters_tometik1.category=monster +atcs.spritesheet.monsters_tometik2.category=monster +atcs.spritesheet.monsters_tometik3.category=monster +atcs.spritesheet.monsters_tometik4.category=monster +atcs.spritesheet.monsters_tometik5.category=monster +atcs.spritesheet.monsters_tometik6.category=monster +atcs.spritesheet.monsters_tometik7.category=monster +atcs.spritesheet.monsters_tometik8.category=monster +atcs.spritesheet.monsters_tometik9.category=monster +atcs.spritesheet.monsters_tometik10.category=monster +atcs.spritesheet.monsters_demon1.category=monster +atcs.spritesheet.monsters_demon1.sizex=64 +atcs.spritesheet.monsters_demon1.sizey=64 +atcs.spritesheet.monsters_demon2.category=monster +atcs.spritesheet.monsters_demon2.sizex=64 +atcs.spritesheet.monsters_demon2.sizey=64 +atcs.spritesheet.monsters_cyclops.category=monster +atcs.spritesheet.monsters_cyclops.sizex=64 +atcs.spritesheet.monsters_cyclops.sizey=96 +atcs.spritesheet.effect_blood3.animate=true +atcs.spritesheet.effect_blood4.animate=true +atcs.spritesheet.effect_bluetentacle.animate=true +atcs.spritesheet.effect_heal2.animate=true +atcs.spritesheet.effect_poison1.animate=true +atcs.spritesheet.effect_tometik1.animate=true +atcs.spritesheet.effect_tometik2.animate=true \ No newline at end of file diff --git a/src/com/gpl/rpg/atcontentstudio/ATContentStudio.java b/src/com/gpl/rpg/atcontentstudio/ATContentStudio.java new file mode 100644 index 0000000..03c6f04 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ATContentStudio.java @@ -0,0 +1,85 @@ +package com.gpl.rpg.atcontentstudio; + +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; + +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; + +import com.gpl.rpg.atcontentstudio.model.Workspace; +import com.gpl.rpg.atcontentstudio.ui.StudioFrame; +import com.gpl.rpg.atcontentstudio.ui.WorkerDialog; +import com.gpl.rpg.atcontentstudio.ui.WorkspaceSelector; + + +public class ATContentStudio { + + public static final String APP_NAME = "Andor's Trail Content Studio"; + public static final String APP_VERSION = "v0.3.3"; + + public static boolean STARTED = false; + public static StudioFrame frame = null; + + /** + * @param args + */ + public static void main(String[] args) { + + ConfigCache.init(); + + try { + String laf = ConfigCache.getFavoriteLaFClassName(); + if (laf == null) laf = UIManager.getSystemLookAndFeelClassName(); + UIManager.setLookAndFeel(laf); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (UnsupportedLookAndFeelException e) { + e.printStackTrace(); + } + + + final WorkspaceSelector wsSelect = new WorkspaceSelector(); + wsSelect.pack(); + Dimension sdim = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension wdim = wsSelect.getSize(); + wsSelect.setLocation((sdim.width - wdim.width)/2, (sdim.height - wdim.height)/2); + wsSelect.setVisible(true); + + wsSelect.addWindowListener(new WindowAdapter() { + @Override + public synchronized void windowClosed(WindowEvent e) { + if (wsSelect.selected != null && !STARTED) { + ATContentStudio.STARTED = true; + final File workspaceRoot = new File(wsSelect.selected); + WorkerDialog.showTaskMessage("Loading your workspace...", null, new Runnable(){ + public void run() { + Workspace.setActive(workspaceRoot); + frame = new StudioFrame(APP_NAME+" "+APP_VERSION); + frame.setVisible(true); + frame.setDefaultCloseOperation(StudioFrame.EXIT_ON_CLOSE); + }; + }); + for (File f : ConfigCache.getKnownWorkspaces()) { + if (workspaceRoot.equals(f)) { + if (!workspaceRoot.equals(ConfigCache.getLatestWorkspace())) { + ConfigCache.setLatestWorkspace(f); + } + return; + } + } + ConfigCache.addWorkspace(workspaceRoot); + ConfigCache.setLatestWorkspace(workspaceRoot); + + } + } + }); + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ConfigCache.java b/src/com/gpl/rpg/atcontentstudio/ConfigCache.java new file mode 100644 index 0000000..42549d6 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ConfigCache.java @@ -0,0 +1,103 @@ +package com.gpl.rpg.atcontentstudio; + +import java.io.File; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import com.gpl.rpg.atcontentstudio.io.SettingsSave; + +public class ConfigCache implements Serializable { + + private static final long serialVersionUID = 4584324644282843961L; + + private static final File CONFIG_CACHE_STORAGE; + + private static ConfigCache instance = null; + + + static { + if (System.getenv("APPDATA") != null) { + CONFIG_CACHE_STORAGE = new File(System.getenv("APPDATA")+File.separator+ATContentStudio.APP_NAME+File.separator+"configCache" ); + } else { + CONFIG_CACHE_STORAGE = new File(System.getenv("HOME")+File.separator+"."+ATContentStudio.APP_NAME+File.separator+"configCache" ); + } + CONFIG_CACHE_STORAGE.getParentFile().mkdirs(); + if (CONFIG_CACHE_STORAGE.exists()) { + ConfigCache.instance = (ConfigCache) SettingsSave.loadInstance(CONFIG_CACHE_STORAGE, "Configuration cache"); + if (ConfigCache.instance == null) { + ConfigCache.instance = new ConfigCache(); + } + } else { + ConfigCache.instance = new ConfigCache(); + } + } + + private void save() { + SettingsSave.saveInstance(instance, ConfigCache.CONFIG_CACHE_STORAGE, "Configuration cache"); + } + + + private List knownWorkspaces = new ArrayList(); + private File latestWorkspace = null; + private String favoriteLaFClassName = null; + private boolean[] notifConfig = new boolean[]{true, true, true, true}; + + + public static List getKnownWorkspaces() { + return instance.knownWorkspaces; + } + + public static void addWorkspace(File w) { + instance.knownWorkspaces.add(w); + instance.save(); + } + + public static void removeWorkspace(File w) { + instance.knownWorkspaces.remove(w); + instance.save(); + } + + public static File getLatestWorkspace() { + return instance.latestWorkspace; + } + + public static void setLatestWorkspace(File latestWorkspace) { + instance.latestWorkspace = latestWorkspace; + instance.save(); + } + + public static String getFavoriteLaFClassName() { + return instance.favoriteLaFClassName; + } + + public static void setFavoriteLaFClassName(String favoriteLaFClassName) { + instance.favoriteLaFClassName = favoriteLaFClassName; + instance.save(); + } + + public static void putNotifViewConfig(boolean[] view) { + for (int i=instance.notifConfig.length; i<0; --i) { + instance.notifConfig[i] = view[i]; + } + instance.save(); + } + + public static boolean[] getNotifViewConfig() { + if (instance == null || instance.notifConfig == null) { + //Not yet initialized. All flags on to help corner out init issues. + return new boolean[]{true, true, true, true}; + } + return instance.notifConfig; + } + + public static void init() {} + + public static void clear() { + instance.knownWorkspaces.clear(); + setFavoriteLaFClassName(null); + instance.notifConfig = new boolean[]{true, true, true, true}; + instance.save(); + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/Notification.java b/src/com/gpl/rpg/atcontentstudio/Notification.java new file mode 100644 index 0000000..5054af7 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/Notification.java @@ -0,0 +1,91 @@ +package com.gpl.rpg.atcontentstudio; + +import java.util.ArrayList; +import java.util.List; + +public class Notification { + + public static List notifs = new ArrayList(); + private static List listeners = new ArrayList(); + public static boolean showS = true, showI = true, showW = true, showE = true; + + static { + boolean[] config = ConfigCache.getNotifViewConfig(); + showS = config[0]; + showI = config[1]; + showW = config[2]; + showE = config[3]; + } + + public static enum Type { + SUCCESS, + INFO, + WARN, + ERROR + } + + public Type type; + public String text; + + public Notification(Type type, String text) { + this.type = type; + this.text = text; + } + + public String toString() { + return "["+type.toString()+"] "+text; + } + + public static void clear() { + int i = notifs.size(); + notifs.clear(); + for (NotificationListener l : listeners) { + l.onListCleared(i); + } + } + + public static void addSuccess(String text) { + if (!showS) return; + Notification n = new Notification(Notification.Type.SUCCESS, text); + notifs.add(n); + for (NotificationListener l : listeners) { + l.onNewNotification(n); + } + } + + public static void addInfo(String text) { + if (!showI) return; + Notification n = new Notification(Notification.Type.INFO, text); + notifs.add(n); + for (NotificationListener l : listeners) { + l.onNewNotification(n); + } + } + + public static void addWarn(String text) { + if (!showW) return; + Notification n = new Notification(Notification.Type.WARN, text); + notifs.add(n); + for (NotificationListener l : listeners) { + l.onNewNotification(n); + } + } + + public static void addError(String text) { + if (!showE) return; + Notification n = new Notification(Notification.Type.ERROR, text); + notifs.add(n); + for (NotificationListener l : listeners) { + l.onNewNotification(n); + } + } + + public static void addNotificationListener(NotificationListener l) { + listeners.add(l); + } + + public static void removeNotificationListener(NotificationListener l) { + listeners.remove(l); + } + +} \ No newline at end of file diff --git a/src/com/gpl/rpg/atcontentstudio/NotificationListener.java b/src/com/gpl/rpg/atcontentstudio/NotificationListener.java new file mode 100644 index 0000000..f5b265f --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/NotificationListener.java @@ -0,0 +1,8 @@ +package com.gpl.rpg.atcontentstudio; + +public interface NotificationListener { + + public void onNewNotification(Notification n); + public void onListCleared(int i); + +} \ No newline at end of file diff --git a/src/com/gpl/rpg/atcontentstudio/img/ATCS.ico b/src/com/gpl/rpg/atcontentstudio/img/ATCS.ico new file mode 100644 index 0000000..4f829e4 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/ATCS.ico differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/Thumbs.db b/src/com/gpl/rpg/atcontentstudio/img/Thumbs.db new file mode 100644 index 0000000..76375de Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/Thumbs.db differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/actor_condition.png b/src/com/gpl/rpg/atcontentstudio/img/actor_condition.png new file mode 100644 index 0000000..b27ee57 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/actor_condition.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/andorstrainer.png b/src/com/gpl/rpg/atcontentstudio/img/andorstrainer.png new file mode 100644 index 0000000..0ad1dbe Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/andorstrainer.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/arrow_down.png b/src/com/gpl/rpg/atcontentstudio/img/arrow_down.png new file mode 100644 index 0000000..1bb3563 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/arrow_down.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/arrow_left.png b/src/com/gpl/rpg/atcontentstudio/img/arrow_left.png new file mode 100644 index 0000000..1601669 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/arrow_left.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/arrow_right.png b/src/com/gpl/rpg/atcontentstudio/img/arrow_right.png new file mode 100644 index 0000000..da7feb2 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/arrow_right.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/arrow_up.png b/src/com/gpl/rpg/atcontentstudio/img/arrow_up.png new file mode 100644 index 0000000..51b4604 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/arrow_up.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/atcs_border_banner.png b/src/com/gpl/rpg/atcontentstudio/img/atcs_border_banner.png new file mode 100644 index 0000000..577a340 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/atcs_border_banner.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/atcs_logo_banner.png b/src/com/gpl/rpg/atcontentstudio/img/atcs_logo_banner.png new file mode 100644 index 0000000..6b4878d Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/atcs_logo_banner.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/char_hero.png b/src/com/gpl/rpg/atcontentstudio/img/char_hero.png new file mode 100644 index 0000000..54e22e5 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/char_hero.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/container.png b/src/com/gpl/rpg/atcontentstudio/img/container.png new file mode 100644 index 0000000..e926072 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/container.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/create_container.png b/src/com/gpl/rpg/atcontentstudio/img/create_container.png new file mode 100644 index 0000000..0468e0e Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/create_container.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/create_key.png b/src/com/gpl/rpg/atcontentstudio/img/create_key.png new file mode 100644 index 0000000..c22ae91 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/create_key.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/create_object_group.png b/src/com/gpl/rpg/atcontentstudio/img/create_object_group.png new file mode 100644 index 0000000..092133b Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/create_object_group.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/create_replace.png b/src/com/gpl/rpg/atcontentstudio/img/create_replace.png new file mode 100644 index 0000000..bd57e6c Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/create_replace.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/create_rest.png b/src/com/gpl/rpg/atcontentstudio/img/create_rest.png new file mode 100644 index 0000000..e2f7e9f Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/create_rest.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/create_script.png b/src/com/gpl/rpg/atcontentstudio/img/create_script.png new file mode 100644 index 0000000..cd0f98c Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/create_script.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/create_sign.png b/src/com/gpl/rpg/atcontentstudio/img/create_sign.png new file mode 100644 index 0000000..4744ca9 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/create_sign.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/create_spawnarea.png b/src/com/gpl/rpg/atcontentstudio/img/create_spawnarea.png new file mode 100644 index 0000000..f168626 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/create_spawnarea.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/create_tile_layer.png b/src/com/gpl/rpg/atcontentstudio/img/create_tile_layer.png new file mode 100644 index 0000000..b049d90 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/create_tile_layer.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/create_tiled.png b/src/com/gpl/rpg/atcontentstudio/img/create_tiled.png new file mode 100644 index 0000000..e5839c1 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/create_tiled.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/dialogue.png b/src/com/gpl/rpg/atcontentstudio/img/dialogue.png new file mode 100644 index 0000000..10ff850 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/dialogue.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/equip_body.png b/src/com/gpl/rpg/atcontentstudio/img/equip_body.png new file mode 100644 index 0000000..1bd1e52 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/equip_body.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/equip_feet.png b/src/com/gpl/rpg/atcontentstudio/img/equip_feet.png new file mode 100644 index 0000000..603c706 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/equip_feet.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/equip_hand.png b/src/com/gpl/rpg/atcontentstudio/img/equip_hand.png new file mode 100644 index 0000000..a1a2061 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/equip_hand.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/equip_head.png b/src/com/gpl/rpg/atcontentstudio/img/equip_head.png new file mode 100644 index 0000000..fb0b3fa Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/equip_head.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/equip_neck.png b/src/com/gpl/rpg/atcontentstudio/img/equip_neck.png new file mode 100644 index 0000000..f8143e2 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/equip_neck.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/equip_ring.png b/src/com/gpl/rpg/atcontentstudio/img/equip_ring.png new file mode 100644 index 0000000..65c03d2 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/equip_ring.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/equip_shield.png b/src/com/gpl/rpg/atcontentstudio/img/equip_shield.png new file mode 100644 index 0000000..6cfdf12 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/equip_shield.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/equip_square.png b/src/com/gpl/rpg/atcontentstudio/img/equip_square.png new file mode 100644 index 0000000..f8b5c9b Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/equip_square.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/equip_weapon.png b/src/com/gpl/rpg/atcontentstudio/img/equip_weapon.png new file mode 100644 index 0000000..fa88fe3 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/equip_weapon.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/error.png b/src/com/gpl/rpg/atcontentstudio/img/error.png new file mode 100644 index 0000000..0476298 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/error.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/file_create.png b/src/com/gpl/rpg/atcontentstudio/img/file_create.png new file mode 100644 index 0000000..4d035a7 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/file_create.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/folder_at_closed.png b/src/com/gpl/rpg/atcontentstudio/img/folder_at_closed.png new file mode 100644 index 0000000..a1ca115 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/folder_at_closed.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/folder_at_open.png b/src/com/gpl/rpg/atcontentstudio/img/folder_at_open.png new file mode 100644 index 0000000..fe34f28 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/folder_at_open.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/folder_json_closed.png b/src/com/gpl/rpg/atcontentstudio/img/folder_json_closed.png new file mode 100644 index 0000000..b6193eb Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/folder_json_closed.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/folder_json_open.png b/src/com/gpl/rpg/atcontentstudio/img/folder_json_open.png new file mode 100644 index 0000000..6186d9e Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/folder_json_open.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/folder_map_closed.png b/src/com/gpl/rpg/atcontentstudio/img/folder_map_closed.png new file mode 100644 index 0000000..91c6115 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/folder_map_closed.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/folder_map_open.png b/src/com/gpl/rpg/atcontentstudio/img/folder_map_open.png new file mode 100644 index 0000000..df85204 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/folder_map_open.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/folder_sav_closed.png b/src/com/gpl/rpg/atcontentstudio/img/folder_sav_closed.png new file mode 100644 index 0000000..4dc7ed7 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/folder_sav_closed.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/folder_sav_open.png b/src/com/gpl/rpg/atcontentstudio/img/folder_sav_open.png new file mode 100644 index 0000000..1aec900 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/folder_sav_open.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/folder_sprite_closed.png b/src/com/gpl/rpg/atcontentstudio/img/folder_sprite_closed.png new file mode 100644 index 0000000..d97ca52 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/folder_sprite_closed.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/folder_sprite_open.png b/src/com/gpl/rpg/atcontentstudio/img/folder_sprite_open.png new file mode 100644 index 0000000..0878195 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/folder_sprite_open.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/folder_std_closed.png b/src/com/gpl/rpg/atcontentstudio/img/folder_std_closed.png new file mode 100644 index 0000000..abd1398 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/folder_std_closed.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/folder_std_open.png b/src/com/gpl/rpg/atcontentstudio/img/folder_std_open.png new file mode 100644 index 0000000..c0a1972 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/folder_std_open.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/folder_tmx_closed.png b/src/com/gpl/rpg/atcontentstudio/img/folder_tmx_closed.png new file mode 100644 index 0000000..283256e Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/folder_tmx_closed.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/folder_tmx_open.png b/src/com/gpl/rpg/atcontentstudio/img/folder_tmx_open.png new file mode 100644 index 0000000..9ec4d37 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/folder_tmx_open.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/info.png b/src/com/gpl/rpg/atcontentstudio/img/info.png new file mode 100644 index 0000000..546d3c1 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/info.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/item.png b/src/com/gpl/rpg/atcontentstudio/img/item.png new file mode 100644 index 0000000..6ce9850 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/item.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/key.png b/src/com/gpl/rpg/atcontentstudio/img/key.png new file mode 100644 index 0000000..21f954a Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/key.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/mapchange.png b/src/com/gpl/rpg/atcontentstudio/img/mapchange.png new file mode 100644 index 0000000..309915e Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/mapchange.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/npc.png b/src/com/gpl/rpg/atcontentstudio/img/npc.png new file mode 100644 index 0000000..ddc5849 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/npc.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/npc_close.png b/src/com/gpl/rpg/atcontentstudio/img/npc_close.png new file mode 100644 index 0000000..4b38791 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/npc_close.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/nsisBorderBanner.bmp b/src/com/gpl/rpg/atcontentstudio/img/nsisBorderBanner.bmp new file mode 100644 index 0000000..4981f69 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/nsisBorderBanner.bmp differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/nsisHeader.bmp b/src/com/gpl/rpg/atcontentstudio/img/nsisHeader.bmp new file mode 100644 index 0000000..199d4c4 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/nsisHeader.bmp differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/nullify.png b/src/com/gpl/rpg/atcontentstudio/img/nullify.png new file mode 100644 index 0000000..27eebd8 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/nullify.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/object_layer.png b/src/com/gpl/rpg/atcontentstudio/img/object_layer.png new file mode 100644 index 0000000..f05a19c Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/object_layer.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/replace.png b/src/com/gpl/rpg/atcontentstudio/img/replace.png new file mode 100644 index 0000000..df2b58b Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/replace.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/rest.png b/src/com/gpl/rpg/atcontentstudio/img/rest.png new file mode 100644 index 0000000..7798807 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/rest.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/script.png b/src/com/gpl/rpg/atcontentstudio/img/script.png new file mode 100644 index 0000000..97a9020 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/script.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/sign.png b/src/com/gpl/rpg/atcontentstudio/img/sign.png new file mode 100644 index 0000000..9186535 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/sign.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/success.png b/src/com/gpl/rpg/atcontentstudio/img/success.png new file mode 100644 index 0000000..d7a83ec Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/success.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/tile_layer.png b/src/com/gpl/rpg/atcontentstudio/img/tile_layer.png new file mode 100644 index 0000000..8389016 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/tile_layer.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/tiled-icon.png b/src/com/gpl/rpg/atcontentstudio/img/tiled-icon.png new file mode 100644 index 0000000..e4f1a7e Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/tiled-icon.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/ui_icon_coins.png b/src/com/gpl/rpg/atcontentstudio/img/ui_icon_coins.png new file mode 100644 index 0000000..821f761 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/ui_icon_coins.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/ui_icon_combat.png b/src/com/gpl/rpg/atcontentstudio/img/ui_icon_combat.png new file mode 100644 index 0000000..f007c69 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/ui_icon_combat.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/ui_icon_equipment.png b/src/com/gpl/rpg/atcontentstudio/img/ui_icon_equipment.png new file mode 100644 index 0000000..80b0cef Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/ui_icon_equipment.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/ui_icon_map.png b/src/com/gpl/rpg/atcontentstudio/img/ui_icon_map.png new file mode 100644 index 0000000..b49bbce Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/ui_icon_map.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/ui_icon_quest.png b/src/com/gpl/rpg/atcontentstudio/img/ui_icon_quest.png new file mode 100644 index 0000000..c2353f7 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/ui_icon_quest.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/warn.png b/src/com/gpl/rpg/atcontentstudio/img/warn.png new file mode 100644 index 0000000..39e347f Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/warn.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/zoom.png b/src/com/gpl/rpg/atcontentstudio/img/zoom.png new file mode 100644 index 0000000..e3640e6 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/zoom.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/io/JsonPrettyWriter.java b/src/com/gpl/rpg/atcontentstudio/io/JsonPrettyWriter.java new file mode 100644 index 0000000..f3e5a80 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/io/JsonPrettyWriter.java @@ -0,0 +1,53 @@ +package com.gpl.rpg.atcontentstudio.io; + +import java.io.StringWriter; + +public class JsonPrettyWriter extends StringWriter { + + private int indentLevel = 0; + private String indentText = " "; + + public JsonPrettyWriter() { + super(); + } + + public JsonPrettyWriter(String indent) { + super(); + this.indentText = indent; + } + + @Override + public void write(int c) { + if (((char) c) == '[' || ((char) c) == '{') { + super.write(c); + super.write('\n'); + indentLevel++; + writeIndentation(); + } else if (((char) c) == ',') { + super.write(c); + super.write('\n'); + writeIndentation(); + } else if (((char) c) == ']' || ((char) c) == '}') { + super.write('\n'); + indentLevel--; + writeIndentation(); + super.write(c); + } else { + super.write(c); + } + + } + + //Horrible hack to remove the horrible escaping of slashes in json-simple.... + @Override + public void write(String str) { + super.write(str.replaceAll("\\\\/", "/")); + } + + private void writeIndentation() { + for (int i = 0; i < indentLevel; i++) { + super.write(indentText); + } + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/io/SettingsSave.java b/src/com/gpl/rpg/atcontentstudio/io/SettingsSave.java new file mode 100644 index 0000000..21bc3bc --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/io/SettingsSave.java @@ -0,0 +1,76 @@ +package com.gpl.rpg.atcontentstudio.io; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import com.gpl.rpg.atcontentstudio.Notification; + +public class SettingsSave { + + public static void saveInstance(Object obj, File f, String type) { + try { + FileOutputStream fos = new FileOutputStream(f); + try { + ObjectOutputStream oos = new ObjectOutputStream(fos); + oos.writeObject(obj); + oos.flush(); + oos.close(); + Notification.addSuccess(type+" successfully saved."); + } catch (IOException e) { + e.printStackTrace(); + Notification.addError(type+" saving error: "+e.getMessage()); + } finally { + try { + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + Notification.addError(type+" saving error: "+e.getMessage()); + } + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + Notification.addError(type+" saving error: "+e.getMessage()); + } + } + + public static Object loadInstance(File f, String type) { + FileInputStream fis; + Object result = null; + try { + fis = new FileInputStream(f); + ObjectInputStream ois; + try { + ois = new ObjectInputStream(fis); + try { + result = ois.readObject(); + Notification.addSuccess(type+" successfully loaded."); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + Notification.addError(type+" loading error: "+e.getMessage()); + } finally { + ois.close(); + } + } catch (IOException e) { + e.printStackTrace(); + Notification.addError(type+" loading error: "+e.getMessage()); + } finally { + try { + fis.close(); + } catch (IOException e) { + e.printStackTrace(); + Notification.addError(type+" loading error: "+e.getMessage()); + } + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + Notification.addError(type+" loading error: "+e.getMessage()); + } + return result; + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/ClosedProject.java b/src/com/gpl/rpg/atcontentstudio/model/ClosedProject.java new file mode 100644 index 0000000..4fe6f4a --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/ClosedProject.java @@ -0,0 +1,119 @@ +package com.gpl.rpg.atcontentstudio.model; + +import java.awt.Image; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import javax.swing.tree.TreeNode; + +import com.gpl.rpg.atcontentstudio.model.GameSource.Type; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class ClosedProject implements ProjectTreeNode { + + String name; + Workspace parent; + + public ClosedProject(Workspace w, String name) { + this.parent = w; + this.name = name; + } + + @Override + public TreeNode getChildAt(int childIndex) { + return null; + } + @Override + public int getChildCount() { + return 0; + } + @Override + public TreeNode getParent() { + return parent; + } + @Override + public int getIndex(TreeNode node) { + return 0; + } + @Override + public boolean getAllowsChildren() { + return false; + } + @Override + public boolean isLeaf() { + return true; + } + @Override + public Enumeration children() { + return null; + } + + @Override + public void childrenAdded(List path) { + path.add(0,this); + parent.childrenAdded(path); + } + @Override + public void childrenChanged(List path) { + path.add(0,this); + parent.childrenChanged(path); + } + @Override + public void childrenRemoved(List path) { + path.add(0,this); + parent.childrenRemoved(path); + } + @Override + public void notifyCreated() { + childrenAdded(new ArrayList()); + } + + @Override + public String getDesc() { + return name+" [closed]"; + } + + @Override + public Project getProject() { + return null; + } + + + @Override + public Image getIcon() { + return getOpenIcon(); + } + @Override + public Image getClosedIcon() { + //TODO Create a cool Project icon. + return DefaultIcons.getStdClosedIcon(); + } + @Override + public Image getLeafIcon() { + //TODO Create a cool Project icon. + return DefaultIcons.getStdClosedIcon(); + } + @Override + public Image getOpenIcon() { + //TODO Create a cool Project icon. + return DefaultIcons.getStdOpenIcon(); + } + + @Override + public GameDataSet getDataSet() { + return null; + } + + @Override + public Type getDataType() { + return null; + } + + @Override + public boolean isEmpty() { + return true; + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/GameDataElement.java b/src/com/gpl/rpg/atcontentstudio/model/GameDataElement.java new file mode 100644 index 0000000..96d6bcf --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/GameDataElement.java @@ -0,0 +1,185 @@ +package com.gpl.rpg.atcontentstudio.model; + +import java.awt.Image; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.tree.TreeNode; + +public abstract class GameDataElement implements ProjectTreeNode, Serializable { + + private static final long serialVersionUID = 2028934451226743389L; + + public static enum State { + init, // We know the object exists, and have its key/ID. + parsed, // We know the object's properties, but related objects are referenced by ID only. + linked, // We know the object fully, and all links to related objects point to objects in the parsed state at least. + created, // This is an object we are creating + modified, // Whether altered or created, this item has been modified since creation from scratch or from JSON. + saved // Whether altered or created, this item has been saved since last modification. + } + + public State state = State.init; + + //Available from state init. + public ProjectTreeNode parent; + + public boolean writable = false; + + //List of objects whose transition to "linked" state made them point to this instance. + private Map backlinks = new HashMap(); + + public String id = null; + + @Override + public Enumeration children() { + return null; + } + @Override + public boolean getAllowsChildren() { + return false; + } + @Override + public TreeNode getChildAt(int arg0) { + return null; + } + @Override + public int getChildCount() { + return 0; + } + @Override + public int getIndex(TreeNode arg0) { + return 0; + } + @Override + public TreeNode getParent() { + return parent; + } + @Override + public boolean isLeaf() { + return true; + } + @Override + public void childrenAdded(List path) { + path.add(0,this); + parent.childrenAdded(path); + } + + @Override + public void childrenChanged(List path) { + path.add(0,this); + parent.childrenChanged(path); + } + + @Override + public void childrenRemoved(List path) { + path.add(0,this); + parent.childrenRemoved(path); + } + @Override + public void notifyCreated() { + childrenAdded(new ArrayList()); + } + @Override + public abstract String getDesc(); + + public static String getStaticDesc() { + return "GameDataElements"; + } + + public abstract void parse(); + public abstract void link(); + + + + @Override + public Project getProject() { + return parent.getProject(); + } + + + public Image getIcon() { + return null; + } + @Override + public Image getClosedIcon() {return null;} + @Override + public Image getOpenIcon() {return null;} + @Override + public Image getLeafIcon() { + return getIcon(); + } + + + public abstract GameDataElement clone(); + + public abstract void elementChanged(GameDataElement oldOne, GameDataElement newOne); + + + @Override + public GameSource.Type getDataType() { + if (parent == null) { + System.out.println("blerf."); + } + return parent.getDataType(); + } + + + public List backlinkListeners = new ArrayList(); + + public void addBacklinkListener(BacklinksListener l) { + backlinkListeners.add(l); + } + + public void removeBacklinkListener(BacklinksListener l) { + backlinkListeners.remove(l); + } + + public void addBacklink(GameDataElement gde) { + if (!backlinks.containsKey(gde)) { + backlinks.put(gde, 1); + for (BacklinksListener l : backlinkListeners) { + l.backlinkAdded(gde); + } + } else { + backlinks.put(gde, backlinks.get(gde) + 1); + } + } + + public void removeBacklink(GameDataElement gde) { + if (backlinks.get(gde) == null) return; + backlinks.put(gde, backlinks.get(gde) - 1); + if (backlinks.get(gde) == 0) { + backlinks.remove(gde); + for (BacklinksListener l : backlinkListeners) { + l.backlinkRemoved(gde); + } + } + } + + public Set getBacklinks() { + return backlinks.keySet(); + } + + public static interface BacklinksListener { + public void backlinkAdded(GameDataElement gde); + public void backlinkRemoved(GameDataElement gde); + } + + @Override + public boolean isEmpty() { + return false; + } + + public abstract String getProjectFilename(); + + public abstract void save(); + + public abstract List attemptSave(); + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/GameSource.java b/src/com/gpl/rpg/atcontentstudio/model/GameSource.java new file mode 100644 index 0000000..a5bb953 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/GameSource.java @@ -0,0 +1,200 @@ +package com.gpl.rpg.atcontentstudio.model; + +import java.awt.Image; +import java.io.File; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import javax.swing.tree.TreeNode; + +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMapSet; +import com.gpl.rpg.atcontentstudio.model.maps.Worldmap; +import com.gpl.rpg.atcontentstudio.model.maps.WorldmapSegment; +import com.gpl.rpg.atcontentstudio.model.sprites.SpriteSheetSet; +import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class GameSource implements ProjectTreeNode, Serializable { + + private static final long serialVersionUID = -1512979360971918158L; + + public transient GameDataSet gameData; + public transient TMXMapSet gameMaps; + public transient SpriteSheetSet gameSprites; + public transient Worldmap worldmap; + private transient SavedSlotCollection v; + + public static enum Type { + source, + referenced, + altered, + created + } + + public File baseFolder; + public Type type; + + public transient Project parent = null; + + public GameSource(File folder, Project parent) { + this.parent = parent; + this.baseFolder = folder; + this.type = Type.source; + initData(); + } + + public GameSource(Project parent, Type type) { + this.parent = parent; + this.baseFolder = new File(parent.baseFolder, type.toString()); + this.type = type; + initData(); + } + + public void refreshTransients(Project p) { + parent = p; + initData(); + } + + public void initData() { + this.gameData = new GameDataSet(this); + this.gameMaps = new TMXMapSet(this); + this.gameSprites = new SpriteSheetSet(this); + this.worldmap = new Worldmap(this); + v = new SavedSlotCollection(); + v.add(gameData); + v.add(gameMaps); + v.add(gameSprites); + v.add(worldmap); + } + + @Override + public Enumeration children() { + return v.getNonEmptyElements(); + } + @Override + public boolean getAllowsChildren() { + return true; + } + @Override + public TreeNode getChildAt(int arg0) { + return v.getNonEmptyElementAt(arg0); + } + @Override + public int getChildCount() { + return v.getNonEmptySize(); + } + @Override + public int getIndex(TreeNode arg0) { + return v.getNonEmptyIndexOf((ProjectTreeNode) arg0); + } + @Override + public TreeNode getParent() { + return parent; + } + @Override + public boolean isLeaf() { + return false; + } + @Override + public void childrenAdded(List path) { + path.add(0, this); + parent.childrenAdded(path); + } + @Override + public void childrenChanged(List path) { + path.add(0, this); + parent.childrenChanged(path); + } + @Override + public void childrenRemoved(List path) { + if (path.size() == 1 && this.v.getNonEmptySize() == 1) { + childrenRemoved(new ArrayList()); + } else { + path.add(0, this); + parent.childrenRemoved(path); + } + } + @Override + public void notifyCreated() { + childrenAdded(new ArrayList()); + for (ProjectTreeNode node : v.getNonEmptyIterable()) { + node.notifyCreated(); + } + } + @Override + public String getDesc() { + switch(type) { + case altered: return "Altered data"; + case created: return "Created data"; + case referenced: return "Referenced data"; + case source: return "AT Source"; //The fact that it is from "source" is already mentionned by its parent. + default: return "Game data"; + } + } + + + @Override + public Project getProject() { + return parent == null ? null : parent.getProject(); + } + + public Image getIcon(String iconId) { + String[] data = iconId.split(":"); + for (Spritesheet sheet : gameSprites.spritesheets) { + if (sheet.id.equals(data[0])) { + return sheet.getIcon(Integer.parseInt(data[1])); + } + } + return null; + } + + public Image getImage(String iconId) { + String[] data = iconId.split(":"); + for (Spritesheet sheet : gameSprites.spritesheets) { + if (sheet.id.equals(data[0])) { + return sheet.getImage(Integer.parseInt(data[1])); + } + } + return null; + } + + + @Override + public Image getIcon() { + return getOpenIcon(); + } + @Override + public Image getClosedIcon() { + return DefaultIcons.getATClosedIcon(); + } + @Override + public Image getLeafIcon() { + return DefaultIcons.getATClosedIcon(); + } + @Override + public Image getOpenIcon() { + return DefaultIcons.getATOpenIcon(); + } + @Override + public GameDataSet getDataSet() { + return null; + } + + + @Override + public Type getDataType() { + return type; + } + + @Override + public boolean isEmpty() { + return v.isEmpty(); + } + + public WorldmapSegment getWorldmapSegment(String id) { + return worldmap.getWorldmapSegment(id); + } +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/Preferences.java b/src/com/gpl/rpg/atcontentstudio/model/Preferences.java new file mode 100644 index 0000000..36fa399 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/Preferences.java @@ -0,0 +1,19 @@ +package com.gpl.rpg.atcontentstudio.model; + +import java.awt.Dimension; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +public class Preferences implements Serializable { + + private static final long serialVersionUID = 2455802658424031276L; + + public Dimension windowSize = null; + public Map splittersPositions = new HashMap(); + + public Preferences() { + + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/Project.java b/src/com/gpl/rpg/atcontentstudio/model/Project.java new file mode 100644 index 0000000..4279bc7 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/Project.java @@ -0,0 +1,964 @@ +package com.gpl.rpg.atcontentstudio.model; + +import java.awt.Image; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Serializable; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import javax.swing.tree.TreeNode; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.json.simple.JSONArray; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.io.JsonPrettyWriter; +import com.gpl.rpg.atcontentstudio.io.SettingsSave; +import com.gpl.rpg.atcontentstudio.model.GameSource.Type; +import com.gpl.rpg.atcontentstudio.model.gamedata.ActorCondition; +import com.gpl.rpg.atcontentstudio.model.gamedata.Dialogue; +import com.gpl.rpg.atcontentstudio.model.gamedata.Droplist; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataCategory; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.model.gamedata.Item; +import com.gpl.rpg.atcontentstudio.model.gamedata.ItemCategory; +import com.gpl.rpg.atcontentstudio.model.gamedata.JSONElement; +import com.gpl.rpg.atcontentstudio.model.gamedata.NPC; +import com.gpl.rpg.atcontentstudio.model.gamedata.Quest; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMapSet; +import com.gpl.rpg.atcontentstudio.model.maps.Worldmap; +import com.gpl.rpg.atcontentstudio.model.maps.WorldmapSegment; +import com.gpl.rpg.atcontentstudio.model.saves.SavedGamesSet; +import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; +import com.gpl.rpg.atcontentstudio.ui.WorkerDialog; +import com.gpl.rpg.atcontentstudio.utils.FileUtils; + +public class Project implements ProjectTreeNode, Serializable { + + private static final long serialVersionUID = 4807454973303366758L; + + //Every instance field that is not transient will be saved in this file. + public static final String SETTINGS_FILE = ".project"; + + public String name; + + public File baseFolder; + public boolean open; + + public GameSource baseContent; //A.k.a library + + public GameSource referencedContent; //Pointers to base content + public transient GameSource alteredContent; //Copied from base content (does not overwrite yet) + public transient GameSource createdContent; //Stand-alone. + + public SavedGamesSet saves; //For simulations. + + public transient SavedSlotCollection v; + + public transient Workspace parent; + + public Properties knownSpritesheetsProperties = null; + + public Project(Workspace w, String name, File source) { + this.parent = w; + this.name = name; + + //CREATE PROJECT + baseFolder = new File(w.baseFolder, name+File.separator); + try { + baseFolder.mkdir(); + } catch (SecurityException e) { + Notification.addError("Eror creating project root folder: "+e.getMessage()); + e.printStackTrace(); + } + open = true; + v = new SavedSlotCollection(); + + knownSpritesheetsProperties = new Properties(); + try { + knownSpritesheetsProperties.load(Project.class.getResourceAsStream("/spritesheets.properties")); + } catch (IOException e) { + Notification.addWarn("Unable to load default spritesheets properties."); + e.printStackTrace(); + } + + + baseContent = new GameSource(source, this); + +// referencedContent = new GameSource(this, GameSource.Type.referenced); + + alteredContent = new GameSource(this, GameSource.Type.altered); + createdContent = new GameSource(this, GameSource.Type.created); + + saves = new SavedGamesSet(this); + + v.add(createdContent); + v.add(alteredContent); +// v.add(referencedContent); + v.add(baseContent); + v.add(saves); + + linkAll(); + + save(); + } + + + @Override + public TreeNode getChildAt(int childIndex) { + return v.getNonEmptyElementAt(childIndex); + } + + @Override + public int getChildCount() { + return v.getNonEmptySize(); + } + + @Override + public TreeNode getParent() { + return parent; + } + + @Override + public int getIndex(TreeNode node) { + return v.getNonEmptyIndexOf((ProjectTreeNode) node); + } + + @Override + public boolean getAllowsChildren() { + return open; + } + + @Override + public boolean isLeaf() { + return !open; + } + + @Override + public Enumeration children() { + return v.getNonEmptyElements(); + } + + @Override + public void childrenAdded(List path) { + path.add(0,this); + parent.childrenAdded(path); + } + + @Override + public void childrenChanged(List path) { + path.add(0,this); + parent.childrenChanged(path); + } + + @Override + public void childrenRemoved(List path) { + path.add(0,this); + parent.childrenRemoved(path); + } + @Override + public void notifyCreated() { + childrenAdded(new ArrayList()); + for (ProjectTreeNode node : v.getNonEmptyIterable()) { + node.notifyCreated(); + } + } + @Override + public String getDesc() { + return name; + } + + + public void close() { + this.open = false; + childrenRemoved(new ArrayList()); + } + + public void open() { + open = true; + } + + + public static Project fromFolder(Workspace w, File projRoot) { + Project p = null; + File f = new File(projRoot, Project.SETTINGS_FILE); + if (!f.exists()) { + Notification.addError("Unable to find "+SETTINGS_FILE+" for project "+projRoot.getName()); + return null; + } else { + p = (Project) SettingsSave.loadInstance(f, "Project"); + } + p.refreshTransients(w); + return p; + } + + public void refreshTransients(Workspace w) { + this.parent = w; + + if (knownSpritesheetsProperties == null) { + try { + knownSpritesheetsProperties = new Properties(); + knownSpritesheetsProperties.load(Project.class.getResourceAsStream("/spritesheets.properties")); + } catch (IOException e) { + Notification.addWarn("Unable to load default spritesheets properties."); + e.printStackTrace(); + } + } + +// long l = new Date().getTime(); + baseContent.refreshTransients(this); +// l = new Date().getTime() - l; +// System.out.println("All initialized in "+l+"ms."); +// referencedContent.refreshTransients(this); + alteredContent = new GameSource(this, GameSource.Type.altered); + createdContent = new GameSource(this, GameSource.Type.created); + + saves.refreshTransients(); + + v = new SavedSlotCollection(); + v.add(createdContent); + v.add(alteredContent); +// v.add(referencedContent); + v.add(baseContent); + v.add(saves); + + + linkAll(); + + projectElementListeners = new HashMap, List>(); + } + + public void linkAll() { + for (ProjectTreeNode node : baseContent.gameData.v.getNonEmptyIterable()) { + if (node instanceof GameDataCategory) { + for (GameDataElement e : ((GameDataCategory) node)) { + e.link(); + } + } + } + for (ProjectTreeNode node : baseContent.gameMaps.tmxMaps) { + ((TMXMap)node).parse(); + } + for (ProjectTreeNode node : alteredContent.gameData.v.getNonEmptyIterable()) { + if (node instanceof GameDataCategory) { + for (GameDataElement e : ((GameDataCategory) node)) { + e.link(); + } + } + } + for (ProjectTreeNode node : alteredContent.gameMaps.tmxMaps) { + ((TMXMap)node).parse(); + } + for (ProjectTreeNode node : createdContent.gameData.v.getNonEmptyIterable()) { + if (node instanceof GameDataCategory) { + for (GameDataElement e : ((GameDataCategory) node)) { + e.link(); + } + } + } + for (ProjectTreeNode node : createdContent.gameMaps.tmxMaps) { + ((TMXMap)node).parse(); + } + for (ProjectTreeNode node : baseContent.gameMaps.tmxMaps) { + ((TMXMap)node).link(); + } + + for (WorldmapSegment node : createdContent.worldmap) { + node.link(); + } + for (WorldmapSegment node : alteredContent.worldmap) { + node.link(); + } + for (WorldmapSegment node : baseContent.worldmap) { + node.link(); + } + } + + public void save() { + SettingsSave.saveInstance(this, new File(baseFolder, Project.SETTINGS_FILE), "Project "+this.name); + } + + + public JSONElement getGameDataElement(Class gdeClass, String id) { + if (gdeClass == ActorCondition.class) { + return getActorCondition(id); + } + if (gdeClass == Dialogue.class) { + return getDialogue(id); + } + if (gdeClass == Droplist.class) { + return getDroplist(id); + } + if (gdeClass == ItemCategory.class) { + return getItemCategory(id); + } + if (gdeClass == Item.class) { + return getItem(id); + } + if (gdeClass == NPC.class) { + return getNPC(id); + } + if (gdeClass == Quest.class) { + return getQuest(id); + } + return null; + } + + public int getNodeCount(Class gdeClass) { + if (gdeClass == ActorCondition.class) { + return getActorConditionCount(); + } + if (gdeClass == Dialogue.class) { + return getDialogueCount(); + } + if (gdeClass == Droplist.class) { + return getDroplistCount(); + } + if (gdeClass == ItemCategory.class) { + return getItemCategoryCount(); + } + if (gdeClass == Item.class) { + return getItemCount(); + } + if (gdeClass == NPC.class) { + return getNPCCount(); + } + if (gdeClass == Quest.class) { + return getQuestCount(); + } + return 0; + } + + public int getNodeIndex(GameDataElement node) { + Class gdeClass = node.getClass(); + if (gdeClass == ActorCondition.class) { + return getActorConditionIndex((ActorCondition) node); + } + if (gdeClass == Dialogue.class) { + return getDialogueIndex((Dialogue) node); + } + if (gdeClass == Droplist.class) { + return getDroplistIndex((Droplist) node); + } + if (gdeClass == ItemCategory.class) { + return getItemCategoryIndex((ItemCategory) node); + } + if (gdeClass == Item.class) { + return getItemIndex((Item) node); + } + if (gdeClass == NPC.class) { + return getNPCIndex((NPC) node); + } + if (gdeClass == Quest.class) { + return getQuestIndex((Quest) node); + } + return 0; + } + + public ActorCondition getActorCondition(String id) { + ActorCondition gde = createdContent.gameData.getActorCondition(id); + if (gde == null) gde = alteredContent.gameData.getActorCondition(id); + if (gde == null) gde = baseContent.gameData.getActorCondition(id); + return gde; + } + + public int getActorConditionCount() { + return createdContent.gameData.actorConditions.size() + baseContent.gameData.actorConditions.size(); + } + + public ActorCondition getActorCondition(int index) { + if (index < createdContent.gameData.actorConditions.size()) { + return createdContent.gameData.actorConditions.get(index); + } else if (index < getActorConditionCount()){ + return getActorCondition(baseContent.gameData.actorConditions.get(index - createdContent.gameData.actorConditions.size()).id); + } + return null; + } + + public int getActorConditionIndex(ActorCondition ac) { + if (ac.getDataType() == GameSource.Type.created) { + return createdContent.gameData.actorConditions.getIndex(ac); + } else { + return createdContent.gameData.actorConditions.size() + baseContent.gameData.actorConditions.indexOf(baseContent.gameData.getActorCondition(ac.id)); + } + } + + + public Dialogue getDialogue(String id) { + Dialogue gde = createdContent.gameData.getDialogue(id); + if (gde == null) gde = alteredContent.gameData.getDialogue(id); + if (gde == null) gde = baseContent.gameData.getDialogue(id); + return gde; + } + + public int getDialogueCount() { + return createdContent.gameData.dialogues.size() + baseContent.gameData.dialogues.size(); + } + + public Dialogue getDialogue(int index) { + if (index < createdContent.gameData.dialogues.size()) { + return createdContent.gameData.dialogues.get(index); + } else if (index < getDialogueCount()){ + return getDialogue(baseContent.gameData.dialogues.get(index - createdContent.gameData.dialogues.size()).id); + } + return null; + } + + public int getDialogueIndex(Dialogue dialogue) { + if (dialogue.getDataType() == GameSource.Type.created) { + return createdContent.gameData.dialogues.getIndex(dialogue); + } else { + return createdContent.gameData.dialogues.size() + baseContent.gameData.dialogues.indexOf(baseContent.gameData.getDialogue(dialogue.id)); + } + } + + + public Droplist getDroplist(String id) { + Droplist gde = createdContent.gameData.getDroplist(id); + if (gde == null) gde = alteredContent.gameData.getDroplist(id); + if (gde == null) gde = baseContent.gameData.getDroplist(id); + return gde; + } + + public int getDroplistCount() { + return createdContent.gameData.droplists.size() + baseContent.gameData.droplists.size(); + } + + public Droplist getDroplist(int index) { + if (index < createdContent.gameData.droplists.size()) { + return createdContent.gameData.droplists.get(index); + } else if (index < getDroplistCount()){ + return getDroplist(baseContent.gameData.droplists.get(index - createdContent.gameData.droplists.size()).id); + } + return null; + } + + public int getDroplistIndex(Droplist droplist) { + if (droplist.getDataType() == GameSource.Type.created) { + return createdContent.gameData.droplists.getIndex(droplist); + } else { + return createdContent.gameData.droplists.size() + baseContent.gameData.droplists.indexOf(baseContent.gameData.getDroplist(droplist.id)); + } + } + + + public Item getItem(String id) { + Item gde = createdContent.gameData.getItem(id); + if (gde == null) gde = alteredContent.gameData.getItem(id); + if (gde == null) gde = baseContent.gameData.getItem(id); + return gde; + } + + public int getItemCount() { + return createdContent.gameData.items.size() + baseContent.gameData.items.size(); + } + + public Item getItem(int index) { + if (index < createdContent.gameData.items.size()) { + return createdContent.gameData.items.get(index); + } else if (index < getItemCount()){ + return getItem(baseContent.gameData.items.get(index - createdContent.gameData.items.size()).id); + } + return null; + } + + public int getItemIndex(Item item) { + if (item.getDataType() == GameSource.Type.created) { + return createdContent.gameData.items.getIndex(item); + } else { + return createdContent.gameData.items.size() + baseContent.gameData.items.indexOf(baseContent.gameData.getItem(item.id)); + } + } + + + public ItemCategory getItemCategory(String id) { + ItemCategory gde = createdContent.gameData.getItemCategory(id); + if (gde == null) gde = alteredContent.gameData.getItemCategory(id); + if (gde == null) gde = baseContent.gameData.getItemCategory(id); + return gde; + } + + public int getItemCategoryCount() { + return createdContent.gameData.itemCategories.size() + baseContent.gameData.itemCategories.size(); + } + + public ItemCategory getItemCategory(int index) { + if (index < createdContent.gameData.itemCategories.size()) { + return createdContent.gameData.itemCategories.get(index); + } else if (index < getItemCategoryCount()){ + return getItemCategory(baseContent.gameData.itemCategories.get(index - createdContent.gameData.itemCategories.size()).id); + } + return null; + } + + public int getItemCategoryIndex(ItemCategory iCat) { + if (iCat.getDataType() == GameSource.Type.created) { + return createdContent.gameData.itemCategories.getIndex(iCat); + } else { + return createdContent.gameData.itemCategories.size() + baseContent.gameData.itemCategories.indexOf(baseContent.gameData.getItemCategory(iCat.id)); + } + } + + + public NPC getNPC(String id) { + NPC gde = createdContent.gameData.getNPC(id); + if (gde == null) gde = alteredContent.gameData.getNPC(id); + if (gde == null) gde = baseContent.gameData.getNPC(id); + return gde; + } + + public int getNPCCount() { + return createdContent.gameData.npcs.size() + baseContent.gameData.npcs.size(); + } + + public NPC getNPC(int index) { + if (index < createdContent.gameData.npcs.size()) { + return createdContent.gameData.npcs.get(index); + } else if (index < getNPCCount()){ + return getNPC(baseContent.gameData.npcs.get(index - createdContent.gameData.npcs.size()).id); + } + return null; + } + + public int getNPCIndex(NPC npc) { + if (npc.getDataType() == GameSource.Type.created) { + return createdContent.gameData.npcs.getIndex(npc); + } else { + return createdContent.gameData.npcs.size() + baseContent.gameData.npcs.indexOf(baseContent.gameData.getNPC(npc.id)); + } + } + + + public Quest getQuest(String id) { + Quest gde = createdContent.gameData.getQuest(id); + if (gde == null) gde = alteredContent.gameData.getQuest(id); + if (gde == null) gde = baseContent.gameData.getQuest(id); + return gde; + } + + public int getQuestCount() { + return createdContent.gameData.quests.size() + baseContent.gameData.quests.size(); + } + + public Quest getQuest(int index) { + if (index < createdContent.gameData.quests.size()) { + return createdContent.gameData.quests.get(index); + } else if (index < getQuestCount()){ + return getQuest(baseContent.gameData.quests.get(index - createdContent.gameData.quests.size()).id); + } + return null; + } + + public int getQuestIndex(Quest quest) { + if (quest.getDataType() == GameSource.Type.created) { + return createdContent.gameData.quests.getIndex(quest); + } else { + return createdContent.gameData.quests.size() + baseContent.gameData.quests.indexOf(baseContent.gameData.getQuest(quest.id)); + } + } + + public WorldmapSegment getWorldmapSegment(String id) { + WorldmapSegment gde = createdContent.getWorldmapSegment(id); + if (gde == null) gde = alteredContent.getWorldmapSegment(id); + if (gde == null) gde = baseContent.getWorldmapSegment(id); + return gde; + } + + public int getWorldmapSegmentCount() { + return createdContent.worldmap.size() + baseContent.worldmap.size(); + } + + public WorldmapSegment getWorldmapSegment(int index) { + if (index < createdContent.worldmap.size()) { + return createdContent.worldmap.get(index); + } else if (index < getWorldmapSegmentCount()){ + return getWorldmapSegment(baseContent.worldmap.get(index - createdContent.worldmap.size()).id); + } + return null; + } + + public int getWorldmapSegmentIndex(WorldmapSegment segment) { + if (segment.getDataType() == GameSource.Type.created) { + return createdContent.worldmap.getIndex(segment); + } else { + return createdContent.worldmap.size() + baseContent.worldmap.indexOf(baseContent.getWorldmapSegment(segment.id)); + } + } + + public Image getIcon(String iconId) { + return baseContent.getIcon(iconId); + } + + public Image getImage(String iconId) { + return baseContent.getImage(iconId); + } + + public Spritesheet getSpritesheet(String id) { + Spritesheet sheet = createdContent.gameSprites.getSpritesheet(id); + if (sheet == null) sheet = alteredContent.gameSprites.getSpritesheet(id); + if (sheet == null) sheet = baseContent.gameSprites.getSpritesheet(id); + return sheet; + } + + + public TMXMap getMap(String id) { + TMXMap map = createdContent.gameMaps.getMap(id); + if (map == null) map = alteredContent.gameMaps.getMap(id); + if (map == null) map = baseContent.gameMaps.getMap(id); + return map; + } + + public int getMapCount() { + return createdContent.gameMaps.getChildCount() + baseContent.gameMaps.getChildCount(); + } + + public TMXMap getMap(int index) { + if (index < createdContent.gameMaps.getChildCount()) { + return createdContent.gameMaps.get(index); + } else if (index < getMapCount()){ + return getMap(baseContent.gameMaps.get(index - createdContent.gameMaps.getChildCount()).id); + } + return null; + } + + public int getMapIndex(TMXMap map) { + if (map.getDataType() == GameSource.Type.created) { + return createdContent.gameMaps.tmxMaps.indexOf(map); + } else { + return createdContent.gameMaps.tmxMaps.size() + baseContent.gameMaps.tmxMaps.indexOf(baseContent.gameMaps.getMap(map.id)); + } + } + + + @Override + public Project getProject() { + return this; + } + + @Override + public Image getIcon() { + return getOpenIcon(); + } + @Override + public Image getClosedIcon() { + //TODO Create a cool Project icon. + return DefaultIcons.getStdClosedIcon(); + } + @Override + public Image getLeafIcon() { + //TODO Create a cool Project icon. + return DefaultIcons.getStdClosedIcon(); + } + @Override + public Image getOpenIcon() { + //TODO Create a cool Project icon. + return DefaultIcons.getStdOpenIcon(); + } + + public void makeWritable(JSONElement node) { + GameSource.Type type = node.getDataType(); + if (type == null) { + Notification.addError("Unable to make "+node.getDesc()+" writable. No owning GameDataSet found."); + } else { + if (type == GameSource.Type.source) { + JSONElement clone = (JSONElement) node.clone(); + for (GameDataElement backlink : node.getBacklinks()) { + backlink.elementChanged(node, clone); + } + node.getBacklinks().clear(); + clone.writable = true; + clone.state = GameDataElement.State.created; + alteredContent.gameData.addElement(clone); + } else { + Notification.addError("Unable to make "+node.getDesc()+" writable. It does not originate from game source material."); + } + } + } + + + + public void makeWritable(TMXMap node) { + GameSource.Type type = node.getDataType(); + if (type == null) { + Notification.addError("Unable to make "+node.getDesc()+" writable. No owning GameDataSet found."); + } else { + if (type == GameSource.Type.source) { + TMXMap clone = node.clone(); + for (GameDataElement backlink : node.getBacklinks()) { + backlink.elementChanged(node, clone); + } + node.getBacklinks().clear(); + clone.writable = true; + clone.state = GameDataElement.State.created; + alteredContent.gameMaps.addMap(clone); + } else { + Notification.addError("Unable to make "+node.getDesc()+" writable. It does not originate from game source material."); + } + } + } + + public void makeWritable(WorldmapSegment node) { + GameSource.Type type = node.getDataType(); + if (type == null) { + Notification.addError("Unable to make "+node.getDesc()+" writable. No owning GameDataSet found."); + } else { + if (type == GameSource.Type.source) { + WorldmapSegment clone = node.clone(); + for (GameDataElement backlink : node.getBacklinks()) { + backlink.elementChanged(node, clone); + } + clone.state = GameDataElement.State.init; + clone.parse(); + node.getBacklinks().clear(); + clone.writable = true; + clone.state = GameDataElement.State.created; + alteredContent.worldmap.addSegment(clone); + } else { + Notification.addError("Unable to make "+node.getDesc()+" writable. It does not originate from game source material."); + } + } + } + + /** + * + * @param node. Before calling this method, make sure that no other node with the same class exist in either created or altered. + */ + public void createElement(JSONElement node) { + node.writable = true; + if (getGameDataElement(node.getClass(), node.id) != null) { + GameDataElement existingNode = getGameDataElement(node.getClass(), node.id); + for (GameDataElement backlink : existingNode.getBacklinks()) { + backlink.elementChanged(existingNode, node); + } + existingNode.getBacklinks().clear(); + node.writable = true; + node.state = GameDataElement.State.created; + alteredContent.gameData.addElement(node); + node.link(); + } else { + createdContent.gameData.addElement(node); + node.state = GameDataElement.State.created; + node.link(); + } + fireElementAdded(node, getNodeIndex(node)); + } + + + public void moveToCreated(JSONElement target) { + target.childrenRemoved(new ArrayList()); + ((GameDataCategory)target.getParent()).remove(target); + target.state = GameDataElement.State.created; + createdContent.gameData.addElement(target); + } + + public void moveToAltered(JSONElement target) { + target.childrenRemoved(new ArrayList()); + ((GameDataCategory)target.getParent()).remove(target); + target.state = GameDataElement.State.created; + ((JSONElement) target).jsonFile = new File(baseContent.gameData.getGameDataElement(((JSONElement)target).getClass(), target.id).jsonFile.getAbsolutePath()); + alteredContent.gameData.addElement((JSONElement) target); + } + + + @Override + public GameDataSet getDataSet() { + return null; + } + + @Override + public Type getDataType() { + return null; + } + + + public String getSpritesheetsProperty(String string) { + return knownSpritesheetsProperties.getProperty(string); + } + + public void setSpritesheetsProperty(String key, String value) { + knownSpritesheetsProperties.setProperty(key, value); + } + + + @Override + public boolean isEmpty() { + return v.isEmpty(); + } + + + public void addSave(File selectedFile) { + saves.addSave(selectedFile); + } + + + public List getSpawnGroup(String spawngroup_id) { + List result = new ArrayList(); + int i = getNPCCount(); + boolean alreadyAdded = false; + int index = -1; + while (--i >= 0) { + NPC npc = getNPC(i); + if (spawngroup_id.equals(npc.spawngroup_id)) { + for (NPC present : result) { + if (present.id.equals(npc.id)) { + alreadyAdded = true; + index = result.indexOf(present); + break; + } + } + if (alreadyAdded) { + result.set(index, npc); + } else { + result.add(npc); + } + } + alreadyAdded = false; + } + return result; + } + + transient Map, List> projectElementListeners = new HashMap, List>(); + + public void addElementListener(Class interestingType, ProjectElementListener listener) { + if (projectElementListeners.get(interestingType) == null) { + projectElementListeners.put(interestingType, new ArrayList()); + } + projectElementListeners.get(interestingType).add(listener); + } + + public void removeElementListener(Class interestingType, ProjectElementListener listener) { + if (projectElementListeners.get(interestingType) != null) projectElementListeners.get(interestingType).remove(listener); + } + + public void fireElementAdded(GameDataElement element, int index) { + if (projectElementListeners.get(element.getClass()) != null) { + for (ProjectElementListener l : projectElementListeners.get(element.getClass())) { + l.elementAdded(element, index); + } + } + } + + public void fireElementRemoved(GameDataElement element, int index) { + if (projectElementListeners.get(element.getClass()) != null) { + for (ProjectElementListener l : projectElementListeners.get(element.getClass())) { + l.elementRemoved(element, index); + } + } + } + + public void generateExportPackage(final File target) { + WorkerDialog.showTaskMessage("Exporting project "+name+"...", ATContentStudio.frame, true, new Runnable() { + @Override + public void run() { + Notification.addInfo("Exporting project \""+name+"\" as "+target.getAbsolutePath()); + File tmpDir = new File(baseFolder, "tmp"); + FileUtils.deleteDir(tmpDir); + tmpDir.mkdir(); + File tmpJsonDataDir = new File(tmpDir, GameDataSet.DEFAULT_REL_PATH_IN_SOURCE); + tmpJsonDataDir.mkdirs(); + + for (File createdJsonFile : createdContent.gameData.baseFolder.listFiles()) { + FileUtils.copyFile(createdJsonFile, new File(tmpJsonDataDir, createdJsonFile.getName())); + } + writeAltered(alteredContent.gameData.actorConditions, baseContent.gameData.actorConditions, ActorCondition.class, tmpJsonDataDir); + writeAltered(alteredContent.gameData.dialogues, baseContent.gameData.dialogues, Dialogue.class, tmpJsonDataDir); + writeAltered(alteredContent.gameData.droplists, baseContent.gameData.droplists, Droplist.class, tmpJsonDataDir); + writeAltered(alteredContent.gameData.itemCategories, baseContent.gameData.itemCategories, ItemCategory.class, tmpJsonDataDir); + writeAltered(alteredContent.gameData.items, baseContent.gameData.items, Item.class, tmpJsonDataDir); + writeAltered(alteredContent.gameData.npcs, baseContent.gameData.npcs, NPC.class, tmpJsonDataDir); + writeAltered(alteredContent.gameData.quests, baseContent.gameData.quests, Quest.class, tmpJsonDataDir); + + File tmpMapDir = new File(tmpDir, TMXMapSet.DEFAULT_REL_PATH_IN_SOURCE); + tmpMapDir.mkdirs(); + for (File createdMapFile : createdContent.gameMaps.mapFolder.listFiles()) { + FileUtils.copyFile(createdMapFile, new File(tmpMapDir, createdMapFile.getName())); + } + for (File alteredMapFile : alteredContent.gameMaps.mapFolder.listFiles()) { + FileUtils.copyFile(alteredMapFile, new File(tmpMapDir, alteredMapFile.getName())); + } + + + if (!createdContent.worldmap.isEmpty() || !alteredContent.worldmap.isEmpty()) { + try { + Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); + doc.setXmlVersion("1.0"); + Element root = doc.createElement("worldmap"); + doc.appendChild(root); + + for (int i = 0; i < getWorldmapSegmentCount(); i++) { + root.appendChild(getWorldmapSegment(i).toXmlElement(doc)); + } + + Worldmap.saveDocToFile(doc, new File(tmpMapDir, "worldmap.xml")); + } catch (ParserConfigurationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + FileUtils.writeToZip(tmpDir, target); + FileUtils.deleteDir(tmpDir); + Notification.addSuccess("Project \""+name+"\" exported as "+target.getAbsolutePath()); + } + }); + } + + @SuppressWarnings("rawtypes") + public void writeAltered(GameDataCategory altered, GameDataCategory source, Class gdeClass, File targetFolder) { + Set alteredFileNames = new HashSet(); + Map> toWrite = new HashMap>(); + for (JSONElement gde : altered) { + alteredFileNames.add(gde.jsonFile.getName()); + } + for (String fName : alteredFileNames) { + for (JSONElement gde : source) { + if (gde.jsonFile.getName().equals(fName)) { + if (toWrite.get(fName) == null) { + toWrite.put(fName, new ArrayList()); + } + toWrite.get(fName).add(getGameDataElement(gdeClass, gde.id).toJson()); + } + } + } + for (String fName : toWrite.keySet()) { + File jsonFile = new File(targetFolder, fName); + StringWriter writer = new JsonPrettyWriter(); + try { + JSONArray.writeJSONString(toWrite.get(fName), writer); + } catch (IOException e) { + //Impossible with a StringWriter + } + String textToWrite = writer.toString(); + try { + FileWriter w = new FileWriter(jsonFile); + w.write(textToWrite); + w.close(); +// Notification.addSuccess("Json file "+jsonFile.getAbsolutePath()+" saved."); + } catch (IOException e) { + Notification.addError("Error while writing json file "+jsonFile.getAbsolutePath()+" : "+e.getMessage()); + e.printStackTrace(); + } + } + } + + + + + + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/ProjectElementListener.java b/src/com/gpl/rpg/atcontentstudio/model/ProjectElementListener.java new file mode 100644 index 0000000..af0daad --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/ProjectElementListener.java @@ -0,0 +1,11 @@ +package com.gpl.rpg.atcontentstudio.model; + +public interface ProjectElementListener { + + public void elementAdded(GameDataElement added, int index); + + public void elementRemoved(GameDataElement removed, int index); + + public Class getDataType(); + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/ProjectTreeNode.java b/src/com/gpl/rpg/atcontentstudio/model/ProjectTreeNode.java new file mode 100644 index 0000000..e8cd643 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/ProjectTreeNode.java @@ -0,0 +1,57 @@ +package com.gpl.rpg.atcontentstudio.model; + +import java.awt.Image; +import java.util.List; + +import javax.swing.tree.TreeNode; + +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; + +public interface ProjectTreeNode extends TreeNode { + + public void childrenAdded(List path); + public void childrenChanged(List path); + public void childrenRemoved(List path); + public void notifyCreated(); + + public String getDesc(); + + /** + * Unnecessary for anything not below a Project. Can return null. + * @return the parent Project or null. + */ + public Project getProject(); + + + /** + * Unnecessary for anything not below a GameDataSet. Can return null. + * @return the parent GameDataSet or null. + */ + public GameDataSet getDataSet(); + + public Image getIcon(); + /** + * + * @return The icon depicting this node when it is an open folder. Can be null for leaves. + */ + public Image getOpenIcon(); + /** + * + * @return The icon depicting this node when it is a closed folder. Can be null for leaves. + */ + public Image getClosedIcon(); + /** + * + * @return The icon depicting this node when it is a leaf. Should return the closed one for empty folders. + */ + public Image getLeafIcon(); + + /** + * Unnecessary for anything not below a GameSource. Can return null. + * @return the parent GameSource or null. + */ + public GameSource.Type getDataType(); + + public boolean isEmpty(); + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/SaveEvent.java b/src/com/gpl/rpg/atcontentstudio/model/SaveEvent.java new file mode 100644 index 0000000..48a51fe --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/SaveEvent.java @@ -0,0 +1,35 @@ +package com.gpl.rpg.atcontentstudio.model; + +public class SaveEvent { + + public enum Type { + moveToAltered, + moveToCreated, + alsoSave + } + + public Type type; + public GameDataElement target; + + public boolean error = false; + public String errorText; + + public SaveEvent(SaveEvent.Type type, GameDataElement target) { + this.type = type; + this.target = target; + } + + public SaveEvent(SaveEvent.Type type, GameDataElement target, boolean error, String errorText) { + this.type = type; + this.target = target; + this.error = error; + this.errorText = errorText; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SaveEvent)) return false; + else return (((SaveEvent)obj).type == this.type) && (((SaveEvent)obj).target == this.target); + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/SavedSlotCollection.java b/src/com/gpl/rpg/atcontentstudio/model/SavedSlotCollection.java new file mode 100644 index 0000000..c3859be --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/SavedSlotCollection.java @@ -0,0 +1,71 @@ +package com.gpl.rpg.atcontentstudio.model; + +import java.util.Enumeration; +import java.util.Vector; + +public class SavedSlotCollection { + + Vector contents = new Vector(); + + public void add(ProjectTreeNode node) { + contents.add(node); + } + + public int getNonEmptySize() { +// return contents.size(); + int size = 0; + for (ProjectTreeNode node : contents) { + if (!node.isEmpty()) size++; + } + return size; + } + + public Enumeration getNonEmptyElements() { +// return contents.elements(); + Vector v = new Vector(); + for (ProjectTreeNode node : contents) { + if (!node.isEmpty()) v.add(node); + } + return v.elements(); + } + + public ProjectTreeNode getNonEmptyElementAt(int index) { +// return contents.get(index); + int i = 0; + while (i < contents.size()) { + if (!contents.get(i).isEmpty()) index--; + if (index == -1) return contents.get(i); + i++; + } + return null; + } + + + public int getNonEmptyIndexOf(ProjectTreeNode node) { +// return contents.indexOf(node); + int index = contents.indexOf(node); + int trueIndex = index; + for (int i = 0; i < trueIndex; i++) { + if (contents.get(i).isEmpty()) index--; + } + return index; + } + + + public Vector getNonEmptyIterable() { +// return contents; + Vector v = new Vector(); + for (ProjectTreeNode node : contents) { + if (!node.isEmpty()) v.add(node); + } + return v; + } + + public boolean isEmpty() { +// return contents.isEmpty(); + for (ProjectTreeNode node : contents) { + if (!node.isEmpty()) return false; + } + return true; + } +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/Workspace.java b/src/com/gpl/rpg/atcontentstudio/model/Workspace.java new file mode 100644 index 0000000..2eb229f --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/Workspace.java @@ -0,0 +1,300 @@ +package com.gpl.rpg.atcontentstudio.model; + +import java.awt.Image; +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.io.SettingsSave; +import com.gpl.rpg.atcontentstudio.model.GameSource.Type; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.ui.WorkerDialog; +import com.gpl.rpg.atcontentstudio.ui.ProjectsTree.ProjectsTreeModel; + +public class Workspace implements ProjectTreeNode, Serializable { + + private static final long serialVersionUID = 7938633033601384956L; + + public static final String WS_SETTINGS_FILE = ".workspace"; + + public static Workspace activeWorkspace; + + public Preferences preferences = new Preferences(); + public File baseFolder; + public File settingsFile; + public transient List projects = new ArrayList(); + public Set projectsName = new HashSet(); + public Map projectsOpenByName = new HashMap(); + public Set knownMapSourcesFolders = new HashSet(); + + public transient ProjectsTreeModel projectsTreeModel = null; + + public Workspace(File workspaceRoot) { + baseFolder = workspaceRoot; + if (!workspaceRoot.exists()) { + try { + workspaceRoot.mkdir(); + } catch (SecurityException e) { + Notification.addError("Error creating workspace directory: "+e.getMessage()); + e.printStackTrace(); + } + } + settingsFile = new File(workspaceRoot, WS_SETTINGS_FILE); + if (!settingsFile.exists()) { + try { + settingsFile.createNewFile(); + } catch (IOException e) { + Notification.addError("Error creating workspace datafile: "+e.getMessage()); + e.printStackTrace(); + } + } + Notification.addSuccess("New workspace created: "+workspaceRoot.getAbsolutePath()); + save(); + } + + + public static void setActive(File workspaceRoot) { + Workspace w = null; + File f = new File(workspaceRoot, WS_SETTINGS_FILE); + if (!workspaceRoot.exists() || !f.exists()) { + w = new Workspace(workspaceRoot); + } else { + w = (Workspace) SettingsSave.loadInstance(f, "Workspace"); + if (w == null) { + w = new Workspace(workspaceRoot); + } else { + w.refreshTransients(); + } + } + activeWorkspace = w; + } + + public static void saveActive() { + activeWorkspace.save(); + } + + public void save() { + SettingsSave.saveInstance(this, settingsFile, "Workspace"); + } + + @Override + public Enumeration children() { + return Collections.enumeration(projects); + } + @Override + public boolean getAllowsChildren() { + return true; + } + @Override + public TreeNode getChildAt(int arg0) { + return projects.get(arg0); + } + @Override + public int getChildCount() { + return projects.size(); + } + @Override + public int getIndex(TreeNode arg0) { + return projects.indexOf(arg0); + } + @Override + public TreeNode getParent() { + return null; + } + @Override + public boolean isLeaf() { + return false; + } + @Override + public void childrenAdded(List path) { + path.add(0, this); + if (projectsTreeModel != null) projectsTreeModel.insertNode(new TreePath(path.toArray())); + } + @Override + public void childrenChanged(List path) { + path.add(0, this); + if (projectsTreeModel != null) projectsTreeModel.changeNode(new TreePath(path.toArray())); + } + @Override + public void childrenRemoved(List path) { + path.add(0, this); + if (projectsTreeModel != null) projectsTreeModel.removeNode(new TreePath(path.toArray())); + } + @Override + public void notifyCreated() { + childrenAdded(new ArrayList()); + for (ProjectTreeNode node : projects) { + if (node != null) node.notifyCreated(); + } + } + @Override + public String getDesc() { + return "Workspace: "+baseFolder.getAbsolutePath(); + } + + + public static void createProject(final String projectName, final File gameSourceFolder) { + WorkerDialog.showTaskMessage("Creating project "+projectName+"...", ATContentStudio.frame, new Runnable() { + @Override + public void run() { + if (activeWorkspace.projectsName.contains(projectName)) { + Notification.addError("A project named "+projectName+" already exists in this workspace."); + return; + } + Project p = new Project(activeWorkspace, projectName, gameSourceFolder); + activeWorkspace.projects.add(p); + activeWorkspace.projectsName.add(projectName); + activeWorkspace.projectsOpenByName.put(projectName, p.open); + activeWorkspace.knownMapSourcesFolders.add(gameSourceFolder); + p.notifyCreated(); + Notification.addSuccess("Project "+projectName+" successfully created"); + saveActive(); + } + }); + } + + public static void closeProject(Project p) { + int index = activeWorkspace.projects.indexOf(p); + if (index < 0) { + Notification.addError("Cannot close unknown project "+p.name); + return; + } + p.close(); + ClosedProject cp = new ClosedProject(activeWorkspace, p.name); + activeWorkspace.projects.set(index, cp); + activeWorkspace.projectsOpenByName.put(p.name, false); + cp.notifyCreated(); + saveActive(); + } + + public static void openProject(final ClosedProject cp) { + WorkerDialog.showTaskMessage("Opening project "+cp.name+"...", ATContentStudio.frame, new Runnable() { + @Override + public void run() { + int index = activeWorkspace.projects.indexOf(cp); + if (index < 0) { + Notification.addError("Cannot open unknown project "+cp.name); + return; + } + cp.childrenRemoved(new ArrayList()); + Project p = Project.fromFolder(activeWorkspace, new File(activeWorkspace.baseFolder, cp.name)); + p.open(); + activeWorkspace.projects.set(index, p); + activeWorkspace.projectsOpenByName.put(p.name, true); + p.notifyCreated(); + saveActive(); + } + }); + } + + public void refreshTransients() { + this.projects = new ArrayList(); + Set projectsFailed = new HashSet(); + for (String projectName : projectsName) { + if (projectsOpenByName.get(projectName)) { + File projRoot = new File(this.baseFolder, projectName); + if (projRoot.exists()) { + Project p = Project.fromFolder(this, projRoot); + if (p != null) { + projects.add(p); + } else { + Notification.addError("Failed to open project "+projectName+". Removing it from workspace (not from filesystem though)."); + projectsFailed.add(projectName); + } + } else { + Notification.addError("Unable to find project "+projectName+"'s root folder. Removing it from workspace"); + projectsFailed.add(projectName); + } + } else { + projects.add(new ClosedProject(this, projectName)); + } + } + for (String projectName : projectsFailed) { + projectsName.remove(projectName); + projectsOpenByName.remove(projectName); + } + notifyCreated(); + } + + @Override + public Project getProject() { + return null; + } + + @Override + public Image getIcon() {return null;} + @Override + public Image getClosedIcon() {return null;} + @Override + public Image getLeafIcon() {return null;} + @Override + public Image getOpenIcon() {return null;} + + + public static void deleteProject(ClosedProject cp) { + cp.childrenRemoved(new ArrayList()); + activeWorkspace.projects.remove(cp); + activeWorkspace.projectsOpenByName.remove(cp.name); + activeWorkspace.projectsName.remove(cp.name); + if (delete(new File(activeWorkspace.baseFolder, cp.name))) { + Notification.addSuccess("Closed project "+cp.name+" successfully deleted."); + } else { + Notification.addError("Error while deleting closed project "+cp.name+". Files may remain in the workspace."); + } + cp = null; + saveActive(); + } + + public static void deleteProject(Project p) { + p.childrenRemoved(new ArrayList()); + activeWorkspace.projects.remove(p); + activeWorkspace.projectsOpenByName.remove(p.name); + activeWorkspace.projectsName.remove(p.name); + if (delete(p.baseFolder)) { + Notification.addSuccess("Project "+p.name+" successfully deleted."); + } else { + Notification.addError("Error while deleting project "+p.name+". Files may remain in the workspace."); + } + p = null; + saveActive(); + } + + private static boolean delete(File f) { + boolean b = true; + if (f.isDirectory()) { + for (File c : f.listFiles()) + b &= delete(c); + } + return b&= f.delete(); + } + + @Override + public GameDataSet getDataSet() { + return null; + } + + @Override + public Type getDataType() { + return null; + } + + @Override + public boolean isEmpty() { + return projects.isEmpty(); + } + + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/gamedata/ActorCondition.java b/src/com/gpl/rpg/atcontentstudio/model/gamedata/ActorCondition.java new file mode 100644 index 0000000..7499684 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/gamedata/ActorCondition.java @@ -0,0 +1,346 @@ +package com.gpl.rpg.atcontentstudio.model.gamedata; + +import java.awt.Image; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.GameSource; + + +public class ActorCondition extends JSONElement { + + private static final long serialVersionUID = -3969824899972048507L; + + // Available from init state + //public String id; inherited. + public String icon_id; + public String display_name; + + // Available from parsed state + public ACCategory category = null; + public Integer positive = null; + public Integer stacking = null; + public RoundEffect round_effect = null; + public RoundEffect full_round_effect = null; + public AbilityEffect constant_ability_effect = null; + + public enum ACCategory { + spiritual, + mental, + physical, + blood + } + + public static class RoundEffect implements Cloneable { + // Available from parsed state + public String visual_effect = null; + public Integer hp_boost_min = null; + public Integer hp_boost_max = null; + public Integer ap_boost_min = null; + public Integer ap_boost_max = null; + + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + return null; + } + } + + public static class AbilityEffect implements Cloneable { + // Available from parsed state + public Integer max_hp_boost = null; + public Integer max_ap_boost = null; + public Integer increase_move_cost = null; + public Integer increase_use_cost = null; + public Integer increase_reequip_cost = null; + public Integer increase_attack_cost = null; + public Integer increase_attack_chance = null; + public Integer increase_damage_min = null; + public Integer increase_damage_max = null; + public Integer increase_critical_skill = null; + public Integer increase_block_chance = null; + public Integer increase_damage_resistance = null; + + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + return null; + } + } + + @Override + public String getDesc() { + return (this.state == State.modified ? "*" : "")+display_name+" ("+id+")"; + } + + @SuppressWarnings("rawtypes") + public static void fromJson(File jsonFile, GameDataCategory category) { + JSONParser parser = new JSONParser(); + FileReader reader = null; + try { + reader = new FileReader(jsonFile); + List actorConditions = (List) parser.parse(reader); + for (Object obj : actorConditions) { + Map aCondJson = (Map)obj; + ActorCondition aCond = fromJson(aCondJson); + aCond.jsonFile = jsonFile; + aCond.parent = category; + if (aCond.getDataType() == GameSource.Type.created || aCond.getDataType() == GameSource.Type.altered) { + aCond.writable = true; + } + category.add(aCond); + } + } catch (FileNotFoundException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (IOException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (ParseException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } finally { + if (reader != null) + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @SuppressWarnings("rawtypes") + public static ActorCondition fromJson(String jsonString) throws ParseException { + Map aCondJson = (Map) new JSONParser().parse(jsonString); + ActorCondition aCond = fromJson(aCondJson); + aCond.parse(aCondJson); + return aCond; + } + + @SuppressWarnings("rawtypes") + public static ActorCondition fromJson(Map aCondJson) { + ActorCondition aCond = new ActorCondition(); + aCond.icon_id = (String) aCondJson.get("iconID"); + aCond.id = (String) aCondJson.get("id"); + aCond.display_name = (String) aCondJson.get("name"); + return aCond; + } + + @SuppressWarnings("rawtypes") + @Override + public void parse(Map aCondJson) { + + if (aCondJson.get("category") != null) this.category = ACCategory.valueOf((String) aCondJson.get("category")); + this.positive = JSONElement.getInteger((Number) aCondJson.get("positive")); + Map abilityEffect = (Map) aCondJson.get("abilityEffect"); + if (abilityEffect != null) { + this.constant_ability_effect = new AbilityEffect(); + this.constant_ability_effect.increase_attack_chance = JSONElement.getInteger((Number) abilityEffect.get("increaseAttackChance")); + if (abilityEffect.get("increaseAttackDamage") != null) { + this.constant_ability_effect.increase_damage_min = JSONElement.getInteger((Number) (((Map)abilityEffect.get("increaseAttackDamage")).get("min"))); + this.constant_ability_effect.increase_damage_max = JSONElement.getInteger((Number) (((Map)abilityEffect.get("increaseAttackDamage")).get("max"))); + } + this.constant_ability_effect.max_hp_boost = JSONElement.getInteger((Number) abilityEffect.get("increaseMaxHP")); + this.constant_ability_effect.max_ap_boost = JSONElement.getInteger((Number) abilityEffect.get("increaseMaxAP")); + this.constant_ability_effect.increase_move_cost = JSONElement.getInteger((Number) abilityEffect.get("increaseMoveCost")); + this.constant_ability_effect.increase_use_cost = JSONElement.getInteger((Number) abilityEffect.get("increaseUseItemCost")); + this.constant_ability_effect.increase_reequip_cost = JSONElement.getInteger((Number) abilityEffect.get("increaseReequipCost")); + this.constant_ability_effect.increase_attack_cost = JSONElement.getInteger((Number) abilityEffect.get("increaseAttackCost")); + this.constant_ability_effect.increase_critical_skill = JSONElement.getInteger((Number) abilityEffect.get("increaseCriticalSkill")); + this.constant_ability_effect.increase_block_chance = JSONElement.getInteger((Number) abilityEffect.get("increaseBlockChance")); + this.constant_ability_effect.increase_damage_resistance = JSONElement.getInteger((Number) abilityEffect.get("increaseDamageResistance")); + } + this.stacking = JSONElement.getInteger((Number) aCondJson.get("isStacking")); + Map roundEffect = (Map) aCondJson.get("roundEffect"); + if (roundEffect != null) { + this.round_effect = new RoundEffect(); + if (roundEffect.get("increaseCurrentHP") != null) { + this.round_effect.hp_boost_max = JSONElement.getInteger((Number) (((Map)roundEffect.get("increaseCurrentHP")).get("min"))); + this.round_effect.hp_boost_min = JSONElement.getInteger((Number) (((Map)roundEffect.get("increaseCurrentHP")).get("max"))); + } + if (roundEffect.get("increaseCurrentAP") != null) { + this.round_effect.ap_boost_max = JSONElement.getInteger((Number) (((Map)roundEffect.get("increaseCurrentAP")).get("min"))); + this.round_effect.ap_boost_min = JSONElement.getInteger((Number) (((Map)roundEffect.get("increaseCurrentAP")).get("max"))); + } + this.round_effect.visual_effect = (String) roundEffect.get("visualEffectID"); + } + Map fullRoundEffect = (Map) aCondJson.get("fullRoundEffect"); + if (fullRoundEffect != null) { + this.full_round_effect = new RoundEffect(); + if (fullRoundEffect.get("increaseCurrentHP") != null) { + this.full_round_effect.hp_boost_max = JSONElement.getInteger((Number) (((Map)fullRoundEffect.get("increaseCurrentHP")).get("min"))); + this.full_round_effect.hp_boost_min = JSONElement.getInteger((Number) (((Map)fullRoundEffect.get("increaseCurrentHP")).get("max"))); + } + if (fullRoundEffect.get("increaseCurrentAP") != null) { + this.full_round_effect.ap_boost_max = JSONElement.getInteger((Number) (((Map)fullRoundEffect.get("increaseCurrentAP")).get("min"))); + this.full_round_effect.ap_boost_min = JSONElement.getInteger((Number) (((Map)fullRoundEffect.get("increaseCurrentAP")).get("max"))); + } + this.full_round_effect.visual_effect = (String) fullRoundEffect.get("visualEffectID"); + } + this.state = State.parsed; + + } + + @Override + public void link() { + if (this.state == State.created || this.state == State.modified || this.state == State.saved) { + //This type of state is unrelated to parsing/linking. + return; + } + if (this.state == State.init) { + //Not parsed yet. + this.parse(); + } else if (this.state == State.linked) { + //Already linked. + return; + } + if (this.icon_id != null) { + String spritesheetId = this.icon_id.split(":")[0]; + getProject().getSpritesheet(spritesheetId).addBacklink(this); + } + + this.state = State.linked; + } + + + public static String getStaticDesc() { + return "Actor Conditions"; + } + + + @Override + public Image getIcon() { + return getProject().getIcon(icon_id); + } + + public Image getImage() { + return getProject().getImage(icon_id); + } + + @Override + public JSONElement clone() { + ActorCondition clone = new ActorCondition(); + clone.jsonFile = this.jsonFile; + clone.state = this.state; + clone.id = this.id; + clone.display_name = this.display_name; + clone.icon_id = this.icon_id; + clone.category = this.category; + clone.positive = this.positive; + clone.stacking = this.stacking; + if (this.round_effect != null) { + clone.round_effect = (RoundEffect) this.round_effect.clone(); + } + if (this.constant_ability_effect != null) { + clone.constant_ability_effect = (AbilityEffect) constant_ability_effect.clone(); + } + if (this.full_round_effect != null) { + clone.full_round_effect = (RoundEffect) this.full_round_effect.clone(); + } + return clone; + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + //Nothing to link to. + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public Map toJson() { + Map jsonAC = new HashMap(); + jsonAC.put("id", this.id); + if (this.icon_id != null) jsonAC.put("iconID", this.icon_id); + if (this.display_name != null) jsonAC.put("name", this.display_name); + if (this.category != null) jsonAC.put("category", this.category.toString()); + if (this.positive != null && this.positive == 1) jsonAC.put("positive", this.positive); + if (this.stacking != null && this.stacking == 1) jsonAC.put("stacking", this.stacking); + if (this.round_effect != null) { + Map jsonRound = new HashMap(); + if (this.round_effect.visual_effect != null) jsonRound.put("visualEffectID", this.round_effect.visual_effect); + if (this.round_effect.hp_boost_min != null || this.round_effect.hp_boost_max != null) { + Map jsonHP = new HashMap(); + if (this.round_effect.hp_boost_min != null) jsonHP.put("min", this.round_effect.hp_boost_min); + else jsonHP.put("min", 0); + if (this.round_effect.hp_boost_max != null) jsonHP.put("max", this.round_effect.hp_boost_max); + else jsonHP.put("max", 0); + jsonRound.put("increaseCurrentHP", jsonHP); + } + if (this.round_effect.ap_boost_min != null || this.round_effect.ap_boost_max != null) { + Map jsonAP = new HashMap(); + if (this.round_effect.ap_boost_min != null) jsonAP.put("min", this.round_effect.ap_boost_min); + else jsonAP.put("min", 0); + if (this.round_effect.ap_boost_max != null) jsonAP.put("max", this.round_effect.ap_boost_max); + else jsonAP.put("max", 0); + jsonRound.put("increaseCurrentAP", jsonAP); + } + jsonAC.put("roundEffect", jsonRound); + } + if (this.full_round_effect != null) { + Map jsonFullRound = new HashMap(); + if (this.full_round_effect.visual_effect != null) jsonFullRound.put("visualEffectID", this.full_round_effect.visual_effect); + if (this.full_round_effect.hp_boost_min != null || this.full_round_effect.hp_boost_max != null) { + Map jsonHP = new HashMap(); + if (this.full_round_effect.hp_boost_min != null) jsonHP.put("min", this.full_round_effect.hp_boost_min); + else jsonHP.put("min", 0); + if (this.full_round_effect.hp_boost_max != null) jsonHP.put("max", this.full_round_effect.hp_boost_max); + else jsonHP.put("max", 0); + jsonFullRound.put("increaseCurrentHP", jsonHP); + } + if (this.full_round_effect.ap_boost_min != null || this.full_round_effect.ap_boost_max != null) { + Map jsonAP = new HashMap(); + if (this.full_round_effect.ap_boost_min != null) jsonAP.put("min", this.full_round_effect.ap_boost_min); + else jsonAP.put("min", 0); + if (this.full_round_effect.ap_boost_max != null) jsonAP.put("max", this.full_round_effect.ap_boost_max); + else jsonAP.put("max", 0); + jsonFullRound.put("increaseCurrentAP", jsonAP); + } + jsonAC.put("fullRoundEffect", jsonFullRound); + } + if (this.constant_ability_effect != null) { + Map jsonAbility = new HashMap(); + if (this.constant_ability_effect.increase_attack_chance != null) jsonAbility.put("increaseAttackChance", this.constant_ability_effect.increase_attack_chance); + if (this.constant_ability_effect.increase_damage_min != null || this.constant_ability_effect.increase_damage_max != null) { + Map jsonAD = new HashMap(); + if (this.constant_ability_effect.increase_damage_min != null) jsonAD.put("min", this.constant_ability_effect.increase_damage_min); + else jsonAD.put("min", 0); + if (this.constant_ability_effect.increase_damage_max != null) jsonAD.put("max", this.constant_ability_effect.increase_damage_max); + else jsonAD.put("max", 0); + jsonAbility.put("increaseCurrentAP", jsonAD); + } + if (this.constant_ability_effect.max_hp_boost != null) jsonAbility.put("increaseMaxHP", this.constant_ability_effect.max_hp_boost); + if (this.constant_ability_effect.max_ap_boost != null) jsonAbility.put("increaseMaxAP", this.constant_ability_effect.max_ap_boost); + if (this.constant_ability_effect.increase_move_cost != null) jsonAbility.put("increaseMoveCost", this.constant_ability_effect.increase_move_cost); + if (this.constant_ability_effect.increase_use_cost != null) jsonAbility.put("increaseUseItemCost", this.constant_ability_effect.increase_use_cost); + if (this.constant_ability_effect.increase_reequip_cost != null) jsonAbility.put("increaseUseItemCost", this.constant_ability_effect.increase_reequip_cost); + if (this.constant_ability_effect.increase_attack_cost != null) jsonAbility.put("increaseReequipCost", this.constant_ability_effect.increase_attack_cost); + if (this.constant_ability_effect.increase_critical_skill != null) jsonAbility.put("increaseCriticalSkill", this.constant_ability_effect.increase_critical_skill); + if (this.constant_ability_effect.increase_block_chance != null) jsonAbility.put("increaseBlockChance", this.constant_ability_effect.increase_block_chance); + if (this.constant_ability_effect.increase_damage_resistance != null) jsonAbility.put("increaseDamageResistance", this.constant_ability_effect.increase_damage_resistance); + jsonAC.put("abilityEffect", jsonAbility); + } + return jsonAC; + } + + @Override + public String getProjectFilename() { + return "actorconditions_"+getProject().name+".json"; + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/gamedata/Dialogue.java b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Dialogue.java new file mode 100644 index 0000000..ddc83fa --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Dialogue.java @@ -0,0 +1,446 @@ +package com.gpl.rpg.atcontentstudio.model.gamedata; + +import java.awt.Image; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.gamedata.Requirement.RequirementType; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + + +public class Dialogue extends JSONElement { + + private static final long serialVersionUID = -6872164604703134683L; + + + //Available from init state + //public String id = null; inherited. + public String message = null; + + //Available from parsed state; + public List rewards = null; + public List replies = null; + public String switch_to_npc_id = null; + + //Available from linked state; + public NPC switch_to_npc = null; + + public static class Reward { + + //Available from parsed state + public RewardType type = null; + public String reward_obj_id = null; + public Integer reward_value = null; + public String map_name = null; + + //Available from linked state + public GameDataElement reward_obj = null; + public TMXMap map = null; + + public enum RewardType { + questProgress, + dropList, + skillIncrease, + actorCondition, + alignmentChange, + giveItem, + createTimer, + spawnAll, + removeSpawnArea, + deactivateSpawnArea, + activateMapChangeArea, + deactivateMapChangeArea + } + } + + public static class Reply { + + public static final String GO_NEXT_TEXT = "N"; + public static final String SHOP_PHRASE_ID = "S"; + public static final String FIGHT_PHRASE_ID = "F"; + public static final String EXIT_PHRASE_ID = "X"; + public static final String REMOVE_PHRASE_ID = "R"; + + public static final List KEY_PHRASE_ID = Arrays.asList(new String[]{SHOP_PHRASE_ID, FIGHT_PHRASE_ID, EXIT_PHRASE_ID, REMOVE_PHRASE_ID}); + + //Available from parsed state + public String text = null; + public String next_phrase_id = null; + public List requirements = null; + + //Available from linked state + public Dialogue next_phrase = null; + + } + + @Override + public String getDesc() { + return (this.state == State.modified ? "*" : "")+id; + } + + public static String getStaticDesc() { + return "Dialogues"; + } + + @SuppressWarnings("rawtypes") + public static void fromJson(File jsonFile, GameDataCategory category) { + JSONParser parser = new JSONParser(); + FileReader reader = null; + try { + reader = new FileReader(jsonFile); + List dialogues = (List) parser.parse(reader); + for (Object obj : dialogues) { + Map dialogueJson = (Map)obj; + Dialogue dialogue = fromJson(dialogueJson); + dialogue.jsonFile = jsonFile; + dialogue.parent = category; + if (dialogue.getDataType() == GameSource.Type.created || dialogue.getDataType() == GameSource.Type.altered) { + dialogue.writable = true; + } + category.add(dialogue); + } + } catch (FileNotFoundException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (IOException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (ParseException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } finally { + if (reader != null) + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @SuppressWarnings("rawtypes") + public static Dialogue fromJson(String jsonString) throws ParseException { + Map dialogueJson = (Map) new JSONParser().parse(jsonString); + Dialogue dialogue = fromJson(dialogueJson); + dialogue.parse(dialogueJson); + return dialogue; + } + + @SuppressWarnings("rawtypes") + public static Dialogue fromJson(Map dialogueJson) { + Dialogue dialogue = new Dialogue(); + dialogue.id = (String) dialogueJson.get("id"); + dialogue.message = (String) dialogueJson.get("message"); + return dialogue; + } + + @SuppressWarnings("rawtypes") + @Override + public void parse(Map dialogueJson) { + this.switch_to_npc_id = (String) dialogueJson.get("switchToNPC"); + List repliesJson = (List) dialogueJson.get("replies"); + if (repliesJson != null && !repliesJson.isEmpty()) { + this.replies = new ArrayList(); + for (Object replyJsonObj : repliesJson) { + Map replyJson = (Map)replyJsonObj; + Reply reply = new Reply(); + reply.text = (String) replyJson.get("text"); + reply.next_phrase_id = (String) replyJson.get("nextPhraseID"); + List requirementsJson = (List) replyJson.get("requires"); + if (requirementsJson != null && !requirementsJson.isEmpty()) { + reply.requirements = new ArrayList(); + for (Object requirementJsonObj : requirementsJson) { + Map requirementJson = (Map) requirementJsonObj; + Requirement requirement = new Requirement(); + requirement.jsonFile = this.jsonFile; + requirement.parent = this; + if (requirementJson.get("requireType") != null) requirement.type = RequirementType.valueOf((String) requirementJson.get("requireType")); + requirement.required_obj_id = (String) requirementJson.get("requireID"); + requirement.required_value = JSONElement.getInteger(Integer.parseInt(requirementJson.get("value").toString())); + if (requirementJson.get("negate") != null) requirement.negated = (Boolean) requirementJson.get("negate"); + requirement.state = State.parsed; + reply.requirements.add(requirement); + } + } + this.replies.add(reply); + } + } + List rewardsJson = (List) dialogueJson.get("rewards"); + if (rewardsJson != null && !rewardsJson.isEmpty()) { + this.rewards = new ArrayList(); + for (Object rewardJsonObj : rewardsJson) { + Map rewardJson = (Map)rewardJsonObj; + Reward reward = new Reward(); + if (rewardJson.get("rewardType") != null) reward.type = Reward.RewardType.valueOf((String) rewardJson.get("rewardType")); + if (rewardJson.get("rewardID") != null) reward.reward_obj_id = (String) rewardJson.get("rewardID"); + if (rewardJson.get("value") != null) reward.reward_value = JSONElement.getInteger((Number) rewardJson.get("value")); + if (rewardJson.get("mapName") != null) reward.map_name = (String) rewardJson.get("mapName"); + this.rewards.add(reward); + } + } + this.state = State.parsed; + } + + + + + @Override + public void link() { + if (this.state == State.created || this.state == State.modified || this.state == State.saved) { + //This type of state is unrelated to parsing/linking. + return; + } + if (this.state == State.init) { + //Not parsed yet. + this.parse(); + } else if (this.state == State.linked) { + //Already linked. + return; + } + Project proj = getProject(); + if (proj == null) { + Notification.addError("Error linking dialogue "+id+". No parent project found."); + return; + } + if (this.switch_to_npc_id != null) this.switch_to_npc = proj.getNPC(this.switch_to_npc_id); + if (this.switch_to_npc != null) this.switch_to_npc.addBacklink(this); + + if (replies != null) { + for (Reply reply : replies) { + if (reply.next_phrase_id != null) { + if (!reply.next_phrase_id.equals(Reply.EXIT_PHRASE_ID) + && !reply.next_phrase_id.equals(Reply.FIGHT_PHRASE_ID) + && !reply.next_phrase_id.equals(Reply.SHOP_PHRASE_ID) + && !reply.next_phrase_id.equals(Reply.REMOVE_PHRASE_ID)) { + reply.next_phrase = proj.getDialogue(reply.next_phrase_id); + } + } + if (reply.next_phrase != null) reply.next_phrase.addBacklink(this); + if (reply.requirements != null) { + for (Requirement requirement : reply.requirements) { + requirement.link(); + } + } + } + } + if (rewards != null) { + for (Reward reward : rewards) { + if (reward.reward_obj_id != null) { + switch (reward.type) { + case activateMapChangeArea: + case deactivateMapChangeArea: + case spawnAll: + case removeSpawnArea: + case deactivateSpawnArea: + reward.map = proj.getMap(reward.map_name); + break; + case actorCondition: + reward.reward_obj = proj.getActorCondition(reward.reward_obj_id); + break; + case alignmentChange: + //Nothing to do (yet ?). + break; + case createTimer: + //Nothing to do. + break; + case dropList: + reward.reward_obj = proj.getDroplist(reward.reward_obj_id); + break; + case giveItem: + reward.reward_obj = proj.getItem(reward.reward_obj_id); + break; + case questProgress: + reward.reward_obj = proj.getQuest(reward.reward_obj_id); + break; + case skillIncrease: + //Nothing to do (yet ?). + break; + } + if (reward.reward_obj != null) reward.reward_obj.addBacklink(this); + if (reward.map != null) reward.map.addBacklink(this); + } + } + } + + this.state = State.linked; + } + + + + @Override + public Image getIcon() { + return DefaultIcons.getDialogueIcon(); + } + + + public Image getImage() { + return DefaultIcons.getDialogueImage(); + } + + @Override + public GameDataElement clone() { + Dialogue clone = new Dialogue(); + clone.jsonFile = this.jsonFile; + clone.state = this.state; + clone.id = this.id; + clone.message = this.message; + clone.switch_to_npc_id = this.switch_to_npc_id; + clone.switch_to_npc = this.switch_to_npc; + if (clone.switch_to_npc != null) { + clone.switch_to_npc.addBacklink(clone); + } + if (this.rewards != null) { + clone.rewards = new ArrayList(); + for (Reward r : this.rewards) { + Reward rclone = new Reward(); + rclone.type = r.type; + rclone.reward_obj_id = r.reward_obj_id; + rclone.reward_value = r.reward_value; + rclone.reward_obj = r.reward_obj; + if (rclone.reward_obj != null) { + rclone.reward_obj.addBacklink(clone); + } + clone.rewards.add(rclone); + } + } + if (this.replies != null) { + clone.replies = new ArrayList(); + for (Reply r : this.replies) { + Reply rclone = new Reply(); + rclone.text = r.text; + rclone.next_phrase_id = r.next_phrase_id; + rclone.next_phrase = r.next_phrase; + if (rclone.next_phrase != null) { + rclone.next_phrase.addBacklink(clone); + } + if (r.requirements != null) { + rclone.requirements = new ArrayList(); + for (Requirement req : r.requirements) { + //Special clone method, as Requirement is a special GDE, hidden from the project tree. + rclone.requirements.add((Requirement) req.clone(clone)); + } + } + clone.replies.add(rclone); + } + } + return clone; + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + if (switch_to_npc == oldOne) { + switch_to_npc = (NPC) newOne; + if (newOne != null) newOne.addBacklink(this); + } else { + if (replies != null) { + for (Reply r : replies) { + if (r.next_phrase == oldOne) { + r.next_phrase = (Dialogue) newOne; + if (newOne != null) newOne.addBacklink(this); + } + if (r.requirements != null) { + for (Requirement req : r.requirements) { + if (req.required_obj == oldOne) { + req.required_obj = newOne; + if (newOne != null) newOne.addBacklink(this); + } + } + } + } + } + if (rewards != null) { + for (Reward r : rewards) { + if (r.reward_obj == oldOne) { + r.reward_obj = newOne; + if (newOne != null) newOne.addBacklink(this); + } + } + } + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public Map toJson() { + Map dialogueJson = new HashMap(); + dialogueJson.put("id", this.id); + if (this.message != null) dialogueJson.put("message", this.message); + if (this.switch_to_npc != null) { + dialogueJson.put("switchToNPC", this.switch_to_npc.id); + } else if (this.switch_to_npc_id != null) { + dialogueJson.put("switchToNPC", this.switch_to_npc_id); + } + if (this.replies != null) { + List repliesJson = new ArrayList(); + dialogueJson.put("replies", repliesJson); + for (Reply reply : this.replies){ + Map replyJson = new HashMap(); + repliesJson.add(replyJson); + if (reply.text != null) replyJson.put("text", reply.text); + if (reply.next_phrase != null) { + replyJson.put("nextPhraseID", reply.next_phrase.id); + } else if (reply.next_phrase_id != null) { + replyJson.put("nextPhraseID", reply.next_phrase_id); + } + if (reply.requirements != null) { + List requirementsJson = new ArrayList(); + replyJson.put("requires", requirementsJson); + for (Requirement requirement : reply.requirements) { + Map requirementJson = new HashMap(); + requirementsJson.add(requirementJson); + if (requirement.type != null) requirementJson.put("requireType", requirement.type.toString()); + if (requirement.required_obj != null) { + requirementJson.put("requireID", requirement.required_obj.id); + } else if (requirement.required_obj_id != null) { + requirementJson.put("requireID", requirement.required_obj_id); + } + if (requirement.required_value != null) { + requirementJson.put("value", requirement.required_value); + } + if (requirement.negated != null) requirementJson.put("negate", requirement.negated); + } + } + } + } + if (this.rewards != null) { + List rewardsJson = new ArrayList(); + dialogueJson.put("rewards", rewardsJson); + for (Reward reward : this.rewards) { + Map rewardJson = new HashMap(); + rewardsJson.add(rewardJson); + if (reward.type != null) rewardJson.put("rewardType", reward.type.toString()); + if (reward.reward_obj != null) { + rewardJson.put("rewardID", reward.reward_obj.id); + } else if (reward.reward_obj_id != null) { + rewardJson.put("rewardID", reward.reward_obj_id); + } + if (reward.reward_value != null) rewardJson.put("value", reward.reward_value); + if (reward.map != null) { + rewardJson.put("mapName", reward.map.id); + } else if (reward.map_name != null) rewardJson.put("mapName", reward.map_name); + } + } + return dialogueJson; + } + + @Override + public String getProjectFilename() { + return "conversationlist_"+getProject().name+".json"; + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/gamedata/Droplist.java b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Droplist.java new file mode 100644 index 0000000..0f95c74 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Droplist.java @@ -0,0 +1,239 @@ +package com.gpl.rpg.atcontentstudio.model.gamedata; + +import java.awt.Image; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + + +public class Droplist extends JSONElement { + + private static final long serialVersionUID = -2903944916807382571L; + + //Available from init state + //public String id = null; inherited. + + //Available from parsed state; + public List dropped_items = null; + + //Available from linked state; + //None + + public static class DroppedItem { + //Available from parsed state; + public String item_id = null; + public Double chance = null; + public Integer quantity_min = null; + public Integer quantity_max = null; + + //Available from linked state; + public Item item = null; + } + + @Override + public String getDesc() { + return (this.state == State.modified ? "*" : "")+id; + } + + public static String getStaticDesc() { + return "Droplists"; + } + + @SuppressWarnings("rawtypes") + public static void fromJson(File jsonFile, GameDataCategory category) { + JSONParser parser = new JSONParser(); + FileReader reader = null; + try { + reader = new FileReader(jsonFile); + List droplists = (List) parser.parse(reader); + for (Object obj : droplists) { + Map droplistJson = (Map)obj; + Droplist droplist = fromJson(droplistJson); + droplist.jsonFile = jsonFile; + droplist.parent = category; + if (droplist.getDataType() == GameSource.Type.created || droplist.getDataType() == GameSource.Type.altered) { + droplist.writable = true; + } + category.add(droplist); + } + } catch (FileNotFoundException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (IOException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (ParseException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } finally { + if (reader != null) + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @SuppressWarnings("rawtypes") + public static Droplist fromJson(String jsonString) throws ParseException { + Map droplistJson = (Map) new JSONParser().parse(jsonString); + Droplist droplist = fromJson(droplistJson); + droplist.parse(droplistJson); + return droplist; + } + + @SuppressWarnings("rawtypes") + public static Droplist fromJson(Map droplistJson) { + Droplist droplist = new Droplist(); + droplist.id = (String) droplistJson.get("id"); + return droplist; + } + + @SuppressWarnings("rawtypes") + @Override + public void parse(Map droplistJson) { + List droppedItemsJson = (List) droplistJson.get("items"); + if (droppedItemsJson != null && !droppedItemsJson.isEmpty()) { + this.dropped_items = new ArrayList(); + for (Object droppedItemJsonObj : droppedItemsJson) { + Map droppedItemJson = (Map)droppedItemJsonObj; + DroppedItem droppedItem = new DroppedItem(); + droppedItem.item_id = (String) droppedItemJson.get("itemID"); + if (droppedItemJson.get("chance") != null) droppedItem.chance = JSONElement.parseChance(droppedItemJson.get("chance").toString()); + Map droppedItemQtyJson = (Map) droppedItemJson.get("quantity"); + if (droppedItemQtyJson != null) { + droppedItem.quantity_min = JSONElement.getInteger((Number) droppedItemQtyJson.get("min")); + droppedItem.quantity_max = JSONElement.getInteger((Number) droppedItemQtyJson.get("max")); + } + this.dropped_items.add(droppedItem); + } + } + this.state = State.parsed; + } + + @Override + public void link() { + if (this.state == State.created || this.state == State.modified || this.state == State.saved) { + //This type of state is unrelated to parsing/linking. + return; + } + if (this.state == State.init) { + //Not parsed yet. + this.parse(); + } else if (this.state == State.linked) { + //Already linked. + return; + } + Project proj = getProject(); + if (proj == null) { + Notification.addError("Error linking droplist "+id+". No parent project found."); + return; + } + if (dropped_items != null) { + for (DroppedItem droppedItem : dropped_items) { + if (droppedItem.item_id != null) droppedItem.item = proj.getItem(droppedItem.item_id); + if (droppedItem.item != null) droppedItem.item.addBacklink(this); + } + } + this.state = State.linked; + } + + + + public static Image getImage() { + return DefaultIcons.getDroplistImage(); + } + + @Override + public Image getIcon() { + return DefaultIcons.getDroplistIcon(); + } + + @Override + public GameDataElement clone() { + Droplist clone = new Droplist(); + clone.jsonFile = this.jsonFile; + clone.state = this.state; + clone.id = this.id; + if (this.dropped_items != null) { + clone.dropped_items = new ArrayList(); + for (DroppedItem di : this.dropped_items) { + DroppedItem diclone = new DroppedItem(); + diclone.chance = di.chance; + diclone.item_id = di.item_id; + diclone.quantity_min = di.quantity_min; + diclone.quantity_max = di.quantity_max; + diclone.item = di.item; + if (diclone.item != null) { + diclone.item.addBacklink(clone); + } + clone.dropped_items.add(diclone); + } + } + return clone; + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + if (dropped_items != null) { + for (DroppedItem di : dropped_items) { + if (di.item == oldOne) { + di.item = (Item) newOne; + if (newOne != null) newOne.addBacklink(this); + } + } + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public Map toJson() { + Map droplistJson = new HashMap(); + droplistJson.put("id", this.id); + if (this.dropped_items != null) { + List droppedItemsJson = new ArrayList(); + droplistJson.put("items", droppedItemsJson); + for (DroppedItem droppedItem : this.dropped_items) { + Map droppedItemJson = new HashMap(); + droppedItemsJson.add(droppedItemJson); + if (droppedItem.item != null) { + droppedItemJson.put("itemID", droppedItem.item.id); + } else if (droppedItem.item_id != null) { + droppedItemJson.put("itemID", droppedItem.item_id); + } + if (droppedItem.chance != null) droppedItemJson.put("chance", JSONElement.printJsonChance(droppedItem.chance)); + if (droppedItem.quantity_min != null || droppedItem.quantity_max != null) { + Map quantityJson = new HashMap(); + droppedItemJson.put("quantity", quantityJson); + if (droppedItem.quantity_min != null) quantityJson.put("min", droppedItem.quantity_min); + else quantityJson.put("min", 0); + if (droppedItem.quantity_max != null) quantityJson.put("max", droppedItem.quantity_max); + else quantityJson.put("max", 0); + } + } + } + return droplistJson; + } + + + @Override + public String getProjectFilename() { + return "droplists_"+getProject().name+".json"; + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/gamedata/GameDataCategory.java b/src/com/gpl/rpg/atcontentstudio/model/gamedata/GameDataCategory.java new file mode 100644 index 0000000..0b8983d --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/gamedata/GameDataCategory.java @@ -0,0 +1,246 @@ +package com.gpl.rpg.atcontentstudio.model.gamedata; + +import java.awt.Image; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Serializable; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.tree.TreeNode; + +import org.json.simple.JSONArray; + +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.io.JsonPrettyWriter; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.GameSource.Type; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.SaveEvent; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class GameDataCategory extends ArrayList implements ProjectTreeNode, Serializable { + + private static final long serialVersionUID = 5486008219704443733L; + + public GameDataSet parent; + public String name; + + public GameDataCategory(GameDataSet parent, String name) { + super(); + this.parent = parent; + this.name = name; + } + + @Override + public TreeNode getChildAt(int childIndex) { + return get(childIndex); + } + + @Override + public int getChildCount() { + return size(); + } + + @Override + public TreeNode getParent() { + return parent; + } + + @Override + public int getIndex(TreeNode node) { + return indexOf(node); + } + + @Override + public boolean getAllowsChildren() { + return true; + } + + @Override + public boolean isLeaf() { + return false; + } + + @Override + public Enumeration children() { + return Collections.enumeration(this); + } + @Override + public void childrenAdded(List path) { + path.add(0, this); + parent.childrenAdded(path); + } + @Override + public void childrenChanged(List path) { + path.add(0, this); + parent.childrenChanged(path); + } + @Override + public void childrenRemoved(List path) { + if (path.size() == 1 && this.getChildCount() == 1) { + childrenRemoved(new ArrayList()); + } else { + path.add(0, this); + parent.childrenRemoved(path); + } + } + @Override + public void notifyCreated() { + childrenAdded(new ArrayList()); + for (E node : this) { + node.notifyCreated(); + } + } + @Override + public String getDesc() { + return this.name; + } + + @Override + public boolean equals(Object o) { + return (o == this); + } + + @Override + public Project getProject() { + return parent.getProject(); + } + + @Override + public Image getIcon() { + return getOpenIcon(); + } + @Override + public Image getClosedIcon() { + return DefaultIcons.getJsonClosedIcon(); + } + @Override + public Image getLeafIcon() { + return DefaultIcons.getJsonClosedIcon(); + } + @Override + public Image getOpenIcon() { + return DefaultIcons.getJsonOpenIcon(); + } + + @Override + public GameDataSet getDataSet() { + return parent.getDataSet(); + } + + @Override + public Type getDataType() { + return parent.getDataType(); + } + + @SuppressWarnings("rawtypes") + public void save(File jsonFile) { + if (getDataType() != GameSource.Type.created && getDataType() != GameSource.Type.altered) { + Notification.addError("Error while trying to write json file "+jsonFile.getAbsolutePath()+" : Game Source type "+getDataType().toString()+" should not be saved."); + return; + } + List dataToSave = new ArrayList(); + for (E element : this) { + if (element.jsonFile.equals(jsonFile)) { + dataToSave.add(element.toJson()); + } + } + if (dataToSave.isEmpty()) { + if (jsonFile.delete()) { + Notification.addSuccess("File "+jsonFile.getAbsolutePath()+" deleted."); + } else { + Notification.addError("Error deleting file "+jsonFile.getAbsolutePath()); + } + + return; + } + StringWriter writer = new JsonPrettyWriter(); + try { + JSONArray.writeJSONString(dataToSave, writer); + } catch (IOException e) { + //Impossible with a StringWriter + } + String toWrite = writer.toString(); + try { + FileWriter w = new FileWriter(jsonFile); + w.write(toWrite); + w.close(); + for (E element : this) { + element.state = GameDataElement.State.saved; + } + Notification.addSuccess("Json file "+jsonFile.getAbsolutePath()+" saved."); + } catch (IOException e) { + Notification.addError("Error while writing json file "+jsonFile.getAbsolutePath()+" : "+e.getMessage()); + e.printStackTrace(); + } + + } + + + public List attemptSave(boolean checkImpactedCategory, String fileName) { + List events = new ArrayList(); + GameDataCategory impactedCategory = null; + String impactedFileName = fileName; + Map containedIds = new HashMap(); + for (JSONElement node : this) { + if (node.getDataType() == GameSource.Type.created && getProject().baseContent.gameData.getGameDataElement(node.getClass(), node.id) != null) { + if (getProject().alteredContent.gameData.getGameDataElement(node.getClass(), node.id) != null) { + events.add(new SaveEvent(SaveEvent.Type.moveToAltered, node, true, "Element ID matches one already present in the altered game content. Change this ID before saving.")); + } else { + events.add(new SaveEvent(SaveEvent.Type.moveToAltered, node)); + impactedFileName = getProject().baseContent.gameData.getGameDataElement(node.getClass(), node.id).jsonFile.getName(); + impactedCategory = getProject().alteredContent.gameData.getCategory(node.getClass()); + } + } else if (this.getDataType() == GameSource.Type.altered && getProject().baseContent.gameData.getGameDataElement(node.getClass(), node.id) == null) { + if (getProject().createdContent.gameData.getGameDataElement(node.getClass(), node.id) != null) { + events.add(new SaveEvent(SaveEvent.Type.moveToCreated, node, true, "Element ID matches one already present in the created game content. Change this ID before saving.")); + } else { + events.add(new SaveEvent(SaveEvent.Type.moveToCreated, node)); + impactedCategory = getProject().createdContent.gameData.getCategory(node.getClass()); + impactedFileName = node.getProjectFilename(); + } + } else if (node.state == GameDataElement.State.modified) { + events.add(new SaveEvent(SaveEvent.Type.alsoSave, node)); + } + if (containedIds.containsKey(node.id)) { + containedIds.put(node.id, containedIds.get(node.id) + 1); + } else { + containedIds.put(node.id, 1); + } + } + for (String key : containedIds.keySet()) { + if (containedIds.get(key) > 1) { + E node = null; + for (E n : this) { + if (key.equals(n.id)) { + node = n; + break; + } + } + events.add(new SaveEvent(SaveEvent.Type.alsoSave, node, true, "There are "+containedIds.get(node.id)+" elements with this ID in this category. Change the conflicting IDs before saving.")); + } + } + if (checkImpactedCategory && impactedCategory != null) { + events.addAll(impactedCategory.attemptSave(false, impactedFileName)); + } + return events; + } + + public boolean remove(E o) { + int index = getProject().getNodeIndex(o); + boolean result = super.remove(o); + getProject().fireElementRemoved(o, index); + return result; + } + + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/gamedata/GameDataSet.java b/src/com/gpl/rpg/atcontentstudio/model/gamedata/GameDataSet.java new file mode 100644 index 0000000..0eb5ba6 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/gamedata/GameDataSet.java @@ -0,0 +1,364 @@ +package com.gpl.rpg.atcontentstudio.model.gamedata; + +import java.awt.Image; +import java.io.File; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import javax.swing.tree.TreeNode; + +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.GameSource.Type; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.SavedSlotCollection; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + + +public class GameDataSet implements ProjectTreeNode, Serializable { + + private static final long serialVersionUID = -8558067213826970968L; + + public static final String DEFAULT_REL_PATH_IN_SOURCE = "res"+File.separator+"raw"+File.separator; + public static final String DEFAULT_REL_PATH_IN_PROJECT = "json"+File.separator; + + public File baseFolder; + + public GameDataCategory actorConditions; + public GameDataCategory dialogues; + public GameDataCategory droplists; + public GameDataCategory items; + public GameDataCategory itemCategories; + public GameDataCategory npcs; + public GameDataCategory quests; + + public GameSource parent; + public SavedSlotCollection v; + + public GameDataSet(GameSource source) { + + this.parent = source; + v = new SavedSlotCollection(); + + if (parent.type.equals(GameSource.Type.altered) || parent.type.equals(GameSource.Type.created)) { + this.baseFolder = new File(parent.baseFolder, GameDataSet.DEFAULT_REL_PATH_IN_PROJECT); + if (!baseFolder.exists()) this.baseFolder.mkdirs(); + } else if (parent.type.equals(GameSource.Type.source)) { + this.baseFolder = new File(source.baseFolder, DEFAULT_REL_PATH_IN_SOURCE); + } + + actorConditions = new GameDataCategory(this, ActorCondition.getStaticDesc()); + dialogues = new GameDataCategory(this, Dialogue.getStaticDesc()); + droplists = new GameDataCategory(this, Droplist.getStaticDesc()); + items = new GameDataCategory(this, Item.getStaticDesc()); + itemCategories = new GameDataCategory(this, ItemCategory.getStaticDesc()); + npcs = new GameDataCategory(this, NPC.getStaticDesc()); + quests = new GameDataCategory(this, Quest.getStaticDesc()); + + v.add(actorConditions); + v.add(dialogues); + v.add(droplists); + v.add(items); + v.add(itemCategories); + v.add(npcs); + v.add(quests); + + //Start parsing to populate categories' content. + if (parent.type != GameSource.Type.referenced) { + for (File f : baseFolder.listFiles()) { + if (f.getName().startsWith("actorconditions_")) { + ActorCondition.fromJson(f, actorConditions); + } else if (f.getName().startsWith("conversationlist_")) { + Dialogue.fromJson(f, dialogues); + } else if (f.getName().startsWith("droplists_")) { + Droplist.fromJson(f, droplists); + } else if (f.getName().startsWith("itemlist_")) { + Item.fromJson(f, items); + } else if (f.getName().startsWith("itemcategories_")) { + ItemCategory.fromJson(f, itemCategories); + } else if (f.getName().startsWith("monsterlist_")) { + NPC.fromJson(f, npcs); + } else if (f.getName().startsWith("questlist")) { + Quest.fromJson(f, quests); + } + } + } + } + + @Override + public Enumeration children() { + return v.getNonEmptyElements(); + } + @Override + public boolean getAllowsChildren() { + return true; + } + @Override + public TreeNode getChildAt(int arg0) { + return v.getNonEmptyElementAt(arg0); + } + @Override + public int getChildCount() { + return v.getNonEmptySize(); + } + @Override + public int getIndex(TreeNode arg0) { + return v.getNonEmptyIndexOf((ProjectTreeNode) arg0); + } + @Override + public TreeNode getParent() { + return parent; + } + @Override + public boolean isLeaf() { + return false; + } + @Override + public void childrenAdded(List path) { + path.add(0, this); + parent.childrenAdded(path); + } + @Override + public void childrenChanged(List path) { + path.add(0, this); + parent.childrenChanged(path); + } + @Override + public void childrenRemoved(List path) { + if (path.size() == 1 && this.v.getNonEmptySize() == 1) { + childrenRemoved(new ArrayList()); + } else { + path.add(0, this); + parent.childrenRemoved(path); + } + } + @Override + public void notifyCreated() { + childrenAdded(new ArrayList()); + for (ProjectTreeNode node : v.getNonEmptyIterable()) { + node.notifyCreated(); + } + } + @Override + public String getDesc() { + return "JSON data"; + } + + + public void refreshTransients() { + + } + + public ActorCondition getActorCondition(String id) { + if (actorConditions == null) return null; + for (ActorCondition gde : actorConditions) { + if (id.equals(gde.id)){ + return gde; + } + } + return null; + } + + public Dialogue getDialogue(String id) { + if (dialogues == null) return null; + for (Dialogue gde : dialogues) { + if (id.equals(gde.id)){ + return gde; + } + } + return null; + } + + public Droplist getDroplist(String id) { + if (droplists == null) return null; + for (Droplist gde : droplists) { + if (id.equals(gde.id)){ + return gde; + } + } + return null; + } + + public Item getItem(String id) { + if (items == null) return null; + for (Item gde : items) { + if (id.equals(gde.id)){ + return gde; + } + } + return null; + } + + public ItemCategory getItemCategory(String id) { + if (itemCategories == null) return null; + for (ItemCategory gde : itemCategories) { + if (id.equals(gde.id)){ + return gde; + } + } + return null; + } + + public NPC getNPC(String id) { + if (npcs == null) return null; + for (NPC gde : npcs) { + if (id.equals(gde.id)){ + return gde; + } + } + return null; + } + + public Quest getQuest(String id) { + if (quests == null) return null; + for (Quest gde : quests) { + if (id.equals(gde.id)){ + return gde; + } + } + return null; + } + + @Override + public Project getProject() { + return parent.getProject(); + } + + + @Override + public Image getIcon() { + return getOpenIcon(); + } + @Override + public Image getClosedIcon() { + return DefaultIcons.getJsonClosedIcon(); + } + @Override + public Image getLeafIcon() { + return DefaultIcons.getJsonClosedIcon(); + } + @Override + public Image getOpenIcon() { + return DefaultIcons.getJsonOpenIcon(); + } + + public void addElement(JSONElement node) { + ProjectTreeNode higherEmptyParent = this; + while (higherEmptyParent != null) { + if (higherEmptyParent.getParent() != null && ((ProjectTreeNode)higherEmptyParent.getParent()).isEmpty()) higherEmptyParent = (ProjectTreeNode)higherEmptyParent.getParent(); + else break; + } + if (higherEmptyParent == this && !this.isEmpty()) higherEmptyParent = null; + if (node instanceof ActorCondition) { + if (actorConditions.isEmpty() && higherEmptyParent == null) higherEmptyParent = actorConditions; + actorConditions.add((ActorCondition) node); + node.parent = actorConditions; + } else if (node instanceof Dialogue) { + if (dialogues.isEmpty() && higherEmptyParent == null) higherEmptyParent = dialogues; + dialogues.add((Dialogue) node); + node.parent = dialogues; + } else if (node instanceof Droplist) { + if (droplists.isEmpty() && higherEmptyParent == null) higherEmptyParent = droplists; + droplists.add((Droplist) node); + node.parent = droplists; + } else if (node instanceof Item) { + if (items.isEmpty() && higherEmptyParent == null) higherEmptyParent = items; + items.add((Item) node); + node.parent = items; + } else if (node instanceof ItemCategory) { + if (itemCategories.isEmpty() && higherEmptyParent == null) higherEmptyParent = itemCategories; + itemCategories.add((ItemCategory) node); + node.parent = itemCategories; + } else if (node instanceof NPC) { + if (npcs.isEmpty() && higherEmptyParent == null) higherEmptyParent = npcs; + npcs.add((NPC) node); + node.parent = npcs; + } else if (node instanceof Quest) { + if (quests.isEmpty() && higherEmptyParent == null) higherEmptyParent = quests; + quests.add((Quest) node); + node.parent = quests; + } else { + Notification.addError("Cannot add "+node.getDesc()+". Unknown data type."); + return; + } + if (node.jsonFile != null && parent.type == GameSource.Type.altered) { + //Altered node. + node.jsonFile = new File(this.baseFolder, node.jsonFile.getName()); + } else { + //Created node. + node.jsonFile = new File(this.baseFolder, node.getProjectFilename()); + } + if (higherEmptyParent != null) higherEmptyParent.notifyCreated(); + else node.notifyCreated(); + } + + + @Override + public GameDataSet getDataSet() { + return this; + } + + @Override + public Type getDataType() { + return parent.getDataType(); + } + + @Override + public boolean isEmpty() { + return v.isEmpty(); + } + + public JSONElement getGameDataElement(Class gdeClass, String id) { + if (gdeClass == ActorCondition.class) { + return getActorCondition(id); + } + if (gdeClass == Dialogue.class) { + return getDialogue(id); + } + if (gdeClass == Droplist.class) { + return getDroplist(id); + } + if (gdeClass == ItemCategory.class) { + return getItemCategory(id); + } + if (gdeClass == Item.class) { + return getItem(id); + } + if (gdeClass == NPC.class) { + return getNPC(id); + } + if (gdeClass == Quest.class) { + return getQuest(id); + } + return null; + } + + public GameDataCategory getCategory(Class gdeClass) { + if (gdeClass == ActorCondition.class) { + return actorConditions; + } + if (gdeClass == Dialogue.class) { + return dialogues; + } + if (gdeClass == Droplist.class) { + return droplists; + } + if (gdeClass == ItemCategory.class) { + return itemCategories; + } + if (gdeClass == Item.class) { + return items; + } + if (gdeClass == NPC.class) { + return npcs; + } + if (gdeClass == Quest.class) { + return quests; + } + return null; + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/gamedata/Item.java b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Item.java new file mode 100644 index 0000000..4eb9f1b --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Item.java @@ -0,0 +1,723 @@ +package com.gpl.rpg.atcontentstudio.model.gamedata; + +import java.awt.Image; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.Project; + +public class Item extends JSONElement { + + private static final long serialVersionUID = -516874303672548638L; + + //Available from init state + //public String id = null; inherited. + public String name = null; + public DisplayType display_type = null; + public String icon_id = null; + + //Available from parsed state + public Integer has_manual_price = null; + public Integer base_market_cost = null; + public String category_id = null; + public String description = null; + public HitEffect hit_effect = null; + public KillEffect kill_effect = null; + public EquipEffect equip_effect = null; + + //Available from linked state + public ItemCategory category = null; + + + + public static class KillEffect { + //Available from parsed state + public Integer hp_boost_min = null; + public Integer hp_boost_max = null; + public Integer ap_boost_min = null; + public Integer ap_boost_max = null; + public List conditions_source = null; + + } + + //Inheritance for code compactness, not semantically correct. + public static class HitEffect extends KillEffect { + //Available from parsed state + public List conditions_target = null; + } + + public static class EquipEffect { + //Available from parsed state + public Integer damage_boost_min = null; + public Integer damage_boost_max = null; + public Integer max_hp_boost = null; + public Integer max_ap_boost = null; + public List conditions = null; + public Integer increase_move_cost = null; + public Integer increase_use_item_cost = null; + public Integer increase_reequip_cost = null; + public Integer increase_attack_cost = null; + public Integer increase_attack_chance = null; + public Integer increase_critical_skill = null; + public Integer increase_block_chance = null; + public Integer increase_damage_resistance = null; + public Double critical_multiplier = null; + } + + public static class ConditionEffect { + //Available from parsed state + public Integer magnitude = null; + public String condition_id = null; + + //Available from linked state + public ActorCondition condition = null; + } + + public static class TimedConditionEffect extends ConditionEffect { + //Available from parsed state + public Integer duration = null; + public Double chance = null; + } + + public static enum DisplayType { + ordinary, + quest, + extraordinary, + legendary, + rare + } + + @Override + public String getDesc() { + return (this.state == State.modified ? "*" : "")+name+" ("+id+")"; + } + + public static String getStaticDesc() { + return "Items"; + } + + @SuppressWarnings("rawtypes") + public static void fromJson(File jsonFile, GameDataCategory category) { + JSONParser parser = new JSONParser(); + FileReader reader = null; + try { + reader = new FileReader(jsonFile); + List items = (List) parser.parse(reader); + for (Object obj : items) { + Map itemJson = (Map)obj; + Item item = fromJson(itemJson); + item.jsonFile = jsonFile; + item.parent = category; + if (item.getDataType() == GameSource.Type.created || item.getDataType() == GameSource.Type.altered) { + item.writable = true; + } + category.add(item); + } + } catch (FileNotFoundException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (IOException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (ParseException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } finally { + if (reader != null) + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @SuppressWarnings("rawtypes") + public static Item fromJson(String jsonString) throws ParseException { + Map itemJson = (Map) new JSONParser().parse(jsonString); + Item item = fromJson(itemJson); + item.parse(itemJson); + return item; + } + + @SuppressWarnings("rawtypes") + public static Item fromJson(Map itemJson) { + Item item = new Item(); + item.icon_id = (String) itemJson.get("iconID"); + item.id = (String) itemJson.get("id"); + item.name = (String) itemJson.get("name"); + if (itemJson.get("displaytype") != null) item.display_type = DisplayType.valueOf((String) itemJson.get("displaytype")); + return item; + } + + @SuppressWarnings("rawtypes") + @Override + public void parse(Map itemJson) { + + this.has_manual_price = JSONElement.getInteger((Number) itemJson.get("hasManualPrice")); + this.base_market_cost = JSONElement.getInteger((Number) itemJson.get("baseMarketCost")); + //TODO change the debug json data.... +// this.category_id = (String) itemJson.get("category"); + if (itemJson.get("category") != null) this.category_id = (String) itemJson.get("category").toString(); + this.description = (String) itemJson.get("description"); + + Map equipEffect = (Map) itemJson.get("equipEffect"); + if (equipEffect != null) { + this.equip_effect = new EquipEffect(); + if (equipEffect.get("increaseAttackDamage") != null) { + this.equip_effect.damage_boost_min = JSONElement.getInteger((Number) (((Map)equipEffect.get("increaseAttackDamage")).get("min"))); + this.equip_effect.damage_boost_max = JSONElement.getInteger((Number) (((Map)equipEffect.get("increaseAttackDamage")).get("max"))); + } + this.equip_effect.max_hp_boost = JSONElement.getInteger((Number) equipEffect.get("increaseMaxHP")); + this.equip_effect.max_ap_boost = JSONElement.getInteger((Number) equipEffect.get("increaseMaxAP")); + this.equip_effect.increase_move_cost = JSONElement.getInteger((Number) equipEffect.get("increaseMoveCost")); + this.equip_effect.increase_use_item_cost = JSONElement.getInteger((Number) equipEffect.get("increaseUseItemCost")); + this.equip_effect.increase_reequip_cost = JSONElement.getInteger((Number) equipEffect.get("increaseReequipCost")); + this.equip_effect.increase_attack_cost = JSONElement.getInteger((Number) equipEffect.get("increaseAttackCost")); + this.equip_effect.increase_attack_chance = JSONElement.getInteger((Number) equipEffect.get("increaseAttackChance")); + this.equip_effect.increase_critical_skill = JSONElement.getInteger((Number) equipEffect.get("increaseCriticalSkill")); + this.equip_effect.increase_block_chance = JSONElement.getInteger((Number) equipEffect.get("increaseBlockChance")); + this.equip_effect.increase_damage_resistance = JSONElement.getInteger((Number) equipEffect.get("increaseDamageResistance")); + //TODO correct game data, to unify format. +// this.equip_effect.critical_multiplier = JSONElement.getDouble((Number) equipEffect.get("setCriticalMultiplier")); + if (equipEffect.get("setCriticalMultiplier") != null) this.equip_effect.critical_multiplier = JSONElement.getDouble(Double.parseDouble(equipEffect.get("setCriticalMultiplier").toString())); + + List conditionsJson = (List) equipEffect.get("addedConditions"); + if (conditionsJson != null && !conditionsJson.isEmpty()) { + this.equip_effect.conditions = new ArrayList(); + for (Object conditionJsonObj : conditionsJson) { + Map conditionJson = (Map)conditionJsonObj; + ConditionEffect condition = new ConditionEffect(); + condition.condition_id = (String) conditionJson.get("condition"); + condition.magnitude = JSONElement.getInteger((Number) conditionJson.get("magnitude")); + this.equip_effect.conditions.add(condition); + } + } + } + + Map hitEffect = (Map) itemJson.get("hitEffect"); + if (hitEffect != null) { + this.hit_effect = new HitEffect(); + if (hitEffect.get("increaseCurrentHP") != null) { + this.hit_effect.hp_boost_min = JSONElement.getInteger((Number) (((Map)hitEffect.get("increaseCurrentHP")).get("min"))); + this.hit_effect.hp_boost_max = JSONElement.getInteger((Number) (((Map)hitEffect.get("increaseCurrentHP")).get("max"))); + } + if (hitEffect.get("increaseCurrentAP") != null) { + this.hit_effect.ap_boost_min = JSONElement.getInteger((Number) (((Map)hitEffect.get("increaseCurrentAP")).get("min"))); + this.hit_effect.ap_boost_max = JSONElement.getInteger((Number) (((Map)hitEffect.get("increaseCurrentAP")).get("max"))); + } + List conditionsSourceJson = (List) hitEffect.get("conditionsSource"); + if (conditionsSourceJson != null && !conditionsSourceJson.isEmpty()) { + this.hit_effect.conditions_source = new ArrayList(); + for (Object conditionJsonObj : conditionsSourceJson) { + Map conditionJson = (Map)conditionJsonObj; + TimedConditionEffect condition = new TimedConditionEffect(); + condition.condition_id = (String) conditionJson.get("condition"); + condition.magnitude = JSONElement.getInteger((Number) conditionJson.get("magnitude")); + condition.duration = JSONElement.getInteger((Number) conditionJson.get("duration")); + if (conditionJson.get("chance") != null) condition.chance = JSONElement.parseChance(conditionJson.get("chance").toString()); + this.hit_effect.conditions_source.add(condition); + } + } + List conditionsTargetJson = (List) hitEffect.get("conditionsTarget"); + if (conditionsTargetJson != null && !conditionsTargetJson.isEmpty()) { + this.hit_effect.conditions_target = new ArrayList(); + for (Object conditionJsonObj : conditionsTargetJson) { + Map conditionJson = (Map)conditionJsonObj; + TimedConditionEffect condition = new TimedConditionEffect(); + condition.condition_id = (String) conditionJson.get("condition"); + condition.magnitude = JSONElement.getInteger((Number) conditionJson.get("magnitude")); + condition.duration = JSONElement.getInteger((Number) conditionJson.get("duration")); + if (conditionJson.get("chance") != null) condition.chance = JSONElement.parseChance(conditionJson.get("chance").toString()); + this.hit_effect.conditions_target.add(condition); + } + } + } + + Map killEffect = (Map) itemJson.get("killEffect"); + if (killEffect == null) { + killEffect = (Map) itemJson.get("useEffect"); + } + if (killEffect != null) { + this.kill_effect = new KillEffect(); + if (killEffect.get("increaseCurrentHP") != null) { + this.kill_effect.hp_boost_min = JSONElement.getInteger((Number) (((Map)killEffect.get("increaseCurrentHP")).get("min"))); + this.kill_effect.hp_boost_max = JSONElement.getInteger((Number) (((Map)killEffect.get("increaseCurrentHP")).get("max"))); + } + if (killEffect.get("increaseCurrentAP") != null) { + this.kill_effect.ap_boost_min = JSONElement.getInteger((Number) (((Map)killEffect.get("increaseCurrentAP")).get("min"))); + this.kill_effect.ap_boost_max = JSONElement.getInteger((Number) (((Map)killEffect.get("increaseCurrentAP")).get("max"))); + } + List conditionsSourceJson = (List) killEffect.get("conditionsSource"); + if (conditionsSourceJson != null && !conditionsSourceJson.isEmpty()) { + this.kill_effect.conditions_source = new ArrayList(); + for (Object conditionJsonObj : conditionsSourceJson) { + Map conditionJson = (Map)conditionJsonObj; + TimedConditionEffect condition = new TimedConditionEffect(); + condition.condition_id = (String) conditionJson.get("condition"); + condition.magnitude = JSONElement.getInteger((Number) conditionJson.get("magnitude")); + condition.duration = JSONElement.getInteger((Number) conditionJson.get("duration")); + if (conditionJson.get("chance") != null) condition.chance = JSONElement.parseChance(conditionJson.get("chance").toString()); + this.kill_effect.conditions_source.add(condition); + } + } + } + this.state = State.parsed; + } + + + @Override + public void link() { + if (this.state == State.created || this.state == State.modified || this.state == State.saved) { + //This type of state is unrelated to parsing/linking. + return; + } + if (this.state == State.init) { + //Not parsed yet. + this.parse(); + } else if (this.state == State.linked) { + //Already linked. + return; + } + Project proj = getProject(); + if (proj == null) { + Notification.addError("Error linking item "+id+". No parent project found."); + return; + } + + if (this.icon_id != null) { + String spritesheetId = this.icon_id.split(":")[0]; + proj.getSpritesheet(spritesheetId).addBacklink(this); + } + if (this.category_id != null) this.category = proj.getItemCategory(this.category_id); + if (this.category != null) this.category.addBacklink(this); + if (this.equip_effect != null && this.equip_effect.conditions != null) { + for (ConditionEffect ce : this.equip_effect.conditions) { + if (ce.condition_id != null) ce.condition = proj.getActorCondition(ce.condition_id); + if (ce.condition != null) ce.condition.addBacklink(this); + } + } + if (this.hit_effect != null && this.hit_effect.conditions_source != null) { + for (TimedConditionEffect ce : this.hit_effect.conditions_source) { + if (ce.condition_id != null) ce.condition = proj.getActorCondition(ce.condition_id); + if (ce.condition != null) ce.condition.addBacklink(this); + } + } + if (this.hit_effect != null && this.hit_effect.conditions_target != null) { + for (TimedConditionEffect ce : this.hit_effect.conditions_target) { + if (ce.condition_id != null) ce.condition = proj.getActorCondition(ce.condition_id); + if (ce.condition != null) ce.condition.addBacklink(this); + } + } + if (this.kill_effect != null && this.kill_effect.conditions_source != null) { + for (TimedConditionEffect ce : this.kill_effect.conditions_source) { + if (ce.condition_id != null) ce.condition = proj.getActorCondition(ce.condition_id); + if (ce.condition != null) ce.condition.addBacklink(this); + } + } + this.state = State.linked; + } + + @Override + public Image getIcon() { + return getProject().getIcon(icon_id); + } + + public Image getImage() { + return getProject().getImage(icon_id); + } + + @Override + public GameDataElement clone() { + Item clone = new Item(); + clone.jsonFile = this.jsonFile; + clone.state = this.state; + clone.id = this.id; + clone.name = this.name; + clone.icon_id = this.icon_id; + clone.base_market_cost = this.base_market_cost; + clone.category = this.category; + if (clone.category != null) { + clone.category.addBacklink(clone); + } + clone.category_id = this.category_id; + clone.description = this.description; + clone.display_type = this.display_type; + clone.has_manual_price = this.has_manual_price; + if (this.equip_effect != null) { + clone.equip_effect = new EquipEffect(); + clone.equip_effect.critical_multiplier = this.equip_effect.critical_multiplier; + clone.equip_effect.damage_boost_max = this.equip_effect.damage_boost_max; + clone.equip_effect.damage_boost_min = this.equip_effect.damage_boost_min; + clone.equip_effect.increase_attack_chance = this.equip_effect.increase_attack_chance; + clone.equip_effect.increase_attack_cost = this.equip_effect.increase_attack_cost; + clone.equip_effect.increase_block_chance = this.equip_effect.increase_block_chance; + clone.equip_effect.increase_critical_skill = this.equip_effect.increase_critical_skill; + clone.equip_effect.increase_damage_resistance = this.equip_effect.increase_damage_resistance; + clone.equip_effect.increase_move_cost = this.equip_effect.increase_move_cost; + clone.equip_effect.increase_reequip_cost = this.equip_effect.increase_reequip_cost; + clone.equip_effect.increase_use_item_cost = this.equip_effect.increase_use_item_cost; + clone.equip_effect.max_ap_boost = this.equip_effect.max_ap_boost; + clone.equip_effect.max_hp_boost = this.equip_effect.max_hp_boost; + if (this.equip_effect.conditions != null) { + clone.equip_effect.conditions = new ArrayList(); + for (ConditionEffect c : this.equip_effect.conditions) { + ConditionEffect cclone = new ConditionEffect(); + cclone.magnitude = c.magnitude; + cclone.condition_id = c.condition_id; + cclone.condition = c.condition; + if (cclone.condition != null) { + cclone.condition.addBacklink(clone); + } + clone.equip_effect.conditions.add(cclone); + } + } + } + if (this.hit_effect != null) { + clone.hit_effect = new HitEffect(); + clone.hit_effect.ap_boost_max = this.hit_effect.ap_boost_max; + clone.hit_effect.ap_boost_min = this.hit_effect.ap_boost_min; + clone.hit_effect.hp_boost_max = this.hit_effect.hp_boost_max; + clone.hit_effect.hp_boost_min = this.hit_effect.hp_boost_min; + if (this.hit_effect.conditions_source != null) { + clone.hit_effect.conditions_source = new ArrayList(); + for (TimedConditionEffect c : this.hit_effect.conditions_source) { + TimedConditionEffect cclone = new TimedConditionEffect(); + cclone.magnitude = c.magnitude; + cclone.condition_id = c.condition_id; + cclone.condition = c.condition; + cclone.chance = c.chance; + cclone.duration = c.duration; + if (cclone.condition != null) { + cclone.condition.addBacklink(clone); + } + clone.hit_effect.conditions_source.add(cclone); + } + } + if (this.hit_effect.conditions_target != null) { + clone.hit_effect.conditions_target = new ArrayList(); + for (TimedConditionEffect c : this.hit_effect.conditions_target) { + TimedConditionEffect cclone = new TimedConditionEffect(); + cclone.magnitude = c.magnitude; + cclone.condition_id = c.condition_id; + cclone.condition = c.condition; + cclone.chance = c.chance; + cclone.duration = c.duration; + if (cclone.condition != null) { + cclone.condition.addBacklink(clone); + } + clone.hit_effect.conditions_target.add(cclone); + } + } + } + if (this.kill_effect != null) { + clone.kill_effect = new KillEffect(); + clone.kill_effect.ap_boost_max = this.kill_effect.ap_boost_max; + clone.kill_effect.ap_boost_min = this.kill_effect.ap_boost_min; + clone.kill_effect.hp_boost_max = this.kill_effect.hp_boost_max; + clone.kill_effect.hp_boost_min = this.kill_effect.hp_boost_min; + if (this.kill_effect.conditions_source != null) { + clone.kill_effect.conditions_source = new ArrayList(); + for (TimedConditionEffect c : this.kill_effect.conditions_source) { + TimedConditionEffect cclone = new TimedConditionEffect(); + cclone.magnitude = c.magnitude; + cclone.condition_id = c.condition_id; + cclone.condition = c.condition; + cclone.chance = c.chance; + cclone.duration = c.duration; + if (cclone.condition != null) { + cclone.condition.addBacklink(clone); + } + clone.kill_effect.conditions_source.add(cclone); + } + } + } + return clone; + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + if (this.category == oldOne) { + this.category = (ItemCategory) newOne; + if (newOne != null) newOne.addBacklink(this); + } else { + if (this.equip_effect != null && this.equip_effect.conditions != null) { + for (ConditionEffect c : this.equip_effect.conditions) { + if (c.condition == oldOne) { + c.condition = (ActorCondition) newOne; + if (newOne != null) newOne.addBacklink(this); + } + } + } + if (this.hit_effect != null && this.hit_effect.conditions_source != null) { + for (TimedConditionEffect c : this.hit_effect.conditions_source) { + if (c.condition == oldOne) { + c.condition = (ActorCondition) newOne; + if (newOne != null) newOne.addBacklink(this); + } + } + } + if (this.hit_effect != null && this.hit_effect.conditions_target != null) { + for (TimedConditionEffect c : this.hit_effect.conditions_target) { + if (c.condition == oldOne) { + c.condition = (ActorCondition) newOne; + if (newOne != null) newOne.addBacklink(this); + } + } + } + + if (this.kill_effect != null && this.kill_effect.conditions_source != null) { + for (TimedConditionEffect c : this.kill_effect.conditions_source) { + if (c.condition == oldOne) { + c.condition = (ActorCondition) newOne; + if (newOne != null) newOne.addBacklink(this); + } + } + } + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public Map toJson() { + Map itemJson = new HashMap(); + itemJson.put("id", this.id); + if (this.icon_id != null) itemJson.put("iconID", this.icon_id); + if (this.name != null) itemJson.put("name", this.name); + if (this.has_manual_price != null) itemJson.put("hasManualPrice", this.has_manual_price); + if (this.base_market_cost != null) itemJson.put("baseMarketCost", this.base_market_cost); + if (this.category != null) { + itemJson.put("category", this.category.id); + } else if (this.category_id != null) { + itemJson.put("category", this.category_id); + } + if (this.description != null) itemJson.put("description", this.description); + if (this.equip_effect != null) { + Map equipEffectJson = new HashMap(); + itemJson.put("equipEffect", equipEffectJson); + if (this.equip_effect.damage_boost_min != null || this.equip_effect.damage_boost_max != null) { + Map damageJson = new HashMap(); + equipEffectJson.put("increaseAttackDamage", damageJson); + if (this.equip_effect.damage_boost_min != null) damageJson.put("min", this.equip_effect.damage_boost_min); + else damageJson.put("min", 0); + if (this.equip_effect.damage_boost_max != null) damageJson.put("max", this.equip_effect.damage_boost_max); + else damageJson.put("max", 0); + } + if (this.equip_effect.max_hp_boost != null) equipEffectJson.put("increaseMaxHP", this.equip_effect.max_hp_boost); + if (this.equip_effect.max_ap_boost != null) equipEffectJson.put("increaseMaxAP", this.equip_effect.max_ap_boost); + if (this.equip_effect.increase_move_cost != null) equipEffectJson.put("increaseMoveCost", this.equip_effect.increase_move_cost); + if (this.equip_effect.increase_use_item_cost != null) equipEffectJson.put("increaseUseItemCost", this.equip_effect.increase_use_item_cost); + if (this.equip_effect.increase_reequip_cost != null) equipEffectJson.put("increaseReequipCost", this.equip_effect.increase_reequip_cost); + if (this.equip_effect.increase_attack_cost != null) equipEffectJson.put("increaseAttackCost", this.equip_effect.increase_attack_cost); + if (this.equip_effect.increase_attack_chance != null) equipEffectJson.put("increaseAttackChance", this.equip_effect.increase_attack_chance); + if (this.equip_effect.increase_critical_skill != null) equipEffectJson.put("increaseCriticalSkill", this.equip_effect.increase_critical_skill); + if (this.equip_effect.increase_block_chance != null) equipEffectJson.put("increaseBlockChance", this.equip_effect.increase_block_chance); + if (this.equip_effect.increase_damage_resistance != null) equipEffectJson.put("increaseDamageResistance", this.equip_effect.increase_damage_resistance); + if (this.equip_effect.critical_multiplier != null) equipEffectJson.put("setCriticalMultiplier", this.equip_effect.critical_multiplier); + if (this.equip_effect.conditions != null) { + List conditionsJson = new ArrayList(); + equipEffectJson.put("addedConditions", conditionsJson); + for (ConditionEffect condition : this.equip_effect.conditions) { + Map conditionJson = new HashMap(); + conditionsJson.add(conditionJson); + if (condition.condition != null) { + conditionJson.put("condition", condition.condition.id); + } else if (condition.condition_id != null) { + conditionJson.put("condition", condition.condition_id); + } + if (condition.magnitude != null) conditionJson.put("magnitude", condition.magnitude); + } + } + } + if (this.hit_effect != null) { + Map hitEffectJson = new HashMap(); + itemJson.put("hitEffect", hitEffectJson); + if (this.hit_effect.hp_boost_min != null || this.hit_effect.hp_boost_max != null) { + Map hpJson = new HashMap(); + hitEffectJson.put("increaseCurrentHP", hpJson); + if (this.hit_effect.hp_boost_min != null) hpJson.put("min", this.hit_effect.hp_boost_min); + else hpJson.put("min", 0); + if (this.hit_effect.hp_boost_max != null) hpJson.put("max", this.hit_effect.hp_boost_max); + else hpJson.put("max", 0); + } + if (this.hit_effect.ap_boost_min != null || this.hit_effect.ap_boost_max != null) { + Map apJson = new HashMap(); + hitEffectJson.put("increaseCurrentAP", apJson); + if (this.hit_effect.ap_boost_min != null) apJson.put("min", this.hit_effect.ap_boost_min); + else apJson.put("min", 0); + if (this.hit_effect.ap_boost_max != null) apJson.put("max", this.hit_effect.ap_boost_max); + else apJson.put("max", 0); + } + if (this.hit_effect.conditions_source != null) { + List conditionsSourceJson = new ArrayList(); + hitEffectJson.put("conditionsSource", conditionsSourceJson); + for (TimedConditionEffect condition : this.hit_effect.conditions_source) { + Map conditionJson = new HashMap(); + conditionsSourceJson.add(conditionJson); + if (condition.condition != null) { + conditionJson.put("condition", condition.condition.id); + } else if (condition.condition_id != null) { + conditionJson.put("condition", condition.condition_id); + } + if (condition.magnitude != null) conditionJson.put("magnitude", condition.magnitude); + if (condition.duration != null) conditionJson.put("duration", condition.duration); + if (condition.chance != null) conditionJson.put("chance", JSONElement.printJsonChance(condition.chance)); + } + } + if (this.hit_effect.conditions_target != null) { + List conditionsTargetJson = new ArrayList(); + hitEffectJson.put("conditionsTarget", conditionsTargetJson); + for (TimedConditionEffect condition : this.hit_effect.conditions_target) { + Map conditionJson = new HashMap(); + conditionsTargetJson.add(conditionJson); + if (condition.condition != null) { + conditionJson.put("condition", condition.condition.id); + } else if (condition.condition_id != null) { + conditionJson.put("condition", condition.condition_id); + } + if (condition.magnitude != null) conditionJson.put("magnitude", condition.magnitude); + if (condition.duration != null) conditionJson.put("duration", condition.duration); + if (condition.chance != null) conditionJson.put("chance", JSONElement.printJsonChance(condition.chance)); + } + } + } + if (this.kill_effect != null) { + Map killEffectJson = new HashMap(); + if (this.category != null && this.category.action_type != null && this.category.action_type == ItemCategory.ActionType.equip) { + itemJson.put("killEffect", killEffectJson); + } else if (this.category != null && this.category.action_type != null && this.category.action_type == ItemCategory.ActionType.use) { + itemJson.put("useEffect", killEffectJson); + } + if (this.kill_effect.hp_boost_min != null || this.kill_effect.hp_boost_max != null) { + Map hpJson = new HashMap(); + killEffectJson.put("increaseCurrentHP", hpJson); + if (this.kill_effect.hp_boost_min != null) hpJson.put("min", this.kill_effect.hp_boost_min); + else hpJson.put("min", 0); + if (this.kill_effect.hp_boost_max != null) hpJson.put("max", this.kill_effect.hp_boost_max); + else hpJson.put("min", 0); + } + if (this.kill_effect.ap_boost_min != null || this.kill_effect.ap_boost_max != null) { + Map apJson = new HashMap(); + killEffectJson.put("increaseCurrentAP", apJson); + if (this.kill_effect.ap_boost_min != null) apJson.put("min", this.kill_effect.ap_boost_min); + else apJson.put("min", 0); + if (this.kill_effect.ap_boost_max != null) apJson.put("max", this.kill_effect.ap_boost_max); + else apJson.put("max", 0); + } + if (this.kill_effect.conditions_source != null) { + List conditionsSourceJson = new ArrayList(); + killEffectJson.put("conditionsSource", conditionsSourceJson); + for (TimedConditionEffect condition : this.kill_effect.conditions_source) { + Map conditionJson = new HashMap(); + conditionsSourceJson.add(conditionJson); + if (condition.condition != null) { + conditionJson.put("condition", condition.condition.id); + } else if (condition.condition_id != null) { + conditionJson.put("condition", condition.condition_id); + } + if (condition.magnitude != null) conditionJson.put("magnitude", condition.magnitude); + if (condition.duration != null) conditionJson.put("duration", condition.duration); + if (condition.chance != null) conditionJson.put("chance", JSONElement.printJsonChance(condition.chance)); + } + } + } + return itemJson; + } + + @Override + public String getProjectFilename() { + return "itemlist_"+getProject().name+".json"; + } + + public Integer computePrice() { + int price = 0; + if (category != null && category.action_type != null) { + if (category.action_type == ItemCategory.ActionType.use) { + price += kill_effect == null ? 0 : calculateUseCost(); + } else if (category.action_type == ItemCategory.ActionType.equip) { + price += equip_effect == null ? 0 : calculateEquipCost(isWeapon());; + price += hit_effect == null ? 0 : calculateHitCost(); + price += kill_effect == null ? 0 : calculateKillCost(); + } + } + return Math.max(1, price); + } + + public int zeroForNull(Integer val) { + return val == null ? 0 : val; + } + + public double zeroForNull(Double val) { + return val == null ? 0 : val; + } + + public boolean isWeapon() { + return category != null && category.action_type != null && category.action_type == ItemCategory.ActionType.equip && category.slot != null && category.slot == ItemCategory.InventorySlot.weapon; + } + + public int calculateUseCost() { + final float averageHPBoost = (zeroForNull(kill_effect.hp_boost_min) + zeroForNull(kill_effect.hp_boost_max)) / 2.0f; + if (averageHPBoost == 0) return 0; + return (int) (0.1*Math.signum(averageHPBoost)*Math.pow(Math.abs(averageHPBoost), 2) + 3*averageHPBoost); + } + + public int calculateEquipCost(boolean isWeapon) { + final int costBC = (int) (3*Math.pow(Math.max(0, zeroForNull(equip_effect.increase_block_chance)), 2.5) + 28*zeroForNull(equip_effect.increase_block_chance)); + final int costAC = (int) (0.4*Math.pow(Math.max(0,zeroForNull(equip_effect.increase_attack_chance)), 2.5) - 6*Math.pow(Math.abs(Math.min(0,zeroForNull(equip_effect.increase_attack_chance))),2.7)); + final int costAP = isWeapon ? + (int) (0.2*Math.pow(10.0f/zeroForNull(equip_effect.increase_attack_cost), 8) - 25*zeroForNull(equip_effect.increase_attack_cost)) + :-3125 * zeroForNull(equip_effect.increase_attack_cost); + final int costDR = 1325 * zeroForNull(equip_effect.increase_damage_resistance); + final int costDMG_Min = isWeapon ? + (int) (10*Math.pow(Math.max(0, zeroForNull(equip_effect.damage_boost_min)), 2.5)) + :(int) (10*Math.pow(Math.max(0, zeroForNull(equip_effect.damage_boost_min)), 3) + zeroForNull(equip_effect.damage_boost_min)*80); + final int costDMG_Max = isWeapon ? + (int) (2*Math.pow(Math.max(0, zeroForNull(equip_effect.damage_boost_max)), 2.1)) + :(int) (2*Math.pow(Math.max(0, zeroForNull(equip_effect.damage_boost_max)), 3) + zeroForNull(equip_effect.damage_boost_max)*20); + final int costCS = (int) (2.2*Math.pow(zeroForNull(equip_effect.increase_critical_skill), 3)); + final int costCM = (int) (50*Math.pow(Math.max(0, zeroForNull(equip_effect.critical_multiplier)), 2)); + + final int costMaxHP = (int) (30*Math.pow(Math.max(0,zeroForNull(equip_effect.max_hp_boost)), 1.2) + 70*zeroForNull(equip_effect.max_hp_boost)); + final int costMaxAP = (int) (50*Math.pow(Math.max(0,zeroForNull(equip_effect.max_ap_boost)), 3) + 750*zeroForNull(equip_effect.max_ap_boost)); + final int costMovement = (int) (510*Math.pow(Math.max(0,-zeroForNull(equip_effect.increase_move_cost)), 2.5) - 350*zeroForNull(equip_effect.increase_move_cost)); + final int costUseItem = (int)(915*Math.pow(Math.max(0,-zeroForNull(equip_effect.increase_use_item_cost)), 3) - 430*zeroForNull(equip_effect.increase_use_item_cost)); + final int costReequip = (int)(450*Math.pow(Math.max(0,-zeroForNull(equip_effect.increase_reequip_cost)), 2) - 250*zeroForNull(equip_effect.increase_reequip_cost)); + + return costBC + costAC + costAP + costDR + costDMG_Min + costDMG_Max + costCS + costCM + + costMaxHP + costMaxAP + + costMovement + costUseItem + costReequip; + } + + + public int calculateHitCost() { + final float averageHPBoost = (zeroForNull(hit_effect.hp_boost_min) + zeroForNull(hit_effect.hp_boost_max)) / 2.0f; + final float averageAPBoost = (zeroForNull(hit_effect.ap_boost_min) + zeroForNull(hit_effect.ap_boost_max)) / 2.0f; + if (averageHPBoost == 0 && averageAPBoost == 0) return 0; + + final int costBoostHP = (int)(2770*Math.pow(Math.max(0,averageHPBoost), 2.5) + 450*averageHPBoost); + final int costBoostAP = (int)(3100*Math.pow(Math.max(0,averageAPBoost), 2.5) + 300*averageAPBoost); + return costBoostHP + costBoostAP; + } + + public int calculateKillCost() { + final float averageHPBoost = (zeroForNull(kill_effect.hp_boost_min) + zeroForNull(kill_effect.hp_boost_max)) / 2.0f; + final float averageAPBoost = (zeroForNull(kill_effect.ap_boost_min) + zeroForNull(kill_effect.ap_boost_max)) / 2.0f; + if (averageHPBoost == 0 && averageAPBoost == 0) return 0; + + final int costBoostHP = (int)(923*Math.pow(Math.max(0,averageHPBoost), 2.5) + 450*averageHPBoost); + final int costBoostAP = (int)(1033*Math.pow(Math.max(0,averageAPBoost), 2.5) + 300*averageAPBoost); + return costBoostHP + costBoostAP; + } +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/gamedata/ItemCategory.java b/src/com/gpl/rpg/atcontentstudio/model/gamedata/ItemCategory.java new file mode 100644 index 0000000..179082b --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/gamedata/ItemCategory.java @@ -0,0 +1,329 @@ +package com.gpl.rpg.atcontentstudio.model.gamedata; + +import java.awt.Image; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.imageio.ImageIO; + +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.GameSource; + +public class ItemCategory extends JSONElement { + + private static final long serialVersionUID = -348864002519568300L; + + public static final String ICON_NO_SLOT_RES = "/com/gpl/rpg/atcontentstudio/img/equip_square.png"; + public static final String ICON_BODY_RES = "/com/gpl/rpg/atcontentstudio/img/equip_body.png"; + public static final String ICON_FEET_RES = "/com/gpl/rpg/atcontentstudio/img/equip_feet.png"; + public static final String ICON_HAND_RES = "/com/gpl/rpg/atcontentstudio/img/equip_hand.png"; + public static final String ICON_HEAD_RES = "/com/gpl/rpg/atcontentstudio/img/equip_head.png"; + public static final String ICON_NECK_RES = "/com/gpl/rpg/atcontentstudio/img/equip_neck.png"; + public static final String ICON_RING_RES = "/com/gpl/rpg/atcontentstudio/img/equip_ring.png"; + public static final String ICON_SHIELD_RES = "/com/gpl/rpg/atcontentstudio/img/equip_shield.png"; + public static final String ICON_WEAPON_RES = "/com/gpl/rpg/atcontentstudio/img/equip_weapon.png"; + + public static Image no_slot_image = null; + public static Image no_slot_icon = null; + + public static Image body_image = null; + public static Image body_icon = null; + + public static Image feet_image = null; + public static Image feet_icon = null; + + public static Image hand_image = null; + public static Image hand_icon = null; + + public static Image head_image = null; + public static Image head_icon = null; + + public static Image neck_image = null; + public static Image neck_icon = null; + + public static Image ring_image = null; + public static Image ring_icon = null; + + public static Image shield_image = null; + public static Image shield_icon = null; + + public static Image weapon_image = null; + public static Image weapon_icon = null; + + + //Available from init state + //public String id = null; inherited. + public String name = null; + public InventorySlot slot = null; + + //Available from parsed state + public ActionType action_type = null; + public Size size = null; + + //Available from linked state + //None + + public static enum ActionType { + none, + use, + equip + } + + public static enum Size { + none, + light, + std, + large + } + + public static enum InventorySlot { + weapon, + shield, + head, + body, + hand, + feet, + neck, + leftring, + rightring + } + + @Override + public String getDesc() { + return (this.state == State.modified ? "*" : "")+name+" ("+id+")"; + } + + public static String getStaticDesc() { + return "Item categories"; + } + + + @SuppressWarnings("rawtypes") + public static void fromJson(File jsonFile, GameDataCategory category) { + JSONParser parser = new JSONParser(); + FileReader reader = null; + try { + reader = new FileReader(jsonFile); + List itemCategories = (List) parser.parse(reader); + for (Object obj : itemCategories) { + Map itemCatJson = (Map)obj; + ItemCategory itemCat = fromJson(itemCatJson); + itemCat.jsonFile = jsonFile; + itemCat.parent = category; + if (itemCat.getDataType() == GameSource.Type.created || itemCat.getDataType() == GameSource.Type.altered) { + itemCat.writable = true; + } + category.add(itemCat); + } + } catch (FileNotFoundException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (IOException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (ParseException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } finally { + if (reader != null) + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @SuppressWarnings("rawtypes") + public static ItemCategory fromJson(String jsonString) throws ParseException { + Map itemCatJson = (Map) new JSONParser().parse(jsonString); + ItemCategory item = fromJson(itemCatJson); + item.parse(itemCatJson); + return item; + } + + @SuppressWarnings("rawtypes") + public static ItemCategory fromJson(Map itemCatJson) { + ItemCategory itemCat = new ItemCategory(); + itemCat.id = (String) itemCatJson.get("id"); + itemCat.name = (String) itemCatJson.get("name"); + if (itemCatJson.get("inventorySlot") != null) itemCat.slot = InventorySlot.valueOf((String) itemCatJson.get("inventorySlot")); + return itemCat; + } + + @SuppressWarnings("rawtypes") + @Override + public void parse(Map itemCatJson) { + if (itemCatJson.get("actionType") != null) action_type = ActionType.valueOf((String) itemCatJson.get("actionType")); + if (itemCatJson.get("size") != null) size = Size.valueOf((String) itemCatJson.get("size")); + this.state = State.parsed; + + } + + @Override + public void link() { + if (this.state == State.created || this.state == State.modified || this.state == State.saved) { + //This type of state is unrelated to parsing/linking. + return; + } + if (this.state == State.init) { + //Not parsed yet. + this.parse(); + } else if (this.state == State.linked) { + //Already linked. + return; + } + + //Nothing to link to :D + this.state = State.linked; + } + + @Override + public Image getIcon() { + return getIcon(this.slot); + } + + public Image getImage() { + return getImage(this.slot); + } + + public static Image getImage(InventorySlot slot) { + if (slot == null) { + return getImage(ICON_NO_SLOT_RES, no_slot_image, "no_slot_image"); + } + switch (slot) { + case body: + return getImage(ICON_BODY_RES, body_image, "body_image"); + case feet: + return getImage(ICON_FEET_RES, feet_image, "feet_image"); + case hand: + return getImage(ICON_HAND_RES, hand_image, "hand_image"); + case head: + return getImage(ICON_HEAD_RES, head_image, "head_image"); + case leftring: + case rightring: + return getImage(ICON_RING_RES, ring_image, "ring_image"); + case neck: + return getImage(ICON_NECK_RES, neck_image, "neck_image"); + case shield: + return getImage(ICON_SHIELD_RES, shield_image, "shield_image"); + case weapon: + return getImage(ICON_WEAPON_RES, weapon_image, "weapon_image"); + default: + return getImage(ICON_NO_SLOT_RES, no_slot_image, "no_slot_image"); + } + } + + public static Image getIcon(InventorySlot slot) { + if (slot == null) { + return getIcon(ICON_NO_SLOT_RES, no_slot_image, no_slot_icon, "no_slot_image", "no_slot_icon"); + } + switch (slot) { + case body: + return getIcon(ICON_BODY_RES, body_image, body_icon, "body_image", "body_icon"); + case feet: + return getIcon(ICON_FEET_RES, feet_image, feet_icon, "feet_image", "feet_icon"); + case hand: + return getIcon(ICON_HAND_RES, hand_image, hand_icon, "hand_image", "hand_icon"); + case head: + return getIcon(ICON_HEAD_RES, head_image, head_icon, "head_image", "head_icon"); + case leftring: + case rightring: + return getIcon(ICON_RING_RES, ring_image, ring_icon, "ring_image", "ring_icon"); + case neck: + return getIcon(ICON_NECK_RES, neck_image, neck_icon, "neck_image", "neck_icon"); + case shield: + return getIcon(ICON_SHIELD_RES, shield_image, shield_icon, "shield_image", "shield_icon"); + case weapon: + return getIcon(ICON_WEAPON_RES, weapon_image, weapon_icon, "weapon_image", "weapon_icon"); + default: + return getIcon(ICON_NO_SLOT_RES, no_slot_image, no_slot_icon, "no_slot_image", "no_slot_icon"); + } + } + + public static Image getImage(String res, Image img, String fieldName) { + if (img == null) { + try { + img = ImageIO.read(ItemCategory.class.getResourceAsStream(res)); + ItemCategory.class.getField(fieldName).set(null, img); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (SecurityException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IOException e) { + Notification.addError("Failed to load item category icon "+res); + e.printStackTrace(); + } + } + return img; + } + + public static Image getIcon(String res, Image img, Image icon, String imgFieldName, String iconFieldName) { + if (icon == null) { + icon = getImage(res, img, imgFieldName).getScaledInstance(16, 16, Image.SCALE_SMOOTH); + try { + ItemCategory.class.getField(iconFieldName).set(null, icon); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (SecurityException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + } + return icon; + } + + @Override + public GameDataElement clone() { + ItemCategory clone = new ItemCategory(); + clone.jsonFile = this.jsonFile; + clone.state = this.state; + clone.id = this.id; + clone.name = this.name; + clone.size = this.size; + clone.slot = this.slot; + clone.action_type = this.action_type; + return clone; + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + // Nothing to link to. + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public Map toJson() { + Map itemCatJson = new HashMap(); + itemCatJson.put("id", this.id); + if (this.name != null) itemCatJson.put("name", this.name); + if (this.action_type != null) itemCatJson.put("actionType", this.action_type.toString()); + if (this.size != null) itemCatJson.put("size", this.size.toString()); + if (this.slot != null) itemCatJson.put("inventorySlot", this.slot.toString()); + return itemCatJson; + } + + + @Override + public String getProjectFilename() { + return "itemcategories_"+getProject().name+".json"; + } + + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/gamedata/JSONElement.java b/src/com/gpl/rpg/atcontentstudio/model/gamedata/JSONElement.java new file mode 100644 index 0000000..ddf0dbc --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/gamedata/JSONElement.java @@ -0,0 +1,169 @@ +package com.gpl.rpg.atcontentstudio.model.gamedata; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; +import java.util.Map; + +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.io.JsonPrettyWriter; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.SaveEvent; + +public abstract class JSONElement extends GameDataElement { + + private static final long serialVersionUID = -8015398814080503982L; + + //Available from state init. + public File jsonFile; + + @SuppressWarnings("rawtypes") + public void parse() { + if (this.state == State.created || this.state == State.modified || this.state == State.saved) { + //This type of state is unrelated to parsing/linking. + return; + } + JSONParser parser = new JSONParser(); + FileReader reader = null; + try { + reader = new FileReader(jsonFile); + List gameDataElements = (List) parser.parse(reader); + for (Object obj : gameDataElements) { + Map jsonObj = (Map)obj; + String id = (String) jsonObj.get("id"); + if (id != null && id.equals(this.id )) { + this.parse(jsonObj); + this.state = State.parsed; + break; + } + } + } catch (FileNotFoundException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (IOException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (ParseException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } finally { + if (reader != null) + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + } + + public abstract void parse(@SuppressWarnings("rawtypes") Map jsonObj); + + @SuppressWarnings("rawtypes") + public abstract Map toJson(); + public String toJsonString() { + StringWriter writer = new JsonPrettyWriter(); + try { + JSONObject.writeJSONString(this.toJson(), writer); + } catch (IOException e) { + //Impossible with a StringWriter + } + return writer.toString(); + } + + + @Override + public GameDataSet getDataSet() { + if (parent == null) { + System.out.println("blerf."); + } + return parent.getDataSet(); + } + + public void save() { + if (this.getParent() instanceof GameDataCategory && writable) { + ((GameDataCategory)this.getParent()).save(this.jsonFile); + } + } + + /** + * Returns null if save occurred (no notable events). + */ + public List attemptSave() { + List events = ((GameDataCategory)this.getParent()).attemptSave(true, this.jsonFile.getName()); + if (events == null || events.isEmpty()) { + return null; + } + if (events.size() == 1 && events.get(0).type == SaveEvent.Type.alsoSave && events.get(0).target == this) { + save(); + return null; + } + return events; + } + + public static Integer getInteger(Number n) { + return n == null ? null : n.intValue(); + } + + public static Double getDouble(Number n) { + return n == null ? null : n.doubleValue(); + } + + public static Double parseChance(String s) { + if (s.equals("100")) return 100d; + else if (s.equals("70")) return 70d; + else if (s.equals("30")) return 30d; + else if (s.equals("25")) return 25d; + else if (s.equals("20")) return 20d; + else if (s.equals("10")) return 10d; + else if (s.equals("5")) return 5d; + else if (s.equals("1")) return 1d; + else if (s.equals("1/1000")) return 0.1; + else if (s.equals("1/10000")) return 0.01; + else if (s.indexOf('/') >= 0) { + int c = s.indexOf('/'); + double a = 1; + try { + a = Integer.parseInt(s.substring(0, c)); + } catch (NumberFormatException nfe) {} + double b = 100; + try { + b = Integer.parseInt(s.substring(c+1)); + } catch (NumberFormatException nfe) {} + return a/b; + } + else { + double a = 10; + try { + a = Double.parseDouble(s); + } catch (NumberFormatException nfe) {} + return a; + } + } + + public static String printJsonChance(Double chance) { + if (chance.equals(100d)) return "100"; + else if (chance.equals(70d)) return "70"; + else if (chance.equals(30d)) return "30"; + else if (chance.equals(25d)) return "25"; + else if (chance.equals(20d)) return "20"; + else if (chance.equals(10d)) return "10"; + else if (chance.equals(5d)) return "5"; + else if (chance.equals(1d)) return "1"; + else if (chance.equals(0.1d)) return "1/1000"; + else if (chance.equals(0.01d)) return "1/10000"; + else { + //TODO Better handling of fractions. Chance description need a complete rehaul in AT. + //This part does not output the input content of parseDouble(String s) in the case of fractions. + return chance.toString(); + } + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/gamedata/NPC.java b/src/com/gpl/rpg/atcontentstudio/model/gamedata/NPC.java new file mode 100644 index 0000000..d75db0a --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/gamedata/NPC.java @@ -0,0 +1,511 @@ +package com.gpl.rpg.atcontentstudio.model.gamedata; + +import java.awt.Image; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.Project; + +public class NPC extends JSONElement { + + private static final long serialVersionUID = 1093728879485491933L; + + //Available from init state + //public String id = null; inherited. + public String name = null; + public String icon_id = null; + + //Available from parsed state + public Integer max_hp = null; + public Integer max_ap = null; + public Integer move_cost = null; + public Integer unique = null; + public MonsterClass monster_class = null; + public MovementType movement_type = null; + public Integer attack_damage_max = null; + public Integer attack_damage_min = null; + public String spawngroup_id = null; + public String faction_id = null; + public String dialogue_id = null; + public String droplist_id = null; + public Integer attack_cost = null; + public Integer attack_chance = null; + public Integer critical_skill = null; + public Double critical_multiplier = null; + public Integer block_chance = null; + public Integer damage_resistance = null; + public HitEffect hit_effect = null; + + //Available from linked state + public Dialogue dialogue = null; + public Droplist droplist = null; + + public enum MonsterClass { + humanoid, + insect, + demon, + construct, + animal, + giant, + undead, + reptile, + ghost + } + + public enum MovementType { + none, + helpOthers, + protectSpawn, + wholeMap + } + + public static class HitEffect { + //Available from parsed state + public Integer hp_boost_min = null; + public Integer hp_boost_max = null; + public Integer ap_boost_min = null; + public Integer ap_boost_max = null; + public List conditions_source = null; + public List conditions_target = null; + } + + public static class TimedConditionEffect { + //Available from parsed state + public Integer magnitude = null; + public String condition_id = null; + public Integer duration = null; + public Double chance = null; + + //Available from linked state + public ActorCondition condition = null; + + } + + @Override + public String getDesc() { + return (this.state == State.modified ? "*" : "")+name+" ("+id+")"; + } + + public static String getStaticDesc() { + return "NPCs"; + } + + + @SuppressWarnings("rawtypes") + public static void fromJson(File jsonFile, GameDataCategory category) { + JSONParser parser = new JSONParser(); + FileReader reader = null; + try { + reader = new FileReader(jsonFile); + List npcs = (List) parser.parse(reader); + for (Object obj : npcs) { + Map npcJson = (Map)obj; + NPC npc = fromJson(npcJson); + npc.jsonFile = jsonFile; + npc.parent = category; + if (npc.getDataType() == GameSource.Type.created || npc.getDataType() == GameSource.Type.altered) { + npc.writable = true; + } + category.add(npc); + } + } catch (FileNotFoundException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (IOException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (ParseException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } finally { + if (reader != null) + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @SuppressWarnings("rawtypes") + public static NPC fromJson(String jsonString) throws ParseException { + Map npcJson = (Map) new JSONParser().parse(jsonString); + NPC npc = fromJson(npcJson); + npc.parse(npcJson); + return npc; + } + + @SuppressWarnings("rawtypes") + public static NPC fromJson(Map npcJson) { + NPC npc = new NPC(); + npc.icon_id = (String) npcJson.get("iconID"); + npc.id = (String) npcJson.get("id"); + npc.name = (String) npcJson.get("name"); + return npc; + } + + + @SuppressWarnings("rawtypes") + @Override + public void parse(Map npcJson) { + + this.max_hp = JSONElement.getInteger((Number) npcJson.get("maxHP")); + this.max_ap = JSONElement.getInteger((Number) npcJson.get("maxAP")); + this.move_cost = JSONElement.getInteger((Number) npcJson.get("moveCost")); + this.unique = JSONElement.getInteger((Number) npcJson.get("unique")); + if (npcJson.get("monsterClass") != null) this.monster_class = MonsterClass.valueOf((String) npcJson.get("monsterClass")); + if (npcJson.get("movementAggressionType") != null) this.movement_type = MovementType.valueOf((String) npcJson.get("movementAggressionType")); + if (npcJson.get("attackDamage") != null) { + this.attack_damage_min = JSONElement.getInteger((Number) (((Map)npcJson.get("attackDamage")).get("min"))); + this.attack_damage_max = JSONElement.getInteger((Number) (((Map)npcJson.get("attackDamage")).get("max"))); + } + this.spawngroup_id = (String) npcJson.get("spawnGroup"); + this.faction_id = (String) npcJson.get("faction"); + this.dialogue_id = (String) npcJson.get("phraseID"); + this.droplist_id = (String) npcJson.get("droplistID"); + this.attack_cost = JSONElement.getInteger((Number) npcJson.get("attackCost")); + this.attack_chance = JSONElement.getInteger((Number) npcJson.get("attackChance")); + this.critical_skill = JSONElement.getInteger((Number) npcJson.get("criticalSkill")); + //TODO correct game data, to unify format. +// this.critical_multiplier = JSONElement.getDouble((Number) npcJson.get("criticalMultiplier")); + if (npcJson.get("criticalMultiplier") != null) this.critical_multiplier = JSONElement.getDouble(Double.parseDouble(npcJson.get("criticalMultiplier").toString())); + + this.block_chance = JSONElement.getInteger((Number) npcJson.get("blockChance")); + this.damage_resistance = JSONElement.getInteger((Number) npcJson.get("damageResistance")); + + Map hitEffect = (Map) npcJson.get("hitEffect"); + if (hitEffect != null) { + this.hit_effect = new HitEffect(); + if (hitEffect.get("increaseCurrentHP") != null) { + this.hit_effect.hp_boost_max = JSONElement.getInteger((Number) (((Map)hitEffect.get("increaseCurrentHP")).get("min"))); + this.hit_effect.hp_boost_min = JSONElement.getInteger((Number) (((Map)hitEffect.get("increaseCurrentHP")).get("max"))); + } + if (hitEffect.get("increaseCurrentAP") != null) { + this.hit_effect.ap_boost_max = JSONElement.getInteger((Number) (((Map)hitEffect.get("increaseCurrentAP")).get("min"))); + this.hit_effect.ap_boost_min = JSONElement.getInteger((Number) (((Map)hitEffect.get("increaseCurrentAP")).get("max"))); + } + List conditionsSourceJson = (List) hitEffect.get("conditionsSource"); + if (conditionsSourceJson != null && !conditionsSourceJson.isEmpty()) { + this.hit_effect.conditions_source = new ArrayList(); + for (Object conditionJsonObj : conditionsSourceJson) { + Map conditionJson = (Map)conditionJsonObj; + TimedConditionEffect condition = new TimedConditionEffect(); + condition.condition_id = (String) conditionJson.get("condition"); + condition.magnitude = JSONElement.getInteger((Number) conditionJson.get("magnitude")); + condition.duration = JSONElement.getInteger((Number) conditionJson.get("duration")); + if (conditionJson.get("chance") != null) condition.chance = JSONElement.parseChance(conditionJson.get("chance").toString()); + this.hit_effect.conditions_source.add(condition); + } + } + List conditionsTargetJson = (List) hitEffect.get("conditionsTarget"); + if (conditionsTargetJson != null && !conditionsTargetJson.isEmpty()) { + this.hit_effect.conditions_target = new ArrayList(); + for (Object conditionJsonObj : conditionsTargetJson) { + Map conditionJson = (Map)conditionJsonObj; + TimedConditionEffect condition = new TimedConditionEffect(); + condition.condition_id = (String) conditionJson.get("condition"); + condition.magnitude = JSONElement.getInteger((Number) conditionJson.get("magnitude")); + condition.duration = JSONElement.getInteger((Number) conditionJson.get("duration")); + if (conditionJson.get("chance") != null) condition.chance = JSONElement.parseChance(conditionJson.get("chance").toString()); + this.hit_effect.conditions_target.add(condition); + } + } + } + + } + + @Override + public void link() { + if (this.state == State.created || this.state == State.modified || this.state == State.saved) { + //This type of state is unrelated to parsing/linking. + return; + } + if (this.state == State.init) { + //Not parsed yet. + this.parse(); + } else if (this.state == State.linked) { + //Already linked. + return; + } + Project proj = getProject(); + if (proj == null) { + Notification.addError("Error linking item "+id+". No parent project found."); + return; + } + if (this.icon_id != null) { + String spritesheetId = this.icon_id.split(":")[0]; + proj.getSpritesheet(spritesheetId).addBacklink(this); + } + + if (this.dialogue_id != null) this.dialogue = proj.getDialogue(this.dialogue_id); + if (this.dialogue != null) this.dialogue.addBacklink(this); + + if (this.droplist_id != null) this.droplist = proj.getDroplist(this.droplist_id); + if (this.droplist != null) this.droplist.addBacklink(this); + + if (this.hit_effect != null && this.hit_effect.conditions_source != null) { + for (TimedConditionEffect ce : this.hit_effect.conditions_source) { + if (ce.condition_id != null) ce.condition = proj.getActorCondition(ce.condition_id); + if (ce.condition != null) ce.condition.addBacklink(this); + } + } + if (this.hit_effect != null && this.hit_effect.conditions_target != null) { + for (TimedConditionEffect ce : this.hit_effect.conditions_target) { + if (ce.condition_id != null) ce.condition = proj.getActorCondition(ce.condition_id); + if (ce.condition != null) ce.condition.addBacklink(this); + } + } + this.state = State.linked; + } + + @Override + public Image getIcon() { + return getProject().getIcon(icon_id); + } + + public Image getImage() { + return getProject().getImage(icon_id); + } + + @Override + public GameDataElement clone() { + NPC clone = new NPC(); + clone.jsonFile = this.jsonFile; + clone.state = this.state; + clone.id = this.id; + clone.name = this.name; + clone.icon_id = this.icon_id; + clone.attack_chance = this.attack_chance; + clone.attack_cost = this.attack_cost; + clone.attack_damage_min = this.attack_damage_min; + clone.attack_damage_max = this.attack_damage_max; + clone.block_chance = this.block_chance; + clone.critical_multiplier = this.critical_multiplier; + clone.critical_skill = this.critical_skill; + clone.damage_resistance = this.damage_resistance; + clone.dialogue = this.dialogue; + if (clone.dialogue != null) { + clone.dialogue.addBacklink(clone); + } + clone.dialogue_id = this.dialogue_id; + clone.droplist = this.droplist; + if (clone.droplist != null) { + clone.droplist.addBacklink(clone); + } + clone.droplist_id = this.droplist_id; + clone.faction_id = this.faction_id; + if (this.hit_effect != null) { + clone.hit_effect = new HitEffect(); + clone.hit_effect.ap_boost_max = this.hit_effect.ap_boost_max; + clone.hit_effect.ap_boost_min = this.hit_effect.ap_boost_min; + clone.hit_effect.hp_boost_max = this.hit_effect.hp_boost_max; + clone.hit_effect.hp_boost_min = this.hit_effect.hp_boost_min; + if (this.hit_effect.conditions_source != null) { + clone.hit_effect.conditions_source = new ArrayList(); + for (TimedConditionEffect c : this.hit_effect.conditions_source) { + TimedConditionEffect cclone = new TimedConditionEffect(); + cclone.magnitude = c.magnitude; + cclone.condition_id = c.condition_id; + cclone.condition = c.condition; + cclone.chance = c.chance; + cclone.duration = c.duration; + if (cclone.condition != null) { + cclone.condition.addBacklink(clone); + } + clone.hit_effect.conditions_source.add(cclone); + } + } + if (this.hit_effect.conditions_target != null) { + clone.hit_effect.conditions_target = new ArrayList(); + for (TimedConditionEffect c : this.hit_effect.conditions_target) { + TimedConditionEffect cclone = new TimedConditionEffect(); + cclone.magnitude = c.magnitude; + cclone.condition_id = c.condition_id; + cclone.condition = c.condition; + cclone.chance = c.chance; + cclone.duration = c.duration; + if (cclone.condition != null) { + cclone.condition.addBacklink(clone); + } + clone.hit_effect.conditions_target.add(cclone); + } + } + } + clone.max_ap = this.max_ap; + clone.max_hp = this.max_hp; + clone.monster_class = this.monster_class; + clone.move_cost = this.move_cost; + clone.movement_type = this.movement_type; + clone.spawngroup_id = this.spawngroup_id; + clone.unique = this.unique; + return clone; + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + if (dialogue == oldOne) { + this.dialogue = (Dialogue) newOne; + if (newOne != null) newOne.addBacklink(this); + } else { + if (this.droplist == oldOne) { + this.droplist = (Droplist) newOne; + if (newOne != null) newOne.addBacklink(this); + } else { + if (this.hit_effect != null && this.hit_effect.conditions_source != null) { + for (TimedConditionEffect tce : this.hit_effect.conditions_source) { + if (tce.condition == oldOne) { + tce.condition = (ActorCondition) newOne; + if (newOne != null) newOne.addBacklink(this); + } + } + } + if (this.hit_effect != null && this.hit_effect.conditions_target != null) { + for (TimedConditionEffect tce : this.hit_effect.conditions_target) { + if (tce.condition == oldOne) { + tce.condition = (ActorCondition) newOne; + if (newOne != null) newOne.addBacklink(this); + } + } + } + } + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public Map toJson() { + Map npcJson = new HashMap(); + npcJson.put("id", this.id); + if (this.name != null) npcJson.put("name", this.name); + if (this.icon_id != null) npcJson.put("iconID", this.icon_id); + if (this.max_hp != null) npcJson.put("maxHP", this.max_hp); + if (this.max_ap != null) npcJson.put("maxAP", this.max_ap); + if (this.move_cost != null) npcJson.put("moveCost", this.move_cost); + if (this.unique != null) npcJson.put("unique", this.unique); + if (this.monster_class != null) npcJson.put("monsterClass", this.monster_class.toString()); + if (this.movement_type != null) npcJson.put("movementAggressionType", this.movement_type.toString()); + if (this.attack_damage_min != null || this.attack_damage_max != null) { + Map adJson = new HashMap(); + npcJson.put("attackDamage", adJson); + if (this.attack_damage_min != null) adJson.put("min", this.attack_damage_min); + else adJson.put("min", 0); + if (this.attack_damage_max != null) adJson.put("max", this.attack_damage_max); + else adJson.put("max", 0); + } + if (this.spawngroup_id != null) npcJson.put("spawnGroup", this.spawngroup_id); + if (this.faction_id != null) npcJson.put("faction", this.faction_id); + if (this.dialogue != null) { + npcJson.put("phraseID", this.dialogue.id); + } else if (this.dialogue_id != null) { + npcJson.put("phraseID", this.dialogue_id); + } + if (this.droplist != null) { + npcJson.put("droplistID", this.droplist.id); + } else if (this.droplist_id != null) { + npcJson.put("droplistID", this.droplist_id); + } + if (this.attack_cost != null) npcJson.put("attackCost", this.attack_cost); + if (this.attack_chance != null) npcJson.put("attackChance", this.attack_chance); + if (this.critical_skill != null) npcJson.put("criticalSkill", this.critical_skill); + if (this.critical_multiplier != null) npcJson.put("criticalMultiplier", this.critical_multiplier); + if (this.block_chance != null) npcJson.put("blockChance", this.block_chance); + if (this.damage_resistance != null) npcJson.put("damageResistance", this.damage_resistance); + if (this.hit_effect != null) { + Map hitEffectJson = new HashMap(); + npcJson.put("hitEffect", hitEffectJson); + if (this.hit_effect.hp_boost_min != null || this.hit_effect.hp_boost_max != null) { + Map hpJson = new HashMap(); + hitEffectJson.put("increaseCurrentHP", hpJson); + if (this.hit_effect.hp_boost_min != null) hpJson.put("min", this.hit_effect.hp_boost_min); + else hpJson.put("min", 0); + if (this.hit_effect.hp_boost_max != null) hpJson.put("max", this.hit_effect.hp_boost_max); + else hpJson.put("max", 0); + } + if (this.hit_effect.ap_boost_min != null || this.hit_effect.ap_boost_max != null) { + Map apJson = new HashMap(); + hitEffectJson.put("increaseCurrentAP", apJson); + if (this.hit_effect.ap_boost_min != null) apJson.put("min", this.hit_effect.ap_boost_min); + else apJson.put("min", 0); + if (this.hit_effect.ap_boost_max != null) apJson.put("max", this.hit_effect.ap_boost_max); + else apJson.put("max", 0); + } + if (this.hit_effect.conditions_source != null) { + List conditionsSourceJson = new ArrayList(); + hitEffectJson.put("conditionsSource", conditionsSourceJson); + for (TimedConditionEffect condition : this.hit_effect.conditions_source) { + Map conditionJson = new HashMap(); + conditionsSourceJson.add(conditionJson); + if (condition.condition != null) { + conditionJson.put("condition", condition.condition.id); + } else if (condition.condition_id != null) { + conditionJson.put("condition", condition.condition_id); + } + if (condition.magnitude != null) conditionJson.put("magnitude", condition.magnitude); + if (condition.duration != null) conditionJson.put("duration", condition.duration); + if (condition.chance != null) conditionJson.put("chance", JSONElement.printJsonChance(condition.chance)); + } + } + if (this.hit_effect.conditions_target != null) { + List conditionsTargetJson = new ArrayList(); + hitEffectJson.put("conditionsTarget", conditionsTargetJson); + for (TimedConditionEffect condition : this.hit_effect.conditions_target) { + Map conditionJson = new HashMap(); + conditionsTargetJson.add(conditionJson); + if (condition.condition != null) { + conditionJson.put("condition", condition.condition.id); + } else if (condition.condition_id != null) { + conditionJson.put("condition", condition.condition_id); + } + if (condition.magnitude != null) conditionJson.put("magnitude", condition.magnitude); + if (condition.duration != null) conditionJson.put("duration", condition.duration); + if (condition.chance != null) conditionJson.put("chance", JSONElement.printJsonChance(condition.chance)); + } + } + } + return npcJson; + } + + + @Override + public String getProjectFilename() { + return "monsterlist_"+getProject().name+".json"; + } + + public int getMonsterExperience() { + double EXP_FACTOR_DAMAGERESISTANCE = 9; + double EXP_FACTOR_SCALING = 0.7; + + double attacksPerTurn = Math.floor((double)(max_ap != null ? max_ap : 10.0) / (double)(attack_cost != null ? attack_cost : 10.0)); + double avgDamagePotential = 0; + if (attack_damage_min != null || attack_damage_max != null) { + avgDamagePotential = ((double)(attack_damage_min != null ? attack_damage_min : 0) + (double)(attack_damage_max != null ? attack_damage_max : 0)) / 2.0; + } + double avgCrit = 0; + if (critical_skill != null && critical_multiplier != null) { + avgCrit = (double)(critical_skill / 100.0) * critical_multiplier; + } + double avgAttackHP = attacksPerTurn * ((double)(attack_chance != null ? attack_chance : 0) / 100.0) * avgDamagePotential * (1 + avgCrit); + double avgDefenseHP = ((max_hp != null ? max_hp : 1) * (1 + ((double)(block_chance != null ? block_chance : 0) / 100.0))) + ( EXP_FACTOR_DAMAGERESISTANCE * (damage_resistance != null ? damage_resistance : 0)); + double attackConditionBonus = 0; + if (hit_effect != null && hit_effect.conditions_target != null && hit_effect.conditions_target.size() > 0) { + attackConditionBonus = 50; + } + double experience = (((avgAttackHP * 3) + avgDefenseHP) * EXP_FACTOR_SCALING) + attackConditionBonus; + + return new Double(Math.ceil(experience)).intValue(); + }; + + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/gamedata/Quest.java b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Quest.java new file mode 100644 index 0000000..a9a1ec2 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Quest.java @@ -0,0 +1,208 @@ +package com.gpl.rpg.atcontentstudio.model.gamedata; + +import java.awt.Image; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class Quest extends JSONElement { + + private static final long serialVersionUID = 2004839647483250099L; + + //Available from init state + //public String id = null; inherited. + public String name = null; + + //Available in parsed state + public Integer visible_in_log = null; + public List stages = null; + + public static class QuestStage implements Cloneable { + public Integer progress = null; + public String log_text = null; + public Integer exp_reward = null; + public Integer finishes_quest = null; + + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + return null; + } + } + + @Override + public String getDesc() { + return (this.state == State.modified ? "*" : "")+name+" ("+id+")"; + } + + public static String getStaticDesc() { + return "Quests"; + } + + + @SuppressWarnings("rawtypes") + public static void fromJson(File jsonFile, GameDataCategory category) { + JSONParser parser = new JSONParser(); + FileReader reader = null; + try { + reader = new FileReader(jsonFile); + List quests = (List) parser.parse(reader); + for (Object obj : quests) { + Map questJson = (Map)obj; + Quest quest = fromJson(questJson); + quest.jsonFile = jsonFile; + quest.parent = category; + if (quest.getDataType() == GameSource.Type.created || quest.getDataType() == GameSource.Type.altered) { + quest.writable = true; + } + category.add(quest); + } + } catch (FileNotFoundException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (IOException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (ParseException e) { + Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } finally { + if (reader != null) + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @SuppressWarnings("rawtypes") + public static Quest fromJson(String jsonString) throws ParseException { + Map questJson = (Map) new JSONParser().parse(jsonString); + Quest quest = fromJson(questJson); + quest.parse(questJson); + return quest; + } + + @SuppressWarnings("rawtypes") + public static Quest fromJson(Map questJson) { + Quest quest = new Quest(); + quest.id = (String) questJson.get("id"); + quest.name = (String) questJson.get("name"); + return quest; + } + + @SuppressWarnings("rawtypes") + @Override + public void parse(Map questJson) { + this.visible_in_log = JSONElement.getInteger((Number) questJson.get("showInLog")); + List questStagesJson = (List) questJson.get("stages"); + if (questStagesJson != null && !questStagesJson.isEmpty()) { + this.stages = new ArrayList(); + for (Object questStageJsonObj : questStagesJson) { + Map questStageJson = (Map)questStageJsonObj; + QuestStage questStage = new QuestStage(); + questStage.progress = JSONElement.getInteger((Number) questStageJson.get("progress")); + questStage.log_text = (String) questStageJson.get("logText"); + questStage.exp_reward = JSONElement.getInteger((Number) questStageJson.get("rewardExperience")); + questStage.finishes_quest = JSONElement.getInteger((Number) questStageJson.get("finishesQuest")); + this.stages.add(questStage); + } + } + + } + + @Override + public void link() { + if (this.state == State.created || this.state == State.modified || this.state == State.saved) { + //This type of state is unrelated to parsing/linking. + return; + } + if (this.state == State.init) { + //Not parsed yet. + this.parse(); + } else if (this.state == State.linked) { + //Already linked. + return; + } + + //Nothing to link to :D + this.state = State.linked; + } + + @Override + public Image getIcon() { + return DefaultIcons.getQuestIcon(); + } + + public Image getImage() { + return DefaultIcons.getQuestImage(); + } + + @Override + public GameDataElement clone() { + Quest clone = new Quest(); + clone.jsonFile = this.jsonFile; + clone.state = this.state; + clone.id = this.id; + clone.name = this.name; + clone.visible_in_log = this.visible_in_log; + if (this.stages != null) { + clone.stages = new ArrayList(); + for (QuestStage stage : this.stages){ + clone.stages.add((QuestStage) stage.clone()); + } + } + return clone; + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + //Nothing to link to. + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public Map toJson() { + Map questJson = new HashMap(); + questJson.put("id", this.id); + if (this.name != null) questJson.put("name", this.name); + if (this.visible_in_log != null) questJson.put("showInLog", this.visible_in_log); + if (this.stages != null) { + List stagesJson = new ArrayList(); + questJson.put("stages", stagesJson); + for (QuestStage stage : this.stages) { + Map stageJson = new HashMap(); + stagesJson.add(stageJson); + if (stage.progress != null) stageJson.put("progress", stage.progress); + if (stage.log_text != null) stageJson.put("logText", stage.log_text); + if (stage.exp_reward != null) stageJson.put("rewardExperience", stage.exp_reward); + if (stage.finishes_quest != null) stageJson.put("finishesQuest", stage.finishes_quest); + } + } + return questJson; + } + + + @Override + public String getProjectFilename() { + return "questlist_"+getProject().name+".json"; + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/gamedata/Requirement.java b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Requirement.java new file mode 100644 index 0000000..cb6fde7 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Requirement.java @@ -0,0 +1,166 @@ +package com.gpl.rpg.atcontentstudio.model.gamedata; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.Project; + +public class Requirement extends JSONElement { + + private static final long serialVersionUID = 7295593297142310955L; + + private static Map> COMPATIBLE_TYPES = new HashMap>(); + + static { + List questTypes = new ArrayList(); + questTypes.add(RequirementType.questProgress); + questTypes.add(RequirementType.questLatestProgress); + COMPATIBLE_TYPES.put(RequirementType.questProgress, questTypes); + COMPATIBLE_TYPES.put(RequirementType.questLatestProgress, questTypes); + + List countedItemTypes = new ArrayList(); + countedItemTypes.add(RequirementType.inventoryRemove); + countedItemTypes.add(RequirementType.inventoryKeep); + countedItemTypes.add(RequirementType.usedItem); + COMPATIBLE_TYPES.put(RequirementType.inventoryRemove, countedItemTypes); + COMPATIBLE_TYPES.put(RequirementType.inventoryKeep, countedItemTypes); + COMPATIBLE_TYPES.put(RequirementType.usedItem, countedItemTypes); + + } + + //Available from parsed state + public RequirementType type = null; + public String required_obj_id = null; + public Integer required_value = null; + public Boolean negated = null; + + //Available from linked state + public GameDataElement required_obj = null; + + public enum RequirementType { + questProgress, + questLatestProgress, + inventoryRemove, + inventoryKeep, + wear, + skillLevel, + killedMonster, + timerElapsed, + usedItem, + spentGold, + consumedBonemeals, + hasActorCondition + } + + @Override + public String getDesc() { + return ((negated != null && negated) ? "NOT " : "")+required_obj_id+(required_value == null ? "" : ":"+required_value.toString()); + } + + @Override + public void parse() { + throw new Error("Thou shalt not reach this method."); + } + + @SuppressWarnings("rawtypes") + @Override + public Map toJson() { + throw new Error("Thou shalt not reach this method."); + } + + @SuppressWarnings("rawtypes") + @Override + public void parse(Map jsonObj) { + throw new Error("Thou shalt not reach this method."); + } + + @Override + public void link() { + if (this.state == State.created || this.state == State.modified || this.state == State.saved) { + //This type of state is unrelated to parsing/linking. + return; + } + if (this.state == State.init) { + //Not parsed yet. + this.parse(); + } else if (this.state == State.linked) { + //Already linked. + return; + } + Project proj = getProject(); + if (proj == null) { + Notification.addError("Error linking requirement "+getDesc()+". No parent project found."); + return; + } + switch (type) { + case hasActorCondition: + this.required_obj = proj.getActorCondition(required_obj_id); + break; + case inventoryKeep: + case inventoryRemove: + case usedItem: + case wear: + this.required_obj = proj.getItem(required_obj_id); + break; + case killedMonster: + this.required_obj = proj.getNPC(required_obj_id); + break; + case questLatestProgress: + case questProgress: + this.required_obj = proj.getQuest(required_obj_id); + break; + case consumedBonemeals: + case skillLevel: + case spentGold: + case timerElapsed: + break; + } + if (this.required_obj != null) this.required_obj.addBacklink((GameDataElement) this.parent); + } + + @Override + public GameDataElement clone() { + return clone(null); + } + + public GameDataElement clone(GameDataElement parent) { + Requirement clone = new Requirement(); + clone.parent = parent; + clone.jsonFile = this.jsonFile; + clone.state = this.state; + clone.required_obj_id = this.required_obj_id; + clone.required_value = this.required_value; + clone.required_obj = this.required_obj; + clone.type = this.type; + if (clone.required_obj != null && parent != null) { + clone.required_obj.addBacklink(parent); + } + return clone; + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + if (this.required_obj == oldOne) { + this.required_obj = newOne; + if (newOne != null) newOne.addBacklink(this); + } + } + @Override + public String getProjectFilename() { + throw new Error("Thou shalt not reach this method."); + } + + public void changeType(RequirementType destType) { + if (COMPATIBLE_TYPES.get(type) == null || !COMPATIBLE_TYPES.get(type).contains(destType)) { + required_obj = null; + required_obj_id = null; + required_value = null; + } + type = destType; + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/ContainerArea.java b/src/com/gpl/rpg/atcontentstudio/model/maps/ContainerArea.java new file mode 100644 index 0000000..5876e7d --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/ContainerArea.java @@ -0,0 +1,44 @@ +package com.gpl.rpg.atcontentstudio.model.maps; + +import java.awt.Image; + +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.gamedata.Droplist; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class ContainerArea extends MapObject { + + public Droplist droplist = null; + + public ContainerArea(tiled.core.MapObject obj) {} + + @Override + public void link() { + droplist = parentMap.getProject().getDroplist(name); + if (droplist != null) { + droplist.addBacklink(parentMap); + } + } + + @Override + public Image getIcon() { + if (droplist != null) return DefaultIcons.getContainerIcon(); + else return DefaultIcons.getNullifyIcon(); + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + if (oldOne == droplist) { + droplist = (Droplist) newOne; + newOne.addBacklink(parentMap); + } + } + + @Override + public void savePropertiesInTmxObject(tiled.core.MapObject tmxObject) { + if (droplist != null) { + tmxObject.setName(droplist.id); + } + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/KeyArea.java b/src/com/gpl/rpg/atcontentstudio/model/maps/KeyArea.java new file mode 100644 index 0000000..8a95193 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/KeyArea.java @@ -0,0 +1,84 @@ +package com.gpl.rpg.atcontentstudio.model.maps; + +import java.awt.Image; + +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.gamedata.Dialogue; +import com.gpl.rpg.atcontentstudio.model.gamedata.Requirement; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class KeyArea extends MapObject { + + public String dialogue_id = null; + public Dialogue dialogue = null; + public Requirement requirement = null; + + public KeyArea(tiled.core.MapObject obj) { + dialogue_id = obj.getProperties().getProperty("phrase"); + String requireType = obj.getProperties().getProperty("requireType"); + String requireId = obj.getProperties().getProperty("requireId"); + String requireValue = obj.getProperties().getProperty("requireValue"); + if (requireId == null) { + String[] fields = obj.getName().split(":"); + if (fields.length == 2) { + requireType = Requirement.RequirementType.questProgress.toString(); + requireValue = fields[1]; + requireId = fields[0]; + } else if (fields.length == 3) { + requireValue = fields[2]; + requireType = fields[0]; + requireId = fields[1]; + } + } + requirement = new Requirement(); + requirement.type = Requirement.RequirementType.valueOf(requireType); + requirement.required_obj_id = requireId; + if (requireValue != null) requirement.required_value = Integer.parseInt(requireValue); + requirement.state = GameDataElement.State.parsed; + } + + @Override + public void link() { + if (dialogue_id != null) dialogue = parentMap.getProject().getDialogue(dialogue_id); + if (dialogue != null) { + dialogue.addBacklink(parentMap); + } + requirement.parent = parentMap; + requirement.link(); + } + + @Override + public Image getIcon() { + if (dialogue != null && requirement != null) return DefaultIcons.getKeyIcon(); + return DefaultIcons.getNullifyIcon(); + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + if (oldOne == dialogue) { + dialogue = (Dialogue) newOne; + newOne.addBacklink(parentMap); + } + } + + @Override + public void savePropertiesInTmxObject(tiled.core.MapObject tmxObject) { + if (dialogue != null) { + tmxObject.getProperties().setProperty("phrase", dialogue.id); + } else if (dialogue_id != null) { + tmxObject.getProperties().setProperty("phrase", dialogue_id); + } + if (requirement != null) { + tmxObject.getProperties().setProperty("requireType", requirement.type.toString()); + if (requirement.required_obj != null) { + tmxObject.getProperties().setProperty("requireId", requirement.required_obj.id); + } else if (requirement.required_obj_id != null) { + tmxObject.getProperties().setProperty("requireId", requirement.required_obj_id); + } + if (requirement.required_value != null) { + tmxObject.getProperties().setProperty("requireValue", requirement.required_value.toString()); + } + } + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/MapChange.java b/src/com/gpl/rpg/atcontentstudio/model/maps/MapChange.java new file mode 100644 index 0000000..5dd672f --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/MapChange.java @@ -0,0 +1,57 @@ +package com.gpl.rpg.atcontentstudio.model.maps; + +import java.awt.Image; + +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + + + +public class MapChange extends MapObject { + + public String map_id = null; + public TMXMap map = null; + public String place_id = null; + + public MapChange(tiled.core.MapObject obj) { + this.map_id = obj.getProperties().getProperty("map"); + this.place_id = obj.getProperties().getProperty("place"); + } + + @Override + public void link() { + if (map_id != null) this.map = parentMap.getProject().getMap(map_id); + if (map != null) { + map.addBacklink(parentMap); + } + //TODO reinstate this if data validation system ever exist. +// else Notification.addWarn("Incomplete mapchange area \""+name+"\" in map \""+parentMap.id+"\". This is OK if it's an arrival only (no exit through this point)."); + } + + @Override + public Image getIcon() { + if (name != null) return DefaultIcons.getTiledIconIcon(); + else return DefaultIcons.getNullifyIcon(); + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + if (oldOne == map) { + map = (TMXMap) newOne; + newOne.addBacklink(parentMap); + } + } + + @Override + public void savePropertiesInTmxObject(tiled.core.MapObject tmxObject) { + if (map != null) { + tmxObject.getProperties().setProperty("map", map.id); + } else if (map_id != null) { + tmxObject.getProperties().setProperty("map", map_id); + } + if (place_id != null) { + tmxObject.getProperties().setProperty("place", place_id); + } + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/MapObject.java b/src/com/gpl/rpg/atcontentstudio/model/maps/MapObject.java new file mode 100644 index 0000000..09cad1e --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/MapObject.java @@ -0,0 +1,163 @@ +package com.gpl.rpg.atcontentstudio.model.maps; + +import java.awt.Image; + +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; + +public abstract class MapObject { + + public int x, y, w, h; + public String name; + + public TMXMap parentMap; + + public Types type; + + protected static enum Types { + mapchange, + spawn, + rest, + key, + replace, + script, + container, + sign + } + + + public static MapObject buildObject(tiled.core.MapObject obj, TMXMap parentMap) { + MapObject result = null; + if (obj.getType() != null && !obj.getType().equals("") && Types.valueOf(obj.getType()) != null) { + switch (Types.valueOf(obj.getType())) { + case key: + result = new KeyArea(obj); + result.type = Types.key; + break; + case mapchange: + result = new MapChange(obj); + result.type = Types.mapchange; + break; + case replace: + result = new ReplaceArea(obj); + result.type = Types.replace; + break; + case rest: + result = new RestArea(obj); + result.type = Types.rest; + break; + case script: + result = new ScriptArea(obj); + result.type = Types.script; + break; + case sign: + result = new SignArea(obj); + result.type = Types.sign; + break; + case spawn: + result = new SpawnArea(obj); + result.type = Types.spawn; + break; + case container: + result = new ContainerArea(obj); + result.type = Types.container; + break; + } + } else { + Notification.addWarn("Unknown map object type: "+obj.getType()+"with name "+obj.getName()+" in map "+parentMap.id); + } + if (result != null) { + result.x = obj.getX(); + result.y = obj.getY(); + result.w = obj.getWidth(); + result.h = obj.getHeight(); + result.name = obj.getName(); + result.parentMap = parentMap; + } + return result; + } + + public abstract void link(); + + public abstract Image getIcon(); + + public abstract void elementChanged(GameDataElement oldOne, GameDataElement newOne); + + public tiled.core.MapObject toTmxObject() { + tiled.core.MapObject tmxObject = new tiled.core.MapObject(x, y, w, h); + tmxObject.setName(name); + tmxObject.setType(type.toString()); + savePropertiesInTmxObject(tmxObject); + return tmxObject; + } + + public abstract void savePropertiesInTmxObject(tiled.core.MapObject tmxObject); + + public static MapObject newMapchange(tiled.core.MapObject obj, TMXMap parentMap) { + MapObject result = new MapChange(obj); + result.type = Types.mapchange; + initObj(result, obj, parentMap); + return result; + } + + public static MapObject newSpawnArea(tiled.core.MapObject obj, TMXMap parentMap) { + MapObject result = new SpawnArea(obj); + result.type = Types.spawn; + initObj(result, obj, parentMap); + return result; + } + + public static MapObject newRest(tiled.core.MapObject obj, TMXMap parentMap) { + MapObject result = new RestArea(obj); + result.type = Types.rest; + initObj(result, obj, parentMap); + return result; + } + + public static MapObject newKey(tiled.core.MapObject obj, TMXMap parentMap) { + MapObject result = new KeyArea(obj); + result.type = Types.key; + initObj(result, obj, parentMap); + return result; + } + + public static MapObject newReplace(tiled.core.MapObject obj, TMXMap parentMap) { + MapObject result = new ReplaceArea(obj); + result.type = Types.replace; + initObj(result, obj, parentMap); + return result; + } + + public static MapObject newScript(tiled.core.MapObject obj, TMXMap parentMap) { + MapObject result = new ScriptArea(obj); + result.type = Types.script; + initObj(result, obj, parentMap); + return result; + } + + public static MapObject newContainer(tiled.core.MapObject obj, TMXMap parentMap) { + MapObject result = new ContainerArea(obj); + result.type = Types.container; + initObj(result, obj, parentMap); + return result; + } + + public static MapObject newSign(tiled.core.MapObject obj, TMXMap parentMap) { + MapObject result = new SignArea(obj); + result.type = Types.sign; + initObj(result, obj, parentMap); + return result; + } + + private static MapObject initObj(MapObject result, tiled.core.MapObject obj, TMXMap parentMap) { + result.x = obj.getX(); + result.y = obj.getY(); + result.w = obj.getWidth(); + result.h = obj.getHeight(); + result.name = obj.getName(); + result.parentMap = parentMap; + return result; + } + + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/MapObjectGroup.java b/src/com/gpl/rpg/atcontentstudio/model/maps/MapObjectGroup.java new file mode 100644 index 0000000..44f2b0b --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/MapObjectGroup.java @@ -0,0 +1,51 @@ +package com.gpl.rpg.atcontentstudio.model.maps; + +import java.util.ArrayList; +import java.util.List; + +import com.gpl.rpg.atcontentstudio.model.GameDataElement; + +public class MapObjectGroup { + + + public tiled.core.ObjectGroup tmxGroup; + public TMXMap parentMap; + public String name; + public boolean visible; + public List mapObjects = new ArrayList(); + + public MapObjectGroup(tiled.core.ObjectGroup layer, TMXMap map) { + this.tmxGroup = layer; + this.name = layer.getName(); + this.visible = layer.isVisible(); + this.parentMap = map; + for (tiled.core.MapObject obj : layer.getObjectsList()) { + mapObjects.add(MapObject.buildObject(obj, map)); + } + } + + public void link() { + for (MapObject obj : mapObjects) { + obj.link(); + } + } + + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + for (MapObject object : mapObjects) { + object.elementChanged(oldOne, newOne); + } + } + + public void pushBackToTiledProperties() { + if (tmxGroup != null) { + tmxGroup.clear(); + } else { + tmxGroup = new tiled.core.ObjectGroup(); + } + tmxGroup.setVisible(visible); + tmxGroup.setName(name); + for (MapObject object : mapObjects) { + tmxGroup.addObject(object.toTmxObject()); + } + } +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/ReplaceArea.java b/src/com/gpl/rpg/atcontentstudio/model/maps/ReplaceArea.java new file mode 100644 index 0000000..0ad7d27 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/ReplaceArea.java @@ -0,0 +1,38 @@ +package com.gpl.rpg.atcontentstudio.model.maps; + +import java.awt.Image; + +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + + +public class ReplaceArea extends MapObject { + + public ReplaceArea(tiled.core.MapObject obj) { + // TODO Auto-generated constructor stub + } + + @Override + public void link() { + // TODO Auto-generated method stub + + } + + @Override + public Image getIcon() { + return DefaultIcons.getReplaceIcon(); + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + // TODO Auto-generated method stub + + } + + @Override + public void savePropertiesInTmxObject(tiled.core.MapObject tmxObject) { + // TODO Auto-generated method stub + + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/RestArea.java b/src/com/gpl/rpg/atcontentstudio/model/maps/RestArea.java new file mode 100644 index 0000000..14f8f26 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/RestArea.java @@ -0,0 +1,31 @@ +package com.gpl.rpg.atcontentstudio.model.maps; + +import java.awt.Image; + +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + + +public class RestArea extends MapObject { + + public RestArea(tiled.core.MapObject obj) {} + + @Override + public void link() {} + + @Override + public Image getIcon() { + return DefaultIcons.getRestIcon(); + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + + } + + @Override + public void savePropertiesInTmxObject(tiled.core.MapObject tmxObject) { + + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/ScriptArea.java b/src/com/gpl/rpg/atcontentstudio/model/maps/ScriptArea.java new file mode 100644 index 0000000..14530d7 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/ScriptArea.java @@ -0,0 +1,60 @@ +package com.gpl.rpg.atcontentstudio.model.maps; + +import java.awt.Image; + +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.gamedata.Dialogue; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class ScriptArea extends MapObject { + + public Dialogue dialogue = null; + public EvaluationTrigger trigger_type = null; + + public enum EvaluationTrigger { + enter, + step, + round, + always + } + + public ScriptArea(tiled.core.MapObject obj) { + String triggerTypeId = obj.getProperties().getProperty("when"); + if (triggerTypeId != null) { + trigger_type = EvaluationTrigger.valueOf(triggerTypeId); + } + } + + @Override + public void link() { + if (name != null) dialogue = parentMap.getProject().getDialogue(name); + if (dialogue != null) { + dialogue.addBacklink(parentMap); + } + } + + @Override + public Image getIcon() { + if (dialogue != null) return DefaultIcons.getScriptIcon(); + else return DefaultIcons.getNullifyIcon(); + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + if (oldOne == dialogue) { + dialogue = (Dialogue) newOne; + newOne.addBacklink(parentMap); + } + } + + @Override + public void savePropertiesInTmxObject(tiled.core.MapObject tmxObject) { + if (dialogue != null) { + tmxObject.setName(dialogue.id); + } + if (trigger_type != null) { + tmxObject.getProperties().setProperty("when", trigger_type.toString()); + } + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/SignArea.java b/src/com/gpl/rpg/atcontentstudio/model/maps/SignArea.java new file mode 100644 index 0000000..4a9f201 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/SignArea.java @@ -0,0 +1,46 @@ +package com.gpl.rpg.atcontentstudio.model.maps; + +import java.awt.Image; + +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.gamedata.Dialogue; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class SignArea extends MapObject { + + public Dialogue dialogue = null; + + public SignArea(tiled.core.MapObject obj) { + + } + + @Override + public void link() { + if (name != null) dialogue = parentMap.getProject().getDialogue(name); + if (dialogue != null) { + dialogue.addBacklink(parentMap); + } + } + + @Override + public Image getIcon() { + if (dialogue != null) return DefaultIcons.getSignIcon(); + else return DefaultIcons.getNullifyIcon(); + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + if (oldOne == dialogue) { + dialogue = (Dialogue) newOne; + newOne.addBacklink(parentMap); + } + } + + @Override + public void savePropertiesInTmxObject(tiled.core.MapObject tmxObject) { + if (dialogue != null) { + tmxObject.setName(dialogue.id); + } + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/SpawnArea.java b/src/com/gpl/rpg/atcontentstudio/model/maps/SpawnArea.java new file mode 100644 index 0000000..9f631d0 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/SpawnArea.java @@ -0,0 +1,72 @@ +package com.gpl.rpg.atcontentstudio.model.maps; + +import java.awt.Image; +import java.util.ArrayList; +import java.util.List; + +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.gamedata.NPC; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class SpawnArea extends MapObject { + + public int quantity = 1; + public int spawnchance = 10; + public List spawnGroup = new ArrayList(); + + public SpawnArea(tiled.core.MapObject obj) { + if (obj.getProperties().getProperty("quantity") != null) { + this.quantity = Integer.parseInt(obj.getProperties().getProperty("quantity")); + } + if (obj.getProperties().getProperty("spawnchance") != null) { + this.spawnchance = Integer.parseInt(obj.getProperties().getProperty("spawnchance")); + } + } + + @Override + public void link() { + if (name != null) { + spawnGroup = parentMap.getProject().getSpawnGroup(name); + } else { + spawnGroup = new ArrayList(); + } + if (spawnGroup != null) { + for (NPC npc : spawnGroup) { + npc.addBacklink(parentMap); + } + } + } + + @Override + public Image getIcon() { + if (spawnGroup != null && !spawnGroup.isEmpty()) { + return spawnGroup.get(0).getIcon(); + } + return DefaultIcons.getNullifyIcon(); + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + int replacedIndex = -1; + for (NPC npc : spawnGroup) { + if (npc == oldOne) { + replacedIndex = spawnGroup.indexOf(npc); + } + } + if (replacedIndex >= 0) { + spawnGroup.set(replacedIndex, (NPC) newOne); + newOne.addBacklink(parentMap); + } + } + + @Override + public void savePropertiesInTmxObject(tiled.core.MapObject tmxObject) { + if (quantity != 1) { + tmxObject.getProperties().setProperty("quantity", Integer.toString(quantity)); + } + if (spawnchance != 10) { + tmxObject.getProperties().setProperty("spawnchance", Integer.toString(spawnchance)); + } + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMap.java b/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMap.java new file mode 100644 index 0000000..9c89d2d --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMap.java @@ -0,0 +1,338 @@ +package com.gpl.rpg.atcontentstudio.model.maps; + +import java.awt.Image; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.swing.tree.TreeNode; + +import tiled.io.TMXMapReader; +import tiled.io.TMXMapWriter; + +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.SaveEvent; +import com.gpl.rpg.atcontentstudio.model.GameSource.Type; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class TMXMap extends GameDataElement { + + private static final long serialVersionUID = 1609502879500898837L; + + public File tmxFile = null; + public tiled.core.Map tmxMap = null; + public Set usedSpritesheets = null; + public List groups = null; + + public ProjectTreeNode parent; + public Integer outside = null; + + public boolean writable = false; + + public TMXMap(TMXMapSet parent, File f) { + this.parent = parent; + this.tmxFile = f; + String name = f.getName(); + id = name.substring(0, name.length() - 4); + } + + public void parse() { + if (this.state == GameDataElement.State.init) { + if (tmxMap != null) return; + usedSpritesheets = new HashSet(); + try { + tmxMap = new TMXMapReader().readMap(tmxFile.getAbsolutePath(), this); + if (tmxMap.getProperties().get("outside") != null) { + outside = new Integer(((String) tmxMap.getProperties().get("outside"))); + } + } catch (FileNotFoundException e) { + Notification.addError("Impossible to load TMX map file "+tmxFile.getAbsolutePath()); + } catch (Exception e) { + Notification.addError("Error while loading TMX map file "+tmxFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } + for (tiled.core.MapLayer layer : tmxMap.getLayers()) { + if (layer instanceof tiled.core.ObjectGroup) { + if (groups == null) { + groups = new ArrayList(); + } + MapObjectGroup group = new MapObjectGroup((tiled.core.ObjectGroup) layer, this); + groups.add(group); + } + } + for (Spritesheet s : usedSpritesheets) { + s.addBacklink(this); + } + state = State.parsed; + } + } + + public void create() { + if (tmxMap != null) return; + tmxMap = new tiled.core.Map(30, 30); + } + + public TMXMap clone() { + TMXMap clone = new TMXMap((TMXMapSet) this.parent, this.tmxFile); + try { + clone.usedSpritesheets = new HashSet(); + clone.tmxMap = new TMXMapReader().readMap(new StringReader(this.toXml()), clone); + if (clone.tmxMap.getProperties().get("outside") != null) { + clone.outside = new Integer(((String) clone.tmxMap.getProperties().get("outside"))); + } + for (tiled.core.MapLayer layer : clone.tmxMap.getLayers()) { + if (layer instanceof tiled.core.ObjectGroup) { + if (clone.groups == null) { + clone.groups = new ArrayList(); + } + MapObjectGroup group = new MapObjectGroup((tiled.core.ObjectGroup) layer, this); + clone.groups.add(group); + } + } + for (Spritesheet s : usedSpritesheets) { + s.addBacklink(clone); + } + } catch (Exception e) { + Notification.addError("Error while cloning map "+this.id+" : "+e.getMessage()); + e.printStackTrace(); + } + + return clone; + } + + @Override + public Enumeration children() { + return null; + } + + @Override + public boolean getAllowsChildren() { + return false; + } + + @Override + public TreeNode getChildAt(int arg0) { + return null; + } + + @Override + public int getChildCount() { + return 0; + } + + @Override + public int getIndex(TreeNode arg0) { + return 0; + } + + @Override + public TreeNode getParent() { + return parent; + } + + @Override + public boolean isLeaf() { + return true; + } + + @Override + public void childrenAdded(List path) { + path.add(0,this); + parent.childrenAdded(path); + } + + @Override + public void childrenChanged(List path) { + path.add(0,this); + parent.childrenChanged(path); + } + + @Override + public void childrenRemoved(List path) { + path.add(0,this); + parent.childrenRemoved(path); + } + @Override + public void notifyCreated() { + childrenAdded(new ArrayList()); + } + @Override + public String getDesc() { + return (this.state == State.modified ? "*" : "")+id; + } + + @Override + public Project getProject() { + return parent.getProject(); + } + + @Override + public Image getIcon() { + return DefaultIcons.getTiledIconIcon(); + } + @Override + public Image getLeafIcon() { + return DefaultIcons.getTiledIconIcon(); + } + @Override + public Image getClosedIcon() {return null;} + @Override + public Image getOpenIcon() {return null;} + + @Override + public GameDataSet getDataSet() { + return null; + } + + @Override + public Type getDataType() { + return parent.getDataType(); + } + + public String toXml() { + for (MapObjectGroup group : groups) { + group.pushBackToTiledProperties(); + if (!tmxMap.containsLayer(group.tmxGroup)) { + tmxMap.addLayer(group.tmxGroup); + } + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + TMXMapWriter writer = new TMXMapWriter(); + writer.settings.layerCompressionMethod = TMXMapWriter.Settings.LAYER_COMPRESSION_METHOD_ZLIB; + if (getDataType() == GameSource.Type.source) { + writer.writeMap(tmxMap, baos, tmxFile.getAbsolutePath()); + } else { + writer.writeMap(tmxMap, baos, getProject().baseContent.gameMaps.mapFolder.getAbsolutePath()+File.separator+"placeholder.tmx"); + } + } catch (Exception e) { + Notification.addError("Error while converting map "+getDesc()+" to XML: "+e.getMessage()); + e.printStackTrace(); + } + return baos.toString(); + } + + @Override + public boolean isEmpty() { + return false; + } + + public void save() { + if (writable) { + try { + FileWriter w = new FileWriter(tmxFile); + w.write(toXml()); + w.close(); + this.state = State.saved; + Notification.addSuccess("TMX file "+tmxFile.getAbsolutePath()+" saved."); + } catch (IOException e) { + Notification.addError("Error while writing TMX file "+tmxFile.getAbsolutePath()+" : "+e.getMessage()); + e.printStackTrace(); + } + } + } + + @Override + public List attemptSave() { + //TODO check cases where map should be moved from altered/created to created/altered.... + save(); + return null; + } + + public void delete() { + if (writable) { + if (tmxFile.exists()) { + if (tmxFile.delete()) { + Notification.addSuccess("TMX file "+tmxFile.getAbsolutePath()+" deleted."); + } else { + Notification.addError("Error while deleting TMX file "+tmxFile.getAbsolutePath()); + } + } + ((TMXMapSet)parent).tmxMaps.remove(this); + //TODO clear blacklinks ? + } + } + + @Override + public void link() { + if (this.state == GameDataElement.State.init) { + parse(); + } + if (this.state == GameDataElement.State.parsed || this.state == GameDataElement.State.created) { + for (MapObjectGroup group : groups) { + group.link(); + } + } + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + for (MapObjectGroup group : groups) { + group.elementChanged(oldOne, newOne); + } + } + + @Override + public String getProjectFilename() { + return tmxFile.getName(); + } + + public void addLayer(tiled.core.MapLayer layer) { + tmxMap.addLayer(layer); + if (layer instanceof tiled.core.ObjectGroup) { + groups.add(new MapObjectGroup((tiled.core.ObjectGroup) layer, this)); + } + } + + public void removeLayer(tiled.core.MapLayer layer) { + tmxMap.removeLayer(tmxMap.getLayerIndex(layer)); + if (layer instanceof tiled.core.ObjectGroup) { + MapObjectGroup toRemove = null; + for (MapObjectGroup group : groups) { + if (group.tmxGroup == layer) { + toRemove = group; + } + } + if (toRemove != null) { + groups.remove(toRemove); + } + } + } + + public MapObjectGroup getGroup(tiled.core.ObjectGroup selectedLayer) { + for (MapObjectGroup group : groups) { + if (group.tmxGroup == selectedLayer) { + return group; + } + } + return null; + } + + public List getMapchangesNames() { + List result = new ArrayList(); + result.add(null); + for (MapObjectGroup group : groups) { + for (MapObject obj : group.mapObjects) { + if (obj.type == MapObject.Types.mapchange) { + result.add(obj.name); + } + } + } + return result; + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMapSet.java b/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMapSet.java new file mode 100644 index 0000000..63f0b33 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMapSet.java @@ -0,0 +1,184 @@ +package com.gpl.rpg.atcontentstudio.model.maps; + +import java.awt.Image; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +import javax.swing.tree.TreeNode; + +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.GameSource.Type; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class TMXMapSet implements ProjectTreeNode { + + public static final String DEFAULT_REL_PATH_IN_SOURCE = "res/xml/"; + public static final String DEFAULT_REL_PATH_IN_PROJECT = "maps/"; + + public File mapFolder = null; + public List tmxMaps; + + public ProjectTreeNode parent; + + public TMXMapSet(GameSource source) { + this.parent = source; + if (source.type == GameSource.Type.source) this.mapFolder = new File(source.baseFolder, DEFAULT_REL_PATH_IN_SOURCE); + else if (source.type == GameSource.Type.created | source.type == GameSource.Type.altered) { + this.mapFolder = new File(source.baseFolder, DEFAULT_REL_PATH_IN_PROJECT); + if (!this.mapFolder.exists()) { + this.mapFolder.mkdirs(); + } + } + this.tmxMaps = new ArrayList(); + + if (this.mapFolder != null) { + for (File f : this.mapFolder.listFiles()) { + if (f.getName().endsWith(".tmx") || f.getName().endsWith(".TMX")) { + TMXMap map = new TMXMap(this, f); + if (source.type == GameSource.Type.created | source.type == GameSource.Type.altered) { + map.writable = true; + } + tmxMaps.add(map); + } + } + } + } + + @Override + public Enumeration children() { + return Collections.enumeration(tmxMaps); + } + @Override + public boolean getAllowsChildren() { + return true; + } + @Override + public TreeNode getChildAt(int arg0) { + return tmxMaps.get(arg0); + } + @Override + public int getChildCount() { + return tmxMaps.size(); + } + @Override + public int getIndex(TreeNode arg0) { + return tmxMaps.indexOf(arg0); + } + @Override + public TreeNode getParent() { + return parent; + } + @Override + public boolean isLeaf() { + return false; + } + @Override + public void childrenAdded(List path) { + path.add(0, this); + parent.childrenAdded(path); + } + @Override + public void childrenChanged(List path) { + path.add(0, this); + parent.childrenChanged(path); + } + @Override + public void childrenRemoved(List path) { + if (path.size() == 1 && this.getChildCount() == 1) { + childrenRemoved(new ArrayList()); + } else { + path.add(0, this); + parent.childrenRemoved(path); + } + } + @Override + public void notifyCreated() { + childrenAdded(new ArrayList()); + for (TMXMap map : tmxMaps) { + map.notifyCreated(); + } + } + @Override + public String getDesc() { + return "TMX Maps"; + } + + @Override + public Project getProject() { + return parent.getProject(); + } + + + @Override + public Image getIcon() { + return getOpenIcon(); + } + @Override + public Image getClosedIcon() { + return DefaultIcons.getTmxClosedIcon(); + } + @Override + public Image getLeafIcon() { + return DefaultIcons.getTmxClosedIcon(); + } + @Override + public Image getOpenIcon() { + return DefaultIcons.getTmxOpenIcon(); + } + + @Override + public GameDataSet getDataSet() { + return null; + } + + @Override + public Type getDataType() { + return parent.getDataType(); + } + + @Override + public boolean isEmpty() { + return tmxMaps.isEmpty(); + } + + public TMXMap getMap(String id) { + if (tmxMaps == null) return null; + for (TMXMap map : tmxMaps) { + if (id.equals(map.id)){ + return map; + } + } + return null; + } + + public void addMap(TMXMap node) { + ProjectTreeNode higherEmptyParent = this; + while (higherEmptyParent != null) { + if (higherEmptyParent.getParent() != null && ((ProjectTreeNode)higherEmptyParent.getParent()).isEmpty()) higherEmptyParent = (ProjectTreeNode)higherEmptyParent.getParent(); + else break; + } + if (higherEmptyParent == this && !this.isEmpty()) higherEmptyParent = null; + tmxMaps.add(node); + if (node.tmxFile != null) { + //Altered node. + node.tmxFile = new File(this.mapFolder, node.tmxFile.getName()); + } else { + //Created node. + node.tmxFile = new File(this.mapFolder, node.id+".tmx"); + } + node.parent = this; + if (higherEmptyParent != null) higherEmptyParent.notifyCreated(); + else node.notifyCreated(); + } + + public TMXMap get(int index) { + return tmxMaps.get(index); + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/Worldmap.java b/src/com/gpl/rpg/atcontentstudio/model/maps/Worldmap.java new file mode 100644 index 0000000..98a88ed --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/Worldmap.java @@ -0,0 +1,281 @@ +package com.gpl.rpg.atcontentstudio.model.maps; + +import java.awt.Image; +import java.awt.Point; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.tree.TreeNode; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerFactoryConfigurationError; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.GameSource.Type; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class Worldmap extends ArrayList implements ProjectTreeNode { + + private static final long serialVersionUID = 4590409256594556179L; + + public static final String DEFAULT_REL_PATH_IN_SOURCE = "res/xml/worldmap.xml"; + public static final String DEFAULT_REL_PATH_IN_PROJECT = "maps"+File.separator+"worldmap.xml"; + + public File worldmapFile; + public GameSource parent; + + public Map> segments = new HashMap>(); + + public Worldmap(GameSource gameSource) { + this.parent = gameSource; + if (getDataType() == Type.source) { + worldmapFile = new File(parent.baseFolder, DEFAULT_REL_PATH_IN_SOURCE); + } else { + worldmapFile = new File(parent.baseFolder, DEFAULT_REL_PATH_IN_PROJECT); + } + if (worldmapFile.exists()) { + loadFromFile(worldmapFile); + } + if (getDataType() == Type.source) { + for (WorldmapSegment segment : this) { + segment.writable = false; + } + } else { + for (WorldmapSegment segment : this) { + segment.writable = true; + } + } + } + + private void loadFromFile(File file) { + if (!file.exists()) return; + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + Document doc; + try { + factory.setIgnoringComments(true); + factory.setIgnoringElementContentWhitespace(true); + factory.setExpandEntityReferences(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + InputSource insrc = new InputSource(new FileInputStream(file)); + insrc.setSystemId("http://worldmap/"); + insrc.setEncoding("UTF-8"); + doc = builder.parse(insrc); + + Element root = (Element) doc.getElementsByTagName("worldmap").item(0); + if (root != null) { + NodeList segmentsList = root.getElementsByTagName("segment"); + if (segmentsList != null) { + for (int i = 0; i < segmentsList.getLength(); i++) { + Element segmentNode = (Element) segmentsList.item(i); + String name = segmentNode.getAttribute("id"); + add(new WorldmapSegment(this, name, segmentNode)); + } + } + } + } catch (SAXException e) { + e.printStackTrace(); + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (ParserConfigurationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + @Override + public Enumeration children() { + return Collections.enumeration(this); + } + + @Override + public boolean getAllowsChildren() { + return true; + } + + @Override + public TreeNode getChildAt(int arg0) { + return get(arg0); + } + + @Override + public int getChildCount() { + return size(); + } + + @Override + public int getIndex(TreeNode arg0) { + return indexOf(arg0); + } + + @Override + public TreeNode getParent() { + return parent; + } + + @Override + public boolean isLeaf() { + return false; + } + + @Override + public void childrenAdded(List path) { + path.add(0,this); + parent.childrenAdded(path); + } + + @Override + public void childrenChanged(List path) { + path.add(0,this); + parent.childrenChanged(path); + } + + @Override + public void childrenRemoved(List path) { + if (path.size() == 1 && this.getChildCount() == 1) { + childrenRemoved(new ArrayList()); + } else { + path.add(0, this); + parent.childrenRemoved(path); + } + } + + @Override + public String getDesc() { + return "Worldmap"; + } + @Override + public void notifyCreated() { + childrenAdded(new ArrayList()); + } + + @Override + public Project getProject() { + return parent.getProject(); + } + + @Override + public Image getIcon() { + return DefaultIcons.getMapClosedIcon(); + } + @Override + public Image getLeafIcon() { + return null; + } + @Override + public Image getClosedIcon() { + return DefaultIcons.getMapClosedIcon(); + } + @Override + public Image getOpenIcon() { + return DefaultIcons.getMapOpenIcon(); + } + + @Override + public GameDataSet getDataSet() { + return null; + } + + @Override + public Type getDataType() { + return parent.getDataType(); + } + + public void save() { + + try { + Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); + doc.setXmlVersion("1.0"); + Element root = doc.createElement("worldmap"); + doc.appendChild(root); + + for (WorldmapSegment segment : this) { + root.appendChild(segment.toXmlElement(doc)); + } + + saveDocToFile(doc, worldmapFile); + } catch (ParserConfigurationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public WorldmapSegment getWorldmapSegment(String id) { + for (WorldmapSegment s : this) { + if (s.id.equals(id)) { + return s; + } + } + return null; + } + + public void addSegment(WorldmapSegment node) { + ProjectTreeNode higherEmptyParent = this; + while (higherEmptyParent != null) { + if (higherEmptyParent.getParent() != null && ((ProjectTreeNode)higherEmptyParent.getParent()).isEmpty()) higherEmptyParent = (ProjectTreeNode)higherEmptyParent.getParent(); + else break; + } + if (higherEmptyParent == this && !this.isEmpty()) higherEmptyParent = null; + add(node); + node.parent = this; + if (higherEmptyParent != null) higherEmptyParent.notifyCreated(); + else node.notifyCreated(); + } + + + public static void saveDocToFile(Document doc, File f) { + try { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + Result output = new StreamResult(new FileOutputStream(f)); + Source input = new DOMSource(doc); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.transform(input, output); + } catch (TransformerConfigurationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (TransformerFactoryConfigurationError e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (TransformerException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/WorldmapSegment.java b/src/com/gpl/rpg/atcontentstudio/model/maps/WorldmapSegment.java new file mode 100644 index 0000000..9b26b27 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/WorldmapSegment.java @@ -0,0 +1,149 @@ +package com.gpl.rpg.atcontentstudio.model.maps; + +import java.awt.Image; +import java.awt.Point; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.SaveEvent; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class WorldmapSegment extends GameDataElement { + + private static final long serialVersionUID = 2658610076889592723L; + + public int segmentX; + public int segmentY; + public Map mapLocations = new HashMap(); + public Map labelLocations = new HashMap(); + public Element xmlNode; + + public WorldmapSegment(Worldmap parent, String name, Element xmlNode) { + this.parent = parent; + this.id = name; + this.xmlNode = xmlNode; + } + + @Override + public GameDataSet getDataSet() { + return parent.getDataSet(); + } + + @Override + public String getDesc() { + return id; + } + + @Override + public void parse() { + segmentX = Integer.parseInt(xmlNode.getAttribute("x")); + segmentY = Integer.parseInt(xmlNode.getAttribute("y")); + NodeList mapsList = xmlNode.getElementsByTagName("map"); + for (int j = 0; j < mapsList.getLength(); j++) { + Element mapNode = (Element) mapsList.item(j); + mapLocations.put(mapNode.getAttribute("id"), new Point(Integer.parseInt(mapNode.getAttribute("x")) - segmentX, Integer.parseInt(mapNode.getAttribute("y")) - segmentY)); + } + NodeList namedAreasNodeList = xmlNode.getElementsByTagName("namedarea"); + for (int j = 0; j < namedAreasNodeList.getLength(); j++) { + Element namedAreaNode = (Element) namedAreasNodeList.item(j); + labelLocations.put(namedAreaNode.getAttribute("id"), new NamedArea(namedAreaNode.getAttribute("id"), namedAreaNode.getAttribute("name"), namedAreaNode.getAttribute("type"))); + } + this.state = State.parsed; + } + + @Override + public void link() { + if (this.state == State.init) { + this.parse(); + } else if (this.state == State.linked) { + return; + } + for (String mapName : mapLocations.keySet()) { + getProject().getMap(mapName).addBacklink(this); + } + } + + @Override + public WorldmapSegment clone() { + WorldmapSegment clone = new WorldmapSegment((Worldmap)parent, id, (Element) xmlNode.cloneNode(true)); + + return clone; + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + oldOne.removeBacklink(this); + newOne.addBacklink(this); + } + + @Override + public String getProjectFilename() { + return "worldmap.xml"; + } + + @Override + public void save() { + ((Worldmap)parent).save(); + } + + public Element toXmlElement(Document doc) { + Element element = doc.createElement("segment"); + element.setAttribute("id", id); + element.setAttribute("x", Integer.toString(segmentX)); + element.setAttribute("y", Integer.toString(segmentY)); + + for (String s : mapLocations.keySet()) { + Element map = doc.createElement("map"); + map.setAttribute("id", s); + map.setAttribute("x", Integer.toString(mapLocations.get(s).x + segmentX)); + map.setAttribute("y", Integer.toString(mapLocations.get(s).y + segmentY)); + element.appendChild(map); + } + + for (NamedArea area : labelLocations.values()) { + Element namedArea = doc.createElement("namedarea"); + namedArea.setAttribute("id", area.id); + namedArea.setAttribute("name", area.name); + namedArea.setAttribute("type", area.type); + element.appendChild(namedArea); + } + + return element; + } + + @Override + public List attemptSave() { + // TODO Auto-generated method stub + save(); + return null; + } + + public static class NamedArea { + String id; + String name; + String type; + + public NamedArea(String id, String name, String type) { + this.id = id; + this.name = name; + this.type = type; + } + } + + @Override + public Image getIcon() { + return DefaultIcons.getUIMapIcon(); + } + @Override + public Image getLeafIcon() { + return DefaultIcons.getUIMapIcon(); + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/saves/SavedGame.java b/src/com/gpl/rpg/atcontentstudio/model/saves/SavedGame.java new file mode 100644 index 0000000..220789c --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/saves/SavedGame.java @@ -0,0 +1,177 @@ +package com.gpl.rpg.atcontentstudio.model.saves; + +import java.awt.Image; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import javax.swing.tree.TreeNode; + +import com.gpl.rpg.andorstrainer.io.SavedGameIO; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.GameSource.Type; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.SaveEvent; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class SavedGame extends GameDataElement { + + private static final long serialVersionUID = -6443495534761084990L; + + public File savedFile; + transient public com.gpl.rpg.andorstrainer.model.SavedGame loadedSave = null; + transient public SavedGamesSet parent; + + public SavedGame(SavedGamesSet parent, File f) throws IOException { + savedFile = f; + refreshTransients(parent); + } + + public void refreshTransients(SavedGamesSet parent) throws IOException { + this.parent = parent; + this.loadedSave = SavedGameIO.loadFile(savedFile); + if (this.loadedSave == null) { + throw new IOException("Unable to load save: "+savedFile.getAbsolutePath()); + } + } + + @Override + public Enumeration children() { + return null; + } + + @Override + public boolean getAllowsChildren() { + return false; + } + + @Override + public TreeNode getChildAt(int arg0) { + return null; + } + + @Override + public int getChildCount() { + return 0; + } + + @Override + public int getIndex(TreeNode arg0) { + return 0; + } + + @Override + public TreeNode getParent() { + return parent; + } + + @Override + public boolean isLeaf() { + return true; + } + + @Override + public void childrenAdded(List path) { + path.add(0,this); + parent.childrenAdded(path); + } + + @Override + public void childrenChanged(List path) { + path.add(0,this); + parent.childrenChanged(path); + } + + @Override + public void childrenRemoved(List path) { + path.add(0,this); + parent.childrenRemoved(path); + } + @Override + public void notifyCreated() { + childrenAdded(new ArrayList()); + } + @Override + public String getDesc() { + return loadedSave.displayInfo; + } + + @Override + public Project getProject() { + return parent.getProject(); + } + + @Override + public Image getIcon() { + return DefaultIcons.getHeroIcon(); + } + @Override + public Image getLeafIcon() { + return DefaultIcons.getHeroIcon(); + } + @Override + public Image getClosedIcon() {return null;} + @Override + public Image getOpenIcon() {return null;} + + @Override + public GameDataSet getDataSet() { + return null; + } + + @Override + public Type getDataType() { + return null; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public void parse() { + // TODO Auto-generated method stub + + } + + @Override + public void link() { + // TODO Auto-generated method stub + + } + + @Override + public GameDataElement clone() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + // TODO Auto-generated method stub + + } + + @Override + public String getProjectFilename() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void save() { + // TODO Auto-generated method stub + + } + + @Override + public List attemptSave() { + // TODO Auto-generated method stub + return null; + } +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/saves/SavedGamesSet.java b/src/com/gpl/rpg/atcontentstudio/model/saves/SavedGamesSet.java new file mode 100644 index 0000000..39998a8 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/saves/SavedGamesSet.java @@ -0,0 +1,171 @@ +package com.gpl.rpg.atcontentstudio.model.saves; + +import java.awt.Image; +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Vector; + +import javax.swing.tree.TreeNode; + +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.model.GameSource.Type; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class SavedGamesSet implements ProjectTreeNode, Serializable { + + private static final long serialVersionUID = -6565834239789184087L; + + public Vector saves; //For simulations. + + public Project parent; + + public SavedGamesSet(Project parent) { + this.parent = parent; + saves = new Vector(); + } + + public void refreshTransients() { + for (SavedGame save : saves) { + try { + save.refreshTransients(this); + } catch (IOException e) { + Notification.addError(e.getMessage()); + } + } + } + + public void addSave(File f) { + try { + ProjectTreeNode higherEmptyParent = this; + while (higherEmptyParent != null) { + if (higherEmptyParent.getParent() != null && ((ProjectTreeNode)higherEmptyParent.getParent()).isEmpty()) higherEmptyParent = (ProjectTreeNode)higherEmptyParent.getParent(); + else break; + } + if (higherEmptyParent == this && !this.isEmpty()) higherEmptyParent = null; + SavedGame node = new SavedGame(this, f); + saves.add(node); + if (higherEmptyParent != null) higherEmptyParent.notifyCreated(); + else node.notifyCreated(); + } catch (IOException e) { + Notification.addError(e.getMessage()); + } + } + + public SavedGame getSave(File f) { + for (SavedGame save : saves) { + if (save.savedFile.equals(f)) return save; + } + return null; + } + + @Override + public Enumeration children() { + return saves.elements(); + } + + @Override + public boolean getAllowsChildren() { + return true; + } + + @Override + public TreeNode getChildAt(int arg0) { + return saves.elementAt(arg0); + } + + @Override + public int getChildCount() { + return saves.size(); + } + + @Override + public int getIndex(TreeNode arg0) { + return saves.indexOf(arg0); + } + + @Override + public TreeNode getParent() { + return parent; + } + + @Override + public boolean isLeaf() { + return false; + } + + @Override + public void childrenAdded(List path) { + path.add(0, this); + parent.childrenAdded(path); + } + @Override + public void childrenChanged(List path) { + path.add(0, this); + parent.childrenChanged(path); + } + @Override + public void childrenRemoved(List path) { + if (path.size() == 1 && this.getChildCount() == 1) { + childrenRemoved(new ArrayList()); + } else { + path.add(0, this); + parent.childrenRemoved(path); + } + } + @Override + public void notifyCreated() { + childrenAdded(new ArrayList()); + for (SavedGame s : saves) { + s.notifyCreated(); + } + } + @Override + public String getDesc() { + return "Saved games"; + } + + @Override + public Project getProject() { + return parent.getProject(); + } + + + @Override + public Image getIcon() { + return getOpenIcon(); + } + @Override + public Image getClosedIcon() { + return DefaultIcons.getSavClosedIcon(); + } + @Override + public Image getLeafIcon() { + return DefaultIcons.getSavClosedIcon(); + } + @Override + public Image getOpenIcon() { + return DefaultIcons.getSavOpenIcon(); + } + + + @Override + public GameDataSet getDataSet() { + return null; + } + @Override + public Type getDataType() { + return null; + } + + @Override + public boolean isEmpty() { + return saves.isEmpty(); + } +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/sprites/SpriteSheetSet.java b/src/com/gpl/rpg/atcontentstudio/model/sprites/SpriteSheetSet.java new file mode 100644 index 0000000..9401cea --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/sprites/SpriteSheetSet.java @@ -0,0 +1,154 @@ +package com.gpl.rpg.atcontentstudio.model.sprites; + +import java.awt.Image; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +import javax.swing.tree.TreeNode; + +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.GameSource.Type; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class SpriteSheetSet implements ProjectTreeNode { + + public static final String DEFAULT_REL_PATH_IN_SOURCE = "res"+File.separator+"drawable"+File.separator; + public static final String DEFAULT_REL_PATH_IN_PROJECT = "spritesheets"+File.separator; + + public File drawableFolder = null; + + public transient List spritesheets; + + public GameSource parent; + + public SpriteSheetSet(GameSource source) { + this.parent = source; + if (source.type == GameSource.Type.source) this.drawableFolder = new File(source.baseFolder, DEFAULT_REL_PATH_IN_SOURCE); + else if (source.type == GameSource.Type.created | source.type == GameSource.Type.altered) { + this.drawableFolder = new File(source.baseFolder, DEFAULT_REL_PATH_IN_PROJECT); + if (!this.drawableFolder.exists()) { + this.drawableFolder.mkdirs(); + } + } + spritesheets = new ArrayList(); + if (this.drawableFolder != null) { + for (File f : this.drawableFolder.listFiles()) { + if (f.getName().endsWith(".png") || f.getName().endsWith(".PNG")) { + spritesheets.add(new Spritesheet(this, f)); + } + } + } + } + + @Override + public Enumeration children() { + return Collections.enumeration(spritesheets); + } + @Override + public boolean getAllowsChildren() { + return true; + } + @Override + public TreeNode getChildAt(int arg0) { + return spritesheets.get(arg0); + } + @Override + public int getChildCount() { + return spritesheets.size(); + } + @Override + public int getIndex(TreeNode arg0) { + return spritesheets.indexOf(arg0); + } + @Override + public TreeNode getParent() { + return parent; + } + @Override + public boolean isLeaf() { + return false; + } + @Override + public void childrenAdded(List path) { + path.add(0, this); + parent.childrenAdded(path); + } + @Override + public void childrenChanged(List path) { + path.add(0, this); + parent.childrenChanged(path); + } + @Override + public void childrenRemoved(List path) { + if (path.size() == 1 && this.getChildCount() == 1) { + childrenRemoved(new ArrayList()); + } else { + path.add(0, this); + parent.childrenRemoved(path); + } + } + @Override + public void notifyCreated() { + childrenAdded(new ArrayList()); + for (Spritesheet s : spritesheets) { + s.notifyCreated(); + } + } + @Override + public String getDesc() { + return "Spritesheets"; + } + + @Override + public Project getProject() { + return parent.getProject(); + } + + @Override + public Image getIcon() { + return getOpenIcon(); + } + @Override + public Image getClosedIcon() { + return DefaultIcons.getSpriteClosedIcon(); + } + @Override + public Image getLeafIcon() { + return DefaultIcons.getSpriteClosedIcon(); + } + @Override + public Image getOpenIcon() { + return DefaultIcons.getSpriteOpenIcon(); + } + + @Override + public GameDataSet getDataSet() { + return null; + } + + @Override + public Type getDataType() { + return parent.getDataType(); + } + + @Override + public boolean isEmpty() { + return spritesheets.isEmpty(); + } + + public Spritesheet getSpritesheet(String id) { + if (spritesheets == null) return null; + for (Spritesheet sheet : spritesheets) { + if (id.equals(sheet.id)){ + return sheet; + } + } + return null; + } +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/sprites/Spritesheet.java b/src/com/gpl/rpg/atcontentstudio/model/sprites/Spritesheet.java new file mode 100644 index 0000000..0dad07d --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/sprites/Spritesheet.java @@ -0,0 +1,271 @@ +package com.gpl.rpg.atcontentstudio.model.sprites; + +import java.awt.Graphics; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.imageio.ImageIO; +import javax.swing.tree.TreeNode; + +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.GameSource.Type; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.SaveEvent; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; + +public class Spritesheet extends GameDataElement { + + private static final long serialVersionUID = -5981708088278528586L; + + public SpriteSheetSet parent; + public File spritesheetFile; + public int spriteWidth = 32; + public int spriteHeight = 32; + public String id; + public Category category = Category.none; + public boolean animated = false; + + public enum Category { + none, + monster, + item, + actorcondition + }; + + //Lazy initialization. + public BufferedImage spritesheet = null; + public Map cache_full_size = new HashMap(); + public Map cache_icon = new HashMap(); + + public Spritesheet(SpriteSheetSet parent, File f) { + this.spritesheetFile = f; + this.id = f.getName().substring(0, f.getName().lastIndexOf(".")); + this.parent = parent; + + String cat = getProject().getSpritesheetsProperty("atcs.spritesheet."+this.id+".category"); + if (cat != null) { + this.category = Category.valueOf(cat); + } + String sizex = getProject().getSpritesheetsProperty("atcs.spritesheet."+this.id+".sizex"); + if (sizex != null) { + this.spriteWidth = Integer.parseInt(sizex); + } + String sizey = getProject().getSpritesheetsProperty("atcs.spritesheet."+this.id+".sizey"); + if (sizey != null) { + this.spriteHeight = Integer.parseInt(sizey); + } + String anim = getProject().getSpritesheetsProperty("atcs.spritesheet."+this.id+".animate"); + if (anim != null) { + this.animated = Boolean.parseBoolean(anim); + } + } + + @Override + public Enumeration children() { + return null; + } + @Override + public boolean getAllowsChildren() { + return false; + } + @Override + public TreeNode getChildAt(int arg0) { + return null; + } + @Override + public int getChildCount() { + return 0; + } + @Override + public int getIndex(TreeNode arg0) { + return 0; + } + @Override + public TreeNode getParent() { + return parent; + } + @Override + public boolean isLeaf() { + return true; + } + @Override + public void childrenAdded(List path) { + path.add(0, this); + parent.childrenAdded(path); + } + @Override + public void childrenChanged(List path) { + path.add(0, this); + parent.childrenChanged(path); + } + @Override + public void childrenRemoved(List path) { + path.add(0, this); + parent.childrenRemoved(path); + } + @Override + public void notifyCreated() { + childrenAdded(new ArrayList()); + } + @Override + public String getDesc() { + return spritesheetFile.getName(); + } + + @Override + public Project getProject() { + return parent.getProject(); + } + + public int getSpriteCount() { + if (spritesheet == null) { + try { + spritesheet = ImageIO.read(spritesheetFile); + } catch (IOException e) { + Notification.addError("Error loading image "+spritesheetFile.getAbsolutePath()+" : "+e.getMessage()); + e.printStackTrace(); + return 0; + } + } + return (int) (Math.ceil(((double)spritesheet.getWidth()) / ((double)spriteWidth)) * Math.ceil(((double)spritesheet.getHeight()) / ((double)spriteHeight))); + } + + public BufferedImage getImage(int index) { + if (spritesheet == null) { + try { + spritesheet = ImageIO.read(spritesheetFile); + } catch (IOException e) { + Notification.addError("Error loading image "+spritesheetFile.getAbsolutePath()+" : "+e.getMessage()); + e.printStackTrace(); + return null; + } + } + if (cache_full_size.get(index) != null) { + return cache_full_size.get(index); + } + BufferedImage result = new BufferedImage(spriteWidth, spriteHeight, BufferedImage.TYPE_INT_ARGB); + Graphics g = result.getGraphics(); + int sx1, sy1; + sx1 = (index * spriteWidth) % spritesheet.getWidth(); + sy1 = spriteHeight * ((index * spriteWidth) / spritesheet.getWidth()); + if (sx1 + spriteWidth > spritesheet.getWidth() || sy1 + spriteHeight > spritesheet.getHeight()) { + g.finalize(); + return null; + } + g.drawImage(spritesheet, 0, 0, spriteWidth, spriteHeight, sx1, sy1, sx1 + spriteWidth, sy1 + spriteHeight, null); + result.flush(); + g.finalize(); + cache_full_size.put(index, result); + return result; + } + + public Image getIcon(int index) { + if (cache_icon.get(index) != null) { + return cache_icon.get(index); + } + Image result = getImage(index); + if (result == null) return null; + result = result.getScaledInstance(16, 16, BufferedImage.SCALE_SMOOTH); + cache_icon.put(index, result); + return result; + } + + public void clearCache() { + cache_full_size.clear(); + cache_icon.clear(); + } + + + @Override + public Image getIcon() { + return getIcon(0); + } + @Override + public Image getLeafIcon() { + return getIcon(); + } + @Override + public Image getClosedIcon() {return null;} + @Override + public Image getOpenIcon() {return null;} + + + @Override + public GameDataSet getDataSet() { + return null; + } + + @Override + public Type getDataType() { + return parent.getDataType(); + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public void parse() { + if(this.state == GameDataElement.State.init){ + this.state = GameDataElement.State.parsed; + } + } + + @Override + public void link() { + if (this.state == GameDataElement.State.init) { + this.parse(); + } + if(this.state == GameDataElement.State.parsed) { + this.state = GameDataElement.State.linked; + } + } + + @Override + public GameDataElement clone() { + Spritesheet clone = new Spritesheet((SpriteSheetSet) getParent(), new File(spritesheetFile.getAbsolutePath())); + clone.id = this.id; + clone.animated = this.animated; + clone.category = this.category; + clone.spriteWidth = this.spriteWidth; + clone.spriteHeight = this.spriteHeight; + return clone; + } + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + //nothing linked. + } + + @Override + public String getProjectFilename() { + return spritesheetFile.getName(); + } + + @Override + public void save() { + if (this.category != null) getProject().setSpritesheetsProperty("atcs.spritesheet."+this.id+".category", this.category.toString()); + if (this.spriteWidth != 32) getProject().setSpritesheetsProperty("atcs.spritesheet."+this.id+".sizex", Integer.toString(this.spriteWidth)); + if (this.spriteHeight != 32) getProject().setSpritesheetsProperty("atcs.spritesheet."+this.id+".sizey", Integer.toString(this.spriteHeight)); + if (this.animated)getProject().setSpritesheetsProperty("atcs.spritesheet."+this.id+".animate", Boolean.toString(this.animated)); + getProject().save(); + + this.state = GameDataElement.State.saved; + } + + @Override + public List attemptSave() { + save(); + return null; + } +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/AboutEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/AboutEditor.java new file mode 100644 index 0000000..c862b55 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/AboutEditor.java @@ -0,0 +1,152 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.BorderLayout; +import java.awt.Desktop; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Scanner; + +import javax.swing.ImageIcon; +import javax.swing.JEditorPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkEvent.EventType; +import javax.swing.event.HyperlinkListener; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.SaveEvent; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.jidesoft.swing.JideTabbedPane; + +public class AboutEditor extends Editor { + + private static final long serialVersionUID = 6230549148222457139L; + + public static final String WELCOME_STRING = + "" + + "" + + "" + + "
Welcome to "+ATContentStudio.APP_NAME+" "+ATContentStudio.APP_VERSION+"
" + + "
" + + "This is a content editor for Andor's Trail.
" + + "Right click on the left area or use the \"File\" menu to create a project.
" + + "
" + + "Play Andor's Trail for free on your Android device.
" + + "Visit the official forum to give or receive help.
" + + "Open the project's Google Code page to check out the game's source code.
" + + "
" + + "For content creation help, make sure to use the following resources:
" + + "The contribution guide on the forums
" + + "The developer section of the Andor's Trail wiki
" + + "The design outline document on Google Drive/Docs
" + + "
" + + "Credits:
" + + "
" + + "Author: Zukero
" + + "Licence: GPL v3
" + + "Sources are included in this package.
" + + "
" + + "This project uses the following libraries:
" + + "JSON.simple by Yidong Fang & Chris Nokleberg.
" + + "License: Apache License 2.0
" + + "
" + + "RSyntaxTextArea by Robert Futrell.
" + + "License: Modified BSD License (a.k.a. 3-Clause BSD)
" + + "
" + + "JIDE Common Layer by JIDE Software.
" + + "License: GPL v2 with classpath exception
" + + "
" + + "A modified version of libtiled-java by Adam Turk & Thorbjorn Lindeijer.
" + + "License: Simplified BSD License (a.k.a 2-Clause BSD)
" + + "Sources of the modified version are included in this package.
" + + "
" + + "Prefuse by the Berkeley Institue of Design.
" + + "License: Modified BSD License (a.k.a 3-Clause BSD)
" + + "
" + + "See the tabs below to find the full license text for each of these.
" + + "
" + + "The Windows installer was created with:
" + + "NSIS (Nullsoft Scriptable Install System) v2.46" + + "
" + + ""; + + + public static final AboutEditor instance = new AboutEditor(); + private AboutEditor() { + this.name="About "+ATContentStudio.APP_NAME; + this.icon = new ImageIcon(DefaultIcons.getMainIconIcon()); + this.target = new GameDataElement(){ + private static final long serialVersionUID = -227480102288529682L; + @Override + public GameDataSet getDataSet() {return null;} + @Override + public String getDesc() {return null;} + @Override + public void parse() {} + @Override + public void link() {} + @Override + public GameDataElement clone() {return null;} + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) {} + @Override + public String getProjectFilename() {return null;} + @Override + public void save() {} + @Override + public List attemptSave() {return null;} + }; + + setLayout(new BorderLayout()); + JideTabbedPane editorTabsHolder = new JideTabbedPane(JideTabbedPane.BOTTOM); + editorTabsHolder.setTabShape(JideTabbedPane.SHAPE_FLAT); + editorTabsHolder.setUseDefaultShowCloseButtonOnTab(false); + editorTabsHolder.setShowCloseButtonOnTab(false); + add(editorTabsHolder, BorderLayout.CENTER); + + editorTabsHolder.add("Welcome", getInfoPane(WELCOME_STRING, "text/html")); + editorTabsHolder.add("JSON.simple License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.JSON.simple.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text")); + editorTabsHolder.add("RSyntaxTextArea License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/RSyntaxTextArea.License.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text")); + editorTabsHolder.add("JIDE Common Layer License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.JIDE.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text")); + editorTabsHolder.add("libtiled-java License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.libtiled.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text")); + editorTabsHolder.add("prefuse License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/license-prefuse.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text")); + editorTabsHolder.add("ATCS License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.GPLv3.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text")); + + } + + private JPanel getInfoPane(String content, String mime) { + JEditorPane welcome = new JEditorPane(); + welcome.setContentType(mime); + welcome.setText(content); + welcome.setEditable(false); + welcome.addHyperlinkListener(new HyperlinkListener() { + @Override + public void hyperlinkUpdate(HyperlinkEvent arg0) { + arg0.getEventType(); + if (arg0.getEventType() == EventType.ACTIVATED) { + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + try { + Desktop.getDesktop().browse(arg0.getURL().toURI()); + } catch (IOException e) { + e.printStackTrace(); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + } + } + } + }); + JPanel pane = new JPanel(); + pane.setLayout(new BorderLayout()); + pane.add(new JScrollPane(welcome), BorderLayout.CENTER); + return pane; + } + + + @Override + public void targetUpdated() {} + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/BooleanBasedCheckBox.java b/src/com/gpl/rpg/atcontentstudio/ui/BooleanBasedCheckBox.java new file mode 100644 index 0000000..b0bd514 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/BooleanBasedCheckBox.java @@ -0,0 +1,17 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import javax.swing.JCheckBox; + +public class BooleanBasedCheckBox extends JCheckBox { + + private static final long serialVersionUID = 3941646360487399554L; + + public Boolean getBooleanValue() { + return isSelected() ? Boolean.TRUE : null; + } + + public void setBooleanValue(Boolean val) { + setSelected(val != null && val); + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/CollapsiblePanel.java b/src/com/gpl/rpg/atcontentstudio/ui/CollapsiblePanel.java new file mode 100644 index 0000000..e19180a --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/CollapsiblePanel.java @@ -0,0 +1,160 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +import javax.swing.BorderFactory; +import javax.swing.JPanel; +import javax.swing.border.TitledBorder; + +public class CollapsiblePanel extends JPanel { + + private static final long serialVersionUID = 319384990345722150L; + + String title; + TitledBorder border; + + public CollapsiblePanel(String title) { + super(); + this.title = title; + border = BorderFactory.createTitledBorder(title); + setBorder(border); + BorderLayout borderLayout = new BorderLayout(); + setLayout(borderLayout); + addMouseListener(mouseListener); + } + + MouseListener mouseListener = new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + toggleVisibility(); + } + }; + + ComponentListener contentComponentListener = new ComponentAdapter() { + @Override + public void componentShown(ComponentEvent e) { + updateBorderTitle(); + } + @Override + public void componentHidden(ComponentEvent e) { + updateBorderTitle(); + } + }; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + String oldTitle = this.title; + this.title = title; + firePropertyChange("title", oldTitle, this.title); + updateBorderTitle(); + } + + @Override + public Component add(Component comp) { + comp.addComponentListener(contentComponentListener); + Component r = super.add(comp); + updateBorderTitle(); + return r; + } + + @Override + public Component add(String name, Component comp) { + comp.addComponentListener(contentComponentListener); + Component r = super.add(name, comp); + updateBorderTitle(); + return r; + } + + @Override + public Component add(Component comp, int index) { + comp.addComponentListener(contentComponentListener); + Component r = super.add(comp, index); + updateBorderTitle(); + return r; + } + + @Override + public void add(Component comp, Object constraints) { + comp.addComponentListener(contentComponentListener); + super.add(comp, constraints); + updateBorderTitle(); + } + + @Override + public void add(Component comp, Object constraints, int index) { + comp.addComponentListener(contentComponentListener); + super.add(comp, constraints, index); + updateBorderTitle(); + } + + @Override + public void remove(int index) { + Component comp = getComponent(index); + comp.removeComponentListener(contentComponentListener); + super.remove(index); + } + + @Override + public void remove(Component comp) { + comp.removeComponentListener(contentComponentListener); + super.remove(comp); + } + + @Override + public void removeAll() { + for (Component c : getComponents()) { + c.removeComponentListener(contentComponentListener); + } + super.removeAll(); + } + + protected void toggleVisibility() { + toggleVisibility(hasInvisibleComponent()); + } + + protected void toggleVisibility(boolean visible) { + for (Component c : getComponents()) { + c.setVisible(visible); + } + updateBorderTitle(); + } + + protected void updateBorderTitle() { + String arrow = ""; + if (getComponentCount() > 0) { + arrow = (hasInvisibleComponent()?"[+] ":"[-] "); + } + border.setTitle(arrow+title); + repaint(); + } + + protected final boolean hasInvisibleComponent() { + for (Component c : getComponents()) { + if (!c.isVisible()) { + return true; + } + } + return false; + } + + public void collapse() { + toggleVisibility(false); + } + public void expand() { + toggleVisibility(true); + } + public void setExpanded(boolean expand) { + toggleVisibility(expand); + } + +} \ No newline at end of file diff --git a/src/com/gpl/rpg/atcontentstudio/ui/DefaultIcons.java b/src/com/gpl/rpg/atcontentstudio/ui/DefaultIcons.java new file mode 100644 index 0000000..0612850 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/DefaultIcons.java @@ -0,0 +1,254 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.Image; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.imageio.ImageIO; + +import com.gpl.rpg.atcontentstudio.Notification; + +public class DefaultIcons { + + private static Map imageCache = new HashMap(); + private static Map iconCache = new HashMap(); + + + private static String MAIN_ICON_RES = "/com/gpl/rpg/atcontentstudio/img/andorstrainer.png"; + public static Image getMainIconImage() { return getImage(MAIN_ICON_RES); } + public static Image getMainIconIcon() { return getIcon(MAIN_ICON_RES); } + + private static String FOLDER_STD_CLOSED_RES = "/com/gpl/rpg/atcontentstudio/img/folder_std_closed.png"; + public static Image getStdClosedImage() { return getImage(FOLDER_STD_CLOSED_RES); } + public static Image getStdClosedIcon() { return getIcon(FOLDER_STD_CLOSED_RES); } + + private static String FOLDER_STD_OPEN_RES = "/com/gpl/rpg/atcontentstudio/img/folder_std_open.png"; + public static Image getStdOpenImage() { return getImage(FOLDER_STD_OPEN_RES); } + public static Image getStdOpenIcon() { return getIcon(FOLDER_STD_OPEN_RES); } + + private static String FOLDER_JSON_CLOSED_RES = "/com/gpl/rpg/atcontentstudio/img/folder_json_closed.png"; + public static Image getJsonClosedImage() { return getImage(FOLDER_JSON_CLOSED_RES); } + public static Image getJsonClosedIcon() { return getIcon(FOLDER_JSON_CLOSED_RES); } + + private static String FOLDER_JSON_OPEN_RES = "/com/gpl/rpg/atcontentstudio/img/folder_json_open.png"; + public static Image getJsonOpenImage() { return getImage(FOLDER_JSON_OPEN_RES); } + public static Image getJsonOpenIcon() { return getIcon(FOLDER_JSON_OPEN_RES); } + + private static String FOLDER_SAV_CLOSED_RES = "/com/gpl/rpg/atcontentstudio/img/folder_sav_closed.png"; + public static Image getSavClosedImage() { return getImage(FOLDER_SAV_CLOSED_RES); } + public static Image getSavClosedIcon() { return getIcon(FOLDER_SAV_CLOSED_RES); } + + private static String FOLDER_SAV_OPEN_RES = "/com/gpl/rpg/atcontentstudio/img/folder_sav_open.png"; + public static Image getSavOpenImage() { return getImage(FOLDER_SAV_OPEN_RES); } + public static Image getSavOpenIcon() { return getIcon(FOLDER_SAV_OPEN_RES); } + + private static String FOLDER_SPRITE_CLOSED_RES = "/com/gpl/rpg/atcontentstudio/img/folder_sprite_closed.png"; + public static Image getSpriteClosedImage() { return getImage(FOLDER_SPRITE_CLOSED_RES); } + public static Image getSpriteClosedIcon() { return getIcon(FOLDER_SPRITE_CLOSED_RES); } + + private static String FOLDER_SPRITE_OPEN_RES = "/com/gpl/rpg/atcontentstudio/img/folder_sprite_open.png"; + public static Image getSpriteOpenImage() { return getImage(FOLDER_SPRITE_OPEN_RES); } + public static Image getSpriteOpenIcon() { return getIcon(FOLDER_SPRITE_OPEN_RES); } + + private static String FOLDER_TMX_CLOSED_RES = "/com/gpl/rpg/atcontentstudio/img/folder_tmx_closed.png"; + public static Image getTmxClosedImage() { return getImage(FOLDER_TMX_CLOSED_RES); } + public static Image getTmxClosedIcon() { return getIcon(FOLDER_TMX_CLOSED_RES); } + + private static String FOLDER_TMX_OPEN_RES = "/com/gpl/rpg/atcontentstudio/img/folder_tmx_open.png"; + public static Image getTmxOpenImage() { return getImage(FOLDER_TMX_OPEN_RES); } + public static Image getTmxOpenIcon() { return getIcon(FOLDER_TMX_OPEN_RES); } + + private static String FOLDER_MAP_CLOSED_RES = "/com/gpl/rpg/atcontentstudio/img/folder_map_closed.png"; + public static Image getMapClosedImage() { return getImage(FOLDER_MAP_CLOSED_RES); } + public static Image getMapClosedIcon() { return getIcon(FOLDER_MAP_CLOSED_RES); } + + private static String FOLDER_MAP_OPEN_RES = "/com/gpl/rpg/atcontentstudio/img/folder_map_open.png"; + public static Image getMapOpenImage() { return getImage(FOLDER_MAP_OPEN_RES); } + public static Image getMapOpenIcon() { return getIcon(FOLDER_MAP_OPEN_RES); } + + private static String FOLDER_AT_CLOSED_RES = "/com/gpl/rpg/atcontentstudio/img/folder_at_closed.png"; + public static Image getATClosedImage() { return getImage(FOLDER_AT_CLOSED_RES); } + public static Image getATClosedIcon() { return getIcon(FOLDER_AT_CLOSED_RES); } + + private static String FOLDER_AT_OPEN_RES = "/com/gpl/rpg/atcontentstudio/img/folder_at_open.png"; + public static Image getATOpenImage() { return getImage(FOLDER_AT_OPEN_RES); } + public static Image getATOpenIcon() { return getIcon(FOLDER_AT_OPEN_RES); } + + private static String TILED_ICON_RES = "/com/gpl/rpg/atcontentstudio/img/tiled-icon.png"; + public static Image getTiledIconImage() { return getImage(TILED_ICON_RES); } + public static Image getTiledIconIcon() { return getIcon(TILED_ICON_RES); } + + private static String UI_MAP_RES = "/com/gpl/rpg/atcontentstudio/img/ui_icon_map.png"; + public static Image getUIMapImage() { return getImage(UI_MAP_RES); } + public static Image getUIMapIcon() { return getIcon(UI_MAP_RES); } + + private static String HERO_RES = "/com/gpl/rpg/atcontentstudio/img/char_hero.png"; + public static Image getHeroImage() { return getImage(HERO_RES); } + public static Image getHeroIcon() { return getIcon(HERO_RES); } + + private static String TILE_LAYER_RES = "/com/gpl/rpg/atcontentstudio/img/tile_layer.png"; + public static Image getTileLayerImage() { return getImage(TILE_LAYER_RES); } + public static Image getTileLayerIcon() { return getIcon(TILE_LAYER_RES); } + + private static String OBJECT_LAYER_RES = "/com/gpl/rpg/atcontentstudio/img/object_layer.png"; + public static Image getObjectLayerImage() { return getImage(OBJECT_LAYER_RES); } + public static Image getObjectLayerIcon() { return getIcon(OBJECT_LAYER_RES); } + + private static String ACTOR_CONDITION_RES = "/com/gpl/rpg/atcontentstudio/img/actor_condition.png"; + public static Image getActorConditionImage() { return getImage(ACTOR_CONDITION_RES); } + public static Image getActorConditionIcon() { return getIcon(ACTOR_CONDITION_RES); } + + private static String ITEM_RES = "/com/gpl/rpg/atcontentstudio/img/item.png"; + public static Image getItemImage() { return getImage(ITEM_RES); } + public static Image getItemIcon() { return getIcon(ITEM_RES); } + + private static String NPC_RES = "/com/gpl/rpg/atcontentstudio/img/npc.png"; + public static Image getNPCImage() { return getImage(NPC_RES); } + public static Image getNPCIcon() { return getIcon(NPC_RES); } + + private static String NPC_CLOSE_RES = "/com/gpl/rpg/atcontentstudio/img/npc_close.png"; + public static Image getNPCCloseImage() { return getImage(NPC_CLOSE_RES); } + public static Image getNPCCloseIcon() { return getIcon(NPC_CLOSE_RES); } + + private static String DIALOGUE_RES = "/com/gpl/rpg/atcontentstudio/img/dialogue.png"; + public static Image getDialogueImage() { return getImage(DIALOGUE_RES); } + public static Image getDialogueIcon() { return getIcon(DIALOGUE_RES); } + + private static String QUEST_RES = "/com/gpl/rpg/atcontentstudio/img/ui_icon_quest.png"; + public static Image getQuestImage() { return getImage(QUEST_RES); } + public static Image getQuestIcon() { return getIcon(QUEST_RES); } + + private static String DROPLIST_RES = "/com/gpl/rpg/atcontentstudio/img/ui_icon_equipment.png"; + public static Image getDroplistImage() { return getImage(DROPLIST_RES); } + public static Image getDroplistIcon() { return getIcon(DROPLIST_RES); } + + private static String COMBAT_RES = "/com/gpl/rpg/atcontentstudio/img/ui_icon_combat.png"; + public static Image getCombatImage() { return getImage(COMBAT_RES); } + public static Image getCombatIcon() { return getIcon(COMBAT_RES); } + + private static String GOLD_RES = "/com/gpl/rpg/atcontentstudio/img/ui_icon_coins.png"; + public static Image getGoldImage() { return getImage(GOLD_RES); } + public static Image getGoldIcon() { return getIcon(GOLD_RES); } + + private static String ITEM_CATEGORY_RES = "/com/gpl/rpg/atcontentstudio/img/equip_weapon.png"; + public static Image getItemCategoryImage() { return getImage(ITEM_CATEGORY_RES); } + public static Image getItemCategoryIcon() { return getIcon(ITEM_CATEGORY_RES); } + + private static String NULLIFY_RES = "/com/gpl/rpg/atcontentstudio/img/nullify.png"; + public static Image getNullifyImage() { return getImage(NULLIFY_RES); } + public static Image getNullifyIcon() { return getIcon(NULLIFY_RES); } + + private static String CREATE_RES = "/com/gpl/rpg/atcontentstudio/img/file_create.png"; + public static Image getCreateImage() { return getImage(CREATE_RES); } + public static Image getCreateIcon() { return getIcon(CREATE_RES); } + + private static String ARROW_UP_RES = "/com/gpl/rpg/atcontentstudio/img/arrow_up.png"; + public static Image getArrowUpImage() { return getImage(ARROW_UP_RES); } + public static Image getArrowUpIcon() { return getIcon(ARROW_UP_RES); } + + private static String ARROW_DOWN_RES = "/com/gpl/rpg/atcontentstudio/img/arrow_down.png"; + public static Image getArrowDownImage() { return getImage(ARROW_DOWN_RES); } + public static Image getArrowDownIcon() { return getIcon(ARROW_DOWN_RES); } + + private static String ARROW_LEFT_RES = "/com/gpl/rpg/atcontentstudio/img/arrow_left.png"; + public static Image getArrowLeftImage() { return getImage(ARROW_LEFT_RES); } + public static Image getArrowLeftIcon() { return getIcon(ARROW_LEFT_RES); } + + private static String ARROW_RIGHT_RES = "/com/gpl/rpg/atcontentstudio/img/arrow_right.png"; + public static Image getArrowRightImage() { return getImage(ARROW_RIGHT_RES); } + public static Image getArrowRightIcon() { return getIcon(ARROW_RIGHT_RES); } + + private static String CONTAINER_RES = "/com/gpl/rpg/atcontentstudio/img/container.png"; + public static Image getContainerImage() { return getImage(CONTAINER_RES); } + public static Image getContainerIcon() { return getIcon(CONTAINER_RES); } + + private static String KEY_RES = "/com/gpl/rpg/atcontentstudio/img/key.png"; + public static Image getKeyImage() { return getImage(KEY_RES); } + public static Image getKeyIcon() { return getIcon(KEY_RES); } + + private static String MAPCHANGE_RES = "/com/gpl/rpg/atcontentstudio/img/mapchange.png"; + public static Image getMapchangeImage() { return getImage(MAPCHANGE_RES); } + public static Image getMapchangeIcon() { return getIcon(MAPCHANGE_RES); } + + private static String REPLACE_RES = "/com/gpl/rpg/atcontentstudio/img/replace.png"; + public static Image getReplaceImage() { return getImage(REPLACE_RES); } + public static Image getReplaceIcon() { return getIcon(REPLACE_RES); } + + private static String REST_RES = "/com/gpl/rpg/atcontentstudio/img/rest.png"; + public static Image getRestImage() { return getImage(REST_RES); } + public static Image getRestIcon() { return getIcon(REST_RES); } + + private static String SCRIPT_RES = "/com/gpl/rpg/atcontentstudio/img/script.png"; + public static Image getScriptImage() { return getImage(SCRIPT_RES); } + public static Image getScriptIcon() { return getIcon(SCRIPT_RES); } + + private static String SIGN_RES = "/com/gpl/rpg/atcontentstudio/img/sign.png"; + public static Image getSignImage() { return getImage(SIGN_RES); } + public static Image getSignIcon() { return getIcon(SIGN_RES); } + + private static String CREATE_CONTAINER_RES = "/com/gpl/rpg/atcontentstudio/img/create_container.png"; + public static Image getCreateContainerImage() { return getImage(CREATE_CONTAINER_RES); } + public static Image getCreateContainerIcon() { return getIcon(CREATE_CONTAINER_RES); } + + private static String CREATE_KEY_RES = "/com/gpl/rpg/atcontentstudio/img/create_key.png"; + public static Image getCreateKeyImage() { return getImage(CREATE_KEY_RES); } + public static Image getCreateKeyIcon() { return getIcon(CREATE_KEY_RES); } + + private static String CREATE_REPLACE_RES = "/com/gpl/rpg/atcontentstudio/img/create_replace.png"; + public static Image getCreateReplaceImage() { return getImage(CREATE_REPLACE_RES); } + public static Image getCreateReplaceIcon() { return getIcon(CREATE_REPLACE_RES); } + + private static String CREATE_REST_RES = "/com/gpl/rpg/atcontentstudio/img/create_rest.png"; + public static Image getCreateRestImage() { return getImage(CREATE_REST_RES); } + public static Image getCreateRestIcon() { return getIcon(CREATE_REST_RES); } + + private static String CREATE_SCRIPT_RES = "/com/gpl/rpg/atcontentstudio/img/create_script.png"; + public static Image getCreateScriptImage() { return getImage(CREATE_SCRIPT_RES); } + public static Image getCreateScriptIcon() { return getIcon(CREATE_SCRIPT_RES); } + + private static String CREATE_SIGN_RES = "/com/gpl/rpg/atcontentstudio/img/create_sign.png"; + public static Image getCreateSignImage() { return getImage(CREATE_SIGN_RES); } + public static Image getCreateSignIcon() { return getIcon(CREATE_SIGN_RES); } + + private static String CREATE_SPAWNAREA_RES = "/com/gpl/rpg/atcontentstudio/img/create_spawnarea.png"; + public static Image getCreateSpawnareaImage() { return getImage(CREATE_SPAWNAREA_RES); } + public static Image getCreateSpawnareaIcon() { return getIcon(CREATE_SPAWNAREA_RES); } + + private static String CREATE_MAPCHANGE_RES = "/com/gpl/rpg/atcontentstudio/img/create_tiled.png"; + public static Image getCreateMapchangeImage() { return getImage(CREATE_MAPCHANGE_RES); } + public static Image getCreateMapchangeIcon() { return getIcon(CREATE_MAPCHANGE_RES); } + + private static String CREATE_OBJECT_GROUP_RES = "/com/gpl/rpg/atcontentstudio/img/create_object_group.png"; + public static Image getCreateObjectGroupImage() { return getImage(CREATE_OBJECT_GROUP_RES); } + public static Image getCreateObjectGroupIcon() { return getIcon(CREATE_OBJECT_GROUP_RES); } + + private static String CREATE_TILE_LAYER_RES = "/com/gpl/rpg/atcontentstudio/img/create_tile_layer.png"; + public static Image getCreateTileLayerImage() { return getImage(CREATE_TILE_LAYER_RES); } + public static Image getCreateTileLayerIcon() { return getIcon(CREATE_TILE_LAYER_RES); } + + private static String ZOOM_RES = "/com/gpl/rpg/atcontentstudio/img/zoom.png"; + public static Image getZoomImage() { return getImage(ZOOM_RES); } + public static Image getZoomIcon() { return getIcon(ZOOM_RES); } + + private static Image getImage(String res) { + if (imageCache.get(res) == null) { + try { + Image img = ImageIO.read(DefaultIcons.class.getResourceAsStream(res)); + imageCache.put(res, img); + } catch (IOException e) { + Notification.addError("Failed to load image "+res); + e.printStackTrace(); + } + } + return imageCache.get(res); + } + + private static Image getIcon(String res) { + if (iconCache.get(res) == null) { + Image icon = getImage(res).getScaledInstance(16, 16, Image.SCALE_SMOOTH); + iconCache.put(res, icon); + } + return iconCache.get(res); + } +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/Editor.java b/src/com/gpl/rpg/atcontentstudio/ui/Editor.java new file mode 100644 index 0000000..d150132 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/Editor.java @@ -0,0 +1,735 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.AbstractListModel; +import javax.swing.ComboBoxModel; +import javax.swing.DefaultListCellRenderer; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.JSpinner.NumberEditor; +import javax.swing.JTextField; +import javax.swing.ListModel; +import javax.swing.SpinnerNumberModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.text.DefaultFormatter; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectElementListener; +import com.gpl.rpg.atcontentstudio.model.gamedata.ActorCondition; +import com.gpl.rpg.atcontentstudio.model.gamedata.Dialogue; +import com.gpl.rpg.atcontentstudio.model.gamedata.Droplist; +import com.gpl.rpg.atcontentstudio.model.gamedata.Item; +import com.gpl.rpg.atcontentstudio.model.gamedata.ItemCategory; +import com.gpl.rpg.atcontentstudio.model.gamedata.JSONElement; +import com.gpl.rpg.atcontentstudio.model.gamedata.NPC; +import com.gpl.rpg.atcontentstudio.model.gamedata.Quest; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; +import com.jidesoft.swing.ComboBoxSearchable; +import com.jidesoft.swing.JideBoxLayout; + +public abstract class Editor extends JPanel implements ProjectElementListener { + + private static final long serialVersionUID = 241750514033596878L; + private static final FieldUpdateListener nullListener = new FieldUpdateListener() {@Override public void valueChanged(JComponent source, Object value) {}}; + + public static final String SAVE = "Save"; + public static final String DELETE = "Delete"; + public static final String REVERT = "Revert to original"; + public static final String ALTER = "Alter"; + public static final String GO_TO_ALTERED = "Go to altered"; + + + public static final String READ_ONLY_MESSAGE = + "" + + "This element is not modifiable.
" + + "Click on the \"Alter\" button to create a writable copy." + + "
"; + + public static final String ALTERED_EXISTS_MESSAGE = + "" + + "This element is not modifiable.
" + + "A writable copy exists in this project. Click on \"Go to altered\" to open it." + + "
"; + + public static final String ALTERED_MESSAGE = + "" + + "This element is a writable copy of an element of the referenced game source.
" + + "Take care not to break existing content when modifying it." + + "
"; + + public static final String CREATED_MESSAGE = + "" + + "This element is a creation of yours.
" + + "Do as you please." + + "
"; + + + public String name = "Editor"; + public Icon icon = null; + public GameDataElement target = null; + + public JLabel message = null; + + + public static JTextField addLabelField(JPanel pane, String label, String value) { + return addTextField(pane, label, value, false, nullListener); + } + + public static JTextField addTextField(JPanel pane, String label, String initialValue, boolean editable, final FieldUpdateListener listener) { + JPanel tfPane = new JPanel(); + tfPane.setLayout(new JideBoxLayout(tfPane, JideBoxLayout.LINE_AXIS, 6)); + JLabel tfLabel = new JLabel(label); + tfPane.add(tfLabel, JideBoxLayout.FIX); + final JTextField tfField = new JTextField(initialValue); + tfField.setEditable(editable); + tfPane.add(tfField, JideBoxLayout.VARY); + JButton nullify = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + tfPane.add(nullify, JideBoxLayout.FIX); + nullify.setEnabled(editable); + pane.add(tfPane, JideBoxLayout.FIX); + + nullify.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + tfField.setText(""); + listener.valueChanged(tfField, null); + } + }); + tfField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + listener.valueChanged(tfField, tfField.getText()); + } + @Override + public void insertUpdate(DocumentEvent e) { + listener.valueChanged(tfField, tfField.getText()); + } + @Override + public void changedUpdate(DocumentEvent e) { + listener.valueChanged(tfField, tfField.getText()); + } + }); + tfField.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + listener.valueChanged(tfField, tfField.getText()); + } + }); + return tfField; + } + +// public static JSpinner addIntegerField(JPanel pane, String label, Integer initialValue, boolean allowNegatives, boolean editable) { +// return addIntegerField(pane, label, initialValue, allowNegatives, editable, nullListener); +// } + + public static JSpinner addIntegerField(JPanel pane, String label, Integer initialValue, boolean allowNegatives, boolean editable, final FieldUpdateListener listener) { + JPanel tfPane = new JPanel(); + tfPane.setLayout(new JideBoxLayout(tfPane, JideBoxLayout.LINE_AXIS, 6)); + JLabel tfLabel = new JLabel(label); + tfPane.add(tfLabel, JideBoxLayout.FIX); + final JSpinner spinner = new JSpinner(new SpinnerNumberModel(initialValue != null ? initialValue.intValue() : 0, allowNegatives ? Integer.MIN_VALUE : 0, Integer.MAX_VALUE, 1)); + ((JSpinner.DefaultEditor)spinner.getEditor()).getTextField().setHorizontalAlignment(JTextField.LEFT); + spinner.setEnabled(editable); + ((DefaultFormatter)((NumberEditor)spinner.getEditor()).getTextField().getFormatter()).setCommitsOnValidEdit(true); + tfPane.add(spinner, JideBoxLayout.VARY); + JButton nullify = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + tfPane.add(nullify, JideBoxLayout.FIX); + nullify.setEnabled(editable); + pane.add(tfPane, JideBoxLayout.FIX); + spinner.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + listener.valueChanged(spinner, spinner.getValue()); + } + }); + nullify.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + spinner.setValue(0); + listener.valueChanged(spinner, null); + } + }); + return spinner; + } + +// public static JSpinner addDoubleField(JPanel pane, String label, Double initialValue, boolean editable) { +// return addDoubleField(pane, label, initialValue, editable, nullListener); +// } + + public static JSpinner addDoubleField(JPanel pane, String label, Double initialValue, boolean editable, final FieldUpdateListener listener) { + JPanel tfPane = new JPanel(); + tfPane.setLayout(new JideBoxLayout(tfPane, JideBoxLayout.LINE_AXIS, 6)); + JLabel tfLabel = new JLabel(label); + tfPane.add(tfLabel, JideBoxLayout.FIX); + final JSpinner spinner = new JSpinner(new SpinnerNumberModel(initialValue != null ? initialValue.doubleValue() : 0.0d, 0.0d, new Float(Float.MAX_VALUE).doubleValue(), 1.0d)); + ((JSpinner.DefaultEditor)spinner.getEditor()).getTextField().setHorizontalAlignment(JTextField.LEFT); + spinner.setEnabled(editable); + ((DefaultFormatter)((NumberEditor)spinner.getEditor()).getTextField().getFormatter()).setCommitsOnValidEdit(true); + tfPane.add(spinner, JideBoxLayout.VARY); + JButton nullify = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + tfPane.add(nullify, JideBoxLayout.FIX); + nullify.setEnabled(editable); + pane.add(tfPane, JideBoxLayout.FIX); + pane.add(tfPane, JideBoxLayout.FIX); + spinner.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + listener.valueChanged(spinner, spinner.getValue()); + } + }); + nullify.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + spinner.setValue(0.0d); + listener.valueChanged(spinner, null); + } + }); + return spinner; + } + + public static IntegerBasedCheckBox addIntegerBasedCheckBox(JPanel pane, String label, Integer initialValue, boolean editable) { + return addIntegerBasedCheckBox(pane, label, initialValue, editable, nullListener); + } + + public static IntegerBasedCheckBox addIntegerBasedCheckBox(JPanel pane, String label, Integer initialValue, boolean editable, final FieldUpdateListener listener) { + JPanel ibcbPane = new JPanel(); + ibcbPane.setLayout(new BorderLayout()); + final IntegerBasedCheckBox ibcb = new IntegerBasedCheckBox(); + ibcb.setText(label); + ibcb.setIntegerValue(initialValue); + ibcb.setEnabled(editable); + ibcbPane.add(ibcb, BorderLayout.WEST); + ibcbPane.add(new JPanel(), BorderLayout.CENTER); + pane.add(ibcbPane, JideBoxLayout.FIX); + ibcb.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + listener.valueChanged(ibcb, ibcb.getIntegerValue()); + } + }); + return ibcb; + } + + public static BooleanBasedCheckBox addBooleanBasedCheckBox(JPanel pane, String label, Boolean initialValue, boolean editable, final FieldUpdateListener listener) { + JPanel bbcbPane = new JPanel(); + bbcbPane.setLayout(new BorderLayout()); + final BooleanBasedCheckBox bbcb = new BooleanBasedCheckBox(); + bbcb.setText(label); + bbcb.setBooleanValue(initialValue); + bbcb.setEnabled(editable); + bbcbPane.add(bbcb, BorderLayout.WEST); + bbcbPane.add(new JPanel(), BorderLayout.CENTER); + pane.add(bbcbPane, JideBoxLayout.FIX); + bbcb.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + listener.valueChanged(bbcb, bbcb.isSelected()); + } + }); + return bbcb; + } + + public static JComboBox addEnumValueBox(JPanel pane, String label, @SuppressWarnings("rawtypes") Enum[] values, @SuppressWarnings("rawtypes") Enum initialValue, boolean writable) { + return addEnumValueBox(pane, label, values, initialValue, writable, new FieldUpdateListener() {@Override public void valueChanged(JComponent source, Object value) {}}); + } + + public static JComboBox addEnumValueBox(JPanel pane, String label, @SuppressWarnings("rawtypes") Enum[] values, @SuppressWarnings("rawtypes") Enum initialValue, boolean writable, final FieldUpdateListener listener) { + JPanel comboPane = new JPanel(); + comboPane.setLayout(new JideBoxLayout(comboPane, JideBoxLayout.LINE_AXIS, 6)); + JLabel comboLabel = new JLabel(label); + comboPane.add(comboLabel, JideBoxLayout.FIX); + final JComboBox enumValuesCombo = new JComboBox(values); + enumValuesCombo.setEnabled(writable); + enumValuesCombo.setSelectedItem(initialValue); + comboPane.add(enumValuesCombo, JideBoxLayout.VARY); + enumValuesCombo.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + listener.valueChanged(enumValuesCombo, e.getItem()); + } + } + }); + JButton nullify = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + comboPane.add(nullify, JideBoxLayout.FIX); + nullify.setEnabled(writable); + nullify.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + enumValuesCombo.setSelectedItem(null); + listener.valueChanged(enumValuesCombo, null); + } + }); + + pane.add(comboPane, JideBoxLayout.FIX); + return enumValuesCombo; + } + + + public MyComboBox addNPCBox(JPanel pane, Project proj, String label, NPC npc, boolean writable, FieldUpdateListener listener) { + final GDEComboModel comboModel = new GDEComboModel(proj, npc){ + private static final long serialVersionUID = 2638082961277241764L; + @Override + public Object getTypedElementAt(int index) { + return project.getNPC(index); + } + @Override + public int getSize() { + return project.getNPCCount()+1; + } + }; + return addGDEBox(pane, label, npc, NPC.class, comboModel, writable, listener); + } + + public MyComboBox addActorConditionBox(JPanel pane, Project proj, String label, ActorCondition acond, boolean writable, FieldUpdateListener listener) { + final GDEComboModel comboModel = new GDEComboModel(proj, acond){ + private static final long serialVersionUID = 2638082961277241764L; + @Override + public Object getTypedElementAt(int index) { + return project.getActorCondition(index); + } + @Override + public int getSize() { + return project.getActorConditionCount()+1; + } + }; + return addGDEBox(pane, label, acond, ActorCondition.class, comboModel, writable, listener); + } + + public MyComboBox addItemBox(JPanel pane, Project proj, String label, Item item, boolean writable, FieldUpdateListener listener) { + final GDEComboModel comboModel = new GDEComboModel(proj, item){ + private static final long serialVersionUID = 2638082961277241764L; + @Override + public Object getTypedElementAt(int index) { + return project.getItem(index); + } + @Override + public int getSize() { + return project.getItemCount()+1; + } + }; + return addGDEBox(pane, label, item, Item.class, comboModel, writable, listener); + } + + public MyComboBox addItemCategoryBox(JPanel pane, Project proj, String label, ItemCategory ic, boolean writable, FieldUpdateListener listener) { + final GDEComboModel comboModel = new GDEComboModel(proj, ic){ + private static final long serialVersionUID = 2638082961277241764L; + @Override + public Object getTypedElementAt(int index) { + return project.getItemCategory(index); + } + @Override + public int getSize() { + return project.getItemCategoryCount()+1; + } + }; + return addGDEBox(pane, label, ic, ItemCategory.class, comboModel, writable, listener); + } + + public MyComboBox addQuestBox(JPanel pane, Project proj, String label, Quest quest, boolean writable, FieldUpdateListener listener) { + final GDEComboModel comboModel = new GDEComboModel(proj, quest){ + private static final long serialVersionUID = 2638082961277241764L; + @Override + public Object getTypedElementAt(int index) { + return project.getQuest(index); + } + @Override + public int getSize() { + return project.getQuestCount()+1; + } + }; + return addGDEBox(pane, label, quest, Quest.class, comboModel, writable, listener); + } + + public MyComboBox addDroplistBox(JPanel pane, Project proj, String label, Droplist droplist, boolean writable, FieldUpdateListener listener) { + final GDEComboModel comboModel = new GDEComboModel(proj, droplist){ + private static final long serialVersionUID = 2638082961277241764L; + @Override + public Object getTypedElementAt(int index) { + return project.getDroplist(index); + } + @Override + public int getSize() { + return project.getDroplistCount()+1; + } + }; + return addGDEBox(pane, label, droplist, Droplist.class, comboModel, writable, listener); + } + + public MyComboBox addDialogueBox(JPanel pane, Project proj, String label, Dialogue dialogue, boolean writable, final FieldUpdateListener listener) { + final GDEComboModel comboModel = new GDEComboModel(proj, dialogue){ + private static final long serialVersionUID = 2638082961277241764L; + @Override + public Object getTypedElementAt(int index) { + return project.getDialogue(index); + } + @Override + public int getSize() { + return project.getDialogueCount()+1; + } + }; + return addGDEBox(pane, label, dialogue, Dialogue.class, comboModel, writable, listener); + } + + public MyComboBox addMapBox(JPanel pane, Project proj, String label, TMXMap map, boolean writable, final FieldUpdateListener listener) { + final GDEComboModel comboModel = new GDEComboModel(proj, map){ + private static final long serialVersionUID = 2638082961277241764L; + @Override + public Object getTypedElementAt(int index) { + return project.getMap(index); + } + @Override + public int getSize() { + return project.getMapCount()+1; + } + }; + return addGDEBox(pane, label, map, TMXMap.class, comboModel, writable, listener); + } + + public MyComboBox addGDEBox(JPanel pane, String label, GameDataElement gde, final Class dataClass, final GDEComboModel comboModel, final boolean writable, final FieldUpdateListener listener) { + JPanel gdePane = new JPanel(); + gdePane.setLayout(new JideBoxLayout(gdePane, JideBoxLayout.LINE_AXIS, 6)); + JLabel gdeLabel = new JLabel(label); + gdePane.add(gdeLabel, JideBoxLayout.FIX); + final MyComboBox gdeBox = new MyComboBox(dataClass, comboModel); + gdeBox.setRenderer(new GDERenderer(false, writable)); + new ComboBoxSearchable(gdeBox){ + @Override + protected String convertElementToString(Object object) { + if (object == null) return "none"; + else return ((GameDataElement)object).getDesc(); + } + }; + gdeBox.setEnabled(writable); + gdePane.add(gdeBox, JideBoxLayout.VARY); + final JButton goToGde = new JButton((Icon) ((gde != null) ? new ImageIcon(gde.getIcon()) : (writable ? new ImageIcon(DefaultIcons.getCreateIcon()) : null))); + goToGde.setEnabled(gde != null || writable); + goToGde.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + GameDataElement selected = ((GameDataElement)comboModel.getSelectedItem()); + if (selected != null) { + ATContentStudio.frame.openEditor(((GameDataElement)comboModel.getSelectedItem())); + ATContentStudio.frame.selectInTree((GameDataElement)comboModel.getSelectedItem()); + } else if (writable) { + JSONCreationWizard wizard = new JSONCreationWizard(((GameDataElement)target).getProject(), dataClass); + wizard.addCreationListener(new JSONCreationWizard.CreationCompletedListener() { + + @Override + public void elementCreated(JSONElement created) { + gdeBox.setSelectedItem(created); + } + }); + wizard.setVisible(true); + } + } + }); + gdePane.add(goToGde, JideBoxLayout.FIX); + gdeBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (gdeBox.getModel().getSelectedItem() == null) { + goToGde.setIcon((writable ? new ImageIcon(DefaultIcons.getCreateIcon()) : null)); + goToGde.setEnabled(writable); + } else { + goToGde.setIcon(new ImageIcon(((GameDataElement)comboModel.getSelectedItem()).getIcon())); + goToGde.setEnabled(true); + } + listener.valueChanged(gdeBox, gdeBox.getModel().getSelectedItem()); + } + }); + JButton nullify = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + gdePane.add(nullify, JideBoxLayout.FIX); + nullify.setEnabled(writable); + nullify.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + gdeBox.setSelectedItem(null); + } + }); + pane.add(gdePane, JideBoxLayout.FIX); + + return gdeBox; + } + + public JList addBacklinksList(JPanel pane, GameDataElement gde) { + final JList list = new JList(new GDEBacklinksListModel(gde)); + list.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + ATContentStudio.frame.openEditor((GameDataElement)list.getSelectedValue()); + ATContentStudio.frame.selectInTree((GameDataElement)list.getSelectedValue()); + } + } + }); + list.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + ATContentStudio.frame.openEditor((GameDataElement)list.getSelectedValue()); + ATContentStudio.frame.selectInTree((GameDataElement)list.getSelectedValue()); + } + } + }); + list.setCellRenderer(new GDERenderer(true, false)); + CollapsiblePanel colPane = new CollapsiblePanel("Elements linking to this one"); + colPane.setLayout(new JideBoxLayout(colPane, JideBoxLayout.PAGE_AXIS)); + colPane.add(new JScrollPane(list), JideBoxLayout.FIX); + colPane.add(new JPanel(), JideBoxLayout.FIX); + if (gde.getBacklinks() == null || gde.getBacklinks().isEmpty()) { + colPane.collapse(); + } + pane.add(colPane, JideBoxLayout.FIX); + return list; + } + + public static abstract class GDEComboModel extends AbstractListModel implements ComboBoxModel { + + private static final long serialVersionUID = -5854574666510314715L; + + public Project project; + public E selected; + + public GDEComboModel(Project proj, E initial) { + this.project = proj; + this.selected = initial; + } + + @Override + public abstract int getSize(); + + @Override + public Object getElementAt(int index) { + if (index == 0) { + return null; + } + return getTypedElementAt(index - 1); + } + + public abstract Object getTypedElementAt(int index); + + @SuppressWarnings("unchecked") + @Override + public void setSelectedItem(Object anItem) { + selected = (E) anItem; + } + + @Override + public Object getSelectedItem() { + return selected; + } + + public void itemAdded(E item, int index) { + fireIntervalAdded(this, index, index); + } + + public void itemRemoved(E item, int index) { + fireIntervalRemoved(this, index, index); + } + + } + + public static class GDERenderer extends DefaultListCellRenderer { + + private static final long serialVersionUID = 6819681566800482793L; + + private boolean includeType = false; + private boolean writable = false; + + public GDERenderer(boolean includeType, boolean writable) { + super(); + this.includeType = includeType; + this.writable = writable; + } + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (value == null) { + label.setText("None"+(writable ? ". Click on the button to create one." : "")); + } else { + if (includeType && ((GameDataElement)value).getDataType() != null) { + label.setText(((GameDataElement)value).getDataType().toString()+"/"+((GameDataElement)value).getDesc()); + } else { + label.setText(((GameDataElement)value).getDesc()); + } + if (((GameDataElement)value).getIcon() == null) { + Notification.addError("Unable to find icon for "+((GameDataElement)value).getDesc()); + } else { + label.setIcon(new ImageIcon(((GameDataElement)value).getIcon())); + } + } + return label; + } + + } + + public static class GDEBacklinksListModel implements ListModel { + + GameDataElement source; + + public GDEBacklinksListModel(GameDataElement source) { + super(); + this.source = source; + source.addBacklinkListener(new GameDataElement.BacklinksListener() { + @Override + public void backlinkRemoved(GameDataElement gde) { + fireListChanged(); + } + @Override + public void backlinkAdded(GameDataElement gde) { + fireListChanged(); + } + }); + } + + @Override + public int getSize() { + return source.getBacklinks().size(); + } + + @Override + public Object getElementAt(int index) { + for (GameDataElement gde : source.getBacklinks()) { + if (index == 0) return gde; + index --; + } + return null; + } + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + + public void fireListChanged() { + for (ListDataListener l : listeners) { + l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, this.getSize())); + } + } + } + + public class MyComboBox extends JComboBox implements ProjectElementListener { + + private static final long serialVersionUID = -4184228604170642567L; + + Class dataType; + + public MyComboBox(Class dataType, ComboBoxModel model) { + super(model); + this.dataType = dataType; + Editor.this.addElementListener(dataType, this); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void elementAdded(GameDataElement added, int index) { + ((GDEComboModel)getModel()).itemAdded(added, index); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void elementRemoved(GameDataElement removed, int index) { + ((GDEComboModel)getModel()).itemRemoved(removed, index); + } + + @Override + public Class getDataType() { + return dataType; + } + + } + + public abstract void targetUpdated(); + + + + transient Map, List> projectElementListeners = new HashMap, List>(); + + public void addElementListener(Class interestingType, ProjectElementListener listener) { + if (projectElementListeners.get(interestingType) == null) { + projectElementListeners.put(interestingType, new ArrayList()); + target.getProject().addElementListener(interestingType, this); + } + projectElementListeners.get(interestingType).add(listener); + } + + public void removeElementListener(ProjectElementListener listener) { + if (listener == null) return; + if (projectElementListeners.get(listener.getDataType()) != null) { + projectElementListeners.get(listener.getDataType()).remove(listener); + if (projectElementListeners.get(listener.getDataType()).isEmpty()) { + target.getProject().removeElementListener(listener.getDataType(), this); + projectElementListeners.remove(listener.getDataType()); + } + } + } + + public void elementAdded(GameDataElement element, int index) { + if (projectElementListeners.get(element.getClass()) != null) { + for (ProjectElementListener l : projectElementListeners.get(element.getClass())) { + l.elementAdded(element, index); + } + } + } + + public void elementRemoved(GameDataElement element, int index) { + if (projectElementListeners.get(element.getClass()) != null) { + for (ProjectElementListener l : projectElementListeners.get(element.getClass())) { + l.elementRemoved(element, index); + } + } + } + + public void clearElementListeners() { + for (Class type : projectElementListeners.keySet()) { + target.getProject().removeElementListener(type, this); + } + } + + public Class getDataType() { + return null; + } + + + + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/EditorsArea.java b/src/com/gpl/rpg/atcontentstudio/ui/EditorsArea.java new file mode 100644 index 0000000..ddfae75 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/EditorsArea.java @@ -0,0 +1,187 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.beans.PropertyChangeListener; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.Action; +import javax.swing.JPanel; + +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.gamedata.ActorCondition; +import com.gpl.rpg.atcontentstudio.model.gamedata.Dialogue; +import com.gpl.rpg.atcontentstudio.model.gamedata.Droplist; +import com.gpl.rpg.atcontentstudio.model.gamedata.Item; +import com.gpl.rpg.atcontentstudio.model.gamedata.ItemCategory; +import com.gpl.rpg.atcontentstudio.model.gamedata.JSONElement; +import com.gpl.rpg.atcontentstudio.model.gamedata.NPC; +import com.gpl.rpg.atcontentstudio.model.gamedata.Quest; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; +import com.gpl.rpg.atcontentstudio.model.maps.WorldmapSegment; +import com.gpl.rpg.atcontentstudio.model.saves.SavedGame; +import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet; +import com.gpl.rpg.atcontentstudio.ui.gamedataeditors.ActorConditionEditor; +import com.gpl.rpg.atcontentstudio.ui.gamedataeditors.DialogueEditor; +import com.gpl.rpg.atcontentstudio.ui.gamedataeditors.DroplistEditor; +import com.gpl.rpg.atcontentstudio.ui.gamedataeditors.ItemCategoryEditor; +import com.gpl.rpg.atcontentstudio.ui.gamedataeditors.ItemEditor; +import com.gpl.rpg.atcontentstudio.ui.gamedataeditors.NPCEditor; +import com.gpl.rpg.atcontentstudio.ui.gamedataeditors.QuestEditor; +import com.gpl.rpg.atcontentstudio.ui.map.TMXMapEditor; +import com.gpl.rpg.atcontentstudio.ui.map.WorldMapEditor; +import com.gpl.rpg.atcontentstudio.ui.saves.SavedGameEditor; +import com.gpl.rpg.atcontentstudio.ui.sprites.SpritesheetEditor; +import com.jidesoft.swing.JideTabbedPane; + +public class EditorsArea extends JPanel { + + private static final long serialVersionUID = 8801849846876081538L; + + private Map editors = new HashMap(); + private JideTabbedPane tabHolder; + + public EditorsArea() { + super(); + setLayout(new BorderLayout()); + tabHolder = new JideTabbedPane(); + tabHolder.setTabPlacement(JideTabbedPane.TOP); + tabHolder.setTabShape(JideTabbedPane.SHAPE_FLAT); + tabHolder.setUseDefaultShowCloseButtonOnTab(false); + tabHolder.setShowCloseButtonOnTab(true); + tabHolder.setCloseAction(new Action() { + @Override + public void actionPerformed(ActionEvent e) { + closeEditor((Editor) e.getSource()); + } + + @Override + public void setEnabled(boolean b) { + } + @Override + public void removePropertyChangeListener(PropertyChangeListener listener) { + } + @Override + public void putValue(String key, Object value) { + } + @Override + public boolean isEnabled() { + return true; + } + @Override + public Object getValue(String key) { + return null; + } + @Override + public void addPropertyChangeListener(PropertyChangeListener listener) { + } + }); + add(tabHolder, BorderLayout.CENTER); + } + + public void openEditor(Editor e) { + if (!editors.containsKey(e.target) && !editors.containsValue(e)) { + editors.put(e.target, e); + tabHolder.addTab(e.name, e.icon, e); + tabHolder.setSelectedComponent(e); + } + } + + public void closeEditor(Editor e) { + if (editors.containsValue(e)) { + tabHolder.remove(e); + editors.remove(e.target); + e.clearElementListeners(); + } + } + + public void openEditor(JSONElement node) { + if (editors.containsKey(node)) { + tabHolder.setSelectedComponent(editors.get(node)); + return; + } + if (node instanceof Quest) { + openEditor(new QuestEditor((Quest)node)); + } else if (node instanceof Dialogue) { + openEditor(new DialogueEditor((Dialogue) node)); + } else if (node instanceof Droplist) { + openEditor(new DroplistEditor((Droplist) node)); + } else if (node instanceof ActorCondition) { + openEditor(new ActorConditionEditor((ActorCondition) node)); + } else if (node instanceof ItemCategory) { + openEditor(new ItemCategoryEditor((ItemCategory) node)); + } else if (node instanceof Item) { + openEditor(new ItemEditor((Item) node)); + } else if (node instanceof NPC) { + openEditor(new NPCEditor((NPC) node)); + } + } + + public void openEditor(Spritesheet node) { + if (editors.containsKey(node)) { + tabHolder.setSelectedComponent(editors.get(node)); + return; + } + node.link(); + openEditor(new SpritesheetEditor((Spritesheet) node)); + } + + public void openEditor(TMXMap node) { + if (editors.containsKey(node)) { + tabHolder.setSelectedComponent(editors.get(node)); + return; + } + node.link(); + openEditor(new TMXMapEditor(node)); + } + + + public void openEditor(SavedGame save) { + if (editors.containsKey(save)) { + tabHolder.setSelectedComponent(editors.get(save)); + return; + } + openEditor(new SavedGameEditor(save)); + } + + + public void openEditor(WorldmapSegment node) { + if (editors.containsKey(node)) { + tabHolder.setSelectedComponent(editors.get(node)); + return; + } + node.link(); + openEditor(new WorldMapEditor(node)); + } + + public void closeEditor(ProjectTreeNode node) { + if (editors.containsKey(node)) { + closeEditor(editors.get(node)); + } + } + + public void editorTabChanged(Editor e) { + int index = tabHolder.indexOfComponent(e); + if (index >= 0) { + tabHolder.setTitleAt(index, e.name); + tabHolder.setIconAt(index, e.icon); + } + } + + public void editorTabChanged(ProjectTreeNode node) { + if (editors.get(node) != null) { + editors.get(node).targetUpdated(); + editorTabChanged(editors.get(node)); + } + } + + public void showAbout() { + if (editors.containsKey(AboutEditor.instance)) { + tabHolder.setSelectedComponent(AboutEditor.instance); + return; + } + openEditor(AboutEditor.instance); + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/FieldUpdateListener.java b/src/com/gpl/rpg/atcontentstudio/ui/FieldUpdateListener.java new file mode 100644 index 0000000..35df936 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/FieldUpdateListener.java @@ -0,0 +1,9 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import javax.swing.JComponent; + +public interface FieldUpdateListener { + + public void valueChanged(JComponent source, Object value); + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/IntegerBasedCheckBox.java b/src/com/gpl/rpg/atcontentstudio/ui/IntegerBasedCheckBox.java new file mode 100644 index 0000000..6d13db8 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/IntegerBasedCheckBox.java @@ -0,0 +1,19 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import javax.swing.JCheckBox; + +public class IntegerBasedCheckBox extends JCheckBox { + + private static final long serialVersionUID = 3941646360487399554L; + + static final Integer one = 1; + + public Integer getIntegerValue() { + return isSelected() ? one : null; + } + + public void setIntegerValue(Integer val) { + setSelected(val != null && val.equals(one)); + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/JMovingIdler.java b/src/com/gpl/rpg/atcontentstudio/ui/JMovingIdler.java new file mode 100644 index 0000000..e859721 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/JMovingIdler.java @@ -0,0 +1,96 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.Color; +import java.awt.GradientPaint; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Paint; + +import javax.swing.JComponent; + + +public class JMovingIdler extends JComponent { + + private static final long serialVersionUID = -2980521421870322717L; + + int position = 0; + boolean destroyed=false, running=false; + Thread moverThread = new Thread(){ + public void run() { + while (!destroyed) { + boolean back = false; + while (running) { + if (back) { + position = --position % 100; + if (position == 0) { + back = false; + } + } else { + position = ++position % 100; + if (position == 99) { + back = true; + } + } + try { + sleep(10); + } catch (InterruptedException e) {} + JMovingIdler.this.revalidate(); + JMovingIdler.this.repaint(); + } + } + } + }; + + public void start() { + if (!moverThread.isAlive()) { + moverThread.start(); + } + running = true; + } + + public void stop() { + running = false; + } + + public void destroy() { + destroyed = true; + running = false; + try { + moverThread.join(); + } catch (InterruptedException e) {} + } + + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; + int w = this.getWidth(); + int h = this.getHeight(); + + g2.setColor(getBackground()); + g2.fillRect(0,0,w,h); + + int x = w * position / 100; + + Paint p = new GradientPaint(x - (w/8), 0, getBackground(), x , 0, getForeground()); + g2.setPaint(p); + g2.fillRect(Math.max(0,x-(w/8)),0, Math.min(x, w), h); + + p = new GradientPaint(x, 0, getForeground(), x + (w/8), 0, getBackground()); + g2.setPaint(p); + g2.fillRect(Math.max(0,x),0, Math.min(x+(w/8), w), h); + + g2.setColor(Color.BLACK); + g2.drawLine(0,0,0,h); + g2.drawLine(0,0,w,0); + g2.drawLine(w,0,w,h); + g2.drawLine(0,h,w,h); + } + + @Override + public void setVisible(boolean aFlag) { + super.setVisible(aFlag); + if (!aFlag) destroy(); + } + +} + diff --git a/src/com/gpl/rpg/atcontentstudio/ui/JSONCreationWizard.java b/src/com/gpl/rpg/atcontentstudio/ui/JSONCreationWizard.java new file mode 100644 index 0000000..c41b4f9 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/JSONCreationWizard.java @@ -0,0 +1,593 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.ComboBoxModel; +import javax.swing.DefaultListCellRenderer; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.event.ListDataListener; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.gamedata.ActorCondition; +import com.gpl.rpg.atcontentstudio.model.gamedata.Dialogue; +import com.gpl.rpg.atcontentstudio.model.gamedata.Droplist; +import com.gpl.rpg.atcontentstudio.model.gamedata.Item; +import com.gpl.rpg.atcontentstudio.model.gamedata.ItemCategory; +import com.gpl.rpg.atcontentstudio.model.gamedata.JSONElement; +import com.gpl.rpg.atcontentstudio.model.gamedata.NPC; +import com.gpl.rpg.atcontentstudio.model.gamedata.Quest; +import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet; +import com.gpl.rpg.atcontentstudio.ui.sprites.SpriteChooser; +import com.jidesoft.swing.JideBoxLayout; + +public class JSONCreationWizard extends JDialog { + + private static final long serialVersionUID = -5744628699021314026L; + + public static enum DataType { + none, + actorCondition, + dialogue, + droplist, + item, + itemCategory, + npc, + quest + } + + private JSONElement creation = null; + final JLabel message; + final JComboBox dataTypeCombo; + final JTextField idField; + final JTextField nameField; + final JButton ok; + final Project proj; + + public JSONCreationWizard(final Project proj, Class dataClass) { + this(proj); + if (dataClass == ActorCondition.class) { + dataTypeCombo.setSelectedItem(DataType.actorCondition); + } else if (dataClass == Dialogue.class) { + dataTypeCombo.setSelectedItem(DataType.dialogue); + } else if (dataClass == Droplist.class) { + dataTypeCombo.setSelectedItem(DataType.droplist); + } else if (dataClass == Item.class) { + dataTypeCombo.setSelectedItem(DataType.item); + } else if (dataClass == ItemCategory.class) { + dataTypeCombo.setSelectedItem(DataType.itemCategory); + } else if (dataClass == NPC.class) { + dataTypeCombo.setSelectedItem(DataType.npc); + } else if (dataClass == Quest.class) { + dataTypeCombo.setSelectedItem(DataType.quest); + } + dataTypeCombo.setEnabled(false); + } + + public JSONCreationWizard(final Project proj) { + super(ATContentStudio.frame); + this.proj = proj; + setTitle("Create Game Data Element (JSON)"); + + JPanel pane = new JPanel(); + pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS, 6)); + + pane.add(new JLabel("Create a new game data element."), JideBoxLayout.FIX); + + message = new JLabel("Select a data type below:"); + pane.add(message, JideBoxLayout.FIX); + + dataTypeCombo = new JComboBox(new DataTypeComboModel()); + dataTypeCombo.setRenderer(new DataTypeComboCellRenderer()); + pane.add(dataTypeCombo); + + final JPanel idPane = new JPanel(); + idPane.setLayout(new BorderLayout()); + JLabel idLabel = new JLabel("Internal ID: "); + idPane.add(idLabel, BorderLayout.WEST); + idField = new JTextField(""); + idField.setEditable(true); + idPane.add(idField, BorderLayout.CENTER); + pane.add(idPane, JideBoxLayout.FIX); + + final JPanel namePane = new JPanel(); + namePane.setLayout(new BorderLayout()); + JLabel nameLabel = new JLabel("Display name: "); + namePane.add(nameLabel, BorderLayout.WEST); + nameField = new JTextField(""); + nameField.setEditable(true); + namePane.add(nameField, BorderLayout.CENTER); + pane.add(namePane, JideBoxLayout.FIX); + + final JPanel iconPane = new JPanel(); + iconPane.setLayout(new BorderLayout()); + final JLabel iconLabel = new JLabel("Icon: "); + iconPane.add(iconLabel, BorderLayout.WEST); + final JButton iconButton = new JButton(new ImageIcon(DefaultIcons.getActorConditionImage())); + iconPane.add(iconButton, BorderLayout.CENTER); + pane.add(iconPane, JideBoxLayout.FIX); + iconPane.setVisible(true); + + dataTypeCombo.addItemListener(new ItemListener() { + + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + idPane.setVisible(true); + switch ((DataType)e.getItem()) { + case actorCondition: + iconPane.setVisible(true); + namePane.setVisible(true); + iconButton.setIcon(new ImageIcon(DefaultIcons.getActorConditionImage())); + creation = new ActorCondition(); + break; + case dialogue: + iconPane.setVisible(false); + namePane.setVisible(false); + creation = new Dialogue(); + break; + case droplist: + iconPane.setVisible(false); + namePane.setVisible(false); + creation = new Droplist(); + break; + case item: + iconPane.setVisible(true); + namePane.setVisible(true); + creation = new Item(); + iconButton.setIcon(new ImageIcon(DefaultIcons.getItemImage())); + break; + case itemCategory: + iconPane.setVisible(false); + namePane.setVisible(true); + creation = new ItemCategory(); + break; + case npc: + iconPane.setVisible(true); + namePane.setVisible(true); + creation = new NPC(); + iconButton.setIcon(new ImageIcon(DefaultIcons.getNPCImage())); + break; + case quest: + iconPane.setVisible(false); + namePane.setVisible(true); + creation = new Quest(); + break; + default: + idPane.setVisible(false); + iconPane.setVisible(false); + namePane.setVisible(false); + creation = null; + break; + } + updateStatus(); + idPane.revalidate(); + namePane.revalidate(); + iconPane.revalidate(); + idPane.repaint(); + namePane.repaint(); + iconPane.repaint(); + } + } + }); + + iconButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + Spritesheet.Category cat = null; + switch ((DataType)dataTypeCombo.getSelectedItem()) { + case actorCondition: + cat = Spritesheet.Category.actorcondition; + break; + case dialogue: + break; + case droplist: + break; + case item: + cat = Spritesheet.Category.item; + break; + case itemCategory: + break; + case npc: + cat = Spritesheet.Category.monster; + break; + case quest: + break; + default: + break; + + } + if (cat == null) return; + SpriteChooser chooser = SpriteChooser.getChooser(proj, cat); + chooser.setSelectionListener(new SpriteChooser.SelectionListener() { + @Override + public void iconSelected(String selected) { + if (selected != null) { + switch ((DataType)dataTypeCombo.getSelectedItem()) { + case actorCondition: + ((ActorCondition)creation).icon_id = selected; + break; + case item: + ((Item)creation).icon_id = selected; + break; + case npc: + ((NPC)creation).icon_id = selected; + break; + case dialogue: + case droplist: + case itemCategory: + case quest: + default: + break; + + } + iconButton.setIcon(new ImageIcon(proj.getImage(selected))); + iconButton.revalidate(); + iconButton.repaint(); + updateStatus(); + } + } + }); + chooser.setVisible(true); + } + }); + + pane.add(new JPanel(), JideBoxLayout.VARY); + + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new JideBoxLayout(buttonPane, JideBoxLayout.LINE_AXIS, 6)); + buttonPane.add(new JPanel(), JideBoxLayout.VARY); + JButton cancel = new JButton("Cancel"); + buttonPane.add(cancel, JideBoxLayout.FIX); + ok = new JButton("Ok"); + buttonPane.add(ok, JideBoxLayout.FIX); + pane.add(buttonPane, JideBoxLayout.FIX); + + ok.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + switch ((DataType)dataTypeCombo.getSelectedItem()) { + case actorCondition: + ((ActorCondition)creation).display_name = nameField.getText(); + break; + case item: + ((Item)creation).name = nameField.getText(); + break; + case npc: + ((NPC)creation).name = nameField.getText(); + break; + case dialogue: + case droplist: + break; + case itemCategory: + ((ItemCategory)creation).name = nameField.getText(); + break; + case quest: + ((Quest)creation).name = nameField.getText(); + break; + default: + return; + } + creation.id = idField.getText(); + JSONCreationWizard.this.setVisible(false); + JSONCreationWizard.this.dispose(); + proj.createElement(creation); + notifyCreated(); + ATContentStudio.frame.selectInTree(creation); + ATContentStudio.frame.openEditor(creation); + } + }); + + cancel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + creation = null; + JSONCreationWizard.this.setVisible(false); + JSONCreationWizard.this.dispose(); + } + }); + + DocumentListener statusUpdater = new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + updateStatus(); + } + @Override + public void insertUpdate(DocumentEvent e) { + updateStatus(); + } + @Override + public void changedUpdate(DocumentEvent e) { + updateStatus(); + } + }; + idField.getDocument().addDocumentListener(statusUpdater); + nameField.getDocument().addDocumentListener(statusUpdater); + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(pane, BorderLayout.CENTER); + + setMinimumSize(new Dimension(350,250)); + idPane.setVisible(false); + iconPane.setVisible(false); + namePane.setVisible(false); + updateStatus(); + pack(); + + Dimension sdim = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension wdim = getSize(); + setLocation((sdim.width - wdim.width)/2, (sdim.height - wdim.height)/2); + } + + public void updateStatus() { + boolean trouble = false; + message.setText("Looks OK to me."); + if (creation == null) { + message.setText("Select a data type below:"); + trouble = true; + } else if (idField.getText() == null || idField.getText().length() <= 0) { + message.setText("Internal ID must not be empty."); + trouble = true; + } else { + switch ((DataType)dataTypeCombo.getSelectedItem()) { + case actorCondition: + if(nameField.getText() == null || nameField.getText().length() <= 0) { + message.setText("An actor condition must have a name."); + trouble = true; + } else if (((ActorCondition)creation).icon_id == null) { + message.setText("An actor condition must have an icon."); + trouble = true; + } else if (proj.getActorCondition(idField.getText()) != null) { + if (proj.getActorCondition(idField.getText()).getDataType() == GameSource.Type.created) { + message.setText("An actor condition with the same ID was already created in this project."); + trouble = true; + } else if (proj.getActorCondition(idField.getText()).getDataType() == GameSource.Type.altered) { + message.setText("An actor condition with the same ID exists in the game and is already altered in this project."); + trouble = true; + } else if (proj.getActorCondition(idField.getText()).getDataType() == GameSource.Type.source) { + message.setText("An actor condition with the same ID exists in the game. It will be added under \"altered\"."); + } + } + break; + case item: + if(nameField.getText() == null || nameField.getText().length() <= 0) { + message.setText("An item must have a name."); + trouble = true; + } else if (((Item)creation).icon_id == null) { + message.setText("An item must have an icon."); + trouble = true; + } else if (proj.getItem(idField.getText()) != null) { + if (proj.getItem(idField.getText()).getDataType() == GameSource.Type.created) { + message.setText("An item with the same ID was already created in this project."); + trouble = true; + } else if (proj.getItem(idField.getText()).getDataType() == GameSource.Type.altered) { + message.setText("An item with the same ID exists in the game and is already altered in this project."); + trouble = true; + } else if (proj.getItem(idField.getText()).getDataType() == GameSource.Type.source) { + message.setText("An item with the same ID exists in the game. It will be added under \"altered\"."); + } + } + break; + case npc: + if(nameField.getText() == null || nameField.getText().length() <= 0) { + message.setText("A NPC must have a name."); + trouble = true; + } else if (((NPC)creation).icon_id == null) { + message.setText("A NPC must have an icon."); + trouble = true; + } else if (proj.getNPC(idField.getText()) != null) { + if (proj.getNPC(idField.getText()).getDataType() == GameSource.Type.created) { + message.setText("A NPC with the same ID was already created in this project."); + trouble = true; + } else if (proj.getNPC(idField.getText()).getDataType() == GameSource.Type.altered) { + message.setText("A NPC with the same ID exists in the game and is already altered in this project."); + trouble = true; + } else if (proj.getNPC(idField.getText()).getDataType() == GameSource.Type.source) { + message.setText("A NPC with the same ID exists in the game. It will be added under \"altered\"."); + } + } + break; + case dialogue: + if (proj.getDialogue(idField.getText()) != null) { + if (proj.getDialogue(idField.getText()).getDataType() == GameSource.Type.created) { + message.setText("A dialogue with the same ID was already created in this project."); + trouble = true; + } else if (proj.getDialogue(idField.getText()).getDataType() == GameSource.Type.altered) { + message.setText("A dialogue with the same ID exists in the game and is already altered in this project."); + trouble = true; + } else if (proj.getDialogue(idField.getText()).getDataType() == GameSource.Type.source) { + message.setText("A dialogue with the same ID exists in the game. It will be added under \"altered\"."); + } + } + break; + case droplist: + if (proj.getDroplist(idField.getText()) != null) { + if (proj.getDroplist(idField.getText()).getDataType() == GameSource.Type.created) { + message.setText("A droplist with the same ID was already created in this project."); + trouble = true; + } else if (proj.getDroplist(idField.getText()).getDataType() == GameSource.Type.altered) { + message.setText("A droplist with the same ID exists in the game and is already altered in this project."); + trouble = true; + } else if (proj.getDroplist(idField.getText()).getDataType() == GameSource.Type.source) { + message.setText("A droplist with the same ID exists in the game. It will be added under \"altered\"."); + } + } + break; + case itemCategory: + if(nameField.getText() == null || nameField.getText().length() <= 0) { + message.setText("An item category must have a name."); + trouble = true; + } else if (proj.getItemCategory(idField.getText()) != null) { + if (proj.getItemCategory(idField.getText()).getDataType() == GameSource.Type.created) { + message.setText("An item category with the same ID was already created in this project."); + trouble = true; + } else if (proj.getItemCategory(idField.getText()).getDataType() == GameSource.Type.altered) { + message.setText("An item category with the same ID exists in the game and is already altered in this project."); + trouble = true; + } else if (proj.getItemCategory(idField.getText()).getDataType() == GameSource.Type.source) { + message.setText("An item category with the same ID exists in the game. It will be added under \"altered\"."); + } + } + break; + case quest: + if(nameField.getText() == null || nameField.getText().length() <= 0) { + message.setText("A quest must have a name."); + trouble = true; + } else if (proj.getQuest(idField.getText()) != null) { + if (proj.getQuest(idField.getText()).getDataType() == GameSource.Type.created) { + message.setText("A quest with the same ID was already created in this project."); + trouble = true; + } else if (proj.getQuest(idField.getText()).getDataType() == GameSource.Type.altered) { + message.setText("A quest with the same ID exists in the game and is already altered in this project."); + trouble = true; + } else if (proj.getQuest(idField.getText()).getDataType() == GameSource.Type.source) { + message.setText("A quest with the same ID exists in the game. It will be added under \"altered\"."); + } + } + break; + default: + break; + } + } + + ok.setEnabled(!trouble); + + message.revalidate(); + message.repaint(); + } + + public static String dataTypeDesc(DataType type) { + switch (type) { + case actorCondition: + return "Actor Condition"; + case dialogue: + return "Dialogue"; + case droplist: + return "Droplist"; + case item: + return "Item"; + case itemCategory: + return "Item Category"; + case npc: + return "NPC"; + case quest: + return "Quest"; + default: + return "Select below"; + } + } + + public static class DataTypeComboModel implements ComboBoxModel { + + DataType selected = DataType.none; + + @Override + public int getSize() { + return DataType.values().length; + } + + @Override + public Object getElementAt(int index) { + return DataType.values()[index]; + } + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + + @Override + public void setSelectedItem(Object anItem) { + selected = (DataType) anItem; + } + + @Override + public Object getSelectedItem() { + return selected; + } + } + + public static class DataTypeComboCellRenderer extends DefaultListCellRenderer { + private static final long serialVersionUID = 5621373849299980998L; + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (c instanceof JLabel) { + ((JLabel)c).setText(JSONCreationWizard.dataTypeDesc((DataType) value)); + switch ((DataType)value) { + case actorCondition: + ((JLabel)c).setIcon(new ImageIcon(DefaultIcons.getActorConditionIcon())); + break; + case dialogue: + ((JLabel)c).setIcon(new ImageIcon(DefaultIcons.getDialogueIcon())); + break; + case droplist: + ((JLabel)c).setIcon(new ImageIcon(DefaultIcons.getDroplistIcon())); + break; + case item: + ((JLabel)c).setIcon(new ImageIcon(DefaultIcons.getItemIcon())); + break; + case itemCategory: + ((JLabel)c).setIcon(new ImageIcon(DefaultIcons.getDroplistIcon())); + break; + case npc: + ((JLabel)c).setIcon(new ImageIcon(DefaultIcons.getNPCIcon())); + break; + case quest: + ((JLabel)c).setIcon(new ImageIcon(DefaultIcons.getQuestIcon())); + break; + default: + break; + + } + } + return c; + } + } + + public static interface CreationCompletedListener { + public void elementCreated(JSONElement created); + } + + private List listeners = new ArrayList(); + + public void addCreationListener(CreationCompletedListener l) { + listeners.add(l); + } + + public void notifyCreated() { + for (CreationCompletedListener l : listeners) { + l.elementCreated(creation); + } + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/JSONImportWizard.java b/src/com/gpl/rpg/atcontentstudio/ui/JSONImportWizard.java new file mode 100644 index 0000000..6cfeb9f --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/JSONImportWizard.java @@ -0,0 +1,720 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.swing.ButtonGroup; +import javax.swing.ComboBoxModel; +import javax.swing.DefaultListCellRenderer; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.ListModel; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; + +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.gamedata.ActorCondition; +import com.gpl.rpg.atcontentstudio.model.gamedata.Dialogue; +import com.gpl.rpg.atcontentstudio.model.gamedata.Droplist; +import com.gpl.rpg.atcontentstudio.model.gamedata.Item; +import com.gpl.rpg.atcontentstudio.model.gamedata.ItemCategory; +import com.gpl.rpg.atcontentstudio.model.gamedata.JSONElement; +import com.gpl.rpg.atcontentstudio.model.gamedata.NPC; +import com.gpl.rpg.atcontentstudio.model.gamedata.Quest; +import com.jidesoft.swing.JideBoxLayout; + +public class JSONImportWizard extends JDialog { + + private static final long serialVersionUID = 661234868711700156L; + + public static enum DataType { + none, + actorCondition, + dialogue, + droplist, + item, + itemCategory, + npc, + quest + } + + Project proj; + + JPanel pane; + JLabel message; + JComboBox dataTypeCombo; + JRadioButton importFromFile; + JRadioButton importPasted; + JPanel fileSelectionPane; + JTextField jsonFileName; + JButton browse; + RSyntaxTextArea jsonPasteArea; + JScrollPane scroller; + JList createdPreview; + JPanel buttonPane; + JButton ok, cancel; + ActionListener okListener, cancelListener; + + public JSONImportWizard(Project proj) { + + super(ATContentStudio.frame); + setTitle("Import data from JSON"); + + this.proj = proj; + + pane = new JPanel(); + pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS, 6)); + + pane.add(new JLabel("Import data in JSON format."), JideBoxLayout.FIX); + + message = new JLabel(); + + dataTypeCombo = new JComboBox(new DataTypeComboModel()); + dataTypeCombo.setRenderer(new DataTypeComboCellRenderer()); + + dataTypeCombo.addItemListener(new ItemListener() { + + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + checkEnableNext(); + } + } + }); + + importPasted = new JRadioButton("Paste JSON text"); + importFromFile = new JRadioButton("Select .json file"); + importPasted.setSelected(true); + ButtonGroup radioGroup = new ButtonGroup(); + radioGroup.add(importPasted); + radioGroup.add(importFromFile); + + importPasted.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (importPasted.isSelected()) { + scroller.setVisible(true); + fileSelectionPane.setVisible(false); + pane.revalidate(); + pane.repaint(); + } + } + }); + importFromFile.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (importFromFile.isSelected()) { + scroller.setVisible(false); + fileSelectionPane.setVisible(true); + pane.revalidate(); + pane.repaint(); + } + } + }); + + jsonFileName = new JTextField(); + jsonFileName.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + checkEnableNext(); + } + @Override + public void insertUpdate(DocumentEvent e) { + checkEnableNext(); + } + @Override + public void changedUpdate(DocumentEvent e) { + checkEnableNext(); + } + }); + browse = new JButton("Browse"); + browse.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JFileChooser jfc = new JFileChooser(){ + private static final long serialVersionUID = -3001082967957619011L; + @Override + public boolean accept(File f) { + if (f.isDirectory() || f.getName().endsWith(".json") || f.getName().endsWith(".JSON")) { + return super.accept(f); + } else { + return false; + } + } + }; + jfc.setMultiSelectionEnabled(false); + int result = jfc.showOpenDialog(ATContentStudio.frame); + if (result == JFileChooser.APPROVE_OPTION) { + jsonFileName.setText(jfc.getSelectedFile().getAbsolutePath()); + checkEnableNext(); + } + } + }); + fileSelectionPane = new JPanel(); + fileSelectionPane.setLayout(new JideBoxLayout(fileSelectionPane, JideBoxLayout.LINE_AXIS, 6)); + fileSelectionPane.add(new JLabel("JSON File: "), JideBoxLayout.FIX); + fileSelectionPane.add(jsonFileName, JideBoxLayout.VARY); + fileSelectionPane.add(browse, JideBoxLayout.FIX); + + jsonPasteArea = new RSyntaxTextArea(); + jsonPasteArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JSON); + + jsonPasteArea.getDocument().addDocumentListener(new DocumentListener() { + + @Override + public void removeUpdate(DocumentEvent e) { + checkEnableNext(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + checkEnableNext(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + checkEnableNext(); + } + + }); + + + + + buttonPane = new JPanel(); + buttonPane.setLayout(new JideBoxLayout(buttonPane, JideBoxLayout.LINE_AXIS, 6)); + buttonPane.add(new JPanel(), JideBoxLayout.VARY); + cancel = new JButton("Cancel"); + buttonPane.add(cancel, JideBoxLayout.FIX); + ok = new JButton("Next"); + buttonPane.add(ok, JideBoxLayout.FIX); + + createdPreview = new JList(new GDEListModel(new ArrayList())); + + showFirstScreen(); + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(pane, BorderLayout.CENTER); + + setMinimumSize(new Dimension(450,350)); + pack(); + + Dimension sdim = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension wdim = getSize(); + setLocation((sdim.width - wdim.width)/2, (sdim.height - wdim.height)/2); + + } + + private void showFirstScreen() { + pane.removeAll(); + message.setText("Select a data type & paste your JSON data below:"); + pane.add(message, JideBoxLayout.FIX); + pane.add(dataTypeCombo, JideBoxLayout.FIX); + pane.add(importPasted, JideBoxLayout.FIX); + pane.add(importFromFile, JideBoxLayout.FIX); + pane.add(fileSelectionPane, JideBoxLayout.FIX); + scroller = new JScrollPane(jsonPasteArea); + scroller.getVerticalScrollBar().setUnitIncrement(16); + JPanel scrollHolder = new JPanel(); + scrollHolder.setLayout(new BorderLayout()); + scrollHolder.add(scroller, BorderLayout.CENTER); + pane.add(scrollHolder, JideBoxLayout.VARY); + pane.add(buttonPane, JideBoxLayout.FIX); + ok.setText("Next"); + ok.setEnabled(jsonPasteArea.getText() != null && jsonPasteArea.getText().length() > 0 && dataTypeCombo.getSelectedItem() != null && dataTypeCombo.getSelectedItem() != DataType.none); + ok.removeActionListener(okListener); + okListener = new ActionListener() { + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void actionPerformed(ActionEvent e) { + List errors = new ArrayList(); + List warnings = new ArrayList(); + List created = new ArrayList(); + Object jsonParserOutput = null; + try { + if (importPasted.isSelected()) { + jsonParserOutput = new JSONParser().parse(jsonPasteArea.getText()); + } else if (importFromFile.isSelected()) { + jsonParserOutput = new JSONParser().parse(new FileReader(new File(jsonFileName.getText()))); + } + } catch (ParseException e1) { + errors.add("Invalid JSON content: "+e1.getMessage()); + } catch (FileNotFoundException e1) { + errors.add("Unable to access file: "+e1.getMessage()); + } catch (IOException e1) { + errors.add("Error while accessing file: "+e1.getMessage()); + } + if (jsonParserOutput != null) { + List jsonObjects = null; + if (jsonParserOutput instanceof List) { + jsonObjects = (List)jsonParserOutput; + } else if (jsonParserOutput instanceof Map) { + jsonObjects = new ArrayList(); + jsonObjects.add((Map) jsonParserOutput); + } else { + errors.add("Invalid JSON content: neither an array nor an object."); + } + if (jsonObjects != null) { + JSONElement node = null; + JSONElement existingNode = null; + int i = 0; + for (Map jsonObject : jsonObjects) { + switch ((DataType)dataTypeCombo.getSelectedItem()) { + case actorCondition: + node = ActorCondition.fromJson(jsonObject); + existingNode = proj.getActorCondition(node.id); + break; + case item: + node = Item.fromJson(jsonObject); + existingNode = proj.getItem(node.id); + break; + case npc: + node = NPC.fromJson(jsonObject); + existingNode = proj.getNPC(node.id); + break; + case dialogue: + node = Dialogue.fromJson(jsonObject); + existingNode = proj.getDialogue(node.id); + break; + case droplist: + node = Droplist.fromJson(jsonObject); + existingNode = proj.getDroplist(node.id); + break; + case itemCategory: + node = ItemCategory.fromJson(jsonObject); + existingNode = proj.getItemCategory(node.id); + break; + case quest: + node = Quest.fromJson(jsonObject); + existingNode = proj.getQuest(node.id); + break; + default: + return; + } + i++; + if (node instanceof JSONElement) { + node.parse(jsonObject); + created.add(node); + if (existingNode != null) { + if (existingNode.getDataType() == GameSource.Type.created) { + errors.add("An item with id "+node.id+" is already created in this project."); + } else if (existingNode.getDataType() == GameSource.Type.altered) { + errors.add("An item with id "+node.id+" is already altered in this project."); + } else { + warnings.add("An item with id "+node.id+" exists in the used game source. This one will be inserted as \"altered\""); + } + existingNode = null; + } + node = null; + } else { + warnings.add("Failed to load element #"+i); + } + } + } + } + if (errors.isEmpty() && warnings.isEmpty()) { + showImportPreviewScreen(created); + } else if (!errors.isEmpty()) { + showErrorScreen(errors); + } else { + showWarningScreen(warnings, created); + } + } + }; + ok.addActionListener(okListener); + cancel.setText("Cancel"); + cancel.removeActionListener(cancelListener); + cancelListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JSONImportWizard.this.setVisible(false); + JSONImportWizard.this.dispose(); + } + }; + cancel.addActionListener(cancelListener); + if (importPasted.isSelected()) { + scroller.setVisible(true); + fileSelectionPane.setVisible(false); + pane.revalidate(); + pane.repaint(); + } else if (importFromFile.isSelected()) { + scroller.setVisible(false); + fileSelectionPane.setVisible(true); + pane.revalidate(); + pane.repaint(); + } + pane.revalidate(); + pane.repaint(); + } + + private void checkEnableNext() { + if (dataTypeCombo.getSelectedItem() != null && dataTypeCombo.getSelectedItem() != DataType.none) { + if (importPasted.isSelected()) { + ok.setEnabled(jsonPasteArea.getText() != null && jsonPasteArea.getText().length() > 0); + } else if (importFromFile.isSelected()) { + ok.setEnabled(jsonFileName.getText() != null && jsonFileName.getText().length() > 0 && new File(jsonFileName.getText()).exists() && !(new File(jsonFileName.getText()).isDirectory())); + } + } + } + + private void showImportPreviewScreen(final List created) { + pane.removeAll(); + message.setText("The following data has been found. Click \"Ok\" to confirm."); + pane.add(message, JideBoxLayout.FIX); + createdPreview.setModel(new GDEListModel(created)); + createdPreview.setCellRenderer(new GDERenderer(false)); + pane.add(new JScrollPane(createdPreview), JideBoxLayout.FIX); + pane.add(new JPanel(), JideBoxLayout.VARY); + pane.add(buttonPane, JideBoxLayout.FIX); + ok.setText("Ok"); + ok.removeActionListener(okListener); + okListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JSONElement lastNode = null; + for (JSONElement node : created) { + proj.createElement(node); + lastNode = node; + } + if (lastNode != null) { + lastNode.save(); + ATContentStudio.frame.selectInTree(lastNode); + } + JSONImportWizard.this.setVisible(false); + JSONImportWizard.this.dispose(); + } + }; + ok.addActionListener(okListener); + cancel.setText("Back"); + cancel.removeActionListener(cancelListener); + cancelListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showFirstScreen(); + } + }; + cancel.addActionListener(cancelListener); + + pane.revalidate(); + pane.repaint(); + } + + private void showErrorScreen(List errors) { + pane.removeAll(); + message.setText("Failed to import. The following error(s) have been encountered:"); + pane.add(message, JideBoxLayout.FIX); + createdPreview.setModel(new GDEListModel(errors)); + createdPreview.setCellRenderer(new ErrorRenderer()); + pane.add(new JScrollPane(createdPreview), JideBoxLayout.VARY); + pane.add(buttonPane, JideBoxLayout.FIX); + cancel.setText("Back"); + cancel.removeActionListener(cancelListener); + cancelListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showFirstScreen(); + } + }; + cancel.addActionListener(cancelListener); + ok.setText("Close"); + ok.removeActionListener(okListener); + okListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JSONImportWizard.this.setVisible(false); + JSONImportWizard.this.dispose(); + } + }; + ok.addActionListener(okListener); + + pane.revalidate(); + pane.repaint(); + } + + private void showWarningScreen(List warnings, final List created) { + pane.removeAll(); + message.setText("The following warnings(s) were raised while importing:"); + pane.add(message, JideBoxLayout.FIX); + createdPreview.setModel(new GDEListModel(warnings)); + createdPreview.setCellRenderer(new WarningRenderer()); + pane.add(new JScrollPane(createdPreview), JideBoxLayout.VARY); + pane.add(buttonPane, JideBoxLayout.FIX); + + ok.setText("Continue anyway"); + ok.removeActionListener(okListener); + okListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showImportPreviewScreen(created); + } + }; + ok.addActionListener(okListener); + + cancel.setText("Close"); + cancel.removeActionListener(cancelListener); + cancelListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JSONImportWizard.this.setVisible(false); + JSONImportWizard.this.dispose(); + } + }; + cancel.addActionListener(cancelListener); + + pane.revalidate(); + pane.repaint(); + } + + public class GDERenderer extends DefaultListCellRenderer { + + private static final long serialVersionUID = 6819681566800482793L; + + private boolean includeType = false; + + public GDERenderer(boolean includeType) { + super(); + this.includeType = includeType; + + } + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (value == null) { + label.setText("none"); + } else { + if (includeType && ((GameDataElement)value).getDataType() != null) { + label.setText(((GameDataElement)value).getDataType().toString()+"/"+((GameDataElement)value).getDesc()); + } else { + label.setText(((GameDataElement)value).getDesc()); + } + switch ((DataType)dataTypeCombo.getSelectedItem()) { + case actorCondition: + label.setIcon(new ImageIcon(proj.getIcon(((ActorCondition)value).icon_id))); + break; + case item: + label.setIcon(new ImageIcon(proj.getIcon(((Item)value).icon_id))); + break; + case npc: + label.setIcon(new ImageIcon(proj.getIcon(((NPC)value).icon_id))); + break; + case dialogue: + label.setIcon(new ImageIcon(((Dialogue)value).getIcon())); + break; + case droplist: + label.setIcon(new ImageIcon(((Droplist)value).getIcon())); + break; + case itemCategory: + label.setIcon(new ImageIcon(((ItemCategory)value).getIcon())); + break; + case quest: + label.setIcon(new ImageIcon(((Quest)value).getIcon())); + break; + default: + Notification.addError("Unable to find icon for "+((GameDataElement)value).getDesc()); + } + } + return label; + } + + } + + public static class ErrorRenderer extends DefaultListCellRenderer { + private static final long serialVersionUID = -4265342800284721660L; + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (c instanceof JLabel) { + ((JLabel)c).setIcon(NotificationsPane.icons.get(Notification.Type.ERROR)); + } + return c; + } + } + + + public static class WarningRenderer extends DefaultListCellRenderer { + private static final long serialVersionUID = -3836045237946111606L; + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (c instanceof JLabel) { + ((JLabel)c).setIcon(NotificationsPane.icons.get(Notification.Type.WARN)); + } + return c; + } + } + + public static class GDEListModel implements ListModel { + + List source; + + public GDEListModel(List source) { + this.source = source; + } + + @Override + public int getSize() { + return source.size(); + } + + @Override + public Object getElementAt(int index) { + for (Object obj : source) { + if (index == 0) return obj; + index --; + } + return null; + } + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + + public void fireListChanged() { + for (ListDataListener l : listeners) { + l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, this.getSize())); + } + } + + } + + + public static class DataTypeComboModel implements ComboBoxModel { + + DataType selected = DataType.none; + + @Override + public int getSize() { + return DataType.values().length; + } + + @Override + public Object getElementAt(int index) { + return DataType.values()[index]; + } + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + + @Override + public void setSelectedItem(Object anItem) { + selected = (DataType) anItem; + } + + @Override + public Object getSelectedItem() { + return selected; + } + } + + public static class DataTypeComboCellRenderer extends DefaultListCellRenderer { + private static final long serialVersionUID = 5621373849299980998L; + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (c instanceof JLabel) { + ((JLabel)c).setText(dataTypeDesc((DataType) value)); + switch ((DataType)value) { + case actorCondition: + ((JLabel)c).setIcon(new ImageIcon(DefaultIcons.getActorConditionIcon())); + break; + case dialogue: + ((JLabel)c).setIcon(new ImageIcon(DefaultIcons.getDialogueIcon())); + break; + case droplist: + ((JLabel)c).setIcon(new ImageIcon(DefaultIcons.getDroplistIcon())); + break; + case item: + ((JLabel)c).setIcon(new ImageIcon(DefaultIcons.getItemIcon())); + break; + case itemCategory: + ((JLabel)c).setIcon(new ImageIcon(DefaultIcons.getDroplistIcon())); + break; + case npc: + ((JLabel)c).setIcon(new ImageIcon(DefaultIcons.getNPCIcon())); + break; + case quest: + ((JLabel)c).setIcon(new ImageIcon(DefaultIcons.getQuestIcon())); + break; + default: + break; + + } + } + return c; + } + } + + public static String dataTypeDesc(DataType type) { + switch (type) { + case actorCondition: + return "Actor Condition"; + case dialogue: + return "Dialogue"; + case droplist: + return "Droplist"; + case item: + return "Item"; + case itemCategory: + return "Item Category"; + case npc: + return "NPC"; + case quest: + return "Quest"; + default: + return "Select below"; + } + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/NotificationsPane.java b/src/com/gpl/rpg/atcontentstudio/ui/NotificationsPane.java new file mode 100644 index 0000000..c2931aa --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/NotificationsPane.java @@ -0,0 +1,113 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.imageio.ImageIO; +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.ListCellRenderer; +import javax.swing.ListModel; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; + +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.NotificationListener; + + +public class NotificationsPane extends JList { + + private static final long serialVersionUID = -1100364214372392608L; + + public static final String success_img_name = "/com/gpl/rpg/atcontentstudio/img/success.png"; + public static final String info_img_name = "/com/gpl/rpg/atcontentstudio/img/info.png"; + public static final String warn_img_name = "/com/gpl/rpg/atcontentstudio/img/warn.png"; + public static final String error_img_name = "/com/gpl/rpg/atcontentstudio/img/error.png"; + + public static final Map icons = new HashMap(Notification.Type.values().length); + + static { + try { + icons.put(Notification.Type.SUCCESS, new ImageIcon(ImageIO.read(NotificationsPane.class.getResourceAsStream(success_img_name)))); + icons.put(Notification.Type.INFO, new ImageIcon(ImageIO.read(NotificationsPane.class.getResourceAsStream(info_img_name)))); + icons.put(Notification.Type.WARN, new ImageIcon(ImageIO.read(NotificationsPane.class.getResourceAsStream(warn_img_name)))); + icons.put(Notification.Type.ERROR, new ImageIcon(ImageIO.read(NotificationsPane.class.getResourceAsStream(error_img_name)))); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + public NotificationsPane() { + super(); + MyListModel model = new MyListModel(); + setModel(model); + setCellRenderer(new ListCellRenderer(){ + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + JLabel label = new JLabel(); + Font f = label.getFont(); + label.setIcon(NotificationsPane.icons.get(((Notification)value).type)); + label.setText(((Notification)value).text); + if (isSelected) { +// label.setBackground(Color.RED); + label.setBorder(BorderFactory.createLineBorder(Color.BLUE)); +// label.setForeground(Color.WHITE); + } + f = f.deriveFont(10f); + label.setFont(f); + return label; + } + }); + Notification.addNotificationListener(model); + } + + + private class MyListModel implements ListModel, NotificationListener { + + @Override + public Object getElementAt(int index) { + return Notification.notifs.get(index); + } + + @Override + public int getSize() { + return Notification.notifs.size(); + } + + @Override + public void onNewNotification(Notification n) { + for (ListDataListener l : listeners) { + l.intervalAdded(new ListDataEvent(NotificationsPane.this, ListDataEvent.INTERVAL_ADDED, Notification.notifs.size() - 1 , Notification.notifs.size() - 1)); + } + NotificationsPane.this.ensureIndexIsVisible(Notification.notifs.indexOf(n)); + } + + @Override + public void onListCleared(int i) { + for (ListDataListener l : listeners) { + l.intervalRemoved(new ListDataEvent(NotificationsPane.this, ListDataEvent.INTERVAL_REMOVED, 0 , i)); + } + } + + private List listeners = new ArrayList(); + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + + } +} \ No newline at end of file diff --git a/src/com/gpl/rpg/atcontentstudio/ui/ProjectCreationWizard.java b/src/com/gpl/rpg/atcontentstudio/ui/ProjectCreationWizard.java new file mode 100644 index 0000000..2fd1f65 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/ProjectCreationWizard.java @@ -0,0 +1,250 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.Workspace; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMapSet; +import com.gpl.rpg.atcontentstudio.model.sprites.SpriteSheetSet; + +public class ProjectCreationWizard extends JDialog { + + private static final long serialVersionUID = -2854969975146867119L; + + final JTextField projectNameField; + final JComboBox atSourceSelectionCombo; + + final JButton browse; + final JButton okButton; + final JButton cancelButton; + + final JLabel errorLabel; + + public ProjectCreationWizard() { + super(ATContentStudio.frame); + setTitle("Create project"); + projectNameField = new JTextField(); + atSourceSelectionCombo = new JComboBox(); + browse = new JButton("Browse..."); + okButton = new JButton("Ok"); + cancelButton = new JButton("Cancel"); + errorLabel = new JLabel("Enter the following information about your project."); + + projectNameField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + updateOkButtonEnablement(); + } + @Override + public void insertUpdate(DocumentEvent e) { + updateOkButtonEnablement(); + } + @Override + public void changedUpdate(DocumentEvent e) { + updateOkButtonEnablement(); + } + }); + for (File f : Workspace.activeWorkspace.knownMapSourcesFolders) { + atSourceSelectionCombo.addItem(f.getAbsolutePath()); + } + atSourceSelectionCombo.setEditable(true); + atSourceSelectionCombo.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + updateOkButtonEnablement(); + } + }); + browse.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JFileChooser chooser = new JFileChooser(); + boolean keepTrying = true; + if (atSourceSelectionCombo.getSelectedItem() != null && ((String)atSourceSelectionCombo.getSelectedItem()).length() > 0) { + File f = new File((String)atSourceSelectionCombo.getSelectedItem()); + if (f.exists()) { + chooser.setCurrentDirectory(f); + keepTrying = false; + } + } + if (keepTrying && Workspace.activeWorkspace.knownMapSourcesFolders != null && !Workspace.activeWorkspace.knownMapSourcesFolders.isEmpty()) { + chooser.setCurrentDirectory(Workspace.activeWorkspace.knownMapSourcesFolders.iterator().next()); + } + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + int result = chooser.showOpenDialog(ProjectCreationWizard.this); + if (result == JFileChooser.APPROVE_OPTION) { + atSourceSelectionCombo.setSelectedItem(chooser.getSelectedFile().getAbsolutePath()); + updateOkButtonEnablement(); + } + } + }); + okButton.setEnabled(false); + okButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + File atSourceFolder = new File((String) atSourceSelectionCombo.getSelectedItem()); + if (!Workspace.activeWorkspace.knownMapSourcesFolders.contains(atSourceFolder)) { + Workspace.activeWorkspace.knownMapSourcesFolders.add(atSourceFolder); + } + Workspace.createProject(projectNameField.getText(), atSourceFolder); + ProjectCreationWizard.this.dispose(); + } + }); + + cancelButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ProjectCreationWizard.this.dispose(); + } + }); + + JPanel panel = new JPanel(); + panel.setLayout(new GridBagLayout()); + GridBagConstraints c =new GridBagConstraints(); + + c.anchor = GridBagConstraints.NORTHWEST; + c.fill = GridBagConstraints.BOTH; + c.gridheight = 1; + c.gridwidth = 3; + c.gridx = 1; + c.gridy = 1; + c.weightx = 100; + c.weighty = 100; + panel.add(errorLabel, c); + + c.gridy++; + c.gridx = 1; + c.gridwidth = 1; + c.weightx = 20; + panel.add(new JLabel("Project name: "), c); + + c.gridx++; + c.gridwidth = 2; + c.weightx = 80; + panel.add(projectNameField, c); + + c.gridy++; + c.gridx = 1; + c.gridwidth = 1; + c.weightx = 20; + panel.add(new JLabel("AT Source: "), c); + + c.gridx++; + c.weightx = 60; + panel.add(atSourceSelectionCombo, c); + + c.gridx++; + c.weightx = 20; + panel.add(browse, c); + + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new GridBagLayout()); + GridBagConstraints c2 = new GridBagConstraints(); + c2.fill = GridBagConstraints.HORIZONTAL; + c2.gridx = 1; + c2.weightx = 80; + + c2.gridx = 1; + c2.weightx = 80; + buttonPane.add(new JLabel(), c2); + + c2.gridx++; + c2.weightx = 10; + c.fill = GridBagConstraints.NONE; + buttonPane.add(cancelButton, c2); + + c2.gridx++; + c2.weightx = 10; + buttonPane.add(okButton, c2); + + c.gridy++; + c.fill = GridBagConstraints.HORIZONTAL; + c.gridwidth = 3; + panel.add(buttonPane, c); + + updateOkButtonEnablement(); + + setContentPane(panel); + + pack(); + Dimension sdim = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension wdim = getSize(); + setLocation((sdim.width - wdim.width)/2, (sdim.height - wdim.height)/2); + } + + + protected void updateOkButtonEnablement() { + if (projectNameField.getText() == null || projectNameField.getText().length() <= 0) { + errorLabel.setText("Select a project name."); + this.okButton.setEnabled(false); + return; + } + if (atSourceSelectionCombo.getSelectedItem() == null || ((String)atSourceSelectionCombo.getSelectedItem()).length() <= 0) { + errorLabel.setText("Select an AT source root folder."); + this.okButton.setEnabled(false); + return; + } + File projFolder = new File(Workspace.activeWorkspace.baseFolder, projectNameField.getText()+File.separator); + File sourceFolder = new File((String) atSourceSelectionCombo.getSelectedItem()); + if (projFolder.exists()) { + errorLabel.setText("A project with this name already exists."); + this.okButton.setEnabled(false); + return; + } else { + try { + projFolder.getCanonicalPath(); + } catch (IOException ioe) { + errorLabel.setText(""+projectNameField.getText()+" is not a valid project name."); + this.okButton.setEnabled(false); + return; + } + } + if (!sourceFolder.exists()) { + errorLabel.setText("The selected AT source root folder does not exist."); + this.okButton.setEnabled(false); + return; + } else { + File res = new File(sourceFolder, GameDataSet.DEFAULT_REL_PATH_IN_SOURCE); + if (!res.exists()) { + errorLabel.setText("The selected AT source root folder does not contain the \"res\" folder."); + this.okButton.setEnabled(false); + return; + } + File drawable = new File(sourceFolder, SpriteSheetSet.DEFAULT_REL_PATH_IN_SOURCE); + if (!drawable.exists()) { + errorLabel.setText("The selected AT source root folder does not contain the \"drawable\" folder."); + this.okButton.setEnabled(false); + return; + } + File xml = new File(sourceFolder, TMXMapSet.DEFAULT_REL_PATH_IN_SOURCE); + if (!xml.exists()) { + errorLabel.setText("The selected AT source root folder does not contain the \"xml\" folder."); + this.okButton.setEnabled(false); + return; + } + } + if (!projFolder.exists() && sourceFolder.exists()) { + errorLabel.setText("Everything looks good !"); + this.okButton.setEnabled(true); + return; + } + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/ProjectsTree.java b/src/com/gpl/rpg/atcontentstudio/ui/ProjectsTree.java new file mode 100644 index 0000000..4524009 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/ProjectsTree.java @@ -0,0 +1,739 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Image; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JSeparator; +import javax.swing.JTree; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; + +import com.gpl.rpg.andorstrainer.AndorsTrainer; +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.Workspace; +import com.gpl.rpg.atcontentstudio.model.gamedata.JSONElement; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; +import com.gpl.rpg.atcontentstudio.model.maps.WorldmapSegment; +import com.gpl.rpg.atcontentstudio.model.saves.SavedGame; +import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet; +import com.jidesoft.swing.TreeSearchable; + +public class ProjectsTree extends JPanel { + + private static final long serialVersionUID = 6332593891796576708L; + + private JTree projectsTree; + + private JPopupMenu popupMenu; + + private Thread konamiTimeout = null; + private boolean exit = false; + private int timeout = 200; + private Integer[] konamiBuffer = new Integer[]{null, null, null, null, null, null, null, null, null, null}; + private boolean konamiCodeEntered = false; + + public ProjectsTree() { + super(); + setLayout(new BorderLayout()); + projectsTree = new JTree(new ProjectsTreeModel()); + new TreeSearchable(projectsTree){ + @Override + protected String convertElementToString(Object object) { + return ((ProjectTreeNode)((TreePath)object).getLastPathComponent()).getDesc(); + } + }; + add(projectsTree, BorderLayout.CENTER); + projectsTree.setRootVisible(false); + projectsTree.setShowsRootHandles(true); + projectsTree.setExpandsSelectedPaths(true); + + popupMenu = new JPopupMenu(); + makePopupMenu(); + + projectsTree.setCellRenderer(new ProjectsTreeCellRenderer()); + projectsTree.addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + if (projectsTree.getSelectionPath() != null) { + itemAction((ProjectTreeNode) projectsTree.getSelectionPath().getLastPathComponent()); + } + } else { + if (konamiTimeout == null) { + startKonamiCount(); + } + int i = 0; + while (i < konamiBuffer.length && konamiBuffer[i] != null) { + i++; + } + if (i < konamiBuffer.length) { + konamiBuffer[i] = e.getKeyCode(); + if (!compareBuffers()) { + exit = true; + } else { + resetTimeout(); + } + } + } + } + }); + projectsTree.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + if (e.isPopupTrigger()) { + popupActivated(e); + } else if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) { + TreePath path = projectsTree.getPathForLocation (e.getX(), e.getY()); + projectsTree.setSelectionPath(path); + if (path != null) { + itemAction((ProjectTreeNode) path.getLastPathComponent()); + } + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (e.isPopupTrigger()) { + popupActivated(e); + } + } + }); + + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + if (e.isPopupTrigger()) { + popupActivated(e); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (e.isPopupTrigger()) { + popupActivated(e); + } + } + }); + projectsTree.addTreeSelectionListener(new TreeSelectionListener() { + @Override + public void valueChanged(TreeSelectionEvent e) { + List newPaths = new ArrayList(); + for (TreePath path : e.getPaths()) { + if (e.isAddedPath(path)) newPaths.add(path); + } + if (e.getPath() == null) { + ATContentStudio.frame.actions.selectionChanged(null, newPaths.toArray(new TreePath[newPaths.size()])); + } else { + ATContentStudio.frame.actions.selectionChanged((ProjectTreeNode) e.getPath().getLastPathComponent(), newPaths.toArray(new TreePath[newPaths.size()])); + } + } + }); + + } + + public void makePopupMenu() { + popupMenu.removeAll(); + + if (ATContentStudio.frame == null || ATContentStudio.frame.actions == null) return; + WorkspaceActions actions = ATContentStudio.frame.actions; + + boolean addNextSeparator = false; + if (actions.createProject.isEnabled()) { + addNextSeparator = true; + popupMenu.add(new JMenuItem(actions.createProject)); + } + if (actions.openProject.isEnabled()) { + addNextSeparator = true; + popupMenu.add(new JMenuItem(actions.openProject)); + } + if (actions.closeProject.isEnabled()) { + addNextSeparator = true; + popupMenu.add(new JMenuItem(actions.closeProject)); + } + if (actions.deleteProject.isEnabled()) { + addNextSeparator = true; + popupMenu.add(new JMenuItem(actions.deleteProject)); + } + if (addNextSeparator) { + popupMenu.add(new JSeparator()); + addNextSeparator = false; + } + + if (actions.saveElement.isEnabled()) { + addNextSeparator = true; + popupMenu.add(new JMenuItem(actions.saveElement)); + } + if (actions.deleteSelected.isEnabled()) { + addNextSeparator = true; + popupMenu.add(new JMenuItem(actions.deleteSelected)); + } + if (addNextSeparator) { + popupMenu.add(new JSeparator()); + addNextSeparator = false; + } + + if (actions.createGDE.isEnabled()) { + addNextSeparator = true; + popupMenu.add(new JMenuItem(actions.createGDE)); + } + if (actions.importJSON.isEnabled()) { + addNextSeparator = true; + popupMenu.add(new JMenuItem(actions.importJSON)); + } + if (actions.loadSave.isEnabled()) { + addNextSeparator = true; + popupMenu.add(new JMenuItem(actions.loadSave)); + } + if (addNextSeparator) { + popupMenu.add(new JSeparator()); + addNextSeparator = false; + } + + + if (actions.compareItems.isEnabled()) { + addNextSeparator = true; + popupMenu.add(new JMenuItem(actions.compareItems)); + } + if (actions.compareNPCs.isEnabled()) { + addNextSeparator = true; + popupMenu.add(new JMenuItem(actions.compareNPCs)); + } + if (actions.exportProject.isEnabled()) { + addNextSeparator = true; + popupMenu.add(new JMenuItem(actions.exportProject)); + } + if (addNextSeparator) { + popupMenu.add(new JSeparator()); + addNextSeparator = false; + } + + if (konamiCodeEntered) { + JMenuItem openTrainer = new JMenuItem("Start Andor's Trainer..."); + popupMenu.add(openTrainer); + popupMenu.addSeparator(); + openTrainer.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + new Thread() { + public void run() { + AndorsTrainer.startApp(false); + } + }.start(); + } + }); + } + +// if (projectsTree.getSelectionPath() == null || projectsTree.getSelectionPath().getLastPathComponent() == null) { +// JMenuItem addProject = new JMenuItem("Create project..."); +// popupMenu.add(addProject); +// addProject.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// new ProjectCreationWizard().setVisible(true); +// } +// }); +// popupMenu.addSeparator(); +// } else if (projectsTree.getSelectionPaths().length > 1) { +// boolean deleteAll = false; +// final List elementsToDelete = new ArrayList(); +// for (TreePath selected : projectsTree.getSelectionPaths()) { +// if (selected.getLastPathComponent() instanceof GameDataElement && ((GameDataElement)selected.getLastPathComponent()).writable) { +// elementsToDelete.add((GameDataElement) selected.getLastPathComponent()); +// deleteAll = true; +// } else { +// deleteAll = false; +// break; +// } +// } +// if (deleteAll) { +// JMenuItem deleteItems = new JMenuItem("Delete all selected elements"); +// popupMenu.add(deleteItems); +// deleteItems.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// final Map, Set> impactedCategories = new IdentityHashMap, Set>(); +// for (GameDataElement element : elementsToDelete) { +// ATContentStudio.frame.closeEditor(element); +// element.childrenRemoved(new ArrayList()); +// if (element instanceof JSONElement) { +// @SuppressWarnings("unchecked") +// GameDataCategory category = (GameDataCategory) element.getParent(); +// category.remove(element); +// if (impactedCategories.get(category) == null) { +// impactedCategories.put(category, new HashSet()); +// } +// impactedCategories.get(category).add(((JSONElement) element).jsonFile); +// } else if (element instanceof TMXMap) { +// TMXMapSet parent = (TMXMapSet) element.getParent(); +// parent.tmxMaps.remove(element); +// } +// } +// new Thread() { +// @Override +// public void run() { +// final List events = new ArrayList(); +// List catEvents = null; +// for (GameDataCategory category : impactedCategories.keySet()) { +// for (File f : impactedCategories.get(category)) { +// catEvents = category.attemptSave(true, f.getName()); +// if (catEvents.isEmpty()) { +// category.save(f); +// } else { +// events.addAll(catEvents); +// } +// } +// } +// if (!events.isEmpty()) { +// new SaveItemsWizard(events, null).setVisible(true); +// } +// } +// }.start(); +// } +// }); +// } +// +// popupMenu.addSeparator(); +// } else { +// final ProjectTreeNode selected = (ProjectTreeNode) projectsTree.getSelectionPath().getLastPathComponent(); +// if (selected instanceof Project) { +// JMenuItem closeProject = new JMenuItem("Close Project..."); +// JMenuItem deleteProject = new JMenuItem("Delete Project..."); +// popupMenu.add(closeProject); +// popupMenu.add(deleteProject); +// popupMenu.addSeparator(); +// closeProject.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// Workspace.closeProject((Project) selected); +// } +// }); +// deleteProject.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// int confirm = JOptionPane.showConfirmDialog(ProjectsTree.this, "Are you sure you wish to delete this project ?\nAll files created for it will be deleted too...", "Delete this project ?", JOptionPane.OK_CANCEL_OPTION); +// if (confirm == JOptionPane.OK_OPTION) { +// Workspace.deleteProject(((Project)projectsTree.getSelectionPath().getLastPathComponent())); +// } +// } +// }); +// } +// if (selected instanceof ClosedProject) { +// JMenuItem openProject = new JMenuItem("Open Project..."); +// JMenuItem deleteProject = new JMenuItem("Delete Project..."); +// popupMenu.add(openProject); +// popupMenu.add(deleteProject); +// popupMenu.addSeparator(); +// openProject.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// Workspace.openProject(((ClosedProject)selected)); +// } +// }); +// deleteProject.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// int confirm = JOptionPane.showConfirmDialog(ProjectsTree.this, "Are you sure you wish to delete this project ?\nAll files created for it will be deleted too...", "Delete this project ?", JOptionPane.OK_CANCEL_OPTION); +// if (confirm == JOptionPane.OK_OPTION) { +// Workspace.deleteProject(((ClosedProject)selected)); +// } +// } +// }); +// } +// if (selected.getProject() != null) { +// final Project proj = ((ProjectTreeNode)selected).getProject(); +// JMenuItem createGDE = new JMenuItem("Create Game Data Element (JSON)"); +// popupMenu.add(createGDE); +// createGDE.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// new JSONCreationWizard(proj).setVisible(true); +// } +// }); +// JMenuItem importJson = new JMenuItem("Import JSON data"); +// popupMenu.add(importJson); +// importJson.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// new JSONImportWizard(proj).setVisible(true); +// } +// }); +// //TODO move somewhere else +// JMenu compareElementsMenu = new JMenu("Open comparator for..."); +// JMenuItem compareItems = new JMenuItem("Items"); +// compareElementsMenu.add(compareItems); +// compareItems.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// ATContentStudio.frame.editors.openEditor(new ItemsTableView(selected.getProject())); +// } +// }); +// JMenuItem compareNPCs = new JMenuItem("NPCs"); +// compareElementsMenu.add(compareNPCs); +// compareNPCs.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// ATContentStudio.frame.editors.openEditor(new NPCsTableView(selected.getProject())); +// } +// }); +// popupMenu.add(compareElementsMenu); +// +// JMenuItem exportProjectPackage = new JMenuItem("Export project"); +// exportProjectPackage.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// JFileChooser chooser = new JFileChooser() { +// private static final long serialVersionUID = 8039332384370636746L; +// public boolean accept(File f) { +// return f.isDirectory() || f.getName().endsWith(".zip") || f.getName().endsWith(".ZIP"); +// } +// }; +// chooser.setMultiSelectionEnabled(false); +// int result = chooser.showSaveDialog(ATContentStudio.frame); +// if (result == JFileChooser.APPROVE_OPTION) { +// selected.getProject().generateExportPackage(chooser.getSelectedFile()); +// } +// } +// }); +// popupMenu.add(exportProjectPackage); +// popupMenu.addSeparator(); +// } +// if (selected instanceof GameDataElement) { +// final GameDataElement node = ((GameDataElement)selected); +// if (node.state == GameDataElement.State.modified){ +// JMenuItem saveItem = new JMenuItem("Save this element"); +// saveItem.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// node.save(); +// ATContentStudio.frame.nodeChanged(node); +// } +// }); +// popupMenu.add(saveItem); +// } +// +// JMenuItem deleteItem = null; +// if (node.getDataType() == GameSource.Type.created) { +// deleteItem = new JMenuItem("Delete this element"); +// } else if (node.getDataType() == GameSource.Type.altered) { +// deleteItem = new JMenuItem("Revert to original"); +// } +// if (deleteItem != null) { +// popupMenu.add(deleteItem); +// deleteItem.addActionListener(new ActionListener() { +// @SuppressWarnings("unchecked") +// @Override +// public void actionPerformed(ActionEvent e) { +// ATContentStudio.frame.closeEditor(node); +// new Thread() { +// @Override +// public void run() { +// node.childrenRemoved(new ArrayList()); +// if (node.getParent() instanceof GameDataCategory) { +// ((GameDataCategory)node.getParent()).remove(node); +// List events = node.attemptSave(); +// if (events == null || events.isEmpty()) { +// node.save(); +// } else { +// new SaveItemsWizard(events, null).setVisible(true); +// } +// } +// } +// }.start(); +// } +// }); +// } +// popupMenu.addSeparator(); +// +// } +// if (selected instanceof Project || selected instanceof SavedGamesSet) { +// JMenuItem addSave = new JMenuItem("Load saved game..."); +// popupMenu.add(addSave); +// popupMenu.addSeparator(); +// addSave.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// JFileChooser chooser = new JFileChooser("Select an Andor's Trail save file"); +// if (chooser.showOpenDialog(ATContentStudio.frame) == JFileChooser.APPROVE_OPTION) { +// selected.getProject().addSave(chooser.getSelectedFile()); +// selected.getProject().save(); +// } +// } +// }); +// +// } +// } +// if (konamiCodeEntered) { +// JMenuItem openTrainer = new JMenuItem("Start Andor's Trainer..."); +// popupMenu.add(openTrainer); +// popupMenu.addSeparator(); +// openTrainer.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// new Thread() { +// public void run() { +// AndorsTrainer.startApp(false); +// } +// }.start(); +// } +// }); +// } +// JMenu changeLaF = new JMenu("Change Look and Feel"); +// for (final LookAndFeelInfo i : UIManager.getInstalledLookAndFeels()) { +// final JMenuItem lafItem = new JMenuItem("Switch to "+i.getName()); +// changeLaF.add(lafItem); +// lafItem.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// try { +// UIManager.setLookAndFeel(i.getClassName()); +// SwingUtilities.updateComponentTreeUI(ATContentStudio.frame); +// ConfigCache.setFavoriteLaFClassName(i.getClassName()); +// } catch (ClassNotFoundException e1) { +// e1.printStackTrace(); +// } catch (InstantiationException e1) { +// e1.printStackTrace(); +// } catch (IllegalAccessException e1) { +// e1.printStackTrace(); +// } catch (UnsupportedLookAndFeelException e1) { +// e1.printStackTrace(); +// } +// } +// }); +// } +// popupMenu.add(changeLaF); +// popupMenu.addSeparator(); +// JMenuItem showAbout = new JMenuItem("About..."); +// popupMenu.add(showAbout); +// popupMenu.addSeparator(); +// showAbout.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// ATContentStudio.frame.showAbout(); +// } +// }); + } + + public void popupActivated(MouseEvent e) { + TreePath path = projectsTree.getPathForLocation (e.getX(), e.getY()); + TreePath[] allSelected = projectsTree.getSelectionPaths(); + boolean selectClickedItem = true; + if (allSelected != null) { + for (TreePath selected : allSelected) { + if (selected.equals(path)) { + selectClickedItem = false; + break; + } + } + } + if (selectClickedItem) projectsTree.setSelectionPath(path); + makePopupMenu(); + if (popupMenu.getComponentCount() > 0) { + popupMenu.show(projectsTree, e.getX(), e.getY()); + } + } + + public void itemAction(ProjectTreeNode node) { + if (node instanceof JSONElement) { + ATContentStudio.frame.openEditor((JSONElement)node); + } else if (node instanceof Spritesheet) { + ATContentStudio.frame.openEditor((Spritesheet)node); + } else if (node instanceof TMXMap) { + ATContentStudio.frame.openEditor((TMXMap)node); + } else if (node instanceof WorldmapSegment) { + ATContentStudio.frame.openEditor((WorldmapSegment)node); + } else if (node instanceof SavedGame) { + if (konamiCodeEntered) { + ATContentStudio.frame.openEditor((SavedGame)node); + } + } + } + + public class ProjectsTreeModel implements TreeModel { + + public ProjectsTreeModel() { + Workspace.activeWorkspace.projectsTreeModel = this; + } + + @Override + public Object getRoot() { + return Workspace.activeWorkspace; + } + + @Override + public Object getChild(Object parent, int index) { + return ((ProjectTreeNode)parent).getChildAt(index); + } + + @Override + public int getChildCount(Object parent) { + return ((ProjectTreeNode)parent).getChildCount(); + } + + @Override + public boolean isLeaf(Object node) { + return ((ProjectTreeNode)node).isLeaf(); + } + + @Override + public void valueForPathChanged(TreePath path, Object newValue) { + //Unused + } + + public void insertNode(TreePath node) { + for (TreeModelListener l : listeners) { + l.treeNodesInserted(new TreeModelEvent(node.getLastPathComponent(), node.getParentPath().getPath(), new int[]{((ProjectTreeNode)node.getParentPath().getLastPathComponent()).getIndex((ProjectTreeNode)node.getLastPathComponent())}, new Object[]{node.getLastPathComponent()} )); + } + } + + public void changeNode(TreePath node) { + for (TreeModelListener l : listeners) { + l.treeNodesChanged(new TreeModelEvent(node.getLastPathComponent(), node.getParentPath(), new int[]{((ProjectTreeNode)node.getParentPath().getLastPathComponent()).getIndex((ProjectTreeNode)node.getLastPathComponent())}, new Object[]{node.getLastPathComponent()} )); + } + } + + public void removeNode(TreePath node) { + for (TreeModelListener l : listeners) { + l.treeNodesRemoved(new TreeModelEvent(node.getLastPathComponent(), node.getParentPath(), new int[]{((ProjectTreeNode)node.getParentPath().getLastPathComponent()).getIndex((ProjectTreeNode)node.getLastPathComponent())}, new Object[]{node.getLastPathComponent()} )); + } + } + + @Override + public int getIndexOfChild(Object parent, Object child) { + return ((ProjectTreeNode)parent).getIndex((ProjectTreeNode) child); + } + + List listeners = new ArrayList(); + + @Override + public void addTreeModelListener(TreeModelListener l) { + listeners.add(l); + } + + @Override + public void removeTreeModelListener(TreeModelListener l) { + listeners.remove(l); + } + + } + + public class ProjectsTreeCellRenderer extends DefaultTreeCellRenderer { + + private static final long serialVersionUID = 8100380694034797135L; + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { + Component c = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); + + if (c instanceof JLabel) { + JLabel label = (JLabel)c; + String text = ((ProjectTreeNode)value).getDesc(); + if (text != null) label.setText(text); + Image img = null; + if (leaf) img = ((ProjectTreeNode)value).getLeafIcon(); + else if (expanded) img = ((ProjectTreeNode)value).getOpenIcon(); + else img = ((ProjectTreeNode)value).getClosedIcon(); + + if (img != null) { + label.setIcon(new ImageIcon(img)); + } + } + + return c; + } + } + + public void setSelectedNode(ProjectTreeNode node) { + List path = new ArrayList(); + path.add(node); + TreeNode parent = node.getParent(); + while (parent != null) { + path.add(0, parent); + parent = parent.getParent(); + } + TreePath tp = new TreePath(path.toArray()); + projectsTree.setSelectionPath(tp); + projectsTree.scrollPathToVisible(tp); + } + + protected void startKonamiCount() { + resetTimeout(); + exit = false; + konamiTimeout = new Thread() { + @Override + public void run() { + while (!exit && timeout > 0) { + try { + Thread.sleep(10); + } catch (InterruptedException e) {} + timeout -= 10; + } + konamiTimeout = null; + konamiBuffer = new Integer[]{null, null, null, null, null, null, null, null, null, null}; + } + }; + konamiTimeout.start(); + } + + protected void resetTimeout() { + timeout = 400; + } + + protected boolean compareBuffers() { + if (konamiBuffer[0] == null) return true; + else if (konamiBuffer[0] != KeyEvent.VK_UP) return false; + + if (konamiBuffer[1] == null) return true; + else if (konamiBuffer[1] != KeyEvent.VK_UP) return false; + + if (konamiBuffer[2] == null) return true; + else if (konamiBuffer[2] != KeyEvent.VK_DOWN) return false; + + if (konamiBuffer[3] == null) return true; + else if (konamiBuffer[3] != KeyEvent.VK_DOWN) return false; + + if (konamiBuffer[4] == null) return true; + else if (konamiBuffer[4] != KeyEvent.VK_LEFT) return false; + + if (konamiBuffer[5] == null) return true; + else if (konamiBuffer[5] != KeyEvent.VK_RIGHT) return false; + + if (konamiBuffer[6] == null) return true; + else if (konamiBuffer[6] != KeyEvent.VK_LEFT) return false; + + if (konamiBuffer[7] == null) return true; + else if (konamiBuffer[7] != KeyEvent.VK_RIGHT) return false; + + if (konamiBuffer[8] == null) return true; + else if (konamiBuffer[8] != KeyEvent.VK_B) return false; + + if (konamiBuffer[9] == null) return true; + else if (konamiBuffer[9] != KeyEvent.VK_A) return false; + + konamiCodeEntered = true; + + exit = true; + return true; + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/SaveItemsWizard.java b/src/com/gpl/rpg/atcontentstudio/ui/SaveItemsWizard.java new file mode 100644 index 0000000..38d5c33 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/SaveItemsWizard.java @@ -0,0 +1,271 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.BorderFactory; +import javax.swing.DefaultListCellRenderer; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.SaveEvent; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataCategory; +import com.gpl.rpg.atcontentstudio.model.gamedata.JSONElement; +import com.jidesoft.swing.JideBoxLayout; + +public class SaveItemsWizard extends JDialog { + + private static final long serialVersionUID = -3301878024575930527L; + + List events; + + JList movedToCreated; + JList movedToAltered; + JList willBeSaved; + + + public SaveItemsWizard(List events, GameDataElement originalRequester) { + super(ATContentStudio.frame); + this.events = events; + final List movedToAlteredList = new ArrayList(); + final List movedToCreatedList = new ArrayList(); + final List alsoSavedList = new ArrayList(); + final List errors = new ArrayList(); + for (SaveEvent event : events) { + if (event.error) { + errors.add(event); + } else { + switch (event.type) { + case alsoSave: + alsoSavedList.add(event); + break; + case moveToAltered: + movedToAlteredList.add(event); + break; + case moveToCreated: + movedToCreatedList.add(event); + break; + } + } + } + + if (!errors.isEmpty()) { + setTitle("Errors in project. Cannot save."); + JPanel pane = new JPanel(); + pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS, 6)); + if (originalRequester != null) { + pane.add(new JLabel(" While trying to save: "), JideBoxLayout.FIX); + JLabel origItemDesc = new JLabel(); + origItemDesc.setIcon(new ImageIcon(originalRequester.getIcon())); + origItemDesc.setText(originalRequester.getDataType().toString()+"/"+originalRequester.id); + pane.add(origItemDesc, JideBoxLayout.FIX); + pane.add(new JLabel(" the following errors have been encountered and must be corrected before saving can occur: "), JideBoxLayout.FIX); + } else { + pane.add(new JLabel("After deleting element(s), the following errors have been encountered and must be coorected before saving can occur: "), JideBoxLayout.FIX); + } + + movedToCreated = new JList(errors.toArray()); + movedToCreated.setCellRenderer(new SaveEventsListCellRenderer()); + JPanel movedToCreatedPane = new JPanel(); + movedToCreatedPane.setLayout(new BorderLayout()); + movedToCreatedPane.add(new JScrollPane(movedToCreated), BorderLayout.CENTER); + pane.add(movedToCreatedPane, JideBoxLayout.FLEXIBLE); + + pane.add(new JPanel(), JideBoxLayout.VARY); + + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new JideBoxLayout(buttonPane, JideBoxLayout.LINE_AXIS, 6)); + buttonPane.add(new JPanel(), JideBoxLayout.VARY); + JButton cancelButton = new JButton("Ok, back to work..."); + buttonPane.add(cancelButton, JideBoxLayout.FIX); + pane.add(buttonPane, JideBoxLayout.FIX); + cancelButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + SaveItemsWizard.this.setVisible(false); + SaveItemsWizard.this.dispose(); + } + }); + + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(pane, BorderLayout.CENTER); + + } else { + setTitle("Other elements impacted."); + JPanel pane = new JPanel(); + pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS, 6)); + if (originalRequester != null) { + pane.add(new JLabel(" While trying to save: "), JideBoxLayout.FIX); + JLabel origItemDesc = new JLabel(); + origItemDesc.setIcon(new ImageIcon(originalRequester.getIcon())); + origItemDesc.setText(originalRequester.getDataType().toString()+"/"+originalRequester.id); + pane.add(origItemDesc, JideBoxLayout.FIX); + pane.add(new JLabel(" the following side-effects have been identified and must be applied to the project before saving: "), JideBoxLayout.FIX); + } else { + pane.add(new JLabel("After deleting element(s), the following side-effects have been identified and must be applied to the project before saving: "), JideBoxLayout.FIX); + } + + if (!movedToCreatedList.isEmpty()) { + movedToCreated = new JList(movedToCreatedList.toArray()); + movedToCreated.setCellRenderer(new SaveEventsListCellRenderer()); + JPanel movedToCreatedPane = new JPanel(); + movedToCreatedPane.setLayout(new BorderLayout()); + movedToCreatedPane.setBorder(BorderFactory.createTitledBorder("The following elements will be moved under the \"Created\" folder and saved:")); + movedToCreatedPane.add(new JScrollPane(movedToCreated), BorderLayout.CENTER); + pane.add(movedToCreatedPane, JideBoxLayout.FLEXIBLE); + } + + if (!movedToAlteredList.isEmpty()) { + movedToAltered = new JList(movedToAlteredList.toArray()); + movedToAltered.setCellRenderer(new SaveEventsListCellRenderer()); + JPanel movedToAlteredPane = new JPanel(); + movedToAlteredPane.setLayout(new BorderLayout()); + movedToAlteredPane.setBorder(BorderFactory.createTitledBorder("The following elements will be moved under the \"Altered\" folder and saved:")); + movedToAlteredPane.add(new JScrollPane(movedToAltered), BorderLayout.CENTER); + pane.add(movedToAlteredPane, JideBoxLayout.FLEXIBLE); + } + + if (!alsoSavedList.isEmpty()) { + willBeSaved = new JList(alsoSavedList.toArray()); + willBeSaved.setCellRenderer(new SaveEventsListCellRenderer()); + JPanel willBeSavedPane = new JPanel(); + willBeSavedPane.setLayout(new BorderLayout()); + willBeSavedPane.setBorder(BorderFactory.createTitledBorder("The following elements will be saved too:")); + willBeSavedPane.add(new JScrollPane(willBeSaved), BorderLayout.CENTER); + pane.add(willBeSavedPane, JideBoxLayout.FLEXIBLE); + } + + pane.add(new JPanel(), JideBoxLayout.VARY); + + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new JideBoxLayout(buttonPane, JideBoxLayout.LINE_AXIS, 6)); + buttonPane.add(new JPanel(), JideBoxLayout.VARY); + JButton cancelButton = new JButton("Cancel"); + buttonPane.add(cancelButton, JideBoxLayout.FIX); + cancelButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + SaveItemsWizard.this.setVisible(false); + SaveItemsWizard.this.dispose(); + } + }); + JButton okButton = new JButton("Apply all changes and save"); + buttonPane.add(okButton, JideBoxLayout.FIX); + pane.add(buttonPane, JideBoxLayout.FIX); + okButton.addActionListener(new ActionListener() { + @SuppressWarnings("unchecked") + @Override + public void actionPerformed(ActionEvent e) { + + Map, Set> jsonToSave = new IdentityHashMap, Set>(); + for (SaveEvent event : movedToCreatedList) { + if (event.target instanceof JSONElement) { + if (!jsonToSave.containsKey(event.target.getParent())){ + jsonToSave.put((GameDataCategory) event.target.getParent(), new HashSet()); + } + jsonToSave.get((GameDataCategory) event.target.getParent()).add(((JSONElement)event.target).jsonFile); + + event.target.getProject().moveToCreated((JSONElement) event.target); + + if (!jsonToSave.containsKey(event.target.getParent())){ + jsonToSave.put((GameDataCategory) event.target.getParent(), new HashSet()); + } + jsonToSave.get((GameDataCategory) event.target.getParent()).add(((JSONElement)event.target).jsonFile); + } + //TODO movable maps, when ID is editable. + + } + for (SaveEvent event : movedToAlteredList) { + if (event.target instanceof JSONElement) { + if (!jsonToSave.containsKey(event.target.getParent())){ + jsonToSave.put((GameDataCategory) event.target.getParent(), new HashSet()); + } + jsonToSave.get((GameDataCategory) event.target.getParent()).add(((JSONElement)event.target).jsonFile); + + event.target.getProject().moveToAltered((JSONElement) event.target); + + if (!jsonToSave.containsKey(event.target.getParent())){ + jsonToSave.put((GameDataCategory) event.target.getParent(), new HashSet()); + } + jsonToSave.get((GameDataCategory) event.target.getParent()).add(((JSONElement)event.target).jsonFile); + } + //TODO movable maps, when ID is editable. + } + for (SaveEvent event : alsoSavedList) { + if (event.target instanceof JSONElement) { + if (!jsonToSave.containsKey(event.target.getParent())){ + jsonToSave.put((GameDataCategory) event.target.getParent(), new HashSet()); + } + jsonToSave.get((GameDataCategory) event.target.getParent()).add(((JSONElement)event.target).jsonFile); + } + } + + for (GameDataCategory cat : jsonToSave.keySet()) { + if (jsonToSave.get(cat) != null && !jsonToSave.get(cat).isEmpty()) { + for (File f : jsonToSave.get(cat)) { + cat.save(f); + } + } + } + + for (SaveEvent event : movedToCreatedList) { + ATContentStudio.frame.nodeChanged(event.target); + } + for (SaveEvent event : movedToAlteredList) { + ATContentStudio.frame.nodeChanged(event.target); + } + for (SaveEvent event : alsoSavedList) { + ATContentStudio.frame.nodeChanged(event.target); + } + SaveItemsWizard.this.setVisible(false); + SaveItemsWizard.this.dispose(); + } + }); + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(pane, BorderLayout.CENTER); + + } + + pack(); + } + + public class SaveEventsListCellRenderer extends DefaultListCellRenderer { + private static final long serialVersionUID = 5764079243906396333L; + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (c instanceof JLabel) { + JLabel label = (JLabel) c; + SaveEvent event = (SaveEvent) value; + label.setIcon(new ImageIcon(event.target.getIcon())); + if (event.error) { + label.setText(event.target.getDataType().toString()+"/"+event.target.id+": "+event.errorText); + } else { + label.setText(event.target.getDataType().toString()+"/"+event.target.id); + } + } + return c; + } + + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/ScrollablePanel.java b/src/com/gpl/rpg/atcontentstudio/ui/ScrollablePanel.java new file mode 100644 index 0000000..805afc4 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/ScrollablePanel.java @@ -0,0 +1,338 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.LayoutManager; +import java.awt.Rectangle; + +import javax.swing.JPanel; +import javax.swing.JViewport; +import javax.swing.Scrollable; +import javax.swing.SwingConstants; + +public class ScrollablePanel extends JPanel implements Scrollable, SwingConstants { + + private static final long serialVersionUID = 6498229143202972325L; + + public enum ScrollableSizeHint + { + NONE, + FIT, + STRETCH; + } + + public enum IncrementType + { + PERCENT, + PIXELS; + } + + private ScrollableSizeHint scrollableHeight = ScrollableSizeHint.NONE; + private ScrollableSizeHint scrollableWidth = ScrollableSizeHint.NONE; + + private IncrementInfo horizontalBlock; + private IncrementInfo horizontalUnit; + private IncrementInfo verticalBlock; + private IncrementInfo verticalUnit; + + /** + * Default constructor that uses a FlowLayout + */ + public ScrollablePanel() + { + this( new FlowLayout() ); + } + + /** + * Constuctor for specifying the LayoutManager of the panel. + * + * @param layout the LayountManger for the panel + */ + public ScrollablePanel(LayoutManager layout) + { + super( layout ); + + IncrementInfo block = new IncrementInfo(IncrementType.PERCENT, 100); + IncrementInfo unit = new IncrementInfo(IncrementType.PERCENT, 10); + + setScrollableBlockIncrement(HORIZONTAL, block); + setScrollableBlockIncrement(VERTICAL, block); + setScrollableUnitIncrement(HORIZONTAL, unit); + setScrollableUnitIncrement(VERTICAL, unit); + } + + /** + * Get the height ScrollableSizeHint enum + * + * @return the ScrollableSizeHint enum for the height + */ + public ScrollableSizeHint getScrollableHeight() + { + return scrollableHeight; + } + + /** + * Set the ScrollableSizeHint enum for the height. The enum is used to + * determine the boolean value that is returned by the + * getScrollableTracksViewportHeight() method. The valid values are: + * + * ScrollableSizeHint.NONE - return "false", which causes the height + * of the panel to be used when laying out the children + * ScrollableSizeHint.FIT - return "true", which causes the height of + * the viewport to be used when laying out the children + * ScrollableSizeHint.STRETCH - return "true" when the viewport height + * is greater than the height of the panel, "false" otherwise. + * + * @param scrollableHeight as represented by the ScrollableSizeHint enum. + */ + public void setScrollableHeight(ScrollableSizeHint scrollableHeight) + { + this.scrollableHeight = scrollableHeight; + revalidate(); + } + + /** + * Get the width ScrollableSizeHint enum + * + * @return the ScrollableSizeHint enum for the width + */ + public ScrollableSizeHint getScrollableWidth() + { + return scrollableWidth; + } + + /** + * Set the ScrollableSizeHint enum for the width. The enum is used to + * determine the boolean value that is returned by the + * getScrollableTracksViewportWidth() method. The valid values are: + * + * ScrollableSizeHint.NONE - return "false", which causes the width + * of the panel to be used when laying out the children + * ScrollableSizeHint.FIT - return "true", which causes the width of + * the viewport to be used when laying out the children + * ScrollableSizeHint.STRETCH - return "true" when the viewport width + * is greater than the width of the panel, "false" otherwise. + * + * @param scrollableWidth as represented by the ScrollableSizeHint enum. + */ + public void setScrollableWidth(ScrollableSizeHint scrollableWidth) + { + this.scrollableWidth = scrollableWidth; + revalidate(); + } + + /** + * Get the block IncrementInfo for the specified orientation + * + * @return the block IncrementInfo for the specified orientation + */ + public IncrementInfo getScrollableBlockIncrement(int orientation) + { + return orientation == SwingConstants.HORIZONTAL ? horizontalBlock : verticalBlock; + } + + /** + * Specify the information needed to do block scrolling. + * + * @param orientation specify the scrolling orientation. Must be either: + * SwingContants.HORIZONTAL or SwingContants.VERTICAL. + * @paran type specify how the amount parameter in the calculation of + * the scrollable amount. Valid values are: + * IncrementType.PERCENT - treat the amount as a % of the viewport size + * IncrementType.PIXEL - treat the amount as the scrollable amount + * @param amount a value used with the IncrementType to determine the + * scrollable amount + */ + public void setScrollableBlockIncrement(int orientation, IncrementType type, int amount) + { + IncrementInfo info = new IncrementInfo(type, amount); + setScrollableBlockIncrement(orientation, info); + } + + /** + * Specify the information needed to do block scrolling. + * + * @param orientation specify the scrolling orientation. Must be either: + * SwingContants.HORIZONTAL or SwingContants.VERTICAL. + * @param info An IncrementInfo object containing information of how to + * calculate the scrollable amount. + */ + public void setScrollableBlockIncrement(int orientation, IncrementInfo info) + { + switch(orientation) + { + case SwingConstants.HORIZONTAL: + horizontalBlock = info; + break; + case SwingConstants.VERTICAL: + verticalBlock = info; + break; + default: + throw new IllegalArgumentException("Invalid orientation: " + orientation); + } + } + + /** + * Get the unit IncrementInfo for the specified orientation + * + * @return the unit IncrementInfo for the specified orientation + */ + public IncrementInfo getScrollableUnitIncrement(int orientation) + { + return orientation == SwingConstants.HORIZONTAL ? horizontalUnit : verticalUnit; + } + + /** + * Specify the information needed to do unit scrolling. + * + * @param orientation specify the scrolling orientation. Must be either: + * SwingContants.HORIZONTAL or SwingContants.VERTICAL. + * @paran type specify how the amount parameter in the calculation of + * the scrollable amount. Valid values are: + * IncrementType.PERCENT - treat the amount as a % of the viewport size + * IncrementType.PIXEL - treat the amount as the scrollable amount + * @param amount a value used with the IncrementType to determine the + * scrollable amount + */ + public void setScrollableUnitIncrement(int orientation, IncrementType type, int amount) + { + IncrementInfo info = new IncrementInfo(type, amount); + setScrollableUnitIncrement(orientation, info); + } + + /** + * Specify the information needed to do unit scrolling. + * + * @param orientation specify the scrolling orientation. Must be either: + * SwingContants.HORIZONTAL or SwingContants.VERTICAL. + * @param info An IncrementInfo object containing information of how to + * calculate the scrollable amount. + */ + public void setScrollableUnitIncrement(int orientation, IncrementInfo info) + { + switch(orientation) + { + case SwingConstants.HORIZONTAL: + horizontalUnit = info; + break; + case SwingConstants.VERTICAL: + verticalUnit = info; + break; + default: + throw new IllegalArgumentException("Invalid orientation: " + orientation); + } + } + + //Implement Scrollable interface + + public Dimension getPreferredScrollableViewportSize() + { + return getPreferredSize(); + } + + public int getScrollableUnitIncrement( + Rectangle visible, int orientation, int direction) + { + switch(orientation) + { + case SwingConstants.HORIZONTAL: + return getScrollableIncrement(horizontalUnit, visible.width); + case SwingConstants.VERTICAL: + return getScrollableIncrement(verticalUnit, visible.height); + default: + throw new IllegalArgumentException("Invalid orientation: " + orientation); + } + } + + public int getScrollableBlockIncrement( + Rectangle visible, int orientation, int direction) + { + switch(orientation) + { + case SwingConstants.HORIZONTAL: + return getScrollableIncrement(horizontalBlock, visible.width); + case SwingConstants.VERTICAL: + return getScrollableIncrement(verticalBlock, visible.height); + default: + throw new IllegalArgumentException("Invalid orientation: " + orientation); + } + } + + protected int getScrollableIncrement(IncrementInfo info, int distance) + { + if (info.getIncrement() == IncrementType.PIXELS) + return info.getAmount(); + else + return distance * info.getAmount() / 100; + } + + public boolean getScrollableTracksViewportWidth() + { + if (scrollableWidth == ScrollableSizeHint.NONE) + return false; + + if (scrollableWidth == ScrollableSizeHint.FIT) + return true; + + // STRETCH sizing, use the greater of the panel or viewport width + + if (getParent() instanceof JViewport) + { + return (((JViewport)getParent()).getWidth() > getPreferredSize().width); + } + + return false; + } + + public boolean getScrollableTracksViewportHeight() + { + if (scrollableHeight == ScrollableSizeHint.NONE) + return false; + + if (scrollableHeight == ScrollableSizeHint.FIT) + return true; + + // STRETCH sizing, use the greater of the panel or viewport height + + + if (getParent() instanceof JViewport) + { + return (((JViewport)getParent()).getHeight() > getPreferredSize().height); + } + + return false; + } + + /** + * Helper class to hold the information required to calculate the scroll amount. + */ + static class IncrementInfo + { + private IncrementType type; + private int amount; + + public IncrementInfo(IncrementType type, int amount) + { + this.type = type; + this.amount = amount; + } + + public IncrementType getIncrement() + { + return type; + } + + public int getAmount() + { + return amount; + } + + public String toString() + { + return + "ScrollablePanel[" + + type + ", " + + amount + "]"; + } + } +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/StudioFrame.java b/src/com/gpl/rpg/atcontentstudio/ui/StudioFrame.java new file mode 100644 index 0000000..d09045a --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/StudioFrame.java @@ -0,0 +1,239 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; + +import javax.swing.JFrame; +import javax.swing.JList; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JSplitPane; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.UIManager.LookAndFeelInfo; +import javax.swing.UnsupportedLookAndFeelException; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.ConfigCache; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.Workspace; +import com.gpl.rpg.atcontentstudio.model.gamedata.JSONElement; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; +import com.gpl.rpg.atcontentstudio.model.maps.WorldmapSegment; +import com.gpl.rpg.atcontentstudio.model.saves.SavedGame; +import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet; + +public class StudioFrame extends JFrame { + + private static final long serialVersionUID = -3391514100319186661L; + + + final ProjectsTree projectTree; + final EditorsArea editors; + + final WorkspaceActions actions = new WorkspaceActions(); + + public StudioFrame(String name) { + super(name); + setIconImage(DefaultIcons.getMainIconImage()); + + final JSplitPane topDown = new JSplitPane(JSplitPane.VERTICAL_SPLIT); + final JSplitPane leftRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + + JList notifs = new NotificationsPane(); + projectTree = new ProjectsTree(); + editors = new EditorsArea(); + + setJMenuBar(new JMenuBar()); + buildMenu(); + + JScrollPane treeScroller = new JScrollPane(projectTree); + treeScroller.getVerticalScrollBar().setUnitIncrement(16); + leftRight.setLeftComponent(treeScroller); + leftRight.setRightComponent(editors); + leftRight.setName("StudioFrame.leftRight"); + topDown.setTopComponent(leftRight); + JScrollPane notifScroller = new JScrollPane(notifs); + notifScroller.getVerticalScrollBar().setUnitIncrement(16); + topDown.setBottomComponent(notifScroller); + topDown.setName("StudioFrame.topDown"); + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(topDown, BorderLayout.CENTER); + + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + Workspace.activeWorkspace.preferences.windowSize = StudioFrame.this.getSize(); + } + }); + + pack(); + if (Workspace.activeWorkspace.preferences.windowSize != null) { + setSize(Workspace.activeWorkspace.preferences.windowSize); + } else { + setSize(800, 600); + } + + if (Workspace.activeWorkspace.preferences.splittersPositions.get(topDown.getName()) != null) { + topDown.setDividerLocation(Workspace.activeWorkspace.preferences.splittersPositions.get(topDown.getName())); + } else { + topDown.setDividerLocation(0.2); + } + topDown.addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + Workspace.activeWorkspace.preferences.splittersPositions.put(topDown.getName(), topDown.getDividerLocation()); + } + }); + if (Workspace.activeWorkspace.preferences.splittersPositions.get(leftRight.getName()) != null) { + leftRight.setDividerLocation(Workspace.activeWorkspace.preferences.splittersPositions.get(leftRight.getName())); + } else { + leftRight.setDividerLocation(0.3); + } + leftRight.addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + Workspace.activeWorkspace.preferences.splittersPositions.put(leftRight.getName(), leftRight.getDividerLocation()); + } + }); + + showAbout(); + + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + Workspace.saveActive(); + } + }); + } + + private void buildMenu() { + JMenu fileMenu = new JMenu("File"); + fileMenu.add(new JMenuItem(actions.createProject)); + fileMenu.add(new JMenuItem(actions.openProject)); + fileMenu.add(new JMenuItem(actions.closeProject)); + fileMenu.add(new JMenuItem(actions.deleteProject)); + fileMenu.add(new JSeparator()); + fileMenu.add(new JMenuItem(actions.exitATCS)); + getJMenuBar().add(fileMenu); + + JMenu projectMenu = new JMenu("Project"); + projectMenu.add(new JMenuItem(actions.saveElement)); + projectMenu.add(new JMenuItem(actions.deleteSelected)); + projectMenu.add(new JSeparator()); + projectMenu.add(new JMenuItem(actions.createGDE)); + projectMenu.add(new JMenuItem(actions.importJSON)); + projectMenu.add(new JMenuItem(actions.loadSave)); + getJMenuBar().add(projectMenu); + + JMenu toolsMenu = new JMenu("Tools"); + toolsMenu.add(new JMenuItem(actions.compareItems)); + toolsMenu.add(new JMenuItem(actions.compareNPCs)); + toolsMenu.add(new JSeparator()); + toolsMenu.add(new JMenuItem(actions.exportProject)); + getJMenuBar().add(toolsMenu); + + JMenu viewMenu = new JMenu("View"); + JMenu changeLaF = new JMenu("Change Look and Feel"); + for (final LookAndFeelInfo i : UIManager.getInstalledLookAndFeels()) { + final JMenuItem lafItem = new JMenuItem("Switch to "+i.getName()); + changeLaF.add(lafItem); + lafItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + try { + UIManager.setLookAndFeel(i.getClassName()); + SwingUtilities.updateComponentTreeUI(ATContentStudio.frame); + ConfigCache.setFavoriteLaFClassName(i.getClassName()); + } catch (ClassNotFoundException e1) { + e1.printStackTrace(); + } catch (InstantiationException e1) { + e1.printStackTrace(); + } catch (IllegalAccessException e1) { + e1.printStackTrace(); + } catch (UnsupportedLookAndFeelException e1) { + e1.printStackTrace(); + } + } + }); + } + viewMenu.add(changeLaF); + viewMenu.add(new JSeparator()); + viewMenu.add(new JMenuItem(actions.showAbout)); + getJMenuBar().add(viewMenu); + } + + public void openEditor(JSONElement node) { + node.link(); + editors.openEditor(node); + } + + public void openEditor(Spritesheet node) { + editors.openEditor(node); + } + + public void openEditor(TMXMap node) { + node.parse(); + editors.openEditor(node); + } + + + public void openEditor(GameDataElement node) { + if (node instanceof JSONElement) { + openEditor((JSONElement) node); + } else if (node instanceof Spritesheet) { + openEditor((Spritesheet) node); + } else if (node instanceof TMXMap) { + openEditor((TMXMap) node); + } + } + + public void openEditor(SavedGame save) { + editors.openEditor(save); + } + + public void openEditor(WorldmapSegment node) { + editors.openEditor(node); + } + + public void closeEditor(ProjectTreeNode node) { + editors.closeEditor(node); + } + + public void selectInTree(ProjectTreeNode node) { + projectTree.setSelectedNode(node); + } + + public void editorChanged(Editor e) { + editors.editorTabChanged(e); + } + + public void editorChanged(ProjectTreeNode node) { + editors.editorTabChanged(node); + } + + public void nodeChanged(ProjectTreeNode node) { + node.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(node); + } + + public void showAbout() { + editors.showAbout(); + } + + + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/WorkerDialog.java b/src/com/gpl/rpg/atcontentstudio/ui/WorkerDialog.java new file mode 100644 index 0000000..94f79ef --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/WorkerDialog.java @@ -0,0 +1,53 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.Toolkit; + +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; + +import com.jidesoft.swing.JideBoxLayout; + + +public class WorkerDialog extends JDialog { +private static final long serialVersionUID = 8239669104275145995L; + + private WorkerDialog(String message, Frame parent) { + super(parent, "Loading..."); + this.setIconImage(DefaultIcons.getMainIconImage()); + this.getContentPane().setLayout(new JideBoxLayout(this.getContentPane(), JideBoxLayout.PAGE_AXIS, 6)); + this.getContentPane().add(new JLabel("Please wait.
"+message+"
"), JideBoxLayout.VARY); + JMovingIdler idler = new JMovingIdler(); + idler.setBackground(Color.WHITE); + idler.setForeground(Color.GREEN); + idler.start(); + this.getContentPane().add(idler, JideBoxLayout.FIX); + this.pack(); + Dimension sdim = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension wdim = this.getSize(); + idler.setPreferredSize(new Dimension(wdim.width, 10)); + this.pack(); + wdim = this.getSize(); + this.setLocation((sdim.width - wdim.width)/2, (sdim.height - wdim.height)/2); + this.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); + } + + public static void showTaskMessage(String message, Frame parent, Runnable workload) { + showTaskMessage(message, parent, false, workload); + } + + public static void showTaskMessage(final String message, final Frame parent, final boolean showConfirm, final Runnable workload) { + new Thread() { + public void run() { + WorkerDialog info = new WorkerDialog(message, parent); + info.setVisible(true); + workload.run(); + info.dispose(); + if (showConfirm) JOptionPane.showMessageDialog(parent, "Done !"); + }; + }.start(); + } +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceActions.java b/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceActions.java new file mode 100644 index 0000000..3483be4 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceActions.java @@ -0,0 +1,389 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.Action; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.KeyStroke; +import javax.swing.tree.TreePath; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.ClosedProject; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.SaveEvent; +import com.gpl.rpg.atcontentstudio.model.Workspace; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataCategory; +import com.gpl.rpg.atcontentstudio.model.gamedata.JSONElement; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMapSet; +import com.gpl.rpg.atcontentstudio.model.saves.SavedGamesSet; +import com.gpl.rpg.atcontentstudio.ui.tools.ItemsTableView; +import com.gpl.rpg.atcontentstudio.ui.tools.NPCsTableView; + +public class WorkspaceActions { + + ProjectTreeNode selectedNode = null; + TreePath[] selectedPaths = null; + + public ATCSAction createProject = new ATCSAction("Create project...", "Opens the project creation wizard") { + public void actionPerformed(ActionEvent e) { + new ProjectCreationWizard().setVisible(true); + }; + }; + + + public ATCSAction closeProject = new ATCSAction("Close project", "Closes the project, unloading all resources from memory") { + public void actionPerformed(ActionEvent e) { + if (!(selectedNode instanceof Project)) return; + Workspace.closeProject((Project) selectedNode); + }; + public void selectionChanged(ProjectTreeNode selectedNode, TreePath[] selectedPaths) { + setEnabled(selectedNode instanceof Project); + }; + }; + + + public ATCSAction openProject = new ATCSAction("Open project", "Opens the project, loading all necessary resources in memory") { + public void actionPerformed(ActionEvent e) { + if (!(selectedNode instanceof ClosedProject)) return; + Workspace.openProject((ClosedProject) selectedNode); + }; + public void selectionChanged(ProjectTreeNode selectedNode, TreePath[] selectedPaths) { + setEnabled(selectedNode instanceof ClosedProject); + }; + }; + + public ATCSAction deleteProject = new ATCSAction("Delete project", "Deletes the project, and all created/altered data, from disk") { + public void actionPerformed(ActionEvent e) { + if (selectedNode instanceof Project) { + if (JOptionPane.showConfirmDialog(ATContentStudio.frame, "Are you sure you wish to delete this project ?\nAll files created for it will be deleted too...", "Delete this project ?", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) { + Workspace.deleteProject((Project)selectedNode); + } + } else if (selectedNode instanceof ClosedProject) { + if (JOptionPane.showConfirmDialog(ATContentStudio.frame, "Are you sure you wish to delete this project ?\nAll files created for it will be deleted too...", "Delete this project ?", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) { + Workspace.deleteProject((ClosedProject)selectedNode); + } + } + }; + public void selectionChanged(ProjectTreeNode selectedNode, TreePath[] selectedPaths) { + setEnabled(selectedNode instanceof Project || selectedNode instanceof ClosedProject); + }; + }; + + public ATCSAction saveElement = new ATCSAction("Save this element", "Saves the current state of this element on disk"){ + public void actionPerformed(ActionEvent e) { + if (!(selectedNode instanceof GameDataElement)) return; + final GameDataElement node = ((GameDataElement)selectedNode); + if (node.state == GameDataElement.State.modified){ + node.save(); + ATContentStudio.frame.nodeChanged(node); + } + }; + public void selectionChanged(ProjectTreeNode selectedNode, TreePath[] selectedPaths) { + if (selectedNode instanceof GameDataElement) { + setEnabled(((GameDataElement)selectedNode).state == GameDataElement.State.modified); + } else { + setEnabled(false); + } + }; + }; + + public ATCSAction deleteSelected = new ATCSAction("Delete", "Deletes the selected items") { + boolean multiMode = false; + List elementsToDelete = null; + public void init() { + putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0)); + }; + public void actionPerformed(ActionEvent e) { + if (multiMode) { + if (elementsToDelete == null) return; + final Map, Set> impactedCategories = new IdentityHashMap, Set>(); + for (GameDataElement element : elementsToDelete) { + ATContentStudio.frame.closeEditor(element); + element.childrenRemoved(new ArrayList()); + if (element instanceof JSONElement) { + @SuppressWarnings("unchecked") + GameDataCategory category = (GameDataCategory) element.getParent(); + category.remove(element); + if (impactedCategories.get(category) == null) { + impactedCategories.put(category, new HashSet()); + } + impactedCategories.get(category).add(((JSONElement) element).jsonFile); + } else if (element instanceof TMXMap) { + TMXMapSet parent = (TMXMapSet) element.getParent(); + parent.tmxMaps.remove(element); + } + } + new Thread() { + @Override + public void run() { + final List events = new ArrayList(); + List catEvents = null; + for (GameDataCategory category : impactedCategories.keySet()) { + for (File f : impactedCategories.get(category)) { + catEvents = category.attemptSave(true, f.getName()); + if (catEvents.isEmpty()) { + category.save(f); + } else { + events.addAll(catEvents); + } + } + } + if (!events.isEmpty()) { + new SaveItemsWizard(events, null).setVisible(true); + } + } + }.start(); + } else { + if (!(selectedNode instanceof GameDataElement)) return; + final GameDataElement node = ((GameDataElement)selectedNode); + ATContentStudio.frame.closeEditor(node); + new Thread() { + @Override + public void run() { + node.childrenRemoved(new ArrayList()); + if (node.getParent() instanceof GameDataCategory) { + ((GameDataCategory)node.getParent()).remove(node); + List events = node.attemptSave(); + if (events == null || events.isEmpty()) { + node.save(); + } else { + new SaveItemsWizard(events, null).setVisible(true); + } + } + } + }.start(); + } + }; + public void selectionChanged(ProjectTreeNode selectedNode, TreePath[] selectedPaths) { + elementsToDelete = null; + if (selectedPaths != null && selectedPaths.length > 1) { + multiMode = false; + elementsToDelete = new ArrayList(); + for (TreePath selected : selectedPaths) { + if (selected.getLastPathComponent() instanceof GameDataElement && ((GameDataElement)selected.getLastPathComponent()).writable) { + elementsToDelete.add((GameDataElement) selected.getLastPathComponent()); + multiMode = true; + } else { + multiMode = false; + break; + } + } + putValue(Action.NAME, "Delete all selected elements"); + setEnabled(multiMode); + } else if (selectedNode instanceof GameDataElement && ((GameDataElement)selectedNode).writable) { + multiMode = false; + if (selectedNode.getDataType() == GameSource.Type.created) { + putValue(Action.NAME, "Delete this element"); + setEnabled(true); + } else if (selectedNode.getDataType() == GameSource.Type.altered) { + putValue(Action.NAME, "Revert to original"); + setEnabled(true); + } else { + setEnabled(false); + } + } else { + setEnabled(false); + } + }; + }; + + public ATCSAction createGDE = new ATCSAction("Create Game Data Element (JSON)", "Opens the game object creation wizard") { + public void actionPerformed(ActionEvent e) { + if (selectedNode == null || selectedNode.getProject() == null) return; + new JSONCreationWizard(selectedNode.getProject()).setVisible(true); + } + public void selectionChanged(ProjectTreeNode selectedNode, TreePath[] selectedPaths) { + setEnabled(selectedNode != null && selectedNode.getProject() != null); + } + }; + + public ATCSAction importJSON = new ATCSAction("Import JSON data", "Opens the JSON import wizard") { + public void actionPerformed(ActionEvent e) { + if (selectedNode == null || selectedNode.getProject() == null) return; + new JSONImportWizard(selectedNode.getProject()).setVisible(true); + } + public void selectionChanged(ProjectTreeNode selectedNode, TreePath[] selectedPaths) { + setEnabled(selectedNode != null && selectedNode.getProject() != null); + } + }; + + public ATCSAction loadSave = new ATCSAction("Load saved game...", "Opens the saved game loading wizard"){ + public void actionPerformed(ActionEvent e) { + if(!(selectedNode instanceof Project || selectedNode instanceof SavedGamesSet)) return; + JFileChooser chooser = new JFileChooser("Select an Andor's Trail save file"); + if (chooser.showOpenDialog(ATContentStudio.frame) == JFileChooser.APPROVE_OPTION) { + selectedNode.getProject().addSave(chooser.getSelectedFile()); + selectedNode.getProject().save(); + } + }; + public void selectionChanged(ProjectTreeNode selectedNode, TreePath[] selectedPaths) { + setEnabled(selectedNode instanceof Project || selectedNode instanceof SavedGamesSet); + }; + }; + + public ATCSAction compareItems = new ATCSAction("Items comparator", "Opens an editor showing all the items of the project in a table"){ + public void actionPerformed(ActionEvent e) { + if (selectedNode == null || selectedNode.getProject() == null) return; + ATContentStudio.frame.editors.openEditor(new ItemsTableView(selectedNode.getProject())); + } + public void selectionChanged(ProjectTreeNode selectedNode, TreePath[] selectedPaths) { + setEnabled(selectedNode != null && selectedNode.getProject() != null); + } + }; + + public ATCSAction compareNPCs = new ATCSAction("NPCs comparator", "Opens an editor showing all the NPCs of the project in a table"){ + public void actionPerformed(ActionEvent e) { + if (selectedNode == null || selectedNode.getProject() == null) return; + ATContentStudio.frame.editors.openEditor(new NPCsTableView(selectedNode.getProject())); + } + public void selectionChanged(ProjectTreeNode selectedNode, TreePath[] selectedPaths) { + setEnabled(selectedNode != null && selectedNode.getProject() != null); + } + }; + + public ATCSAction exportProject = new ATCSAction("Export project", "Generates a zip file containing all the created & altered resources of the project, ready to merge with the game source."){ + public void actionPerformed(ActionEvent e) { + if (selectedNode == null || selectedNode.getProject() == null) return; + JFileChooser chooser = new JFileChooser() { + private static final long serialVersionUID = 8039332384370636746L; + public boolean accept(File f) { + return f.isDirectory() || f.getName().endsWith(".zip") || f.getName().endsWith(".ZIP"); + } + }; + chooser.setMultiSelectionEnabled(false); + int result = chooser.showSaveDialog(ATContentStudio.frame); + if (result == JFileChooser.APPROVE_OPTION) { + File f = chooser.getSelectedFile(); + if (!f.getAbsolutePath().substring(f.getAbsolutePath().length() - 4, f.getAbsolutePath().length()).equalsIgnoreCase(".zip")) { + f = new File(f.getAbsolutePath()+".zip"); + } + selectedNode.getProject().generateExportPackage(f); + } + }; + public void selectionChanged(ProjectTreeNode selectedNode, TreePath[] selectedPaths) { + setEnabled(selectedNode != null && selectedNode.getProject() != null); + }; + }; + + public ATCSAction showAbout = new ATCSAction("About...", "Displays credits and other informations about ATCS"){ + public void actionPerformed(ActionEvent e) { + ATContentStudio.frame.showAbout(); + }; + }; + + public ATCSAction exitATCS = new ATCSAction("Exit", "Closes the program"){ + public void actionPerformed(ActionEvent e) { + //TODO ouch. + System.exit(0); + }; + }; + + List actions = new ArrayList(); + + public WorkspaceActions() { + actions.add(createProject); + actions.add(closeProject); + actions.add(openProject); + actions.add(deleteProject); + actions.add(saveElement); + actions.add(deleteSelected); + actions.add(createGDE); + actions.add(importJSON); + actions.add(loadSave); + actions.add(compareItems); + actions.add(compareNPCs); + actions.add(exportProject); + actions.add(showAbout); + actions.add(exitATCS); + selectionChanged(null, null); + } + + public void selectionChanged(ProjectTreeNode selectedNode, TreePath[] selectedPaths){ + this.selectedNode = selectedNode; + this.selectedPaths = selectedPaths; + synchronized(actions) { + for (ATCSAction action : actions) { + action.selectionChanged(selectedNode, selectedPaths); + } + } + } + + public static class ATCSAction implements Action { + + boolean enabled = true; + + + public ATCSAction(String name, String desc) { + putValue(Action.NAME, name); + putValue(Action.SHORT_DESCRIPTION, desc); + init(); + } + + public void init(){} + + public void selectionChanged(ProjectTreeNode selectedNode, TreePath[] selectedPaths){} + + @Override + public void actionPerformed(ActionEvent e) {}; + + public Map values = new HashMap(); + + @Override + public Object getValue(String key) { + return values.get(key); + } + + @Override + public void putValue(String key, Object value) { + PropertyChangeEvent event = new PropertyChangeEvent(this, key, values.get(key), value); + values.put(key, value); + for (PropertyChangeListener l : listeners) { + l.propertyChange(event); + } + } + + @Override + public void setEnabled(boolean b) { + PropertyChangeEvent event = new PropertyChangeEvent(this, "enabled", isEnabled(), b); + enabled = b; + for (PropertyChangeListener l : listeners) { + l.propertyChange(event); + } + } + + @Override + public boolean isEnabled() { + return enabled; + } + + private Set listeners = new HashSet(); + + @Override + public void addPropertyChangeListener(PropertyChangeListener listener) { + listeners.add(listener); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener listener) { + listeners.remove(listener); + } + + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceSelector.java b/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceSelector.java new file mode 100644 index 0000000..7302638 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceSelector.java @@ -0,0 +1,143 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import com.gpl.rpg.atcontentstudio.ConfigCache; + +public class WorkspaceSelector extends JFrame { + + private static final long serialVersionUID = 7518745499760748574L; + + public String selected = null; + + public WorkspaceSelector() { + super("Select your workspace"); + setIconImage(DefaultIcons.getMainIconImage()); + + //Data + final List workspaces = ConfigCache.getKnownWorkspaces(); + final List wsPaths = new ArrayList(); + + //Active widgets declaration + final JComboBox combo = new JComboBox(); + final JButton browse = new JButton("Browse..."); + final JButton cancel = new JButton("Cancel"); + final JButton ok = new JButton("Ok"); + + //Widgets behavior + combo.setEditable(true); + for (File f : workspaces) { + String path = f.getAbsolutePath(); + wsPaths.add(path); + combo.addItem(path); + } + if (ConfigCache.getLatestWorkspace() != null) { + combo.setSelectedItem(wsPaths.get(workspaces.indexOf(ConfigCache.getLatestWorkspace()))); + } + combo.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (combo.getSelectedItem() != null) { + ok.setEnabled(true); + } + } + }); + + + ok.setEnabled(ConfigCache.getLatestWorkspace() != null); + ok.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + WorkspaceSelector.this.selected = (String) combo.getSelectedItem(); + WorkspaceSelector.this.dispose(); + } + }); + + cancel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + WorkspaceSelector.this.selected = null; + WorkspaceSelector.this.dispose(); + } + }); + + browse.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JFileChooser fc; + if(workspaces.isEmpty()) { + fc = new JFileChooser(); + } else { + if (ConfigCache.getLatestWorkspace() != null) { + fc = new JFileChooser(ConfigCache.getLatestWorkspace()); + } else { + fc = new JFileChooser(workspaces.get(0)); + } + } + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + fc.setMultiSelectionEnabled(false); + fc.setAcceptAllFileFilterUsed(false); + int result = fc.showSaveDialog(WorkspaceSelector.this); + if (result == JFileChooser.APPROVE_OPTION) { + String selected = fc.getSelectedFile().getAbsolutePath(); + for (String s : wsPaths) { + if (s.equals(selected)) { + selected = s; + } + } + combo.setSelectedItem(selected); + } + } + }); + + + //Layout, labels and dialog behavior. + setTitle("Select your workspace"); + + JPanel dialogPane = new JPanel(); + dialogPane.setLayout(new BorderLayout()); + + dialogPane.add(new JLabel("Workspace : "), BorderLayout.WEST); + dialogPane.add(combo, BorderLayout.CENTER); + dialogPane.add(browse, BorderLayout.EAST); + + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.BOTH; + c.anchor = GridBagConstraints.EAST; + c.gridx = 0; + c.gridy = 0; + c.weightx = 1; + buttonPane.add(new JLabel(), c); + + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + c.gridx++; + buttonPane.add(cancel, c); + + c.gridx++; + buttonPane.add(ok, c); + + dialogPane.add(buttonPane, BorderLayout.SOUTH); + + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setContentPane(dialogPane); + setResizable(false); + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/ActorConditionEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/ActorConditionEditor.java new file mode 100644 index 0000000..2573847 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/ActorConditionEditor.java @@ -0,0 +1,526 @@ +package com.gpl.rpg.atcontentstudio.ui.gamedataeditors; + +import java.util.ArrayList; + +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.JTextField; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.gamedata.ActorCondition; +import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet; +import com.gpl.rpg.atcontentstudio.ui.CollapsiblePanel; +import com.gpl.rpg.atcontentstudio.ui.FieldUpdateListener; +import com.gpl.rpg.atcontentstudio.ui.IntegerBasedCheckBox; +import com.jidesoft.swing.JideBoxLayout; + +public class ActorConditionEditor extends JSONElementEditor { + + private static final long serialVersionUID = 799130864545495819L; + + private static final String form_view_id = "Form"; + private static final String json_view_id = "JSON"; + + private JButton acIcon; + private JTextField idField; + private JTextField nameField; + private JComboBox categoryBox; + private IntegerBasedCheckBox positiveBox; + private IntegerBasedCheckBox stackingBox; + + private JTextField roundVisualField; + private JSpinner roundHpMinField; + private JSpinner roundHpMaxField; + private JSpinner roundApMinField; + private JSpinner roundApMaxField; + + private JTextField fullRoundVisualField; + private JSpinner fullRoundHpMinField; + private JSpinner fullRoundHpMaxField; + private JSpinner fullRoundApMinField; + private JSpinner fullRoundApMaxField; + + private JSpinner abilityHpField; + private JSpinner abilityApField; + private JSpinner abilityMoveCost; + private JSpinner abilityUseCost; + private JSpinner abilityReequipCost; + private JSpinner abilityAttackCost; + private JSpinner abilityAttackChance; + private JSpinner abilityDamageMinField; + private JSpinner abilityDamageMaxField; + private JSpinner abilityCriticalSkill; + private JSpinner abilityBlockChance; + private JSpinner abilityDamageResistance; + + + public ActorConditionEditor(ActorCondition ac) { + super(ac, ac.getDesc(), ac.getIcon()); + addEditorTab(form_view_id, getFormView()); + addEditorTab(json_view_id, getJSONView()); + } + + @Override + public void insertFormViewDataField(JPanel pane) { + final ActorCondition ac = ((ActorCondition)target); + + final FieldUpdateListener listener = new ActorConditionFieldUpdater(); + + acIcon = createButtonPane(pane, ac.getProject(), ac, ActorCondition.class, ac.getImage(), Spritesheet.Category.actorcondition, listener); + + idField = addTextField(pane, "Internal ID: ", ac.id, ac.writable, listener); + nameField = addTextField(pane, "Display name: ", ac.display_name, ac.writable, listener); + categoryBox = addEnumValueBox(pane, "Category: ", ActorCondition.ACCategory.values(), ac.category, ac.writable, listener); + positiveBox = addIntegerBasedCheckBox(pane, "Positive", ac.positive, ac.writable, listener); + stackingBox = addIntegerBasedCheckBox(pane, "Stacking", ac.stacking, ac.writable, listener); + + + CollapsiblePanel roundEffectPane = new CollapsiblePanel("Effect every round (4s): "); + roundEffectPane.setLayout(new JideBoxLayout(roundEffectPane, JideBoxLayout.PAGE_AXIS)); + final ActorCondition.RoundEffect roundEffect; + if (ac.round_effect != null) { + roundEffect = ac.round_effect; + } else { + roundEffect = new ActorCondition.RoundEffect(); + } + roundVisualField = addTextField(roundEffectPane, "Visual effect ID: ", roundEffect.visual_effect, ac.writable, listener); + roundHpMinField = addIntegerField(roundEffectPane, "HP Bonus Min: ", roundEffect.hp_boost_min, true, ac.writable, listener); + roundHpMaxField = addIntegerField(roundEffectPane, "HP Bonus Max: ", roundEffect.hp_boost_max, true, ac.writable, listener); + roundApMinField = addIntegerField(roundEffectPane, "AP Bonus Min: ", roundEffect.ap_boost_min, true, ac.writable, listener); + roundApMaxField = addIntegerField(roundEffectPane, "AP Bonus Max: ", roundEffect.ap_boost_max, true, ac.writable, listener); + roundEffectPane.setExpanded(ac.round_effect != null); + pane.add(roundEffectPane, JideBoxLayout.FIX); + + + CollapsiblePanel fullRoundEffectPane = new CollapsiblePanel("Effect every full round (20s): "); + fullRoundEffectPane.setLayout(new JideBoxLayout(fullRoundEffectPane, JideBoxLayout.PAGE_AXIS)); + final ActorCondition.RoundEffect fullRoundEffect; + if (ac.full_round_effect != null) { + fullRoundEffect = ac.full_round_effect; + } else { + fullRoundEffect = new ActorCondition.RoundEffect(); + } + fullRoundVisualField = addTextField(fullRoundEffectPane, "Visual effect ID: ", fullRoundEffect.visual_effect, ac.writable, listener); + fullRoundHpMinField = addIntegerField(fullRoundEffectPane, "HP Bonus min: ", fullRoundEffect.hp_boost_min, true, ac.writable, listener); + fullRoundHpMaxField = addIntegerField(fullRoundEffectPane, "HP Bonus max: ", fullRoundEffect.hp_boost_max, true, ac.writable, listener); + fullRoundApMinField = addIntegerField(fullRoundEffectPane, "AP Bonus min: ", fullRoundEffect.ap_boost_min, true, ac.writable, listener); + fullRoundApMaxField = addIntegerField(fullRoundEffectPane, "AP Bonus max: ", fullRoundEffect.ap_boost_max, true, ac.writable, listener); + fullRoundEffectPane.setExpanded(ac.full_round_effect != null); + pane.add(fullRoundEffectPane, JideBoxLayout.FIX); + + CollapsiblePanel abilityEffectPane = new CollapsiblePanel("Constant ability effect: "); + abilityEffectPane.setLayout(new JideBoxLayout(abilityEffectPane, JideBoxLayout.PAGE_AXIS)); + ActorCondition.AbilityEffect abilityEffect; + if (ac.constant_ability_effect != null) { + abilityEffect = ac.constant_ability_effect; + } else { + abilityEffect = new ActorCondition.AbilityEffect(); + } + abilityHpField = addIntegerField(abilityEffectPane, "Boost max HP: ", abilityEffect.max_hp_boost, true, ac.writable, listener); + abilityApField = addIntegerField(abilityEffectPane, "Boost max AP: ", abilityEffect.max_ap_boost, true, ac.writable, listener); + abilityDamageMinField = addIntegerField(abilityEffectPane, "Boost min damage: ", abilityEffect.increase_damage_min, true, ac.writable, listener); + abilityDamageMaxField = addIntegerField(abilityEffectPane, "Boost max damage: ", abilityEffect.increase_damage_max, true, ac.writable, listener); + abilityAttackChance = addIntegerField(abilityEffectPane, "Boost attack chance: ", abilityEffect.increase_attack_chance, true, ac.writable, listener); + abilityBlockChance = addIntegerField(abilityEffectPane, "Boost block chance: ", abilityEffect.increase_block_chance, true, ac.writable, listener); + abilityCriticalSkill = addIntegerField(abilityEffectPane, "Boost critical skill: ", abilityEffect.increase_critical_skill, true, ac.writable, listener); + abilityDamageResistance = addIntegerField(abilityEffectPane, "Boost damage resistance: ", abilityEffect.increase_damage_resistance, true, ac.writable, listener); + abilityMoveCost = addIntegerField(abilityEffectPane, "Increase move cost: ", abilityEffect.increase_move_cost, true, ac.writable, listener); + abilityAttackCost = addIntegerField(abilityEffectPane, "Increase attack cost: ", abilityEffect.increase_attack_cost, true, ac.writable, listener); + abilityUseCost = addIntegerField(abilityEffectPane, "Increase item use cost: ", abilityEffect.increase_use_cost, true, ac.writable, listener); + abilityReequipCost = addIntegerField(abilityEffectPane, "Increase reequip cost: ", abilityEffect.increase_reequip_cost, true, ac.writable, listener); + abilityEffectPane.setExpanded(ac.constant_ability_effect != null); + pane.add(abilityEffectPane, JideBoxLayout.FIX); + + } + + + //TODO enhancement. Split this in smaller pieces (one for each base field, and one for each "*Effect". later, later.... + public class ActorConditionFieldUpdater implements FieldUpdateListener { + @Override + public void valueChanged(JComponent source, Object value) { + ActorCondition aCond = (ActorCondition)target; + if (source == idField) { + aCond.id = (String) value; + ActorConditionEditor.this.name = aCond.getDesc(); + aCond.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(ActorConditionEditor.this); + } else if (source == nameField) { + aCond.display_name = (String) value; + ActorConditionEditor.this.name = aCond.getDesc(); + aCond.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(ActorConditionEditor.this); + } else if (source == acIcon) { + aCond.icon_id = (String) value; + aCond.childrenChanged(new ArrayList()); + ActorConditionEditor.this.icon = new ImageIcon(aCond.getProject().getIcon((String) value)); + ATContentStudio.frame.editorChanged(ActorConditionEditor.this); + acIcon.setIcon(new ImageIcon(aCond.getProject().getImage((String) value))); + acIcon.revalidate(); + acIcon.repaint(); + } else if (source == positiveBox) { + aCond.positive = (Integer) value; + } else if (source == stackingBox) { + aCond.positive = (Integer) value; + } else if (source == categoryBox) { + aCond.category = (ActorCondition.ACCategory) value; + } else if (source == roundVisualField) { + if (value == null) { + if (aCond.round_effect != null) { + aCond.round_effect.visual_effect = null; + if (isEmpty(aCond.round_effect)) { + aCond.round_effect = null; + } + } + } else { + if (aCond.round_effect == null) { + aCond.round_effect = new ActorCondition.RoundEffect(); + } + aCond.round_effect.visual_effect = (String) value; + } + } else if (source == roundHpMinField) { + if (value == null) { + if (aCond.round_effect != null) { + aCond.round_effect.hp_boost_min = null; + if (isEmpty(aCond.round_effect)) { + aCond.round_effect = null; + } + } + } else { + if (aCond.round_effect == null) { + aCond.round_effect = new ActorCondition.RoundEffect(); + } + aCond.round_effect.hp_boost_min = (Integer) value; + } + } else if (source == roundHpMaxField) { + if (value == null) { + if (aCond.round_effect != null) { + aCond.round_effect.hp_boost_max = null; + if (isEmpty(aCond.round_effect)) { + aCond.round_effect = null; + } + } + } else { + if (aCond.round_effect == null) { + aCond.round_effect = new ActorCondition.RoundEffect(); + } + aCond.round_effect.hp_boost_max = (Integer) value; + } + } else if (source == roundApMinField) { + if (value == null) { + if (aCond.round_effect != null) { + aCond.round_effect.ap_boost_min = null; + if (isEmpty(aCond.round_effect)) { + aCond.round_effect = null; + } + } + } else { + if (aCond.round_effect == null) { + aCond.round_effect = new ActorCondition.RoundEffect(); + } + aCond.round_effect.ap_boost_min = (Integer) value; + } + } else if (source == roundApMaxField) { + if (value == null) { + if (aCond.round_effect != null) { + aCond.round_effect.ap_boost_max = null; + if (isEmpty(aCond.round_effect)) { + aCond.round_effect = null; + } + } + } else { + if (aCond.round_effect == null) { + aCond.round_effect = new ActorCondition.RoundEffect(); + } + aCond.round_effect.ap_boost_max = (Integer) value; + } + } else if (source == fullRoundVisualField) { + if (value == null) { + if (aCond.full_round_effect != null) { + aCond.full_round_effect.visual_effect = null; + if (isEmpty(aCond.full_round_effect)) { + aCond.full_round_effect = null; + } + } + } else { + if (aCond.full_round_effect == null) { + aCond.full_round_effect = new ActorCondition.RoundEffect(); + } + aCond.full_round_effect.visual_effect = (String) value; + } + } else if (source == fullRoundHpMinField) { + if (value == null) { + if (aCond.full_round_effect != null) { + aCond.full_round_effect.hp_boost_min = null; + if (isEmpty(aCond.full_round_effect)) { + aCond.full_round_effect = null; + } + } + } else { + if (aCond.full_round_effect == null) { + aCond.full_round_effect = new ActorCondition.RoundEffect(); + } + aCond.full_round_effect.hp_boost_min = (Integer) value; + } + } else if (source == fullRoundHpMaxField) { + if (value == null) { + if (aCond.full_round_effect != null) { + aCond.full_round_effect.hp_boost_max = null; + if (isEmpty(aCond.full_round_effect)) { + aCond.full_round_effect = null; + } + } + } else { + if (aCond.full_round_effect == null) { + aCond.full_round_effect = new ActorCondition.RoundEffect(); + } + aCond.full_round_effect.hp_boost_max = (Integer) value; + } + } else if (source == fullRoundApMinField) { + if (value == null) { + if (aCond.full_round_effect != null) { + aCond.full_round_effect.ap_boost_min = null; + if (isEmpty(aCond.full_round_effect)) { + aCond.full_round_effect = null; + } + } + } else { + if (aCond.full_round_effect == null) { + aCond.full_round_effect = new ActorCondition.RoundEffect(); + } + aCond.full_round_effect.ap_boost_min = (Integer) value; + } + } else if (source == fullRoundApMaxField) { + if (value == null) { + if (aCond.full_round_effect != null) { + aCond.full_round_effect.ap_boost_max = null; + if (isEmpty(aCond.full_round_effect)) { + aCond.full_round_effect = null; + } + } + } else { + if (aCond.full_round_effect == null) { + aCond.full_round_effect = new ActorCondition.RoundEffect(); + } + aCond.full_round_effect.ap_boost_max = (Integer) value; + } + } else if (source == abilityHpField) { + if (value == null) { + if (aCond.constant_ability_effect != null) { + aCond.constant_ability_effect.max_hp_boost = null; + if (isEmpty(aCond.constant_ability_effect)) { + aCond.constant_ability_effect = null; + } + } + } else { + if (aCond.constant_ability_effect == null) { + aCond.constant_ability_effect = new ActorCondition.AbilityEffect(); + } + aCond.constant_ability_effect.max_hp_boost = (Integer) value; + } + } else if (source == abilityApField) { + if (value == null) { + if (aCond.constant_ability_effect != null) { + aCond.constant_ability_effect.max_ap_boost = null; + if (isEmpty(aCond.constant_ability_effect)) { + aCond.constant_ability_effect = null; + } + } + } else { + if (aCond.constant_ability_effect == null) { + aCond.constant_ability_effect = new ActorCondition.AbilityEffect(); + } + aCond.constant_ability_effect.max_ap_boost = (Integer) value; + } + } else if (source == abilityMoveCost) { + if (value == null) { + if (aCond.constant_ability_effect != null) { + aCond.constant_ability_effect.increase_move_cost = null; + if (isEmpty(aCond.constant_ability_effect)) { + aCond.constant_ability_effect = null; + } + } + } else { + if (aCond.constant_ability_effect == null) { + aCond.constant_ability_effect = new ActorCondition.AbilityEffect(); + } + aCond.constant_ability_effect.increase_move_cost = (Integer) value; + } + } else if (source == abilityUseCost) { + if (value == null) { + if (aCond.constant_ability_effect != null) { + aCond.constant_ability_effect.increase_use_cost = null; + if (isEmpty(aCond.constant_ability_effect)) { + aCond.constant_ability_effect = null; + } + } + } else { + if (aCond.constant_ability_effect == null) { + aCond.constant_ability_effect = new ActorCondition.AbilityEffect(); + } + aCond.constant_ability_effect.increase_use_cost = (Integer) value; + } + } else if (source == abilityReequipCost) { + if (value == null) { + if (aCond.constant_ability_effect != null) { + aCond.constant_ability_effect.increase_reequip_cost = null; + if (isEmpty(aCond.constant_ability_effect)) { + aCond.constant_ability_effect = null; + } + } + } else { + if (aCond.constant_ability_effect == null) { + aCond.constant_ability_effect = new ActorCondition.AbilityEffect(); + } + aCond.constant_ability_effect.increase_reequip_cost = (Integer) value; + } + } else if (source == abilityAttackCost) { + if (value == null) { + if (aCond.constant_ability_effect != null) { + aCond.constant_ability_effect.increase_attack_cost = null; + if (isEmpty(aCond.constant_ability_effect)) { + aCond.constant_ability_effect = null; + } + } + } else { + if (aCond.constant_ability_effect == null) { + aCond.constant_ability_effect = new ActorCondition.AbilityEffect(); + } + aCond.constant_ability_effect.increase_attack_cost = (Integer) value; + } + } else if (source == abilityAttackChance) { + if (value == null) { + if (aCond.constant_ability_effect != null) { + aCond.constant_ability_effect.increase_attack_chance = null; + if (isEmpty(aCond.constant_ability_effect)) { + aCond.constant_ability_effect = null; + } + } + } else { + if (aCond.constant_ability_effect == null) { + aCond.constant_ability_effect = new ActorCondition.AbilityEffect(); + } + aCond.constant_ability_effect.increase_attack_chance = (Integer) value; + } + } else if (source == abilityDamageMinField) { + if (value == null) { + if (aCond.constant_ability_effect != null) { + aCond.constant_ability_effect.increase_damage_min = null; + if (isEmpty(aCond.constant_ability_effect)) { + aCond.constant_ability_effect = null; + } + } + } else { + if (aCond.constant_ability_effect == null) { + aCond.constant_ability_effect = new ActorCondition.AbilityEffect(); + } + aCond.constant_ability_effect.increase_damage_min = (Integer) value; + } + } else if (source == abilityDamageMaxField) { + if (value == null) { + if (aCond.constant_ability_effect != null) { + aCond.constant_ability_effect.increase_damage_max = null; + if (isEmpty(aCond.constant_ability_effect)) { + aCond.constant_ability_effect = null; + } + } + } else { + if (aCond.constant_ability_effect == null) { + aCond.constant_ability_effect = new ActorCondition.AbilityEffect(); + } + aCond.constant_ability_effect.increase_damage_max = (Integer) value; + } + } else if (source == abilityCriticalSkill) { + if (value == null) { + if (aCond.constant_ability_effect != null) { + aCond.constant_ability_effect.increase_critical_skill = null; + if (isEmpty(aCond.constant_ability_effect)) { + aCond.constant_ability_effect = null; + } + } + } else { + if (aCond.constant_ability_effect == null) { + aCond.constant_ability_effect = new ActorCondition.AbilityEffect(); + } + aCond.constant_ability_effect.increase_critical_skill = (Integer) value; + } + } else if (source == abilityBlockChance) { + if (value == null) { + if (aCond.constant_ability_effect != null) { + aCond.constant_ability_effect.increase_block_chance = null; + if (isEmpty(aCond.constant_ability_effect)) { + aCond.constant_ability_effect = null; + } + } + } else { + if (aCond.constant_ability_effect == null) { + aCond.constant_ability_effect = new ActorCondition.AbilityEffect(); + } + aCond.constant_ability_effect.increase_block_chance = (Integer) value; + } + } else if (source == abilityDamageResistance) { + if (value == null) { + if (aCond.constant_ability_effect != null) { + aCond.constant_ability_effect.increase_damage_resistance = null; + if (isEmpty(aCond.constant_ability_effect)) { + aCond.constant_ability_effect = null; + } + } + } else { + if (aCond.constant_ability_effect == null) { + aCond.constant_ability_effect = new ActorCondition.AbilityEffect(); + } + aCond.constant_ability_effect.increase_damage_resistance = (Integer) value; + } + } + + if (aCond.state != GameDataElement.State.modified) { + aCond.state = GameDataElement.State.modified; + ActorConditionEditor.this.name = aCond.getDesc(); + aCond.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(ActorConditionEditor.this); + } + updateJsonViewText(aCond.toJsonString()); + } + + private boolean isEmpty(ActorCondition.RoundEffect round_effect) { + return round_effect == null || ( + round_effect.visual_effect == null && + round_effect.hp_boost_min == null && + round_effect.hp_boost_max == null && + round_effect.ap_boost_min == null && + round_effect.ap_boost_max == null + ); + } + + private boolean isEmpty(ActorCondition.AbilityEffect ability_effect) { + return ability_effect == null || ( + ability_effect.max_hp_boost == null && + ability_effect.max_ap_boost == null && + ability_effect.increase_move_cost == null && + ability_effect.increase_use_cost == null && + ability_effect.increase_reequip_cost == null && + ability_effect.increase_attack_cost == null && + ability_effect.increase_attack_chance == null && + ability_effect.increase_damage_min == null && + ability_effect.increase_damage_max == null && + ability_effect.increase_critical_skill == null && + ability_effect.increase_block_chance == null && + ability_effect.increase_damage_resistance == null + ); + } + } + + + + + + + + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/DialogueEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/DialogueEditor.java new file mode 100644 index 0000000..9b19a6a --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/DialogueEditor.java @@ -0,0 +1,1114 @@ +package com.gpl.rpg.atcontentstudio.ui.gamedataeditors; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.JTextField; +import javax.swing.ListModel; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.gamedata.ActorCondition; +import com.gpl.rpg.atcontentstudio.model.gamedata.Dialogue; +import com.gpl.rpg.atcontentstudio.model.gamedata.Droplist; +import com.gpl.rpg.atcontentstudio.model.gamedata.Item; +import com.gpl.rpg.atcontentstudio.model.gamedata.NPC; +import com.gpl.rpg.atcontentstudio.model.gamedata.Quest; +import com.gpl.rpg.atcontentstudio.model.gamedata.Requirement; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; +import com.gpl.rpg.atcontentstudio.ui.BooleanBasedCheckBox; +import com.gpl.rpg.atcontentstudio.ui.CollapsiblePanel; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; +import com.gpl.rpg.atcontentstudio.ui.FieldUpdateListener; +import com.gpl.rpg.atcontentstudio.ui.gamedataeditors.dialoguetree.DialogueGraphView; +import com.jidesoft.swing.JideBoxLayout; + +public class DialogueEditor extends JSONElementEditor { + + private static final long serialVersionUID = 4140553240585599873L; + + private static final String form_view_id = "Form"; + private static final String json_view_id = "JSON"; + private static final String graph_view_id = "Dialogue Tree"; + + private Dialogue.Reward selectedReward; + private Dialogue.Reply selectedReply; + private Requirement selectedRequirement; + + + private static final String[] replyTypes = new String[]{ + "Phrase leads to another without replies.", + "NPC replies too.", + "Reply ends dialogue.", + "Engage fight with NPC.", + "Remove NPC from map.", + "Start trading with NPC." + }; + private static final int GO_NEXT_INDEX = 0; + private static final int STD_REPLY_INDEX = 1; + private static final int END_INDEX = 2; + private static final int FIGHT_INDEX = 3; + private static final int REMOVE_INDEX = 4; + private static final int SHOP_INDEX = 5; + + private JTextField idField; + private JTextField messageField; + private MyComboBox switchToNpcBox; + + private RewardsListModel rewardsListModel; + private JList rewardsList; + private JComboBox rewardTypeCombo; + private JPanel rewardsParamsPane; + private MyComboBox rewardMap; + private JTextField rewardObjId; + private MyComboBox rewardObj; + private JSpinner rewardValue; + + private RepliesListModel repliesListModel; + private JList repliesList; + private JPanel repliesParamsPane; + private JComboBox replyTypeCombo; + private MyComboBox replyNextPhrase; + private String replyTextCache = null; + private JTextField replyText; + + private ReplyRequirementsListModel requirementsListModel; + private JList requirementsList; + private JComboBox requirementTypeCombo; + private JPanel requirementParamsPane; + private MyComboBox requirementObj; + private JTextField requirementObjId; + private JSpinner requirementValue; + private BooleanBasedCheckBox requirementNegated; + + + public DialogueEditor(Dialogue dialogue) { + super(dialogue, dialogue.getDesc(), dialogue.getIcon()); + addEditorTab(form_view_id, getFormView()); + addEditorTab(json_view_id, getJSONView()); + JPanel pane = new JPanel(); + pane.setLayout(new BorderLayout()); + pane.add(new JScrollPane(new DialogueGraphView(dialogue, null)), BorderLayout.CENTER); + addEditorTab(graph_view_id, pane); + } + + public void insertFormViewDataField(final JPanel pane) { + + final Dialogue dialogue = (Dialogue) target; + final FieldUpdateListener listener = new DialogueFieldUpdater(); + + createButtonPane(pane, dialogue.getProject(), dialogue, Dialogue.class, dialogue.getImage(), null, listener); + + idField = addTextField(pane, "Internal ID: ", dialogue.id, dialogue.writable, listener); + messageField = addTextField(pane, "Message: ", dialogue.message, dialogue.writable, listener); + switchToNpcBox = addNPCBox(pane, dialogue.getProject(), "Switch active NPC to: ", dialogue.switch_to_npc, dialogue.writable, listener); + + CollapsiblePanel rewards = new CollapsiblePanel("Reaching this phrase gives the following rewards: "); + rewards.setLayout(new JideBoxLayout(rewards, JideBoxLayout.PAGE_AXIS)); + rewardsListModel = new RewardsListModel(dialogue); + rewardsList = new JList(rewardsListModel); + rewardsList.setCellRenderer(new RewardsCellRenderer()); + rewardsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + rewards.add(new JScrollPane(rewardsList), JideBoxLayout.FIX); + final JPanel rewardsEditorPane = new JPanel(); + final JButton createReward = new JButton(new ImageIcon(DefaultIcons.getCreateIcon())); + final JButton deleteReward = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + deleteReward.setEnabled(false); + rewardsList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + selectedReward = (Dialogue.Reward) rewardsList.getSelectedValue(); + if (selectedReward == null) { + deleteReward.setEnabled(false); + } else { + deleteReward.setEnabled(true); + } + updateRewardsEditorPane(rewardsEditorPane, selectedReward, listener); + } + }); + if (dialogue.writable) { + JPanel listButtonsPane = new JPanel(); + listButtonsPane.setLayout(new JideBoxLayout(listButtonsPane, JideBoxLayout.LINE_AXIS, 6)); + createReward.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Dialogue.Reward reward = new Dialogue.Reward(); + rewardsListModel.addItem(reward); + rewardsList.setSelectedValue(reward, true); + listener.valueChanged(new JLabel(), null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + }); + deleteReward.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (selectedReward != null) { + rewardsListModel.removeItem(selectedReward); + selectedReward = null; + rewardsList.clearSelection(); + listener.valueChanged(new JLabel(), null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + } + }); + + listButtonsPane.add(createReward, JideBoxLayout.FIX); + listButtonsPane.add(deleteReward, JideBoxLayout.FIX); + listButtonsPane.add(new JPanel(), JideBoxLayout.VARY); + rewards.add(listButtonsPane, JideBoxLayout.FIX); + } + if (dialogue.rewards == null || dialogue.rewards.isEmpty()) { + rewards.collapse(); + } + rewardsEditorPane.setLayout(new JideBoxLayout(rewardsEditorPane, JideBoxLayout.PAGE_AXIS)); + rewards.add(rewardsEditorPane, JideBoxLayout.FIX); + + pane.add(rewards, JideBoxLayout.FIX); + + CollapsiblePanel replies = new CollapsiblePanel("Replies / Next Phrase: "); + replies.setLayout(new JideBoxLayout(replies, JideBoxLayout.PAGE_AXIS)); + repliesListModel = new RepliesListModel(dialogue); + repliesList = new JList(repliesListModel); + repliesList.setCellRenderer(new RepliesCellRenderer()); + repliesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + replies.add(new JScrollPane(repliesList), JideBoxLayout.FIX); + final JPanel repliesEditorPane = new JPanel(); + final JButton createReply = new JButton(new ImageIcon(DefaultIcons.getCreateIcon())); + final JButton deleteReply = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + final JButton moveReplyUp = new JButton(new ImageIcon(DefaultIcons.getArrowUpIcon())); + final JButton moveReplyDown = new JButton(new ImageIcon(DefaultIcons.getArrowDownIcon())); + deleteReply.setEnabled(false); + moveReplyUp.setEnabled(false); + moveReplyDown.setEnabled(false); + repliesList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + selectedReply = (Dialogue.Reply) repliesList.getSelectedValue(); + if (selectedReply != null && !Dialogue.Reply.GO_NEXT_TEXT.equals(selectedReply.text)) { + replyTextCache = selectedReply.text; + } else { + replyTextCache = null; + } + if (selectedReply != null) { + deleteReply.setEnabled(true); + moveReplyUp.setEnabled(repliesList.getSelectedIndex() > 0); + moveReplyDown.setEnabled(repliesList.getSelectedIndex() < (repliesListModel.getSize() - 1)); + } else { + deleteReply.setEnabled(false); + moveReplyUp.setEnabled(false); + moveReplyDown.setEnabled(false); + } + updateRepliesEditorPane(repliesEditorPane, selectedReply, listener); + } + }); + if (dialogue.writable) { + JPanel listButtonsPane = new JPanel(); + listButtonsPane.setLayout(new JideBoxLayout(listButtonsPane, JideBoxLayout.LINE_AXIS, 6)); + createReply.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Dialogue.Reply reply = new Dialogue.Reply(); + repliesListModel.addItem(reply); + repliesList.setSelectedValue(reply, true); + listener.valueChanged(new JLabel(), null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + }); + deleteReply.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (selectedReply != null) { + repliesListModel.removeItem(selectedReply); + selectedReply = null; + repliesList.clearSelection(); + listener.valueChanged(new JLabel(), null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + } + }); + moveReplyUp.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + if (selectedReply != null) { + repliesListModel.moveUp(selectedReply); + repliesList.setSelectedValue(selectedReply, true); + listener.valueChanged(new JLabel(), null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + } + }); + moveReplyDown.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + if (selectedReply != null) { + repliesListModel.moveDown(selectedReply); + repliesList.setSelectedValue(selectedReply, true); + listener.valueChanged(new JLabel(), null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + } + }); + listButtonsPane.add(createReply, JideBoxLayout.FIX); + listButtonsPane.add(deleteReply, JideBoxLayout.FIX); + listButtonsPane.add(moveReplyUp, JideBoxLayout.FIX); + listButtonsPane.add(moveReplyDown, JideBoxLayout.FIX); + listButtonsPane.add(new JPanel(), JideBoxLayout.VARY); + replies.add(listButtonsPane, JideBoxLayout.FIX); + } + if (dialogue.replies == null || dialogue.replies.isEmpty()) { + replies.collapse(); + } + repliesEditorPane.setLayout(new JideBoxLayout(repliesEditorPane, JideBoxLayout.PAGE_AXIS)); + replies.add(repliesEditorPane, JideBoxLayout.FIX); + + pane.add(replies, JideBoxLayout.FIX); + + + } + + public void updateRewardsEditorPane(final JPanel pane, final Dialogue.Reward reward, final FieldUpdateListener listener) { + pane.removeAll(); + if (rewardMap != null) { + removeElementListener(rewardMap); + } + if (rewardObj != null) { + removeElementListener(rewardObj); + } + + if (reward != null) { + rewardTypeCombo = addEnumValueBox(pane, "Reward type: ", Dialogue.Reward.RewardType.values(), reward.type, ((Dialogue)target).writable, listener); + rewardsParamsPane = new JPanel(); + rewardsParamsPane.setLayout(new JideBoxLayout(rewardsParamsPane, JideBoxLayout.PAGE_AXIS)); + updateRewardsParamsEditorPane(rewardsParamsPane, reward, listener); + pane.add(rewardsParamsPane, JideBoxLayout.FIX); + } + pane.revalidate(); + pane.repaint(); + } + + public void updateRewardsParamsEditorPane(final JPanel pane, final Dialogue.Reward reward, final FieldUpdateListener listener) { + boolean writable = ((Dialogue)target).writable; + pane.removeAll(); + if (rewardMap != null) { + removeElementListener(rewardMap); + } + if (rewardObj != null) { + removeElementListener(rewardObj); + } + if (reward.type != null) { + switch (reward.type) { + case activateMapChangeArea: + case deactivateMapChangeArea: + case deactivateSpawnArea: + case removeSpawnArea: + case spawnAll: + rewardMap = addMapBox(pane, ((Dialogue)target).getProject(), "Map Name: ", reward.map, writable, listener); + rewardObjId = addTextField(pane, "Area ID: ", reward.reward_obj_id, writable, listener); + rewardObj = null; + rewardValue = null; + break; + case actorCondition: + rewardMap = null; + rewardObjId = null; + rewardObj = addActorConditionBox(pane, ((Dialogue)target).getProject(), "Actor Condition: ", (ActorCondition) reward.reward_obj, writable, listener); + rewardValue = addIntegerField(pane, "Duration: ", reward.reward_value, false, writable, listener); + break; + case alignmentChange: + rewardMap = null; + rewardObjId = addTextField(pane, "Faction: ", reward.reward_obj_id, writable, listener); + rewardObj = null; + rewardValue = addIntegerField(pane, "Value: ", reward.reward_value, true, writable, listener); + break; + case createTimer: + rewardMap = null; + rewardObjId = addTextField(pane, "Timer ID: ", reward.reward_obj_id, writable, listener); + rewardObj = null; + rewardValue = null; + break; + case dropList: + rewardMap = null; + rewardObjId = null; + rewardObj = addDroplistBox(pane, ((Dialogue)target).getProject(), "Droplist: ", (Droplist) reward.reward_obj, writable, listener); + rewardValue = null; + break; + case giveItem: + rewardMap = null; + rewardObjId = null; + rewardObj = addItemBox(pane, ((Dialogue)target).getProject(), "Item: ", (Item) reward.reward_obj, writable, listener); + rewardValue = addIntegerField(pane, "Quantity: ", reward.reward_value, false, writable, listener); + break; + case questProgress: + rewardMap = null; + rewardObjId = null; + rewardObj = addQuestBox(pane, ((Dialogue)target).getProject(), "Quest: ", (Quest) reward.reward_obj, writable, listener); + rewardValue = addIntegerField(pane, "Step ID: ", reward.reward_value, false, writable, listener); + break; + case skillIncrease: + rewardMap = null; + rewardObjId = addTextField(pane, "Skill ID: ", reward.reward_obj_id, writable, listener); + rewardObj = null; + rewardValue = null; + break; + + } + } + pane.revalidate(); + pane.repaint(); + } + + public void updateRepliesEditorPane(final JPanel pane, final Dialogue.Reply reply, final FieldUpdateListener listener) { + pane.removeAll(); + if (replyNextPhrase != null) { + removeElementListener(replyNextPhrase); + } + if (requirementObj != null) { + removeElementListener(requirementObj); + } + if (reply == null) return; + + JPanel comboPane = new JPanel(); + comboPane.setLayout(new BorderLayout()); + JLabel comboLabel = new JLabel("Reply type: "); + comboPane.add(comboLabel, BorderLayout.WEST); + + replyTypeCombo = new JComboBox(replyTypes); + replyTypeCombo.setEnabled(((Dialogue)target).writable); + repliesParamsPane = new JPanel(); + repliesParamsPane.setLayout(new JideBoxLayout(repliesParamsPane, JideBoxLayout.PAGE_AXIS)); + if (Dialogue.Reply.GO_NEXT_TEXT.equals(reply.text)) { + replyTypeCombo.setSelectedItem(replyTypes[GO_NEXT_INDEX]); + } else if (Dialogue.Reply.EXIT_PHRASE_ID.equals(reply.next_phrase_id)) { + replyTypeCombo.setSelectedItem(replyTypes[END_INDEX]); + } else if (Dialogue.Reply.FIGHT_PHRASE_ID.equals(reply.next_phrase_id)) { + replyTypeCombo.setSelectedItem(replyTypes[FIGHT_INDEX]); + } else if (Dialogue.Reply.REMOVE_PHRASE_ID.equals(reply.next_phrase_id)) { + replyTypeCombo.setSelectedItem(replyTypes[REMOVE_INDEX]); + } else if (Dialogue.Reply.SHOP_PHRASE_ID.equals(reply.next_phrase_id)) { + replyTypeCombo.setSelectedItem(replyTypes[SHOP_INDEX]); + } else { + replyTypeCombo.setSelectedItem(replyTypes[STD_REPLY_INDEX]); + } + replyTypeCombo.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + if (replyTypes[GO_NEXT_INDEX].equals(e.getItem())) { + if (!Dialogue.Reply.GO_NEXT_TEXT.equals(reply.text) && reply.text != null) { + replyTextCache = reply.text; + } + reply.text = Dialogue.Reply.GO_NEXT_TEXT; + if (Dialogue.Reply.KEY_PHRASE_ID.contains(selectedReply.next_phrase_id)) { + reply.next_phrase_id = null; + reply.next_phrase = null; + } + } else { + if (Dialogue.Reply.GO_NEXT_TEXT.equals(reply.text) || reply.text == null) { + reply.text = replyTextCache; + } + if (!replyTypes[STD_REPLY_INDEX].equals(e.getItem())) { + if (replyTypes[END_INDEX].equals(e.getItem())) { + reply.next_phrase_id = Dialogue.Reply.EXIT_PHRASE_ID; + reply.next_phrase = null; + } else if (replyTypes[FIGHT_INDEX].equals(e.getItem())) { + reply.next_phrase_id = Dialogue.Reply.FIGHT_PHRASE_ID; + reply.next_phrase = null; + } else if (replyTypes[REMOVE_INDEX].equals(e.getItem())) { + reply.next_phrase_id = Dialogue.Reply.REMOVE_PHRASE_ID; + reply.next_phrase = null; + } else if (replyTypes[SHOP_INDEX].equals(e.getItem())) { + reply.next_phrase_id = Dialogue.Reply.SHOP_PHRASE_ID; + reply.next_phrase = null; + } + } else if (Dialogue.Reply.KEY_PHRASE_ID.contains(selectedReply.next_phrase_id)) { + reply.next_phrase_id = null; + reply.next_phrase = null; + } + } + listener.valueChanged(replyTypeCombo, null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + } + }); + comboPane.add(replyTypeCombo, BorderLayout.CENTER); + pane.add(comboPane, JideBoxLayout.FIX); + updateRepliesParamsEditorPane(repliesParamsPane, reply, listener); + pane.add(repliesParamsPane, JideBoxLayout.FIX); + + CollapsiblePanel requirementsPane = new CollapsiblePanel("Requirements the player must fulfill to select this reply: "); + requirementsPane.setLayout(new JideBoxLayout(requirementsPane, JideBoxLayout.PAGE_AXIS)); + requirementsListModel = new ReplyRequirementsListModel(reply); + requirementsList = new JList(requirementsListModel); + requirementsList.setCellRenderer(new ReplyRequirementsCellRenderer()); + requirementsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + requirementsPane.add(new JScrollPane(requirementsList), JideBoxLayout.FIX); + final JPanel requirementsEditorPane = new JPanel(); + final JButton createReq = new JButton(new ImageIcon(DefaultIcons.getCreateIcon())); + final JButton deleteReq = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + deleteReq.setEnabled(false); + requirementsList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + selectedRequirement = (Requirement) requirementsList.getSelectedValue(); + if (selectedRequirement != null) { + deleteReq.setEnabled(true); + } else { + deleteReq.setEnabled(false); + } + updateRequirementsEditorPane(requirementsEditorPane, selectedRequirement, listener); + } + }); + requirementsList.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + if (requirementsList.getSelectedValue() != null && ((Requirement)requirementsList.getSelectedValue()).required_obj != null) { + ATContentStudio.frame.openEditor(((Requirement)requirementsList.getSelectedValue()).required_obj); + ATContentStudio.frame.selectInTree(((Requirement)requirementsList.getSelectedValue()).required_obj); + } + } + } + }); + requirementsList.addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + ATContentStudio.frame.openEditor(((Requirement)requirementsList.getSelectedValue()).required_obj); + ATContentStudio.frame.selectInTree(((Requirement)requirementsList.getSelectedValue()).required_obj); + } + } + }); + if (((Dialogue)target).writable) { + JPanel listButtonsPane = new JPanel(); + listButtonsPane.setLayout(new JideBoxLayout(listButtonsPane, JideBoxLayout.LINE_AXIS, 6)); + createReq.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Requirement req = new Requirement(); + requirementsListModel.addItem(req); + requirementsList.setSelectedValue(req, true); + listener.valueChanged(new JLabel(), null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + }); + deleteReq.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (selectedRequirement != null) { + requirementsListModel.removeItem(selectedRequirement); + selectedRequirement = null; + requirementsList.clearSelection(); + listener.valueChanged(new JLabel(), null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + } + }); + + listButtonsPane.add(createReq, JideBoxLayout.FIX); + listButtonsPane.add(deleteReq, JideBoxLayout.FIX); + listButtonsPane.add(new JPanel(), JideBoxLayout.VARY); + requirementsPane.add(listButtonsPane, JideBoxLayout.FIX); + } + requirementsEditorPane.setLayout(new JideBoxLayout(requirementsEditorPane, JideBoxLayout.PAGE_AXIS)); + requirementsPane.add(requirementsEditorPane, JideBoxLayout.FIX); + if (reply.requirements == null || reply.requirements.isEmpty()) { + requirementsPane.collapse(); + } + pane.add(requirementsPane, JideBoxLayout.FIX); + + pane.revalidate(); + pane.repaint(); + } + + public void updateRepliesParamsEditorPane(final JPanel pane, final Dialogue.Reply reply, final FieldUpdateListener listener) { + boolean writable = ((Dialogue)target).writable; + pane.removeAll(); + + if (replyNextPhrase != null) { + removeElementListener(replyNextPhrase); + } + if (requirementObj != null) { + removeElementListener(requirementObj); + } + + if (Dialogue.Reply.GO_NEXT_TEXT.equals(reply.text)) { + replyText = null; + replyNextPhrase = addDialogueBox(pane, ((Dialogue)target).getProject(), "Next phrase: ", reply.next_phrase, writable, listener); + } else if (Dialogue.Reply.KEY_PHRASE_ID.contains(reply.next_phrase_id)) { + replyText = addTextField(pane, "Reply text: ", reply.text, writable, listener); + replyNextPhrase = null; + } else { + replyText = addTextField(pane, "Reply text: ", reply.text, writable, listener); + replyNextPhrase = addDialogueBox(pane, ((Dialogue)target).getProject(), "Next phrase: ", reply.next_phrase, writable, listener); + } + + + pane.revalidate(); + pane.repaint(); + } + + public void updateRequirementsEditorPane(final JPanel pane, final Requirement requirement, final FieldUpdateListener listener) { + boolean writable = ((Dialogue)target).writable; + pane.removeAll(); + + if (requirementObj != null) { + removeElementListener(requirementObj); + } + + requirementTypeCombo = addEnumValueBox(pane, "Requirement type: ", Requirement.RequirementType.values(), requirement.type, writable, listener); + requirementParamsPane = new JPanel(); + requirementParamsPane.setLayout(new JideBoxLayout(requirementParamsPane, JideBoxLayout.PAGE_AXIS)); + updateRequirementParamsEditorPane(requirementParamsPane, requirement, listener); + pane.add(requirementParamsPane, JideBoxLayout.FIX); + pane.revalidate(); + pane.repaint(); + } + + public void updateRequirementParamsEditorPane(final JPanel pane, final Requirement requirement, final FieldUpdateListener listener) { + boolean writable = ((Dialogue)target).writable; + Project project = ((Dialogue)target).getProject(); + pane.removeAll(); + if (requirementObj != null) { + removeElementListener(requirementObj); + } + + if (requirement.type != null) { + switch (requirement.type) { + case consumedBonemeals: + case spentGold: + requirementObj = null; + requirementObjId = null; + requirementValue = addIntegerField(pane, "Quantity: ", requirement.required_value, false, writable, listener); + break; + case hasActorCondition: + requirementObj = addActorConditionBox(pane, project, "Actor Condition: ", (ActorCondition) requirement.required_obj, writable, listener); + requirementObjId = null; + requirementValue = null; + break; + case inventoryKeep: + case inventoryRemove: + case usedItem: + requirementObj = addItemBox(pane, project, "Item: ", (Item) requirement.required_obj, writable, listener); + requirementObjId = null; + requirementValue = addIntegerField(pane, "Quantity: ", requirement.required_value, false, writable, listener); + break; + case killedMonster: + requirementObj = addNPCBox(pane, project, "Monster: ", (NPC) requirement.required_obj, writable, listener); + requirementObjId = null; + requirementValue = addIntegerField(pane, "Quantity: ", requirement.required_value, false, writable, listener); + break; + case questLatestProgress: + case questProgress: + requirementObj = addQuestBox(pane, project, "Quest: ", (Quest) requirement.required_obj, writable, listener); + requirementObjId = null; + requirementValue = addIntegerField(pane, "Quest stage: ", requirement.required_value, false, writable, listener); + break; + case skillLevel: + requirementObj = null; + requirementObjId = addTextField(pane, "Skill ID:", requirement.required_obj_id, writable, listener); + requirementValue = addIntegerField(pane, "Level: ", requirement.required_value, false, writable, listener); + break; + case timerElapsed: + requirementObj = null; + requirementObjId = addTextField(pane, "Timer ID:", requirement.required_obj_id, writable, listener); + requirementValue = addIntegerField(pane, "Timer value: ", requirement.required_value, false, writable, listener); + break; + case wear: + requirementObj = addItemBox(pane, project, "Item: ", (Item) requirement.required_obj, writable, listener); + requirementObjId = null; + requirementValue = null; + break; + } + requirementNegated = addBooleanBasedCheckBox(pane, "Negate this requirement.", requirement.negated, writable, listener); + } + pane.revalidate(); + pane.repaint(); + } + + + public static class RewardsListModel implements ListModel { + + Dialogue source; + + public RewardsListModel(Dialogue dialogue) { + this.source = dialogue; + } + + @Override + public int getSize() { + if (source.rewards == null) return 0; + return source.rewards.size(); + } + + @Override + public Object getElementAt(int index) { + if (source.rewards == null) return null; + return source.rewards.get(index); + } + + public void addItem(Dialogue.Reward item) { + if (source.rewards == null) { + source.rewards = new ArrayList(); + } + source.rewards.add(item); + int index = source.rewards.indexOf(item); + for (ListDataListener l : listeners) { + l.intervalAdded(new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index)); + } + } + + public void removeItem(Dialogue.Reward item) { + int index = source.rewards.indexOf(item); + source.rewards.remove(item); + if (source.rewards.isEmpty()) { + source.rewards = null; + } + for (ListDataListener l : listeners) { + l.intervalRemoved(new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index)); + } + } + + public void itemChanged(Dialogue.Reward item) { + int index = source.rewards.indexOf(item); + for (ListDataListener l : listeners) { + l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index, index)); + } + } + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + } + + public static class RewardsCellRenderer extends DefaultListCellRenderer { + private static final long serialVersionUID = 7987880146189575234L; + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (c instanceof JLabel) { + JLabel label = ((JLabel)c); + Dialogue.Reward reward = (Dialogue.Reward)value; + + if (reward.type != null) { + String rewardObjDesc = null; + if( reward.reward_obj != null) { + rewardObjDesc = reward.reward_obj.getDesc(); + } else if (reward.reward_obj_id != null) { + rewardObjDesc = reward.reward_obj_id; + } + switch (reward.type) { + case activateMapChangeArea: + label.setText("Activate mapchange area "+rewardObjDesc+" on map "+reward.map_name); + break; + case actorCondition: + label.setText("Give actor condition "+rewardObjDesc+" for "+reward.reward_value+" turns"); + if (reward.reward_obj != null) label.setIcon(new ImageIcon(reward.reward_obj.getIcon())); + break; + case alignmentChange: + label.setText("Change alignment for faction "+rewardObjDesc+" : "+reward.reward_value); + break; + case createTimer: + label.setText("Create timer "+rewardObjDesc); + break; + case deactivateMapChangeArea: + label.setText("Deactivate mapchange area "+rewardObjDesc+" on map "+reward.map_name); + break; + case deactivateSpawnArea: + label.setText("Deactivate spawnarea area "+rewardObjDesc+" on map "+reward.map_name); + break; + case dropList: + label.setText("Give contents of droplist "+rewardObjDesc); + if (reward.reward_obj != null) label.setIcon(new ImageIcon(reward.reward_obj.getIcon())); + break; + case giveItem: + label.setText("Give "+reward.reward_value+" "+rewardObjDesc); + if (reward.reward_obj != null) label.setIcon(new ImageIcon(reward.reward_obj.getIcon())); + break; + case questProgress: + label.setText("Give quest progress "+rewardObjDesc+":"+reward.reward_value); + if (reward.reward_obj != null) label.setIcon(new ImageIcon(reward.reward_obj.getIcon())); + break; + case removeSpawnArea: + label.setText("Remove all monsters in spawnarea area "+rewardObjDesc+" on map "+reward.map_name); + break; + case skillIncrease: + label.setText("Increase skill "+rewardObjDesc+" level"); + break; + case spawnAll: + label.setText("Respawn all monsters in spawnarea area "+rewardObjDesc+" on map "+reward.map_name); + break; + } + } else { + label.setText("New, undefined reward"); + } + } + return c; + } + } + + + public static class RepliesListModel implements ListModel { + + Dialogue source; + + public RepliesListModel(Dialogue dialogue) { + this.source = dialogue; + } + + + @Override + public int getSize() { + if (source.replies == null) return 0; + return source.replies.size(); + } + + @Override + public Object getElementAt(int index) { + if (source.replies == null) return null; + return source.replies.get(index); + } + + public void addItem(Dialogue.Reply item) { + if (source.replies == null) { + source.replies = new ArrayList(); + } + source.replies.add(item); + int index = source.replies.indexOf(item); + for (ListDataListener l : listeners) { + l.intervalAdded(new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index)); + } + } + + public void removeItem(Dialogue.Reply item) { + int index = source.replies.indexOf(item); + source.replies.remove(item); + if (source.replies.isEmpty()) { + source.replies = null; + } + for (ListDataListener l : listeners) { + l.intervalRemoved(new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index)); + } + } + + public void itemChanged(Dialogue.Reply item) { + int index = source.replies.indexOf(item); + for (ListDataListener l : listeners) { + l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index, index)); + } + } + + public void moveUp(Dialogue.Reply item) { + int index = source.replies.indexOf(item); + Dialogue.Reply exchanged = source.replies.get(index - 1); + source.replies.set(index, exchanged); + source.replies.set(index - 1, item); + for (ListDataListener l : listeners) { + l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index - 1, index)); + } + } + + public void moveDown(Dialogue.Reply item) { + int index = source.replies.indexOf(item); + Dialogue.Reply exchanged = source.replies.get(index + 1); + source.replies.set(index, exchanged); + source.replies.set(index + 1, item); + for (ListDataListener l : listeners) { + l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index, index + 1)); + } + } + + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + } + + public static class RepliesCellRenderer extends DefaultListCellRenderer { + private static final long serialVersionUID = 7987880146189575234L; + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (c instanceof JLabel) { + JLabel label = ((JLabel)c); + Dialogue.Reply reply = (Dialogue.Reply)value; + StringBuffer buf = new StringBuffer(); + if (reply.requirements != null) { + buf.append("[Reqs]"); + } + if (reply.next_phrase_id != null && reply.next_phrase_id.equals(Dialogue.Reply.EXIT_PHRASE_ID)) { + buf.append("[Ends dialogue] "); + buf.append((reply.text != null ? reply.text : "")); + label.setIcon(new ImageIcon(DefaultIcons.getNullifyIcon())); + } else if (reply.next_phrase_id != null && reply.next_phrase_id.equals(Dialogue.Reply.FIGHT_PHRASE_ID)) { + buf.append("[Starts fight] "); + buf.append((reply.text != null ? reply.text : "")); + label.setIcon(new ImageIcon(DefaultIcons.getCombatIcon())); + } else if (reply.next_phrase_id != null && reply.next_phrase_id.equals(Dialogue.Reply.REMOVE_PHRASE_ID)) { + buf.append("[NPC vanishes] "); + buf.append((reply.text != null ? reply.text : "")); + label.setIcon(new ImageIcon(DefaultIcons.getNPCCloseIcon())); + } else if (reply.next_phrase_id != null && reply.next_phrase_id.equals(Dialogue.Reply.SHOP_PHRASE_ID)) { + buf.append("[Start trading] "); + buf.append((reply.text != null ? reply.text : "")); + label.setIcon(new ImageIcon(DefaultIcons.getGoldIcon())); + } else if (reply.text != null && reply.text.equals(Dialogue.Reply.GO_NEXT_TEXT)) { + buf.append("[NPC keeps talking] "); + buf.append(reply.next_phrase_id); + label.setIcon(new ImageIcon(DefaultIcons.getArrowRightIcon())); + } else if (reply.next_phrase_id != null) { + buf.append("[Dialogue goes on] "); + buf.append((reply.text != null ? reply.text : "")); + buf.append(" -> "); + buf.append(reply.next_phrase_id); + label.setIcon(new ImageIcon(DefaultIcons.getDialogueIcon())); + } else if (reply.next_phrase == null && reply.next_phrase_id == null && reply.requirements == null && reply.text == null) { + buf.append("New, undefined reply"); + } else { + buf.append("[Incomplete reply]"); + } + label.setText(buf.toString()); + } + return c; + } + } + + public static class ReplyRequirementsListModel implements ListModel { + + Dialogue.Reply reply; + + public ReplyRequirementsListModel(Dialogue.Reply reply) { + this.reply = reply; + } + + @Override + public int getSize() { + if (reply.requirements == null) return 0; + return reply.requirements.size(); + } + + @Override + public Object getElementAt(int index) { + if (reply.requirements == null) return null; + return reply.requirements.get(index); + } + + + + public void addItem(Requirement item) { + if (reply.requirements == null) { + reply.requirements = new ArrayList(); + } + reply.requirements.add(item); + int index = reply.requirements.indexOf(item); + for (ListDataListener l : listeners) { + l.intervalAdded(new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index)); + } + } + + public void removeItem(Requirement item) { + int index = reply.requirements.indexOf(item); + reply.requirements.remove(item); + if (reply.requirements.isEmpty()) { + reply.requirements = null; + } + for (ListDataListener l : listeners) { + l.intervalRemoved(new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index)); + } + } + + public void itemChanged(Requirement item) { + int index = reply.requirements.indexOf(item); + for (ListDataListener l : listeners) { + l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index, index)); + } + } + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + + } + + public static class ReplyRequirementsCellRenderer extends DefaultListCellRenderer { + private static final long serialVersionUID = 7987880146189575234L; + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (c instanceof JLabel) { + ((JLabel)c).setText(((Requirement)value).getDesc()); + if (((Requirement)value).required_obj != null) { + if (((Requirement)value).required_obj.getIcon() != null) { + ((JLabel)c).setIcon(new ImageIcon(((Requirement)value).required_obj.getIcon())); + } + } if (((Requirement)value).type == null) { + ((JLabel)c).setText("New, undefined requirement."); + } + } + return c; + } + } + + public class DialogueFieldUpdater implements FieldUpdateListener { + @Override + public void valueChanged(JComponent source, Object value) { + Dialogue dialogue = (Dialogue) target; + if (source == idField) { + dialogue.id = (String) value; + DialogueEditor.this.name = dialogue.getDesc(); + dialogue.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(DialogueEditor.this); + } else if (source == messageField) { + dialogue.message = (String) value; + } else if (source == switchToNpcBox) { + if (dialogue.switch_to_npc != null) { + dialogue.switch_to_npc.removeBacklink(dialogue); + } + dialogue.switch_to_npc = (NPC) value; + if (dialogue.switch_to_npc != null) { + dialogue.switch_to_npc_id = dialogue.switch_to_npc.id; + dialogue.switch_to_npc.addBacklink(dialogue); + } else { + dialogue.switch_to_npc_id = null; + } + } else if (source == rewardTypeCombo) { + if (selectedReward.type != value) { + selectedReward.type = (Dialogue.Reward.RewardType) value; + selectedReward.map_name = null; + selectedReward.reward_obj = null; + selectedReward.reward_obj_id = null; + selectedReward.reward_value = null; + rewardsListModel.itemChanged(selectedReward); + updateRewardsParamsEditorPane(rewardsParamsPane, selectedReward, this); + } + } else if (source == rewardMap) { + if (selectedReward.map != null) { + selectedReward.map.removeBacklink(dialogue); + } + selectedReward.map = (TMXMap) value; + if (selectedReward.map != null) { + selectedReward.map_name = selectedReward.map.id; + selectedReward.map.addBacklink(dialogue); + } else { + selectedReward.map_name = null; + } + rewardsListModel.itemChanged(selectedReward); + } else if (source == rewardObjId) { + selectedReward.reward_obj_id = rewardObjId.getText(); + rewardsListModel.itemChanged(selectedReward); + } else if (source == rewardObj) { + if (selectedReward.reward_obj != null) { + selectedReward.reward_obj.removeBacklink(dialogue); + } + selectedReward.reward_obj = (GameDataElement) value; + if (selectedReward.reward_obj != null) { + selectedReward.reward_obj_id = selectedReward.reward_obj.id; + selectedReward.reward_obj.addBacklink(dialogue); + } else { + selectedReward.reward_obj_id = null; + } + rewardsListModel.itemChanged(selectedReward); + } else if (source == rewardValue) { + selectedReward.reward_value = (Integer) value; + rewardsListModel.itemChanged(selectedReward); + } else if (source == replyTypeCombo) { + updateRepliesParamsEditorPane(repliesParamsPane, selectedReply, this); + repliesListModel.itemChanged(selectedReply); + } else if (source == replyText) { + selectedReply.text = (String) value; + repliesListModel.itemChanged(selectedReply); + } else if (source == replyNextPhrase) { + if (selectedReply.next_phrase != null) { + selectedReply.next_phrase.removeBacklink(dialogue); + } + selectedReply.next_phrase = (Dialogue) value; + if (selectedReply.next_phrase != null) { + selectedReply.next_phrase_id = selectedReply.next_phrase.id; + selectedReply.next_phrase.addBacklink(dialogue); + } else { + selectedReply.next_phrase_id = null; + } + repliesListModel.itemChanged(selectedReply); + } else if (source == requirementTypeCombo) { + selectedRequirement.changeType((Requirement.RequirementType)requirementTypeCombo.getSelectedItem()); + updateRequirementParamsEditorPane(requirementParamsPane, selectedRequirement, this); + requirementsListModel.itemChanged(selectedRequirement); + } else if (source == requirementObj) { + if (selectedRequirement.required_obj != null) { + selectedRequirement.required_obj.removeBacklink(dialogue); + } + selectedRequirement.required_obj = (GameDataElement) value; + if (selectedRequirement.required_obj != null) { + selectedRequirement.required_obj_id = selectedRequirement.required_obj.id; + selectedRequirement.required_obj.addBacklink(dialogue); + } else { + selectedRequirement.required_obj_id = null; + } + requirementsListModel.itemChanged(selectedRequirement); + } else if (source == requirementObjId) { + selectedRequirement.required_obj_id = (String) value; + selectedRequirement.required_obj = null; + requirementsListModel.itemChanged(selectedRequirement); + } else if (source == requirementValue) { + selectedRequirement.required_value = (Integer) value; + requirementsListModel.itemChanged(selectedRequirement); + } else if (source == requirementNegated) { + selectedRequirement.negated = (Boolean) value; + } + + if (dialogue.state != GameDataElement.State.modified) { + dialogue.state = GameDataElement.State.modified; + DialogueEditor.this.name = dialogue.getDesc(); + dialogue.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(DialogueEditor.this); + } + updateJsonViewText(dialogue.toJsonString()); + } + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/DroplistEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/DroplistEditor.java new file mode 100644 index 0000000..40e774f --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/DroplistEditor.java @@ -0,0 +1,285 @@ +package com.gpl.rpg.atcontentstudio.ui.gamedataeditors; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.JTextField; +import javax.swing.ListModel; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.gamedata.Droplist; +import com.gpl.rpg.atcontentstudio.model.gamedata.Droplist.DroppedItem; +import com.gpl.rpg.atcontentstudio.model.gamedata.Item; +import com.gpl.rpg.atcontentstudio.ui.CollapsiblePanel; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; +import com.gpl.rpg.atcontentstudio.ui.FieldUpdateListener; +import com.jidesoft.swing.JideBoxLayout; + +public class DroplistEditor extends JSONElementEditor { + + private static final long serialVersionUID = 1139455254096811058L; + + private static final String form_view_id = "Form"; + private static final String json_view_id = "JSON"; + + private Droplist.DroppedItem selectedItem; + + private JTextField idField; + private MyComboBox itemCombo; + private DroppedItemsListModel droppedItemsListModel; + private JSpinner qtyMinField; + private JSpinner qtyMaxField; + private JSpinner chanceField; + + public DroplistEditor(Droplist droplist) { + super(droplist, droplist.getDesc(), droplist.getIcon()); + addEditorTab(form_view_id, getFormView()); + addEditorTab(json_view_id, getJSONView()); + } + + @Override + public void insertFormViewDataField(JPanel pane) { + + final Droplist droplist = (Droplist)target; + final FieldUpdateListener listener = new DroplistFieldUpdater(); + + createButtonPane(pane, droplist.getProject(), droplist, Droplist.class, Droplist.getImage(), null, listener); + + idField = addTextField(pane, "Droplist ID: ", droplist.id, droplist.writable, listener); + + CollapsiblePanel itemsPane = new CollapsiblePanel("Items in this droplist: "); + itemsPane.setLayout(new JideBoxLayout(itemsPane, JideBoxLayout.PAGE_AXIS)); + droppedItemsListModel = new DroppedItemsListModel(droplist); + final JList itemsList = new JList(droppedItemsListModel); + itemsList.setCellRenderer(new DroppedItemsCellRenderer()); + itemsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + itemsPane.add(new JScrollPane(itemsList), JideBoxLayout.FIX); + final JPanel droppedItemsEditorPane = new JPanel(); + final JButton createDroppedItem = new JButton(new ImageIcon(DefaultIcons.getCreateIcon())); + final JButton deleteDroppedItem = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + deleteDroppedItem.setEnabled(false); + itemsList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + selectedItem = (Droplist.DroppedItem) itemsList.getSelectedValue(); + if (selectedItem == null) { + deleteDroppedItem.setEnabled(false); + } else { + deleteDroppedItem.setEnabled(true); + } + updateDroppedItemsEditorPane(droppedItemsEditorPane, selectedItem, listener); + } + }); + if (droplist.writable) { + JPanel listButtonsPane = new JPanel(); + listButtonsPane.setLayout(new JideBoxLayout(listButtonsPane, JideBoxLayout.LINE_AXIS, 6)); + createDroppedItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Droplist.DroppedItem tempItem = new Droplist.DroppedItem(); + droppedItemsListModel.addItem(tempItem); + itemsList.setSelectedValue(tempItem, true); + listener.valueChanged(new JLabel(), null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + }); + deleteDroppedItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (selectedItem != null) { + droppedItemsListModel.removeItem(selectedItem); + selectedItem = null; + itemsList.clearSelection(); + listener.valueChanged(new JLabel(), null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + } + }); + + listButtonsPane.add(createDroppedItem, JideBoxLayout.FIX); + listButtonsPane.add(deleteDroppedItem, JideBoxLayout.FIX); + listButtonsPane.add(new JPanel(), JideBoxLayout.VARY); + itemsPane.add(listButtonsPane, JideBoxLayout.FIX); + } + droppedItemsEditorPane.setLayout(new JideBoxLayout(droppedItemsEditorPane, JideBoxLayout.PAGE_AXIS)); + itemsPane.add(droppedItemsEditorPane, JideBoxLayout.FIX); + if (droplist.dropped_items == null || droplist.dropped_items.isEmpty()) { + itemsPane.collapse(); + } + + pane.add(itemsPane, JideBoxLayout.FIX); + + } + + public void updateDroppedItemsEditorPane(JPanel pane, DroppedItem di, FieldUpdateListener listener) { + boolean writable = ((Droplist)target).writable; + Project proj = ((Droplist)target).getProject(); + pane.removeAll(); + if (itemCombo != null) { + removeElementListener(itemCombo); + } + if (di != null) { + itemCombo = addItemBox(pane, proj, "Item: ", di.item, writable, listener); + qtyMinField = addIntegerField(pane, "Quantity min: ", di.quantity_min, false, writable, listener); + qtyMaxField = addIntegerField(pane, "Quantity max: ", di.quantity_max, false, writable, listener); + chanceField = addDoubleField(pane, "Chance: ", di.chance, writable, listener); + } + pane.revalidate(); + pane.repaint(); + } + + public class DroppedItemsListModel implements ListModel { + + Droplist source; + + public DroppedItemsListModel(Droplist droplist) { + this.source = droplist; + } + + @Override + public int getSize() { + if (source.dropped_items == null) return 0; + return source.dropped_items.size(); + } + + @Override + public Object getElementAt(int index) { + if (source.dropped_items == null) return null; + return source.dropped_items.get(index); + } + + public void addItem(Droplist.DroppedItem item) { + if (source.dropped_items == null) { + source.dropped_items = new ArrayList(); + } + source.dropped_items.add(item); + int index = source.dropped_items.indexOf(item); + for (ListDataListener l : listeners) { + l.intervalAdded(new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index)); + } + } + + public void removeItem(Droplist.DroppedItem item) { + int index = source.dropped_items.indexOf(item); + source.dropped_items.remove(item); + if (source.dropped_items.isEmpty()) { + source.dropped_items = null; + } + for (ListDataListener l : listeners) { + l.intervalRemoved(new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index)); + } + } + + public void itemChanged(Droplist.DroppedItem item) { + int index = source.dropped_items.indexOf(item); + for (ListDataListener l : listeners) { + l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index, index)); + } + } + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + } + + public static class DroppedItemsCellRenderer extends DefaultListCellRenderer { + private static final long serialVersionUID = 7987880146189575234L; + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (c instanceof JLabel) { + JLabel label = ((JLabel)c); + Droplist.DroppedItem di = (Droplist.DroppedItem)value; + if (di.item != null) { + label.setIcon(new ImageIcon(di.item.getIcon())); + label.setText(di.chance+"% to get "+di.quantity_min+"-"+di.quantity_max+" "+di.item.getDesc()); + } else if (!isNull(di)) { + label.setText(di.chance+"% to get "+di.quantity_min+"-"+di.quantity_max+" "+di.item_id); + } else { + label.setText("New, undefined, dropped item."); + } + } + return c; + } + + public boolean isNull(Droplist.DroppedItem item) { + return ((item == null) || ( + item.item == null && + item.item_id == null && + item.quantity_min == null && + item.quantity_max == null && + item.chance == null + )); + } + } + + + public class DroplistFieldUpdater implements FieldUpdateListener { + @Override + public void valueChanged(JComponent source, Object value) { + Droplist droplist = ((Droplist)target); + if (source == idField) { + droplist.id = (String) value; + DroplistEditor.this.name = droplist.getDesc(); + droplist.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(DroplistEditor.this); + } else if (source == itemCombo) { + if (selectedItem.item != null) { + selectedItem.item.removeBacklink(droplist); + } + selectedItem.item = (Item) value; + if (selectedItem.item != null) { + selectedItem.item_id = selectedItem.item.id; + selectedItem.item.addBacklink(droplist); + } else { + selectedItem.item_id = null; + } + droppedItemsListModel.itemChanged(selectedItem); + } else if (source == qtyMinField) { + selectedItem.quantity_min = (Integer) value; + droppedItemsListModel.itemChanged(selectedItem); + } else if (source == qtyMaxField) { + selectedItem.quantity_max = (Integer) value; + droppedItemsListModel.itemChanged(selectedItem); + } else if (source == chanceField) { + selectedItem.chance = (Double) value; + droppedItemsListModel.itemChanged(selectedItem); + } + + if (droplist.state != GameDataElement.State.modified) { + droplist.state = GameDataElement.State.modified; + DroplistEditor.this.name = droplist.getDesc(); + droplist.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(DroplistEditor.this); + } + updateJsonViewText(droplist.toJsonString()); + } + } +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/ItemCategoryEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/ItemCategoryEditor.java new file mode 100644 index 0000000..40fd9cc --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/ItemCategoryEditor.java @@ -0,0 +1,128 @@ +package com.gpl.rpg.atcontentstudio.ui.gamedataeditors; + +import java.awt.Component; +import java.util.ArrayList; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.gamedata.ItemCategory; +import com.gpl.rpg.atcontentstudio.ui.FieldUpdateListener; + +public class ItemCategoryEditor extends JSONElementEditor { + + private static final long serialVersionUID = -2893876158803488355L; + + private static final String form_view_id = "Form"; + private static final String json_view_id = "JSON"; + + public ItemCategoryEditor(ItemCategory ic) { + super(ic, ic.getDesc(), ic.getIcon()); + addEditorTab(form_view_id, getFormView()); + addEditorTab(json_view_id, getJSONView()); + } + + private JButton icIcon; + private JTextField idField; + private JTextField nameField; + private JComboBox slotBox; + private JComboBox typeBox; + private JComboBox sizeBox; + + @Override + public void insertFormViewDataField(JPanel pane) { + final ItemCategory ic = ((ItemCategory)target); + final FieldUpdateListener listener = new ItemCategoryFieldUpdater(); + + icIcon = createButtonPane(pane, ic.getProject(), ic, ItemCategory.class, ic.getImage(), null, listener); + + + idField = addTextField(pane, "Internal ID: ", ic.id, ic.writable, listener); + nameField = addTextField(pane, "Display name: ", ic.name, ic.writable, listener); + typeBox = addEnumValueBox(pane, "Action type: ", ItemCategory.ActionType.values(), ic.action_type, ic.writable, listener); + slotBox = addEnumValueBox(pane, "Inventory slot: ", ItemCategory.InventorySlot.values(), ic.slot, ic.writable, listener); + sizeBox = addEnumValueBox(pane, "Item size: ", ItemCategory.Size.values(), ic.size, ic.writable, listener); + if (ic.action_type != ItemCategory.ActionType.equip) { + slotBox.setEnabled(false); + } + slotBox.setRenderer(new SlotCellRenderer()); + } + + public class SlotCellRenderer extends DefaultListCellRenderer { + private static final long serialVersionUID = -8359181274986492979L; + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (c instanceof JLabel) { + ((JLabel)c).setIcon(new ImageIcon(ItemCategory.getIcon((ItemCategory.InventorySlot) value))); + if (value == null) { + if (typeBox.getSelectedItem() == ItemCategory.ActionType.equip) { + ((JLabel)c).setText("Undefined. Select the slot to use when equipped."); + } else { + ((JLabel)c).setText("Non equippable. Select \"equip\" action type."); + } + } + } + return c; + } + } + + public class ItemCategoryFieldUpdater implements FieldUpdateListener { + @Override + public void valueChanged(JComponent source, Object value) { + ItemCategory ic = (ItemCategory)target; + if (source == idField) { + ic.id = (String) value; + ItemCategoryEditor.this.name = ic.getDesc(); + ic.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(ItemCategoryEditor.this); + } else if (source == nameField) { + ic.name = (String) value; + ItemCategoryEditor.this.name = ic.getDesc(); + ic.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(ItemCategoryEditor.this); + } else if (source == slotBox) { + ic.slot = (ItemCategory.InventorySlot) value; + icIcon.setIcon(new ImageIcon(ic.getImage())); + ItemCategoryEditor.this.icon = new ImageIcon(ic.getIcon()); + ic.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(ItemCategoryEditor.this); + } else if (source == typeBox) { + ic.action_type = (ItemCategory.ActionType) value; + if (ic.action_type != ItemCategory.ActionType.equip && ic.slot != null) { + ic.slot = null; + slotBox.setSelectedItem(null); + slotBox.setEnabled(false); + icIcon.setIcon(new ImageIcon(ic.getImage())); + ItemCategoryEditor.this.icon = new ImageIcon(ic.getIcon()); + ic.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(ItemCategoryEditor.this); + } else if (ic.action_type == ItemCategory.ActionType.equip) { + slotBox.setEnabled(true); + } + } else if (source == sizeBox) { + ic.size = (ItemCategory.Size) value; + } + + if (ic.state != GameDataElement.State.modified) { + ic.state = GameDataElement.State.modified; + ItemCategoryEditor.this.name = ic.getDesc(); + ic.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(ItemCategoryEditor.this); + } + updateJsonViewText(ic.toJsonString()); + } + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/ItemEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/ItemEditor.java new file mode 100644 index 0000000..35b6885 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/ItemEditor.java @@ -0,0 +1,1102 @@ +package com.gpl.rpg.atcontentstudio.ui.gamedataeditors; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.JTextField; +import javax.swing.ListModel; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.gamedata.ActorCondition; +import com.gpl.rpg.atcontentstudio.model.gamedata.Item; +import com.gpl.rpg.atcontentstudio.model.gamedata.ItemCategory; +import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet; +import com.gpl.rpg.atcontentstudio.ui.CollapsiblePanel; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; +import com.gpl.rpg.atcontentstudio.ui.FieldUpdateListener; +import com.gpl.rpg.atcontentstudio.ui.IntegerBasedCheckBox; +import com.jidesoft.swing.JideBoxLayout; + +public class ItemEditor extends JSONElementEditor { + + private static final long serialVersionUID = 7538154592029351986L; + + private static final String form_view_id = "Form"; + private static final String json_view_id = "JSON"; + + private static final String killLabel = "Effect on every kill: "; + private static final String useLabel = "Effect on use: "; + + + private Item.ConditionEffect selectedEquipEffectCondition; + private Item.TimedConditionEffect selectedHitEffectSourceCondition; + private Item.TimedConditionEffect selectedHitEffectTargetCondition; + private Item.TimedConditionEffect selectedKillEffectCondition; + + + private JButton itemIcon; + private JTextField idField; + private JTextField nameField; + private JTextField descriptionField; + private JComboBox typeBox; + private IntegerBasedCheckBox manualPriceBox; + private JSpinner baseCostField; + private MyComboBox categoryBox; + private Integer baseManualPrice = null; + + private CollapsiblePanel equipEffectPane; + private Item.EquipEffect equipEffect; + private JSpinner equipDmgMin; + private JSpinner equipDmgMax; + private JSpinner equipBoostHP; + private JSpinner equipBoostAP; + private JSpinner equipBoostAC; + private JSpinner equipBoostBC; + private JSpinner equipBoostCS; + private JSpinner equipSetCM; + private JSpinner equipBoostDR; + private JSpinner equipIncMoveCost; + private JSpinner equipIncUseCost; + private JSpinner equipIncReequipCost; + private JSpinner equipIncAttackCost; + private ConditionsListModel equipConditionsModel; + private JList equipConditionsList; + private MyComboBox equipConditionBox; + private JSpinner equipConditionMagnitude; + + private CollapsiblePanel hitEffectPane; + private Item.HitEffect hitEffect; + private JSpinner hitHPMin; + private JSpinner hitHPMax; + private JSpinner hitAPMin; + private JSpinner hitAPMax; + private SourceTimedConditionsListModel hitSourceConditionsModel; + private JList hitSourceConditionsList; + private MyComboBox hitSourceConditionBox; + private JSpinner hitSourceConditionMagnitude; + private JSpinner hitSourceConditionDuration; + private JSpinner hitSourceConditionChance; + private TargetTimedConditionsListModel hitTargetConditionsModel; + private JList hitTargetConditionsList; + private MyComboBox hitTargetConditionBox; + private JSpinner hitTargetConditionMagnitude; + private JSpinner hitTargetConditionDuration; + private JSpinner hitTargetConditionChance; + + private CollapsiblePanel killEffectPane; + private Item.KillEffect killEffect; + private JSpinner killHPMin; + private JSpinner killHPMax; + private JSpinner killAPMin; + private JSpinner killAPMax; + private SourceTimedConditionsListModel killSourceConditionsModel; + private JList killSourceConditionsList; + private MyComboBox killSourceConditionBox; + private JSpinner killSourceConditionMagnitude; + private JSpinner killSourceConditionDuration; + private JSpinner killSourceConditionChance; + + + public ItemEditor(Item item) { + super(item, item.getDesc(), item.getIcon()); + addEditorTab(form_view_id, getFormView()); + addEditorTab(json_view_id, getJSONView()); + } + + @Override + public void insertFormViewDataField(JPanel pane) { + + final Item item = (Item) target; + + final FieldUpdateListener listener = new ItemFieldUpdater(); + + itemIcon = createButtonPane(pane, item.getProject(), item, Item.class, item.getImage(), Spritesheet.Category.item, listener); + + idField = addTextField(pane, "Internal ID: ", item.id, item.writable, listener); + nameField = addTextField(pane, "Display name: ", item.name, item.writable, listener); + descriptionField = addTextField(pane, "Description: ", item.description, item.writable, listener); + typeBox = addEnumValueBox(pane, "Type: ", Item.DisplayType.values(), item.display_type, item.writable, listener); + manualPriceBox = addIntegerBasedCheckBox(pane, "Has manual price", item.has_manual_price, item.writable, listener); + baseManualPrice = item.base_market_cost; + baseCostField = addIntegerField(pane, "Base market cost: ", (item.has_manual_price != null && item.has_manual_price == 1) ? item.base_market_cost : item.computePrice(), false, item.writable, listener); + if (!manualPriceBox.isSelected()) { + baseCostField.setEnabled(false); + } + categoryBox = addItemCategoryBox(pane, item.getProject(), "Category: ", item.category, item.writable, listener); + + equipEffectPane = new CollapsiblePanel("Effect when equipped: "); + equipEffectPane.setLayout(new JideBoxLayout(equipEffectPane, JideBoxLayout.PAGE_AXIS)); + if (item.equip_effect == null) { + equipEffect = new Item.EquipEffect(); + } else { + equipEffect = item.equip_effect; + } + equipDmgMin = addIntegerField(equipEffectPane, "Attack Damage min: ", equipEffect.damage_boost_min, true, item.writable, listener); + equipDmgMax = addIntegerField(equipEffectPane, "Attack Damage max: ", equipEffect.damage_boost_max, true, item.writable, listener); + equipBoostHP = addIntegerField(equipEffectPane, "Boost max HP: ", equipEffect.max_hp_boost, true, item.writable, listener); + equipBoostAP = addIntegerField(equipEffectPane, "Boost max AP: ", equipEffect.max_ap_boost, true, item.writable, listener); + equipBoostAC = addIntegerField(equipEffectPane, "Boost attack chance: ", equipEffect.increase_attack_chance, true, item.writable, listener); + equipBoostBC = addIntegerField(equipEffectPane, "Boost block chance: ", equipEffect.increase_block_chance, true, item.writable, listener); + equipBoostCS = addIntegerField(equipEffectPane, "Boost critical skill: ", equipEffect.increase_critical_skill, true, item.writable, listener); + equipSetCM = addDoubleField(equipEffectPane, "Critical multiplier: ", equipEffect.critical_multiplier, item.writable, listener); + equipBoostDR = addIntegerField(equipEffectPane, "Boost damage resistance: ", equipEffect.increase_damage_resistance, true, item.writable, listener); + equipIncMoveCost = addIntegerField(equipEffectPane, "Increase move cost: ", equipEffect.increase_move_cost, true, item.writable, listener); + equipIncUseCost = addIntegerField(equipEffectPane, "Increase item use cost: ", equipEffect.increase_use_item_cost, true, item.writable, listener); + equipIncReequipCost = addIntegerField(equipEffectPane, "Increase reequip cost: ", equipEffect.increase_reequip_cost, true, item.writable, listener); + equipIncAttackCost = addIntegerField(equipEffectPane, "Increase attack cost: ", equipEffect.increase_attack_cost, true, item.writable, listener); + CollapsiblePanel equipConditionsPane = new CollapsiblePanel("Actor Conditions applied when equipped: "); + equipConditionsPane.setLayout(new JideBoxLayout(equipConditionsPane, JideBoxLayout.PAGE_AXIS)); + equipConditionsModel = new ConditionsListModel(equipEffect); + equipConditionsList = new JList(equipConditionsModel); + equipConditionsList.setCellRenderer(new ConditionsCellRenderer()); + equipConditionsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + equipConditionsPane.add(new JScrollPane(equipConditionsList), JideBoxLayout.FIX); + final JPanel equipConditionsEditorPane = new JPanel(); + final JButton createEquipCondition = new JButton(new ImageIcon(DefaultIcons.getCreateIcon())); + final JButton deleteEquipCondition = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + equipConditionsList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + selectedEquipEffectCondition = (Item.ConditionEffect) equipConditionsList.getSelectedValue(); + if (selectedEquipEffectCondition == null) { + deleteEquipCondition.setEnabled(false); + } else { + deleteEquipCondition.setEnabled(true); + } + updateEquipConditionEditorPane(equipConditionsEditorPane, selectedEquipEffectCondition, listener); + } + }); + if (item.writable) { + JPanel listButtonsPane = new JPanel(); + listButtonsPane.setLayout(new JideBoxLayout(listButtonsPane, JideBoxLayout.LINE_AXIS, 6)); + createEquipCondition.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Item.ConditionEffect condition = new Item.ConditionEffect(); + equipConditionsModel.addItem(condition); + equipConditionsList.setSelectedValue(condition, true); + listener.valueChanged(equipConditionsList, null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + }); + deleteEquipCondition.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (selectedEquipEffectCondition != null) { + equipConditionsModel.removeItem(selectedEquipEffectCondition); + selectedEquipEffectCondition = null; + equipConditionsList.clearSelection(); + listener.valueChanged(equipConditionsList, null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + } + }); + + listButtonsPane.add(createEquipCondition, JideBoxLayout.FIX); + listButtonsPane.add(deleteEquipCondition, JideBoxLayout.FIX); + listButtonsPane.add(new JPanel(), JideBoxLayout.VARY); + equipConditionsPane.add(listButtonsPane, JideBoxLayout.FIX); + } + equipConditionsEditorPane.setLayout(new JideBoxLayout(equipConditionsEditorPane, JideBoxLayout.PAGE_AXIS)); + equipConditionsPane.add(equipConditionsEditorPane, JideBoxLayout.FIX); + if (item.equip_effect == null || item.equip_effect.conditions == null || item.equip_effect.conditions.isEmpty()) { + equipConditionsPane.collapse(); + } + equipEffectPane.add(equipConditionsPane, JideBoxLayout.FIX); + pane.add(equipEffectPane, JideBoxLayout.FIX); + if (item.equip_effect == null) { + equipEffectPane.collapse(); + } + + hitEffectPane = new CollapsiblePanel("Effect on every hit: "); + hitEffectPane.setLayout(new JideBoxLayout(hitEffectPane, JideBoxLayout.PAGE_AXIS)); + if (item.hit_effect == null) { + hitEffect = new Item.HitEffect(); + } else { + hitEffect = item.hit_effect; + } + hitHPMin = addIntegerField(hitEffectPane, "HP bonus min: ", hitEffect.hp_boost_min, true, item.writable, listener); + hitHPMax = addIntegerField(hitEffectPane, "HP bonus max: ", hitEffect.hp_boost_max, true, item.writable, listener); + hitAPMin = addIntegerField(hitEffectPane, "AP bonus min: ", hitEffect.ap_boost_min, true, item.writable, listener); + hitAPMax = addIntegerField(hitEffectPane, "AP bonus max: ", hitEffect.ap_boost_max, true, item.writable, listener); + final CollapsiblePanel hitSourceConditionsPane = new CollapsiblePanel("Actor Conditions applied to the source: "); + hitSourceConditionsPane.setLayout(new JideBoxLayout(hitSourceConditionsPane, JideBoxLayout.PAGE_AXIS)); + hitSourceConditionsModel = new SourceTimedConditionsListModel(hitEffect); + hitSourceConditionsList = new JList(hitSourceConditionsModel); + hitSourceConditionsList.setCellRenderer(new TimedConditionsCellRenderer()); + hitSourceConditionsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + hitSourceConditionsPane.add(new JScrollPane(hitSourceConditionsList), JideBoxLayout.FIX); + final JPanel sourceTimedConditionsEditorPane = new JPanel(); + final JButton createHitSourceCondition = new JButton(new ImageIcon(DefaultIcons.getCreateIcon())); + final JButton deleteHitSourceCondition = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + hitSourceConditionsList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + selectedHitEffectSourceCondition = (Item.TimedConditionEffect) hitSourceConditionsList.getSelectedValue(); + updateHitSourceTimedConditionEditorPane(sourceTimedConditionsEditorPane, selectedHitEffectSourceCondition, listener); + if (selectedHitEffectSourceCondition == null) { + deleteHitSourceCondition.setEnabled(false); + } else { + deleteHitSourceCondition.setEnabled(true); + } + } + }); + if (item.writable) { + JPanel listButtonsPane = new JPanel(); + listButtonsPane.setLayout(new JideBoxLayout(listButtonsPane, JideBoxLayout.LINE_AXIS, 6)); + createHitSourceCondition.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Item.TimedConditionEffect condition = new Item.TimedConditionEffect(); + hitSourceConditionsModel.addItem(condition); + hitSourceConditionsList.setSelectedValue(condition, true); + listener.valueChanged(hitSourceConditionsList, null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + }); + deleteHitSourceCondition.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (selectedHitEffectSourceCondition != null) { + hitSourceConditionsModel.removeItem(selectedHitEffectSourceCondition); + selectedHitEffectSourceCondition = null; + hitSourceConditionsList.clearSelection(); + listener.valueChanged(hitSourceConditionsList, null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + } + }); + + listButtonsPane.add(createHitSourceCondition, JideBoxLayout.FIX); + listButtonsPane.add(deleteHitSourceCondition, JideBoxLayout.FIX); + listButtonsPane.add(new JPanel(), JideBoxLayout.VARY); + hitSourceConditionsPane.add(listButtonsPane, JideBoxLayout.FIX); + } + sourceTimedConditionsEditorPane.setLayout(new JideBoxLayout(sourceTimedConditionsEditorPane, JideBoxLayout.PAGE_AXIS)); + hitSourceConditionsPane.add(sourceTimedConditionsEditorPane, JideBoxLayout.FIX); + if (item.hit_effect == null || item.hit_effect.conditions_source == null || item.hit_effect.conditions_source.isEmpty()) { + hitSourceConditionsPane.collapse(); + } + hitEffectPane.add(hitSourceConditionsPane, JideBoxLayout.FIX); + final CollapsiblePanel hitTargetConditionsPane = new CollapsiblePanel("Actor Conditions applied to the target: "); + hitTargetConditionsPane.setLayout(new JideBoxLayout(hitTargetConditionsPane, JideBoxLayout.PAGE_AXIS)); + hitTargetConditionsModel = new TargetTimedConditionsListModel(hitEffect); + hitTargetConditionsList = new JList(hitTargetConditionsModel); + hitTargetConditionsList.setCellRenderer(new TimedConditionsCellRenderer()); + hitTargetConditionsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + hitTargetConditionsPane.add(new JScrollPane(hitTargetConditionsList), JideBoxLayout.FIX); + final JPanel targetTimedConditionsEditorPane = new JPanel(); + final JButton createHitTargetCondition = new JButton(new ImageIcon(DefaultIcons.getCreateIcon())); + final JButton deleteHitTargetCondition = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + hitTargetConditionsList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + selectedHitEffectTargetCondition = (Item.TimedConditionEffect) hitTargetConditionsList.getSelectedValue(); + updateHitTargetTimedConditionEditorPane(targetTimedConditionsEditorPane, selectedHitEffectTargetCondition, listener); + if (selectedHitEffectTargetCondition == null) { + deleteHitTargetCondition.setEnabled(false); + } else { + deleteHitTargetCondition.setEnabled(true); + } + } + }); + if (item.writable) { + JPanel listButtonsPane = new JPanel(); + listButtonsPane.setLayout(new JideBoxLayout(listButtonsPane, JideBoxLayout.LINE_AXIS, 6)); + createHitTargetCondition.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Item.TimedConditionEffect condition = new Item.TimedConditionEffect(); + hitTargetConditionsModel.addItem(condition); + hitTargetConditionsList.setSelectedValue(condition, true); + listener.valueChanged(hitTargetConditionsList, null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + }); + deleteHitTargetCondition.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (selectedHitEffectTargetCondition != null) { + hitTargetConditionsModel.removeItem(selectedHitEffectTargetCondition); + selectedHitEffectTargetCondition = null; + hitTargetConditionsList.clearSelection(); + listener.valueChanged(hitTargetConditionsList, null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + } + }); + + listButtonsPane.add(createHitTargetCondition, JideBoxLayout.FIX); + listButtonsPane.add(deleteHitTargetCondition, JideBoxLayout.FIX); + listButtonsPane.add(new JPanel(), JideBoxLayout.VARY); + hitTargetConditionsPane.add(listButtonsPane, JideBoxLayout.FIX); + } + targetTimedConditionsEditorPane.setLayout(new JideBoxLayout(targetTimedConditionsEditorPane, JideBoxLayout.PAGE_AXIS)); + hitTargetConditionsPane.add(targetTimedConditionsEditorPane, JideBoxLayout.FIX); + if (item.hit_effect == null || item.hit_effect.conditions_target == null || item.hit_effect.conditions_target.isEmpty()) { + hitTargetConditionsPane.collapse(); + } + hitEffectPane.add(hitTargetConditionsPane, JideBoxLayout.FIX); + if (item.hit_effect == null) { + hitEffectPane.collapse(); + } + pane.add(hitEffectPane, JideBoxLayout.FIX); + + + + killEffectPane = new CollapsiblePanel(killLabel); + killEffectPane.setLayout(new JideBoxLayout(killEffectPane, JideBoxLayout.PAGE_AXIS)); + if (item.kill_effect == null) { + killEffect = new Item.KillEffect(); + } else { + killEffect = item.kill_effect; + } + killHPMin = addIntegerField(killEffectPane, "HP bonus min: ", killEffect.hp_boost_min, true, item.writable, listener); + killHPMax = addIntegerField(killEffectPane, "HP bonus max: ", killEffect.hp_boost_max, true, item.writable, listener); + killAPMin = addIntegerField(killEffectPane, "AP bonus min: ", killEffect.ap_boost_min, true, item.writable, listener); + killAPMax = addIntegerField(killEffectPane, "AP bonus max: ", killEffect.ap_boost_max, true, item.writable, listener); + final CollapsiblePanel killSourceConditionsPane = new CollapsiblePanel("Actor Conditions applied to the source: "); + killSourceConditionsPane.setLayout(new JideBoxLayout(killSourceConditionsPane, JideBoxLayout.PAGE_AXIS)); + killSourceConditionsModel = new SourceTimedConditionsListModel(killEffect); + killSourceConditionsList = new JList(killSourceConditionsModel); + killSourceConditionsList.setCellRenderer(new TimedConditionsCellRenderer()); + killSourceConditionsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + killSourceConditionsPane.add(new JScrollPane(killSourceConditionsList), JideBoxLayout.FIX); + final JPanel killSourceTimedConditionsEditorPane = new JPanel(); + final JButton createKillSourceCondition = new JButton(new ImageIcon(DefaultIcons.getCreateIcon())); + final JButton deleteKillSourceCondition = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + killSourceConditionsList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + selectedKillEffectCondition = (Item.TimedConditionEffect) killSourceConditionsList.getSelectedValue(); + updateKillSourceTimedConditionEditorPane(killSourceTimedConditionsEditorPane, selectedKillEffectCondition, listener); + if (selectedKillEffectCondition == null) { + deleteKillSourceCondition.setEnabled(false); + } else { + deleteKillSourceCondition.setEnabled(true); + } + } + }); + if (item.writable) { + JPanel listButtonsPane = new JPanel(); + listButtonsPane.setLayout(new JideBoxLayout(listButtonsPane, JideBoxLayout.LINE_AXIS, 6)); + createKillSourceCondition.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Item.TimedConditionEffect condition = new Item.TimedConditionEffect(); + killSourceConditionsModel.addItem(condition); + killSourceConditionsList.setSelectedValue(condition, true); + listener.valueChanged(killSourceConditionsList, null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + }); + deleteKillSourceCondition.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (selectedKillEffectCondition != null) { + killSourceConditionsModel.removeItem(selectedKillEffectCondition); + selectedKillEffectCondition = null; + killSourceConditionsList.clearSelection(); + listener.valueChanged(killSourceConditionsList, null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + } + }); + + listButtonsPane.add(createKillSourceCondition, JideBoxLayout.FIX); + listButtonsPane.add(deleteKillSourceCondition, JideBoxLayout.FIX); + listButtonsPane.add(new JPanel(), JideBoxLayout.VARY); + killSourceConditionsPane.add(listButtonsPane, JideBoxLayout.FIX); + } + killSourceTimedConditionsEditorPane.setLayout(new JideBoxLayout(killSourceTimedConditionsEditorPane, JideBoxLayout.PAGE_AXIS)); + killSourceConditionsPane.add(killSourceTimedConditionsEditorPane, JideBoxLayout.FIX); + if (item.kill_effect == null || item.kill_effect.conditions_source == null || item.kill_effect.conditions_source.isEmpty()) { + killSourceConditionsPane.collapse(); + } + killEffectPane.add(killSourceConditionsPane, JideBoxLayout.FIX); + if (item.kill_effect == null) { + killEffectPane.collapse(); + } + pane.add(killEffectPane, JideBoxLayout.FIX); + + if (item.category == null || item.category.action_type == null || item.category.action_type == ItemCategory.ActionType.none) { + equipEffectPane.setVisible(false); + hitEffectPane.setVisible(false); + killEffectPane.setVisible(false); + } else if (item.category.action_type == ItemCategory.ActionType.use) { + equipEffectPane.setVisible(false); + hitEffectPane.setVisible(false); + killEffectPane.setVisible(true); + killEffectPane.setTitle(useLabel); + killEffectPane.revalidate(); + killEffectPane.repaint(); + } else if (item.category.action_type == ItemCategory.ActionType.equip) { + equipEffectPane.setVisible(true); + hitEffectPane.setVisible(true); + killEffectPane.setVisible(true); + killEffectPane.setTitle(killLabel); + killEffectPane.revalidate(); + killEffectPane.repaint(); + } + + } + + public void updateHitSourceTimedConditionEditorPane(JPanel pane, Item.TimedConditionEffect condition, FieldUpdateListener listener) { + pane.removeAll(); + if (hitSourceConditionBox != null) { + removeElementListener(hitSourceConditionBox); + } + boolean writable = ((Item)target).writable; + Project proj = ((Item)target).getProject(); + + hitSourceConditionBox = addActorConditionBox(pane, proj, "Actor Condition: ", condition.condition, writable, listener); + hitSourceConditionMagnitude = addIntegerField(pane, "Magnitude: ", condition.magnitude, false, writable, listener); + hitSourceConditionDuration = addIntegerField(pane, "Duration: ", condition.duration, false, writable, listener); + hitSourceConditionChance = addDoubleField(pane, "Chance: ", condition.chance, writable, listener); + + pane.revalidate(); + pane.repaint(); + } + + public void updateHitTargetTimedConditionEditorPane(JPanel pane, Item.TimedConditionEffect condition, FieldUpdateListener listener) { + pane.removeAll(); + if (hitTargetConditionBox != null) { + removeElementListener(hitTargetConditionBox); + } + + boolean writable = ((Item)target).writable; + Project proj = ((Item)target).getProject(); + + hitTargetConditionBox = addActorConditionBox(pane, proj, "Actor Condition: ", condition.condition, writable, listener); + hitTargetConditionMagnitude = addIntegerField(pane, "Magnitude: ", condition.magnitude, false, writable, listener); + hitTargetConditionDuration = addIntegerField(pane, "Duration: ", condition.duration, false, writable, listener); + hitTargetConditionChance = addDoubleField(pane, "Chance: ", condition.chance, writable, listener); + + pane.revalidate(); + pane.repaint(); + } + + public void updateKillSourceTimedConditionEditorPane(JPanel pane, Item.TimedConditionEffect condition, FieldUpdateListener listener) { + pane.removeAll(); + if (killSourceConditionBox != null) { + removeElementListener(killSourceConditionBox); + } + + boolean writable = ((Item)target).writable; + Project proj = ((Item)target).getProject(); + + killSourceConditionBox = addActorConditionBox(pane, proj, "Actor Condition: ", condition.condition, writable, listener); + killSourceConditionMagnitude = addIntegerField(pane, "Magnitude: ", condition.magnitude, false, writable, listener); + killSourceConditionDuration = addIntegerField(pane, "Duration: ", condition.duration, false, writable, listener); + killSourceConditionChance = addDoubleField(pane, "Chance: ", condition.chance, writable, listener); + + pane.revalidate(); + pane.repaint(); + } + + public void updateEquipConditionEditorPane(JPanel pane, Item.ConditionEffect condition, FieldUpdateListener listener) { + pane.removeAll(); + if (equipConditionBox != null) { + removeElementListener(equipConditionBox); + } + + boolean writable = ((Item)target).writable; + Project proj = ((Item)target).getProject(); + + equipConditionBox = addActorConditionBox(pane, proj, "Actor Condition: ", condition.condition, writable, listener); + equipConditionMagnitude = addIntegerField(pane, "Magnitude: ", condition.magnitude, false, writable, listener); + + pane.revalidate(); + pane.repaint(); + } + + public static class SourceTimedConditionsListModel implements ListModel { + + Item.KillEffect source; + + public SourceTimedConditionsListModel(Item.KillEffect effect) { + this.source = effect;; + } + + @Override + public int getSize() { + if (source.conditions_source == null) return 0; + return source.conditions_source.size(); + } + + @Override + public Object getElementAt(int index) { + if (source.conditions_source == null) return null; + return source.conditions_source.get(index); + } + + public void addItem(Item.TimedConditionEffect item) { + if (source.conditions_source == null) { + source.conditions_source = new ArrayList(); + } + source.conditions_source.add(item); + int index = source.conditions_source.indexOf(item); + for (ListDataListener l : listeners) { + l.intervalAdded(new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index)); + } + } + + public void removeItem(Item.TimedConditionEffect item) { + int index = source.conditions_source.indexOf(item); + source.conditions_source.remove(item); + if (source.conditions_source.isEmpty()) { + source.conditions_source = null; + } + for (ListDataListener l : listeners) { + l.intervalRemoved(new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index)); + } + } + + public void itemChanged(Item.TimedConditionEffect item) { + int index = source.conditions_source.indexOf(item); + for (ListDataListener l : listeners) { + l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index, index)); + } + } + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + } + + public static class TargetTimedConditionsListModel implements ListModel { + + Item.HitEffect source; + + public TargetTimedConditionsListModel(Item.HitEffect effect) { + this.source = effect;; + } + + @Override + public int getSize() { + if (source.conditions_target == null) return 0; + return source.conditions_target.size(); + } + + @Override + public Object getElementAt(int index) { + if (source.conditions_target == null) return null; + return source.conditions_target.get(index); + } + + public void addItem(Item.TimedConditionEffect item) { + if (source.conditions_target == null) { + source.conditions_target = new ArrayList(); + } + source.conditions_target.add(item); + int index = source.conditions_target.indexOf(item); + for (ListDataListener l : listeners) { + l.intervalAdded(new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index)); + } + } + + public void removeItem(Item.TimedConditionEffect item) { + int index = source.conditions_target.indexOf(item); + source.conditions_target.remove(item); + if (source.conditions_target.isEmpty()) { + source.conditions_target = null; + } + for (ListDataListener l : listeners) { + l.intervalRemoved(new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index)); + } + } + + public void itemChanged(Item.TimedConditionEffect item) { + int index = source.conditions_target.indexOf(item); + for (ListDataListener l : listeners) { + l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index, index)); + } + } + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + } + + public static class TimedConditionsCellRenderer extends DefaultListCellRenderer { + private static final long serialVersionUID = 7987880146189575234L; + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (c instanceof JLabel) { + JLabel label = ((JLabel)c); + Item.TimedConditionEffect effect = (Item.TimedConditionEffect) value; + + if (effect.condition != null) { + label.setIcon(new ImageIcon(effect.condition.getIcon())); + label.setText(effect.chance+"% chances to give "+effect.duration+" rounds of "+effect.condition.getDesc()+" x"+effect.magnitude); + } else { + label.setText("New, undefined actor condition effect."); + } + } + return c; + } + } + + public static class ConditionsListModel implements ListModel { + + Item.EquipEffect source; + + public ConditionsListModel(Item.EquipEffect equipEffect) { + this.source = equipEffect; + } + + @Override + public int getSize() { + if (source.conditions == null) return 0; + return source.conditions.size(); + } + + @Override + public Object getElementAt(int index) { + if (source.conditions == null) return null; + return source.conditions.get(index); + } + + public void addItem(Item.ConditionEffect item) { + if (source.conditions == null) { + source.conditions = new ArrayList(); + } + source.conditions.add(item); + int index = source.conditions.indexOf(item); + for (ListDataListener l : listeners) { + l.intervalAdded(new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index)); + } + } + + public void removeItem(Item.ConditionEffect item) { + int index = source.conditions.indexOf(item); + source.conditions.remove(item); + if (source.conditions.isEmpty()) { + source.conditions = null; + } + for (ListDataListener l : listeners) { + l.intervalRemoved(new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index)); + } + } + + public void itemChanged(Item.ConditionEffect item) { + int index = source.conditions.indexOf(item); + for (ListDataListener l : listeners) { + l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index, index)); + } + } + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + } + + public static class ConditionsCellRenderer extends DefaultListCellRenderer { + private static final long serialVersionUID = 7987880146189575234L; + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (c instanceof JLabel) { + JLabel label = ((JLabel)c); + Item.ConditionEffect effect = (Item.ConditionEffect) value; + + if (effect.condition != null) { + label.setIcon(new ImageIcon(effect.condition.getIcon())); + label.setText("Applies "+effect.condition.getDesc()+" x"+effect.magnitude); + } else { + label.setText("New, undefined actor condition effect."); + } + } + return c; + } + } + + public static boolean isNull(Item.EquipEffect effect) { + if (effect.conditions != null) return false; + if (effect.critical_multiplier != null) return false; + if (effect.damage_boost_max != null) return false; + if (effect.damage_boost_min != null) return false; + if (effect.increase_attack_chance != null) return false; + if (effect.increase_attack_cost != null) return false; + if (effect.increase_block_chance != null) return false; + if (effect.increase_critical_skill != null) return false; + if (effect.increase_damage_resistance != null) return false; + if (effect.increase_move_cost != null) return false; + if (effect.increase_reequip_cost != null) return false; + if (effect.increase_use_item_cost != null) return false; + if (effect.max_ap_boost != null) return false; + if (effect.max_hp_boost != null) return false; + return true; + } + + + public static boolean isNull(Item.HitEffect effect) { + if (effect.ap_boost_min != null) return false; + if (effect.ap_boost_max != null) return false; + if (effect.hp_boost_min != null) return false; + if (effect.hp_boost_max != null) return false; + if (effect.conditions_source != null) return false; + if (effect.conditions_target != null) return false; + return true; + } + + + public static boolean isNull(Item.KillEffect effect) { + if (effect.ap_boost_min != null) return false; + if (effect.ap_boost_max != null) return false; + if (effect.hp_boost_min != null) return false; + if (effect.hp_boost_max != null) return false; + if (effect.conditions_source != null) return false; + return true; + } + + public class ItemFieldUpdater implements FieldUpdateListener { + + @Override + public void valueChanged(JComponent source, Object value) { + Item item = (Item)target; + boolean updatePrice, updateEquip, updateHit, updateKill; + updatePrice = updateEquip = updateHit = updateKill = false; + if (source == idField) { + item.id = (String) value; + ItemEditor.this.name = item.getDesc(); + item.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(ItemEditor.this); + } else if (source == nameField) { + item.name = (String) value; + ItemEditor.this.name = item.getDesc(); + item.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(ItemEditor.this); + } else if (source == itemIcon) { + item.icon_id = (String) value; + item.childrenChanged(new ArrayList()); + ItemEditor.this.icon = new ImageIcon(item.getProject().getIcon((String) value)); + ATContentStudio.frame.editorChanged(ItemEditor.this); + itemIcon.setIcon(new ImageIcon(item.getProject().getImage((String) value))); + itemIcon.revalidate(); + itemIcon.repaint(); + } else if (source == descriptionField) { + item.description = descriptionField.getText(); + } else if (source == typeBox) { + item.display_type = (Item.DisplayType) value; + } else if (source == manualPriceBox) { + item.has_manual_price = (Integer) value; + if (!manualPriceBox.isSelected()) { + baseCostField.setEnabled(false); + updatePrice = true; + } else { + baseCostField.setEnabled(true); + if (baseManualPrice != null) { + baseCostField.setValue(baseManualPrice); + } + } + } else if (source == baseCostField) { + if (manualPriceBox.isSelected()) { + item.base_market_cost = (Integer) value; + baseManualPrice = item.base_market_cost; + } + } else if (source == categoryBox) { + if (item.category != null) { + item.category.removeBacklink(item); + } + item.category = (ItemCategory) value; + if (item.category != null) { + item.category_id = item.category.id; + item.category.addBacklink(item); + } else { + item.category_id = null; + } + if (item.category == null || item.category.action_type == null || item.category.action_type == ItemCategory.ActionType.none) { + equipEffectPane.setVisible(false); + item.equip_effect = null; + hitEffectPane.setVisible(false); + item.hit_effect = null; + killEffectPane.setVisible(false); + item.kill_effect = null; + ItemEditor.this.revalidate(); + ItemEditor.this.repaint(); + } else if (item.category.action_type == ItemCategory.ActionType.use) { + equipEffectPane.setVisible(false); + item.equip_effect = null; + hitEffectPane.setVisible(false); + item.hit_effect = null; + killEffectPane.setVisible(true); + updateKill = true; + killEffectPane.setTitle(useLabel); + ItemEditor.this.revalidate(); + ItemEditor.this.repaint(); + } else if (item.category.action_type == ItemCategory.ActionType.equip) { + equipEffectPane.setVisible(true); + updateEquip = true; + hitEffectPane.setVisible(true); + updateEquip = true; + killEffectPane.setVisible(true); + updateKill = true; + killEffectPane.setTitle(killLabel); + ItemEditor.this.revalidate(); + ItemEditor.this.repaint(); + } + updatePrice = true; + } else if (source == equipDmgMin) { + equipEffect.damage_boost_min = (Integer) value; + updatePrice = true; + updateEquip = true; + } else if (source == equipDmgMax) { + equipEffect.damage_boost_max = (Integer) value; + updatePrice = true; + updateEquip = true; + } else if (source == equipBoostHP) { + equipEffect.max_hp_boost = (Integer) value; + updatePrice = true; + updateEquip = true; + } else if (source == equipBoostAP) { + equipEffect.max_ap_boost = (Integer) value; + updatePrice = true; + updateEquip = true; + } else if (source == equipBoostAC) { + equipEffect.increase_attack_chance = (Integer) value; + updatePrice = true; + updateEquip = true; + } else if (source == equipBoostBC) { + equipEffect.increase_block_chance = (Integer) value; + updatePrice = true; + updateEquip = true; + } else if (source == equipBoostCS) { + equipEffect.increase_critical_skill = (Integer) value; + updatePrice = true; + updateEquip = true; + } else if (source == equipSetCM) { + equipEffect.critical_multiplier = (Double) value; + updatePrice = true; + updateEquip = true; + } else if (source == equipBoostDR) { + equipEffect.increase_damage_resistance = (Integer) value; + updatePrice = true; + updateEquip = true; + } else if (source == equipIncMoveCost) { + equipEffect.increase_move_cost = (Integer) value; + updatePrice = true; + updateEquip = true; + } else if (source == equipIncUseCost) { + equipEffect.increase_use_item_cost = (Integer) value; + updatePrice = true; + updateEquip = true; + } else if (source == equipIncReequipCost) { + equipEffect.increase_reequip_cost = (Integer) value; + updatePrice = true; + updateEquip = true; + } else if (source == equipIncAttackCost) { + equipEffect.increase_attack_cost = (Integer) value; + updatePrice = true; + updateEquip = true; + } else if (source == equipConditionsList) { + updateEquip = true; + } else if (source == equipConditionBox) { + if (selectedEquipEffectCondition.condition != null) { + selectedEquipEffectCondition.condition.removeBacklink(item); + } + selectedEquipEffectCondition.condition = (ActorCondition) value; + if (selectedEquipEffectCondition.condition != null) { + selectedEquipEffectCondition.condition_id = selectedEquipEffectCondition.condition.id; + selectedEquipEffectCondition.condition.addBacklink(item); + } else { + selectedEquipEffectCondition.condition_id = null; + } + equipConditionsModel.itemChanged(selectedEquipEffectCondition); + } else if (source == equipConditionMagnitude) { + selectedEquipEffectCondition.magnitude = (Integer) value; + equipConditionsModel.itemChanged(selectedEquipEffectCondition); + } else if (source == hitHPMin) { + hitEffect.hp_boost_min = (Integer) value; + updatePrice = true; + updateHit = true; + } else if (source == hitHPMax) { + hitEffect.hp_boost_max = (Integer) value; + updatePrice = true; + updateHit = true; + } else if (source == hitAPMin) { + hitEffect.ap_boost_min = (Integer) value; + updatePrice = true; + updateHit = true; + } else if (source == hitAPMax) { + hitEffect.ap_boost_max = (Integer) value; + updatePrice = true; + updateHit = true; + } else if (source == hitSourceConditionsList) { + updateHit = true; + } else if (source == hitSourceConditionBox) { + if (selectedHitEffectSourceCondition.condition != null) { + selectedHitEffectSourceCondition.condition.removeBacklink(item); + } + selectedHitEffectSourceCondition.condition = (ActorCondition) value; + if (selectedHitEffectSourceCondition.condition != null) { + selectedHitEffectSourceCondition.condition_id = selectedHitEffectSourceCondition.condition.id; + selectedHitEffectSourceCondition.condition.addBacklink(item); + } else { + selectedHitEffectSourceCondition.condition_id = null; + } + hitSourceConditionsModel.itemChanged(selectedHitEffectSourceCondition); + updateHit = true; + } else if (source == hitSourceConditionMagnitude) { + selectedHitEffectSourceCondition.magnitude = (Integer) value; + hitSourceConditionsModel.itemChanged(selectedHitEffectSourceCondition); + updateHit = true; + } else if (source == hitSourceConditionDuration) { + selectedHitEffectSourceCondition.duration = (Integer) value; + hitSourceConditionsModel.itemChanged(selectedHitEffectSourceCondition); + updateHit = true; + } else if (source == hitSourceConditionChance) { + selectedHitEffectSourceCondition.chance = (Double) value; + hitSourceConditionsModel.itemChanged(selectedHitEffectSourceCondition); + updateHit = true; + } else if (source == hitTargetConditionsList) { + updateHit = true; + } else if (source == hitTargetConditionBox) { + if (selectedHitEffectTargetCondition.condition != null) { + selectedHitEffectTargetCondition.condition.removeBacklink(item); + } + selectedHitEffectTargetCondition.condition = (ActorCondition) value; + if (selectedHitEffectTargetCondition.condition != null) { + selectedHitEffectTargetCondition.condition_id = selectedHitEffectTargetCondition.condition.id; + selectedHitEffectTargetCondition.condition.addBacklink(item); + } else { + selectedHitEffectTargetCondition.condition_id = null; + } + hitTargetConditionsModel.itemChanged(selectedHitEffectTargetCondition); + updateHit = true; + } else if (source == hitTargetConditionMagnitude) { + selectedHitEffectTargetCondition.magnitude = (Integer) value; + hitTargetConditionsModel.itemChanged(selectedHitEffectTargetCondition); + updateHit = true; + } else if (source == hitTargetConditionDuration) { + selectedHitEffectTargetCondition.duration = (Integer) value; + hitTargetConditionsModel.itemChanged(selectedHitEffectTargetCondition); + updateHit = true; + } else if (source == hitTargetConditionChance) { + selectedHitEffectTargetCondition.chance = (Double) value; + hitTargetConditionsModel.itemChanged(selectedHitEffectTargetCondition); + updateHit = true; + } else if (source == killHPMin) { + killEffect.hp_boost_min = (Integer) value; + updatePrice = true; + updateKill = true; + } else if (source == killHPMax) { + killEffect.hp_boost_max = (Integer) value; + updatePrice = true; + updateKill = true; + } else if (source == killAPMin) { + killEffect.ap_boost_min = (Integer) value; + updatePrice = true; + updateKill = true; + } else if (source == killAPMax) { + killEffect.ap_boost_max = (Integer) value; + updatePrice = true; + updateKill = true; + } else if (source == killSourceConditionsList) { + updateKill = true; + } else if (source == killSourceConditionBox) { + if (selectedKillEffectCondition.condition != null) { + selectedKillEffectCondition.condition.removeBacklink(item); + } + selectedKillEffectCondition.condition = (ActorCondition) value; + if (selectedKillEffectCondition.condition != null) { + selectedKillEffectCondition.condition_id = selectedKillEffectCondition.condition.id; + selectedKillEffectCondition.condition.addBacklink(item); + } else { + selectedKillEffectCondition.condition_id = null; + } + killSourceConditionsModel.itemChanged(selectedKillEffectCondition); + updateKill = true; + } else if (source == killSourceConditionMagnitude) { + selectedKillEffectCondition.magnitude = (Integer) value; + killSourceConditionsModel.itemChanged(selectedKillEffectCondition); + updateKill = true; + } else if (source == killSourceConditionDuration) { + selectedKillEffectCondition.duration = (Integer) value; + killSourceConditionsModel.itemChanged(selectedKillEffectCondition); + updateKill = true; + } else if (source == killSourceConditionChance) { + selectedKillEffectCondition.chance = (Double) value; + killSourceConditionsModel.itemChanged(selectedKillEffectCondition); + updateKill = true; + } + + if (updateEquip) { + if (isNull(equipEffect)) { + item.equip_effect = null; + } else { + item.equip_effect = equipEffect; + } + } + if (updateHit) { + if (isNull(hitEffect)) { + item.hit_effect = null; + } else { + item.hit_effect = hitEffect; + } + } + if (updateKill) { + if (isNull(killEffect)) { + item.kill_effect = null; + } else { + item.kill_effect = killEffect; + } + } + if (updatePrice && !manualPriceBox.isSelected()) { + baseCostField.setValue(item.computePrice()); + } + + + if (item.state != GameDataElement.State.modified) { + item.state = GameDataElement.State.modified; + ItemEditor.this.name = item.getDesc(); + item.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(ItemEditor.this); + } + updateJsonViewText(item.toJsonString()); + + } + + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/JSONElementEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/JSONElementEditor.java new file mode 100644 index 0000000..57d810f --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/JSONElementEditor.java @@ -0,0 +1,269 @@ +package com.gpl.rpg.atcontentstudio.ui.gamedataeditors; + +import java.awt.BorderLayout; +import java.awt.Image; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; + +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.SaveEvent; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataCategory; +import com.gpl.rpg.atcontentstudio.model.gamedata.JSONElement; +import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; +import com.gpl.rpg.atcontentstudio.ui.Editor; +import com.gpl.rpg.atcontentstudio.ui.FieldUpdateListener; +import com.gpl.rpg.atcontentstudio.ui.SaveItemsWizard; +import com.gpl.rpg.atcontentstudio.ui.sprites.SpriteChooser; +import com.jidesoft.swing.JideBoxLayout; +import com.jidesoft.swing.JideTabbedPane; + +public abstract class JSONElementEditor extends Editor { + + private static final long serialVersionUID = -5889046987755079563L; + + + Map editorTabs = new HashMap(); + JideTabbedPane editorTabsHolder; + RSyntaxTextArea jsonEditorPane; + + public JSONElementEditor(JSONElement target, String desc, Image icon) { + super(); + this.target = target; + this.name = desc; + this.icon = new ImageIcon(icon); + + setLayout(new BorderLayout()); + editorTabsHolder = new JideTabbedPane(JideTabbedPane.BOTTOM); + editorTabsHolder.setTabShape(JideTabbedPane.SHAPE_FLAT); + editorTabsHolder.setUseDefaultShowCloseButtonOnTab(false); + editorTabsHolder.setShowCloseButtonOnTab(false); + add(editorTabsHolder, BorderLayout.CENTER); + } + + public void addEditorTab(String id, JPanel editor) { + JScrollPane scroller = new JScrollPane(editor); + scroller.getVerticalScrollBar().setUnitIncrement(16); + editorTabsHolder.addTab(id, scroller); + editorTabs.put(id, editor); + } + + public JPanel getJSONView() { + jsonEditorPane = new RSyntaxTextArea(); + jsonEditorPane.setText(((JSONElement)target).toJsonString()); + jsonEditorPane.setEditable(((JSONElement)target).writable); + jsonEditorPane.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JSON); + JPanel result = new JPanel(); + result.setLayout(new BorderLayout()); + result.add(jsonEditorPane, BorderLayout.CENTER); + return result; + } + + public void updateJsonViewText(String text) { + jsonEditorPane.setText(text); + } + + public JPanel getFormView() { + JPanel pane = new JPanel(); + pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS, 6)); + if (((JSONElement)target).jsonFile != null) { + addLabelField(pane, "JSON File: ", ((JSONElement)target).jsonFile.getAbsolutePath()); + } + + insertFormViewDataField(pane); + + addBacklinksList(pane, (JSONElement) target); + + //Placeholder. Fills the eventual remaining space. + pane.add(new JPanel(), JideBoxLayout.VARY); + + return pane; + } + + public abstract void insertFormViewDataField(JPanel pane); + + + + public JButton createButtonPane(JPanel pane, final Project proj, final JSONElement node, final Class concreteNodeClass, Image icon, final Spritesheet.Category iconCat, final FieldUpdateListener listener) { + final JButton gdeIcon = new JButton(new ImageIcon(icon)); + JPanel savePane = new JPanel(); + savePane.add(gdeIcon, JideBoxLayout.FIX); + savePane.setLayout(new JideBoxLayout(savePane, JideBoxLayout.LINE_AXIS, 6)); + if (node.writable) { + if (iconCat != null) { + gdeIcon.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + SpriteChooser chooser = SpriteChooser.getChooser(proj, iconCat); + chooser.setSelectionListener(new SpriteChooser.SelectionListener() { + @Override + public void iconSelected(String selected) { + if (selected != null) { + listener.valueChanged(gdeIcon, selected); + } + } + }); + chooser.setVisible(true); + } + }); + } + if (node.getDataType() == GameSource.Type.altered) { + savePane.add(message = new JLabel(ALTERED_MESSAGE), JideBoxLayout.FIX); + } else if (node.getDataType() == GameSource.Type.created) { + savePane.add(message = new JLabel(CREATED_MESSAGE), JideBoxLayout.FIX); + } + JButton save = new JButton(SAVE); + save.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (node.getParent() instanceof GameDataCategory) { + if (node.state != GameDataElement.State.saved) { + final List events = node.attemptSave(); + if (events == null) { + ATContentStudio.frame.nodeChanged(node); + } else { + new Thread() { + @Override + public void run() { + new SaveItemsWizard(events, node).setVisible(true); + } + }.start(); + } + } + } + } + }); + savePane.add(save, JideBoxLayout.FIX); + JButton delete = new JButton(DELETE); + if (node.getDataType() == GameSource.Type.altered) { + delete.setText(REVERT); + } + delete.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ATContentStudio.frame.closeEditor(node); + node.childrenRemoved(new ArrayList()); + if (node.getParent() instanceof GameDataCategory) { + ((GameDataCategory)node.getParent()).remove(node); + node.save(); + for (GameDataElement backlink : node.getBacklinks()) { + backlink.elementChanged(node, proj.getGameDataElement(node.getClass(), node.id)); + } + } + } + }); + savePane.add(delete, JideBoxLayout.FIX); + } else { + if (proj.alteredContent.gameData.getGameDataElement(concreteNodeClass, node.id) != null) { + savePane.add(message = new JLabel(ALTERED_EXISTS_MESSAGE), JideBoxLayout.FIX); + JButton makeWritable = new JButton("Go to altered"); + makeWritable.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (node.getProject().getGameDataElement(concreteNodeClass, node.id) != node) { + ATContentStudio.frame.openEditor(node.getProject().getGameDataElement(concreteNodeClass, node.id)); + ATContentStudio.frame.closeEditor(node); + ATContentStudio.frame.selectInTree(node.getProject().getGameDataElement(concreteNodeClass, node.id)); + } + } + }); + savePane.add(makeWritable, JideBoxLayout.FIX); + + } else { + savePane.add(message = new JLabel(READ_ONLY_MESSAGE), JideBoxLayout.FIX); + JButton makeWritable = new JButton("Alter"); + makeWritable.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (node.getProject().getGameDataElement(concreteNodeClass, node.id) == node) { + node.getProject().makeWritable(node); + } + if (node.getProject().getGameDataElement(concreteNodeClass, node.id) != node) { + ATContentStudio.frame.openEditor(node.getProject().getGameDataElement(concreteNodeClass, node.id)); + ATContentStudio.frame.closeEditor(node); + ATContentStudio.frame.selectInTree(node.getProject().getGameDataElement(concreteNodeClass, node.id)); + } + updateMessage(); + } + }); + savePane.add(makeWritable, JideBoxLayout.FIX); + } + } + JButton prev = new JButton(new ImageIcon(DefaultIcons.getArrowLeftIcon())); + JButton next = new JButton(new ImageIcon(DefaultIcons.getArrowRightIcon())); + savePane.add(prev, JideBoxLayout.FIX); + savePane.add(next, JideBoxLayout.FIX); + if (node.getParent().getIndex(node) == 0) { + prev.setEnabled(false); + } + if (node.getParent().getIndex(node) == node.getParent().getChildCount() - 1) { + next.setEnabled(false); + } + prev.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ProjectTreeNode prevNode = (ProjectTreeNode) node.getParent().getChildAt(node.getParent().getIndex(node) - 1); + if (prevNode != null && prevNode instanceof GameDataElement) { + ATContentStudio.frame.openEditor((GameDataElement) prevNode); + } + } + }); + next.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ProjectTreeNode nextNode = (ProjectTreeNode) node.getParent().getChildAt(node.getParent().getIndex(node) + 1); + if (nextNode != null && nextNode instanceof GameDataElement) { + ATContentStudio.frame.openEditor((GameDataElement) nextNode); + } + } + }); + //Placeholder. Fills the eventual remaining space. + savePane.add(new JPanel(), JideBoxLayout.VARY); + pane.add(savePane, JideBoxLayout.FIX); + return gdeIcon; + } + + + @Override + public void targetUpdated() { + this.icon = new ImageIcon(((GameDataElement)target).getIcon()); + this.name = ((GameDataElement)target).getDesc(); + updateMessage(); + } + + public void updateMessage() { + + //TODO make this a full update of the button panel. + JSONElement node = (JSONElement) target; + if (node.writable) { + if (node.getDataType() == GameSource.Type.altered) { + message.setText(ALTERED_MESSAGE); + } else if (node.getDataType() == GameSource.Type.created) { + message.setText(CREATED_MESSAGE); + } + } else if (node.getProject().alteredContent.gameData.getGameDataElement(node.getClass(), node.id) != null) { + message.setText(ALTERED_EXISTS_MESSAGE); + } else { + message.setText(READ_ONLY_MESSAGE); + } + message.revalidate(); + message.repaint(); + } +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/NPCEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/NPCEditor.java new file mode 100644 index 0000000..777c093 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/NPCEditor.java @@ -0,0 +1,612 @@ +package com.gpl.rpg.atcontentstudio.ui.gamedataeditors; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.JTextField; +import javax.swing.ListModel; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.gamedata.ActorCondition; +import com.gpl.rpg.atcontentstudio.model.gamedata.Dialogue; +import com.gpl.rpg.atcontentstudio.model.gamedata.Droplist; +import com.gpl.rpg.atcontentstudio.model.gamedata.NPC; +import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet; +import com.gpl.rpg.atcontentstudio.ui.CollapsiblePanel; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; +import com.gpl.rpg.atcontentstudio.ui.FieldUpdateListener; +import com.gpl.rpg.atcontentstudio.ui.IntegerBasedCheckBox; +import com.gpl.rpg.atcontentstudio.ui.gamedataeditors.dialoguetree.DialogueGraphView; +import com.jidesoft.swing.JideBoxLayout; + +public class NPCEditor extends JSONElementEditor { + + private static final long serialVersionUID = 4001483665523721800L; + + private static final String form_view_id = "Form"; + private static final String json_view_id = "JSON"; + + private NPC.TimedConditionEffect selectedHitEffectSourceCondition; + private NPC.TimedConditionEffect selectedHitEffectTargetCondition; + + private JButton npcIcon; + private JTextField idField; + private JTextField nameField; + private JTextField spawnGroupField; + private JSpinner experienceField; + private MyComboBox dialogueBox; + private MyComboBox droplistBox; + private JComboBox monsterClassBox; + private IntegerBasedCheckBox uniqueBox; + private JComboBox moveTypeBox; + + private CollapsiblePanel combatTraitPane; + private JSpinner maxHP; + private JSpinner maxAP; + private JSpinner moveCost; + private JSpinner atkDmgMin; + private JSpinner atkDmgMax; + private JSpinner atkCost; + private JSpinner atkChance; + private JSpinner critSkill; + private JSpinner critMult; + private JSpinner blockChance; + private JSpinner dmgRes; + + private NPC.HitEffect hitEffect; + private CollapsiblePanel hitEffectPane; + private JSpinner hitEffectHPMin; + private JSpinner hitEffectHPMax; + private JSpinner hitEffectAPMin; + private JSpinner hitEffectAPMax; + + private SourceTimedConditionsListModel hitSourceConditionsListModel; + private JList hitSourceConditionsList; + private MyComboBox sourceConditionBox; + private JSpinner sourceConditionMagnitude; + private JSpinner sourceConditionDuration; + private JSpinner sourceConditionChance; + + private TargetTimedConditionsListModel hitTargetConditionsListModel; + private JList hitTargetConditionsList; + private MyComboBox targetConditionBox; + private JSpinner targetConditionMagnitude; + private JSpinner targetConditionDuration; + private JSpinner targetConditionChance; + + public NPCEditor(NPC npc) { + super(npc, npc.getDesc(), npc.getIcon()); + addEditorTab(form_view_id, getFormView()); + addEditorTab(json_view_id, getJSONView()); + if (npc.dialogue != null) { + JPanel pane = new JPanel(); + pane.setLayout(new BorderLayout()); + pane.add(new JScrollPane(new DialogueGraphView(npc.dialogue, npc)), BorderLayout.CENTER); + addEditorTab("Dialogue Tree", pane); + } + } + + @Override + public void insertFormViewDataField(JPanel pane) { + final NPC npc = (NPC) target; + + final FieldUpdateListener listener = new NPCFieldUpdate(); + + npcIcon = createButtonPane(pane, npc.getProject(), npc, NPC.class, npc.getImage(), Spritesheet.Category.monster, listener); + + idField = addTextField(pane, "Internal ID: ", npc.id, npc.writable, listener); + nameField = addTextField(pane, "Display name: ", npc.name, npc.writable, listener); + spawnGroupField = addTextField(pane, "Spawn group ID: ", npc.spawngroup_id, npc.writable, listener); + experienceField = addIntegerField(pane, "Experience reward: ", npc.getMonsterExperience(), false, false, listener); + dialogueBox = addDialogueBox(pane, npc.getProject(), "Initial phrase: ", npc.dialogue, npc.writable, listener); + droplistBox = addDroplistBox(pane, npc.getProject(), "Droplist / Shop inventory: ", npc.droplist, npc.writable, listener); + monsterClassBox = addEnumValueBox(pane, "Monster class: ", NPC.MonsterClass.values(), npc.monster_class, npc.writable, listener); + uniqueBox = addIntegerBasedCheckBox(pane, "Unique", npc.unique, npc.writable, listener); + moveTypeBox = addEnumValueBox(pane, "Movement type: ", NPC.MovementType.values(), npc.movement_type, npc.writable, listener); + combatTraitPane = new CollapsiblePanel("Combat traits: "); + combatTraitPane.setLayout(new JideBoxLayout(combatTraitPane, JideBoxLayout.PAGE_AXIS, 6)); + maxHP = addIntegerField(combatTraitPane, "Max HP: ", npc.max_hp, false, npc.writable, listener); + maxAP = addIntegerField(combatTraitPane, "Max AP: ", npc.max_ap, false, npc.writable, listener); + moveCost = addIntegerField(combatTraitPane, "Move cost: ", npc.move_cost, false, npc.writable, listener); + atkDmgMin = addIntegerField(combatTraitPane, "Attack Damage min: ", npc.attack_damage_min, false, npc.writable, listener); + atkDmgMax = addIntegerField(combatTraitPane, "Attack Damage max: ", npc.attack_damage_max, false, npc.writable, listener); + atkCost = addIntegerField(combatTraitPane, "Attack cost: ", npc.attack_cost, false, npc.writable, listener); + atkChance = addIntegerField(combatTraitPane, "Attack chance: ", npc.attack_chance, false, npc.writable, listener); + critSkill = addIntegerField(combatTraitPane, "Critical skill: ", npc.critical_skill, false, npc.writable, listener); + critMult = addDoubleField(combatTraitPane, "Critical multiplier: ", npc.critical_multiplier, npc.writable, listener); + blockChance = addIntegerField(combatTraitPane, "Block chance: ", npc.block_chance, false, npc.writable, listener); + dmgRes = addIntegerField(combatTraitPane, "Damage resistance: ", npc.damage_resistance, false, npc.writable, listener); + hitEffectPane = new CollapsiblePanel("Effect on every hit: "); + hitEffectPane.setLayout(new JideBoxLayout(hitEffectPane, JideBoxLayout.PAGE_AXIS)); + if (npc.hit_effect == null) { + hitEffect = new NPC.HitEffect(); + } else { + hitEffect = npc.hit_effect; + } + hitEffectHPMin = addIntegerField(hitEffectPane, "HP bonus min: ", hitEffect.hp_boost_min, true, npc.writable, listener); + hitEffectHPMax = addIntegerField(hitEffectPane, "HP bonus max: ", hitEffect.hp_boost_max, true, npc.writable, listener); + hitEffectAPMin = addIntegerField(hitEffectPane, "AP bonus min: ", hitEffect.ap_boost_min, true, npc.writable, listener); + hitEffectAPMax = addIntegerField(hitEffectPane, "AP bonus max: ", hitEffect.ap_boost_max, true, npc.writable, listener); + + CollapsiblePanel hitSourceConditionsPane = new CollapsiblePanel("Actor Conditions applied to the source: "); + hitSourceConditionsPane.setLayout(new JideBoxLayout(hitSourceConditionsPane, JideBoxLayout.PAGE_AXIS)); + hitSourceConditionsListModel = new SourceTimedConditionsListModel(hitEffect); + hitSourceConditionsList = new JList(hitSourceConditionsListModel); + hitSourceConditionsList.setCellRenderer(new TimedConditionsCellRenderer()); + hitSourceConditionsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + hitSourceConditionsPane.add(new JScrollPane(hitSourceConditionsList), JideBoxLayout.FIX); + final JPanel sourceTimedConditionsEditorPane = new JPanel(); + final JButton createHitSourceCondition = new JButton(new ImageIcon(DefaultIcons.getCreateIcon())); + final JButton deleteHitSourceCondition = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + hitSourceConditionsList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + selectedHitEffectSourceCondition = (NPC.TimedConditionEffect) hitSourceConditionsList.getSelectedValue(); + updateSourceTimedConditionEditorPane(sourceTimedConditionsEditorPane, selectedHitEffectSourceCondition, listener); + } + }); + if (npc.writable) { + JPanel listButtonsPane = new JPanel(); + listButtonsPane.setLayout(new JideBoxLayout(listButtonsPane, JideBoxLayout.LINE_AXIS, 6)); + createHitSourceCondition.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + NPC.TimedConditionEffect condition = new NPC.TimedConditionEffect(); + hitSourceConditionsListModel.addItem(condition); + hitSourceConditionsList.setSelectedValue(condition, true); + listener.valueChanged(hitSourceConditionsList, null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + }); + deleteHitSourceCondition.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (selectedHitEffectSourceCondition != null) { + hitSourceConditionsListModel.removeItem(selectedHitEffectSourceCondition); + selectedHitEffectSourceCondition = null; + hitSourceConditionsList.clearSelection(); + listener.valueChanged(hitSourceConditionsList, null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + } + }); + + listButtonsPane.add(createHitSourceCondition, JideBoxLayout.FIX); + listButtonsPane.add(deleteHitSourceCondition, JideBoxLayout.FIX); + listButtonsPane.add(new JPanel(), JideBoxLayout.VARY); + hitSourceConditionsPane.add(listButtonsPane, JideBoxLayout.FIX); + } + sourceTimedConditionsEditorPane.setLayout(new JideBoxLayout(sourceTimedConditionsEditorPane, JideBoxLayout.PAGE_AXIS)); + hitSourceConditionsPane.add(sourceTimedConditionsEditorPane, JideBoxLayout.FIX); + if (npc.hit_effect == null || npc.hit_effect.conditions_source == null || npc.hit_effect.conditions_source.isEmpty()) { + hitSourceConditionsPane.collapse(); + } + hitEffectPane.add(hitSourceConditionsPane, JideBoxLayout.FIX); + final CollapsiblePanel hitTargetConditionsPane = new CollapsiblePanel("Actor Conditions applied to the target: "); + hitTargetConditionsPane.setLayout(new JideBoxLayout(hitTargetConditionsPane, JideBoxLayout.PAGE_AXIS)); + hitTargetConditionsListModel = new TargetTimedConditionsListModel(hitEffect); + hitTargetConditionsList = new JList(hitTargetConditionsListModel); + hitTargetConditionsList.setCellRenderer(new TimedConditionsCellRenderer()); + hitTargetConditionsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + hitTargetConditionsPane.add(new JScrollPane(hitTargetConditionsList), JideBoxLayout.FIX); + final JPanel targetTimedConditionsEditorPane = new JPanel(); + final JButton createHitTargetCondition = new JButton(new ImageIcon(DefaultIcons.getCreateIcon())); + final JButton deleteHitTargetCondition = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + hitTargetConditionsList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + selectedHitEffectTargetCondition = (NPC.TimedConditionEffect) hitTargetConditionsList.getSelectedValue(); + updateTargetTimedConditionEditorPane(targetTimedConditionsEditorPane, selectedHitEffectTargetCondition, listener); + } + }); + if (npc.writable) { + JPanel listButtonsPane = new JPanel(); + listButtonsPane.setLayout(new JideBoxLayout(listButtonsPane, JideBoxLayout.LINE_AXIS, 6)); + createHitTargetCondition.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + NPC.TimedConditionEffect condition = new NPC.TimedConditionEffect(); + hitTargetConditionsListModel.addItem(condition); + hitTargetConditionsList.setSelectedValue(condition, true); + listener.valueChanged(hitTargetConditionsList, null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + }); + deleteHitTargetCondition.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (selectedHitEffectTargetCondition != null) { + hitTargetConditionsListModel.removeItem(selectedHitEffectTargetCondition); + selectedHitEffectTargetCondition = null; + hitTargetConditionsList.clearSelection(); + listener.valueChanged(hitTargetConditionsList, null); //Item changed, but we took care of it, just do the usual notification and JSON update stuff. + } + } + }); + + listButtonsPane.add(createHitTargetCondition, JideBoxLayout.FIX); + listButtonsPane.add(deleteHitTargetCondition, JideBoxLayout.FIX); + listButtonsPane.add(new JPanel(), JideBoxLayout.VARY); + hitTargetConditionsPane.add(listButtonsPane, JideBoxLayout.FIX); + } + targetTimedConditionsEditorPane.setLayout(new JideBoxLayout(targetTimedConditionsEditorPane, JideBoxLayout.PAGE_AXIS)); + hitTargetConditionsPane.add(targetTimedConditionsEditorPane, JideBoxLayout.FIX); + hitEffectPane.add(hitTargetConditionsPane, JideBoxLayout.FIX); + if (npc.hit_effect == null || npc.hit_effect.conditions_target == null || npc.hit_effect.conditions_target.isEmpty()) { + hitTargetConditionsPane.collapse(); + } + combatTraitPane.add(hitEffectPane, JideBoxLayout.FIX); + + pane.add(combatTraitPane, JideBoxLayout.FIX); + } + + public void updateSourceTimedConditionEditorPane(JPanel pane, NPC.TimedConditionEffect condition, FieldUpdateListener listener) { + pane.removeAll(); + if (sourceConditionBox != null) { + removeElementListener(sourceConditionBox); + } + + boolean writable = ((NPC)target).writable; + Project proj = ((NPC)target).getProject(); + + sourceConditionBox = addActorConditionBox(pane, proj, "Actor Condition: ", condition.condition, writable, listener); + sourceConditionMagnitude = addIntegerField(pane, "Magnitude: ", condition.magnitude, false, writable, listener); + sourceConditionDuration = addIntegerField(pane, "Duration: ", condition.duration, false, writable, listener); + sourceConditionChance = addDoubleField(pane, "Chance: ", condition.chance, writable, listener); + + pane.revalidate(); + pane.repaint(); + } + + public void updateTargetTimedConditionEditorPane(JPanel pane, NPC.TimedConditionEffect condition, FieldUpdateListener listener) { + pane.removeAll(); + if (targetConditionBox != null) { + removeElementListener(targetConditionBox); + } + + boolean writable = ((NPC)target).writable; + Project proj = ((NPC)target).getProject(); + + targetConditionBox = addActorConditionBox(pane, proj, "Actor Condition: ", condition.condition, writable, listener); + targetConditionMagnitude = addIntegerField(pane, "Magnitude: ", condition.magnitude, false, writable, listener); + targetConditionDuration = addIntegerField(pane, "Duration: ", condition.duration, false, writable, listener); + targetConditionChance = addDoubleField(pane, "Chance: ", condition.chance, writable, listener); + + pane.revalidate(); + pane.repaint(); + } + + public static class TargetTimedConditionsListModel implements ListModel { + + NPC.HitEffect source; + + public TargetTimedConditionsListModel(NPC.HitEffect effect) { + this.source = effect; + } + + @Override + public int getSize() { + if (source.conditions_target == null) return 0; + return source.conditions_target.size(); + } + + @Override + public Object getElementAt(int index) { + if (source.conditions_target == null) return null; + return source.conditions_target.get(index); + } + + public void addItem(NPC.TimedConditionEffect item) { + if (source.conditions_target == null) { + source.conditions_target = new ArrayList(); + } + source.conditions_target.add(item); + int index = source.conditions_target.indexOf(item); + for (ListDataListener l : listeners) { + l.intervalAdded(new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index)); + } + } + + public void removeItem(NPC.TimedConditionEffect item) { + int index = source.conditions_target.indexOf(item); + source.conditions_target.remove(item); + if (source.conditions_target.isEmpty()) { + source.conditions_target = null; + } + for (ListDataListener l : listeners) { + l.intervalRemoved(new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index)); + } + } + + public void itemChanged(NPC.TimedConditionEffect item) { + int index = source.conditions_target.indexOf(item); + for (ListDataListener l : listeners) { + l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index, index)); + } + } + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + } + + public static class SourceTimedConditionsListModel implements ListModel { + + NPC.HitEffect source; + + public SourceTimedConditionsListModel(NPC.HitEffect effect) { + this.source = effect; + } + + @Override + public int getSize() { + if (source.conditions_source == null) return 0; + return source.conditions_source.size(); + } + + @Override + public Object getElementAt(int index) { + if (source.conditions_source == null) return null; + return source.conditions_source.get(index); + } + + public void addItem(NPC.TimedConditionEffect item) { + if (source.conditions_source == null) { + source.conditions_source = new ArrayList(); + } + source.conditions_source.add(item); + int index = source.conditions_source.indexOf(item); + for (ListDataListener l : listeners) { + l.intervalAdded(new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index)); + } + } + + public void removeItem(NPC.TimedConditionEffect item) { + int index = source.conditions_source.indexOf(item); + source.conditions_source.remove(item); + if (source.conditions_source.isEmpty()) { + source.conditions_source = null; + } + for (ListDataListener l : listeners) { + l.intervalRemoved(new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index)); + } + } + + public void itemChanged(NPC.TimedConditionEffect item) { + int index = source.conditions_source.indexOf(item); + for (ListDataListener l : listeners) { + l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index, index)); + } + } + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + } + + public static class TimedConditionsCellRenderer extends DefaultListCellRenderer { + private static final long serialVersionUID = 7987880146189575234L; + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (c instanceof JLabel) { + JLabel label = ((JLabel)c); + NPC.TimedConditionEffect effect = (NPC.TimedConditionEffect) value; + + if (effect.condition != null) { + label.setIcon(new ImageIcon(effect.condition.getIcon())); + label.setText(effect.chance+"% chances to give "+effect.duration+" rounds of "+effect.condition.getDesc()+" x"+effect.magnitude); + } else { + label.setText("New, undefined actor condition effect."); + } + } + return c; + } + } + + public static boolean isNull(NPC.HitEffect effect) { + if (effect.ap_boost_min != null) return false; + if (effect.ap_boost_max != null) return false; + if (effect.hp_boost_min != null) return false; + if (effect.hp_boost_max != null) return false; + if (effect.conditions_source != null) return false; + if (effect.conditions_target != null) return false; + return true; + } + + public class NPCFieldUpdate implements FieldUpdateListener { + + @Override + public void valueChanged(JComponent source, Object value) { + NPC npc = (NPC)target; + boolean updateHit = false; + if (source == idField) { + npc.id = (String) value; + NPCEditor.this.name = npc.getDesc(); + npc.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(NPCEditor.this); + } else if (source == nameField) { + npc.name = (String) value; + NPCEditor.this.name = npc.getDesc(); + npc.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(NPCEditor.this); + } else if (source == npcIcon) { + npc.icon_id = (String) value; + npc.childrenChanged(new ArrayList()); + NPCEditor.this.icon = new ImageIcon(npc.getProject().getIcon((String) value)); + ATContentStudio.frame.editorChanged(NPCEditor.this); + npcIcon.setIcon(new ImageIcon(npc.getProject().getImage((String) value))); + npcIcon.revalidate(); + npcIcon.repaint(); + } else if (source == spawnGroupField) { + npc.spawngroup_id = (String) value; + } else if (source == dialogueBox) { + if (npc.dialogue != null) { + npc.dialogue.removeBacklink(npc); + } + npc.dialogue = (Dialogue) value; + if (npc.dialogue != null) { + npc.dialogue_id =npc.dialogue.id; + npc.dialogue.addBacklink(npc); + } else { + npc.dialogue_id = null; + } + } else if (source == droplistBox) { + if (npc.droplist != null) { + npc.droplist.removeBacklink(npc); + } + npc.droplist = (Droplist) value; + if (npc.droplist != null) { + npc.droplist_id = npc.droplist.id; + npc.droplist.addBacklink(npc); + } else { + npc.droplist_id = null; + } + } else if (source == monsterClassBox) { + npc.monster_class = (NPC.MonsterClass) value; + } else if (source == uniqueBox) { + npc.unique = (Integer) value; + } else if (source == moveTypeBox) { + npc.movement_type = (NPC.MovementType) value; + } else if (source == maxHP) { + npc.max_hp = (Integer) value; + } else if (source == maxAP) { + npc.max_ap = (Integer) value; + } else if (source == moveCost) { + npc.move_cost = (Integer) value; + } else if (source == atkDmgMin) { + npc.attack_damage_min = (Integer) value; + } else if (source == atkDmgMax) { + npc.attack_damage_max = (Integer) value; + } else if (source == atkCost) { + npc.attack_cost = (Integer) value; + } else if (source == atkChance) { + npc.attack_chance = (Integer) value; + } else if (source == critSkill) { + npc.critical_skill = (Integer) value; + } else if (source == critMult) { + npc.critical_multiplier = (Double) value; + } else if (source == blockChance) { + npc.block_chance = (Integer) value; + } else if (source == dmgRes) { + npc.damage_resistance = (Integer) value; + } else if (source == hitEffectHPMin) { + hitEffect.hp_boost_min = (Integer) value; + updateHit = true; + } else if (source == hitEffectHPMax) { + hitEffect.hp_boost_max = (Integer) value; + updateHit = true; + } else if (source == hitEffectAPMin) { + hitEffect.ap_boost_min = (Integer) value; + updateHit = true; + } else if (source == hitEffectAPMax) { + hitEffect.ap_boost_max = (Integer) value; + updateHit = true; + } else if (source == hitSourceConditionsList) { + //TODO + } else if (source == sourceConditionBox) { + if (selectedHitEffectSourceCondition.condition != null) { + selectedHitEffectSourceCondition.condition.removeBacklink(npc); + } + selectedHitEffectSourceCondition.condition = (ActorCondition) value; + if (selectedHitEffectSourceCondition.condition != null) { + selectedHitEffectSourceCondition.condition.addBacklink(npc); + selectedHitEffectSourceCondition.condition_id = selectedHitEffectSourceCondition.condition.id; + } else { + selectedHitEffectSourceCondition.condition_id = null; + } + hitSourceConditionsListModel.itemChanged(selectedHitEffectSourceCondition); + } else if (source == sourceConditionMagnitude) { + selectedHitEffectSourceCondition.magnitude = (Integer) value; + hitSourceConditionsListModel.itemChanged(selectedHitEffectSourceCondition); + } else if (source == sourceConditionDuration) { + selectedHitEffectSourceCondition.duration = (Integer) value; + hitSourceConditionsListModel.itemChanged(selectedHitEffectSourceCondition); + } else if (source == sourceConditionChance) { + selectedHitEffectSourceCondition.chance = (Double) value; + hitSourceConditionsListModel.itemChanged(selectedHitEffectSourceCondition); + } else if (source == hitTargetConditionsList) { + //TODO + } else if (source == targetConditionBox) { + if (selectedHitEffectTargetCondition.condition != null) { + selectedHitEffectTargetCondition.condition.removeBacklink(npc); + } + selectedHitEffectTargetCondition.condition = (ActorCondition) value; + if (selectedHitEffectTargetCondition.condition != null) { + selectedHitEffectTargetCondition.condition_id = selectedHitEffectTargetCondition.condition.id; + selectedHitEffectTargetCondition.condition.addBacklink(npc); + } else { + selectedHitEffectTargetCondition.condition_id = null; + } + hitTargetConditionsListModel.itemChanged(selectedHitEffectTargetCondition); + } else if (source == targetConditionMagnitude) { + selectedHitEffectTargetCondition.magnitude = (Integer) value; + hitTargetConditionsListModel.itemChanged(selectedHitEffectTargetCondition); + } else if (source == targetConditionDuration) { + selectedHitEffectTargetCondition.duration = (Integer) value; + hitTargetConditionsListModel.itemChanged(selectedHitEffectTargetCondition); + } else if (source == targetConditionChance) { + selectedHitEffectTargetCondition.chance = (Double) value; + hitTargetConditionsListModel.itemChanged(selectedHitEffectTargetCondition); + } + + if (updateHit) { + if (isNull(hitEffect)) { + npc.hit_effect = null; + } else { + npc.hit_effect = hitEffect; + } + } + + experienceField.setValue(npc.getMonsterExperience()); + + if (npc.state != GameDataElement.State.modified) { + npc.state = GameDataElement.State.modified; + NPCEditor.this.name = npc.getDesc(); + npc.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(NPCEditor.this); + } + updateJsonViewText(npc.toJsonString()); + + } + + } + + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/QuestEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/QuestEditor.java new file mode 100644 index 0000000..559273a --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/QuestEditor.java @@ -0,0 +1,334 @@ +package com.gpl.rpg.atcontentstudio.ui.gamedataeditors; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; +import javax.swing.table.TableModel; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.gamedata.Quest; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; +import com.gpl.rpg.atcontentstudio.ui.FieldUpdateListener; +import com.gpl.rpg.atcontentstudio.ui.IntegerBasedCheckBox; +import com.jidesoft.swing.JideBoxLayout; + +public class QuestEditor extends JSONElementEditor { + + private static final long serialVersionUID = 5701667955210615366L; + + private static final Integer one = 1; + + private static final String form_view_id = "Form"; + private static final String json_view_id = "JSON"; + + private JTextField idField; + private JTextField nameField; + private IntegerBasedCheckBox visibleBox; + private QuestStageTableModel stagesModel; + private JTable stagesTable; + private JButton createStage; + private JButton deleteStage; + private JButton moveUp; + private JButton moveDown; + + + public QuestEditor(Quest quest) { + super(quest, quest.getDesc(), quest.getIcon()); + addEditorTab(form_view_id, getFormView()); + addEditorTab(json_view_id, getJSONView()); + } + + public void insertFormViewDataField(JPanel pane) { + final Quest quest = ((Quest)target); + + final FieldUpdateListener listener = new QuestFieldUpdater(); + + createButtonPane(pane, quest.getProject(), quest, Quest.class, quest.getImage(), null, listener); + + + addTextField(pane, "Internal ID: ", quest.id, quest.writable, listener); + addTextField(pane, "Quest Name: ", quest.name, quest.writable, listener); + addIntegerBasedCheckBox(pane, "Visible in quest log", quest.visible_in_log, quest.writable, listener); + + JPanel stagesPane = new JPanel(); + stagesPane.setLayout(new JideBoxLayout(stagesPane, JideBoxLayout.PAGE_AXIS, 6)); + stagesModel = new QuestStageTableModel(quest, listener); + stagesTable = new JTable(stagesModel); + stagesTable.getColumnModel().getColumn(0).setMinWidth(100); + stagesTable.getColumnModel().getColumn(0).setMaxWidth(100); +// stagesTable.getColumnModel().getColumn(1).setPreferredWidth(40); +// stagesTable.getColumnModel().getColumn(1).setPreferredWidth(40); + stagesTable.getColumnModel().getColumn(2).setMinWidth(100); + stagesTable.getColumnModel().getColumn(2).setMaxWidth(100); + stagesTable.getColumnModel().getColumn(3).setMinWidth(130); + stagesTable.getColumnModel().getColumn(3).setMaxWidth(130); + stagesTable.setCellSelectionEnabled(true); + stagesPane.add(new JScrollPane(stagesTable), BorderLayout.CENTER); + if (quest.writable) { + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new JideBoxLayout(buttonPane, JideBoxLayout.LINE_AXIS, 6)); + createStage = new JButton(new ImageIcon(DefaultIcons.getCreateIcon())); + deleteStage = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + moveUp = new JButton(new ImageIcon(DefaultIcons.getArrowUpIcon())); + moveDown = new JButton(new ImageIcon(DefaultIcons.getArrowDownIcon())); + buttonPane.add(createStage, JideBoxLayout.FIX); + buttonPane.add(deleteStage, JideBoxLayout.FIX); + buttonPane.add(moveUp, JideBoxLayout.FIX); + buttonPane.add(moveDown, JideBoxLayout.FIX); + buttonPane.add(new JPanel(), JideBoxLayout.VARY); + deleteStage.setEnabled(false); + moveUp.setEnabled(false); + moveDown.setEnabled(false); + + stagesTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + + @Override + public void valueChanged(ListSelectionEvent e) { + updateTableButtons(); + } + }); + + createStage.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + stagesModel.createStage(); + listener.valueChanged(stagesTable, null); + stagesTable.revalidate(); + stagesTable.repaint(); + } + }); + deleteStage.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + stagesModel.deleteRow(stagesTable.getSelectedRow()); + listener.valueChanged(stagesTable, null); + stagesTable.revalidate(); + stagesTable.repaint(); + updateTableButtons(); + } + }); + moveUp.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + stagesModel.moveRow(stagesTable.getSelectedRow(), true); + listener.valueChanged(stagesTable, null); + stagesTable.setRowSelectionInterval(stagesTable.getSelectedRow() - 1, stagesTable.getSelectedRow() - 1); + updateTableButtons(); + } + }); + moveDown.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + stagesModel.moveRow(stagesTable.getSelectedRow(), false); + listener.valueChanged(stagesTable, null); + stagesTable.setRowSelectionInterval(stagesTable.getSelectedRow() + 1, stagesTable.getSelectedRow() + 1); + updateTableButtons(); + } + }); + stagesPane.add(buttonPane, JideBoxLayout.FIX); + } + pane.add(stagesPane, JideBoxLayout.FIX); + + } + + public void updateTableButtons() { + + if (stagesTable.getSelectedRow() >= 0 && stagesTable.getSelectedRow() < stagesModel.getRowCount()) { + deleteStage.setEnabled(true); + if (stagesTable.getSelectedRow() == 0) { + moveUp.setEnabled(false); + } else { + moveUp.setEnabled(true); + } + if (stagesTable.getSelectedRow() >= stagesModel.getRowCount() - 1) { + moveDown.setEnabled(false); + } else { + moveDown.setEnabled(true); + } + } else { + deleteStage.setEnabled(false); + moveUp.setEnabled(false); + moveDown.setEnabled(false); + } + + } + + public class QuestStageTableModel implements TableModel { + + Quest quest; + FieldUpdateListener listener; + + public QuestStageTableModel(Quest q, FieldUpdateListener listener) { + this.quest = q; + this.listener = listener; + } + + @Override + public int getRowCount() { + if (quest.stages == null) return 0; + return quest.stages.size(); + } + + @Override + public int getColumnCount() { + return 4; + } + + @Override + public String getColumnName(int columnIndex) { + switch (columnIndex) { + case 0: + return "Progress ID"; + case 1: + return "Log text"; + case 2: + return "XP reward"; + case 3: + return "Finishes quest"; + default: + return "???"; + } + } + + @Override + public Class getColumnClass(int columnIndex) { + switch (columnIndex) { + case 0: + return Integer.class; + case 1: + return String.class; + case 2: + return Integer.class; + case 3: + return Boolean.class; + default: + return null; + } + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return quest.writable; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + switch (columnIndex) { + case 0: + return quest.stages.get(rowIndex).progress; + case 1: + return quest.stages.get(rowIndex).log_text; + case 2: + return quest.stages.get(rowIndex).exp_reward; + case 3: + return quest.stages.get(rowIndex).finishes_quest != null && quest.stages.get(rowIndex).finishes_quest.equals(1); + default: + return null; + } + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + switch (columnIndex) { + case 0: + quest.stages.get(rowIndex).progress = (Integer)aValue; + break; + case 1: + quest.stages.get(rowIndex).log_text = (String)aValue; + break; + case 2: + quest.stages.get(rowIndex).exp_reward = (Integer)aValue; + break; + case 3: + quest.stages.get(rowIndex).finishes_quest = ((Boolean)aValue) ? one : null; + break; + } + listener.valueChanged(stagesTable, aValue); + } + + public void createStage() { + quest.stages.add(new Quest.QuestStage()); + for (TableModelListener l: listeners) { + l.tableChanged(new TableModelEvent(this, quest.stages.size() - 1)); + } + } + + public void moveRow(int rowNumber, boolean moveUp) { + Quest.QuestStage stage = quest.stages.get(rowNumber); + quest.stages.remove(stage); + quest.stages.add(rowNumber + (moveUp ? -1 : 1), stage); + for (TableModelListener l : listeners) { + l.tableChanged(new TableModelEvent(this, rowNumber + (moveUp ? -1 : 0), rowNumber + (moveUp ? 0 : 1))); + } + } + + public void deleteRow(int rowNumber) { + quest.stages.remove(rowNumber); + for (TableModelListener l: listeners) { + l.tableChanged(new TableModelEvent(this, rowNumber, quest.stages.size())); + } + } + + public List listeners = new ArrayList(); + + @Override + public void addTableModelListener(TableModelListener l) { + listeners.add(l); + } + + @Override + public void removeTableModelListener(TableModelListener l) { + listeners.remove(l); + } + + } + + public class QuestFieldUpdater implements FieldUpdateListener { + + @Override + public void valueChanged(JComponent source, Object value) { + Quest quest = (Quest) target; + if (source == idField) { + quest.id = (String) value; + QuestEditor.this.name = quest.getDesc(); + quest.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(QuestEditor.this); + } else if (source == nameField) { + quest.name = (String) value; + QuestEditor.this.name = quest.getDesc(); + quest.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(QuestEditor.this); + } else if (source == visibleBox) { + quest.visible_in_log = (Integer) value; + } + + + if (quest.state != GameDataElement.State.modified) { + quest.state = GameDataElement.State.modified; + QuestEditor.this.name = quest.getDesc(); + quest.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(QuestEditor.this); + } + updateJsonViewText(quest.toJsonString()); + } + + } + + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/dialoguetree/DialogueGraphView.java b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/dialoguetree/DialogueGraphView.java new file mode 100644 index 0000000..d3d5951 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/dialoguetree/DialogueGraphView.java @@ -0,0 +1,440 @@ +package com.gpl.rpg.atcontentstudio.ui.gamedataeditors.dialoguetree; + +import java.awt.BasicStroke; +import java.awt.Image; +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import prefuse.Display; +import prefuse.Visualization; +import prefuse.action.ActionList; +import prefuse.action.RepaintAction; +import prefuse.action.assignment.ColorAction; +import prefuse.action.assignment.FontAction; +import prefuse.action.assignment.StrokeAction; +import prefuse.action.layout.Layout; +import prefuse.action.layout.graph.NodeLinkTreeLayout; +import prefuse.activity.Activity; +import prefuse.controls.ControlAdapter; +import prefuse.controls.PanControl; +import prefuse.controls.WheelZoomControl; +import prefuse.controls.ZoomControl; +import prefuse.data.Edge; +import prefuse.data.Graph; +import prefuse.data.Node; +import prefuse.data.Schema; +import prefuse.render.DefaultRendererFactory; +import prefuse.render.EdgeRenderer; +import prefuse.render.LabelRenderer; +import prefuse.util.ColorLib; +import prefuse.util.GraphicsLib; +import prefuse.util.PrefuseLib; +import prefuse.visual.DecoratorItem; +import prefuse.visual.EdgeItem; +import prefuse.visual.VisualItem; +import prefuse.visual.expression.InGroupPredicate; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.gamedata.Dialogue; +import com.gpl.rpg.atcontentstudio.model.gamedata.NPC; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class DialogueGraphView extends Display { + + private static final long serialVersionUID = -6431503090775579301L; + + public static final String GRAPH = "graph"; + public static final String NODES = "graph.nodes"; + public static final String EDGES = "graph.edges"; + public static final String EDGES_LABELS = "edgesLabels"; + public static final String AGGR = "aggregates"; + + public static final String LABEL = "label"; + public static final String ICON = "icon"; + public static final String TARGET = "target"; + public static final String REPLY = "reply"; + public static final String HAS_REQS = "has_reqs"; + + private static final Schema DECORATOR_SCHEMA = PrefuseLib.getVisualItemSchema(); + + private Dialogue dialogue; + private Image npcIcon; + private Graph graph; + + private Map cells = new HashMap(); + + public DialogueGraphView(Dialogue dialogue, NPC npc) { + super(new Visualization()); + this.dialogue = dialogue; + if (npc != null) { + npcIcon = npc.getIcon(); + } else { + npcIcon = DefaultIcons.getNPCIcon(); + } + loadGraph(); + + // add visual data groups + m_vis.addGraph(GRAPH, graph); + m_vis.setInteractive(EDGES, null, false); + + LabelRenderer nodeR = new MyLabelRenderer(LABEL); + nodeR.setHorizontalTextAlignment(prefuse.Constants.LEFT); + + EdgeRenderer edgeR = new EdgeRenderer(prefuse.Constants.EDGE_TYPE_LINE, prefuse.Constants.EDGE_ARROW_FORWARD); +// edgeR.setEdgeType(prefuse.Constants.EDGE_TYPE_CURVE); + + LabelRenderer edgeLabelR = new LabelRenderer(LABEL); + edgeLabelR.setRenderType(LabelRenderer.RENDER_TYPE_DRAW); + + DefaultRendererFactory drf = new DefaultRendererFactory(); + drf.setDefaultRenderer(nodeR); + drf.setDefaultEdgeRenderer(edgeR); + drf.add(new InGroupPredicate(EDGES_LABELS), edgeLabelR); + m_vis.setRendererFactory(drf); + DECORATOR_SCHEMA.setDefault(VisualItem.FILLCOLOR, ColorLib.gray(255)); + DECORATOR_SCHEMA.setDefault(VisualItem.STROKECOLOR, ColorLib.rgba(0, 0, 0, 0)); + DECORATOR_SCHEMA.setDefault(VisualItem.TEXTCOLOR, ColorLib.gray(0)); + m_vis.addDecorators(EDGES_LABELS, EDGES, DECORATOR_SCHEMA); + + // set up the visual operators + // first set up all the color actions + ColorAction nStrokeColor = new ColorAction(NODES, VisualItem.STROKECOLOR); + nStrokeColor.setDefaultColor(ColorLib.gray(100)); + nStrokeColor.add("_hover", ColorLib.rgb(255,100,100)); + StrokeAction nStroke = new EdgesStrokeAction(NODES); + + ColorAction nFill = new NPCPhraseColorAction(NODES, VisualItem.FILLCOLOR); + + ColorAction eEdges = new ConnectedEdgeColorAction(EDGES, VisualItem.STROKECOLOR); + ColorAction eArrows = new ConnectedEdgeColorAction(EDGES, VisualItem.FILLCOLOR); + ColorAction eEdgesLabels = new ConnectedEdgeColorAction(EDGES_LABELS, VisualItem.TEXTCOLOR); + + StrokeAction eStroke = new EdgesStrokeAction(EDGES); + + FontAction aFont = new FontAction(); + ColorAction aFontColor = new ColorAction(NODES, VisualItem.TEXTCOLOR); + aFontColor.setDefaultColor(ColorLib.rgb(0, 0, 0)); + + // bundle the color actions + ActionList colors = new ActionList(Activity.INFINITY); + colors.add(nStrokeColor); + colors.add(nFill); + colors.add(nStroke); + colors.add(eEdges); + colors.add(eArrows); + colors.add(eEdgesLabels); + colors.add(eStroke); + colors.add(aFont); + colors.add(aFontColor); + colors.add(new RepaintAction()); + m_vis.putAction("colors", colors); + + // now create the main layout routine + ActionList layout = new ActionList();//Activity.INFINITY); + NodeLinkTreeLayout treeLayout = new NodeLinkTreeLayout(GRAPH, prefuse.Constants.ORIENT_LEFT_RIGHT, 120, 40, 40); + treeLayout.setLayoutAnchor(new Point2D.Double(25,300)); + layout.add(treeLayout); + layout.add(new EdgesLabelDecoratorLayout(EDGES_LABELS)); + layout.add(new RepaintAction()); + m_vis.putAction("layout", layout); + + // set up the display + setSize(500,500); + pan(250, 250); + setHighQuality(true); + addControlListener(new DoubleClickControl()); + addControlListener(new WheelZoomControl()); + addControlListener(new ZoomControl()); + addControlListener(new PanControl()); + + // set things running + m_vis.run("colors"); + m_vis.run("layout"); + } + + public void loadGraph() { + graph = new Graph(true); + graph.addColumn(LABEL, String.class, ""); + graph.addColumn(ICON, Image.class, DefaultIcons.getNullifyIcon()); + graph.addColumn(TARGET, GameDataElement.class, null); + graph.addColumn(REPLY, Dialogue.Reply.class, null); + graph.addColumn(HAS_REQS, boolean.class, false); + addDialogue(dialogue, npcIcon); + } + + public Node addDialogue(Dialogue dialogue, Image npcIcon) { + if (cells.get(dialogue) == null) { + if (dialogue.switch_to_npc != null) { + npcIcon = dialogue.switch_to_npc.getIcon(); + } + Node dNode = graph.addNode(); + cells.put(dialogue, dNode); + dNode.setString(LABEL, dialogue.message != null ? dialogue.message : "[Selector]"); + dNode.set(ICON, npcIcon); + dNode.set(TARGET, dialogue); + if (dialogue.replies != null) { + Node rNode; + int i = 1; + for (Dialogue.Reply r : dialogue.replies) { + rNode = addReply(dialogue, r, npcIcon); + Edge e = graph.addEdge(dNode, rNode); + e.setString(LABEL, "#"+i++); + e.setBoolean(HAS_REQS, r.requirements != null && !r.requirements.isEmpty()); + } + } + } + return cells.get(dialogue); + } + + public Node addReply(Dialogue d, Dialogue.Reply r, Image npcIcon) { + Node rNode; + if (r.text != null && !r.text.equals(Dialogue.Reply.GO_NEXT_TEXT)) { + //Normal reply... + rNode = graph.addNode(); + rNode.setString(LABEL, r.text); + rNode.set(ICON, DefaultIcons.getHeroIcon()); + rNode.set(TARGET, d); + rNode.set(REPLY, r); + if (r.next_phrase != null) { + //...that leads to another phrase + Node dNode = addDialogue(r.next_phrase, npcIcon); + graph.addEdge(rNode, dNode); + } else if (Dialogue.Reply.KEY_PHRASE_ID.contains(r.next_phrase_id)) { + //...that leads to a key phrase + Node kNode = addKeyPhraseNode(d, r.next_phrase_id); + kNode.set(REPLY, r); + graph.addEdge(rNode, kNode); + } + } else if (r.next_phrase != null) { + //Go directly to next phrase + rNode = addDialogue(r.next_phrase, npcIcon); + } else if (Dialogue.Reply.KEY_PHRASE_ID.contains(r.next_phrase_id)) { + //Go directly to key phrase + rNode = addKeyPhraseNode(d, r.next_phrase_id); + rNode.set(REPLY, r); + } else { + //Incomplete. + rNode = graph.addNode(); + rNode.setString(LABEL, "[Incomplete reply]"); + rNode.set(ICON, DefaultIcons.getNullifyIcon()); + } + return rNode; + } + + public Node addKeyPhraseNode(Dialogue d, String key) { + Node kNode = graph.addNode(); + if (key.equals(Dialogue.Reply.EXIT_PHRASE_ID)) { + kNode.setString(LABEL, "[Ends dialogue]"); + kNode.set(ICON, DefaultIcons.getNullifyIcon()); + } else if (key.equals(Dialogue.Reply.FIGHT_PHRASE_ID)) { + kNode.setString(LABEL, "[Starts fight]"); + kNode.set(ICON, DefaultIcons.getCombatIcon()); + } else if (key.equals(Dialogue.Reply.REMOVE_PHRASE_ID)) { + kNode.setString(LABEL, "[NPC vanishes]"); + kNode.set(ICON, DefaultIcons.getNPCCloseIcon()); + } else if (key.equals(Dialogue.Reply.SHOP_PHRASE_ID)) { + kNode.setString(LABEL, "[Start trading]"); + kNode.set(ICON, DefaultIcons.getGoldIcon()); + } else { + //Should never reach, unless new key phrase ID are added to the model, but not here... + kNode.setString(LABEL, "[WTF !!]"+key); + } + kNode.set(TARGET, d); + return kNode; + } + + class MyLabelRenderer extends LabelRenderer { + public MyLabelRenderer(String label) { + super(label); + } + + @Override + protected Image getImage(VisualItem item) { + return (Image) item.get(ICON); + } + + @Override + protected String getText(VisualItem item) { + return wordWrap(super.getText(item), 40); + } + + public String wordWrap(String in, int length) { + final String newline = "\n"; + //:: Trim + while(in.length() > 0 && (in.charAt(0) == '\t' || in.charAt(0) == ' ')) in = in.substring(1); + //:: If Small Enough Already, Return Original + if(in.length() < length) return in; + //:: If Next length Contains Newline, Split There + if(in.substring(0, length).contains(newline)) return in.substring(0, in.indexOf(newline)).trim() + newline + wordWrap(in.substring(in.indexOf("\n") + 1), length); + //:: Otherwise, Split Along Nearest Previous Space/Tab/Dash + int spaceIndex = Math.max(Math.max( in.lastIndexOf(" ", length), in.lastIndexOf("\t", length)), in.lastIndexOf("-", length)); + //:: If No Nearest Space, Split At length + if(spaceIndex == -1) spaceIndex = length; + //:: Split + return in.substring(0, spaceIndex).trim() + newline + wordWrap(in.substring(spaceIndex), length); + } + } + + class ConnectedEdgeColorAction extends ColorAction { + + final int outgoing = ColorLib.rgb(255, 100, 100); + final int incoming = ColorLib.rgb(100, 255, 100); + final int none = ColorLib.gray(100); + + public ConnectedEdgeColorAction(String group, String field) { + super(group, field); + } + + @Override + public int getColor(VisualItem item) { + if (item instanceof DecoratorItem) { + item = ((DecoratorItem) item).getDecoratedItem(); + } + if (item instanceof EdgeItem) { + if (((EdgeItem) item).getSourceItem().isHover()) { + return outgoing; + } else if (((EdgeItem) item).getTargetItem().isHover()) { + return incoming; + } + } + + return none; + } + } + + class NPCPhraseColorAction extends ColorAction { + + final int none = ColorLib.gray(255); + final int hover = ColorLib.gray(200); + final int has_rewards = ColorLib.rgb(255, 255, 0); + final int has_rewards_hover = ColorLib.rgb(200, 200, 0); + + + public NPCPhraseColorAction(String group, String field) { + super(group, field); + } + + @Override + public int getColor(VisualItem item) { + // Change color for Dialogues, not replies. + if (item.get(TARGET) != null && item.get(REPLY) == null) { + Dialogue d = (Dialogue) item.get(TARGET); + if (d.rewards != null && !d.rewards.isEmpty()) { + if (item.isHover()) { + return has_rewards_hover; + } else { + return has_rewards; + } + } + } + if (item.isHover()) { + return hover; + } else { + return none; + } + } + } + + class EdgesStrokeAction extends StrokeAction { + + public final BasicStroke req_stroke = new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 1.0f, new float[]{3.5f, 2.5f}, 1.0f); + public final BasicStroke hover_stroke = new BasicStroke(3.0f); + public final BasicStroke hover_req_stroke = new BasicStroke(4.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 1.0f, new float[]{3.5f, 2.5f}, 1.0f); + + public EdgesStrokeAction(String group) { + super(group); + } + + @Override + public BasicStroke getStroke(VisualItem item) { + if (item.getBoolean(HAS_REQS)) { + if (item instanceof EdgeItem) { + if (((EdgeItem) item).getSourceItem().isHover()) { + return hover_req_stroke; + } else if (((EdgeItem) item).getTargetItem().isHover()) { + return hover_req_stroke; + } + } + return req_stroke; + } else { + if (item.isHover()) { + return hover_stroke; + } else if (item instanceof EdgeItem) { + if (((EdgeItem) item).getSourceItem().isHover()) { + return hover_stroke; + } else if (((EdgeItem) item).getTargetItem().isHover()) { + return hover_stroke; + } + } + } + return super.getStroke(item); + } + + } + + class EdgesLabelDecoratorLayout extends Layout { + public EdgesLabelDecoratorLayout(String group) { + super(group); + } + public void run(double frac) { + Iterator iter = m_vis.items(m_group); + while ( iter.hasNext() ) { + DecoratorItem decorator = (DecoratorItem)iter.next(); + if( decorator.getDecoratedItem() instanceof EdgeItem) { + EdgeItem edgeItem = (EdgeItem) decorator.getDecoratedItem(); + double deltaX = edgeItem.getTargetItem().getX() - edgeItem.getSourceItem().getX(); + double deltaY = edgeItem.getTargetItem().getY() - edgeItem.getSourceItem().getY(); + double hypo = Math.hypot(deltaX, deltaY); + + double edgePropX = deltaX / hypo; + double edgePropY = deltaY / hypo; + + Point2D start = new Point2D.Double(edgeItem.getSourceItem().getBounds().getCenterX(), edgeItem.getSourceItem().getBounds().getCenterY()); + Point2D end = new Point2D.Double(edgeItem.getTargetItem().getBounds().getCenterX(), edgeItem.getTargetItem().getBounds().getCenterY()); + Point2D[] realStart = new Point2D[]{new Point2D.Double(), new Point2D.Double()}; +// Point2D[] realEnd = new Point2D[]{new Point2D.Double(), new Point2D.Double()}; + + int i = GraphicsLib.intersectLineRectangle(start, end, edgeItem.getSourceItem().getBounds(), realStart); + if (i > 0) { + start = realStart[0]; + } +// i = GraphicsLib.intersectLineRectangle(start, end, edgeItem.getTargetItem().getBounds(), realEnd); +// if (i > 0) { +// end = realEnd[0]; +// } + + double coef = 20 * Math.atan(hypo / 100.0) + 20; + + setX(decorator, null, start.getX() + coef * edgePropX + 6 * Math.random() - 3); + setY(decorator, null, start.getY() + coef * edgePropY + 6 * Math.random() - 3); + } else { + VisualItem decoratedItem = decorator.getDecoratedItem(); + Rectangle2D bounds = decoratedItem.getBounds(); + double x = bounds.getCenterX(); + double y = bounds.getCenterY(); + setX(decorator, null, x + 10 * Math.random() - 5); + setY(decorator, null, y + 10 * Math.random() - 5); + } + } + } + } + + class DoubleClickControl extends ControlAdapter { + @Override + public void itemClicked(VisualItem item, MouseEvent e) { + if (e.getClickCount() == 2) { + if (item.get(TARGET) != null) { + ATContentStudio.frame.openEditor((GameDataElement)item.get(TARGET)); + } + } + } + } + +} + + diff --git a/src/com/gpl/rpg/atcontentstudio/ui/map/TMXMapEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/map/TMXMapEditor.java new file mode 100644 index 0000000..d37724d --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/map/TMXMapEditor.java @@ -0,0 +1,1434 @@ +package com.gpl.rpg.atcontentstudio.ui.map; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionListener; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.BorderFactory; +import javax.swing.DefaultComboBoxModel; +import javax.swing.DefaultListCellRenderer; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.JSplitPane; +import javax.swing.JTextField; +import javax.swing.ListModel; +import javax.swing.ListSelectionModel; +import javax.swing.ScrollPaneConstants; +import javax.swing.Scrollable; +import javax.swing.SwingConstants; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; + +import tiled.view.MapRenderer; +import tiled.view.OrthogonalRenderer; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.gamedata.ActorCondition; +import com.gpl.rpg.atcontentstudio.model.gamedata.Dialogue; +import com.gpl.rpg.atcontentstudio.model.gamedata.Droplist; +import com.gpl.rpg.atcontentstudio.model.gamedata.Item; +import com.gpl.rpg.atcontentstudio.model.gamedata.JSONElement; +import com.gpl.rpg.atcontentstudio.model.gamedata.NPC; +import com.gpl.rpg.atcontentstudio.model.gamedata.Quest; +import com.gpl.rpg.atcontentstudio.model.gamedata.Requirement; +import com.gpl.rpg.atcontentstudio.model.maps.ContainerArea; +import com.gpl.rpg.atcontentstudio.model.maps.KeyArea; +import com.gpl.rpg.atcontentstudio.model.maps.MapChange; +import com.gpl.rpg.atcontentstudio.model.maps.MapObject; +import com.gpl.rpg.atcontentstudio.model.maps.MapObjectGroup; +import com.gpl.rpg.atcontentstudio.model.maps.ReplaceArea; +import com.gpl.rpg.atcontentstudio.model.maps.RestArea; +import com.gpl.rpg.atcontentstudio.model.maps.ScriptArea; +import com.gpl.rpg.atcontentstudio.model.maps.SignArea; +import com.gpl.rpg.atcontentstudio.model.maps.SpawnArea; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; +import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet; +import com.gpl.rpg.atcontentstudio.ui.BooleanBasedCheckBox; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; +import com.gpl.rpg.atcontentstudio.ui.Editor; +import com.gpl.rpg.atcontentstudio.ui.FieldUpdateListener; +import com.gpl.rpg.atcontentstudio.ui.IntegerBasedCheckBox; +import com.gpl.rpg.atcontentstudio.ui.ScrollablePanel; +import com.jidesoft.swing.JideBoxLayout; +import com.jidesoft.swing.JideTabbedPane; + +public class TMXMapEditor extends Editor { + + private static final long serialVersionUID = -3079451876618342442L; + + + Map editorTabs = new HashMap(); + JideTabbedPane editorTabsHolder; + + private RSyntaxTextArea editorPane; + + private IntegerBasedCheckBox outsideBox; + private LayerListModel layerListModel; + private JList layerList; + private tiled.core.MapLayer selectedLayer; + private JButton addTileLayer; + private JButton addObjectGroup; + private JButton deleteLayer; + + private JPanel layerDetailsPane; + private BooleanBasedCheckBox layerVisibleBox; + private JTextField layerNameField; + private MapObjectsListModel groupObjectsListModel; + private JList groupObjectsList; + private MapObject selectedMapObject; + private JButton addMapchange; + private JButton addSpawn; + private JButton addRest; + private JButton addKey; + private JButton addReplace; + private JButton addScript; + private JButton addContainer; + private JButton addSign; + private JButton deleteObject; + + private JPanel mapObjectSettingsPane; + private JComboBox droplistBox; + private JComboBox dialogueBox; + private JComboBox mapBox; + private JTextField areaField; + private JComboBox targetAreaCombo; + private JComboBox evaluateTriggerBox; + private JSpinner quantityField; + private JList npcList; + private SpawnGroupNpcListModel npcListModel; + + private JComboBox requirementTypeCombo; + private JPanel requirementParamsPane; + private JComboBox requirementObj; + private JTextField requirementObjId; + private JSpinner requirementValue; + private BooleanBasedCheckBox requirementNegated; + + private TMXViewer tmxViewer; + + public TMXMapEditor(TMXMap map) { + this.target = map; + this.name = map.getDesc(); + this.icon = new ImageIcon(DefaultIcons.getTiledIconIcon()); + + + setLayout(new BorderLayout()); + editorTabsHolder = new JideTabbedPane(JideTabbedPane.BOTTOM); + editorTabsHolder.setTabShape(JideTabbedPane.SHAPE_FLAT); + editorTabsHolder.setUseDefaultShowCloseButtonOnTab(false); + editorTabsHolder.setShowCloseButtonOnTab(false); + add(editorTabsHolder, BorderLayout.CENTER); + + JScrollPane tmxScroller = new JScrollPane(getTmxEditorPane(), JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);; + editorTabsHolder.add("TMX", tmxScroller); + editorTabsHolder.add("XML", new JScrollPane(getXmlEditorPane())); + + } + + public JPanel getTmxEditorPane() { + final TMXMap map = (TMXMap) target; + final FieldUpdateListener listener = new MapFieldUpdater(); + + ScrollablePanel pane = new ScrollablePanel(); + pane.setScrollableWidth( ScrollablePanel.ScrollableSizeHint.FIT ); + pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS, 6)); + + addLabelField(pane, "TMX File: ", ((TMXMap)target).tmxFile.getAbsolutePath()); + createButtonPane(pane, map.getProject(), map, listener); + outsideBox = addIntegerBasedCheckBox(pane, "Map is outdoors", map.outside, map.writable, listener); + + JSplitPane layersViewSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + layerListModel = new LayerListModel(map); + layerList = new JList(layerListModel); + layerList.setCellRenderer(new LayerListRenderer()); + layerList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + JScrollPane layerListScroller = new JScrollPane(layerList); + layerListScroller.getVerticalScrollBar().setUnitIncrement(16); + layerList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + selectedLayer = (tiled.core.MapLayer) layerList.getSelectedValue(); + selectedMapObject = null; + if (selectedLayer != null && map.writable) { + deleteLayer.setEnabled(true); + } else { + deleteLayer.setEnabled(false); + } + updateLayerDetailsPane(layerDetailsPane, selectedLayer, listener); + listener.valueChanged(layerList, selectedLayer); + } + }); + JPanel layersListPane = new JPanel(); + layersListPane.setLayout(new JideBoxLayout(layersListPane, JideBoxLayout.PAGE_AXIS, 6)); + layersListPane.add(layerListScroller, JideBoxLayout.VARY); + addTileLayer = new JButton(new ImageIcon(DefaultIcons.getCreateTileLayerIcon())); + addTileLayer.setToolTipText("Create new tile layer (graphics layer)."); + addTileLayer.setEnabled(map.writable); + addTileLayer.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + layerListModel.addObject(new tiled.core.TileLayer()); + } + }); + addObjectGroup = new JButton(new ImageIcon(DefaultIcons.getCreateObjectGroupIcon())); + addObjectGroup.setToolTipText("Create new object group."); + addObjectGroup.setEnabled(map.writable); + addObjectGroup.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + layerListModel.addObject(new tiled.core.ObjectGroup()); + } + }); + deleteLayer = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + deleteLayer.setToolTipText("Delete selected layer/group."); + deleteLayer.setEnabled(false); + deleteLayer.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + layerListModel.removeObject(selectedLayer); + } + }); + JPanel layersButtonsPane = new JPanel(); + layersButtonsPane.setLayout(new JideBoxLayout(layersButtonsPane, JideBoxLayout.LINE_AXIS, 6)); + layersButtonsPane.add(addTileLayer, JideBoxLayout.FIX); + layersButtonsPane.add(addObjectGroup, JideBoxLayout.FIX); + layersButtonsPane.add(new JPanel(), JideBoxLayout.VARY); + layersButtonsPane.add(deleteLayer, JideBoxLayout.FIX); + layersListPane.add(layersButtonsPane, JideBoxLayout.FIX); + layersViewSplitPane.setLeftComponent(layersListPane); + layerDetailsPane = new JPanel(); + layerDetailsPane.setLayout(new JideBoxLayout(layerDetailsPane, JideBoxLayout.PAGE_AXIS, 6)); + layersViewSplitPane.setRightComponent(layerDetailsPane); + pane.add(layersViewSplitPane, JideBoxLayout.FIX); + + tmxViewer = new TMXViewer(((TMXMap)target), listener); + JScrollPane tmxScroller = new JScrollPane(tmxViewer); + tmxScroller.getVerticalScrollBar().setUnitIncrement(16); + tmxScroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); + tmxScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + pane.add(tmxScroller, JideBoxLayout.FIX); + + addTMXMapSpritesheetsList(pane, ((TMXMap)target)); + + addBacklinksList(pane, map); + + pane.add(new JPanel(), JideBoxLayout.VARY); + return pane; + } + + public void updateLayerDetailsPane(JPanel pane, tiled.core.MapLayer selected, final FieldUpdateListener listener) { + final TMXMap map = (TMXMap)target; + pane.removeAll(); + if (selected == null) { + return; + } else if (selected instanceof tiled.core.TileLayer) { + layerNameField = addTextField(pane, "Layer name: ", selected.getName(), map.writable, listener); + layerVisibleBox = addBooleanBasedCheckBox(pane, "Visible", selected.isVisible(), true, listener); + pane.add(new JPanel(), JideBoxLayout.VARY); + } else if (selected instanceof tiled.core.ObjectGroup) { + JSplitPane objectGroupDetailsSplitter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + JPanel groupDetailPane = new JPanel(); + groupDetailPane.setLayout(new JideBoxLayout(groupDetailPane, JideBoxLayout.PAGE_AXIS, 6)); + objectGroupDetailsSplitter.setLeftComponent(groupDetailPane); + layerNameField = addTextField(groupDetailPane, "Group name: ", selected.getName(), map.writable, listener); + layerVisibleBox = addBooleanBasedCheckBox(groupDetailPane, "Visible", selected.isVisible(), true, listener); + MapObjectGroup objGroup = null; + for (MapObjectGroup group : map.groups) { + if (group.tmxGroup == selected) { + objGroup = group; + break; + } + } + groupObjectsListModel = new MapObjectsListModel(objGroup); + groupObjectsList = new JList(groupObjectsListModel); + groupObjectsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + groupObjectsList.setCellRenderer(new GroupObjectsRenderer()); + JScrollPane groupObjectsScroller = new JScrollPane(groupObjectsList); + groupObjectsScroller.getVerticalScrollBar().setUnitIncrement(16); + groupDetailPane.add(groupObjectsScroller, JideBoxLayout.VARY); + groupObjectsList.addListSelectionListener(new ListSelectionListener() { + + @Override + public void valueChanged(ListSelectionEvent e) { + selectedMapObject = (MapObject) groupObjectsList.getSelectedValue(); + updateMapObjectSettingsPane(mapObjectSettingsPane, selectedMapObject, listener); + listener.valueChanged(groupObjectsList, selectedMapObject); + if (selectedMapObject != null && map.writable) { + deleteObject.setEnabled(true); + } else { + deleteObject.setEnabled(false); + } + } + }); + + addMapchange = new JButton(new ImageIcon(DefaultIcons.getCreateMapchangeIcon())); + addMapchange.setToolTipText("Create new mapchange area."); + addMapchange.setEnabled(map.writable); + addMapchange.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + groupObjectsListModel.addObject(MapObject.newMapchange(new tiled.core.MapObject(0, 0, 32, 32), map)); + } + }); + addSpawn = new JButton(new ImageIcon(DefaultIcons.getCreateSpawnareaIcon())); + addSpawn.setToolTipText("Create new spawn area."); + addSpawn.setEnabled(map.writable); + addSpawn.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + groupObjectsListModel.addObject(MapObject.newSpawnArea(new tiled.core.MapObject(0, 0, 32, 32), map)); + } + }); + addRest = new JButton(new ImageIcon(DefaultIcons.getCreateRestIcon())); + addRest.setToolTipText("Create new rest area."); + addRest.setEnabled(map.writable); + addRest.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + groupObjectsListModel.addObject(MapObject.newRest(new tiled.core.MapObject(0, 0, 32, 32), map)); + } + }); + addKey = new JButton(new ImageIcon(DefaultIcons.getCreateKeyIcon())); + addKey.setToolTipText("Create new key area."); + addKey.setEnabled(map.writable); + addKey.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + groupObjectsListModel.addObject(MapObject.newKey(new tiled.core.MapObject(0, 0, 32, 32), map)); + } + }); + addReplace = new JButton(new ImageIcon(DefaultIcons.getCreateReplaceIcon())); + addReplace.setToolTipText("Create new replace area."); + addReplace.setEnabled(map.writable); + addReplace.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + groupObjectsListModel.addObject(MapObject.newReplace(new tiled.core.MapObject(0, 0, 32, 32), map)); + } + }); + addScript = new JButton(new ImageIcon(DefaultIcons.getCreateScriptIcon())); + addScript.setToolTipText("Create new script area."); + addScript.setEnabled(map.writable); + addScript.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + groupObjectsListModel.addObject(MapObject.newScript(new tiled.core.MapObject(0, 0, 32, 32), map)); + } + }); + addContainer = new JButton(new ImageIcon(DefaultIcons.getCreateContainerIcon())); + addContainer.setToolTipText("Create new container."); + addContainer.setEnabled(map.writable); + addContainer.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + groupObjectsListModel.addObject(MapObject.newContainer(new tiled.core.MapObject(0, 0, 32, 32), map)); + } + }); + addSign = new JButton(new ImageIcon(DefaultIcons.getCreateSignIcon())); + addSign.setToolTipText("Create new sign post."); + addSign.setEnabled(map.writable); + addSign.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + groupObjectsListModel.addObject(MapObject.newSign(new tiled.core.MapObject(0, 0, 32, 32), map)); + } + }); + deleteObject = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + deleteObject.setToolTipText("Delete selected map object."); + deleteObject.setEnabled(false); + deleteObject.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + groupObjectsListModel.removeObject(selectedMapObject); + } + }); + + JPanel groupButtonsPane1 = new JPanel(); + groupButtonsPane1.setLayout(new JideBoxLayout(groupButtonsPane1, JideBoxLayout.LINE_AXIS, 6)); + groupButtonsPane1.add(addMapchange, JideBoxLayout.FIX); + groupButtonsPane1.add(addSpawn, JideBoxLayout.FIX); + groupButtonsPane1.add(addRest, JideBoxLayout.FIX); + groupButtonsPane1.add(addKey, JideBoxLayout.FIX); + groupButtonsPane1.add(new JPanel(), JideBoxLayout.VARY); + JPanel groupButtonsPane2 = new JPanel(); + groupButtonsPane2.setLayout(new JideBoxLayout(groupButtonsPane2, JideBoxLayout.LINE_AXIS, 6)); + groupButtonsPane2.add(addReplace, JideBoxLayout.FIX); + groupButtonsPane2.add(addScript, JideBoxLayout.FIX); + groupButtonsPane2.add(addContainer, JideBoxLayout.FIX); + groupButtonsPane2.add(addSign, JideBoxLayout.FIX); + groupButtonsPane2.add(new JPanel(), JideBoxLayout.VARY); + groupButtonsPane2.add(deleteObject, JideBoxLayout.FIX); + groupDetailPane.add(groupButtonsPane1, JideBoxLayout.FIX); + groupDetailPane.add(groupButtonsPane2, JideBoxLayout.FIX); + + mapObjectSettingsPane = new JPanel(); + mapObjectSettingsPane.setLayout(new JideBoxLayout(mapObjectSettingsPane, JideBoxLayout.PAGE_AXIS, 6)); + JScrollPane mapObjectSettingsScroller = new JScrollPane(mapObjectSettingsPane); + mapObjectSettingsScroller.getVerticalScrollBar().setUnitIncrement(16); + objectGroupDetailsSplitter.setRightComponent(mapObjectSettingsScroller); + pane.add(objectGroupDetailsSplitter, JideBoxLayout.VARY); + } + pane.revalidate(); + pane.repaint(); + } + + public void updateMapObjectSettingsPane(JPanel pane, final MapObject selected, final FieldUpdateListener listener) { + pane.removeAll(); + boolean needVary = true; + if (selected instanceof ContainerArea) { + droplistBox = addDroplistBox(pane, ((TMXMap)target).getProject(), "Droplist: ", ((ContainerArea)selected).droplist, ((TMXMap)target).writable, listener); + } else if (selected instanceof KeyArea) { + dialogueBox = addDialogueBox(pane, ((TMXMap)target).getProject(), "Message when locked: ", ((KeyArea)selected).dialogue, ((TMXMap)target).writable, listener); + requirementTypeCombo = addEnumValueBox(pane, "Requirement type: ", Requirement.RequirementType.values(), ((KeyArea)selected).requirement.type, ((TMXMap)target).writable, listener); + requirementParamsPane = new JPanel(); + requirementParamsPane.setLayout(new JideBoxLayout(requirementParamsPane, JideBoxLayout.PAGE_AXIS, 6)); + pane.add(requirementParamsPane, JideBoxLayout.FIX); + updateRequirementParamsPane(requirementParamsPane, ((KeyArea)selected).requirement, listener); + } else if (selected instanceof MapChange) { + areaField = addTextField(pane, "Area ID: ", ((MapChange)selected).name, ((TMXMap)target).writable, listener); + mapBox = addMapBox(pane, ((TMXMap)target).getProject(), "Target map: ", ((MapChange)selected).map, ((TMXMap)target).writable, listener); + targetAreaCombo = new JComboBox(); + if (((MapChange)selected).map != null) { + ((MapChange)selected).map.link(); + targetAreaCombo.setModel(new DefaultComboBoxModel((((MapChange)selected).map.getMapchangesNames().toArray()))); + } + targetAreaCombo.setEditable(false); + targetAreaCombo.setEnabled(((TMXMap)target).writable); + targetAreaCombo.setSelectedItem(((MapChange)selected).place_id); + JPanel tACPane = new JPanel(); + tACPane.setLayout(new JideBoxLayout(tACPane, JideBoxLayout.LINE_AXIS, 6)); + tACPane.add(new JLabel("Target mapchange area ID: "), JideBoxLayout.FIX); + tACPane.add(targetAreaCombo, JideBoxLayout.VARY); + JButton nullifyTargetArea = new JButton(new ImageIcon(DefaultIcons.getNullifyIcon())); + tACPane.add(nullifyTargetArea, JideBoxLayout.FIX); + nullifyTargetArea.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + targetAreaCombo.setSelectedItem(null); + listener.valueChanged(targetAreaCombo, null); + } + }); + targetAreaCombo.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + listener.valueChanged(targetAreaCombo, targetAreaCombo.getSelectedItem()); + } + }); + pane.add(tACPane, JideBoxLayout.FIX); + } else if (selected instanceof ReplaceArea) { + //TODO replace area edition + } else if (selected instanceof RestArea) { + pane.add(new JLabel("Rest areas have no parameters"), JideBoxLayout.FIX); + } else if (selected instanceof ScriptArea) { + evaluateTriggerBox = addEnumValueBox(pane, "Evaluate on every: ", ScriptArea.EvaluationTrigger.values(), ((ScriptArea)selected).trigger_type, ((TMXMap)target).writable, listener); + dialogueBox = addDialogueBox(pane, ((TMXMap)target).getProject(), "Script: ", ((ScriptArea)selected).dialogue, ((TMXMap)target).writable, listener); + } else if (selected instanceof SignArea) { + dialogueBox = addDialogueBox(pane, ((TMXMap)target).getProject(), "Message: ", ((SignArea)selected).dialogue, ((TMXMap)target).writable, listener); + } else if (selected instanceof SpawnArea) { + areaField = addTextField(pane, "Spawn group ID: ", ((SpawnArea)selected).name, ((TMXMap)target).writable, listener); + quantityField = addIntegerField(pane, "Number of spawned NPCs: ", ((SpawnArea)selected).quantity, false, ((TMXMap)target).writable, listener); + npcListModel = new SpawnGroupNpcListModel((SpawnArea) selected); + npcList = new JList(npcListModel); + npcList.setCellRenderer(new GDERenderer(true, ((TMXMap)target).writable)); + npcList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + npcList.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + ATContentStudio.frame.openEditor((JSONElement)npcList.getSelectedValue()); + ATContentStudio.frame.selectInTree((JSONElement)npcList.getSelectedValue()); + } + } + }); + npcList.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + ATContentStudio.frame.openEditor((JSONElement)npcList.getSelectedValue()); + ATContentStudio.frame.selectInTree((JSONElement)npcList.getSelectedValue()); + } + } + }); + JScrollPane npcListScroller = new JScrollPane(npcList); + npcListScroller.getVerticalScrollBar().setUnitIncrement(16); + pane.add(npcListScroller, JideBoxLayout.VARY); + needVary = false; + } + if (needVary) pane.add(new JPanel(), JideBoxLayout.VARY); + pane.revalidate(); + pane.repaint(); + } + + public void updateRequirementParamsPane(JPanel pane, Requirement requirement, FieldUpdateListener listener) { + boolean writable = ((TMXMap)target).writable; + Project project = ((TMXMap)target).getProject(); + pane.removeAll(); + if (requirement.type != null) { + switch (requirement.type) { + case consumedBonemeals: + case spentGold: + requirementObj = null; + requirementObjId = null; + requirementValue = addIntegerField(pane, "Quantity: ", requirement.required_value, false, writable, listener); + break; + case hasActorCondition: + requirementObj = addActorConditionBox(pane, project, "Actor Condition: ", (ActorCondition) requirement.required_obj, writable, listener); + requirementObjId = null; + requirementValue = null; + break; + case inventoryKeep: + case inventoryRemove: + case usedItem: + requirementObj = addItemBox(pane, project, "Item: ", (Item) requirement.required_obj, writable, listener); + requirementObjId = null; + requirementValue = addIntegerField(pane, "Quantity: ", requirement.required_value, false, writable, listener); + break; + case killedMonster: + requirementObj = addNPCBox(pane, project, "Monster: ", (NPC) requirement.required_obj, writable, listener); + requirementObjId = null; + requirementValue = addIntegerField(pane, "Quantity: ", requirement.required_value, false, writable, listener); + break; + case questLatestProgress: + case questProgress: + requirementObj = addQuestBox(pane, project, "Quest: ", (Quest) requirement.required_obj, writable, listener); + requirementObjId = null; + requirementValue = addIntegerField(pane, "Quest stage: ", requirement.required_value, false, writable, listener); + break; + case skillLevel: + requirementObj = null; + requirementObjId = addTextField(pane, "Skill ID:", requirement.required_obj_id, writable, listener); + requirementValue = addIntegerField(pane, "Level: ", requirement.required_value, false, writable, listener); + break; + case timerElapsed: + requirementObj = null; + requirementObjId = addTextField(pane, "Timer ID:", requirement.required_obj_id, writable, listener); + requirementValue = addIntegerField(pane, "Timer value: ", requirement.required_value, false, writable, listener); + break; + case wear: + requirementObj = addItemBox(pane, project, "Item: ", (Item) requirement.required_obj, writable, listener); + requirementObjId = null; + requirementValue = null; + break; + } + } + requirementNegated = addBooleanBasedCheckBox(pane, "Negate this requirement.", requirement.negated, writable, listener); + pane.revalidate(); + pane.repaint(); + } + + public JPanel getXmlEditorPane() { + JPanel pane = new JPanel(); + pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS, 6)); + editorPane = new RSyntaxTextArea(); + editorPane.setText(((TMXMap)target).toXml()); + editorPane.setEditable(false); + editorPane.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_XML); + pane.add(editorPane, JideBoxLayout.VARY); + return pane; + } + + public void updateXmlViewText(String text) { + editorPane.setText(text); + } + + public static JList addTMXMapSpritesheetsList(JPanel pane, TMXMap tmxMap) { + final JList list = new JList(new TMXMapSpritesheetsListModel(tmxMap)); + list.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + ATContentStudio.frame.openEditor((Spritesheet)list.getSelectedValue()); + ATContentStudio.frame.selectInTree((Spritesheet)list.getSelectedValue()); + } + } + }); + list.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + ATContentStudio.frame.openEditor((Spritesheet)list.getSelectedValue()); + ATContentStudio.frame.selectInTree((Spritesheet)list.getSelectedValue()); + } + } + }); + list.setCellRenderer(new SpritesheetCellRenderer(true)); + JScrollPane scroller = new JScrollPane(list); + scroller.setBorder(BorderFactory.createTitledBorder("Spritesheets used in this map.")); + pane.add(scroller, JideBoxLayout.FIX); + return list; + } + + public class LayerListModel implements ListModel { + + public TMXMap map; + + public LayerListModel(TMXMap map) { + this.map = map; + } + + @Override + public int getSize() { + return map.tmxMap.getLayerCount(); + } + + @Override + public Object getElementAt(int index) { + return map.tmxMap.getLayer(index); + } + + + public void objectChanged(tiled.core.MapLayer layer) { + int index = map.tmxMap.getLayerIndex(layer); + for (ListDataListener l : listeners) { + l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index, index)); + } + } + + public void addObject(tiled.core.MapLayer layer) { + map.addLayer(layer); + int index = map.tmxMap.getLayerIndex(layer); + for (ListDataListener l : listeners) { + l.intervalAdded(new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index)); + } + } + + public void removeObject(tiled.core.MapLayer layer) { + int index = map.tmxMap.getLayerIndex(layer); + map.removeLayer(layer); + for (ListDataListener l : listeners) { + l.intervalRemoved(new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index)); + } + } + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + + + } + + public class LayerListRenderer extends DefaultListCellRenderer { + private static final long serialVersionUID = -6182599528961565957L; + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (c instanceof JLabel) { + JLabel label = (JLabel)c; + tiled.core.MapLayer layer = (tiled.core.MapLayer)value; + label.setText(layer.getName()); + if (layer instanceof tiled.core.TileLayer) { + label.setIcon(new ImageIcon(DefaultIcons.getTileLayerIcon())); + } else if (layer instanceof tiled.core.ObjectGroup) { + label.setIcon(new ImageIcon(DefaultIcons.getObjectLayerIcon())); + } + } + return c; + } + } + + public class MapObjectsListModel implements ListModel { + + public MapObjectGroup group; + + public MapObjectsListModel(MapObjectGroup group) { + this.group = group; + } + + @Override + public int getSize() { + return group.mapObjects.size(); + } + + @Override + public Object getElementAt(int index) { + return group.mapObjects.get(index); + } + + public void objectChanged(MapObject area) { + for (ListDataListener l : listeners) { + l.contentsChanged(new ListDataEvent(groupObjectsList, ListDataEvent.CONTENTS_CHANGED, group.mapObjects.indexOf(area), group.mapObjects.indexOf(area))); + } + } + + public void addObject(MapObject area) { + group.mapObjects.add(area); + int index = group.mapObjects.indexOf(area); + for (ListDataListener l : listeners) { + l.intervalAdded(new ListDataEvent(groupObjectsList, ListDataEvent.INTERVAL_ADDED, index, index)); + } + } + + public void removeObject(MapObject area) { + int index = group.mapObjects.indexOf(area); + group.mapObjects.remove(area); + for (ListDataListener l : listeners) { + l.intervalRemoved(new ListDataEvent(groupObjectsList, ListDataEvent.INTERVAL_REMOVED, index, index)); + } + } + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + + } + + public class GroupObjectsRenderer extends DefaultListCellRenderer { + private static final long serialVersionUID = -6182599528961565957L; + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (c instanceof JLabel) { + ((JLabel)c).setText(((MapObject)value).name); + ((JLabel)c).setIcon(new ImageIcon(((MapObject)value).getIcon())); + } + return c; + } + } + + public class SpawnGroupNpcListModel implements ListModel { + + public SpawnArea area; + + public SpawnGroupNpcListModel(SpawnArea area) { + this.area = area; + } + + @Override + public int getSize() { + return area.spawnGroup.size(); + } + + @Override + public Object getElementAt(int index) { + return area.spawnGroup.get(index); + } + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + + } + + public class TMXViewer extends JPanel implements Scrollable { + + private static final long serialVersionUID = 2845032142029325865L; + public tiled.core.MapObject highlighted = null; + private MapRenderer renderer; + private FieldUpdateListener listener; + + public boolean resizing = false; + public boolean moving = false; + + public Rectangle getResizeHitArea() { + //16x16 px square in the lower right corner of area + return new Rectangle(selectedMapObject.x + selectedMapObject.w - 16, selectedMapObject.y + selectedMapObject.h - 16, 16, 16); + } + + public Rectangle getMoveHitArea() { + //16x16 px square in the upper left corner of area + return new Rectangle(selectedMapObject.x, selectedMapObject.y, 16, 16); + } + + public Rectangle getSelectHitArea(MapObject obj) { + //16x16 px square in the upper left corner of area + return new Rectangle(obj.x, obj.y, 16, 16); + } + + public Point getClosestTileCorner(Point p) { + return new Point(getClosestMultiple(p.x, 32), getClosestMultiple(p.y, 32)); + } + + public int getClosestMultiple(int num, int ref) { + int rest = num % ref; + int result = num - rest; + if (rest >= ref / 2) { + result += ref; + } + return result; + } + + public TMXViewer(final TMXMap map, FieldUpdateListener listener) { + this.listener = listener; + renderer = createRenderer(map.tmxMap); + + setPreferredSize(renderer.getMapSize()); + setOpaque(true); + + addMouseListener(new MouseAdapter() { + + @Override + public void mouseClicked(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + if (!moving && !resizing) { + select: for (MapObjectGroup group : map.groups) { + if (group.visible) { + for (MapObject obj : group.mapObjects) { + if (getSelectHitArea(obj).contains(e.getPoint())) { + TMXMapEditor.this.selectMapObject(obj); + break select; + } + } + } + } + } + } + } + }); + + if (((TMXMap)target).writable) { + addMouseListener(new MouseAdapter() { + + @Override + public void mouseReleased(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + resizing = false; + moving = false; + } + } + + @Override + public void mousePressed(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + if (selectedMapObject != null && selectedLayer.isVisible()) { + if (getResizeHitArea().contains(e.getPoint())) { + resizing = true; + } else if (getMoveHitArea().contains(e.getPoint())) { + moving = true; + } + } + } + } + + }); + + addMouseMotionListener(new MouseMotionListener() { + @Override + public void mouseMoved(MouseEvent e) { + if (selectedMapObject == null) return; + if (!resizing && !moving) { + if (getResizeHitArea().contains(e.getPoint())) { + setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR)); + } else if (getMoveHitArea().contains(e.getPoint())) { + setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); + } else { + setCursor(Cursor.getDefaultCursor()); + } + } + } + + @Override + public void mouseDragged(MouseEvent e) { + if (selectedMapObject == null) return; + boolean valueChanged = false; + if (resizing) { + Point p = getClosestTileCorner(e.getPoint()); + if (p.x > selectedMapObject.x) { + int oldW = selectedMapObject.w; + selectedMapObject.w = Math.min(p.x - selectedMapObject.x, map.tmxMap.getWidth() * map.tmxMap.getTileWidth()); + if (selectedMapObject.w != oldW) valueChanged = true; + } + if (p.y > selectedMapObject.y) { + int oldH = selectedMapObject.h; + selectedMapObject.h = Math.min(p.y - selectedMapObject.y, map.tmxMap.getHeight() * map.tmxMap.getTileHeight()); + if (selectedMapObject.h != oldH) valueChanged = true; + } + } else if (moving) { + Point p = getClosestTileCorner(e.getPoint()); + if (p.x + selectedMapObject.w <= map.tmxMap.getWidth() * map.tmxMap.getTileWidth()) { + int oldX = selectedMapObject.x; + selectedMapObject.x = Math.max(p.x, 0); + if (selectedMapObject.x != oldX) valueChanged = true; + } + if (p.y + selectedMapObject.h <= map.tmxMap.getHeight() * map.tmxMap.getTileHeight()) { + int oldY = selectedMapObject.y; + selectedMapObject.y = Math.max(p.y, 0); + if (selectedMapObject.y != oldY) valueChanged = true; + } + } + if (valueChanged) { + TMXViewer.this.listener.valueChanged(TMXViewer.this, null); + TMXViewer.this.revalidate(); + TMXViewer.this.repaint(); + } + } + }); + } + } + + public void setHighlight(tiled.core.MapObject selected) { + highlighted = selected; + invalidate(); + } + + public void paintComponent(Graphics g) { + final Graphics2D g2d = (Graphics2D) g.create(); + final Rectangle clip = g2d.getClipBounds(); + + // Draw a gray background + g2d.setPaint(new Color(100, 100, 100)); + g2d.fill(clip); + + // Draw each tile map layer + boolean paintSelected = false; + for (tiled.core.MapLayer layer : ((TMXMap)target).tmxMap) { + if (layer instanceof tiled.core.TileLayer && layer.isVisible()) { + renderer.paintTileLayer(g2d, (tiled.core.TileLayer) layer); + } else if (layer instanceof tiled.core.ObjectGroup && layer.isVisible()) { + paintSelected |= paintObjectGroup(g2d, (tiled.core.ObjectGroup) layer); + } + } + if (paintSelected) { + //TODO make this less ugly..... visually speaking. + g2d.setColor(new Color(190, 20, 20)); + g2d.drawRect(selectedMapObject.x + selectedMapObject.w - 16, selectedMapObject.y + selectedMapObject.h - 16, 15, 15); + g2d.drawRect(selectedMapObject.x + selectedMapObject.w - 12, selectedMapObject.y + selectedMapObject.h - 12, 11, 11); + drawObject(selectedMapObject, g2d, new Color(190, 20, 20)); + } + + g2d.dispose(); + } + + private boolean paintObjectGroup(Graphics2D g2d, tiled.core.ObjectGroup layer) { + boolean paintSelected = false; + for (MapObjectGroup group : ((TMXMap)target).groups) { + if (group.tmxGroup == layer) { + for (MapObject object : group.mapObjects) { + if (object == selectedMapObject) { + paintSelected = true; + continue; + } else { + drawObject(object, g2d, new Color(20, 20, 190)); + } + } + break; + } + } + return paintSelected; + } + + private void drawObject(MapObject object, Graphics2D g2d, Color color) { + g2d.setPaint(color); + g2d.drawRect(object.x+1, object.y+1, object.w-3, object.h-3); + g2d.drawRect(object.x+2, object.y+2, object.w-5, object.h-5); + g2d.setPaint(color.darker().darker()); + g2d.drawLine(object.x, object.y + object.h - 1, object.x + object.w - 1, object.y + object.h - 1); + g2d.drawLine(object.x + object.w - 1, object.y, object.x + object.w - 1, object.y + object.h - 1); + g2d.drawLine(object.x + 3, object.y + 3, object.x + object.w - 4, object.y + 3); + g2d.drawLine(object.x + 3, object.y + 3, object.x + 3, object.y + object.h - 4); + g2d.setPaint(color.brighter().brighter().brighter()); + g2d.drawLine(object.x, object.y, object.x + object.w - 1, object.y); + g2d.drawLine(object.x, object.y, object.x, object.y + object.h - 1); + g2d.drawLine(object.x + 3, object.y + object.h - 4, object.x + object.w - 4, object.y + object.h - 4); + g2d.drawLine(object.x + object.w - 4, object.y + 3, object.x + object.w - 4, object.y + object.h - 4); + Image img = object.getIcon(); + g2d.setColor(new Color(255, 255, 255, 120)); + g2d.fillRect(object.x + 2, object.y + 2, img.getWidth(null), img.getHeight(null)); + g2d.drawImage(object.getIcon(), object.x + 2, object.y + 2, null); + } + + private MapRenderer createRenderer(tiled.core.Map map) { + switch (map.getOrientation()) { + case tiled.core.Map.ORIENTATION_ORTHOGONAL: + return new OrthogonalRenderer(map); + default: + return null; + } + } + + public Dimension getPreferredScrollableViewportSize() { + return getPreferredSize(); + } + + public int getScrollableUnitIncrement(Rectangle visibleRect, + int orientation, int direction) { + if (orientation == SwingConstants.HORIZONTAL) + return ((TMXMap)target).tmxMap.getTileWidth(); + else + return ((TMXMap)target).tmxMap.getTileHeight(); + } + + public int getScrollableBlockIncrement(Rectangle visibleRect, + int orientation, int direction) { + if (orientation == SwingConstants.HORIZONTAL) { + final int tileWidth = ((TMXMap)target).tmxMap.getTileWidth(); + return (visibleRect.width / tileWidth - 1) * tileWidth; + } else { + final int tileHeight = ((TMXMap)target).tmxMap.getTileHeight(); + return (visibleRect.height / tileHeight - 1) * tileHeight; + } + } + + public boolean getScrollableTracksViewportWidth() { + return false; + } + + public boolean getScrollableTracksViewportHeight() { + return false; + } + + } + + public static class TMXMapSpritesheetsListModel implements ListModel { + + TMXMap map; + + public TMXMapSpritesheetsListModel(TMXMap map) { + this.map = map; + } + + @Override + public int getSize() { + return map.usedSpritesheets.size(); + } + + @Override + public Object getElementAt(int index) { + for (Spritesheet sheet : map.usedSpritesheets) { + if (index == 0) return sheet; + index --; + } + return null; + } + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + + public void fireListChanged() { + for (ListDataListener l : listeners) { + l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, this.getSize())); + } + } + } + + public static class SpritesheetCellRenderer extends DefaultListCellRenderer { + + private static final long serialVersionUID = 6819681566800482793L; + + private boolean includeType = false; + + public SpritesheetCellRenderer(boolean includeType) { + super(); + this.includeType = includeType; + + } + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (value == null) { + label.setText("none"); + } else { + if (includeType && ((Spritesheet)value).getDataType() != null) { + label.setText(((Spritesheet)value).getDataType().toString()+"/"+((Spritesheet)value).getDesc()); + } else { + label.setText(((Spritesheet)value).getDesc()); + } + if (((Spritesheet)value).getIcon() == null) { + Notification.addError("Unable to find icon for "+((Spritesheet)value).getDesc()); + } else { + label.setIcon(new ImageIcon(((Spritesheet)value).getIcon())); + } + } + return label; + } + } + + @Override + public void targetUpdated() { + this.name = ((TMXMap)target).getDesc(); + updateMessage(); + } + + + + + protected void selectMapObject(MapObject obj) { + for (MapObjectGroup group : ((TMXMap)target).groups) { + if (group.mapObjects.contains(obj)) { + layerList.setSelectedValue(group.tmxGroup, true); + groupObjectsList.setSelectedValue(obj, true); + } + } + } + + public JButton createButtonPane(JPanel pane, final Project proj, final TMXMap map, final FieldUpdateListener listener) { + final JButton gdeIcon = new JButton(new ImageIcon(DefaultIcons.getTiledIconImage())); + JPanel savePane = new JPanel(); + savePane.add(gdeIcon, JideBoxLayout.FIX); + savePane.setLayout(new JideBoxLayout(savePane, JideBoxLayout.LINE_AXIS, 6)); + if (map.writable) { + if (map.getDataType() == GameSource.Type.altered) { + savePane.add(message = new JLabel(ALTERED_MESSAGE), JideBoxLayout.FIX); + } else if (map.getDataType() == GameSource.Type.created) { + savePane.add(message = new JLabel(CREATED_MESSAGE), JideBoxLayout.FIX); + } + JButton save = new JButton(SAVE); + save.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (map.state != TMXMap.State.saved) { + map.save(); + ATContentStudio.frame.nodeChanged(map); + } + } + }); + savePane.add(save, JideBoxLayout.FIX); + JButton delete = new JButton(DELETE); + if (map.getDataType() == GameSource.Type.altered) { + delete.setText(REVERT); + } + delete.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ATContentStudio.frame.closeEditor(map); + map.childrenRemoved(new ArrayList()); + map.delete(); + } + }); + savePane.add(delete, JideBoxLayout.FIX); + } else { + if (proj.getMap(map.id) != map) { + savePane.add(message = new JLabel(ALTERED_EXISTS_MESSAGE), JideBoxLayout.FIX); + JButton makeWritable = new JButton(GO_TO_ALTERED); + makeWritable.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (map.getProject().getMap(map.id) != map) { + ATContentStudio.frame.openEditor(map.getProject().getMap(map.id)); + ATContentStudio.frame.closeEditor(map); + ATContentStudio.frame.selectInTree(map.getProject().getMap(map.id)); + } + } + }); + savePane.add(makeWritable, JideBoxLayout.FIX); + + } else { + savePane.add(message = new JLabel(READ_ONLY_MESSAGE), JideBoxLayout.FIX); + JButton makeWritable = new JButton(ALTER); + makeWritable.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (map.getProject().getMap(map.id) == map) { + map.getProject().makeWritable(map); + } + if (map.getProject().getMap(map.id) != map) { + ATContentStudio.frame.openEditor(map.getProject().getMap(map.id)); + ATContentStudio.frame.closeEditor(map); + ATContentStudio.frame.selectInTree(map.getProject().getMap(map.id)); + } + } + }); + savePane.add(makeWritable, JideBoxLayout.FIX); + } + } + JButton prev = new JButton(new ImageIcon(DefaultIcons.getArrowLeftIcon())); + JButton next = new JButton(new ImageIcon(DefaultIcons.getArrowRightIcon())); + savePane.add(prev, JideBoxLayout.FIX); + savePane.add(next, JideBoxLayout.FIX); + if (map.getParent().getIndex(map) == 0) { + prev.setEnabled(false); + } + if (map.getParent().getIndex(map) == map.getParent().getChildCount() - 1) { + next.setEnabled(false); + } + prev.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ProjectTreeNode prevNode = (ProjectTreeNode) map.getParent().getChildAt(map.getParent().getIndex(map) - 1); + if (prevNode != null && prevNode instanceof GameDataElement) { + ATContentStudio.frame.openEditor((GameDataElement) prevNode); + } + } + }); + next.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ProjectTreeNode nextNode = (ProjectTreeNode) map.getParent().getChildAt(map.getParent().getIndex(map) + 1); + if (nextNode != null && nextNode instanceof GameDataElement) { + ATContentStudio.frame.openEditor((GameDataElement) nextNode); + } + } + }); + //Placeholder. Fills the eventual remaining space. + savePane.add(new JPanel(), JideBoxLayout.VARY); + pane.add(savePane, JideBoxLayout.FIX); + return gdeIcon; + } + + + + public class MapFieldUpdater implements FieldUpdateListener { + @Override + public void valueChanged(JComponent source, Object value) { + TMXMap map = (TMXMap) target; + boolean modified = true; + if (source == layerNameField) { + selectedLayer.setName((String) value); + if (selectedLayer instanceof tiled.core.ObjectGroup){ + map.getGroup((tiled.core.ObjectGroup) selectedLayer).name = (String) value; + } + layerListModel.objectChanged(selectedLayer); + } else if (source == layerVisibleBox) { + selectedLayer.setVisible(layerVisibleBox.isSelected()); + if (selectedLayer instanceof tiled.core.ObjectGroup) { + map.getGroup((tiled.core.ObjectGroup) selectedLayer).visible = layerVisibleBox.isSelected(); + } + modified = false; + tmxViewer.revalidate(); + tmxViewer.repaint(); + } else if (source == layerList) { + modified = false; + tmxViewer.revalidate(); + tmxViewer.repaint(); + } else if (source == groupObjectsList) { + modified = false; + tmxViewer.revalidate(); + tmxViewer.repaint(); + } else if (source == areaField) { + if (selectedMapObject instanceof SpawnArea) { + SpawnArea area = (SpawnArea)selectedMapObject; + if (area.spawnGroup != null && !area.spawnGroup.isEmpty()) { + for (NPC npc : area.spawnGroup) { + npc.removeBacklink(map); + } + } + area.name = (String) value; + selectedMapObject.link(); + npcList.setModel(new SpawnGroupNpcListModel(area)); + groupObjectsListModel.objectChanged(area); + npcList.revalidate(); + npcList.repaint(); + tmxViewer.revalidate(); + tmxViewer.repaint(); + } else if (selectedMapObject instanceof MapChange) { + MapChange area = (MapChange) selectedMapObject; + area.name = (String) value; + groupObjectsListModel.objectChanged(area); + } + } else if (source == targetAreaCombo) { + if (selectedMapObject instanceof MapChange) { + MapChange area = (MapChange) selectedMapObject; + area.place_id = (String) value; + } + } else if (source == outsideBox) { + map.outside = (Integer)value; + } else if (source == droplistBox) { + if (selectedMapObject instanceof ContainerArea) { + ContainerArea area = (ContainerArea)selectedMapObject; + if (area.droplist != null) { + area.droplist.removeBacklink(map); + } + area.droplist = (Droplist) value; + if (area.droplist != null) { + area.name = area.droplist.id; + } else { + area.name = null; + } + groupObjectsListModel.objectChanged(area); + tmxViewer.revalidate(); + tmxViewer.repaint(); + } + } else if (source == dialogueBox) { + if (selectedMapObject instanceof KeyArea) { + KeyArea area = (KeyArea) selectedMapObject; + if (area.dialogue != null) { + area.dialogue.removeBacklink(map); + } + area.dialogue = (Dialogue) value; + if (area.dialogue != null) { + area.dialogue_id = area.dialogue.id; + } else { + area.dialogue_id = null; + } + tmxViewer.revalidate(); + tmxViewer.repaint(); + } else if (selectedMapObject instanceof ScriptArea) { + ScriptArea area = (ScriptArea) selectedMapObject; + if (area.dialogue != null) { + area.dialogue.removeBacklink(map); + } + area.dialogue = (Dialogue) value; + if (area.dialogue != null) { + area.name = area.dialogue.id; + } else { + area.name = null; + } + groupObjectsListModel.objectChanged(area); + tmxViewer.revalidate(); + tmxViewer.repaint(); + } else if (selectedMapObject instanceof SignArea) { + SignArea area = (SignArea) selectedMapObject; + if (area.dialogue != null) { + area.dialogue.removeBacklink(map); + } + area.dialogue = (Dialogue) value; + if (area.dialogue != null) { + area.name = area.dialogue.id; + } else { + area.name = null; + } + groupObjectsListModel.objectChanged(area); + tmxViewer.revalidate(); + tmxViewer.repaint(); + } + } else if (source == mapBox) { + if (selectedMapObject instanceof MapChange) { + MapChange area = (MapChange) selectedMapObject; + if (area.map != null) { + area.map.removeBacklink(map); + } + area.map = (TMXMap) value; + if (area.map != null) { + area.map_id = area.map.id; + targetAreaCombo.setModel(new DefaultComboBoxModel((area.map.getMapchangesNames().toArray()))); + } else { + area.map_id = null; + } + tmxViewer.revalidate(); + tmxViewer.repaint(); + } + } else if (source == evaluateTriggerBox) { + if (selectedMapObject instanceof ScriptArea) { + ScriptArea area = (ScriptArea) selectedMapObject; + area.trigger_type = (ScriptArea.EvaluationTrigger) value; + } + } else if (source == quantityField) { + if (selectedMapObject instanceof SpawnArea) { + SpawnArea area = (SpawnArea) selectedMapObject; + area.quantity = (Integer) value; + } + } else if (source == requirementTypeCombo) { + if (selectedMapObject instanceof KeyArea) { + KeyArea area = (KeyArea) selectedMapObject; + area.requirement.changeType((Requirement.RequirementType)requirementTypeCombo.getSelectedItem()); + updateRequirementParamsPane(requirementParamsPane, area.requirement, this); + } + } else if (source == requirementObj) { + if (selectedMapObject instanceof KeyArea) { + KeyArea area = (KeyArea) selectedMapObject; + area.requirement.required_obj = (GameDataElement) value; + if (area.requirement.required_obj != null) { + area.requirement.required_obj_id = area.requirement.required_obj.id; + } else { + area.requirement.required_obj_id = null; + } + } + } else if (source == requirementObjId) { + if (selectedMapObject instanceof KeyArea) { + KeyArea area = (KeyArea) selectedMapObject; + area.requirement.required_obj_id = (String) value; + area.requirement.required_obj = null; + } + } else if (source == requirementValue) { + if (selectedMapObject instanceof KeyArea) { + KeyArea area = (KeyArea) selectedMapObject; + area.requirement.required_value = (Integer) value; + } + } else if (source == requirementNegated) { + if (selectedMapObject instanceof KeyArea) { + KeyArea area = (KeyArea) selectedMapObject; + area.requirement.negated = (Boolean) value; + } + } + if (modified) { + if (map.state != GameDataElement.State.modified) { + map.state = GameDataElement.State.modified; + TMXMapEditor.this.name = map.getDesc(); + map.childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(TMXMapEditor.this); + } + updateXmlViewText(map.toXml()); + } + } + } + + + public void updateMessage() { + + //TODO make this a full update of the button panel. + TMXMap node = (TMXMap) target; + if (node.writable) { + if (node.getDataType() == GameSource.Type.altered) { + message.setText(ALTERED_MESSAGE); + } else if (node.getDataType() == GameSource.Type.created) { + message.setText(CREATED_MESSAGE); + } + } else if (node.getProject().getMap(node.id) != node) { + message.setText(ALTERED_EXISTS_MESSAGE); + } else { + message.setText(READ_ONLY_MESSAGE); + } + message.revalidate(); + message.repaint(); + } + + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapEditor.java new file mode 100644 index 0000000..83f1d83 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapEditor.java @@ -0,0 +1,511 @@ +package com.gpl.rpg.atcontentstudio.ui.map; + +import java.awt.BorderLayout; +import java.awt.Point; +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.util.ArrayList; +import java.util.List; + +import javax.swing.ButtonGroup; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JSlider; +import javax.swing.JViewport; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.SaveEvent; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; +import com.gpl.rpg.atcontentstudio.model.maps.Worldmap; +import com.gpl.rpg.atcontentstudio.model.maps.WorldmapSegment; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; +import com.gpl.rpg.atcontentstudio.ui.Editor; +import com.gpl.rpg.atcontentstudio.ui.SaveItemsWizard; +import com.jidesoft.swing.ComboBoxSearchable; +import com.jidesoft.swing.JideBoxLayout; +import com.jidesoft.swing.JideTabbedPane; + +public class WorldMapEditor extends Editor { + + private static final long serialVersionUID = -8358238912588729094L; + + public EditMode editMode = EditMode.moveViewSelect; + + public enum EditMode { + moveViewSelect, + moveMaps, + deleteMaps, + addMap + } + + public String mapBeingAddedID = null; + + public WorldMapEditor(WorldmapSegment worldmap) { + target = worldmap; + this.name = worldmap.id; + this.icon = new ImageIcon(worldmap.getIcon()); + setLayout(new BorderLayout()); + + JideTabbedPane editorTabsHolder = new JideTabbedPane(JideTabbedPane.BOTTOM); + editorTabsHolder.setTabShape(JideTabbedPane.SHAPE_FLAT); + editorTabsHolder.setUseDefaultShowCloseButtonOnTab(false); + editorTabsHolder.setShowCloseButtonOnTab(false); + add(editorTabsHolder, BorderLayout.CENTER); + + editorTabsHolder.add("Map", buildSegmentTab(worldmap)); + } + + @Override + public void targetUpdated() { + // TODO Auto-generated method stub + + } + + + private JPanel buildSegmentTab(final WorldmapSegment worldmap) { + JPanel pane = new JPanel(); + pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS)); + + addLabelField(pane, "Worldmap File: ", ((Worldmap)worldmap.getParent()).worldmapFile.getAbsolutePath()); + pane.add(createButtonPane(worldmap), JideBoxLayout.FIX); + + final WorldMapView mapView = new WorldMapView(worldmap); + JScrollPane mapScroller = new JScrollPane(mapView); + final JViewport vPort = mapScroller.getViewport(); + + final JSlider zoomSlider = new JSlider(WorldMapView.MIN_ZOOM, WorldMapView.MAX_ZOOM, (int)(mapView.zoomLevel / WorldMapView.ZOOM_RATIO)); + zoomSlider.setSnapToTicks(true); + zoomSlider.setMinorTickSpacing(WorldMapView.INC_ZOOM); + zoomSlider.setOrientation(JSlider.VERTICAL); + JPanel zoomSliderPane = new JPanel(); + zoomSliderPane.setLayout(new JideBoxLayout(zoomSliderPane, JideBoxLayout.PAGE_AXIS)); + zoomSliderPane.add(zoomSlider, JideBoxLayout.VARY); + zoomSliderPane.add(new JLabel(new ImageIcon(DefaultIcons.getZoomIcon())), JideBoxLayout.FIX); + + if (target.writable) { + JPanel mapToolsPane = new JPanel(); + mapToolsPane.setLayout(new JideBoxLayout(mapToolsPane, JideBoxLayout.LINE_AXIS)); + ButtonGroup mapToolsGroup = new ButtonGroup(); + JRadioButton moveView = new JRadioButton("Select maps"); + mapToolsGroup.add(moveView); + mapToolsPane.add(moveView, JideBoxLayout.FIX); + JRadioButton moveMaps = new JRadioButton("Move selected map(s)"); + mapToolsGroup.add(moveMaps); + mapToolsPane.add(moveMaps, JideBoxLayout.FIX); + JRadioButton deleteMaps = new JRadioButton("Delete maps"); + mapToolsGroup.add(deleteMaps); + mapToolsPane.add(deleteMaps, JideBoxLayout.FIX); + JRadioButton addMap = new JRadioButton("Add map"); + mapToolsGroup.add(addMap); + mapToolsPane.add(addMap, JideBoxLayout.FIX); + final GDEComboModel mapComboModel = new GDEComboModel(worldmap.getProject(), null){ + private static final long serialVersionUID = 2638082961277241764L; + @Override + public Object getTypedElementAt(int index) { + return project.getMap(index); + } + @Override + public int getSize() { + return project.getMapCount()+1; + } + }; + final MyComboBox mapBox = new MyComboBox(TMXMap.class, mapComboModel); + mapBox.setRenderer(new GDERenderer(false, false)); + new ComboBoxSearchable(mapBox){ + @Override + protected String convertElementToString(Object object) { + if (object == null) return "none"; + else return ((GameDataElement)object).getDesc(); + } + }; + mapBox.setEnabled(false); + mapToolsPane.add(mapBox, JideBoxLayout.FIX); + + mapToolsPane.add(new JPanel(), JideBoxLayout.VARY); + moveView.setSelected(true); + pane.add(mapToolsPane, JideBoxLayout.FIX); + + moveView.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + editMode = EditMode.moveViewSelect; + mapBox.setEnabled(false); + if (mapBeingAddedID != null) { + mapView.mapLocations.remove(mapBeingAddedID); + mapBeingAddedID = null; + mapView.revalidate(); + mapView.repaint(); + } + } + }); + + moveMaps.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + editMode = EditMode.moveMaps; + mapBox.setEnabled(false); + if (mapBeingAddedID != null) { + mapView.mapLocations.remove(mapBeingAddedID); + mapBeingAddedID = null; + mapView.revalidate(); + mapView.repaint(); + } + } + }); + + deleteMaps.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + editMode = EditMode.deleteMaps; + mapBox.setEnabled(false); + if (mapBeingAddedID != null) { + mapView.mapLocations.remove(mapBeingAddedID); + mapBeingAddedID = null; + mapView.revalidate(); + mapView.repaint(); + } + } + }); + + addMap.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + editMode = EditMode.addMap; + mapBox.setEnabled(true); + if (mapBox.getSelectedItem() != null) { + mapBeingAddedID = ((TMXMap)mapBox.getSelectedItem()).id; + } + } + }); + + mapBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (mapBox.getSelectedItem() == null) { + mapBeingAddedID = null; + } else { + mapBeingAddedID = ((TMXMap)mapBox.getSelectedItem()).id; + } + } + }); + + } + + JPanel mapZoomPane = new JPanel(); + mapZoomPane.setLayout(new BorderLayout()); + mapZoomPane.add(zoomSliderPane, BorderLayout.WEST); + mapZoomPane.add(mapScroller, BorderLayout.CENTER); + pane.add(mapZoomPane, JideBoxLayout.VARY); + + zoomSlider.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + + Rectangle view = vPort.getViewRect(); + + float oldZoomLevel = mapView.zoomLevel; + mapView.zoomLevel = zoomSlider.getValue() * WorldMapView.ZOOM_RATIO; + + int newCenterX = (int) (view.getCenterX() / oldZoomLevel * mapView.zoomLevel); + int newCenterY = (int) (view.getCenterY() / oldZoomLevel * mapView.zoomLevel); + + view.x = newCenterX - (view.width / 2); + view.y = newCenterY - (view.height / 2); + + mapView.scrollRectToVisible(view); + mapView.revalidate(); + mapView.repaint(); + } + }); + + MouseAdapter mouseListener = new MouseAdapter() { + final int skipRecomputeDefault = 5; + Point dragStart = null; + int skipRecompute = 0; + + @Override + public void mouseClicked(MouseEvent e) { + String selectedMap = null; + boolean update = false; + int x = (int) (e.getX() / mapView.zoomLevel); + int y = (int) (e.getY() / mapView.zoomLevel); + for (String s : mapView.mapLocations.keySet()) { + if (mapView.mapLocations.get(s).contains(x, y)) { + selectedMap = s; + break; + } + } + if (editMode == EditMode.moveViewSelect) { + if (selectedMap == null) return; + if (e.getButton() == MouseEvent.BUTTON1) { + if (e.isControlDown() || e.isShiftDown()) { + if (mapView.selected.contains(selectedMap)) { + mapView.selected.remove(selectedMap); + update = true; + } else { + mapView.selected.add(selectedMap); + update = true; + } + } else { + mapView.selected.clear(); + mapView.selected.add(selectedMap); + update = true; + } + if (e.getClickCount() == 2) { + ATContentStudio.frame.openEditor(worldmap.getProject().getMap(selectedMap)); + } + } + } else if (editMode == EditMode.deleteMaps) { + worldmap.mapLocations.remove(selectedMap); + worldmap.labelLocations.remove(selectedMap); + mapView.selected.remove(selectedMap); + mapView.updateFromModel(); + update = true; + } else if (editMode == EditMode.addMap && mapBeingAddedID != null) { + if (e.getButton() == MouseEvent.BUTTON1) { + mapView.recomputeSize(); + mapView.pushToModel(); + } + mapView.updateFromModel(); + update = true; + mapBeingAddedID = null; + } + if (update) { + mapView.revalidate(); + mapView.repaint(); + } + } + + @Override + public void mousePressed(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + dragStart = e.getLocationOnScreen(); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + dragStart = null; + if (editMode == EditMode.moveMaps) { + mapView.pushToModel(); + } + } + + @Override + public void mouseMoved(MouseEvent e) { + if (editMode == EditMode.addMap && mapBeingAddedID != null) { + int x = (int) (e.getX() / mapView.zoomLevel); + int y = (int) (e.getY() / mapView.zoomLevel); + TMXMap map = target.getProject().getMap(mapBeingAddedID); + int w = map.tmxMap.getWidth() * WorldMapView.TILE_SIZE; + int h = map.tmxMap.getHeight() * WorldMapView.TILE_SIZE; + x -= w / 2; + x -= x % WorldMapView.TILE_SIZE; + y -= h / 2; + y -= y % WorldMapView.TILE_SIZE; + mapView.mapLocations.put(mapBeingAddedID, new Rectangle(x, y, w, h)); + if (--skipRecompute <= 0) { + if (mapView.recomputeSize()) { + skipRecompute = skipRecomputeDefault; + } + } + mapView.revalidate(); + mapView.repaint(); + } + } + + @Override + public void mouseDragged(MouseEvent e) { + if (dragStart == null) return; + int deltaX = e.getXOnScreen() - dragStart.x; + int deltaY = e.getYOnScreen() - dragStart.y; + + if (editMode != EditMode.moveMaps) { + Rectangle view = vPort.getViewRect(); + view.setLocation(view.x - deltaX, view.y - deltaY); + mapView.scrollRectToVisible(view); + + dragStart = e.getLocationOnScreen(); + } else { + int mapDeltaX = (int) ((deltaX / mapView.zoomLevel)); + int mapDeltaY = (int) ((deltaY / mapView.zoomLevel)); + + mapDeltaX -= mapDeltaX % WorldMapView.TILE_SIZE; + mapDeltaY -= mapDeltaY % WorldMapView.TILE_SIZE; + + for (String s : mapView.selected) { + mapView.mapLocations.get(s).x = (worldmap.mapLocations.get(s).x * WorldMapView.TILE_SIZE) + mapDeltaX; + mapView.mapLocations.get(s).y = (worldmap.mapLocations.get(s).y * WorldMapView.TILE_SIZE) + mapDeltaY; + } + + if (--skipRecompute <= 0) { + if (mapView.recomputeSize()) { + skipRecompute = skipRecomputeDefault; + } + } + + mapView.revalidate(); + mapView.repaint(); + } + } + }; + + mapView.addMouseListener(mouseListener); + mapView.addMouseMotionListener(mouseListener); + + return pane; + } + + public JPanel createButtonPane(final WorldmapSegment node) { + final JButton gdeIcon = new JButton(new ImageIcon(DefaultIcons.getUIMapImage())); + JPanel savePane = new JPanel(); + savePane.add(gdeIcon, JideBoxLayout.FIX); + savePane.setLayout(new JideBoxLayout(savePane, JideBoxLayout.LINE_AXIS, 6)); + if (node.writable) { + if (node.getDataType() == GameSource.Type.altered) { + savePane.add(message = new JLabel(ALTERED_MESSAGE), JideBoxLayout.FIX); + } else if (node.getDataType() == GameSource.Type.created) { + savePane.add(message = new JLabel(CREATED_MESSAGE), JideBoxLayout.FIX); + } + JButton save = new JButton(SAVE); + save.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (node.getParent() instanceof Worldmap) { + if (node.state != GameDataElement.State.saved) { + final List events = node.attemptSave(); + if (events == null) { + ATContentStudio.frame.nodeChanged(node); + } else { + new Thread() { + @Override + public void run() { + new SaveItemsWizard(events, node).setVisible(true); + } + }.start(); + } + } + } + } + }); + savePane.add(save, JideBoxLayout.FIX); + JButton delete = new JButton(DELETE); + if (node.getDataType() == GameSource.Type.altered) { + delete.setText(REVERT); + } + delete.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ATContentStudio.frame.closeEditor(node); + node.childrenRemoved(new ArrayList()); + if (node.getParent() instanceof Worldmap) { + ((Worldmap)node.getParent()).remove(node); + node.save(); + for (GameDataElement backlink : node.getBacklinks()) { + backlink.elementChanged(node, node.getProject().getWorldmapSegment(node.id)); + } + } + } + }); + savePane.add(delete, JideBoxLayout.FIX); + } else { + if (node.getProject().alteredContent.getWorldmapSegment(node.id) != null) { + savePane.add(message = new JLabel(ALTERED_EXISTS_MESSAGE), JideBoxLayout.FIX); + JButton makeWritable = new JButton("Go to altered"); + makeWritable.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (node.getProject().getWorldmapSegment(node.id) != node) { + ATContentStudio.frame.openEditor(node.getProject().getWorldmapSegment(node.id)); + ATContentStudio.frame.closeEditor(node); + ATContentStudio.frame.selectInTree(node.getProject().getWorldmapSegment(node.id)); + } + } + }); + savePane.add(makeWritable, JideBoxLayout.FIX); + + } else { + savePane.add(message = new JLabel(READ_ONLY_MESSAGE), JideBoxLayout.FIX); + JButton makeWritable = new JButton("Alter"); + makeWritable.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (node.getProject().getWorldmapSegment(node.id) == node) { + node.getProject().makeWritable(node); + } + if (node.getProject().getWorldmapSegment(node.id) != node) { + ATContentStudio.frame.openEditor(node.getProject().getWorldmapSegment(node.id)); + ATContentStudio.frame.closeEditor(node); + ATContentStudio.frame.selectInTree(node.getProject().getWorldmapSegment(node.id)); + } + updateMessage(); + } + }); + savePane.add(makeWritable, JideBoxLayout.FIX); + } + } + JButton prev = new JButton(new ImageIcon(DefaultIcons.getArrowLeftIcon())); + JButton next = new JButton(new ImageIcon(DefaultIcons.getArrowRightIcon())); + savePane.add(prev, JideBoxLayout.FIX); + savePane.add(next, JideBoxLayout.FIX); + if (node.getParent().getIndex(node) == 0) { + prev.setEnabled(false); + } + if (node.getParent().getIndex(node) == node.getParent().getChildCount() - 1) { + next.setEnabled(false); + } + prev.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ProjectTreeNode prevNode = (ProjectTreeNode) node.getParent().getChildAt(node.getParent().getIndex(node) - 1); + if (prevNode != null && prevNode instanceof GameDataElement) { + ATContentStudio.frame.openEditor((GameDataElement) prevNode); + } + } + }); + next.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ProjectTreeNode nextNode = (ProjectTreeNode) node.getParent().getChildAt(node.getParent().getIndex(node) + 1); + if (nextNode != null && nextNode instanceof GameDataElement) { + ATContentStudio.frame.openEditor((GameDataElement) nextNode); + } + } + }); + //Placeholder. Fills the eventual remaining space. + savePane.add(new JPanel(), JideBoxLayout.VARY); + return savePane; + } + + public void updateMessage() { + + //TODO make this a full update of the button panel. + WorldmapSegment node = (WorldmapSegment) target; + if (node.writable) { + if (node.getDataType() == GameSource.Type.altered) { + message.setText(ALTERED_MESSAGE); + } else if (node.getDataType() == GameSource.Type.created) { + message.setText(CREATED_MESSAGE); + } + } else if (node.getProject().alteredContent.getWorldmapSegment(node.id) != null) { + message.setText(ALTERED_EXISTS_MESSAGE); + } else { + message.setText(READ_ONLY_MESSAGE); + } + message.revalidate(); + message.repaint(); + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapView.java b/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapView.java new file mode 100644 index 0000000..2d34b36 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapView.java @@ -0,0 +1,245 @@ +package com.gpl.rpg.atcontentstudio.ui.map; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.JComponent; +import javax.swing.Scrollable; + +import tiled.view.MapRenderer; +import tiled.view.OrthogonalRenderer; + +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; +import com.gpl.rpg.atcontentstudio.model.maps.WorldmapSegment; + +public class WorldMapView extends JComponent implements Scrollable { + + private static final long serialVersionUID = -4111374378777093799L; + + public static final int TILE_SIZE = 32; + public static final int MIN_ZOOM = 5; + public static final int MAX_ZOOM = 250; + public static final int INC_ZOOM = 5; + public static final float ZOOM_RATIO = 0.01f; + + WorldmapSegment worldmap; + Project proj; + + + public Map mapLocations = new HashMap(); + + public Set selected = new HashSet(); + + public float zoomLevel = 0.1f; + int sizeX = 0, sizeY = 0; + int offsetX = 0, offsetY = 0; + + public WorldMapView(WorldmapSegment worldmap) { + this.worldmap = worldmap; + this.proj = worldmap.getProject(); + updateFromModel(); + } + + private void paintOnGraphics(Graphics2D g2) { + g2.setPaint(new Color(100, 100, 100)); + g2.fillRect(0, 0, sizeX, sizeY); + + g2.setPaint(new Color(255, 0, 0)); + g2.setStroke(new BasicStroke(4)); + + for (String s : mapLocations.keySet()) { + + int x = mapLocations.get(s).x; + int y = mapLocations.get(s).y; + + g2.translate(x, y); + + + TMXMap map = proj.getMap(s); + if (map == null) continue; + MapRenderer renderer = new OrthogonalRenderer(map.tmxMap); + + // Draw each tile map layer + for (tiled.core.MapLayer layer : ((TMXMap)map).tmxMap) { + if (layer instanceof tiled.core.TileLayer && layer.isVisible()) { + if (layer.getName().equalsIgnoreCase("walkable")) continue; + renderer.paintTileLayer(g2, (tiled.core.TileLayer) layer); + } else if (layer instanceof tiled.core.ObjectGroup && layer.isVisible()) { +// paintObjectGroup(g2, map, (tiled.core.ObjectGroup) layer); + } + } + if (selected.contains(s)) { + g2.drawRect(0, 0, map.tmxMap.getWidth() * TILE_SIZE, map.tmxMap.getHeight() * TILE_SIZE); + } + + g2.translate(-x, -y); + + } + } + + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2 = (Graphics2D)g.create(); + try { + g2.scale(zoomLevel, zoomLevel); +// g2.drawImage(img, 0, 0, null); + paintOnGraphics(g2); + + } finally { + g2.dispose(); + } + + } + +// private boolean paintObjectGroup(Graphics2D g2d, TMXMap map, tiled.core.ObjectGroup layer) { +// boolean paintSelected = false; +// for (MapObjectGroup group : map.groups) { +// if (group.tmxGroup == layer) { +// for (MapObject object : group.mapObjects) { +// drawObject(object, g2d, new Color(20, 20, 190)); +// } +// break; +// } +// } +// return paintSelected; +// } +// +// private void drawObject(MapObject object, Graphics2D g2d, Color color) { +// g2d.setPaint(color); +// g2d.drawRect(object.x+1, object.y+1, object.w-3, object.h-3); +// g2d.drawRect(object.x+2, object.y+2, object.w-5, object.h-5); +// g2d.setPaint(color.darker().darker()); +// g2d.drawLine(object.x, object.y + object.h - 1, object.x + object.w - 1, object.y + object.h - 1); +// g2d.drawLine(object.x + object.w - 1, object.y, object.x + object.w - 1, object.y + object.h - 1); +// g2d.drawLine(object.x + 3, object.y + 3, object.x + object.w - 4, object.y + 3); +// g2d.drawLine(object.x + 3, object.y + 3, object.x + 3, object.y + object.h - 4); +// g2d.setPaint(color.brighter().brighter().brighter()); +// g2d.drawLine(object.x, object.y, object.x + object.w - 1, object.y); +// g2d.drawLine(object.x, object.y, object.x, object.y + object.h - 1); +// g2d.drawLine(object.x + 3, object.y + object.h - 4, object.x + object.w - 4, object.y + object.h - 4); +// g2d.drawLine(object.x + object.w - 4, object.y + 3, object.x + object.w - 4, object.y + object.h - 4); +// Image img = object.getIcon(); +// g2d.setColor(new Color(255, 255, 255, 120)); +// g2d.fillRect(object.x + 2, object.y + 2, img.getWidth(null), img.getHeight(null)); +// g2d.drawImage(object.getIcon(), object.x + 2, object.y + 2, null); +// } + + @Override + public Dimension getPreferredSize() { + return new Dimension((int)(zoomLevel * sizeX), (int)(zoomLevel * sizeY)); + } + + @Override + public Dimension getPreferredScrollableViewportSize() { + return getPreferredSize(); + } + + @Override + public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { + return TILE_SIZE; + } + + @Override + public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { + return 4 * TILE_SIZE; + } + + @Override + public boolean getScrollableTracksViewportWidth() { + return false; + } + + @Override + public boolean getScrollableTracksViewportHeight() { + return false; + } + + public boolean recomputeSize() { + sizeX = sizeY = 0; + boolean originMoved = false; + int minX = Integer.MAX_VALUE, minY= Integer.MAX_VALUE; + for (String s : mapLocations.keySet()) { + int x = mapLocations.get(s).x; + int w = proj.getMap(s).tmxMap.getWidth() * TILE_SIZE; + int y = mapLocations.get(s).y; + int h = proj.getMap(s).tmxMap.getHeight() * TILE_SIZE; + + sizeX = Math.max(sizeX, x + w); + sizeY = Math.max(sizeY, y + h); + minX = Math.min(minX, x); + minY = Math.min(minY, y); + } + + if (minX != 0) { + for (String s : mapLocations.keySet()) { + mapLocations.get(s).x -= minX; + } + sizeX -= minX; + offsetX += minX; + originMoved = true; + } + + if (minY != 0) { + for (String s : mapLocations.keySet()) { + mapLocations.get(s).y -= minY; + } + sizeY -= minY; + offsetY += minY; + originMoved = true; + } + return originMoved; + } + + public void updateFromModel() { + mapLocations.clear(); + sizeX = sizeY = 0; + offsetX = worldmap.segmentX * TILE_SIZE; + offsetY = worldmap.segmentY * TILE_SIZE; + for (String s : worldmap.mapLocations.keySet()) { + int x = worldmap.mapLocations.get(s).x * TILE_SIZE; + int w = proj.getMap(s).tmxMap.getWidth() * TILE_SIZE; + int y = worldmap.mapLocations.get(s).y * TILE_SIZE; + int h = proj.getMap(s).tmxMap.getHeight() * TILE_SIZE; + + sizeX = Math.max(sizeX, x + w); + sizeY = Math.max(sizeY, y + h); + + mapLocations.put(s, new Rectangle(x, y, w, h)); + } + } + + public void pushToModel() { + worldmap.segmentX = offsetX / TILE_SIZE; + worldmap.segmentY = offsetY / TILE_SIZE; + worldmap.mapLocations.clear(); + for (String s : mapLocations.keySet()) { + int x = mapLocations.get(s).x / TILE_SIZE; + int y = mapLocations.get(s).y / TILE_SIZE; + + worldmap.mapLocations.put(s, new Point(x, y)); + } + + List toRemove = new ArrayList(); + for (String s : worldmap.labelLocations.keySet()) { + if (!mapLocations.containsKey(s)) { + toRemove.add(s); + } + } + for (String s : toRemove) { + worldmap.labelLocations.remove(s); + } + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/saves/SavedGameEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/saves/SavedGameEditor.java new file mode 100644 index 0000000..7944465 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/saves/SavedGameEditor.java @@ -0,0 +1,32 @@ +package com.gpl.rpg.atcontentstudio.ui.saves; + +import java.awt.BorderLayout; + +import javax.swing.ImageIcon; +import javax.swing.JScrollPane; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.saves.SavedGame; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; +import com.gpl.rpg.atcontentstudio.ui.Editor; + +public class SavedGameEditor extends Editor { + + private static final long serialVersionUID = 6055910379650778737L; + + public SavedGameEditor(SavedGame save) { + this.name = save.loadedSave.displayInfo; + this.icon = new ImageIcon(DefaultIcons.getHeroIcon()); + this.target = save; + setLayout(new BorderLayout()); + add(new JScrollPane(new com.gpl.rpg.andorstrainer.ui.SavedGameEditor(save.loadedSave, ATContentStudio.frame)), BorderLayout.CENTER); + + } + + @Override + public void targetUpdated() { + // TODO Auto-generated method stub + + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/sprites/SpriteChooser.java b/src/com/gpl/rpg/atcontentstudio/ui/sprites/SpriteChooser.java new file mode 100644 index 0000000..004689d --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/sprites/SpriteChooser.java @@ -0,0 +1,235 @@ +package com.gpl.rpg.atcontentstudio.ui.sprites; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.ButtonGroup; +import javax.swing.ImageIcon; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JToggleButton; +import javax.swing.ScrollPaneConstants; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet; +import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet.Category; + + +public class SpriteChooser extends JDialog { + + private static final long serialVersionUID = -6018113265015159521L; + + private static final int STD_WIDTH = 32; + private static final int STD_HEIGHT = 32; + private static final int MAX_PER_ROW = 10; + + public static Map> cache = new HashMap>(); + + public static SpriteChooser getChooser(Project proj, Spritesheet.Category category) { + if (cache.get(proj) == null) { + cache.put(proj, new HashMap()); + } + if (cache.get(proj).get(category) == null) { + cache.get(proj).put(category, new SpriteChooser(proj, category)); + } + SpriteChooser wanted = cache.get(proj).get(category); + wanted.group.clearSelection(); + wanted.selectedIconId = null; +// wanted.selectedOne = null; + wanted.listener = null; +// wanted.ok.setEnabled(false); + wanted.pack(); + return wanted; + } + + private ButtonGroup group; +// private IconButton selectedOne = null; +// private JButton ok; +// private JButton cancel; + + public String selectedIconId = null; + + public SpriteChooser(Project proj, Category category) { + super(ATContentStudio.frame); + setTitle("Select a sprite"); + setModalityType(ModalityType.APPLICATION_MODAL); + List spritesheets = new ArrayList(); + for (Spritesheet sheet : proj.baseContent.gameSprites.spritesheets) { + if (sheet.category == category) { + spritesheets.add(sheet); + } + } + + + JPanel pane = new JPanel(); + pane.setLayout(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + c.weightx = 1; + c.weighty = 1; + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.anchor = GridBagConstraints.NORTHWEST; + c.fill = GridBagConstraints.BOTH; + + List reservedSlots = new ArrayList(); + Point nextFreeSlot = new Point(0, 0); + + int i; + Image img; + group = new ButtonGroup(); + for (Spritesheet sheet : spritesheets) { + i = 0; + while ((img = sheet.getImage(i)) != null) { + IconButton button = new IconButton(img, sheet.id, i); + group.add(button); + if (sheet.spriteWidth == STD_WIDTH && sheet.spriteHeight == STD_HEIGHT) { + pane.add(button, c); + c.gridx++; + if (c.gridx >= MAX_PER_ROW) { + c.gridx = 0; + c.gridy++; + } + nextFreeSlot.setLocation(c.gridx, c.gridy); + } else { + c.gridwidth = (sheet.spriteWidth / STD_WIDTH) + (sheet.spriteWidth % STD_WIDTH == 0 ? 0 : 1); + c.gridheight = (sheet.spriteHeight / STD_HEIGHT) + (sheet.spriteHeight % STD_HEIGHT == 0 ? 0 : 1); + boolean slotOk = false; + while (!slotOk) { + slotOk = true; + for (int x = c.gridx; x < c.gridx + c.gridwidth; x++) { + for (int y = c.gridy; y < c.gridy + c.gridwidth; y++) { + if (reservedSlots.contains(new Point(x, y))) { + slotOk = false; + } + } + } + if (slotOk && c.gridx + c.gridwidth > MAX_PER_ROW) { + c.gridx = 0; + c.gridy++; + slotOk = false; + } + } + pane.add(button, c); + for (int x = c.gridx; x < c.gridx + c.gridwidth; x++) { + for (int y = c.gridy; y < c.gridy + c.gridwidth; y++) { + reservedSlots.add(new Point(x, y)); + } + } + c.gridwidth = 1; + c.gridheight = 1; + c.gridx = nextFreeSlot.x; + c.gridy = nextFreeSlot.y; + } + while (reservedSlots.contains(nextFreeSlot)) { + c.gridx++; + if (c.gridx >= MAX_PER_ROW) { + c.gridx = 0; + c.gridy++; + } + nextFreeSlot.setLocation(c.gridx, c.gridy); + } + i++; + } + } + +// ok = new JButton("Ok"); +// cancel = new JButton("Cancel"); + + c.gridx = 0; + boolean emptyLine = false; + while (!emptyLine) { + c.gridy++; + emptyLine = true; + for (i = MAX_PER_ROW - 1; i >= 0; i--) { + if (reservedSlots.contains(new Point(i, c.gridy))) { + emptyLine = false; + continue; + } + } + } + +// JPanel buttonPane = new JPanel(); +// buttonPane.add(cancel, BorderLayout.WEST); +// buttonPane.add(ok, BorderLayout.EAST); + + JPanel wrapper = new JPanel(); + wrapper.setLayout(new BorderLayout()); + JScrollPane scroller = new JScrollPane(pane); + scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + wrapper.add(scroller, BorderLayout.CENTER); +// wrapper.add(buttonPane, BorderLayout.SOUTH); + +// ok.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// selectedIconId = selectedOne.sheetId+":"+selectedOne.spriteIndex; +// SpriteChooser.this.setVisible(false); +// SpriteChooser.this.dispose(); +// if (listener != null) listener.iconSelected(selectedIconId); +// } +// }); + +// cancel.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// selectedIconId = null; +// SpriteChooser.this.setVisible(false); +// SpriteChooser.this.dispose(); +// if (listener != null) listener.iconSelected(null); +// } +// }); + + setContentPane(wrapper); + } + + private SpriteChooser.SelectionListener listener = null; + + public void setSelectionListener(SpriteChooser.SelectionListener l) { + listener = l; + } + + + public class IconButton extends JToggleButton { + + private static final long serialVersionUID = 7559407153561178455L; + + public String sheetId; + public int spriteIndex; + + public IconButton(Image img, String sheetId, int spriteIndex) { + super(new ImageIcon(img)); + this.sheetId = sheetId; + this.spriteIndex = spriteIndex; + setToolTipText(sheetId+":"+spriteIndex); + addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (IconButton.this.isSelected()) { + selectedIconId = IconButton.this.sheetId+":"+IconButton.this.spriteIndex; + SpriteChooser.this.setVisible(false); + SpriteChooser.this.dispose(); + if (listener != null) listener.iconSelected(selectedIconId); + } + } + }); + } + } + + public static interface SelectionListener { + public void iconSelected(String selected); + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/sprites/SpritesheetEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/sprites/SpritesheetEditor.java new file mode 100644 index 0000000..d2f4893 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/sprites/SpritesheetEditor.java @@ -0,0 +1,431 @@ +package com.gpl.rpg.atcontentstudio.ui.sprites; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Image; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.BorderFactory; +import javax.swing.DefaultListCellRenderer; +import javax.swing.ImageIcon; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.JTable; +import javax.swing.ListModel; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.event.TableModelListener; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.table.TableModel; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; +import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet; +import com.gpl.rpg.atcontentstudio.ui.Editor; +import com.gpl.rpg.atcontentstudio.ui.FieldUpdateListener; +import com.jidesoft.swing.JideBoxLayout; +import com.jidesoft.swing.JideTabbedPane; + +public class SpritesheetEditor extends Editor { + + private static final long serialVersionUID = 3956109815682889863L; + + Map editorTabs = new HashMap(); + JideTabbedPane editorTabsHolder; + + private JSpinner widthField; + private JSpinner heightField; + private JCheckBox animatedBox; + private JComboBox categoryBox; + private JPanel spriteViewPane; + + + public static JComponent getWarningLabel() { + JLabel label = new JLabel( + "" + + "The data presented here is not part of the game.
" + + "What you change here will be changed in your ATCS project.
" + + "None of this is exported to JSON or TMX, although it must be set correctly in order to choose tiles & icons correctly.
" + + "
"); + return label; + } + + public SpritesheetEditor(Spritesheet sheet) { + super(); + this.icon = new ImageIcon(sheet.getIcon(0)); + this.name = sheet.id; + this.target = sheet; + + JPanel pane = new JPanel(); + + final FieldUpdateListener listener = new SpritesheetFieldUpdater(); + + pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS, 6)); + + add(getWarningLabel(), JideBoxLayout.FIX); + addLabelField(pane, "Spritesheet ID: ", sheet.id); + addLabelField(pane, "File: ", sheet.spritesheetFile.getAbsolutePath()); + widthField = addIntegerField(pane, "Sprite width (px): ", sheet.spriteWidth, false, true, listener); + heightField = addIntegerField(pane, "Sprite height (px): ", sheet.spriteHeight, false, true, listener); + animatedBox = addBooleanBasedCheckBox(pane, "Is an animation", sheet.animated, true, listener); + categoryBox = addEnumValueBox(pane, "Category: ", Spritesheet.Category.values(), sheet.category, true, listener); + + spriteViewPane = new JPanel(); + updateView(spriteViewPane); + pane.add(spriteViewPane, JideBoxLayout.FIX); + + addBacklinksList(pane, sheet); + + //Placeholder. Fills the eventual remaining space. + pane.add(new JPanel(), JideBoxLayout.VARY); + + setLayout(new BorderLayout()); + editorTabsHolder = new JideTabbedPane(JideTabbedPane.BOTTOM); + editorTabsHolder.setTabShape(JideTabbedPane.SHAPE_FLAT); + editorTabsHolder.setUseDefaultShowCloseButtonOnTab(false); + editorTabsHolder.setShowCloseButtonOnTab(false); + add(editorTabsHolder, BorderLayout.CENTER); + + JScrollPane sheetScroller = new JScrollPane(pane); + sheetScroller.getVerticalScrollBar().setUnitIncrement(16); + editorTabsHolder.add("Spritesheet",sheetScroller); + JScrollPane rawScroller = new JScrollPane(new JLabel(new ImageIcon(sheet.spritesheet))); + rawScroller.getVerticalScrollBar().setUnitIncrement(16); + editorTabsHolder.add("Raw image", rawScroller); + } + + private Thread animator = new Thread(); + private boolean animate = true; + private JLabel iconLabel; + private List icons = null; + + public void updateView(JPanel pane) { + Spritesheet sheet = (Spritesheet)target; + pane.removeAll(); + pane.setLayout(new BorderLayout()); + if (sheet.animated) { + iconLabel = new JLabel(); + iconLabel.setBackground(Color.WHITE); + if (icons == null) { + icons = new ArrayList(); + } else { + icons.clear(); + } + int i = 0; + Image img; + while ((img = sheet.getImage(i++)) != null) { + icons.add(new ImageIcon(img)); + } + if (i > 0) { + iconLabel.setIcon(icons.get(0)); + } + pane.add(iconLabel, BorderLayout.CENTER); + resetAnimator(); + } else { + JTable spritesTable = new JTable(new SpritesheetTableModel(sheet)); + spritesTable.setDefaultRenderer(Image.class, new SpritesheetCellRenderer(sheet)); + spritesTable.setCellSelectionEnabled(true); + spritesTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + spritesTable.getTableHeader().setVisible(false); + Enumeration columns = spritesTable.getColumnModel().getColumns(); + TableColumn col; + while (columns.hasMoreElements()) { + col = columns.nextElement(); + col.setMinWidth(sheet.spriteWidth + 1); + col.setMaxWidth(sheet.spriteWidth + 1); + } + spritesTable.setRowHeight(sheet.spriteHeight + 1); + pane.add(new JScrollPane(spritesTable), BorderLayout.CENTER); + } + } + + + public static JList addBacklinksList(JPanel pane, Spritesheet sheet) { + final JList list = new JList(new SpritesheetsBacklinksListModel(sheet)); + list.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + if (list.getSelectedValue() instanceof TMXMap) { + ATContentStudio.frame.openEditor((TMXMap)list.getSelectedValue()); + ATContentStudio.frame.selectInTree((TMXMap)list.getSelectedValue()); + } else if (list.getSelectedValue() instanceof GameDataElement) { + ATContentStudio.frame.openEditor((GameDataElement)list.getSelectedValue()); + ATContentStudio.frame.selectInTree((GameDataElement)list.getSelectedValue()); + } + } + } + }); + list.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + if (list.getSelectedValue() instanceof TMXMap) { + ATContentStudio.frame.openEditor((TMXMap)list.getSelectedValue()); + ATContentStudio.frame.selectInTree((TMXMap)list.getSelectedValue()); + } else if (list.getSelectedValue() instanceof GameDataElement) { + ATContentStudio.frame.openEditor((GameDataElement)list.getSelectedValue()); + ATContentStudio.frame.selectInTree((GameDataElement)list.getSelectedValue()); + } + } + } + }); + list.setCellRenderer(new BacklinkCellRenderer(true)); + JScrollPane scroller = new JScrollPane(list); + scroller.setBorder(BorderFactory.createTitledBorder("Elements pointing to this spritesheet.")); + pane.add(scroller, JideBoxLayout.FIX); + return list; + } + + public static class SpritesheetTableModel implements TableModel { + + Spritesheet sheet; + + public SpritesheetTableModel(Spritesheet sheet) { + this.sheet = sheet; + } + + @Override + public int getRowCount() { + return (sheet.spritesheet.getHeight() / sheet.spriteHeight) + ((sheet.spritesheet.getHeight() % sheet.spriteHeight) == 0 ? 0 : 1); + } + + @Override + public int getColumnCount() { + return (sheet.spritesheet.getWidth() / sheet.spriteWidth) + ((sheet.spritesheet.getWidth() % sheet.spriteWidth) == 0 ? 0 : 1); + } + + @Override + public String getColumnName(int columnIndex) { + return ""; + } + + @Override + public Class getColumnClass(int columnIndex) { + return Image.class; + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + return sheet.getImage((rowIndex * getColumnCount()) + columnIndex); + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + + } + + List listeners = new ArrayList(); + + @Override + public void addTableModelListener(TableModelListener l) { + listeners.add(l); + } + + @Override + public void removeTableModelListener(TableModelListener l) { + listeners.remove(l); + } + + } + + public static class SpritesheetCellRenderer extends DefaultTableCellRenderer { + private static final long serialVersionUID = -4213756343124247612L; + Spritesheet sheet; + public SpritesheetCellRenderer(Spritesheet sheet) { + super(); + this.sheet = sheet; + } + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + if (c instanceof JLabel) { + ((JLabel)c).setText(""); + if (value != null) { + ((JLabel)c).setIcon(new ImageIcon((Image)value)); + ((JLabel)c).setToolTipText(sheet.id+":"+((row * table.getColumnCount())+column)); + } + + } + return c; + } + } + + @Override + public void setVisible(boolean aFlag) { + super.setVisible(aFlag); + animate = aFlag; + if (aFlag && animator != null) { + resetAnimator(); + } + } + + private void resetAnimator() { + new Thread() { + public void run() { + if (animator != null && animator.isAlive()) { + try { + animator.join(); + } catch (InterruptedException e) {} + } + animate = true; + animator = new Thread() { + public void run() { + int i = -1; + while (animate) { + if (icons != null) { + synchronized (icons) { + i = (i + 1) % icons.size(); + iconLabel.setIcon(icons.get(i)); + } + iconLabel.revalidate(); + } + try { + Thread.sleep(100); + } catch (InterruptedException e) {} + } + }; + }; + animator.start(); + } + }.start(); + } + + + public static class SpritesheetsBacklinksListModel implements ListModel { + + Spritesheet sheet; + + public SpritesheetsBacklinksListModel(Spritesheet sheet) { + this.sheet = sheet; + } + + @Override + public int getSize() { + return sheet.getBacklinks().size(); + } + + @Override + public Object getElementAt(int index) { + for (ProjectTreeNode node : sheet.getBacklinks()) { + if (index == 0) return node; + index --; + } + return null; + } + + List listeners = new ArrayList(); + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + + public void fireListChanged() { + for (ListDataListener l : listeners) { + l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, this.getSize())); + } + } + } + + public static class BacklinkCellRenderer extends DefaultListCellRenderer { + + private static final long serialVersionUID = 6819681566800482793L; + + private boolean includeType = false; + + public BacklinkCellRenderer(boolean includeType) { + super(); + this.includeType = includeType; + + } + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (value == null) { + label.setText("none"); + } else { + if (includeType && ((ProjectTreeNode)value).getDataType() != null) { + label.setText(((ProjectTreeNode)value).getDataType().toString()+"/"+((ProjectTreeNode)value).getDesc()); + } else { + label.setText(((ProjectTreeNode)value).getDesc()); + } + if (((ProjectTreeNode)value).getIcon() == null) { + Notification.addError("Unable to find icon for "+((ProjectTreeNode)value).getDesc()); + } else { + label.setIcon(new ImageIcon(((ProjectTreeNode)value).getIcon())); + } + } + return label; + } + + } + + @Override + public void targetUpdated() { + this.icon = new ImageIcon(((Spritesheet)target).getIcon(0)); + this.name = ((Spritesheet)target).id; + } + + public class SpritesheetFieldUpdater implements FieldUpdateListener { + @Override + public void valueChanged(JComponent source, Object value) { + Spritesheet sheet = (Spritesheet) target; + if (source == widthField) { + sheet.spriteWidth = (Integer) value; + sheet.clearCache(); + updateView(spriteViewPane); + spriteViewPane.revalidate(); + spriteViewPane.repaint(); + } else if (source == heightField) { + sheet.spriteHeight = (Integer) value; + sheet.clearCache(); + updateView(spriteViewPane); + spriteViewPane.revalidate(); + spriteViewPane.repaint(); + } else if (source == animatedBox) { + sheet.animated = (Boolean) value; + if (!sheet.animated) { + animate = false; + } + updateView(spriteViewPane); + spriteViewPane.revalidate(); + spriteViewPane.repaint(); + } else if (source == categoryBox) { + sheet.category = (Spritesheet.Category) value; + } + } + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/tools/ElementTableView.java b/src/com/gpl/rpg/atcontentstudio/ui/tools/ElementTableView.java new file mode 100644 index 0000000..3bdd38a --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/tools/ElementTableView.java @@ -0,0 +1,81 @@ +package com.gpl.rpg.atcontentstudio.ui.tools; + +import java.awt.BorderLayout; +import java.util.List; + +import javax.swing.Icon; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.TableModel; + +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.SaveEvent; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.ui.Editor; + +public class ElementTableView extends Editor { + + private static final long serialVersionUID = 8048693233599125878L; + + public ElementTableView(TableModel elementTableModel, String title, Icon icon) { + this.target = new DummyGDE(); + this.name = title; + this.icon = icon; + + setLayout(new BorderLayout()); + + JTable table = new JTable(elementTableModel) { + private static final long serialVersionUID = -2738230330859706440L; + public boolean getScrollableTracksViewportWidth() { + return getPreferredSize().width < getParent().getWidth(); + } + }; + table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); + table.setAutoCreateRowSorter(true); + table.setAutoscrolls(true); + table.setCellSelectionEnabled(false); + table.setRowSelectionAllowed(true); + table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + add(new JScrollPane(table), BorderLayout.CENTER); + } + + @Override + public void targetUpdated() { + + } + + class DummyGDE extends GameDataElement { + + private static final long serialVersionUID = 5889666999423783180L; + + @Override + public GameDataSet getDataSet() {return null;} + + @Override + public String getDesc() {return null;} + + @Override + public void parse() {} + + @Override + public void link() {} + + @Override + public GameDataElement clone() {return null;} + + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) {} + + @Override + public String getProjectFilename() {return null;} + + @Override + public void save() {} + + @Override + public List attemptSave() {return null;} + + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/tools/ItemsTableView.java b/src/com/gpl/rpg/atcontentstudio/ui/tools/ItemsTableView.java new file mode 100644 index 0000000..22a44bd --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/tools/ItemsTableView.java @@ -0,0 +1,214 @@ +package com.gpl.rpg.atcontentstudio.ui.tools; + +import java.util.ArrayList; +import java.util.List; + +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.event.TableModelListener; +import javax.swing.table.TableModel; + +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.gamedata.Item; +import com.gpl.rpg.atcontentstudio.model.gamedata.ItemCategory; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class ItemsTableView extends ElementTableView { + + private static final long serialVersionUID = 1474255176349837609L; + + public ItemsTableView(Project proj) { + super(new ItemsTableModel(proj), "Compare "+proj.getItemCount()+" items.", new ImageIcon(DefaultIcons.getItemIcon())); + } + + private static class ItemsTableModel implements TableModel { + + Project proj; + + public ItemsTableModel(Project proj) { + this.proj = proj; + } + + @Override + public int getRowCount() { +// return proj.getItemCount() + 1; + return proj.getItemCount(); + } + + @Override + public int getColumnCount() { + return 32; + } + + @Override + public String getColumnName(int columnIndex) { + switch (columnIndex) { + case 0: return "Icon"; //Icon + case 1: return "ID"; //ID + case 2: return "Name"; //Name + case 3: return "Folder type"; //Source type (created, altered, source) + case 4: return "Use type"; //Use type ("none", "use", or equip slot name). + case 5: return "Category"; //Category id. + case 6: return "Manually set price ?"; //Has manual price + case 7: return "Price"; //Price + case 8: return "On use/hit - HP min"; + case 9: return "On use/hit - HP max"; + case 10: return "On use/hit - AP min"; + case 11: return "On use/hit - AP max"; + case 12: return "On use/hit - # conditions"; + case 13: return "On kill - HP min"; + case 14: return "On kill - HP max"; + case 15: return "On kill - AP min"; + case 16: return "On kill - AP max"; + case 17: return "On kill - # conditions"; + case 18: return "AD min"; + case 19: return "AD max"; + case 20: return "Max HP"; + case 21: return "Max AP"; + case 22: return "Attack cost"; + case 23: return "AC"; + case 24: return "BC"; + case 25: return "DR"; + case 26: return "CS"; + case 27: return "CM"; + case 28: return "Move cost"; + case 29: return "Use cost"; + case 30: return "Reequip cost"; + case 31: return "# conditions"; + } + return null; + } + + @Override + public Class getColumnClass(int columnIndex) { +// return String.class; + switch (columnIndex) { + case 0: return Icon.class; // Icon + case 1: return String.class; //ID + case 2: return String.class; //Name + case 3: return String.class; //Source type (created, altered, source) + case 4: return String.class; //Use type ("none", "use", or equip slot name). + case 5: return String.class; //Category id. + case 6: return Boolean.class; //Has manual price + case 7: return Integer.class; //Price + case 8: return Integer.class;//"On use/hit - HP min"; + case 9: return Integer.class;//"On use/hit - HP max"; + case 10: return Integer.class;//"On use/hit - AP min"; + case 11: return Integer.class;//"On use/hit - AP max"; + case 12: return Integer.class;//"On use/hit - # conditions"; + case 13: return Integer.class;//"On kill - HP min"; + case 14: return Integer.class;//"On kill - HP max"; + case 15: return Integer.class;//"On kill - AP min"; + case 16: return Integer.class;//"On kill - AP max"; + case 17: return Integer.class;//"On kill - # conditions"; + case 18: return Integer.class;//"AD min"; + case 19: return Integer.class;//"AD max"; + case 20: return Integer.class;//"Max HP"; + case 21: return Integer.class;//"Max AP"; + case 22: return Integer.class;//"Attack cost"; + case 23: return Integer.class;//"AC"; + case 24: return Integer.class;//"BC"; + case 25: return Integer.class;//"DR"; + case 26: return Integer.class;//"CS"; + case 27: return Double.class;//"CM"; + case 28: return Integer.class;//"Move cost"; + case 29: return Integer.class;//"Use cost"; + case 30: return Integer.class;//"Reequip cost"; + case 31: return Integer.class;//"# conditions"; + } + return null; + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { +// if (rowIndex == 0) { +// return getColumnName(columnIndex); +// } +// Item item = proj.getItem(rowIndex - 1); + Item item = proj.getItem(rowIndex); + boolean canUse = item.category != null && item.category.action_type == ItemCategory.ActionType.use; + boolean canEquip = item.category != null && item.category.action_type == ItemCategory.ActionType.equip; + switch (columnIndex) { + case 0: return new ImageIcon(item.getIcon()); //Icon + case 1: return item.id; //ID + case 2: return item.name; //Name + case 3: return item.getDataType().toString(); //Source type (created, altered, source) + case 4: //Use type ("none", "use", or equip slot name). + if (item.category == null) return "none"; + if (item.category.action_type == null) return "none"; + if (item.category.action_type != ItemCategory.ActionType.equip) return item.category.action_type.toString(); + return item.category.slot.toString(); + case 5: return item.category != null ? item.category.id : (item.category_id != null ? item.category_id : null ); //Category id. + case 6: return item.has_manual_price == null ? false : (item.has_manual_price == 1); //Has manual price + case 7: //Price + if (item.has_manual_price == null || item.has_manual_price != 1) return item.computePrice(); + return item.base_market_cost; + case 8: return canUse ? (item.kill_effect != null ? item.kill_effect.hp_boost_min : null) : (item.hit_effect != null ? item.hit_effect.hp_boost_min : null);//"On use/hit - HP min"; + case 9: return canUse ? (item.kill_effect != null ? item.kill_effect.hp_boost_max : null) : (item.hit_effect != null ? item.hit_effect.hp_boost_max : null);//"On use/hit - HP max"; + case 10: return canUse ? (item.kill_effect != null ? item.kill_effect.ap_boost_min : null) : (item.hit_effect != null ? item.hit_effect.ap_boost_min : null);//"On use/hit - AP min"; + case 11: return canUse ? (item.kill_effect != null ? item.kill_effect.ap_boost_max : null) : (item.hit_effect != null ? item.hit_effect.ap_boost_max : null);//"On use/hit - AP max"; + case 12: //"On use/hit - # conditions"; + if (canUse) { + if (item.kill_effect != null && item.kill_effect.conditions_source != null) { + return item.kill_effect.conditions_source.size(); + } + return 0; + } else if (item.hit_effect != null) { + int val = 0; + if (item.hit_effect.conditions_source != null) { + val += item.hit_effect.conditions_source.size(); + } + if (item.hit_effect.conditions_target != null) { + val += item.hit_effect.conditions_target.size(); + } + return val; + } + return null; + case 13: return (!canUse && item.kill_effect != null) ? item.kill_effect.hp_boost_min : null;//"On kill - HP min"; + case 14: return (!canUse && item.kill_effect != null) ? item.kill_effect.hp_boost_max : null;//"On kill - HP max"; + case 15: return (!canUse && item.kill_effect != null) ? item.kill_effect.ap_boost_min : null;//"On kill - AP min"; + case 16: return (!canUse && item.kill_effect != null) ? item.kill_effect.ap_boost_max : null;//"On kill - AP max"; + case 17: return (!canUse && item.kill_effect != null && item.kill_effect.conditions_source != null) ? item.kill_effect.conditions_source.size() : null;//"On kill - # conditions"; + case 18: return (canEquip && item.equip_effect != null) ? item.equip_effect.damage_boost_min : null;//"AD min"; + case 19: return (canEquip && item.equip_effect != null) ? item.equip_effect.damage_boost_max : null;//"AD max"; + case 20: return (canEquip && item.equip_effect != null) ? item.equip_effect.max_hp_boost : null;//"Max HP"; + case 21: return (canEquip && item.equip_effect != null) ? item.equip_effect.max_ap_boost : null;//"Max AP"; + case 22: return (canEquip && item.equip_effect != null) ? item.equip_effect.increase_attack_cost : null;//"Attack cost"; + case 23: return (canEquip && item.equip_effect != null) ? item.equip_effect.increase_attack_chance : null;//"AC"; + case 24: return (canEquip && item.equip_effect != null) ? item.equip_effect.increase_block_chance : null;//"BC"; + case 25: return (canEquip && item.equip_effect != null) ? item.equip_effect.increase_damage_resistance : null;//"DR"; + case 26: return (canEquip && item.equip_effect != null) ? item.equip_effect.increase_critical_skill : null;//"CS"; + case 27: return (canEquip && item.equip_effect != null) ? item.equip_effect.critical_multiplier : null;//"CM"; + case 28: return (canEquip && item.equip_effect != null) ? item.equip_effect.increase_move_cost : null;//"Move cost"; + case 29: return (canEquip && item.equip_effect != null) ? item.equip_effect.increase_use_item_cost : null;//"Use cost"; + case 30: return (canEquip && item.equip_effect != null) ? item.equip_effect.increase_reequip_cost : null;//"Reequip cost"; + case 31: return (canEquip && item.equip_effect != null && item.equip_effect.conditions != null) ? item.equip_effect.conditions.size() : null;//"# conditions"; + } + return null; + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + //not editable. + } + + List listeners = new ArrayList(); + + @Override + public void addTableModelListener(TableModelListener l) { + listeners.add(l); + } + + @Override + public void removeTableModelListener(TableModelListener l) { + listeners.remove(l); + } + + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/tools/NPCsTableView.java b/src/com/gpl/rpg/atcontentstudio/ui/tools/NPCsTableView.java new file mode 100644 index 0000000..2aef2ec --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/tools/NPCsTableView.java @@ -0,0 +1,175 @@ +package com.gpl.rpg.atcontentstudio.ui.tools; + +import java.util.ArrayList; +import java.util.List; + +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.event.TableModelListener; +import javax.swing.table.TableModel; + +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.gamedata.NPC; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class NPCsTableView extends ElementTableView { + + private static final long serialVersionUID = -4196852140899079621L; + + public NPCsTableView(Project proj) { + super(new NPCsTableModel(proj), "Compare "+proj.getNPCCount()+" NPCs.", new ImageIcon(DefaultIcons.getNPCIcon())); + } + + private static class NPCsTableModel implements TableModel { + + Project proj; + + public NPCsTableModel(Project proj) { + this.proj = proj; + } + + @Override + public int getRowCount() { + return proj.getNPCCount(); + } + + @Override + public int getColumnCount() { + return 25; + } + + @Override + public String getColumnName(int columnIndex) { + switch (columnIndex) { + case 0: return "Icon"; //Icon + case 1: return "ID"; //ID + case 2: return "Name"; //Name + case 3: return "Category type"; //Source type (created, altered, source) + case 4: return "Unique"; + case 5: return "Class"; + case 6: return "Movement type"; + case 7: return "Spawngroup"; + case 8: return "Faction"; + case 9: return "HP"; + case 10: return "AP"; + case 11: return "Attack Cost"; + case 12: return "AC"; + case 13: return "BC"; + case 14: return "AD min"; + case 15: return "AD max"; + case 16: return "DR"; + case 17: return "CS"; + case 18: return "CM"; + case 19: return "On hit - HP min"; + case 20: return "On hit - HP max"; + case 21: return "On hit - AP min"; + case 22: return "On hit - AP max"; + case 23: return "On hit - # conditions"; + case 24: return "Experience reward"; + } + return null; + } + + @Override + public Class getColumnClass(int columnIndex) { + switch (columnIndex) { + case 0: return Icon.class; //Icon + case 1: return String.class; //ID + case 2: return String.class; //Name + case 3: return String.class; //Source type (created, altered, source) + case 4: return Boolean.class; //"Unique"; + case 5: return String.class; //"Class"; + case 6: return String.class; //"Movement type"; + case 7: return String.class; //"Spawngroup"; + case 8: return String.class; //"Faction"; + case 9: return Integer.class; //"HP"; + case 10: return Integer.class; //"AP"; + case 11: return Integer.class; //"Attack Cost"; + case 12: return Integer.class; //"AC"; + case 13: return Integer.class; //"BC"; + case 14: return Integer.class; //"AD min"; + case 15: return Integer.class; //"AD max"; + case 16: return Integer.class; //"DR"; + case 17: return Integer.class; //"CS"; + case 18: return Double.class; //"CM"; + case 19: return Integer.class; //"On hit - HP min"; + case 20: return Integer.class; //"On hit - HP max"; + case 21: return Integer.class; //"On hit - AP min"; + case 22: return Integer.class; //"On hit - AP max"; + case 23: return Integer.class; //"On hit - # conditions"; + case 24: return Integer.class; //"Experience reward"; + } + return null; + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + NPC npc = proj.getNPC(rowIndex); + switch (columnIndex) { + case 0: return new ImageIcon(npc.getIcon()); // Icon + case 1: return npc.id; //ID + case 2: return npc.name; //Name + case 3: return npc.getDataType().toString(); //Source type (created, altered, source) + case 4: return npc.unique != null && npc.unique == 1;//"Unique"; + case 5: return npc.monster_class != null ? npc.monster_class.toString() : null; //"Class"; + case 6: return npc.movement_type != null ? npc.movement_type.toString() : null; //"Movement type"; + case 7: return npc.spawngroup_id; //"Spawngroup"; + case 8: return npc.faction_id; //"Faction"; + case 9: return npc.max_hp; //"HP"; + case 10: return npc.max_ap; //"AP"; + case 11: return npc.attack_cost; //"Attack Cost"; + case 12: return npc.attack_chance; //"AC"; + case 13: return npc.block_chance; //"BC"; + case 14: return npc.attack_damage_min; //"AD min"; + case 15: return npc.attack_damage_max; //"AD max"; + case 16: return npc.damage_resistance; //"DR"; + case 17: return npc.critical_skill; //"CS"; + case 18: return npc.critical_multiplier; //"CM"; + case 19: return npc.hit_effect != null ? npc.hit_effect.hp_boost_min : null; //"On hit - HP min"; + case 20: return npc.hit_effect != null ? npc.hit_effect.hp_boost_max : null; //"On hit - HP max"; + case 21: return npc.hit_effect != null ? npc.hit_effect.ap_boost_min : null; //"On hit - AP min"; + case 22: return npc.hit_effect != null ? npc.hit_effect.ap_boost_max : null; //"On hit - AP max"; + case 23: //"On hit - # conditions"; + if (npc.hit_effect != null) { + Integer val = null; + if (npc.hit_effect.conditions_source != null) { + val = npc.hit_effect.conditions_source.size(); + } + if (npc.hit_effect.conditions_target != null) { + if (val == null) val = npc.hit_effect.conditions_target.size(); + else val += npc.hit_effect.conditions_target.size(); + } + return val; + } + return null; + case 24: return npc.getMonsterExperience(); + } + return null; + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + //not editable. + } + + List listeners = new ArrayList(); + + @Override + public void addTableModelListener(TableModelListener l) { + listeners.add(l); + } + + @Override + public void removeTableModelListener(TableModelListener l) { + listeners.remove(l); + } + + } + + +} diff --git a/src/com/gpl/rpg/atcontentstudio/utils/FileUtils.java b/src/com/gpl/rpg/atcontentstudio/utils/FileUtils.java new file mode 100644 index 0000000..d217f2f --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/utils/FileUtils.java @@ -0,0 +1,115 @@ +package com.gpl.rpg.atcontentstudio.utils; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class FileUtils { + + public static void deleteDir(File dir) { + if (dir.exists()) { + for (File f : dir.listFiles()) { + if (f.isDirectory()) { + deleteDir(f); + } else { + f.delete(); + } + } + dir.delete(); + } + } + + public static void copyFile(File sourceLocation , File targetLocation) { + try { + InputStream in = new FileInputStream(sourceLocation); + OutputStream out = new FileOutputStream(targetLocation); + + // Copy the bits from instream to outstream + byte[] buf = new byte[1024]; + int len; + try { + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + } catch (IOException e) { + // TODO Auto-generated catch block + } finally { + try { + in.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + } + try { + out.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + } + } + } catch (FileNotFoundException e1) { + // TODO Auto-generated catch block + } + } + + private static final int BUFFER = 2048; + public static void writeToZip(File folder, File target) { + try { + FileOutputStream dest = new FileOutputStream(target); + ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest)); + zipDir(folder, "", out); + out.flush(); + out.close(); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + + private static void zipDir(File dir, String prefix, ZipOutputStream zos) { + for (File f : dir.listFiles()) { + if (f.isDirectory()) { + zipDir(f, prefix+File.separator+f.getName(), zos); + } else { + FileInputStream fis; + try { + fis = new FileInputStream(f); + BufferedInputStream origin = new BufferedInputStream(fis, BUFFER); + ZipEntry entry = new ZipEntry(prefix+File.separator+f.getName()); + try { + zos.putNextEntry(entry); + int count; + byte data[] = new byte[BUFFER]; + while ((count = origin.read(data, 0, BUFFER)) != -1) { + zos.write(data, 0, count); + zos.flush(); + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + try { + origin.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + + } + +}