Orcsune Visual Novel Script Documentation

Published: 15 November, 2024
Last Edited: 16 March, 2025
Version: 1.0.0
Installation

TBD

Also check out the GUI editor for OVNS here:

https://orcsune.com/ovns-builder-documentation/

TBD

VisualNovel:

Interact with the static VisualNovel class in code to

Quick Terminology:

The Speaker Box defines the box in which the speaker’s name is displayed.

The Text Box defines the box in which most other text is displayed. By default, the text displayed in the text box is subject to a quick text-crawling effect with an accompanying crawl noise.

Asset Maps:

Asset maps are dictionaries that map a string (key) to an asset of some type (value). Right now, there are 3 basic asset maps included for sprites, audio clips, and fonts. Throughout your scripting, you may need to use asset keys to refer to the specific assets within your asset maps. For instance, when changing a sprite, you would reference the key for a sprite asset in your sprite asset map. The same is true for audio clips and fonts.

An asset map is any class that implements the generic IAssetKeyMap<T>. You may use the provided BasicSpriteKeyMap, BasicAudioKeyMap, and BasicFontKeyMap, but there must be EXACTLY one of each of these maps implemented on a GameObject’s MonoBehaviour in the scene so that the Visual Novel can find and incorporate them. If you have alternate/custom ways to access various assets using string keys, you may use them as long as they implement the aforementioned interface and exist on a GameObject at runtime.

Story sequence files can be organized into packages

The default package name is “base”, so any sequence file that does not specify a package with a <<package>> directive before any sequences will use the “base” package name. This is important if any information is needed from sequence files that have been developed elsewhere.

When using variables within <<line>> directives, <<choice>> directives (a.k.a. when printing), you must append the package name followed by an underscore ‘_’ to the variable name (e.g. <<v packagename_mystat>>). However, variables/stats that are edited or used in conditional checks do not need the the package name because they only ever work within the package defined by that file’s <<package>> directive (e.g. <<stat>> mystat =3 does NOT need to be packagename_mystat).

OVNS has recently been updated to allow the use of stat names without the package name prefix from anywhere. If a stat is not found, then the stat map attempts to append all seen package names to the provided stat name to find a stat that matches. If “mystat” is not found, then all “<packagename>_mystat” names will be checked until one or none is found. Keep in mind that if two different packages have a stat with the same name, you must still manually use the stat with the package name prefix to guarantee that the desired stat is used, otherwise, the used stat will be from whichever package name was first parsed by the parser.

<<package>> mypackage
<<stat>> mystat =3

{{
    <if>
    mystat ==3
    {{
        <<line>>Wow, mystat is <<v mypackage_mystat>>
    }}
    </if>
}}

Basic Directives:

Setup: Before creating story sequences, it is recommended to have some initialization directives outside of story sequences. The following directives are supported outside of story sequences:

// Define a package name for the stats included here
<<package>> my_package

// Stats/Variables should be defined and set outside of a story sequence
<<stat>> my_int_stat =0
<<statstr>> fav_animal ="pygmy hippo"

// Macros
<<macro>> make_sprites_red
<<sprite>> slot:0 color:1,0,0,1
<<sprite>> slot:1 color:1,0,0,1
<<sprite>> slot:2 color:1,0,0,1
<<sprite>> slot:3 color:1,0,0,1
<<sprite>> slot:4 color:1,0,0,1
<</macro>>

// Settings should be defined outside of sequences
<<new_settings>> main_character
<<speaker>> name:Bob color:1,1,1,1
<<text>> color:1,1,1,1 audio:crawl_noise pitch:1
<<sprite>> key:bob_sprite_casual size:80%v,80%v flip:auto

Story Sequence: A story sequence is simply a sequence of story events/directives that have been grouped together. Define a story sequence block by encasing it with double curly brackets:

{{
    // ...
}}

Line Event: A line event displays a line of text in the text box. By default, the text displayed in the text box is subject to a quick text-crawling effect with an accompanying crawl noise. To run the line event directive, call it with <<line>>

{{
    <<line>>Your line here.
}}

Choice Event: A choice event displays a description and multiple options that can be chosen by the user. Each option can divert to another sequence. The first line after the choice directive is the description which, like the line event, only displays text in the text box. After the description, every option is defined by a single text line and another story sequence. The text line is the text that is displayed on the option buttons displayed to the user, and the corresponding sequence shows the following path that the scene continues down if that option is chosen. Every <<choice>> directive must end with an enclosing <</choice>> directive.

