beasty-visual-novel / reference / variable-keys.md

Variable keys

Every key that lives in the variable store, and how to write a condition against it. This is the page to open when you need a condition on the game time, a quest state, a character’s location or the player’s inventory.

One store for everything

There is a single flat key/value store. Your variables, character variables, the clock, quest progress, the inventory and the dictionary all live in it, under different key namespaces. That is why any of them can be used in any condition, why all of them are saved, and why all of them rewind correctly.

WhatKey formatExample key
Your variableskey (no prefix)gold
Character variables@char:<id>:<field>@char:maya:affection
Character name (reserved)@char:<id>:@name@char:maya:@name
Routine (reserved)@char:<id>:@routineLocation, @char:<id>:@routineSpot, @char:<id>:@routineMode@char:maya:@routineLocation
Game time@time:daypart, @time:hour, @time:day, @time:weekday, @time:season@time:daypart
Quests@quest:<id>:@state, @quest:<id>:@stage, @quest:<id>:@period, @quest:<id>:@rewarded, @quest:<id>:@penalized, @quest:<id>:@lastResult, @quest:<id>:<objectiveId>@quest:ana_m1:@state
Inventoryitem.<id>, inventory.orderitem.potion
Dictionarythe token key (no prefix)city

Reserved fields are spelled with a leading @. An author-defined key can never produce one, so your variables can never collide with the engine’s.

Note A key that was never written reads as an empty value. A condition against it is false unless you compare it to an empty value.

How you write a condition

In the editor you never type a key. Every condition field has a searchable picker that lists the keys and inserts the right one. The picker shows a friendly label, but what gets stored is always the exact key from the table above.

NamespaceLabel in the picker
Your variablesThe key itself: gold
Character variables<id>.<field>: maya.affection
Routine<id>.<field>, keeping the reserved @: maya.@routineLocation
Game timetime.<field>: time.daypart
Questsquest.<id>:<field>: quest.ana_m1:@state
InventoryThe key itself: item.potion

In the .vnbeasty text script there is no picker, so you type the key yourself, exactly as it appears in the key column: if @time:daypart == Morning, not if time.daypart == Morning. See the .vnbeasty syntax.

Operators

Equals, NotEquals, Greater, Less, GreaterOrEqual, LessOrEqual, Contains. In the text script: ==, !=, >, <, >=, <=, contains.

Clauses join with And or Or.

Warning AND binds tighter than OR. a AND b OR c means (a AND b) OR c, never a AND (b OR c). There are no brackets. Reorder your clauses, or split the branch in two.

Two rules that catch everyone once:

  • An empty condition is always true. No condition means “always”.
  • A clause with no key selected is incomplete and evaluates to false. It is reported in the console once.

The full picture is in Variables and conditions.

Your own variables

Declared in the Variables tab of the Beasty VN window. The key has no prefix and is exactly what you typed.

gold >= 10
saw_intro == true
player_class == Mage

A bare flag in the text script means == true:

choice "Buy a sword" if gold >= 10 { gold -= 10 } -> smith
if saw_intro -> chapter2

Values are typed: String, Int, Float, Bool. An Enum variable offers its allowed values as a dropdown in the condition editor instead of free text.

Character variables

Every field on a character - the universal ones from the CharacterVariableSchema and the character’s own - is stored under @char:<id>:<field>.

KeyMeaning
@char:<id>:<field>One field on one character.
@char:<id>:@nameReserved. The character’s displayed name, once it has been changed or the player has named them.
@char:@self:<field>A placeholder used in a shared condition. It resolves to whichever character the condition is being evaluated for.

Example conditions:

maya.affection >= 3          picker label
@char:maya:affection >= 3    the actual key
@char:@self:met == true      a shared condition, evaluated per character

Use @self when one condition template has to apply to the whole cast, for example the visibility rule of the in-game cast list.

See Characters.

Routine keys

