proj_format.md 45 KB

TirNanoG Project Format - Revision 0

[[TOC]]

Overall Directory Structure

All files stored under the TirNanoG directory in your home folder (on Linux, /home/(user)/TirNanoG and on Windows C:\Users\(user)\TirNanoG). Projects are separated in subdirectories called (gameid). The directory structure looks like this:

~/TirNanoG
 |- /(gameid)           each project has a separate directory
 |    |- /backs         (probably large) fullscreen background images in PNG (.png)
 |    |- /chars         directory to hold the character (player, NPC and equipted objects) sprites in PNG (.png)
 |    |- /ext           extension configurations (.cfg)
 |    |- /fonts         fonts in SSFN (.sfn) format
 |    |- /game          character attribute (.atr), character options (.opt) definitions and other config (.cfg) files
 |    |- /npcs          NPC definitons (.npc, _npc.png) and spawner (.swr, _swr.png)
 |    |- /dialogs       conversation dialog definitons (.dlg) and object crafting dialogs (.cft)
 |    |- /maps          the map definitons (.tmx) in a Tiled compatible format
 |    |- /movies        movies in Theora Vorbis (.ogv) format
 |    |- /music         stereo or surround audio files that contain only instrumental music in Vorbis (.ogg) format
 |    |- /objects       object meta definitions (.obj)
 |    |- /portraits     this directory holds the NPC portraits in PNG (.png) format
 |    |- /quests        quests definitions (.qst)
 |    |- /sounds        mono channel audio files of sound effects in Vorbis (.ogg) format
 |    |- /speech        mono channel audio files of localized voices in Vorbis (.ogg) format
 |    |- /tiles         this directory contains the sprites in PNG (.png) format and definitions (.til)
 |    |- /ui            user interface images (.png), definitions (.cfg), choosers (.chr) and cutscenes (.cut)
 |    |- config         the game's configuration file (plain text)
 |    |- game.tng       the production ready output for the TirNanoG Player
 |    |- (name).lnk     a game launcher shortcut link for Windows users (ship it to end users along with game.tng)
 |    \- (name).desktop a game launcher shortcut link for Linux users (ship it to end users along with game.tng)
 |- /(another game)     more project directory may exist
 |    | ...
 |    |- config         the game's configuration file (plain text)
 |    |- extension.tng  optional game extension for the TirNanoG Player
 |    |- license.txt    if you have an encrypted game file creation license, it has to be here
 |    \- endusers.txt   the licenses (decryption keys) generated for your users
 |- /.common            the palettes (.gpl), layout definitions (.lyt) and sprite generator files (.png) common to all projects
 |- (template1).zip     project templates are stored here as zip files
 \- (templateN).zip     there can be more templates

You are free to modify these files outside of the editor as long as you keep a few rules in mind: filenames must only contain a-zA-Z0-9_. characters, and they are sometimes suffixed (see bellow). There's no additional database, the editor picks up modified and new files automatically.

All configuration stored in plain text files in which the first line is a magic. Line endings can be both UNIX NL or Windows CRLF. On read, TirNanoG Editor can read both, and it always emits Windows-compatible CRLF ('\r' and '\n') on writes. Some strings (which appear in game) inside these text files are fully translatable UTF-8 strings, otherwise these files are ASCII.

The reason behind plain text files is that (if you need to) you can quickly edit them with a text editor like Notepad, and also to allow you to write third party scripts to generate them.

Production Ready Game (game.tng)

Starts with the ASCII line #!/usr/bin/tngp, but this is a binary file. For the detailed format, see the specification.

You can save other .tng files as game extensions. The only difference between a game file and an extension is that a game file always contains everything, while you have full control about which parts to include in an extension.

Project Config File (config)

Starts with a TirNanoG Project first line, followed by the revision number. The second line contains the name of the project (UTF-8, up to 128 bytes), and the third line the properties (type, tile size and map size which must be power of two).

TirNanoG Project (revision)
(project name / game title)
("ortho"|"iso"|"hexv"|"hexh"|"3d") (tile width number) (tile height number) (map size)

Revision is currently 0. This is used to provide backward compatibility for loading older projects in future editor versions, should the project format ever change.

Palette Files (.gpl)

Stored in .common folder one directory up to the project's directory, common to all projects. They are in GIMP Palette format.

GIMP Palette
Name: (name, not used)
#
(r) (g) (b)
(r) (g) (b)
(r) (g) (b)
(r) (g) (b)
...
(r) (g) (b)

Supports up to 256 colors, but 16, 32 and 128 colors are much more common (the reason for a palette is to reduce the number of used colors for a more consistent look-n-feel in the game).

Besides of GIMP, you can use Gpick to manipulate these palette files directly. You can import palettes from various formats: Adobe Photoshop Color, Adobe Swatch Exchange, etc. plus any image file.

Layout Files (.lyt)

Stored in .common folder one directory up to the project's directory, common to all projects. These specify how sprites should be imported from a sprite sheet image. Category is one of: "ui", "tiles", "chars", "portraits", "backs" or "-".