{{
    <<choice>>
    What is your favorite animal?
    	Dog
    	{{
    	    // If "Dog" is chosen
    		<<line>>Great! I like dogs, too.
    	}}
    	Cat
    	{{
    	    // If "Cat" is chosen
    		<<line>>Great! I like cats, too.
    	}}
    	Tardigrade
    	{{
    	    // If "Tardigrade" is chosen
    	    <<line>>Great! I like tardigrades, too.
    	}}
    	// More options, if desired...
    <</choice>>
}}

Conditional Event: A conditional event is a subsequence that is pursued in the scene only if a condition is met. Conditions use Variables as described in the Variables & Tagged Words section. Begin an If directive with <if> and enclose it with </if>. The <if> directive is followed by a single conditional check and a subsequence. The subsequence is pursued if the conditional check is true, and is skipped otherwise. Between <if> and </if>, you may have a single <else> statement that works like <if> but is pursued only if the condition described under <if> is false.

{{
    // Single If directive
    <if>
    my_stat ==4
    {{
        <<line>>Your stat is exactly equal to 4.
    }}
    </if>
}}
{{
    // If/Else
    <if>
    num_creatures <<4
    {{
        <<line>>There fewer than 4 creatures hidden in your wall.
    }}
    <else>
    {{
        <<line>>There are 4 or more creatures hidden in your wall.
    }}
    </if>
}}

Dimensions:

What do numbers mean? Numbers can mean different stuff depending on their units. The same is true in OVNS. Sometimes you want a sprite to be a specific size in pixels, and other times you want it to be a percentage of the screen size. Using Dimensions allows for some flexibility here. Dimensions are used for positional and sizing (geometric) values. At the moment, they do not apply to more abstract properties like color.

There are 5 DimensionUnits:

  • Absolute (ab): The default unit if none is specified. This value is not processed in any special way. What you give is what you get. Use this for specifying specific pixel sizes.
  • Percent Horizontal (%h): This represents a percentage of the screen width (horizontal). The value, stored as a fraction, is multiplied by the screen width to provide the final (absolute) value of the Dimension.
  • Percent Vertical (%v): This represents a percentage of the screen height (vertical). The value, stored as a fraction, is multiplied by the screen height to provide the final (absolute) value of the Dimension.
  • Percent Original (%o): This represents a percentage of some original relativeValue. The value, stored as a fraction, is multiplied by a given relativeValue to provide the final (absolute) value of the Dimension.
  • Percent Relative (%r): Another dimension representing a dimension that should be scaled relative to another dimension. Used in special cases like preserving aspect ratio. For sprite scale, 100%r represents making the dimension scale with aspect ratio (e.g. size:50%h,100%r makes the sprite 50% of the screen width wide and makes it as tall as it needs to be to preserve aspect ratio). For position, it refers to some percent of the other dimension’s value.

Use the units in the following situations:

  • ab – When no processing should be applied to the final value.
  • %h – When you want the final value to change with screen width.
  • %v – When you want the final value to change with screen height.
  • %o – Mostly used in animations, where there is special logic that scales %o properties by the original sprite property values. Use this when, for example, you want an animated sprite to scale to twice its original size, whatever that size is (200%o).
  • %r – Preserve aspect ratio when setting sprite slot size.

Dimensions currently work for:

  • Sprite Slot (size)
  • Sprite Slot (pos)
  • Animation (x)
  • Animation (y)
  • Animation (width)
  • Animation (height)
// Defining Example Animation:
// ------------------------------
// Stretches and squashes a sprite
// to first be wider and shorter
// and then taller and thinner before
// going back to its original size.
<<start_animation>> stretchnsquash
    0%:     width:100%o height:100%o
    33%:    width:130%o height:70%o
    66%:    width:70%o height:130%o
    100%:   width:100%o height:100%o
<<end_animation>>

{{
    // Set some settings
    <<set_settings>> main_character 2
    // Set the sprite to be 80% of the vertical screen height
    // in both width and height.
    <<sprite>> slot:2 size:80%v,80%v
    // Perform the stretchnsquash animation on it.
    <<animation>> key:stretchnsquash slot:2 time:0.5
}}

Variables:

Variables are arbitrary “boxes” that you may use to hold and edit information throughout the course of your visual novel. Variables can be used to check for conditional checks with an If directive. Variables are either integers, as defined by <<stat>>, or strings as defined by <<statstr>>.

Basic Operations:

You can set or modify integer variables by a constant or another integer variable.

{{
    // Set to constants
    <<stat>> temp1 =1
    <<stat>> temp2 =2
    // Addition, temp1: 1 -> 2
    <<stat>> temp1 +1
    // Multiplication, temp1: 2 -> 6
    <<stat>> temp1 *3
    // Division by another stat, temp1: 6 -> 3
    <<stat>> temp1 /temp2
    // Subtraction, temp1: 3 -> 2
    <<stat>> temp1 -1
    // Set to an existing stat (temp3: ? -> 2)
    <<stat>> temp3 =temp1
}}

