Word Substitution
Word Substitution replaces a word in spoken text based on an Actor's properties.
This is useful for gendered language, for example when the text needs to say "He" or "She" depending on the player's gender.
Writing a Substitution
In a Spoken Line's text, a substitution is written like this:
Have you seen •him¦Alice•? •He¦Alice• left early.
If Alice's Gender property is "F", this displays: "Have you seen her? She left early."
The keyword and Actor Reference can also be variable references or Role references:
Have you seen •him¦°companion•?
Here, the Actor currently assigned to the "companion" Role is used.
Symbols
The • and ¦ symbols can be customized in Candy_Properties.gd, through the substitution_start_symbol, substitution_end_symbol and separator_symbol variables.
The start and end symbols can be identical. The separator symbol is shared with other features, such as comparisons in the Substitution Table (see below).
Substitution Table
The substitution rules are defined in the substitution_table dictionary, in the Substitution_Table.gd script. It's a 'safe' file: updates never overwrite it.
The table has the following components.
var substitution_table := {
"Default*": { # Variation Table
"he, she": [ # Keywords
"auto", # Case Rule
{"Gender": { # Property Rule
"M": "he", # Value: Substitute
"F": "she",
}},
],
},
}
Variation Tables
The top-level keys are matched against the name of the Spoken Line's chosen Variation (e.g. "Default", "French", "Default_S=F", etc.). This is helpful for creating separate rules for each language.
- "*" applies to every Variation.
- A key ending with * (e.g. "Default*") applies to every Variation whose name starts with it.
- A key without * (e.g. "Default") only applies to the Variation with that exact name.
Keys are case-sensitive, like Variation names: "french" doesn't apply to the French Variation.
When several keys contain the keyword, the most specific one is used: an exact name first, then the longest key ending with *, then "*".
Note that the most specific key's entry is used as a whole: its rules are not combined with the rules of less specific keys.
Keywords
Each key inside a Variation table (e.g. "he, she") lists one or more keywords, separated by commas.
Writing any of them in a substitution uses that entry, so you can write whichever reads best in your dialogue: •he¦Alice• and •she¦Alice• return the same result.
Keywords in the dictionary are not case-sensitive, but they are case-sensitive in spoken text (see 'Case Rule' below).
Case Rule
The first element of a keyword's list can be a case rule, which decides the case of the displayed word:
- "auto" (or "a"): follows how the keyword is written in the substitution ("he" → "she", "He" → "She", "HE" → "SHE").
- "lower" (or "l"): always lowercase ("he", "He", "HE" → "she").
- "upper" (or "u"): always uppercase ("he", "He", "HE" → "SHE")
- "cap" (or "c"): always capitalized ("he", "He", "HE" → "She").
- "custom" (or "x"): writes the Substitute exactly as in the table.
If the list doesn't start with a case rule, "auto" is used by default.
Property Rules
After the optional case rule, a keyword's list holds one or more dictionaries. Each one names an Actor property (from the actors dictionary, in Actors.gd), and maps its values to the word to display:
{"Gender": {
"M": "his",
"F": "her",
}},
Rules are checked in order, and the first one that matches is used.
Values are compared as text: a Gender of "M" matches the rule "M".
Nested properties can be read with dots, through dictionary keys or array indexes: "Stats.Age", "Titles.0".
Multiple Properties
A dictionary can check multiple properties at once, separated by commas. Each rule then checks one value per property, in the same order:
{"Class, Gender": {
"Warrior": "Warrior",
"Sorcer, M": "Sorcerer",
"Sorcer, F": "Sorceress",
}},
In the example above, the "Sorcer, M" case compares the 'Sorcerer' value to the Actor's Class, and the 'M' value to the Actor's Gender.
Notice that a rule can have fewer values than there are properties: the "Warrior" case compares the 'Warrior' value against the Actor's Class, and compares nothing against the Actor's Gender.
To skip a property in a rule, write '_' in its place:
{"Gender, Class": {
"_, Warrior": "Warrior",
"M, Sorcer": "Sorcerer",
}},
In this example, notice that the properties have been inverted: the Gender property is now first, and Class is second. In the "_, Warrior" case, no value is compared to the Actor's Gender (the first value is '_'), and the 'Warrior' value is compared to the Actor's Class. Without the '_', 'Warrior' would be compared to the Actor's Gender.
Comparisons
Number properties can be compared with >, <, >= or <=, followed by the separator symbol and a number:
{"Age": {
">=¦18": "adult",
"<¦18": "minor",
}},
To check a range, name the same property twice:
{"Age, Age": {
">=¦18": "Adult",
"<¦18, >¦11": "Teenager",
"<¦18, <=¦11": "Child",
}},
A comparison never matches a property that isn't a number.
Types
A value can be assigned a type, after the separator symbol: int, float, bool or string.
The Actor's value must then match the type, and both are compared as that type:
"18¦int": "...",
"true¦bool": "...",
Without a type, an Actor's value of 18 (int) and a property of "18" (string) both match a rule value "18". With "18¦int", the Actor's value is only considered a match if it's also an int.
A type followed by "type" only checks the property's type, whatever its value.
"int¦type": "...",
"bool¦type": "...",
"array¦type": "...",
To clarify: a rule value of "int¦type" matches if the Actor's value is any int.
Multiple Property Rules
Substitution cases (i.e. keywords) can feature multiple Property Rules, like this:
"id": [
"auto",
{"Origins": {
"France": "French",
"England": "English",
"Italy": "Italian",
}},
{"Language": {
"FR": "French",
"ENG": "English",
"IT": "Italian",
}},
],
By default, only the first Property Rule is checked. In the example above, this would be Origins.
To choose another Property Rule, a third item can be added to the substitution string in the spoken text:
"Alice is a •id¦Alice¦Origins• student."
or
"Alice is a •id¦Alice¦1• student."
- A number picks a Property Rule by its order (not by index -- the first dictionary is always '1' even if there is no case rule!).
- A property name picks the dictionary that checks it: •id¦Alice¦Origins•. For a dictionary that checks several properties, write them all, in the same order and with the same case: •cl¦Alice¦Gender, Class•.
This can be helpful when you want to re-use a keyword for different Property Rules.
No Match
If a substitution can't find and match, it is left in the text as written (the Engine prints a warning to help you identify which Line the issue is in).
This can happen when:
- The substitution has no Actor Reference.
- The Actor isn't in the actors dictionary.
- No Variation key matching the line's variation has the keyword.
- No rule matches the Actor's properties (or the Actor doesn't have the property).
- The matching rule's word is empty.
To give a substitution a fallback word, end its dictionary with a rule that skips every property, such as "_". Note that it only matches Actors who have the properties.
Characters to Avoid
Commas and the separator symbol divide the table's keys, so they can't be part of a value:
- Don't use commas or the separator symbol (¦) in keywords, property names or rule values. "Smith, John" would be read as two values, "Smith" and "John".
- Don't use _ alone as a rule value: it always skips the property instead of matching "_".
- Array and dict values can't be written in a rule, since they contain commas: only check their type ("array¦type", "dict¦type").
In spoken text:
- Don't use the separator symbol or the end symbol (•) inside a substitution's parts.
Escaping Symbols
If you want to display the • symbols literally, you can escape them by writing them twice: ••
This will display • on screen, and the text that follows won't be mistaken for a variable.
This works with your own custom symbols, and even with symbols made of multiple characters.
The separator_symbol ¦ doesn't have an escape because it does not need one: when written outside of the • • symbols, it is already displayed literally.