Compare commits

..

104 Commits

Author SHA1 Message Date
Zukero
75d6f8e98f v0.6.11 released. 2018-03-04 15:43:45 +01:00
Zukero
38c206cbaf TMX Maps editor's "Replacement" tab is now called "Testing" and has more
tools.
2018-03-04 15:39:30 +01:00
Zukero
b12ed1802f Added hero sprite rendering to help debug layering issues in
"Replacements" tab of TMX Map editor.
2018-03-04 12:19:56 +01:00
Zukero
0b8bc8448a Fixed memory leaks.
Closed projects were held in memory because the Map folder watcher
threads were still alive.
2018-02-28 16:25:28 +01:00
Zukero
221a031c2b Added support for new spawn area property: ignoreAreas. 2018-02-22 16:25:38 +01:00
Zukero
f2e4767eb0 Fixed export bug multiplying the data. 2018-02-18 11:28:03 +01:00
Zukero
78ceacb0ce Warnings hunt. 2018-02-13 15:26:13 +01:00
Zukero
1604373e6c Fixed icons alignment. 2018-02-13 14:11:42 +01:00
Zukero
6e2ee13da7 Fixed issue where worldmap.xml was unduely included in the generated
loadresources.xml
2018-02-12 14:18:02 +01:00
Zukero
ea28b7475a Fixed non-critical NPE. 2018-02-12 11:40:36 +01:00
Zukero
ffe6a14cd9 v0.6.10 released. 2018-02-11 13:19:28 +01:00
Zukero
3ab233761f Merge branch 'master' of https://github.com/Zukero/ATCS.git 2018-02-10 14:45:50 +01:00
Zukero
e5bb59b876 Added support for the"alignmentSet" dialogue reward type 2018-02-10 14:43:22 +01:00
Zukero
1fb22ab73f Completed formatting of loadresources.xml files generating by project
export.
2018-02-08 17:19:25 +01:00
Zukero
6b834e0f0e Added sadly convoluted way of pretty-printing loadresources.xml
Still not perfect.
2018-02-05 23:39:07 +01:00
Zukero
9e6e1d936d Fixed a bug in loadresources.xml generation.
Enhanced custom command handling for desktop tools integration.
2018-02-05 18:15:18 +01:00
Zukero
c3144db751 Fixed bug in "Npc->Effect on every hit->Actor conditions applied to the
target". Widget state wasn't updated correctly upon using the radio
buttons.
2018-02-04 11:50:32 +01:00
Zukero
daeb394373 Fixed dialogue reward editor. giveItem reward type now allows negative
amounts.
Added Nut's spritesheets to spritesheet.properties
2018-01-16 23:37:58 +01:00
Zukero
e697f93cf5 Fixed export issue zhere the loadresources.xml file couldn't be created
because its parent folder do not exist.
2017-12-18 19:04:51 +01:00
Zukero
407d50a01e Added Wizard for export project settings. Added export to game source
folder directly. Untested...
2017-12-18 18:58:09 +01:00
Zukero
a475180bb5 Fixed unwanted link following in project deletion that led to deleting
the game source's drawable folder. First steps towards enhancements of
export package generation.
2017-12-17 22:40:46 +01:00
Zukero
259442710b Merge branch 'master' of https://github.com/Zukero/ATCS.git 2017-12-09 15:51:26 +01:00
Zukero
3c63ace6c1 v0.6.9 released. 2017-12-09 15:51:03 +01:00
Zukero
1786860a3b Buf fixes for worldmaps upon contained map deletion. 2017-10-31 15:50:04 +01:00
Zukero
cbc101b3b1 Fixed parsing issue in ActorCondition.
The full round effect's visual effect was not read from the correct
field, causing NPEs.
2017-10-24 11:38:47 +02:00
Zukero
33260137d9 Bug fix in Worldmaps when a composing map is deleted. 2017-10-22 18:21:48 +02:00
Zukero
5a1d8637f9 v0.6.8 released. 2017-10-20 13:36:47 +02:00
Zukero
97119b7101 Fixed copy-pasting bug and made wording more consistent. 2017-10-09 18:52:08 +02:00
Zukero
104029124b Added support for the "revenge strike" feature in NPCs and Items.
Untested yet, and error-prone, because it was made with a LOT of
copy-paste-replacing.
2017-10-09 18:36:41 +02:00
Zukero
2aad37549c Bug fixes and added icon overlay for immunity management. 2017-08-28 13:30:08 +02:00
Zukero
8dc05bd26a Updated UI and model to support Actor Condition Immunity.
Overall, a much better UI to tune Actor Condition-based effects on
Dialogues, Items and NPCs.
2017-08-28 01:05:19 +02:00
Zukero
de0274a5be Updated version number in main class to v0.6.7. 2017-08-25 11:13:02 +02:00
Zukero
3d2dbb9f51 v0.6.7 released! 2017-08-24 20:26:43 +02:00
Zukero
f53302cb18 Font scaling is now also scaling icons, and works in most of the UI. 2017-08-24 18:37:30 +02:00
Zukero
8d6a40eb13 Added font scaling support. It's nowhere near perfect, and some look and
feels don't support it. To set it up, add the -DFONT_SCALE=1.0 (or other
float number) in the JAVA_OPTS environment variables in the startup
scripts.
2017-08-24 18:04:56 +02:00
Zukero
0199bcfb4c Small bug fixes and UI improvements. 2017-08-23 18:17:55 +02:00
Zukero
3fe895a668 Icon for alignmentChange rewards and factionScore requirements 2017-08-22 17:36:46 +02:00
Zukero
025a63af28 Visual effect widget is now a combo box, as the data is an Enum in the
game.
2017-08-21 18:32:11 +02:00
Zukero
528ac7a7e3 Added support for the new "factionScore" requirement type. 2017-08-20 11:19:54 +02:00
Zukero
506afb95ed Simple UI improvements.
Combo box to select skill ID in skill level requirement.
Icons for most requirements types in requirements list in dialogue
editor.
More verbose description of requirement in requirement list.
2017-08-19 11:47:30 +02:00
Zukero
5d802ed0e3 v0.6.6 released. 2017-08-18 22:19:29 +02:00
Zukero
33cbd059ec More bug fixes in Requirement management for Key and Replace areas.
Much better handling of the old school form, and smooth, automatic
transitionning from old school to new school, but only when necessary!
2017-08-18 17:22:06 +02:00
Zukero
6cb0941ca6 Replace areas can now use any type of requirement.
Many bug fix in Key Areas and Replace Areas, especially for the
requirements management.
2017-08-18 16:20:25 +02:00
Zukero
cfb38c33d6 Fixed bug preventing Quests with no quest stages from loading. 2017-08-18 11:51:48 +02:00
Zukero
ec3afaaf36 v0.6.5 released! 2017-08-11 22:32:54 +02:00
Zukero
9f978591ff Fixed XML-refreshing bug in TMX Map editor, and added area name editor
for key areas.
2017-08-11 22:16:08 +02:00
Zukero
7fff58d8f9 Merge branch 'master' of https://github.com/Zukero/ATCS.git 2017-08-11 21:31:57 +02:00
Zukero
6192bd8dce Fixed issue with symlinks creatipon in windows for paths containing
spaces.
2017-08-11 21:31:34 +02:00
Zukero
be43a2a5d4 Fixed display of incorrect default values when the underlying datais
null. Fixed KeyArea initialization.
2017-08-11 18:43:44 +02:00
Zukero
99524bf043 Introducing the help panel for Dialogue Sketches' editor's shortcuts. 2017-08-09 18:04:30 +02:00
Zukero
f2144ab446 Modified marker (*) handled for Dialogue Sketches too.
Typo fixed too, now displays "Dialogue sketches" instead of "Dialogue
sketchs"
2017-08-08 18:43:15 +02:00
Zukero
ada045a13b Fixed JSON import of existing element.
When an imported item was already present in the game sources, it wasnt
associated with the correct file, leading to trouble.
2017-08-08 18:25:01 +02:00
Zukero
4c4f7e5b92 Better management of scrolling in TMX Maps editor's Replacement
simulator.
2017-07-30 17:38:50 +02:00
Zukero
ef521207de v0.6.4 released. 2017-07-28 13:15:42 +02:00
Zukero
3ef0f7e0f1 Update checker now honors the "Use internet" setting. Another setting,
"check for updates" also controls this behavior.
2017-07-28 13:11:30 +02:00
Zukero
74808cdd3a Fixed a bug improperly restoring the state of WriteModeData (Dialogue
sketches) after loading it from disk. This created a risk of data loss
upon subsequent edition of dialogue sketches.
2017-07-27 20:50:10 +02:00
Zukero
1e8d08ee3a Added online version check, to warn of availability of a new version. 2017-07-27 01:07:01 +02:00
Zukero
8d8a1e122e v0.6.3 released! 2017-07-26 23:49:16 +02:00
Zukero
fe62c05b4b Fixed many bugs in the TMX Maps management. Added the
removeQuestProgress dialogue reward type. Initiallized the GDEVisitor
class to help with finding dependencies (through the Beanshell console
only for now).
2017-07-26 15:50:50 +02:00
Zukero
f93d03dbd3 Deleting a TMX Map also removes it from created/altered worldmaps, and
marks these as modified.
2017-07-25 19:16:35 +02:00
Zukero
7eb5c7c208 Fixed NPC editor bug where inflicted actor conditions weren't managed
correctly.
Fixed Worldmap saving and deletion (mostly the * management was crap)
Fixed multi-selection deletion.
2017-07-25 17:01:31 +02:00
Zukero
e04c3ee2fd v0.6.2 released! Redesigned WorldMap editor. Many new UI features. Fixed
Trainer.
2017-05-05 15:07:50 +02:00
Zukero
38a1e90aad Redesigned Worldmap editor. Better UI & support for town labels.
Searchable map list that mirrors the on-map selection.
2017-05-04 14:27:38 +02:00
Zukero
83d459021b Simple bug fix that broke the NPC icon selection window. 2017-04-21 15:55:15 +02:00
Zukero
fb8dcb9fb4 v0.6.1! Rebuilt completely the Quest editor. Each quest stage has its
own backlinks now. Quest log entries and dialogue replies are now
translatable too. Multiple minor UI improvements (notably multiline text
area are now taller, and rewards and requirements appear more clearly in
the dialogue tree view).
2017-04-14 15:52:32 +02:00
Zukero
5e73b59d06 v0.6.0 released! Weblate integration is complete. New icons (created for
weblate integration) now replace to old ugly ones for the notification
area.
2017-04-12 15:57:35 +02:00
Zukero
bca28781bd Level 2 of weblate integration. Rudimentary UI, but working well. 2017-04-11 23:51:50 +02:00
Zukero
899e94c5bc Better layout for clickable links to weblate. 2017-04-10 23:05:52 +02:00
Zukero
0a7cb40dbc Added link to Weblate from translatable strings once the translator mode
is activated in the workspace settings. This required the addition of a
new lib (in source form) for a SipHash implementation.
2017-04-10 18:16:17 +02:00
Zukero
bd8576df0c v0.5.4 released! Keyword is data-protection. Many small bugfixes, but
main changes are:
- Modified marker (* character before name) goes up the project tree by
marking all parents of a modified object as modified.
- Impact management when changing an object's ID. Allows for some
refactoring to occur, while marking the impacted elements as modified,
or aven creating "altered" versions if imapcted element was only in game
source.
2017-04-07 10:01:31 +02:00
Zukero
49f19abb91 Forgot the necessary cleanup of old jars in windows NSIS version. 2017-04-03 17:57:03 +02:00
Zukero
300b7bbbdd Fixed caching issue with spritesheet chooser. Improved actor condition
management for items.
2017-04-03 17:53:44 +02:00
Zukero
bbee5bef25 Bug fix regarding object groups and spawn areas "active in a new game"
parameter.
2017-03-31 09:29:53 +02:00
Zukero
1fc1cef233 Fixed issue with batch scripts being overly painful to use, and have
horrible quoting and escaping rules.
2017-03-06 18:01:05 +01:00
Zukero
ae9a6695b0 v0.5.2 updated. Messed up something in scripts about paths containing
spaces.
2017-03-03 17:34:21 +01:00
Zukero
572704fd73 v0.5.2 ! Upped the bash & batch game. Can run from any directory,
auto-adaptation to jar files in lib, creation, use, and preservation
upon update or reinstall of a startup customization file (Max JVM
memory, custom java binary and custom JVM options as environment
variables). This is valid for all OS types (bash or batch startup) and
all installation methods (zip and nsis). Robustified a stupid version
management for workspace settings.
2017-03-03 17:15:16 +01:00
Zukero
dfe3cc8480 UAC dirty script for better windows integration to create symlinks. More
data using a thread-safe collection. Some NPEs fixed. 
More data protection in tiled integration functions (better state
checking, backup tiled-made file before saving ATCS-made data).
jardesc file added for convenience.
2017-03-02 18:10:03 +01:00
Zukero
97ae63693a Enhanced Tiled integration. ATCS offers to save a modified map before
opening in Tiled, and attempts to save a backup of the
externally-modified one when saving in ATCS if necessary.
2017-03-02 15:30:02 +01:00
Zukero
940996aa30 Some refactoring. Replaced all "listeners" list by instances of
CopyOnWriteArrayList, to allow listeners to unregister themselves due to
an event while preventing ConcurrentModificationExceptions.
Modified all GameDataElement.elementChanged concrete implementation to
remove the backlink from the oldOne.An element pointed by an altered
element will not show the game source element in its backlink list
anymore.
2017-03-02 13:53:24 +01:00
Zukero
2a4cfb0684 Enhanced Tiled integration. Can now reload a map that has changed
externaly. Still some rough edges, notably the number of nitification
sto skip when saving in ATCS.
2017-03-01 19:02:00 +01:00
Zukero
061a0fa11b Create new TMX maps in your project, and edit them with Tiled from ATCS
!! Also, "created" elements show the "unsaved" asterisk in their
description until they're saved to disk. 
Added a button to the spritesheet editor to open image in an external
tool too. This may or may not remain in the app.
2017-02-28 18:43:47 +01:00
Zukero
41462137d6 Enhanced Tiled integration. Now in "File->Edit workspace settings" menu,
you have tools to help selecting external tools to bview/edit images and
tmx maps. Still rough.
2017-02-27 18:18:17 +01:00
Zukero
9888dfe678 Introducing the WorkspaceSettings, workspace-wide user-modifiable
settings. No UI yet, so it's useless for now.
2017-02-24 17:45:34 +01:00
Zukero
baa027bc41 Version 0.5.1 with bugfixes and support for map color filter changes as
dialogue reward.
2017-02-23 18:54:01 +01:00
Zukero
8f2e835e9c Fixed nasty and stupid bug about Actor Conditions' JSON format
(isStacking and isPositive)
2017-02-21 19:23:27 +01:00
Zukero
8333fe3621 Added support for the upcoming script reward "changeColorFilter" 2017-01-25 19:11:45 +01:00
Zukero
291808a564 Naive attempt at Tiled integration. Very primitive yet. Requires Java7
!! for symlink creation.
2017-01-20 18:38:44 +01:00
Zukero
900d0bc9b5 v0.5.0 ! First release of new Dialogue Sketch feature. 2016-12-20 16:43:10 +01:00
Zukero
6fe4d2a171 Warning hunt. Dialogue sketch I/O progress. Sketch creation from
existing Dialogue begins.
2016-12-19 18:10:25 +01:00
Kevin
1076693322 Dialogue sketches can be saved. Bug fixes in sketch export. 2016-12-19 08:51:00 +01:00
Zukero
5a9e66b7c9 Detect changes in preparation of edition of existing dialogues. 2016-12-16 15:03:57 +01:00
Zukero
b1d0175a9d Progress on Dialogue Sketch to JSON Data conversion.
Opportunistic bug fix in JSON import wizard.
2016-12-16 09:41:04 +01:00
Zukero
6ac332834d Progress on WriterMode. Project export package now create
deterministically-ordered JSON. Should make nicer diffs.
2016-12-15 19:16:53 +01:00
Zukero
32711449b2 Temporary commit 2016-10-14 17:13:03 +02:00
Zukero
c18ff9d2b4 First drafts of "Writer Mode".
Some bug fixes.
2016-09-16 15:23:00 +02:00
Zukero
ce908f0033 v0.4.8. Removed stupid feature (almost a bug) that made it painful to
handle adding spritesheets. Fixed colorfilters not showing on the
worldmap and replacements simulator. Added "display type" (a.k.a rarity)
item field in the Items comparator.
2016-08-12 15:35:17 +02:00
Zukero
1a70f87897 v0.4.7 ! 2016-08-10 18:03:11 +02:00
Zukero
c07fb4ddf3 Implemented all colorfilters in TMXMapEditor, thanks to the new
MatrixComposite that really emulates the ColorMatrixColorFilter of
Android ! Hi-fidelity filter emulation !
2016-08-10 16:20:48 +02:00
Zukero
1458fb0aaa Bug fix for the map's "outside" property that wasn't handled properly.
Added support for the new "colorfilter" map property. Due to Java2D
having no correct color filter support, only the "blackXX" values can be
previwed. "bw" and "invert" cannot, the performance cost was simply way
too high.
2016-08-08 16:31:38 +02:00
Zukero
57b8209b26 v0.4.6 ! Oh so many bugfixes... 2016-07-21 16:39:55 +02:00
Zukero
a7224755ff Fixed NPE in KeyArea creation. Rest areas can be renamed. 2016-07-19 18:49:42 +02:00
Zukero
e97168c62e v0.4.5 complete with packaging. 2016-07-19 17:57:02 +02:00
Zukero
1e8dd405c3 Addedchanges to match new datamodel in AT. Notably, ability to disable a
Map Object Group in a new game in a map, and the associated rewards
((de)activateMapObjectGroup) in dialogues that replace
(de)activateMapChangeArea.
2016-07-19 17:37:54 +02:00
Zukero
830e9de56b v0.4.5 to cope with changes to spawnareas definition in game code.
replaced gcode references by github repo.
2016-07-18 18:55:16 +02:00
147 changed files with 12823 additions and 1349 deletions

View File

@@ -3,6 +3,7 @@
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="res"/>
<classpathentry kind="src" path="hacked-libtiled"/>
<classpathentry kind="src" path="siphash-zackehh/src/main/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="lib/jide-oss.jar"/>
<classpathentry kind="lib" path="lib/json_simple-1.1.jar"/>
@@ -11,6 +12,7 @@
<classpathentry kind="lib" path="lib/rsyntaxtextarea.jar"/>
<classpathentry kind="lib" path="lib/ui.jar"/>
<classpathentry kind="lib" path="lib/bsh-2.0b4.jar"/>
<classpathentry kind="lib" path="lib/AndorsTrainer_v0.1.2.jar"/>
<classpathentry kind="lib" path="lib/jsoup-1.10.2.jar" sourcepath="lib/jsoup-1.10.2-sources.jar"/>
<classpathentry kind="lib" path="lib/AndorsTrainer_v0.1.4.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

19
ATCS_JAR.jardesc Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="WINDOWS-1252" standalone="no"?>
<jardesc>
<jar path="ATContentStudio/ATCS_v0.6.11.jar"/>
<options buildIfNeeded="true" compress="true" descriptionLocation="/ATContentStudio/ATCS_JAR.jardesc" exportErrors="true" exportWarnings="true" includeDirectoryEntries="false" overwrite="false" saveDescription="true" storeRefactorings="false" useSourceFolders="false"/>
<storedRefactorings deprecationInfo="true" structuralOnly="false"/>
<selectedProjects/>
<manifest generateManifest="true" manifestLocation="" manifestVersion="1.0" reuseManifest="false" saveManifest="false" usesManifest="true">
<sealing sealJar="false">
<packagesToSeal/>
<packagesToUnSeal/>
</sealing>
</manifest>
<selectedElements exportClassFiles="true" exportJavaFiles="true" exportOutputFolder="false">
<javaElement handleIdentifier="=ATContentStudio/hacked-libtiled"/>
<javaElement handleIdentifier="=ATContentStudio/siphash-zackehh\/src\/main\/java"/>
<javaElement handleIdentifier="=ATContentStudio/src"/>
<javaElement handleIdentifier="=ATContentStudio/res"/>
</selectedElements>
</jardesc>

View File