Three reserved fields per character, written by the routine system on every room enter and whenever the store changes. You never write @routineLocation or @routineSpot yourself.

KeyValuePicker label
@char:<id>:@routineLocationThe room id the character is in right now, or empty when they are absent.<id>.@routineLocation
@char:<id>:@routineSpotThe spot id inside that room, or empty.<id>.@routineSpot
@char:<id>:@routineModeThe name of the active routine profile.<id>.@routineMode

@routineLocation and @routineMode are offered as dropdowns: the room list and the profile list of that character.

Example conditions:

@char:maya:@routineLocation == bakery     Maya is in the bakery
@char:maya:@routineLocation ==            Maya is absent (empty = not on the map)
@char:maya:@routineMode == Working        Maya is on her Working schedule

@routineMode is the one routine key you DO write. Setting it with a Set variable block (or the Routine override block) swaps a character’s whole schedule from the story.

See Character routines.

Time keys

Five reserved keys. They exist only while a Time Config is assigned on the BeastyManager.

KeyPicker labelAvailabilityValue
@time:dayparttime.daypartAlwaysThe name of the current daypart, e.g. Morning.
@time:hourtime.hourClock mode onlyAn integer, e.g. 14.
@time:daytime.dayAlwaysAn integer, starting at 1.
@time:weekdaytime.weekdayWhen weekdays are configuredThe weekday name.
@time:seasontime.seasonWhen seasons are configuredThe season name.

@time:daypart is offered as a dropdown of your configured daypart names.

Example conditions:

@time:daypart == Morning
@time:hour >= 18
@time:day > 3
@time:weekday == Monday
@time:season == Summer

Warning With no Time Config assigned, the time system is off: none of these keys is ever written, and every condition on them is false. This is the most common cause of “my time conditions do nothing”.

Time never advances on its own. You move it with the Advance time block, with a free-roam object’s advanceTimeOnClick, or from code. See Game time.

Quest keys

Six reserved fields per quest, plus one key per objective. All written by the quest system.

KeyValue
@quest:<id>:@statenotstarted, active, completed or failed.
@quest:<id>:@stageThe current objective index in an Ordered quest.
@quest:<id>:<objectiveId>true once that objective is done.
@quest:<id>:@periodThe period index a recurring quest is tracking.
@quest:<id>:@rewardedLatch: the reward was paid for the current period.
@quest:<id>:@penalizedLatch: the penalty was applied for the current failure.
@quest:<id>:@lastResultcompleted or failed, for the last settled period of a recurring quest.

@state and @lastResult are offered as dropdowns of their allowed values. @stage only appears for Ordered quests; @lastResult only for recurring ones.

Example conditions:

@quest:ana_m1:@state == active           the quest is running
@quest:ana_m1:@state == completed        it is done
@quest:ana_m1:@stage >= 2                the player reached the third objective
@quest:ana_m1:talk_to_ana == true        that one objective is done
@quest:ana_m1:@lastResult == failed      they failed yesterday's daily

Compare a quest state against the string, not against a number. The four states are lowercase.

See Quests.

Inventory keys

KeyValue
item.<id>How many of that item the player holds. A Key item reads as a bool, a Consumable as an int.
inventory.orderThe player’s chosen slot order. Do not condition on it.

Example conditions:

item.potion >= 1        the player has at least one potion
item.rusty_key == true  a key item they own
item.potion == 0        they ran out

Give and Take clamp to the item’s maxQuantity and to 0, so an item count is never negative and never over the cap.

See Items and inventory.

Dictionary keys

A dictionary token is stored under its own key, with no prefix, exactly like one of your variables. When a Set dictionary block or the player writes a value, it goes into the store and shadows the author default - so it saves and rewinds like everything else.

city == Madrid

Because a dictionary token shares the plain namespace with your variables, give tokens distinct names. Tools > Beasty VN > Validate > Find duplicate ids will not catch a token that shadows a variable.

See The dictionary.

See also