TirNanoG Layout
(sprite width) (sprite height) (left margin) (top margin) (vertical padding) (horizontal padding) (category)
("so"|"sw"|"we"|"nw"|"no"|"ne"|"ea"|"se") ("o"|"f"|"l") (suffix|"-") (x,y) (x1,y1,x2,y2) ...
("so"|"sw"|"we"|"nw"|"no"|"ne"|"ea"|"se") ("o"|"f"|"l") (suffix|"-") (x,y) (x1,y1,x2,y2) ...
...
("so"|"sw"|"we"|"nw"|"no"|"ne"|"ea"|"se") ("o"|"f"|"l") (suffix|"-") (x,y) (x1,y1,x2,y2) ...

The lines starting from the third contain the sprite definitons, each sprite one line. The first value in the line stores the facing direction (no North, ne North East, ea East, etc.). If you're unsure, use south. The second value identifies the animation type (o play once, f play forth and back again, l play in a loop). If the sprite has an additional name suffix, that follows. If it doesn't have any, then a - is stored. After that comes as many block specifications as the number of frames in the animation (up to 32 frames). One block specification looks like either (x,y) or (x1,y1,x2,y2) (if a sprite consist of multiple adjacent blocks). These are indeces to the sprite grid specified by the second line in the file, starting from zero. For example a double tall water fountain animation with 5 frames, stored vertically might look like

so f - (0,0,1,1) (1,0,2,1) (2,0,3,1) (3,0,4,1) (4,0,5,1)

while a simple walking in right direction animation with 4 frames, stored horizontally in the image might look like

ea l walking (0,0) (0,1) (0,2) (0,3)

Sprite Generator (*.png)

Stored in .common folder one directory up to the project's directory, common to all projects.

These images hold layerable sprite sheets of character assets in PNG format.

Layer icons are named like (layer).png and must be 16 x 16 in size, and they define user customizable layers.

Templates named as (layer)_(number)_(variant).png, where layer is one of body, head, toolb, toolf or one of the icon names. For example body_1_male.png or toolf_1_longsword.png. (The tools layer is special in a way that it has a background layer toolb and a foreground layer toolf too, handled together.) The (number) part isn't shown, it's just there to help with the ordering of the overlays. Dependencies are added at the end of the filename, in parenthesis, like ears_1_elf_(male).png.

For more details, read the generator manual.