String variables can only be set or added. Addition simply concatenates one string onto another. Remember to use double quotes (“…”) when using string literals.

{{
    // Set to constants
    <<statstr>> temp1 ="Hello"
    <<statstr>> temp2 ="!"
    // Addition with string literal, temp1: "Hello" -> "Hello World"
    <<statstr>> temp1 +" World"
    // Addition with variable, temp1: "Hello World" -> "Hello World!"
    <<statstr>> temp1 +temp2
}}

Randomness:

You can use basic random number generation with variables as well. Random number generation can be invoked in any integer operation (=,+,-,*,/). The generator produces an integer between two given values, including each endpoint.

{{
    // Produce a number between 25 and 30 (inclusive)
    <<stat>> rand1 =<<r 25 30>>
    // Can also be performed with operations
    <<stat>> rand1 +<<r 25 30>>
    <<stat>> rand1 -<<r 5 10>>
    <<stat>> rand1 *<<r 1 2>>
    <<stat>> rand1 /<<r 1 3>>
}}

Story Sequence Conditionals:

Variables can also be used to restrict viability of top-level story sequences. If a story sequence does not meet all the conditions defined with the <<cond>> directive, then the sequence will not be considered “viable”, which can be checked in custom code with the “StorySequence.meetsConditions” property.

// A top-level story sequence
{{
    <<cond>> num_plays==0
    <<line>>You have not played this story sequence before!
    // Changing the stat so that this sequence is no longer viable.
    <<stat>> num_plays +1
}}

Tagged Words:

Tagged words are words that can be randomly queried based on a set of tags from within the visual novel player. These are useful if your game is more repetitive or stochastic in order to add variety to similar, repeated scenes. You can define lists of words in .CSV files and apply tags to each word in order to assign them to those categories. Within your story sequences, you can then query your tagged words for a random word within some set of words defined by two set operations.

Your .CSV files should take the following format:

id,word,tag,tag,tag,...
2,word1,tag1,tag2,tag3,...
3,word2,tag1,tag4,,...
...

As an example, here is a small subset of tagged words which are animals. Each word can have as many or few tags as you want. Here, 4 words are tagged as animal, but only hippo, giraffe, and lion are tagged as large while pygmy hippo is tagged as small and variant. We also see house, which is large but not an animal.

id,word,tag,tag,tag,...
2,hippo,animal,large,,...
3,pygmy hippo,animal,small,variant,...
4,giraffe,animal,large,,...
5,lion,animal,large,,...
6,house,large,,,...

After defining all your tagged words through input files, you can then invoke a random tagged word through set operations on the tags:

  • && : Combine the words of each tag through an intersection. This should only shrink the viable word set. “Only words with both tag1 AND tag2”
  • || : Combine the words of each tag through a union. This should only increase the viable word set. “Words with either tag1 or tag2 or both.”
  • ! : Unary operator that allows any word except those with a certain tag.

Invoke a random tagged word with the <<w tag_operations>>

{{
    // This will output one of the following lines:
    // "One possible large animal is a hippo."
    // "One possible large animal is a giraffe."
    // "One possible large animal is a lion."
    <<line>>One possible large animal is a <<w animal && large>>.
}}

You can also reference a previous tagged word using <<p …>>. This references a prior (p) tagged word in the same line. The index refers to how many tagged words back you would like to reference. The index counting includes other references to prior tagged words.

{{
    // This should output:
    // "I like pygmy hippos and houses. I think pygmy hippos and houses are very cool."
    // The first <<p -2>> looks 2 tagged words back to <<w animal && small>>.
    // The second <<p -2>> looks past the first <<p -2>> and gets <<w large && !animal>>.
    <<line>>I like <<w animal && small>>s and <<w large && !animal>>s. I think <<p -2>>s and <<p -2>>s are very cool.
}}

Settings:

While possible to set sprite, text, and speaker information on-demand from within a story sequence, it can get cumbersome when lots of switching occurs. Settings help alleviate this problem by storing lots of this configuration information in a named set. Settings just need to be defined outside of a story sequence with <<new_settings>>, and can then be invoked in a story sequence with <<set_settings>>.

// Note that this is defined outside of a sequence.
// Speaker, text, and sprite directives defined after new_settings will apply to this names settings set.
<<new_settings>> main_character
// Set speaker box text and text color
<<speaker>> name:Bob color:1,1,1,1
// Set text box color and crawl noise and pitch.
<<text>> color:1,1,1,1 audio:crawl_noise pitch:1
// Set default character sprite, size, and flip settings
<<sprite>> key:bob_sprite_casual size:80%v,80%v flip:auto