@@ -29,7 +29,9 @@
package tiled.core;
import java.awt.Rectangle;
import java.util.*;
import java.util.Iterator;
import java.util.Properties;
import java.util.Vector;
/**
* The Map class is the focal point of the <code>tiled.core</code> package.
@@ -109,11 +111,14 @@ public class Map implements Iterable<MapLayer>
public MapLayer addLayer(MapLayer layer) {
layer.setMap(this);
layers.add(layer);
if (layer instanceof TileLayer) {
tileLayers.add((TileLayer) layer);
layers.add(layer);
} else if (layer instanceof ObjectGroup) {
layers.insertElementAt(layer, objectGroups.size());
objectGroups.add((ObjectGroup) layer);
} else {
layers.add(layer);
}
return layer;
}

View File

@@ -28,10 +28,12 @@
package tiled.core;
import java.awt.*;
import java.util.Properties;
import java.awt.Image;
import java.awt.Rectangle;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import javax.imageio.ImageIO;
/**

View File

@@ -33,8 +33,8 @@ import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**

View File

@@ -28,7 +28,7 @@
package tiled.core;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Properties;
/**
@@ -36,7 +36,7 @@ import java.util.Properties;
*/
public class Tile
{
private Image image;
private BufferedImage image;
private int id = -1;
private Properties properties;
private TileSet tileset;
@@ -76,7 +76,7 @@ public class Tile
*
* @param i the new image of the tile
*/
public void setImage(Image i) {
public void setImage(BufferedImage i) {
image = i;
}
@@ -133,7 +133,7 @@ public class Tile
*
* @return Image
*/
public Image getImage() {
public BufferedImage getImage() {
if (tileset != null && tileset.sheet != null) return tileset.sheet.getImage(getId());
return image;
}

View File

@@ -161,7 +161,7 @@ public class TileSet implements Iterable<Tile>
tilesPerRow = basicTileCutter.getTilesPerRow();
}
Image tileImage = cutter.getNextTile();
BufferedImage tileImage = cutter.getNextTile();
while (tileImage != null) {
Tile tile = new Tile();
tile.setImage(tileImage);
@@ -220,7 +220,7 @@ public class TileSet implements Iterable<Tile>
tileDimensions = new Rectangle(tileCutter.getTileDimensions());
int id = 0;
Image tileImage = tileCutter.getNextTile();
BufferedImage tileImage = tileCutter.getNextTile();
while (tileImage != null) {
Tile tile = getTile(id);
tile.setImage(tileImage);

View File

@@ -31,13 +31,13 @@ package tiled.io;
import java.awt.Color;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -223,9 +223,9 @@ public class TMXMapReader
return o;
}
private Image unmarshalImage(Node t, String baseDir) throws IOException
private BufferedImage unmarshalImage(Node t, String baseDir) throws IOException
{
Image img = null;
BufferedImage img = null;
String source = getAttributeValue(t, "source");
@@ -253,7 +253,7 @@ public class TMXMapReader
// size, somehow makes drawing of the tiles a lot
// faster on various systems (seen on Linux, Windows
// and MacOS X).
img = img.getScaledInstance(
img = (BufferedImage) img.getScaledInstance(
img.getWidth(null), img.getHeight(null),
Image.SCALE_FAST);
}
@@ -534,7 +534,7 @@ public class TMXMapReader
Node child = children.item(i);
if ("image".equalsIgnoreCase(child.getNodeName())) {
int id = getAttribute(child, "id", -1);
Image img = unmarshalImage(child, baseDir);
BufferedImage img = unmarshalImage(child, baseDir);
tile.setImage(img);
} else if ("animation".equalsIgnoreCase(child.getNodeName())) {
// TODO: fill this in once TMXMapWriter is complete

View File

@@ -30,14 +30,32 @@ package tiled.io;
import java.awt.Color;
import java.awt.Rectangle;
import java.io.*;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Vector;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPOutputStream;
import tiled.core.*;
import tiled.core.AnimatedTile;
import tiled.core.Map;
import tiled.core.MapLayer;
import tiled.core.MapObject;
import tiled.core.ObjectGroup;
import tiled.core.Sprite;
import tiled.core.Tile;
import tiled.core.TileLayer;
import tiled.core.TileSet;
import tiled.io.xml.XMLWriter;
import tiled.util.Base64;
@@ -163,7 +181,12 @@ public class TMXMapWriter
firstgid += tileset.getMaxTileId() + 1;
}
for (MapLayer layer : map) {
for (MapLayer layer : map.getTileLayers()) {
writeMapLayer(layer, w, wp);
}
for (MapLayer layer : map.getObjectGroup()) {
if (map.getTileLayers().contains(layer)) continue;
writeMapLayer(layer, w, wp);
}
firstGidPerTileset = null;

View File

@@ -28,9 +28,8 @@
package tiled.io.xml;
import java.lang.String;
import java.io.Writer;
import java.io.IOException;
import java.io.Writer;
import java.util.Stack;
/**

View File

@@ -29,7 +29,6 @@
package tiled.util;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.image.BufferedImage;
/**
@@ -64,7 +63,7 @@ public class BasicTileCutter implements TileCutter
this.image = image;
}
public Image getNextTile() {
public BufferedImage getNextTile() {
if (nextY + tileHeight + tileMargin <= image.getHeight()) {
BufferedImage tile =
image.getSubimage(nextX, nextY, tileWidth, tileHeight);

View File

@@ -33,6 +33,7 @@ import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
/**

View File

@@ -29,7 +29,6 @@
package tiled.util;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.image.BufferedImage;
/**
@@ -48,7 +47,7 @@ public interface TileCutter
* @return the next tile image, or <code>null</code> when no more tile
* images are available
*/
public Image getNextTile();
public BufferedImage getNextTile();
/**
* Resets the tile cutter so that the next call to <code>getNextTile</code>

View File

@@ -27,9 +27,10 @@
package tiled.view;
import tiled.core.TileLayer;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.*;
import tiled.core.TileLayer;
/**
* An interface defining methods to render a map.

View File

@@ -27,12 +27,15 @@
package tiled.view;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import tiled.core.Map;
import tiled.core.Tile;
import tiled.core.TileLayer;
import java.awt.*;
/**
* The orthogonal map renderer. This is the most basic map renderer, dealing
* with maps that use rectangular tiles.
@@ -74,10 +77,12 @@ public class OrthogonalRenderer implements MapRenderer
final Tile tile = layer.getTileAt(x, y);
if (tile == null)
continue;
final Image image = tile.getImage();
final BufferedImage image = tile.getImage();
if (image == null)
continue;
g.drawImage(
image,
x * tileWidth,

BIN
itemScroll.xcf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
lib/jsoup-1.10.2.jar Normal file

Binary file not shown.

1
packaging/ATCS_latest Normal file
View File

@@ -0,0 +1 @@
v0.6.11

View File

@@ -1 +1,20 @@
start "" "javaw.exe" -Xmx512M -cp "lib\ATCS_v0.4.4.jar;lib\jide-oss.jar;lib\ui.jar;lib\junit-4.10.jar;lib\json_simple-1.1.jar;lib\rsyntaxtextarea.jar;lib\prefuse.jar;lib\AndorsTrainer_v0.1.2.jar;lib\bsh-2.0b4.jar" com.gpl.rpg.atcontentstudio.ATContentStudio
@echo off
set "ATCS_DIR=%~dp0"
set "MAX_MEM=512M"
set "CP=%ATCS_DIR%lib\*"
set "JAVA=javaw.exe"
set "JAVA_OPTS=-DFONT_SCALE=1.0 -Dswing.aatext=true"
set "ENV_FILE=%ATCS_DIR%ATCS.env.bat"
set "MAIN_CLASS=com.gpl.rpg.atcontentstudio.ATContentStudio"
if exist "%ENV_FILE%" (
call "%ENV_FILE%"
) else (
echo REM set "MAX_MEM=%MAX_MEM%">"%ENV_FILE%"
echo REM set "JAVA=%JAVA%">>"%ENV_FILE%"
echo REM set "JAVA_OPTS=%JAVA_OPTS%">>"%ENV_FILE%"
echo.>>"%ENV_FILE%"
)
start "" "%JAVA%" %JAVA_OPTS% -Xmx%MAX_MEM% -cp "%CP%" %MAIN_CLASS%

View File

@@ -1,2 +1,22 @@
#!/bin/bash
java -Xmx512M -cp lib/AndorsTrainer_v0.1.2.jar:lib/ATCS_v0.4.4.jar:lib/prefuse.jar:lib/json_simple-1.1.jar:lib/jide-oss.jar:lib/ui.jar:lib/junit-4.10.jar:lib/rsyntaxtextarea.jar:lib/bsh-2.0b4.jar com.gpl.rpg.atcontentstudio.ATContentStudio
ATCS_DIR=$(dirname $(readlink -f "$0" || greadlink -f "$0" || stat -f "$0"))
MAX_MEM=512M
CP=$(find ${ATCS_DIR}/lib/ -name '*.jar' | paste -sd: -)
JAVA=java
JAVA_OPTS='-DFONT_SCALE=1.0 -Dswing.aatext=true'
ENV_FILE=${ATCS_DIR}/ATCS.env
MAIN_CLASS=com.gpl.rpg.atcontentstudio.ATContentStudio
if [ -f ${ENV_FILE} ]
then
source ${ENV_FILE}
else
echo "#MAX_MEM=${MAX_MEM}" > ${ENV_FILE}
echo "#JAVA=${JAVA}" >> ${ENV_FILE}
echo "#JAVA_OPTS=${JAVA_OPTS}" >> ${ENV_FILE}
echo "" >> ${ENV_FILE}
fi
export ENV_FILE
$JAVA ${JAVA_OPTS} -Xmx${MAX_MEM} -cp ${CP} ${MAIN_CLASS}

View File

@@ -1 +1,20 @@
start "" "javaw.exe" -Xmx512M -cp "lib\jide-oss.jar;lib\ui.jar;lib\junit-4.10.jar;lib\json_simple-1.1.jar;lib\rsyntaxtextarea.jar;lib\prefuse.jar;lib\ATCS_v0.4.3.jar;lib\AndorsTrainer_v0.1.2.jar;lib\bsh-2.0b4.jar" com.gpl.rpg.atcontentstudio.ATContentStudio
@echo off
set "ATCS_DIR=%~dp0"
set "MAX_MEM=512M"
set "CP=%ATCS_DIR%lib\*"
set "JAVA=javaw.exe"
set "JAVA_OPTS=-DFONT_SCALE=1.0 -Dswing.aatext=true"
set "ENV_FILE=%ATCS_DIR%ATCS.env.bat"
set "MAIN_CLASS=com.gpl.rpg.atcontentstudio.ATContentStudio"
if exist "%ENV_FILE%" (
call "%ENV_FILE%"
) else (
echo REM set "MAX_MEM=%MAX_MEM%">"%ENV_FILE%"
echo REM set "JAVA=%JAVA%">>"%ENV_FILE%"
echo REM set "JAVA_OPTS=%JAVA_OPTS%">>"%ENV_FILE%"
echo.>>"%ENV_FILE%"
)
start "" "%JAVA%" %JAVA_OPTS% -Xmx%MAX_MEM% -cp "%CP%" %MAIN_CLASS%

View File

@@ -1,7 +1,8 @@
!include MUI2.nsh
!define VERSION "0.4.4"
!define JAVA_BIN "java"
!define VERSION "0.6.11"
!define TRAINER_VERSION "0.1.4"
!define JAVA_BIN "javaw"
Name "Andor's Trail Content Studio v${VERSION}"
OutFile "ATCS_v${VERSION}_Setup.exe"
@@ -54,22 +55,44 @@ Section install
SetOutPath $INSTDIR
file "ATCS.ico"
Delete "$INSTDIR\lib\*"
Call GetJRE
Pop $R0
FileOpen $9 "ATCS.cmd" w
FileWrite $9 'start "" "$R0" -Xmx512M -cp "lib\AndorsTrainer_v0.1.1.jar;lib\jide-oss.jar;lib\ui.jar;lib\junit-4.10.jar;lib\json_simple-1.1.jar;lib\rsyntaxtextarea.jar;lib\prefuse.jar;lib\bsh-2.0b4.jar;lib\ATCS_v${VERSION}.jar" com.gpl.rpg.atcontentstudio.ATContentStudio'
FileWrite $9 '@echo off$\r$\n'
FileWrite $9 '$\r$\n'
FileWrite $9 'set "ATCS_DIR=%~dp0"$\r$\n'
FileWrite $9 'set "MAX_MEM=512M"$\r$\n'
FileWrite $9 'set "CP=%ATCS_DIR%lib\*"$\r$\n'
FileWrite $9 'set "JAVA=$R0"$\r$\n'
FileWrite $9 'set "JAVA_OPTS="$\r$\n'
FileWrite $9 'set "ENV_FILE=%ATCS_DIR%ATCS.env.bat"$\r$\n'
FileWrite $9 'set "MAIN_CLASS=com.gpl.rpg.atcontentstudio.ATContentStudio"$\r$\n'
FileWrite $9 '$\r$\n'
FileWrite $9 'if exist "%ENV_FILE%" ($\r$\n'
FileWrite $9 ' call "%ENV_FILE%"$\r$\n'
FileWrite $9 ') else ($\r$\n'
FileWrite $9 ' echo REM set "MAX_MEM=%MAX_MEM%">"%ENV_FILE%"$\r$\n'
FileWrite $9 ' echo REM set "JAVA=%JAVA%">>"%ENV_FILE%"$\r$\n'
FileWrite $9 ' echo REM set "JAVA_OPTS=%JAVA_OPTS%">>"%ENV_FILE%"$\r$\n'
FileWrite $9 ' echo.>>"%ENV_FILE%"$\r$\n'
FileWrite $9 ')$\r$\n'
FileWrite $9 '$\r$\n'
FileWrite $9 'start "" "%JAVA%" %JAVA_OPTS% -Xmx%MAX_MEM% -cp "%CP%" %MAIN_CLASS%$\r$\n'
FileClose $9
SetOutPath "$INSTDIR\lib\"
file "jide-oss.jar"
file "ui.jar"
file "AndorsTrainer_v0.1.2.jar"
file "AndorsTrainer_v${TRAINER_VERSION}.jar"
file "junit-4.10.jar"
file "json_simple-1.1.jar"
file "ATCS_v${VERSION}.jar"
file "rsyntaxtextarea.jar"
file "prefuse.jar"
file "bsh-2.0b4.jar"
file "jsoup-1.10.2.jar"
SetOutPath $INSTDIR
@@ -93,13 +116,16 @@ Section uninstall
Delete "$INSTDIR\lib\ui.jar"
Delete "$INSTDIR\lib\junit-4.10.jar"
Delete "$INSTDIR\lib\json_simple-1.1.jar"
Delete "$INSTDIR\lib\AndorsTrainer_v0.1.2.jar"
Delete "$INSTDIR\lib\AndorsTrainer_v${TRAINER_VERSION}.jar"
Delete "$INSTDIR\lib\ATCS_v${VERSION}.jar"
Delete "$INSTDIR\lib\rsyntaxtextarea.jar"
Delete "$INSTDIR\lib\prefuse.jar"
Delete "$INSTDIR\lib\bsh-2.0b4.jar"
Delete "$INSTDIR\lib\jsoup-1.10.2.jar"
RMDir "$INSTDIR\lib\"
Delete "$INSTDIR\ATCS.ico"
Delete "$INSTDIR\ATCS.cmd"
Delete "$INSTDIR\ATCS.env.bat"
Delete "$INSTDIR\Uninstall.exe"

21
res/LICENSE.jsoup.txt Normal file
View File

@@ -0,0 +1,21 @@
The MIT License
© 2009-2017, Jonathan Hedley <jonathan@hedley.net>
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 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.

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Isaac Whitfield
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 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.

View File

@@ -1,5 +1,6 @@
atcs.spritesheet.actorconditions_1.category=actorcondition
atcs.spritesheet.actorconditions_2.category=actorcondition
atcs.spritesheet.actorconditions_japozero.category=actorcondition
atcs.spritesheet.items_armours.category=item
atcs.spritesheet.items_armours_2.category=item
atcs.spritesheet.items_armours_3.category=item
@@ -10,6 +11,7 @@ atcs.spritesheet.items_jewelry.category=item
atcs.spritesheet.items_rings_1.category=item
atcs.spritesheet.items_necklaces_1.category=item
atcs.spritesheet.items_consumables.category=item
atcs.spritesheet.items_japozero.category=item
atcs.spritesheet.items_books.category=item
atcs.spritesheet.items_misc.category=item
atcs.spritesheet.items_misc_2.category=item
@@ -82,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
atcs.spritesheet.effect_tometik2.animate=true
atcs.spritesheet.monsters_guynmart.category=monster

19
siphash-zackehh/.gitignore vendored Normal file
View File

@@ -0,0 +1,19 @@
*.class
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# IntelliJ
.idea
*.iml
# build
target

View File

@@ -0,0 +1,3 @@
language: java
script:
- mvn clean test jacoco:report coveralls:report

21
siphash-zackehh/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Isaac Whitfield
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 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.

77
siphash-zackehh/README.md Normal file
View File

@@ -0,0 +1,77 @@
# SipHash
[![Build Status](https://travis-ci.org/zackehh/siphash-java.svg?branch=master)](https://travis-ci.org/zackehh/siphash-java) [![Coverage Status](https://coveralls.io/repos/zackehh/siphash-java/badge.svg?branch=master&service=github)](https://coveralls.io/github/zackehh/siphash-java?branch=master)
A Java implementation of the SipHash cryptographic hash family. Supports any variation, although defaults to the widely used SipHash-2-4. Can be used with either full input, or used as a streaming digest.
This library was heavily influenced by [veorq's C implementation](https://github.com/veorq/siphash) and [Forward C&C's reference implementation](http://www.forward.com.au/pfod/SipHashJavaLibrary/) - I just decided it was time a Java implementation of SipHash made it onto Maven :).
## Setup
`siphash` is available on Maven central, via Sonatype OSS:
```
<dependency>
<groupId>com.zackehh</groupId>
<artifactId>siphash</artifactId>
<version>1.0.0</version>
</dependency>
```
## Usage
There are two ways of using SipHash (see below). Both return a `SipHashResult` which can be used to retrieve the result in various forms. All constructors can take arguments to specify the compression rounds. For further usage, please visit the [documentation](http://www.javadoc.io/doc/com.zackehh/siphash).
#### Full Input Hash
The first is to simple create a `SipHash` instance and use it to repeatedly hash using the same key.
The internal state is immutable, so you can hash many inputs without having to recreate a new `SipHash` instance (unless you want a new key).
```java
SipHash hasher = new SipHash("0123456789ABCDEF".getBytes());
SipHashResult result = hasher.hash("my-input".getBytes());
System.out.println(result.get()); // 182795880124085484 <-- this can overflow
System.out.println(result.getHex()); // "2896be26d3374ec"
System.out.println(result.getHex(true)); // "02896be26d3374ec"
System.out.println(result.getHex(SipHashCase.UPPER)); // "2896BE26D3374EC"
System.out.println(result.getHex(true, SipHashCase.UPPER)); // "02896BE26D3374EC"
```
#### Streaming Hash
The second is to use the library as a streaming hash, meaning you can apply chunks of bytes to the hash as they become available.
Using this method you must create a new digest every time you want to hash a different input as the internal state is mutable.
```java
SipHashDigest digest = new SipHashDigest("0123456789ABCDEF".getBytes());
digest.update("chu".getBytes());
digest.update("nked".getBytes());
digest.update(" string".getBytes());
SipHashResult result = digest.finish();
System.out.println(result.get()); // 3502906798476177428 <-- this can overflow
System.out.println(result.getHex()); // "309cd32c8c793014"
System.out.println(result.getHex(true)); // "309cd32c8c793014"
System.out.println(result.getHex(SipHashCase.UPPER)); // "309CD32C8C793014"
System.out.println(result.getHex(true, SipHashCase.UPPER)); // "309CD32C8C793014"
```
## Contributing
If you wish to contribute (awesome!), please file an issue first! All PRs should pass `mvn clean verify` and maintain 100% test coverage.
## Testing
Tests are run using `mvn`. I aim to maintain 100% coverage where possible (both line and branch).
Tests can be run as follows:
```bash
$ mvn clean verify
```

171
siphash-zackehh/pom.xml Normal file
View File

@@ -0,0 +1,171 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zackehh</groupId>
<artifactId>siphash</artifactId>
<version>1.1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SipHash</name>
<description>
A SipHash implementation in Java.
</description>
<url>https://github.com/zackehh/siphash-java</url>
<properties>
<jacoco.version>0.7.5.201505241946</jacoco.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<developers>
<developer>
<id>iwhitfield</id>
<name>Isaac Whitfield</name>
<email>iw@zackehh.com</email>
<organization>Appcelerator, Inc.</organization>
<organizationUrl>http://www.appcelerator.com</organizationUrl>
</developer>
</developers>
<scm>
<connection>scm:git:git@github.com:zackehh/siphash-java.git</connection>
<developerConnection>scm:git:git@github.com:zackehh/siphash-java.git</developerConnection>
<url>git@github.com:zackehh/siphash-java.git</url>
</scm>
<licenses>
<license>
<name>MIT License</name>
<url>http://www.opensource.org/licenses/mit-license.php</url>
</license>
</licenses>
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>ossrh</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.8.7</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.3</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>false</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.eluder.coveralls</groupId>
<artifactId>coveralls-maven-plugin</artifactId>
<version>4.1.0</version>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,111 @@
package com.zackehh.siphash;
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
* interface. Assuming you have your full String to hash,
* you can simply provide it to ${@link SipHash#hash(byte[])}.
*
* This class can be initialized and stored in case the
* developer wishes to use the same key over and over again.
*
* This avoids the overhead of having to create the initial
* key over and over again.
*
* <pre>
* {@code
* List<String> inputs = Arrays.asList("input1", "input2", "input3");
* SipHash hasher = new SipHash("this key is mine".getBytes());
* for (int i = 0; i < inputs.size(); i++) {
* hasher.hash(inputs.get(i));
* }
* }
* </pre>
*/
public class SipHash {
/**
* The values of SipHash-c-d, to determine which of the SipHash
* family we're using for this hash.
*/
private final int c, d;
/**
* Initial seeded value of v0.
*/
private final long v0;
/**
* Initial seeded value of v1.
*/
private final long v1;
/**
* Initial seeded value of v2.
*/
private final long v2;
/**
* Initial seeded value of v3.
*/
private final long v3;
/**
* Accepts a 16 byte key input, and uses it to initialize
* the state of the hash. This uses the default values of
* c/d, meaning that we default to SipHash-2-4.
*
* @param key a 16 byte key input
*/
public SipHash(byte[] key){
this(key, DEFAULT_C, DEFAULT_D);
}
/**
* Accepts a 16 byte key input, and uses it to initialize
* the state of the hash. This constructor allows for
* providing the c/d values, allowing the developer to
* select any of the SipHash family to use for hashing.
*
* @param key a 16 byte key input
* @param c the number of compression rounds
* @param d the number of finalization rounds
*/
public SipHash(byte[] key, int c, int d){
this.c = c;
this.d = d;
SipHashKey hashKey = new SipHashKey(key);
this.v0 = (INITIAL_V0 ^ hashKey.k0);
this.v1 = (INITIAL_V1 ^ hashKey.k1);
this.v2 = (INITIAL_V2 ^ hashKey.k0);
this.v3 = (INITIAL_V3 ^ hashKey.k1);
}
/**
* The basic hash implementation provided in the library.
* Assuming you have your full input, you can provide it and
* it will be hashed based on the values which were provided
* to the constructor of this class.
*
* @param data the bytes to hash
* @return a ${@link SipHashResult} instance
*/
public SipHashResult hash(byte[] data) {
SipHashDigest digest = new SipHashDigest(v0, v1, v2, v3, c, d);
for (byte aData : data) {
digest.update(aData);
}
return digest.finish();
}
}

View File

@@ -0,0 +1,8 @@
package com.zackehh.siphash;
/**
* A basic enum to determine the case of a String.
*
* A String can either be UPPER or LOWER case.
*/
public enum SipHashCase { UPPER, LOWER }

View File

@@ -0,0 +1,63 @@
package com.zackehh.siphash;
/**
* Class containing several constants for use alongside
* hashing. Fields such as initial states and defaults,
* as they will not change throughout hashing.
*/
class SipHashConstants {
/**
* This constructor is private, nobody should be
* accessing it!
*
* @throws IllegalAccessException
*/
private SipHashConstants() throws IllegalAccessException {
throw new IllegalAccessException();
}
/**
* Initial magic number for v0.
*/
static final long INITIAL_V0 = 0x736f6d6570736575L;
/**
* Initial magic number for v1.
*/
static final long INITIAL_V1 = 0x646f72616e646f6dL;
/**
* Initial magic number for v2.
*/
static final long INITIAL_V2 = 0x6c7967656e657261L;
/**
* Initial magic number for v3.
*/
static final long INITIAL_V3 = 0x7465646279746573L;
/**
* The default number of rounds of compression during per block.
* This defaults to 2 as the default implementation is SipHash-2-4.
*/
static final int DEFAULT_C = 2;
/**
* The default number of rounds of compression during finalization.
* This defaults to 4 as the default implementation is SipHash-2-4.
*/
static final int DEFAULT_D = 4;
/**
* Whether or not we should pad any hashes by default.
*/
static final boolean DEFAULT_PADDING = false;
/**
* The default String casing for any output Hex Strings. We default
* to lower case as it's the least expensive path.
*/
static final SipHashCase DEFAULT_CASE = SipHashCase.LOWER;
}

View File

@@ -0,0 +1,225 @@
package com.zackehh.siphash;
import static com.zackehh.siphash.SipHashConstants.DEFAULT_C;
import static com.zackehh.siphash.SipHashConstants.DEFAULT_D;
/**
* A streaming implementation of SipHash, to be used when
* you don't have all input available at the same time. Chunks
* of bytes can be applied as they're received, and will be hashed
* accordingly.
*
* As with ${@link SipHash}, the compression and finalization rounds
* can be customized.
*/
public class SipHashDigest {
/**
* The values of SipHash-c-d, to determine which of the SipHash
* family we're using for this hash.
*/
private final int c, d;
/**
* Initial seeded value of v0.
*/
private long v0;
/**
* Initial seeded value of v1.
*/
private long v1;
/**
* Initial seeded value of v2.
*/
private long v2;
/**
* Initial seeded value of v3.
*/
private long v3;
/**
* A counter to keep track of the length of the input.
*/
private byte input_len = 0;
/**
* A counter to keep track of the current position inside
* of a chunk of bytes. Seeing as bytes are applied in chunks
* of 8, this is necessary.
*/
private int m_idx = 0;
/**
* The `m` value from the SipHash algorithm. Every 8 bytes, this
* value will be applied to the current state of the hash.
*/
private long m;
/**
* Accepts a 16 byte key input, and uses it to initialize
* the state of the hash. This uses the default values of
* c/d, meaning that we default to SipHash-2-4.
*
* @param key a 16 byte key input
*/
public SipHashDigest(byte[] key) {
this(key, DEFAULT_C, DEFAULT_D);
}
/**
* Accepts a 16 byte key input, and uses it to initialize
* the state of the hash. This constructor allows for
* providing the c/d values, allowing the developer to
* select any of the SipHash family to use for hashing.
*
* @param key a 16 byte key input
* @param c the number of compression rounds
* @param d the number of finalization rounds
*/
public SipHashDigest(byte[] key, int c, int d) {
this.c = c;
this.d = d;
SipHashKey hashKey = new SipHashKey(key);
this.v0 = SipHashConstants.INITIAL_V0 ^ hashKey.k0;
this.v1 = SipHashConstants.INITIAL_V1 ^ hashKey.k1;
this.v2 = SipHashConstants.INITIAL_V2 ^ hashKey.k0;
this.v3 = SipHashConstants.INITIAL_V3 ^ hashKey.k1;
}
/**
* This constructor is used by the ${@link SipHash} implementation,
* and takes an initial (seeded) value of v0/v1/v2/v3. This is used
* when the key has been pre-calculated. This constructor also
* receives the values of `c` and `d` to use in this hash.
*
* @param v0 an initial seeded v0
* @param v1 an initial seeded v1
* @param v2 an initial seeded v2
* @param v3 an initial seeded v3
* @param c the number of compression rounds
* @param d the number of finalization rounds
*/
SipHashDigest(long v0, long v1, long v2, long v3, int c, int d) {
this.c = c;
this.d = d;
this.v0 = v0;
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
}
/**
* Updates the current state of the hash with a single byte. This
* is the streaming implementation which shifts as required to ensure
* we can take an arbitrary number of bytes at any given time. We only
* apply the block once the index (`m_idx`) has reached 8. The number
* of compression rounds is determined by the `c` value passed in by
* the developer.
*
* This method returns this instance, as a way of allowing the developer
* to chain.
*
* @return a ${@link SipHashDigest} instance
*/
public SipHashDigest update(byte b) {
input_len++;
m |= (((long) b & 0xff) << (m_idx * 8));
m_idx++;
if (m_idx >= 8) {
v3 ^= m;
for (int i = 0; i < c; i++) {
round();
}
v0 ^= m;
m_idx = 0;
m = 0;
}
return this;
}
/**
* A convenience method to allow passing a chunk of bytes at once, rather
* than a byte at a time.
*
* @return a ${@link SipHashDigest} instance
*/
public SipHashDigest update(byte[] bytes) {
for (byte b : bytes) {
update(b);
}
return this;
}
/**
* Finalizes the hash by padding 0s until the next multiple of
* 8 (as we operate in 8 byte chunks). The last byte added to
* the hash is the length of the input, which we keep inside the
* `input_len` counter. The number of rounds is based on the value
* of `d` as specified by the developer.
*
* This method returns a ${@link SipHashResult}, as no further updates
* should occur (i.e. the lack of chaining here shows we're done).
*
* @return a ${@link SipHashResult} instance
*/
public SipHashResult finish() {
byte msgLenMod256 = input_len;
while (m_idx < 7) {
update((byte) 0);
}
update(msgLenMod256);
v2 ^= 0xff;
for (int i = 0; i < d; i++) {
round();
}
return new SipHashResult(v0 ^ v1 ^ v2 ^ v3);
}
/**
* Performs the equivalent of SipRound on the provided state.
* This method affects the state of this digest, in that it
* mutates the v states directly.
*/
private void round() {
v0 += v1;
v2 += v3;
v1 = rotateLeft(v1, 13);
v3 = rotateLeft(v3, 16);
v1 ^= v0;
v3 ^= v2;
v0 = rotateLeft(v0, 32);
v2 += v1;
v0 += v3;
v1 = rotateLeft(v1, 17);
v3 = rotateLeft(v3, 21);
v1 ^= v2;
v3 ^= v0;
v2 = rotateLeft(v2, 32);
}
/**
* Rotates an input number `val` left by `shift` number of bits. Bits which are
* pushed off to the left are rotated back onto the right, making this a left
* rotation (a circular shift).
*
* @param val the value to be shifted
* @param shift how far left to shift
* @return a long value once shifted
*/
private long rotateLeft(long val, int shift) {
return (val << shift) | val >>> (64 - shift);
}
}

View File

@@ -0,0 +1,53 @@
package com.zackehh.siphash;
/**
* A container class to store both k0 and k1. These
* values are created from a 16 byte key passed into
* the constructor. This isn't ideal as it's another
* alloc, but it'll do for now.
*/
class SipHashKey {
/**
* The value of k0.
*/
final long k0;
/**
* The value of k1.
*/
final long k1;
/**
* Accepts a 16 byte input key and converts the
* first and last 8 byte chunks to little-endian.
* These values become k0 and k1.
*
* @param key the 16 byte key input
*/
public SipHashKey(byte[] key) {
if (key.length != 16) {
throw new IllegalArgumentException("Key must be exactly 16 bytes!");
}
this.k0 = bytesToLong(key, 0);
this.k1 = bytesToLong(key, 8);
}
/**
* Converts a chunk of 8 bytes to a number in little-endian
* format. Accepts an offset to determine where the chunk
* begins in the byte array.
*
* @param b our byte array
* @param offset the index to start at
* @return a little-endian long representation
*/
private static long bytesToLong(byte[] b, int offset) {
long m = 0;
for (int i = 0; i < 8; i++) {
m |= ((((long) b[i + offset]) & 0xff) << (8 * i));
}
return m;
}
}

View File

@@ -0,0 +1,121 @@
package com.zackehh.siphash;
import static com.zackehh.siphash.SipHashConstants.DEFAULT_CASE;
import static com.zackehh.siphash.SipHashConstants.DEFAULT_PADDING;
/**
* A container class of the result of a hash. This class exists
* to allow the developer to retrieve the result in any format
* they like. Currently available formats are `long` and ${@link java.lang.String}.
* When retrieving as a String, the developer can specify the case
* they want it in, and whether or not we should pad the left side
* to 16 characters with 0s.
*/
public class SipHashResult {
/**
* The internal hash result.
*/
private final long result;
/**
* A package-private constructor, as only
* SipHash should be creating results.
*
* @param result the result of a hash
*/
SipHashResult(long result){
this.result = result;
}
/**
* Simply returns the hash result as a long.
*
* @return the hash value as a long
*/
public long get(){
return result;
}
/**
* Returns the result as a Hex String, using
* the default padding and casing values.
*
* @return the hash value as a Hex String
*/
public String getHex(){
return getHex(DEFAULT_PADDING, DEFAULT_CASE);
}
/**
* Returns the result as a Hex String, using
* a custom padding value and default casing value.
*
* @param padding whether or not to pad the string
* @return the hash value as a Hex String
*/
public String getHex(boolean padding){
return getHex(padding, DEFAULT_CASE);
}
/**
* Returns the result as a Hex String, using
* a default padding value and custom casing value.
*
* @param s_case the case to convert the output to
* @return the hash value as a Hex String
*/
public String getHex(SipHashCase s_case){
return getHex(DEFAULT_PADDING, s_case);
}
/**
* Returns the result as a Hex String, taking in
* various arguments to customize the output further,
* such as casing and padding.
*
* @param padding whether or not to pad the string
* @param s_case the case to convert the output to
* @return a Hex String in the custom format
*/
public String getHex(boolean padding, SipHashCase s_case){
String str = Long.toHexString(get());
if (padding) {
str = leftPad(str, 16, "0");
}
if (s_case == SipHashCase.UPPER) {
str = str.toUpperCase();
}
return str;
}
/**
* Modified for https://github.com/Zukero/ATCS
* Replaces the StringUtils.leftPad from apache commons, to remove dependency.
*
* @param str the string to pad
* @param len the total desired length
* @param pad the padding string
* @return str prefixed with enough repetitions of the pad to have a total length matching len
*/
public String leftPad(String str, int len, String pad) {
StringBuilder sb = new StringBuilder(len);
int padlen = len - str.length();
int partialPadLen = padlen % pad.length();
int padCount = padlen / pad.length();
while (padCount >= 0) {
sb.append(pad);
padCount--;
}
if (partialPadLen > 0) {
sb.append(pad.substring(0, partialPadLen));
}
sb.append(str);
return sb.toString();
}
}

View File

@@ -0,0 +1,27 @@
package com.zackehh.siphash;
import org.testng.Assert;
import org.testng.annotations.Test;
public class SipHashCaseTest {
@Test
public void ensureAllValues() throws Exception {
SipHashCase[] cases = SipHashCase.values();
Assert.assertEquals(cases[0], SipHashCase.UPPER);
Assert.assertEquals(cases[1], SipHashCase.LOWER);
}
@Test
public void ensureValueOf() throws Exception {
Assert.assertEquals(SipHashCase.valueOf("UPPER"), SipHashCase.UPPER);
Assert.assertEquals(SipHashCase.valueOf("LOWER"), SipHashCase.LOWER);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void invalidCaseValueOf() throws Exception {
SipHashCase.valueOf("invalid");
}
}

View File

@@ -0,0 +1,32 @@
package com.zackehh.siphash;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
public class SipHashConstantsTest {
@Test
public void ensureAllConstants() throws Exception {
Assert.assertEquals(SipHashConstants.INITIAL_V0, 0x736f6d6570736575L);
Assert.assertEquals(SipHashConstants.INITIAL_V1, 0x646f72616e646f6dL);
Assert.assertEquals(SipHashConstants.INITIAL_V2, 0x6c7967656e657261L);
Assert.assertEquals(SipHashConstants.INITIAL_V3, 0x7465646279746573L);
Assert.assertEquals(SipHashConstants.DEFAULT_C, 2);
Assert.assertEquals(SipHashConstants.DEFAULT_D, 4);
Assert.assertEquals(SipHashConstants.DEFAULT_CASE, SipHashCase.LOWER);
Assert.assertEquals(SipHashConstants.DEFAULT_PADDING, false);
}
@Test(expectedExceptions = InvocationTargetException.class)
public void ensureCannotInstance() throws Exception {
Constructor<SipHashConstants> ctor = SipHashConstants.class.getDeclaredConstructor();
ctor.setAccessible(true);
Assert.assertTrue(Modifier.isPrivate(ctor.getModifiers()));
ctor.newInstance();
}
}

View File

@@ -0,0 +1,148 @@
package com.zackehh.siphash;
import org.testng.Assert;
import org.testng.annotations.Test;
public class SipHashDigestTest {
@Test
public void initializeDigestWithKey() throws Exception {
SipHashDigest sipHash = new SipHashDigest("0123456789ABCDEF".getBytes());
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, SipHashConstants.DEFAULT_C);
Assert.assertEquals(d, SipHashConstants.DEFAULT_D);
}
@Test
public void initializeDigestWithKeyAndCD() throws Exception {
SipHashDigest sipHash = new SipHashDigest("0123456789ABCDEF".getBytes(), 4, 8);
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, 4);
Assert.assertEquals(d, 8);
}
@Test
public void initializeDigestWithKeyThenHash() throws Exception {
SipHashDigest sipHash = new SipHashDigest("0123456789ABCDEF".getBytes());
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, SipHashConstants.DEFAULT_C);
Assert.assertEquals(d, SipHashConstants.DEFAULT_D);
for(byte b : "zymotechnics".getBytes()){
sipHash.update(b);
}
SipHashResult hashResult = sipHash.finish();
Assert.assertEquals(hashResult.get(), 699588702094987020L);
Assert.assertEquals(hashResult.getHex(), "9b57037cd3f8f0c");
Assert.assertEquals(hashResult.getHex(true), "09b57037cd3f8f0c");
Assert.assertEquals(hashResult.getHex(SipHashCase.UPPER), "9B57037CD3F8F0C");
Assert.assertEquals(hashResult.getHex(true, SipHashCase.UPPER), "09B57037CD3F8F0C");
}
@Test
public void initializeStateWithKeyAndCDThenHash() throws Exception {
SipHashDigest sipHash = new SipHashDigest("0123456789ABCDEF".getBytes(), 4, 8);
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, 4);
Assert.assertEquals(d, 8);
for(byte b : "zymotechnics".getBytes()){
sipHash.update(b);
}
SipHashResult hashResult = sipHash.finish();
Assert.assertEquals(hashResult.get(), -3891084581787974112L); // overflow
Assert.assertEquals(hashResult.getHex(), "ca0017304f874620");
Assert.assertEquals(hashResult.getHex(true), "ca0017304f874620");
Assert.assertEquals(hashResult.getHex(SipHashCase.UPPER), "CA0017304F874620");
Assert.assertEquals(hashResult.getHex(true, SipHashCase.UPPER), "CA0017304F874620");
}
@Test
public void updateWithAByteArrayChunk() throws Exception {
SipHashDigest sipHash = new SipHashDigest("0123456789ABCDEF".getBytes());
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, SipHashConstants.DEFAULT_C);
Assert.assertEquals(d, SipHashConstants.DEFAULT_D);
sipHash.update("zymo".getBytes());
sipHash.update("techni".getBytes());
sipHash.update("cs".getBytes());
SipHashResult hashResult = sipHash.finish();
Assert.assertEquals(hashResult.get(), 699588702094987020L);
Assert.assertEquals(hashResult.getHex(), "9b57037cd3f8f0c");
Assert.assertEquals(hashResult.getHex(true), "09b57037cd3f8f0c");
Assert.assertEquals(hashResult.getHex(SipHashCase.UPPER), "9B57037CD3F8F0C");
Assert.assertEquals(hashResult.getHex(true, SipHashCase.UPPER), "09B57037CD3F8F0C");
}
}

View File

@@ -0,0 +1,27 @@
package com.zackehh.siphash;
import org.testng.Assert;
import org.testng.annotations.Test;
public class SipHashKeyTest {
@Test
public void initializeWithKey() throws Exception {
SipHashKey key = new SipHashKey("0123456789ABCDEF".getBytes());
long k0 = SipHashTestUtils.getPrivateField(key, "k0", Long.class);
long k1 = SipHashTestUtils.getPrivateField(key, "k1", Long.class);
Assert.assertEquals(k0, 3978425819141910832L);
Assert.assertEquals(k1, 5063528411713059128L);
}
@Test(
expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Key must be exactly 16 bytes!"
)
public void initializeWithKeyTooLong() throws Exception {
new SipHashKey("0123456789ABCDEFG".getBytes()); // 17 bytes
}
}

View File

@@ -0,0 +1,107 @@
package com.zackehh.siphash;
import org.testng.Assert;
import org.testng.annotations.Test;
public class SipHashTest {
@Test
public void initializeStateWithKey() throws Exception {
SipHash sipHash = new SipHash("0123456789ABCDEF".getBytes());
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, SipHashConstants.DEFAULT_C);
Assert.assertEquals(d, SipHashConstants.DEFAULT_D);
}
@Test
public void initializeStateWithKeyAndCD() throws Exception {
SipHash sipHash = new SipHash("0123456789ABCDEF".getBytes(), 4, 8);
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, 4);
Assert.assertEquals(d, 8);
}
@Test
public void initializeStateWithKeyThenHash() throws Exception {
SipHash sipHash = new SipHash("0123456789ABCDEF".getBytes());
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, SipHashConstants.DEFAULT_C);
Assert.assertEquals(d, SipHashConstants.DEFAULT_D);
SipHashResult hashResult = sipHash.hash("zymotechnics".getBytes());
Assert.assertEquals(hashResult.get(), 699588702094987020L);
Assert.assertEquals(hashResult.getHex(), "9b57037cd3f8f0c");
Assert.assertEquals(hashResult.getHex(true), "09b57037cd3f8f0c");
Assert.assertEquals(hashResult.getHex(SipHashCase.UPPER), "9B57037CD3F8F0C");
Assert.assertEquals(hashResult.getHex(true, SipHashCase.UPPER), "09B57037CD3F8F0C");
}
@Test
public void initializeStateWithKeyAndCDThenHash() throws Exception {
SipHash sipHash = new SipHash("0123456789ABCDEF".getBytes(), 4, 8);
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, 4);
Assert.assertEquals(d, 8);
SipHashResult hashResult = sipHash.hash("zymotechnics".getBytes());
Assert.assertEquals(hashResult.get(), -3891084581787974112L); // overflow
Assert.assertEquals(hashResult.getHex(), "ca0017304f874620");
Assert.assertEquals(hashResult.getHex(true), "ca0017304f874620");
Assert.assertEquals(hashResult.getHex(SipHashCase.UPPER), "CA0017304F874620");
Assert.assertEquals(hashResult.getHex(true, SipHashCase.UPPER), "CA0017304F874620");
}
}

