diff --git a/.classpath b/.classpath index 520551c..e6df400 100644 --- a/.classpath +++ b/.classpath @@ -4,6 +4,7 @@ + @@ -28,4 +29,4 @@ - + \ No newline at end of file diff --git a/ATCS_JAR.jardesc b/ATCS_JAR.jardesc index c177a5a..0624630 100644 --- a/ATCS_JAR.jardesc +++ b/ATCS_JAR.jardesc @@ -1,7 +1,7 @@ - + - - + + @@ -11,9 +11,9 @@ - - - - + + + + diff --git a/folderIconBase.xcf b/folderIconBase.xcf index 6ee67cf..1a0a6c2 100644 Binary files a/folderIconBase.xcf and b/folderIconBase.xcf differ diff --git a/hacked-libtiled/tiled/core/TileSet.java b/hacked-libtiled/tiled/core/TileSet.java index afd99af..fcacaf7 100644 --- a/hacked-libtiled/tiled/core/TileSet.java +++ b/hacked-libtiled/tiled/core/TileSet.java @@ -98,27 +98,42 @@ public class TileSet implements Iterable File f = new File(imgFilename); - Image image = ImageIO.read(f.getCanonicalFile()); + BufferedImage image = ImageIO.read(f.getCanonicalFile()); if (image == null) { - throw new IOException("Failed to load " + tilebmpFile); + throw new IOException("Failed to load " + imgFilename); } - Toolkit tk = Toolkit.getDefaultToolkit(); + tilebmpFile = f; + tileDimensions = new Rectangle(cutter.getTileDimensions()); + +// 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); - if (transparentColor != null) { - int rgb = transparentColor.getRGB(); - image = tk.createImage( - new FilteredImageSource(image.getSource(), - new TransparentImageFilter(rgb))); - } + importTileBitmap(image, cutter); + } + + public void weakImportTileBitmap(String imgFilename, TileCutter cutter) + throws IOException + { + setTilesetImageFilename(imgFilename); + + File f = new File(imgFilename); - BufferedImage buffered = new BufferedImage( - image.getWidth(null), - image.getHeight(null), - BufferedImage.TYPE_INT_ARGB); - buffered.getGraphics().drawImage(image, 0, 0, null); + tilebmpFile = f; + tileDimensions = new Rectangle(cutter.getTileDimensions()); - importTileBitmap(buffered, cutter); } public void loadFromProject(String name, TMXMap tmxMap, int tileWidth, int tileHeight) { diff --git a/minify/com/whoischarles/util/json/Minify.java b/minify/com/whoischarles/util/json/Minify.java new file mode 100644 index 0000000..bc7c688 --- /dev/null +++ b/minify/com/whoischarles/util/json/Minify.java @@ -0,0 +1,405 @@ +/** + * ---------------------- + * Minify.java 2015-10-04 + * ---------------------- + * + * Copyright (c) 2015 Charles Bihis (www.whoischarles.com) + * + * This work is an adaptation of JSMin.java published by John Reilly which is a translation from C to Java of jsmin.c + * published by Douglas Crockford. Permission is hereby granted to use this Java version under the same conditions as + * the original jsmin.c on which all of these derivatives are based. + * + * + * + * --------------------- + * JSMin.java 2006-02-13 + * --------------------- + * + * Copyright (c) 2006 John Reilly (www.inconspicuous.org) + * + * This work is a translation from C to Java of jsmin.c published by Douglas Crockford. Permission is hereby granted to + * use the Java version under the same conditions as the jsmin.c on which it is based. + * + * + * + * ------------------ + * jsmin.c 2003-04-21 + * ------------------ + * + * Copyright (c) 2002 Douglas Crockford (www.crockford.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the + * Software. + * + * The Software shall be used for Good, not Evil. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.whoischarles.util.json; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PushbackInputStream; +import java.nio.charset.StandardCharsets; + +/** + * Minify.java is written by Charles Bihis (www.whoischarles.com) and is adapted from JSMin.java written by John Reilly + * (www.inconspicuous.org) which is itself a translation of jsmin.c written by Douglas Crockford (www.crockford.com). + * + * @see http://www.unl.edu/ucomm/templatedependents/JSMin.java + * @see http://www.crockford.com/javascript/jsmin.c + */ +public class Minify { + + private static final int EOF = -1; + + private PushbackInputStream in; + private OutputStream out; + private int currChar; + private int nextChar; + private int line; + private int column; + + public static enum Action { + OUTPUT_CURR, DELETE_CURR, DELETE_NEXT + } + + public Minify() { + this.in = null; + this.out = null; + } + + /** + * Minifies the input JSON string. + * + * Takes the input JSON string and deletes the characters which are insignificant to JavaScipt. Comments will be + * removed, tabs will be replaced with spaces, carriage returns will be replaced with line feeds, and most spaces + * and line feeds will be removed. The result will be returned. + * + * @param json The JSON string for which to minify + * @return A minified, yet functionally identical, version of the input JSON string + */ + public String minify(String json) { + InputStream in = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + try { + minify(in, out); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + + return out.toString().trim(); + } + + /** + * Takes an input stream to a JSON string and outputs minified JSON to the output stream. + * + * Takes the input JSON via the input stream and deletes the characters which are insignificant to JavaScript. + * Comments will be removed, tabs will be replaced with spaaces, carriage returns will be replaced with line feeds, + * and most spaces and line feeds will be removed. The result is streamed to the output stream. + * + * @param in The InputStream from which to get the un-minified JSON + * @param out The OutputStream where the resulting minified JSON will be streamed to + * @throws IOException + * @throws UnterminatedRegExpLiteralException + * @throws UnterminatedCommentException + * @throws UnterminatedStringLiteralException + */ + public void minify(InputStream in, OutputStream out) throws IOException, UnterminatedRegExpLiteralException, + UnterminatedCommentException, + UnterminatedStringLiteralException { + + // Initialize + this.in = new PushbackInputStream(in); + this.out = out; + this.line = 0; + this.column = 0; +// currChar = '\n'; +// action(Action.DELETE_NEXT); + currChar = get(); + nextChar = peek(); + + // Process input + while (currChar != EOF) { + switch (currChar) { + + case ' ': + if (isAlphanum(nextChar)) { + action(Action.OUTPUT_CURR); + } else { + action(Action.DELETE_CURR); + } + break; + + case '\n': + switch (nextChar) { + case '{': + case '[': + case '(': + case '+': + case '-': + action(Action.OUTPUT_CURR); + break; + case ' ': + action(Action.DELETE_NEXT); + break; + default: + if (isAlphanum(nextChar)) { + action(Action.OUTPUT_CURR); + } else { + action(Action.DELETE_CURR); + } + } + break; + + default: + switch (nextChar) { + case ' ': + if (isAlphanum(currChar)) { + action(Action.OUTPUT_CURR); + break; + } + action(Action.DELETE_NEXT); + break; + case '\n': + switch (currChar) { + case '}': + case ']': + case ')': + case '+': + case '-': + case '"': + case '\'': + action(Action.OUTPUT_CURR); + break; + default: + if (isAlphanum(currChar)) { + action(Action.OUTPUT_CURR); + } else { + action(Action.DELETE_NEXT); + } + } + break; + default: + action(Action.OUTPUT_CURR); + break; + } + } + } + out.flush(); + } + + /** + * Process the current character with an appropriate action. + * + * The action that occurs is determined by the current character. The options are: + * + * 1. Output currChar: output currChar, copy nextChar to currChar, get the next character and save it to nextChar + * 2. Delete currChar: copy nextChar to currChar, get the next character and save it to nextChar + * 3. Delete nextChar: get the next character and save it to nextChar + * + * This method essentially treats a string as a single character. Also recognizes regular expressions if they are + * preceded by '(', ',', or '='. + * + * @param action The action to perform + * @throws IOException + * @throws UnterminatedRegExpLiteralException + * @throws UnterminatedCommentException + * @throws UnterminatedStringLiteralException + */ + private void action(Action action) throws IOException, UnterminatedRegExpLiteralException, UnterminatedCommentException, + UnterminatedStringLiteralException { + + // Process action + switch (action) { + + case OUTPUT_CURR: + out.write(currChar); + + case DELETE_CURR: + currChar = nextChar; + + if (currChar == '\'' || currChar == '"') { + for ( ; ; ) { + out.write(currChar); + currChar = get(); + if (currChar == nextChar) { + break; + } + if (currChar <= '\n') { + throw new UnterminatedStringLiteralException(line, + column); + } + if (currChar == '\\') { + out.write(currChar); + currChar = get(); + } + } + } + + case DELETE_NEXT: + nextChar = next(); + if (nextChar == '/' + && (currChar == '(' || currChar == ',' || currChar == '=' || currChar == ':')) { + out.write(currChar); + out.write(nextChar); + for ( ; ; ) { + currChar = get(); + if (currChar == '/') { + break; + } else if (currChar == '\\') { + out.write(currChar); + currChar = get(); + } else if (currChar <= '\n') { + throw new UnterminatedRegExpLiteralException(line, + column); + } + out.write(currChar); + } + nextChar = next(); + } + } + } + + /** + * Determines whether a given character is a letter, digit, underscore, dollar sign, or non-ASCII character. + * + * @param c The character to compare + * @return True if the character is a letter, digit, underscore, dollar sign, or non-ASCII character. False otherwise. + */ + private boolean isAlphanum(int c) { + return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') + || c == '_' || c == '$' || c == '\\' || c > 126); + } + + /** + * Returns the next character from the input stream. + * + * Will pop the next character from the input stack. If the character is a control character, translate it to a space + * or line feed. + * + * @return The next character from the input stream + * @throws IOException + */ + private int get() throws IOException { + int c = in.read(); + + if (c == '\n') { + line++; + column = 0; + } else { + column++; + } + + if (c >= ' ' || c == '\n' || c == EOF) { + return c; + } + + if (c == '\r') { + column = 0; + return '\n'; + } + + return ' '; + } + + /** + * Returns the next character from the input stream without popping it from the stack. + * + * @return The next character from the input stream + * @throws IOException + */ + private int peek() throws IOException { + int lookaheadChar = in.read(); + in.unread(lookaheadChar); + return lookaheadChar; + } + + /** + * Get the next character from the input stream, excluding comments. + * + * Will read from the input stream via the get() method. Will exclude characters that are part of + * comments. peek() is used to se if a '/' is followed by a '/' or a '*' for the purpose of identifying + * comments. + * + * @return The next character from the input stream, excluding characters from comments + * @throws IOException + * @throws UnterminatedCommentException + */ + private int next() throws IOException, UnterminatedCommentException { + int c = get(); + + if (c == '/') { + switch (peek()) { + + case '/': + for ( ; ; ) { + c = get(); + if (c <= '\n') { + return c; + } + } + + case '*': + get(); + for ( ; ; ) { + switch (get()) { + case '*': + if (peek() == '/') { + get(); + return ' '; + } + break; + case EOF: + throw new UnterminatedCommentException(line, column); + } + } + + default: + return c; + } + + } + return c; + } + + /** + * Exception to be thrown when an unterminated comment appears in the input. + */ + public static class UnterminatedCommentException extends Exception { + public UnterminatedCommentException(int line, int column) { + super("Unterminated comment at line " + line + " and column " + column); + } + } + + /** + * Exception to be thrown when an unterminated string literal appears in the input. + */ + public static class UnterminatedStringLiteralException extends Exception { + public UnterminatedStringLiteralException(int line, int column) { + super("Unterminated string literal at line " + line + " and column " + column); + } + } + + /** + * Exception to be thrown when an unterminated regular expression literal appears in the input. + */ + public static class UnterminatedRegExpLiteralException extends Exception { + public UnterminatedRegExpLiteralException(int line, int column) { + super("Unterminated regular expression at line " + line + " and column " + column); + } + } +} \ No newline at end of file diff --git a/packaging/ATCS_latest b/packaging/ATCS_latest index 52c6119..19d7022 100644 --- a/packaging/ATCS_latest +++ b/packaging/ATCS_latest @@ -1 +1 @@ -v0.6.7 \ No newline at end of file +v0.6.14 \ No newline at end of file diff --git a/packaging/Windows/ATCS_Installer.nsi b/packaging/Windows/ATCS_Installer.nsi index 581c9bc..3e98602 100644 --- a/packaging/Windows/ATCS_Installer.nsi +++ b/packaging/Windows/ATCS_Installer.nsi @@ -1,6 +1,6 @@ !include MUI2.nsh -!define VERSION "0.6.8" +!define VERSION "0.6.14" !define TRAINER_VERSION "0.1.4" !define JAVA_BIN "javaw" diff --git a/res/LICENSE.minify b/res/LICENSE.minify new file mode 100644 index 0000000..53fb404 --- /dev/null +++ b/res/LICENSE.minify @@ -0,0 +1,43 @@ + +---------------------- +Minify.java 2015-10-04 +---------------------- + +Copyright (c) 2015 Charles Bihis (www.whoischarles.com) + +This work is an adaptation of JSMin.java published by John Reilly which is a translation from C to Java of jsmin.c +published by Douglas Crockford. Permission is hereby granted to use this Java version under the same conditions as +the original jsmin.c on which all of these derivatives are based. + + +--------------------- +JSMin.java 2006-02-13 +--------------------- + +Copyright (c) 2006 John Reilly (www.inconspicuous.org) + +This work is a translation from C to Java of jsmin.c published by Douglas Crockford. Permission is hereby granted to +use the Java version under the same conditions as the jsmin.c on which it is based. + + + +------------------ +jsmin.c 2003-04-21 +------------------ + +Copyright (c) 2002 Douglas Crockford (www.crockford.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/res/spritesheets.properties b/res/spritesheets.properties index 771c6ad..78000a8 100644 --- a/res/spritesheets.properties +++ b/res/spritesheets.properties @@ -84,4 +84,5 @@ 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 +atcs.spritesheet.effect_tometik2.animate=true +atcs.spritesheet.monsters_guynmart.category=monster diff --git a/siphash-zackehh/src/main/java/com/zackehh/siphash/SipHash.java b/siphash-zackehh/src/main/java/com/zackehh/siphash/SipHash.java index d6d9da3..c82cb1f 100644 --- a/siphash-zackehh/src/main/java/com/zackehh/siphash/SipHash.java +++ b/siphash-zackehh/src/main/java/com/zackehh/siphash/SipHash.java @@ -1,6 +1,11 @@ package com.zackehh.siphash; -import static com.zackehh.siphash.SipHashConstants.*; +import static com.zackehh.siphash.SipHashConstants.DEFAULT_C; +import static com.zackehh.siphash.SipHashConstants.DEFAULT_D; +import static com.zackehh.siphash.SipHashConstants.INITIAL_V0; +import static com.zackehh.siphash.SipHashConstants.INITIAL_V1; +import static com.zackehh.siphash.SipHashConstants.INITIAL_V2; +import static com.zackehh.siphash.SipHashConstants.INITIAL_V3; /** * Main entry point for SipHash, providing a basic hash diff --git a/src/com/gpl/rpg/atcontentstudio/ATContentStudio.java b/src/com/gpl/rpg/atcontentstudio/ATContentStudio.java index 3965439..7ece794 100644 --- a/src/com/gpl/rpg/atcontentstudio/ATContentStudio.java +++ b/src/com/gpl/rpg/atcontentstudio/ATContentStudio.java @@ -43,18 +43,18 @@ import org.eclipse.jgit.lib.ReflogReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; +import prefuse.data.expression.parser.ExpressionParser; + 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; -import prefuse.data.expression.parser.ExpressionParser; - public class ATContentStudio { public static final String APP_NAME = "Andor's Trail Content Studio"; - public static final String APP_VERSION = "v0.6.8"; + public static final String APP_VERSION = "v0.6.14"; public static final String CHECK_UPDATE_URL = "https://andorstrail.com/static/ATCS_latest"; public static final String DOWNLOAD_URL = "https://andorstrail.com/viewtopic.php?f=6&t=4806"; diff --git a/src/com/gpl/rpg/atcontentstudio/img/bookmark_active.png b/src/com/gpl/rpg/atcontentstudio/img/bookmark_active.png new file mode 100644 index 0000000..4ad606c Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/bookmark_active.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/bookmark_inactive.png b/src/com/gpl/rpg/atcontentstudio/img/bookmark_inactive.png new file mode 100644 index 0000000..28bf189 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/bookmark_inactive.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/folder_bookmark_closed.png b/src/com/gpl/rpg/atcontentstudio/img/folder_bookmark_closed.png new file mode 100644 index 0000000..718313d Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/folder_bookmark_closed.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/img/folder_bookmark_open.png b/src/com/gpl/rpg/atcontentstudio/img/folder_bookmark_open.png new file mode 100644 index 0000000..a4c74b7 Binary files /dev/null and b/src/com/gpl/rpg/atcontentstudio/img/folder_bookmark_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 index 91c6115..5a343ba 100644 Binary files a/src/com/gpl/rpg/atcontentstudio/img/folder_map_closed.png 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 index df85204..9646243 100644 Binary files a/src/com/gpl/rpg/atcontentstudio/img/folder_map_open.png and b/src/com/gpl/rpg/atcontentstudio/img/folder_map_open.png differ diff --git a/src/com/gpl/rpg/atcontentstudio/model/GameDataElement.java b/src/com/gpl/rpg/atcontentstudio/model/GameDataElement.java index 534b715..5643f57 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/GameDataElement.java +++ b/src/com/gpl/rpg/atcontentstudio/model/GameDataElement.java @@ -11,6 +11,8 @@ import java.util.concurrent.ConcurrentHashMap; import javax.swing.tree.TreeNode; +import com.gpl.rpg.atcontentstudio.model.bookmarks.BookmarkEntry; + public abstract class GameDataElement implements ProjectTreeNode, Serializable { private static final long serialVersionUID = 2028934451226743389L; @@ -31,6 +33,8 @@ public abstract class GameDataElement implements ProjectTreeNode, Serializable { public boolean writable = false; + public BookmarkEntry bookmark = null; + //List of objects whose transition to "linked" state made them point to this instance. private Map backlinks = new ConcurrentHashMap(); diff --git a/src/com/gpl/rpg/atcontentstudio/model/Project.java b/src/com/gpl/rpg/atcontentstudio/model/Project.java index 69ea424..e8182ca 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/Project.java +++ b/src/com/gpl/rpg/atcontentstudio/model/Project.java @@ -1,34 +1,54 @@ package com.gpl.rpg.atcontentstudio.model; import java.awt.Image; +import java.io.ByteArrayInputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.Serializable; +import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; +import java.util.LinkedList; 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.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.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; import org.json.simple.JSONArray; +import org.w3c.dom.Comment; 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.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.bookmarks.BookmarksRoot; import com.gpl.rpg.atcontentstudio.model.gamedata.ActorCondition; import com.gpl.rpg.atcontentstudio.model.gamedata.Dialogue; import com.gpl.rpg.atcontentstudio.model.gamedata.Droplist; @@ -68,6 +88,8 @@ public class Project implements ProjectTreeNode, Serializable { 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 transient BookmarksRoot bookmarks; + public SavedGamesSet saves; //For simulations. @@ -116,6 +138,7 @@ public class Project implements ProjectTreeNode, Serializable { alteredContent = new GameSource(this, GameSource.Type.altered); createdContent = new GameSource(this, GameSource.Type.created); + bookmarks = new BookmarksRoot(this); saves = new SavedGamesSet(this); @@ -124,6 +147,7 @@ public class Project implements ProjectTreeNode, Serializable { // v.add(referencedContent); v.add(baseContent); v.add(saves); + v.add(bookmarks); linkAll(); @@ -243,6 +267,7 @@ public class Project implements ProjectTreeNode, Serializable { // referencedContent.refreshTransients(this); alteredContent = new GameSource(this, GameSource.Type.altered); createdContent = new GameSource(this, GameSource.Type.created); + bookmarks = new BookmarksRoot(this); saves.refreshTransients(); @@ -252,6 +277,7 @@ public class Project implements ProjectTreeNode, Serializable { // v.add(referencedContent); v.add(baseContent); v.add(saves); + v.add(bookmarks); linkAll(); @@ -951,6 +977,10 @@ public class Project implements ProjectTreeNode, Serializable { fireElementAdded(node, getNodeIndex(node)); } + public void bookmark(GameDataElement gde) { + bookmarks.addBookmark(gde); + } + @Override public GameDataSet getDataSet() { @@ -1043,85 +1073,149 @@ public class Project implements ProjectTreeNode, Serializable { } } - public void generateExportPackage(final File target) { + public void exportProjectAsZipPackage(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(); - } - } + File tmpDir = exportProjectToTmpDir(); 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 LinkedHashSet(); - Map> toWrite = new LinkedHashMap>(); - for (JSONElement gde : altered) { - alteredFileNames.add(gde.jsonFile.getName()); + public void exportProjectOverGameSource(final File target) { + WorkerDialog.showTaskMessage("Exporting project "+name+"...", ATContentStudio.frame, true, new Runnable() { + @Override + public void run() { + Notification.addInfo("Exporting project \""+name+"\" into "+target.getAbsolutePath()); + + File tmpDir = exportProjectToTmpDir(); + + FileUtils.copyOver(tmpDir, target); + FileUtils.deleteDir(tmpDir); + Notification.addSuccess("Project \""+name+"\" exported into "+target.getAbsolutePath()); + } + + + }); + } + + public File exportProjectToTmpDir() { + 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())); +// } + Map, List> writtenFilesPerDataType = new LinkedHashMap, List>(); + List writtenFiles; + writtenFiles = writeDataDeltaForDataType(createdContent.gameData.actorConditions, alteredContent.gameData.actorConditions, baseContent.gameData.actorConditions, ActorCondition.class, tmpJsonDataDir); + writtenFilesPerDataType.put(ActorCondition.class, writtenFiles); + writtenFiles = writeDataDeltaForDataType(createdContent.gameData.dialogues, alteredContent.gameData.dialogues, baseContent.gameData.dialogues, Dialogue.class, tmpJsonDataDir); + writtenFilesPerDataType.put(Dialogue.class, writtenFiles); + writtenFiles = writeDataDeltaForDataType(createdContent.gameData.droplists, alteredContent.gameData.droplists, baseContent.gameData.droplists, Droplist.class, tmpJsonDataDir); + writtenFilesPerDataType.put(Droplist.class, writtenFiles); + writtenFiles = writeDataDeltaForDataType(createdContent.gameData.itemCategories, alteredContent.gameData.itemCategories, baseContent.gameData.itemCategories, ItemCategory.class, tmpJsonDataDir); + writtenFilesPerDataType.put(ItemCategory.class, writtenFiles); + writtenFiles = writeDataDeltaForDataType(createdContent.gameData.items, alteredContent.gameData.items, baseContent.gameData.items, Item.class, tmpJsonDataDir); + writtenFilesPerDataType.put(Item.class, writtenFiles); + writtenFiles = writeDataDeltaForDataType(createdContent.gameData.npcs, alteredContent.gameData.npcs, baseContent.gameData.npcs, NPC.class, tmpJsonDataDir); + writtenFilesPerDataType.put(NPC.class, writtenFiles); + writtenFiles = writeDataDeltaForDataType(createdContent.gameData.quests, alteredContent.gameData.quests, baseContent.gameData.quests, Quest.class, tmpJsonDataDir); + writtenFilesPerDataType.put(Quest.class, writtenFiles); + + File tmpMapDir = new File(tmpDir, TMXMapSet.DEFAULT_REL_PATH_IN_SOURCE); + tmpMapDir.mkdirs(); + writtenFiles = new LinkedList(); + for (File createdMapFile : createdContent.gameMaps.mapFolder.listFiles()) { + if (createdMapFile.getName().equalsIgnoreCase("worldmap.xml")) continue; + FileUtils.copyFile(createdMapFile, new File(tmpMapDir, createdMapFile.getName())); + writtenFiles.add(createdMapFile.getName()); } - for (String fName : alteredFileNames) { + for (File alteredMapFile : alteredContent.gameMaps.mapFolder.listFiles()) { + if (alteredMapFile.getName().equalsIgnoreCase("worldmap.xml")) continue; + FileUtils.copyFile(alteredMapFile, new File(tmpMapDir, alteredMapFile.getName())); + writtenFiles.add(alteredMapFile.getName()); + } + writtenFilesPerDataType.put(TMXMap.class, writtenFiles); + + if (sourceSetToUse == ResourceSet.gameData) { + writeResourceListXml(writtenFilesPerDataType, GameSource.DEFAULT_REL_PATH_FOR_GAME_RESOURCE, baseContent.baseFolder, tmpDir); + } else if (sourceSetToUse == ResourceSet.debugData) { + writeResourceListXml(writtenFilesPerDataType, GameSource.DEFAULT_REL_PATH_FOR_DEBUG_RESOURCE, baseContent.baseFolder, tmpDir); + } + + + 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(); + } + } + + return tmpDir; + } + + @SuppressWarnings("rawtypes") + public List writeDataDeltaForDataType(GameDataCategory created, GameDataCategory altered, GameDataCategory source, Class gdeClass, File targetFolder) { + List filenamesToWrite = new LinkedList(); + Map> dataToWritePerFilename = new LinkedHashMap>(); + for (JSONElement gde : altered) { + if (!filenamesToWrite.contains(gde.jsonFile.getName())) { + filenamesToWrite.add(gde.jsonFile.getName()); + } + } + for (JSONElement gde : created) { + if (!filenamesToWrite.contains(gde.jsonFile.getName())) { + filenamesToWrite.add(gde.jsonFile.getName()); + } + } + for (String fName : filenamesToWrite) { for (JSONElement gde : source) { if (gde.jsonFile.getName().equals(fName)) { - if (toWrite.get(fName) == null) { - toWrite.put(fName, new ArrayList()); + if (dataToWritePerFilename.get(fName) == null) { + dataToWritePerFilename.put(fName, new ArrayList()); } - toWrite.get(fName).add(getGameDataElement(gdeClass, gde.id).toJson()); + //Automatically fetches altered element over source element. + dataToWritePerFilename.get(fName).add(getGameDataElement(gdeClass, gde.id).toJson()); + } + } + for (JSONElement gde : created) { + if (gde.jsonFile.getName().equals(fName)) { + if (dataToWritePerFilename.get(fName) == null) { + dataToWritePerFilename.put(fName, new ArrayList()); + } + //Add the created elements. + dataToWritePerFilename.get(fName).add(getGameDataElement(gdeClass, gde.id).toJson()); } } } - for (String fName : toWrite.keySet()) { + for (String fName : dataToWritePerFilename.keySet()) { File jsonFile = new File(targetFolder, fName); StringWriter writer = new JsonPrettyWriter(); try { - JSONArray.writeJSONString(toWrite.get(fName), writer); + JSONArray.writeJSONString(dataToWritePerFilename.get(fName), writer); } catch (IOException e) { //Impossible with a StringWriter } @@ -1136,8 +1230,138 @@ public class Project implements ProjectTreeNode, Serializable { e.printStackTrace(); } } + return filenamesToWrite; } + + private void writeResourceListXml(Map, List> writtenFilesPerDataType, String xmlFileRelPath, File baseFolder, File tmpDir) { + File xmlFile = new File(baseFolder, xmlFileRelPath); + File outputFile = new File(tmpDir, xmlFileRelPath); + + Map> classNamesByArrayNames = new HashMap>(); + classNamesByArrayNames.put("loadresource_itemcategories", ItemCategory.class); + classNamesByArrayNames.put("loadresource_actorconditions", ActorCondition.class); + classNamesByArrayNames.put("loadresource_items", Item.class); + classNamesByArrayNames.put("loadresource_droplists", Droplist.class); + classNamesByArrayNames.put("loadresource_quests", Quest.class); + classNamesByArrayNames.put("loadresource_conversationlists", Dialogue.class); + classNamesByArrayNames.put("loadresource_monsters", NPC.class); + classNamesByArrayNames.put("loadresource_maps", TMXMap.class); + + String jsonResPrefix = "@raw/"; + String tmxResPrefix = "@xml/"; + String jsonFileSuffix = ".json"; + String tmxFileSuffix = ".tmx"; + + if (!xmlFile.exists()) return; + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + Document doc; + try { + factory.setIgnoringElementContentWhitespace(true); + factory.setExpandEntityReferences(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + + InputSource insrc = new InputSource(new FileInputStream(xmlFile)); + insrc.setEncoding("UTF-8"); + doc = builder.parse(insrc); + + Element arrayNode; + String name, resPrefix, fileSuffix, resName, resToFile, fileToRes; + Class clazz; + List writtenFiles; + + Element root = (Element) doc.getElementsByTagName("resources").item(0); + if (root != null) { + NodeList arraysList = root.getElementsByTagName("array"); + if (arraysList != null) { + for (int i = 0; i < arraysList.getLength(); i++) { + arrayNode = (Element) arraysList.item(i); + name = arrayNode.getAttribute("name"); + clazz = classNamesByArrayNames.get(name); + if (clazz == null) continue; + writtenFiles = writtenFilesPerDataType.get(clazz); + if (writtenFiles == null) continue; + if (clazz == TMXMap.class) { + resPrefix = tmxResPrefix; + fileSuffix = tmxFileSuffix; + } else { + resPrefix = jsonResPrefix; + fileSuffix = jsonFileSuffix; + } + NodeList arrayItems = arrayNode.getElementsByTagName("item"); + if (arrayItems != null) { + for (int j = 0; j < arrayItems.getLength(); j++) { + resName = ((Element)arrayItems.item(j)).getTextContent(); + if (resName == null) continue; + resToFile = resName.replaceFirst("\\A"+resPrefix, "")+fileSuffix; + writtenFiles.remove(resToFile); + } + } + if (!writtenFiles.isEmpty()) { + Comment com = doc.createComment("Added by ATCS "+ATContentStudio.APP_VERSION+" for project "+getProject().name); + arrayNode.appendChild(com); + Collections.sort(writtenFiles); + for (String missingRes : writtenFiles) { + Element item = doc.createElement("item"); + fileToRes = resPrefix+missingRes.replaceFirst(fileSuffix+"\\z", ""); + item.setTextContent(fileToRes); + arrayNode.appendChild(item); + } + } + } + } + } + + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + if (!outputFile.getParentFile().exists()) { + outputFile.getParentFile().mkdirs(); + } + StringWriter temp = new StringWriter(); + Result output = new StreamResult(temp); + Source input = new DOMSource(doc); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.transform(input, output); + + String tempString = temp.toString(); + doc = builder.parse(new ByteArrayInputStream(tempString.getBytes("UTF-8"))); + input = new DOMSource(doc); + transformer = TransformerFactory.newInstance().newTransformer(new StreamSource(new StringReader( + "\r\n" + + "\r\n" + + " \r\n" + + " \r\n" + + "\r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + "\r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + ""))); + output = new StreamResult(new FileOutputStream(outputFile)); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); + transformer.transform(input, output); + + + } catch (SAXException e) { + e.printStackTrace(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (ParserConfigurationException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (TransformerException e) { + e.printStackTrace(); + } + } @Override public boolean needsSaving() { diff --git a/src/com/gpl/rpg/atcontentstudio/model/Workspace.java b/src/com/gpl/rpg/atcontentstudio/model/Workspace.java index 7e12910..8bf298a 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/Workspace.java +++ b/src/com/gpl/rpg/atcontentstudio/model/Workspace.java @@ -4,6 +4,7 @@ import java.awt.Image; import java.io.File; import java.io.IOException; import java.io.Serializable; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; @@ -332,7 +333,9 @@ public class Workspace implements ProjectTreeNode, Serializable { private static boolean delete(File f) { boolean b = true; - if (f.isDirectory()) { + if (Files.isSymbolicLink(f.toPath())) { + b &= f.delete(); + } else if (f.isDirectory()) { for (File c : f.listFiles()) b &= delete(c); } diff --git a/src/com/gpl/rpg/atcontentstudio/model/WorkspaceSettings.java b/src/com/gpl/rpg/atcontentstudio/model/WorkspaceSettings.java index 9bc85ab..3072542 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/WorkspaceSettings.java +++ b/src/com/gpl/rpg/atcontentstudio/model/WorkspaceSettings.java @@ -38,7 +38,7 @@ public class WorkspaceSettings { public static String DEFAULT_IMG_EDITOR_COMMAND = "gimp"; public Setting imageEditorCommand = new PrimitiveSetting("imageEditorCommand", DEFAULT_IMG_EDITOR_COMMAND); - public static String[] LANGUAGE_LIST = new String[]{null, "de", "ru", "pl", "fr", "it", "es", "nl", "uk", "ca", "sv", "pt", "pt_BR", "zh_Hant", "zh_Hans", "ja", "cs", "tr", "ko", "hu", "sl", "bg", "id", "fi", "th", "gl", "ms" ,"pa", "az"}; + public static String[] LANGUAGE_LIST = new String[]{null, "de", "ru", "pl", "fr", "it", "es", "nl", "uk", "ca", "sv", "pt", "pt_BR", "zh_Hant", "zh_Hans", "ja", "cs", "tr", "ko", "hu", "sl", "bg", "id", "fi", "th", "gl", "ms" ,"pa", "az", "nb"}; public Setting translatorLanguage = new NullDefaultPrimitiveSetting("translatorLanguage"); public static Boolean DEFAULT_ALLOW_INTERNET = true; public Setting useInternet = new PrimitiveSetting("useInternet", DEFAULT_ALLOW_INTERNET); @@ -158,7 +158,7 @@ public class WorkspaceSettings { value = defaultValue; } - public abstract void readFromJson(Map json); + public abstract void readFromJson(@SuppressWarnings("rawtypes") Map json); @SuppressWarnings({ "rawtypes", "unchecked" }) public void saveToJson(Map json) { @@ -188,6 +188,7 @@ public class WorkspaceSettings { super(id, null); } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public void saveToJson(Map json) { if (value != null) json.put(id, value); @@ -201,6 +202,7 @@ public class WorkspaceSettings { this.value = this.defaultValue = defaultValue; } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public void readFromJson(Map json) { value = new ArrayList(); diff --git a/src/com/gpl/rpg/atcontentstudio/model/bookmarks/BookmarkEntry.java b/src/com/gpl/rpg/atcontentstudio/model/bookmarks/BookmarkEntry.java new file mode 100644 index 0000000..17f811d --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/bookmarks/BookmarkEntry.java @@ -0,0 +1,155 @@ +package com.gpl.rpg.atcontentstudio.model.bookmarks; + +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.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.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.model.gamedata.Quest; +import com.gpl.rpg.atcontentstudio.model.gamedata.QuestStage; + +public class BookmarkEntry implements BookmarkNode { + + public GameDataElement bookmarkedElement; + public BookmarkFolder parent; + + public BookmarkEntry(BookmarkFolder parent, GameDataElement target) { + this.parent = parent; + this.bookmarkedElement = target; + target.bookmark = this; + parent.contents.add(this); + } + + @Override + public Enumeration children() { + return null; + } + + @Override + public boolean getAllowsChildren() { + return false; + } + + @Override + public TreeNode getChildAt(int childIndex) { + return null; + } + + @Override + public int getChildCount() { + return 0; + } + + @Override + public int getIndex(TreeNode node) { + 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() { + if (bookmarkedElement instanceof QuestStage) { + String text = ((GameDataElement)bookmarkedElement).getDesc(); + if (text.length() > 60) { + text = text.substring(0, 57)+"..."; + } + return ((GameDataElement)bookmarkedElement).getDataType().toString()+"/"+((Quest)((QuestStage)bookmarkedElement).parent).id+"#"+((QuestStage)bookmarkedElement).progress+":"+text; + } else { + return ((GameDataElement)bookmarkedElement).getDataType().toString()+"/"+((GameDataElement)bookmarkedElement).getDesc(); + } + } + + @Override + public Project getProject() { + return parent.getProject(); + } + + @Override + public GameDataSet getDataSet() { + return null; + } + + @Override + public Image getIcon() { + return bookmarkedElement.getIcon(); + } + + @Override + public Image getOpenIcon() { + return null; + } + + @Override + public Image getClosedIcon() { + return null; + } + + @Override + public Image getLeafIcon() { + return getIcon(); + } + + @Override + public Type getDataType() { + return null; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public boolean needsSaving() { + return false; + } + + public void delete() { + bookmarkedElement.bookmark = null; + parent.delete(this); + } + + @Override + public void save() { + parent.save(); + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/bookmarks/BookmarkFolder.java b/src/com/gpl/rpg/atcontentstudio/model/bookmarks/BookmarkFolder.java new file mode 100644 index 0000000..c34be73 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/bookmarks/BookmarkFolder.java @@ -0,0 +1,168 @@ +package com.gpl.rpg.atcontentstudio.model.bookmarks; + +import java.awt.Image; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.tree.TreeNode; + +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 BookmarkFolder implements BookmarkNode { + + List contents = new LinkedList(); + BookmarkNode parent; + String name; + Image closedIcon, openIcon; + + public BookmarkFolder(BookmarkNode parent, String name) { + this(parent, name, DefaultIcons.getStdClosedIcon(), DefaultIcons.getStdOpenIcon()); + } + + public BookmarkFolder(BookmarkNode parent, String name, Image closedIcon, Image openIcon) { + this.parent = parent; + this.name = name; + this.closedIcon = closedIcon; + this.openIcon = openIcon; + } + + @Override + public Enumeration children() { + return Collections.enumeration(contents); + } + + @Override + public boolean getAllowsChildren() { + return true; + } + + @Override + public TreeNode getChildAt(int childIndex) { + return contents.get(childIndex); + } + + @Override + public int getChildCount() { + return contents.size(); + } + + @Override + public int getIndex(TreeNode node) { + return contents.indexOf(node); + } + + @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()); + } + + @Override + public String getDesc() { + return name; + } + + @Override + public Project getProject() { + return parent.getProject(); + } + + @Override + public GameDataSet getDataSet() { + return null; + } + + @Override + public Image getIcon() { + return getClosedIcon(); + } + + @Override + public Image getOpenIcon() { + return openIcon; + } + + @Override + public Image getClosedIcon() { + return closedIcon; + } + + @Override + public Image getLeafIcon() { + return getClosedIcon(); + } + + @Override + public Type getDataType() { + return null; + } + + @Override + public boolean isEmpty() { + return contents.isEmpty(); + } + + @Override + public boolean needsSaving() { + return false; + } + + public void delete(BookmarkEntry bookmarkEntry) { + if (contents.contains(bookmarkEntry)) { + bookmarkEntry.childrenRemoved(new ArrayList()); + contents.remove(bookmarkEntry); + save(); + } + } + + public void delete(BookmarkFolder bookmarkFolder) { + // TODO Auto-generated method stub + + } + + public void save() { + parent.save(); + } + + public void delete() { + + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/bookmarks/BookmarkNode.java b/src/com/gpl/rpg/atcontentstudio/model/bookmarks/BookmarkNode.java new file mode 100644 index 0000000..4b64420 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/bookmarks/BookmarkNode.java @@ -0,0 +1,10 @@ +package com.gpl.rpg.atcontentstudio.model.bookmarks; + +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; + +public interface BookmarkNode extends ProjectTreeNode{ + + public void save(); + public void delete(); + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/bookmarks/BookmarksRoot.java b/src/com/gpl/rpg/atcontentstudio/model/bookmarks/BookmarksRoot.java new file mode 100644 index 0000000..501cfff --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/bookmarks/BookmarksRoot.java @@ -0,0 +1,210 @@ +package com.gpl.rpg.atcontentstudio.model.bookmarks; + +import java.awt.Image; +import java.io.File; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import javax.swing.tree.TreeNode; + +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.SavedSlotCollection; +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.GameDataSet; +import com.gpl.rpg.atcontentstudio.model.gamedata.Item; +import com.gpl.rpg.atcontentstudio.model.gamedata.ItemCategory; +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.sprites.Spritesheet; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class BookmarksRoot implements BookmarkNode { + + SavedSlotCollection v = new SavedSlotCollection(); + + public transient Project parent = null; + + BookmarkFolder ac, diag, dl, it, ic, npc, q, tmx, sp, wm; + + public BookmarksRoot(Project parent) { + this.parent = parent; + + v.add(ac = new BookmarkFolder(this, ActorCondition.getStaticDesc(), DefaultIcons.getJsonClosedIcon(), DefaultIcons.getJsonOpenIcon())); + v.add(diag = new BookmarkFolder(this, Dialogue.getStaticDesc(), DefaultIcons.getJsonClosedIcon(), DefaultIcons.getJsonOpenIcon())); + v.add(dl = new BookmarkFolder(this, Droplist.getStaticDesc(), DefaultIcons.getJsonClosedIcon(), DefaultIcons.getJsonOpenIcon())); + v.add(it = new BookmarkFolder(this, Item.getStaticDesc(), DefaultIcons.getJsonClosedIcon(), DefaultIcons.getJsonOpenIcon())); + v.add(ic = new BookmarkFolder(this, ItemCategory.getStaticDesc(), DefaultIcons.getJsonClosedIcon(), DefaultIcons.getJsonOpenIcon())); + v.add(npc = new BookmarkFolder(this, NPC.getStaticDesc(), DefaultIcons.getJsonClosedIcon(), DefaultIcons.getJsonOpenIcon())); + v.add(q = new BookmarkFolder(this, Quest.getStaticDesc(), DefaultIcons.getJsonClosedIcon(), DefaultIcons.getJsonOpenIcon())); + + v.add(tmx = new BookmarkFolder(this, "TMX Maps", DefaultIcons.getTmxClosedIcon(), DefaultIcons.getTmxOpenIcon())); + v.add(sp = new BookmarkFolder(this, "Spritesheets", DefaultIcons.getSpriteClosedIcon(), DefaultIcons.getSpriteOpenIcon())); + v.add(wm = new BookmarkFolder(this, "Worldmap", DefaultIcons.getSpriteClosedIcon(), DefaultIcons.getSpriteOpenIcon())); + } + + @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 (needsSaving() ? "*" : "")+"Bookmarks"; + } + + @Override + public Project getProject() { + return parent == null ? null : parent.getProject(); + } + + @Override + public GameDataSet getDataSet() { + return null; + } + + @Override + public Image getIcon() { + return getOpenIcon(); + } + + @Override + public Image getOpenIcon() { + return DefaultIcons.getBookmarkOpenIcon(); + } + + @Override + public Image getClosedIcon() { + return DefaultIcons.getBookmarkClosedIcon(); + } + + @Override + public Image getLeafIcon() { + return getClosedIcon(); + } + + @Override + public Type getDataType() { + return null; + } + + @Override + public boolean isEmpty() { + return v.isEmpty(); + } + + @Override + public boolean needsSaving() { + return false; + } + + public void save() { + + } + + @Override + public void delete() {} + + public void addBookmark(GameDataElement target) { + BookmarkEntry node; + BookmarkFolder folder = null; + if (target instanceof ActorCondition) { + folder = ac; + } else if (target instanceof Dialogue) { + folder = diag; + } else if (target instanceof Droplist) { + folder = dl; + } else if (target instanceof Item) { + folder = it; + } else if (target instanceof ItemCategory) { + folder = ic; + } else if (target instanceof NPC) { + folder = npc; + } else if (target instanceof Quest) { + folder = q; + } else if (target instanceof TMXMap) { + folder = tmx; + } else if (target instanceof Spritesheet) { + folder = sp; + } else if (target instanceof WorldmapSegment) { + folder = wm; + } else { + return; + } + ProjectTreeNode higherEmptyParent = folder; + while (higherEmptyParent != null) { + if (higherEmptyParent.getParent() != null && ((ProjectTreeNode)higherEmptyParent.getParent()).isEmpty()) higherEmptyParent = (ProjectTreeNode)higherEmptyParent.getParent(); + else break; + } + if (higherEmptyParent == this && !this.isEmpty()) higherEmptyParent = null; + + node = new BookmarkEntry(folder, target); + if (higherEmptyParent != null) higherEmptyParent.notifyCreated(); + else node.notifyCreated(); + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/gamedata/ActorCondition.java b/src/com/gpl/rpg/atcontentstudio/model/gamedata/ActorCondition.java index 6b69fed..b721ed4 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/gamedata/ActorCondition.java +++ b/src/com/gpl/rpg/atcontentstudio/model/gamedata/ActorCondition.java @@ -208,7 +208,7 @@ public class ActorCondition extends JSONElement { this.full_round_effect.ap_boost_max = JSONElement.getInteger((Number) (((Map)fullRoundEffect.get("increaseCurrentAP")).get("max"))); this.full_round_effect.ap_boost_min = JSONElement.getInteger((Number) (((Map)fullRoundEffect.get("increaseCurrentAP")).get("min"))); } - String vfx = (String) roundEffect.get("visualEffectID"); + String vfx = (String) fullRoundEffect.get("visualEffectID"); this.full_round_effect.visual_effect = null; if (vfx != null) { try { diff --git a/src/com/gpl/rpg/atcontentstudio/model/gamedata/Dialogue.java b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Dialogue.java index 2cc77c7..1863db0 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/gamedata/Dialogue.java +++ b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Dialogue.java @@ -60,6 +60,7 @@ public class Dialogue extends JSONElement { actorCondition, actorConditionImmunity, alignmentChange, + alignmentSet, giveItem, createTimer, spawnAll, @@ -255,7 +256,8 @@ public class Dialogue extends JSONElement { reward.reward_obj = proj.getActorCondition(reward.reward_obj_id); break; case alignmentChange: - //Nothing to do (yet ?). + case alignmentSet: + //Nothing to do (yet ?). break; case createTimer: //Nothing to do. diff --git a/src/com/gpl/rpg/atcontentstudio/model/gamedata/Requirement.java b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Requirement.java index 47087a0..edf1b54 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/gamedata/Requirement.java +++ b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Requirement.java @@ -172,6 +172,7 @@ public class Requirement extends JSONElement { case skillLevel: case spentGold: case timerElapsed: + case factionScore: break; } if (this.required_obj != null) this.required_obj.addBacklink((GameDataElement) this.parent); @@ -190,6 +191,7 @@ public class Requirement extends JSONElement { clone.state = this.state; clone.required_obj_id = this.required_obj_id; clone.required_value = this.required_value; + clone.negated = this.negated; clone.required_obj = this.required_obj; clone.type = this.type; if (clone.required_obj != null && parent != null) { diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/KeyArea.java b/src/com/gpl/rpg/atcontentstudio/model/maps/KeyArea.java index ed3aa2e..c583328 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/maps/KeyArea.java +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/KeyArea.java @@ -19,6 +19,7 @@ public class KeyArea extends MapObject { String requireType = obj.getProperties().getProperty("requireType"); String requireId = obj.getProperties().getProperty("requireId"); String requireValue = obj.getProperties().getProperty("requireValue"); + String requireNegation = obj.getProperties().getProperty("requireNegation"); oldSchoolRequirement = false; if (requireType == null) { String[] fields = obj.getName().split(":"); @@ -38,7 +39,9 @@ public class KeyArea extends MapObject { if (requireType != null) requirement.type = Requirement.RequirementType.valueOf(requireType); requirement.required_obj_id = requireId; if (requireValue != null) requirement.required_value = Integer.parseInt(requireValue); + if (requireNegation != null) requirement.negated = Boolean.parseBoolean(requireNegation); requirement.state = GameDataElement.State.parsed; + } @Override @@ -98,7 +101,7 @@ public class KeyArea extends MapObject { public void updateNameFromRequirementChange() { if (oldSchoolRequirement && Requirement.RequirementType.questProgress.equals(requirement.type) && (requirement.negated == null || !requirement.negated)) { - name = requirement.required_obj_id+":"+((requirement.required_value == null) ? "" : Integer.toString(requirement.required_value)); + name = (requirement.negated != null && requirement.negated) ? "NOT " : "" + requirement.required_obj_id+":"+((requirement.required_value == null) ? "" : Integer.toString(requirement.required_value)); } else if (oldSchoolRequirement) { int i = 0; String futureName = requirement.type.toString() + "#" + Integer.toString(i); diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/ReplaceArea.java b/src/com/gpl/rpg/atcontentstudio/model/maps/ReplaceArea.java index 34e0ef7..ed64ea0 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/maps/ReplaceArea.java +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/ReplaceArea.java @@ -20,6 +20,7 @@ public class ReplaceArea extends MapObject { String requireType = obj.getProperties().getProperty("requireType"); String requireId = obj.getProperties().getProperty("requireId"); String requireValue = obj.getProperties().getProperty("requireValue"); + String requireNegation = obj.getProperties().getProperty("requireNegation"); if (requireType == null) { String[] fields = obj.getName().split(":"); if (fields.length == 2) { @@ -37,6 +38,7 @@ public class ReplaceArea extends MapObject { if (requireType != null) requirement.type = Requirement.RequirementType.valueOf(requireType); requirement.required_obj_id = requireId; if (requireValue != null) requirement.required_value = Integer.parseInt(requireValue); + if (requireNegation != null) requirement.negated = Boolean.parseBoolean(requireNegation); requirement.state = GameDataElement.State.parsed; @@ -112,7 +114,7 @@ public class ReplaceArea extends MapObject { //Don't use yet ! public void updateNameFromRequirementChange() { if (oldSchoolRequirement && Requirement.RequirementType.questProgress.equals(requirement.type) && (requirement.negated == null || !requirement.negated)) { - name = requirement.required_obj_id+":"+((requirement.required_value == null) ? "" : Integer.toString(requirement.required_value)); + name = (requirement.negated != null && requirement.negated) ? "NOT " : "" + requirement.required_obj_id+":"+((requirement.required_value == null) ? "" : Integer.toString(requirement.required_value)); } else if (oldSchoolRequirement) { int i = 0; String futureName = requirement.type.toString() + "#" + Integer.toString(i); diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/SpawnArea.java b/src/com/gpl/rpg/atcontentstudio/model/maps/SpawnArea.java index f04b3ac..1052c40 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/maps/SpawnArea.java +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/SpawnArea.java @@ -13,6 +13,7 @@ public class SpawnArea extends MapObject { public int quantity = 1; public int spawnchance = 10; public boolean active = true; + public boolean ignoreAreas = false; public String spawngroup_id; public List spawnGroup = new ArrayList(); @@ -26,6 +27,9 @@ public class SpawnArea extends MapObject { if (obj.getProperties().getProperty("active") != null) { this.active = Boolean.parseBoolean(obj.getProperties().getProperty("active")); } + if (obj.getProperties().getProperty("ignoreAreas") != null) { + this.ignoreAreas = Boolean.parseBoolean(obj.getProperties().getProperty("ignoreAreas")); + } if (obj.getProperties().getProperty("spawngroup") != null) { this.spawngroup_id = obj.getProperties().getProperty("spawngroup"); } else if (obj.getName() != null ){ @@ -84,6 +88,9 @@ public class SpawnArea extends MapObject { if (!this.active) { tmxObject.getProperties().setProperty("active", Boolean.toString(active)); } + if (this.ignoreAreas) { + tmxObject.getProperties().setProperty("ignoreAreas", Boolean.toString(ignoreAreas)); + } } } diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMap.java b/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMap.java index 2a6ec8a..4a2fe88 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMap.java +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMap.java @@ -38,6 +38,7 @@ public class TMXMap extends GameDataElement { public static final String GROUND_LAYER_NAME = "Ground"; public static final String OBJECTS_LAYER_NAME = "Objects"; public static final String ABOVE_LAYER_NAME = "Above"; + public static final String TOP_LAYER_NAME = "Top"; public static final String WALKABLE_LAYER_NAME = "Walkable"; public enum ColorFilter { @@ -398,6 +399,7 @@ public class TMXMap extends GameDataElement { return GROUND_LAYER_NAME.equalsIgnoreCase(name) || OBJECTS_LAYER_NAME.equalsIgnoreCase(name) || ABOVE_LAYER_NAME.equalsIgnoreCase(name) || + TOP_LAYER_NAME.equalsIgnoreCase(name) || WALKABLE_LAYER_NAME.equalsIgnoreCase(name); } diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMapSet.java b/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMapSet.java index 00af523..031cb42 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMapSet.java +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMapSet.java @@ -15,6 +15,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.List; +import java.util.concurrent.TimeUnit; import javax.swing.tree.TreeNode; @@ -92,29 +93,31 @@ public class TMXMapSet implements ProjectTreeNode { }); if (source.type == GameSource.Type.created | source.type == GameSource.Type.altered) { final Path folderPath = Paths.get(mapFolder.getAbsolutePath()); - Thread watcher = new Thread("Map folder watcher for "+source.type) { + Thread watcher = new Thread("Map folder watcher for "+getProject().name+"/"+source.type) { public void run() { WatchService watchService; while(getProject().open) { try { watchService = FileSystems.getDefault().newWatchService(); - WatchKey watchKey = folderPath.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY); + /*WatchKey watchKey = */folderPath.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY); WatchKey wk; validService: while(getProject().open) { - wk = watchService.take(); - for (WatchEvent event : wk.pollEvents()) { - Path changed = (Path) event.context(); - String name = changed.getFileName().toString(); - String id = name.substring(0, name.length() - 4); - TMXMap map = getMap(id); - if (map != null) { - map.mapChangedOnDisk(); + wk = watchService.poll(10, TimeUnit.SECONDS); + if (wk != null) { + for (WatchEvent event : wk.pollEvents()) { + Path changed = (Path) event.context(); + String name = changed.getFileName().toString(); + String id = name.substring(0, name.length() - 4); + TMXMap map = getMap(id); + if (map != null) { + map.mapChangedOnDisk(); + } + } + if(!wk.reset()) { + watchService.close(); + break validService; } - } - if(!wk.reset()) { - watchService.close(); - break validService; } } } catch (IOException e) { diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/WorldmapSegment.java b/src/com/gpl/rpg/atcontentstudio/model/maps/WorldmapSegment.java index 342b320..d8daeae 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/maps/WorldmapSegment.java +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/WorldmapSegment.java @@ -23,6 +23,7 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; +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.SaveEvent; @@ -112,12 +113,20 @@ public class WorldmapSegment extends GameDataElement { mapLocations.remove(oldOne.id); modified = true; } + List deprecatedLabels = new ArrayList(); for (String label : labelledMaps.keySet()) { if (labelledMaps.get(label).contains(oldOne.id)) { labelledMaps.get(label).remove(oldOne.id); modified = true; + if (labelledMaps.get(label).isEmpty()) { + deprecatedLabels.add(label); + } } } + for (String label : deprecatedLabels) { + labelledMaps.remove(label); + labels.remove(label); + } } oldOne.removeBacklink(this); @@ -126,6 +135,7 @@ public class WorldmapSegment extends GameDataElement { if (modified) { this.state = GameDataElement.State.modified; childrenChanged(new ArrayList()); + ATContentStudio.frame.editorChanged(this); } } diff --git a/src/com/gpl/rpg/atcontentstudio/model/tools/i18n/PoPotWriter.java b/src/com/gpl/rpg/atcontentstudio/model/tools/i18n/PoPotWriter.java new file mode 100644 index 0000000..9a836e8 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/tools/i18n/PoPotWriter.java @@ -0,0 +1,86 @@ +package com.gpl.rpg.atcontentstudio.model.tools.i18n; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class PoPotWriter { + + Map> stringsResources = new LinkedHashMap>(); + Map translations = new LinkedHashMap(); + File f; + + public static void writePoFile(Map> stringsResources, Map translations, File destination) { + try { + FileWriter fw = new FileWriter(destination); + if (translations.get("") != null) { + fw.write(translations.get("")); + writeEndOfEntry(fw); + } + if (translations.get("translator-credits") != null) { + List refs = new LinkedList(); + refs.add("[none]"); + writeReferences(fw, refs); + writeMsgId(fw, "translator-credits"); + writeMsgStr(fw, translations.get("translator-credits")); + writeEndOfEntry(fw); + } + for (String msg : stringsResources.keySet()) { + writeReferences(fw, stringsResources.get(msg)); + writeMsgId(fw, msg); + writeMsgStr(fw, translations.get(msg)); + writeEndOfEntry(fw); + } + fw.flush(); + fw.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void writePotFile(Map> stringsResources, File destination) { + try { + FileWriter fw = new FileWriter(destination); + for (String msg : stringsResources.keySet()) { + writeReferences(fw, stringsResources.get(msg)); + writeMsgId(fw, msg); + writeMsgStr(fw, ""); + writeEndOfEntry(fw); + } + fw.flush(); + fw.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static void writeReferences(Writer w, List references) throws IOException { + for (String ref : references) { + w.write("#: "); + w.write(ref); + w.write("\n"); + } + } + + private static void writeMsgId(Writer w, String msg) throws IOException { + w.write("msgid \""); + w.write(msg); + w.write("\"\n"); + } + + private static void writeMsgStr(Writer w, String translation) throws IOException { + w.write("msgstr \""); + w.write(translation == null ? "" : translation); + w.write("\"\n"); + } + + private static void writeEndOfEntry(Writer w) throws IOException { + w.write("\n"); + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/tools/i18n/PotComparator.java b/src/com/gpl/rpg/atcontentstudio/model/tools/i18n/PotComparator.java new file mode 100644 index 0000000..916b3aa --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/tools/i18n/PotComparator.java @@ -0,0 +1,310 @@ +package com.gpl.rpg.atcontentstudio.model.tools.i18n; + +import java.io.File; +import java.io.FileFilter; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import javax.swing.JOptionPane; + +import com.gpl.rpg.atcontentstudio.model.Project; + +import net.launchpad.tobal.poparser.POEntry; +import net.launchpad.tobal.poparser.POFile; +import net.launchpad.tobal.poparser.POParser; + + +/** + * + * @author Kevin + * + * To use this, paste the following script in the beanshell console of ATCS. + * Don't forget to change the project number to suit your needs. + * +import com.gpl.rpg.atcontentstudio.model.Workspace; +import com.gpl.rpg.atcontentstudio.model.tools.i18n.PotGenerator; +import com.gpl.rpg.atcontentstudio.model.tools.i18n.PotComparator; + +proj = Workspace.activeWorkspace.projects.get(7); +PotGenerator.generatePotFileForProject(proj); +comp = new PotComparator(proj); +comp.compare(); +comp.updatePoFiles(proj); + * + * + * + */ +public class PotComparator { + + Map> stringsResourcesNew = new LinkedHashMap>(); + Map resourcesStringsNew = new LinkedHashMap(); + + Map> stringsResourcesOld = new LinkedHashMap>(); + Map resourcesStringsOld = new LinkedHashMap(); + + Map msgIdToReplace = new LinkedHashMap(); + List msgIdToReview = new LinkedList(); + List msgIdOutdated = new LinkedList(); + + + public PotComparator(Project proj) { + POParser parser = new POParser(); + + POFile newPot = parser.parseFile(new File(proj.alteredContent.baseFolder.getAbsolutePath()+File.separator+"english.pot")); + if (newPot == null) { + System.err.println("Cannot locate new english.pot file at "+proj.alteredContent.baseFolder.getAbsolutePath()+File.separator); + } + extractFromPoFile(newPot, stringsResourcesNew, resourcesStringsNew); + + POFile oldPot = parser.parseFile(new File(proj.baseContent.baseFolder.getAbsolutePath()+File.separator+"assets"+File.separator+"translation"+File.separator+"english.pot")); + if (oldPot == null) { + System.err.println("Cannot locate old english.pot file at "+proj.baseContent.baseFolder.getAbsolutePath()+File.separator+"assets"+File.separator+"translations"+File.separator); + } + extractFromPoFile(oldPot, stringsResourcesOld, resourcesStringsOld); + } + + + private void extractFromPoFile(POFile po, Map> stringsResources, Map resourcesStrings) { + for (POEntry entry : po.getEntryArray()) { + Vector resources = entry.getStringsByType(POEntry.StringType.REFERENCE); + Vector msgids = entry.getStringsByType(POEntry.StringType.MSGID); + if (resources == null || resources.size() == 0 || msgids == null || msgids.size() == 0) continue; + String msgid = msgids.get(0); + if (msgids.size() > 1) { + for (int i = 1; i < msgids.size(); i++) { + msgid += msgids.get(i); + } + } + if (msgid.contains("\\n")) { + msgid = msgid.replaceAll("\\\\n", "\\\\n\"\n\""); + msgid = "\"\n\""+msgid; + } + for (String resLine : resources) { + String[] resArray = resLine.split(" "); + for (String res : resArray) { + resourcesStrings.put(res, msgid); + if (stringsResources.get(msgid) == null) { + stringsResources.put(msgid, new LinkedList()); + } + stringsResources.get(msgid).add(res); + } + } + } + } + + public void compare() { + for (String oldRes : resourcesStringsOld.keySet()) { + String newString = resourcesStringsNew.get(oldRes); + String oldString = resourcesStringsOld.get(oldRes); + if (newString != null) { + if (!newString.equals(oldString)) { + List allOldResources = stringsResourcesOld.get(oldString); + List allNewResources = stringsResourcesNew.get(oldString); + StringBuffer sb = new StringBuffer(); + sb.append("---------------------------------------------\n"); + sb.append("--- TYPO CHECK ------------------------------\n"); + sb.append("---------------------------------------------\n"); + sb.append("String at: "+oldRes+"\n"); + if (allOldResources.size() > 1) { + sb.append("Also present at:\n"); + for (String res : allOldResources) { + if (!res.equals(oldRes)) { + sb.append("- "+res+"\n"); + } + } + } + if (allNewResources != null) { + sb.append("Still present at: \n"); + for (String res : allNewResources) { + sb.append("- "+res+"\n"); + } + } + sb.append("Was : \""+oldString+"\"\n"); + sb.append("Now : \""+newString+"\"\n"); + System.out.println(sb.toString()); + showTypoDialog(oldString, newString, sb.toString()); + } + } else { + List allOldResources = stringsResourcesOld.get(oldString); + List allNewResources = stringsResourcesNew.get(oldString); + if (allOldResources.size() >= 1) { + System.out.println("---------------------------------------------"); + System.out.println("--- REMOVED RESOURCE ------------------------"); + System.out.println("---------------------------------------------"); + System.out.println("String at: "+oldRes); + if (allOldResources.size() > 1) { + System.out.println("And also at:"); + for (String res : allOldResources) { + if (!res.equals(oldRes)) { + System.out.println("- "+res); + } + } + } + System.out.println("Was: \""+oldString+"\""); + if (allNewResources == null) { + System.out.println("Absent from new."); + } else { + System.out.println("Still present at: "); + for (String res : allNewResources) { + System.out.println("- "+res); + } + + } + } + } + } + removedStrings: for (String oldString : stringsResourcesOld.keySet()) { + if (stringsResourcesNew.get(oldString) == null) { + List allOldResources = stringsResourcesOld.get(oldString); + if (allOldResources.size() >= 1) { + if (allOldResources.size() > 0) { + for (String res : allOldResources) { + String newString = resourcesStringsNew.get(res); + if (newString != null) { + continue removedStrings; + } + } + } + System.out.println("---------------------------------------------"); + System.out.println("--- REMOVED STRING --------------------------"); + System.out.println("---------------------------------------------"); + System.out.println("String: \""+oldString+"\""); + if (allOldResources.size() > 0) { + System.out.println("Was at:"); + for (String res : allOldResources) { + System.out.println("- "+res); + } + } + System.out.println("This string is absent from the new file, and its attached resources are missing too."); + } + } + } + } + + private void showTypoDialog(String oldMsg, String newMsg, String checkReport) { + String typo = "Typo"; + String review = "Review"; + String outdated = "Outdated"; + String none = "None"; + Object[] options = new Object[] {typo, review, outdated, none}; + + int result = JOptionPane.showOptionDialog(null, checkReport, "Choose action", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, typo); + + if (result < 0 || result >= options.length) { + System.out.println("No decision"); + return; + } + + System.out.println("Decision: "+options[result]); + + if (options[result] != none) { + msgIdToReplace.put(oldMsg, newMsg); + if (options[result] == review) { + msgIdToReview.add(newMsg); + } else if (options[result] == outdated) { + msgIdOutdated.add(newMsg); + } + } + + } + + + public void updatePoFiles(Project proj) { + File poFolder = new File(proj.baseContent.baseFolder.getAbsolutePath()+File.separator+"assets"+File.separator+"translation"); + File[] poFiles = poFolder.listFiles(new FileFilter() { + @Override + public boolean accept(File arg0) { + return arg0.isFile() && arg0.getName().endsWith(".po"); + } + }); + + for (File f : poFiles) { + updatePoFile(proj, f); + } + } + + private void updatePoFile(Project proj, File f) { + POParser parser = new POParser(); + POFile poFile = parser.parseFile(f); + + Map translations = new LinkedHashMap(); + + //Collect existing translations + if (poFile.getHeader() != null) { + Vector msgstrs = poFile.getHeader().getStringsByType(POEntry.StringType.HEADER); + String header = ""; + if (!msgstrs.isEmpty()) { + if (msgstrs.size() == 1) { + header = msgstrs.get(0); + } else { + for (String msgstr : msgstrs) { + header += msgstr; + header += "\n"; + } + } + } + translations.put("", header); + } + + for (POEntry entry : poFile.getEntryArray()) { + Vector msgids = entry.getStringsByType(POEntry.StringType.MSGID); + Vector msgstrs = entry.getStringsByType(POEntry.StringType.MSGSTR); + if (msgids == null || msgids.size() == 0) continue; + String msgid = msgids.get(0); + if (msgids.size() > 1) { + for (int i = 1; i < msgids.size(); i++) { + msgid += msgids.get(i); + } + } + if (msgid.contains("\\n")) { + msgid = msgid.replaceAll("\\\\n", "\\\\n\"\n\""); + msgid = "\"\n\""+msgid; + } + String translation = ""; + if (!msgstrs.isEmpty()) { + if (msgstrs.size() == 1) { + translation = msgstrs.get(0); + } else { + for (String msgstr : msgstrs) { + translation += msgstr; + } + } + if (translation.contains("\\n")) { + translation = translation.replaceAll("\\\\n", "\\\\n\"\n\""); + translation = "\"\n\""+translation; + } + } + translations.put(msgid, translation); + } + + //Patch data + for (String oldId : msgIdToReplace.keySet()) { + String newId = msgIdToReplace.get(oldId); + if (translations.containsKey(oldId)) { + String trans = translations.get(oldId); + translations.remove(oldId); + translations.put(newId, trans); + } + } + + for (String msgid : msgIdToReview) { + if (translations.containsKey(msgid)) { + String trans = translations.get(msgid); + if (trans != null && trans.length() >= 1) translations.put(msgid, "[REVIEW]"+trans); + } + } + + for (String msgid : msgIdOutdated) { + if (translations.containsKey(msgid)) { + String trans = translations.get(msgid); + if (trans != null && trans.length() >= 1) translations.put(msgid, "[OUTDATED]"+trans); + } + } + + PoPotWriter.writePoFile(stringsResourcesNew, translations, new File(proj.alteredContent.baseFolder.getAbsolutePath()+File.separator+f.getName())); + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/tools/i18n/PotGenerator.java b/src/com/gpl/rpg/atcontentstudio/model/tools/i18n/PotGenerator.java new file mode 100644 index 0000000..df42692 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/tools/i18n/PotGenerator.java @@ -0,0 +1,100 @@ +package com.gpl.rpg.atcontentstudio.model.tools.i18n; + +import java.io.File; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +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.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.gamedata.QuestStage; +import com.gpl.rpg.atcontentstudio.model.maps.WorldmapSegment; + +public class PotGenerator { + + public static void generatePotFileForProject(Project proj) { + Map> stringsResources = new LinkedHashMap>(); + Map resourcesStrings = new LinkedHashMap(); + + GameSource gsrc = proj.baseContent; + + for (ActorCondition ac : gsrc.gameData.actorConditions) { + pushString(stringsResources, resourcesStrings, ac.display_name, getPotContextComment(ac)); + } + + for (Dialogue d : gsrc.gameData.dialogues ) { + pushString(stringsResources, resourcesStrings, d.message, getPotContextComment(d)); + if (d.replies == null) continue; + for (Dialogue.Reply r : d.replies) { + if (r.text != null && !r.text.equals(Dialogue.Reply.GO_NEXT_TEXT) ) { + pushString(stringsResources, resourcesStrings, r.text, getPotContextComment(d)+":"+d.replies.indexOf(r)); + } + } + } + + for (ItemCategory ic : gsrc.gameData.itemCategories) { + pushString(stringsResources, resourcesStrings, ic.name, getPotContextComment(ic)); + } + + for (Item i : gsrc.gameData.items) { + pushString(stringsResources, resourcesStrings, i.name, getPotContextComment(i)); + pushString(stringsResources, resourcesStrings, i.description, getPotContextComment(i)+":description"); + } + + for (NPC npc : gsrc.gameData.npcs ) { + pushString(stringsResources, resourcesStrings, npc.name, getPotContextComment(npc)); + } + + for (Quest q : gsrc.gameData.quests) { + if (q.visible_in_log != null && q.visible_in_log != 0) { + pushString(stringsResources, resourcesStrings, q.name, getPotContextComment(q)); + for (QuestStage qs : q.stages) { + pushString(stringsResources, resourcesStrings, qs.log_text, getPotContextComment(q)+":"+Integer.toString(qs.progress)); + } + } + } + + for (WorldmapSegment ws : gsrc.worldmap) { + for (WorldmapSegment.NamedArea area : ws.labels.values()) { + pushString(stringsResources, resourcesStrings, area.name, gsrc.worldmap.worldmapFile.getName()+":"+ws.id+":"+area.id); + } + } + + File f = new File(proj.alteredContent.baseFolder, "english.pot"); + PoPotWriter.writePotFile(stringsResources, f); + + } + + private static void pushString (Map> stringsResources, Map resourcesStrings, String translatableString, String resourceIdentifier) { + if (translatableString == null) return; + if (translatableString.length() == 0) return; + if (translatableString.contains("\"")) { + translatableString = translatableString.replaceAll("\"", "\\\\\""); + } + if (translatableString.contains("\n")) { + translatableString = translatableString.replaceAll("\n", "\\\\n\"\n\""); + translatableString = "\"\n\""+translatableString; + } + resourcesStrings.put(resourceIdentifier, translatableString); + List resourceIdentifiers = stringsResources.get(translatableString); + if (resourceIdentifiers == null) { + resourceIdentifiers = new LinkedList(); + stringsResources.put(translatableString, resourceIdentifiers); + } + resourceIdentifiers.add(resourceIdentifier); + } + + private static String getPotContextComment(JSONElement e) { + return e.jsonFile.getName()+":"+e.id; + } + + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/tools/resoptimizer/ResourcesCompactor.java b/src/com/gpl/rpg/atcontentstudio/model/tools/resoptimizer/ResourcesCompactor.java new file mode 100644 index 0000000..b294fdd --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/tools/resoptimizer/ResourcesCompactor.java @@ -0,0 +1,381 @@ +package com.gpl.rpg.atcontentstudio.model.tools.resoptimizer; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.CharBuffer; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.imageio.ImageIO; + +import org.json.simple.JSONArray; + +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.Project; +import com.gpl.rpg.atcontentstudio.model.gamedata.ActorCondition; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.model.gamedata.Item; +import com.gpl.rpg.atcontentstudio.model.gamedata.NPC; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMapSet; +import com.gpl.rpg.atcontentstudio.model.sprites.SpriteSheetSet; +import com.gpl.rpg.atcontentstudio.utils.FileUtils; +import com.whoischarles.util.json.Minify; +import com.whoischarles.util.json.Minify.UnterminatedCommentException; +import com.whoischarles.util.json.Minify.UnterminatedRegExpLiteralException; +import com.whoischarles.util.json.Minify.UnterminatedStringLiteralException; + +import tiled.core.TileSet; +import tiled.io.TMXMapWriter; + +/** + * + * @author Kevin + * + * To use this, paste the following script in the beanshell console of ATCS. + * Don't forget to change the project number to suit your needs. + * +import com.gpl.rpg.atcontentstudio.model.tools.resoptimizer.ResourcesCompactor; +import com.gpl.rpg.atcontentstudio.model.Workspace; + +proj = Workspace.activeWorkspace.projects.get(0); +new ResourcesCompactor(proj).compactData(); + * + */ +public class ResourcesCompactor { + + public static String DEFAULT_REL_PATH_IN_PROJECT = "compressed"+File.separator; + + private Project proj; + private File baseFolder; + private List compressedSpritesheets = new LinkedList(); + private List preservedSpritesheets = new LinkedList(); + + private Map spritesRelocationForObjects = new LinkedHashMap(); + private Integer currentSpritesheetIndexForObjects = 0; + private CompressedSpritesheet currentSpritesheetForObjects = null; + + private Map spritesRelocationForMaps = new LinkedHashMap(); + private Map spritesheetsBySidForMaps = new LinkedHashMap(); + private Integer currentSpritesheetIndexForMaps = 0; + private CompressedSpritesheet currentSpritesheetForMaps = null; + + public ResourcesCompactor(Project proj) { + this.proj = proj; + this.baseFolder = new File(proj.baseFolder, DEFAULT_REL_PATH_IN_PROJECT); + if (!baseFolder.exists()) baseFolder.mkdirs(); + } + + public void compactData() { + compactJsonData(); + for(CompressedSpritesheet cs : compressedSpritesheets) { + cs.drawFile(); + } + for (File preserved : preservedSpritesheets) { + FileUtils.copyFile(preserved, new File(baseFolder.getAbsolutePath()+File.separator+DEFAULT_DRAWABLE_REL_PATH+File.separator+preserved.getName())); + } + compactMaps(); + } + + public void compactJsonData() { + final List filesCovered = new LinkedList(); + + File folder = new File(baseFolder.getAbsolutePath()+File.separator+GameDataSet.DEFAULT_REL_PATH_IN_SOURCE); + if (!folder.exists()) folder.mkdirs(); + + for (ActorCondition ac : proj.baseContent.gameData.actorConditions) { + if (filesCovered.contains(ac.jsonFile)) continue; + File currentFile = ac.jsonFile; + filesCovered.add(currentFile); + List dataToSave = new ArrayList(); + for (ActorCondition acond : proj.baseContent.gameData.actorConditions) { + if (!acond.jsonFile.equals(currentFile)) continue; + Map json = acond.toJson(); + json.put("iconID", convertObjectSprite(acond.icon_id).toStringID()); + dataToSave.add(json); + } + File target = new File(folder, ac.jsonFile.getName()); + writeJson(dataToSave, target); + } + + for (Item it : proj.baseContent.gameData.items) { + if (filesCovered.contains(it.jsonFile)) continue; + File currentFile = it.jsonFile; + filesCovered.add(currentFile); + List dataToSave = new ArrayList(); + for (Item item : proj.baseContent.gameData.items) { + if (!item.jsonFile.equals(currentFile)) continue; + Map json = item.toJson(); + json.put("iconID", convertObjectSprite(item.icon_id).toStringID()); + dataToSave.add(json); + } + File target = new File(folder, it.jsonFile.getName()); + writeJson(dataToSave, target); + } + + + for (NPC np : proj.baseContent.gameData.npcs) { + if (filesCovered.contains(np.jsonFile)) continue; + File currentFile = np.jsonFile; + filesCovered.add(currentFile); + List dataToSave = new ArrayList(); + for (NPC npc : proj.baseContent.gameData.npcs) { + if (!npc.jsonFile.equals(currentFile)) continue; + Map json = npc.toJson(); + if (proj.getImage(npc.icon_id).getWidth(null) == TILE_WIDTH_IN_PIXELS || proj.getImage(npc.icon_id).getHeight(null) == TILE_HEIGHT_IN_PIXELS) { + json.put("iconID", convertObjectSprite(npc.icon_id).toStringID()); + } + dataToSave.add(json); + } + File target = new File(folder, np.jsonFile.getName()); + writeJson(dataToSave, target); + } + + File[] remainingFiles = proj.baseContent.gameData.baseFolder.listFiles(new FileFilter() { + @Override + public boolean accept(File arg0) { + return arg0.getName().endsWith(".json") && !filesCovered.contains(arg0); + } + }); + + for (File source : remainingFiles) { + File target = new File(folder, source.getName()); + minifyJson(source, target); + } + } + + private Minify jsonMinifier = new Minify(); + + private void writeJson(List dataToSave, File target) { + 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(target); + w.write(jsonMinifier.minify(toWrite)); + w.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void minifyJson(File source, File target) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + FileInputStream fis = new FileInputStream(source); + jsonMinifier.minify(fis, baos); + FileWriter w = new FileWriter(target); + w.write(baos.toString()); + w.close(); + } catch (IOException e) { + e.printStackTrace(); + } catch (UnterminatedRegExpLiteralException e) { + e.printStackTrace(); + } catch (UnterminatedCommentException e) { + e.printStackTrace(); + } catch (UnterminatedStringLiteralException e) { + e.printStackTrace(); + } + } + private void compactMaps() { + for (TMXMap map : proj.baseContent.gameMaps.tmxMaps) { + TMXMap clone = map.clone(); + for (GameDataElement gde : clone.getBacklinks()) { + gde.removeBacklink(clone); + } + clone.getBacklinks().clear(); + tiled.core.Map tmx = clone.tmxMap; + compactMap(tmx, map.id); + clone.tmxMap = null; + clone.groups.clear(); + clone = null; + } + } + + private void compactMap(tiled.core.Map tmx, String name) { + File target = new File(baseFolder.getAbsolutePath()+File.separator+TMXMapSet.DEFAULT_REL_PATH_IN_SOURCE+File.separator+name+".tmx"); + if (!target.getParentFile().exists()) target.getParentFile().mkdirs(); + + Map localConvertions = new LinkedHashMap(); + List usedSpritesheets = new LinkedList(); + + List toRemove = new LinkedList(); + + for (tiled.core.TileSet ts : tmx.getTileSets()) { + if (!ts.getName().equalsIgnoreCase("map_dynamic_placeholders")) { + toRemove.add(ts); + } + } + + for (tiled.core.TileLayer layer : tmx.getTileLayers()) { + for (int x = 0; x < layer.getWidth(); x++) { + for (int y = 0; y < layer.getHeight(); y++) { + tiled.core.Tile tile = layer.getTileAt(x, y); + if (tile != null && !tile.getTileSet().getName().equalsIgnoreCase("map_dynamic_placeholders")) { + SpritesheetId sid = convertMapSprite(SpritesheetId.toStringID(tile.getTileSet().getName(), tile.getId())); + localConvertions.put(tile, sid); + if (!usedSpritesheets.contains(spritesheetsBySidForMaps.get(sid))) { + usedSpritesheets.add(spritesheetsBySidForMaps.get(sid)); + } + } + } + } + } + + Map csToTs = new LinkedHashMap(); + for (CompressedSpritesheet cs : usedSpritesheets) { + cs.drawFile(); + tiled.core.TileSet ts = new tiled.core.TileSet(); + csToTs.put(cs, ts); + tiled.util.BasicTileCutter cutter = new tiled.util.BasicTileCutter(TILE_WIDTH_IN_PIXELS, TILE_HEIGHT_IN_PIXELS, 0, 0); + try { + ts.importTileBitmap(cs.f.getAbsolutePath(), cutter); + } catch (IOException e) { + e.printStackTrace(); + } + ts.setName(cs.prefix+Integer.toString(cs.index)); + //ts.setSource("../drawable/"+ts.getName()+TILESHEET_SUFFIX); + tmx.addTileset(ts); + } + + for (tiled.core.TileLayer layer : tmx.getTileLayers()) { + for (tiled.core.Tile tile : localConvertions.keySet()) { + SpritesheetId sid = localConvertions.get(tile); + layer.replaceTile(tile, csToTs.get(spritesheetsBySidForMaps.get(sid)).getTile(sid.offset)); + } + } + + for (tiled.core.TileSet ts : toRemove) { + tmx.removeTileset(ts); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + TMXMapWriter writer = new TMXMapWriter(); + writer.settings.layerCompressionMethod = TMXMapWriter.Settings.LAYER_COMPRESSION_METHOD_ZLIB; + try { + writer.writeMap(tmx, baos, target.getAbsolutePath()); + String xml = baos.toString(); + FileWriter w = new FileWriter(target); + w.write(xml); + w.close(); + } catch (Exception e) { + e.printStackTrace(); + } + + } + + private SpritesheetId convertObjectSprite(String originalSpriteId) { + if (spritesRelocationForObjects.containsKey(SpritesheetId.getInstance(originalSpriteId))) { + return spritesRelocationForObjects.get(SpritesheetId.getInstance(originalSpriteId)); + } else if (currentSpritesheetForObjects == null || !currentSpritesheetForObjects.hasFreeSlot()) { + currentSpritesheetForObjects = new CompressedSpritesheet(TILESHEET_PREFIX_FOR_OBJECTS, currentSpritesheetIndexForObjects); + compressedSpritesheets.add(currentSpritesheetForObjects); + currentSpritesheetIndexForObjects++; + } + SpritesheetId sid = currentSpritesheetForObjects.addSprite(originalSpriteId); + spritesRelocationForObjects.put(SpritesheetId.getInstance(originalSpriteId), sid); + return sid; + } + + private SpritesheetId convertMapSprite(String originalSpriteId) { + if (spritesRelocationForMaps.containsKey(SpritesheetId.getInstance(originalSpriteId))) { + return spritesRelocationForMaps.get(SpritesheetId.getInstance(originalSpriteId)); + } else if (currentSpritesheetForMaps == null || !currentSpritesheetForMaps.hasFreeSlot()) { + currentSpritesheetForMaps = new CompressedSpritesheet(TILESHEET_PREFIX_FOR_MAPS, currentSpritesheetIndexForMaps); + compressedSpritesheets.add(currentSpritesheetForMaps); + currentSpritesheetIndexForMaps++; + } + SpritesheetId sid = currentSpritesheetForMaps.addSprite(originalSpriteId); + spritesRelocationForMaps.put(SpritesheetId.getInstance(originalSpriteId), sid); + spritesheetsBySidForMaps.put(sid, currentSpritesheetForMaps); + return sid; + } + + + private static final int TILESHEET_WIDTH_IN_SPRITES = 8; + private static final int TILESHEET_HEIGHT_IN_SPRITES = 8; + private static final int TILE_WIDTH_IN_PIXELS = 32; + private static final int TILE_HEIGHT_IN_PIXELS = 32; + + private static final String TILESHEET_PREFIX_FOR_OBJECTS = "obj_"; + private static final String TILESHEET_PREFIX_FOR_MAPS = "map_"; + private static final String TILESHEET_SUFFIX = ".png"; + + private static final String DEFAULT_DRAWABLE_REL_PATH = SpriteSheetSet.DEFAULT_REL_PATH_IN_SOURCE; + + private class CompressedSpritesheet { + String prefix; + int index; + File f; + + + boolean mustDraw = true; + int nextFreeSlot = 0; + String[] originalSpritesId = new String[TILESHEET_WIDTH_IN_SPRITES * TILESHEET_HEIGHT_IN_SPRITES]; + + public CompressedSpritesheet(String prefix, int index) { + this.prefix = prefix; + this.index = index; + + File folder = new File(ResourcesCompactor.this.baseFolder.getAbsolutePath()+File.separator+DEFAULT_DRAWABLE_REL_PATH); + if (!folder.exists()) folder.mkdirs(); + this.f = new File(folder, prefix+Integer.toString(index)+TILESHEET_SUFFIX); + } + + public boolean hasFreeSlot() { + return nextFreeSlot < TILESHEET_WIDTH_IN_SPRITES * TILESHEET_HEIGHT_IN_SPRITES; + } + + public SpritesheetId addSprite(String spriteId) { + mustDraw = true; + originalSpritesId[nextFreeSlot] = spriteId; + nextFreeSlot++; + return SpritesheetId.getInstance(prefix+Integer.toString(index), nextFreeSlot - 1); + } + + + public void drawFile() { + if (!mustDraw) return; + BufferedImage img = new BufferedImage(TILESHEET_WIDTH_IN_SPRITES * TILE_WIDTH_IN_PIXELS, TILESHEET_HEIGHT_IN_SPRITES * TILE_HEIGHT_IN_PIXELS, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = (Graphics2D)img.getGraphics(); + Color transparent = new Color(0, 0, 0, 0); + g.setColor(transparent); + g.fillRect(0, 0, img.getWidth(), img.getHeight()); + for (int i = 0; i < nextFreeSlot; i++) { + g.drawImage( + proj.getImage(originalSpritesId[i]), + (i % TILESHEET_WIDTH_IN_SPRITES) * TILE_WIDTH_IN_PIXELS, + (i / TILESHEET_WIDTH_IN_SPRITES) * TILE_HEIGHT_IN_PIXELS, + TILE_WIDTH_IN_PIXELS, + TILE_HEIGHT_IN_PIXELS, + null); + } + try { + ImageIO.write(img, "png", f); + mustDraw = false; + } catch (IOException e) { + e.printStackTrace(); + } + } + } + +} + + \ No newline at end of file diff --git a/src/com/gpl/rpg/atcontentstudio/model/tools/resoptimizer/SpritesheetId.java b/src/com/gpl/rpg/atcontentstudio/model/tools/resoptimizer/SpritesheetId.java new file mode 100644 index 0000000..1200da7 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/tools/resoptimizer/SpritesheetId.java @@ -0,0 +1,38 @@ +package com.gpl.rpg.atcontentstudio.model.tools.resoptimizer; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class SpritesheetId { + static Map instancesCache = new LinkedHashMap(); + + String tileset; + int offset; + + static SpritesheetId getInstance(String id) { + String[] values = id.split(":"); + return getInstance(values[0], Integer.parseInt(values[1])); + } + + static SpritesheetId getInstance(String tilesetId, int offset) { + if (!instancesCache.containsKey(toStringID(tilesetId, offset))) { + SpritesheetId instance = new SpritesheetId(tilesetId, offset); + instancesCache.put(instance.toStringID(), instance); + } + return instancesCache.get(toStringID(tilesetId, offset)); + } + + private SpritesheetId(String tileset, int offset) { + this.tileset = tileset; + this.offset = offset; + } + + public String toStringID() { + return toStringID(tileset, offset); + } + + static String toStringID(String tileset, int offset) { + return tileset+":"+Integer.toString(offset); + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/AboutEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/AboutEditor.java index ed3574b..ab6de8d 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/AboutEditor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/AboutEditor.java @@ -51,6 +51,7 @@ public class AboutEditor extends Editor { "
" + "Contributors:
" + "Quentin Delvallet
" + + "Žižkin
" + "
" + "This project uses the following libraries:
" + "JSON.simple by Yidong Fang & Chris Nokleberg.
" + @@ -78,6 +79,12 @@ public class AboutEditor extends Editor { "jsoup by Jonathan Hedley
" + "License: MIT License
" + "
" + + "A slightly modified version of General PO Parser by Balázs Tóth
" + + "License: GPL v3
" + + "
" + + "A slightly modified version of Minify.java by Charles Bihis
" + + "License: Douglas Crockford variant of MIT License
" + + "
" + "See the tabs below to find the full license text for each of these.
" + "
" + "The Windows installer was created with:
" + @@ -129,6 +136,8 @@ public class AboutEditor extends Editor { editorTabsHolder.add("BeanShell License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.LGPLv3.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text")); editorTabsHolder.add("SipHash for Java License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.siphash-zackehh.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text")); editorTabsHolder.add("jsoup License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.jsoup.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text")); + editorTabsHolder.add("General PO Parser License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.GPLv3.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text")); + editorTabsHolder.add("Minify.java License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.minify.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")); } diff --git a/src/com/gpl/rpg/atcontentstudio/ui/DefaultIcons.java b/src/com/gpl/rpg/atcontentstudio/ui/DefaultIcons.java index 2035460..fd29106 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/DefaultIcons.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/DefaultIcons.java @@ -75,6 +75,14 @@ public class DefaultIcons { 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 FOLDER_BOOKMARK_CLOSED_RES = "/com/gpl/rpg/atcontentstudio/img/folder_bookmark_closed.png"; + public static Image getBookmarkClosedImage() { return getImage(FOLDER_BOOKMARK_CLOSED_RES); } + public static Image getBookmarkClosedIcon() { return getIcon(FOLDER_BOOKMARK_CLOSED_RES); } + + private static String FOLDER_BOOKMARK_OPEN_RES = "/com/gpl/rpg/atcontentstudio/img/folder_bookmark_open.png"; + public static Image getBookmarkOpenImage() { return getImage(FOLDER_BOOKMARK_OPEN_RES); } + public static Image getBookmarkOpenIcon() { return getIcon(FOLDER_BOOKMARK_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); } @@ -276,6 +284,14 @@ public class DefaultIcons { public static Image getStatusUnknownImage() { return getImage(STATUS_UNKNOWN_RES); } public static Image getStatusUnknownIcon() { return getIcon(STATUS_UNKNOWN_RES); } + private static String BOOKMARK_INACTIVE = "/com/gpl/rpg/atcontentstudio/img/bookmark_inactive.png"; + public static Image getBookmarkInactiveImage() { return getImage(BOOKMARK_INACTIVE); } + public static Image getBookmarkInactiveIcon() { return getIcon(BOOKMARK_INACTIVE); } + + private static String BOOKMARK_ACTIVE = "/com/gpl/rpg/atcontentstudio/img/bookmark_active.png"; + public static Image getBookmarkActiveImage() { return getImage(BOOKMARK_ACTIVE); } + public static Image getBookmarkActiveIcon() { return getIcon(BOOKMARK_ACTIVE); } + private static Image getImage(String res) { if (imageCache.get(res) == null) { diff --git a/src/com/gpl/rpg/atcontentstudio/ui/Editor.java b/src/com/gpl/rpg/atcontentstudio/ui/Editor.java index 703e701..edb5912 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/Editor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/Editor.java @@ -39,7 +39,6 @@ import javax.swing.JSpinner.NumberEditor; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.ListModel; -import javax.swing.Scrollable; import javax.swing.SpinnerNumberModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; @@ -653,7 +652,7 @@ public abstract class Editor extends JPanel implements ProjectElementListener { return gdeBox; } - public JComboBox addQuestStageBox(JPanel pane, Project proj, String label, Integer initialValue, boolean writable, final FieldUpdateListener listener, Quest quest, final JComboBox questSelectionBox) { + public JComboBox addQuestStageBox(JPanel pane, Project proj, String label, Integer initialValue, boolean writable, final FieldUpdateListener listener, Quest quest, @SuppressWarnings("rawtypes") final JComboBox questSelectionBox) { JPanel gdePane = new JPanel(); gdePane.setLayout(new JideBoxLayout(gdePane, JideBoxLayout.LINE_AXIS, 6)); JLabel gdeLabel = new JLabel(label); @@ -703,7 +702,7 @@ public abstract class Editor extends JPanel implements ProjectElementListener { - @SuppressWarnings({ "rawtypes", "unchecked" }) + @SuppressWarnings({ "rawtypes"}) public JList addBacklinksList(JPanel pane, GameDataElement gde) { return addBacklinksList(pane, gde, "Elements linking to this one"); } @@ -813,7 +812,7 @@ public abstract class Editor extends JPanel implements ProjectElementListener { if (text.length() > 60) { text = text.substring(0, 57)+"..."; } - label.setText(((GameDataElement)value).getDataType().toString()+"/"+((Quest)((QuestStage)value).parent).id+":"+text); + label.setText(((GameDataElement)value).getDataType().toString()+"/"+((Quest)((QuestStage)value).parent).id+"#"+((QuestStage)value).progress+":"+text); } else { label.setText(((GameDataElement)value).getDataType().toString()+"/"+((GameDataElement)value).getDesc()); } @@ -867,7 +866,6 @@ public abstract class Editor extends JPanel implements ProjectElementListener { return currentQuest.stages.get(index - 1); } - @SuppressWarnings("unchecked") @Override public void setSelectedItem(Object anItem) { selected = (QuestStage) anItem; diff --git a/src/com/gpl/rpg/atcontentstudio/ui/ExportProjectWizard.java b/src/com/gpl/rpg/atcontentstudio/ui/ExportProjectWizard.java new file mode 100644 index 0000000..8179a29 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/ExportProjectWizard.java @@ -0,0 +1,226 @@ +package com.gpl.rpg.atcontentstudio.ui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; + +import javax.swing.ButtonGroup; +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.JRadioButton; + +import com.gpl.rpg.atcontentstudio.ATContentStudio; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.model.maps.TMXMapSet; +import com.gpl.rpg.atcontentstudio.model.sprites.SpriteSheetSet; +import com.jidesoft.swing.JideBoxLayout; + +public class ExportProjectWizard extends JDialog { + + private static final long serialVersionUID = -8745083621008868612L; + + JPanel pane; + JLabel errorLabel, fileSelectionLabel; + JRadioButton asZip, overSources; + JComboBox target; + JButton browse; + JButton okButton, cancelButton; + + Project proj; + + public ExportProjectWizard(Project proj) { + + super(ATContentStudio.frame); + setTitle("Export project for inclusion in-game"); + + this.proj = proj; + + pane = new JPanel(); + pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS, 6)); + + errorLabel = new JLabel(); + + pane.add(errorLabel, JideBoxLayout.FIX); + pane.add(new JLabel("Export this ATCS project..."), JideBoxLayout.FIX); + + ButtonGroup radioGroup = new ButtonGroup(); + + asZip = new JRadioButton("... as a Zip archive"); + radioGroup.add(asZip); + overSources = new JRadioButton("... into a game source folder"); + radioGroup.add(overSources); + asZip.setSelected(true); + + pane.add(asZip, JideBoxLayout.FIX); + pane.add(overSources, JideBoxLayout.FIX); + + ActionListener updateListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + updateState(); + } + }; + asZip.addActionListener(updateListener); + overSources.addActionListener(updateListener); + + target = new JComboBox(); + target.setEditable(true); + target.addActionListener(updateListener); + 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 (asZip.isSelected()) { + if (f.isDirectory() || f.getName().endsWith(".zip") || f.getName().endsWith(".ZIP")) { + return super.accept(f); + } else { + return false; + } + } else { + return f.isDirectory(); + } + } + }; + jfc.setFileSelectionMode(asZip.isSelected() ? JFileChooser.FILES_AND_DIRECTORIES : JFileChooser.DIRECTORIES_ONLY); + jfc.setSelectedFile(new File(target.getSelectedItem() == null ? "" : target.getSelectedItem().toString())); + jfc.setMultiSelectionEnabled(false); + int result = jfc.showOpenDialog(ATContentStudio.frame); + if (result == JFileChooser.APPROVE_OPTION) { + File f = jfc.getSelectedFile(); + if (asZip.isSelected() && !f.getAbsolutePath().substring(f.getAbsolutePath().length() - 4, f.getAbsolutePath().length()).equalsIgnoreCase(".zip")) { + f = new File(f.getAbsolutePath()+".zip"); + } + target.setSelectedItem(f.getAbsolutePath()); + updateState(); + } + } + }); + JPanel fileSelectionPane = new JPanel(); + fileSelectionPane.setLayout(new JideBoxLayout(fileSelectionPane, JideBoxLayout.LINE_AXIS, 6)); + fileSelectionLabel = new JLabel("Zip file: "); + fileSelectionPane.add(fileSelectionLabel, JideBoxLayout.FIX); + fileSelectionPane.add(target, JideBoxLayout.VARY); + fileSelectionPane.add(browse, JideBoxLayout.FIX); + + pane.add(fileSelectionPane, JideBoxLayout.FIX); + + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new JideBoxLayout(buttonPane, JideBoxLayout.LINE_AXIS, 6)); + buttonPane.add(new JPanel(), JideBoxLayout.VARY); + cancelButton = new JButton("Cancel"); + buttonPane.add(cancelButton, JideBoxLayout.FIX); + okButton = new JButton("Ok"); + buttonPane.add(okButton, JideBoxLayout.FIX); + + pane.add(new JPanel(), JideBoxLayout.VARY); + + pane.add(buttonPane, JideBoxLayout.FIX); + + + cancelButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + ExportProjectWizard.this.setVisible(false); + ExportProjectWizard.this.dispose(); + } + }); + + okButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + if (asZip.isSelected()) { + ExportProjectWizard.this.proj.exportProjectAsZipPackage(new File(target.getSelectedItem().toString())); + } else { + ExportProjectWizard.this.proj.exportProjectOverGameSource(new File(target.getSelectedItem().toString())); + } + ExportProjectWizard.this.setVisible(false); + ExportProjectWizard.this.dispose(); + } + }); + + updateState(); + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(pane, BorderLayout.CENTER); + + setMinimumSize(new Dimension(500,150)); + pack(); + + Dimension sdim = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension wdim = getSize(); + setLocation((sdim.width - wdim.width)/2, (sdim.height - wdim.height)/2); + } + + private void updateState() { + if (asZip.isSelected()) { + fileSelectionLabel.setText("Zip file: "); + } else { + fileSelectionLabel.setText("Game source folder: "); + } + + + File f = new File(target.getSelectedItem() == null ? "" : target.getSelectedItem().toString()); + if (asZip.isSelected()) { + if (target.getSelectedItem() == null || target.getSelectedItem().toString().length() <= 0) { + errorLabel.setText("You must select where to save the zip file."); + okButton.setEnabled(false); + } else if (f.isDirectory()) { + errorLabel.setText("The selected target is a directory. It should be a zip file."); + okButton.setEnabled(false); + } else if (!(f.getName().toLowerCase().endsWith(".zip"))) { + errorLabel.setText("The selected target is not a zip file. It should be a zip file."); + okButton.setEnabled(false); + } else if (f.exists()) { + errorLabel.setText("The selected target is an existing zip file. It will be overwritten."); + okButton.setEnabled(true); + } else { + errorLabel.setText("Everything looks good !"); + okButton.setEnabled(true); + } + } else { + if (target.getSelectedItem() == null || target.getSelectedItem().toString().length() <= 0) { + errorLabel.setText("You must select an AT source root folder."); + okButton.setEnabled(false); + } else if (!f.isDirectory() || !f.exists()) { + errorLabel.setText("The selected AT source is not a folder. It should be an existing AT source root folder."); + okButton.setEnabled(false); + } else { + File res = new File(f, GameDataSet.DEFAULT_REL_PATH_IN_SOURCE); + File drawable = new File(f, SpriteSheetSet.DEFAULT_REL_PATH_IN_SOURCE); + File xml = new File(f, TMXMapSet.DEFAULT_REL_PATH_IN_SOURCE); + if (!res.exists()) { + errorLabel.setText("The selected AT source root folder does not contain the \"res\" folder."); + okButton.setEnabled(true); + } else if (!drawable.exists()) { + errorLabel.setText("The selected AT source root folder does not contain the \"drawable\" folder."); + okButton.setEnabled(true); + } else if (!xml.exists()) { + errorLabel.setText("The selected AT source root folder does not contain the \"xml\" folder."); + okButton.setEnabled(true); + } else { + errorLabel.setText("Everything looks good !"); + okButton.setEnabled(true); + } + } + } + revalidate(); + repaint(); + + } + + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/ProjectsTree.java b/src/com/gpl/rpg/atcontentstudio/ui/ProjectsTree.java index 577ba15..0d8148b 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/ProjectsTree.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/ProjectsTree.java @@ -33,6 +33,7 @@ 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.bookmarks.BookmarkEntry; import com.gpl.rpg.atcontentstudio.model.gamedata.JSONElement; import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; import com.gpl.rpg.atcontentstudio.model.maps.WorldmapSegment; @@ -595,6 +596,8 @@ public class ProjectsTree extends JPanel { ATContentStudio.frame.openEditor((WorldmapSegment)node); } else if (node instanceof WriterModeData) { ATContentStudio.frame.openEditor((WriterModeData)node); + } else if (node instanceof BookmarkEntry) { + ATContentStudio.frame.openEditor(((BookmarkEntry)node).bookmarkedElement); } else if (node instanceof SavedGame) { if (konamiCodeEntered) { ATContentStudio.frame.openEditor((SavedGame)node); diff --git a/src/com/gpl/rpg/atcontentstudio/ui/StudioFrame.java b/src/com/gpl/rpg/atcontentstudio/ui/StudioFrame.java index 2af28da..25ccaa5 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/StudioFrame.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/StudioFrame.java @@ -213,6 +213,8 @@ public class StudioFrame extends JFrame { openEditor((Spritesheet) node); } else if (node instanceof TMXMap) { openEditor((TMXMap) node); + } else if (node instanceof WorldmapSegment) { + openEditor((WorldmapSegment) node); } else if (node instanceof WriterModeData) { openEditor((WriterModeData) node); } diff --git a/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceActions.java b/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceActions.java index 35123c8..4ef50c6 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceActions.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceActions.java @@ -34,7 +34,6 @@ import com.gpl.rpg.atcontentstudio.model.gamedata.JSONElement; import com.gpl.rpg.atcontentstudio.model.gamedata.Quest; import com.gpl.rpg.atcontentstudio.model.gamedata.QuestStage; 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; @@ -60,6 +59,7 @@ public class WorkspaceActions { public void actionPerformed(ActionEvent e) { if (!(selectedNode instanceof Project)) return; Workspace.closeProject((Project) selectedNode); + selectedNode = null; }; public void selectionChanged(ProjectTreeNode selectedNode, TreePath[] selectedPaths) { setEnabled(selectedNode instanceof Project); @@ -353,21 +353,7 @@ public class WorkspaceActions { 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); - } + new ExportProjectWizard(selectedNode.getProject()).setVisible(true); }; public void selectionChanged(ProjectTreeNode selectedNode, TreePath[] selectedPaths) { setEnabled(selectedNode != null && selectedNode.getProject() != null); diff --git a/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceSettingsEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceSettingsEditor.java index 0172366..95c7ce5 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceSettingsEditor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceSettingsEditor.java @@ -182,12 +182,19 @@ public class WorkspaceSettingsEditor extends JDialog { useInternetBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - translatorLanguagesBox.setEnabled(useInternetBox.isSelected()); + translatorLanguagesBox.setEnabled(useInternetBox.isSelected() && translatorModeBox.isSelected()); translatorModeBox.setEnabled(useInternetBox.isSelected()); checkUpdatesBox.setEnabled(useInternetBox.isSelected()); } }); + translatorModeBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + translatorLanguagesBox.setEnabled(translatorModeBox.isSelected()); + } + }); + return pane; } diff --git a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/ActorConditionEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/ActorConditionEditor.java index 266de5b..b87c20e 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/ActorConditionEditor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/ActorConditionEditor.java @@ -36,6 +36,7 @@ public class ActorConditionEditor extends JSONElementEditor { private IntegerBasedCheckBox stackingBox; //private JTextField roundVisualField; + @SuppressWarnings("rawtypes") private JComboBox roundVisualField; private JSpinner roundHpMinField; private JSpinner roundHpMaxField; @@ -43,6 +44,7 @@ public class ActorConditionEditor extends JSONElementEditor { private JSpinner roundApMaxField; //private JTextField fullRoundVisualField; + @SuppressWarnings("rawtypes") private JComboBox fullRoundVisualField; private JSpinner fullRoundHpMinField; private JSpinner fullRoundHpMaxField; diff --git a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/DialogueEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/DialogueEditor.java index f83c84a..fd09e6d 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/DialogueEditor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/DialogueEditor.java @@ -96,6 +96,7 @@ public class DialogueEditor extends JSONElementEditor { private JPanel rewardsParamsPane; private MyComboBox rewardMap; private JTextField rewardObjId; + @SuppressWarnings("rawtypes") private JComboBox rewardObjIdCombo; private MyComboBox rewardObj; private JComponent rewardValue; @@ -120,6 +121,7 @@ public class DialogueEditor extends JSONElementEditor { private JComboBox requirementTypeCombo; private JPanel requirementParamsPane; private MyComboBox requirementObj; + @SuppressWarnings("rawtypes") private JComboBox requirementSkill; private JTextField requirementObjId; private JComponent requirementValue; @@ -446,6 +448,7 @@ public class DialogueEditor extends JSONElementEditor { } break; case alignmentChange: + case alignmentSet: rewardMap = null; rewardObjId = addTextField(pane, "Faction: ", reward.reward_obj_id, writable, listener); rewardObjIdCombo = null; @@ -470,7 +473,7 @@ public class DialogueEditor extends JSONElementEditor { 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); + rewardValue = addIntegerField(pane, "Quantity: ", reward.reward_value, true, writable, listener); break; case removeQuestProgress: case questProgress: @@ -694,7 +697,7 @@ public class DialogueEditor extends JSONElementEditor { removeElementListener(requirementObj); } - requirementTypeCombo = addEnumValueBox(pane, "Requirement type: ", Requirement.RequirementType.values(), requirement.type, writable, listener); + requirementTypeCombo = addEnumValueBox(pane, "Requirement type: ", Requirement.RequirementType.values(), requirement == null ? null : requirement.type, writable, listener); requirementParamsPane = new JPanel(); requirementParamsPane.setLayout(new JideBoxLayout(requirementParamsPane, JideBoxLayout.PAGE_AXIS)); updateRequirementParamsEditorPane(requirementParamsPane, requirement, listener); @@ -711,7 +714,7 @@ public class DialogueEditor extends JSONElementEditor { removeElementListener(requirementObj); } - if (requirement.type != null) { + if (requirement != null && requirement.type != null) { switch (requirement.type) { case consumedBonemeals: case spentGold: @@ -885,6 +888,10 @@ public class DialogueEditor extends JSONElementEditor { label.setText("Change alignment for faction "+rewardObjDesc+" : "+reward.reward_value); label.setIcon(new ImageIcon(DefaultIcons.getAlignmentIcon())); break; + case alignmentSet: + label.setText("Set alignment for faction "+rewardObjDesc+" : "+reward.reward_value); + label.setIcon(new ImageIcon(DefaultIcons.getAlignmentIcon())); + break; case createTimer: label.setText("Create timer "+rewardObjDesc); label.setIcon(new ImageIcon(DefaultIcons.getTimerIcon())); diff --git a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/JSONElementEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/JSONElementEditor.java index 48f4316..e313805 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/JSONElementEditor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/JSONElementEditor.java @@ -44,7 +44,6 @@ import com.gpl.rpg.atcontentstudio.ui.ScrollablePanel; import com.gpl.rpg.atcontentstudio.ui.ScrollablePanel.ScrollableSizeHint; import com.gpl.rpg.atcontentstudio.ui.sprites.SpriteChooser; import com.jidesoft.swing.JideBoxLayout; -import com.jidesoft.swing.JideScrollPane; import com.jidesoft.swing.JideTabbedPane; public abstract class JSONElementEditor extends Editor { @@ -205,6 +204,7 @@ public abstract class JSONElementEditor extends Editor { } }); 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); @@ -243,8 +243,10 @@ public abstract class JSONElementEditor extends Editor { } JButton prev = new JButton(new ImageIcon(DefaultIcons.getArrowLeftIcon())); JButton next = new JButton(new ImageIcon(DefaultIcons.getArrowRightIcon())); + final JButton bookmark = new JButton(new ImageIcon(node.bookmark != null ? DefaultIcons.getBookmarkActiveIcon() : DefaultIcons.getBookmarkInactiveIcon())); savePane.add(prev, JideBoxLayout.FIX); savePane.add(next, JideBoxLayout.FIX); + savePane.add(bookmark, JideBoxLayout.FIX); if (node.getParent().getIndex(node) == 0) { prev.setEnabled(false); } @@ -269,6 +271,20 @@ public abstract class JSONElementEditor extends Editor { } } }); + bookmark.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + if (node.bookmark == null) { + node.getProject().bookmark(node); + bookmark.setIcon(new ImageIcon(DefaultIcons.getBookmarkActiveIcon())); + } else { + node.bookmark.delete(); + bookmark.setIcon(new ImageIcon(DefaultIcons.getBookmarkInactiveIcon())); + } + } + }); + + //Placeholder. Fills the eventual remaining space. savePane.add(new JPanel(), JideBoxLayout.VARY); pane.add(savePane, JideBoxLayout.FIX); @@ -303,6 +319,7 @@ public abstract class JSONElementEditor extends Editor { } + @SuppressWarnings("unchecked") public boolean idChanging() { JSONElement node = (JSONElement) target; List toModify = new LinkedList(); diff --git a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/NPCEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/NPCEditor.java index e27101b..639c33d 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/NPCEditor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/NPCEditor.java @@ -63,6 +63,7 @@ public class NPCEditor extends JSONElementEditor { private JTextField idField; private JTextField nameField; private JTextField spawnGroupField; + private JTextField factionField; private JSpinner experienceField; private MyComboBox dialogueBox; private MyComboBox droplistBox; @@ -246,6 +247,7 @@ public class NPCEditor extends JSONElementEditor { idField = addTextField(pane, "Internal ID: ", npc.id, npc.writable, listener); nameField = addTranslatableTextField(pane, "Display name: ", npc.name, npc.writable, listener); spawnGroupField = addTextField(pane, "Spawn group ID: ", npc.spawngroup_id, npc.writable, listener); + factionField = addTextField(pane, "Faction ID: ", npc.faction_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); @@ -1256,6 +1258,8 @@ public class NPCEditor extends JSONElementEditor { npcIcon.repaint(); } else if (source == spawnGroupField) { npc.spawngroup_id = (String) value; + } else if (source == factionField) { + npc.faction_id = (String) value; } else if (source == dialogueBox) { if (npc.dialogue != null) { npc.dialogue.removeBacklink(npc); @@ -1398,7 +1402,7 @@ public class NPCEditor extends JSONElementEditor { } else if (source == hitTargetConditionClear && (Boolean) value) { selectedHitEffectTargetCondition.magnitude = ActorCondition.MAGNITUDE_CLEAR; selectedHitEffectTargetCondition.duration = null; - updateHitSourceTimedConditionWidgets(selectedHitEffectTargetCondition); + updateHitTargetTimedConditionWidgets(selectedHitEffectTargetCondition); hitTargetConditionsListModel.itemChanged(selectedHitEffectTargetCondition); updateHit = true; } else if (source == hitTargetConditionApply && (Boolean) value) { @@ -1407,7 +1411,7 @@ public class NPCEditor extends JSONElementEditor { if (selectedHitEffectTargetCondition.duration == null || selectedHitEffectTargetCondition.duration == ActorCondition.DURATION_NONE) { selectedHitEffectTargetCondition.duration = 1; } - updateHitSourceTimedConditionWidgets(selectedHitEffectTargetCondition); + updateHitTargetTimedConditionWidgets(selectedHitEffectTargetCondition); hitTargetConditionsListModel.itemChanged(selectedHitEffectTargetCondition); updateHit = true; } else if (source == hitTargetConditionImmunity && (Boolean) value) { @@ -1416,7 +1420,7 @@ public class NPCEditor extends JSONElementEditor { if (selectedHitEffectTargetCondition.duration == null || selectedHitEffectTargetCondition.duration == ActorCondition.DURATION_NONE) { selectedHitEffectTargetCondition.duration = 1; } - updateHitSourceTimedConditionWidgets(selectedHitEffectTargetCondition); + updateHitTargetTimedConditionWidgets(selectedHitEffectTargetCondition); hitTargetConditionsListModel.itemChanged(selectedHitEffectTargetCondition); updateHit = true; } else if (source == hitTargetConditionMagnitude) { @@ -1428,12 +1432,12 @@ public class NPCEditor extends JSONElementEditor { if (selectedHitEffectTargetCondition.duration == null || selectedHitEffectTargetCondition.duration == ActorCondition.DURATION_NONE) { selectedHitEffectTargetCondition.duration = 1; } - updateHitSourceTimedConditionWidgets(selectedHitEffectTargetCondition); + updateHitTargetTimedConditionWidgets(selectedHitEffectTargetCondition); hitTargetConditionsListModel.itemChanged(selectedHitEffectTargetCondition); updateHit = true; } else if (source == hitTargetConditionForever && (Boolean) value) { selectedHitEffectTargetCondition.duration = ActorCondition.DURATION_FOREVER; - updateHitSourceTimedConditionWidgets(selectedHitEffectTargetCondition); + updateHitTargetTimedConditionWidgets(selectedHitEffectTargetCondition); hitTargetConditionsListModel.itemChanged(selectedHitEffectTargetCondition); updateHit = true; } else if (source == hitTargetConditionDuration) { diff --git a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/QuestEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/QuestEditor.java index 4e416f5..39a0b73 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/QuestEditor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/QuestEditor.java @@ -40,8 +40,6 @@ 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"; diff --git a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/dialoguetree/DialogueGraphView.java b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/dialoguetree/DialogueGraphView.java index 52fbe53..d091d8b 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/dialoguetree/DialogueGraphView.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/dialoguetree/DialogueGraphView.java @@ -49,11 +49,14 @@ import prefuse.visual.expression.InGroupPredicate; import com.gpl.rpg.atcontentstudio.ATContentStudio; import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.Workspace; import com.gpl.rpg.atcontentstudio.model.gamedata.Dialogue; import com.gpl.rpg.atcontentstudio.model.gamedata.NPC; import com.gpl.rpg.atcontentstudio.model.gamedata.Requirement; import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; import com.gpl.rpg.atcontentstudio.ui.gamedataeditors.DialogueEditor; +import com.gpl.rpg.atcontentstudio.utils.WeblateIntegration; +import com.gpl.rpg.atcontentstudio.utils.WeblateIntegration.WeblateTranslationUnit; import com.jidesoft.swing.JideBoxLayout; public class DialogueGraphView extends Display { @@ -72,11 +75,16 @@ public class DialogueGraphView extends Display { public static final String HIDDEN_REPLY = "hidden_reply"; public static final String HAS_REQS = "has_reqs"; + private static final String TRANSLATION_LOADING="Loading translation..."; + private String translationHeader="\n---[ Translation from weblate ]---\n"; + + private static final Schema DECORATOR_SCHEMA = PrefuseLib.getVisualItemSchema(); private Dialogue dialogue; private Image npcIcon; private Graph graph; + private Boolean translatorMode; private Map cells = new HashMap(); @@ -88,6 +96,10 @@ public class DialogueGraphView extends Display { } else { npcIcon = DefaultIcons.getNPCIcon(); } + translatorMode = Workspace.activeWorkspace.settings.useInternet.getCurrentValue() && Workspace.activeWorkspace.settings.translatorLanguage.getCurrentValue() != null; + if (translatorMode) { + translationHeader = "\n---[ Translation in "+Workspace.activeWorkspace.settings.translatorLanguage.getCurrentValue()+" ]---\n"; + } loadGraph(); // add visual data groups @@ -148,7 +160,7 @@ public class DialogueGraphView extends Display { // 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); + NodeLinkTreeLayout treeLayout = new NodeLinkTreeLayout(GRAPH, prefuse.Constants.ORIENT_LEFT_RIGHT, 120, translatorMode ? 80 : 40, translatorMode ? 80 : 40); treeLayout.setLayoutAnchor(new Point2D.Double(25,300)); layout.add(treeLayout); layout.add(new EdgesLabelDecoratorLayout(EDGES_LABELS)); @@ -186,9 +198,26 @@ public class DialogueGraphView extends Display { if (dialogue.switch_to_npc != null) { npcIcon = dialogue.switch_to_npc.getIcon(); } - Node dNode = graph.addNode(); + final Node dNode = graph.addNode(); cells.put(dialogue, dNode); - dNode.setString(LABEL, dialogue.message != null ? dialogue.message : "[Selector]"); + String label; + Thread t = null; + if (dialogue.message == null) { + label = "[Selector]"; + } else if (translatorMode) { + label = dialogue.message+translationHeader+TRANSLATION_LOADING; + final String message = dialogue.message; + t = new Thread("Get weblate translation for "+message) { + public void run() { + WeblateTranslationUnit unit = WeblateIntegration.getTranslationUnit(message); + dNode.setString(LABEL, message+translationHeader+unit.translatedText); + }; + }; + } else { + label = dialogue.message; + } + dNode.setString(LABEL, label); + if (t != null) t.start(); dNode.set(ICON, npcIcon); dNode.set(TARGET, dialogue); if (dialogue.replies != null) { @@ -206,11 +235,27 @@ public class DialogueGraphView extends Display { } public Node addReply(Dialogue d, Dialogue.Reply r, Image npcIcon) { - Node rNode; + final 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.setString(LABEL, translatorMode ? r.text + "\n---\n" + WeblateIntegration.getTranslationUnit(r.text).translatedText : r.text); + String label; + Thread t = null; + if (translatorMode) { + label = r.text+translationHeader+TRANSLATION_LOADING; + final String message = r.text; + t = new Thread("Get weblate translation for "+message) { + public void run() { + WeblateTranslationUnit unit = WeblateIntegration.getTranslationUnit(message); + rNode.setString(LABEL, message+translationHeader+unit.translatedText); + }; + }; + } else { + label = r.text; + } + rNode.setString(LABEL, label); + if (t != null) t.start(); rNode.set(ICON, DefaultIcons.getHeroIcon()); rNode.set(TARGET, d); rNode.set(REPLY, r); diff --git a/src/com/gpl/rpg/atcontentstudio/ui/map/TMXMapEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/map/TMXMapEditor.java index 0ebf936..edf68a8 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/map/TMXMapEditor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/map/TMXMapEditor.java @@ -162,6 +162,7 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe private JComboBox evaluateTriggerBox; private JSpinner quantityField; private JCheckBox spawnActiveForNewGame; + private JCheckBox spawnIgnoreAreas; private JTextField spawngroupField; @SuppressWarnings("rawtypes") private JList npcList; @@ -211,7 +212,7 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe editorTabsHolder.add("TMX", tmxScroller); editorTabsHolder.add("XML", xmlScroller); //editorTabsHolder.add("Replacements", replScroller); - editorTabsHolder.add("Replacements", getReplacementSimulatorPane()); + editorTabsHolder.add("Testing", getReplacementSimulatorPane()); } @@ -629,6 +630,7 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe spawngroupField = addTextField(pane, "Spawn group ID: ", ((SpawnArea)selected).spawngroup_id, ((TMXMap)target).writable, listener); quantityField = addIntegerField(pane, "Number of spawned NPCs: ", ((SpawnArea)selected).quantity, false, ((TMXMap)target).writable, listener); spawnActiveForNewGame = addBooleanBasedCheckBox(pane, "Active in a new game: ", ((SpawnArea)selected).active, ((TMXMap)target).writable, listener); + spawnIgnoreAreas = addBooleanBasedCheckBox(pane, "Monsters can walk on other game objects: ", ((SpawnArea)selected).ignoreAreas, ((TMXMap)target).writable, listener); npcListModel = new SpawnGroupNpcListModel((SpawnArea) selected); npcList = new JList(npcListModel); npcList.setCellRenderer(new GDERenderer(true, ((TMXMap)target).writable)); @@ -780,7 +782,11 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe public JPanel getReplacementSimulatorPane() { JPanel replacementSimulator = new JPanel(); replacementSimulator.setLayout(new JideBoxLayout(replacementSimulator, JideBoxLayout.PAGE_AXIS)); + JPanel toolsPane = new JPanel(); + toolsPane.setLayout(new JideBoxLayout(toolsPane, JideBoxLayout.LINE_AXIS)); final JCheckBox walkableVisibleBox = new JCheckBox("Show \""+TMXMap.WALKABLE_LAYER_NAME+"\" layer."); + final JCheckBox showHeroBox = new JCheckBox("Show hero on walkable tiles under the mouse pointer."); + final JCheckBox showTooltipBox = new JCheckBox("Show tooltip with stack of tiles."); JPanel areasActivationPane = new JPanel(); areasActivationPane.setLayout(new JideBoxLayout(areasActivationPane, JideBoxLayout.PAGE_AXIS)); TreeModel areasTreeModel = new ReplaceAreasActivationTreeModel(); @@ -798,10 +804,15 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe activateAndViewPane.add(areasActivationPane, JideBoxLayout.FIX); activateAndViewPane.add(new JScrollPane(viewer), JideBoxLayout.VARY); - replacementSimulator.add(walkableVisibleBox, JideBoxLayout.FIX); + toolsPane.add(walkableVisibleBox, JideBoxLayout.FIX); + toolsPane.add(showTooltipBox, JideBoxLayout.FIX); + toolsPane.add(showHeroBox, JideBoxLayout.FIX); + toolsPane.add(new JPanel(), JideBoxLayout.VARY); + + replacementSimulator.add(toolsPane, JideBoxLayout.FIX); replacementSimulator.add(activateAndViewPane, JideBoxLayout.VARY); - walkableVisibleBox.setSelected(true); + walkableVisibleBox.setSelected(viewer.showWalkable); walkableVisibleBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -810,6 +821,21 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe viewer.repaint(); } }); + + showHeroBox.setSelected(viewer.showHeroWithMouse); + showHeroBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + viewer.showHeroWithMouse = showHeroBox.isSelected(); + } + }); + showTooltipBox.setSelected(viewer.showTooltip); + showTooltipBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + viewer.showTooltip = showTooltipBox.isSelected(); + } + }); activate.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -1753,6 +1779,20 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe } }); savePane.add(delete, JideBoxLayout.FIX); + final JButton bookmark = new JButton(new ImageIcon(map.bookmark != null ? DefaultIcons.getBookmarkActiveIcon() : DefaultIcons.getBookmarkInactiveIcon())); + savePane.add(bookmark, JideBoxLayout.FIX); + bookmark.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + if (map.bookmark == null) { + map.getProject().bookmark(map); + bookmark.setIcon(new ImageIcon(DefaultIcons.getBookmarkActiveIcon())); + } else { + map.bookmark.delete(); + bookmark.setIcon(new ImageIcon(DefaultIcons.getBookmarkInactiveIcon())); + } + } + }); } else { if (proj.getMap(map.id) != map) { savePane.add(message = new JLabel(ALTERED_EXISTS_MESSAGE), JideBoxLayout.FIX); @@ -1816,6 +1856,20 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe } } }); + final JButton bookmark = new JButton(new ImageIcon(map.bookmark != null ? DefaultIcons.getBookmarkActiveIcon() : DefaultIcons.getBookmarkInactiveIcon())); + savePane.add(bookmark, JideBoxLayout.FIX); + bookmark.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + if (map.bookmark == null) { + map.getProject().bookmark(map); + bookmark.setIcon(new ImageIcon(DefaultIcons.getBookmarkActiveIcon())); + } else { + map.bookmark.delete(); + bookmark.setIcon(new ImageIcon(DefaultIcons.getBookmarkInactiveIcon())); + } + } + }); //Placeholder. Fills the eventual remaining space. savePane.add(new JPanel(), JideBoxLayout.VARY); pane.add(savePane, JideBoxLayout.FIX); @@ -1993,6 +2047,11 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe SpawnArea area = (SpawnArea) selectedMapObject; area.active = (Boolean) value; } + } else if (source == spawnIgnoreAreas) { + if (selectedMapObject instanceof SpawnArea) { + SpawnArea area = (SpawnArea) selectedMapObject; + area.ignoreAreas = (Boolean) value; + } } else if (source == requirementTypeCombo) { if (selectedMapObject instanceof KeyArea) { KeyArea area = (KeyArea) selectedMapObject; @@ -2176,11 +2235,13 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe private TMXMap map; public ReplaceArea highlighted = null; public boolean showWalkable = true; + public boolean showHeroWithMouse = true; + public boolean showTooltip = true; private OrthogonalRenderer renderer; - private String groundName, objectsName, aboveName, walkableName; + private String groundName, objectsName, aboveName, topName, walkableName; private Map layersByName = new LinkedHashMap(); - private tiled.core.TileLayer ground, objects, above, walkable; + private tiled.core.TileLayer ground, objects, above, top, walkable; private Map> replacementsForLayer = new LinkedHashMap>(); public Map activeReplacements = new LinkedHashMap(); @@ -2199,8 +2260,9 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe addMouseMotionListener(new MouseMotionAdapter() { @Override public void mouseMoved(MouseEvent e) { + Point oldTooltippedTile = new Point(tooltippedTile); tooltippedTile.setLocation(e.getX() / 32, e.getY() / 32); - if (!((TMXMap)target).tmxMap.contains(tooltippedTile.x, tooltippedTile.y)) { + if (!showTooltip || !((TMXMap)target).tmxMap.contains(tooltippedTile.x, tooltippedTile.y)) { if (tooltipActivated) { //Hides the tooltip... ToolTipManager.sharedInstance().setEnabled(false); @@ -2208,12 +2270,16 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe tooltipActivated = false; } } else { - if (!tooltipActivated) { + if (showTooltip && !tooltipActivated) { ToolTipManager.sharedInstance().registerComponent(TMXReplacementViewer.this); ToolTipManager.sharedInstance().setEnabled(true); tooltipActivated = true; } } + if (showHeroWithMouse && (oldTooltippedTile.x != tooltippedTile.x || oldTooltippedTile.y != tooltippedTile.y) ) { + TMXReplacementViewer.this.revalidate(); + TMXReplacementViewer.this.repaint(); + } } }); @@ -2223,9 +2289,9 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe } public void init() { - groundName = objectsName = aboveName = walkableName = null; + groundName = objectsName = aboveName = walkableName = topName = null; layersByName.clear(); - ground = objects = above = walkable = null; + ground = objects = above = walkable = top = null; replacementsForLayer.clear(); for (tiled.core.MapLayer layer : map.tmxMap.getLayers()) { @@ -2237,6 +2303,8 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe objectsName = layer.getName(); } else if (TMXMap.ABOVE_LAYER_NAME.equalsIgnoreCase(layer.getName())) { aboveName = layer.getName(); + } else if (TMXMap.TOP_LAYER_NAME.equalsIgnoreCase(layer.getName())) { + topName = layer.getName(); } else if (TMXMap.WALKABLE_LAYER_NAME.equalsIgnoreCase(layer.getName())) { walkableName = layer.getName(); } @@ -2266,6 +2334,7 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe ground = mergeReplacements(groundName); objects = mergeReplacements(objectsName); above = mergeReplacements(aboveName); + top = mergeReplacements(topName); walkable = mergeReplacements(walkableName); } @@ -2321,10 +2390,18 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe renderer.paintTileLayer(g2d, objects); } + if (showHeroWithMouse && tooltippedTile != null && ((TMXMap)target).tmxMap.contains(tooltippedTile.x, tooltippedTile.y) && + walkable != null && walkable.getTileAt(tooltippedTile.x, tooltippedTile.y) == null) { + g2d.drawImage(DefaultIcons.getHeroImage(), tooltippedTile.x * 32, tooltippedTile.y * 32, 32, 32, null); + } + if (above != null) { renderer.paintTileLayer(g2d, above); } - + + if (top != null) { + renderer.paintTileLayer(g2d, top); + } if (walkable != null && showWalkable) { renderer.paintTileLayer(g2d, walkable); } @@ -2381,10 +2458,12 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe JLabel noTileGround = new JLabel(new ImageIcon(DefaultIcons.getNullifyImage().getScaledInstance(32, 32, Image.SCALE_DEFAULT))); JLabel noTileObjects = new JLabel(new ImageIcon(DefaultIcons.getNullifyImage().getScaledInstance(32, 32, Image.SCALE_DEFAULT))); JLabel noTileAbove = new JLabel(new ImageIcon(DefaultIcons.getNullifyImage().getScaledInstance(32, 32, Image.SCALE_DEFAULT))); + JLabel noTileTop = new JLabel(new ImageIcon(DefaultIcons.getNullifyImage().getScaledInstance(32, 32, Image.SCALE_DEFAULT))); { noTileGround.setPreferredSize(new Dimension(32, 32)); noTileObjects.setPreferredSize(new Dimension(32, 32)); noTileAbove.setPreferredSize(new Dimension(32, 32)); + noTileTop.setPreferredSize(new Dimension(32, 32)); } Point tooltippedTile = new Point(); JToolTip tt = null; @@ -2406,6 +2485,20 @@ public class TMXMapEditor extends Editor implements TMXMap.MapChangedOnDiskListe if (tooltippedTile != null) { Image tile; JLabel label; + + if (top != null && top.getTileAt(tooltippedTile.x, tooltippedTile.y) != null) { + tile = top.getTileAt(tooltippedTile.x, tooltippedTile.y).getImage(); + } else { + tile = null; + } + if (tile != null) { + label = new JLabel(new ImageIcon(tile)); + label.setPreferredSize(new Dimension(32,32)); + content.add(label, JideBoxLayout.FIX); + //Fix when (if?) Top is advertised publicly. +// } else { +// content.add(noTileTop, JideBoxLayout.FIX); + } if (above != null && above.getTileAt(tooltippedTile.x, tooltippedTile.y) != null) { tile = above.getTileAt(tooltippedTile.x, tooltippedTile.y).getImage(); diff --git a/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapEditor.java index 3b60527..7e1afdd 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapEditor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapEditor.java @@ -316,7 +316,7 @@ public class WorldMapEditor extends Editor implements FieldUpdateListener { @Override public void mouseClicked(MouseEvent e) { String selectedMap = null; - boolean update = false; +// boolean update = false; int x = (int) (e.getX() / mapView.zoomLevel); int y = (int) (e.getY() / mapView.zoomLevel); for (String s : mapView.mapLocations.keySet()) { @@ -333,19 +333,19 @@ public class WorldMapEditor extends Editor implements FieldUpdateListener { if (mapView.getSelectedMapsIDs().size() > 1) { removeFromSelection(selectedMap); // mapView.selected.remove(selectedMap); - update = true; +// update = true; } } else { addToSelection(selectedMap); // mapView.selected.add(selectedMap); - update = true; +// update = true; } } else { clearSelection(); // mapView.selected.clear(); addToSelection(selectedMap); // mapView.selected.add(selectedMap); - update = true; +// update = true; } } } else if (editMode == EditMode.addMap && mapBeingAddedID != null) { @@ -354,7 +354,7 @@ public class WorldMapEditor extends Editor implements FieldUpdateListener { pushToModel(); } mapView.updateFromModel(); - update = true; +// update = true; mapBeingAddedID = null; } // if (update) { @@ -986,6 +986,20 @@ public class WorldMapEditor extends Editor implements FieldUpdateListener { } } }); + final JButton bookmark = new JButton(new ImageIcon(node.bookmark != null ? DefaultIcons.getBookmarkActiveIcon() : DefaultIcons.getBookmarkInactiveIcon())); + savePane.add(bookmark, JideBoxLayout.FIX); + bookmark.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + if (node.bookmark == null) { + node.getProject().bookmark(node); + bookmark.setIcon(new ImageIcon(DefaultIcons.getBookmarkActiveIcon())); + } else { + node.bookmark.delete(); + bookmark.setIcon(new ImageIcon(DefaultIcons.getBookmarkInactiveIcon())); + } + } + }); //Placeholder. Fills the eventual remaining space. savePane.add(new JPanel(), JideBoxLayout.VARY); return savePane; diff --git a/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapView.java b/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapView.java index 094fcca..68f16e9 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapView.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/map/WorldMapView.java @@ -23,7 +23,6 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import javax.swing.JComponent; @@ -86,7 +85,7 @@ public class WorldMapView extends JComponent implements Scrollable { @Override public void mouseClicked(MouseEvent e) { String selectedMap = null; - boolean update = false; +// boolean update = false; int x = (int) (e.getX() / zoomLevel); int y = (int) (e.getY() / zoomLevel); for (String s : mapLocations.keySet()) { @@ -191,7 +190,7 @@ public class WorldMapView extends JComponent implements Scrollable { FontMetrics mifm = g2.getFontMetrics(); int mapIdLabelHeight = mifm.getHeight(); - for (String s : mapLocations.keySet()) { + for (String s : new HashSet(mapLocations.keySet())) { int x = mapLocations.get(s).x; int y = mapLocations.get(s).y; @@ -481,6 +480,10 @@ public class WorldMapView extends JComponent implements Scrollable { offsetX = worldmap.segmentX * TILE_SIZE; offsetY = worldmap.segmentY * TILE_SIZE; for (String s : worldmap.mapLocations.keySet()) { + if (proj.getMap(s) == null) { + System.err.println("Warning. Worldmap "+worldmap.id+" references map "+s+" but it doesn't exist in this project"); + continue; + } 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; @@ -497,6 +500,10 @@ public class WorldMapView extends JComponent implements Scrollable { worldmap.segmentX = offsetX / TILE_SIZE; worldmap.segmentY = offsetY / TILE_SIZE; for (String id : worldmap.mapLocations.keySet()) { + if (worldmap.getProject().getMap(id) == null) { + System.err.println("Warning. Worldmap "+worldmap.id+" references map "+id+" but it doesn't exist in this project"); + continue; + } worldmap.getProject().getMap(id).removeBacklink(worldmap); } worldmap.mapLocations.clear(); @@ -508,6 +515,10 @@ public class WorldMapView extends JComponent implements Scrollable { } for (String id : worldmap.mapLocations.keySet()) { + if (worldmap.getProject().getMap(id) == null) { + System.err.println("Warning. Worldmap "+worldmap.id+" references map "+id+" but it doesn't exist in this project"); + continue; + } worldmap.getProject().getMap(id).addBacklink(worldmap); } diff --git a/src/com/gpl/rpg/atcontentstudio/ui/sprites/SpritesheetEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/sprites/SpritesheetEditor.java index 89692e5..d3f9a3f 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/sprites/SpritesheetEditor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/sprites/SpritesheetEditor.java @@ -100,6 +100,20 @@ public class SpritesheetEditor extends Editor { }); buttonPane.add(openImage, JideBoxLayout.FIX); buttonPane.add(getWarningLabel(), JideBoxLayout.FIX); + final JButton bookmark = new JButton(new ImageIcon(sheet.bookmark != null ? DefaultIcons.getBookmarkActiveIcon() : DefaultIcons.getBookmarkInactiveIcon())); + buttonPane.add(bookmark, JideBoxLayout.FIX); + bookmark.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + if (target.bookmark == null) { + target.getProject().bookmark(target); + bookmark.setIcon(new ImageIcon(DefaultIcons.getBookmarkActiveIcon())); + } else { + target.bookmark.delete(); + bookmark.setIcon(new ImageIcon(DefaultIcons.getBookmarkInactiveIcon())); + } + } + }); buttonPane.add(new JPanel(), JideBoxLayout.VARY); pane.add(buttonPane, JideBoxLayout.FIX); addLabelField(pane, "Spritesheet ID: ", sheet.id); diff --git a/src/com/gpl/rpg/atcontentstudio/ui/tools/BeanShellView.java b/src/com/gpl/rpg/atcontentstudio/ui/tools/BeanShellView.java index 4984898..fbca8b7 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/tools/BeanShellView.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/tools/BeanShellView.java @@ -22,6 +22,7 @@ import bsh.EvalError; import bsh.Interpreter; import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; +import com.gpl.rpg.atcontentstudio.ui.WorkerDialog; import com.jidesoft.swing.JideBoxLayout; public class BeanShellView extends JFrame { @@ -85,17 +86,29 @@ public class BeanShellView extends JFrame { run.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - Interpreter shInt = new Interpreter(); - PrintStream printOut = new PrintStream(new AreaOutputStream(outArea)); + final Interpreter shInt = new Interpreter(); + final PrintStream printOut = new PrintStream(new AreaOutputStream(outArea)); shInt.setOut(printOut); - PrintStream printErr = new PrintStream(new AreaOutputStream(errArea)); + final PrintStream printErr = new PrintStream(new AreaOutputStream(errArea)); shInt.setErr(printErr); - try { - shInt.eval(shArea.getText()); - } catch (EvalError e1) { - e1.printStackTrace(printErr); - } + WorkerDialog.showTaskMessage("Running your script...", null, new Runnable() { + @Override + public void run() { + + try { + shInt.eval(shArea.getText()); + } catch (EvalError e1) { + e1.printStackTrace(printErr); + } + printOut.flush(); + printErr.flush(); + printOut.close(); + printErr.close(); + + } + }); + } }); diff --git a/src/com/gpl/rpg/atcontentstudio/ui/tools/writermode/WriterModeEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/tools/writermode/WriterModeEditor.java index a83c68e..47509d1 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/tools/writermode/WriterModeEditor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/tools/writermode/WriterModeEditor.java @@ -10,13 +10,11 @@ import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; -import java.io.IOException; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import javax.imageio.ImageIO; import javax.swing.AbstractAction; import javax.swing.ImageIcon; import javax.swing.JButton; @@ -343,12 +341,12 @@ public class WriterModeEditor extends Editor { } Node rNode; - int i = 1; +// int i = 1; for (WriterModeData.WriterReply reply : dialogue.replies) { if (reply instanceof EmptyReply && reply.next_dialogue != null) { if (cells.get(reply.next_dialogue) == null) { rNode = addDialogueNode(reply.next_dialogue); - Edge e = graph.addEdge(dNode, rNode); + /*Edge e = */graph.addEdge(dNode, rNode); } else { rNode = cells.get(reply.next_dialogue); Edge e = graph.addEdge(dNode, rNode); @@ -357,7 +355,7 @@ public class WriterModeEditor extends Editor { } else { if (cells.get(reply) == null) { rNode = addReplyNode(reply); - Edge e = graph.addEdge(dNode, rNode); + /*Edge e = */graph.addEdge(dNode, rNode); // e.setString(LABEL, "#"+i++); } else { rNode = cells.get(reply); @@ -382,7 +380,7 @@ public class WriterModeEditor extends Editor { if (reply.next_dialogue != null) { if (cells.get(reply.next_dialogue) == null) { Node dNode = addDialogueNode(reply.next_dialogue); - Edge e = graph.addEdge(rNode, dNode); + /*Edge e = */graph.addEdge(rNode, dNode); } else { Node dNode = cells.get(reply.next_dialogue); Edge e = graph.addEdge(rNode, dNode); @@ -667,7 +665,7 @@ public class WriterModeEditor extends Editor { cells.get(selected).set(LABEL, selected.text); } - static final String createNextDefaultNodeString = "createNextDefaultNode"; +// static final String createNextDefaultNodeString = "createNextDefaultNode"; final AbstractAction createNextDefaultNode = new AbstractAction("Create next default") { private static final long serialVersionUID = 1658086056088672748L; @@ -691,7 +689,7 @@ public class WriterModeEditor extends Editor { } } if (newNode!= null) { - Edge edge = graph.addEdge(cells.get(selected), newNode); + /*Edge edge = */graph.addEdge(cells.get(selected), newNode); setSelected(newWrNode); m_vis.run("colors"); @@ -734,7 +732,7 @@ public class WriterModeEditor extends Editor { temp.next_dialogue = newWrNode; newNode = addDialogueNode(newWrNode); - Edge edge = graph.addEdge(cells.get(selected), newNode); + /*Edge edge = */graph.addEdge(cells.get(selected), newNode); setSelected(newWrNode); m_vis.run("colors"); @@ -760,7 +758,7 @@ public class WriterModeEditor extends Editor { if (selected instanceof WriterReply) { newWrNode = data.new WriterReply(((WriterReply) selected).parent); newNode = addReplyNode(newWrNode); - Edge edge = graph.addEdge(cells.get(((WriterReply) selected).parent), newNode); + /*Edge edge = */graph.addEdge(cells.get(((WriterReply) selected).parent), newNode); setSelected(newWrNode); m_vis.run("colors"); @@ -905,18 +903,18 @@ public class WriterModeEditor extends Editor { } } - public void selectScrollAndEdit(WriterNode node) { - if (node != null) { - setSelected(node); - - m_vis.run("colors"); - m_vis.run("layout"); - m_vis.run("scrollToSelectedAndEdit"); - - revalidate(); - repaint(); - } - } +// public void selectScrollAndEdit(WriterNode node) { +// if (node != null) { +// setSelected(node); +// +// m_vis.run("colors"); +// m_vis.run("layout"); +// m_vis.run("scrollToSelectedAndEdit"); +// +// revalidate(); +// repaint(); +// } +// } diff --git a/src/com/gpl/rpg/atcontentstudio/utils/DesktopIntegration.java b/src/com/gpl/rpg/atcontentstudio/utils/DesktopIntegration.java index 91ae199..cd229ff 100644 --- a/src/com/gpl/rpg/atcontentstudio/utils/DesktopIntegration.java +++ b/src/com/gpl/rpg/atcontentstudio/utils/DesktopIntegration.java @@ -3,6 +3,9 @@ package com.gpl.rpg.atcontentstudio.utils; import java.awt.Desktop; import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Locale; import com.gpl.rpg.atcontentstudio.model.Workspace; @@ -18,7 +21,7 @@ public class DesktopIntegration { } } else { try { - Runtime.getRuntime().exec(Workspace.activeWorkspace.settings.mapEditorCommand.getCurrentValue()+" \""+f.getAbsolutePath()+"\""); + Runtime.getRuntime().exec(tokenize(Workspace.activeWorkspace.settings.mapEditorCommand.getCurrentValue()+" \""+f.getAbsolutePath()+"\"")); } catch (IOException e) { e.printStackTrace(); } @@ -40,7 +43,7 @@ public class DesktopIntegration { } } else { try { - Runtime.getRuntime().exec(Workspace.activeWorkspace.settings.imageEditorCommand.getCurrentValue()+" \""+f.getAbsolutePath()+"\""); + Runtime.getRuntime().exec(tokenize(Workspace.activeWorkspace.settings.imageEditorCommand.getCurrentValue()+" \""+f.getAbsolutePath()+"\"")); } catch (IOException e) { e.printStackTrace(); } @@ -63,5 +66,43 @@ public class DesktopIntegration { return OSType.Other; } + + private static List quotes = Arrays.asList(new Character[]{'\'', '"'}); + private static List delims = Arrays.asList(new Character[]{' ', '\r', '\n', '\t'}); + + private static String[] tokenize(String command) { + List tokens = new ArrayList(); + boolean inQuote = false; + char usedQuote = '\0'; + StringBuilder sb = new StringBuilder(); + + for (char c : command.toCharArray()) { + if (inQuote) { + if (c == usedQuote) { + inQuote = false; + continue; + } else { + sb.append(c); + } + } else { + if (quotes.contains(c)) { + inQuote = true; + usedQuote = c; + } else if (delims.contains(c)) { + if (sb.length() > 0) { + tokens.add(sb.toString()); + sb = new StringBuilder(); + } + } else { + sb.append(c); + } + } + } + if (sb.length() > 0) { + tokens.add(sb.toString()); + } + return tokens.toArray(new String[tokens.size()]); + } + } diff --git a/src/com/gpl/rpg/atcontentstudio/utils/FileUtils.java b/src/com/gpl/rpg/atcontentstudio/utils/FileUtils.java index 643a413..4e54ae8 100644 --- a/src/com/gpl/rpg/atcontentstudio/utils/FileUtils.java +++ b/src/com/gpl/rpg/atcontentstudio/utils/FileUtils.java @@ -78,16 +78,42 @@ public class FileUtils { } + /** + * cp sourceFolder/* targetFolder/ + * @param sourceFolder + * @param targetFolder + */ + public static void copyOver(File sourceFolder, File targetFolder) { + if (!sourceFolder.isDirectory() || !targetFolder.isDirectory()) return; + for (File f : sourceFolder.listFiles()) { + if (Files.isSymbolicLink(f.toPath())) { + //Skip symlinks + continue; + } else if (f.isDirectory()) { + File dest = new File(targetFolder, f.getName()); + if (!dest.exists()) { + dest.mkdir(); + } + copyOver(f, dest); + } else { + copyFile(f, new File(targetFolder, f.getName())); + } + } + } + private static void zipDir(File dir, String prefix, ZipOutputStream zos) { + if (prefix != "") { + prefix = prefix + File.separator; + } for (File f : dir.listFiles()) { if (f.isDirectory()) { - zipDir(f, prefix+File.separator+f.getName(), zos); + zipDir(f, prefix+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()); + ZipEntry entry = new ZipEntry(prefix+f.getName()); try { zos.putNextEntry(entry); int count; diff --git a/src/net/launchpad/tobal/poparser/POEntry.java b/src/net/launchpad/tobal/poparser/POEntry.java new file mode 100644 index 0000000..140c0cc --- /dev/null +++ b/src/net/launchpad/tobal/poparser/POEntry.java @@ -0,0 +1,100 @@ +/** + * + * @author Balázs Tóth (tobal17@gmail.com) + * + * Modified by Kevin POCHAT for ATCS + */ +package net.launchpad.tobal.poparser; + +import java.util.Vector; + +public class POEntry +{ + private POLine[] Lines; + + public enum StringType + { + /**translator comments*/ + TRLCMNT, + /**extracted comments*/ + EXTCMNT, + /**reference*/ + REFERENCE, + /**flag*/ + FLAG, + /**previous context*/ + PREVCTXT, + /**previous untranslated string singular*/ + PREVUNTRSTRSING, + /**previous untranslated string plural*/ + PREVUNTRSTRPLUR, + /**untranslated string singular*/ + MSGID, + /**translated string*/ + MSGSTR, + /**context*/ + MSGCTXT, + /**header line*/ + HEADER + + // TODO: support for plural untranslated strings, + // and translated string cases + } + + POEntry() + { + Lines = new POLine[0]; + } + + public void addLine(StringType type, String string) + { + boolean hasType = false; + POLine line = null; + for(int i = 0; i < Lines.length; i++) + { + if(Lines[i].getType() == type) + { + hasType = true; + line = Lines[i]; + break; + } + } + if(hasType) + { + line.addString(string); + } + else + { + line = new POLine(type, string); + POLine[] templines = Lines.clone(); + Lines = new POLine[Lines.length+1]; + for(int i = 0; i < Lines.length-1; i++) + { + Lines[i] = templines[i]; + } + Lines[Lines.length-1] = line; + } + } + + public POLine[] getLines() + { + return Lines; + } + + public Vector getStringsFromLine(int index) + { + return Lines[index].getStrings(); + } + + public Vector getStringsByType(POEntry.StringType type) + { + for(int i = 0; i < Lines.length; i++) + { + if(Lines[i].getType() == type) + { + return Lines[i].getStrings(); + } + } + return null; + } +} diff --git a/src/net/launchpad/tobal/poparser/POFile.java b/src/net/launchpad/tobal/poparser/POFile.java new file mode 100644 index 0000000..919db09 --- /dev/null +++ b/src/net/launchpad/tobal/poparser/POFile.java @@ -0,0 +1,143 @@ +/** + * + * @author Balázs Tóth (tobal17@gmail.com) + * + * Modified by Kevin POCHAT for ATCS + */ +package net.launchpad.tobal.poparser; + +import java.io.File; +import java.util.Vector; + +public class POFile +{ + private POEntry[] entries; + private POEntry header; + private File file; + + POFile(POEntry[] entries, POEntry header, File file) + { + this.entries = entries; + this.header = header; + this.file = file; + } + + /** + * Returns with the name of the po file + * @return name of po file + */ + public String getFileName() + { + return file == null ? null : file.getAbsolutePath(); + } + + /** + * Returns with the POEntry object array + * @return POEntry array + */ + public POEntry[] getEntryArray() + { + return entries; + } + + /** + * Gets the POEntry object specified by the index + * @param index, index of the entry + * @return one POEntry object + */ + public POEntry getEntry(int index) + { + return entries[index]; + } + + /** + * Returns how many entries are there in the po file + * @return count of entries + */ + public int getEntryLength() + { + return entries.length; + } + + public POEntry getHeader() { + return header; + } + + /** + * Checks if the specified flag is set in the entry, + * given by the entry index. + * @param flag, string representing the flag + * @param entryIndex, index of the entry to examine + * @return true, if the flag is set, false otherwise + */ + public boolean checkFlag(String flag, int entryIndex) + { + boolean status = false; + Vector strings = new Vector(); + strings = entries[entryIndex].getStringsByType(POEntry.StringType.FLAG); + if (strings != null) + { + for(int i = 0; i < strings.size(); i++) + { + if (strings.get(i).contains(flag)) + { + status = true; + } + } + } + return status; + } + + /** + * Returns with all the strings of the given type, from + * the specified entry. + * @param entryIndex + * @param type + * @return String array of specified type + */ + public String[] getStringsFromEntryByType(int entryIndex, POEntry.StringType type) + { + Vector vector = entries[entryIndex].getStringsByType(type); + String[] str = new String[vector.size()]; + for(int i = 0; i < str.length; i++) + { + str[i] = vector.get(i); + } + return str; + } + + /** + * For debug purposes + */ + public void printFile() + { + for(int i = 0; i < entries.length; i++) + { + POLine[] lines = entries[i].getLines(); + for(int j = 0; j < lines.length; j++) + { + Vector strings = lines[j].getStrings(); + for(int k = 0; k < strings.size(); k++) + { + System.out.println(strings.get(k)); + } + } + } + } + + /** + * For debug purposes + */ + public void printHeader() + { + POLine[] lines = header.getLines(); + for(int j = 0; j < lines.length; j++) + { + Vector strings = lines[j].getStrings(); + for(int k = 0; k < strings.size(); k++) + { + System.out.println(strings.get(k)); + } + } + } +} diff --git a/src/net/launchpad/tobal/poparser/POLine.java b/src/net/launchpad/tobal/poparser/POLine.java new file mode 100644 index 0000000..59b0f8d --- /dev/null +++ b/src/net/launchpad/tobal/poparser/POLine.java @@ -0,0 +1,42 @@ +/** + * + * @author Balázs Tóth (tobal17@gmail.com) + * + * Modified by Kevin POCHAT for ATCS + */ +package net.launchpad.tobal.poparser; + +import java.util.Vector; + +public class POLine +{ + private POEntry.StringType type; + private Vector strings; + + POLine(POEntry.StringType type, String string) + { + this.type = type; + this.strings = new Vector(); + this.strings.add(string); + } + + public void addString(String string) + { + strings.add(string); + } + + public Vector getStrings() + { + return strings; + } + + public POEntry.StringType getType() + { + return type; + } + + public int getVectorSize() + { + return strings.size(); + } +} \ No newline at end of file diff --git a/src/net/launchpad/tobal/poparser/POParser.java b/src/net/launchpad/tobal/poparser/POParser.java new file mode 100644 index 0000000..ec315f1 --- /dev/null +++ b/src/net/launchpad/tobal/poparser/POParser.java @@ -0,0 +1,274 @@ +/** + * + * @author Balázs Tóth (tobal17@gmail.com) + * + * Based on the work of István Nyitrai + * + * Modified by Kevin POCHAT for ATCS + */ +package net.launchpad.tobal.poparser; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Vector; + +public class POParser +{ + private POEntry[] entries; + private POEntry header; +// private File file; + private POEntry.StringType parserMode; + + /** + * Creates a POParser object. Use getPOFile() method, + * to access parsed data. + * @param file, File object of the PO file + */ + public POParser() + { + parserMode = null; + } + + public POFile parseFile(File file) + { + return parse(file); + } + + public POFile parseStream(BufferedReader br) throws IOException { + return parse(br); + } + + private String unQuote(String string) + { + String str = new String(); + if(string.startsWith("\"")) + { + str = string.substring(1); + string = str; + } + if(string.endsWith("\"")) + { + str = string.substring(0, string.length()-1); + } + return str; + } + + private POFile parse(File file) + { + POFile result = null; + try + { + FileReader fr = new FileReader(file); + BufferedReader br = new BufferedReader(fr); + result = parse(br); + br.close(); + fr.close(); + } + catch (java.io.FileNotFoundException e) + { + System.out.println(e.toString()); + } + catch (java.io.IOException e) { + System.out.println(e.toString()); + } + + return result; + } + + private POFile parse(BufferedReader br) throws IOException { + Vector rawentry = new Vector(1, 1); + Vector> rawentries = new Vector>(); + String line; + int id = 0; + while((line = br.readLine()) != null) + { + if(!line.equals("")) + { + if(!line.startsWith("#~")) // ignore + { + rawentry.add(line); + } + } + else + { + if(rawentry.size() > 0) + { + rawentry.add(0, String.valueOf(id)); + id++; + rawentries.add(new Vector(rawentry)); + rawentry = new Vector(1, 1); + } + } + } + + if(!rawentry.equals(rawentries.lastElement()) && rawentry.size() > 0) + { + rawentry.add(0, String.valueOf(id)); + rawentries.add(new Vector(rawentry)); + } + this.header = parseHeader(rawentries); + this.entries = parseEntries(rawentries); + + return new POFile(entries, header, null); + } + + private POEntry parseHeader(Vector> vectors) + { + POEntry tempheader = new POEntry(); + + // is this header? + Vector rawentry = vectors.get(0); + if(new Integer(rawentry.get(0)) == 0 && rawentry.contains("msgid \"\"")) + { + for(int i = 1; i < rawentry.size(); i++) + { + String str = rawentry.get(i); + tempheader.addLine(POEntry.StringType.HEADER, str); + str = new String(); + } + return tempheader; + } + else + { + return null; + } + } + + private POEntry[] parseEntries(Vector> vectors) + { + String line = new String(); + boolean thereIsHeader = false; + + // is this header + Vector rawentry = vectors.get(0); + if(new Integer(rawentry.get(0)) == 0 && rawentry.contains("msgid \"\"")) + { + thereIsHeader = true; + } + + int size; + if(thereIsHeader) + { + size = vectors.size()-1; + } + else + { + size = vectors.size(); + } + + POEntry[] tempentries = new POEntry[size]; + + for(int i = 0; i < size; i++) + { + POEntry entry = new POEntry(); + + if(thereIsHeader) + rawentry = vectors.get(i+1); + else + rawentry = vectors.get(i); + + rawentry.remove(0); + for(int j = 0; j < rawentry.size(); j++) + { + line = rawentry.get(j); + POEntry.StringType strType = null; + int subStrIndex = 0; + if(line.startsWith("#")) + { + parserMode = null; + if(line.startsWith("# ")) + { + strType = POEntry.StringType.TRLCMNT; + if (line.startsWith("# ")) + { + subStrIndex = 3; + } + else + { + subStrIndex = 2; + } + } + if(line.startsWith("#.")) + { + strType = POEntry.StringType.EXTCMNT; + subStrIndex = 3; + } + if(line.startsWith("#:")) + { + strType = POEntry.StringType.REFERENCE; + subStrIndex = 3; + } + if(line.startsWith("#,")) + { + strType = POEntry.StringType.FLAG; + subStrIndex = 3; + } + if(line.startsWith("#|")) + { + // TODO: can these comments be multi line? if no, + // drop support for it + if(line.startsWith("#| msgctxt ")) + { + strType = POEntry.StringType.PREVCTXT; + parserMode = strType; + subStrIndex = 11; + } + if(line.startsWith("#| msgid ")) + { + strType = POEntry.StringType.PREVUNTRSTRSING; + parserMode = strType; + subStrIndex = 9; + } + if(line.startsWith("#| msgid_plural ")) + { + strType = POEntry.StringType.PREVUNTRSTRPLUR; + parserMode = strType; + subStrIndex = 16; + } + } + String str = new String(); + str = line.substring(subStrIndex); + entry.addLine(strType, str); + } + else if(line.startsWith("msg")) + { + parserMode = null; + if(line.startsWith("msgctxt ")) + { + strType = POEntry.StringType.MSGCTXT; + parserMode = strType; + subStrIndex = 8; + } + if(line.startsWith("msgid ")) + { + strType = POEntry.StringType.MSGID; + parserMode = strType; + subStrIndex = 6; + } + if(line.startsWith("msgstr ")) + { + strType = POEntry.StringType.MSGSTR; + parserMode = strType; + subStrIndex = 7; + } + String str = new String(); + // TODO: is unquoting nessessary? + str = unQuote(line.substring(subStrIndex)); + entry.addLine(strType, str); + } + else + { + if(parserMode != null) + { + entry.addLine(parserMode, unQuote(line)); + } + } + } + tempentries[i] = entry; + } + + return tempentries; + } +} diff --git a/src/net/launchpad/tobal/poparser/ParserTest.java b/src/net/launchpad/tobal/poparser/ParserTest.java new file mode 100644 index 0000000..5964aab --- /dev/null +++ b/src/net/launchpad/tobal/poparser/ParserTest.java @@ -0,0 +1,24 @@ +/** + * @author Balázs Tóth (tobal17@gmail.com) + * + * Modified by Kevin POCHAT for ATCS + */ +package net.launchpad.tobal.poparser; + +import java.io.File; + +public class ParserTest +{ + public static void main(String args[]) + { + File file = new File("C:\\file.po"); + POParser parser = new POParser(); + POFile po = parser.parseFile(file); + po.printHeader(); + po.printFile(); + // is the 3th entry fuzzy? + boolean fuzzy = po.checkFlag("fuzzy", 3); + // give me the msgid of the 4th entry + String[] str = po.getStringsFromEntryByType(4, POEntry.StringType.MSGID); + } +}