Table of Contents
Introduction
Sloppy MarkUp Language (SMUL) is a procedural markup language that primarily
utilizes troff
- or groff
-like syntax, with tokens on specific lines
dedicating all following text for a specific format. SMUL is designed primarily
for use in community contributions to projects like In Search of Dangerous
Sloppy, where passages are formed from discrete nodes that make up objects like
paragraphs, images, and titles. SMUL is designed to be easily chunked into such
nodes while also being easy to write and parse by eye.
SMUL is not meant to be used as a general-purpose markup language, nor is it
meant to be processed into a general-purpose output like *.txt
or *.html
. It
is meant only for processing into specialized formats stored in a viewer
program’s memory to be freed after viewing. For example, in In Search of
Dangerous Sloppy, SMUL documents are rendered into a Godot node tree containing
RichTextLabel and TextureRect objects. Such objects, as invoked in SMUL, are
referred to in this document as constructs.
This Specification
Sloppy MarkUp Language 1.0 is designed not to be a specification of a SMUL parser implementation. Rather, it is a specification of how a SMUL document should be written. In other words, this document serves as some overhead, and then a general reference. No specifics are given as to how a SMUL parser should interpret and render SMUL to an object tree.
Know your Structure
Projects like In Search of Dangerous Sloppy that use SMUL have a specific file structure that SMUL can use for constructer variables. It’s good to know which part of the project structure you’re accessing with a SMUL construct and therefore which parts of it are inaccessible. A basic rundown is as follows:
root
├data
│ └characters
│ └*.tres
├graphics
│ ├landscapes
│ │ └*.png
│ └portraits
│ └character_name
│ └*.png
├scenes
│ └*.txt
└scripts
├*.gd
└scene_scripts
├*.gd
└character_name
├win.gd
└loss.gd
As In Search of Dangerous Sloppy is made in Godot Engine, the example file tree here is given in the form of a godot project. The following subsections will explain the most relevant directories.
/data/characters
This directory contains character files that describe a character’s appearance and attributes. For example, it may contain the character’s name, gender, stats, and class.
/graphics/landscapes
This directory contains the images (in *.png
format) that are designed to take
up the full width of the in-game passage display. It is recommended to use
images that are twice as wide as they are tall.
/graphics/portraits/character_name
This directory contains the portrait images for character_name
. This doesn’t
refer to a literal directory; it is many directories, each named after a
character. This directory may be further subdivided to categorize characters.
Valid directories are things like /graphics/portraits/witch/neutral.png
and
/graphics/portraits/goblin/male/neutral.png
.
/scenes
This is where SMUL documents themselves go.
/scripts
This is where scripts related to the project’s functions go. This has nothing to do with SMUL. The subdirectories here have little to do with SMUL, but are handy to know for various reasons when writing SMUL scenes.
/scripts/scene_scripts
This is where scene scripts go. Scene scripts are scripts (*.gd
in the case of
ISoDS) that are loaded alongside their matching scene. These are used to change
the navigation buttons and perform complex logic, and are required such that
each scene has an outlet somewhere.
It is recommended that scene scripts do not have any function upon load other than setting the navigational buttons, as most scenes can be saved at and reloaded an arbitrary number of times. In other words, if a scene raises the player’s stats, that should be done upon clicking a “Next” button or something similar, or the actual stat raising should be done when clicking the button in another scene that leads to this scene.
/scripts/scene_scripts/character_name
Much like the equivalent directory in /graphics/portraits
, this is not one
literal directory. This directory should contain at least two scripts: win
and
loss
. These respective scripts are executed upon defeating or losing to
character_name
in battle. These scripts can be as simple as sending the player
back to their base, or can invoke special scenes, or have a plethora of other
effects.
Forming a SMUL Document
SMUL documents are written in plain *.txt
files. This is done in the name of
portability and compatibility, as programs like Godot Engine cannot access any
file if their extension is unrecognized, and I don’t want to write a custom
importer plugin.
By convention, the file name should be the snake_case
equivalent of the
scene’s title. If the scene lacks a title, it should be a short description of
the scene or its purpose. For example, document_title.txt
.
No particular construct is necessary in a SMUL document. You can have a scene with no title, no author, no paragraphs, dialogue, or anything else, though an empty scene is kind of pointless, isn’t it? This is all just to say that if you don’t want a title, you needn’t include one; titles are large and take up a lot of space, and for minor scenes can just look gaudy. Additionally, if you don’t want to be credited for a scene, don’t include an author construct - simple as.
Constructs
SMUL documents are made of constructs that indicate how content should be laid out and styled. All text rendered to the scene will belong to a construct. That is to say, any text laid out in the beginning of the document before a construct and any text laid out following a construct that does not collect text will be discarded by the SMUL parser. The following are the tokens supported in version 1.0, along with examples of their usage. A table is given below as referance.
Invocation | Name | Rendering |
---|---|---|
_TL |
Title | Large font size |
_AU |
Author | Small font size |
_HR |
Horizontal Rule | Horizontal Rule |
_PP |
Paragraph | Indented paragraph |
_LP |
Unindented Paragraph | Unindented paragraph |
_DL [character_name]/[character_expression] |
Left-Aligned Dialogue | Dialogue box |
_DR [character_name]/[character_expression] |
Right-Aligned Dialogue | Dialogue box |
_IM [image_name] |
Landscape Image | Image filling passage view |
_CH [character_name] |
Character Register | None |
Title: _TL
The constructor _TL
is used to invoke a title construct. For example:
_TL
This is a title
Title constructs have a much larger font size than normal paragraphs.
Author: _AU
The constructor _AU
is used to invoke an author construct. For example:
_AU
SloppyDev
Author constructs have a much smaller font size than normal paragraphs.
Horizontal Rule: _HR
Horizontal rule constructs appear as horizontal bars in the passage. Horizontal rules do not collect the text that follows them. It is recommended to use a horizontal rule after the passage’s title and author, as well as to denote narrative time skips. For example:
_TL
Example Title
_AU
SloppyDev
_HR
This text will be discarded and never show up in-game.
Indented Paragraph: _PP
Paragraph constructs are invoked with _PP
. Paragraphs formed with _PP
are
automatically indented. It is recommended to use this construct for most
applications. For example:
_PP
This is a paragraph. When rendered, it will be automatically indented. Any lines
that follow a paragraph constructor (or any other text-collecting constructor)
are automatically merged into the corresponding construct, with newlines being
replaced with spaces. This allows for documents to be arbitrarily broken when
written and appear in-tact when rendered - Vim (et al.) users rejoice!
Unindented Paragraph: _LP
Unindented paragraphs are identical to normal paragraphs, just without the indentation. This is most useful in conjunction with justification formattings through BBCode, where the indentation of a normal paragraph would cause an errant line-break to appear. For example:
_LP
[center]Despite being called an 'unindented' paragraph, this paragraph uses
BBCode to stay centered![/center]
For more about BBCode, see its section later on.
Dialogue: _DL
& _DR
There are two types of dialogue constructs in SMUL, left-oriented and right-oriented, which are constructed in the following format:
_DL [character_name]/[character_expression]
Dialogue text.
_DR [character_name]/[character_expression]
Dialogue text.
In this context, [character_name]
is the name of the directory in
/graphics/portraits
and [character_expression]
is the name of the *.png
file (without the extension) in the aforementioned directory. Lines following
the invocation of the constructor are collected and formatted into the body of
the dialogue construct.
Left-oriented dialogue should always be preferred to right-oriented dialogue, except in cases where multiple dialogue constructs follow each other, in which case they should alternate between left and right. For example:
_DL witch_apprentice/neutral
Give me your credit card information.
_DR goblin/male/confused
What's a credit card?
_DL goblin/female/resolute
It's just some witchy nonsense. It doesn't exist!
_DR witch_apprentice/flustered
N-no, it does exist. You just wouldn't understand it!
Landscape Image: _IM
The constructor _IM
invokes a landscape image, which will fill the width of
the in-game passage display. It is formed as _IM [image_name]
where
[image_name]
is the name of the *.png
file (without the extension) located
in the /graphics/landscape
directory. Landscape images do not collect the
text that follows them. For example:
_IM desert_wastes
This text will be discarded and never show up in-game.
Character Register: _CH
The character register construct is a special construct that loads a character
data resource from /data/characters
directory into the scene managers. This
character data is used to parse dollar-sign notation as described in the next
section. Character register constructs do not appear in the passage and do not
collect the text that follows them. For example:
_CH wolf_girl
This text will be discarded and never show up in-game.
Dollar-Sign Notation
Dollar-sign notation is used to replace an identifier expression with text
corresponding to attributes of either the player or the character data loaded
into the character register. These attributes include variables of the data, as
well as pronouns (and other related things). These attributes are invoked with
$
followed by either PLAYER
for the player or CHAR
for the character data
loaded into the character register, followed by a .
and the variable or
pronoun feature you wish to access. For example, if the player’s name is “Joe”:
_CH wolf_girl
_PP
This is a paragraph. When rendered to the in-game passage view, "$PLAYER.name"
will become the player's name, and "$CHAR.ref" will be replaced with the current
loaded character's reflexive pronoun.
The above excerpt would render in-game as:
This is a paragraph. When rendered to the in-game passage view, “Joe” will become the player’s name, and “herself” will be replaced with the current loaded character’s reflexive pronoun.
Pronoun Access
There are two types of attributes that can be accessed through dollar-sign
notation: pronouns and character data. While called “pronouns” in the context of
SMUL, they include third-person pronouns as well as some other terms that can be
declined by gender. A full table is given below. In the “Invocation” column,
$CHAR
can be replaced with $PLAYER
to refer to the player instead of the
character register. Additionally, if the first letter of the attribute is
capitalized (as in $CHAR.Nom
), its rendering will be as well, this is useful
for capitalizing the beginning of sentences.
Invocation | Name | Rendering |
---|---|---|
$CHAR.nom |
Nominative | he, she |
$CHAR.acc |
Accusative | him, her |
$CHAR.ref |
Reflexive | himself, herself |
$CHAR.pos |
Possessive | his, her |
$CHAR.gen |
Genitive | his, hers |
$CHAR.bln |
Blond(e) | blond, blonde |
$CHAR.dim |
Diminutive | boy, girl |
$CHAR.com |
Common Noun | man, woman |
$CHAR.for |
Formal Noun | gentleman, lady |
$CHAR.hon |
Honorific | sir, ma’am |
Character Data Access
If you try to access an attribute that isn’t 3 letters long, the parser assumes you are trying to access a variable from the character data file. This includes things like name and species, as well as more technical details of the character. A table of relevant attributes is given below. If you try to access an attribute that does not exist, no operation will be performed on it and it will render just as you typed it.
Invocation | Rendering |
---|---|
$CHAR.name |
The character’s name |
$CHAR.species |
Species name, lowercase |
$CHAR.sex |
Male, female, herm, null, lowercase¹ |
$CHAR.appearance |
Masculine-feminine scale, lowercase |
$CHAR.height |
Height in inches |
$CHAR.tall |
Height in feet & inches |
$CHAR.breast_size |
Flat-gigantic scale, lowercase |
$CHAR.ass_size |
Same as above, lowercase |
$CHAR.penis_size |
None-gigantic scale, lowercase |
$CHAR.character_class |
Class name, uppercase |
$CHAR.skin_color |
Skin color, lowercase |
$CHAR.hair_color |
Hair color, lowercase |
¹Sometimes unset; defaults to male; manually set on a basis of ‘seems right’.
BBCode
While not part of SMUL, In Search of Dangerous Sloppy uses rich text labels to
display the text parsed by SMUL, and those labels support Godot Engine’s flavor
of BBCode. A reference for it can be found
here.
Additionally, if you wish to use [color]
tags for emphasis, please stick to
Godot’s supported subset of X11 color constants, a reference for which can be
found here.
With these tags, you can format text with italic, bold, and colored fonts, as
well as control the text’s alignment and other features. As a general style
guide, the primary emphasis over just a few words should be [b]bold[/b]
. The
stronger emphasis should be [color=webmaroon][b]'webmaroon' colored bold[/b][/color]
. When emphasizing an entire sentence, only [i]italics[/i]
should be used. As a particularly striking emphasis, [shake]shaking text[/shake]
may be used.
Considerations
As it stands, more advanced logic, including setting ISoDS’s navigational
buttons has to be done with scripting. When loading a SMUL scene, the parser
checks /scripts/scene_scripts
for a GDScript file that matches the name of the
scene. If it exists, it is allowed to take over after the parser builds the
scene in the passage display. With this, anything that is possible in GDScript
can be done. This route has been chosen over including complex logic in SMUL for
two reasons: it’s easier to simply use GDScript in conjunction with SMUL scenes,
and anything that could be implemented in SMUL already exists in GDScript and is
no doubt easier to use. SMUL exists to make common passage components easier to
write, not to control the flow and logic of the entire game.
If the parser is instructed to load a scene file that does not exist, it still searches for a GDScript file of the same name and, if it exists, allows it to take over as usual. This allows for a scene transition to dump the player at a metaphorical hub that can instantly deide which scene the player is supposed to see next. For example, a scene transition call could be set to transition to a sceneless script that then decides randomly between several encounters.
Sample Scene
For completion, here is a sample of a scene written in SMUL:
_TL
Credit Card Fraud
_AU
SloppyDev
_HR
_PP
Once upon a time, a Witch Apprentice came across two free-range goblins babbling
over something unimportant. Seeing an opportunity, the witch apprentice
introduced herself.
_DL witch_apprentice/neutral
Hello, goblins. Give me your credit card information.
_DR goblin/male/neutral
What's a credit card?
_DL goblin/female/angry
It's just some witchy nonsense! It doesn't exist!
_DR witch_apprentice/angry
N-no, it does! Credit cards are, uh, um...
_DL goblin/female/angry
Doesn't exist! Doesn't exist!
_IM witch_apprentice_storming_off
SMUL