Speaker Settings: Speaker settings mostly define the style of the text that shows up within the speaker box as well as basic display characteristics of the speaker box itself. The following describe all applicable attributes for the <<speaker>> setting directive:

  • name: Text that will be shown in the speaker box.
  • color: Color of the text in the speaker box.
  • font: Font key of the font used for speaker box text.
  • textsize: Font size of the speaker box text.
  • boxcolor: Color of the speaker box background.
  • boxmargin: Position and size of the speaker box relative to the upper left corner of the text box. The margins follow the format: position x, position y, width, height, where the positions are relative to the upper left corner of the text box and speaker box.
  • textmargin: Position and size of the text area in the speaker box relative to the dimensions of the speaker box. The margins follow the format: fromleft, fromright, fromtop, frombottom where each value represents the number of pixels from the left, right, top, and bottom of the speaker box the text is positioned.
// Speaker box settings:
// Speaker Text = Character
// Font Color = White
// Box Background Color = Transluscent blue
// Box Margin = 0px to the left of text box, 100px from top of text box, 500px wide, 90px tall.
// Font size = 50pt
// Text Area Margin = 20px from left, 10px from the right, 5px from top and bottom of box.
<<new_settings>> speaker_example
<<speaker>> name:Character color:1,1,1,1 boxcolor:0,50,255,210 boxmargin:0,100,500,90 textsize:50 textmargin:20,10,5,5

Text Settings: Text settings are similar to speaker settings, but these mostly define the style of the text that shows up within the main text box as well as basic display characteristics of the text box itself. The following describe all applicable attributes for the <<text>> setting directive:

  • color: Color of the text in the text box.
  • font: Font key of the font used for text box text.
  • textsize: Font size of the text box text.
  • boxcolor: Color of the text box background.
  • boxmargin: Position and size of the text box relative to the sides and bottom of the screen. The margins follow the format: fromleft, fromright, frombottom, height, where each value except the height represents the number of pixels from each side of the screen.
  • textmargin: Position and size of the text area in the text box relative to the dimensions of the box itself. The margins follow the format: fromleft, fromright, fromtop, frombottom where each value represents the number of pixels from the left, right, top, and bottom of the text box the text is positioned.
  • audio: Audio key for the audio clip that plays during text crawling.
  • pitch: Pitch value for the text crawling.
// Text box settings:
// Font Color = White
// Text Crawl Noise = tone
// Text Crawl Noise Pitch = 1.0
// Box Background Color = Transluscent blue
// Box Margin = 100px from left and right, 50px from bottom of screen, 230px tall.
// Font size = 50pt
// Text Area Margin = 50px from left and right of box, 20px from top and bottom of box.
<<new_settings>> speaker_example
<<text>> color:1,1,1,1 audio:tone pitch:1 boxcolor:0,50,255,210 boxmargin:100,100,50,230 textsize:50 textmargin:50,50,20,20

Sprite Settings: Sprite settings define some default settings for a sprite to be used :

  • slot: Only needed when showing a sprite. Defines the sprite slot to use to display the sprite.
  • key: The sprite key for the default sprite for these settings.
  • color: Color shade for the sprite (typically just 1,1,1,1 or 255,255,255,255 for white).
  • size: X and Y size of the sprite. You may define this using Dimensions (absolute pixel units, %v, %h, %o).
  • flip: Flip protocol for the sprite:
    • none: No flipping.
    • autox: Flips the sprite horizontally when on the right half of the screen.
    • autoy: Flips the sprite vertically when on the top half of the screen.
    • -autox: Opposite of autox.
    • -autoy: Opposite of autoy.
    • auto: Both autox and autoy.
    • -auto: Both -autox and -autoy.
  • pos: Position of the sprite. Position is a 2D vector describing the fraction of the screen away from the bottom left of the screen.
  • layer: Integer representing the sprite layer. Higher layer values are shown on top. Can be negative to describe how many layers from the top (e.g. layer:-1 would be the top sprite).
// Sprite settings:
// Sprite = orc_smile
// Sprite Color = White
// Sprite Size = 10x10
// Flip Rule = autox
// Position = 25% of the way from the left of the screen. On the bottom of the screen.
<<new_settings>> sprite_example
<<sprite>> key:orc_smile color:1,1,1,1 size:80%v,80%v flip:autox pos:25%h,0

Using Settings:

Settings can be invoked in a story sequence with <<set_settings>>. Invoking settings this way does not include the sprite information because sprites must be associated with a sprite slot. To display the corresponding sprite and change its settings, include a sprite slot key after the directive. The sprite slot key can be 0 through 4 for default sprite slots or an arbitrary string for custom sprite slots.

