# tnhScript (Thief 2)

This is the fourth public release of the scripts I have written as of 2005/01/10 and patched on 2005/05/12.

If you're a user and simply want to get the the script module installed so you can play a mission, try just running Quick Install.wsf. Or copy tnhScript.osm into your Thief 2 program folder. (The quick install may not work for everyone, or most people, or on any machine other than my own.)

This release provides limited support for Thief 1 (or Thief Gold) and System Shock 2. The script modules for these games are in folders named Thief1 and SShock2. These ports are documented separately. The scripts are not as well tested for these games, and barely at all in the case of System Shock 2. Support for these games is not guaranteed in future releases of tnhScript.

## VersionCheck

Included with this package is the script module version.osm. It contains only one script: VersionCheck. The purpose of this module is to allow you to use these, or other scripts without having to distribute the script module with your mission. Instead, you include version.osm, and use VersionCheck to inform the player if a required script module needs to be installed, and where it may be downloaded from.

There are two copies of version.osm in this release. The standard module only supports Thief 2. There is a newer version that supports all games with a single OSM. If you need to use the script with other games, or would like to test the new version, you will find it in the VersionCheck2 folder.

The script activates at Sim start. It reads a list of module names and versions from Editor\Design Note in the form '`ModuleA=1.0;ModuleB=1.0`'. The version number may be omitted to only check that the module is present. (This is necessary if the module file doesn't include version information.) If the module name does not have an extension then '`.osm`' will be appended to the name.

If all the modules specified are found, then the script sends TurnOn along its ControlDevice links. Otherwise, it will send TurnOff and display a message. Use the Book\Text property to specify what to display. It will be shown on-screen (with a very long timeout period) unless Book\Art is present, then it will be shown in book-mode. In either case, the object with the script is destroyed once the modules have been checked.

## Fan-Mission Authors

If you use these scripts in your fan mission, then I'm very happy that you have found my work useful. That's all I care about. Feel free to include a copy of the script module with your FM. (If you don't, then I suggest you use version.osm.) The only thing I require is that you include the copyright notification from copyright.txt, and that your fan mission fully complies with the DromEd EULA.

## Generic Scripts

Many of these scripts are derived from three generic scripts.

- GenericTrap - A switch-like script which responds to TurnOn and TurnOff messages.
- GenericScale - A knob-like script which works with a numerical argument.
- GenericControl - A script which can take a string argument.

The generic scripts share many behaviors common to traps.

- TrapControlFlags - The script will respect the flags, although a flag may be meaningless in some situations. (`NoOff` when the script only responds to TurnOn.)
- Locks - The script can be prevented from operating by setting the Locked property, or with a Lock link. (A lock will not prevent a revert timer from occuring if the object was unlocked when the timer began.)
- Timing - When the script recieves TurnOn, it will automatically revert to TurnOff after the time specified in Script\Timing. TurnOff messages do not revert. If you need to, then either use the `Invert` trap control flag or relay the message through a RevertTrap.
- ScriptControl - The script can be arbitrarily controlled by sending a ScriptControl message to it. The first parameter of the message is an argument for the script.
  - GenericTrap - Converts the argument to TurnOn or TurnOff. A numerical argument is TurnOn if it is non-zero, and TurnOff otherwise. A string argument can either be a number, which acts the same way, or could be one of: `on`, `off`, `turnon`, `turnoff`, `true`, `false`, `yes`, `no`.
  - GenericScale - Accepts a numerical argument, and attempts to convert a string to a number.
  - GenericControl - Only accepts string arguments.
  The ScriptController script can be used to generate a ScriptControl message from a trap.

The Editor\Design Note property is used to specify most parameters for a script. The design note is a string that can be a list of parameters, or a single parameter by itself. A parameters is a keyword, followed by an equals sign (`=`), then an argument. The argument may be surrounded with single or double quotes. Multiple parameters are seperated with a semi-colon (`;`). If you need to include a semi-colon in an argument, then it must be surrounded by quotes. A quote mark can be included by using the other type of quote mark to surround the argument, or escape the quote mark with a back-slash. (e.g. `'a \'quoted\' argument'`)

When a script looks for an integer or flag parameter, you can have it read a quest variable. After the equals sign, type a dollar sign (`$`) and the name of the quest variable. There may not be space between the dollar sign and the variable name.

Some scripts use a parameter for a time value. If this is in the Script\Timing property, then the time is in milliseconds. But where the time is read from the design note, or a string file, then you can add a suffix to have the time interpreted differently. Add the letter `s` to indicate seconds, or `m` for minutes. Without a letter, the time is milliseconds.

The GenericScale and GenericControl scripts respond to TurnOn and TurnOff by looking for default parameters. When the script is turned on, it first looks for the parameter `on`. If the parameter doesn't exist, it will consider the entire design note as a parameter. When it is turned off, the parameter `off` is looked for. If it doesn't exist, then GenericScale will consider the whole design note as a value, and negate it. GenericControl will do nothing. Some parameters are simple flags. The value of a flag is either 1 or 0. If not present, a parameter will be assumed to be 0 or an empty string.

Using an entire design note as the parameter for a generic script will be removed in tnhScript 2. Please only use explicit `on` and `off` parameters.

All of the generic scripts should put a meaningful value in the message return argument, or at least set it to non-zero if I couldn't think of anything "meaningful." This is so the scripts can be used directly in a pseudo-script. Returning zero causes the pseudo-script to abort, which usually is too drastic.

There is also a GenericTrigger script. Unlike the others, which are all meant to be the base class for your script, GenericTrigger is a supplemental class. It provides an interface for easily sending TurnOn and TurnOff messages, with appropriate handling of trap control flags.

## The Scripts

### Traps/Triggers

#### TrapCampaignQVar (GenericTrap)

Just like TrapQVar, but uses the campaign database. The quest variable will always be saved to the campaign database, even if it was previously set in the mission database. All the operations of TrapSetQVar from Thief 2 are supported, including `"`, `?`, and `d`. Additionally, the operation `#` is like `"` but will limit the number of digits in the qvar to 4. (Makes working with the odometer easier.)

#### TrapFrobber (GenericTrap)

Sends a FrobWorldEnd message to this object when it's turned on. Most useful for converting a book/plaque/scroll into a trap. The object doesn't have to be frobbable. Set the parameter `inv` to send FrobInvEnd instead. Set the parameter `player_frob` to have the Player be the frobber.

#### TrigFrobber

Translates a FrobWorldEnd message into TurnOn. The reverse of TrapFrobber, basically. The trap control flags are ignored, so you can use them for a more useful script. Will respond to FrobInvEnd instead if you set the parameter `inv`. You can send a different message by specifying the parameter `frobmsg`. But be careful about sending messages that are supposed to be sent with extra data.

#### TrapRandomTimer

Relay TurnOn and TurnOff after a random delay. This isn't a generic trap, but it respects all the trap control flags. The Script\Timing property is ignored. The parameters are `min_time` and `max_time` using a standard time string.

#### TrapRelayRepeat

Repeat TurnOn a number of times, with a delay between firings. The delay is specified in the parameter `interval` as a standard time string. By default, the relay will repeat indefinitely. The parameter `repeat` will limit how many times it fires; a negative value is the same as the default. TurnOff will halt the script. If the parameter `autostart` is set, then the relay will begin automatically at Sim start. Respects the trap control flags `Once` and `NoOff`.

#### TrapSiblingRelay (GenericTrap)

This is a little complicated.

Create a group of relays with this script on them. Using ScriptParams links with the data `Sibling`, connect the relays together in a ring configuration: exactly one link from each relay to the next, and the last one linked back to the first. Now when any one of the relays receives TurnOn or TurnOff, the message will be sent along ControlDevice links from all the relays in the network.

The network shares a common setting for Script\Trap Control Flags, Script\Timing, and Engine Features\Locked. (But not Editor\Design Note, you should notice.) In each network, the object with the lowest ID is selected as the reference for these properties. When creating a network, you should set these properties on all the relays. The object with the lowest ID is not guaranteed to be the same from one editing session to the next.

All the trap control flags are respected, and reflect the messages that are relayed out of the network. You can change how messages are propogated in the network with the `exclusive` parameter. If `exclusive` is `on`, then when the relay that received the message sends TurnOn (taking into account the `Invert` trap control flag), the other relays will send TurnOff. TurnOff messages are propogated as normal. Setting `exclusive` to `off` allows only one relay to send TurnOff at a time, with TurnOn messages being propogated normally. Or with `exclusive=all`, it will do that for both messages.

Each relay can have Owns links to other objects. When the relay receives a message from the network, it will send TurnOn or TurnOff as appropriate to its owned objects. These will normally be levers that are used to activate the relays directly. Switching a lever causes it to send a message back to the relay that switched it in the first place, however. TrapSiblingRelay anticipates these messages and will ignore them. The script needs to know whether it is "on" or "off" in order to switch the owned objects appropriately. It normally assumes that it begins in the off position; use `start_on` to change this. If the owned objects send back a message that is opposite what was sent to them, then `invert_levers` should be set. (Not counting the `Invert` trap control flag on the network.) Or you can just have the script not ignore messages by setting the `no_swallow` parameter.

The parameters `no_swallow` and `invert_levers` apply to all owned objects.

The network will self-heal, so it is safe to destroy relays. If the object being destroyed is being used as the reference for the network (has the lowest ID), then it will copy the Script\Trap Control Flags, Script\Timing, and Engine Features\Locked properties to the object that will become the reference. It propogates the message NewBaseObject through the network to inform the other relays of the change.

If you're still confused, look at the demo mission. This script is difficult to explain, but fairly simple to use.

#### TrapThreshold (GenericTrap)

Will send TurnOn after having received a certain number of prior TurnOn messages. That is, every n-th TurnOn will be relayed. The counter that tracks messages can be decremented by sending TurnOff, or they can be ignored by setting the trap control flag `NoOff`.

- `threshold` - The number of messages that will trigger a TurnOn.
- `initial` - The initial state of the counter.
- `floor` - The minimum value the counter can reach.
- `global` - Use a single counter for all messages. Otherwise, a seperate counter is used for each message source.

#### TrigContact

Sends TurnOn and TurnOff when another object makes or breaks contact with this one. Link from this object to another using ScriptParams with the data `Contact`. The linked object may be an archetype, and any object which inherits from it will trigger. The trigger may be locked, and all the trap control flags are respected.

#### TrigDoorOpening

Sends GoForward when a door begins to open, and GoReverse when it begins to close. When the door halts (for whatever reason), then TurnOff is sent. The messages GoForward and GoReverse can be handled with the standard script TweqOnOff to control a tweqable object. The messages can also be reversed with the `Invert` trap control flag.

#### VisibleRelay

Relays TurnOn and TurnOff conditional on whether an object is currently rendered. A pseudo-script can send the message TestVisible and the script will reply with whether or not the condition is true. When relaying TurnOn or TurnOff, the trap control flag `Invert` will have the script relay only if the object is not visible. The TestVisible message must be sent with the first data parameter set to the condition to test for: `visible` or `invisible`. The object to check is specified using a ScriptParams link with the data set to `Visible`. If there is no link, then the script object will be checked. The trap control flag `Once` is respected when relaying, but not when replying to TestVisible.

#### TrigProxFrob

When the object is frobbed, either in the world or inventory, then a TurnOn will be sent along ControlDevice links if the frobber is currently within a certain area. Use a Route link to the proximity object. The script looks for a Population link to the frobber from this object. The scripts TrigOBB and TrigRoomCreature will create Population links.

#### ScriptController (GenericControl)

A trap that allows you to specify the data for a ScriptControl message without having to resort to pseudo-scripts. When used as a trap, a ScriptControl message is sent to all ControlDevice linked objects. The control string (specified as the `on` or `off` parameter) is the data parameter for the message. The script can also be used in pseudo-scripts to send a ScriptControl message to any other object. If there is a second data argument, it will be interpreted as the object ID or name of an object that should receive the message. The first data argument will simply be relayed with the message.

### Object System

#### ContainerPhysics

When a container is slain, this script transfers its location and velocity to the objects it contains. If the object is killed by a collision, the contents will bounce away from the collision. (This is to avoid pushing the objects into solid space.) As well, the contents are scattered randomly within the dimensions of the container.

#### ObjFrobInert

An object with this script can link to other objects that will be kept un-frobbable until this one is dead. Create ScriptParams links with data `FrobInert` to the dependant objects. The metaproperty FrobInert will be added to the objects initially, then removed once this object is slain or destroyed. Most useful for banners, or even door-like objects such as a window.

This script also acts as a base for other scripts that act similarly. Those scripts may be more specific about whether to make the objects frobbable or not, or they may change the interpretation of "dead".

#### TrapFrobInert (ObjFrobInert)

Responds to the standard messages of TurnOn (frobbable) and TurnOff (unfrobbable). Has all the expected behaviors of a trap.

#### DoorFrobInert (ObjFrobInert)

Objects linked from this script will be frobbable based on the state of the door. They will be un-frobbable when the door is closed, and frobbable otherwise. A door will not be considered "dead" when slain unless the parameter `breakable` is set. A wooden door, for example, is still solid even when slain, but a glass door isn't.

#### GrabFrobInert (ObjFrobInert)

Makes objects frobbable in response to FrobWorldEnd. Used for objects which can be picked-up to reveal others.

#### TweqFrobInert (ObjFrobInert)

The frobbability of linked objects is controlled by the joint tweq. When the primary joint state is `Reverse`, then the objects are frobbable. Responds to TweqComplete. This is suitable for working with container objects that are already in DromEd; other tweqable objects may or may not also work.

#### UnlinkOnContained

When the object is contained, this script deletes all the links from another object to this one. The source(s) of the links to remove is determined from a ScriptParams link with data `Unlink`. (Useful for objects that might be frob-inerted by ObjFrobInert or a related script.)

#### TrapContainer (GenericTrap)

Moves the items that are contained by this object to a ControlDevice linked object. If there parameter `track_contains` is set, then each object that is moved into the recipient container will get an Owns link to it from the trap. Then, when the trap is turned off, only those items will be removed. Otherwise, it will collect all the objects that the linked container has. The ControlDevice can link to the starting point to use the Player.

#### TrapDispenser (GenericTrap)

Creates a new object and adds it to the container linked with ControlDevice. The object archetype to create is named in the `dispense` parameter. Using the player as a container works as expected.

#### TrapMoveRelative (GenericControl)

Move and apply velocity to an object relative to its current position.

Works similar to a teleport trap. The parameters are `x`, `y`, `z`, (position), `dx`, `dy`, `dz`, (linear velocity), `h`, `p`, `b`, (facing), `dh`, `dp`, `db` (rotational velocity). Objects without a physics model cannot have velocity.

#### TurnAtWaypoints

Will turn a moving terrain object so it has the same orientation of the waypoint it just reached. The initial values of the Tweq\Rotate property determine which axes will be rotated and at what speed. Only the `X` ("Rate") value for each axis is used. If it is 0, then that axis will be ignored. The `Primary Axis` of the tweq should be set to 0. When the object reaches a waypoint, the appropriate values are read from the waypoint and the Tweq\Rotate property will be modified so the object will turn to face the same direction. The direction of the turn will be whichever is the shortest distance. The object will not turn if the object isn't moving.

#### RecallDevice (GenericTrap)

When turned on, all ControlDevice linked objects will be teleported to the location of the Player.

#### ResetTweqState

Works with all tweq types except Lock or Delete. When the tweq halts, the appropriate properties for the object will be reset to default values. The defaults are read from the immediate archetype of the object, or from the original rotation of the object for a rotate tweq. Joint tweqs use the Shape\Joint Positions property. Model tweqs use Shape\Model Name. Scale tweqs use Shape\Scale. Emitter and flicker tweqs use the "Cur Time" and "Frame #" fields of the tweq state property.

### Books and Plaques

#### FocusPlaque

Displays the book text on-screen when the object is hilighted. The text is erased when you look away, unless the trap control flag `NoOff` is specified.

#### TrapText (GenericTrap)

Displays text when turned on, frobbed, or focused. If there is no Book\Text property, then Editor\Design Note is displayed.

This script was written as a convenience for the demo mission, but hadn't been documented until now.

#### OnScreenText (GenericScript)

This is a trap (which means it responds to TurnOn, not FrobWorldEnd) for displaying pages from a book file on-screen. Each page can be a different color, and you can control how long a page is displayed. All of the page options are written in the book file. The object only has the parameter `page` which is the page number that will be shown next. The parameter is modified as the book is read. It defaults to 0, of course. The book can be controlled manually with the ScriptControl message.

The book text is written the same as any other book: create a `STR` file with each page in the string `page_n`. The color of the text is in the string `page_n_color`, and is written in HTML format. ("`#RRGGBB`", where each color is a two-digit hexadecimal number.) The text will stay on-screen for the time specified in `page_n_time`. The time is specified as a standard time string. If no time is specified, or it is `0`, then the display time will be calculated based on the length of the text. The text can auto-scroll to the next page (it doesn't actually scroll, it's just a figure of speech) with `page_n_auto`. The next page will be shown the specified time after the current page is put on-screen. That is, a page that is displayed for two seconds and auto-scrolls in three seconds will display the next page one second after the current page is removed from the screen. If the auto time is less than the display time, then the auto-scroll time will be the same as the display time. Usually, the next page is one greater than the current page. The `page_n_next` string will override this. If the next page doesn't exist, however, the script will go back to page 0. The script will do nothing if the current page doesn't exist. A page exists if it has at least one character in the text. A blank string isn't enough; put a single space in quotes if you want to display a blank page.

These books are fully compatible with other scripts that read books. The standard scripts will display just page 0 if there is no Book\Art property, or multiple pages in book-mode if the property exists. OnScreenText ignores the Book\Art property.

The ScriptControl message will interpret the data as either a number or a string. If a number, the current page of the book will be set to the argument but not displayed. The control message may be an operator character followed by a numeric argument. The operators are:

- `@` - Sets the current page.
- `+` - Moves forward a number of pages.
- `-` - Moves backward a number of pages. Will not go past 0.
- `#` - Display a page, or the current page if the argument is `.` (period/full-stop).

The first three only change the current page without displaying anything.

### Usable Items

#### GenericPotion

Works alongside the standard TimedPotion script to create effects using metaproperties. A metaproperty will be added to an object while the potion is active, then removed when it times-out. The metaproperty is specified with the parameter `potion_effect`. The metaproperty is normally added to the frobber of the potion, but you can override this with the parameter `potion_dest`.

#### InventoryMetaprop

Adds a metaproperty to the Player while the object is in his inventory. The metaproperty is specified with `inv_effect`.

#### LockHelper / KeyHelper / PickHelper

These three scripts work together to allow event triggers from locked objects. Put LockHelper on the locked object, KeyHelper on all keys, and PickHelper on the lockpicks. When one of the events occurs, LockHelper will send TurnOn along the ScriptParams links that have the data set to the name of the event. The events are:

- FrobLock - When the object is frobbed while locked.
- UseKey - When a key successfully locks or unlocks the object.
- WrongKey - When the wrong key is used.
- PickBegin/PickEnd - When the lock is being picked.

FrobLock and WrongKey don't need KeyHelper or PickHelper to work.

#### NewCrystal

Substitute this object for another when contained by the player. Make a ScriptParams link from this object (or its archetype) to the archetype of the object that will be placed in the inventory. Set the data for the link to `Crystal`. (This is not a patch like the other "New" scripts are.)

#### NewKey

The StdKey script of Thief has a bug that prevents schemas with the tags `LockState` and `Event StateChange` from playing correctly. Specifically, the script is supposed to send PlayerToolFrob to the object being frobbed when the frobber is the player, but it sends the message after the LockSounds script has played the schema.

This script is a patch for StdKey. To use it, replace StdKey with NewKey in the hierarchy. (Or use the "Don't Inherit" option. All the usual behavior of StdKey is preserved in NewKey.

Create a schema with the tags `(Event StateChange) (LockState Unlocked) (CreatureType Player)` to test the behavior with StdKey and NewKey.

#### NewGasLight

An enhanced replacement for GasLight or Extinguishable. With this script, frobbing a light turns it off, as well as on. You can also add an emitter tweq to the light that will simulate a jet of gas. The emitter is activated when the light is extinguished with a water or KOGas stim. It's turned off when the lamp is relit, or if it receives a TurnOff message. If the light is frobbed while the emitter is active, it turns off the emitter but doesn't turn on the light. The emitter options can be disabled by setting the parameter `no_emitter`.

The script will also make sure that the light is not suspicious when the game starts, so you can use the Difficulty\Turn On (Off) property.

#### TimedGrenade

A timed grenade is armed when it is thrown (not dropped) from the player's inventory. The grenade arms itself by playing a schema and activating a model tweq. After a delay, determined by the Script\Timing property, the grenade is slain. If the archetype for the grenade has a RenderFlash link, the flash is activated. If you pick up the grenade while it is armed, it will deactivate (halt the tweq and schema). Except if there is less than 2 seconds on the timer, then it will explode. It also explodes if you poke or bash it when armed. And a fire stim will explode it even when unarmed. You need to set the appropriate Receptrons with a `Send to Scripts` reaction.

The actual explosion is linked as the corpse of the grenade. Look at ActiveMine and similar archetypes as examples.

The schemas are played using event tags: `Activate`, `ActiveLoop`, `Deactivate`, and `Death`. Setting the schemas as a major anomaly makes any AI that hear it wander around, often out of the range of the explosion. Make it a minor anomaly, or set the volume very low, and they'll be more likely to get hit. `Death`, of course, is pretty major.

A TimedGrenade can automatically arm when launched from an emitter. Set the parameter `launch_active` on the archetype for the grenade. (This won't work for "emitting" a concrete object.)

### Inventory and Stores

#### InvCarryOver

The first of a pair of scripts that allow you to have persistant containers. To make an object carry over, create a ScriptParams link with data `CarryOver` from the container object to the object, or its archetype. When a mission ends, then contained objects that inherit from one of the carry-over archetypes will be recorded as campaign quest variables. The variables will be named "`inv_`" plus the name of the archetype. If the parameter `inv_code` is set, then that will also be added before each name. (So you can have multiple independent carry-overs.) The value of each variable will be the stack count of the object, which may be 0. To make the script work on objects in the player's inventory, set the parameter `inv_player`. In that case, the ScriptParams links will be read from the script object, but it will get the contained objects from the Player object. The player's inventory will also be used if the script is on the starting-point marker.

Any properties placed on the concrete object will be lost. This is mostly an issue with loot objects. If you want to carry-over loot from the player's inventory, you should use the LastMissionLoot script. Loot in other containers will work, but you have to set the loot amount on the archetype, not the created object.

#### InvLoadUp

Creates objects based on quest variables that were set in a previous mission. It looks for quest variables that begin with "`inv_`". If the parameter `inv_code` is set, then the variables must begin with that code plus "`inv_`". An object with the name of the variable (minus the prefix code) is created and added to the contents of the script object. If the parameter `inv_player` is set, then the new objects are added to the player's inventory. The player's inventory will also be used if the script is on the starting-point marker. If the value of the quest variable is not zero, then the stack count of the object is set to that value. Otherwise, the object is created unmodified. Each quest variable that matches the prefix code is deleted, even if an object can't be created. (So it's safe to have another carry-over later. You can even have a carry-over skip missions.)

You can make the script only activate on certain difficulty levels by setting the Difficulty\Script property. The objects will be created only on the difficulty levels that you specify. The quest variables with the correct prefix code will be deleted even if the objects aren't created.

#### TrigPurchase

Put this on an item in the pre-mission store, and it will signal whether it was purchased or not. Specifically, it will test if it is in the Player's inventory after the load-out screen is exited. If it is, then TurnOn is sent along ControlDevice links; otherwise it will send TurnOff. You can invert and filter the trigger.

#### TrapDisarmPlayer (GenericTrap)

Remove offensive items from the player's inventory. Any items that have been removed are returned to the player when the trap is turned off. The parameter `nopotions` will allow the player to keep potions. Normally, all weapons, projectiles, crystals, grenades, and potions are removed.

### AI Interaction

#### KnockOnDoor

This will signal an AI when you frob a door that is closed and locked. The signal will be sent to the AI that "own" the door. (Owns link from the AI to the door.) They will be signalled with `knock_knock`.

#### InteractFrobHack

A replacement for the standard script CorpseFrobHack. This is needed for scripts which allow you to frob an active AI. To use it, you need the metaproperty FrobInteract with the appropriate frob settings. (Usually `World: Script`.) The metaproperty will be placed on the AI where CorpseFrobHack would otherwise have used FrobInert. (That is, when the AI isn't dead or unconscious.)

#### AIInteraction

Listens for Interact messages and will sent TurnOn along a ScriptParams link when one is received. The data sent with the message is first checked against the link data. If no matching links are found, then links that have an empty data field are used. Then one of the appropriate links is randomly chosen as the relay target.

#### PlayerInteraction

When the object is tool-frobbed it will send the message Interact to the object it was frobbed on. The value of the Inventory\Object Name property will be sent as the data of the message. If there is a colon in the name, then everything after (and including) the colon is discarded. (So you can name objects without a `ObjNames.str` file.) The schema `new_speech` is played when the object is contained by the player.

#### AwarenessFilter

When placed on another object, an AI can send a CanISeeYou or CanIHearYou message to test for awareness of the object from the AI. When placed on the AI, it can send CanISeePlayer or CanIHearPlayer messages to itself to test for awareness of the Player object. Or it can send CanISeeObject or CanIHearObject with the object specified in the first data parameter. The reply will be non-zero if the condition is true, which will allow a pseudo-script to continue. Otherwise, the result is zero and the pseudo-script will be aborted.

#### TrapSignalAwareness (GenericTrap)

When turned on, a signal is sent to all AI that have an AIAwareness link to each ControlDevice linked object. The signal to send is specified in the parameter `signal`. By default, any first-hand awareness is accepted, use the `awareness` parameter to specify a more restrictive level. If it's `1`, then the AI must be able to see the object, if `2`, then it must have only heard it.

#### PrivateRoom

Allows an AI that is normally non-hostile to become alarmed if it sees the player in a room. When the player enters the room, the metaproperty M-PlayerBreach is added to the player, and AIWatchObj links are created from every Human object to the Player. If any AI can see the player at the moment he enters the room, then the AI is signalled with `alarm`. When the player exits the room, then the metaproperty is removed as are the AIWatchObj links. There is a slight pause after leaving a room before the links are destroyed. The length of the pause can be set with the `exit_time` parameter; the default is 1 second. The idea is that an AI will be alerted if it sees that you've just walked out of the room, even if he didn't actually see you in the room itself.

The M-PlayerBreach should have the AI\Utility\Watch: Watch link defaults property set to `Player intrusion`. You then want to set more restrictive awareness requirements (at least moderate level and line-of-sight) or use the AwarenessFilter script.

#### HighlySuspicious

A replacement for SuspiciousReactions. Responds to a Suspicious message (which is usually sent from a pseudo-script) based on parameters read from the string file `Strings\SuspParm.str`. The first data parameter is the name or ID of the suspicious object, the second is the suspicious type. If the suspicious type is `generic`, then it is translated based on the object's archetype. Windows objects have type `glass`, ExBanner is `banner`, and RopeArrowRope is `rope`. If the type is `door` or `lock` then the message will be ignored if the AI that originally frobbed the door is on the same or neutral team as this AI.

The level of suspiciousness is calculated for the object. If it's greater than 1.0, then the actions (if any) are carried out. The base level is specified with the string `type_factor`. The number of other suspicious objects that the AI is aware of is counted, as well as the number that have the same suspicious type. These counts are multiplied by `type_factor_others` and `type_factor_similar` then added to the base level. Then a random factor from 0.0 to `type_factor_random` is added.

The actions that can be performed are to raise the AI's alert level, add an AIWatchObj link to the suspicious object, or go to then frob the object. The string setting `type_alert` will raise the alert level to the level specified. The alert level won't be lowered if it's already higher. After a period of time, specified in either `type_alert_time` or a global setting, the alert level will be allowed to go back down. Although, that doesn't mean that it will be lowered right away. If the string `type_watch` is set, then an AIWatchObj link is created to the object. The value of the string is ignored. If `type_watch_time` exists, then the link will be removed after the time specified. If `type_frob` is set then the AI will first go to the suspicious object and then frob it. The object will not be frobbed if it is no longer suspicious when the AI reaches it. For the `gaslight` type, the value of the string can be "on" which will have the AI only frob the light to turn it on. Otherwise, a suspicious light that is on may be frobbed off. (If you're using the NewGasLight script.) If the suspicious type of the object is `lock`, then the "frob" action will set the Engine Features\Locked property of the object instead of frobbing it.

After all the actions are completed, then the other suspicious objects that the AI is aware of are scanned and their suspiciousness levels added together, but disregarding the link-counting factors. Each object will also be adjusted based on the time it was noticed. The global setting `fade_time` is how quickly the suspiciousness level will be reduced. The level will go down 0.01 for the time specified. If `fade_time` is 0 (the default), then suspiciousness will not be reduced. The total of all levels is compared to `alert_level_3`, `alert_level_2`, and `alert_level_1`. The alert level for the AI will be raised to the highest alert that the suspiciousness is greater than. Each alert level will be sustained for the amount of time specified in `alert_time_3`, `alert_time_2`, and `alert_time_1`. These alert times will also apply to specific actions which don't have their own time setting. The default time is 10 seconds for each level.

All these factors take quite a bit of experimentation to get "just-right". The example `SuspParm.str` file I've included is based on the behavior of SuspiciousReactions. You may get better results with other settings.

#### NewSecurityDoor

This version of the SecurityDoor script will ignore state changes when there is an AIDoor link to the door or to its double. Otherwise, an AI that walks through a secure door will broadcast a suspicious concept, even though the SuspiciousReactions script ignores it.

### Miscellaneous

#### CommandControl (GenericControl)

Executes a command using IDebugScrSrv::Command. You can get a list of commands by typing `dump_cmds cmds.txt` in DromEd. Multiple command may be executed at a time by seperating them with '`;`'. Most commands will only work in DromEd, however.

#### RandomPatrolPath

This script allows you to create multiple patrol paths for a single AI. Create the patrol paths as normal, but manually link the AI to the possible starting points using AICurrentPatrol. When Sim starts, all but one of the AICurrentPatrol links will be removed, causing the AI to patrol to the destination of the remaining link. Set the parameter `teleport_ai` to have the AI immediately teleported to the starting point, instead of patrolling to it.

#### RandomStartingPoint

Moves the starting point to a randomly-selected location before the Player object is created. A single starting point is created as normal. From that point, create ScriptParams links to markers at the other locations. Create a link back to the starting point if you want it to consider its current position as well. Each ScriptParams link has the data `Startdiff`, where diff is the difficulty levels that the link is valid on. 0 is easy, 2 is hard, "012" will work on all difficulty levels. When Sim starts, the starting point will be teleported to the destination of one of the links.

#### TrapCamAttach (GenericTrap)

Attach the camera to this object. Return to the normal view when turned off. The parameters are `nolens`, `static`, and `forcereturn`. A `static` camera cannot be moved by the player. An object other than the one the camera is attached to can only return the camera if `forcereturn` is on. Only objects that have a physics model will show the lens. (The `nolens` option will not work in a regular game, I'm afraid.)

#### TrapFadeOut (GenericScale)

Gradually fade-out to black. The argument is the duration of the fade, in seconds. The screen will remain black until a TurnOff message is sent to the script. There is no fade-in, however. A negative argument does the same thing.

You can fake a fade-in with TrapRenderFlash. Trigger the render flash before sending TurnOff to TrapFadeOut and the after-effect of the flash will act as the fade.

This script is ineffective if the DromEd variable `no_endgame` is set.

#### TrapRenderFlash (GenericTrap)

Display a render flash and optionally play a sound. Make a RenderFlash link from the object (or its archetype) to the archetype of a flash SFX. To play a sound, use a SoundDescription link.

The way the flash is created causes the current weapon to be deselected.

#### TrapFreezePlayer (GenericScale)

Prevents the player from moving. Basically, the extreme opposite of a speed potion. This responds to TurnOn and TurnOff more like a trap than a scale. You can change the speed scale with the parameter `freeze_speed`. A value of 0.0 (the default) will prevent the player from moving at all, and 1.0 is normal speed. An arbitrary value can be specified with a ScriptControl message. Each speed modification is named, and the default name for this trap is "Freeze"; use the parameter `freeze_name` to change it.

Negative values are allowable, with interesting results.

#### TrapKillEvents (GenericTrap)

This can disable user input. All user input, which means escape doesn't even work. So the script will always timeout. The default timing is 10 seconds. You can change it with Script\Timing, but it must be something other than zero.

It takes the parameter `event_mask`. When the parameter is specified in the design note, it is a binary string. A quest variable is read as a regular number. The mask bits are:

- 1000 - Allow mouse movement.
- 0100 - Allow mouse buttons.
- 0010 - Unknown. Might be joystick.
- 0001 - Allow keyboard.

You can specify more than four bits, but I don't know if it will do anything.

#### ZeroGravRoom

A room script. It sets the player's gravity to zero when entered, and to normal gravity when exited. (Note the side-effect when a lo-grav potion is used.) You might recognize this if you've ever messed with ShockEd.

The idea is to create a negative-gravity room that lifts the player. Then at the point you want to stop create another room with positive gravity and add this script to it. (For best results, the room boundary should be at least 0.5–1.0 units above the floor that the player will walk out onto.)

## Revision History

- 1.5.4 - 2005/05/12
  ScriptController was broken.
  Add the `player_frob` parameter to TrapFrobber.
  Port to Thief: TDP/Gold and System Shock 2. (quasi-unsupported)
- 1.5.3 - 2005/03/10
  Yet another major bug in TrapCampaignQVar.
  Add ScriptController because it's useful.
  Fix show-stopping bug in version.osm.
  Back-port various clean-ups, additions, and tweaks.
- 1.5.2 - 2005/01/13
  Critical bug with MSVCRT 6.
  Cosmetic bug in monolog output.
- 1.5 - 2004/12/22
  The "Obligatory Christmas Episode" release.
  Added ObjFrobInert, GrabFrobInert, TweqFrobInert, TrapFrobInert and modified DoorFrobInert to fit-in.
  Also TrigFrobber, TrapDispenser, TurnAtWaypoints, VisibleRelay, ResetTweqState, RecallDevice, TrigProxFrob, HighlySuspicious, NewSecurityDoor.
  AI interaction scripts: InteractFrobHack, AwarenessFilter, TrapSignalAwareness, AIInteraction, PlayerInteraction, PrivateRoom.
  Expanded control messages to OnScreenText.
  When displaying text, time is calculated based on the number of words in the string. Affects OnScreenText, TrapText, and FocusPlaque.
  NewGasLight plays around with the light mode so random lights will brighten immediately. (Actually uses "smoothly brighten".)
  NewGasLight no longer mucks with the FrobInert metaproperty, so it can actually be used with other torch-like objects.
  Added `no_emitter` to NewGasLight
  TrapRelayRepeat can repeat forever and auto-start.
- 1.4.1 - 2004/10/26
  Patch update fixing bugs in InvCarryOver/InvLoadUp
  Reduced the size of version.osm 50% by using W32 functions instead of iostreams.
  ScriptLib: Add functions for finding inherited links.
  lg: Important change to script message handling that prevents crashing when used with the GayleSaver scripts.
- 1.4 - 2004/09/27
  The "Exiled" release.
  Added OnScreenText, RandomStartingPoint, RandomPatrolPath, TrigDoorOpening, InvCarryOver, InvLoadUp.
  Also GenericPotion, InventoryMetaprop, TrapContainer, TrigPurchase.
  ScriptLib: Fixed a NULL-pointer error in GetParamInt and GetParamFloat.
  TrapCampaignQVar supports the Thief 2 ops. Also jumps through some hoops to work better (maybe) with TrigQVar.
- 1.3 - 2003/11/02
  The "Wait'll you see me now" release.
  More hacking goodness: NewGasLight.
  GenericScript: `OnLocked` changed to `IsLocked`; added `SetLock`; `InitTrapVars` is virtual. That should make things more interesting.
  Memory leak in TrapCampaignQVar. Why the hell don't I just use `<string>` for all that crap?
  Added TrapSiblingRelay; this one is l33t.
  Added TrigContact, FocusPlaque, ContainerPhysics.
  Lots of changes in the lg headers. I don't usually mention that, but this is pretty significant. I think I finally have all of the IIDs from Thief, Thief 2, and System Shock 2.
- 1.2 - 2003/10/25
  The "I actually know what I'm doing... Honest!" release.
  Initialize `m_iTiming` and `m_iFlags` in GenericScript to avoid nasty bug.
  Completed TimedGrenade which was non-functional in 1.0.
  TrapMoveRelative now supports rotational velocity.
  New scripts: ZeroGravRoom, TrapFrobber, and CommandControl. TrapFadeOut, KnockOnDoor, and NewKey. TrapThreshold, and LockHelper/KeyHelper/PickHelper.
- 1.1 - 2003/10/14
  The "Okay, I'm an idiot" release.
  Fixed TrapRandomTimer bug.
  Default timeout for TrapKillEvents is now 10 seconds, instead of 60. (Just in case you forget and think the game isn't working.)
- 1.0 - 2003/10/08
  The "Put it back in the oven, it's not done yet" release.