beasty-visual-novel / world / variables-and-conditions.md

Variables and conditions

Everything your game remembers is a variable. Everything your game decides is a condition. This page is the foundation for the rest of the World section: quests, items, time, routines and screens are all built on the two ideas below, and once you understand the store you understand all of them.

Creating a variable

Open Tools > Beasty VN > Editor, go to the Variables tab and press + New Variable.

A variable has:

FieldWhat it is
KeyIts name, written without brackets: gold. In dialogue you write it as [gold].
Value typeString, Int, Float or Bool.
KindHow the value is produced. See below.
Default valueThe value before anything sets it.
Prompt at runtimeAsk the player for this value when the game starts.

The four kinds describe where a value comes from. They are a label for you and your team, and they change what the editor offers you:

KindMeaning
PlayerInputThe player names it (their hero’s title, their ship’s name).
FixedA constant you set and the game does not change.
EnumOne of a fixed list of allowed values you type in. An Enum variable is always stored as a String.
ComputedDerived by game logic — the story writes it, you never hand-set it.

Asking the player

Two ways to let the player fill a variable:

  • Turn on Prompt at runtime on the variable itself, and give it a prompt label. The player is asked for it at the start.
  • Use the Ask -> variable block (palette category Input) anywhere in the story. It is a self-contained stop: it shows a question line — with an optional speaker, like any dialogue line — and opens the input box at the same time. It has a default used when the player leaves the box blank, and a required flag that rejects a blank answer instead.

There are two sibling blocks, Ask -> dictionary and Ask -> character name, which do the same thing for a dictionary token and for a character’s displayed name.

Changing a variable

The Set variable block (palette category State) is the whole story. Pick a variable, pick an operation, type a value:

OperationWhat it does
AssignThe variable becomes the value.
AddNumeric. gold + 10.
SubtractNumeric. gold - 10.
ToggleFlips a Bool. The value box is ignored.

Add and Subtract work in whole numbers when both sides are whole numbers, and in decimals as soon as either side has a fractional part. A variable holding something that is not a number counts as 0 for these two.

To change a value on a character — maya.affection, juan.met — use the Character variable block instead. It is the same three fields plus the character. It exists as its own block only so the editor can show you that character’s fields to pick from.

The same mutation is available outside a block, as an effect. See Effects below.

The store

Here is the part that makes the rest of the package make sense.

There is one flat key/value store, and everything is in it. Your variables, your character’s stats, the game clock, where every character currently is, every quest’s state, every item the player holds, and every dictionary token — one store, one list of keys and values. The subsystems do not each keep their own private data. They write into the same place your gold variable lives.

WhatThe key it lives under
Your variablesgold — the key, no prefix
Character variables@char:<id>:<field>
Character routine state@char:<id>:@routineLocation, @char:<id>:@routineSpot, @char:<id>:@routineMode
Game time@time:daypart, @time:hour, @time:day, @time:weekday, @time:season
Quests@quest:<id>:@state, @quest:<id>:@stage, @quest:<id>:@period, @quest:<id>:@rewarded, @quest:<id>:@penalized, @quest:<id>:@lastResult, and @quest:<id>:<objectiveId> per objective
Inventoryitem.<id> (how many are held), inventory.order (the slot order)
Dictionarythe token’s own key

Three things follow from this, and they are the reason the package is built this way:

  1. Any condition can read any of it. “It is Tuesday evening, Maya is in the bakery, the player has three coins and has finished chapter one” is one condition with four clauses, not four different systems talking to each other. You never write glue code to ask the time system a question.
  2. All of it is saved, and you do nothing. A save writes the store. Add a quest, add an item, advance the clock — it is already in every save file, with no extra work and nothing to remember.
  3. Rewind works on all of it. The player rolling back three lines rolls back the gold they spent, the quest they started and the hour that passed, because all three are the same kind of thing.

You will not normally type these keys by hand. The condition picker and the block pickers show them, and they show them with friendly labels: time.daypart, time.hour, maya.location, maya.spot, maya.routineMode, maya.affection. The full list is in Variable keys.

Note The reserved namespaces all start with @ or contain a ., which a variable key you type cannot produce. Your gold can never collide with the engine’s.

Conditions

A condition is a list of clauses. A clause is three things: a token (any key in the store), an operator, and a value.

Where you can attach one

Attached toWhat it decides
A choice optionWhether the player is offered it
A decision branchWhether the story takes that route
A talk menu entryWhether the player can say that
A room’s conditional backgroundWhich background the room shows
A room object’s Shown whenWhether the object is on screen
A door’s access exceptionWhether the door is open, and which line plays when it is not
A door’s conditional VN actionWhich scene the door leads into
A routine ruleWhere a character is at this moment
A quest’s Start when / Fail when, and an objective’s Complete whenWhether the quest starts, fails, or that objective is done
An item’s Use conditionWhether the player may use it here
A screen, and a screen itemWhether the HUD button or overlay is shown, and which icon and label it uses
A character’s cast-list visibilityWhether the player sees them in the cast list

Everywhere, the same editor and the same evaluator. Learn it once.

Operators

OperatorMeaning
EqualsThe value matches
NotEqualsThe value does not match
GreaterNumeric >
LessNumeric <
GreaterOrEqualNumeric >=
LessOrEqualNumeric <=
ContainsThe text contains the value

And, Or, and precedence

Each clause after the first carries a join: And or Or.

AND binds tighter than OR. A condition written as

a  AND  b  OR  c

means (a AND b) OR c, not a AND (b OR c). This is ordinary boolean precedence, and it is the single most common source of a condition that “does not work”. If you want the other grouping, reorder the clauses or split the logic across two branches of a decision node.

The two rules that catch everyone out

Warning An empty condition is always true. No clauses means “no condition” means “yes”. A choice with an empty condition is always offered; a door with an empty access exception is always open. This is what you want almost all of the time — it is why you can leave the condition field alone and everything just shows — but it means you cannot gate something by leaving the condition blank.

Warning A clause with no token is incomplete, and evaluates to false. If you add a clause and forget to pick a variable, the whole thing fails closed: the choice never appears, the door never opens. It fails closed on purpose — a half-written condition that passed would silently unlock all the content behind it. The engine reports it once in the Console, naming the clause, so you can find it.

Effects

An effect is the same change a Set variable block makes, applied at a moment when a block cannot run. It is the same three fields — a key, an operation (Assign / Add / Subtract / Toggle), a value — and it behaves identically.

Effects are attached to:

  • A choice option. Applied when the player picks it.
  • A decision branch. Applied when the branch is taken.
  • A Return node. Applied when a subgraph returns its outcome.
  • A quest. Its reward effects on completion, its penalty effects on failure.
  • An item. Its use effects, applied when the item is used.
  • A screen item. Its click effects, applied when the player clicks that HUD button.

So “picking this choice costs 10 gold and makes Maya like you less” is two effects on the choice, and no blocks at all. See Choices and decisions.

Shared conditions and @self

@self is a placeholder that stands for “whichever character this is being evaluated for”. It is used where one condition is applied to the whole cast in turn — the shared visibility condition of the cast list is the main one. Write the condition once against @self, and each character is tested against their own field:

@self.met == true

That one line reveals each character in the cast list as soon as their own met flag is set. Without @self you would write the same condition once per character. See Character screens.

For programmers

You can read and write every one of these keys from C#, and you do not have to type the key strings. Tools > Beasty VN > Codegen generates VNVars and VNChars: typed, compile-time-checked accessors for the variables and characters that exist in your project. See Generated accessors, and Gameplay APIs for the time, routine, quest and inventory facades.

See also