// Note that this is in a story sequence.
{{
    // Set the main_character settings
    // including a sprite slot key so that
    // the character's sprite will show up in
    // sprite slot 0.
    <<set_settings>> main_character 0
}}
{{
    // You don't need to include a sprite slot
    // if you don't want to change a sprite
    <<set_settings>> main_character
}}

You may also directly invoke custom setting changes on-demand with the <<text>>, <<speaker>>, and <<sprite>> directives from within a story sequence.

{{
    <<text>> boxmargin:300,300,200,230
	<<speaker>> name:Bob boxmargin:100,150,250,150
	<<sprite>> slot:0 key:orc_smile
}}

Sprites:

One of the most important parts of a visual novel is the visuals. That’s where sprites come in. As shown in the Settings section, you may specify the sprite key, color, size, flip rules, and more for sprite settings ahead of time. These settings, however, have little meaning without a sprite slot. A sprite slot is just an available box that can be changed to fit your sprite settings. In other words, a sprite slot is the “place” where you show your sprite. This occurs automatically when calling <<set_settings>> with a sprite slot argument, but you can also make a direct sprite invocation at runtime using the slot attribute in the <<sprite>> directive while inside a story sequence. The <<sprite>> directive allows the same sprite settings as while parsing <<new_settings>>, as well as for an on-demand approach. No need to create new settings each time you want to change a minor aspect of a sprite.

Other information/directives:

  • <<reset_slots>> slot1 slot2 … : Reset specific sprite slots to their initial/default state.
  • <<reset_all_slots>> : Resets all default sprite slots to their initial states and deletes all custom sprite slots.
{{
    // Set some settings
    <<set_settings>> main_character 2
    // Move sprite slot 2 75% of the way across the screen, change color to red, show sprite on top
    <<sprite>> slot:2 pos:75%h,0, color:1,0,0,1, layer -1
}}

{{
    // Reset default sprite slots
    <<reset_slots>>
}}

Animations:

Once you can show a sprite, you may also want to animate it. Animations allow you to define basic, CSS-style animations by creating keyframes which define the sprite properties at different times in the animation.

To define a new animation, you need two new directives and keyframes in the following formation:

// Define a new animation called "my_animation"
<<start_animation>> my_animation
    // Define each keyframe starting with
    // a percentage value representing the
    // property states at this point in the
    // animation. Keyframes take the form:
    // keyframe_percent1: keyframe_attribute1:value ...
    // For example:
    0%: color:1,1,1,1
    100%: color:1,0,0,1
    // At the start of this animation, the sprite color
    // is set to white and by the end of the animation
    // the color should have lerped to be red.
<<end_animation>>

The following are available properties that can be lerped with a keyframe:

  • width : The width of the sprite. It is advised to use Dimensions with this property.
  • height : The height of the sprite. It is advised to use Dimensions with this property.
  • x : The x position of the sprite. You may use Dimensions with this property.
  • y : The y position of the sprite. You may use Dimensions with this property.
  • color : The base color of the sprite. Define this as a vector of 4 values between 0 and 1.

New directives:

  • <<stop_animations>> slot1 slot2 … : Pass in slots for which you wish to stop all running animations.
  • <<start_animation>> animation_name: Start parsing animation keyframes.
  • <<end_animation>>: End parsing animation keyframes.
  • <<animation>> invocation_args… : Invokes a named animation on a sprite slot along with other invocation arguments. Here are the available invocation arguments:
    • key : Required. The name of the animation to play.
    • slot : Required. The sprite slot key to which you want to apply the animation.
    • time : Required. The amount of time the animation lasts per repetition.
    • block : The trigger block type.
      • none‘ : Default. Does not block execution of following directives.
      • auto‘ : Blocks execution of following directives while the animation is playing. Once the animation is over, execution proceeds automatically to the next directive.
      • wait‘ : Blocks execution of following directives while the animation is playing. Once the animation is over, it remains blocked until the user provides input.
    • interrupt : Boolean. If true, then running this animation will auto-finish any animations currently playing on the target sprite slot before beginning. If false, this animation applies on top of any current animations.
    • reset : Boolean. Whether the sprite slot should revert to its pre-animation state at the end of the animation.
    • repeat : An integer describing how many times the animation should repeat. Default is 1.
    • repeatType : How the animation repeats.
      • loop‘ : Default. The animation starts over from the beginning at the start of each repetition.
      • reverse‘ : The animation regresses in the opposite direction once it finishes. This causes the animation to take twice as long for one repetition.
    • ease : Which preset ease function to apply to the animation fraction.
      • linear‘ : Default. The output value follows the form f(input) = input.
      • easeInOutCubic‘ : A function that makes the animation slower at both ends and faster in the middle.
      • easeInOutElastic‘ : Like easeInOutCubic except with some elastic properties.
      • easeInQuint‘ : A function that starts slow and speeds up at the end.
      • easeOutQuint‘ : A function that starts fast and slows down at the end.
    • delay : The amount of time to wait before the animation initially starts.
    • repeatDelay : The amount of time to wait between each repetition of the animation.
