Vivifica dialogue system
Vivifica is the only system in Chronicles of Torisia that is so powerful, that I even gave it a name :D While I built it for extreme flexibility and simplicity, it also means it's easy for something to break.
All dialogue lines are stored in an SQLite table DialogueLines.
Dialogue indexes
Dialogue indexes are used to create branching dialogue. Indexes follow a simple structure. The dialogue goes to whatever index the selected reply option tells it to go to.
Tip: It's recommended to use very high differences between indexes (like 1, 1000, 2000) instead of 1, 2, 3 as it makes branching a lot easier to read and change
Examples:
- Player is on a line with index 3 which didn't open reply options so Vivifica automatically continues by going to a line index stored in reply 1 index.
- Player is on a line with index 3 which opened reply options. Vivifica continues based on what reply option the player clicks on - clicking on option 4 leads to index stored in reply index 4.
Every line must have a valid index and at least one reply target index.
Vivifica opens reply options automatically. This is done purely based on if the reply 2 text field contains something or not.
- Reply 2 text field is empty - Vivifica doesn't show reply options and automatically goes to the reply 1 target index as if the player clicked on it.
- Reply 2 text field contains something - Vivifica reads the text of all replies and builds the reply options based on that.
Special indexes
You can use only numbers from 1 and higher for indexes. 0 are ignored and negative values are reserved for special interactions. All special indexes can be used only as a reply index.
- 1
- This index is reserved as a guided exit and . If the reply option itself leads to index - 1 right by clicking on it, it colors its text to orange.
- Requirements:
- None but reaching it always closes the dialogue
- Common uses:
- Closing dialogue
- With instructions to give/receive quest since it's leading the player towards it by coloring the path yellow.
- 2
- This index is reserved as a return index. Upon reaching it, the dialogue automatically moves back to whatever line is saved as "returnNode".
- Requirements:
- It must have a node to return to, this is created by using "returnNode" instructions
- Common uses:
- Any kind of "fluff" dialogue like history of settlements, asking questions about Torisia, etc.
- 3
- Opens NPC actions
- Requirements:
- None
- Common uses:
- Most commonly used when the NPC cannot interact right away and showing NPC actions must be delayed (E.g. NPC is sleeping
- 100
- This index is reserved as a placeholder. Reaching it does nothing but it avoids breaking Vivifica.
- Requirements:
- It must be used with reply instructions to actually do something, otherwise nothing will happen
- Common uses:
- Anything that needs to execute instructions but won't continue in the current dialogue
- Services - Open UI windows like trade, bank, etc.
- Loading different dialogue from the current one
NPC actions
Whenever you talk to NPCs, Vivifica collects all possibilities this NPC can talk about.
- If there's only one possibility, it triggers right away. E.g. - opens trade right when you start talking to the NPC
- If there's multiple possibilities, it opens a reply menu, allowing you to choose what to talk about.
Overrides
This behavior can however be overriden.
- NPC is sleeping - In this state, the NPC finds its line to say by looking for ID "default_npcIsSleeping"
- NPC is talking to someone else - In this state, the NPC finds its line to say by looking for ID "default_npcIsYapping"
Flags
Vivifica allows creating flags for custom conditions without ever touching the code. These flags are also stored in long term memory of NPCs so they persist even through loading a save file. There are 2 types of flags:
- Static - these flags have a code support as they require handling that cannot be scripted through VitaScriptum alone.
- Custom - these flags can be created by VitaScriptum without touching the code. However, they're limited to only using and comparing one float value and their time of last update.
VitaScriptum flag commands:
Set and update flag
Both instructions work in almost the same way except for the way the set values.
Syntax: setFlag:flagType:flagID:flagValue:cooldown or updateFlag:flagType:flagID:flagValue:cooldown
- FlagType: Sets flag type, this works essentially as a group of flags. Since done manually, it should technically support almost any name but it's strongly recommended to set custom flag type as "custom" to avoid future conflicts. Before adding a new type, you should check "Built in flags" under this text to make sure this functionality doesn't exist yet.
- FlagID: Sets flag ID, this ID is not used by code at all, it's only used to find a specific condition that checks for it.
- FlagValue: Sets flag value in a decimal format with a big difference between commands -
- setFlag: Creates a flag and sets its value, if the dialogue hits setFlag for existing flag, it does nothing. E.g. this flag is at value 30, setFlag:custom:fartsReleased:-5 does nothing
- updateFlag: Creates a flag and sets its value but it will add a value to it on every trigger. Also supports negative values. E.g. this flag is at value 30, setFlag:custom:fartsReleased:-5 changes the flag's value to 25
- setGlobal and updateGlobal: Work exactly like setFlag and updateFlag but for a global (not NPC specific) memory. E.g. this flag is at value 30, setGlobal:custom:fartsReleased:-5 changes the flag's value to 25
- forceUpdateFlag and forceUpdateGlobal: These work in the same way as their regular version but they bypass the flag's cooldown
- Flag cooldown: All flags track the last time they've been updated. You can use flag cooldown to set the time before this flag can be updated again. E.g. setting cooldown as 20 means that if you update a flag, it can be updated again only at least 20 seconds later - Useful to prevent updating flags (like opinion) too often or even multiple times per dialogue. Its timer can also be accessed via memoryTime and used in conditions but you can also set it to 0 to ignore.
- Outside of entering static values, flags can also parse command values.
- now - Stores the current playtime in seconds. E.g. setFlag:custom:timeWhenFarted:now
Reading flags:
Read also: conditions
You can use flags stored in the NPC's long term memory for conditions via VitaScriptum. Every flag has 2 values. You can choose what value to compare by changing the start of your condition command.
Format: memory:flagType:flagID:flagValue, E.g. memory:opinion:player:< 50 checks if the creature's opinion on player is below 50.
- memory - Uses the stored value handle by setFlag or updateFlag
- memoryTime - Is changed automatically and cannot be changed manually but it can be read. It stores the total play time (since you clicked on "New game") in seconds and it automatically updates to the current total play time anytime its value changes.
- global - Stores global values that cannot be tied to NPCs themselves.
Flag type and flag ID must match what is in the NPC's memory. If the flag is not there, it will simply not do anything.
Built in flag types:
These flag types behave differently to what are custom made flags capable of.
- opinion: Stores NPC's opinion on the player. It should be read only. For changes, you should use forceChangeOpinioninstruction instead, E.g. "forceChangeOpinion:30"
- opinionTimeSinceDecay: Used to store the last time the player visited this NPC to calculate its opinion decay based on it. Should never be changed manually as it's updated by opening dialogue window. To get the time, you should access "last changed" parameter via "memoryTime" command. Its value is always 0.
- item: This flag is automatically created with ID gaveItem-itemName when the item is spawned by spawnItem instructions.
- amount: These flags are created automatically with these IDs
- visits - Tracks the amount of time player visits this NPC
- petted - Tracks the amount of times this NPC has been petted. Updates with a 60s cooldown
- goldPlayerGained INACTIVE - Tracks the amount of gold player received from this NPC by trading. Both gold options are adjusted to level gold inflation so selling at level 1 can do as much as selling at level 60
- goldPlayerSpent INACTIVE - Tracks the amount of gold player spent to this NPC by trading
- itemPlayerSold INACTIVE - Tracks the amount of items sold to this NPC. This gives every item +1, regardless if it's a potion or a weapon
Triggers
Vivifica can create triggers when used as instructions that listen for a certain action to happen and return back to a dialogue index after.
Syntax: trigger:triggerName:targetIndex:npcID but you can use 3 parameters instruction only (without npcID). Doing so is better in most situations as it automatically uses the ID of the NPC the player is talking to. Using ID manually would
Requirements:
- There can be only one trigger active at one time.
- You might want to use "close" instructions on trigger creation if there's something supposed to happen (like a cutscene)
How it works:
- You create a trigger with trigger:instruction.
- Vivifica will store the trigger name and wait. (Ideally you close the dialogue as otherwise the game will stay paused)
- Once any trigger in the game activates that's using the same name as the trigger name in command, it reopens the dialogue window with the same dialogue but on targetIndex specified in instructions.
Usage
- Useful whenever you need to do something by unpausing the game
| Trigger | When it triggers |
|---|---|
| trigger:wokeUp:dlgIndexToReopen | Reopens dialogue when the talked to NPC wakes up |
VitaScriptum instructions
Chronicles of Torisia uses a custom built VitaScriptum scripting language to allow dialogue to trigger actions. There are 2 ways it can do so.
- On load - These instructions are executed right when the dialogue line starts being written
- On reply - These instructions are executed when clicking on a reply option. If there's only one reply option (so Vivifica goes to its target index instantly), it executes these instructions the moment the dialogue text disappears.
Syntax:
- Vivifica can execute unlimited amount of commands but every command must be separated by a semicolon ";".
- Command parts are split by a colon ":".
- Instructions are executed from left to right so it's recommended to use any dialogue closing instructions like openTrade as a last instruction.
| Command | Info |
|---|---|
| showDlgText:dialogueID |
|
| acceptQuest:questID |
|
| finishQuest:questID |
|
| changeName:name |
|
| pet |
|
| openTrade |
|
| openPetBank |
|
| openEnchant |
|
| spawnItem:itemName:amount:location |
|
| changeOpinion:amount |
|
| takeItem:itemName:amount |
|
| close |
|
| wakeUp |
|
| hideIf:replyOptionIndex:condition |
|
| giveExp:skillType:amount |
|
| addStatus:statusName:potency:duration |
|
| removeStatus:statusName |
|
| stopTalking |
|
| acceptDelivery | |
| createDelivery:itemName:
itemAmount:dialogueID:gold:exp |
|
| addQuestProgress:questType:
objectiveID:amountToAdd:QuestID |
|
| showHandInOptions:questID |
|
| addLoreOptions:dialogueID:dialogueIndex |
|
| returnNode |
|
| tempValue |
|
| blockClose |
|
| speechBubble:trigger:speechBubbleRowID |
|
| reportCrimes | NPC reports its unreported crimes to the reputation faction they belong to. Can be used anywhere, though it doesn't make much sense outside of talking to a guard |
| stopArrest | Wipes the player's crimes in what reputation faction does the NPC belong to. Most commonly used when player convinces guards to stop attacking |
| hostileArrest | Gives NPC (guard) aggro towards the player to start attacking them. Most commonly used when the player resist an arrest |
| jailPlayer | Moves player to jail. Most commonly used when player agrees with this when caught for committing crimes. |
| punishPlayer | Applies all punishments to player based on their crimes. Used for punishing for the crimes that do not require going to jail |
| addCrime:crimeType | Attempts to add crime report to the NPC that the player is currently talking to. The crime still must not be on a cooldown and the NPC has to report it first. |
| investInGold | Invests in merchant's gold capacity. Is done automatically via lines in default_investingGold. |
| investInProfitShare | Invests in profit share with the merchant. Is done automatically via lines in default_investingProfitShare. |
| openInvestmentOptions | Opens investment options for this merchant. |
Conditions
Conditions are used to filter dialogue lines. If a condition does not return true, the entire dialogue row is ignored. VitaScriptum also supports complex conditions and unlike with instructions, you shouldn't use semicolon ";" between conditions but operators instead.
VitaScriptum supports:
- | as an "OR" symbol
- & as an "AND" symbol
You must use one of these symbols between every 2 conditions. VitaScriptum always prioritizes & symbol first.
| Condition | Result | Explanation |
|---|---|---|
| true&true | true | With & symbol, all conditions must be true |
| true&false | false | With & symbol, all conditions must be true |
| true|true | true | With | symbol, at least one condition must be true |
| true|false | true | With | symbol, at least one condition must be true |
| false|false | false | With | symbol, at least one condition must be true |
| false|false&true | false | false&true return false so neither | symbol sides return true |
| true|false&true | true | false&true return false but there is at least one | side returning true |
| false|true&true | true | true&true return true so there is at least one | side returning true |
Negation
Some conditions allow you to use the ! prefix to invert their value. These are:
- status
- inCombat
- hasItem
- questState
- weather
While status:burn returns true only if the player is currently burning, using status:!burn returns true only if the player is NOT burning.
Value comparissons
VitaScriptum supports comparing numerical values. Most commonly for flags but it works with regular commands too, like hasItem. To use it, simply add a symbol before the numeric value. E.g.: hasItem:Apple:<5 returns true if the player has less than 5 apples in their inventory.
Supported symbols are:
- < Returns true if the in game value is lower
- > Returns true if the in game value is higher
- >= Returns true if the in game value is higher or the same
- <= Returns true if the in game value is lower or the same
- == Returns true if the in game value is the same
Special prefixes
Value comparissons can also handle prefixes to do simple math actions. To use this prefix, it should be in front of the comparisson symbols (E.g. cd<5).
- cd - This prefix for cooldown is very useful for comparing any time values (often used with memoryTime flag). Also works to check global flags (global: or globalTime:)
- memoryTime:item:gaveItem-Egg:<50 - This returns true if the time stored in this flag is under 50 seconds so it triggers only as long as it's been under 50 seconds since starting the current playthrough.
- memoryTime:item:gaveItem-Egg:cd<50 - This uses time difference as a value to compare so it returns true only if it's been under 50 seconds since the flag got updated (as Vivifica always updates the time when updating flags).
Commands
There are all commands that can be used by the conditional system. You can also use memory: to access NPC's flags.
| Command | Info |
|---|---|
| questState:state:questID |
|
| questObj:state:rowID |
|
| status:statusName |
|
| inCombat |
|
| hasItem |
|
| level |
|
| weather:weatherType |
|
| repSource |
|
| hasPetOut | Returns true if the player has a pet active |
| timePeriod:time |
|
Randomization and priority
You can use multiple dialogue lines with the same ID and index. Vivifica automatically finds the highest possible priority with a valid condition and picks any other lines with the same priority, index and a valid condition. Vivifica then randomly picks between these.
E.g. If there are 2 lines with the same priority, index, ID and valid (doesn't have to be the same but it must be true) condition, Vivifica will have 50% to show either line.
Text formatting
You can write certain commands directly into the dialogue or reply text and Vivifica will convert them by using in game values.
| Command | Info |
|---|---|
| {playerName} | Converts to player's name |
| {dayNightPhase} | Converts to the current time of the day (morning, midday, afternoon,evening,night) |
| {enemyInCombat} | If in combat, converts into a random enemy's in combat name |
| {readDlg:npcOrQuestID} | Finds text in npcOrQuestID and replaces the current text with it |
| {talkingToNPC} | Converts to the name of NPC this NPC is currently talking to |
| {specialTargetName} | Speech bubbles only. Converts to current special target (most commonly used for NPCs picking up items) |
| {tempValue} | Converts to the current temp value. This value can be changed with instructions |
Coloring
Vivifica supports multiple colors to write the reply text with.
- Green - Automatically used by services, E.g. enchanting window
- White - Used by unread lines
- Gray - Used by read lines. Vivifica tracks these automatically. It also ignores all lines starting with ID "default_" or "global_"
- Orange - Automatically colors options that are currently visible and use either index -1 or -2
- Yellow - Used to mark quests. Vivifica automatically colors all replies that lead to any reply option with "acceptQuest" or "finishQuest" instructions to make it easier to see what text is actually important. To prevent everything from being yellow, Vivifica colors only a path that leads to higher indexes. E.g. going from index 1000 to 2000 and eventually leading to "acceptQuest" turns it yellow but a detour from 1000 to 900 before going back to 1000 stays white. This must be done only at the start so all the next indexes can keep going up, as long as there's a lower one blocking the path
However, it's also possible to manually force a reply option to be of a set color. This can be done by adding a command to the reply option's instructions
- colorContinue - Yellow
- colorService - Green
- colorExit - Orange
- colorUnread - White
ID
Vivifica can differentiate between specific IDs and NPC groups. This can be done simply by changing the ID in database.
- Direct NPC ID - Simply reads the NPC ID
- Group ID - NPCs can have a group assigned to them. If they do, they will try to get both dialogue types, individual and a group. Group ID can be used to assign the same lines to a group of NPCs (like all chickens).