View File

@@ -0,0 +1,13 @@
package com.zackehh.siphash;
import java.lang.reflect.Field;
public class SipHashTestUtils {
static <T> T getPrivateField(Object obj, String name, Class<T> clazz) throws Exception {
Field f = obj.getClass().getDeclaredField(name);
f.setAccessible(true);
return clazz.cast(f.get(obj));
}
}

View File

@@ -1,13 +1,38 @@
package com.gpl.rpg.atcontentstudio;
import java.awt.Color;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.plaf.FontUIResource;
import prefuse.data.expression.parser.ExpressionParser;
import com.gpl.rpg.atcontentstudio.model.Workspace;
import com.gpl.rpg.atcontentstudio.ui.StudioFrame;
@@ -18,15 +43,35 @@ import com.gpl.rpg.atcontentstudio.ui.WorkspaceSelector;
public class ATContentStudio {
public static final String APP_NAME = "Andor's Trail Content Studio";
public static final String APP_VERSION = "v0.4.4";
public static final String APP_VERSION = "v0.6.11";
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";
public static final String FONT_SCALE_ENV_VAR_NAME = "FONT_SCALE";
public static boolean STARTED = false;
public static float SCALING=1.0f;
public static StudioFrame frame = null;
//Need to keep a strong reference to it, to avoid garbage collection that'll reset these loggers.
public static final List<Logger> configuredLoggers = new LinkedList<Logger>();
/**
* @param args
*/
public static void main(String[] args) {
String fontScaling = System.getProperty(FONT_SCALE_ENV_VAR_NAME);
Float fontScale = null;
if (fontScaling != null) {
try {
fontScale = Float.parseFloat(fontScaling);
SCALING=fontScale;
} catch (NumberFormatException e) {
System.err.println("Failed to parse font scaling parameter. Using default.");
e.printStackTrace();
}
}
ConfigCache.init();
@@ -43,7 +88,14 @@ public class ATContentStudio {
} catch (UnsupportedLookAndFeelException e) {
e.printStackTrace();
}
scaleUIFont();
//Need to keep a strong reference to it, to avoid garbage collection that'll reset this setting.
Logger l = Logger.getLogger(ExpressionParser.class.getName());
l.setLevel(Level.OFF);
configuredLoggers.add(l);
final WorkspaceSelector wsSelect = new WorkspaceSelector();
wsSelect.pack();
@@ -61,9 +113,14 @@ public class ATContentStudio {
WorkerDialog.showTaskMessage("Loading your workspace...", null, new Runnable(){
public void run() {
Workspace.setActive(workspaceRoot);
if (Workspace.activeWorkspace.settings.useInternet.getCurrentValue() && Workspace.activeWorkspace.settings.checkUpdates.getCurrentValue()) {
new Thread() {
public void run() {checkUpdate();}
}.start();
}
frame = new StudioFrame(APP_NAME+" "+APP_VERSION);
frame.setVisible(true);
frame.setDefaultCloseOperation(StudioFrame.EXIT_ON_CLOSE);
frame.setDefaultCloseOperation(StudioFrame.DO_NOTHING_ON_CLOSE);
};
});
for (File f : ConfigCache.getKnownWorkspaces()) {
@@ -82,4 +139,89 @@ public class ATContentStudio {
});
}
private static void checkUpdate() {
BufferedReader in = null;
try {
URL url = new URL(CHECK_UPDATE_URL);
in = new BufferedReader(new InputStreamReader(url.openStream()));
String inputLine, lastLine = null;
while ((inputLine = in.readLine()) != null) {lastLine = inputLine;}
if (lastLine != null && !lastLine.equals(APP_VERSION)) {
// for copying style
JLabel label = new JLabel();
Font font = label.getFont();
Color color = label.getBackground();
// create some css from the label's font
StringBuffer style = new StringBuffer("font-family:" + font.getFamily() + ";");
style.append("font-weight:" + (font.isBold() ? "bold" : "normal") + ";");
style.append("font-size:" + font.getSize() + "pt;");
style.append("background-color: rgb("+color.getRed()+","+color.getGreen()+","+color.getBlue()+");");
JEditorPane ep = new JEditorPane("text/html", "<html><body style=\"" + style + "\">"
+ "You are not running the latest ATCS version.<br/>"
+ "You can get the latest version ("+lastLine+") by clicking the link below.<br/>"
+ "<a href=\""+DOWNLOAD_URL+"\">"+DOWNLOAD_URL+"</a><br/>"
+ "<br/>"
+ "</body></html>");
ep.setEditable(false);
ep.setBorder(null);
ep.addHyperlinkListener(new HyperlinkListener() {
@Override
public void hyperlinkUpdate(HyperlinkEvent e) {
try {
if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
Desktop.getDesktop().browse(e.getURL().toURI());
}
} catch (IOException e1) {
e1.printStackTrace();
} catch (URISyntaxException e1) {
e1.printStackTrace();
}
}
});
JOptionPane.showMessageDialog(null, ep, "Update available", JOptionPane.INFORMATION_MESSAGE);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null) in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void scaleUIFont() {
if (SCALING != 1.0f) {
System.out.println("Scaling fonts to "+SCALING);
UIDefaults defaults = UIManager.getLookAndFeelDefaults();
Map<Object, Object> newDefaults = new HashMap<Object, Object>();
for (Enumeration<Object> e = defaults.keys(); e.hasMoreElements();) {
Object key = e.nextElement();
Object value = defaults.get(key);
if (value instanceof Font) {
Font font = (Font) value;
int newSize = (int)(font.getSize() * SCALING);
if (value instanceof FontUIResource) {
newDefaults.put(key, new FontUIResource(font.getName(), font.getStyle(), newSize));
} else {
newDefaults.put(key, new Font(font.getName(), font.getStyle(), newSize));
}
}
}
for (Object key : newDefaults.keySet()) {
defaults.put(key, newDefaults.get(key));
}
}
}
}

View File

@@ -2,11 +2,12 @@ package com.gpl.rpg.atcontentstudio;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class Notification {
public static List<Notification> notifs = new ArrayList<Notification>();
private static List<NotificationListener> listeners = new ArrayList<NotificationListener>();
private static List<NotificationListener> listeners = new CopyOnWriteArrayList<NotificationListener>();
public static boolean showS = true, showI = true, showW = true, showE = true;
static {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1013 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1023 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 481 B

View File

@@ -116,4 +116,9 @@ public class ClosedProject implements ProjectTreeNode {
return true;
}
@Override
public boolean needsSaving() {
return false;
}
}

View File

@@ -4,10 +4,10 @@ import java.awt.Image;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.swing.tree.TreeNode;
@@ -32,7 +32,7 @@ public abstract class GameDataElement implements ProjectTreeNode, Serializable {
public boolean writable = false;
//List of objects whose transition to "linked" state made them point to this instance.
private Map<GameDataElement, Integer> backlinks = new HashMap<GameDataElement, Integer>();
private Map<GameDataElement, Integer> backlinks = new ConcurrentHashMap<GameDataElement, Integer>();
public String id = null;
@@ -99,7 +99,7 @@ public abstract class GameDataElement implements ProjectTreeNode, Serializable {
@Override
public Project getProject() {
return parent.getProject();
return parent == null ? null : parent.getProject();
}
@@ -176,6 +176,10 @@ public abstract class GameDataElement implements ProjectTreeNode, Serializable {
return false;
}
public boolean needsSaving() {
return this.state == State.modified || this.state == State.created;
}
public abstract String getProjectFilename();
public abstract void save();

View File

@@ -9,7 +9,6 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -31,6 +30,7 @@ import com.gpl.rpg.atcontentstudio.model.maps.Worldmap;
import com.gpl.rpg.atcontentstudio.model.maps.WorldmapSegment;
import com.gpl.rpg.atcontentstudio.model.sprites.SpriteSheetSet;
import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet;
import com.gpl.rpg.atcontentstudio.model.tools.writermode.WriterModeDataSet;
import com.gpl.rpg.atcontentstudio.ui.DefaultIcons;
public class GameSource implements ProjectTreeNode, Serializable {
@@ -44,6 +44,7 @@ public class GameSource implements ProjectTreeNode, Serializable {
public transient TMXMapSet gameMaps;
public transient SpriteSheetSet gameSprites;
public transient Worldmap worldmap;
public transient WriterModeDataSet writerModeDataSet;
private transient SavedSlotCollection v;
public static enum Type {
@@ -86,6 +87,9 @@ public class GameSource implements ProjectTreeNode, Serializable {
readResourceList();
}
}
if (type == Type.created) {
this.writerModeDataSet = new WriterModeDataSet(this);
}
this.gameData = new GameDataSet(this);
this.gameMaps = new TMXMapSet(this);
this.gameSprites = new SpriteSheetSet(this);
@@ -95,6 +99,9 @@ public class GameSource implements ProjectTreeNode, Serializable {
v.add(gameMaps);
v.add(gameSprites);
v.add(worldmap);
if (type == Type.created) {
v.add(writerModeDataSet);
}
}
public void readResourceList() {
@@ -127,7 +134,7 @@ public class GameSource implements ProjectTreeNode, Serializable {
for (int i = 0; i < arraysList.getLength(); i++) {
Element arrayNode = (Element) arraysList.item(i);
String name = arrayNode.getAttribute("name");
List<String> arrayContents = new LinkedList<String>();
List<String> arrayContents = new ArrayList<String>();
NodeList arrayItems = arrayNode.getElementsByTagName("item");
if (arrayItems != null) {
for (int j = 0; j < arrayItems.getLength(); j++) {
@@ -206,11 +213,11 @@ public class GameSource implements ProjectTreeNode, Serializable {
@Override
public String getDesc() {
switch(type) {
case altered: return "Altered data";
case created: return "Created data";
case referenced: return "Referenced data";
case source: return "AT Source"; //The fact that it is from "source" is already mentionned by its parent.
default: return "Game data";
case altered: return (needsSaving() ? "*" : "")+"Altered data";
case created: return (needsSaving() ? "*" : "")+"Created data";
case referenced: return (needsSaving() ? "*" : "")+"Referenced data";
case source: return (needsSaving() ? "*" : "")+"AT Source"; //The fact that it is from "source" is already mentionned by its parent.
default: return (needsSaving() ? "*" : "")+"Game data";
}
}
@@ -276,4 +283,12 @@ public class GameSource implements ProjectTreeNode, Serializable {
public WorldmapSegment getWorldmapSegment(String id) {
return worldmap.getWorldmapSegment(id);
}
@Override
public boolean needsSaving() {
for (ProjectTreeNode node : v.getNonEmptyIterable()) {
if (node.needsSaving()) return true;
}
return false;
}
}

View File

@@ -1,27 +1,47 @@
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.HashSet;
import java.util.LinkedHashMap;
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;
@@ -38,12 +58,14 @@ 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.TMXMap;
import com.gpl.rpg.atcontentstudio.model.maps.TMXMapSet;
import com.gpl.rpg.atcontentstudio.model.maps.Worldmap;
import com.gpl.rpg.atcontentstudio.model.maps.WorldmapSegment;
import com.gpl.rpg.atcontentstudio.model.saves.SavedGamesSet;
import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet;
import com.gpl.rpg.atcontentstudio.model.tools.writermode.WriterModeData;
import com.gpl.rpg.atcontentstudio.ui.DefaultIcons;
import com.gpl.rpg.atcontentstudio.ui.WorkerDialog;
import com.gpl.rpg.atcontentstudio.utils.FileUtils;
@@ -189,7 +211,7 @@ public class Project implements ProjectTreeNode, Serializable {
}
@Override
public String getDesc() {
return name;
return (needsSaving() ? "*" : "")+name;
}
@@ -219,14 +241,14 @@ public class Project implements ProjectTreeNode, Serializable {
public void refreshTransients(Workspace w) {
this.parent = w;
if (knownSpritesheetsProperties == null) {
try {
knownSpritesheetsProperties = new Properties();
knownSpritesheetsProperties.load(Project.class.getResourceAsStream("/spritesheets.properties"));
} catch (IOException e) {
Notification.addWarn("Unable to load default spritesheets properties.");
e.printStackTrace();
}
projectElementListeners = new HashMap<Class<? extends GameDataElement>, List<ProjectElementListener>>();
try {
knownSpritesheetsProperties = new Properties();
knownSpritesheetsProperties.load(Project.class.getResourceAsStream("/spritesheets.properties"));
} catch (IOException e) {
Notification.addWarn("Unable to load default spritesheets properties.");
e.printStackTrace();
}
if (sourceSetToUse == null) {
@@ -253,7 +275,6 @@ public class Project implements ProjectTreeNode, Serializable {
linkAll();
projectElementListeners = new HashMap<Class<? extends GameDataElement>, List<ProjectElementListener>>();
}
public void linkAll() {
@@ -265,7 +286,7 @@ public class Project implements ProjectTreeNode, Serializable {
}
}
for (ProjectTreeNode node : baseContent.gameMaps.tmxMaps) {
((TMXMap)node).parse();
((TMXMap)node).link();
}
for (ProjectTreeNode node : alteredContent.gameData.v.getNonEmptyIterable()) {
if (node instanceof GameDataCategory<?>) {
@@ -275,7 +296,7 @@ public class Project implements ProjectTreeNode, Serializable {
}
}
for (ProjectTreeNode node : alteredContent.gameMaps.tmxMaps) {
((TMXMap)node).parse();
((TMXMap)node).link();
}
for (ProjectTreeNode node : createdContent.gameData.v.getNonEmptyIterable()) {
if (node instanceof GameDataCategory<?>) {
@@ -285,9 +306,6 @@ public class Project implements ProjectTreeNode, Serializable {
}
}
for (ProjectTreeNode node : createdContent.gameMaps.tmxMaps) {
((TMXMap)node).parse();
}
for (ProjectTreeNode node : baseContent.gameMaps.tmxMaps) {
((TMXMap)node).link();
}
@@ -665,6 +683,26 @@ public class Project implements ProjectTreeNode, Serializable {
return sheet;
}
public int getSpritesheetCount() {
return createdContent.gameSprites.spritesheets.size() + baseContent.gameSprites.spritesheets.size();
}
public Spritesheet getSpritesheet(int index) {
if (index < createdContent.gameSprites.spritesheets.size()) {
return createdContent.gameSprites.spritesheets.get(index);
} else if (index < getSpritesheetCount()){
return getSpritesheet(baseContent.gameSprites.spritesheets.get(index - createdContent.gameSprites.spritesheets.size()).id);
}
return null;
}
public int getSpritesheetIndex(Spritesheet spritesheet) {
if (spritesheet.getDataType() == GameSource.Type.created) {
return createdContent.gameSprites.spritesheets.indexOf(spritesheet);
} else {
return createdContent.gameSprites.spritesheets.size() + baseContent.gameSprites.spritesheets.indexOf(baseContent.gameSprites.getSpritesheet(spritesheet.id));
}
}
public TMXMap getMap(String id) {
TMXMap map = createdContent.gameMaps.getMap(id);
@@ -694,6 +732,20 @@ public class Project implements ProjectTreeNode, Serializable {
}
}
public int getWriterSketchCount() {
return createdContent.writerModeDataSet.getChildCount();
}
public WriterModeData getWriterSketch(String id) {
return createdContent.writerModeDataSet.getWriterSketch(id);
}
public WriterModeData getWriterSketch(int index) {
if (index < createdContent.writerModeDataSet.getChildCount()) {
return createdContent.writerModeDataSet.get(index);
}
return null;
}
@Override
public Project getProject() {
@@ -727,6 +779,15 @@ public class Project implements ProjectTreeNode, Serializable {
} else {
if (type == GameSource.Type.source) {
JSONElement clone = (JSONElement) node.clone();
if (node instanceof Quest) {
for (QuestStage oldStage : ((Quest) node).stages) {
QuestStage newStage = ((Quest) clone).getStage(oldStage.progress);
for (GameDataElement backlink : oldStage.getBacklinks()) {
backlink.elementChanged(oldStage, newStage);
}
oldStage.getBacklinks().clear();
}
}
for (GameDataElement backlink : node.getBacklinks()) {
backlink.elementChanged(node, clone);
}
@@ -786,7 +847,7 @@ public class Project implements ProjectTreeNode, Serializable {
/**
*
* @param node. Before calling this method, make sure that no other node with the same class exist in either created or altered.
* @param node. Before calling this method, make sure that no other node with the same class and id exist in either created or altered.
*/
public void createElement(JSONElement node) {
node.writable = true;
@@ -808,6 +869,62 @@ public class Project implements ProjectTreeNode, Serializable {
fireElementAdded(node, getNodeIndex(node));
}
/**
*
* @param node. Before calling this method, make sure that no other node with the same class and id exist in either created or altered.
*/
public void createElements(List<? extends JSONElement> nodes) {
for (JSONElement node : nodes) {
//Already added.
if (node.getProject() != null) continue;
node.writable = true;
if (getGameDataElement(node.getClass(), node.id) != null) {
GameDataElement existingNode = getGameDataElement(node.getClass(), node.id);
for (GameDataElement backlink : existingNode.getBacklinks()) {
backlink.elementChanged(existingNode, node);
}
existingNode.getBacklinks().clear();
node.writable = true;
alteredContent.gameData.addElement(node);
} else {
createdContent.gameData.addElement(node);
}
}
for (JSONElement node : nodes) {
node.link();
node.state = GameDataElement.State.created;
fireElementAdded(node, getNodeIndex(node));
}
}
/**
*
* @param node. Before calling this method, make sure that no other map with the same id exist in either created or altered.
*/
public void createElement(TMXMap node) {
node.writable = true;
if (getMap(node.id) != null) {
GameDataElement existingNode = getMap(node.id);
for (GameDataElement backlink : existingNode.getBacklinks()) {
backlink.elementChanged(existingNode, node);
}
existingNode.getBacklinks().clear();
node.writable = true;
node.tmxFile = new File(alteredContent.baseFolder, node.tmxFile.getName());
node.parent = alteredContent.gameMaps;
alteredContent.gameMaps.addMap(node);
node.link();
node.state = GameDataElement.State.created;
} else {
node.tmxFile = new File(createdContent.baseFolder, node.tmxFile.getName());
node.parent = createdContent.gameMaps;
createdContent.gameMaps.addMap(node);
node.link();
node.state = GameDataElement.State.created;
}
fireElementAdded(node, getNodeIndex(node));
}
public void moveToCreated(JSONElement target) {
target.childrenRemoved(new ArrayList<ProjectTreeNode>());
@@ -844,6 +961,15 @@ public class Project implements ProjectTreeNode, Serializable {
fireElementAdded(node, getNodeIndex(node));
}
public void createWriterSketch(WriterModeData node) {
node.writable = true;
createdContent.writerModeDataSet.add(node);
node.link();
fireElementAdded(node, getNodeIndex(node));
}
@Override
public GameDataSet getDataSet() {
@@ -936,85 +1062,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<? extends JSONElement> altered, GameDataCategory<? extends JSONElement> source, Class<? extends JSONElement> gdeClass, File targetFolder) {
Set<String> alteredFileNames = new HashSet<String>();
Map<String, List<Map>> toWrite = new HashMap<String, List<Map>>();
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<Class<? extends GameDataElement>, List<String>> writtenFilesPerDataType = new LinkedHashMap<Class<? extends GameDataElement>, List<String>>();
List<String> 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<String>();
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<String> writeDataDeltaForDataType(GameDataCategory<? extends JSONElement> created, GameDataCategory<? extends JSONElement> altered, GameDataCategory<? extends JSONElement> source, Class<? extends JSONElement> gdeClass, File targetFolder) {
List<String> filenamesToWrite = new LinkedList<String>();
Map<String, List<Map>> dataToWritePerFilename = new LinkedHashMap<String, List<Map>>();
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<Map>());
if (dataToWritePerFilename.get(fName) == null) {
dataToWritePerFilename.put(fName, new ArrayList<Map>());
}
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<Map>());
}
//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
}
@@ -1029,8 +1219,146 @@ public class Project implements ProjectTreeNode, Serializable {
e.printStackTrace();
}
}
return filenamesToWrite;
}
private void writeResourceListXml(Map<Class<? extends GameDataElement>, List<String>> writtenFilesPerDataType, String xmlFileRelPath, File baseFolder, File tmpDir) {
File xmlFile = new File(baseFolder, xmlFileRelPath);
File outputFile = new File(tmpDir, xmlFileRelPath);
Map<String, Class<? extends GameDataElement>> classNamesByArrayNames = new HashMap<String, Class<? extends GameDataElement>>();
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<? extends GameDataElement> clazz;
List<String> 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(
"<?xml version=\"1.0\"?>\r\n" +
"<xsl:stylesheet version=\"1.0\"\r\n" +
" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\r\n" +
" <xsl:strip-space elements=\"*\" />\r\n" +
" <xsl:output method=\"xml\" indent=\"yes\" />\r\n" +
"\r\n" +
" <xsl:template match=\"node() | @*\" name=\"identity\">\r\n" +
" <xsl:copy>\r\n" +
" <xsl:apply-templates select=\"node() | @*\" />\r\n" +
" </xsl:copy>\r\n" +
" </xsl:template>\r\n" +
"\r\n" +
" <xsl:template match=\"array\">\r\n" +
" <xsl:call-template name=\"identity\"/>\r\n" +
" <xsl:text>&#xA;&#xA;&#x20;&#x20;&#x20;&#x20;</xsl:text>\r\n" +
" </xsl:template>\r\n" +
"</xsl:stylesheet>")));
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() {
for (ProjectTreeNode node : v.getNonEmptyIterable()) {
if (node.needsSaving()) return true;
}
return false;
}

View File

@@ -53,5 +53,7 @@ public interface ProjectTreeNode extends TreeNode {
public GameSource.Type getDataType();
public boolean isEmpty();
public boolean needsSaving();
}

View File

@@ -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;
@@ -21,50 +22,54 @@ import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.io.SettingsSave;
import com.gpl.rpg.atcontentstudio.model.GameSource.Type;
import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet;
import com.gpl.rpg.atcontentstudio.ui.WorkerDialog;
import com.gpl.rpg.atcontentstudio.ui.ProjectsTree.ProjectsTreeModel;
import com.gpl.rpg.atcontentstudio.ui.WorkerDialog;
public class Workspace implements ProjectTreeNode, Serializable {
private static final long serialVersionUID = 7938633033601384956L;
public static final String WS_SETTINGS_FILE = ".workspace";
public static Workspace activeWorkspace;
public Preferences preferences = new Preferences();
public File baseFolder;
public File settingsFile;
public transient WorkspaceSettings settings;
public transient List<ProjectTreeNode> projects = new ArrayList<ProjectTreeNode>();
public Set<String> projectsName = new HashSet<String>();
public Map<String, Boolean> projectsOpenByName = new HashMap<String, Boolean>();
public Set<File> knownMapSourcesFolders = new HashSet<File>();
public transient ProjectsTreeModel projectsTreeModel = null;
public Workspace(File workspaceRoot) {
baseFolder = workspaceRoot;
if (!workspaceRoot.exists()) {
try {
workspaceRoot.mkdir();
} catch (SecurityException e) {
Notification.addError("Error creating workspace directory: "+e.getMessage());
Notification.addError("Error creating workspace directory: "
+ e.getMessage());
e.printStackTrace();
}
}
settings = new WorkspaceSettings(this);
settingsFile = new File(workspaceRoot, WS_SETTINGS_FILE);
if (!settingsFile.exists()) {
try {
settingsFile.createNewFile();
} catch (IOException e) {
Notification.addError("Error creating workspace datafile: "+e.getMessage());
Notification.addError("Error creating workspace datafile: "
+ e.getMessage());
e.printStackTrace();
}
}
Notification.addSuccess("New workspace created: "+workspaceRoot.getAbsolutePath());
Notification.addSuccess("New workspace created: "
+ workspaceRoot.getAbsolutePath());
save();
}
public static void setActive(File workspaceRoot) {
Workspace w = null;
@@ -81,12 +86,13 @@ public class Workspace implements ProjectTreeNode, Serializable {
}
activeWorkspace = w;
}
public static void saveActive() {
activeWorkspace.save();
}
public void save() {
settings.save();
SettingsSave.saveInstance(this, settingsFile, "Workspace");
}
@@ -94,82 +100,111 @@ public class Workspace implements ProjectTreeNode, Serializable {
public Enumeration<ProjectTreeNode> children() {
return Collections.enumeration(projects);
}
@Override
public boolean getAllowsChildren() {
return true;
}
@Override
public TreeNode getChildAt(int arg0) {
return projects.get(arg0);
}
@Override
public int getChildCount() {
return projects.size();
}
@Override
public int getIndex(TreeNode arg0) {
return projects.indexOf(arg0);
}
@Override
public TreeNode getParent() {
return null;
}
@Override
public boolean isLeaf() {
return false;
}
@Override
public void childrenAdded(List<ProjectTreeNode> path) {
path.add(0, this);
if (projectsTreeModel != null) projectsTreeModel.insertNode(new TreePath(path.toArray()));
if (projectsTreeModel != null)
projectsTreeModel.insertNode(new TreePath(path.toArray()));
}
@Override
public void childrenChanged(List<ProjectTreeNode> path) {
path.add(0, this);
if (projectsTreeModel != null) projectsTreeModel.changeNode(new TreePath(path.toArray()));
ProjectTreeNode last = path.get(path.size() - 1);
if (projectsTreeModel != null) {
while (path.size() > 1) {
projectsTreeModel.changeNode(new TreePath(path.toArray()));
path.remove(path.size()-1);
}
}
ATContentStudio.frame.editorChanged(last);
}
@Override
public void childrenRemoved(List<ProjectTreeNode> path) {
path.add(0, this);
if (projectsTreeModel != null) projectsTreeModel.removeNode(new TreePath(path.toArray()));
if (projectsTreeModel != null)
projectsTreeModel.removeNode(new TreePath(path.toArray()));
}
@Override
public void notifyCreated() {
childrenAdded(new ArrayList<ProjectTreeNode>());
for (ProjectTreeNode node : projects) {
if (node != null) node.notifyCreated();
if (node != null)
node.notifyCreated();
}
}
@Override
public String getDesc() {
return "Workspace: "+baseFolder.getAbsolutePath();
return "Workspace: " + baseFolder.getAbsolutePath();
}
public static void createProject(final String projectName, final File gameSourceFolder, final Project.ResourceSet sourceSet) {
WorkerDialog.showTaskMessage("Creating project "+projectName+"...", ATContentStudio.frame, new Runnable() {
@Override
public void run() {
if (activeWorkspace.projectsName.contains(projectName)) {
Notification.addError("A project named "+projectName+" already exists in this workspace.");
return;
}
Project p = new Project(activeWorkspace, projectName, gameSourceFolder, sourceSet);
activeWorkspace.projects.add(p);
activeWorkspace.projectsName.add(projectName);
activeWorkspace.projectsOpenByName.put(projectName, p.open);
activeWorkspace.knownMapSourcesFolders.add(gameSourceFolder);
p.notifyCreated();
Notification.addSuccess("Project "+projectName+" successfully created");
saveActive();
}
});
public static void createProject(final String projectName,
final File gameSourceFolder, final Project.ResourceSet sourceSet) {
WorkerDialog.showTaskMessage("Creating project " + projectName + "...",
ATContentStudio.frame, new Runnable() {
@Override
public void run() {
if (activeWorkspace.projectsName.contains(projectName)) {
Notification.addError("A project named "
+ projectName
+ " already exists in this workspace.");
return;
}
Project p = new Project(activeWorkspace, projectName,
gameSourceFolder, sourceSet);
activeWorkspace.projects.add(p);
activeWorkspace.projectsName.add(projectName);
activeWorkspace.projectsOpenByName.put(projectName,
p.open);
activeWorkspace.knownMapSourcesFolders
.add(gameSourceFolder);
p.notifyCreated();
Notification.addSuccess("Project " + projectName
+ " successfully created");
saveActive();
}
});
}
public static void closeProject(Project p) {
int index = activeWorkspace.projects.indexOf(p);
if (index < 0) {
Notification.addError("Cannot close unknown project "+p.name);
Notification.addError("Cannot close unknown project " + p.name);
return;
}
p.close();
@@ -179,28 +214,33 @@ public class Workspace implements ProjectTreeNode, Serializable {
cp.notifyCreated();
saveActive();
}
public static void openProject(final ClosedProject cp) {
WorkerDialog.showTaskMessage("Opening project "+cp.name+"...", ATContentStudio.frame, new Runnable() {
@Override
public void run() {
int index = activeWorkspace.projects.indexOf(cp);
if (index < 0) {
Notification.addError("Cannot open unknown project "+cp.name);
return;
}
cp.childrenRemoved(new ArrayList<ProjectTreeNode>());
Project p = Project.fromFolder(activeWorkspace, new File(activeWorkspace.baseFolder, cp.name));
p.open();
activeWorkspace.projects.set(index, p);
activeWorkspace.projectsOpenByName.put(p.name, true);
p.notifyCreated();
saveActive();
}
});
WorkerDialog.showTaskMessage("Opening project " + cp.name + "...",
ATContentStudio.frame, new Runnable() {
@Override
public void run() {
int index = activeWorkspace.projects.indexOf(cp);
if (index < 0) {
Notification
.addError("Cannot open unknown project "
+ cp.name);
return;
}
cp.childrenRemoved(new ArrayList<ProjectTreeNode>());
Project p = Project.fromFolder(activeWorkspace,
new File(activeWorkspace.baseFolder, cp.name));
p.open();
activeWorkspace.projects.set(index, p);
activeWorkspace.projectsOpenByName.put(p.name, true);
p.notifyCreated();
saveActive();
}
});
}
public void refreshTransients() {
this.settings = new WorkspaceSettings(this);
this.projects = new ArrayList<ProjectTreeNode>();
Set<String> projectsFailed = new HashSet<String>();
for (String projectName : projectsName) {
@@ -211,11 +251,16 @@ public class Workspace implements ProjectTreeNode, Serializable {
if (p != null) {
projects.add(p);
} else {
Notification.addError("Failed to open project "+projectName+". Removing it from workspace (not from filesystem though).");
Notification
.addError("Failed to open project "
+ projectName
+ ". Removing it from workspace (not from filesystem though).");
projectsFailed.add(projectName);
}
} else {
Notification.addError("Unable to find project "+projectName+"'s root folder. Removing it from workspace");
Notification.addError("Unable to find project "
+ projectName
+ "'s root folder. Removing it from workspace");
projectsFailed.add(projectName);
}
} else {
@@ -228,21 +273,31 @@ public class Workspace implements ProjectTreeNode, Serializable {
}
notifyCreated();
}
@Override
public Project getProject() {
return null;
}
@Override
public Image getIcon() {return null;}
@Override
public Image getClosedIcon() {return null;}
@Override
public Image getLeafIcon() {return null;}
@Override
public Image getOpenIcon() {return null;}
@Override
public Image getIcon() {
return null;
}
@Override
public Image getClosedIcon() {
return null;
}
@Override
public Image getLeafIcon() {
return null;
}
@Override
public Image getOpenIcon() {
return null;
}
public static void deleteProject(ClosedProject cp) {
cp.childrenRemoved(new ArrayList<ProjectTreeNode>());
@@ -250,37 +305,43 @@ public class Workspace implements ProjectTreeNode, Serializable {
activeWorkspace.projectsOpenByName.remove(cp.name);
activeWorkspace.projectsName.remove(cp.name);
if (delete(new File(activeWorkspace.baseFolder, cp.name))) {
Notification.addSuccess("Closed project "+cp.name+" successfully deleted.");
Notification.addSuccess("Closed project " + cp.name
+ " successfully deleted.");
} else {
Notification.addError("Error while deleting closed project "+cp.name+". Files may remain in the workspace.");
Notification.addError("Error while deleting closed project "
+ cp.name + ". Files may remain in the workspace.");
}
cp = null;
saveActive();
}
public static void deleteProject(Project p) {
p.childrenRemoved(new ArrayList<ProjectTreeNode>());
activeWorkspace.projects.remove(p);
activeWorkspace.projectsOpenByName.remove(p.name);
activeWorkspace.projectsName.remove(p.name);
if (delete(p.baseFolder)) {
Notification.addSuccess("Project "+p.name+" successfully deleted.");
Notification.addSuccess("Project " + p.name
+ " successfully deleted.");
} else {
Notification.addError("Error while deleting project "+p.name+". Files may remain in the workspace.");
Notification.addError("Error while deleting project " + p.name
+ ". Files may remain in the workspace.");
}
p = null;
saveActive();
}
private static boolean delete(File f) {
boolean b = true;
if (f.isDirectory()) {
if (Files.isSymbolicLink(f.toPath())) {
b &= f.delete();
} else if (f.isDirectory()) {
for (File c : f.listFiles())
b &= delete(c);
}
return b&= f.delete();
return b &= f.delete();
}
@Override
public GameDataSet getDataSet() {
return null;
@@ -290,11 +351,19 @@ public class Workspace implements ProjectTreeNode, Serializable {
public Type getDataType() {
return null;
}
@Override
public boolean isEmpty() {
return projects.isEmpty();
}
@Override
public boolean needsSaving() {
for (ProjectTreeNode node : projects) {
if (node.needsSaving()) return true;
}
return false;
}
}

View File

@@ -0,0 +1,220 @@
package com.gpl.rpg.atcontentstudio.model;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.io.JsonPrettyWriter;
public class WorkspaceSettings {
public static final String VERSION_KEY = "ATCS_Version";
public static final String FILENAME = "workspace_settings.json";
public static final int SETTINGS_VERSION = 1;
public Workspace parent;
public File file;
public static Boolean DEFAULT_USE_SYS_MAP_EDITOR = true;
public Setting<Boolean> useSystemDefaultMapEditor = new PrimitiveSetting<Boolean>("useSystemDefaultMapEditor", DEFAULT_USE_SYS_MAP_EDITOR);
public static String DEFAULT_MAP_EDITOR_COMMAND = "tiled";
public Setting<String> mapEditorCommand = new PrimitiveSetting<String>("mapEditorCommand", DEFAULT_MAP_EDITOR_COMMAND);
public static Boolean DEFAULT_USE_SYS_IMG_VIEWER = true;
public Setting<Boolean> useSystemDefaultImageViewer = new PrimitiveSetting<Boolean>("useSystemDefaultImageViewer", DEFAULT_USE_SYS_IMG_VIEWER);
public static Boolean DEFAULT_USE_SYS_IMG_EDITOR = false;
public Setting<Boolean> useSystemDefaultImageEditor = new PrimitiveSetting<Boolean>("useSystemDefaultImageEditor", DEFAULT_USE_SYS_IMG_EDITOR);
public static String DEFAULT_IMG_EDITOR_COMMAND = "gimp";
public Setting<String> imageEditorCommand = new PrimitiveSetting<String>("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 Setting<String> translatorLanguage = new NullDefaultPrimitiveSetting<String>("translatorLanguage");
public static Boolean DEFAULT_ALLOW_INTERNET = true;
public Setting<Boolean> useInternet = new PrimitiveSetting<Boolean>("useInternet", DEFAULT_ALLOW_INTERNET);
public static Boolean DEFAULT_CHECK_UPDATE = true;
public Setting<Boolean> checkUpdates = new PrimitiveSetting<Boolean>("checkUpdates", DEFAULT_CHECK_UPDATE);
public List<Setting<? extends Object>> settings = new ArrayList<Setting<? extends Object>>();
public WorkspaceSettings(Workspace parent) {
this.parent = parent;
settings.add(useSystemDefaultMapEditor);
settings.add(mapEditorCommand);
settings.add(useSystemDefaultImageViewer);
settings.add(useSystemDefaultImageEditor);
settings.add(imageEditorCommand);
settings.add(translatorLanguage);
settings.add(useInternet);
settings.add(checkUpdates);
file = new File(parent.baseFolder, FILENAME);
if (file.exists()) {
load(file);
}
}
public void load(File f) {
JSONParser parser = new JSONParser();
FileReader reader = null;
try {
reader = new FileReader(f);
@SuppressWarnings("rawtypes")
Map jsonSettings = (Map) parser.parse(reader);
Integer version = ((Number) jsonSettings.get(VERSION_KEY)).intValue();
if (version != null) {
if (version >= 1) {
loadv1(jsonSettings);
}
}
} catch (Exception e) {
Notification.addError("Error while parsing workspace settings: "+e.getMessage());
e.printStackTrace();
} finally {
if (reader != null)
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@SuppressWarnings("rawtypes")
private void loadv1(Map jsonSettings) {
for (Setting s : settings) {
s.readFromJson(jsonSettings);
}
}
@SuppressWarnings("unchecked")
public void save() {
@SuppressWarnings("rawtypes")
Map json = new LinkedHashMap();
for (Setting<? extends Object> s : settings) {
s.saveToJson(json);
}
if (json.isEmpty()) {
//Everything is default.
file.delete();
return;
}
json.put(VERSION_KEY, SETTINGS_VERSION);
StringWriter writer = new JsonPrettyWriter();
try {
JSONObject.writeJSONString(json, writer);
} catch (IOException e) {
//Impossible with a StringWriter
}
String toWrite = writer.toString();
try {
FileWriter w = new FileWriter(file);
w.write(toWrite);
w.close();
Notification.addSuccess("Workspace settings saved.");
} catch (IOException e) {
Notification.addError("Error while saving workspace settings : "+e.getMessage());
e.printStackTrace();
}
}
public void resetDefault() {
for (Setting<? extends Object> s : settings) {
s.resetDefault();
}
}
public abstract class Setting<X extends Object> {
public String id;
public X value, defaultValue;
public void setCurrentValue(X value) {
this.value = value;
}
public X getCurrentValue() {
return value;
}
public X getDefaultValue() {
return defaultValue;
}
public void resetDefault() {
value = defaultValue;
}
public abstract void readFromJson(@SuppressWarnings("rawtypes") Map json);
@SuppressWarnings({ "rawtypes", "unchecked" })
public void saveToJson(Map json) {
if (!defaultValue.equals(value)) json.put(id, value);
}
}
public class PrimitiveSetting<X extends Object> extends Setting<X> {
public PrimitiveSetting(String id, X defaultValue) {
this.id = id;
this.value = this.defaultValue = defaultValue;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public void readFromJson(Map json) {
if (json.get(id) != null) value = (X)json.get(id);
}
}
public class NullDefaultPrimitiveSetting<X extends Object> extends PrimitiveSetting<X> {
public NullDefaultPrimitiveSetting(String id) {
super(id, null);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public void saveToJson(Map json) {
if (value != null) json.put(id, value);
}
}
public class ListSetting<X extends Object> extends Setting<List<X>> {
public ListSetting(String id, List<X> defaultValue) {
this.id = id;
this.value = this.defaultValue = defaultValue;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public void readFromJson(Map json) {
value = new ArrayList<X>();
if (json.get(id) != null) {
for (Object o : ((List)json.get(id))) {
value.add((X)o);
}
}
}
}
}

View File

@@ -5,7 +5,7 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -20,6 +20,10 @@ import com.gpl.rpg.atcontentstudio.model.GameSource;
public class ActorCondition extends JSONElement {
private static final long serialVersionUID = -3969824899972048507L;
public static final Integer MAGNITUDE_CLEAR = -99;
public static final Integer DURATION_FOREVER = 999;;
public static final Integer DURATION_NONE = 0;
// Available from init state
//public String id; inherited.
@@ -41,9 +45,16 @@ public class ActorCondition extends JSONElement {
blood
}
public static enum VisualEffectID {
redSplash
,blueSwirl
,greenSplash
,miss
}
public static class RoundEffect implements Cloneable {
// Available from parsed state
public String visual_effect = null;
public VisualEffectID visual_effect = null;
public Integer hp_boost_min = null;
public Integer hp_boost_max = null;
public Integer ap_boost_min = null;
@@ -86,7 +97,7 @@ public class ActorCondition extends JSONElement {
@Override
public String getDesc() {
return (this.state == State.modified ? "*" : "")+display_name+" ("+id+")";
return (needsSaving() ? "*" : "")+display_name+" ("+id+")";
}
@SuppressWarnings("rawtypes")
@@ -147,7 +158,7 @@ public class ActorCondition extends JSONElement {
public void parse(Map aCondJson) {
if (aCondJson.get("category") != null) this.category = ACCategory.valueOf((String) aCondJson.get("category"));
this.positive = JSONElement.getInteger((Number) aCondJson.get("positive"));
this.positive = JSONElement.getInteger((Number) aCondJson.get("isPositive"));
Map abilityEffect = (Map) aCondJson.get("abilityEffect");
if (abilityEffect != null) {
this.constant_ability_effect = new AbilityEffect();
@@ -178,7 +189,13 @@ public class ActorCondition extends JSONElement {
this.round_effect.ap_boost_max = JSONElement.getInteger((Number) (((Map)roundEffect.get("increaseCurrentAP")).get("max")));
this.round_effect.ap_boost_min = JSONElement.getInteger((Number) (((Map)roundEffect.get("increaseCurrentAP")).get("min")));
}
this.round_effect.visual_effect = (String) roundEffect.get("visualEffectID");
String vfx = (String) roundEffect.get("visualEffectID");
this.round_effect.visual_effect = null;
if (vfx != null) {
try {
this.round_effect.visual_effect = VisualEffectID.valueOf(vfx);
} catch(IllegalArgumentException e) {}
}
}
Map fullRoundEffect = (Map) aCondJson.get("fullRoundEffect");
if (fullRoundEffect != null) {
@@ -191,7 +208,13 @@ 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")));
}
this.full_round_effect.visual_effect = (String) fullRoundEffect.get("visualEffectID");
String vfx = (String) fullRoundEffect.get("visualEffectID");
this.full_round_effect.visual_effect = null;
if (vfx != null) {
try {
this.full_round_effect.visual_effect = VisualEffectID.valueOf(vfx);
} catch(IllegalArgumentException e) {}
}
}
this.state = State.parsed;
@@ -212,6 +235,9 @@ public class ActorCondition extends JSONElement {
}
if (this.icon_id != null) {
String spritesheetId = this.icon_id.split(":")[0];
if (getProject().getSpritesheet(spritesheetId) == null) {
System.out.println(this.id);
}
getProject().getSpritesheet(spritesheetId).addBacklink(this);
}
@@ -264,18 +290,18 @@ public class ActorCondition extends JSONElement {
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public Map toJson() {
Map jsonAC = new HashMap();
Map jsonAC = new LinkedHashMap();
jsonAC.put("id", this.id);
if (this.icon_id != null) jsonAC.put("iconID", this.icon_id);
if (this.display_name != null) jsonAC.put("name", this.display_name);
if (this.category != null) jsonAC.put("category", this.category.toString());
if (this.positive != null && this.positive == 1) jsonAC.put("positive", this.positive);
if (this.stacking != null && this.stacking == 1) jsonAC.put("stacking", this.stacking);
if (this.positive != null && this.positive == 1) jsonAC.put("isPositive", this.positive);
if (this.stacking != null && this.stacking == 1) jsonAC.put("isStacking", this.stacking);
if (this.round_effect != null) {
Map jsonRound = new HashMap();
if (this.round_effect.visual_effect != null) jsonRound.put("visualEffectID", this.round_effect.visual_effect);
Map jsonRound = new LinkedHashMap();
if (this.round_effect.visual_effect != null) jsonRound.put("visualEffectID", this.round_effect.visual_effect.toString());
if (this.round_effect.hp_boost_min != null || this.round_effect.hp_boost_max != null) {
Map jsonHP = new HashMap();
Map jsonHP = new LinkedHashMap();
if (this.round_effect.hp_boost_min != null) jsonHP.put("min", this.round_effect.hp_boost_min);
else jsonHP.put("min", 0);
if (this.round_effect.hp_boost_max != null) jsonHP.put("max", this.round_effect.hp_boost_max);
@@ -283,7 +309,7 @@ public class ActorCondition extends JSONElement {
jsonRound.put("increaseCurrentHP", jsonHP);
}
if (this.round_effect.ap_boost_min != null || this.round_effect.ap_boost_max != null) {
Map jsonAP = new HashMap();
Map jsonAP = new LinkedHashMap();
if (this.round_effect.ap_boost_min != null) jsonAP.put("min", this.round_effect.ap_boost_min);
else jsonAP.put("min", 0);
if (this.round_effect.ap_boost_max != null) jsonAP.put("max", this.round_effect.ap_boost_max);
@@ -293,10 +319,10 @@ public class ActorCondition extends JSONElement {
jsonAC.put("roundEffect", jsonRound);
}
if (this.full_round_effect != null) {
Map jsonFullRound = new HashMap();
if (this.full_round_effect.visual_effect != null) jsonFullRound.put("visualEffectID", this.full_round_effect.visual_effect);
Map jsonFullRound = new LinkedHashMap();
if (this.full_round_effect.visual_effect != null) jsonFullRound.put("visualEffectID", this.full_round_effect.visual_effect.toString());
if (this.full_round_effect.hp_boost_min != null || this.full_round_effect.hp_boost_max != null) {
Map jsonHP = new HashMap();
Map jsonHP = new LinkedHashMap();
if (this.full_round_effect.hp_boost_min != null) jsonHP.put("min", this.full_round_effect.hp_boost_min);
else jsonHP.put("min", 0);
if (this.full_round_effect.hp_boost_max != null) jsonHP.put("max", this.full_round_effect.hp_boost_max);
@@ -304,7 +330,7 @@ public class ActorCondition extends JSONElement {
jsonFullRound.put("increaseCurrentHP", jsonHP);
}
if (this.full_round_effect.ap_boost_min != null || this.full_round_effect.ap_boost_max != null) {
Map jsonAP = new HashMap();
Map jsonAP = new LinkedHashMap();
if (this.full_round_effect.ap_boost_min != null) jsonAP.put("min", this.full_round_effect.ap_boost_min);
else jsonAP.put("min", 0);
if (this.full_round_effect.ap_boost_max != null) jsonAP.put("max", this.full_round_effect.ap_boost_max);
@@ -314,10 +340,10 @@ public class ActorCondition extends JSONElement {
jsonAC.put("fullRoundEffect", jsonFullRound);
}
if (this.constant_ability_effect != null) {
Map jsonAbility = new HashMap();
Map jsonAbility = new LinkedHashMap();
if (this.constant_ability_effect.increase_attack_chance != null) jsonAbility.put("increaseAttackChance", this.constant_ability_effect.increase_attack_chance);
if (this.constant_ability_effect.increase_damage_min != null || this.constant_ability_effect.increase_damage_max != null) {
Map jsonAD = new HashMap();
Map jsonAD = new LinkedHashMap();
if (this.constant_ability_effect.increase_damage_min != null) jsonAD.put("min", this.constant_ability_effect.increase_damage_min);
else jsonAD.put("min", 0);
if (this.constant_ability_effect.increase_damage_max != null) jsonAD.put("max", this.constant_ability_effect.increase_damage_max);

View File

@@ -7,7 +7,7 @@ import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -54,17 +54,21 @@ public class Dialogue extends JSONElement {
public enum RewardType {
questProgress,
removeQuestProgress,
dropList,
skillIncrease,
actorCondition,
actorConditionImmunity,
alignmentChange,
alignmentSet,
giveItem,
createTimer,
spawnAll,
removeSpawnArea,
deactivateSpawnArea,
activateMapChangeArea,
deactivateMapChangeArea
activateMapObjectGroup,
deactivateMapObjectGroup,
changeMapFilter
}
}
@@ -90,7 +94,7 @@ public class Dialogue extends JSONElement {
@Override
public String getDesc() {
return (this.state == State.modified ? "*" : "")+id;
return (needsSaving() ? "*" : "")+id;
}
public static String getStaticDesc() {
@@ -223,10 +227,7 @@ public class Dialogue extends JSONElement {
if (replies != null) {
for (Reply reply : replies) {
if (reply.next_phrase_id != null) {
if (!reply.next_phrase_id.equals(Reply.EXIT_PHRASE_ID)
&& !reply.next_phrase_id.equals(Reply.FIGHT_PHRASE_ID)
&& !reply.next_phrase_id.equals(Reply.SHOP_PHRASE_ID)
&& !reply.next_phrase_id.equals(Reply.REMOVE_PHRASE_ID)) {
if (!Reply.KEY_PHRASE_ID.contains(reply.next_phrase_id)) {
reply.next_phrase = proj.getDialogue(reply.next_phrase_id);
}
}
@@ -242,18 +243,21 @@ public class Dialogue extends JSONElement {
for (Reward reward : rewards) {
if (reward.reward_obj_id != null) {
switch (reward.type) {
case activateMapChangeArea:
case deactivateMapChangeArea:
case activateMapObjectGroup:
case deactivateMapObjectGroup:
case spawnAll:
case removeSpawnArea:
case deactivateSpawnArea:
reward.map = proj.getMap(reward.map_name);
case changeMapFilter:
reward.map = reward.map_name != null ? proj.getMap(reward.map_name) : null;
break;
case actorCondition:
case actorConditionImmunity:
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.
@@ -265,7 +269,14 @@ public class Dialogue extends JSONElement {
reward.reward_obj = proj.getItem(reward.reward_obj_id);
break;
case questProgress:
case removeQuestProgress:
reward.reward_obj = proj.getQuest(reward.reward_obj_id);
if (reward.reward_obj != null && reward.reward_value != null) {
QuestStage stage = ((Quest)reward.reward_obj).getStage(reward.reward_value);
if (stage != null) {
stage.addBacklink(this);
}
}
break;
case skillIncrease:
//Nothing to do (yet ?).
@@ -315,6 +326,11 @@ public class Dialogue extends JSONElement {
if (rclone.reward_obj != null) {
rclone.reward_obj.addBacklink(clone);
}
rclone.map = r.map;
rclone.map_name = r.map_name;
if (rclone.map != null) {
rclone.map.addBacklink(clone);
}
clone.rewards.add(rclone);
}
}
@@ -344,21 +360,20 @@ public class Dialogue extends JSONElement {
@Override
public void elementChanged(GameDataElement oldOne, GameDataElement newOne) {
if (switch_to_npc == oldOne) {
oldOne.removeBacklink(this);
switch_to_npc = (NPC) newOne;
if (newOne != null) newOne.addBacklink(this);
} else {
if (replies != null) {
for (Reply r : replies) {
if (r.next_phrase == oldOne) {
oldOne.removeBacklink(this);
r.next_phrase = (Dialogue) newOne;
if (newOne != null) newOne.addBacklink(this);
}
if (r.requirements != null) {
for (Requirement req : r.requirements) {
if (req.required_obj == oldOne) {
req.required_obj = newOne;
if (newOne != null) newOne.addBacklink(this);
}
req.elementChanged(oldOne, newOne);
}
}
}
@@ -366,9 +381,16 @@ public class Dialogue extends JSONElement {
if (rewards != null) {
for (Reward r : rewards) {
if (r.reward_obj == oldOne) {
oldOne.removeBacklink(this);
r.reward_obj = newOne;
if (newOne != null) newOne.addBacklink(this);
}
if (oldOne instanceof QuestStage) {
if (r.reward_obj != null && r.reward_obj.equals(oldOne.parent) && r.reward_value != null && r.reward_value.equals(((QuestStage) oldOne).progress)) {
oldOne.removeBacklink((GameDataElement) this);
if (newOne != null) newOne.addBacklink((GameDataElement) this);
}
}
}
}
}
@@ -377,7 +399,7 @@ public class Dialogue extends JSONElement {
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public Map toJson() {
Map dialogueJson = new HashMap();
Map dialogueJson = new LinkedHashMap();
dialogueJson.put("id", this.id);
if (this.message != null) dialogueJson.put("message", this.message);
if (this.switch_to_npc != null) {
@@ -389,7 +411,7 @@ public class Dialogue extends JSONElement {
List repliesJson = new ArrayList();
dialogueJson.put("replies", repliesJson);
for (Reply reply : this.replies){
Map replyJson = new HashMap();
Map replyJson = new LinkedHashMap();
repliesJson.add(replyJson);
if (reply.text != null) replyJson.put("text", reply.text);
if (reply.next_phrase != null) {
@@ -401,7 +423,7 @@ public class Dialogue extends JSONElement {
List requirementsJson = new ArrayList();
replyJson.put("requires", requirementsJson);
for (Requirement requirement : reply.requirements) {
Map requirementJson = new HashMap();
Map requirementJson = new LinkedHashMap();
requirementsJson.add(requirementJson);
if (requirement.type != null) requirementJson.put("requireType", requirement.type.toString());
if (requirement.required_obj != null) {
@@ -421,7 +443,7 @@ public class Dialogue extends JSONElement {
List rewardsJson = new ArrayList();
dialogueJson.put("rewards", rewardsJson);
for (Reward reward : this.rewards) {
Map rewardJson = new HashMap();
Map rewardJson = new LinkedHashMap();
rewardsJson.add(rewardJson);
if (reward.type != null) rewardJson.put("rewardType", reward.type.toString());
if (reward.reward_obj != null) {

View File

@@ -6,7 +6,7 @@ import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -46,7 +46,7 @@ public class Droplist extends JSONElement {
@Override
public String getDesc() {
return (this.state == State.modified ? "*" : "")+id;
return (needsSaving() ? "*" : "")+id;
}
public static String getStaticDesc() {
@@ -193,6 +193,7 @@ public class Droplist extends JSONElement {
if (dropped_items != null) {
for (DroppedItem di : dropped_items) {
if (di.item == oldOne) {
oldOne.removeBacklink(this);
di.item = (Item) newOne;
if (newOne != null) newOne.addBacklink(this);
}
@@ -203,13 +204,13 @@ public class Droplist extends JSONElement {
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public Map toJson() {
Map droplistJson = new HashMap();
Map droplistJson = new LinkedHashMap();
droplistJson.put("id", this.id);
if (this.dropped_items != null) {
List droppedItemsJson = new ArrayList();
droplistJson.put("items", droppedItemsJson);
for (DroppedItem droppedItem : this.dropped_items) {
Map droppedItemJson = new HashMap();
Map droppedItemJson = new LinkedHashMap();
droppedItemsJson.add(droppedItemJson);
if (droppedItem.item != null) {
droppedItemJson.put("itemID", droppedItem.item.id);
@@ -218,7 +219,7 @@ public class Droplist extends JSONElement {
}
if (droppedItem.chance != null) droppedItemJson.put("chance", JSONElement.printJsonChance(droppedItem.chance));
if (droppedItem.quantity_min != null || droppedItem.quantity_max != null) {
Map quantityJson = new HashMap();
Map quantityJson = new LinkedHashMap();
droppedItemJson.put("quantity", quantityJson);
if (droppedItem.quantity_min != null) quantityJson.put("min", droppedItem.quantity_min);
else quantityJson.put("min", 0);

View File

@@ -9,7 +9,7 @@ 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.List;
import java.util.Map;
@@ -102,7 +102,7 @@ public class GameDataCategory<E extends JSONElement> extends ArrayList<E> implem
}
@Override
public String getDesc() {
return this.name;
return (needsSaving() ? "*" : "")+this.name;
}
@Override
@@ -154,7 +154,7 @@ public class GameDataCategory<E extends JSONElement> extends ArrayList<E> implem
dataToSave.add(element.toJson());
}
}
if (dataToSave.isEmpty()) {
if (dataToSave.isEmpty() && jsonFile.exists()) {
if (jsonFile.delete()) {
Notification.addSuccess("File "+jsonFile.getAbsolutePath()+" deleted.");
} else {
@@ -190,7 +190,7 @@ public class GameDataCategory<E extends JSONElement> extends ArrayList<E> implem
List<SaveEvent> events = new ArrayList<SaveEvent>();
GameDataCategory<? extends JSONElement> impactedCategory = null;
String impactedFileName = fileName;
Map<String, Integer> containedIds = new HashMap<String, Integer>();
Map<String, Integer> containedIds = new LinkedHashMap<String, Integer>();
for (JSONElement node : this) {
if (node.getDataType() == GameSource.Type.created && getProject().baseContent.gameData.getGameDataElement(node.getClass(), node.id) != null) {
if (getProject().alteredContent.gameData.getGameDataElement(node.getClass(), node.id) != null) {
@@ -208,7 +208,7 @@ public class GameDataCategory<E extends JSONElement> extends ArrayList<E> implem
impactedCategory = getProject().createdContent.gameData.getCategory(node.getClass());
impactedFileName = node.getProjectFilename();
}
} else if (node.state == GameDataElement.State.modified) {
} else if (node.needsSaving()) {
events.add(new SaveEvent(SaveEvent.Type.alsoSave, node));
}
if (containedIds.containsKey(node.id)) {
@@ -242,5 +242,12 @@ public class GameDataCategory<E extends JSONElement> extends ArrayList<E> implem
return result;
}
@Override
public boolean needsSaving() {
for (E node : this) {
if (node.needsSaving()) return true;
}
return false;
}
}

View File

@@ -236,7 +236,7 @@ public class GameDataSet implements ProjectTreeNode, Serializable {
}
@Override
public String getDesc() {
return "JSON data";
return (needsSaving() ? "*" : "")+"JSON data";
}
@@ -463,4 +463,11 @@ public class GameDataSet implements ProjectTreeNode, Serializable {
return null;
}
@Override
public boolean needsSaving() {
for (ProjectTreeNode node : v.getNonEmptyIterable()) {
if (node.needsSaving()) return true;
}
return false;
}
}

View File

@@ -6,7 +6,7 @@ import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -34,6 +34,7 @@ public class Item extends JSONElement {
public String category_id = null;
public String description = null;
public HitEffect hit_effect = null;
public HitReceivedEffect hit_received_effect = null;
public KillEffect kill_effect = null;
public EquipEffect equip_effect = null;
@@ -58,6 +59,14 @@ public class Item extends JSONElement {
public List<TimedConditionEffect> conditions_target = null;
}
public static class HitReceivedEffect extends HitEffect {
//Available from parsed state
public Integer hp_boost_min_target = null;
public Integer hp_boost_max_target = null;
public Integer ap_boost_min_target = null;
public Integer ap_boost_max_target = null;
}
public static class EquipEffect {
//Available from parsed state
public Integer damage_boost_min = null;
@@ -101,7 +110,7 @@ public class Item extends JSONElement {
@Override
public String getDesc() {
return (this.state == State.modified ? "*" : "")+name+" ("+id+")";
return (needsSaving() ? "*" : "")+name+" ("+id+")";
}
public static String getStaticDesc() {
@@ -246,6 +255,53 @@ public class Item extends JSONElement {
}
}
Map hitReceivedEffect = (Map) itemJson.get("hitReceivedEffect");
if (hitReceivedEffect != null) {
this.hit_received_effect = new HitReceivedEffect();
if (hitReceivedEffect.get("increaseCurrentHP") != null) {
this.hit_received_effect.hp_boost_min = JSONElement.getInteger((Number) (((Map)hitReceivedEffect.get("increaseCurrentHP")).get("min")));
this.hit_received_effect.hp_boost_max = JSONElement.getInteger((Number) (((Map)hitReceivedEffect.get("increaseCurrentHP")).get("max")));
}
if (hitReceivedEffect.get("increaseCurrentAP") != null) {
this.hit_received_effect.ap_boost_min = JSONElement.getInteger((Number) (((Map)hitReceivedEffect.get("increaseCurrentAP")).get("min")));
this.hit_received_effect.ap_boost_max = JSONElement.getInteger((Number) (((Map)hitReceivedEffect.get("increaseCurrentAP")).get("max")));
}
if (hitReceivedEffect.get("increaseAttackerCurrentHP") != null) {
this.hit_received_effect.hp_boost_min_target = JSONElement.getInteger((Number) (((Map)hitReceivedEffect.get("increaseAttackerCurrentHP")).get("min")));
this.hit_received_effect.hp_boost_max_target = JSONElement.getInteger((Number) (((Map)hitReceivedEffect.get("increaseAttackerCurrentHP")).get("max")));
}
if (hitReceivedEffect.get("increaseAttackerCurrentAP") != null) {
this.hit_received_effect.ap_boost_min_target = JSONElement.getInteger((Number) (((Map)hitReceivedEffect.get("increaseAttackerCurrentAP")).get("min")));
this.hit_received_effect.ap_boost_max_target = JSONElement.getInteger((Number) (((Map)hitReceivedEffect.get("increaseAttackerCurrentAP")).get("max")));
}
List conditionsSourceJson = (List) hitReceivedEffect.get("conditionsSource");
if (conditionsSourceJson != null && !conditionsSourceJson.isEmpty()) {
this.hit_received_effect.conditions_source = new ArrayList<Item.TimedConditionEffect>();
for (Object conditionJsonObj : conditionsSourceJson) {
Map conditionJson = (Map)conditionJsonObj;
TimedConditionEffect condition = new TimedConditionEffect();
condition.condition_id = (String) conditionJson.get("condition");
condition.magnitude = JSONElement.getInteger((Number) conditionJson.get("magnitude"));
condition.duration = JSONElement.getInteger((Number) conditionJson.get("duration"));
if (conditionJson.get("chance") != null) condition.chance = JSONElement.parseChance(conditionJson.get("chance").toString());
this.hit_received_effect.conditions_source.add(condition);
}
}
List conditionsTargetJson = (List) hitReceivedEffect.get("conditionsTarget");
if (conditionsTargetJson != null && !conditionsTargetJson.isEmpty()) {
this.hit_received_effect.conditions_target = new ArrayList<Item.TimedConditionEffect>();
for (Object conditionJsonObj : conditionsTargetJson) {
Map conditionJson = (Map)conditionJsonObj;
TimedConditionEffect condition = new TimedConditionEffect();
condition.condition_id = (String) conditionJson.get("condition");
condition.magnitude = JSONElement.getInteger((Number) conditionJson.get("magnitude"));
condition.duration = JSONElement.getInteger((Number) conditionJson.get("duration"));
if (conditionJson.get("chance") != null) condition.chance = JSONElement.parseChance(conditionJson.get("chance").toString());
this.hit_received_effect.conditions_target.add(condition);
}
}
}
Map killEffect = (Map) itemJson.get("killEffect");
if (killEffect == null) {
killEffect = (Map) itemJson.get("useEffect");
@@ -321,6 +377,18 @@ public class Item extends JSONElement {
if (ce.condition != null) ce.condition.addBacklink(this);
}
}
if (this.hit_received_effect != null && this.hit_received_effect.conditions_source != null) {
for (TimedConditionEffect ce : this.hit_received_effect.conditions_source) {
if (ce.condition_id != null) ce.condition = proj.getActorCondition(ce.condition_id);
if (ce.condition != null) ce.condition.addBacklink(this);
}
}
if (this.hit_received_effect != null && this.hit_received_effect.conditions_target != null) {
for (TimedConditionEffect ce : this.hit_received_effect.conditions_target) {
if (ce.condition_id != null) ce.condition = proj.getActorCondition(ce.condition_id);
if (ce.condition != null) ce.condition.addBacklink(this);
}
}
if (this.kill_effect != null && this.kill_effect.conditions_source != null) {
for (TimedConditionEffect ce : this.kill_effect.conditions_source) {
if (ce.condition_id != null) ce.condition = proj.getActorCondition(ce.condition_id);
@@ -422,6 +490,47 @@ public class Item extends JSONElement {
}
}
}
if (this.hit_received_effect != null) {
clone.hit_received_effect = new HitReceivedEffect();
clone.hit_received_effect.ap_boost_max = this.hit_received_effect.ap_boost_max;
clone.hit_received_effect.ap_boost_min = this.hit_received_effect.ap_boost_min;
clone.hit_received_effect.hp_boost_max = this.hit_received_effect.hp_boost_max;
clone.hit_received_effect.hp_boost_min = this.hit_received_effect.hp_boost_min;
clone.hit_received_effect.ap_boost_max_target = this.hit_received_effect.ap_boost_max_target;
clone.hit_received_effect.ap_boost_min_target = this.hit_received_effect.ap_boost_min_target;
clone.hit_received_effect.hp_boost_max_target = this.hit_received_effect.hp_boost_max_target;
clone.hit_received_effect.hp_boost_min_target = this.hit_received_effect.hp_boost_min_target;
if (this.hit_received_effect.conditions_source != null) {
clone.hit_received_effect.conditions_source = new ArrayList<Item.TimedConditionEffect>();
for (TimedConditionEffect c : this.hit_received_effect.conditions_source) {
TimedConditionEffect cclone = new TimedConditionEffect();
cclone.magnitude = c.magnitude;
cclone.condition_id = c.condition_id;
cclone.condition = c.condition;
cclone.chance = c.chance;
cclone.duration = c.duration;
if (cclone.condition != null) {
cclone.condition.addBacklink(clone);
}
clone.hit_received_effect.conditions_source.add(cclone);
}
}
if (this.hit_received_effect.conditions_target != null) {
clone.hit_received_effect.conditions_target = new ArrayList<Item.TimedConditionEffect>();
for (TimedConditionEffect c : this.hit_received_effect.conditions_target) {
TimedConditionEffect cclone = new TimedConditionEffect();
cclone.magnitude = c.magnitude;
cclone.condition_id = c.condition_id;
cclone.condition = c.condition;
cclone.chance = c.chance;
cclone.duration = c.duration;
if (cclone.condition != null) {
cclone.condition.addBacklink(clone);
}
clone.hit_received_effect.conditions_target.add(cclone);
}
}
}
if (this.kill_effect != null) {
clone.kill_effect = new KillEffect();
clone.kill_effect.ap_boost_max = this.kill_effect.ap_boost_max;
@@ -450,12 +559,14 @@ public class Item extends JSONElement {
@Override
public void elementChanged(GameDataElement oldOne, GameDataElement newOne) {
if (this.category == oldOne) {
oldOne.removeBacklink(this);
this.category = (ItemCategory) newOne;
if (newOne != null) newOne.addBacklink(this);
} else {
if (this.equip_effect != null && this.equip_effect.conditions != null) {
for (ConditionEffect c : this.equip_effect.conditions) {
if (c.condition == oldOne) {
oldOne.removeBacklink(this);
c.condition = (ActorCondition) newOne;
if (newOne != null) newOne.addBacklink(this);
}
@@ -464,6 +575,7 @@ public class Item extends JSONElement {
if (this.hit_effect != null && this.hit_effect.conditions_source != null) {
for (TimedConditionEffect c : this.hit_effect.conditions_source) {
if (c.condition == oldOne) {
oldOne.removeBacklink(this);
c.condition = (ActorCondition) newOne;
if (newOne != null) newOne.addBacklink(this);
}
@@ -472,6 +584,7 @@ public class Item extends JSONElement {
if (this.hit_effect != null && this.hit_effect.conditions_target != null) {
for (TimedConditionEffect c : this.hit_effect.conditions_target) {
if (c.condition == oldOne) {
oldOne.removeBacklink(this);
c.condition = (ActorCondition) newOne;
if (newOne != null) newOne.addBacklink(this);
}
@@ -481,6 +594,7 @@ public class Item extends JSONElement {
if (this.kill_effect != null && this.kill_effect.conditions_source != null) {
for (TimedConditionEffect c : this.kill_effect.conditions_source) {
if (c.condition == oldOne) {
oldOne.removeBacklink(this);
c.condition = (ActorCondition) newOne;
if (newOne != null) newOne.addBacklink(this);
}
@@ -492,10 +606,12 @@ public class Item extends JSONElement {
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public Map toJson() {
Map itemJson = new HashMap();
Map itemJson = new LinkedHashMap();
itemJson.put("id", this.id);
if (this.icon_id != null) itemJson.put("iconID", this.icon_id);
if (this.name != null) itemJson.put("name", this.name);
if(this.display_type != null) itemJson.put("displaytype", this.display_type.toString());
if (this.has_manual_price != null) itemJson.put("hasManualPrice", this.has_manual_price);
if (this.base_market_cost != null) itemJson.put("baseMarketCost", this.base_market_cost);
if (this.category != null) {
@@ -505,10 +621,10 @@ public class Item extends JSONElement {
}
if (this.description != null) itemJson.put("description", this.description);
if (this.equip_effect != null) {
Map equipEffectJson = new HashMap();
Map equipEffectJson = new LinkedHashMap();
itemJson.put("equipEffect", equipEffectJson);
if (this.equip_effect.damage_boost_min != null || this.equip_effect.damage_boost_max != null) {
Map damageJson = new HashMap();
Map damageJson = new LinkedHashMap();
equipEffectJson.put("increaseAttackDamage", damageJson);
if (this.equip_effect.damage_boost_min != null) damageJson.put("min", this.equip_effect.damage_boost_min);
else damageJson.put("min", 0);
@@ -530,7 +646,7 @@ public class Item extends JSONElement {
List conditionsJson = new ArrayList();
equipEffectJson.put("addedConditions", conditionsJson);
for (ConditionEffect condition : this.equip_effect.conditions) {
Map conditionJson = new HashMap();
Map conditionJson = new LinkedHashMap();
conditionsJson.add(conditionJson);
if (condition.condition != null) {
conditionJson.put("condition", condition.condition.id);
@@ -542,10 +658,10 @@ public class Item extends JSONElement {
}
}
if (this.hit_effect != null) {
Map hitEffectJson = new HashMap();
Map hitEffectJson = new LinkedHashMap();
itemJson.put("hitEffect", hitEffectJson);
if (this.hit_effect.hp_boost_min != null || this.hit_effect.hp_boost_max != null) {
Map hpJson = new HashMap();
Map hpJson = new LinkedHashMap();
hitEffectJson.put("increaseCurrentHP", hpJson);
if (this.hit_effect.hp_boost_min != null) hpJson.put("min", this.hit_effect.hp_boost_min);
else hpJson.put("min", 0);
@@ -553,7 +669,7 @@ public class Item extends JSONElement {
else hpJson.put("max", 0);
}
if (this.hit_effect.ap_boost_min != null || this.hit_effect.ap_boost_max != null) {
Map apJson = new HashMap();
Map apJson = new LinkedHashMap();
hitEffectJson.put("increaseCurrentAP", apJson);
if (this.hit_effect.ap_boost_min != null) apJson.put("min", this.hit_effect.ap_boost_min);
else apJson.put("min", 0);
@@ -564,7 +680,7 @@ public class Item extends JSONElement {
List conditionsSourceJson = new ArrayList();
hitEffectJson.put("conditionsSource", conditionsSourceJson);
for (TimedConditionEffect condition : this.hit_effect.conditions_source) {
Map conditionJson = new HashMap();
Map conditionJson = new LinkedHashMap();
conditionsSourceJson.add(conditionJson);
if (condition.condition != null) {
conditionJson.put("condition", condition.condition.id);
@@ -580,7 +696,75 @@ public class Item extends JSONElement {
List conditionsTargetJson = new ArrayList();
hitEffectJson.put("conditionsTarget", conditionsTargetJson);
for (TimedConditionEffect condition : this.hit_effect.conditions_target) {
Map conditionJson = new HashMap();
Map conditionJson = new LinkedHashMap();
conditionsTargetJson.add(conditionJson);
if (condition.condition != null) {
conditionJson.put("condition", condition.condition.id);
} else if (condition.condition_id != null) {
conditionJson.put("condition", condition.condition_id);
}
if (condition.magnitude != null) conditionJson.put("magnitude", condition.magnitude);
if (condition.duration != null) conditionJson.put("duration", condition.duration);
if (condition.chance != null) conditionJson.put("chance", JSONElement.printJsonChance(condition.chance));
}
}
}
if (this.hit_received_effect != null) {
Map hitReceivedEffectJson = new LinkedHashMap();
itemJson.put("hitReceivedEffect", hitReceivedEffectJson);
if (this.hit_received_effect.hp_boost_min != null || this.hit_received_effect.hp_boost_max != null) {
Map hpJson = new LinkedHashMap();
hitReceivedEffectJson.put("increaseCurrentHP", hpJson);
if (this.hit_received_effect.hp_boost_min != null) hpJson.put("min", this.hit_received_effect.hp_boost_min);
else hpJson.put("min", 0);
if (this.hit_received_effect.hp_boost_max != null) hpJson.put("max", this.hit_received_effect.hp_boost_max);
else hpJson.put("max", 0);
}
if (this.hit_received_effect.ap_boost_min != null || this.hit_received_effect.ap_boost_max != null) {
Map apJson = new LinkedHashMap();
hitReceivedEffectJson.put("increaseCurrentAP", apJson);
if (this.hit_received_effect.ap_boost_min != null) apJson.put("min", this.hit_received_effect.ap_boost_min);
else apJson.put("min", 0);
if (this.hit_received_effect.ap_boost_max != null) apJson.put("max", this.hit_received_effect.ap_boost_max);
else apJson.put("max", 0);
}
if (this.hit_received_effect.hp_boost_min_target != null || this.hit_received_effect.hp_boost_max_target != null) {
Map hpJson = new LinkedHashMap();
hitReceivedEffectJson.put("increaseAttackerCurrentHP", hpJson);
if (this.hit_received_effect.hp_boost_min_target != null) hpJson.put("min", this.hit_received_effect.hp_boost_min_target);
else hpJson.put("min", 0);
if (this.hit_received_effect.hp_boost_max_target != null) hpJson.put("max", this.hit_received_effect.hp_boost_max_target);
else hpJson.put("max", 0);
}
if (this.hit_received_effect.ap_boost_min_target != null || this.hit_received_effect.ap_boost_max_target != null) {
Map apJson = new LinkedHashMap();
hitReceivedEffectJson.put("increaseAttackerCurrentAP", apJson);
if (this.hit_received_effect.ap_boost_min_target != null) apJson.put("min", this.hit_received_effect.ap_boost_min_target);
else apJson.put("min", 0);
if (this.hit_received_effect.ap_boost_max_target != null) apJson.put("max", this.hit_received_effect.ap_boost_max_target);
else apJson.put("max", 0);
}
if (this.hit_received_effect.conditions_source != null) {
List conditionsSourceJson = new ArrayList();
hitReceivedEffectJson.put("conditionsSource", conditionsSourceJson);
for (TimedConditionEffect condition : this.hit_received_effect.conditions_source) {
Map conditionJson = new LinkedHashMap();
conditionsSourceJson.add(conditionJson);
if (condition.condition != null) {
conditionJson.put("condition", condition.condition.id);
} else if (condition.condition_id != null) {
conditionJson.put("condition", condition.condition_id);
}
if (condition.magnitude != null) conditionJson.put("magnitude", condition.magnitude);
if (condition.duration != null) conditionJson.put("duration", condition.duration);
if (condition.chance != null) conditionJson.put("chance", JSONElement.printJsonChance(condition.chance));
}
}
if (this.hit_received_effect.conditions_target != null) {
List conditionsTargetJson = new ArrayList();
hitReceivedEffectJson.put("conditionsTarget", conditionsTargetJson);
for (TimedConditionEffect condition : this.hit_received_effect.conditions_target) {
Map conditionJson = new LinkedHashMap();
conditionsTargetJson.add(conditionJson);
if (condition.condition != null) {
conditionJson.put("condition", condition.condition.id);
@@ -594,14 +778,14 @@ public class Item extends JSONElement {
}
}
if (this.kill_effect != null) {
Map killEffectJson = new HashMap();
Map killEffectJson = new LinkedHashMap();
if (this.category != null && this.category.action_type != null && this.category.action_type == ItemCategory.ActionType.equip) {
itemJson.put("killEffect", killEffectJson);
} else if (this.category != null && this.category.action_type != null && this.category.action_type == ItemCategory.ActionType.use) {
itemJson.put("useEffect", killEffectJson);
}
if (this.kill_effect.hp_boost_min != null || this.kill_effect.hp_boost_max != null) {
Map hpJson = new HashMap();
Map hpJson = new LinkedHashMap();
killEffectJson.put("increaseCurrentHP", hpJson);
if (this.kill_effect.hp_boost_min != null) hpJson.put("min", this.kill_effect.hp_boost_min);
else hpJson.put("min", 0);
@@ -609,7 +793,7 @@ public class Item extends JSONElement {
else hpJson.put("min", 0);
}
if (this.kill_effect.ap_boost_min != null || this.kill_effect.ap_boost_max != null) {
Map apJson = new HashMap();
Map apJson = new LinkedHashMap();
killEffectJson.put("increaseCurrentAP", apJson);
if (this.kill_effect.ap_boost_min != null) apJson.put("min", this.kill_effect.ap_boost_min);
else apJson.put("min", 0);
@@ -620,7 +804,7 @@ public class Item extends JSONElement {
List conditionsSourceJson = new ArrayList();
killEffectJson.put("conditionsSource", conditionsSourceJson);
for (TimedConditionEffect condition : this.kill_effect.conditions_source) {
Map conditionJson = new HashMap();
Map conditionJson = new LinkedHashMap();
conditionsSourceJson.add(conditionJson);
if (condition.condition != null) {
conditionJson.put("condition", condition.condition.id);

View File

@@ -5,7 +5,7 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -99,7 +99,7 @@ public class ItemCategory extends JSONElement {
@Override
public String getDesc() {
return (this.state == State.modified ? "*" : "")+name+" ("+id+")";
return (needsSaving() ? "*" : "")+name+" ("+id+")";
}
public static String getStaticDesc() {
@@ -310,7 +310,7 @@ public class ItemCategory extends JSONElement {
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public Map toJson() {
Map itemCatJson = new HashMap();
Map itemCatJson = new LinkedHashMap();
itemCatJson.put("id", this.id);
if (this.name != null) itemCatJson.put("name", this.name);
if (this.action_type != null) itemCatJson.put("actionType", this.action_type.toString());

View File

@@ -6,7 +6,7 @@ import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -47,6 +47,8 @@ public class NPC extends JSONElement {
public Integer block_chance = null;
public Integer damage_resistance = null;
public HitEffect hit_effect = null;
public HitReceivedEffect hit_received_effect = null;
public DeathEffect death_effect = null;
//Available from linked state
public Dialogue dialogue = null;
@@ -70,17 +72,29 @@ public class NPC extends JSONElement {
protectSpawn,
wholeMap
}
public static class HitEffect {
public static class DeathEffect {
//Available from parsed state
public Integer hp_boost_min = null;
public Integer hp_boost_max = null;
public Integer ap_boost_min = null;
public Integer ap_boost_max = null;
public List<TimedConditionEffect> conditions_source = null;
}
public static class HitEffect extends DeathEffect {
//Available from parsed state
public List<TimedConditionEffect> conditions_target = null;
}
public static class HitReceivedEffect extends HitEffect {
//Available from parsed state
public Integer hp_boost_min_target = null;
public Integer hp_boost_max_target = null;
public Integer ap_boost_min_target = null;
public Integer ap_boost_max_target = null;
}
public static class TimedConditionEffect {
//Available from parsed state
public Integer magnitude = null;
@@ -95,7 +109,7 @@ public class NPC extends JSONElement {
@Override
public String getDesc() {
return (this.state == State.modified ? "*" : "")+name+" ("+id+")";
return (needsSaving() ? "*" : "")+name+" ("+id+")";
}
public static String getStaticDesc() {
@@ -224,6 +238,79 @@ public class NPC extends JSONElement {
}
}
Map hitReceivedEffect = (Map) npcJson.get("hitReceivedEffect");
if (hitReceivedEffect != null) {
this.hit_received_effect = new HitReceivedEffect();
if (hitReceivedEffect.get("increaseCurrentHP") != null) {
this.hit_received_effect.hp_boost_max = JSONElement.getInteger((Number) (((Map)hitReceivedEffect.get("increaseCurrentHP")).get("max")));
this.hit_received_effect.hp_boost_min = JSONElement.getInteger((Number) (((Map)hitReceivedEffect.get("increaseCurrentHP")).get("min")));
}
if (hitReceivedEffect.get("increaseCurrentAP") != null) {
this.hit_received_effect.ap_boost_max = JSONElement.getInteger((Number) (((Map)hitReceivedEffect.get("increaseCurrentAP")).get("max")));
this.hit_received_effect.ap_boost_min = JSONElement.getInteger((Number) (((Map)hitReceivedEffect.get("increaseCurrentAP")).get("min")));
}
if (hitReceivedEffect.get("increaseAttackerCurrentHP") != null) {
this.hit_received_effect.hp_boost_max_target = JSONElement.getInteger((Number) (((Map)hitReceivedEffect.get("increaseAttackerCurrentHP")).get("max")));
this.hit_received_effect.hp_boost_min_target = JSONElement.getInteger((Number) (((Map)hitReceivedEffect.get("increaseAttackerCurrentHP")).get("min")));
}
if (hitReceivedEffect.get("increaseAttackerCurrentAP") != null) {
this.hit_received_effect.ap_boost_max_target = JSONElement.getInteger((Number) (((Map)hitReceivedEffect.get("increaseAttackerCurrentAP")).get("max")));
this.hit_received_effect.ap_boost_min_target = JSONElement.getInteger((Number) (((Map)hitReceivedEffect.get("increaseAttackerCurrentAP")).get("min")));
}
List conditionsSourceJson = (List) hitReceivedEffect.get("conditionsSource");
if (conditionsSourceJson != null && !conditionsSourceJson.isEmpty()) {
this.hit_received_effect.conditions_source = new ArrayList<NPC.TimedConditionEffect>();
for (Object conditionJsonObj : conditionsSourceJson) {
Map conditionJson = (Map)conditionJsonObj;
TimedConditionEffect condition = new TimedConditionEffect();
condition.condition_id = (String) conditionJson.get("condition");
condition.magnitude = JSONElement.getInteger((Number) conditionJson.get("magnitude"));
condition.duration = JSONElement.getInteger((Number) conditionJson.get("duration"));
if (conditionJson.get("chance") != null) condition.chance = JSONElement.parseChance(conditionJson.get("chance").toString());
this.hit_received_effect.conditions_source.add(condition);
}
}
List conditionsTargetJson = (List) hitReceivedEffect.get("conditionsTarget");
if (conditionsTargetJson != null && !conditionsTargetJson.isEmpty()) {
this.hit_received_effect.conditions_target = new ArrayList<NPC.TimedConditionEffect>();
for (Object conditionJsonObj : conditionsTargetJson) {
Map conditionJson = (Map)conditionJsonObj;
TimedConditionEffect condition = new TimedConditionEffect();
condition.condition_id = (String) conditionJson.get("condition");
condition.magnitude = JSONElement.getInteger((Number) conditionJson.get("magnitude"));
condition.duration = JSONElement.getInteger((Number) conditionJson.get("duration"));
if (conditionJson.get("chance") != null) condition.chance = JSONElement.parseChance(conditionJson.get("chance").toString());
this.hit_received_effect.conditions_target.add(condition);
}
}
}
Map deathEffect = (Map) npcJson.get("deathEffect");
if (deathEffect != null) {
this.death_effect = new HitEffect();
if (deathEffect.get("increaseCurrentHP") != null) {
this.death_effect.hp_boost_max = JSONElement.getInteger((Number) (((Map)deathEffect.get("increaseCurrentHP")).get("max")));
this.death_effect.hp_boost_min = JSONElement.getInteger((Number) (((Map)deathEffect.get("increaseCurrentHP")).get("min")));
}
if (deathEffect.get("increaseCurrentAP") != null) {
this.death_effect.ap_boost_max = JSONElement.getInteger((Number) (((Map)deathEffect.get("increaseCurrentAP")).get("max")));
this.death_effect.ap_boost_min = JSONElement.getInteger((Number) (((Map)deathEffect.get("increaseCurrentAP")).get("min")));
}
List conditionsSourceJson = (List) deathEffect.get("conditionsSource");
if (conditionsSourceJson != null && !conditionsSourceJson.isEmpty()) {
this.death_effect.conditions_source = new ArrayList<NPC.TimedConditionEffect>();
for (Object conditionJsonObj : conditionsSourceJson) {
Map conditionJson = (Map)conditionJsonObj;
TimedConditionEffect condition = new TimedConditionEffect();
condition.condition_id = (String) conditionJson.get("condition");
condition.magnitude = JSONElement.getInteger((Number) conditionJson.get("magnitude"));
condition.duration = JSONElement.getInteger((Number) conditionJson.get("duration"));
if (conditionJson.get("chance") != null) condition.chance = JSONElement.parseChance(conditionJson.get("chance").toString());
this.death_effect.conditions_source.add(condition);
}
}
}
}
@Override
@@ -267,6 +354,24 @@ public class NPC extends JSONElement {
if (ce.condition != null) ce.condition.addBacklink(this);
}
}
if (this.hit_received_effect != null && this.hit_received_effect.conditions_source != null) {
for (TimedConditionEffect ce : this.hit_received_effect.conditions_source) {
if (ce.condition_id != null) ce.condition = proj.getActorCondition(ce.condition_id);
if (ce.condition != null) ce.condition.addBacklink(this);
}
}
if (this.hit_received_effect != null && this.hit_received_effect.conditions_target != null) {
for (TimedConditionEffect ce : this.hit_received_effect.conditions_target) {
if (ce.condition_id != null) ce.condition = proj.getActorCondition(ce.condition_id);
if (ce.condition != null) ce.condition.addBacklink(this);
}
}
if (this.death_effect != null && this.death_effect.conditions_source != null) {
for (TimedConditionEffect ce : this.death_effect.conditions_source) {
if (ce.condition_id != null) ce.condition = proj.getActorCondition(ce.condition_id);
if (ce.condition != null) ce.condition.addBacklink(this);
}
}
this.state = State.linked;
}
@@ -343,6 +448,69 @@ public class NPC extends JSONElement {
}
}
}
if (this.hit_received_effect != null) {
clone.hit_received_effect = new HitReceivedEffect();
clone.hit_received_effect.ap_boost_max = this.hit_received_effect.ap_boost_max;
clone.hit_received_effect.ap_boost_min = this.hit_received_effect.ap_boost_min;
clone.hit_received_effect.hp_boost_max = this.hit_received_effect.hp_boost_max;
clone.hit_received_effect.hp_boost_min = this.hit_received_effect.hp_boost_min;
clone.hit_received_effect.ap_boost_max_target = this.hit_received_effect.ap_boost_max_target;
clone.hit_received_effect.ap_boost_min_target = this.hit_received_effect.ap_boost_min_target;
clone.hit_received_effect.hp_boost_max_target = this.hit_received_effect.hp_boost_max_target;
clone.hit_received_effect.hp_boost_min_target = this.hit_received_effect.hp_boost_min_target;
if (this.hit_received_effect.conditions_source != null) {
clone.hit_received_effect.conditions_source = new ArrayList<TimedConditionEffect>();
for (TimedConditionEffect c : this.hit_received_effect.conditions_source) {
TimedConditionEffect cclone = new TimedConditionEffect();
cclone.magnitude = c.magnitude;
cclone.condition_id = c.condition_id;
cclone.condition = c.condition;
cclone.chance = c.chance;
cclone.duration = c.duration;
if (cclone.condition != null) {
cclone.condition.addBacklink(clone);
}
clone.hit_received_effect.conditions_source.add(cclone);
}
}
if (this.hit_received_effect.conditions_target != null) {
clone.hit_received_effect.conditions_target = new ArrayList<TimedConditionEffect>();
for (TimedConditionEffect c : this.hit_received_effect.conditions_target) {
TimedConditionEffect cclone = new TimedConditionEffect();
cclone.magnitude = c.magnitude;
cclone.condition_id = c.condition_id;
cclone.condition = c.condition;
cclone.chance = c.chance;
cclone.duration = c.duration;
if (cclone.condition != null) {
cclone.condition.addBacklink(clone);
}
clone.hit_received_effect.conditions_target.add(cclone);
}
}
}
if (this.death_effect != null) {
clone.death_effect = new DeathEffect();
clone.death_effect.ap_boost_max = this.death_effect.ap_boost_max;
clone.death_effect.ap_boost_min = this.death_effect.ap_boost_min;
clone.death_effect.hp_boost_max = this.death_effect.hp_boost_max;
clone.death_effect.hp_boost_min = this.death_effect.hp_boost_min;
if (this.death_effect.conditions_source != null) {
clone.death_effect.conditions_source = new ArrayList<TimedConditionEffect>();
for (TimedConditionEffect c : this.death_effect.conditions_source) {
TimedConditionEffect cclone = new TimedConditionEffect();
cclone.magnitude = c.magnitude;
cclone.condition_id = c.condition_id;
cclone.condition = c.condition;
cclone.chance = c.chance;
cclone.duration = c.duration;
if (cclone.condition != null) {
cclone.condition.addBacklink(clone);
}
clone.death_effect.conditions_source.add(cclone);
}
}
}
clone.max_ap = this.max_ap;
clone.max_hp = this.max_hp;
clone.monster_class = this.monster_class;
@@ -356,16 +524,19 @@ public class NPC extends JSONElement {
@Override
public void elementChanged(GameDataElement oldOne, GameDataElement newOne) {
if (dialogue == oldOne) {
oldOne.removeBacklink(this);
this.dialogue = (Dialogue) newOne;
if (newOne != null) newOne.addBacklink(this);
} else {
if (this.droplist == oldOne) {
oldOne.removeBacklink(this);
this.droplist = (Droplist) newOne;
if (newOne != null) newOne.addBacklink(this);
} else {
if (this.hit_effect != null && this.hit_effect.conditions_source != null) {
for (TimedConditionEffect tce : this.hit_effect.conditions_source) {
if (tce.condition == oldOne) {
oldOne.removeBacklink(this);
tce.condition = (ActorCondition) newOne;
if (newOne != null) newOne.addBacklink(this);
}
@@ -374,6 +545,7 @@ public class NPC extends JSONElement {
if (this.hit_effect != null && this.hit_effect.conditions_target != null) {
for (TimedConditionEffect tce : this.hit_effect.conditions_target) {
if (tce.condition == oldOne) {
oldOne.removeBacklink(this);
tce.condition = (ActorCondition) newOne;
if (newOne != null) newOne.addBacklink(this);
}
@@ -386,7 +558,7 @@ public class NPC extends JSONElement {
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public Map toJson() {
Map npcJson = new HashMap();
Map npcJson = new LinkedHashMap();
npcJson.put("id", this.id);
if (this.name != null) npcJson.put("name", this.name);
if (this.icon_id != null) npcJson.put("iconID", this.icon_id);
@@ -397,7 +569,7 @@ public class NPC extends JSONElement {
if (this.monster_class != null) npcJson.put("monsterClass", this.monster_class.toString());
if (this.movement_type != null) npcJson.put("movementAggressionType", this.movement_type.toString());
if (this.attack_damage_min != null || this.attack_damage_max != null) {
Map adJson = new HashMap();
Map adJson = new LinkedHashMap();
npcJson.put("attackDamage", adJson);
if (this.attack_damage_min != null) adJson.put("min", this.attack_damage_min);
else adJson.put("min", 0);
@@ -423,10 +595,10 @@ public class NPC extends JSONElement {
if (this.block_chance != null) npcJson.put("blockChance", this.block_chance);
if (this.damage_resistance != null) npcJson.put("damageResistance", this.damage_resistance);
if (this.hit_effect != null) {
Map hitEffectJson = new HashMap();
Map hitEffectJson = new LinkedHashMap();
npcJson.put("hitEffect", hitEffectJson);
if (this.hit_effect.hp_boost_min != null || this.hit_effect.hp_boost_max != null) {
Map hpJson = new HashMap();
Map hpJson = new LinkedHashMap();
hitEffectJson.put("increaseCurrentHP", hpJson);
if (this.hit_effect.hp_boost_min != null) hpJson.put("min", this.hit_effect.hp_boost_min);
else hpJson.put("min", 0);
@@ -434,7 +606,7 @@ public class NPC extends JSONElement {
else hpJson.put("max", 0);
}
if (this.hit_effect.ap_boost_min != null || this.hit_effect.ap_boost_max != null) {
Map apJson = new HashMap();
Map apJson = new LinkedHashMap();
hitEffectJson.put("increaseCurrentAP", apJson);
if (this.hit_effect.ap_boost_min != null) apJson.put("min", this.hit_effect.ap_boost_min);
else apJson.put("min", 0);
@@ -445,7 +617,7 @@ public class NPC extends JSONElement {
List conditionsSourceJson = new ArrayList();
hitEffectJson.put("conditionsSource", conditionsSourceJson);
for (TimedConditionEffect condition : this.hit_effect.conditions_source) {
Map conditionJson = new HashMap();
Map conditionJson = new LinkedHashMap();
conditionsSourceJson.add(conditionJson);
if (condition.condition != null) {
conditionJson.put("condition", condition.condition.id);
@@ -461,7 +633,7 @@ public class NPC extends JSONElement {
List conditionsTargetJson = new ArrayList();
hitEffectJson.put("conditionsTarget", conditionsTargetJson);
for (TimedConditionEffect condition : this.hit_effect.conditions_target) {
Map conditionJson = new HashMap();
Map conditionJson = new LinkedHashMap();
conditionsTargetJson.add(conditionJson);
if (condition.condition != null) {
conditionJson.put("condition", condition.condition.id);
@@ -474,6 +646,110 @@ public class NPC extends JSONElement {
}
}
}
if (this.hit_received_effect != null) {
Map hitReceivedEffectJson = new LinkedHashMap();
npcJson.put("hitReceivedEffect", hitReceivedEffectJson);
if (this.hit_received_effect.hp_boost_min != null || this.hit_received_effect.hp_boost_max != null) {
Map hpJson = new LinkedHashMap();
hitReceivedEffectJson.put("increaseCurrentHP", hpJson);
if (this.hit_received_effect.hp_boost_min != null) hpJson.put("min", this.hit_received_effect.hp_boost_min);
else hpJson.put("min", 0);
if (this.hit_received_effect.hp_boost_max != null) hpJson.put("max", this.hit_received_effect.hp_boost_max);
else hpJson.put("max", 0);
}
if (this.hit_received_effect.ap_boost_min != null || this.hit_received_effect.ap_boost_max != null) {
Map apJson = new LinkedHashMap();
hitReceivedEffectJson.put("increaseCurrentAP", apJson);
if (this.hit_received_effect.ap_boost_min != null) apJson.put("min", this.hit_received_effect.ap_boost_min);
else apJson.put("min", 0);
if (this.hit_received_effect.ap_boost_max != null) apJson.put("max", this.hit_received_effect.ap_boost_max);
else apJson.put("max", 0);
}
if (this.hit_received_effect.hp_boost_min_target != null || this.hit_received_effect.hp_boost_max_target != null) {
Map hpJson = new LinkedHashMap();
hitReceivedEffectJson.put("increaseAttackerCurrentHP", hpJson);
if (this.hit_received_effect.hp_boost_min_target != null) hpJson.put("min", this.hit_received_effect.hp_boost_min_target);
else hpJson.put("min", 0);
if (this.hit_received_effect.hp_boost_max_target != null) hpJson.put("max", this.hit_received_effect.hp_boost_max_target);
else hpJson.put("max", 0);
}
if (this.hit_received_effect.ap_boost_min_target != null || this.hit_received_effect.ap_boost_max_target != null) {
Map apJson = new LinkedHashMap();
hitReceivedEffectJson.put("increaseAttackerCurrentAP", apJson);
if (this.hit_received_effect.ap_boost_min_target != null) apJson.put("min", this.hit_received_effect.ap_boost_min_target);
else apJson.put("min", 0);
if (this.hit_received_effect.ap_boost_max_target != null) apJson.put("max", this.hit_received_effect.ap_boost_max_target);
else apJson.put("max", 0);
}
if (this.hit_received_effect.conditions_source != null) {
List conditionsSourceJson = new ArrayList();
hitReceivedEffectJson.put("conditionsSource", conditionsSourceJson);
for (TimedConditionEffect condition : this.hit_received_effect.conditions_source) {
Map conditionJson = new LinkedHashMap();
conditionsSourceJson.add(conditionJson);
if (condition.condition != null) {
conditionJson.put("condition", condition.condition.id);
} else if (condition.condition_id != null) {
conditionJson.put("condition", condition.condition_id);
}
if (condition.magnitude != null) conditionJson.put("magnitude", condition.magnitude);
if (condition.duration != null) conditionJson.put("duration", condition.duration);
if (condition.chance != null) conditionJson.put("chance", JSONElement.printJsonChance(condition.chance));
}
}
if (this.hit_received_effect.conditions_target != null) {
List conditionsTargetJson = new ArrayList();
hitReceivedEffectJson.put("conditionsTarget", conditionsTargetJson);
for (TimedConditionEffect condition : this.hit_received_effect.conditions_target) {
Map conditionJson = new LinkedHashMap();
conditionsTargetJson.add(conditionJson);
if (condition.condition != null) {
conditionJson.put("condition", condition.condition.id);
} else if (condition.condition_id != null) {
conditionJson.put("condition", condition.condition_id);
}
if (condition.magnitude != null) conditionJson.put("magnitude", condition.magnitude);
if (condition.duration != null) conditionJson.put("duration", condition.duration);
if (condition.chance != null) conditionJson.put("chance", JSONElement.printJsonChance(condition.chance));
}
}
}
if (this.death_effect != null) {
Map deathEffectJson = new LinkedHashMap();
npcJson.put("deathEffect", deathEffectJson);
if (this.death_effect.hp_boost_min != null || this.death_effect.hp_boost_max != null) {
Map hpJson = new LinkedHashMap();
deathEffectJson.put("increaseCurrentHP", hpJson);
if (this.death_effect.hp_boost_min != null) hpJson.put("min", this.death_effect.hp_boost_min);
else hpJson.put("min", 0);
if (this.death_effect.hp_boost_max != null) hpJson.put("max", this.death_effect.hp_boost_max);
else hpJson.put("max", 0);
}
if (this.death_effect.ap_boost_min != null || this.death_effect.ap_boost_max != null) {
Map apJson = new LinkedHashMap();
deathEffectJson.put("increaseCurrentAP", apJson);
if (this.death_effect.ap_boost_min != null) apJson.put("min", this.death_effect.ap_boost_min);
else apJson.put("min", 0);
if (this.death_effect.ap_boost_max != null) apJson.put("max", this.death_effect.ap_boost_max);
else apJson.put("max", 0);
}
if (this.death_effect.conditions_source != null) {
List conditionsSourceJson = new ArrayList();
deathEffectJson.put("conditionsSource", conditionsSourceJson);
for (TimedConditionEffect condition : this.death_effect.conditions_source) {
Map conditionJson = new LinkedHashMap();
conditionsSourceJson.add(conditionJson);
if (condition.condition != null) {
conditionJson.put("condition", condition.condition.id);
} else if (condition.condition_id != null) {
conditionJson.put("condition", condition.condition_id);
}
if (condition.magnitude != null) conditionJson.put("magnitude", condition.magnitude);
if (condition.duration != null) conditionJson.put("duration", condition.duration);
if (condition.chance != null) conditionJson.put("chance", JSONElement.printJsonChance(condition.chance));
}
}
}
return npcJson;
}

View File

@@ -6,7 +6,7 @@ import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -30,25 +30,9 @@ public class Quest extends JSONElement {
public Integer visible_in_log = null;
public List<QuestStage> stages = null;
public static class QuestStage implements Cloneable {
public Integer progress = null;
public String log_text = null;
public Integer exp_reward = null;
public Integer finishes_quest = null;
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
@Override
public String getDesc() {
return (this.state == State.modified ? "*" : "")+name+" ("+id+")";
return (needsSaving() ? "*" : "")+name+" ("+id+")";
}
public static String getStaticDesc() {
@@ -105,6 +89,8 @@ public class Quest extends JSONElement {
Quest quest = new Quest();
quest.id = (String) questJson.get("id");
quest.name = (String) questJson.get("name");
//Quests have to be parsed to have their stages initialized.
quest.parse(questJson);
return quest;
}
@@ -113,19 +99,16 @@ public class Quest extends JSONElement {
public void parse(Map questJson) {
this.visible_in_log = JSONElement.getInteger((Number) questJson.get("showInLog"));
List questStagesJson = (List) questJson.get("stages");
this.stages = new ArrayList<QuestStage>();
if (questStagesJson != null && !questStagesJson.isEmpty()) {
this.stages = new ArrayList<QuestStage>();
for (Object questStageJsonObj : questStagesJson) {
Map questStageJson = (Map)questStageJsonObj;
QuestStage questStage = new QuestStage();
questStage.progress = JSONElement.getInteger((Number) questStageJson.get("progress"));
questStage.log_text = (String) questStageJson.get("logText");
questStage.exp_reward = JSONElement.getInteger((Number) questStageJson.get("rewardExperience"));
questStage.finishes_quest = JSONElement.getInteger((Number) questStageJson.get("finishesQuest"));
QuestStage questStage = new QuestStage(this);
questStage.parse(questStageJson);
this.stages.add(questStage);
}
}
this.state = State.parsed;
}
@Override
@@ -142,6 +125,9 @@ public class Quest extends JSONElement {
return;
}
for (QuestStage stage : stages) {
stage.link();
}
//Nothing to link to :D
this.state = State.linked;
}
@@ -164,9 +150,9 @@ public class Quest extends JSONElement {
clone.name = this.name;
clone.visible_in_log = this.visible_in_log;
if (this.stages != null) {
clone.stages = new ArrayList<Quest.QuestStage>();
clone.stages = new ArrayList<QuestStage>();
for (QuestStage stage : this.stages){
clone.stages.add((QuestStage) stage.clone());
clone.stages.add((QuestStage) stage.clone(clone));
}
}
return clone;
@@ -180,7 +166,7 @@ public class Quest extends JSONElement {
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public Map toJson() {
Map questJson = new HashMap();
Map questJson = new LinkedHashMap();
questJson.put("id", this.id);
if (this.name != null) questJson.put("name", this.name);
if (this.visible_in_log != null) questJson.put("showInLog", this.visible_in_log);
@@ -188,12 +174,7 @@ public class Quest extends JSONElement {
List stagesJson = new ArrayList();
questJson.put("stages", stagesJson);
for (QuestStage stage : this.stages) {
Map stageJson = new HashMap();
stagesJson.add(stageJson);
if (stage.progress != null) stageJson.put("progress", stage.progress);
if (stage.log_text != null) stageJson.put("logText", stage.log_text);
if (stage.exp_reward != null) stageJson.put("rewardExperience", stage.exp_reward);
if (stage.finishes_quest != null) stageJson.put("finishesQuest", stage.finishes_quest);
stagesJson.add(stage.toJson());
}
}
return questJson;
@@ -204,5 +185,14 @@ public class Quest extends JSONElement {
public String getProjectFilename() {
return "questlist_"+getProject().name+".json";
}
public QuestStage getStage(Integer stageId) {
for (QuestStage stage : stages) {
if (stage.progress.equals(stageId)) {
return stage;
}
}
return null;
}
}

View File

@@ -0,0 +1,103 @@
package com.gpl.rpg.atcontentstudio.model.gamedata;
import java.awt.Image;
import java.util.LinkedHashMap;
import java.util.Map;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.ui.DefaultIcons;
public class QuestStage extends JSONElement {
private static final long serialVersionUID = 8313645819951513431L;
public Integer progress = null;
public String log_text = null;
public Integer exp_reward = null;
public Integer finishes_quest = null;
public QuestStage(Quest parent){
this.parent = parent;
}
public QuestStage clone(Quest cloneParent) {
QuestStage clone = new QuestStage(cloneParent);
clone.progress = progress != null ? new Integer(progress) : null;
clone.log_text = log_text != null ? new String(log_text) : null;
clone.exp_reward = exp_reward != null ? new Integer(exp_reward) : null;
clone.finishes_quest = finishes_quest != null ? new Integer(finishes_quest) : null;
clone.id = this.id;
return clone;
}
@SuppressWarnings("rawtypes")
@Override
public void parse(Map jsonObj) {
progress = JSONElement.getInteger((Number) jsonObj.get("progress"));
this.id = ((Quest)parent).id+":"+progress;
log_text = (String) jsonObj.get("logText");
exp_reward = JSONElement.getInteger((Number) jsonObj.get("rewardExperience"));
finishes_quest = JSONElement.getInteger((Number) jsonObj.get("finishesQuest"));
state = State.parsed;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public Map toJson() {
Map stageJson = new LinkedHashMap();
if (progress != null) stageJson.put("progress", progress);
if (log_text != null) stageJson.put("logText", log_text);
if (exp_reward != null) stageJson.put("rewardExperience", exp_reward);
if (finishes_quest != null) stageJson.put("finishesQuest", finishes_quest);
return stageJson;
}
@Override
public String getDesc() {
return progress+" - "+(exp_reward != null ? "["+exp_reward+"XP]" : "")+((finishes_quest != null) && (finishes_quest == 1) ? "[END]" : "")+log_text;
}
@Override
public void link() {
if (this.state == State.created || this.state == State.modified || this.state == State.saved) {
//This type of state is unrelated to parsing/linking.
return;
}
if (this.state == State.init) {
//Not parsed yet.
this.parse();
} else if (this.state == State.linked) {
//Already linked.
return;
}
//Nothing to link to :D
this.state = State.linked;
}
@Override
public void elementChanged(GameDataElement oldOne, GameDataElement newOne) {
// Nothing to link to.
}
@Override
public String getProjectFilename() {
return ((Quest)parent).getProjectFilename();
}
@Override
public GameDataElement clone() {
return null;
}
@Override
public Image getIcon() {
return DefaultIcons.getQuestIcon();
}
public Image getImage() {
return DefaultIcons.getQuestImage();
}
}

View File

@@ -1,7 +1,7 @@
package com.gpl.rpg.atcontentstudio.model.gamedata;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -13,7 +13,7 @@ public class Requirement extends JSONElement {
private static final long serialVersionUID = 7295593297142310955L;
private static Map<RequirementType, List<RequirementType>> COMPATIBLE_TYPES = new HashMap<RequirementType, List<RequirementType>>();
private static Map<RequirementType, List<RequirementType>> COMPATIBLE_TYPES = new LinkedHashMap<RequirementType, List<RequirementType>>();
static {
List<RequirementType> questTypes = new ArrayList<RequirementType>();
@@ -53,12 +53,61 @@ public class Requirement extends JSONElement {
usedItem,
spentGold,
consumedBonemeals,
hasActorCondition
hasActorCondition,
factionScore
}
public enum SkillID {
weaponChance
,weaponDmg
,barter
,dodge
,barkSkin
,moreCriticals
,betterCriticals
,speed // Raises max ap
,coinfinder
,moreExp
,cleave // +10ap on kill
,eater // +1hp per kill
,fortitude // +N hp per levelup
,evasion // increase successful flee chance & reduce chance of monster attack
,regeneration // +N hp per round
,lowerExploss
,magicfinder
,resistanceMental // lowers chance to get negative active conditions by monsters (Mental like Dazed)
,resistancePhysical // lowers chance to get negative active conditions by monsters (Physical Capacity like Minor fatigue)
,resistanceBlood // lowers chance to get negative active conditions by monsters (Blood Disorder like Weak Poison)
,shadowBless
,crit1 // lowers atk ability
,crit2 // lowers def ability ,rejuvenation // Reduces magnitudes of conditions
,rejuvenation // Reduces magnitudes of conditions
,taunt // Causes AP loss of attackers that miss
,concussion // AC loss for monsters with (AC-BC)>N
,weaponProficiencyDagger
,weaponProficiency1hsword
,weaponProficiency2hsword
,weaponProficiencyAxe
,weaponProficiencyBlunt
,weaponProficiencyUnarmed
,armorProficiencyShield
,armorProficiencyUnarmored
,armorProficiencyLight
,armorProficiencyHeavy
,fightstyleDualWield
,fightstyle2hand
,fightstyleWeaponShield
,specializationDualWield
,specialization2hand
,specializationWeaponShield
}
@Override
public String getDesc() {
return ((negated != null && negated) ? "NOT " : "")+required_obj_id+(required_value == null ? "" : ":"+required_value.toString());
return ((negated != null && negated) ? "NOT " : "")
+(type == null ? "" : type.toString()+":")
+(required_obj_id == null ? "" : required_obj_id+":")
+(required_value == null ? "" : required_value.toString());
}
@Override
@@ -112,14 +161,22 @@ public class Requirement extends JSONElement {
case questLatestProgress:
case questProgress:
this.required_obj = proj.getQuest(required_obj_id);
if (this.required_obj != null && this.required_value != null) {
QuestStage stage = ((Quest)this.required_obj).getStage(this.required_value);
if (stage != null) {
stage.addBacklink((GameDataElement) this.parent);
}
}
break;
case consumedBonemeals:
case skillLevel:
case spentGold:
case timerElapsed:
case factionScore:
break;
}
if (this.required_obj != null) this.required_obj.addBacklink((GameDataElement) this.parent);
this.state = State.linked;
}
@Override
@@ -145,8 +202,15 @@ public class Requirement extends JSONElement {
@Override
public void elementChanged(GameDataElement oldOne, GameDataElement newOne) {
if (this.required_obj == oldOne) {
oldOne.removeBacklink((GameDataElement) this.parent);
this.required_obj = newOne;
if (newOne != null) newOne.addBacklink(this);
if (newOne != null) newOne.addBacklink((GameDataElement) this.parent);
}
if (oldOne instanceof QuestStage) {
if (this.required_obj != null && this.required_obj.equals(oldOne.parent) && this.required_value != null && this.required_value.equals(((QuestStage) oldOne).progress)) {
oldOne.removeBacklink((GameDataElement) this.parent);
if (newOne != null) newOne.addBacklink((GameDataElement) this.parent);
}
}
}
@Override

View File

@@ -29,8 +29,9 @@ public class ContainerArea extends MapObject {
@Override
public void elementChanged(GameDataElement oldOne, GameDataElement newOne) {
if (oldOne == droplist) {
oldOne.removeBacklink(parentMap);
droplist = (Droplist) newOne;
newOne.addBacklink(parentMap);
if (newOne != null) newOne.addBacklink(parentMap);
}
}

View File

@@ -19,23 +19,23 @@ public class KeyArea extends MapObject {
String requireType = obj.getProperties().getProperty("requireType");
String requireId = obj.getProperties().getProperty("requireId");
String requireValue = obj.getProperties().getProperty("requireValue");
if (requireId == null) {
oldSchoolRequirement = false;
if (requireType == null) {
String[] fields = obj.getName().split(":");
if (fields.length == 2) {
requireType = Requirement.RequirementType.questProgress.toString();
requireValue = fields[1];
requireId = fields[0];
oldSchoolRequirement = true;
} else if (fields.length == 3) {
requireValue = fields[2];
requireType = fields[0];
requireId = fields[1];
oldSchoolRequirement = true;
}
oldSchoolRequirement = true;
} else {
oldSchoolRequirement = false;
}
requirement = new Requirement();
requirement.type = Requirement.RequirementType.valueOf(requireType);
if (requireType != null) requirement.type = Requirement.RequirementType.valueOf(requireType);
requirement.required_obj_id = requireId;
if (requireValue != null) requirement.required_value = Integer.parseInt(requireValue);
requirement.state = GameDataElement.State.parsed;
@@ -60,8 +60,9 @@ public class KeyArea extends MapObject {
@Override
public void elementChanged(GameDataElement oldOne, GameDataElement newOne) {
if (oldOne == dialogue) {
oldOne.removeBacklink(parentMap);
dialogue = (Dialogue) newOne;
newOne.addBacklink(parentMap);
if (newOne != null) newOne.addBacklink(parentMap);
}
requirement.elementChanged(oldOne, newOne);
}
@@ -75,9 +76,11 @@ public class KeyArea extends MapObject {
}
if (requirement != null) {
if (oldSchoolRequirement && Requirement.RequirementType.questProgress.equals(requirement.type) && (requirement.negated == null || !requirement.negated)) {
tmxObject.setName(requirement.required_obj_id+":"+Integer.toString(requirement.required_value));
tmxObject.setName(requirement.required_obj_id+":"+((requirement.required_value == null) ? "" : Integer.toString(requirement.required_value)));
} else {
tmxObject.getProperties().setProperty("requireType", requirement.type.toString());
if (requirement.type != null) {
tmxObject.getProperties().setProperty("requireType", requirement.type.toString());
}
if (requirement.required_obj != null) {
tmxObject.getProperties().setProperty("requireId", requirement.required_obj.id);
} else if (requirement.required_obj_id != null) {
@@ -86,13 +89,16 @@ public class KeyArea extends MapObject {
if (requirement.required_value != null) {
tmxObject.getProperties().setProperty("requireValue", requirement.required_value.toString());
}
if (requirement.negated != null) {
tmxObject.getProperties().setProperty("requireNegation", Boolean.toString(requirement.negated));
}
}
}
}
public void updateNameFromRequirementChange() {
if (oldSchoolRequirement && Requirement.RequirementType.questProgress.equals(requirement.type) && (requirement.negated == null || !requirement.negated)) {
name = requirement.required_obj_id+":"+Integer.toString(requirement.required_value);
name = 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);

View File

@@ -37,8 +37,9 @@ public class MapChange extends MapObject {
@Override
public void elementChanged(GameDataElement oldOne, GameDataElement newOne) {
if (oldOne == map) {
oldOne.removeBacklink(parentMap);
map = (TMXMap) newOne;
newOne.addBacklink(parentMap);
if (newOne != null) newOne.addBacklink(parentMap);
}
}

View File

@@ -13,12 +13,18 @@ public class MapObjectGroup {
public String name;
public boolean visible;
public List<MapObject> mapObjects = new ArrayList<MapObject>();
public Boolean active;
public MapObjectGroup(tiled.core.ObjectGroup layer, TMXMap map) {
this.tmxGroup = layer;
this.name = layer.getName();
this.visible = layer.isVisible();
this.parentMap = map;
if (layer.getProperties().get("active") != null) {
active = new Boolean(((String) layer.getProperties().get("active")));
} else {
active = true;
}
for (tiled.core.MapObject obj : layer.getObjectsList()) {
mapObjects.add(MapObject.buildObject(obj, map));
}
@@ -44,6 +50,9 @@ public class MapObjectGroup {
}
tmxGroup.setVisible(visible);
tmxGroup.setName(name);
if (!active) {
tmxGroup.getProperties().put("active", Boolean.toString(active));
}
for (MapObject object : mapObjects) {
tmxGroup.addObject(object.toTmxObject());
}

View File

@@ -1,10 +1,8 @@
package com.gpl.rpg.atcontentstudio.model.maps;
import java.awt.Image;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.model.gamedata.Requirement;
@@ -14,40 +12,37 @@ import com.gpl.rpg.atcontentstudio.ui.DefaultIcons;
public class ReplaceArea extends MapObject {
public Requirement requirement = null;
public boolean oldSchoolRequirement = true;
public boolean oldSchoolRequirement = false;
public List<ReplaceArea.Replacement> replacements = null;
public ReplaceArea(tiled.core.MapObject obj) {
// String requireType = obj.getProperties().getProperty("requireType");
String requireType = obj.getProperties().getProperty("requireType");
String requireId = obj.getProperties().getProperty("requireId");
String requireValue = obj.getProperties().getProperty("requireValue");
if (requireId == null) {
if (requireType == null) {
String[] fields = obj.getName().split(":");
if (fields.length == 2) {
// requireType = Requirement.RequirementType.questProgress.toString();
requireType = Requirement.RequirementType.questProgress.toString();
requireValue = fields[1];
requireId = fields[0];
oldSchoolRequirement = true;
} /*else if (fields.length == 3) {
requireValue = fields[2];
requireType = fields[0];
requireId = fields[1];
}*/
oldSchoolRequirement = true;
} else {
oldSchoolRequirement = false;
}
requirement = new Requirement();
//Replace areas only support questProgress requirements ATM
//requirement.type = Requirement.RequirementType.valueOf(requireType);
requirement.type = Requirement.RequirementType.questProgress;
if (requireType != null) requirement.type = Requirement.RequirementType.valueOf(requireType);
requirement.required_obj_id = requireId;
if (requireValue != null) requirement.required_value = Integer.parseInt(requireValue);
requirement.state = GameDataElement.State.parsed;
for (Object s : obj.getProperties().keySet()) {
if (replacements == null) replacements = new LinkedList<ReplaceArea.Replacement>();
if (!TMXMap.isPaintedLayerName(s.toString())) continue;
if (replacements == null) replacements = new ArrayList<ReplaceArea.Replacement>();
replacements.add(new Replacement(s.toString(), obj.getProperties().getProperty(s.toString())));
}
@@ -76,7 +71,7 @@ public class ReplaceArea extends MapObject {
}
public void addReplacement(ReplaceArea.Replacement repl) {
if (replacements == null) replacements = new LinkedList<ReplaceArea.Replacement>();
if (replacements == null) replacements = new ArrayList<ReplaceArea.Replacement>();
replacements.add(repl);
}
@@ -96,7 +91,7 @@ public class ReplaceArea extends MapObject {
}
if (requirement != null) {
if (oldSchoolRequirement && Requirement.RequirementType.questProgress.equals(requirement.type) && (requirement.negated == null || !requirement.negated)) {
tmxObject.setName(requirement.required_obj_id+":"+Integer.toString(requirement.required_value));
tmxObject.setName(requirement.required_obj_id+":"+((requirement.required_value == null) ? "" : Integer.toString(requirement.required_value)));
} else {
tmxObject.getProperties().setProperty("requireType", requirement.type.toString());
if (requirement.required_obj != null) {
@@ -107,6 +102,9 @@ public class ReplaceArea extends MapObject {
if (requirement.required_value != null) {
tmxObject.getProperties().setProperty("requireValue", requirement.required_value.toString());
}
if (requirement.negated != null) {
tmxObject.getProperties().setProperty("requireNegation", Boolean.toString(requirement.negated));
}
}
}
}
@@ -114,7 +112,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+":"+Integer.toString(requirement.required_value);
name = 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);

View File

@@ -42,8 +42,9 @@ public class ScriptArea extends MapObject {
@Override
public void elementChanged(GameDataElement oldOne, GameDataElement newOne) {
if (oldOne == dialogue) {
oldOne.removeBacklink(parentMap);
dialogue = (Dialogue) newOne;
newOne.addBacklink(parentMap);
if (newOne != null) newOne.addBacklink(parentMap);
}
}

View File

@@ -31,8 +31,9 @@ public class SignArea extends MapObject {
@Override
public void elementChanged(GameDataElement oldOne, GameDataElement newOne) {
if (oldOne == dialogue) {
oldOne.removeBacklink(parentMap);
dialogue = (Dialogue) newOne;
newOne.addBacklink(parentMap);
if (newOne != null) newOne.addBacklink(parentMap);
}
}

View File

@@ -13,6 +13,8 @@ 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<NPC> spawnGroup = new ArrayList<NPC>();
public SpawnArea(tiled.core.MapObject obj) {
@@ -25,12 +27,20 @@ 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 ){
this.spawngroup_id = obj.getName();
}
}
@Override
public void link() {
if (name != null) {
spawnGroup = parentMap.getProject().getSpawnGroup(name);
if (spawngroup_id != null) {
spawnGroup = parentMap.getProject().getSpawnGroup(spawngroup_id);
} else {
spawnGroup = new ArrayList<NPC>();
}
@@ -58,13 +68,17 @@ public class SpawnArea extends MapObject {
}
}
if (replacedIndex >= 0) {
oldOne.removeBacklink(parentMap);
spawnGroup.set(replacedIndex, (NPC) newOne);
newOne.addBacklink(parentMap);
if (newOne != null) newOne.addBacklink(parentMap);
}
}
@Override
public void savePropertiesInTmxObject(tiled.core.MapObject tmxObject) {
if (spawngroup_id != null) {
tmxObject.getProperties().setProperty("spawngroup", spawngroup_id);
}
if (quantity != 1) {
tmxObject.getProperties().setProperty("quantity", Integer.toString(quantity));
}
@@ -74,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));
}
}
}

View File

@@ -12,6 +12,7 @@ import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.swing.tree.TreeNode;
@@ -21,11 +22,12 @@ import tiled.io.TMXMapWriter;
import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.model.GameSource;
import com.gpl.rpg.atcontentstudio.model.SaveEvent;
import com.gpl.rpg.atcontentstudio.model.GameSource.Type;
import com.gpl.rpg.atcontentstudio.model.Project;
import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode;
import com.gpl.rpg.atcontentstudio.model.SaveEvent;
import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet;
import com.gpl.rpg.atcontentstudio.model.gamedata.NPC;
import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet;
import com.gpl.rpg.atcontentstudio.ui.DefaultIcons;
@@ -36,8 +38,21 @@ 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 {
none,
black20,
black40,
black60,
black80,
invert,
bw,
redtint,
greentint,
bluetint
}
public File tmxFile = null;
public tiled.core.Map tmxMap = null;
@@ -46,8 +61,10 @@ public class TMXMap extends GameDataElement {
public ProjectTreeNode parent;
public Integer outside = null;
public ColorFilter colorFilter = null;
public boolean writable = false;
public boolean changedOnDisk = false;
public int dismissNextChangeNotif = 0;
public TMXMap(TMXMapSet parent, File f) {
this.parent = parent;
@@ -62,8 +79,11 @@ public class TMXMap extends GameDataElement {
usedSpritesheets = new HashSet<Spritesheet>();
try {
tmxMap = new TMXMapReader().readMap(tmxFile.getAbsolutePath(), this);
if (tmxMap.getProperties().get("outside") != null) {
outside = new Integer(((String) tmxMap.getProperties().get("outside")));
if (tmxMap.getProperties().get("outdoors") != null) {
outside = new Integer(((String) tmxMap.getProperties().get("outdoors")));
}
if (tmxMap.getProperties().get("colorfilter") != null) {
colorFilter = ColorFilter.valueOf(((String) tmxMap.getProperties().get("colorfilter")));
}
} catch (FileNotFoundException e) {
Notification.addError("Impossible to load TMX map file "+tmxFile.getAbsolutePath());
@@ -97,8 +117,11 @@ public class TMXMap extends GameDataElement {
try {
clone.usedSpritesheets = new HashSet<Spritesheet>();
clone.tmxMap = new TMXMapReader().readMap(new StringReader(this.toXml()), clone);
if (clone.tmxMap.getProperties().get("outside") != null) {
clone.outside = new Integer(((String) clone.tmxMap.getProperties().get("outside")));
if (clone.tmxMap.getProperties().get("outdoors") != null) {
clone.outside = new Integer(((String) clone.tmxMap.getProperties().get("outdoors")));
}
if (clone.tmxMap.getProperties().get("colorfilter") != null) {
clone.colorFilter = ColorFilter.valueOf(((String) tmxMap.getProperties().get("colorfilter")));
}
for (tiled.core.MapLayer layer : clone.tmxMap.getLayers()) {
if (layer instanceof tiled.core.ObjectGroup) {
@@ -106,6 +129,7 @@ public class TMXMap extends GameDataElement {
clone.groups = new ArrayList<MapObjectGroup>();
}
MapObjectGroup group = new MapObjectGroup((tiled.core.ObjectGroup) layer, this);
group.link();
clone.groups.add(group);
}
}
@@ -178,7 +202,7 @@ public class TMXMap extends GameDataElement {
}
@Override
public String getDesc() {
return (this.state == State.modified ? "*" : "")+id;
return (needsSaving() ? "*" : "")+id;
}
@Override
@@ -210,6 +234,17 @@ public class TMXMap extends GameDataElement {
}
public String toXml() {
if (outside != null && outside == 1) {
tmxMap.getProperties().put("outdoors", Integer.toString(outside));
} else {
tmxMap.getProperties().remove("outdoors");
}
if (colorFilter != null) {
tmxMap.getProperties().put("colorfilter", colorFilter.toString());
} else {
tmxMap.getProperties().remove("colorfilter");
}
for (MapObjectGroup group : groups) {
group.pushBackToTiledProperties();
if (!tmxMap.containsLayer(group.tmxGroup)) {
@@ -239,11 +274,15 @@ public class TMXMap extends GameDataElement {
public void save() {
if (writable) {
String xml = toXml();
try {
//TODO: check in fileutils, to test the workspace's filesystem once at startup, and figure out how many of these can occur, instead of hard-coded '2'
dismissNextChangeNotif += 2;
FileWriter w = new FileWriter(tmxFile);
w.write(toXml());
w.write(xml);
w.close();
this.state = State.saved;
changedOnDisk = false;
Notification.addSuccess("TMX file "+tmxFile.getAbsolutePath()+" saved.");
} catch (IOException e) {
Notification.addError("Error while writing TMX file "+tmxFile.getAbsolutePath()+" : "+e.getMessage());
@@ -278,9 +317,11 @@ public class TMXMap extends GameDataElement {
if (this.state == GameDataElement.State.init) {
parse();
}
if (this.state == GameDataElement.State.parsed || this.state == GameDataElement.State.created) {
for (MapObjectGroup group : groups) {
group.link();
if (this.state == GameDataElement.State.parsed) {
if (groups != null) {
for (MapObjectGroup group : groups) {
group.link();
}
}
}
}
@@ -358,7 +399,80 @@ 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);
}
public void reload() {
tmxMap = null;
for (Spritesheet s : usedSpritesheets) {
s.elementChanged(this, null);
}
usedSpritesheets.clear();
for (MapObjectGroup g : groups) {
for (MapObject o : g.mapObjects) {
if (o instanceof ContainerArea) {
if (((ContainerArea)o).droplist != null) ((ContainerArea)o).droplist.elementChanged(this, null);
} else if (o instanceof KeyArea) {
if (((KeyArea)o).dialogue != null) ((KeyArea)o).dialogue.elementChanged(this, null);
if (((KeyArea)o).requirement != null && ((KeyArea)o).requirement.required_obj != null) ((KeyArea)o).requirement.required_obj.elementChanged(this, null);
} else if (o instanceof MapChange) {
if (((MapChange)o).map != null) ((MapChange)o).map.elementChanged(this, null);
} else if (o instanceof ReplaceArea) {
if (((ReplaceArea)o).requirement != null && ((ReplaceArea)o).requirement.required_obj != null) ((ReplaceArea)o).requirement.required_obj.elementChanged(this, null);
} else if (o instanceof RestArea) {
} else if (o instanceof ScriptArea) {
if (((ScriptArea)o).dialogue != null) ((ScriptArea)o).dialogue.elementChanged(this, null);
} else if (o instanceof SignArea) {
if (((SignArea)o).dialogue != null) ((SignArea)o).dialogue.elementChanged(this, null);
} else if (o instanceof SpawnArea) {
if (((SpawnArea)o).spawnGroup != null) {
for (NPC n : ((SpawnArea)o).spawnGroup) {
n.elementChanged(this, null);
}
}
}
}
}
groups.clear();
outside = null;
colorFilter = null;
state = GameDataElement.State.init;
this.link();
changedOnDisk = false;
for (MapChangedOnDiskListener l : listeners) {
l.mapReloaded();
}
}
public void mapChangedOnDisk() {
if (dismissNextChangeNotif > 0) {
dismissNextChangeNotif--;
} else {
changedOnDisk = true;
for (MapChangedOnDiskListener l : listeners) {
l.mapChanged();
}
}
}
public interface MapChangedOnDiskListener {
public void mapChanged();
public void mapReloaded();
}
private List<MapChangedOnDiskListener> listeners = new CopyOnWriteArrayList<TMXMap.MapChangedOnDiskListener>();
public void addMapChangedOnDiskListener(MapChangedOnDiskListener l) {
listeners.add(l);
}
public void removeMapChangedOnDiskListener(MapChangedOnDiskListener l) {
listeners.remove(l);
}
}

View File

@@ -2,28 +2,38 @@ package com.gpl.rpg.atcontentstudio.model.maps;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.ArrayList;
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;
import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.model.GameSource;
import com.gpl.rpg.atcontentstudio.model.GameSource.Type;
import com.gpl.rpg.atcontentstudio.model.Project.ResourceSet;
import com.gpl.rpg.atcontentstudio.model.Project;
import com.gpl.rpg.atcontentstudio.model.Project.ResourceSet;
import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode;
import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet;
import com.gpl.rpg.atcontentstudio.model.gamedata.Item;
import com.gpl.rpg.atcontentstudio.ui.DefaultIcons;
import com.gpl.rpg.atcontentstudio.utils.FileUtils;
public class TMXMapSet implements ProjectTreeNode {
public static final String DEFAULT_REL_PATH_IN_SOURCE = "res/xml/";
public static final String DEFAULT_REL_PATH_IN_PROJECT = "maps/";
public static final String DEFAULT_REL_PATH_IN_SOURCE = "res"+File.separator+"xml"+File.separator;
public static final String DEFAULT_REL_PATH_IN_PROJECT = "maps"+File.separator;
public static final String DEFAULT_REL_PATH_TO_DRAWABLE = ".."+File.separator+"drawable"+File.separator;
public static final String GAME_MAPS_ARRAY_NAME = "loadresource_maps";
public static final String DEBUG_SUFFIX = "_debug";
@@ -45,6 +55,7 @@ public class TMXMapSet implements ProjectTreeNode {
if (!this.mapFolder.exists()) {
this.mapFolder.mkdirs();
}
FileUtils.makeSymlink(getProject().baseContent.gameSprites.drawableFolder, new File(mapFolder.getAbsolutePath()+File.separator+DEFAULT_REL_PATH_TO_DRAWABLE));
}
this.tmxMaps = new ArrayList<TMXMap>();
@@ -80,6 +91,45 @@ public class TMXMapSet implements ProjectTreeNode {
return o1.id.compareTo(o2.id);
}
});
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 "+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 wk;
validService: while(getProject().open) {
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;
}
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
};
watcher.start();
}
}
@Override
@@ -138,7 +188,7 @@ public class TMXMapSet implements ProjectTreeNode {
}
@Override
public String getDesc() {
return "TMX Maps";
return (needsSaving() ? "*" : "")+"TMX Maps";
}
@Override
@@ -213,4 +263,12 @@ public class TMXMapSet implements ProjectTreeNode {
return tmxMaps.get(index);
}
@Override
public boolean needsSaving() {
for (ProjectTreeNode node : tmxMaps) {
if (node.needsSaving()) return true;
}
return false;
}
}

View File

@@ -10,7 +10,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -36,6 +35,7 @@ import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.model.GameSource;
import com.gpl.rpg.atcontentstudio.model.GameSource.Type;
import com.gpl.rpg.atcontentstudio.model.Project;
@@ -174,7 +174,7 @@ public class Worldmap extends ArrayList<WorldmapSegment> implements ProjectTreeN
@Override
public String getDesc() {
return "Worldmap";
return (needsSaving() ? "*" : "")+"Worldmap";
}
@Override
public void notifyCreated() {
@@ -223,6 +223,7 @@ public class Worldmap extends ArrayList<WorldmapSegment> implements ProjectTreeN
for (WorldmapSegment segment : this) {
root.appendChild(segment.toXmlElement(doc));
segment.state = GameDataElement.State.saved;
}
saveDocToFile(doc, worldmapFile);
@@ -278,6 +279,13 @@ public class Worldmap extends ArrayList<WorldmapSegment> implements ProjectTreeN
e.printStackTrace();
}
}
@Override
public boolean needsSaving() {
for (ProjectTreeNode node : this) {
if (node.needsSaving()) return true;
}
return false;
}
}

View File

@@ -3,11 +3,8 @@ package com.gpl.rpg.atcontentstudio.model.maps;
import java.awt.Image;
import java.awt.Point;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -26,7 +23,9 @@ 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;
import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet;
import com.gpl.rpg.atcontentstudio.ui.DefaultIcons;
@@ -35,6 +34,8 @@ public class WorldmapSegment extends GameDataElement {
private static final long serialVersionUID = 2658610076889592723L;
public static final String TEMP_LABEL_KEY = "ATCS_INTERNAL_TEMPORARY_KEY_FOR_LABEL";
public int segmentX;
public int segmentY;
public Map<String, Point> mapLocations = new LinkedHashMap<String, Point>();
@@ -55,7 +56,7 @@ public class WorldmapSegment extends GameDataElement {
@Override
public String getDesc() {
return id;
return (needsSaving() ? "*" : "")+id;
}
@Override
@@ -69,7 +70,7 @@ public class WorldmapSegment extends GameDataElement {
String area;
if ((area = mapNode.getAttribute("area")) != null && !"".equals(area)) {
if (labelledMaps.get(area) == null) {
labelledMaps.put(area, new LinkedList<String>());
labelledMaps.put(area, new ArrayList<String>());
}
labelledMaps.get(area).add(mapNode.getAttribute("id"));
}
@@ -105,8 +106,37 @@ public class WorldmapSegment extends GameDataElement {
@Override
public void elementChanged(GameDataElement oldOne, GameDataElement newOne) {
boolean modified = false;
if (newOne == null && writable) {
//A referenced map may have been deleted.
if (mapLocations.containsKey(oldOne.id)) {
mapLocations.remove(oldOne.id);
modified = true;
}
List<String> deprecatedLabels = new ArrayList<String>();
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);
newOne.addBacklink(this);
if(newOne != null) newOne.addBacklink(this);
if (modified) {
this.state = GameDataElement.State.modified;
childrenChanged(new ArrayList<ProjectTreeNode>());
ATContentStudio.frame.editorChanged(this);
}
}
@Override
@@ -157,6 +187,7 @@ public class WorldmapSegment extends GameDataElement {
map.setAttribute("x", Integer.toString(mapLocations.get(s).x + segmentX));
map.setAttribute("y", Integer.toString(mapLocations.get(s).y + segmentY));
for (String label : labelledMaps.keySet()) {
if (TEMP_LABEL_KEY.equals(label)) continue;
if (labelledMaps.get(label).contains(s)) {
map.setAttribute("area", label);
}
@@ -164,7 +195,9 @@ public class WorldmapSegment extends GameDataElement {
element.appendChild(map);
}
for (NamedArea area : labels.values()) {
for (String key : labels.keySet()) {
if (TEMP_LABEL_KEY.equals(key)) continue;
NamedArea area = labels.get(key);
Element namedArea = doc.createElement("namedarea");
namedArea.setAttribute("id", area.id);
namedArea.setAttribute("name", area.name);

View File

@@ -97,7 +97,7 @@ public class SavedGame extends GameDataElement {
}
@Override
public String getDesc() {
return loadedSave.displayInfo;
return (needsSaving() ? "*" : "")+loadedSave.displayInfo;
}
@Override

View File

@@ -128,7 +128,7 @@ public class SavedGamesSet implements ProjectTreeNode, Serializable {
}
@Override
public String getDesc() {
return "Saved games";
return (needsSaving() ? "*" : "")+"Saved games";
}
@Override
@@ -168,4 +168,13 @@ public class SavedGamesSet implements ProjectTreeNode, Serializable {
public boolean isEmpty() {
return saves.isEmpty();
}
@Override
public boolean needsSaving() {
for (ProjectTreeNode node : saves) {
if (node.needsSaving()) return true;
}
return false;
}
}

View File

@@ -102,7 +102,7 @@ public class SpriteSheetSet implements ProjectTreeNode {
}
@Override
public String getDesc() {
return "Spritesheets";
return (needsSaving() ? "*" : "")+"Spritesheets";
}
@Override
@@ -151,4 +151,12 @@ public class SpriteSheetSet implements ProjectTreeNode {
}
return null;
}
@Override
public boolean needsSaving() {
for (ProjectTreeNode node : spritesheets) {
if (node.needsSaving()) return true;
}
return false;
}
}

View File

@@ -7,13 +7,14 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.swing.tree.TreeNode;
import com.gpl.rpg.atcontentstudio.ATContentStudio;
import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.model.GameSource.Type;
@@ -43,8 +44,8 @@ public class Spritesheet extends GameDataElement {
//Lazy initialization.
public BufferedImage spritesheet = null;
public Map<Integer, BufferedImage> cache_full_size = new HashMap<Integer, BufferedImage>();
public Map<Integer, Image> cache_icon = new HashMap<Integer, Image>();
public Map<Integer, BufferedImage> cache_full_size = new LinkedHashMap<Integer, BufferedImage>();
public Map<Integer, Image> cache_icon = new LinkedHashMap<Integer, Image>();
public Spritesheet(SpriteSheetSet parent, File f) {
this.spritesheetFile = f;
@@ -118,7 +119,7 @@ public class Spritesheet extends GameDataElement {
}
@Override
public String getDesc() {
return spritesheetFile.getName();
return (needsSaving() ? "*" : "")+spritesheetFile.getName();
}
@Override
@@ -174,7 +175,7 @@ public class Spritesheet extends GameDataElement {
}
Image result = getImage(index);
if (result == null) return null;
result = result.getScaledInstance(16, 16, BufferedImage.SCALE_SMOOTH);
result = result.getScaledInstance((int)(16*ATContentStudio.SCALING), (int)(16*ATContentStudio.SCALING), Image.SCALE_SMOOTH);
cache_icon.put(index, result);
return result;
}

View File

@@ -0,0 +1,657 @@
package com.gpl.rpg.atcontentstudio.model.tools.writermode;
import java.awt.Image;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.model.Project;
import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode;
import com.gpl.rpg.atcontentstudio.model.SaveEvent;
import com.gpl.rpg.atcontentstudio.model.gamedata.Dialogue;
import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet;
import com.gpl.rpg.atcontentstudio.ui.DefaultIcons;
public class WriterModeData extends GameDataElement {
private static final long serialVersionUID = -7062544089063979696L;
public File jsonFile;
public Image npcIcon;
// public String sketchName;
public List<String> rootsId = new ArrayList<String>();
public List<WriterDialogue> roots = new ArrayList<WriterDialogue>();
public WriterDialogue begin;
public Map<String, WriterDialogue> nodesById = new LinkedHashMap<String, WriterDialogue>();
//public Map<String, WriterDialogue> dialogueThreads = new LinkedHashMap<String, WriterDialogue>();
public Map<String, Integer> threadsNextIndex = new LinkedHashMap<String, Integer>();
public WriterModeData(String id_prefix){
this.id = id_prefix;
}
@SuppressWarnings("rawtypes")
public WriterModeData(WriterModeDataSet parent, Map jsonObj) {
this.parent = parent;
this.jsonFile = parent.writerFile;
this.parse(jsonObj);
this.state = State.parsed;
}
public WriterModeData(String id_prefix, Dialogue imported) {
this.id = id_prefix;
this.begin = new WriterDialogue(imported);
this.state = State.linked;
}
public int getNextIndex(String id_prefix) {
Integer index = threadsNextIndex.get(id_prefix);
if (index == null) index = 0;
while (getProject().getDialogue(id_prefix+index) != null) {
index++;
}
threadsNextIndex.put(id_prefix, index + 1);
return index;
}
public abstract class WriterNode {
public String text;
public abstract String getTitle();
}
public WriterDialogue createDialogue(Dialogue dialogue) {
if (dialogue.message == null) {
return new SelectorDialogue(dialogue);
} else {
return new WriterDialogue(dialogue);
}
}
public class WriterDialogue extends WriterNode {
public String id;
public String id_prefix;
public int index;
public List<WriterReply> replies = new ArrayList<WriterReply>();
public List<WriterReply> parents = new ArrayList<WriterReply>();
public String dialogue_id;
public Dialogue dialogue;
public WriterDialogue() {}
public WriterDialogue(Dialogue dialogue) {
this.dialogue = dialogue;
this.text = dialogue.message;
this.id = this.dialogue_id = dialogue.id;
Pattern p = Pattern.compile("(.*)([0-9]+)");
Matcher m = p.matcher(dialogue.id);
if (m.matches()) {
this.id_prefix = m.group(1);
this.index = Integer.parseInt(m.group(2));
} else {
this.id_prefix = this.id+"_";
}
nodesById.put(this.id, this);
if (dialogue.replies != null) {
for (Dialogue.Reply reply : dialogue.replies) {
if (Dialogue.Reply.GO_NEXT_TEXT.equals(reply.text) || reply.text == null) {
replies.add(new EmptyReply(this, reply));
} else {
replies.add(new WriterReply(this, reply));
}
}
}
}
public WriterDialogue(String id_prefix) {
text = "";
this.id_prefix = id_prefix;
index = getNextIndex(id_prefix);
}
@Override
public String getTitle() {
return "Dialogue "+getID();
}
public String getID() {
return this.id != null ? this.id : this.id_prefix+this.index;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public void toJson(List<WriterDialogue> visited, List<Map> jsonData) {
if (visited.contains(this)) return;
visited.add(this);
Map dialogueJson = new LinkedHashMap();
jsonData.add(dialogueJson);
dialogueJson.put("id", id);
dialogueJson.put("id_prefix", id_prefix);
dialogueJson.put("index", index);
dialogueJson.put("text", text);
if (dialogue != null) {
dialogueJson.put("dialogue", dialogue.id);
} else if (dialogue_id != null) {
dialogueJson.put("dialogue", dialogue_id);
}
dialogueJson.put("special", isSpecial());
dialogueJson.put("begin", begin == this);
if (!replies.isEmpty()) {
List repliesJson = new ArrayList();
for (WriterReply reply : replies) {
repliesJson.add(reply.toJson(visited, jsonData));
}
dialogueJson.put("replies", repliesJson);
}
}
@SuppressWarnings("rawtypes")
public WriterDialogue(Map json) {
this.id = (String) json.get("id");
this.index = ((Number)json.get("index")).intValue();
this.id_prefix = (String) json.get("id_prefix");
if (threadsNextIndex.get(id_prefix) == null || threadsNextIndex.get(id_prefix) <= index) {
threadsNextIndex.put(id_prefix, index+1);
}
this.text = (String) json.get("text");
this.dialogue_id = (String) json.get("dialogue");
if (json.get("begin") != null && ((Boolean)json.get("begin"))) begin = this;
if (json.get("replies") != null) {
List repliesJson = (List) json.get("replies");
for (Object rJson : repliesJson) {
if (((Map)rJson).get("special") != null && (Boolean)((Map)rJson).get("special")) {
//TODO Check different cases. But there are none currently.
this.replies.add(new EmptyReply(this, ((Map)rJson)));
} else {
this.replies.add(new WriterReply(this, (Map)rJson));
}
}
}
}
public boolean isSpecial() {return false;}
public Dialogue toDialogue(Map<WriterDialogue, Dialogue> visited, List<Dialogue> created, List<Dialogue> modified) {
if (visited.get(this) != null) return visited.get(this);
//Creating a new Dialogue
if (dialogue == null) {
dialogue = new Dialogue();
dialogue.id = getID();
dialogue.state = GameDataElement.State.parsed;
created.add(dialogue);
} else {
if (hasChanged()) {
if (dialogue.writable) {
//Modifying a created or altered Dialogue
dialogue.state = GameDataElement.State.modified;
modified.add(dialogue);
} else {
//Altering a game source Dialogue
//Dialogue clone = (Dialogue) dialogue.clone();
dialogue.getProject().makeWritable(dialogue);
Dialogue clone = dialogue.getProject().getDialogue(dialogue.id);
if (this.replies != null) {
for (WriterReply wReply : this.replies) {
if (wReply.reply != null) {
wReply.reply = clone.replies.get(dialogue.replies.indexOf(wReply.reply));
}
}
}
dialogue = clone;
dialogue.state = GameDataElement.State.parsed;
created.add(dialogue);
}
}
}
visited.put(this, dialogue);
dialogue.message = this.text;
if (this.replies != null && !this.replies.isEmpty()) {
if (dialogue.replies == null) {
dialogue.replies = new ArrayList<Dialogue.Reply>();
} else {
dialogue.replies.clear();
}
for (WriterReply wReply : this.replies) {
//if (wReply.reply != null && dialogue.replies)
dialogue.replies.add(wReply.toReply(visited, created, modified));
}
} else {
dialogue.replies = null;
}
return dialogue;
}
public boolean hasChanged() {
return dialogue == null ||
text == null ? dialogue.message!=null : !text.equals(dialogue.message) ||
repliesHaveChanged();
}
public boolean repliesHaveChanged() {
if (replies.isEmpty() && (dialogue.replies == null || dialogue.replies.isEmpty())) return false;
if (!replies.isEmpty() && (dialogue.replies == null || dialogue.replies.isEmpty())) return true;
if (replies.isEmpty() && (dialogue.replies != null && !dialogue.replies.isEmpty())) return true;
if (replies.size() != dialogue.replies.size()) return true;
for (WriterReply reply : replies) {
if (reply.hasChanged()) return true;
}
return false;
}
}
public abstract class SpecialDialogue extends WriterDialogue {
public SpecialDialogue() {}
public boolean isSpecial() {return true;}
public abstract SpecialDialogue duplicate();
public SpecialDialogue(Dialogue dialogue) {
super(dialogue);
}
}
public class SelectorDialogue extends SpecialDialogue {
public SelectorDialogue() {}
public SpecialDialogue duplicate() {return new SelectorDialogue();}
public SelectorDialogue(Dialogue dialogue) {
super(dialogue);
}
}
public class ShopDialogue extends SpecialDialogue {
public static final String id = Dialogue.Reply.SHOP_PHRASE_ID;
public SpecialDialogue duplicate() {return new ShopDialogue();}
}
public class FightDialogue extends SpecialDialogue {
public static final String id = Dialogue.Reply.FIGHT_PHRASE_ID;
public SpecialDialogue duplicate() {return new FightDialogue();}
}
public class EndDialogue extends SpecialDialogue {
public static final String id = Dialogue.Reply.EXIT_PHRASE_ID;
public SpecialDialogue duplicate() {return new EndDialogue();}
}
public class RemoveNPCDialogue extends SpecialDialogue {
public static final String id = Dialogue.Reply.REMOVE_PHRASE_ID;
public SpecialDialogue duplicate() {return new RemoveNPCDialogue();}
}
public class WriterReply extends WriterNode {
public WriterDialogue parent;
public String next_dialogue_id;
public WriterDialogue next_dialogue;
public Dialogue.Reply reply;
public WriterReply() {}
public WriterReply(WriterDialogue parent) {
this.parent = parent;
this.text = "";
parent.replies.add(this);
}
public WriterReply(WriterDialogue parent, Dialogue.Reply reply) {
this.parent = parent;
this.reply = reply;
this.text = reply.text;
this.next_dialogue_id = reply.next_phrase_id;
if (nodesById.get(this.next_dialogue_id) != null) {
this.next_dialogue = nodesById.get(this.next_dialogue_id);
} else if (reply.next_phrase != null ){
this.next_dialogue = new WriterDialogue(reply.next_phrase);
}
}
@SuppressWarnings("rawtypes")
public WriterReply(WriterDialogue parent, Map json) {
this.parent = parent;
this.text = (String) json.get("text");
if (json.containsKey("next_dialogue_id")) {
next_dialogue_id = (String) json.get("next_dialogue_id");
}
}
@Override
public String getTitle() {
return "Reply in "+parent.id_prefix+parent.index;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public Map toJson(List<WriterDialogue> visited, List<Map> jsonData) {
Map replyJson = new LinkedHashMap();
replyJson.put("text", text);
replyJson.put("special", isSpecial());
if (next_dialogue != null) {
replyJson.put("next_dialogue_id", next_dialogue.getID());
next_dialogue.toJson(visited, jsonData);
}
return replyJson;
}
public boolean isSpecial() {return false;}
public Dialogue.Reply toReply(Map<WriterDialogue, Dialogue> visited, List<Dialogue> created, List<Dialogue> modified) {
if (reply == null) {
reply = new Dialogue.Reply();
}
reply.text = this.text;
if (this.next_dialogue != null) {
this.next_dialogue.toDialogue(visited, created, modified);
reply.next_phrase_id = this.next_dialogue.getID();
} else if (this.next_dialogue_id != null) {
reply.next_phrase_id = this.next_dialogue_id;
} else {
reply.next_phrase_id = Dialogue.Reply.EXIT_PHRASE_ID;
}
return reply;
}
public boolean hasChanged() {
if (reply == null) return true;
if (text == null && reply.text != null) return true;
if (text != null && reply.text == null) return true;
if (text != null && !text.equals(reply.text)) return true;
String targetDialogueId = next_dialogue != null ? next_dialogue.getID() : next_dialogue_id;
String replyTargetDialogueId = reply.next_phrase != null ? reply.next_phrase.id : reply.next_phrase_id;
if (targetDialogueId == null && replyTargetDialogueId != null) return true;
if (targetDialogueId != null && replyTargetDialogueId == null) return true;
if (targetDialogueId != null && !targetDialogueId.equals(replyTargetDialogueId)) return true;
return false;
}
}
public class SpecialReply extends WriterReply {
public boolean isSpecial() {return true;}
public SpecialReply(WriterDialogue parent, Dialogue.Reply reply) {
super(parent, reply);
}
public SpecialReply(WriterDialogue parent) {
super(parent);
}
public SpecialReply(WriterDialogue parent, @SuppressWarnings("rawtypes") Map json) {
super(parent, json);
}
}
public class EmptyReply extends SpecialReply {
public EmptyReply(WriterDialogue parent, Dialogue.Reply reply) {
super(parent, reply);
text = Dialogue.Reply.GO_NEXT_TEXT;
}
public EmptyReply(WriterDialogue parent) {
super(parent);
text = Dialogue.Reply.GO_NEXT_TEXT;
}
public EmptyReply(WriterDialogue parent, @SuppressWarnings("rawtypes") Map json) {
super(parent, json);
text = Dialogue.Reply.GO_NEXT_TEXT;
}
}
@Override
public String getDesc() {
return (needsSaving() ? "*" : "")+id;
}
@Override
public Project getProject() {
return parent.getProject();
}
@Override
public Image getIcon() {
return DefaultIcons.getDialogueIcon();
}
@Override
public Image getOpenIcon() {
return null;
}
@Override
public Image getClosedIcon() {
return null;
}
@Override
public Image getLeafIcon() {
return getIcon();
}
@Override
public GameDataSet getDataSet() {
return null;
}
@Override
public GameDataElement clone() {
//TODO
return null;
}
@Override
public void elementChanged(GameDataElement oldOne, GameDataElement newOne) {
// Useless here.
}
@Override
public String getProjectFilename() {
return WriterModeDataSet.DEFAULT_REL_PATH_IN_PROJECT;
}
@Override
public void save() {
((WriterModeDataSet)this.getParent()).save(this.jsonFile);
}
@Override
public List<SaveEvent> attemptSave() {
List<SaveEvent> events = ((WriterModeDataSet)parent).attemptSave();
if (events == null || events.isEmpty()) {
return null;
}
if (events.size() == 1 && events.get(0).type == SaveEvent.Type.alsoSave && events.get(0).target == this) {
save();
return null;
}
return events;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public Map toJson() {
List<Map> jsonData = new ArrayList<Map>();
begin.toJson(new ArrayList<WriterModeData.WriterDialogue>(), jsonData);
Map jsonObj = new LinkedHashMap();
jsonObj.put("id", id);
jsonObj.put("dialogues", jsonData);
return jsonObj;
}
@SuppressWarnings("rawtypes")
public void parse() {
if (this.state == State.created || this.state == State.modified || this.state == State.saved) {
//This type of state is unrelated to parsing/linking.
return;
}
JSONParser parser = new JSONParser();
FileReader reader = null;
try {
reader = new FileReader(jsonFile);
List gameDataElements = (List) parser.parse(reader);
for (Object obj : gameDataElements) {
Map jsonObj = (Map)obj;
String id = (String) jsonObj.get("id");
if (id != null && id.equals(this.id )) {
this.parse(jsonObj);
this.state = State.parsed;
break;
}
}
} catch (FileNotFoundException e) {
Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage());
e.printStackTrace();
} catch (IOException e) {
Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage());
e.printStackTrace();
} catch (ParseException e) {
Notification.addError("Error while parsing JSON file "+jsonFile.getAbsolutePath()+": "+e.getMessage());
e.printStackTrace();
} finally {
if (reader != null)
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@SuppressWarnings("rawtypes")
public void parse(Map json) {
this.id = (String) json.get("id");
// this.sketchName = (String) json.get("name");
// List jsonRootsId = (List) json.get("roots_id");
// if (jsonRootsId != null) {
// for (Object jsonRootId : jsonRootsId) {
// rootsId.add((String) jsonRootId);
// }
// }
List jsonDialogues = (List) json.get("dialogues");
if (jsonDialogues != null) {
for (Object jsonDialogue : jsonDialogues) {
WriterDialogue dialogue = new WriterDialogue((Map)jsonDialogue);
nodesById.put(dialogue.getID(), dialogue);
}
}
this.state = State.parsed;
}
@Override
public void link() {
if (this.state == State.created) {
this.begin = new WriterDialogue();
begin.id_prefix = id;
begin.index = getNextIndex(id);
begin.text = "";
}
if (this.state == State.init) {
//Not parsed yet.
this.parse();
}
if (this.state == State.parsed) {
for (String prefix : threadsNextIndex.keySet()) {
while (getProject().getDialogue(prefix+threadsNextIndex.get(prefix)) != null) {
threadsNextIndex.put(prefix, threadsNextIndex.get(prefix)+1);
}
}
for (WriterDialogue dialogue : nodesById.values()) {
if (dialogue.dialogue_id != null) {
dialogue.dialogue = getProject().getDialogue(dialogue.dialogue_id);
}
if (dialogue.replies == null) continue;
for (WriterReply reply : dialogue.replies) {
if (reply.next_dialogue_id != null) {
if (isSpecial(reply.next_dialogue_id)) {
reply.next_dialogue = getSpecial(reply.next_dialogue_id);
} else {
reply.next_dialogue = nodesById.get(reply.next_dialogue_id);
}
}
//TODO Seriously, this is failure-prone by design. Can't do much better though...
if (dialogue.dialogue != null) {
List<Dialogue.Reply> linked = new ArrayList<Dialogue.Reply>(dialogue.dialogue.replies.size());
if (dialogue.dialogue != null && dialogue.dialogue.replies != null) {
//Try to hook to existing replies... not as easy when there's no ID.
Dialogue.Reply best = null;
int score, maxScore = 0;
for (Dialogue.Reply dReply : dialogue.dialogue.replies) {
//Never link twice to the same...
if (linked.contains(dReply)) continue;
score = 0;
//Arbitrary values... hopefully this gives good results.
//Same target gives good hope of preserving at least the structure.
if (dReply.next_phrase_id != null && dReply.next_phrase_id.equals(reply.next_dialogue_id)) score +=50;
//Same text is almost as good as an ID, but there may be duplicates due to requirements system...
if (dReply.text != null && dReply.text.equals(reply.text)) score +=40;
//Same slot in the list. That's not so bad if all else fails, and could help sort duplicates with same text.
if (dialogue.dialogue.replies.indexOf(dReply) == dialogue.replies.indexOf(reply)) score +=20;
//Both have null text. It's not much, but it's something....
if (dReply.text == null && reply.text == null) score += 10;
if (score > maxScore) {
maxScore = score;
best = dReply;
}
}
if (maxScore > 0) {
reply.reply = best;
linked.add(best);
}
}
}
}
}
for (String rootId : rootsId) {
roots.add(nodesById.get(rootId));
}
}
if (this.state == State.linked) {
//Already linked.
return;
}
this.state = State.linked;
}
public boolean isSpecial(String id) {
if (id == null) return false;
if (ShopDialogue.id.equals(id)) return true;
if (FightDialogue.id.equals(id)) return true;
if (EndDialogue.id.equals(id)) return true;
if (RemoveNPCDialogue.id.equals(id)) return true;
return false;
}
public SpecialDialogue getSpecial(String id) {
if (id == null) return null;
if (ShopDialogue.id.equals(id)) return new ShopDialogue();
if (FightDialogue.id.equals(id)) return new FightDialogue();
if (EndDialogue.id.equals(id)) return new EndDialogue();
if (RemoveNPCDialogue.id.equals(id)) return new RemoveNPCDialogue();
return null;
}
public List<Dialogue> toDialogue(){
Map<WriterModeData.WriterDialogue, Dialogue> visited = new LinkedHashMap<WriterModeData.WriterDialogue, Dialogue>();
List<Dialogue> created = new ArrayList<Dialogue>();
List<Dialogue> modified = new ArrayList<Dialogue>();
begin.toDialogue(visited, created, modified);
for (Dialogue modifiedDialogue : modified) {
modifiedDialogue.childrenChanged(new ArrayList<ProjectTreeNode>());
}
return created;
}
}

View File

@@ -0,0 +1,274 @@
package com.gpl.rpg.atcontentstudio.model.tools.writermode;
import java.awt.Image;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import javax.swing.tree.TreeNode;
import org.json.simple.JSONArray;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.io.JsonPrettyWriter;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.model.GameSource;
import com.gpl.rpg.atcontentstudio.model.GameSource.Type;
import com.gpl.rpg.atcontentstudio.model.Project;
import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode;
import com.gpl.rpg.atcontentstudio.model.SaveEvent;
import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet;
import com.gpl.rpg.atcontentstudio.ui.DefaultIcons;
public class WriterModeDataSet implements ProjectTreeNode, Serializable {
private static final long serialVersionUID = 5434504851883441971L;
public static final String DEFAULT_REL_PATH_IN_PROJECT = "writer.json";
public GameSource parent;
public File writerFile;
public List<WriterModeData> writerModeDataList = new ArrayList<WriterModeData>();
public WriterModeDataSet(GameSource gameSource) {
this.parent = gameSource;
writerFile = new File(parent.baseFolder, DEFAULT_REL_PATH_IN_PROJECT);
parse();
}
@Override
public TreeNode getChildAt(int childIndex) {
return writerModeDataList.get(childIndex);
}
@Override
public int getChildCount() {
return writerModeDataList.size();
}
@Override
public TreeNode getParent() {
return parent;
}
@Override
public int getIndex(TreeNode node) {
return writerModeDataList.indexOf(node);
}
@Override
public boolean getAllowsChildren() {
return true;
}
@Override
public boolean isLeaf() {
return false;
}
@SuppressWarnings("rawtypes")
@Override
public Enumeration children() {
return Collections.enumeration(writerModeDataList);
}
@Override
public void childrenAdded(List<ProjectTreeNode> path) {
path.add(0,this);
parent.childrenAdded(path);
}
@Override
public void childrenChanged(List<ProjectTreeNode> path) {
path.add(0,this);
parent.childrenChanged(path);
}
@Override
public void childrenRemoved(List<ProjectTreeNode> path) {
path.add(0,this);
parent.childrenRemoved(path);
}
@Override
public void notifyCreated() {
childrenAdded(new ArrayList<ProjectTreeNode>());
}
@Override
public String getDesc() {
return (needsSaving() ? "*" : "")+"Dialogue sketches";
}
@Override
public Project getProject() {
return parent.getProject();
}
@Override
public GameDataSet getDataSet() {
return null;
}
@Override
public Image getIcon() {
return DefaultIcons.getStdClosedIcon();
}
@Override
public Image getOpenIcon() {
return DefaultIcons.getStdOpenIcon();
}
@Override
public Image getClosedIcon() {
return DefaultIcons.getStdClosedIcon();
}
@Override
public Image getLeafIcon() {
return null;
}
@Override
public Type getDataType() {
return parent.getDataType();
}
@Override
public boolean isEmpty() {
return writerModeDataList.isEmpty();
}
@SuppressWarnings("rawtypes")
public void save(File jsonFile) {
List<Map> dataToSave = new ArrayList<Map>();
for (WriterModeData data : writerModeDataList) {
if (data.jsonFile.equals(jsonFile)) {
dataToSave.add(data.toJson());
}
}
if (dataToSave.isEmpty() && writerFile.exists()) {
if (writerFile.delete()) {
Notification.addSuccess("File "+writerFile.getAbsolutePath()+" deleted.");
} else {
Notification.addError("Error deleting file "+writerFile.getAbsolutePath());
}
return;
}
StringWriter writer = new JsonPrettyWriter();
try {
JSONArray.writeJSONString(dataToSave, writer);
} catch (IOException e) {
//Impossible with a StringWriter
}
String toWrite = writer.toString();
try {
FileWriter w = new FileWriter(writerFile);
w.write(toWrite);
w.close();
for (WriterModeData element : writerModeDataList) {
element.state = GameDataElement.State.saved;
}
Notification.addSuccess("Json file "+writerFile.getAbsolutePath()+" saved.");
} catch (IOException e) {
Notification.addError("Error while writing json file "+writerFile.getAbsolutePath()+" : "+e.getMessage());
e.printStackTrace();
}
}
public List<SaveEvent> attemptSave() {
List<SaveEvent> events = new ArrayList<SaveEvent>();
for (WriterModeData data : writerModeDataList) {
if (data.needsSaving()) {
events.add(new SaveEvent(SaveEvent.Type.alsoSave, data));
}
}
return events;
}
@SuppressWarnings("rawtypes")
public void parse() {
if (!writerFile.exists()) return;
JSONParser parser = new JSONParser();
FileReader reader = null;
try {
reader = new FileReader(writerFile);
List writerDataListJson = (List) parser.parse(reader);
for (Object obj : writerDataListJson) {
Map jsonObj = (Map)obj;
WriterModeData data = new WriterModeData(this, jsonObj);
data.writable = true;
writerModeDataList.add(data);
}
} catch (FileNotFoundException e) {
Notification.addError("Error while parsing JSON file "+writerFile.getAbsolutePath()+": "+e.getMessage());
e.printStackTrace();
} catch (IOException e) {
Notification.addError("Error while parsing JSON file "+writerFile.getAbsolutePath()+": "+e.getMessage());
e.printStackTrace();
} catch (ParseException e) {
Notification.addError("Error while parsing JSON file "+writerFile.getAbsolutePath()+": "+e.getMessage());
e.printStackTrace();
} finally {
if (reader != null)
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public WriterModeData getWriterSketch(String id) {
for (WriterModeData sketch : writerModeDataList) {
if (id.equals(sketch.id)){
return sketch;
}
}
return null;
}
public WriterModeData get(int index) {
return writerModeDataList.get(index);
}
public void add(WriterModeData node) {
ProjectTreeNode higherEmptyParent = this;
while (higherEmptyParent != null) {
if (higherEmptyParent.getParent() != null && ((ProjectTreeNode)higherEmptyParent.getParent()).isEmpty()) higherEmptyParent = (ProjectTreeNode)higherEmptyParent.getParent();
else break;
}
if (higherEmptyParent == this && !this.isEmpty()) higherEmptyParent = null;
writerModeDataList.add(node);
node.writable = true;
if (node.jsonFile == null) node.jsonFile = this.writerFile;
node.parent = this;
if (higherEmptyParent != null) higherEmptyParent.notifyCreated();
else node.notifyCreated();
}
@Override
public boolean needsSaving() {
for (ProjectTreeNode node : writerModeDataList) {
if (node.needsSaving()) return true;
}
return false;
}
}

View File

@@ -36,7 +36,7 @@ public class AboutEditor extends Editor {
"<br/>" +
"Play <a href=\"https://play.google.com/store/apps/details?id=com.gpl.rpg.AndorsTrail\">Andor's Trail</a> for free on your Android device.<br/>" +
"Visit <a href=\"http://andorstrail.com/\">the official forum</a> to give or receive help.<br/>" +
"Open the project's <a href=\"https://code.google.com/p/andors-trail/\">Google Code page</a> to check out the game's source code.<br/>" +
"Open the project's <a href=\"https://github.com/Zukero/andors-trail/\">GitHub project page</a> to check out the game's source code.<br/>" +
"<br/>" +
"For content creation help, make sure to use the following resources:<br/>" +
"<a href=\"http://andorstrail.com/viewtopic.php?f=6&t=4560\">The contribution guide on the forums</a><br/>" +
@@ -72,6 +72,12 @@ public class AboutEditor extends Editor {
"<a href=\"http://www.beanshell.org/\">BeanShell</a> by Pat Niemeyer.<br/>" +
"License: <a href=\"http://www.beanshell.org/license.html\">LGPL v3</a><br/>" +
"<br/>" +
"A slightly modified version of <a href=\"https://github.com/zackehh/siphash-java\">SipHash for Java</a> by Isaac Whitfield.<br/>" +
"License: <a href=\"https://github.com/zackehh/siphash-java/blob/master/LICENSE\">MIT License</a><br/>" +
"<br/>" +
"<a href=\"https://jsoup.org/\">jsoup</a> by Jonathan Hedley<br/>" +
"License: <a href=\"https://jsoup.org/license\">MIT License</a><br/>" +
"<br/>" +
"See the tabs below to find the full license text for each of these.<br/>" +
"<br/>" +
"The Windows installer was created with:<br/>" +
@@ -81,6 +87,7 @@ public class AboutEditor extends Editor {
public static final AboutEditor instance = new AboutEditor();
@SuppressWarnings("resource")
private AboutEditor() {
this.name="About "+ATContentStudio.APP_NAME;
this.icon = new ImageIcon(DefaultIcons.getMainIconIcon());
@@ -120,6 +127,8 @@ public class AboutEditor extends Editor {
editorTabsHolder.add("libtiled-java License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.libtiled.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text"));
editorTabsHolder.add("prefuse License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/license-prefuse.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text"));
editorTabsHolder.add("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("ATCS License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.GPLv3.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text"));
}

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