// Defining Example Animations:
// ------------------------------
// Stretches and squashes a sprite
// to first be wider and shorter
// and then taller and thinner before
// going back to its original size.
<<start_animation>> stretchnsquash
    0%:     width:100%o height:100%o
    33%:    width:130%o height:70%o
    66%:    width:70%o height:130%o
    100%:   width:100%o height:100%o
<<end_animation>>
// ------------------------------
// Cycles through different color
// hues on the sprite.
<<start_animation>> hue_cycle
    0%:         color:"1,0,0,1"
    16.67%:     color:"1,1,0,1"
    33.33%:     color:"0,1,0,1"
    50%:        color:"0,1,1,1"
    66.67%:     color:"0,0,1,1"
    83.33%:     color:"1,0,1,1"
    100%:       color:"1,0,0,1"
<<end_animation>>
// ------------------------------
// Fades out the sprite.
<<start_animation>> fade_out
    0%:     color:"1,1,1,1"
    100%:   color:"1,1,1,0"
<<end_animation>>

{{
    // Set some settings
    <<set_settings>> main_character 2
    // Set the sprite to be 80% of the vertical screen height
    // in both width and height.
    <<sprite>> slot:2 size:80%v,80%v
    // Perform the stretchnsquash animation on it.
    // Once it is done, it requires user input to move
    // on to the next directive because block=wait.
    <<animation>> key:stretchnsquash slot:2 time:0.5 block:wait
    // Fades out the sprite and fades back in twice.
    // repeatType=reverse causes the fade-in and repeat=2 makes
    // the entire cycle happen twice.
    <<animation>> key:fade_out slot:2 time:0.5 block:wait repeat:2 repeatType:reverse
    // Now we cycle through the hues a bunch and show a line
    // at the same time because block=none (default).
    <<animation>> key:hue_cycle slot:2 time:1 block:none repeat:100 reset:true
    <<line>>This should show while the sprite animates.
    // If we continue from the prior line before the last animation completes,
    // then it stops the color-flashing animation early (interrupt=true),
    // resets the sprite to its normal color (reset=true), and begins playing
    // the stretchnsquash animation
    <<animation>> key:stretchnsquash slot:2 time:0.5 block:wait interrupt:true
}}

Audio:

TBD

{{
    
}}

Macros:

As flexible as the system is, it may be cumbersome at times to copy-paste similar sections over and over depending on how you want your visual novel sections to look. This is where macros come in. Macros are procedures that define a custom story sequence that can be reused within other story sequences.

First, macros are defined outside of story sequences with the <<macro>> directive and must be ended with the <</macro>> directive. You may place any directives that you can define in a normal story sequence within this macro definition. You must also include a name after the macro directive. Here is an example directive that “highlights” the sprite in slot 0 by dimming all the other sprites:

// Define a macro named "focus0"
// It dims slots 1-4 and brings slot 0 to the front.
<<macro>> focus0
<<sprite>> slot:1 color:0.3,0.3,0.3,0.5
<<sprite>> slot:2 color:0.3,0.3,0.3,0.5
<<sprite>> slot:3 color:0.3,0.3,0.3,0.5
<<sprite>> slot:4 color:0.3,0.3,0.3,0.5
<<sprite>> slot:0 color:1,1,1,1 layer:-1
<<macro_end>>

To invoke a macro within a story sequence, all you need to do is invoke the <<macro>> directive with the corresponding macro name.

{{
    // setup sprites
    <<sprite>> slot:0 key:orc_smile color:1,1,1,1
    <<sprite>> slot:1 key:orc_smile color:1,1,1,1
    <<sprite>> slot:2 key:orc_smile color:1,1,1,1
    <<sprite>> slot:3 key:orc_smile color:1,1,1,1
    <<sprite>> slot:4 key:orc_smile color:1,1,1,1
    // focus slot 0
    <<macro>> focus0
}}

This works well, but can become cumbersome in its own right when you want to make different macros that are all slightly different from each other. To solve this, macros also have basic parameter inclusion. After the macro name, you can define any number of parameters that must be passed into the macro during invocation in the story sequence. These parameters use a basic find-and-replace preprocessing approach, where every occurrence of the parameter name in the contained macro lines is directly replaced with the input during invocation. As an example of a revised “focus” macro, here is one that allows the user to pass in the slot key that should become focused:

