Progress on WriterMode. Project export package now create

deterministically-ordered JSON. Should make nicer diffs.
This commit is contained in:
Zukero
2016-12-15 19:16:53 +01:00
parent 32711449b2
commit 6ac332834d
55 changed files with 248 additions and 182 deletions

View File

@@ -0,0 +1,453 @@
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.Collection;
import java.util.LinkedHashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.model.Project;
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;
//Available from state init.
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;
}
public WriterModeData(WriterModeDataSet parent, @SuppressWarnings("rawtypes") Map jsonObj) {
this.parent = parent;
this.begin = new WriterDialogue(jsonObj);
this.id = begin.id_prefix;
this.state = State.parsed;
}
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();
@SuppressWarnings("rawtypes")
public abstract Map toJson();
}
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 WriterDialogue() {}
public WriterDialogue(String id_prefix) {
text = "";
this.id_prefix = id_prefix;
index = getNextIndex(id_prefix);
}
@Override
public String getTitle() {
return "Dialogue "+id_prefix+index;
}
public String getID() {
return this.id != null ? this.id : this.id_prefix+this.index;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public Map toJson() {
Map dialogueJson = new LinkedHashMap();
dialogueJson.put("id", id);
dialogueJson.put("id_prefix", id_prefix);
dialogueJson.put("index", index);
dialogueJson.put("text", text);
dialogueJson.put("special", isSpecial());
if (!replies.isEmpty()) {
List repliesJson = new ArrayList();
for (WriterReply reply : replies) {
repliesJson.add(reply.toJson());
}
dialogueJson.put("replies", repliesJson);
}
return dialogueJson;
}
@SuppressWarnings("rawtypes")
public WriterDialogue(Map json) {
this.id = (String) json.get("id");
this.index = Integer.parseInt((String) json.get("index"));
this.id_prefix = (String) json.get("id_prefix");
this.text = (String) json.get("text");
List repliesJson = (List) json.get("replies");
for (Object rJson : repliesJson) {
if (Boolean.parseBoolean((String)((Map)rJson).get("special"))) {
//TODO Check different cases. But there are none currently.
this.replies.add(new EmptyReply(this, ((Map)rJson)));
}
this.replies.add(new WriterReply(this, (Map)rJson));
}
}
public boolean isSpecial() {return false;}
public Dialogue toDialogue(Map<WriterDialogue, Dialogue> visited){
if (visited.get(this) != null) return visited.get(this);
Dialogue dialogue = new Dialogue();
dialogue.state = GameDataElement.State.parsed;
visited.put(this, dialogue);
dialogue.id = getID();
dialogue.message = this.text;
if (this.replies != null && !this.replies.isEmpty()) {
dialogue.replies = new ArrayList<Dialogue.Reply>();
for (WriterReply wReply : this.replies) {
dialogue.replies.add(wReply.toReply(visited));
}
}
return dialogue;
}
}
public abstract class SpecialDialogue extends WriterDialogue {
public boolean isSpecial() {return true;}
public abstract SpecialDialogue duplicate();
}
public class SelectorDialogue extends SpecialDialogue {
public SpecialDialogue duplicate() {return new SelectorDialogue();}
}
public class ShopDialogue extends SpecialDialogue {
public static final String id = "S";
public SpecialDialogue duplicate() {return new ShopDialogue();}
}
public class FightDialogue extends SpecialDialogue {
public static final String id = "F";
public SpecialDialogue duplicate() {return new FightDialogue();}
}
public class EndDialogue extends SpecialDialogue {
public static final String id = "X";
public SpecialDialogue duplicate() {return new EndDialogue();}
}
public class RemoveNPCDialogue extends SpecialDialogue {
public static final String id = "R";
public SpecialDialogue duplicate() {return new RemoveNPCDialogue();}
}
public class WriterReply extends WriterNode {
public WriterDialogue parent;
public String next_dialogue_id;
public WriterDialogue next_dialogue;
public WriterReply() {}
public WriterReply(WriterDialogue parent) {
this.parent = parent;
this.text = "";
parent.replies.add(this);
}
@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" })
@Override
public Map toJson() {
Map replyJson = new LinkedHashMap();
replyJson.put("text", text);
replyJson.put("special", isSpecial());
if (next_dialogue != null) {
replyJson.put("next_dialogue_id", next_dialogue.id);
}
return replyJson;
}
public boolean isSpecial() {return false;}
public Dialogue.Reply toReply(Map<WriterDialogue, Dialogue> visited) {
Dialogue.Reply reply = new Dialogue.Reply();
reply.text = this.text;
if (this.next_dialogue != null) {
this.next_dialogue.toDialogue(visited);
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 = "X";
}
return reply;
}
}
public class SpecialReply extends WriterReply {
public boolean isSpecial() {return true;}
public SpecialReply(WriterDialogue parent) {
super(parent);
}
public SpecialReply(WriterDialogue parent, Map json) {
super(parent, json);
}
}
public class EmptyReply extends SpecialReply {
public EmptyReply(WriterDialogue parent) {
super(parent);
text="N";
}
public EmptyReply(WriterDialogue parent, Map json) {
super(parent, json);
text="N";
}
}
@Override
public String getDesc() {
return (this.state == State.modified ? "*" : "")+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")
public Map toJson() {
return begin.toJson();
}
@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.id, 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();
} else if (this.state == State.parsed) {
for (WriterDialogue dialogue : nodesById.values()) {
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);
}
}
}
}
for (String rootId : rootsId) {
roots.add(nodesById.get(rootId));
}
} else 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 Collection<Dialogue> toDialogue(){
Map<WriterModeData.WriterDialogue, Dialogue> visited = new LinkedHashMap<WriterModeData.WriterDialogue, Dialogue>();
begin.toDialogue(visited);
return visited.values();
}
}

View File

@@ -0,0 +1,261 @@
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.ArrayList;
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.GameDataElement.State;
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;
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;
}
@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 "Dialogue sketchs";
}
@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.state == State.created || data.state == State.modified) {
events.add(new SaveEvent(SaveEvent.Type.alsoSave, data));
}
}
return events;
}
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;
writerModeDataList.add(new WriterModeData(this, jsonObj));
}
} 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.parent = this;
if (higherEmptyParent != null) higherEmptyParent.notifyCreated();
else node.notifyCreated();
}
}