scraps

Abandon all hope, ye who enter here.
git clone https://git.neptards.moe/neptards/scraps.git
Log | Files | Refs | Submodules | README | LICENSE

actions.md (105979B)


      1 # XML syntax
      2 Sample:
      3 
      4 ```xml
      5 <Action>
      6     <Name><![CDATA[Examine]]></Name>
      7     <OverrideName><![CDATA[]]></OverrideName>
      8     <actionparent><![CDATA[None]]></actionparent>
      9     <Active><![CDATA[True]]></Active>
     10     <FailOnFirst><![CDATA[True]]></FailOnFirst>
     11     <InputType><![CDATA[None]]></InputType>
     12     <CustomChoiceTitle><![CDATA[]]></CustomChoiceTitle>
     13     <EnhancedInputData>
     14         <BackgroundColor><![CDATA[-1929379841]]></BackgroundColor>
     15         <TextColor><![CDATA[-16777216]]></TextColor>
     16         <Imagename><![CDATA[]]></Imagename>
     17         <UseEnhancedGraphics><![CDATA[True]]></UseEnhancedGraphics>
     18         <AllowCancel><![CDATA[True]]></AllowCancel>
     19         <NewImage><![CDATA[]]></NewImage>
     20         <TextFont><![CDATA[Microsoft Sans Serif, 12pt]]></TextFont>
     21     </EnhancedInputData>
     22     <PassCommands>
     23         <Command>
     24             <CmdType><![CDATA[CT_DISPLAYPICTURE]]></CmdType>
     25             <CommandText><![CDATA[]]></CommandText>
     26             <Part2><![CDATA[Toybox.jpg]]></Part2>
     27             <Part3><![CDATA[]]></Part3>
     28             <Part4><![CDATA[]]></Part4>
     29             <EnhancedInputData>
     30                 <BackgroundColor><![CDATA[-1929379841]]></BackgroundColor>
     31                 <TextColor><![CDATA[-16777216]]></TextColor>
     32                 <Imagename><![CDATA[]]></Imagename>
     33                 <UseEnhancedGraphics><![CDATA[True]]></UseEnhancedGraphics>
     34                 <AllowCancel><![CDATA[True]]></AllowCancel>
     35                 <NewImage><![CDATA[]]></NewImage>
     36                 <TextFont><![CDATA[Microsoft Sans Serif, 12pt]]></TextFont>
     37             </EnhancedInputData>
     38         </Command>
     39     </PassCommands>
     40 </Action>
     41 ```
     42 
     43 ## Action
     44 * `Name`: `string`
     45 * `OverrideName`: `string`, default `""`
     46 * `actionparent`: `string`, default `"None"`
     47 * `Active`: `bool` (`True` / `False`)
     48 * `FailOnFirst`: `bool`, default `True`
     49 * `InputType`: `ActionInputType` (`None`, `Object`, `Character`,
     50   `ObjectOrCharacter`, `Text`, `Custom`, `Inventory`)
     51 * `CustomChoiceTitle`: `string`, default `""`
     52 * `CustomChoices`: `CustomChoice[]`, only if array not empty
     53 * `EnhancedInputData`: `EnhancedInputData`
     54 * `Conditions`: `Condition[]`, only if array not empty
     55 * `PassCommands`: `variant<Command, Condition>[]`, only if array not empty
     56 * `FailCommands`: `variant<Command, Condition>[]`, only if array not empty
     57 
     58 ## CustomChoice
     59 * `Name`: `string`
     60 
     61 ## EnhancedInputData
     62 * `BackgroundColor`: `color`: `color.ToArgb() == 0xaarrggbb` reinterpret_casted
     63   to `int32_t`, default `rgba(255, 255, 255, 140)`
     64 * `TextColor`: `color`, default `rgba(0, 0, 0, 255)`
     65 * `Imagename`: `string`, default `""`. Ignored?
     66 * `UseEnhancedGraphics`: `bool`, default true. Called `Use Overlay Graphics` in
     67   GUI. When true, replace image + choices overlaid, when false, `NewImage`
     68   ignored and popup window with normal winforms table and ok/cancel button (even
     69   when `AllowCancel` is false).
     70 * `AllowCancel`: `bool`, default true
     71 * `NewImage`: `string`, default `""`
     72 * `TextFont`: `font`, default `Times New Roman Bold, 12pt`. Ignored/buggy?
     73 
     74 ## Condition
     75 * `Name`: `string`, default `""`
     76 * `Checks`: `Check[]`, only if array not empty
     77 * `PassCommands`: only if array not empty
     78 * `FailCommands`: only if array not empty
     79 
     80 ## Check
     81 * `CondType`: `ConditionType`, default `CT_Item_Held_By_Player`
     82 * `CkType`: `CheckType` (`CT_Uninitialized`, `And`, `Or`)
     83 * `Step2`: `string`, default `""`
     84 * `Step3`: `string`, default `""`
     85 * `Step4`: `string`, default `""`
     86 
     87 ## Command
     88 * `CmdType`: `CommandType`
     89 * `CommandText`: `string`, default `""`
     90 * `Part2`: `string`, default `""`
     91 * `Part3`: `string`, default `""`
     92 * `Part4`: `string`, default `""`
     93 * `CustomChoices`: `CustomChoice[]`, only if not empty.
     94 * `EnhancedInputData`: `EnhancedInputData`.
     95 
     96 # Action
     97 * `ActionParent`: used to generate submenus in right-click menu (yet another
     98   ad-hoc grouping). If parent is inactive, action is inactive too.
     99 * `FailOnFirst`: pseudocode, `c.Check()` runs PassCommands for each iteration
    100   when it is a loop (yes, when `FailOnFirst` is false, `PassCommands` is
    101   executed one more time)
    102   ```c++
    103   bool success = FailOnFirst || Conditions.empty();
    104   for (c : Conditions)
    105   {
    106     if (c.Check()) // check succeeded or it was a loop
    107     {
    108       if (!FailOnFirst) success = true;
    109       if (!FailOnFirst || !c.loop) c.PassCommands.Run();
    110     }
    111     else
    112     {
    113       c.FailCommands.Run();
    114       if (FailOnFirst) { success = false; break; }
    115     }
    116   }
    117   (success ? PassCommands : FailCommands).Run();
    118   ```
    119 * `PassCommands`, `FailCommands`: one of them is executed based on the
    120   conditions' result, see above for exact semantics, but generally, with
    121   `FailOnFirst` it means all checks passed (so `and` with short-circuit), and
    122   without `FailOnFirst` it means at least one check passed (so `or` without
    123   short-circuit).
    124 * `EnhancedData`: only used when `InputType != None && InputType != Text &&
    125   UseEnhancedGraphics && !HideMainPicDisplay`
    126 * `InputType`: ask a question at the beginning of the action if not None.
    127   * `Object`: select an object currently visible in the player GUI. In practice,
    128     this means any object in the inventory, any object in the current room
    129     (including portals), any subobject of any of them; or any object held by a
    130     character in the current room with allow inventory interactions. (Subobjects
    131     inside characters don't appear but subobjects inside subobjects do, albeit
    132     buggy! Just what did you expect?) List items: object DisplayNames, no tag
    133     when `!UseEnhancedGraphics`. `AdditionalData` value: object's name (override
    134     ignored).
    135   * `Character`: select a character in the current room. List object: character
    136     DisplayName with tag. `AdditionalData` value: character's name (override
    137     ignored).
    138   * `ObjectOrCharacter`: union of the previous two, except characters lack tag
    139     with `UseEnhancedGraphics`...
    140   * `Text`: input is not a selection from a list, but a free form text.
    141     `EnhancedData` is ignored in this case.
    142   * `Custom`: `CustomChoices` contains a list of choices. When not using overlay
    143     graphics, the color of the text can be changed with `[c r,g,b]...[/c]`
    144     (except the actual ranges are ignored, `foo[c 0,255,0]bar` sets the full
    145     text to green, in case of multiple `[c]` tags, the last one wins. When using
    146     overlay graphics, these tags are displayed as-is, without any processing.
    147     Did you expect any logic behind this?)
    148   * `Inventory`: objects in the player's inventory. No tag when
    149     `!UseEnhancedGraphics`.
    150   * Note that this doesn't nest properly. Executing an action inside an action
    151     means that after returning to the parent action, the selection is lost.
    152 * `action_object`: a hidden parameter. This is set to the object when executing
    153   an action on an object, null otherwise (this is nesting properly).
    154 * `player_moving`: another hidden parameter, only available in `<<On Player
    155   Leave First Time>>` and `<<On Player Leave>>` actions, when triggered by a the
    156   player (the 3DPD) trying to leave the current room. It is set to invalid in
    157   other cases.
    158 * Execution:
    159   1. Clear `AdditionalData`
    160   2. When there is an `EhancedData` with an image, `ShowMainImage` and
    161      `OverlayGraphics` is true, and `InputType` is not `None` or `Text`, set the
    162      image to that.
    163   3. When `InputType` is not `None`, ask the input. If canceled, return (action
    164      result is false). Otherwise get the `AdditionalData` value to set, then
    165      print it, unless it is an uuid and there is an object with that uuid,
    166      because in that case it prints that object's name.
    167   4. Execute conditions
    168   5. Execute pass/fail commands
    169 
    170 # Command
    171 * `CustomChoices`: can be set on `CT_SETVARIABLEBYINPUT`
    172 * `EnhancedData`: can be set on `CT_SETVARIABLEBYINPUT`
    173 * Exception handling: exceptions are caught when executing commands. Rags
    174   displays a message, then continues with the next command.
    175 
    176 ## Generic command info
    177 * `Part2`, `Part3`, `Part4` and `Text` values are always run through the [text
    178   replacement function][] before usage.
    179 * Object/room UUID or name: this generally means rags first tries to find a
    180   room/object with the given UUID, and if there's no one, it tries to find one
    181   with the name. In this case everything is processed as strings, so if you end
    182   up with an object whose UUID is in fact not a valid UUID, it will still find
    183   it. On the other hand, this requires exact match, i.e. 8-4-4-4-12 format
    184   without white spaces. However, there are a few cases when Rags actually tries
    185   to parse the given string as a UUID and somehow work differently depending on
    186   the outcome, but they're clearly marked as "room UUID (**can be parsed**) or
    187   name". In this case you can ue other formats, see [allowed GUID formats][guid]
    188   [(archive)][guid_archive] for details. (And undocumented details: except the
    189   last format, spaces are not allowed, but the string is trimmed before use, so
    190   `" {123..."` is valid, but `"{ 123..."` is not).
    191 * Aborts the command list: subsequent items in the currently executing
    192   `PassCommands`/`FailCommands` will not run, but it does not affect commands
    193   above. So for example,
    194   ```
    195   Action
    196   \-PassCommands
    197   . |-Condition
    198   . | |-PassCommands
    199   . | | |-Command cmd0
    200   . | | |-Command cmd1
    201   . | | \-Command cmd2
    202   . | \-FailCommands
    203   . | . \-Command cmd3
    204   . |-Command cmd4
    205   . \-Command cmd5
    206   ```
    207   If `cmd1` aborts, `cmd2` won't be executed (and neither `cmd3`), but instead
    208   it will continue with `cmd4` and `cmd5`. This means that a `CT_LOOP_BREAK` not
    209   directly in the loop's `PassCommands` can act weird.
    210 
    211 [guid]: https://docs.microsoft.com/en-us/dotnet/api/system.guid.-ctor?view=net-5.0#System_Guid__ctor_System_String_
    212 [guid_archive]: https://archive.md/wtYUh
    213 
    214 ### Set custom property commands {#set-custom-property}
    215 * `Part2`: `name:property_name`
    216 * `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` (**case
    217   sensitive**)
    218 * `Part4`: value (**double replaced**)
    219 * `Text`: ignored
    220 
    221 The exact interpretation of `Part2` varies, but generally if it doesn't have
    222 exactly one colon, or if the entity doesn't exists, it doesn't do anything. If
    223 the entity doesn't have a property with name `property_name` (**case
    224 sensitively**), it also doesn't do anything.
    225 
    226 `Part4` is double evaluated, then in case of `*_JS` commands, it is evaluated as
    227 a JavaScript code (whatever dialect Microsoft's JScript supports), unless it is
    228 an empty string anfter trimming. In this case, it is replaced with `-1`. If the
    229 return value is `null`, it is replaced with `"Null value returned from
    230 evaluate."` string, otherwise it is converted to a string, using whatever rules
    231 this [this shit][jscript] [(archive)][jscript_archive] uses.
    232 
    233 If `Part3` is not one of the allowed values, it doesn't do anything. If `Part3`
    234 is `Equals`, set the property's value to `Part4`. Otherwise it tries to
    235 interpret both the property's current value and `Part4` as a double and do the
    236 arithmetic operation. If one of the values are not a number, it doesn't do
    237 anything, except that with `Subtract` if `Part4` is not a double, it **throws an
    238 exception**.
    239 
    240 [jscript]: https://docs.microsoft.com/en-us/dotnet/api/microsoft.jscript.eval.jscriptevaluate?view=netframework-4.8
    241 [jscript_archive]: https://archive.md/YhYP6
    242 
    243 ### Room enter procedure {#room-enter-procedure}
    244 This is used by multiple commands. Action and timer executions are skipped in
    245 some cases.
    246 
    247 * Prints an empty line to the log.
    248 * Sets room & main image (inline or to the main image area, handling layered
    249   images) to the room's image (removes it if the room doesn't have one).
    250 * When not skipping actions: when the room's `EnterFirstTime` is false, set it
    251   to true and execute inner procedure with `<<On Player Enter First Time>>`.
    252   Afterwards, execute inner procedure with `<<On Player Enter>>`.
    253 * If the actions changed the player's room, don't do anything else.
    254 * Prints the player's room description.
    255 * If notifications are enabled:
    256   * If the room has visible objects, it prints `"You can see "`, for each
    257     visible object preposition + `" "` + display name (if preposition after
    258     trimming is empty, preposition + space is skipped), separated by `", "`, all
    259     on one line.
    260   * For each character in the room, it prints display name + `" is here."`
    261 * When not skipping timers: execute [multi timer
    262   procedure](general.md#timer-multi).
    263 
    264 #### Inner procedure
    265 * There's a try-catch around the whole stuff, silently discarding any error
    266   inside (but action execution already contains a try-catch inside, so nothing
    267   should throw in this shit).
    268 * First save the player's current room (original room)
    269 * If the player's room has the specified action, execute it without
    270   `action_object`.
    271 * If the player has the specified action, execute it without `action_object`.
    272 * Go through all objects in the player's current room.
    273   * If the object's `EnterFirstTime` flag is false, set it to true and execute
    274     its `<<On Player Enter First Time>>` action if it exists with object as
    275     `action_object`.
    276   * When the inner procedure is called with `<<On Player Enter>>` and the object
    277     has an action with that name, and the player is still in the original room,
    278     execute it with object as `action_object`.
    279   * If the object is a `Container` and it is `Open` or not `Openable`, go
    280     through all subobjects and do the previous two points for these subobjects.
    281     Note that this operation is not recursive, so subobjects of the subobjects
    282     are not considered.
    283 * Go through all characters in the player's current room.
    284   * If the character's `EnterFirstTime` flag is false, set it to true and
    285     execute its `<<On Player Enter First Time>>` action if it exists without
    286     `action_object`.
    287   * When the inner procedure is called with `<<On Player Enter>>` and the
    288     character has an action with that name, and the player is still in the
    289     original room, execute it without `action_object`.
    290 
    291 This means that for example if the player in entering the room for the first
    292 time, object/character enter first time events are executed before the room's
    293 ``<<On Player Enter>>`, but after if the player is entering a second time...
    294 
    295 ## Available CommandTypes
    296 ### CT_UNINITIALIZED
    297 ### CT_ACTION_ADD_CUSTOMCHOICE {#CT_ACTION_ADD_CUSTOMCHOICE}
    298 * `Part2`: `type:name:action_name`
    299 * `Part3`: ignored
    300 * `Part4`: ignored
    301 * `Text`: choice to add (**case sensitive**)
    302 * FIXUP: none
    303 
    304 If `Part2` doesn't contain a colon, this action doesn't do anything. If it only
    305 contains one colon, it is parsed as `type:action_name` and name is treated as
    306 empty. A third colon and everything after it is ignored.
    307 
    308 `type` is **case sensitively** one of the following:
    309 * `Chr`: `name` is a character name (case insensitive). If the character does
    310   not exists, it **throws an exception**.
    311 * `Obj`: `name` is an object UUID or name (case insensitive). If the object does
    312   not exist, it doesn't do anything.
    313 * `Player`: `name` is ignored.
    314 * `Room`: `name` is a room UUID (**can be parsed**) or name (case insensitive).
    315   If the room doesn't exist, it doesn't do anything.
    316 * `Timer`: `name` is a timer (case insensitive). If the timer doesn't exist, it
    317   doesn't do anything.
    318 * Anything else: it doesn't do anything.
    319 
    320 `action_name` names an action inside the specified entity (case insensitive). If
    321 it doesn't exists, this action doesn't do anything. If the action already has a
    322 custom choice named `Text`, it doesn't do anything. Otherwise it adds `Text` to
    323 the end of the custom choices.
    324 
    325 ### CT_ACTION_CLEAR_CUSTOMCHOICE
    326 * `Part2`: `type:name:action_name`
    327 * `Part3`: ignored
    328 * `Part4`: ignored
    329 * `Text`: ignored
    330 * FIXUP: none
    331 
    332 See [CT_ACTION_ADD_CUSTOMCHOICE](#CT_ACTION_ADD_CUSTOMCHOICE) for `Part2`
    333 handling. If it specifies a valid action, this action removes all custom choices
    334 from it.
    335 
    336 ### CT_ACTION_REMOVE_CUSTOMCHOICE
    337 Same as [CT_ACTION_ADD_CUSTOMCHOICE](#CT_ACTION_ADD_CUSTOMCHOICE), except
    338 this action removes `Text` from the custom choices, if the custom choice exists.
    339 
    340 ### CT_DISPLAYCHARDESC
    341 * `Part2`: character name (case insensitive)
    342 * `Part3`: ignored
    343 * `Part4`: ignored
    344 * `Text`: ignored
    345 * FIXUP: none
    346 
    347 If the specified character does not exist, it **throws an exception**.
    348 Otherwise, it prints the characters description to the log.
    349 
    350 Additionally, if notifications are not turned off, and the character has any
    351 `Visible` and `Worn` object in it's inventory, it prints `<character display
    352 name> is wearing:`, then the display names of all worn objects in separate
    353 lines. If the character has any `Visible` but not `Worn` objects, it prints
    354 `<character display name> is carrying:`, then the display names of all not worn
    355 objects, separated by newlines.
    356 
    357 ### CT_CHAR_DISPLAYPORT
    358 * `Part2`: character name (case insensitive)
    359 * `Part3`: ignored
    360 * `Part4`: ignored
    361 * `Text`: ignored
    362 * FIXUP: none
    363 
    364 If the specified character does not exist, it doesn't do anything. Otherwise
    365 what it does depends on the value of `HideMainPicDisplay` and `UseInlineImages`:
    366 * `HideMainPicDisplay` and `UseInlineImages`: if the character doesn't have a
    367   portrait, **throw an exception**. If the portrait is an image, paste it into
    368   the log (without scaling and buggily, so sometimes it will just insert an
    369   empty line...). If it is an audio/video, print the full path of the temporary
    370   file to the log (where rags extracted the media file)...
    371 * `HideMainPicDisplay` and not `UseInlineImages`: it doesn't do anything.
    372 * Else: if the character doesn't have a portrait, or it is not an image, it
    373   doesn't do anything. Otherwise, it sets the main picture and its overlay(!)
    374   (right to the log) to character's portrait, including layered images. (Since
    375   file overlays are over the main overlay, this will work, but if the base image
    376   has transparency, it will be weird because of the double draw.) (I think rags
    377   author wanted to support videos here based on the code, he just fucked it up.)
    378 
    379 ### CT_MOVECHAR
    380 * `Part2`: character name (case insensitive)
    381 * `Part3`: room UUID (**can be parsed**) or name (case insensitive). Special
    382   values (not parsed, must match exactly):
    383   * `00000000-0000-0000-0000-000000000001`: player's current room
    384   * `00000000-0000-0000-0000-000000000002`: "void" room
    385 * `Part4`: ignored
    386 * `Text`: ignored
    387 * FIXUP: if `Part3` is `<CurrentRoom>` (**case sensitively**), replace it with
    388   `00000000-0000-0000-0000-000000000001`. If `Part3` is `<Void>`, replace it
    389   with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
    390   as an UUID and there is a room with that name, replace it with the room's
    391   UUID.
    392 
    393 If the specified character does not exist, **throws an exception**. If the
    394 character is in the same room as the player, and it has an action named `<<On
    395 Character Leave>>`, execute that (without a `loop_item`).
    396 
    397 If `Part3` is `00000000-0000-0000-0000-000000000001`, move the character to the
    398 same room as the character, and if it has an action named `<<On Character
    399 Enter>>`, execute it. If `Part3` is `00000000-0000-0000-0000-000000000002`, move
    400 the character nowhere.
    401 
    402 If `Part3` is not one of those special values, check if it **can be parsed** as
    403 a UUID. If yes, use that as-is, otherwise look up the room with that name. If
    404 there is no room with that name, **DISPLAY A FUCKING MESSAGE BOX** (<code>"Error
    405 in command CT_MoveChar.&nbsp; Could not locate a room called "</code> + `Part3`)
    406 and returns. (But UUIDs are not checked for validity, so it's possible to move
    407 someone to a not existing room.) Otherwise, move the character to the specified
    408 room. If the room is the player's current room, and the character has an `<<On
    409 Character Enter>>` action, execute it.
    410 
    411 ### CT_MOVECHARINVTOPLAYER
    412 * `Part2`: character name (case insensitive)
    413 * `Part3`: ignored
    414 * `Part4`: ignored
    415 * `Text`: ignored
    416 * FIXUP: none
    417 
    418 If the specified character does not exist, it doesn't do anything. Otherwise
    419 take all `Carryable` and `Visible` objects from the character's inventory and
    420 move them to the player's inventory.
    421 
    422 ### CT_CHAR_MOVETOOBJ
    423 * `Part2`: character name (case insensitive)
    424 * `Part3`: object UUID (case insensitive)
    425 * `Part4`: ignored
    426 * `Text`: ignored
    427 * FIXUP: none
    428 
    429 If the specified character or object doesn't exist, it doesn't do anything. If
    430 the object is in a room, move the character to that room. If the object's
    431 location's UUID (after move) is the same as the player's room UUID (this can be
    432 actually true when the object is in a character, but the character's UUID is the
    433 same as the player's room's UUID...), [execute room enter
    434 procedure](#room-enter-procedure) without actions.
    435 
    436 ### CT_CHAR_SETPORT
    437 * `Part2`: character name (case insensitive)
    438 * `Part3`: file name
    439 * `Part4`: ignored
    440 * `Text`: ignored
    441 * FIXUP: none
    442 
    443 If the specified character doesn't exist, it doesn't do anything. Otherwise set
    444 the character's image to the specified file name, without error checking (so it
    445 is possible to set it to a non-existing file).
    446 
    447 ### CT_SETCHARACTION
    448 * `Part2`: character name (case insensitive)
    449 * `Part3`: `action_name-state`
    450 * `Part4`: ignored
    451 * `Text`: ignored
    452 * FIXUP: none
    453 
    454 If the specified character doesn't exist, it doesn't do anything. If `Part3`
    455 doesn't contain a hyphen, it **throws an exception**. Otherwise everything until
    456 the last hyphen is the `action_name`. If the character doesn't have an action
    457 with that name (case insensitively), it doesn't do anything. Otherwise it sets
    458 its active flag, to true if `state` is `Active` (**case sensitively**), to false
    459 otherwise.
    460 
    461 ### CT_CHAR_SET_CUSTOM_PROPERTY {#CT_CHAR_SET_CUSTOM_PROPERTY}
    462 * `Part2`: `character_name:property_name`
    463 * `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` (**case
    464   sensitive**)
    465 * `Part4`: value (**double replaced**)
    466 * `Text`: ignored
    467 * FIXUP: none
    468 
    469 [See for more info on setting custom properties](#set-custom-property). If a
    470 character named `character_name` doesn't exists (case insensitively) it doesn't
    471 do anything.
    472 
    473 ### CT_CHAR_SET_CUSTOM_PROPERTY_JS
    474 Same as [CT_CHAR_SET_CUSTOM_PROPERTY](#CT_CHAR_SET_CUSTOM_PROPERTY), but with
    475 JavaScript.
    476 
    477 ### CT_SETCHARDESC
    478 * `Part2`: character name (case insensitive)
    479 * `Part3`: ignored
    480 * `Part4`: ignored
    481 * `Text`: new character description
    482 * FIXUP: none
    483 
    484 If the specified character does not exist, **throws an exception**. Otherwise
    485 sets the character's description to `Text`.
    486 
    487 ### CT_CHAR_SET_GENDER
    488 * `Part2`: character name (case insensitive)
    489 * `Part3`: `Male` / `Female` / `Other` (**case sensitive**)
    490 * `Part4`: ignored
    491 * `Text`: ignored
    492 * FIXUP: none
    493 
    494 If the specified character doesn't exist, it doesn't do anything. If `Part3`
    495 after trimming is not one of the allowed values, it **throws an exception**.
    496 Otherwise it sets the character's gender.
    497 
    498 ### CT_CHAR_SET_NAME
    499 * `Part2`: character name (case insensitive)
    500 * `Part3`: ignored
    501 * `Part4`: new character name override
    502 * `Text`: ignored
    503 * FIXUP: none
    504 
    505 If the specified character doesn't exist, it doesn't do anything. Otherwise it
    506 sets the character's name override.
    507 
    508 ### CT_COMMENT
    509 * `Part2`: ignored
    510 * `Part3`: ignored
    511 * `Part4`: ignored
    512 * `Text`: ignored
    513 * FIXUP: none
    514 
    515 This doesn't do anything.
    516 
    517 ### CT_DEBUGTEXT
    518 * `Part2`: ignored
    519 * `Part3`: ignored
    520 * `Part4`: ignored
    521 * `Text`: debug text
    522 * FIXUP: none
    523 
    524 If the second command line argument is `DEBUG` (**case sensitively**), it prints
    525 `Text` to the log.
    526 
    527 ### CT_JAVA_SET_RAGS
    528 * `Part2`: ignored
    529 * `Part3`: ignored
    530 * `Part4`: ignored
    531 * `Text`: JavaScript code (**double replaced**)
    532 * FIXUP: none
    533 
    534 If the JavaScript code does not return an array, it does nothing. Otherwise it
    535 iterates through the returned array:
    536 * non array elements are skipped
    537 * if the element array doesn't have at least 2 items, it **throws an exception**
    538 * otherwise the element is `[key, value]`
    539   * convert them to strings
    540   * if `value` is an empty string, replace it with a space (i.e. `" "`)
    541   * call [text replacement function][] in set mode with `"[" + key + "]`
    542 
    543 ### CT_DISPLAYTEXT
    544 * `Part2`: ignored
    545 * `Part3`: ignored
    546 * `Part4`: ignored
    547 * `Text`: text
    548 * FIXUP: none
    549 
    550 Prints `Text` to the log.
    551 
    552 ### CT_EXPORTVARIABLE
    553 * `Part2`: variable name (case insensitive)
    554 * `Part3`: ignored
    555 * `Part4`: ignored
    556 * `Text`: ignored
    557 * FIXUP: none
    558 
    559 If the specified variable doesn't exist, it doesn't do anything. Otherwise it
    560 pops up a save file dialog, and if the user doesn't cancel it, it serializes the
    561 full variable state to a file. If the save fails, it **displays a message box**.
    562 
    563 ### CT_IMPORTVARIABLE
    564 * `Part2`: ignored
    565 * `Part3`: ignored
    566 * `Part4`: ignored
    567 * `Text`: ignored
    568 * FIXUP: none
    569 
    570 Pops up a file load dialog. If the user cancels it, it doesn't do anything. If
    571 the load fails, it **displays a message box**. If there is already a variable
    572 with the given name, it is removed, then the imported variable is added.
    573 
    574 _Note_: this means it is possible to create a new variable by importing an
    575 unrelated game's exported variable...
    576 
    577 ### CT_PAUSEGAME
    578 * `Part2`: ignored
    579 * `Part3`: ignored
    580 * `Part4`: ignored
    581 * `Text`: ignored
    582 * FIXUP: none
    583 
    584 Prints `--------------------------------` (32 hyphens), then pauses the game.
    585 
    586 ### CT_DISPLAYLAYEREDPICTURE
    587 * `Part2`: file name (case insensitive)
    588 * `Part3`: ignored
    589 * `Part4`: ignored
    590 * `Text`: ignored
    591 * FIXUP: none
    592 
    593 When `HideMainPicDisplay`, it doesn't do anything. Otherwise, it sets the
    594 temporary overlay image, it sets it to `Part2` if it exist, removes the overlay
    595 otherwise.
    596 
    597 Temporary overlay is something that is drawn on top of the base image, but below
    598 other overlay images and removed when a different image is displayed. Doesn't
    599 work with videos.
    600 
    601 _Note_: base image is set as `BackgroundImage` on a `PictureBox`, this action
    602 sets the `Image` of the same `PictureBox` temporarily, Other overlays are drawn
    603 from a paint event handler on top of this. This also has a weird side effect
    604 that gif animations only work when set with this action, and only if the game is
    605 not paused in an action.
    606 
    607 ### CT_DISPLAYPICTURE
    608 * `Part2`: file name (case insensitive)
    609 * `Part3`: ignored
    610 * `Part4`: ignored
    611 * `Text`: ignored
    612 * FIXUP: none
    613 
    614 What it does depends on the value of `HideMainPicDisplay` and `UseInlineImages`:
    615 * `HideMainPicDisplay` and `UseInlineImages`: if the file doesn't exist, **throw
    616   an exception**. If it is an image, paste it into the log (without scaling and
    617   buggily, so sometimes it will just insert an empty line...). If it is an
    618   audio/video, print the full path of the temporary file to the log (where rags
    619   extracted the media file)...
    620 * `HideMainPicDisplay` and not `UseInlineImages`: it doesn't do anything.
    621 * Else: If the file is an image, it sets the main picture (right to the log),
    622   including layered images. If it is something else, it switches to a windows
    623   media player control and tries to open it. If the file doesn't exist, it still
    624   switches to WMP, but doesn't load anything into it.
    625 
    626 ### CT_MM_SET_BACKGROUND_MUSIC {#CT_MM_SET_BACKGROUND_MUSIC}
    627 * `Part2`: file name (case insensitive)
    628 * `Part3`: ignored
    629 * `Part4`: ignored
    630 * `Text`: ignored
    631 * FIXUP: none
    632 
    633 If the file exists and it is an image, it doesn't do anything. Otherwise it sets
    634 the background music volume to zero by decreasing it by 1% every 15ms (so it
    635 will take 1.5s, and it blocks the execution while doing this (it looks like it
    636 blocks the whole fucking GUI thread, but I'm not 100% sure on this). Then it
    637 sets the WMP control to the file if it exists, it unloads whatever was loaded if
    638 it doesn't exist.
    639 
    640 ### CT_MM_STOP_BACKGROUND_MUSIC
    641 * `Part2`: ignored
    642 * `Part3`: ignored
    643 * `Part4`: ignored
    644 * `Text`: ignored
    645 * FIXUP: none
    646 
    647 It sets the background music volume to zero in the same way as in
    648 [CT_MM_SET_BACKGROUND_MUSIC](#CT_MM_SET_BACKGROUND_MUSIC), then it stops the
    649 playback. This doesn't unload anything.
    650 
    651 ### CT_MM_PLAY_SOUNDEFFECT
    652 * `Part2`: file name (case insensitive)
    653 * `Part3`: ignored
    654 * `Part4`: ignored
    655 * `Text`: ignored
    656 * FIXUP: none
    657 
    658 If the file exists and it is an image, it doesn't do anything. Otherwise it sets
    659 the background sound effect to `Part2` if it exists, it unloads it otherwise.
    660 There is no fiddling with the volume like with background music and you can't
    661 play more than one sound effect at the same time.
    662 
    663 ### CT_MM_SET_MAIN_COMPASS {#CT_MM_SET_MAIN_COMPASS}
    664 * `Part2`: file name or `<Default>`
    665 * `Part3`: ignored
    666 * `Part4`: ignored
    667 * `Text`: ignored
    668 * FIXUP: none
    669 
    670 If `Part2` is `<Default>` (**case sensitively**), it sets the compass image back
    671 to rags' built-in default. Otherwise, it is a (case insensitive) filename. If
    672 the file doesn't exists, or if it is not an image, it doesn't do anything.
    673 Otherwise it sets the compass image (anim gifs and layered images not
    674 supported).
    675 
    676 ### CT_MM_SET_UD_COMPASS
    677 Same as [CT_MM_SET_MAIN_COMPASS](#CT_MM_SET_MAIN_COMPASS), except it sets the
    678 up-down compass image on success.
    679 
    680 ### CT_LAYEREDIMAGE_ADD
    681 * `Part2`: base image file name (case insensitive)
    682 * `Part3`: layer file name
    683 * `Part4`: ignored
    684 * `Text`: ignored
    685 * FIXUP: none
    686 
    687 If the base image specified by `Part2` doesn't exist (or it's not an image), it
    688 doesn't do anything. Otherwise it adds `Part3` to the end of the layered images
    689 list (so it will be on the top) (even if it doesn't exist, it will simply be
    690 ignored when drawing).
    691 
    692 ### CT_LAYEREDIMAGE_CLEAR
    693 * `Part2`: base image file name (case insensitive)
    694 * `Part3`: ignored
    695 * `Part4`: ignored
    696 * `Text`: ignored
    697 * FIXUP: none
    698 
    699 If the base image specified by `Part2` doesn't exist (or it's not an image), it
    700 doesn't do anything. Otherwise it clears all layered images.
    701 
    702 ### CT_LAYEREDIMAGE_REMOVE
    703 * `Part2`: base image file name (case insensitive)
    704 * `Part3`: layer file name (**case sensitive**)
    705 * `Part4`: ignored
    706 * `Text`: ignored
    707 * FIXUP: none
    708 
    709 If the base image specified by `Part2` doesn't exist (or it's not an image), it
    710 doesn't do anything. Otherwise, remove the first (i.e. most bottom) occurrence of
    711 `Part3` from `Part2`'s layered images.
    712 
    713 ### CT_LAYEREDIMAGE_REPLACE
    714 * `Part2`: base image file name (case insensitive)
    715 * `Part3`: layer to replace file name (**case sensitive**)
    716 * `Part4`: layer to add file name (case insensitive)
    717 * `Text`: ignored
    718 * FIXUP: none
    719 
    720 If the image specified by `Part2` or `Part4` doesn't exist (or not an image), it
    721 doesn't do anything. Otherwise it removes the first occurrence of `Part3` from
    722 `Part2`'s layers, and if found, add `Part4` to the end of the layers.
    723 
    724 ### CT_DISPLAYITEMDESC
    725 * `Part2`: object UUID or name (case insensitive). Special values:
    726   * `00000000-0000-0000-0000-000000000004` :: use `action_object`
    727 * `Part3`: ignored
    728 * `Part4`: ignored
    729 * `Text`: ignored
    730 * FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
    731   `00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
    732   UUID, and there is an object with that name, replace `Part2` with the object's
    733   UUID.
    734 
    735 When `Part2` is `00000000-0000-0000-0000-000000000004`: if there is a
    736 `action_object`, it displays that object's description, otherwise it does
    737 nothing.
    738 
    739 When `Part2` is not a special value, try to get an object with than UUID,
    740 failing that name. If it exists, it displays the description, otherwise it does
    741 nothing.
    742 
    743 Display description: if notifications are turned off, it just prints the
    744 object's description. Otherwise, it prints the object's description, then:
    745 * `".  It is open."` (double space!) if it is `Openable` and `Open`.
    746 * `".  It is closed."`  (double space!) if it is `Openable` and not `Open`.
    747 * `".  It contains:"` (double space!) if it is a `Container`, and (not
    748   `Openable` or `Open`), and it is not a portal. Then it prints every visible
    749   subobject's preposition + space + name on a new line, where every line except
    750   the first is prefixed with `"and "`. If there are no visible subobjects, it
    751   prints `"Nothing"`.
    752 
    753 ### CT_ITEM_LAYERED_REMOVE {#CT_ITEM_LAYERED_REMOVE}
    754 * `Part2`: object UUID or name (case insensitive)
    755 * `Part3`: ignored
    756 * `Part4`: ignored
    757 * `Text`: ignored
    758 * FIXUP: none
    759 
    760 If the specified object doesn't exist, it doesn't do anything. Otherwise it
    761 iterates over all other `Worn` objects in the same location (actually it's
    762 buggy: if the object is not in a character/player, it iterates over all objects
    763 in the same location *type*). If the two objects share a clothing zone, and if
    764 `Part2's level <= other's level`, it fails.
    765 
    766 The action collects all failures, and print a message to the log and returns. If
    767 the object is in a character's inventory, the message prefix is: character name
    768 (override ignored!) + `" cannot remove "` + `Part2`'s display name + `". "` +
    769 character name + `" will need to remove "`, otherwise the message prefix is:
    770 `"You cannot remove "` + `Part2`'s display name + `". You need to remove "`. The
    771 message continues with the display names of all conflicting objects, separated
    772 by `" and "`, and the message suffix is `" first."`.
    773 
    774 If there are no failures, it prints a confirmation message. If the object is in
    775 a character, it prints: character name (override ignored) + `" takes off "` +
    776 `Part2`'s display name + `"."`, otherwise it prints `"You take off "` +
    777 `Part2`'s display name + `"."`. Finally, it set's `Part2`'s `Worn` to false.
    778 
    779 Display name in this case: if preposition not empty, prefix is `preposition` +
    780 `" "`. If name override after trim is not empty, name override without trim,
    781 otherwise name.
    782 
    783 ### CT_ITEM_LAYERED_WEAR
    784 Same as [CT_ITEM_LAYERED_REMOVE](#CT_ITEM_LAYERED_REMOVE), except it sets `Worn`
    785 to true and the messages are different. `You/character_name cannot wear` on fail
    786 and `You put on/character_name puts on` on failure.
    787 
    788 ### CT_MOVEITEMTOCHAR
    789 * `Part2`: object UUID or name (case insensitive). Special values:
    790   * `00000000-0000-0000-0000-000000000004` :: use `action_object`
    791 * `Part3`: character name
    792 * `Part4`: ignored
    793 * `Text`: ignored
    794 * FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
    795   `00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
    796   UUID, and there is an object with that name, replace `Part2` with the object's
    797   UUID.
    798 
    799 It the specified object doesn't exists, or in case of
    800 `00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
    801 nothing. Otherwise it sets the character's location to be inside `Part3` without
    802 any checking (so it is possible to move the object into a non-existing
    803 character).
    804 
    805 ### CT_MOVEITEMTOINV
    806 * `Part2`: object UUID or name (case insensitive). Special values:
    807   * `00000000-0000-0000-0000-000000000004` :: use `action_object`
    808 * `Part3`: ignored
    809 * `Part4`: ignored
    810 * `Text`: ignored
    811 * FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
    812   `00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
    813   UUID, and there is an object with that name, replace `Part2` with the object's
    814   UUID.
    815 
    816 It the specified object doesn't exists, or in case of
    817 `00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
    818 nothing. Otherwise, if the player has enforce weight limit set, checks if the
    819 recursive sum of the carried items + the recursive weight of the new item does
    820 not exceed the limit. If it does, it **DISPLAY A FUCKING MESSAGE BOX** with the
    821 text `"The "` + object name (override ignored) + <code>" is too heavy to lift at
    822 the moment.&nbsp; unload some stuff first."</code> (with two spaces before
    823 unload and a lower case character at the beginning of the sentence), then
    824 **CANCELS LOOP BREAK** and **ABORTS THE FUCKING COMMAND LIST**.
    825 
    826 Otherwise it sets the object location to the player.
    827 
    828 ### CT_MOVEITEMTOOBJ
    829 * `Part2`: object UUID or name (case insensitive). Special values:
    830   * `00000000-0000-0000-0000-000000000004` :: use `action_object`
    831 * `Part3`: object UUID (**can be parsed**) or name (case insensitive).
    832 * `Part4`: ignored
    833 * `Text`: ignored
    834 * FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
    835   `00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
    836   UUID, and there is an object with that name, replace `Part2` with the object's
    837   UUID. Same with `Part3`.
    838 
    839 It the object specified by `Part2` doesn't exists, or in case of
    840 `00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
    841 nothing. If `Part3` **can be parsed** as a UUID, set `Part2`s location to
    842 `Part3` without any checking (so it is possible to move the object into a
    843 non-existing object). Otherwise, it tries to find an object with the name given
    844 in `Part3`, if it finds one, it moves `Part2` into it, otherwise it does
    845 nothing.
    846 
    847 ### CT_MOVEITEMTOROOM
    848 * `Part2`: object UUID or name (case insensitive). Special values:
    849   * `00000000-0000-0000-0000-000000000004` :: use `action_object`
    850 * `Part3`: room UUID (**can be parsed**) or name (case insensitive). Special
    851   values:
    852   * `00000000-0000-0000-0000-000000000001`: player's current room
    853   * `00000000-0000-0000-0000-000000000002`: "void" room
    854 * `Part4`: ignored
    855 * `Text`: ignored
    856 * FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
    857   `00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
    858   UUID, and there is an object with that name, replace `Part2` with the object's
    859   UUID. If `Part3` is `<CurrentRoom>` (**case sensitively**), replace it with
    860   `00000000-0000-0000-0000-000000000001`. If `Part3` is `<Void>`, replace it
    861   with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
    862   as an UUID and there is a room with that name, replace it with the room's
    863   UUID.
    864 
    865 It the object specified by `Part2` doesn't exists, or in case of
    866 `00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
    867 nothing. Get a room depending on `Part3`:
    868 * `00000000-0000-0000-0000-000000000001`: get the player's current room.
    869 * `00000000-0000-0000-0000-000000000002`: no room (i.e. move the object to
    870   nowhere)
    871 * **can be parsed** as a UUID: use the specified UUID directly without any
    872   checking (so it is possible to move the object to a non-existing room).
    873 * anything else: find a room with the specified name. If the room doesn't
    874   exists, it **DISPLAY A FUCKING MESSAGE BOX** (<code>"Error in command
    875   CT_MoveItemToRoom.&nbsp; Could not locate a room called "</code> + `Part3`)
    876   and returns.
    877 
    878 It sets the object location to the room.
    879 
    880 ### CT_SETOBJECTACTION
    881 * `Part2`: object UUID or name (case insensitive). Special values:
    882   * `00000000-0000-0000-0000-000000000004` :: use `action_object`
    883 * `Part3`: `action_name-state`
    884 * `Part4`: ignored
    885 * `Text`: ignored
    886 * FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
    887   `00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
    888   UUID, and there is an object with that name, replace `Part2` with the object's
    889   UUID.
    890 
    891 It the object specified by `Part2` doesn't exists, or in case of
    892 `00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
    893 nothing. If `Part3` doesn't contain a hyphen, it **throws an exception**.
    894 Otherwise everything until the last hyphen is the `action_name`. If the
    895 object doesn't have an action with that name (case insensitively), it doesn't
    896 do anything. Otherwise it sets its active flag, to true if `state` is `Active`
    897 (**case sensitively**), to false otherwise.
    898 
    899 ### CT_ITEM_SET_CUSTOM_PROPERTY {#CT_ITEM_SET_CUSTOM_PROPERTY}
    900 * `Part2`: `object_name:property_name`
    901 * `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` (**case
    902   sensitive**)
    903 * `Part4`: value (**double replaced**)
    904 * `Text`: ignored
    905 * FIXUP: none
    906 
    907 [See for more info on setting custom properties](#set-custom-property). If
    908 object name is `<Self>` (**case sensitively**, single `<>`), it operates on
    909 `action_object`, otherwise it finds an object with name `object_name` (case
    910 insensitively). If it doesn't exist, it doesn't do anything.
    911 
    912 ### CT_ITEM_SET_CUSTOM_PROPERTY_JS
    913 Same as [CT_ITEM_SET_CUSTOM_PROPERTY](#CT_ITEM_SET_CUSTOM_PROPERTY), but with
    914 JavaScript.
    915 
    916 ### CT_SETITEMDESC
    917 * `Part2`: object UUID or name (case insensitive). Special values:
    918   * `00000000-0000-0000-0000-000000000004` :: use `action_object`
    919 * `Part3`: ignored
    920 * `Part4`: ignored
    921 * `Text`: new description
    922 * FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
    923   `00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
    924   UUID, and there is an object with that name, replace `Part2` with the object's
    925   UUID.
    926 
    927 It the object specified by `Part2` doesn't exists, or in case of
    928 `00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
    929 nothing. Otherwise it sets the object's description to `Text`.
    930 
    931 ### CT_ITEM_SET_NAME_OVERRIDE
    932 * `Part2`: object UUID or name (case insensitive).
    933 * `Part3`: ignored
    934 * `Part4`: new name override
    935 * `Text`: ignored
    936 * FIXUP: none
    937 
    938 It the object specified by `Part2` doesn't exists, it does nothing. Otherwise it
    939 sets the object's name override to `Part4`.
    940 
    941 ### CT_SETLOCKEDUNLOCKED
    942 * `Part2`: object UUID or name (case insensitive). Special values:
    943   * `00000000-0000-0000-0000-000000000004` :: use `action_object`
    944 * `Part3`: `Locked` / anything else (**case sensitive**)
    945 * `Part4`: ignored
    946 * `Text`: ignored
    947 * FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
    948   `00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
    949   UUID, and there is an object with that name, replace `Part2` with the object's
    950   UUID.
    951 
    952 It the object specified by `Part2` doesn't exists, or in case of
    953 `00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
    954 nothing. Otherwise if `Part3` is `Locked`, it sets the object's `Locked`
    955 property to true, false otherwise.
    956 
    957 ### CT_SETOPENCLOSED
    958 * `Part2`: object UUID or name (case insensitive). Special values:
    959   * `00000000-0000-0000-0000-000000000004` :: use `action_object`
    960 * `Part3`: `Open` / anything else (**case sensitive**)
    961 * `Part4`: ignored
    962 * `Text`: ignored
    963 * FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
    964   `00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
    965   UUID, and there is an object with that name, replace `Part2` with the object's
    966   UUID.
    967 
    968 It the object specified by `Part2` doesn't exists, or in case of
    969 `00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
    970 nothing. Otherwise if `Part3` is `Open`, it sets the object's `Open` property to
    971 true, false otherwise.
    972 
    973 ### CT_SETITEMTOWORN
    974 * `Part2`: object UUID or name (case insensitive). Special values:
    975   * `00000000-0000-0000-0000-000000000004` :: use `action_object`
    976 * `Part3`: `Worn` / anything else (**case sensitive**)
    977 * `Part4`: ignored
    978 * `Text`: ignored
    979 * FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
    980   `00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
    981   UUID, and there is an object with that name, replace `Part2` with the object's
    982   UUID.
    983 
    984 It the object specified by `Part2` doesn't exists, or in case of
    985 `00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
    986 nothing. Otherwise if `Part3` is `Worn`, it sets the object's `Worn` property to
    987 true, false otherwise.
    988 
    989 ### CT_ITEM_SET_VISIBILITY
    990 * `Part2`: object UUID or name (case insensitive). Special values:
    991   * `00000000-0000-0000-0000-000000000004` :: use `action_object`
    992 * `Part3`: `Visible` / anything else (**case sensitive**)
    993 * `Part4`: ignored
    994 * `Text`: ignored
    995 * FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
    996   `00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
    997   UUID, and there is an object with that name, replace `Part2` with the object's
    998   UUID.
    999 
   1000 It the object specified by `Part2` doesn't exists, or in case of
   1001 `00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
   1002 nothing. Otherwise if `Part3` is `Visible`, it sets the object's `Visible`
   1003 property to true, false otherwise.
   1004 
   1005 ### CT_ITEMS_MOVE_CONTAINER_TO_CONTAINER
   1006 * `Part2`: object UUID or name (case insensitive). Special values:
   1007   * `00000000-0000-0000-0000-000000000004` :: use `action_object`
   1008 * `Part3`: object UUID or name (case insensitive). Special values:
   1009   * `00000000-0000-0000-0000-000000000004` :: use `action_object`
   1010 * `Part4`: ignored
   1011 * `Text`: ignored
   1012 * FIXUP: none
   1013 
   1014 It the object specified by `Part2` or `Part3` doesn't exists, or in case of
   1015 `00000000-0000-0000-0000-000000000004` there is no `action_object`, it does
   1016 nothing. Otherwise it moves all objects directly in `Part2` to `Part3`.
   1017 
   1018 ### CT_LOOP_BREAK
   1019 * `Part2`: ignored
   1020 * `Part3`: ignored
   1021 * `Part4`: ignored
   1022 * `Text`: ignored
   1023 * FIXUP: none
   1024 
   1025 Schedules a loop break and **aborts the current command list**.
   1026 
   1027 ### CT_CANCELMOVE
   1028 * `Part2`: ignored
   1029 * `Part3`: ignored
   1030 * `Part4`: ignored
   1031 * `Text`: ignored
   1032 * FIXUP: none
   1033 
   1034 Sets a flag to cancel the player's current move. This can be called anywhere,
   1035 but only has an effect inside `<<On Player Leave First Time>>` and `<<On Player
   1036 Leave>>` player actions when executed as a result of the player (the 3DPD one)
   1037 selecting a move target on the GUI.
   1038 
   1039 ### CT_DISPLAYPLAYERDESC
   1040 * `Part2`: ignored
   1041 * `Part3`: ignored
   1042 * `Part4`: ignored
   1043 * `Text`: ignored
   1044 * FIXUP: none
   1045 
   1046 Prints the player's description after replacement (without `loop_item`) to the
   1047 log.
   1048 
   1049 ### CT_SETLAYEREDPLAYERPORTRAIT
   1050 * `Part2`: file name or `<None>`
   1051 * `Part3`: ignored
   1052 * `Part4`: ignored
   1053 * `Text`: ignored
   1054 * FIXUP: none
   1055 
   1056 If `Part2` is `<None>` (**case sensitively**), it is treated as an empty string.
   1057 This sets the player's overlay image to `Part2` without any checking (so it is
   1058 possible to set it to a non-existing file, it will be ignored when drawing).
   1059 
   1060 ### CT_MOVEINVENTORYTOCHAR
   1061 * `Part2`: character name
   1062 * `Part3`: ignored
   1063 * `Part4`: ignored
   1064 * `Text`: ignored
   1065 * FIXUP: if `Part2` is `<<Self>>` (**case sensitively**), replace it with
   1066   `00000000-0000-0000-0000-000000000004`. Otherwise, if it can't be parsed as an
   1067   UUID, and there is an object with that name, replace `Part2` with the object's
   1068   UUID. (This is completely pointless, as `Part2` must be a character name, not
   1069   an object UUID).
   1070 
   1071 Moves every item from the player's inventory to `Part2`'s inventory without any
   1072 checking (so it is possible to move it into a non-existing character).
   1073 
   1074 ### CT_MOVEINVENTORYTOROOM
   1075 * `Part2`: room's UUID. Special values (not parsed, must match exactly):
   1076   * `00000000-0000-0000-0000-000000000001` :: player's current room
   1077   * `00000000-0000-0000-0000-000000000002` :: "void" room
   1078 * `Part3`: ignored
   1079 * `Part4`: ignored
   1080 * `Text`: ignored
   1081 * FIXUP: if `Part2` is `<CurrentRoom>` (**case sensitively**), replace it with
   1082   `00000000-0000-0000-0000-000000000001`. If `Part2` is `<Void>`, replace it
   1083   with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
   1084   as an UUID and there is a room with that name, replace it with the room's
   1085   UUID.
   1086 
   1087 Moves every item from the player's inventory to the specified location:
   1088 * `00000000-0000-0000-0000-000000000001`: move to the player's current room.
   1089 * `00000000-0000-0000-0000-000000000002`: move to nowhere.
   1090 * anything else: move to the room with the specified UUID without any checking
   1091   (so it is possible to move items to a not existing room).
   1092 
   1093 ### CT_MOVEPLAYER
   1094 * `Part2`: room uuid (**can be parsed**) or name (case insensitive).
   1095 * `Part3`: ignored
   1096 * `Part4`: ignored
   1097 * `Text`: ignored
   1098 * FIXUP: if `Part2` is `<CurrentRoom>` (**case sensitively**), replace it with
   1099   `00000000-0000-0000-0000-000000000001`. If `Part2` is `<Void>`, replace it
   1100   with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
   1101   as an UUID and there is a room with that name, replace it with the room's
   1102   UUID.
   1103 
   1104 If `Part2` **can be parsed** as a UUID, find a room with that UUID, else find a
   1105 room with that name, and move the player there. If the room doesn't exist, move
   1106 the player to nowhere instead (which is a surefire way to fuck up rags and
   1107 receive exceptions from everywhere). Otherwise, [execute room enter
   1108 procedure](#room-enter-procedure) with actions but without timers.
   1109 
   1110 ### CT_MOVETOCHAR {#CT_MOVETOCHAR}
   1111 * `Part2`: character name (case insensitive).
   1112 * `Part3`: ignored
   1113 * `Part4`: ignored
   1114 * `Text`: ignored
   1115 * FIXUP: none
   1116 
   1117 If the character doesn't exist, or the character is nowhere or in the same room
   1118 as the player, it doesn't do anything. Otherwise it moves the player to the same
   1119 room as the character is in, if it exists, it moves the player to nowhere if it
   1120 doesn't exist. Finally, if the player is in a room, it [executes room enter
   1121 procedure](#room-enter-procedure) with actions but without timers.
   1122 
   1123 *Note*: this action catches any inner exceptions and silently discards them.
   1124 
   1125 ### CT_MOVETOOBJ
   1126 * `Part2`: object UUID (case insensitive).
   1127 * `Part3`: ignored
   1128 * `Part4`: ignored
   1129 * `Text`: ignored
   1130 * FIXUP: none
   1131 
   1132 If the object doesn't exist, it doesn't do anything. If the object is in a room,
   1133 moves the player to that room, or nowhere if it doesn't. Finally, if the player
   1134 is in a room, it [executes room enter procedure](#room-enter-procedure) with
   1135 actions but without timers.
   1136 
   1137 *Note*: this action catches any inner exceptions and silently discards them.
   1138 
   1139 *Note*: unlike [CT_MOVETOCHAR](#CT_MOVETOCHAR), this executes room enter actions
   1140 when the object is not in a room or it is already in the same room as the
   1141 player.
   1142 
   1143 ### CT_PLAYER_SET_CUSTOM_PROPERTY {#CT_PLAYER_SET_CUSTOM_PROPERTY}
   1144 * `Part2`: `property_name`
   1145 * `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` (**case
   1146   sensitive**)
   1147 * `Part4`: value (**double replaced**)
   1148 * `Text`: ignored
   1149 * FIXUP: none
   1150 
   1151 [See for more info on setting custom properties](#set-custom-property). Since
   1152 there is only one player, `Part2` is treated as `property_name`, even if it
   1153 contains colons.
   1154 
   1155 ### CT_PLAYER_SET_CUSTOM_PROPERTY_JS
   1156 Same as [CT_PLAYER_SET_CUSTOM_PROPERTY](#CT_PLAYER_SET_CUSTOM_PROPERTY), but
   1157 with JavaScript.
   1158 
   1159 ### CT_SETPLAYERACTION
   1160 * `Part2`: ignored
   1161 * `Part3`: `action_name-state`
   1162 * `Part4`: ignored
   1163 * `Text`: ignored
   1164 * FIXUP: none
   1165 
   1166 If `Part3` doesn't contain a hyphen, it **throws an exception**. Otherwise
   1167 everything until the last hyphen is the `action_name`. If the player doesn't
   1168 have an action with that name (case insensitively), it doesn't do anything.
   1169 Otherwise it sets its active flag, to true if `state` is `Active` (**case
   1170 sensitively**), to false otherwise.
   1171 
   1172 ### CT_SETPLAYERDESC
   1173 * `Part2`: ignored
   1174 * `Part3`: ignored
   1175 * `Part4`: ignored
   1176 * `Text`: new description
   1177 * FIXUP: none
   1178 
   1179 Set the player's description to `Text`.
   1180 
   1181 ### CT_SETPLAYERGENDER
   1182 * `Part2`: `Male` / `Female` / `Other` (**case sensitive**)
   1183 * `Part3`: ignored
   1184 * `Part4`: ignored
   1185 * `Text`: ignored
   1186 * FIXUP: none
   1187 
   1188 If `Part2` is not one of the allowed values, it **throws an exception**,
   1189 otherwise it sets the player's gender.
   1190 
   1191 ### CT_SETPLAYERNAME
   1192 * `Part2`: ignored
   1193 * `Part3`: ignored
   1194 * `Part4`: new player name (**double replaced**)
   1195 * `Text`: ignored
   1196 * FIXUP: none
   1197 
   1198 Sets the player's name.
   1199 
   1200 ### CT_SETPLAYERPORTRAIT
   1201 * `Part2`: file name
   1202 * `Part3`: ignored
   1203 * `Part4`: ignored
   1204 * `Text`: ignored
   1205 * FIXUP: none
   1206 
   1207 Sets the player's image to `Part2` without any checking (so it is possible to
   1208 set it to a non-existing file).
   1209 
   1210 ### CT_DISPLAYROOMDESCRIPTION
   1211 * `Part2`: room UUID (**can be parsed**) or name (case insensitive). Special
   1212   values:
   1213   * `00000000-0000-0000-0000-000000000001`: player's current room
   1214 * `Part3`: ignored
   1215 * `Part4`: ignored
   1216 * `Text`: ignored
   1217 * FIXUP: if `Part2` is `<CurrentRoom>` (**case sensitively**), replace it with
   1218   `00000000-0000-0000-0000-000000000001`. If `Part2` is `<Void>`, replace it
   1219   with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
   1220   as an UUID and there is a room with that name, replace it with the room's
   1221   UUID.
   1222 
   1223 If `Part2` is `00000000-0000-0000-0000-000000000001` it uses the player's
   1224 current room, otherwise it finds a room with the given UUID if it **can be
   1225 parsed** as a UUID, by a name otherwise. If the room doesn't exists it doesn't
   1226 do anything, otherwise it prints its description (and just the description, and
   1227 not 28472 unrelated things).
   1228 
   1229 ### CT_DISPLAYROOMPICTURE
   1230 * `Part2`: room UUID (**can be parsed**) or name (case insensitive). Special
   1231   values:
   1232   * `00000000-0000-0000-0000-000000000001`: player's current room
   1233 * `Part3`: ignored
   1234 * `Part4`: ignored
   1235 * `Text`: ignored
   1236 * FIXUP: if `Part2` is `<CurrentRoom>` (**case sensitively**), replace it with
   1237   `00000000-0000-0000-0000-000000000001`. If `Part2` is `<Void>`, replace it
   1238   with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
   1239   as an UUID and there is a room with that name, replace it with the room's
   1240   UUID.
   1241 
   1242 If `Part2` is `00000000-0000-0000-0000-000000000001` it uses the player's
   1243 current room, otherwise it finds a room with the given UUID if it **can be
   1244 parsed** as a UUID, by a name otherwise. If the room doesn't exists it doesn't
   1245 do anything. What it does depends on the value of `HideMainPicDisplay` and
   1246 `UseInlineImages`:
   1247 * `HideMainPicDisplay` and `UseInlineImages`: if the file doesn't exist, **throw
   1248   an exception**. If it is an image, paste it into the log (without scaling and
   1249   buggily, so sometimes it will just insert an empty line...). If it is an
   1250   audio/video, print the full path of the temporary file to the log (where rags
   1251   extracted the media file)...
   1252 * `HideMainPicDisplay` and not `UseInlineImages`: it doesn't do anything.
   1253 * Else: If the file is an image, it sets the main picture (right to the log),
   1254   including (single) layered image. If both of them are images, it works
   1255   correctly. If any of them missing, they're not set. If one is an image and and
   1256   the other is a video, the last one wins (so overlay beats normal image).
   1257 
   1258 ### CT_ROOM_MOVE_ITEMS_TO_PLAYER
   1259 * `Part2`: room UUID (case insensitive). Special values (not parsed, must match
   1260   exactly):
   1261   * `00000000-0000-0000-0000-000000000001`: player's current room
   1262 * `Part3`: ignored
   1263 * `Part4`: ignored
   1264 * `Text`: ignored
   1265 * FIXUP: none
   1266 
   1267 If `Part2` is not a valid UUID, it **throws an exception**. Otherwise, if it is
   1268 `00000000-0000-0000-0000-000000000001` uses the player's current room, otherwise
   1269 it finds the room with the given UUID. If there is no such room, it doesn't do
   1270 anything. Otherwise it moves all `Carryable` and `Visible` in the room to the
   1271 player's inventory.
   1272 
   1273 ### CT_SETROOMACTION
   1274 * `Part2`: room UUID (**can be parsed**) or name (case insensitive). Special
   1275   values:
   1276   * `00000000-0000-0000-0000-000000000001`: player's current room
   1277 * `Part3`: `action_name-state`
   1278 * `Part4`: ignored
   1279 * `Text`: ignored
   1280 * FIXUP: if `Part2` is `<CurrentRoom>` (**case sensitively**), replace it with
   1281   `00000000-0000-0000-0000-000000000001`. If `Part2` is `<Void>`, replace it
   1282   with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
   1283   as an UUID and there is a room with that name, replace it with the room's
   1284   UUID.
   1285 
   1286 It the room specified by `Part2` doesn't exists, it does nothing. If `Part3`
   1287 doesn't contain a hyphen, it **throws an exception**. Otherwise everything until
   1288 the last hyphen is the `action_name`. If the room doesn't have an action with
   1289 that name (case insensitively), it doesn't do anything. Otherwise it sets its
   1290 active flag, to true if `state` is `Active` (**case sensitively**), to false
   1291 otherwise.
   1292 
   1293 ### CT_ROOM_SET_CUSTOM_PROPERTY {#CT_ROOM_SET_CUSTOM_PROPERTY}
   1294 * `Part2`: `room_name:property_name`
   1295 * `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` (**case
   1296   sensitive**)
   1297 * `Part4`: value (**double replaced**)
   1298 * `Text`: ignored
   1299 * FIXUP: none
   1300 
   1301 [See for more info on setting custom properties](#set-custom-property). If room
   1302 name is `<CurrentRoom>` (**case sensitively**), it operates on the player's
   1303 current room, otherwise it finds a room with name `room_name` (case
   1304 insensitively). If it doesn't exist, it doesn't do anything.
   1305 
   1306 ### CT_ROOM_SET_CUSTOM_PROPERTY_JS
   1307 Same as [CT_ROOM_SET_CUSTOM_PROPERTY](#CT_ROOM_SET_CUSTOM_PROPERTY), but with
   1308 JavaScript.
   1309 
   1310 ### CT_SETROOMDESCRIPTION
   1311 * `Part2`: room UUID (**can be parsed**) or name (case insensitive). Special
   1312   values:
   1313   * `00000000-0000-0000-0000-000000000001`: player's current room
   1314 * `Part3`: ignored
   1315 * `Part4`: ignored
   1316 * `Text`: new description
   1317 * FIXUP: if `Part2` is `<CurrentRoom>` (**case sensitively**), replace it with
   1318   `00000000-0000-0000-0000-000000000001`. If `Part2` is `<Void>`, replace it
   1319   with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
   1320   as an UUID and there is a room with that name, replace it with the room's
   1321   UUID.
   1322 
   1323 If `Part2` is `00000000-0000-0000-0000-000000000001`, it operates on the
   1324 player's current room, otherwise if it **can be parsed** as a UUID, it finds a
   1325 room with that UUID, else it find a room with that name. If the specified room
   1326 doesn't exist, it doesn't do anything, otherwise it sets the room's description.
   1327 
   1328 ### CT_SETEXIT
   1329 * `Part2`: room UUID (**can be parsed**) or name (case insensitive).
   1330 * `Part3`: `direction-active` (**case sensitive**)
   1331 * `Part4`: ignored
   1332 * `Text`: ignored
   1333 * FIXUP: if `Part2` is `<CurrentRoom>` (**case sensitively**), replace it with
   1334   `00000000-0000-0000-0000-000000000001`. If `Part2` is `<Void>`, replace it
   1335   with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
   1336   as an UUID and there is a room with that name, replace it with the room's
   1337   UUID. (*Note*: the action doesn't hande these values...)
   1338 
   1339 If `Part2` **can be parsed** as a UUID, it finds a room with that UUID, else it
   1340 find a room with that name. If the specified room doesn't exist, it doesn't do
   1341 anything. If `Part3` doesn't contain a hyphen, it **throws an exception**,
   1342 otherwise everything until the first hyphen is the `direction`. If there is a
   1343 second hyphen, it and everything after it is ignored. `active` is trimmed of
   1344 white-space, but `direction` is not (unlike other enum parsing places...).
   1345 
   1346 If `direction` does not refer to a valid direction (`North`, `South`,
   1347 `NorthEast`, ...), it doesn't do anything. Otherwise it sets its `Active`
   1348 status, to true if `active` is `Active`, false otherwise.
   1349 
   1350 ### CT_SETEXITDESTINATION
   1351 * `Part2`: room UUID (**can be parsed**) or name (case insensitive).
   1352 * `Part3`: direction (**case sensitive**)
   1353 * `Part4`: `<None>` or destination room UUID (**can be parsed**) or name
   1354 * `Text`: ignored
   1355 * FIXUP: none
   1356 
   1357 If `Part2` **can be parsed** as a UUID, it finds a room with that UUID, else it
   1358 find a room with that name. If the specified room doesn't exist, it doesn't do
   1359 anything. If `direction` does not refer to a valid direction (without trimming),
   1360 it doesn't do anything.
   1361 
   1362 If `Part4` is `<None>` (**case sensitively**), it sets the exit's destination to
   1363 nowhere and sets its `Active` flag to false. Otherwise, if `Part4` **can be
   1364 parsed** as a UUID, it finds a room with that UUID, otherwise it finds a room
   1365 with that name. If the room does not exists, it does nothing, otherwise it sets
   1366 the exit's destination to the room and the exit's `Active` status to true.
   1367 
   1368 ### CT_ROOM_SET_NAME_OVERRIDE
   1369 * `Part2`: room UUID (**can be parsed**) or name (case insensitive).
   1370 * `Part3`: ignored
   1371 * `Part4`: name override
   1372 * `Text`: ignored
   1373 * FIXUP: none
   1374 
   1375 If `Part2` **can be parsed** as a UUID, it finds a room with that UUID, else it
   1376 find a room with that name. If the specified room doesn't exist, it doesn't do
   1377 anything, otherwise it sets the room's name override.
   1378 
   1379 ### CT_SETROOMLAYEREDPIC
   1380 * `Part2`: room UUID (**can be parsed**) or name (case insensitive). Special
   1381   values:
   1382   * `00000000-0000-0000-0000-000000000001` :: player's current room
   1383 * `Part3`: file name
   1384 * `Part4`: ignored
   1385 * `Text`: ignored
   1386 * FIXUP: none
   1387 
   1388 If `Part2` is `00000000-0000-0000-0000-000000000001`, it operates on the
   1389 player's current room, otherwise if it **can be parsed** as a UUID, it finds a
   1390 room with that UUID, else it find a room with that name. If the specified room
   1391 doesn't exist, it doesn't do anything.
   1392 
   1393 If `Part3` is `<None>`, it is treated as empty string, then it sets the room's
   1394 overlay image to `Part3` without any checking (so it can be set to a
   1395 non-existing image).
   1396 
   1397 If the room is the current player's room, and `HideMainPicDisplay` is false, it
   1398 updates the main image and room image, but without handling videos.
   1399 
   1400 ### CT_SETROOMPIC
   1401 * `Part2`: room UUID (**can be parsed**) or name (case insensitive). Special
   1402   values:
   1403   * `00000000-0000-0000-0000-000000000001` :: player's current room
   1404 * `Part3`: file name
   1405 * `Part4`: ignored
   1406 * `Text`: ignored
   1407 * FIXUP: if `Part2` is `<CurrentRoom>` (**case sensitively**), replace it with
   1408   `00000000-0000-0000-0000-000000000001`. If `Part2` is `<Void>`, replace it
   1409   with `00000000-0000-0000-0000-000000000002`. Otherwise, if it can't be parsed
   1410   as an UUID and there is a room with that name, replace it with the room's
   1411   UUID.
   1412 
   1413 If `Part2` is `00000000-0000-0000-0000-000000000001`, it operates on the
   1414 player's current room, otherwise if it **can be parsed** as a UUID, it finds a
   1415 room with that UUID, else it find a room with that name. If the specified room
   1416 doesn't exist, it doesn't do anything, otherwise it sets the room's image
   1417 without any checking (so it can be set to a non-existing image).
   1418 
   1419 If the room is the current player's room, it tries to redisplay the room image:
   1420 * If `HideMainPicDisplay` and `UseInlineImages`: paste the room image into the
   1421   log with the usual bugs.
   1422 * If `HideMainPicDisplay`: no further actions.
   1423 * Else: it updates the main image (but not the room image), without handling
   1424   videos.
   1425 
   1426 ### CT_Status_ItemVisibleInvisible
   1427 * `Part2`: status bar item name (case insensitive)
   1428 * `Part3`: `Visible` / anything else (case insensitive)
   1429 * `Part4`: ignored
   1430 * `Text`: ignored
   1431 * FIXUP: none
   1432 
   1433 If the status bar item specified by `Part2` doesn't exist, it doesn't do
   1434 anything. Otherwise it sets its `Visible` flag, to true if `Part3` is `Visible`,
   1435 to false otherwise.
   1436 
   1437 ### CT_EXECUTETIMER {#CT_EXECUTETIMER}
   1438 * `Part2`: timer name (case insensitive)
   1439 * `Part3`: ignored
   1440 * `Part4`: ignored
   1441 * `Text`: ignored
   1442 * FIXUP: none
   1443 
   1444 If the timer with the given name doesn't exist, or it doesn't have an action
   1445 with name `<<On Each Turn>>`, it doesn't do anything. Otherwise it executes the
   1446 action (without an `action_object`), repeating it as many times as it is reset.
   1447 
   1448 ### CT_RESETTIMER
   1449 * `Part2`: timer name (case insensitive)
   1450 * `Part3`: ignored
   1451 * `Part4`: ignored
   1452 * `Text`: ignored
   1453 * FIXUP: none
   1454 
   1455 If the timer with the given name doesn't exist, it doesn't do anything.
   1456 
   1457 Otherwise it sets the timer's `TurnNumber` to zero. If `Part2` is the currently
   1458 executing timer, it queues a reset (which will cause
   1459 [CT_EXECUTETIMER](#CT_EXECUTETIMER) to execute it again, or when normally
   1460 executing timers, it will skip subsequent On ... Turn actions), otherwise it
   1461 **clears the queued reset**. In any case it **CANCELS LOOP BREAK** and **ABORTS
   1462 THE FUCKING COMMAND LIST**.
   1463 
   1464 ### CT_TIMER_SET_CUSTOM_PROPERTY {#CT_TIMER_SET_CUSTOM_PROPERTY}
   1465 * `Part2`: `timer_name:property_name` (case insensitive)
   1466 * `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` (**case
   1467   sensitive**)
   1468 * `Part4`: value (**double replaced**)
   1469 * `Text`: ignored
   1470 * FIXUP: none
   1471 
   1472 [See for more info on setting custom properties](#set-custom-property). It finds
   1473 a timer with name `timer_name` (case insensitively). If it doesn't exist, it
   1474 doesn't do anything.
   1475 
   1476 ### CT_TIMER_SET_CUSTOM_PROPERTY_JS
   1477 Same as [CT_TIMER_SET_CUSTOM_PROPERTY](#CT_TIMER_SET_CUSTOM_PROPERTY), but with
   1478 JavaScript.
   1479 
   1480 ### CT_SETTIMER
   1481 * `Part2`: timer name (case insensitive)
   1482 * `Part3`: `Active` / anything else (case insensitive)
   1483 * `Part4`: ignored
   1484 * `Text`: ignored
   1485 * FIXUP: none
   1486 
   1487 If the timer with the given name doesn't exist, it doesn't do anything.
   1488 Otherwise it sets the timer's `Active` flag, to true if `Part3` is `Active`, to
   1489 false if it is anything else.
   1490 
   1491 ### CT_DISPLAYVARIABLE
   1492 * `Part2`: variable name + optional indices (case insensitive)
   1493 * `Part3`: `Display Date & Time` / `Display Date Only` / `Display Time Only` /
   1494   `Display Weekday Only` (**case sensitive**)
   1495 * `Part4`: ignored
   1496 * `Text`: ignored
   1497 * FIXUP: none
   1498 
   1499 If the variable specified by `Part2` doesn't exist, it doesn't do anything. Here
   1500 is what happens, when the number of indices specified doesn't match the variable
   1501 type:
   1502 | Var type   | 0 idx      | 1 idx                          | 2 idx         |
   1503 |------------|------------|--------------------------------|---------------|
   1504 | Single     | single val | single val                     | single val    |
   1505 | 1D         | nothing    | value                          | **exception** |
   1506 | Num/Str 2D | nothing    | `System.Collections.ArrayList` | value         |
   1507 | DT 2D      | nothing    | **exception**                  | value         |
   1508 
   1509 (Array indices are completely ignored with non-array variables.) Out-of-range
   1510 accesses generate exceptions except in "single val" and "nothing" cells.
   1511 
   1512 In case of number or string variables, `Part3` is ignored, they're converted to
   1513 string normally and printed. With DateTime variables it specifies a date format:
   1514 * `Display Date & Time`: `dddd, MMMM, dd yyyy hh:mm:ss tt`, except in case of 2D
   1515   arrays, where it just prints whatever string representation the date is stored
   1516   in (so it doesn't throw if you only specify one index, it prints
   1517   `System.Collections.ArrayList`).
   1518 * `Display Date Only`: `dddd, MMMM, dd yyyy`
   1519 * `Display Time Only`: `hh:mm:ss tt`
   1520 * `Display Weekday Only`: `dddd`
   1521 If `Part3` is not one of the allowed values, it doesn't print anything.
   1522 
   1523 ### CT_SETVARIABLE {#CT_SETVARIABLE}
   1524 * `Part2`: variable name + optional indices (case insensitive)
   1525 * `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` / `Add Days` /
   1526   `Add Hours` / `Add Minutes` / `Add Seconds` / `Subtract Days` / `Subtract
   1527   Hours` / `Subtract Minutes` / `Subtract Seconds` / `Set Day Of Month To` /
   1528   `Set Hours To` / `Set Minutes To` / `Set Seconds To` (**case sensitive**)
   1529 * `Part4`: value (**double replaced**)
   1530 * `Text`: also value (**double replaced**)
   1531 * FIXUP: none
   1532 
   1533 If `Part2` contains the string `Array:` (anywhere, **case sensitive**), the
   1534 first 6 characters of `Part2` is removed and it will be an array set, otherwise
   1535 a normal set. The rest is parsed for variable name and indices as usual, if the
   1536 variable doesn't exists, this action does nothing.
   1537 
   1538 In case of a JavaScript set
   1539 ([CT_VARIABLE_SET_JAVASCRIPT](#CT_VARIABLE_SET_JAVASCRIPT)), it runs `Part4`
   1540 through the JavaScript interpreter (after double replace), and converts the
   1541 result to string.
   1542 
   1543 Here is what happens generally when the number of indices doesn't match, but
   1544 watch out for exceptions below.
   1545 | Var type | 0 idx      | 1 idx                          | 2 idx                |
   1546 |----------|------------|--------------------------------|----------------------|
   1547 | Single   | set single | ignore/**exception**           | ignore/**exception** |
   1548 | 1D       | set single | set                            | **exception**        |
   1549 | 2D       | set single | **fuckup**                     | set                  |
   1550 * ignore/**exception**: this means that the code will try to overwrite the array
   1551   part of the variable, which depending on what leftover garbage is there, might
   1552   actually work, but you're more likely to get an exception.
   1553 * **fuckup**: in this case you'll end up with an array where a row is replaced
   1554   by a string/number/dt, and... you can expect things to break left and right if
   1555   you do this.
   1556 
   1557 With number and datetime variables, array set is only working when `Part3` is
   1558 `Equals`, but for string variables `Part3` is ignored. An array set is
   1559 only working with JS set, without JS it just clears the variable (it will be 0
   1560 rows, indefinite columns, it leaves single value alone).
   1561 
   1562 With a normal set, with a number variable, if `Part4` is not a number, it
   1563 **throws an exception**, otherwise it just sets the specified cell with the
   1564 error handling above. In case of `Add`, `Subtract`, `Multiply` and `Divide` only
   1565 normal sets are possible, `Array:` is ignored and **fuckup** changes into
   1566 **exception**. If `Part3` has any other value, the value is not updated.
   1567 
   1568 If the variable has `EnforceRestrictions`, the set (or not set in case `Part3`
   1569 is invalid...) value is validated. If it is smaller than replaced and converted
   1570 to double min, it is set to min, and if after it is larger than max, it is set
   1571 to max (if the result of replacement is not a number, it **throws an
   1572 exception**). In case of `Equals` and **fuckup**, this will end up in an
   1573 exception, but only after fucking up the variable...
   1574 
   1575 In case of string variables, `Part3` is ignored (only simple settings is
   1576 supported) and the value is read from `Text` instead of `Part4` (`Text` is also
   1577 evaluated with JS, but only with string variables). Array set and normal set is
   1578 supported.
   1579 
   1580 DateTime works similarly to numbers, except: every possible value of `Part3` is
   1581 supported except `Add`, `Subtract`, `Multiply`, `Divide`, and there are no
   1582 min-max checks. `Equals` expects a date time, parsed by .NET's super permissive
   1583 parser, everything else an int32, if the conversion fails it **throws an
   1584 exception**.
   1585 
   1586 ### CT_ITEM_GETRANDOMGROUP {#CT_ITEM_GETRANDOMGROUP}
   1587 * `Part2`: variable name (without indices!) (case insensitive)
   1588 * `Part3`: group name (case insensitive)
   1589 * `Part4`: ignored
   1590 * `Text`: ignored
   1591 * FIXUP: none
   1592 
   1593 If the variable specified by `Part2` doesn't exist, it doesn't do anything.
   1594 
   1595 **This operates directly on SQL**, not the in-memory structures (that gets
   1596 serialized into savegames). If `Part3` doesn't name a valid object group, it is
   1597 treated as being empty. Otherwise it collects all objects recursively in the
   1598 object group.
   1599 
   1600 It sets the variable's single string value (even if it is not a string variable)
   1601 to the name of a random object in this set, or to the empty string if the object
   1602 group is empty.
   1603 
   1604 ### CT_MM_GETRANDOMGROUP
   1605 * `Part2`: variable name (without indices!) (case insensitive)
   1606 * `Part3`: group name (case insensitive)
   1607 * `Part4`: ignored
   1608 * `Text`: ignored
   1609 * FIXUP: none
   1610 
   1611 Exactly the same as [CT_ITEM_GETRANDOMGROUP](#CT_ITEM_GETRANDOMGROUP), except it
   1612 gets a random file name from a file group.
   1613 
   1614 ### CT_VARIABLE_SET_JAVASCRIPT {#CT_VARIABLE_SET_JAVASCRIPT}
   1615 See [CT_SETVARIABLE](#CT_SETVARIABLE) for details. I'm only going to document
   1616 how JavaScript arrays are turned into rags variable arrays:
   1617 
   1618 If the returned value is not a JavaScript array, the result is an empty array.
   1619 Otherwise each item of the array is converted as follows:
   1620 * it is not an array and variable type is number: the value is converted to a
   1621   string, then to a double. If any of this fails, the value is `-1`.
   1622 * it is not an array and variable type is string: the value is converted to a
   1623   string. If this fails, the exception is quietly swallowed and it leaves the
   1624   array in a half-set state.
   1625 * it is not an array and variable type is DateTime: it is converted to a string,
   1626   then parsed with .net's super permissive parser. If this fails somehow, it is
   1627   set to `0001-01-01 00:00:00` unspecified timezone (can be treated as local).
   1628 * it is an array: if it is an empty array, it is skipped, otherwise each item of
   1629   this inner array is simply converted to a string. (2D arrays are always stored
   1630   as strings in rags.) (If it is not a 2D array, **fuckup**.)
   1631 
   1632 ### CT_SETVARIABLEBYINPUT {#CT_SETVARIABLEBYINPUT}
   1633 * `Part2`: input type: `Text` / `Characters` / `Characters And Objects` /
   1634   `Objects` / `Custom` / `Inventory` (**case sensitive**)
   1635 * `Part3`: variable name + optional indices (case insensitive)
   1636 * `Part4`: custom choice title (**double replaced**)
   1637 * `Text`: ignored
   1638 * `EnhancedData`: one of the few functions that use it
   1639 * FIXUP: none
   1640 
   1641 If input type is not one of the allowed values, it is treated as being `None`
   1642 (which means an empty selection list. Interestingly, without ovelays, you can
   1643 still click on OK even with zero items, overlay forces a cancel button in this
   1644 case). In case of a custom choice, custom values are taken from `EnhancedData`,
   1645 after replacement, otherwise the same as with normal action input.
   1646 
   1647 When using overlay input, the title is taken from `Part4`, or if it empty after
   1648 replace, it is `Please make a selection:`. Without overlay, it is always taken
   1649 from `Part4`, even if it is empty. Non overlay query is never cancelable, while
   1650 overlay input is cancelable if the list is empty or `EnhancedData.AllowCancel`.
   1651 
   1652 Without overlay, it never uses tags, with overlay it's same as the normal
   1653 action. There's no post-processing of the selected tag. If the selection is
   1654 canceled, it **CANCELS LOOP BREAK** and **ABORTS THE FUCKING COMMAND LIST**.
   1655 
   1656 If the variable does not exists, or if input type is `None`, it doesn't do
   1657 anything (but it only checks this after the user made a selection...). This
   1658 action expects the variable to be a string, it sets single and array string
   1659 values without checking, ending up in ignore/fuckup cases when done. With `Text`
   1660 input, the selection is simply stored. Otherwise, first the single value is set
   1661 to an empty string, and if the selection is not an empty string, the normal
   1662 value is set. Here is what happens when the number of indices doesn't match the
   1663 variable type:
   1664 
   1665 | Var type | 0 idx      | 1 idx                          | 2 idx                |
   1666 |----------|------------|--------------------------------|----------------------|
   1667 | Single   | set single | ignore/**exception**           | ignore/**exception** |
   1668 | 1D       | set single | set                            | **exception**        |
   1669 | 2D       | set single | **fuckup**                     | set                  |
   1670 
   1671 ### CT_SETVARIABLE_NUMERIC_BYINPUT
   1672 * `Part2`: input type: `Text` / `Custom` (**case sensitive**)
   1673 * `Part3`: variable name + optional indices (case insensitive)
   1674 * `Part4`: custom choice title (**double replaced**)
   1675 * `Text`: ignored
   1676 * `EnhancedData`: one of the few functions that use it
   1677 * FIXUP: none
   1678 
   1679 If input type is not one of the allowed values, it is treated as being `None`.
   1680 Largely the same as [CT_SETVARIABLEBYINPUT](#CT_SETVARIABLEBYINPUT), except it
   1681 shows a `Sorry, you must enter a valid number.` message with `Text` input when
   1682 you enter an invalid input (custom choices are not checked, you get an
   1683 **exception** when the code actually tries to set the value.
   1684 
   1685 It sets the variable's number fields (with a possibility to **fuckup**), except
   1686 that it also handles `EnforceRestrictions`.
   1687 
   1688 ### CT_VARIABLE_SET_CUSTOM_PROPERTY {#CT_VARIABLE_SET_CUSTOM_PROPERTY}
   1689 * `Part2`: `variable_name:property_name` (case insensitive)
   1690 * `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` (**case
   1691   sensitive**)
   1692 * `Part4`: value (**double replaced**)
   1693 * `Text`: ignored
   1694 * FIXUP: none
   1695 
   1696 [See for more info on setting custom properties](#set-custom-property). It finds
   1697 a variable with name `variable_name` (case insensitively, without indices). If
   1698 it doesn't exist, it doesn't do anything.
   1699 
   1700 ### CT_VARIABLE_SET_CUSTOM_PROPERTY_JS
   1701 Same as [CT_VARIABLE_SET_CUSTOM_PROPERTY](#CT_VARIABLE_SET_CUSTOM_PROPERTY), but
   1702 with JavaScript.
   1703 
   1704 ### CT_VARIABLE_SET_RANDOMLY
   1705 * `Step2`: variable name + optional indices (case insensitive)
   1706 * `Step3`: ignored
   1707 * `Step4`: ignored
   1708 * `Text`: ignored
   1709 * FIXUP: none
   1710 
   1711 If the variable doesn't exist, it doesn't do anything. It will always set number
   1712 fields, so using this on a non-number variable allows a **fuckup**. It generates
   1713 an integer random number between `[int32(double(min)), int32(double(max))+1)`
   1714 (string is first converted to double, then converted to int32 by rounding to
   1715 zero). Throws an error if the upper bound is smaller than the lower, returns min
   1716 when they're equal. Here is what happens when the number of indices doesn't
   1717 match the variable type:
   1718 
   1719 | Var type | 0 idx      | 1 idx                          | 2 idx                |
   1720 |----------|------------|--------------------------------|----------------------|
   1721 | Single   | set single | ignore/**exception**           | ignore/**exception** |
   1722 | 1D       | set single | set                            | **exception**        |
   1723 | 2D       | set single | **fuckup**                     | set                  |
   1724 
   1725 ### CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE {#CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE}
   1726 * `Step2`: variable name + optional indices (case insensitive)
   1727 * `Step3`: `character_name:property_name`
   1728 * `Step4`: ignored (**double replaced**)
   1729 * `Text`: ignored
   1730 * FIXUP: none
   1731 
   1732 If the specified variable doesn't exist, it doesn't do anything. If `Step3`
   1733 doesn't exactly have one colon, or the character (case insensitively) doesn't
   1734 exist, or the custom property (**case sensitively**) doesn't exist, it is
   1735 treated as being an empty string. If the variable is a DateTime variable, it
   1736 doesn't do anything.
   1737 
   1738 Here is what happens when the number of indices doesn't match the variable type:
   1739 | Var type | 0 idx      | 1 idx                          | 2 idx                |
   1740 |----------|------------|--------------------------------|----------------------|
   1741 | Single   | set single | ignore/**exception**           | ignore/**exception** |
   1742 | 1D       | set single | set                            | **exception**        |
   1743 | 2D       | set single | **fuckup**                     | set                  |
   1744 
   1745 In case of a number variable, if it is a "set single", the string value is
   1746 converted to a double, if it fails it prints some kind of debug log and does
   1747 nothing. In case of arrays, it just stores the string into the array (even in
   1748 case of an 1D array), for random **fuckup**s in the future.
   1749 
   1750 ### CT_VARIABLE_SET_WITH_ITEMPROPERTYVALUE
   1751 * `Step2`: variable name + optional indices (case insensitive)
   1752 * `Step3`: `object_name:property_name`
   1753 * `Step4`: ignored (**double replaced**)
   1754 * `Text`: ignored
   1755 * FIXUP: none
   1756 
   1757 Copy-paste of
   1758 [CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE](#CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE),
   1759 but with objects instead of characters. Additionally, `object_name` can be
   1760 `<Self>` (**case sensitive**) to mean `action_object` if it is specified, it
   1761 uses empty string if it is not specified.
   1762 
   1763 ### CT_VARIABLE_SET_WITH_PLAYERPROPERTYVALUE
   1764 * `Step2`: variable name + optional indices (case insensitive)
   1765 * `Step3`: `property_name` (**case sensitive**)
   1766 * `Step4`: ignored (**double replaced**)
   1767 * `Text`: ignored
   1768 * FIXUP: none
   1769 
   1770 Copy-paste of
   1771 [CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE](#CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE),
   1772 but with player instead of characters. `Step3` is treated as a property name,
   1773 even if it contains colons.
   1774 
   1775 ### CT_VARIABLE_SET_WITH_ROOMPROPERTYVALUE
   1776 * `Step2`: variable name + optional indices (case insensitive)
   1777 * `Step3`: `room_name:property_name`
   1778 * `Step4`: ignored (**double replaced**)
   1779 * `Text`: ignored
   1780 * FIXUP: none
   1781 
   1782 Copy-paste of
   1783 [CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE](#CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE),
   1784 but with rooms instead of characters. Additionally, `room_name` can be
   1785 `<CurrentRoom>` (**case sensitively**) to mean the player's current room.
   1786 
   1787 ### CT_VARIABLE_SET_WITH_TIMERPROPERTYVALUE
   1788 * `Step2`: variable name + optional indices (case insensitive)
   1789 * `Step3`: `timer_name:property_name`
   1790 * `Step4`: ignored (**double replaced**)
   1791 * `Text`: ignored
   1792 * FIXUP: none
   1793 
   1794 Copy-paste of
   1795 [CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE](#CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE),
   1796 but with timers instead of characters.
   1797 
   1798 ### CT_VARIABLE_SET_WITH_VARIABLE
   1799 * `Step2`: variable to set name + optional indices (case insensitive)
   1800 * `Part3`: `Equals` / `Add` / `Subtract` / `Multiply` / `Divide` / `Add Days` /
   1801   `Add Hours` / `Add Minutes` / `Add Seconds` / `Subtract Days` / `Subtract
   1802   Hours` / `Subtract Minutes` / `Subtract Seconds` / `Set Day Of Month To` /
   1803   `Set Hours To` / `Set Minutes To` / `Set Seconds To` (**case sensitive**)
   1804 * `Step4`: variable to read name + optional indices (case insensitive)
   1805 * `Text`: ignored
   1806 
   1807 If the variable specified by `Step4` doesn't exist, it **throws an exception**.
   1808 Here is what happens when the number of indices doesn't match the variable type:
   1809 | Var type | 0 idx      | 1 idx                 | 2 idx                 |
   1810 |----------|------------|-----------------------|-----------------------|
   1811 | Single   | single val | garbage/**exception** | garbage/**exception** |
   1812 | 1D       | single val | OK                    | **exception**         |
   1813 | 2D       | single val | **exception**         | OK                    |
   1814 
   1815 The variable is also treated as a number variable, even if not: in case of
   1816 single values, this will read garbage, with 1D it **throws an exception**, and
   1817 with 2D it tries to parse the stored string as a number and **throws an
   1818 excepton** if it fails.
   1819 
   1820 With two indices, if the value is not an integer, it **throws an exception**. If
   1821 the value can not be represented as an int32 (after tuncation), it also **throws
   1822 an exception**.
   1823 
   1824 If the variable specified by `Step2` doesn't exist, or it is a string variable,
   1825 it doesn't do anything.
   1826 
   1827 Here is what happens generally when the number of indices doesn't match with
   1828 `Step2`:
   1829 | Var type | 0 idx      | 1 idx                          | 2 idx                |
   1830 |----------|------------|--------------------------------|----------------------|
   1831 | Single   | set single | ignore/**exception**           | ignore/**exception** |
   1832 | 1D       | set single | set                            | **exception**        |
   1833 | 2D       | set single | **fuckup**                     | set                  |
   1834 
   1835 With number variables, `Step4` is treated as a double, except with 2D arrays and
   1836 `Equal`, or single values and `Divide`, where it is treated as an integer. If
   1837 `Part2` is not `Equals`, `Add`, `Subtract`, `Multiply` or `Divide`, it doesn't
   1838 update the variable. `EnforceRestrictions` are handled on number variables, even
   1839 if there was no update.
   1840 
   1841 DateTime with `Equals` is a buggy piece of shit. With not 2D arrays, it just
   1842 **throws an exception**, with 2D arrays, it sets the the number converted to
   1843 string, which rags later won't be able to parse back (so it's a kind of
   1844 **fuckup**). The rest date-related operations actually work though, but they
   1845 operate on the truncated integers (so you can't add 1.5 days to a DT).
   1846 
   1847 ### CT_VARIABLE_SET_WITH_VARIABLEPROPERTYVALUE
   1848 * `Step2`: variable name + optional indices (case insensitive)
   1849 * `Step3`: `timer_name:property_name`
   1850 * `Step4`: ignored (**double replaced**)
   1851 * `Text`: ignored
   1852 * FIXUP: none
   1853 
   1854 Copy-paste of
   1855 [CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE](#CT_VARIABLE_SET_WITH_CHARPROPERTYVALUE),
   1856 but with variables instead of characters.
   1857 
   1858 ### CT_ENDGAME
   1859 * `Step2`: ignored
   1860 * `Step3`: ignored
   1861 * `Step4`: ignored
   1862 * `Text`: display text
   1863 * FIXUP: none
   1864 
   1865 Prints `Text` to the log and ends the game. It also displays a dialog box
   1866 <code>The game has ended.&nbsp;  Would you like to play again?</code> (with two
   1867 spaces between the sentences) and buttons `Restart`, `Load` and `No`. Unless
   1868 selecting `Restart`, the remaining commands will be run.
   1869 
   1870 # Condition
   1871 It has two subtypes, loop and if.
   1872 ## Loop
   1873 * It has a single check, `CT_Loop_*`
   1874 * `PassCommands` will be called for each iteration, setting `loop_item`
   1875 * `FailCommands` are ignored
   1876 * `CT_LOOP_BREAK` can be used to break out of the innermost loop. However, if
   1877   break is inside a Condition's `PassCommands` or `FailCommads`, and there are
   1878   subsequent Commands in the same list, some later commands can cancel the
   1879   break. See the individual command documentations.
   1880 ## If
   1881 * Normally it has one or more checks, neither of them being `CT_Loop_*`
   1882 * Rags designer doesn't allow you to create a condition with zero checks, but if
   1883   you manage to create one it will be regarded as true.
   1884 * However you can create an if with loops inside, if you want a bugfest: first,
   1885   the loop body will be executed when the check is evaluated. Second, they don't
   1886   modify the variable used to store the return value, so their "value" will be
   1887   whatever before them produced (or true if they're the first in the list).
   1888   Third, since they're not loops, `PassCommands` or `FailCommands` will be
   1889   executed after them, but without `loop_item`. For example, having an if with
   1890   "loop exits and loop exits", PassCommands will be executed 25 times (unless it
   1891   breaks): 12 times with the exits of the first room, 12 times with the exits of
   1892   the second room, then one more time without `loop_item`.
   1893 * `PassCommands` or `FailCommands` are executed depending on the check
   1894 * Handling multiple checks: every check has a `CkType`, but it's ignored for the
   1895   first check. For the rest, they kinda also short-circuit:
   1896   * `Or` check following a true: result is true, no other checks are executed.
   1897   * `And` check following a false: executing the `And` check is skipped, but
   1898     subsequent checks are still executed
   1899   This (I think) corresponds to `And` having higher precedence, so a list of
   1900   `Ignored, A`, `And, B`, `Or, C`, `And, D` is parsed as `(A && B) || (C && D)`.
   1901 
   1902 ## Generic check info
   1903 `Step2`, `Step3` and `Step4` values are always run through the [text replacement
   1904 function][] before usage.
   1905 
   1906 Note for X in Y checks: rags store the location of the objects/characters by
   1907 string, so it's possible for an object/character to be in a
   1908 room/object/character that doesn't exists. Unfortunately this information is
   1909 lost during import to scraps.
   1910 
   1911 ### CustomPropertyChecks {#CustomPropertyChecks}
   1912 * `Step2`: colon separated pair: `name:custom_property_name`
   1913 * `Step3`: `Equals` / `Not Equals` / `Contains` / `Greater Than` / `Greater Than
   1914   or Equals` / `Less Than` / `Less Than or Equals` (**case sensitive**)
   1915 * `Step4`: value to compare with. **Double replaced!**
   1916 
   1917 Interpretation of `name` varies, but generally if `Step2` doesn't have exactly
   1918 one colon, or the named entity doesn't exists, or it doesn't have a custom
   1919 property named `custom_property_name` (**case insensitively**), the check
   1920 **doesn't return anything**. If `Step3` is not one of the allowed values, it
   1921 **returns true**.
   1922 
   1923 If both the custom property's value and `Step4` can be converted to a double,
   1924 they're treated as numbers otherwise as strings, except `Contains` which is
   1925 always string based. (This means that for example `012` equals to `12` but
   1926 `012x` doesn't equal to `12x`.) String compare is **REVERSED** and case
   1927 insensitive (i.e. `1 < 2` but `1x > 2x`). Returns the result of the comparison.
   1928 
   1929 ## Available ConditionTypes
   1930 ### CT_Uninitialized
   1931 Not exactly a valid check, Rags will treat it as any other invalid option:
   1932 **don't return anything**.
   1933 
   1934 ### CT_AdditionalDataCheck
   1935 * `Step2`: object/character name to compare with (case insensitive)
   1936 * `Step3`: ignored
   1937 * `Step4`: text value to compare with (case insensitive)
   1938 
   1939 Checks the value selected at the beginning of the action. If `InputType` is
   1940 `Text`, it compares the entered string with `Step4` (`Step2` is ignored in this
   1941 case). Otherwise, `Step4` is ignored and:
   1942 1. If selection and `Step2` equals, return true.
   1943 2. If there is an object named selection, returns `object.uuid == Step2`.
   1944 3. If there is a character named selection, returns `character.ToString() ==
   1945    Step2`, where `ToString` is `Name` if `NameOverride` is not empty, else
   1946    `Name` if we're outside a game (?), else `NameOverride` with text
   1947    replacements (!!).
   1948 4. If there is an object uuid matching the selection, returns `object.name ==
   1949    Step2`.
   1950 5. Otherwise returns false.
   1951 ### CT_Character_CustomPropertyCheck
   1952 * `Step2`: `character_name:custom_property_name` (case insensitive)
   1953 * `Step3`: comparison type
   1954 * `Step4`: value
   1955 
   1956 [See for more info on CustomPropertyChecks](#CustomPropertyChecks).
   1957 `character_name` is a name of a character.
   1958 
   1959 ### CT_Character_Gender
   1960 * `Step2`: character's name (case insensitive)
   1961 * `Step3`: `Male` / `Female` / anything else (**case sensitive**)
   1962 * `Step4`: ignored
   1963 
   1964 If the character doesn't exist, **doesn't return anything**. If `Step3` is
   1965 neither `Male` nor `Female`, it's treated as `Other`. Returns whether the
   1966 character's gender equals to `Step3`.
   1967 
   1968 ### CT_Character_In_Room
   1969 * `Step2`: character's name (case insensitive)
   1970 * `Step3`: room's uuid (**case sensitive**). Special values (not parsed, must
   1971   match exactly):
   1972   * `00000000-0000-0000-0000-000000000001` :: player's current room
   1973   * `00000000-0000-0000-0000-000000000002` :: "void" room
   1974 * `Step4`: ignored
   1975 
   1976 If the character doesn't exists, **throws an exception**. Returns whether the
   1977 character is currently in the specified room (see special values above).
   1978 
   1979 ### CT_Character_In_RoomGroup {#CT_Character_In_RoomGroup}
   1980 * `Step2`: character's name (case insensitive)
   1981 * `Step3`: room group or `None` (**case sensitive**)
   1982 * `Step4`: ignored
   1983 
   1984 If the character doesn't exists, **throws an exception**. If the character is
   1985 not in a room, returns false. Returns whether character's current room's group's
   1986 name equals to `Step3` (use `None` to check if the room is not in any group).
   1987 
   1988 _Note_: in rags, room groups are not recursive, but they are in scraps. To be
   1989 consistent with `CT_Item_InGroup` and `CT_MultiMedia_InGroup`, scraps does a
   1990 recursive check here aswell.
   1991 
   1992 ### CT_Item_CustomPropertyCheck
   1993 * `Step2`: `object_name:custom_property_name` (case insensitive)
   1994 * `Step3`: comparison type
   1995 * `Step4`: value
   1996 
   1997 [See for more info on CustomPropertyChecks](#CustomPropertyChecks). If
   1998 `object_name` is `<Self>` (**case sensitively**), it refers to the hidden object
   1999 parameter, otherwise it is an object's name (case insensitively).
   2000 
   2001 ### CT_Item_InGroup {#CT_Item_InGroup}
   2002 * `Step2`: name of the object (case insensitive)
   2003 * `Step3`: group name (**case sensitive**)
   2004 * `Step4`: ignored
   2005 
   2006 **This is buggy in designer!** Rags designer puts the object's uuid into
   2007 `Step2`, but the player expects an object name. To fix it, you have to *type*
   2008 the object name into the `Choose Item` box, and not select from the drop-down
   2009 menu.
   2010 
   2011 **This operates directly on SQL**, not the in-memory structures (that gets
   2012 serialized into savegames). If there is no such object in the SQL, it is treated
   2013 as having an empty group name. If the object's group name equals to `Step3`,
   2014 returns true. Otherwise it gets the children of the specified group and
   2015 recursively checks all of them (same as getting the parent of the object group,
   2016 and recursively checking the parent groups, except much more inefficient).
   2017 Returns false if not found.
   2018 
   2019 ### CT_Item_Held_By_Character
   2020 * `Step2`: character name (**case sensitive**)
   2021 * `Step3`: object UUID or name (case insensitive)
   2022 * `Step4`: ignored
   2023 * FIXUP: if `Step3` can't be parsed as an UUID, and there is an object with that
   2024   name, replace `Step3` with the object's UUID.
   2025 
   2026 If the object doesn't exist, returns false. Otherwise returns whether the
   2027 object's location is directly a character with the specified name (not
   2028 subobject).
   2029 
   2030 ### CT_Item_Held_By_Player
   2031 * `Step2`: object UUID or name (case insensitive)
   2032 * `Step3`: ignored
   2033 * `Step4`: ignored
   2034 * FIXUP: if `Step2` can't be parsed as an UUID, and there is an object with that
   2035   name, replace `Step2` with the object's UUID.
   2036 
   2037 If the object doesn't exist, returns false. Otherwise returns whether the
   2038 object's location is a player with the specified name **or it is recursively
   2039 inside an object held by the player**.
   2040 
   2041 ### CT_Item_In_Object
   2042 * `Step2`: object UUID or name (case insensitive)
   2043 * `Step3`: other object's UUID (**case sensitive**)
   2044 * `Step4`: ignored
   2045 * FIXUP: if `Step2` can't be parsed as an UUID, and there is an object with that
   2046   name, replace `Step2` with the object's UUID.
   2047 
   2048 If the object specified by `Step2` doesn't exists, return false. Otherwise
   2049 returns whether `Step2` is directly inside `Step3`.
   2050 
   2051 ### CT_Item_In_Room
   2052 * `Step2`: object UUID or name (case insensitive)
   2053 * `Step3`: room UUID (**case sensitive**). Special values (not parsed, must
   2054   match exactly):
   2055   * `00000000-0000-0000-0000-000000000001` :: player's current room
   2056 * `Step4`: ignored
   2057 * FIXUP: if `Step2` can't be parsed as an UUID, and there is an object with that
   2058   name, replace `Step2` with the object's UUID. Same with `Step3` and room.
   2059 
   2060 If the object doesn't exists, returns false. Otherwise returns whether the
   2061 object is currently in the specified room (see special values above) directly
   2062 (i.e. not subobjects).
   2063 
   2064 ### CT_Item_In_RoomGroup
   2065 * `Step2`: object UUID (case insensitive)
   2066 * `Step3`: room group or `None` (**case sensitive**)
   2067 * `Step4`: ignored
   2068 
   2069 If the object doesn't exists or it is not in a room, returns false. Returns
   2070 whether the object's current room's group's name equals to `Step3`.
   2071 
   2072 [See also CT_Character_In_RoomGroup](#CT_Character_In_RoomGroup).
   2073 
   2074 ### CT_Item_Not_Held_By_Player
   2075 * `Step2`: object UUID or name (case insensitive)
   2076 * `Step3`: ignored
   2077 * `Step4`: ignored
   2078 * FIXUP: if `Step2` can't be parsed as an UUID, and there is an object with that
   2079   name, replace `Step2` with the object's UUID.
   2080 
   2081 If the object doesn't exists, return false. Otherwise returns whether the object
   2082 is not in the player's inventory **DIRECTLY**. Consistency, where the fuck are
   2083 you!?
   2084 
   2085 ### CT_Item_Not_In_Object
   2086 * `Step2`: object UUID or name (case insensitive)
   2087 * `Step3`: other object's UUID (**case sensitive**)
   2088 * `Step4`: ignored
   2089 
   2090 If the object specified by `Step2` doesn't exists, return false. Otherwise
   2091 returns whether `Step2` is not directly inside `Step3`.
   2092 
   2093 ### CT_Item_State_Check
   2094 * `Step2`: object UUID or name (case insensitive)
   2095 * `Step3`: `Open` / `Closed` / `Locked` / `Unlocked` / `Worn` / `Removed` /
   2096   `Read` / `Unread` / `Visible` / `Invisible` (**case sensitive**)
   2097 * `Step4`: ignored
   2098 * FIXUP: if `Step2` can't be parsed as an UUID, and there is an object with that
   2099   name, replace `Step2` with the object's UUID.
   2100 
   2101 If the object doesn't exists, it returns false. Otherwise, if `Step3` is not one
   2102 of the allowed values, it **doesn't return anything**. Otherwise, returns
   2103 whether the object is in the specified state. (Every second item in the list is
   2104 the negation of the previous one, so `Closed` is checking for not `Open`,
   2105 `Removed` is checking for not `Worn`, etc.)
   2106 
   2107 ### CT_Loop_While
   2108 Parameters are the same as [CT_Variable_Comparison](#CT_Variable_Comparison),
   2109 execute `PassCommands` until variable comparison is true. `loop_item` is passed
   2110 through unmodified.
   2111 
   2112 ### CT_Loop_Characters
   2113 * `Step2`: ignored
   2114 * `Step3`: ignored
   2115 * `Step4`: ignored
   2116 
   2117 Iterate through each character in the game (skipping player, since player is not
   2118 a character in rags), setting `loop_item`.
   2119 
   2120 ### CT_Loop_Items
   2121 * `Step2`: ignored
   2122 * `Step3`: ignored
   2123 * `Step4`: ignored
   2124 
   2125 Iterate through each object in the game, setting `loop_item`.
   2126 
   2127 ### CT_Loop_Item_Group
   2128 * `Step2`: object group name (**case sensitive**)
   2129 * `Step3`: ignored
   2130 * `Step4`: ignored
   2131 
   2132 Iterate through each object in the game. If the object is in the specified
   2133 object group directly, call `PassCommands` setting `loop_item`.
   2134 
   2135 ### CT_Loop_Item_Container
   2136 * `Step2`: object UUID (**case sensitive**)
   2137 * `Step3`: ignored
   2138 * `Step4`: ignored
   2139 
   2140 Iterate through each object in the game. If the object is in the specified
   2141 object, call `PassCommands` setting `loop_item`.
   2142 
   2143 ### CT_Loop_Item_Room
   2144 * `Step2`: room UUID (**case sensitive**)
   2145 * `Step3`: ignored
   2146 * `Step4`: ignored
   2147 
   2148 Iterate through each object in the game. If the object is in the specified
   2149 room, call `PassCommands` setting `loop_item`.
   2150 
   2151 ### CT_Loop_Item_Inventory
   2152 * `Step2`: ignored
   2153 * `Step3`: ignored
   2154 * `Step4`: ignored
   2155 
   2156 Iterate through each object in the game. If the object is in the player's
   2157 inventory, call `PassCommands` setting `loop_item`.
   2158 
   2159 ### CT_Loop_Item_Char_Inventory
   2160 * `Step2`: character name (**case sensitive**)
   2161 * `Step3`: ignored
   2162 * `Step4`: ignored
   2163 
   2164 Iterate through each object in the game. If the object is in the specified
   2165 character's inventory, call `PassCommands` setting `loop_item`.
   2166 
   2167 ### CT_Loop_Rooms
   2168 * `Step2`: ignored
   2169 * `Step3`: ignored
   2170 * `Step4`: ignored
   2171 
   2172 Iterate through each room in the game, setting `loop_item`.
   2173 
   2174 ### CT_Loop_Exits
   2175 * `Step2`: room UUID (**can be parsed**) or name (case insensitive)
   2176 * `Step3`: ignored
   2177 * `Step4`: ignored
   2178 
   2179 If the specified room doesn't exists, doesn't do anything. Otherwise call
   2180 `PassCommands` settings `loop_item` with each exit of the room (including
   2181 disabled ones, i.e. the loop will be executed exactly 12 times, unless broken).
   2182 
   2183 ### CT_MultiMedia_InGroup
   2184 * `Step2`: name of the object (case insensitive)
   2185 * `Step3`: group name (**case sensitive**)
   2186 * `Step4`: ignored
   2187 
   2188 Works similarly to [CT_Item_InGroup](#CT_Item_InGroup).
   2189 
   2190 ### CT_Player_CustomPropertyCheck
   2191 * `Step2`: `custom_property_name`
   2192 * `Step3`: comparison type
   2193 * `Step4`: value
   2194 
   2195 [See for more info on CustomPropertyChecks](#CustomPropertyChecks). Since there
   2196 is only one player in rags, `Step2` only contains a custom property name. Colons
   2197 are treated as part of the property name!
   2198 
   2199 ### CT_Player_Gender
   2200 * `Step2`: `Male` / `Female` / anything else (**case sensitive**)
   2201 * `Step3`: ignored
   2202 * `Step4`: ignored
   2203 
   2204 If `Step2` is neither `Male` nor `Female`, it's treated as `Other`. Returns
   2205 whether the player's gender equals to `Step2`.
   2206 
   2207 ### CT_Player_In_Room
   2208 * `Step2`: room's UUID (**case sensitive**). Special values (not parsed, must
   2209   match exactly):
   2210   * `00000000-0000-0000-0000-000000000002` :: "void" room (crashes rags if you
   2211     actually manage to put the player here, so probably can be ignored)
   2212 * `Step3`: ignored
   2213 * `Step4`: ignored
   2214 * FIXUP: if `Step2` can't be parsed as an UUID, and there is a room with that
   2215   name, replace `Step2` with the room's UUID.
   2216 
   2217 Returns whether the player is currently in the specified room.
   2218 
   2219 ### CT_Player_In_RoomGroup
   2220 * `Step2`: room group or `None` (**case sensitive**)
   2221 * `Step3`: ignored
   2222 * `Step4`: ignored
   2223 
   2224 (If the player is not in a room, throws an exception.) Returns whether player's
   2225 current room's group's name equals to `Step2`.
   2226 
   2227 [See also CT_Character_In_RoomGroup](#CT_Character_In_RoomGroup).
   2228 
   2229 ### CT_Player_In_Same_Room_As
   2230 * `Step2`: character name (case insensitive)
   2231 * `Step3`: ignored
   2232 * `Step4`: ignored
   2233 
   2234 If the character doesn't exists, **throws an exception**. Returns whether the
   2235 player and the specified character is in the same room.
   2236 
   2237 ### CT_Player_Moving
   2238 * `Step2`: `Empty` / `North` / `South` / ... (**case sensitive**)
   2239 * `Step3`: ignored
   2240 * `Step4`: ignored
   2241 
   2242 If `Step2` is not a valid direction, **throws an exception**. Otherwise checks
   2243 whether the player currently moves in the specified direction (only in room's on
   2244 player leave events, otherwise the player is moving in the `Empty` direction).
   2245 
   2246 ### CT_Room_CustomPropertyCheck
   2247 * `Step2`: `room_uuid:custom_property_name` (case insensitive)
   2248 * `Step3`: comparison type
   2249 * `Step4`: value
   2250 
   2251 [See for more info on CustomPropertyChecks](#CustomPropertyChecks). `room_uuid`
   2252 can be `<CurrentRoom>` (**case sensitively!**) to mean the player's room,
   2253 otherwise it is a room uuid.
   2254 
   2255 ### CT_Timer_CustomPropertyCheck
   2256 * `Step2`: `timer_name:custom_property_name` (case insensitive)
   2257 * `Step3`: comparison type
   2258 * `Step4`: value
   2259 
   2260 [See for more info on CustomPropertyChecks](#CustomPropertyChecks). `timer_name`
   2261 is a name of a timer.
   2262 
   2263 ### CT_Variable_Comparison {#CT_Variable_Comparison}
   2264 * `Step2`: variable name + optional indices (case insensitive)
   2265 * `Step3`: `Equals` / `Not Equals` / `Greater Than` / `Greater Than or Equals` /
   2266   `Less Than` / `Less Than or Equals` / `Contains` / `DayOfWeek Is` / `Hour
   2267   Equals` / `Hour Is Greater Than` / `Hour Is Less Than` / `Minute Equals` /
   2268   `Minute Is Greater Than` / `Minute Is Less Than` / `Seconds Equals` / `Seconds
   2269   Is Greater Than` / `Seconds Is Less Than` (**case sensitive**)
   2270 * `Step4`: value to compare with. **Double replaced!**
   2271 
   2272 Optional index handling: everything before the first `(` is the variable name,
   2273 without trimming. (No `(` -> full string is the variable name.) There is a `(`
   2274 and a `)` afterwards: everything between them is converted to int32, if fails
   2275 treat as no index. There is a second `(` and a `)` afterwards: do the same
   2276 conversion. Examples: `foo(0)(0)` -> name `"foo"`, indices 0 and 0. `foo((2)` ->
   2277 name `"foo "`, indices invalid and 2.
   2278 
   2279 If the variable does not exists, **returns true**. Here is what happens when the
   2280 number of indices specified doesn't match the variable type (invalid first and
   2281 valid second indices: 2 idx, but later exception due to out of range):
   2282 | Var type   | 0 idx   | 1 idx                          | 2 idx                 |
   2283 |------------|---------|--------------------------------|-----------------------|
   2284 | Num single | OK      | garbage/**exception**          | garbage/**exception** |
   2285 | Num 1D     | garbage | OK                             | **exception**         |
   2286 | Num 2D     | garbage | **exception**                  | OK                    |
   2287 | Str single | OK      | garbage/**exception**          | garbage/**exception** |
   2288 | Str 1D     | garbage | OK                             | **exception**         |
   2289 | Str 2D     | garbage | `System.Collections.ArrayList` | OK                    |
   2290 | DT single  | OK      | garbage/**exception**          | garbage/**exception** |
   2291 | DT 1D      | garbage | OK                             | **exception**         |
   2292 | DT 2D      | garbage | **exception**                  | OK                    |
   2293 
   2294 Explanations:
   2295 * Garbage in 0 idx column: rags doesn't store variable values in a union, and
   2296   selecting a different type in rags designer doesn't clear out the values
   2297   stored for other types. It also treats array and single as separate types.
   2298   Thus when not specifying any index, rags will use this single type's value
   2299   with whatever garbage value left behind. Scraps emulates this behavior.
   2300 * garbage/exception: similar to the previous. If the variable happen to have a
   2301   garbage array data, it will use it, otherwise exception. Scraps always throws
   2302   an exception in this case.
   2303 * `System.Collections.ArrayList`: rags will treat the variable as having this
   2304   value.
   2305 
   2306 If the index is out of range, it **throws an exception**. If `Step2` has an
   2307 index, the value used to store single number values (refer to garbage in 0 idx
   2308 column above) is **overwritten** with the current array cell value. (Unlike
   2309 `CT_Variable_To_Variable_Comparison`, this is done for every type, not just
   2310 numbers).
   2311 
   2312 If an unknown/invalid comparison type is specified, it **returns true**.
   2313 Different variable types support an arbitrary subset of the possible comparisons
   2314 (O=supported, X=not supported):
   2315 | Comparison                | Num | Str | DT |
   2316 |---------------------------|-----|-----|----|
   2317 | `Equals`                  | O   | O   | O  |
   2318 | `Not Equals`              | O   | O   | O  |
   2319 | `Greater Than`            | O   | O   | O  |
   2320 | `Greater Than or Equals`  | O   | X   | O  |
   2321 | `Less Than`               | O   | O   | O  |
   2322 | `Less Than or Equals`     | O   | X   | O  |
   2323 | `Contains`                | X   | O   | X  |
   2324 | `DayOfWeek Is`            | X   | X   | O  |
   2325 | `Hour Equals`             | X   | X   | O  |
   2326 | `Hour Is Greater Than`    | X   | X   | O  |
   2327 | `Hour Is Less Than`       | X   | X   | O  |
   2328 | `Minute Equals`           | X   | X   | O  |
   2329 | `Minute Is Greater Than`  | X   | X   | O  |
   2330 | `Minute Is Less Than`     | X   | X   | O  |
   2331 | `Seconds Equals`          | X   | X   | O  |
   2332 | `Seconds Is Greater Than` | X   | X   | O  |
   2333 | `Seconds Is Less Than`    | X   | X   | O  |
   2334 
   2335 In case of a number variable, `Step4` is converted to a double, if it fails it
   2336 **throws an exception**. In case of a string variable, formatting macros are
   2337 stripped from the variable's value ([see text.md for
   2338 details](text.md#strip-format)), but not from `Step4`. Comparison is case
   2339 insensitive. In case of a DateTime variable, normal comparisons are parsed with
   2340 .NET's super permissive parser, hour/minute/second checks are parsed as int32,
   2341 if it fails it **throws an exception**. DayOfWeek check is special, `Step4` is
   2342 one of `Sunday` / `Monday` / `Tuesday` / `Wednesday` / `Thursday` / `Friday` /
   2343 `Saturday` (checked case insensitively).
   2344 
   2345 Finally it returns the result of the comparison.
   2346 
   2347 ### CT_Variable_To_Variable_Comparison
   2348 * `Step2`: variable name + optional indices (case insensitive)
   2349 * `Step3`: `Equals` / `Not Equals` / `Greater Than` / `Greater Than or Equals` /
   2350   `Less Than` / `Less Than or Equals` (**case sensitive**)
   2351 * `Step4`: variable name + optional indices (case insensitive)
   2352 
   2353 If the variable specified by `Step4` doesn't exists, **throws an exception**. If
   2354 `Step4` is a DateTime variable, it is treated as having an empty string as
   2355 value. Here is what happens, when the number of indices specified in `Step4`
   2356 doesn't match the variable type:
   2357 | Var type | 0 idx   | 1 idx                          | 2 idx                 |
   2358 |----------|---------|--------------------------------|-----------------------|
   2359 | Single   | OK      | garbage/**exception**          | garbage/**exception** |
   2360 | 1D       | garbage | OK                             | **exception**         |
   2361 | 2D       | garbage | `System.Collections.ArrayList` | OK                    |
   2362 
   2363 See [the previous section for explanations](#CT_Variable_Comparison). Rags
   2364 converts number values to string, this is why you have
   2365 `System.Collections.ArrayList` even with numbers.
   2366 
   2367 If the variable specified by `Step2` doesn't exists or it is a DateTime
   2368 variable, it **doesn't return anything**. Here is what happens when the indices
   2369 don't match, this time for `Step2`:
   2370 
   2371 | Var type   | 0 idx   | 1 idx                          | 2 idx                 |
   2372 |------------|---------|--------------------------------|-----------------------|
   2373 | Num single | OK      | garbage/**exception**          | garbage/**exception** |
   2374 | Num 1D     | garbage | OK                             | **exception**         |
   2375 | Num 2D     | garbage | **exception**                  | OK                    |
   2376 | Str single | OK      | garbage/**exception**          | garbage/**exception** |
   2377 | Str 1D     | garbage | OK                             | **exception**         |
   2378 | Str 2D     | garbage | `System.Collections.ArrayList` | OK                    |
   2379 
   2380 If `Step3` does not have an allowed value (string variables only support
   2381 `Equals` and `Not Equals`), it **doesn't return anything**.
   2382 
   2383 In case of a number variable, the string representation of whatever that was
   2384 read from `Step4` is converted to a double, if this fails it is treated as
   2385 `0.0`. If `Step2` has an index, the value used to store single number values
   2386 (refer to garbage in 0 idx column above) is **overwritten** with the current
   2387 array cell value. (This does not happen with string vars!) Afterward, it returns
   2388 the value of the comparison.
   2389 
   2390 In case of a string variable, formatting macros are stripped from `Step2`'s
   2391 value ([see text.md for details](text.md#strip-format)), but not from `Step4`,
   2392 then they're compared for equality. (Less than, greater than not supported).
   2393 
   2394 ### CT_Variable_CustomPropertyCheck
   2395 * `Step2`: `variable_name:custom_property_name` (case insensitive)
   2396 * `Step3`: comparison type
   2397 * `Step4`: value
   2398 
   2399 [See for more info on CustomPropertyChecks](#CustomPropertyChecks).
   2400 `variable_name` is a name of a variable and can contain indices, but they're
   2401 ignored.
   2402 
   2403 [text replacement function]: text.md#replacement