// Macro named "focus"
// with a single input parameter named "slotKey"
<<macro>> focus slotKey
<<sprite>> slot:0 color:0.3,0.3,0.3,0.5
<<sprite>> slot:1 color:0.3,0.3,0.3,0.5
<<sprite>> slot:2 color:0.3,0.3,0.3,0.5
<<sprite>> slot:3 color:0.3,0.3,0.3,0.5
<<sprite>> slot:4 color:0.3,0.3,0.3,0.5
// NOTE: slotKey in this line will be find-replaced
// with whatever argument you invoke the macro with.
<<sprite>> slot:slotKey color:1,1,1,1 layer:-1
<<macro_end>>

{{
    <<reset_slots>>
    // setup sprites
    <<sprite>> slot:0 key:orc_smile color:1,1,1,1
    <<sprite>> slot:1 key:orc_smile color:1,1,1,1
    <<sprite>> slot:2 key:orc_smile color:1,1,1,1
    <<sprite>> slot:3 key:orc_smile color:1,1,1,1
    <<sprite>> slot:4 key:orc_smile color:1,1,1,1
    // Macro "focus" called with
    // input argument 0 for slotKey.
    // The modified line in the macro would then look like:
    // <<sprite>> slot:0 color:1,1,1,1 layer:-1
    <<macro>> focus 0
}}

WARNING: The simplicity of the parameter feature has the potential to cause unintended side-effects. Because things are find-replaced, please try to pick a parameter name that you will not use anywhere else in the macro. Here’s an example of a macro gone wrong:

<<package>> test
<<stat>> stat_macro_bad =0

// Macro named "bad"
// with parameter named "max"
<<macro>> bad max
<<line>>I made this cool macro with a parameter named "max".
<<line>>It should only define the upper bound for my random roll here.
<<stat>> stat_macro_bad =<<r 0 max>>
<<line>>See, I rolled a <<v test_stat_macro_bad>> when the max value was max
<<macro_end>>

{{
    <<line>>Prepare for the macro invocation...
    <<macro>> bad 1234
    <<line>>I think there was something wrong with that...
    // The macro printed something like:
    // 		I made this cool macro with a parameter named "1234".
	//		It should only define the upper bound for my random roll here.
	//		See, I rolled a <randomnumber> when the 1234 value was 1234.
}}

Here are some fun macros as examples of how they can be useful and convenient:

// Macro Example:
//  Faction Favor
//  Given inputs, it displays a choice and alters
//  faction favor stats depending on the selected option.
<<package>> test
<<stat>> faction1_favor =0
<<stat>> faction2_favor =0
<<stat>> faction3_favor =0

// Macro named "faction_choice"
// With parameters:
//  fc_desc: Choice Desciption
//	fco1: First faction stat name prefix
//	fco2: Second faction stat name prefix
//	fcon2: First faction display name
//	fcon2: Second faction display name
//	f1stakes: Amount of favor on the line for first faction
//	f2stakes: Amount of favor on the line for second faction
<<macro>> faction_choice fc_desc fco1 fco2 fcon1 fcon2 f1stakes f2stakes
<<choice>>
fc_desc
    fcon1
    {{
        <<stat>> fco1_favor +f1stakes
        <<stat>> fco2_favor -f2stakes
        <<line>>Gained f1stakes favor with fcon1.
        <<line>>Lost f2stakes favor with fcon2.
    }}
    fcon2
    {{
        <<stat>> fco1_favor -f1stakes
        <<stat>> fco2_favor +f2stakes
        <<line>>Gained f2stakes favor with fcon2.
        <<line>>Lost f1stakes favor with fcon1.
    }}
<</choice>>
<<macro_end>>

{{
	<<speaker>> name:"The Narrator"
    <<macro>> faction_choice "Who will you choose?" faction1 faction2 "Faction 1" "Faction 2" 1 1
	<<macro>> faction_choice "Who will you choose?" faction1 faction3 "Faction 1" "Faction 3" 1 1
	<<macro>> faction_choice "Who will you choose?" faction2 faction3 "Faction 2" "Faction 3" 1 1
	<<line>>Faction 1 favor: <<v test_faction1_favor>>
	<<line>>Faction 2 favor: <<v test_faction2_favor>>
	<<line>>Faction 3 favor: <<v test_faction3_favor>>
	
}}
// Macro Example:
//  Animals
<<package>> test

// Macro named "animal_choice"
// With parameters:
//  animal_tag: tagged word tag for animal group
<<macro>> animal_choice animal_tag
<<line>>I think you would like a <<w animal && animal_tag>>!
<<line>>Maybe not a <<w animal && !animal_tag>>, though...
<<macro_end>>

{{
	<<speaker>> name:"Sammy Smiles"
	<<choice>>
	What kind of animals do you like?
	I like big animals!
	{{
	    <<macro>> animal_choice large
	}}
	Small animals are more my style.
	{{
	    <<macro>> animal_choice small
	}}
	<</choice>>
}}

