diff --git a/src/net/torvald/spriteassembler/ADPropertyObject.kt b/src/net/torvald/spriteassembler/ADPropertyObject.kt new file mode 100644 index 000000000..0292bfdb4 --- /dev/null +++ b/src/net/torvald/spriteassembler/ADPropertyObject.kt @@ -0,0 +1,115 @@ +package net.torvald.spriteassembler + +import java.io.InputStream +import java.io.Reader +import java.util.* +import kotlin.collections.HashMap + +class ADProperties { + private val javaProp = Properties() + + /** Every key is CAPITALISED */ + private val propTable = HashMap>() + + constructor(reader: Reader) { + javaProp.load(reader) + continueLoad() + } + + constructor(inputStream: InputStream) { + javaProp.load(inputStream) + continueLoad() + } + + private fun continueLoad() { + javaProp.keys.forEach { propName -> + val propsStr = javaProp.getProperty(propName as String) + val propsList = propsStr.split(';').map { ADPropertyObject(it) } + + propTable[propName.capitalize()] = propsList + } + } + + operator fun get(identifier: String) = propTable[identifier.capitalize()] + val keys + get() = propTable.keys + fun containsKey(key: String) = propTable.containsKey(key) + fun forEach(predicate: (String, List) -> Unit) = propTable.forEach(predicate) +} + +/** + * @param propertyRaw example inputs: + * - ```DELAY 0.15``` + * - ```LEG_RIGHT 0,-1``` + * + * Created by minjaesong on 2019-01-05. + */ +class ADPropertyObject(propertyRaw: String) { + + /** If the input is like ```UPPER_TORSO``` (that is, not a variable-input pair), this holds the string UPPER_TORSO. */ + val variable: String + val input: Any? + get() = when (type) { + ADPropertyType.IVEC2 -> field!! as Vector2i + ADPropertyType.FLOAT -> field!! as Float + else -> null + } + val type: ADPropertyType + + init { + val propPair = propertyRaw.split(variableInputSepRegex) + + if (isADvariable(propertyRaw)) { + variable = propPair[0] + val inputStr = propPair[1]!! + + if (isADivec2(inputStr)) { + type = ADPropertyType.IVEC2 + input = toADivec2(inputStr) + } + else if (isADfloat(inputStr)) { + type = ADPropertyType.FLOAT + input = toADfloat(inputStr) + } + else { + throw IllegalArgumentException("Malformed input, input property seems not ivec2, float nor string: $propertyRaw") + } + } + else { + variable = propertyRaw + input = null + type = ADPropertyType.STRING + } + } + + companion object { + private val floatRegex = Regex("""-?[0-9]+(\.[0-9]*)?""") + private val ivec2Regex = Regex("""-?[0-9]+,-?[0-9]+""") + private val variableInputSepRegex = Regex(""" +""") + + fun isADivec2(s: String) = ivec2Regex.matches(s) + fun isADfloat(s: String) = floatRegex.matches(s) && !ivec2Regex.containsMatchIn(s) + + fun toADivec2(s: String) = if (isADivec2(s)) + Vector2i(s.substringBefore(',').toInt(), s.substringAfter(',').toInt()) + else throw IllegalArgumentException("Input not in ivec2 format: $s") + fun toADfloat(s: String) = if (isADfloat(s)) + s.toFloat() + else throw IllegalArgumentException("Input not in ivec2 format: $s") + + /** example valid input: ```LEG_RIGHT 0,1``` */ + fun isADvariable(property: String) = variableInputSepRegex.containsMatchIn(property) + /** example valid input: ```sprites/test.tga``` */ + fun isADstring(property: String) = !isADvariable(property) + } + + data class Vector2i(var x: Int, var y: Int) + + enum class ADPropertyType { + STRING, IVEC2, FLOAT + } + + override fun toString(): String { + return "$variable ${input ?: ""}: $type" + } +} \ 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 new file mode 100644 index 000000000..4087fc745 --- /dev/null +++ b/src/net/torvald/spriteassembler/ANIMATION_DESCRIPTION_LANGUAGE.md @@ -0,0 +1,97 @@ +## Animation Description Language + +This is a text version of my drawing of same name. 2018-01-04 CuriousTorvald + +Author's node: yet another non-JSON domain-specific language because why not? + +## Objective + +* Java .properties-compatible +* Case insensitive + +## Example code + +``` +SPRITESHEET=sprites/test +EXTENSION=.tga.gz + +ANIM_RUN=DELAY 0.15;ROW 2 +ANIM_RUN_BODYPARTS=HEAD;UPPER_TORSO;LOWER_TORSO;ARM_FWD_LEFT;ARM_FWD_RIGHT;LEG_LEFT;LEG_RIGHT +ANIM_RUN_1=LEG_RIGHT 1,-1;LEG_LEFT -1,0 +ANIM_RUN_2=ALL 0,-1;LEG_RIGHT 0,1;LEG_LEFT 0,-1 +ANIM_RUN_3=LEG_RIGHT -1,0;LEG_LEFT 1,-1 +ANIM_RUN_4=ALL 0,-1;LEG_RIGHT 0,-1;LEG_LEFT 0,1 + +ANIM_IDLE=DELAY 2;ROW 1 +ANIM_IDLE_BODYPARTS=HEAD;UPPER_TORSO;LOWER_TORSO;ARM_REST_LEFT;ARM_REST_RIGHT;LEG_LEFT;LEG_RIGHT +ANIM_IDLE_1= +! ANIM_IDLE_1 will not make any transformation +ANIM_IDLE_2=UPPER_TORSO 0,-1 + +ANIM_CROUCH=DELAY 1;ROW 3 +ANIM_CROUCH_BODYPARTS=HEAD;UPPER_TORSO;LOWER_TORSO;ARM_FWD_LEFT;ARM_FWD_RIGHT;LEG_CROUCH_LEFT;LEG_CROUCH_RIGHT +ANIM_CROUCH_1= +... +``` + +### In-detail + +``` +ANIM_RUN=DELAY 0.15;ROW 2 +``` + +Each line defines one property. A property is a field-value pair. In this code, field is ```ANIM_RUN```, and the value is ```DELAY 0.15;ROW 2``` + +The values are further parsed using ```;``` (semicolon with NO spaces attached) as a separator. + +``` +In this example, ANIM_RUN contains two variables: + +DELAY = 0.15 +ROW = 2 +``` + +A value of the field is consisted of zero or more variable-input pairs. Variable and the input are separated with one or more connected spaces. + +#### Variables + +Variables can have only one of the two types: ```float``` and ```ivec2```. Single integer value ('2' in the ROW) are regarded as a float. + +Float and Ivec2 are determined by: + +* Ivec2: inputs that are matched by the regex ```-?[0-9]+,-?[0-9]+``` (we call this "ivec2 regex") +* Float: inputs that are matched by the regex ```-?[0-9]+(\.[0-9]*)?```, but not even partially matched by the ivec2 regex. + +Any argument to the body parts takes ivec2, to move the parts accordingly. + +#### Just one exception: SPRITESHEET and EXTENSION + +SPRITESHEET and EXTENSION property is not parsed as a property, it's just a single string like the original .properties + +### Naming convention of properties + +If a field is recognised as an animation (in this case ANIM_RUN), the assembler will look for the fields named like ANIM_RUN_1, ANIM_RUN_2, ... , ANIM_RUN_9, ANIM_RUN_10 and beyond. + +### Naming convention of files + +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. + +#### Reserved keywords + +|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| + +### Notes + +* All indices are one-based + +## Operation + +* Each line describes transformation +* Transformation are applied sequentially from left to right. In other words, their order matters. Be wary of the clipping that may occur! +* The Field is an identifier the game code -- sprite assembler -- recognises. +* The Field of animation's name is the name the game code looks for. Example: ```this.setAnim("ANIM_RUN")``` diff --git a/src/net/torvald/terrarum/tests/ADLParsingTest.kt b/src/net/torvald/terrarum/tests/ADLParsingTest.kt new file mode 100644 index 000000000..d59189927 --- /dev/null +++ b/src/net/torvald/terrarum/tests/ADLParsingTest.kt @@ -0,0 +1,49 @@ +package net.torvald.terrarum.tests + +import net.torvald.spriteassembler.ADProperties +import java.io.StringReader + +/** + * Created by minjaesong on 2019-01-05. + */ +class ADLParsingTest { + + val TEST_STR = """ + SPRITESHEET=sprites/test + EXTENSION=.tga.gz + + ANIM_RUN=DELAY 0.15;ROW 2 + ANIM_RUN_BODYPARTS=HEAD;UPPER_TORSO;LOWER_TORSO;ARM_FWD_LEFT;ARM_FWD_RIGHT;LEG_LEFT;LEG_RIGHT + ANIM_RUN_1=LEG_RIGHT 1,-1;LEG_LEFT -1,0 + ANIM_RUN_2=ALL 0,-1;LEG_RIGHT 0,1;LEG_LEFT 0,-1 + ANIM_RUN_3=LEG_RIGHT -1,0;LEG_LEFT 1,-1 + ANIM_RUN_4=ALL 0,-1;LEG_RIGHT 0,-1;LEG_LEFT 0,1 + + ANIM_IDLE=DELAY 2;ROW 1 + ANIM_IDLE_BODYPARTS=HEAD;UPPER_TORSO;LOWER_TORSO;ARM_REST_LEFT;ARM_REST_RIGHT;LEG_LEFT;LEG_RIGHT + ANIM_IDLE_1= + ! ANIM_IDLE_1 will not make any transformation + ANIM_IDLE_2=UPPER_TORSO 0,-1 + + ANIM_CROUCH=DELAY 1;ROW 3 + ANIM_CROUCH_BODYPARTS=HEAD;UPPER_TORSO;LOWER_TORSO;ARM_FWD_LEFT;ARM_FWD_RIGHT;LEG_CROUCH_LEFT;LEG_CROUCH_RIGHT + ANIM_CROUCH_1= + """.trimIndent() + + operator fun invoke() { + val prop = ADProperties(StringReader(TEST_STR)) + + prop.forEach { s, list -> + println(s) + + list.forEach { + println("\t$it") + } + } + } + +} + +fun main(args: Array) { + ADLParsingTest().invoke() +} \ No newline at end of file diff --git a/src/org/dyn4j/geometry/Vector2.kt b/src/org/dyn4j/geometry/Vector2.kt index 3b9724322..e1629da4e 100644 --- a/src/org/dyn4j/geometry/Vector2.kt +++ b/src/org/dyn4j/geometry/Vector2.kt @@ -57,7 +57,7 @@ import org.dyn4j.Epsilon * |a rotate th|Vector2(a).rotate(th)| * |a to b |Vector2(a).to(b) | * - * @author William Bittle + * @author William Bittle and Minjaesong (Torvald) * * * @version 3.1.11 * *