You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2404 lines
104 KiB
Markdown

# XML syntax
Sample:
```xml
<Action>
<Name><![CDATA[Examine]]></Name>
<OverrideName><![CDATA[]]></OverrideName>
<actionparent><![CDATA[None]]></actionparent>
<Active><![CDATA[True]]></Active>
<FailOnFirst><![CDATA[True]]></FailOnFirst>
<InputType><![CDATA[None]]></InputType>
<CustomChoiceTitle><![CDATA[]]></CustomChoiceTitle>
<EnhancedInputData>
<BackgroundColor><![CDATA[-1929379841]]></BackgroundColor>
<TextColor><![CDATA[-16777216]]></TextColor>
<Imagename><![CDATA[]]></Imagename>
<UseEnhancedGraphics><![CDATA[True]]></UseEnhancedGraphics>
<AllowCancel><![CDATA[True]]></AllowCancel>
<NewImage><![CDATA[]]></NewImage>
<TextFont><![CDATA[Microsoft Sans Serif, 12pt]]></TextFont>
</EnhancedInputData>
<PassCommands>
<Command>
<CmdType><![CDATA[CT_DISPLAYPICTURE]]></CmdType>
<CommandText><![CDATA[]]></CommandText>
<Part2><![CDATA[Toybox.jpg]]></Part2>
<Part3><![CDATA[]]></Part3>
<Part4><![CDATA[]]></Part4>
<EnhancedInputData>
<BackgroundColor><![CDATA[-1929379841]]></BackgroundColor>
<TextColor><![CDATA[-16777216]]></TextColor>
<Imagename><![CDATA[]]></Imagename>
<UseEnhancedGraphics><![CDATA[True]]></UseEnhancedGraphics>
<AllowCancel><![CDATA[True]]></AllowCancel>
<NewImage><![CDATA[]]></NewImage>
<TextFont><![CDATA[Microsoft Sans Serif, 12pt]]></TextFont>
</EnhancedInputData>
</Command>
</PassCommands>
</Action>
```
## Action
* `Name`: `string`
* `OverrideName`: `string`, default `""`
* `actionparent`: `string`, default `"None"`
* `Active`: `bool` (`True` / `False`)
* `FailOnFirst`: `bool`, default `True`
* `InputType`: `ActionInputType` (`None`, `Object`, `Character`,
`ObjectOrCharacter`, `Text`, `Custom`, `Inventory`)
* `CustomChoiceTitle`: `string`, default `""`
* `CustomChoices`: `CustomChoice[]`, only if array not empty
* `EnhancedInputData`: `EnhancedInputData`
* `Conditions`: `Condition[]`, only if array not empty
* `PassCommands`: `variant<Command, Condition>[]`, only if array not empty
* `FailCommands`: `variant<Command, Condition>[]`, only if array not empty
## CustomChoice
* `Name`: `string`
## EnhancedInputData
* `BackgroundColor`: `color`: `color.ToArgb() == 0xaarrggbb` reinterpret_casted
to `int32_t`, default `rgba(255, 255, 255, 140)`
* `TextColor`: `color`, default `rgba(0, 0, 0, 255)`
* `Imagename`: `string`, default `""`. Ignored?
* `UseEnhancedGraphics`: `bool`, default true. Called `Use Overlay Graphics` in
GUI. When true, replace image + choices overlaid, when false, `NewImage`
ignored and popup window with normal winforms table and ok/cancel button (even
when `AllowCancel` is false).
* `AllowCancel`: `bool`, default true
* `NewImage`: `string`, default `""`
* `TextFont`: `font`, default `Times New Roman Bold, 12pt`. Ignored/buggy?
## Condition
* `Name`: `string`, default `""`
* `Checks`: `Check[]`, only if array not empty
* `PassCommands`: only if array not empty
* `FailCommands`: only if array not empty
## Check
* `CondType`: `ConditionType`, default `CT_Item_Held_By_Player`
* `CkType`: `CheckType` (`CT_Uninitialized`, `And`, `Or`)
* `Step2`: `string`, default `""`
* `Step3`: `string`, default `""`
* `Step4`: `string`, default `""`
## Command
* `CmdType`: `CommandType`
* `CommandText`: `string`, default `""`
* `Part2`: `string`, default `""`
* `Part3`: `string`, default `""`
* `Part4`: `string`, default `""`
* `CustomChoices`: `CustomChoice[]`, only if not empty.
* `EnhancedInputData`: `EnhancedInputData`.
# Action
* `ActionParent`: used to generate submenus in right-click menu (yet another
ad-hoc grouping). If parent is inactive, action is inactive too.
* `FailOnFirst`: pseudocode, `c.Check()` runs PassCommands for each iteration
when it is a loop (yes, when `FailOnFirst` is false, `PassCommands` is
executed one more time)
```c++
bool success = FailOnFirst || Conditions.empty();
for (c : Conditions)
{
if (c.Check()) // check succeeded or it was a loop
{
if (!FailOnFirst) success = true;
if (!FailOnFirst || !c.loop) c.PassCommands.Run();
}
else
{
c.FailCommands.Run();
if (FailOnFirst) { success = false; break; }
}
}
(success ? PassCommands : FailCommands).Run();
```
* `PassCommands`, `FailCommands`: one of them is executed based on the
conditions' result, see above for exact semantics, but generally, with
`FailOnFirst` it means all checks passed (so `and` with short-circuit), and
without `FailOnFirst` it means at least one check passed (so `or` without
short-circuit).
* `EnhancedData`: only used when `InputType != None && InputType != Text &&
UseEnhancedGraphics && !HideMainPicDisplay`
* `InputType`: ask a question at the beginning of the action if not None.
* `Object`: select an object currently visible in the player GUI. In practice,
this means any object in the inventory, any object in the current room
(including portals), any subobject of any of them; or any object held by a
character in the current room with allow inventory interactions. (Subobjects
inside characters don't appear but subobjects inside subobjects do, albeit
buggy! Just what did you expect?) List items: object DisplayNames, no tag
when `!UseEnhancedGraphics`. `AdditionalData` value: object's name (override
ignored).
* `Character`: select a character in the current room. List object: character
DisplayName with tag. `AdditionalData` value: character's name (override
ignored).
* `ObjectOrCharacter`: union of the previous two, except characters lack tag
with `UseEnhancedGraphics`...
* `Text`: input is not a selection from a list, but a free form text.
`EnhancedData` is ignored in this case.
* `Custom`: `CustomChoices` contains a list of choices. When not using overlay
graphics, the color of the text can be changed with `[c r,g,b]...[/c]`
(except the actual ranges are ignored, `foo[c 0,255,0]bar` sets the full
text to green, in case of multiple `[c]` tags, the last one wins. When using
overlay graphics, these tags are displayed as-is, without any processing.
Did you expect any logic behind this?)
* `Inventory`: objects in the player's inventory. No tag when
`!UseEnhancedGraphics`.
* Note that this doesn't nest properly. Executing an action inside an action
means that after returning to the parent action, the selection is lost.
* `action_object`: a hidden parameter. This is set to the object when executing
an action on an object, null otherwise (this is nesting properly).
* `player_moving`: another hidden parameter, only available in `<<On Player
Leave First Time>>` and `<<On Player Leave>>` actions, when triggered by a the
player (the 3DPD) trying to leave the current room. It is set to invalid in
other cases.
* Execution:
1. Clear `AdditionalData`
2. When there is an `EhancedData` with an image, `ShowMainImage` and
`OverlayGraphics` is true, and `InputType` is not `None` or `Text`, set the
image to that.
3. When `InputType` is not `None`, ask the input. If canceled, return (action
result is false). Otherwise get the `AdditionalData` value to set, then
print it, unless it is an uuid and there is an object with that uuid,
because in that case it prints that object's name.
4. Execute conditions
5. Execute pass/fail commands
# Command
* `CustomChoices`: can be set on `CT_SETVARIABLEBYINPUT`
* `EnhancedData`: can be set on `CT_SETVARIABLEBYINPUT`
* Exception handling: exceptions are caught when executing commands. Rags
displays a message, then continues with the next command.
## Generic command info
* `Part2`, `Part3`, `Part4` and `Text` values are always run through the [text
replacement function][] before usage.
* Object/room UUID or name: this generally means rags first tries to find a
room/object with the given UUID, and if there's no one, it tries to find one
with the name. In this case everything is processed as strings, so if you end
up with an object whose UUID is in fact not a valid UUID, it will still find
it. On the other hand, this requires exact match, i.e. 8-4-4-4-12 format
without white spaces. However, there are a few cases when Rags actually tries
to parse the given string as a UUID and somehow work differently depending on
the outcome, but they're clearly marked as "room UUID (**can be parsed**) or
name". In this case you can ue other formats, see [allowed GUID formats][guid]
[(archive)][guid_archive] for details. (And undocumented details: except the
last format, spaces are not allowed, but the string is trimmed before use, so
`" {123..."` is valid, but `"{ 123..."` is not).
* Aborts the command list: subsequent items in the currently executing
`PassCommands`/`FailCommands` will not run, but it does not affect commands
above. So for example,
```
Action
\-PassCommands
. |-Condition
. | |-PassCommands
. | | |-Command cmd0
. | | |-Command cmd1
. | | \-Command cmd2
. | \-FailCommands
. | . \-Command cmd3
. |-Command cmd4
. \-Command cmd5
```
If `cmd1` aborts, `cmd2` won't be executed (and neither `cmd3`), but instead
it will continue with `cmd4` and `cmd5`. This means that a `CT_LOOP_BREAK` not
directly in the loop's `PassCommands` can act weird.
[guid]: https://docs.microsoft.com/en-us/dotnet/api/system.guid.-ctor?view=net-5.0#System_Guid__ctor_System_String_
[guid_archive]: https://archive.md/wtYUh
### Set custom property commands {#set-custom-property}
* `Part2`: `name:property_name`
* `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` (**case
sensitive**)
* `Part4`: value (**double replaced**)
* `Text`: ignored
The exact interpretation of `Part2` varies, but generally if it doesn't have
exactly one colon, or if the entity doesn't exists, it doesn't do anything. If
the entity doesn't have a property with name `property_name` (**case
sensitively**), it also doesn't do anything.
`Part4` is double evaluated, then in case of `*_JS` commands, it is evaluated as
a JavaScript code (whatever dialect Microsoft's JScript supports), unless it is
an empty string anfter trimming. In this case, it is replaced with `-1`. If the
return value is `null`, it is replaced with `"Null value returned from
evaluate."` string, otherwise it is converted to a string, using whatever rules
this [this shit][jscript] [(archive)][jscript_archive] uses.
If `Part3` is not one of the allowed values, it doesn't do anything. If `Part3`
is `Equals`, set the property's value to `Part4`. Otherwise it tries to
interpret both the property's current value and `Part4` as a double and do the
arithmetic operation. If one of the values are not a number, it doesn't do
anything, except that with `Subtract` if `Part4` is not a double, it **throws an
exception**.
[jscript]: https://docs.microsoft.com/en-us/dotnet/api/microsoft.jscript.eval.jscriptevaluate?view=netframework-4.8
[jscript_archive]: https://archive.md/YhYP6
### Room enter procedure {#room-enter-procedure}
This is used by multiple commands. Action and timer executions are skipped in
some cases.
* Prints an empty line to the log.
* Sets room & main image (inline or to the main image area, handling layered
images) to the room's image (removes it if the room doesn't have one).
* When not skipping actions: when the room's `EnterFirstTime` is false, set it
to true and execute inner procedure with `<<On Player Enter First Time>>`.
Afterwards, execute inner procedure with `<<On Player Enter>>`.
* If the actions changed the player's room, don't do anything else.
* Prints the player's room description.
* If notifications are enabled:
* If the room has visible objects, it prints `"You can see "`, for each
visible object preposition + `" "` + display name (if preposition after
trimming is empty, preposition + space is skipped), separated by `", "`, all
on one line.
* For each character in the room, it prints display name + `" is here."`
* When not skipping timers: execute [multi timer
procedure](general.md#timer-multi).
#### Inner procedure
* There's a try-catch around the whole stuff, silently discarding any error
inside (but action execution already contains a try-catch inside, so nothing
should throw in this shit).
* First save the player's current room (original room)
* If the player's room has the specified action, execute it without
`action_object`.
* If the player has the specified action, execute it without `action_object`.
* Go through all objects in the player's current room.
* If the object's `EnterFirstTime` flag is false, set it to true and execute
its `<<On Player Enter First Time>>` action if it exists with object as
`action_object`.
* When the inner procedure is called with `<<On Player Enter>>` and the object
has an action with that name, and the player is still in the original room,
execute it with object as `action_object`.
* If the object is a `Container` and it is `Open` or not `Openable`, go
through all subobjects and do the previous two points for these subobjects.
Note that this operation is not recursive, so subobjects of the subobjects
are not considered.
* Go through all characters in the player's current room.
* If the character's `EnterFirstTime` flag is false, set it to true and
execute its `<<On Player Enter First Time>>` action if it exists without
`action_object`.
* When the inner procedure is called with `<<On Player Enter>>` and the
character has an action with that name, and the player is still in the
original room, execute it without `action_object`.
This means that for example if the player in entering the room for the first
time, object/character enter first time events are executed before the room's
``<<On Player Enter>>`, but after if the player is entering a second time...
## Available CommandTypes
### CT_UNINITIALIZED
### CT_ACTION_ADD_CUSTOMCHOICE {#CT_ACTION_ADD_CUSTOMCHOICE}
* `Part2`: `type:name:action_name`
* `Part3`: ignored
* `Part4`: ignored
* `Text`: choice to add (**case sensitive**)
* FIXUP: none
If `Part2` doesn't contain a colon, this action doesn't do anything. If it only
contains one colon, it is parsed as `type:action_name` and name is treated as
empty. A third colon and everything after it is ignored.
`type` is **case sensitively** one of the following:
* `Chr`: `name` is a character name (case insensitive). If the character does
not exists, it **throws an exception**.
* `Obj`: `name` is an object UUID or name (case insensitive). If the object does
not exist, it doesn't do anything.
* `Player`: `name` is ignored.
* `Room`: `name` is a room UUID (**can be parsed**) or name (case insensitive).
If the room doesn't exist, it doesn't do anything.
* `Timer`: `name` is a timer (case insensitive). If the timer doesn't exist, it
doesn't do anything.
* Anything else: it doesn't do anything.
`action_name` names an action inside the specified entity (case insensitive). If
it doesn't exists, this action doesn't do anything. If the action already has a
custom choice named `Text`, it doesn't do anything. Otherwise it adds `Text` to
the end of the custom choices.
### CT_ACTION_CLEAR_CUSTOMCHOICE
* `Part2`: `type:name:action_name`
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
See [CT_ACTION_ADD_CUSTOMCHOICE](#CT_ACTION_ADD_CUSTOMCHOICE) for `Part2`
handling. If it specifies a valid action, this action removes all custom choices
from it.
### CT_ACTION_REMOVE_CUSTOMCHOICE
Same as [CT_ACTION_ADD_CUSTOMCHOICE](#CT_ACTION_ADD_CUSTOMCHOICE), except
this action removes `Text` from the custom choices, if the custom choice exists.
### CT_DISPLAYCHARDESC
* `Part2`: character name (case insensitive)
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the specified character does not exist, it **throws an exception**.
Otherwise, it prints the characters description to the log.
Additionally, if notifications are not turned off, and the character has any
`Visible` and `Worn` object in it's inventory, it prints `<character display
name> is wearing:`, then the display names of all worn objects in separate
lines. If the character has any `Visible` but not `Worn` objects, it prints
`<character display name> is carrying:`, then the display names of all not worn
objects, separated by newlines.
### CT_CHAR_DISPLAYPORT
* `Part2`: character name (case insensitive)
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the specified character does not exist, it doesn't do anything. Otherwise
what it does depends on the value of `HideMainPicDisplay` and `UseInlineImages`:
* `HideMainPicDisplay` and `UseInlineImages`: if the character doesn't have a
portrait, **throw an exception**. If the portrait is an image, paste it into
the log (without scaling and buggily, so sometimes it will just insert an
empty line...). If it is an audio/video, print the full path of the temporary
file to the log (where rags extracted the media file)...
* `HideMainPicDisplay` and not `UseInlineImages`: it doesn't do anything.
* Else: if the character doesn't have a portrait, or it is not an image, it
doesn't do anything. Otherwise, it sets the main picture and its overlay(!)
(right to the log) to character's portrait, including layered images. (Since
file overlays are over the main overlay, this will work, but if the base image
has transparency, it will be weird because of the double draw.) (I think rags
author wanted to support videos here based on the code, he just fucked it up.)
### CT_MOVECHAR
* `Part2`: character name (case insensitive)
* `Part3`: room UUID (**can be parsed**) or name (case insensitive). Special
values (not parsed, must match exactly):
* `00000000-0000-0000-0000-000000000001`: player's current room
* `00000000-0000-0000-0000-000000000002`: "void" room
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part3` is `<CurrentRoom>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000001`. If `Part3` is `<Void>`, replace it
with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
as an UUID and there is a room with that name, replace it with the room's
UUID.
If the specified character does not exist, **throws an exception**. If the
character is in the same room as the player, and it has an action named `<<On
Character Leave>>`, execute that (without a `loop_item`).
If `Part3` is `00000000-0000-0000-0000-000000000001`, move the character to the
same room as the character, and if it has an action named `<<On Character
Enter>>`, execute it. If `Part3` is `00000000-0000-0000-0000-000000000002`, move
the character nowhere.
If `Part3` is not one of those special values, check if it **can be parsed** as
a UUID. If yes, use that as-is, otherwise look up the room with that name. If
there is no room with that name, **DISPLAY A FUCKING MESSAGE BOX** (<code>"Error
in command CT_MoveChar.&nbsp; Could not locate a room called "</code> + `Part3`)
and returns. (But UUIDs are not checked for validity, so it's possible to move
someone to a not existing room.) Otherwise, move the character to the specified
room. If the room is the player's current room, and the character has an `<<On
Character Enter>>` action, execute it.
### CT_MOVECHARINVTOPLAYER
* `Part2`: character name (case insensitive)
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the specified character does not exist, it doesn't do anything. Otherwise
take all `Carryable` and `Visible` objects from the character's inventory and
move them to the player's inventory.
### CT_CHAR_MOVETOOBJ
* `Part2`: character name (case insensitive)
* `Part3`: object UUID (case insensitive)
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the specified character or object doesn't exist, it doesn't do anything. If
the object is in a room, move the character to that room. If the object's
location's UUID (after move) is the same as the player's room UUID (this can be
actually true when the object is in a character, but the character's UUID is the
same as the player's room's UUID...), [execute room enter
procedure](#room-enter-procedure) without actions.
### CT_CHAR_SETPORT
* `Part2`: character name (case insensitive)
* `Part3`: file name
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the specified character doesn't exist, it doesn't do anything. Otherwise set
the character's image to the specified file name, without error checking (so it
is possible to set it to a non-existing file).
### CT_SETCHARACTION
* `Part2`: character name (case insensitive)
* `Part3`: `action_name-state`
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the specified character doesn't exist, it doesn't do anything. If `Part3`
doesn't contain a hyphen, it **throws an exception**. Otherwise everything until
the last hyphen is the `action_name`. If the character doesn't have an action
with that name (case insensitively), it doesn't do anything. Otherwise it sets
its active flag, to true if `state` is `Active` (**case sensitively**), to false
otherwise.
### CT_CHAR_SET_CUSTOM_PROPERTY {#CT_CHAR_SET_CUSTOM_PROPERTY}
* `Part2`: `character_name:property_name`
* `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` (**case
sensitive**)
* `Part4`: value (**double replaced**)
* `Text`: ignored
* FIXUP: none
[See for more info on setting custom properties](#set-custom-property). If a
character named `character_name` doesn't exists (case insensitively) it doesn't
do anything.
### CT_CHAR_SET_CUSTOM_PROPERTY_JS
Same as [CT_CHAR_SET_CUSTOM_PROPERTY](#CT_CHAR_SET_CUSTOM_PROPERTY), but with
JavaScript.
### CT_SETCHARDESC
* `Part2`: character name (case insensitive)
* `Part3`: ignored
* `Part4`: ignored
* `Text`: new character description
* FIXUP: none
If the specified character does not exist, **throws an exception**. Otherwise
sets the character's description to `Text`.
### CT_CHAR_SET_GENDER
* `Part2`: character name (case insensitive)
* `Part3`: `Male` / `Female` / `Other` (**case sensitive**)
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the specified character doesn't exist, it doesn't do anything. If `Part3`
after trimming is not one of the allowed values, it **throws an exception**.
Otherwise it sets the character's gender.
### CT_CHAR_SET_NAME
* `Part2`: character name (case insensitive)
* `Part3`: ignored
* `Part4`: new character name override
* `Text`: ignored
* FIXUP: none
If the specified character doesn't exist, it doesn't do anything. Otherwise it
sets the character's name override.
### CT_COMMENT
* `Part2`: ignored
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
This doesn't do anything.
### CT_DEBUGTEXT
* `Part2`: ignored
* `Part3`: ignored
* `Part4`: ignored
* `Text`: debug text
* FIXUP: none
If the second command line argument is `DEBUG` (**case sensitively**), it prints
`Text` to the log.
### CT_JAVA_SET_RAGS
* `Part2`: ignored
* `Part3`: ignored
* `Part4`: ignored
* `Text`: JavaScript code (**double replaced**)
* FIXUP: none
If the JavaScript code does not return an array, it does nothing. Otherwise it
iterates through the returned array:
* non array elements are skipped
* if the element array doesn't have at least 2 items, it **throws an exception**
* otherwise the element is `[key, value]`
* convert them to strings
* if `value` is an empty string, replace it with a space (i.e. `" "`)
* call [text replacement function][] in set mode with `"[" + key + "]`
### CT_DISPLAYTEXT
* `Part2`: ignored
* `Part3`: ignored
* `Part4`: ignored
* `Text`: text
* FIXUP: none
Prints `Text` to the log.
### CT_EXPORTVARIABLE
* `Part2`: variable name (case insensitive)
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the specified variable doesn't exist, it doesn't do anything. Otherwise it
pops up a save file dialog, and if the user doesn't cancel it, it serializes the
full variable state to a file. If the save fails, it **displays a message box**.
### CT_IMPORTVARIABLE
* `Part2`: ignored
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
Pops up a file load dialog. If the user cancels it, it doesn't do anything. If
the load fails, it **displays a message box**. If there is already a variable
with the given name, it is removed, then the imported variable is added.
_Note_: this means it is possible to create a new variable by importing an
unrelated game's exported variable...
### CT_PAUSEGAME
* `Part2`: ignored
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
Prints `--------------------------------` (32 hyphens), then pauses the game.
### CT_DISPLAYLAYEREDPICTURE
* `Part2`: file name (case insensitive)
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
When `HideMainPicDisplay`, it doesn't do anything. Otherwise, it sets the
temporary overlay image, it sets it to `Part2` if it exist, removes the overlay
otherwise.
Temporary overlay is something that is drawn on top of the base image, but below
other overlay images and removed when a different image is displayed. Doesn't
work with videos.
_Note_: base image is set as `BackgroundImage` on a `PictureBox`, this action
sets the `Image` of the same `PictureBox` temporarily, Other overlays are drawn
from a paint event handler on top of this. This also has a weird side effect
that gif animations only work when set with this action, and only if the game is
not paused in an action.
### CT_DISPLAYPICTURE
* `Part2`: file name (case insensitive)
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
What it does depends on the value of `HideMainPicDisplay` and `UseInlineImages`:
* `HideMainPicDisplay` and `UseInlineImages`: if the file doesn't exist, **throw
an exception**. If it is an image, paste it into the log (without scaling and
buggily, so sometimes it will just insert an empty line...). If it is an
audio/video, print the full path of the temporary file to the log (where rags
extracted the media file)...
* `HideMainPicDisplay` and not `UseInlineImages`: it doesn't do anything.
* Else: If the file is an image, it sets the main picture (right to the log),
including layered images. If it is something else, it switches to a windows
media player control and tries to open it. If the file doesn't exist, it still
switches to WMP, but doesn't load anything into it.
### CT_MM_SET_BACKGROUND_MUSIC {#CT_MM_SET_BACKGROUND_MUSIC}
* `Part2`: file name (case insensitive)
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the file exists and it is an image, it doesn't do anything. Otherwise it sets
the background music volume to zero by decreasing it by 1% every 15ms (so it
will take 1.5s, and it blocks the execution while doing this (it looks like it
blocks the whole fucking GUI thread, but I'm not 100% sure on this). Then it
sets the WMP control to the file if it exists, it unloads whatever was loaded if
it doesn't exist.
### CT_MM_STOP_BACKGROUND_MUSIC
* `Part2`: ignored
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
It sets the background music volume to zero in the same way as in
[CT_MM_SET_BACKGROUND_MUSIC](#CT_MM_SET_BACKGROUND_MUSIC), then it stops the
playback. This doesn't unload anything.
### CT_MM_PLAY_SOUNDEFFECT
* `Part2`: file name (case insensitive)
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the file exists and it is an image, it doesn't do anything. Otherwise it sets
the background sound effect to `Part2` if it exists, it unloads it otherwise.
There is no fiddling with the volume like with background music and you can't
play more than one sound effect at the same time.
### CT_MM_SET_MAIN_COMPASS {#CT_MM_SET_MAIN_COMPASS}
* `Part2`: file name or `<Default>`
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If `Part2` is `<Default>` (**case sensitively**), it sets the compass image back
to rags' built-in default. Otherwise, it is a (case insensitive) filename. If
the file doesn't exists, or if it is not an image, it doesn't do anything.
Otherwise it sets the compass image (anim gifs and layered images not
supported).
### CT_MM_SET_UD_COMPASS
Same as [CT_MM_SET_MAIN_COMPASS](#CT_MM_SET_MAIN_COMPASS), except it sets the
up-down compass image on success.
### CT_LAYEREDIMAGE_ADD
* `Part2`: base image file name (case insensitive)
* `Part3`: layer file name
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the base image specified by `Part2` doesn't exist (or it's not an image), it
doesn't do anything. Otherwise it adds `Part3` to the end of the layered images
list (so it will be on the top) (even if it doesn't exist, it will simply be
ignored when drawing).
### CT_LAYEREDIMAGE_CLEAR
* `Part2`: base image file name (case insensitive)
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the base image specified by `Part2` doesn't exist (or it's not an image), it
doesn't do anything. Otherwise it clears all layered images.
### CT_LAYEREDIMAGE_REMOVE
* `Part2`: base image file name (case insensitive)
* `Part3`: layer file name (**case sensitive**)
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the base image specified by `Part2` doesn't exist (or it's not an image), it
doesn't do anything. Otherwise, remove the first (i.e. most bottom) occurrence of
`Part3` from `Part2`'s layered images.
### CT_LAYEREDIMAGE_REPLACE
* `Part2`: base image file name (case insensitive)
* `Part3`: layer to replace file name (**case sensitive**)
* `Part4`: layer to add file name (case insensitive)
* `Text`: ignored
* FIXUP: none
If the image specified by `Part2` or `Part4` doesn't exist (or not an image), it
doesn't do anything. Otherwise it removes the first occurrence of `Part3` from
`Part2`'s layers, and if found, add `Part4` to the end of the layers.
### CT_DISPLAYITEMDESC
* `Part2`: object UUID or name (case insensitive). Special values:
* `00000000-0000-0000-0000-000000000004` :: use `action_object`
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
UUID, and there is an object with that name, replace `Part2` with the object's
UUID.
When `Part2` is `00000000-0000-0000-0000-000000000004`: if there is a
`action_object`, it displays that object's description, otherwise it does
nothing.
When `Part2` is not a special value, try to get an object with than UUID,
failing that name. If it exists, it displays the description, otherwise it does
nothing.
Display description: if notifications are turned off, it just prints the
object's description. Otherwise, it prints the object's description, then:
* `". It is open."` (double space!) if it is `Openable` and `Open`.
* `". It is closed."` (double space!) if it is `Openable` and not `Open`.
* `". It contains:"` (double space!) if it is a `Container`, and (not
`Openable` or `Open`), and it is not a portal. Then it prints every visible
subobject's preposition + space + name on a new line, where every line except
the first is prefixed with `"and "`. If there are no visible subobjects, it
prints `"Nothing"`.
### CT_ITEM_LAYERED_REMOVE {#CT_ITEM_LAYERED_REMOVE}
* `Part2`: object UUID or name (case insensitive)
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the specified object doesn't exist, it doesn't do anything. Otherwise it
iterates over all other `Worn` objects in the same location (actually it's
buggy: if the object is not in a character/player, it iterates over all objects
in the same location *type*). If the two objects share a clothing zone, and if
`Part2's level <= other's level`, it fails.
The action collects all failures, and print a message to the log and returns. If
the object is in a character's inventory, the message prefix is: character name
(override ignored!) + `" cannot remove "` + `Part2`'s display name + `". "` +
character name + `" will need to remove "`, otherwise the message prefix is:
`"You cannot remove "` + `Part2`'s display name + `". You need to remove "`. The
message continues with the display names of all conflicting objects, separated
by `" and "`, and the message suffix is `" first."`.
If there are no failures, it prints a confirmation message. If the object is in
a character, it prints: character name (override ignored) + `" takes off "` +
`Part2`'s display name + `"."`, otherwise it prints `"You take off "` +
`Part2`'s display name + `"."`. Finally, it set's `Part2`'s `Worn` to false.
Display name in this case: if preposition not empty, prefix is `preposition` +
`" "`. If name override after trim is not empty, name override without trim,
otherwise name.
### CT_ITEM_LAYERED_WEAR
Same as [CT_ITEM_LAYERED_REMOVE](#CT_ITEM_LAYERED_REMOVE), except it sets `Worn`
to true and the messages are different. `You/character_name cannot wear` on fail
and `You put on/character_name puts on` on failure.
### CT_MOVEITEMTOCHAR
* `Part2`: object UUID or name (case insensitive). Special values:
* `00000000-0000-0000-0000-000000000004` :: use `action_object`
* `Part3`: character name
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
UUID, and there is an object with that name, replace `Part2` with the object's
UUID.
It the specified object doesn't exists, or in case of
`00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
nothing. Otherwise it sets the character's location to be inside `Part3` without
any checking (so it is possible to move the object into a non-existing
character).
### CT_MOVEITEMTOINV
* `Part2`: object UUID or name (case insensitive). Special values:
* `00000000-0000-0000-0000-000000000004` :: use `action_object`
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
UUID, and there is an object with that name, replace `Part2` with the object's
UUID.
It the specified object doesn't exists, or in case of
`00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
nothing. Otherwise, if the player has enforce weight limit set, checks if the
recursive sum of the carried items + the recursive weight of the new item does
not exceed the limit. If it does, it **DISPLAY A FUCKING MESSAGE BOX** with the
text `"The "` + object name (override ignored) + <code>" is too heavy to lift at
the moment.&nbsp; unload some stuff first."</code> (with two spaces before
unload and a lower case character at the beginning of the sentence), then
**CANCELS LOOP BREAK** and **ABORTS THE FUCKING COMMAND LIST**.
Otherwise it sets the object location to the player.
### CT_MOVEITEMTOOBJ
* `Part2`: object UUID or name (case insensitive). Special values:
* `00000000-0000-0000-0000-000000000004` :: use `action_object`
* `Part3`: object UUID (**can be parsed**) or name (case insensitive).
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
UUID, and there is an object with that name, replace `Part2` with the object's
UUID. Same with `Part3`.
It the object specified by `Part2` doesn't exists, or in case of
`00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
nothing. If `Part3` **can be parsed** as a UUID, set `Part2`s location to
`Part3` without any checking (so it is possible to move the object into a
non-existing object). Otherwise, it tries to find an object with the name given
in `Part3`, if it finds one, it moves `Part2` into it, otherwise it does
nothing.
### CT_MOVEITEMTOROOM
* `Part2`: object UUID or name (case insensitive). Special values:
* `00000000-0000-0000-0000-000000000004` :: use `action_object`
* `Part3`: room UUID (**can be parsed**) or name (case insensitive). Special
values:
* `00000000-0000-0000-0000-000000000001`: player's current room
* `00000000-0000-0000-0000-000000000002`: "void" room
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
UUID, and there is an object with that name, replace `Part2` with the object's
UUID. If `Part3` is `<CurrentRoom>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000001`. If `Part3` is `<Void>`, replace it
with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
as an UUID and there is a room with that name, replace it with the room's
UUID.
It the object specified by `Part2` doesn't exists, or in case of
`00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
nothing. Get a room depending on `Part3`:
* `00000000-0000-0000-0000-000000000001`: get the player's current room.
* `00000000-0000-0000-0000-000000000002`: no room (i.e. move the object to
nowhere)
* **can be parsed** as a UUID: use the specified UUID directly without any
checking (so it is possible to move the object to a non-existing room).
* anything else: find a room with the specified name. If the room doesn't
exists, it **DISPLAY A FUCKING MESSAGE BOX** (<code>"Error in command
CT_MoveItemToRoom.&nbsp; Could not locate a room called "</code> + `Part3`)
and returns.
It sets the object location to the room.
### CT_SETOBJECTACTION
* `Part2`: object UUID or name (case insensitive). Special values:
* `00000000-0000-0000-0000-000000000004` :: use `action_object`
* `Part3`: `action_name-state`
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
UUID, and there is an object with that name, replace `Part2` with the object's
UUID.
It the object specified by `Part2` doesn't exists, or in case of
`00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
nothing. If `Part3` doesn't contain a hyphen, it **throws an exception**.
Otherwise everything until the last hyphen is the `action_name`. If the
object doesn't have an action with that name (case insensitively), it doesn't
do anything. Otherwise it sets its active flag, to true if `state` is `Active`
(**case sensitively**), to false otherwise.
### CT_ITEM_SET_CUSTOM_PROPERTY {#CT_ITEM_SET_CUSTOM_PROPERTY}
* `Part2`: `object_name:property_name`
* `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` (**case
sensitive**)
* `Part4`: value (**double replaced**)
* `Text`: ignored
* FIXUP: none
[See for more info on setting custom properties](#set-custom-property). If
object name is `<Self>` (**case sensitively**, single `<>`), it operates on
`action_object`, otherwise it finds an object with name `object_name` (case
insensitively). If it doesn't exist, it doesn't do anything.
### CT_ITEM_SET_CUSTOM_PROPERTY_JS
Same as [CT_ITEM_SET_CUSTOM_PROPERTY](#CT_ITEM_SET_CUSTOM_PROPERTY), but with
JavaScript.
### CT_SETITEMDESC
* `Part2`: object UUID or name (case insensitive). Special values:
* `00000000-0000-0000-0000-000000000004` :: use `action_object`
* `Part3`: ignored
* `Part4`: ignored
* `Text`: new description
* FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
UUID, and there is an object with that name, replace `Part2` with the object's
UUID.
It the object specified by `Part2` doesn't exists, or in case of
`00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
nothing. Otherwise it sets the object's description to `Text`.
### CT_ITEM_SET_NAME_OVERRIDE
* `Part2`: object UUID or name (case insensitive).
* `Part3`: ignored
* `Part4`: new name override
* `Text`: ignored
* FIXUP: none
It the object specified by `Part2` doesn't exists, it does nothing. Otherwise it
sets the object's name override to `Part4`.
### CT_SETLOCKEDUNLOCKED
* `Part2`: object UUID or name (case insensitive). Special values:
* `00000000-0000-0000-0000-000000000004` :: use `action_object`
* `Part3`: `Locked` / anything else (**case sensitive**)
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
UUID, and there is an object with that name, replace `Part2` with the object's
UUID.
It the object specified by `Part2` doesn't exists, or in case of
`00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
nothing. Otherwise if `Part3` is `Locked`, it sets the object's `Locked`
property to true, false otherwise.
### CT_SETOPENCLOSED
* `Part2`: object UUID or name (case insensitive). Special values:
* `00000000-0000-0000-0000-000000000004` :: use `action_object`
* `Part3`: `Open` / anything else (**case sensitive**)
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
UUID, and there is an object with that name, replace `Part2` with the object's
UUID.
It the object specified by `Part2` doesn't exists, or in case of
`00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
nothing. Otherwise if `Part3` is `Open`, it sets the object's `Open` property to
true, false otherwise.
### CT_SETITEMTOWORN
* `Part2`: object UUID or name (case insensitive). Special values:
* `00000000-0000-0000-0000-000000000004` :: use `action_object`
* `Part3`: `Worn` / anything else (**case sensitive**)
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
UUID, and there is an object with that name, replace `Part2` with the object's
UUID.
It the object specified by `Part2` doesn't exists, or in case of
`00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
nothing. Otherwise if `Part3` is `Worn`, it sets the object's `Worn` property to
true, false otherwise.
### CT_ITEM_SET_VISIBILITY
* `Part2`: object UUID or name (case insensitive). Special values:
* `00000000-0000-0000-0000-000000000004` :: use `action_object`
* `Part3`: `Visible` / anything else (**case sensitive**)
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
UUID, and there is an object with that name, replace `Part2` with the object's
UUID.
It the object specified by `Part2` doesn't exists, or in case of
`00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
nothing. Otherwise if `Part3` is `Visible`, it sets the object's `Visible`
property to true, false otherwise.
### CT_ITEMS_MOVE_CONTAINER_TO_CONTAINER
* `Part2`: object UUID or name (case insensitive). Special values:
* `00000000-0000-0000-0000-000000000004` :: use `action_object`
* `Part3`: object UUID or name (case insensitive). Special values:
* `00000000-0000-0000-0000-000000000004` :: use `action_object`
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
It the object specified by `Part2` or `Part3` doesn't exists, or in case of
`00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
nothing. Otherwise it moves all objects directly in `Part2` to `Part3`.
### CT_LOOP_BREAK
* `Part2`: ignored
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
Schedules a loop break and **aborts the current command list**.
### CT_CANCELMOVE
* `Part2`: ignored
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
Sets a flag to cancel the player's current move. This can be called anywhere,
but only has an effect inside `<<On Player Leave First Time>>` and `<<On Player
Leave>>` player actions when executed as a result of the player (the 3DPD one)
selecting a move target on the GUI.
### CT_DISPLAYPLAYERDESC
* `Part2`: ignored
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
Prints the player's description after replacement (without `loop_item`) to the
log.
### CT_SETLAYEREDPLAYERPORTRAIT
* `Part2`: file name or `<None>`
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If `Part2` is `<None>` (**case sensitively**), it is treated as an empty string.
This sets the player's overlay image to `Part2` without any checking (so it is
possible to set it to a non-existing file, it will be ignored when drawing).
### CT_MOVEINVENTORYTOCHAR
* `Part2`: character name
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
UUID, and there is an object with that name, replace `Part2` with the object's
UUID. (This is completely pointless, as `Part2` must be a character name, not
an object UUID).
Moves every item from the player's inventory to `Part2`'s inventory without any
checking (so it is possible to move it into a non-existing character).
### CT_MOVEINVENTORYTOROOM
* `Part2`: room's UUID. Special values (not parsed, must match exactly):
* `00000000-0000-0000-0000-000000000001` :: player's current room
* `00000000-0000-0000-0000-000000000002` :: "void" room
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<CurrentRoom>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000001`. If `Part2` is `<Void>`, replace it
with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
as an UUID and there is a room with that name, replace it with the room's
UUID.
Moves every item from the player's inventory to the specified location:
* `00000000-0000-0000-0000-000000000001`: move to the player's current room.
* `00000000-0000-0000-0000-000000000002`: move to nowhere.
* anything else: move to the room with the specified UUID without any checking
(so it is possible to move items to a not existing room).
### CT_MOVEPLAYER
* `Part2`: room uuid (**can be parsed**) or name (case insensitive).
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<CurrentRoom>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000001`. If `Part2` is `<Void>`, replace it
with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
as an UUID and there is a room with that name, replace it with the room's
UUID.
If `Part2` **can be parsed** as a UUID, find a room with that UUID, else find a
room with that name, and move the player there. If the room doesn't exist, move
the player to nowhere instead (which is a surefire way to fuck up rags and
receive exceptions from everywhere). Otherwise, [execute room enter
procedure](#room-enter-procedure) with actions but without timers.
### CT_MOVETOCHAR {#CT_MOVETOCHAR}
* `Part2`: character name (case insensitive).
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the character doesn't exist, or the character is nowhere or in the same room
as the player, it doesn't do anything. Otherwise it moves the player to the same
room as the character is in, if it exists, it moves the player to nowhere if it
doesn't exist. Finally, if the player is in a room, it [executes room enter
procedure](#room-enter-procedure) with actions but without timers.
*Note*: this action catches any inner exceptions and silently discards them.
### CT_MOVETOOBJ
* `Part2`: object UUID (case insensitive).
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the object doesn't exist, it doesn't do anything. If the object is in a room,
moves the player to that room, or nowhere if it doesn't. Finally, if the player
is in a room, it [executes room enter procedure](#room-enter-procedure) with
actions but without timers.
*Note*: this action catches any inner exceptions and silently discards them.
*Note*: unlike [CT_MOVETOCHAR](#CT_MOVETOCHAR), this executes room enter actions
when the object is not in a room or it is already in the same room as the
player.
### CT_PLAYER_SET_CUSTOM_PROPERTY {#CT_PLAYER_SET_CUSTOM_PROPERTY}
* `Part2`: `property_name`
* `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` (**case
sensitive**)
* `Part4`: value (**double replaced**)
* `Text`: ignored
* FIXUP: none
[See for more info on setting custom properties](#set-custom-property). Since
there is only one player, `Part2` is treated as `property_name`, even if it
contains colons.
### CT_PLAYER_SET_CUSTOM_PROPERTY_JS
Same as [CT_PLAYER_SET_CUSTOM_PROPERTY](#CT_PLAYER_SET_CUSTOM_PROPERTY), but
with JavaScript.
### CT_SETPLAYERACTION
* `Part2`: ignored
* `Part3`: `action_name-state`
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If `Part3` doesn't contain a hyphen, it **throws an exception**. Otherwise
everything until the last hyphen is the `action_name`. If the player doesn't
have an action with that name (case insensitively), it doesn't do anything.
Otherwise it sets its active flag, to true if `state` is `Active` (**case
sensitively**), to false otherwise.
### CT_SETPLAYERDESC
* `Part2`: ignored
* `Part3`: ignored
* `Part4`: ignored
* `Text`: new description
* FIXUP: none
Set the player's description to `Text`.
### CT_SETPLAYERGENDER
* `Part2`: `Male` / `Female` / `Other` (**case sensitive**)
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If `Part2` is not one of the allowed values, it **throws an exception**,
otherwise it sets the player's gender.
### CT_SETPLAYERNAME
* `Part2`: ignored
* `Part3`: ignored
* `Part4`: new player name (**double replaced**)
* `Text`: ignored
* FIXUP: none
Sets the player's name.
### CT_SETPLAYERPORTRAIT
* `Part2`: file name
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
Sets the player's image to `Part2` without any checking (so it is possible to
set it to a non-existing file).
### CT_DISPLAYROOMDESCRIPTION
* `Part2`: room UUID (**can be parsed**) or name (case insensitive). Special
values:
* `00000000-0000-0000-0000-000000000001`: player's current room
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<CurrentRoom>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000001`. If `Part2` is `<Void>`, replace it
with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
as an UUID and there is a room with that name, replace it with the room's
UUID.
If `Part2` is `00000000-0000-0000-0000-000000000001` it uses the player's
current room, otherwise it finds a room with the given UUID if it **can be
parsed** as a UUID, by a name otherwise. If the room doesn't exists it doesn't
do anything, otherwise it prints its description (and just the description, and
not 28472 unrelated things).
### CT_DISPLAYROOMPICTURE
* `Part2`: room UUID (**can be parsed**) or name (case insensitive). Special
values:
* `00000000-0000-0000-0000-000000000001`: player's current room
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<CurrentRoom>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000001`. If `Part2` is `<Void>`, replace it
with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
as an UUID and there is a room with that name, replace it with the room's
UUID.
If `Part2` is `00000000-0000-0000-0000-000000000001` it uses the player's
current room, otherwise it finds a room with the given UUID if it **can be
parsed** as a UUID, by a name otherwise. If the room doesn't exists it doesn't
do anything. What it does depends on the value of `HideMainPicDisplay` and
`UseInlineImages`:
* `HideMainPicDisplay` and `UseInlineImages`: if the file doesn't exist, **throw
an exception**. If it is an image, paste it into the log (without scaling and
buggily, so sometimes it will just insert an empty line...). If it is an
audio/video, print the full path of the temporary file to the log (where rags
extracted the media file)...
* `HideMainPicDisplay` and not `UseInlineImages`: it doesn't do anything.
* Else: If the file is an image, it sets the main picture (right to the log),
including (single) layered image. If both of them are images, it works
correctly. If any of them missing, they're not set. If one is an image and and
the other is a video, the last one wins (so overlay beats normal image).
### CT_ROOM_MOVE_ITEMS_TO_PLAYER
* `Part2`: room UUID (case insensitive). Special values (not parsed, must match
exactly):
* `00000000-0000-0000-0000-000000000001`: player's current room
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If `Part2` is not a valid UUID, it **throws an exception**. Otherwise, if it is
`00000000-0000-0000-0000-000000000001` uses the player's current room, otherwise
it finds the room with the given UUID. If there is no such room, it doesn't do
anything. Otherwise it moves all `Carryable` and `Visible` in the room to the
player's inventory.
### CT_SETROOMACTION
* `Part2`: room UUID (**can be parsed**) or name (case insensitive). Special
values:
* `00000000-0000-0000-0000-000000000001`: player's current room
* `Part3`: `action_name-state`
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<CurrentRoom>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000001`. If `Part2` is `<Void>`, replace it
with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
as an UUID and there is a room with that name, replace it with the room's
UUID.
It the room specified by `Part2` doesn't exists, it does nothing. If `Part3`
doesn't contain a hyphen, it **throws an exception**. Otherwise everything until
the last hyphen is the `action_name`. If the room doesn't have an action with
that name (case insensitively), it doesn't do anything. Otherwise it sets its
active flag, to true if `state` is `Active` (**case sensitively**), to false
otherwise.
### CT_ROOM_SET_CUSTOM_PROPERTY {#CT_ROOM_SET_CUSTOM_PROPERTY}
* `Part2`: `room_name:property_name`
* `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` (**case
sensitive**)
* `Part4`: value (**double replaced**)
* `Text`: ignored
* FIXUP: none
[See for more info on setting custom properties](#set-custom-property). If room
name is `<CurrentRoom>` (**case sensitively**), it operates on the player's
current room, otherwise it finds a room with name `room_name` (case
insensitively). If it doesn't exist, it doesn't do anything.
### CT_ROOM_SET_CUSTOM_PROPERTY_JS
Same as [CT_ROOM_SET_CUSTOM_PROPERTY](#CT_ROOM_SET_CUSTOM_PROPERTY), but with
JavaScript.
### CT_SETROOMDESCRIPTION
* `Part2`: room UUID (**can be parsed**) or name (case insensitive). Special
values:
* `00000000-0000-0000-0000-000000000001`: player's current room
* `Part3`: ignored
* `Part4`: ignored
* `Text`: new description
* FIXUP: if `Part2` is `<CurrentRoom>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000001`. If `Part2` is `<Void>`, replace it
with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
as an UUID and there is a room with that name, replace it with the room's
UUID.
If `Part2` is `00000000-0000-0000-0000-000000000001`, it operates on the
player's current room, otherwise if it **can be parsed** as a UUID, it finds a
room with that UUID, else it find a room with that name. If the specified room
doesn't exist, it doesn't do anything, otherwise it sets the room's description.
### CT_SETEXIT
* `Part2`: room UUID (**can be parsed**) or name (case insensitive).
* `Part3`: `direction-active` (**case sensitive**)
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<CurrentRoom>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000001`. If `Part2` is `<Void>`, replace it
with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
as an UUID and there is a room with that name, replace it with the room's
UUID. (*Note*: the action doesn't hande these values...)
If `Part2` **can be parsed** as a UUID, it finds a room with that UUID, else it
find a room with that name. If the specified room doesn't exist, it doesn't do
anything. If `Part3` doesn't contain a hyphen, it **throws an exception**,
otherwise everything until the first hyphen is the `direction`. If there is a
second hyphen, it and everything after it is ignored. `active` is trimmed of
white-space, but `direction` is not (unlike other enum parsing places...).
If `direction` does not refer to a valid direction (`North`, `South`,
`NorthEast`, ...), it doesn't do anything. Otherwise it sets its `Active`
status, to true if `active` is `Active`, false otherwise.
### CT_SETEXITDESTINATION
* `Part2`: room UUID (**can be parsed**) or name (case insensitive).
* `Part3`: direction (**case sensitive**)
* `Part4`: `<None>` or destination room UUID (**can be parsed**) or name
* `Text`: ignored
* FIXUP: none
If `Part2` **can be parsed** as a UUID, it finds a room with that UUID, else it
find a room with that name. If the specified room doesn't exist, it doesn't do
anything. If `direction` does not refer to a valid direction (without trimming),
it doesn't do anything.
If `Part4` is `<None>` (**case sensitively**), it sets the exit's destination to
nowhere and sets its `Active` flag to false. Otherwise, if `Part4` **can be
parsed** as a UUID, it finds a room with that UUID, otherwise it finds a room
with that name. If the room does not exists, it does nothing, otherwise it sets
the exit's destination to the room and the exit's `Active` status to true.
### CT_ROOM_SET_NAME_OVERRIDE
* `Part2`: room UUID (**can be parsed**) or name (case insensitive).
* `Part3`: ignored
* `Part4`: name override
* `Text`: ignored
* FIXUP: none
If `Part2` **can be parsed** as a UUID, it finds a room with that UUID, else it
find a room with that name. If the specified room doesn't exist, it doesn't do
anything, otherwise it sets the room's name override.
### CT_SETROOMLAYEREDPIC
* `Part2`: room UUID (**can be parsed**) or name (case insensitive). Special
values:
* `00000000-0000-0000-0000-000000000001` :: player's current room
* `Part3`: file name
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If `Part2` is `00000000-0000-0000-0000-000000000001`, it operates on the
player's current room, otherwise if it **can be parsed** as a UUID, it finds a
room with that UUID, else it find a room with that name. If the specified room
doesn't exist, it doesn't do anything.
If `Part3` is `<None>`, it is treated as empty string, then it sets the room's
overlay image to `Part3` without any checking (so it can be set to a
non-existing image).
If the room is the current player's room, and `HideMainPicDisplay` is false, it
updates the main image and room image, but without handling videos.
### CT_SETROOMPIC
* `Part2`: room UUID (**can be parsed**) or name (case insensitive). Special
values:
* `00000000-0000-0000-0000-000000000001` :: player's current room
* `Part3`: file name
* `Part4`: ignored
* `Text`: ignored
* FIXUP: if `Part2` is `<CurrentRoom>` (**case sensitively**), replace it with
`00000000-0000-0000-0000-000000000001`. If `Part2` is `<Void>`, replace it
with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
as an UUID and there is a room with that name, replace it with the room's
UUID.
If `Part2` is `00000000-0000-0000-0000-000000000001`, it operates on the
player's current room, otherwise if it **can be parsed** as a UUID, it finds a
room with that UUID, else it find a room with that name. If the specified room
doesn't exist, it doesn't do anything, otherwise it sets the room's image
without any checking (so it can be set to a non-existing image).
If the room is the current player's room, it tries to redisplay the room image:
* If `HideMainPicDisplay` and `UseInlineImages`: paste the room image into the
log with the usual bugs.
* If `HideMainPicDisplay`: no further actions.
* Else: it updates the main image (but not the room image), without handling
videos.
### CT_Status_ItemVisibleInvisible
* `Part2`: status bar item name (case insensitive)
* `Part3`: `Visible` / anything else (case insensitive)
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the status bar item specified by `Part2` doesn't exist, it doesn't do
anything. Otherwise it sets its `Visible` flag, to true if `Part3` is `Visible`,
to false otherwise.
### CT_EXECUTETIMER {#CT_EXECUTETIMER}
* `Part2`: timer name (case insensitive)
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the timer with the given name doesn't exist, or it doesn't have an action
with name `<<On Each Turn>>`, it doesn't do anything. Otherwise it executes the
action (without an `action_object`), repeating it as many times as it is reset.
### CT_RESETTIMER
* `Part2`: timer name (case insensitive)
* `Part3`: ignored
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the timer with the given name doesn't exist, it doesn't do anything.
Otherwise it sets the timer's `TurnNumber` to zero. If `Part2` is the currently
executing timer, it queues a reset (which will cause
[CT_EXECUTETIMER](#CT_EXECUTETIMER) to execute it again, or when normally
executing timers, it will skip subsequent On ... Turn actions), otherwise it
**clears the queued reset**. In any case it **CANCELS LOOP BREAK** and **ABORTS
THE FUCKING COMMAND LIST**.
### CT_TIMER_SET_CUSTOM_PROPERTY {#CT_TIMER_SET_CUSTOM_PROPERTY}
* `Part2`: `timer_name:property_name` (case insensitive)
* `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` (**case
sensitive**)
* `Part4`: value (**double replaced**)
* `Text`: ignored
* FIXUP: none
[See for more info on setting custom properties](#set-custom-property). It finds
a timer with name `timer_name` (case insensitively). If it doesn't exist, it
doesn't do anything.
### CT_TIMER_SET_CUSTOM_PROPERTY_JS
Same as [CT_TIMER_SET_CUSTOM_PROPERTY](#CT_TIMER_SET_CUSTOM_PROPERTY), but with
JavaScript.
### CT_SETTIMER
* `Part2`: timer name (case insensitive)
* `Part3`: `Active` / anything else (case insensitive)
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the timer with the given name doesn't exist, it doesn't do anything.
Otherwise it sets the timer's `Active` flag, to true if `Part3` is `Active`, to
false if it is anything else.
### CT_DISPLAYVARIABLE
* `Part2`: variable name + optional indices (case insensitive)
* `Part3`: `Display Date & Time` / `Display Date Only` / `Display Time Only` /
`Display Weekday Only` (**case sensitive**)
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the variable specified by `Part2` doesn't exist, it doesn't do anything. Here
is what happens, when the number of indices specified doesn't match the variable
type:
| Var type | 0 idx | 1 idx | 2 idx |
|------------|------------|--------------------------------|---------------|
| Single | single val | single val | single val |
| 1D | nothing | value | **exception** |
| Num/Str 2D | nothing | `System.Collections.ArrayList` | value |
| DT 2D | nothing | **exception** | value |
(Array indices are completely ignored with non-array variables.) Out-of-range
accesses generate exceptions except in "single val" and "nothing" cells.
In case of number or string variables, `Part3` is ignored, they're converted to
string normally and printed. With DateTime variables it specifies a date format:
* `Display Date & Time`: `dddd, MMMM, dd yyyy hh:mm:ss tt`, except in case of 2D
arrays, where it just prints whatever string representation the date is stored
in (so it doesn't throw if you only specify one index, it prints
`System.Collections.ArrayList`).
* `Display Date Only`: `dddd, MMMM, dd yyyy`
* `Display Time Only`: `hh:mm:ss tt`
* `Display Weekday Only`: `dddd`
If `Part3` is not one of the allowed values, it doesn't print anything.
### CT_SETVARIABLE {#CT_SETVARIABLE}
* `Part2`: variable name + optional indices (case insensitive)
* `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` / `Add Days` /
`Add Hours` / `Add Minutes` / `Add Seconds` / `Subtract Days` / `Subtract
Hours` / `Subtract Minutes` / `Subtract Seconds` / `Set Day Of Month To` /
`Set Hours To` / `Set Minutes To` / `Set Seconds To` (**case sensitive**)
* `Part4`: value (**double replaced**)
* `Text`: also value (**double replaced**)
* FIXUP: none
If `Part2` contains the string `Array:` (anywhere, **case sensitive**), the
first 6 characters of `Part2` is removed and it will be an array set, otherwise
a normal set. The rest is parsed for variable name and indices as usual, if the
variable doesn't exists, this action does nothing.
In case of a JavaScript set
([CT_VARIABLE_SET_JAVASCRIPT](#CT_VARIABLE_SET_JAVASCRIPT)), it runs `Part4`
through the JavaScript interpreter (after double replace), and converts the
result to string.
Here is what happens generally when the number of indices doesn't match, but
watch out for exceptions below.
| Var type | 0 idx | 1 idx | 2 idx |
|----------|------------|--------------------------------|----------------------|
| Single | set single | ignore/**exception** | ignore/**exception** |
| 1D | set single | set | **exception** |
| 2D | set single | **fuckup** | set |
* ignore/**exception**: this means that the code will try to overwrite the array
part of the variable, which depending on what leftover garbage is there, might
actually work, but you're more likely to get an exception.
* **fuckup**: in this case you'll end up with an array where a row is replaced
by a string/number/dt, and... you can expect things to break left and right if
you do this.
With number and datetime variables, array set is only working when `Part3` is
`Equals`, but for string variables `Part3` is ignored. An array set is
only working with JS set, without JS it just clears the variable (it will be 0
rows, indefinite columns, it leaves single value alone).
With a normal set, with a number variable, if `Part4` is not a number, it
**throws an exception**, otherwise it just sets the specified cell with the
error handling above. In case of `Add`, `Subtract`, `Multiply` and `Divide` only
normal sets are possible, `Array:` is ignored and **fuckup** changes into
**exception**. If `Part3` has any other value, the value is not updated.
If the variable has `EnforceRestrictions`, the set (or not set in case `Part3`
is invalid...) value is validated. If it is smaller than replaced and converted
to double min, it is set to min, and if after it is larger than max, it is set
to max (if the result of replacement is not a number, it **throws an
exception**). In case of `Equals` and **fuckup**, this will end up in an
exception, but only after fucking up the variable...
In case of string variables, `Part3` is ignored (only simple settings is
supported) and the value is read from `Text` instead of `Part4` (`Text` is also
evaluated with JS, but only with string variables). Array set and normal set is
supported.
DateTime works similarly to numbers, except: every possible value of `Part3` is
supported except `Add`, `Subtract`, `Multiply`, `Divide`, and there are no
min-max checks. `Equals` expects a date time, parsed by .NET's super permissive
parser, everything else an int32, if the conversion fails it **throws an
exception**.
### CT_ITEM_GETRANDOMGROUP {#CT_ITEM_GETRANDOMGROUP}
* `Part2`: variable name (without indices!) (case insensitive)
* `Part3`: group name (case insensitive)
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
If the variable specified by `Part2` doesn't exist, it doesn't do anything.
**This operates directly on SQL**, not the in-memory structures (that gets
serialized into savegames). If `Part3` doesn't name a valid object group, it is
treated as being empty. Otherwise it collects all objects recursively in the
object group.
It sets the variable's single string value (even if it is not a string variable)
to the name of a random object in this set, or to the empty string if the object
group is empty.
### CT_MM_GETRANDOMGROUP
* `Part2`: variable name (without indices!) (case insensitive)
* `Part3`: group name (case insensitive)
* `Part4`: ignored
* `Text`: ignored
* FIXUP: none
Exactly the same as [CT_ITEM_GETRANDOMGROUP](#CT_ITEM_GETRANDOMGROUP), except it
gets a random file name from a file group.
### CT_VARIABLE_SET_JAVASCRIPT {#CT_VARIABLE_SET_JAVASCRIPT}
See [CT_SETVARIABLE](#CT_SETVARIABLE) for details. I'm only going to document
how JavaScript arrays are turned into rags variable arrays:
If the returned value is not a JavaScript array, the result is an empty array.
Otherwise each item of the array is converted as follows:
* it is not an array and variable type is number: the value is converted to a
string, then to a double. If any of this fails, the value is `-1`.
* it is not an array and variable type is string: the value is converted to a
string. If this fails, the exception is quietly swallowed and it leaves the
array in a half-set state.
* it is not an array and variable type is DateTime: it is converted to a string,
then parsed with .net's super permissive parser. If this fails somehow, it is
set to `0001-01-01 00:00:00` unspecified timezone (can be treated as local).
* it is an array: if it is an empty array, it is skipped, otherwise each item of
this inner array is simply converted to a string. (2D arrays are always stored
as strings in rags.) (If it is not a 2D array, **fuckup**.)
### CT_SETVARIABLEBYINPUT {#CT_SETVARIABLEBYINPUT}
* `Part2`: input type: `Text` / `Characters` / `Characters And Objects` /
`Objects` / `Custom` / `Inventory` (**case sensitive**)
* `Part3`: variable name + optional indices (case insensitive)
* `Part4`: custom choice title (**double replaced**)
* `Text`: ignored
* `EnhancedData`: one of the few functions that use it
* FIXUP: none
If input type is not one of the allowed values, it is treated as being `None`
(which means an empty selection list. Interestingly, without ovelays, you can
still click on OK even with zero items, overlay forces a cancel button in this
case). In case of a custom choice, custom values are taken from `EnhancedData`,
after replacement, otherwise the same as with normal action input.
When using overlay input, the title is taken from `Part4`, or if it empty after
replace, it is `Please make a selection:`. Without overlay, it is always taken
from `Part4`, even if it is empty. Non overlay query is never cancelable, while
overlay input is cancelable if the list is empty or `EnhancedData.AllowCancel`.
Without overlay, it never uses tags, with overlay it's same as the normal
action. There's no post-processing of the selected tag. If the selection is
canceled, it **CANCELS LOOP BREAK** and **ABORTS THE FUCKING COMMAND LIST**.
If the variable does not exists, or if input type is `None`, it doesn't do
anything (but it only checks this after the user made a selection...). This
action expects the variable to be a string, it sets single and array string
values without checking, ending up in ignore/fuckup cases when done. With `Text`
input, the selection is simply stored. Otherwise, first the single value is set
to an empty string, and if the selection is not an empty string, the normal
value is set. Here is what happens when the number of indices doesn't match the
variable type:
| Var type | 0 idx | 1 idx | 2 idx |
|----------|------------|--------------------------------|----------------------|
| Single | set single | ignore/**exception** | ignore/**exception** |
| 1D | set single | set | **exception** |
| 2D | set single | **fuckup** | set |
### CT_SETVARIABLE_NUMERIC_BYINPUT
* `Part2`: input type: `Text` / `Custom` (**case sensitive**)
* `Part3`: variable name + optional indices (case insensitive)
* `Part4`: custom choice title (**double replaced**)
* `Text`: ignored
* `EnhancedData`: one of the few functions that use it
* FIXUP: none
If input type is not one of the allowed values, it is treated as being `None`.
Largely the same as [CT_SETVARIABLEBYINPUT](#CT_SETVARIABLEBYINPUT), except it
shows a `Sorry, you must enter a valid number.` message with `Text` input when
you enter an invalid input (custom choices are not checked, you get an
**exception** when the code actually tries to set the value.
It sets the variable's number fields (with a possibility to **fuckup**), except
that it also handles `EnforceRestrictions`.
### CT_VARIABLE_SET_CUSTOM_PROPERTY {#CT_VARIABLE_SET_CUSTOM_PROPERTY}
* `Part2`: `variable_name:property_name` (case insensitive)
* `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` (**case
sensitive**)
* `Part4`: value (**double replaced**)
* `Text`: ignored
* FIXUP: none
[See for more info on setting custom properties](#set-custom-property). It finds
a variable with name `variable_name` (case insensitively, without indices). If
it doesn't exist, it doesn't do anything.
### CT_VARIABLE_SET_CUSTOM_PROPERTY_JS
Same as [CT_VARIABLE_SET_CUSTOM_PROPERTY](#CT_VARIABLE_SET_CUSTOM_PROPERTY), but
with JavaScript.
### CT_VARIABLE_SET_RANDOMLY
* `Step2`: variable name + optional indices (case insensitive)
* `Step3`: ignored
* `Step4`: ignored
* `Text`: ignored
* FIXUP: none
If the variable doesn't exist, it doesn't do anything. It will always set number
fields, so using this on a non-number variable allows a **fuckup**. It generates
an integer random number between `[int32(double(min)), int32(double(max))+1)`
(string is first converted to double, then converted to int32 by rounding to
zero). Throws an error if the upper bound is smaller than the lower, returns min
when they're equal. Here is what happens when the number of indices doesn't
match the variable type:
| Var type | 0 idx | 1 idx | 2 idx |
|----------|------------|--------------------------------|----------------------|
| Single | set single | ignore/**exception** | ignore/**exception** |
| 1D | set single | set | **exception** |
| 2D | set single | **fuckup** | set |
### CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE {#CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE}
* `Step2`: variable name + optional indices (case insensitive)
* `Step3`: `character_name:property_name`
* `Step4`: ignored (**double replaced**)
* `Text`: ignored
* FIXUP: none
If the specified variable doesn't exist, it doesn't do anything. If `Step3`
doesn't exactly have one colon, or the character (case insensitively) doesn't
exist, or the custom property (**case sensitively**) doesn't exist, it is
treated as being an empty string. If the variable is a DateTime variable, it
doesn't do anything.
Here is what happens when the number of indices doesn't match the variable type:
| Var type | 0 idx | 1 idx | 2 idx |
|----------|------------|--------------------------------|----------------------|
| Single | set single | ignore/**exception** | ignore/**exception** |
| 1D | set single | set | **exception** |
| 2D | set single | **fuckup** | set |
In case of a number variable, if it is a "set single", the string value is
converted to a double, if it fails it prints some kind of debug log and does
nothing. In case of arrays, it just stores the string into the array (even in
case of an 1D array), for random **fuckup**s in the future.
### CT_VARIABLE_SET_WITH_ITEMPROPERTYVALUE
* `Step2`: variable name + optional indices (case insensitive)
* `Step3`: `object_name:property_name`
* `Step4`: ignored (**double replaced**)
* `Text`: ignored
* FIXUP: none
Copy-paste of
[CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE](#CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE),
but with objects instead of characters. Additionally, `object_name` can be
`<Self>` (**case sensitive**) to mean `action_object` if it is specified, it
uses empty string if it is not specified.
### CT_VARIABLE_SET_WITH_PLAYERPROPERTYVALUE
* `Step2`: variable name + optional indices (case insensitive)
* `Step3`: `property_name` (**case sensitive**)
* `Step4`: ignored (**double replaced**)
* `Text`: ignored
* FIXUP: none
Copy-paste of
[CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE](#CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE),
but with player instead of characters. `Step3` is treated as a property name,
even if it contains colons.
### CT_VARIABLE_SET_WITH_ROOMPROPERTYVALUE
* `Step2`: variable name + optional indices (case insensitive)
* `Step3`: `room_name:property_name`
* `Step4`: ignored (**double replaced**)
* `Text`: ignored
* FIXUP: none
Copy-paste of
[CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE](#CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE),
but with rooms instead of characters. Additionally, `room_name` can be
`<CurrentRoom>` (**case sensitively**) to mean the player's current room.
### CT_VARIABLE_SET_WITH_TIMERPROPERTYVALUE
* `Step2`: variable name + optional indices (case insensitive)
* `Step3`: `timer_name:property_name`
* `Step4`: ignored (**double replaced**)
* `Text`: ignored
* FIXUP: none
Copy-paste of
[CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE](#CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE),
but with timers instead of characters.
### CT_VARIABLE_SET_WITH_VARIABLE
* `Step2`: variable to set name + optional indices (case insensitive)
* `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` / `Add Days` /
`Add Hours` / `Add Minutes` / `Add Seconds` / `Subtract Days` / `Subtract
Hours` / `Subtract Minutes` / `Subtract Seconds` / `Set Day Of Month To` /
`Set Hours To` / `Set Minutes To` / `Set Seconds To` (**case sensitive**)
* `Step4`: variable to read name + optional indices (case insensitive)
* `Text`: ignored
If the variable specified by `Step4` doesn't exist, it **throws an exception**.
Here is what happens when the number of indices doesn't match the variable type:
| Var type | 0 idx | 1 idx | 2 idx |
|----------|------------|-----------------------|-----------------------|
| Single | single val | garbage/**exception** | garbage/**exception** |
| 1D | single val | OK | **exception** |
| 2D | single val | **exception** | OK |
The variable is also treated as a number variable, even if not: in case of
single values, this will read garbage, with 1D it **throws an exception**, and
with 2D it tries to parse the stored string as a number and **throws an
excepton** if it fails.
With two indices, if the value is not an integer, it **throws an exception**. If
the value can not be represented as an int32 (after tuncation), it also **throws
an exception**.
If the variable specified by `Step2` doesn't exist, or it is a string variable,
it doesn't do anything.
Here is what happens generally when the number of indices doesn't match with
`Step2`:
| Var type | 0 idx | 1 idx | 2 idx |
|----------|------------|--------------------------------|----------------------|
| Single | set single | ignore/**exception** | ignore/**exception** |
| 1D | set single | set | **exception** |
| 2D | set single | **fuckup** | set |
With number variables, `Step4` is treated as a double, except with 2D arrays and
`Equal`, or single values and `Divide`, where it is treated as an integer. If
`Part2` is not `Equals`, `Add`, `Subtract`, `Multiply` or `Divide`, it doesn't
update the variable. `EnforceRestrictions` are handled on number variables, even
if there was no update.
DateTime with `Equals` is a buggy piece of shit. With not 2D arrays, it just
**throws an exception**, with 2D arrays, it sets the the number converted to
string, which rags later won't be able to parse back (so it's a kind of
**fuckup**). The rest date-related operations actually work though, but they
operate on the truncated integers (so you can't add 1.5 days to a DT).
### CT_VARIABLE_SET_WITH_VARIABLEPROPERTYVALUE
* `Step2`: variable name + optional indices (case insensitive)
* `Step3`: `timer_name:property_name`
* `Step4`: ignored (**double replaced**)
* `Text`: ignored
* FIXUP: none
Copy-paste of
[CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE](#CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE),
but with variables instead of characters.
### CT_ENDGAME
* `Step2`: ignored
* `Step3`: ignored
* `Step4`: ignored
* `Text`: display text
* FIXUP: none
Prints `Text` to the log and ends the game. It also displays a dialog box
<code>The game has ended.&nbsp; Would you like to play again?</code> (with two
spaces between the sentences) and buttons `Restart`, `Load` and `No`. Unless
selecting `Restart`, the remaining commands will be run.
# Condition
It has two subtypes, loop and if.
## Loop
* It has a single check, `CT_Loop_*`
* `PassCommands` will be called for each iteration, setting `loop_item`
* `FailCommands` are ignored
* `CT_LOOP_BREAK` can be used to break out of the innermost loop. However, if
break is inside a Condition's `PassCommands` or `FailCommads`, and there are
subsequent Commands in the same list, some later commands can cancel the
break. See the individual command documentations.
## If
* Normally it has one or more checks, neither of them being `CT_Loop_*`
* Rags designer doesn't allow you to create a condition with zero checks, but if
you manage to create one it will be regarded as true.
* However you can create an if with loops inside, if you want a bugfest: first,
the loop body will be executed when the check is evaluated. Second, they don't
modify the variable used to store the return value, so their "value" will be
whatever before them produced (or true if they're the first in the list).
Third, since they're not loops, `PassCommands` or `FailCommands` will be
executed after them, but without `loop_item`. For example, having an if with
"loop exits and loop exits", PassCommands will be executed 25 times (unless it
breaks): 12 times with the exits of the first room, 12 times with the exits of
the second room, then one more time without `loop_item`.
* `PassCommands` or `FailCommands` are executed depending on the check
* Handling multiple checks: every check has a `CkType`, but it's ignored for the
first check. For the rest, they kinda also short-circuit:
* `Or` check following a true: result is true, no other checks are executed.
* `And` check following a false: executing the `And` check is skipped, but
subsequent checks are still executed
This (I think) corresponds to `And` having higher precedence, so a list of
`Ignored, A`, `And, B`, `Or, C`, `And, D` is parsed as `(A && B) || (C && D)`.
## Generic check info
`Step2`, `Step3` and `Step4` values are always run through the [text replacement
function][] before usage.
Note for X in Y checks: rags store the location of the objects/characters by
string, so it's possible for an object/character to be in a
room/object/character that doesn't exists. Unfortunately this information is
lost during import to scraps.
### CustomPropertyChecks {#CustomPropertyChecks}
* `Step2`: colon separated pair: `name:custom_property_name`
* `Step3`: `Equals` / `Not Equals` / `Contains` / `Greater Than` / `Greater Than
or Equals` / `Less Than` / `Less Than or Equals` (**case sensitive**)
* `Step4`: value to compare with. **Double replaced!**
Interpretation of `name` varies, but generally if `Step2` doesn't have exactly
one colon, or the named entity doesn't exists, or it doesn't have a custom
property named `custom_property_name` (**case insensitively**), the check
**doesn't return anything**. If `Step3` is not one of the allowed values, it
**returns true**.
If both the custom property's value and `Step4` can be converted to a double,
they're treated as numbers otherwise as strings, except `Contains` which is
always string based. (This means that for example `012` equals to `12` but
`012x` doesn't equal to `12x`.) String compare is **REVERSED** and case
insensitive (i.e. `1 < 2` but `1x > 2x`). Returns the result of the comparison.
## Available ConditionTypes
### CT_Uninitialized
Not exactly a valid check, Rags will treat it as any other invalid option:
**don't return anything**.
### CT_AdditionalDataCheck
* `Step2`: object/character name to compare with (case insensitive)
* `Step3`: ignored
* `Step4`: text value to compare with (case insensitive)
Checks the value selected at the beginning of the action. If `InputType` is
`Text`, it compares the entered string with `Step4` (`Step2` is ignored in this
case). Otherwise, `Step4` is ignored and:
1. If selection and `Step2` equals, return true.
2. If there is an object named selection, returns `object.uuid == Step2`.
3. If there is a character named selection, returns `character.ToString() ==
Step2`, where `ToString` is `Name` if `NameOverride` is not empty, else
`Name` if we're outside a game (?), else `NameOverride` with text
replacements (!!).
4. If there is an object uuid matching the selection, returns `object.name ==
Step2`.
5. Otherwise returns false.
### CT_Character_CustomPropertyCheck
* `Step2`: `character_name:custom_property_name` (case insensitive)
* `Step3`: comparison type
* `Step4`: value
[See for more info on CustomPropertyChecks](#CustomPropertyChecks).
`character_name` is a name of a character.
### CT_Character_Gender
* `Step2`: character's name (case insensitive)
* `Step3`: `Male` / `Female` / anything else (**case sensitive**)
* `Step4`: ignored
If the character doesn't exist, **doesn't return anything**. If `Step3` is
neither `Male` nor `Female`, it's treated as `Other`. Returns whether the
character's gender equals to `Step3`.
### CT_Character_In_Room
* `Step2`: character's name (case insensitive)
* `Step3`: room's uuid (**case sensitive**). Special values (not parsed, must
match exactly):
* `00000000-0000-0000-0000-000000000001` :: player's current room
* `00000000-0000-0000-0000-000000000002` :: "void" room
* `Step4`: ignored
If the character doesn't exists, **throws an exception**. Returns whether the
character is currently in the specified room (see special values above).
### CT_Character_In_RoomGroup {#CT_Character_In_RoomGroup}
* `Step2`: character's name (case insensitive)
* `Step3`: room group or `None` (**case sensitive**)
* `Step4`: ignored
If the character doesn't exists, **throws an exception**. If the character is
not in a room, returns false. Returns whether character's current room's group's
name equals to `Step3` (use `None` to check if the room is not in any group).
_Note_: in rags, room groups are not recursive, but they are in scraps. To be
consistent with `CT_Item_InGroup` and `CT_MultiMedia_InGroup`, scraps does a
recursive check here aswell.
### CT_Item_CustomPropertyCheck
* `Step2`: `object_name:custom_property_name` (case insensitive)
* `Step3`: comparison type
* `Step4`: value
[See for more info on CustomPropertyChecks](#CustomPropertyChecks). If
`object_name` is `<Self>` (**case sensitively**), it refers to the hidden object
parameter, otherwise it is an object's name (case insensitively).
### CT_Item_InGroup {#CT_Item_InGroup}
* `Step2`: name of the object (case insensitive)
* `Step3`: group name (**case sensitive**)
* `Step4`: ignored
**This is buggy in designer!** Rags designer puts the object's uuid into
`Step2`, but the player expects an object name. To fix it, you have to *type*
the object name into the `Choose Item` box, and not select from the drop-down
menu.
**This operates directly on SQL**, not the in-memory structures (that gets
serialized into savegames). If there is no such object in the SQL, it is treated
as having an empty group name. If the object's group name equals to `Step3`,
returns true. Otherwise it gets the children of the specified group and
recursively checks all of them (same as getting the parent of the object group,
and recursively checking the parent groups, except much more inefficient).
Returns false if not found.
### CT_Item_Held_By_Character
* `Step2`: character name (**case sensitive**)
* `Step3`: object UUID or name (case insensitive)
* `Step4`: ignored
* FIXUP: if `Step3` can't be parsed as an UUID, and there is an object with that
name, replace `Step3` with the object's UUID.
If the object doesn't exist, returns false. Otherwise returns whether the
object's location is directly a character with the specified name (not
subobject).
### CT_Item_Held_By_Player
* `Step2`: object UUID or name (case insensitive)
* `Step3`: ignored
* `Step4`: ignored
* FIXUP: if `Step2` can't be parsed as an UUID, and there is an object with that
name, replace `Step2` with the object's UUID.
If the object doesn't exist, returns false. Otherwise returns whether the
object's location is a player with the specified name **or it is recursively
inside an object held by the player**.
### CT_Item_In_Object
* `Step2`: object UUID or name (case insensitive)
* `Step3`: other object's UUID (**case sensitive**)
* `Step4`: ignored
* FIXUP: if `Step2` can't be parsed as an UUID, and there is an object with that
name, replace `Step2` with the object's UUID.
If the object specified by `Step2` doesn't exists, return false. Otherwise
returns whether `Step2` is directly inside `Step3`.
### CT_Item_In_Room
* `Step2`: object UUID or name (case insensitive)
* `Step3`: room UUID (**case sensitive**). Special values (not parsed, must
match exactly):
* `00000000-0000-0000-0000-000000000001` :: player's current room
* `Step4`: ignored
* FIXUP: if `Step2` can't be parsed as an UUID, and there is an object with that
name, replace `Step2` with the object's UUID. Same with `Step3` and room.
If the object doesn't exists, returns false. Otherwise returns whether the
object is currently in the specified room (see special values above) directly
(i.e. not subobjects).
### CT_Item_In_RoomGroup
* `Step2`: object UUID (case insensitive)
* `Step3`: room group or `None` (**case sensitive**)
* `Step4`: ignored
If the object doesn't exists or it is not in a room, returns false. Returns
whether the object's current room's group's name equals to `Step3`.
[See also CT_Character_In_RoomGroup](#CT_Character_In_RoomGroup).
### CT_Item_Not_Held_By_Player
* `Step2`: object UUID or name (case insensitive)
* `Step3`: ignored
* `Step4`: ignored
* FIXUP: if `Step2` can't be parsed as an UUID, and there is an object with that
name, replace `Step2` with the object's UUID.
If the object doesn't exists, return false. Otherwise returns whether the object
is not in the player's inventory **DIRECTLY**. Consistency, where the fuck are
you!?
### CT_Item_Not_In_Object
* `Step2`: object UUID or name (case insensitive)
* `Step3`: other object's UUID (**case sensitive**)
* `Step4`: ignored
If the object specified by `Step2` doesn't exists, return false. Otherwise
returns whether `Step2` is not directly inside `Step3`.
### CT_Item_State_Check
* `Step2`: object UUID or name (case insensitive)
* `Step3`: `Open` / `Closed` / `Locked` / `Unlocked` / `Worn` / `Removed` /
`Read` / `Unread` / `Visible` / `Invisible` (**case sensitive**)
* `Step4`: ignored
* FIXUP: if `Step2` can't be parsed as an UUID, and there is an object with that
name, replace `Step2` with the object's UUID.
If the object doesn't exists, it returns false. Otherwise, if `Step3` is not one
of the allowed values, it **doesn't return anything**. Otherwise, returns
whether the object is in the specified state. (Every second item in the list is
the negation of the previous one, so `Closed` is checking for not `Open`,
`Removed` is checking for not `Worn`, etc.)
### CT_Loop_While
Parameters are the same as [CT_Variable_Comparison](#CT_Variable_Comparison),
execute `PassCommands` until variable comparison is true. `loop_item` is passed
through unmodified.
### CT_Loop_Characters
* `Step2`: ignored
* `Step3`: ignored
* `Step4`: ignored
Iterate through each character in the game (skipping player, since player is not
a character in rags), setting `loop_item`.
### CT_Loop_Items
* `Step2`: ignored
* `Step3`: ignored
* `Step4`: ignored
Iterate through each object in the game, setting `loop_item`.
### CT_Loop_Item_Group
* `Step2`: object group name (**case sensitive**)
* `Step3`: ignored
* `Step4`: ignored
Iterate through each object in the game. If the object is in the specified
object group directly, call `PassCommands` setting `loop_item`.
### CT_Loop_Item_Container
* `Step2`: object UUID (**case sensitive**)
* `Step3`: ignored
* `Step4`: ignored
Iterate through each object in the game. If the object is in the specified
object, call `PassCommands` setting `loop_item`.
### CT_Loop_Item_Room
* `Step2`: room UUID (**case sensitive**)
* `Step3`: ignored
* `Step4`: ignored
Iterate through each object in the game. If the object is in the specified
room, call `PassCommands` setting `loop_item`.
### CT_Loop_Item_Inventory
* `Step2`: ignored
* `Step3`: ignored
* `Step4`: ignored
Iterate through each object in the game. If the object is in the player's
inventory, call `PassCommands` setting `loop_item`.
### CT_Loop_Item_Char_Inventory
* `Step2`: character name (**case sensitive**)
* `Step3`: ignored
* `Step4`: ignored
Iterate through each object in the game. If the object is in the specified
character's inventory, call `PassCommands` setting `loop_item`.
### CT_Loop_Rooms
* `Step2`: ignored
* `Step3`: ignored
* `Step4`: ignored
Iterate through each room in the game, setting `loop_item`.
### CT_Loop_Exits
* `Step2`: room UUID (**can be parsed**) or name (case insensitive)
* `Step3`: ignored
* `Step4`: ignored
If the specified room doesn't exists, doesn't do anything. Otherwise call
`PassCommands` settings `loop_item` with each exit of the room (including
disabled ones, i.e. the loop will be executed exactly 12 times, unless broken).
### CT_MultiMedia_InGroup
* `Step2`: name of the object (case insensitive)
* `Step3`: group name (**case sensitive**)
* `Step4`: ignored
Works similarly to [CT_Item_InGroup](#CT_Item_InGroup).
### CT_Player_CustomPropertyCheck
* `Step2`: `custom_property_name`
* `Step3`: comparison type
* `Step4`: value
[See for more info on CustomPropertyChecks](#CustomPropertyChecks). Since there
is only one player in rags, `Step2` only contains a custom property name. Colons
are treated as part of the property name!
### CT_Player_Gender
* `Step2`: `Male` / `Female` / anything else (**case sensitive**)
* `Step3`: ignored
* `Step4`: ignored
If `Step2` is neither `Male` nor `Female`, it's treated as `Other`. Returns
whether the player's gender equals to `Step2`.
### CT_Player_In_Room
* `Step2`: room's UUID (**case sensitive**). Special values (not parsed, must
match exactly):
* `00000000-0000-0000-0000-000000000002` :: "void" room (crashes rags if you
actually manage to put the player here, so probably can be ignored)
* `Step3`: ignored
* `Step4`: ignored
* FIXUP: if `Step2` can't be parsed as an UUID, and there is a room with that
name, replace `Step2` with the room's UUID.
Returns whether the player is currently in the specified room.
### CT_Player_In_RoomGroup
* `Step2`: room group or `None` (**case sensitive**)
* `Step3`: ignored
* `Step4`: ignored
(If the player is not in a room, throws an exception.) Returns whether player's
current room's group's name equals to `Step2`.
[See also CT_Character_In_RoomGroup](#CT_Character_In_RoomGroup).
### CT_Player_In_Same_Room_As
* `Step2`: character name (case insensitive)
* `Step3`: ignored
* `Step4`: ignored
If the character doesn't exists, **throws an exception**. Returns whether the
player and the specified character is in the same room.
### CT_Player_Moving
* `Step2`: `Empty` / `North` / `South` / ... (**case sensitive**)
* `Step3`: ignored
* `Step4`: ignored
If `Step2` is not a valid direction, **throws an exception**. Otherwise checks
whether the player currently moves in the specified direction (only in room's on
player leave events, otherwise the player is moving in the `Empty` direction).
### CT_Room_CustomPropertyCheck
* `Step2`: `room_uuid:custom_property_name` (case insensitive)
* `Step3`: comparison type
* `Step4`: value
[See for more info on CustomPropertyChecks](#CustomPropertyChecks). `room_uuid`
can be `<CurrentRoom>` (**case sensitively!**) to mean the player's room,
otherwise it is a room uuid.
### CT_Timer_CustomPropertyCheck
* `Step2`: `timer_name:custom_property_name` (case insensitive)
* `Step3`: comparison type
* `Step4`: value
[See for more info on CustomPropertyChecks](#CustomPropertyChecks). `timer_name`
is a name of a timer.
### CT_Variable_Comparison {#CT_Variable_Comparison}
* `Step2`: variable name + optional indices (case insensitive)
* `Step3`: `Equals` / `Not Equals` / `Greater Than` / `Greater Than or Equals` /
`Less Than` / `Less Than or Equals` / `Contains` / `DayOfWeek Is` / `Hour
Equals` / `Hour Is Greater Than` / `Hour Is Less Than` / `Minute Equals` /
`Minute Is Greater Than` / `Minute Is Less Than` / `Seconds Equals` / `Seconds
Is Greater Than` / `Seconds Is Less Than` (**case sensitive**)
* `Step4`: value to compare with. **Double replaced!**
Optional index handling: everything before the first `(` is the variable name,
without trimming. (No `(` -> full string is the variable name.) There is a `(`
and a `)` afterwards: everything between them is converted to int32, if fails
treat as no index. There is a second `(` and a `)` afterwards: do the same
conversion. Examples: `foo(0)(0)` -> name `"foo"`, indices 0 and 0. `foo((2)` ->
name `"foo "`, indices invalid and 2.
If the variable does not exists, **returns true**. Here is what happens when the
number of indices specified doesn't match the variable type (invalid first and
valid second indices: 2 idx, but later exception due to out of range):
| Var type | 0 idx | 1 idx | 2 idx |
|------------|---------|--------------------------------|-----------------------|
| Num single | OK | garbage/**exception** | garbage/**exception** |
| Num 1D | garbage | OK | **exception** |
| Num 2D | garbage | **exception** | OK |
| Str single | OK | garbage/**exception** | garbage/**exception** |
| Str 1D | garbage | OK | **exception** |
| Str 2D | garbage | `System.Collections.ArrayList` | OK |
| DT single | OK | garbage/**exception** | garbage/**exception** |
| DT 1D | garbage | OK | **exception** |
| DT 2D | garbage | **exception** | OK |
Explanations:
* Garbage in 0 idx column: rags doesn't store variable values in a union, and
selecting a different type in rags designer doesn't clear out the values
stored for other types. It also treats array and single as separate types.
Thus when not specifying any index, rags will use this single type's value
with whatever garbage value left behind. Scraps emulates this behavior.
* garbage/exception: similar to the previous. If the variable happen to have a
garbage array data, it will use it, otherwise exception. Scraps always throws
an exception in this case.
* `System.Collections.ArrayList`: rags will treat the variable as having this
value.
If the index is out of range, it **throws an exception**. If `Step2` has an
index, the value used to store single number values (refer to garbage in 0 idx
column above) is **overwritten** with the current array cell value. (Unlike
`CT_Variable_To_Variable_Comparison`, this is done for every type, not just
numbers).
If an unknown/invalid comparison type is specified, it **returns true**.
Different variable types support an arbitrary subset of the possible comparisons
(O=supported, X=not supported):
| Comparison | Num | Str | DT |
|---------------------------|-----|-----|----|
| `Equals` | O | O | O |
| `Not Equals` | O | O | O |
| `Greater Than` | O | O | O |
| `Greater Than or Equals` | O | X | O |
| `Less Than` | O | O | O |
| `Less Than or Equals` | O | X | O |
| `Contains` | X | O | X |
| `DayOfWeek Is` | X | X | O |
| `Hour Equals` | X | X | O |
| `Hour Is Greater Than` | X | X | O |
| `Hour Is Less Than` | X | X | O |
| `Minute Equals` | X | X | O |
| `Minute Is Greater Than` | X | X | O |
| `Minute Is Less Than` | X | X | O |
| `Seconds Equals` | X | X | O |
| `Seconds Is Greater Than` | X | X | O |
| `Seconds Is Less Than` | X | X | O |
In case of a number variable, `Step4` is converted to a double, if it fails it
**throws an exception**. In case of a string variable, formatting macros are
stripped from the variable's value ([see text.md for
details](text.md#strip-format)), but not from `Step4`. Comparison is case
insensitive. In case of a DateTime variable, normal comparisons are parsed with
.NET's super permissive parser, hour/minute/second checks are parsed as int32,
if it fails it **throws an exception**. DayOfWeek check is special, `Step4` is
one of `Sunday` / `Monday` / `Tuesday` / `Wednesday` / `Thursday` / `Friday` /
`Saturday` (checked case insensitively).
Finally it returns the result of the comparison.
### CT_Variable_To_Variable_Comparison
* `Step2`: variable name + optional indices (case insensitive)
* `Step3`: `Equals` / `Not Equals` / `Greater Than` / `Greater Than or Equals` /
`Less Than` / `Less Than or Equals` (**case sensitive**)
* `Step4`: variable name + optional indices (case insensitive)
If the variable specified by `Step4` doesn't exists, **throws an exception**. If
`Step4` is a DateTime variable, it is treated as having an empty string as
value. Here is what happens, when the number of indices specified in `Step4`
doesn't match the variable type:
| Var type | 0 idx | 1 idx | 2 idx |
|----------|---------|--------------------------------|-----------------------|
| Single | OK | garbage/**exception** | garbage/**exception** |
| 1D | garbage | OK | **exception** |
| 2D | garbage | `System.Collections.ArrayList` | OK |
See [the previous section for explanations](#CT_Variable_Comparison). Rags
converts number values to string, this is why you have
`System.Collections.ArrayList` even with numbers.
If the variable specified by `Step2` doesn't exists or it is a DateTime
variable, it **doesn't return anything**. Here is what happens when the indices
don't match, this time for `Step2`:
| Var type | 0 idx | 1 idx | 2 idx |
|------------|---------|--------------------------------|-----------------------|
| Num single | OK | garbage/**exception** | garbage/**exception** |
| Num 1D | garbage | OK | **exception** |
| Num 2D | garbage | **exception** | OK |
| Str single | OK | garbage/**exception** | garbage/**exception** |
| Str 1D | garbage | OK | **exception** |
| Str 2D | garbage | `System.Collections.ArrayList` | OK |
If `Step3` does not have an allowed value (string variables only support
`Equals` and `Not Equals`), it **doesn't return anything**.
In case of a number variable, the string representation of whatever that was
read from `Step4` is converted to a double, if this fails it is treated as
`0.0`. If `Step2` has an index, the value used to store single number values
(refer to garbage in 0 idx column above) is **overwritten** with the current
array cell value. (This does not happen with string vars!) Afterward, it returns
the value of the comparison.
In case of a string variable, formatting macros are stripped from `Step2`'s
value ([see text.md for details](text.md#strip-format)), but not from `Step4`,
then they're compared for equality. (Less than, greater than not supported).
### CT_Variable_CustomPropertyCheck
* `Step2`: `variable_name:custom_property_name` (case insensitive)
* `Step3`: comparison type
* `Step4`: value
[See for more info on CustomPropertyChecks](#CustomPropertyChecks).
`variable_name` is a name of a variable and can contain indices, but they're
ignored.
[text replacement function]: text.md#replacement