Directive Summary:

Setup:

  • <<package>>: Package definition. Defines the default package for the sequence file.

Basic Events:

  • <<line>>: Line directive. Use it to change the text showing in the text box.
  • <<choice>>: Choice directive. Use it to display description text and provide options to the player.
  • <if>: Conditional check. Checks whether provided condition is true, and if so, branches into a subsequence.
  • <else>: Fallback for conditional checks. If the <if> condition is false, the <else> subsequence is executed instead.
  • </if>: End of conditional check.
  • <<cond>>: Sequence condition directive. Checks a conditional statement like the <if> directive, but alters the viability of the story sequence as a whole.

Variables/Stats:

  • <<stat>>: Stat directive. Initialize or edit an exiting integer stat.
  • <<statstr>>: String stat directive. Initialize or edit an existing string stat.
  • <<r min_value max_value>>: Randomness directive. Used when initializing or modifying an integer stat. Rolls a random integer between min_value and max_value. Inclusive of min_value and max_value.

Tagged Words:

  • <<w tag_expression>>: Retrieve a random tagged word matching tag_expression.
  • <<p relative_index>>: Retrieve a previously retrieved tagged word that is relative_index retrieved words away from the the <<p…>> directive.

Settings:

  • <<new_settings>>: Define the start of a new settings set.
  • <<set_settings>>: Invoke named settings and apply those settings to the speaker box, text box, and optionally sprite slots.
  • <<speaker>>: Define speaker box-specific settings. Also use as directive in story sequence to change speaker settings on-demand.
  • <<text>>: Define text box-specific settings. Also use as directive in story sequence to change text settings on-demand.
  • <<sprite>>: Define sprite settings. Also use as directive in story sequence to change sprite settings on-demand.
  • <<reset_slots>>: Resets default sprite slots to their initial settings.

Animations:

  • <<start_animation>>: Define the start of an animation. Include the animation name on the same line.
  • <<end_animation>>: End parsing the currently defined animation.
  • <<animation>>: Invoke an animation by name with multiple optional input arguments.

Macro:

  • <<macro>>: Begin macro definition.
  • <<macro_end>>: End macro definition.

v0.9.1

December 1, 2024

  • Changed the way on-demand sprite modifications work.
  • Removed the <<slot>> directive.
  • Added the slot: attribute to the <<sprite>> directive. To invoke a sprite change on a slot, now call <<sprite>> slot:my_slot …

v0.9.2

December 2, 2024

  • Changed the VN logic so that non-blocking events (settings, sprites, etc.) no longer cause a 1-frame delay in processing. Previously, when a non-blocking event was run, execution yielded until the next frame. Now, execution continues on the event queue until a blocking event is reached or the event queue empties. No more stacking 1-frame delays when many non-blocking events are adjacent to one another.

v1.0.0

March 6, 2025

  • Version 1! Why is this version 1? Because I felt like it.
  • Added limited CSS-style animations for sprites. Pre-define animations with keyframes that vary properties like size, position, and color.
  • New/Changed directives:
    • <<reset_slots>>: You now pass in slot keys which you want to reset.
    • <<reset_all_slots>>: Now resets all slots and deletes all custom sprite slots.
    • <<stop_animations>>: Pass in slots for which you wish to stop all running animations.
    • <<start_animation>>: Start parsing animation keyframes.
    • <<end_animation>>: End parsing animation keyframes.
    • <<animation>>: Invokes a named animation on a sprite slot along with other invocation arguments.
  • New measurements. Now we have Dimensions, which are just numbers with a unit. Most use absolute units, but some can define values which are percentages of other values. These include percent of screen width, screen height, or some custom “relative value”. This allows you to do things like resize a sprite to be “80% of the screen height tall”, etc.
    • Sprite position and scale now use Dimensions.
  • Lots of new classes and stuff that you don’t care about:
    • AnimationMap: Maps animation names to animations.
    • Animation: Represents a series of Keyframes. Can query the Animation at various “fractions” through [0-1].
    • Keyframe: Defines property states at a certain percentage through the Animation.
    • PropertyTimeline: Kind of inverse of a Keyframe. Represents a single property’s values at all points through an Animation.
    • AnimationInvocation: Represents a call to an Animation.
    • RunningAnimation: Represents the current state of a running Animation.
    • Dimension: A scalar with an attached unit. Convert or combine Dimensions by casting to absolute units.
    • IRunningTrigger: Represents any kind of running trigger. Helpful for TriggerControl to managed blocking trigger directives.
    • Others that are ancillary to the animation stuff.
  • Lots of bugfixes along the way.