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.
This commit is contained in:
Zukero
2017-02-28 18:43:47 +01:00
parent 41462137d6
commit 061a0fa11b
14 changed files with 369 additions and 15 deletions

View File

@@ -850,6 +850,34 @@ public class Project implements ProjectTreeNode, Serializable {
}
}
/**
*
* @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>());

View File

@@ -86,7 +86,7 @@ public class ActorCondition extends JSONElement {
@Override
public String getDesc() {
return (this.state == State.modified ? "*" : "")+display_name+" ("+id+")";
return ((this.state == State.modified || this.state == State.created) ? "*" : "")+display_name+" ("+id+")";
}
@SuppressWarnings("rawtypes")

View File

@@ -18,6 +18,7 @@ import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.model.GameSource;
import com.gpl.rpg.atcontentstudio.model.Project;
import com.gpl.rpg.atcontentstudio.model.GameDataElement.State;
import com.gpl.rpg.atcontentstudio.model.gamedata.Requirement.RequirementType;
import com.gpl.rpg.atcontentstudio.model.maps.TMXMap;
import com.gpl.rpg.atcontentstudio.ui.DefaultIcons;
@@ -91,7 +92,7 @@ public class Dialogue extends JSONElement {
@Override
public String getDesc() {
return (this.state == State.modified ? "*" : "")+id;
return ((this.state == State.modified || this.state == State.created) ? "*" : "")+id;
}
public static String getStaticDesc() {

View File

@@ -17,6 +17,7 @@ import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.model.GameSource;
import com.gpl.rpg.atcontentstudio.model.Project;
import com.gpl.rpg.atcontentstudio.model.GameDataElement.State;
import com.gpl.rpg.atcontentstudio.ui.DefaultIcons;
@@ -46,7 +47,7 @@ public class Droplist extends JSONElement {
@Override
public String getDesc() {
return (this.state == State.modified ? "*" : "")+id;
return ((this.state == State.modified || this.state == State.created) ? "*" : "")+id;
}
public static String getStaticDesc() {

View File

@@ -17,6 +17,7 @@ import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.model.GameSource;
import com.gpl.rpg.atcontentstudio.model.Project;
import com.gpl.rpg.atcontentstudio.model.GameDataElement.State;
public class Item extends JSONElement {
@@ -101,7 +102,7 @@ public class Item extends JSONElement {
@Override
public String getDesc() {
return (this.state == State.modified ? "*" : "")+name+" ("+id+")";
return ((this.state == State.modified || this.state == State.created) ? "*" : "")+name+" ("+id+")";
}
public static String getStaticDesc() {

View File

@@ -17,6 +17,7 @@ import org.json.simple.parser.ParseException;
import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.model.GameSource;
import com.gpl.rpg.atcontentstudio.model.GameDataElement.State;
public class ItemCategory extends JSONElement {
@@ -99,7 +100,7 @@ public class ItemCategory extends JSONElement {
@Override
public String getDesc() {
return (this.state == State.modified ? "*" : "")+name+" ("+id+")";
return ((this.state == State.modified || this.state == State.created) ? "*" : "")+name+" ("+id+")";
}
public static String getStaticDesc() {

View File

@@ -17,6 +17,7 @@ import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.model.GameSource;
import com.gpl.rpg.atcontentstudio.model.Project;
import com.gpl.rpg.atcontentstudio.model.GameDataElement.State;
public class NPC extends JSONElement {
@@ -95,7 +96,7 @@ public class NPC extends JSONElement {
@Override
public String getDesc() {
return (this.state == State.modified ? "*" : "")+name+" ("+id+")";
return ((this.state == State.modified || this.state == State.created) ? "*" : "")+name+" ("+id+")";
}
public static String getStaticDesc() {

View File

@@ -16,6 +16,7 @@ import org.json.simple.parser.ParseException;
import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.model.GameSource;
import com.gpl.rpg.atcontentstudio.model.GameDataElement.State;
import com.gpl.rpg.atcontentstudio.ui.DefaultIcons;
public class Quest extends JSONElement {
@@ -48,7 +49,7 @@ public class Quest extends JSONElement {
@Override
public String getDesc() {
return (this.state == State.modified ? "*" : "")+name+" ("+id+")";
return ((this.state == State.modified || this.state == State.created) ? "*" : "")+name+" ("+id+")";
}
public static String getStaticDesc() {

View File

@@ -197,7 +197,7 @@ public class TMXMap extends GameDataElement {
}
@Override
public String getDesc() {
return (this.state == State.modified ? "*" : "")+id;
return ((this.state == State.modified || this.state == State.created) ? "*" : "")+id;
}
@Override
@@ -309,8 +309,10 @@ public class TMXMap extends GameDataElement {
parse();
}
if (this.state == GameDataElement.State.parsed || this.state == GameDataElement.State.created) {
for (MapObjectGroup group : groups) {
group.link();
if (groups != null) {
for (MapObjectGroup group : groups) {
group.link();
}
}
}
}

View File

@@ -202,6 +202,10 @@ public class ProjectsTree extends JPanel {
addNextSeparator = true;
popupMenu.add(new JMenuItem(actions.importJSON));
}
if (actions.createMap.isEnabled()) {
addNextSeparator = true;
popupMenu.add(new JMenuItem(actions.createMap));
}
if (actions.createWorldmap.isEnabled()) {
addNextSeparator = true;
popupMenu.add(new JMenuItem(actions.createWorldmap));

View File

@@ -140,6 +140,7 @@ public class StudioFrame extends JFrame {
projectMenu.add(new JSeparator());
projectMenu.add(new JMenuItem(actions.createGDE));
projectMenu.add(new JMenuItem(actions.importJSON));
projectMenu.add(new JMenuItem(actions.createMap));
projectMenu.add(new JMenuItem(actions.createWorldmap));
projectMenu.add(new JMenuItem(actions.loadSave));
getJMenuBar().add(projectMenu);

View File

@@ -0,0 +1,299 @@
package com.gpl.rpg.atcontentstudio.ui;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ButtonGroup;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListDataListener;
import com.gpl.rpg.atcontentstudio.ATContentStudio;
import com.gpl.rpg.atcontentstudio.model.GameDataElement.State;
import com.gpl.rpg.atcontentstudio.model.GameSource;
import com.gpl.rpg.atcontentstudio.model.Project;
import com.gpl.rpg.atcontentstudio.model.maps.TMXMap;
import com.jidesoft.swing.JideBoxLayout;
public class TMXMapCreationWizard extends JDialog {
private static final long serialVersionUID = -474689694453543575L;
private static final String DEFAULT_TEMPLATE = "template.tmx";
private TMXMap creation = null;
final File templateFile;
final JLabel message;
final JRadioButton useTemplate, copyMap;
final JComboBox<TMXMap> templateCombo;
final JTextField idField;
final JButton ok;
final Project proj;
@SuppressWarnings({ "unchecked", "rawtypes" })
public TMXMapCreationWizard(final Project proj) {
super(ATContentStudio.frame);
this.proj = proj;
templateFile=new File(proj.baseContent.gameMaps.mapFolder, DEFAULT_TEMPLATE);
setTitle("Create new TMX map");
JPanel pane = new JPanel();
pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS, 6));
pane.add(new JLabel("Create a new TMX map."), JideBoxLayout.FIX);
message = new JLabel("Enter new map name:");
pane.add(message, JideBoxLayout.FIX);
final JPanel idPane = new JPanel();
idPane.setLayout(new BorderLayout());
JLabel idLabel = new JLabel("Internal ID: ");
idPane.add(idLabel, BorderLayout.WEST);
idField = new JTextField("");
idField.setEditable(true);
idPane.add(idField, BorderLayout.CENTER);
pane.add(idPane, JideBoxLayout.FIX);
useTemplate = new JRadioButton("Use default template file ("+DEFAULT_TEMPLATE+")");
useTemplate.setToolTipText(templateFile.getAbsolutePath());
pane.add(useTemplate, JideBoxLayout.FIX);
copyMap = new JRadioButton("Copy existing map");
pane.add(copyMap, JideBoxLayout.FIX);
ButtonGroup radioGroup = new ButtonGroup();
radioGroup.add(useTemplate);
radioGroup.add(copyMap);
final JPanel templatePane = new JPanel();
templatePane.setLayout(new BorderLayout());
JLabel templateLabel = new JLabel("Template to copy: ");
templatePane.add(templateLabel, BorderLayout.WEST);
templateCombo = new JComboBox(new TemplateComboModel());
templateCombo.setRenderer(new TemplateComboCellRenderer());
if (proj.getMap(DEFAULT_TEMPLATE) != null) templateCombo.setSelectedItem(proj.getMap(DEFAULT_TEMPLATE));
templatePane.add(templateCombo, BorderLayout.CENTER);
pane.add(templatePane, JideBoxLayout.FIX);
pane.add(templateCombo);
if (templateFile.exists()) {
useTemplate.setSelected(true);
copyMap.setSelected(false);
templateCombo.setEnabled(false);
} else {
useTemplate.setSelected(false);
useTemplate.setEnabled(false);
useTemplate.setToolTipText("Cannot find file "+templateFile.getAbsolutePath());
templateCombo.setEnabled(true);
copyMap.setSelected(true);
}
ActionListener radioListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (useTemplate.isSelected()) {
templateCombo.setEnabled(false);
} else if(copyMap.isSelected()) {
templateCombo.setEnabled(true);
}
updateStatus();
}
};
useTemplate.addActionListener(radioListener);
copyMap.addActionListener(radioListener);
templateCombo.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateStatus();
}
});
pane.add(new JPanel(), JideBoxLayout.VARY);
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new JideBoxLayout(buttonPane, JideBoxLayout.LINE_AXIS, 6));
buttonPane.add(new JPanel(), JideBoxLayout.VARY);
JButton cancel = new JButton("Cancel");
buttonPane.add(cancel, JideBoxLayout.FIX);
ok = new JButton("Ok");
buttonPane.add(ok, JideBoxLayout.FIX);
pane.add(new JPanel(), JideBoxLayout.VARY);
pane.add(buttonPane, JideBoxLayout.FIX);
ok.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (copyMap.isSelected()) {
creation = ((TMXMap)templateCombo.getSelectedItem()).clone();
} else if (useTemplate.isSelected()) {
creation = new TMXMap(proj.createdContent.gameMaps, templateFile);
creation.parse();
}
creation.id = idField.getText();
creation.tmxFile = new File(creation.id+".tmx");
TMXMapCreationWizard.this.setVisible(false);
TMXMapCreationWizard.this.dispose();
creation.state = State.created;
proj.createElement(creation);
notifyCreated();
ATContentStudio.frame.selectInTree(creation);
ATContentStudio.frame.openEditor(creation);
}
});
cancel.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
creation = null;
TMXMapCreationWizard.this.setVisible(false);
TMXMapCreationWizard.this.dispose();
}
});
DocumentListener statusUpdater = new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
updateStatus();
}
@Override
public void insertUpdate(DocumentEvent e) {
updateStatus();
}
@Override
public void changedUpdate(DocumentEvent e) {
updateStatus();
}
};
idField.getDocument().addDocumentListener(statusUpdater);
getContentPane().setLayout(new BorderLayout());
getContentPane().add(pane, BorderLayout.CENTER);
setMinimumSize(new Dimension(350,250));
updateStatus();
pack();
Dimension sdim = Toolkit.getDefaultToolkit().getScreenSize();
Dimension wdim = getSize();
setLocation((sdim.width - wdim.width)/2, (sdim.height - wdim.height)/2);
}
public void updateStatus() {
boolean trouble = false;
message.setText("<html><font color=\"#00AA00\">Looks OK to me.</font></html>");
if (copyMap.isSelected() && templateCombo.getSelectedItem() == null) {
message.setText("<html><font color=\"#FF0000\">Select a map template below:</font></html>");
trouble = true;
} else if (idField.getText() == null || idField.getText().length() <= 0) {
message.setText("<html><font color=\"#FF0000\">Internal ID must not be empty.</font></html>");
trouble = true;
} else if (proj.getMap(idField.getText()) != null) {
if (proj.getMap(idField.getText()).getDataType() == GameSource.Type.created) {
message.setText("<html><font color=\"#FF0000\">A map with the same ID was already created in this project.</font></html>");
trouble = true;
} else if (proj.getMap(idField.getText()).getDataType() == GameSource.Type.altered) {
message.setText("<html><font color=\"#FF0000\">A map with the same ID exists in the game and is already altered in this project.</font></html>");
trouble = true;
} else if (proj.getMap(idField.getText()).getDataType() == GameSource.Type.source) {
message.setText("<html><font color=\"#FF9000\">A map with the same ID exists in the game. The new one will be added under \"altered\".</font></html>");
}
}
ok.setEnabled(!trouble);
message.revalidate();
message.repaint();
}
public static interface CreationCompletedListener {
public void mapCreated(TMXMap created);
}
private List<CreationCompletedListener> listeners = new ArrayList<TMXMapCreationWizard.CreationCompletedListener>();
public void addCreationListener(CreationCompletedListener l) {
listeners.add(l);
}
public void notifyCreated() {
for (CreationCompletedListener l : listeners) {
l.mapCreated(creation);
}
}
class TemplateComboModel implements ComboBoxModel<TMXMap> {
Object selected;
@Override
public int getSize() {
return proj.getMapCount();
}
@Override
public TMXMap getElementAt(int index) {
return proj.getMap(index);
}
List<ListDataListener> listeners = new ArrayList<ListDataListener>();
@Override
public void addListDataListener(ListDataListener l) {
listeners.add(l);
}
@Override
public void removeListDataListener(ListDataListener l) {
listeners.remove(l);
}
@Override
public void setSelectedItem(Object anItem) {
selected = anItem;
}
@Override
public Object getSelectedItem() {
return selected;
}
}
class TemplateComboCellRenderer extends DefaultListCellRenderer {
private static final long serialVersionUID = 5621373849299980998L;
@Override
public Component getListCellRendererComponent(@SuppressWarnings("rawtypes") JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (c instanceof JLabel && value != null) {
((JLabel)c).setText(((TMXMap)value).getDesc());
((JLabel)c).setIcon(new ImageIcon(DefaultIcons.getTiledIconIcon()));
}
return c;
}
}
}

View File

@@ -226,7 +226,16 @@ public class WorkspaceActions {
}
};
public ATCSAction createMap = new ATCSAction("Create TMX Map", "Opens the TMX Map creation wizard") {
public void actionPerformed(ActionEvent e) {
if (selectedNode == null || selectedNode.getProject() == null) return;
new TMXMapCreationWizard(selectedNode.getProject()).setVisible(true);
}
public void selectionChanged(ProjectTreeNode selectedNode, TreePath[] selectedPaths) {
setEnabled(selectedNode != null && selectedNode.getProject() != null);
}
};
public ATCSAction createWorldmap = new ATCSAction("Create Worldmap segment", "Opens the worldmap segment creation wizard") {
public void actionPerformed(ActionEvent e) {
if (selectedNode == null || selectedNode.getProject() == null) return;
@@ -389,6 +398,7 @@ public class WorkspaceActions {
actions.add(saveElement);
actions.add(deleteSelected);
actions.add(createGDE);
actions.add(createMap);
actions.add(importJSON);
actions.add(loadSave);
actions.add(compareItems);

View File

@@ -69,8 +69,8 @@ public class SpritesheetEditor extends Editor {
public static JComponent getWarningLabel() {
JLabel label = new JLabel(
"<html><i>" +
"The data presented here is not part of the game.<br/>" +
"What you change here will be changed in your ATCS project.<br/>" +
"The data accompamying the image here is not part of the game.<br/>" +
"What you change here will be changed in your ATCS project only.<br/>" +
"None of this is exported to JSON or TMX, although it must be set correctly in order to choose tiles & icons correctly.<br/>" +
"</i></html>");
return label;
@@ -88,7 +88,8 @@ public class SpritesheetEditor extends Editor {
pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS, 6));
add(getWarningLabel(), JideBoxLayout.FIX);
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new JideBoxLayout(buttonPane, JideBoxLayout.LINE_AXIS));
JButton openImage = new JButton(new ImageIcon(DefaultIcons.getTileLayerImage()));
openImage.addActionListener(new ActionListener() {
@Override
@@ -96,7 +97,10 @@ public class SpritesheetEditor extends Editor {
DesktopIntegration.openImage(((Spritesheet)target).spritesheetFile);
}
});
pane.add(openImage, JideBoxLayout.FIX);
buttonPane.add(openImage, JideBoxLayout.FIX);
buttonPane.add(getWarningLabel(), JideBoxLayout.FIX);
buttonPane.add(new JPanel(), JideBoxLayout.VARY);
pane.add(buttonPane, JideBoxLayout.FIX);
addLabelField(pane, "Spritesheet ID: ", sheet.id);
addLabelField(pane, "File: ", sheet.spritesheetFile.getAbsolutePath());
widthField = addIntegerField(pane, "Sprite width (px): ", sheet.spriteWidth, false, true, listener);