Arcanum Scripting

Abstract

Problem: How do you build a scripting system for an RPG when you have no budget for dedicated scripters and your programmers are already overloaded?

Approach: Tim Cain designed a visual script editor called "Sock Monkey" that used drop-down menus and an if-then conditional structure, making it accessible to non-programmers including artists.

Findings: The system worked through attachment points on game objects, a return/return-to-default mechanism for overriding behavior, and built-in safeguards against common errors. It was fast to execute and shipped as part of the modding tools.

Key insight: By making every script line secretly an if-then conditional and using drop-down menus instead of free-form text, Cain eliminated syntax errors and made scripting accessible to the entire 14-person team — including artists with no coding experience.

Source: https://www.youtube.com/watch?v=coPkWyJIl-M

Background and Constraints

Tim Cain explains that Arcanum's scripting system was born from necessity. The team didn't have the budget to hire dedicated scripters, and the programmers already had too much on their plates to handle all the scripting alongside the engine coding. Cain had considered comparing it with Fallout's scripting, but noted that Fallout's script system came directly from Starfleet Academy — he never used it himself and wasn't the one who integrated it, so he deferred that comparison.

Sock Monkey: The Script Editor

Cain wrote a visual script editor called Sock Monkey. His design goals were:

  • Simplicity — anyone could use it, not just coders
  • Intuitiveness — drop-down menus instead of free-form code
  • Error prevention — eliminate the common bugs he'd seen in Fallout, particularly syntax errors and infinite loops

He tried to record a video demonstration of Sock Monkey but couldn't because none of the popup windows within the tool would show up in screen capture software.

How Scripts Worked

Everything Is a Conditional

Every line in Sock Monkey was either a conditional or an action — but secretly, they were all if-then statements. If you chose an "action," the editor internally wrote if true then [do this] else [do nothing], so it would always execute. The user saw it displayed as a simple action, but under the hood the engine only needed to handle one construct.

Script Flags

Every script had its own metadata flags that could be checked without looking inside the script:

  • Trap flag — marked the script as a trap, so the "Detect Traps" skill could highlight objects with trap scripts
  • Run once — the script executes once then deletes itself
  • Teleport trigger — normally scripts on tiles only fired when walked over; this flag ensured they also triggered when someone teleported across the tile in a straight line

Local and Global State

Each script had its own local counters and flags preserved between executions. Scripts could also access global arrays of variables and flags, and could read any data kept on the player character.

Conditions and Actions via Drop-Downs

Conditions were selected from a drop-down menu — things like:

  • obj_has_item — does an object have a specific item?
  • obj_is_within_n_tiles_of_obj — proximity checks

Actions were also selected from drop-downs:

  • set_global_n — set a global variable
  • print_message_n — display a message
  • have_obj_cast_spell_on_obj — trigger spell casting

Parameters were either typed as numbers or looked up from pre-existing game lists (message lists, item lists, spell lists, etc.).

Predefined Object References

The system had several predefined object references accessible in any script:

  • Player — the player character
  • Trigger — whoever activated the script (e.g., the person opening a door)
  • Attachee — the object the script is attached to (e.g., the door itself)
  • Loop object — the current iteration target in a loop

Every and Any

Cain added "every" and "any" quantifiers, powerful for both conditionals and loops:

  • every follower / any follower
  • everyone on team (player + all followers)
  • anyone in party (all players in multiplayer)

For conditionals, you could write things like "if anyone in the party has key 5, then open the door." For loops, you could say "for everyone in party" and run actions on each member via the special loop object reference.

Attachment Points

Scripts were attached to objects at specific attachment points — a long list of interaction hooks:

  • Use — when an object is used
  • Open — when a door or container is opened
  • Pickup — when an item is picked up
  • Wield on — when an item is equipped
  • Wield off — when an item is unequipped
  • Buy — when a merchant attempts to buy an item
  • Sell — when selling to someone

Return vs. Return to Default

The return value mechanism was crucial:

  • Return — tells the calling system "I handled it, don't do the default action." The door stays locked, the merchant won't buy, the item can't be equipped.
  • Return to default — tells the calling system "proceed normally." The door opens, the purchase goes through, the item equips.

Practical Examples

Locked door with a key: Attach a script to the door's open point. Check if anyone in the party has key #5. If yes, return to default (door opens). If no, return (door stays locked) and print "you can't open the door."

Unsellable quest item: Put a script on the merchant's buy attachment point that simply returns, preventing the purchase.

Magic-only sword: Attach a wield-on script that checks if the wielder's magic skill is above 90. Below threshold: return (can't equip). Above: return to default (equip normally).

Cursed item: The wield-off attachment point simply returns, preventing removal. Only a specific quest script or a particular spell cast on the item could remove the curse by overriding this behavior.

Safeguards

No Infinite Loops (Originally No Go-To)

The system originally had no go-to statements. However, scripts needed to call other scripts, and Cain discovered people would create circular calls (script A calls script B, which calls script A). So he added go-tos for jumping within a single script, but implemented a fail-safe: if a script executed more than ~1,000 lines, the system would terminate it, assuming it was stuck in an infinite loop.

Results and Legacy

Cain considers Sock Monkey a success for several reasons:

  • Accessibility — many team members wrote scripts, including artists and people with no coding experience, which was essential for a team of only 14 people
  • Performance — the way data was written out made scripts execute very fast, important since Arcanum was already using every available CPU cycle
  • Modding — Sock Monkey shipped as part of the World Editor and modding tools, enabling community content creation

References