Font Files (fonts/*.sfn)

These are in Scalable Screen Font format. You can import virtually all existing font formats. For details see the fonts manual.

All Image Files (ui/*.png, tiles/*.png, chars/*.png etc.)

Must be in Portable Network Graphics format. Pixels in 32 bit RGBA (alpha, transparency channel). You can use GIMP to create images, it's native .xcf format is supported too by the importer (with certain limitations, so I recommend to export PNGs from GIMP instead).

Sprites are named specially, with a suffix. If the sprite covers the entire screen, then it must end in _full.png. Otherwise the suffix looks like _(direction)(animtype)(numframes).png. Here direction and animation type are the same as described in section Layouts Files (no, ne, ea, etc. and o, l, f). The number of frames is encoded in uppercase letter quad-hex minus one. So for example 0 means one frame, 1 means two frames, 9 means 10 frames, A means 11 frames, F means 16 frames, G 17 frames and V means 32 frames.

If the suffix is atls, then the image contains multiple sprites, and atlas info is loaded from the PNG comment.

TirNanoG Atlas
(x) (y) (width) (height) (name)_(direction)(animtype)(numframes)
(x) (y) (width) (height) (name)_(direction)(animtype)(numframes)
...
(x) (y) (width) (height) (name)_(direction)(animtype)(numframes)

For details see the sprites manual and models manual.

Music, Sound Effects and Localized Voices (music/.ogg, sounds/.ogg, speech/*.ogg)

All sounds in Vorbis format, 44.1 kHz. Furthermore while music isn't limited in channels (can be stereo or surround), sound effects and voices must be mono channel (those are mixed into audio output with positioning effect).

Localized voices in the speech directory are suffixed with a two letter lowercase ISO-639-1 language code _(lang).ogg, for example _en.ogg or _ja.ogg.

For details, see the media manual.

Movies, Videos (movies/*.ogv)

All movies in Theora format. Pixels in planar IYUV420 format (most common and the default, others aren't supported). Audio must be in vorbis, 44100 Hz stereo channels. For more details, see the media manual.

Common User Interface Elements (ui/elements.cfg)

The magic is TirNanoG Elements. The second line contains 10 color codes, after that comes 6 font specification lines with padding values, a line with two sounds, and the rest are just sprite names, one for each element.

TirNanoG Elements
(normal button label color hex) (button selected) (button pressed) (button inactive) (chat text) (tab title) (selected title) \
    (dialog title) (text) (selected text)
("B"|".") ("I"|".") (size) (tab title font name) (tab top padding)
("B"|".") ("I"|".") (size) (selected tab title font name) (selected top padding)
("B"|".") ("I"|".") (size) (dialog title font name) (dialog title to ppadding)
("B"|".") ("I"|".") (size) (text font name) (window top padding)
("B"|".") ("I"|".") (size) (selected text font name) (window left padding)
("B"|".") ("I"|".") (size) (button font name) (button top padding)
(button selected sound) (button pressed sound) (error message sound)
(pointer cursor sprite)
(clicking cursor sprite)
(not allowed cursor sprite)
(action/use cursor sprite)
(loading cursor sprite)
(unchecked checkbox sprite)
(checked checkbox sprite)
...
(chat input right sprite)
(application icon)

Main Menu Configuration (ui/mainmenu.cfg)

The magic is TirNanoG MainMenu. Follows the structure of the on-screen form. The game's title is stored in "config".

TirNanoG MainMenu
(background music) (background movie) (background image)
(parallax dx) (parallax dy) (parallax dt) (parallax sprite)
(parallax dx) (parallax dy) (parallax dt) (parallax sprite)
(parallax dx) (parallax dy) (parallax dt) (parallax sprite)
(header image)
(0/1 don't crop bg) (0/1 title enabled) (title color) (shadow color) ("B"|".") ("I"|".") (size) (title font name)
(website URL when title clicked)
("B"|".") ("I"|".") (size) (button font name)
(normal color) (normal button left image) (normal button background image) (normal button right image)
(selected color) (selected button left image) (selected button background image) (selected button right image)
(pressed color) (pressed button left image) (pressed button background image) (pressed button right image)
(inactive color) (inactive button left image) (inactive button background image) (inactive button right image)
(input box left image) (input box background image) (input box right image)
(graph color) (graph grid color)

Heads-Up Display Configuration (ui/hud.cfg)

The magic is TirNanoG HUD. The first 7 lines more or less follow the structure of the on-screen form, the rest are multiple records, progressbar definitions starting with p, equipment slots with an s.

TirNanoG HUD
(navcircle image) (action buttons image)
(itembar background) (itembar foreground)
(item count) (item width) (item height) (item left margin) (item top margin) (gap between items) (gap before handheld item)
(statusbar background) (statusbar foreground) (0/1 statusbar combined flag)
(inventory icon) (pressed icon) (in slot item width) (in slot item height) (in slot item left) (in slot item top)
("B"|".") ("I"|".") (size) (number of items font name)
(slot background image) (selected slot image) (equipment background image)
p (direction) (left margin) (top margin) (value variable) (maximum variable) (foreground image) (background image)
p (direction) (left margin) (top margin) (value variable) (maximum variable) (foreground image) (background image)
...
p (direction) (left margin) (top margin) (value variable) (maximum variable) (foreground image) (background image)
s (left margin) (top margin) (unique name of the slot)
s (left margin) (top margin) (unique name of the slot)
...
s (left margin) (top margin) (unique name of the slot)

Directions: 0 = left to right (typical progress bar), 1 = bottom to top (orb-like progress bar), 2 = alpha channel, 3 = enemy's HP progressbar definition

Equipment slots definition positions are relative to the equipment image, and they have the same item size as the other slots.

Alert Configuration (ui/alerts.cfg)

The magic is TirNanoG Alerts. Follows the on screen form. Currently only the quest notifiers stored here, but might be expanded in the future.

TirNanoG Alerts
("B"|".") ("I"|".") (size) (quest alert font name)
(duration in hsec) (fade in ends) (fade out starts)
(new quest given color) (new quest given audio) (new quest given title)
(quest completed color) (quest completed audio) (quest completed title)
(quest failed color) (quest failed audio) (quest failed title)

Credits, Attributions (ui/credits.ui)

The magic is TirNanoG Credits. The first 4 lines specify the background image, music and fonts. The others are the names of the authors, prefixed by a category character (g = game design and story, c = concept art, d = design, a = animators, f = font designers, m = music, s = sound effects, v = voice actors, t = translators).

TirNanoG Credits
(background music) (background image)
(header color) ("B"|".") ("I"|".") (size) (header font name)
(text color) ("B"|".") ("I"|".") (size) (text font name)
("g"|"c"|"d"|"a"|"f"|"m"|"s"|"v"|"t") (author's name)
("g"|"c"|"d"|"a"|"f"|"m"|"s"|"v"|"t") (author's name)
...
("g"|"c"|"d"|"a"|"f"|"m"|"s"|"v"|"t") (author's name)

Depending on what assets you use, you might be legally obliged to fill in credits properly and attribute the authors.

Choosers (ui/*.chr)

The magic is TirNanoG Chooser. The second line specifies the margins and paddings, the others are chooser option records.

TirNanoG Chooser
(margin left / right) (margin top / bottom) (horizontal padding) (vertical padding)
(position 0 - 12) (keep running) (normal sprite) (selected sprite) (pressed sprite) "0" (requires) (provides)
..
(position 0 - 12) (keep running) (normal sprite) (selected sprite) (pressed sprite) "0" (requires) (provides)
(position 13) (keep running) (x1) (y1) (x2) (y2) "-" (provides)
..
(position 13) (keep running) (x1) (y1) (x2) (y2) "-" (provides)

There are two different kind of records: if position is less than 13, then the 2nd, 3rd, 4th coloumns are sprites and the 5th is zero. With position code being 13, these coloumns contain coordinates with integer numbers. Furthermore, for position 13 the 6th coloumn, "requires" must be empty (a hypen -), because you cannot remove part of the background. With sprite stacks, "requires" can be either empty -, a one variable condition (var)=(value) or two variables (var1)=(value1)&(var2)>(value2). For the "provides" coloumn, it is either empty - or (var)=(value). Variables can be b - z (local variables) or an attribute (local variable a is reserved for returning the selected chooser option).

Cutscene Files (ui/*.cut)

The magic is TirNanoG CutScene. The second line specifies the background (background movies, music). Other lines add elements to the timeline. The length and all timestamps are in 1/100th second units, stored as a single number.

TirNanoG CutScene
(play once movie) (loop movie) (play once music) (loop music)
("i") (from) (fadein) (fadeout) (to) (color hex) (image name|"-")
("s") (from) (speech name)
("t") (from) (fadein) (fadeout) (to) (x) (y) (alignment) (color hex) ("B"|"." + "I"|".") (size) (font name) (subtitle text)
e {
    (script)
}

With slideshow records i, either the color is non-zero and image is - or color must be 00000000 and image not -.

Start Up Script (game/startup.cfg)

The magic is TirNanoG StartUp. Stores a couple of scripts. All scripts run on game start, except start runs only once, while the other are called again repeatedly in regular intervals.

TirNanoG StartUp
e start {
    (script)
}
e timer1 {
    (script)
}
e timer2 {
    (script)
}
e timer3 {
    (script)
}

For the script's encoding, see section "Event Handlers".

Attribute Parameters (game/attributes.cfg)

The magic is TirNanoG AttrConf, followed by two integer numbers in the next line, the total and a maximum per attribute values in this order. After that the name of the wielded light radius attribute, weight attribute and the speed attribute.

TirNanoG AttrConf
(total points) (max per attribute)
(light attribute|"-")
(wight attribute|"-")
(speed attribute|"-")

Attributes (game/*.atr)

The magic is TirNanoG Attribute. First comes the type code (p = primary attribute, v = variable attribute, c = calculated attribute, g = global variable attribute, G = global calculated attribute) followed by the title if any. If the title is missing, then the attribute won't be shown to the user during the game on the inventory's stats tab. In the next line there's an expression which is how the calculated attributes are - well - calculated, and that is the default value for variables and globals.

TirNanoG Attribute
("p"|"v"|"c"|"g"|"G") (title)
(expression)

For variables and globals, this is followed by an event handler script definition:

e onchange {
    (script)
}

For the script's encoding, see section "Event Handlers".

Actions (game/actions.cfg)

The magic is TirNanoG Actions. This contains the names of user defined actions and their default event handlers.

TirNanoG Actions
("e") ("action1"|"action2".."action9") (range attribute) (action name) {
  (script)
}

For the script's encoding, see section "Event Handlers".

Character Options (game/*.opt)

The magic is TirNanoG CharOptions. The first line is the translatable group name. For the other lines, each option starts with a line beginning with o followed by the option's name. That might be followed by lines beginning with a describing a provided attribute and r a required one, i for a starter inventory item, or l for animation sprites layers. This might followed by another one or more o option blocks.

TirNanoG CharOptions
(option group name)
("p") (palettes sprite)
("o") (option name)
 ("d") (description text)
 ("a") (attribute name) (value)
 ("r") (attribute name)(relation)(value)
...
 ("a") (attribute name) (value)
 ("i") (quantity) (inventory object)
 ("l") (sprite idle) (sprite walk) (sprite climb) ... (sprite action 9) (portrait sprite)
 ("l") (sprite idle) (sprite walk) (sprite climb) ... (sprite action 9) (portrait sprite)
...
 ("l") (sprite idle) (sprite walk) (sprite climb) ... (sprite action 9) (portrait sprite)
...
("o") (option name)
...

Padding with spaces is not mandatory, it's just a visual aid. Provided attributes a and required ones r differ only in that required ones also have a relation, for example a hair 1 and r class!=2.

Character Base (game/character.cfg)

The magic is TirNanoG CharBase. This file just stores how much it should be moved upward when drawing and which character option group contains the base body sprites.

TirNanoG CharBase
(delta y) (character option)

Non-Player Characters (npc/.npc, npc/_npc.png)

The magic is TirNanoG NPC. This is followed by a title or name presented in-game, then a line specifying the portrait and movements of the NPC, then a bunch of numbers in the next line, which are the behaviour code, partrol code etc. After that the format is flexible, and the first character in the line tells what kind of record it is.

TirNanoG NPC
(translatable title / name)
(movement type "stand"/"random"/"patrol") (movement speed) (direction change freq) (patrol path)
("a") (attribute name) (value)
("a") (attribute name) (value)
...
("a") (attribute name) (value)
("r") (attribute name)(relation)(value)
("l") (sprite idle) (sprite walk) (sprite climb) ... (sprite action 9) (portrait sprite)
("l") (sprite idle) (sprite walk) (sprite climb) ... (sprite action 9) (portrait sprite)
...
("l") (sprite idle) (sprite walk) (sprite climb) ... (sprite action 9) (portrait sprite)
("i") (chance percentage) (quantity) (replanish minutes) (inventory object)
("i") (chance percentage) (quantity) (replanish minutes) (inventory object)
...
("i") (chance percentage) (quantity) (replanish minutes) (inventory object)
("e click"|"e touch"|"e action1".."e action9"|"e sell"|"e buy") {
    (script)
}
("e approach"|"e leave") (distance) {
    (script)
}
("e using1"|"e using2"|"e using3") (object) {
    (script)
}

The codes a, r, l, are the same as with character options. Additionally code i encodes an inventory item, and e an event handler script. The approach and leave handlers have a numeric argument, the radius in tiles, and using handlers an object (read: when this object used on the NPC run this script).

There's an exactly same named image file for each and every NPC, suffixed _npc.png. These are automatically generated when you save the .npc file in the editor.

NPC Spawners (npc/.swr, npc/_swr.png)

The magic is TirNanoG Spawner. This followed by a line with the number of NPC handled by this spawner and spawning frequency. Other lines are NPC kind records, listing what kind of NPCs can be spawned by this spawner. If an NPC is repeated multiple times, that increases the chances of spawning that kind.

TirNanoG Spawner
(number of NPC to spawn) (frequency in hsec)
(requires|"-") (provides|"-") (spawning animation|"-") (NPC)
(requires|"-") (provides|"-") (spawning animation|"-") (NPC)
...
(requires|"-") (provides|"-") (spawning animation|"-") (NPC)

The requires field looks like (attribute)(relation)(value), for example level>=5. The provides is always in the form (attribute)=(value).

There's an exactly same named image file for each and every spawner, suffixed _swr.png. These are automatically generated when you save the .swr file in the editor.

Dialogs (dialogs/*.dlg)

The magic is TirNanoG Dialog. The next 3 lines are fixed, followed by the choose options if there are any.

TirNanoG Dialog
(dialog title)
(dialog message)
(speech audio|"-") (portrait sprite|"-") (1 if aligned left)
(requires|"-") (provides|"-") (speech audio|"-") (option 1 text)
...
(requires|"-") (provides|"-") (speech audio|"-") (option n text)

Objects (objects/*.obj)

The magic is TirNanoG Object. These are named exactly as the object png, and they add extra information to the object. After the magic the first 2 lines are fixed, followed by the collision mask, the other depends on the first character of the line. The codes are the same as with Characters and NPCs, plus s for equipment slots and m for attribute modifiers (it has two more integers than a attribute lines, on the other hand, required attributes r are the same). Projectile animation sprites are prefixed by p. The o options must match the character base's options, and under that the first sprite matrix layer is the foreground f, and if exists, the second is the background b. If f and b appears before the first o line, those encode the defaults. No more sprite layers allowed, just fore and back. Event handlers are the same as with NPCs.

TirNanoG Object
(title, name in inventory)
(collision mask width) (collision mask height) (action category) (inventory sprite) (inventory sound) (price) (unit) (category) (1 if skill) (layer order)
(mask value 1) (mask value 2) .. (mask value width-th)
(mask value 1) (mask value 2) .. (mask value width-th)
..
(mask value 1) (mask value 2) .. (mask value width-th)
("s") (equipment slot) (equipment slot sprite|"-")
("s") (equipment slot) (equipment slot sprite|"-")
...
("s") (equipment slot) (equipment slot sprite|"-")
("r") (required attribute name)(relation)(value)
("r") (required attribute name)(relation)(value)
...
("r") (required attribute name)(relation)(value)
("m") (1 if modifier only applies when equipted) ("0") ("-") (transportation value)
("m") (1 if modifier only applies when equipted) (1 if value is in percentage) (modified attribute's name) (value)
("m") (1 if modifier only applies when equipted) (1 if value is in percentage) (modified attribute's name) (value)
...
("m") (1 if modifier only applies when equipted) (1 if value is in percentage) (modified attribute's name) (value)
("p") (projectile sprite) (projectile animation duration) (projectile ammo)
("f") (default foreground sprite idle) (sprite walk) (sprite climb) ... (sprite action 9) (portrait sprite)
("b") (default background sprite idle) (sprite walk) (sprite climb) ... (sprite action 9) (portrait sprite)
("o") (character option name)
 ("f") (character option specific foreground sprite idle) (sprite walk) (sprite climb) ... (sprite action 9) (portrait sprite)
 ("b") (character option specific background sprite idle) (sprite walk) (sprite climb) ... (sprite action 9) (portrait sprite)
("o") (option name)
...
("e click"|"e touch"|"e action1".."e action9"|"e sell"|"e buy") {
    (script)
}
("e approach"|"e leave") (distance) {
    (script)
}
("e using1"|"e using2"|"e using3") (object) {
    (script)
}

For the modifier, there's a special case when the attribute is -, in that case the value encodes transportation method: 0 = foot (on ground, walk, climb, jump, run), 1 = swimming (naval), 2 = fly (aero), 3 = drive (horse mounted, etc.). These influence which map collision masks apply to the user.

For the orthogonal maps, collision mask is stored trivially. For isometric, every even line stores a value half a tile width to the right on the sprite:

+-----+
|0 1 2|
| 3 4 5
|6 7 8|
+-----+

This means that the last value in every even line (5 in this example) is always zero, because that's only half way in the sprite, so it cannot store a mask. Otherwise bits 0 - 2 encode type (0 = walkable, 1 = blocking, 2 = swimmable, 3 = flyable, 4 = climable (variant of walkable), 5 = jumpable (variant of walkable), 6 = walkable above 7 = walkable below; and bit 3 means event trigger tile, so for example 9 means blocking and triggers "on touch", "on click" etc. event handlers.

Vertical hexagonal grid (pointy topped) is saved exactly like the isometric. Horizontal hexagonal (flat topped) on the other hand saved as

+-----+
|0 2 4|
| 1 3 |
|5 7 9|
+-6-8-+

So every even value in the last row must be zero.

When no collision mask is specified, by default the center tile on the bottom line is blocking and is an event trigger.

Crafting Rules (dialogs/*.cft)

The magic is TirNanoG CraftRules. The next 3 lines are fixed, others are repeated as many times as many rules (recipes) are in this crafting dialog.

TirNanoG CraftRules
(crafting dialog title)
(crafting dialog message|"-")
(item crafted audio|"-") (portrait sprite|"-") (1 if ingredient alignment is irrelevant)
(crafted count) (crafted object) (ingredient 1 count) (ingredient 1 object) ... (ingredient 16 count) (ingredient 16 object)
(crafted count) (crafted object) (ingredient 1 count) (ingredient 1 object) ... (ingredient 16 count) (ingredient 16 object)
...
(crafted count) (crafted object) (ingredient 1 count) (ingredient 1 object) ... (ingredient 16 count) (ingredient 16 object)

Tile Meta Info (tiles/*.til)

The magic is TirNanoG Tile. These are named exactly as the tile png, and they add extra information to the tile.

TirNanoG TileRule
(on walk speed modifier) (on walk sound effect)
(on swim speed modifier) (on swim sound effect)
(on fly speed modifier) (on fly sound effect)
(on drive speed modifier) (on drive sound effect)
(on TBD modifier) (on TBD effect)
(automap mask #1) (automap mask #2) (automap mask #3) ... (automap mask #9)
(automap mask #1) (automap mask #2) (automap mask #3) ... (automap mask #9)
...
(automap mask #1) (automap mask #2) (automap mask #3) ... (automap mask #9)

The fifth modifier is to be defined. For hexagonal grids (both vertical and horizontal), the first and the last automap mask is always -.

Orthonogal grids are stored trivially.

0  1  2
3  4  5
6  7  8

Isometric like

    0
  3   1
6   4   2
  7   5
    8

Vertical hexagonal:

  1
3   2
  4
6   5
  7        0 -, 8 -

And horizontal:

  1   2
3   4   5
  6   7    0 -, 8 -

It is made so that no matter the type, mask index 4 is always at the centre.

Maps (maps/*.tmx)

The magic is < ?xml version="1.0" encoding="UTF-8"? >. It uses a subset of Tiled's TMX format for interoperability and compatibility. It is only guaranteed that everything that TirNanoG Editor saves can be opened and edited in Tiled, but loading any arbitrary Tiled map into TirNanoG was never the goal (Tiled is very flexible, can also save maps which are fundamentally incompatible with a TirNanoG game). Also note that TirNanoG supports tilesets in sprite atlases, while Tiled doesn't.

The <map> tag is parsed for the orientation, tilewidth and tileheight attributes (and for hexagonal maps, staggeraxis too). Also for the version attribute should Tiled fix the isometric coordinate bug or implement tile atlases in the future.

The <properties> tag is parsed for the following properties:

  • title the translated, UTF-8 name of the map.
  • daylight values are (daylight length in minutes) (daylight palette sprite).
  • music the background music
  • parallax values are (0/1 foreground) (speed x) (delta x) (speed y) (delta y) (move interval time) (sprite).
  • south, west, north, east, south-west, north-east etc. the value is another map (filename without '.tmx' suffix).

All properties must occur only once, except parallax which might be repeated as many times as many parallaxes there are.

The <tileset> tag is used specially because TirNanoG uses one image per tile with all animation frames (which can be packed in an atlas), while Tiled uses fixed sized tiles only with a strange way to describe animations. So for simplicity, each sprite is saved as a separate tileset, but only the first frame is specified for a single tile for Tiled. The firstgid attribute is parsed to get the id used on layers and name attribure for the tile's category and name. No other attributes or sub-tags needed or parsed by TirNanoG, but it saves them to provide compatibility and bug workarounds. There's a bug in Tiled with tile alignments, so the objectalignment attribute is explicitly set and a <tileoffset> tag is added.

WARNING!!! Tiled can't handle sprite atlases in tilesets, so if you really intend to open maps in Tiled, you must unpack atlases first with tnge -u (this is a known and open issue since 2015 so don't get your hopes up for a Tiled bugfix soon).

The <layer> tag's id is used to identify the layer, other attributes (like name for example) doesn't matter. The width and height attributes is also parsed to get the layer's size (it doesn't matter if the layer is bigger or smaller than the map's size, TirNanoG Editor reads in the layer correctly: filling up extra space as empty and simply skipping layer parts which are off the map).

  • id = 1 ground 1 layer with tiles
  • id = 2 ground 2 layer with tiles
  • id = 3 ground 3 layer with tiles
  • id = 4 ground 4 layer with tiles
  • id = 5 objects layer with tiles
  • id = 6 NPCs layer with NPC sprites
  • id = 7 spawners layer with spawner sprites
  • id = 8 roof 1 layer with tiles
  • id = 9 roof 2 layer with tiles
  • id = 10 roof 3 layer with tiles
  • id = 11 roof 4 layer with tiles

Collision is NOT stored in the tmx file, because that's automatically generated from objects' masks. For the <data> tag only uncompressed csv values are written (which is Tiled's default), but on load all data encodings and compression variants (XML, base64, gzip, zlib, zstd) supported.

Finally, patrol paths are stored as <polygon> objects (sorry, Tiled can't handle directed vector lists. The doc says there's a polyline tag, but I couldn't find that on the user interface). To get these, only the first <objectgroup> tag is parsed, no matter the id, and in it each <object> tag's name attribute is used for the path's id. So object group's and object's id does NOT matter, only the name attribute of the object tag (Tiled tend to mess up id tags on objects, so we cannot rely on those). It worth mentioning that Tiled has a bug with coordinates and they are incorrectly stored for isometric maps. The map x coordinate can be calculated as (x - tileheight / 2) / (tilewidth / 2) instead of a simple x / tilewidth for some reason unknown to me. This only affects the x coordinate, the y coordinate works as expected. Anyway, you won't notice anything about this because the editor workarounds file format details, saving and loading patrol paths just works (TM).

Quests (quests/*.qst)

The magic is TirNanoG Quest. Follows the format of the form.

TirNanoG Quest
(1 for globally unique quests, 0 otherwise)
(quest title)
(quest description)
("e complete") {
    (script)
}

Translations (lang/.po, lang/.txt)

The magic is # TirNanoG Translation. Filenames are using 2 letter ISO-639-1 language codes. Follows the format of the form. It resembles the standard .po file to allow compatibility with existing translation tools, but it is important to keep the comment in the first line intact, and that the first block has the 2 letter language code as id and it contains the native name of the language as translated string. The second block is always the game's title. Multiple lines for "msgid" and "msgstr" not allowed, they must be in a single line. Within the strings, single quotes ('), duoble quotes (") and backslashes () must be escaped by a backslash. Other special characters (like \t tab or \n newline for example) not allowed (both the TirNanoG Editor and the TirNanoG Player knows how to cut a long text into lines depending on the user's screen size).

# TirNanoG Translation

msgid "(two letter language code)"
msgstr "(language name)"

msgid "(original game title)"
msgstr "(translated game title)"

msgid "(original string)"
msgstr "(translated string)"

msgid "(original string)"
msgstr "(translated string)"

...
msgid "(original string)"
msgstr "(translated string)"

There's no .pot file because strings are subject to change as you use the editor, so they are rather automatically collected every time and matched with the strings from the translation files. To create an empty template, just enter the language code and name, do not fill in any text and press "Save".

The other kind of translation files are named as (module)_(language code).txt. These files have no magic, and they do not influence the created game either. They contain simple (spritename)=(tranlated name) lines for the editor.

Extension Configurations (ext/*.cfg)

The magic is TirNanoG Extension. The second line is fixed, others are variable depending on the first character, o for checked options, l for checked languages and m for checked maps. It just stores the save extension form's values. Options are the same as the Interface and Game menus, 0 is for UI elements, 1 main menu, ..., 17 quests.

TirNanoG Extension
(0/1 encrypt) (url|"-")
("o") (option id)
("o") (option id)
...
("o") (option id)
("l") (two letter language code)
("l") (two letter language code)
...
("l") (two letter language code)
("m") (map name)
("m") (map name)
...
("m") (map name)

Event Handlers

These scripts can be found in several files. They are always start with the magic e (referring to event handler), followed by the event's name and optionally by parameters. Then the script's body is enclosed in { } brackets.

("e") ("event name") {
    (script)
}

For example,

e ontimer10 {
    sound(clock)
}

means play the "clock" audio every ten seconds.

Within the scripts the controlling structures look like:

Iteration (loop):

while((expression)) {
    (script)
}

Conditionals:

switch((expression)) {
    case (number) {
        (script)
    }
    case (number) {
        (script)
    }
    ...
}

When there are two case branches, one for 1 and the other for 0, that works exactly like an "if" / "else" block. With more branches, it works like "switch". Numbers are always listed in descending order, meaning 0 is the last option.

Assigning values:

(attribute)=(expression)
@(attribute)=(expression)
(local variable)=(expression)

Attribute refers to the object's attribute that the event is executed on. For example if it's an object's "on touch" event, then it refers to the touched object's attributes. When prefixed by @, then it refers to the other party in the event. That can be an NPC (if it's in a player's default action handler) or the player (if it's in an NPC's or object's event handler). If the attribute is a global variable, then it doesn't matter if it's prefixed or not, it will refer to the same attribute. Each script has 26 general purpose local variables, labelled as a through z. Each script can use these independently, and they only exists during the execution of the event handler (attribute names must be at least two characters long to avoid collision).

Within the expressions, you can use basic algebra, operators (addition +, subtraction -, multiplication *, division /, modulo (remainder) %, grouping with parenthesis ( ), logical operators (and &, or |, not !), comparators (equal =, not equal !=, greater >, greater or equal >=, less <, less or equal <=), the local variables (a - z), attributes and a few functions which can only be used in expressions (but not as individual commands, because these push their return value on the stack):

min(a,b)        - returns the smallest in the list
max(a,b)        - returns the biggest in the list
rnd(max)        - returns a random value between zero and max - 1 (for example rnd(6) is like a dice, returning 0..5)
sum(attribute)  - returns the sum of the given attribute of the items in inventory
cnt(object)     - returns the number of given items in the inventory
cnt()           - returns the total number of items in the inventory
@sum(attribute) - returns the sum of the given attribute of the items in opponent's inventory
@cnt(object)    - returns the number of given items in opponent's inventory
@cnt()          - returns the total number of items in opponent's inventory

For example, let's say you have a goldcoin and a silvercoin object in the game. 10 silvercoins give one gold. To get how much money the player has, use a := cnt(goldcoin) * 10 + cnt(silvercoin). To get the total weight of all the items that the player is carrying, use sum(weight) (assuming you have a weight attribute configured).

There are other procedures which are controlling the game, and hence used as individual commands and not allowed in expressions. For example

sound(audio)    - plays a sound
music(audio)    - replaces the music being played
delay(num)      - delays script for num 1/100 seconds
waitnpc()       - wait for NPC to finish it's command queue
exit()          - exits the game and returns to the main menu
...etc.             (for a full list, see cmd_types[] in ui_cmd.c)

These never return a value, with two notable exception, the chooser(chr) and dialog(dlg,timeout) functions, which return the selected option in the local variable a (choosers might change other variables as well). This is only if the given dialog has user options configured, and for choosers if not the background was clicked. Options are numbers in descending order, so first is (number of options - 1), the second is (number of options - 2), etc. and the last option is 0. With other words, if you put a conditional block with an expression of "a" right after a dialog call, then the first option will correspond to the first coloumn, the second option to the second coloumn, third option to the third coloumn, etc. and the last option to the last coloumn.

For example, there's a dialog called joeasks which looks like this:

+---[ Joe ]----------------+
| What do you want?        |
|   (option A)             |
|   (option B)             |
|   (option C)             |
+--------------------------+

then in scripts:

+------------------------+
| (dlg)  joeasks   00:00 |
+------------------------+
+-----------------------------+
| /\ [+] [-]  a               |
|    2    |    1    |    0    |
+---------+---------+---------+
| things  | things  | things  |
| to do   | to do   | to do   |
| when    | when    | when    |
| option  | option  | option  |
| A       | B       | C       |
| was     | was     | was     |
| choosen | choosen | choosen |
+---------+---------+---------+

Creating functions and subprocedures is not possible (not within the event handler scripts), instead you add event handlers to the player, NPCs or objects. Each of these event handlers act as a subprocedure. If you need more complex code, you can create hidden objects, and execute their "on click" event handler from any other event handlers. In that case the called handler will inherit a copy of the local variables, and its a local variable will be copied back to the caller's local variable when the callee has finished.

Scripts are visually structured in the files. This is not a must, the deserializer doesn't care about indentation, it just makes the script more readable. There's a minimal syntax check when loading scripts, but in general invalid scripts result in an undefined behaviour and broken game logic. Watch out if you edit the files by hand.

License Files (license.txt)

If you have an encrypted game file creation license, it must start with the line "--------TirNanoG Editor License File--------", and must be placed in the project's directory, next to the "config" file by the name "license.txt".

Decryption keys will be saved in "endusers.txt" file, one in each line. This file serves two purposes: first, by checking it the editor can make sure that all generated keys are unique, and second you can parse it from your webshop to sell game license keys for your game. For details, see the webshop manual.