diff --git a/src/net/torvald/spriteassembler/ADProperties.kt b/src/net/torvald/spriteassembler/ADProperties.kt index 451c5174f..86774d0c3 100644 --- a/src/net/torvald/spriteassembler/ADProperties.kt +++ b/src/net/torvald/spriteassembler/ADProperties.kt @@ -3,7 +3,9 @@ package net.torvald.spriteassembler import java.io.InputStream import java.io.Reader import java.util.* +import kotlin.collections.ArrayList import kotlin.collections.HashMap +import kotlin.collections.HashSet class ADProperties { private val javaProp = Properties() @@ -11,6 +13,16 @@ class ADProperties { /** Every key is CAPITALISED */ private val propTable = HashMap>() + /** list of bodyparts used by all the skeletons */ + lateinit var bodyparts: List; private set + /** properties that are being used as skeletons */ + lateinit var skeletons: List; private set + /** properties that are recognised as animations */ + lateinit var animations: List; private set + + private val reservedProps = listOf("SPRITESHEET", "EXTENSION") + private val animMustContain = listOf("DELAY", "ROW", "SKELETON") + constructor(reader: Reader) { javaProp.load(reader) continueLoad() @@ -28,6 +40,38 @@ class ADProperties { propTable[propName.capitalize()] = propsList } + + val bodyparts = HashSet() + val skeletons = HashSet() + val animations = ArrayList() + // populate skeletons and animations + forEach { s, list -> + // linear search. If variable == "SKELETON", the 's' is likely an animation + // and thus, uses whatever the "input" used by the SKELETON is a skeleton + list.forEach { + if (it.variable == "SKELETON") { + skeletons.add(it.input as String) + animations.add(s) + } + } + } + + // populate the bodyparts using skeletons + skeletons.forEach { skeletonName -> + val prop = get(skeletonName) + + if (prop == null) throw Error("The skeleton definition for $skeletonName does not exist") + + prop.forEach { bodypart -> + bodyparts.add(bodypart.variable) + } + } + + + + this.bodyparts = bodyparts.toList().sorted() + this.skeletons = skeletons.toList().sorted() + this.animations = animations.sorted() } operator fun get(identifier: String) = propTable[identifier.capitalize()] @@ -35,6 +79,7 @@ class ADProperties { get() = propTable.keys fun containsKey(key: String) = propTable.containsKey(key) fun forEach(predicate: (String, List) -> Unit) = propTable.forEach(predicate) + } /** @@ -57,6 +102,7 @@ class ADPropertyObject(propertyRaw: String) { } val type: ADPropertyType + init { val propPair = propertyRaw.split(variableInputSepRegex) @@ -115,6 +161,6 @@ class ADPropertyObject(propertyRaw: String) { } override fun toString(): String { - return "$variable ${input ?: ""}: $type" + return "$variable ${input ?: ""}: ${type.toString().toLowerCase()}" } } \ No newline at end of file diff --git a/src/net/torvald/spriteassembler/ANIMATION_DESCRIPTION_LANGUAGE.md b/src/net/torvald/spriteassembler/ANIMATION_DESCRIPTION_LANGUAGE.md index e557012cb..4c1820a25 100644 --- a/src/net/torvald/spriteassembler/ANIMATION_DESCRIPTION_LANGUAGE.md +++ b/src/net/torvald/spriteassembler/ANIMATION_DESCRIPTION_LANGUAGE.md @@ -76,14 +76,26 @@ If a field is recognised as an animation (in this case ANIM_RUN), the assembler If the animation specifies a "body part" (in this example LEG_LEFT and LEG_RIGHT), the assembler will look for a file ```sprites/test_leg_left.tga.gz``` and ```sprites/test_leg_right.tga.gz``` respectively. Filenames are advised to be kept all lowercase. -#### Reserved keywords +### Reserved keywords + +These values must exist so that the file can be parsed successfully. + +#### Root |Name|Type|Meaning| |---|---|---| -|SPRITESHEET|property/string|base file name of the images| -|EXTENSION|property/string|extension of the base file| -|DELAY|variable: float|delay between frames, in seconds| -|ROW|vareable: float|which row the animation goes in the spritesheet| +|SPRITESHEET|properties: NAME_ONLY|Base file name of the images| +|EXTENSION|properties: NAME_ONLY|Extension of the base file| + +#### Animation + +Remember that 'variables' are contained within 'properties' + +|Name|Type|Meaning| +|---|---|---| +|DELAY|variable: float|Delay between frames, in seconds| +|ROW|variable: float|which row the animation goes in the spritesheet| +|SKELETON|variable: string_pair|Which skeleton this animation uses ### Notes diff --git a/src/net/torvald/spriteassembler/SpriteAssemblerApp.java b/src/net/torvald/spriteassembler/SpriteAssemblerApp.java deleted file mode 100644 index 9ea1eb7e9..000000000 --- a/src/net/torvald/spriteassembler/SpriteAssemblerApp.java +++ /dev/null @@ -1,74 +0,0 @@ -package net.torvald.spriteassembler; - -import javax.imageio.ImageIO; -import javax.swing.*; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; - -/** - * Created by minjaesong on 2019-01-05. - */ -public class SpriteAssemblerApp extends JFrame { - - private ImagePanel panelPreview = new ImagePanel(); - private JTree panelProperties = new JTree(); - private JList panelBodypartsList = new JList(); - private JTextPane panelCode = new JTextPane(); - private JTextArea statBar = new JTextArea("Null."); - - - - public SpriteAssemblerApp() { - JSplitPane panelDataView = new JSplitPane(JSplitPane.VERTICAL_SPLIT, new JScrollPane(panelProperties), new JScrollPane(panelBodypartsList)); - - JSplitPane panelTop = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(panelPreview), panelDataView); - JSplitPane panelMain = new JSplitPane(JSplitPane.VERTICAL_SPLIT, panelTop, new JScrollPane(panelCode)); - - JMenuBar menu = new JMenuBar(); - menu.add(new JMenu("File")); - menu.add(new JMenu("Parse")).addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - System.out.println("Hello"); - } - }); - menu.add(new JMenu("Run")); - - this.setLayout(new BorderLayout()); - this.add(menu, BorderLayout.NORTH); - this.add(panelMain, BorderLayout.CENTER); - this.add(statBar, BorderLayout.SOUTH); - this.setTitle("Terrarum Sprite Assembler and Viewer"); - this.setVisible(true); - this.setSize(1154, 768); - this.setDefaultCloseOperation(EXIT_ON_CLOSE); - } - - public static void main(String[] args) { - new SpriteAssemblerApp(); - } -} - -class ImagePanel extends JPanel { - - private BufferedImage image; - - public ImagePanel() { - try { - image = ImageIO.read(new File("image name and path")); - } catch (IOException ex) { - // handle exception... - } - } - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - g.drawImage(image, 0, 0, this); // see javadoc for more info on the parameters - } - -} \ No newline at end of file diff --git a/src/net/torvald/spriteassembler/SpriteAssemblerApp.kt b/src/net/torvald/spriteassembler/SpriteAssemblerApp.kt new file mode 100644 index 000000000..9d3fabcfe --- /dev/null +++ b/src/net/torvald/spriteassembler/SpriteAssemblerApp.kt @@ -0,0 +1,210 @@ +package net.torvald.spriteassembler + +import java.awt.BorderLayout +import java.awt.Graphics +import java.awt.event.MouseAdapter +import java.awt.event.MouseEvent +import java.awt.image.BufferedImage +import java.io.File +import java.io.IOException +import java.io.StringReader +import java.util.* +import javax.imageio.ImageIO +import javax.swing.* +import javax.swing.tree.DefaultMutableTreeNode +import javax.swing.tree.DefaultTreeModel + +/** + * Created by minjaesong on 2019-01-05. + */ +class SpriteAssemblerApp : JFrame() { + + private val panelPreview = ImagePanel() + private val panelProperties = JTree() + private val panelAnimationsList = JList() + private val panelBodypartsList = JList() + private val panelSkeletonsList = JList() + private val panelCode = JTextPane() + private val statBar = JTextArea("Null.") + + private var adProperties: ADProperties? = null + + private val props = Properties() + private val lang = Properties() + + private val captionProperties = "" + // dummy string to make IDE happy with the auto indent + + "id=ID of this block\n" + + "drop=ID of the block this very block should drop when mined\n" + + "name=String identifier of the block\n" + + "shdr=Shade Red (light absorption). Valid range 0.0-4.0\n" + + "shdg=Shade Green (light absorption). Valid range 0.0-4.0\n" + + "shdb=Shade Blue (light absorption). Valid range 0.0-4.0\n" + + "shduv=Shade UV (light absorbtion). Valid range 0.0-4.0\n" + + "lumr=Luminosity Red (light intensity). Valid range 0.0-4.0\n" + + "lumg=Luminosity Green (light intensity). Valid range 0.0-4.0\n" + + "lumb=Luminosity Blue (light intensity). Valid range 0.0-4.0\n" + + "lumuv=Luminosity UV (light intensity). Valid range 0.0-4.0\n" + + "str=Strength of the block\n" + + "dsty=Density of the block. Water have 1000 in the in-game scale\n" + + "mate=Material of the block\n" + + "solid=Whether the file has full collision\n" + + "plat=Whether the block should behave like a platform\n" + + "wall=Whether the block can be used as a wall\n" + + "fall=Whether the block should fall through the empty space\n" + + "dlfn=Dynamic Light Function. 0=Static. Please see notes\n" + + "fv=Vertical friction when player slide on the cliff. 0 means not slide-able\n" + + "fr=Horizontal friction. <16:slippery 16:regular >16:sticky\n" + + /** + * ¤ is used as a \n marker + */ + private val translations = "" + + "WARNING_CONTINUE=Continue?\n" + + "WARNING_YOUR_DATA_WILL_GONE=Existing edits will be lost.\n" + + "OPERATION_CANCELLED=Operation cancelled.\n" + + "NO_SUCH_FILE=No such file exists, operation cancelled.\n" + + "NEW_ROWS=Enter the number of rows to initialise the new CSV.¤Remember, you can always add or delete rows later.\n" + + "ADD_ROWS=Enter the number of rows to add:\n" + + "WRITE_FAIL=Writing to file has failed:\n" + + "STAT_INIT=Creating a new CSV. You can still open existing file.\n" + + "STAT_SAVE_SUCCESSFUL=File saved successfully.\n" + + "STAT_NEW_FILE=New CSV created.\n" + + "STAT_LOAD_SUCCESSFUL=File loaded successfully.\n" + + "ERROR_INTERNAL=Something went wrong.\n" + + "ERROR_PARSE_FAIL=Parsing failed\n" + + "SPRITE_DEF_LOAD_SUCCESSFUL=Sprite definition loaded." + + init { + // setup application properties // + try { + props.load(StringReader(captionProperties)) + lang.load(StringReader(translations)) + } + catch (e: Throwable) { + + } + + + panelAnimationsList.model = DefaultListModel() + panelBodypartsList.model = DefaultListModel() + panelSkeletonsList.model = DefaultListModel() + + val panelPartsList = JTabbedPane(JTabbedPane.TOP) + panelPartsList.add("Animations", JScrollPane(panelAnimationsList)) + panelPartsList.add("Bodyparts", JScrollPane(panelBodypartsList)) + panelPartsList.add("Skeletons", JScrollPane(panelSkeletonsList)) + + + val panelDataView = JSplitPane(JSplitPane.VERTICAL_SPLIT, JScrollPane(panelProperties), panelPartsList) + + val panelTop = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, JScrollPane(panelPreview), panelDataView) + val panelMain = JSplitPane(JSplitPane.VERTICAL_SPLIT, panelTop, JScrollPane(panelCode)) + + val menu = JMenuBar() + menu.add(JMenu("File")) + menu.add(JMenu("Parse")).addMouseListener(object : MouseAdapter() { + override fun mousePressed(e: MouseEvent?) { + try { + adProperties = ADProperties(StringReader(panelCode.text)) + statBar.text = lang.getProperty("SPRITE_DEF_LOAD_SUCCESSFUL") + + val propRoot = DefaultMutableTreeNode("Properties") + + adProperties?.forEach { s, list -> + // build tree node for the properties display + val propNode = DefaultMutableTreeNode(s) + propRoot.add(propNode) + list.forEach { + propNode.add(DefaultMutableTreeNode(it.toString())) + } + } + + panelProperties.model = DefaultTreeModel(propRoot) + + // clean the data views + panelAnimationsList.model = DefaultListModel() + panelBodypartsList.model = DefaultListModel() + panelSkeletonsList.model = DefaultListModel() + + // populate animations view + adProperties!!.animations.forEach { + (panelAnimationsList.model as DefaultListModel).addElement(it) + } + // populate bodyparts view + adProperties!!.bodyparts.forEach { + (panelBodypartsList.model as DefaultListModel).addElement(it) + } + // populate skeletons view + adProperties!!.skeletons.forEach { + (panelSkeletonsList.model as DefaultListModel).addElement(it) + } + } + catch (fehler: Throwable) { + displayError("ERROR_PARSE_FAIL", fehler) + fehler.printStackTrace() + } + + } + }) + menu.add(JMenu("Run")) + + this.layout = BorderLayout() + this.add(menu, BorderLayout.NORTH) + this.add(panelMain, BorderLayout.CENTER) + this.add(statBar, BorderLayout.SOUTH) + this.title = "Terrarum Sprite Assembler and Viewer" + this.isVisible = true + this.setSize(1154, 768) + this.defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE + } + + private fun displayMessage(messageKey: String) { + JOptionPane.showOptionDialog( + null, + lang.getProperty(messageKey), null, + JOptionPane.DEFAULT_OPTION, + JOptionPane.INFORMATION_MESSAGE, null, + arrayOf("OK", "Cancel"), + "Cancel" + ) + } + + private fun displayError(messageKey: String, cause: Throwable) { + JOptionPane.showOptionDialog(null, + lang.getProperty(messageKey) + "\n" + cause.toString(), null, + JOptionPane.DEFAULT_OPTION, + JOptionPane.ERROR_MESSAGE, null, + arrayOf("OK", "Cancel"), + "Cancel" + ) + } + + companion object { + @JvmStatic + fun main(args: Array) { + SpriteAssemblerApp() + } + } +} + +internal class ImagePanel : JPanel() { + + private var image: BufferedImage? = null + + init { + try { + image = ImageIO.read(File("image name and path")) + } + catch (ex: IOException) { + // handle exception... + } + + } + + override fun paintComponent(g: Graphics) { + super.paintComponent(g) + g.drawImage(image, 0, 0, this) // see javadoc for more info on the parameters + } + +} \ No newline at end of file