//============================================================================
//
// Name: CS Resting Subsystem - Main Include File
// File: cs_rest
// Author: Craig Smith (Galap) <craig@smith.dropbear.id.au>
//
// $Id: cs_rest.nss,v 1.7 2004/12/29 10:17:07 cs Exp $
// $Source: /local/cvs/nwn/resting/cs_rest.nss,v $
//
//----------------------------------------------------------------------------
// This software is distributed in the hope that it will be useful. It is
// provided "as is" WITHOUT WARRANTY OF ANY KIND, either expressed or implied,
// including, but not limited to, the implied warranties of merchantability
// and fitness for a particular purpose. You may redistribute or modify this
// software for your own purposes so long as all original credit information
// remains intact.
//----------------------------------------------------------------------------
//
// Introduction
// ------------
// The CS Resting Subsystem provides a framework for setting various popular
// forms of resting rules in a module, and allows these resting rules to be
// changed dynamically while the module is running.
//
// Installing The CS Resting Subsystem
// -----------------------------------
// To install the CS Resting Subsystem, you need to import the scripts and
// blueprints into your module. Open the module in the Aurora Toolset and
// select the File menu. Then select the Import item from the menu and
// navigate through your computer's directory structure until you locate
// the ERF file for the CS Resting Subsystem. Select the ERF file and click
// on the Ok button. If the toolset warns you about missing files with a .ncs
// extension, ignore the warning and continue--the .ncs files are compiled
// script files and will be created if necessary when you build your module.
// Once the import finishes, you have installed the CS Resting Subsystem!
//
// To use the CS Resting Subsystem in a module, you will need to change
// the module's OnPlayerRest event handler to be the "cs_rest_handler"
// script. To do this, pull down the Edit menu and select Module Properties.
// Select the Events tab and either drop down the list for the OnPlayerRest
// event to scroll through and find "cs_rest_handler", or simply delete the
// script name that is already there and type in "cs_rest_handler" in place.
// Then select the Ok button and you're done.
//
// Configuring Resting
// -------------------
// One of the design goals of the CS Resting Subsystem package is that the
// resting rules must be able to be managed and configured completely
// through the standard toolset interface *without* the need to resort to
// editing scripts. It is of course possible to extend the resting subsystem
// through writing your own resting profiles or user scripts, however if you
// choose to do so, you will also need to manage any configuration for your
// own resting extension. For most situations, however, the standard resting
// subsystem should be sufficient.
//
// To this end, configuration is handled through the use of a container and
// special items. I personally use chests but any placeable object with an
// inventory will work. Place the container object somewhere in your module
// and then change its tag to be "CS_REST_GLOBAL_CONFIG". Since this container
// is accessed by tag, it may be placed anywhere in the module though it
// should be placed somewhere that players cannot access. I recommend creating
// a DM room area and placing things like this global config container there.
//
// Once you have a placed the container with the correct tag, you now need
// to place the custom configuration items into the container, and modify
// any of the item tags as required for your needs. Items that represent
// integer values must have their tags set to the integer itself. Some other
// items require their tags to be set to strings such as the names of scripts.
//
// Once you have created the configuration container and put all of the desired
// configuration items into its inventory, the resting subsystem will function
// as specified by the items. This particular container is referred to as the
// global configuration container, but the resting subsystem allows the use of
// other configuration containers as well (see Other Configuration Facilities
// for more details). The custom configuration items are found under the
// "Special|Custom4" category of the Custom items palette.
//
// NOTE: If you don't place the global configuration container and set the
// "CS Rest: Enable" and "CS Rest: Default Profile" items as the absolute
// minimum configuration, the CS Resting Subsystem will *not* function and
// the standard Bioware default resting will remain in place.
//
// The complete list of available items, with descriptions, follows.
//
// CS Rest: Enable
//     Default value: FALSE
//     Enabled the resting subsystem, enforcing the rules as configured through
//     the use of these items. The resting subsystem is *disabled* by default
//     and one of these items *must* be placed in a configuration container
//     before it will function. To use this item, you simply place it in the
//     configuration container's inventory. No change to this item's tag is
//     required. This item is the opposite of the Disable item below and only
//     one of the two may be used at the same time.
// CS Rest: Disable
//     Default value: TRUE
//     Disable the resting subsystem, returning the player to the standard
//     Bioware resting rules if placed in the global configuration container,
//     or just causing an area or trigger configuration container to be
//     ignored. To use this item, you simply place it in the configuration
//     container's inventory. No change to this item's tag is required. This
//     item is the opposite of the Enable item above and only one of the two
//     may be used at the same time.
// CS Rest: Default Profile
//     Default value: Not set
//     Sets the name of the resting profile script to be executed by default
//     when a player is not within a resting trigger. The tag of this item
//     should be set to the name of one of the standard profile scripts:
//         cs_rest_cnt  This script disallows resting completely
//         cs_rest_cmp  This script allows resting
//         cs_rest_rom  This script only allows resting when in a room with
//                      all doors closed.
//     One of these items *must* be placed in the global config container with
//     its tag set correctly or the resting subsystem will fail to operate in
//     an expected manner. It it of course possible to write your own scripts
//     for handling resting profile. See Creating Your Own Resting Profile
//     for further information, though please note that there is no need to
//     create new resting profiles in most situations (and most probably *all*
//     situations, since I can't think of any more).
// CS Rest: Standard HP
//     Default value: TRUE
//     Use the standard Bioware-style of HP regain and allow the player to
//     regain all lost hit points after resting. To use this item, you simply
//     place it in the configuration container's inventory. No change to this
//     item's tag is required. This item is the opposite of the 3rd Ed Style HP
//     item below and only one of the two may be used at the same time.
// CS Rest: 3rd Ed Style HP
//     Default value: FALSE
//     Use a style of HP gain modelled on the 3rd edition D&D manuals, wherein
//     players regain one hit point per level each time they rest. If resting
//     is interrupted part way through, the player will regain hit points in
//     a pro-rata fashion, i.e. if resting was half complete, the player will
//     regain half the number of hit points that they would have otherwise
//     To use this item, you simply place it in the configuration container's
//     inventory. No change to this item's tag is required. This item is the
//     opposite of the Standard HP item above and only one of the two may be
//     used at the same time.
// CS Rest: Day Only
//     Default value: FALSE
//     Sets the resting subsystem to allow resting only during daylight hours.
//     To use this item, you simply place it in the configuration container's
//     inventory. No change to this item's tag is required. Note that this
//     item is the opposite of the Night Only item below. Only one of the two
//     items may be used at the same time. The All Hours below item may be
//     used to reset either Day Only or Night Only operation.
// CS Rest: Night Only
//     Default value: FALSE
//     Sets the resting subsystem to allow resting only during night hours.
//     To use this item, you simply place it in the configuration container's
//     inventory. No change to this item's tag is required. Note that this
//     item is the opposite of the Night Only item above. Only one of the two
//     items may be used at the same time. The All Hours item below may be
//     used to reset either Day Only or Night Only operation.
// CS Rest: All Hours
//     Default value: TRUE
//     Sets the resting subsystem to allow resting at any time of the day or
//     night. This item may be used after setting day only or night only
//     operation to restore the resting subsystem to its default operation. To
//     use this item, you simply place it in the configuration container's
//     inventory. No change to this item's tag is required. Note that this
//     item should not be used at the same time as either the Day Only or
//     Night Only items. Remove such items from the configuration container's
//     inventory before placing this item.
// CS Rest: Day Start
//     Default value: 6
//     Sets the hour of the day at which the resting subsystem considers "day"
//     to begin. When the resting subsystem is operating in Day Only mode, it
//     will only allow resting after this hour, or only before this hour if
//     in Night Only mode. The Day Start value may be set to any integer
//     between 0 and 22 inclusive. To use  this item, you must set its
//     tag to an integer value.
// CS Rest: Day End
//     Default value: 18
//     Sets the hour of the day at which the resting subsystem considers "day"
//     to end. When the resting subsystem is operating in Day Only mode, it
//     will only allow resting before this hour, or only after this hour if in
//     Night Only mode. The Day End value may be set to any integer between
//     1 and 23 inclusive.  To use  this item, you must set its tag to an
//     integer value.
// CS Rest: Armour Not Allowed
//     Default value: FALSE
//     Stops players from resting when then are wearing rigid armour or
//     helmets. Armour of AC 5 or less may be worn while resting. To use
//     this item, you simply place it in the configuration container's
//     inventory. No change to this item's tag is required. Note that this
//     item is the opposite of the Armour Allowed item below. Only one of the
//     two items may be used at the same time.
// CS Rest: Armour Allowed
//     Default value: TRUE
//     Allow player to rest regardless of the armour they are wearing. To
//     use this item, you simply place it in the configuration container's
//     inventory. No change to this item's tag is required. Note that this
//     item is the opposite of the Armour Not Allowed item above. Only one
//     of the two items may be used at the same time.
// CS Rest: Weapons Not Allowed
//     Default value: FALSE
//     Stops players from resting when then are carrying weapons. To use this
//     item, you simply place it in the configuration container's inventory.
//     No change to this item's tag is required. Note that this item is the
//     opposite of the Weapons Allowed item below. Only one of the two items
//     may be used at the same time.
// CS Rest: Weapons Allowed
//     Default value: TRUE
//     Allow player to rest regardless of whether they are armed or not. To
//     use this item, you simply place it in the configuration container's
//     inventory. No change to this item's tag is required. Note that this
//     item is the opposite of the Weapons Not Allowed item above. Only one
//     of the two items may be used at the same time.
// CS Rest: Fade Screen
//     Default value: FALSE
//     Fade the player's screen display to black when resting starts, and fade
//     back to a visible screen again when resting finishes.
// CS Rest: 1 Minute Limit
// CS Rest: 2 Minute Limit
// CS Rest: 3 Minute Limit
// CS Rest: 4 Minute Limit
// CS Rest: 5 Minute Limit
// CS Rest: 6 Minute Limit
// CS Rest: 7 Minute Limit
// CS Rest: 8 Minute Limit
// CS Rest: 9 Minute Limit
// CS Rest: 10 Minute Limit
// CS Rest: 12 Minute Limit
// CS Rest: 14 Minute Limit
// CS Rest: 16 Minute Limit
// CS Rest: 18 Minute Limit
// CS Rest: 20 Minute Limit
// CS Rest: 25 Minute Limit
// CS Rest: 30 Minute Limit
//     Default Value: No Limit
//     All of these items may be used to control how frequently players may
//     rest. Pick the item that matches the time limit you wish to set and
//     place it into the configuration container. No change to the item's tag
//     is necessary. You may only use one of these items at a time, and you
//     may not use any of these items at the same time as the Rest Time Limit
//     or No Limit items below.
// CS Rest: Rest Time Limit
//     Default Value: No Limit
//     Use this item if you wish more fine-grained control over the time limit
//     between resting. Place this item in a configuration container and then
//     change its tag to an integer value representing the number of seconds
//     that players must wait before being allowed to rest again. You may not
//     use this item at the same time as any of the time limit items above, or
//     the No Limit item below.
// CS Rest: No Limit
//     Default value: No Limit
//     Use this item to restore the default unlimited resting behaviour like
//     the standard Bioware resting system. To use this item, you simply place
//     it in the configuration container's inventory. No change to this item's
//     tag is required. You may not use this item at the same time as any of
//     the time limit items or the Rest Time Limit item above.
// CS Rest: Bedroll In Use
//     Default value: FALSE
//     Use bedrolls if the player has one in his or her inventory on resting.
//     When bedrolls are in use, the player only regains full hit points, as
//     per the regain style in use, when they have a bedroll. If they do not
//     have a bedroll, then only half the normal number of hit points will be
//     regained. Bedrolls are consumable items and after being used 20 times,
//     they are destroyed. To use this item, you must place it in the
//     configuration container's inventory and change its tag to be the same
//     as the item that is to be used as a bedroll, e.g. if you have an item
//     with a tag of "BEDROLL" that you intend for players to use as a bedroll,
//     then you must set the tag of *this* item to "BEDROLL" as well. This
//     item is the opposite of the Bedroll Not Used item below and you may
//     not use the two items at the same time. Note that there is not yet any
//     persistence used for storing the number of times a bedroll has been
//     used.
// CS Rest: Bedroll Not Used
//     Default value: TRUE
//     Allow players to rest without bedrolls with no penalty to hit point
//     regain. To use this item, you simply place it in the configuration
//     container's inventory. No change to this item's tag is required. This
//     item is the opposite of the Bedroll In Use item above and you may not
//     use the two items at the same time.
// CS Rest: Bedroll HP multiplier
//     Default value: 50
//     This item sets the numerical modifier applied to hit points when
//     bedrolls are in use and the player does not have one. If the player
//     does not have a bedroll when resting, the hitpoints regained will be
//     multiplied by the value set with this item to determine the final
//     number of hit points regained. To define this value, set the item's
//     tag to the pecentage number for the multiplier, e.g. for half hit
//     points, use 50, for a third use 33, etc. By default, this value is 50,
//     so players will regain half the normal hit points when resting without
//     a bedroll. If you were to set this value to 25, then players would
//     regain a quarter of the number of hitpoints, and setting this value to
//     100 would mean that bedrolls would have no effect at all (which is
//     kind of silly but nevertheless will work). Setting this value to 0
//     will stop players from being allowed to rest at all if they do not
//     have a bedroll.
// CS Rest: HP Multiplier
//     Default value: 100
//     This item sets a numerical value that is used as a multiplier against
//     the number of hit points that a player would normally regain from
//     resting *when non-standard rest modes are in use*, i.e. the number
//     of hitpoints is calculated first, and then multiplied by the value
//     set with this item to determine the final number of hit points regained.
//     To define this value, set the item's tag to the percentage number for
//     the multiplier, e.g. for half hit points, use 50, etc. By default,
//     this value is 100 (i.e. 100% or full hit points) and will have no effect
//     on the hit points regained by players. If you were to set this value to
//     200, then players would regain twice the number of hitpoints, setting
//     the value to 300 would give three times the normal number, and 50 will
//     give half. Note that if you are using the standard HP regain policy,
//     as set by the Standard HP item above (or if you set no item for HP
//     regain at all) then this item is ignored as the standard regain policy
//     returns the player the most hit points possible.
// CS Rest: Use CON Bonus
//     Default value: FALSE
//     Use this item to give players additional hit points equal to their CON
//     bonus *when non-standard rest modes are in use*. Note that if you are
//     using the standard HP regain policy, as set by the Standard HP item
//     above (or if you set no item for HP regain at all) then this item is
//     ignored as the standard regain policy returns the player the most hit
//     points possible anyway. The CON bonus hit points are applied *after*
//     the calculation of the multiplier value, so if 3rd edition style HP
//     regain is enabled with a multipler of 2, players will receive hit
//     points equalling (level * 2) + CON Bonus. This item is the opposite of
//     the No CON Bonus item below and you may not use both items at the same
//     time.
// CS Rest: No CON Bonus
//     Default value: TRUE
//     Use this item to disable the addition of CON bonus hit points when a
//     non-standard hit point regain policy is in use. This item is the
//     opposite of the Use CON Bonus item above and you may not use both
//     items at the same time.
// CS Rest: User Script
//     Default value: Not set
//     Sets the name of the user script to be executed at specific points in
//     the resting process. The tag of this item should be set to the name
//     of the script to be executed. There are no standard user scripts
//     provided with the CS Resting Subsystem as by definition all customised
//     features must be provided by users of the subsystem. See Writing User
//     Scripts for more information.
// CS Rest: Text Messages
//     Default value: TRUE, with built in messages from cs_rest_text script
//     This item will enable the display of floaty text messages, and allows
//     the text of the messages to be changed. Once an instance of this item
//     has been placed in the relevant configuration container, edit the item
//     properties and click on the Description tab, then select the Variables
//     button if you wish to change the text of the messages. The item
//     contains all of the default messages already, so simply select the one
//     to be changed, and change or overwrite the message that is displayed in
//     the text field at the bottom of the dialogue box. Select the Replace
//     button to update the message variable on the item, and then repeat
//     for any other messages that require change. Then click Ok to save the
//     changes to the item. The names of the variables, and explanation of
//     when the corresponding messages are displayed, are:
//         Variable Name                Displayed When
//         -------------                --------------
//         cs_rest_startText            A player starts resting.
//         cs_rest_finishText           A player finishes resting properly.
//         cs_rest_cancelText           A rest attempt is interrupted and
//                                      so is unfinished. The player concerned
//                                      will regain a portion of normal HP.
//         cs_rest_cannotText           A player may not rest due to rule
//                                      restrictions.
//         cs_rest_tooSoonText          A time limit rule is in force and the
//                                      player has not waited long enough.
//         cs_rest_dayOnlyText          The Day Only rest rule is in force and
//                                      a player tries to rest at night.
//         cs_rest_nightOnlyText        The Night Only rest rule is in force
//                                      and a player tries to rest during the
//                                      day.
//         cs_rest_noArmourText         The No Armour rest rule is in force
//                                      and the player is wearing armour, a
//                                      helmet or carrying a shield.
//         cs_rest_noWeaponText         The no Weapons rest rule is in force
//                                      and the player is carrying a weapon.
//         cs_rest_bedrollRuinedText    A bedroll is used for the last time
//                                      before it becomes useless. The player
//                                      gains a full rest, but must then
//                                      replace the bedroll.
//         cs_rest_notComfortable       The Bedroll rest rule is in force, the
//                                      player has no bedroll, but the bedroll
//                                      multiplier is set to a value > 0.
//         cs_rest_enterZoneText        The player enters a Safe Camp resting
//                                      trigger.
//         cs_rest_exitZoneText         The player leaves a Safe Camp resting
//                                      trigger.
//         cs_rest_unsafeText           A player tries to rest within a Safe
//                                      Room trigger without closing all doors.
//         cs_rest_enterNoZoneText      The player enters a No Rest resting
//                                      trigger.
//         cs_rest_exitNoZoneText       The player leaves a no Rest resting
//                                      trigger.
//         cs_rest_noBedrollText        The Bedroll rest rule is in force, the
//                                      player has no bedroll, and the bedroll
//                                      multiplier is set to 0.
//     When a text message is displayed, any occurances of the string "<MIN>"
//     will be replaced with the number of minutes remaining until the player
//     will be permitted to rest again. This is really only useful in the
//     "cs_rest_tooSoonText" variable. In addition, any variable that is set
//     to the string "off" will not be displayed. This allows the individual
//     messages to be controlled. This item is the opposite of the No Messages
//     item below and you may not use both items at the same time.
// CS Rest: No Messages
//     Default value: FALSE
//     Use this item to disable the display of any floaty text messages. This
//     item is the opposite of the Text Messages item above and you may not use
//     both items at the same time.
// CS Rest: Effects
//     Default value: FALSE
//     Use this item to apply visual effects to resting players. Once a copy of
//     this item has been placed in the relevant configuration container, edit
//     the item properties and click on the Description tab, then select the
//     Variables button to change the effects to be applied. The item already
//     has the necessary variable, which is named "cs_rest_effects", set though
//     with an empty value. Select the variable name in the list, and enter
//     the numeric effect constant values desired in the text field at the
//     bottom of the dialogue box, separated by commas. Select the Replace
//     button to update the variable on the item, then click Ok to save the
//     changes to the item. The numeric effect constants can be determined by
//     opening the script named "nwscript" in the script editor and searching
//     for "VFX". Don't forget to select the "All Resources" radio button.
//     I'd list them all here, but there are many hundreds and most are not
//     very useful for resting purposes. Some effects that may be quite useful
//     for resting are:
//         Description          Constant Name           Value
//         -----------          -------------           -----
//         Darkness             VFX_DUR_DARKNESS        1
//         Sleep                VFX_IMP_SLEEP           94
//     As an example, to set darkness and sleep effects on a player during
//     resting, use a value of "1,94" for the "cs_rest_effects" variable
//     on the item. Note that these are *visual* effects only, and some may
//     run for fixed durations, finishing either before or after the resting
//     period.
// CS Rest: Enable Debug
//     Default value: FALSE
//     Use this item to enable debug output from the resting subsystem. The
//     debug output will be written to the message window of the player who
//     is resting, and to server log. Debug is managed with the CS Debug
//     subsystem, a separate package for displaying debug information. A
//     minimal set of scripts from the CS Debug package is included with the
//     CS Resting Subsystem.
//
// Using The Resting Triggers
// --------------------------
// The resting triggers track the entry and exit of players, and apply specific
// resting rules while a player is within the bounds of the trigger. The three
// standard triggers provided with the CS Resting Subsystem are the Camp
// trigger, the Room trigger and the No Rest trigger.
//
// NOTE: you do not need to use the triggers to make use of the resting
// subsystem, however in many instances the triggers are easier and more
// flexible to work with. Should you wish to block resting in most parts of
// an area and only allow resting at specific points, such as campsites, then
// using Camp triggers is the logical approach. Likewise, if you only wish to
// stop players from resting at specific points, such as within a boss room,
// then the No Rest trigger is the perfect tool.
//
// Placing a resting trigger basically involves drawing one somewhere in an
// area and setting any configuration necessary. The following steps provide
// a minimal guide to creating a Camp Trigger.
//
// 1/. Select the "CS Camp Resting Trigger" trigger from the "Generic
//     Triggers" category of the Custom triggers palette.
// 2/. Draw a trigger polygon somewhere in an area, such as around a campfire
//     tile.
// 3/. Edit the properties of the trigger (right click within the polygon
//     and select "Properties" from the menu that is displayed) and change
//     the Tag field to something sensible for your module. If you place
//     more than one trigger with the same tag, they will all use the same
//     configuration. This feature allows a module to have resting triggers
//     that provide different combinations of resting rules.
// 4/. If you wish, create a container and place configuration items in
//     its inventory to modify the operation of the trigger (see the section
//     on configuring the CS Resting Subsystem for more details).
//
// You now have a functioning rest trigger. Save your module and load
// it up in Neverwinter Nights to see what happens, however unless you have
// placed a configuration container and changed the default operation, the
// trigger won't actually do anything different to standard Bioware resting.
//
// Other Configuration Facilities
// ------------------------------
// It is possible however to configure resting triggers differently to the
// global rules defined in the global configuration container. You do this
// by creating another container and setting its tag to match that of the
// trigger in question with the string "_REST_CONFIG" appended, i.e. if the
// trigger has a tag of "NoRestZone", the container's tag must be set to
// "NoRestZone_REST_CONFIG". If the tag is not set correctly, the trigger
// won't be able to find the container so be sure to double check this.
// Note that the length of the tag set on the trigger may not be more than
// 20 characters, since "_REST_CONFIG" is 12 characters. Tags are may a maximum
// of 32 characters, so 32 - 12 leaves 20 for the trigger tag.
//
// Once you create a configuration container that is specific to a trigger,
// any configuration items that you place in it will apply only to triggers
// with matching tags (and to *all* triggers with matching tags).
//
// But wait, there's more! It is also possible, in exactly the same way, to
// apply a set of resting rules to a specific area! Say you create an area
// and set its tag to be "BigBadForest". Create a container, set its tag to
// be "BigBadForest_REST_CONFIG" and place some configuration items in the
// container. This particular resting configuration will now apply to all
// players whenever they rest within that area. If however, there is a
// trigger within the area that has a different set of rules, the trigger's
// own rules will apply should the player rest within it.
//
// When a player rests, the resting subsystem will apply the most specific
// rules that it can find based on where the player actually rests. First
// a check is made to see if the player is within a trigger, then the area
// is checked for a specific config container, and finally the global config
// container is checked. It is important to note that this search is performed
// for each configuration item, not just for the configuration container itself
// and so if an item is not present in a trigger's configuration container,
// but *is* present in the global container or an area container, the resting
// subsystem will still apply it to the player! If you wish a specific rest
// rule to apply, then be careful which container you put it in.
//
// A Worked Example Of Setting Up The CS Resting Subsystem
// -------------------------------------------------------
// Ok, lets take a closer look at precisely how to make use to the CS Resting
// Subsystem. Let's say that you're building a module containing three areas: a
// city, an inn and a forest. You decide that there should be limited resting
// available in the forest, but no resting should be possible in the city
// itself since players are supposed to rest in the inn. Within the inn, full
// resting operates.
//
// First set the module's OnPlayerRest event script to "cs_rest_handler".
// Pull down the Edit menu and select Module Properties. Select the Events
// tab and either drop down the list for the OnPlayerRest event to scroll
// through and find "cs_rest_handler", or simply delete the script name
// that is already there and type in "cs_rest_handler" in place. Now select
// the Ok button and the module's rest script is set.
//
// Now create three areas and set their tags to be "TheCity", "TheInn" and
// "BigBadForest". Since the configuration containers for the resting subsystem
// should not be accessible to players, create a fourth area specifically to
// hold the configuration containers. DMs can jump to this area if necessary,
// and when editing the module in the toolset the location of the containers
// is irrelevant. For this module, the global resting rules will apply to the
// forest area while the city and inn have their own specific configuration.
//
// Create three containers and place them in the fourth area. Set their tags to
// be "CS_REST_GLOBAL_CONFIG", "TheCity_REST_CONFIG" and "TheInn_REST_CONFIG".
// Place configuration items into the three containers as indicated below:
//
// Container tagged "CS_REST_GLOBAL_CONFIG":
//     CS Rest: Enable
//     CS Rest: 3rd Ed Style HP
//     CS Rest: Default Profile; set this item's tag to "cs_rest_cmp"
//     CS Rest: 4 Minute Limit
//
// Container tagged "TheCity_REST_CONFIG":
//     CS Rest: Default Profile; set this item's tag to "cs_rest_cnt"
//
// Container tagged "TheInn_REST_CONFIG":
//     CS Rest: Standard HP
//     CS Rest: Armour Not Allowed
//     CS Rest: No Limit
//
// We're done! You now have a module configured to use the CS Resting Subsystem
// instead of the Bioware default resting. In the forest area, and by extension
// any others areas you may add to this module, players may rest once every 4
// minutes and will regain 1 hit point per level. No resting is permitted in
// the city area at all, and players may rest as often as they wish, and regain
// full hit points each time, in the inn but they will have to remove their
// armour to do it.
//
// Notice how not all configuration items need to be set in each container.
// When the resting subsystem is looking for a particular rule, such as whether
// to allow resting in armour, it looks first in any trigger-specific
// container that exists, then it looks in the area-specific container, and
// then it checks the global container. The first item found sets the rule for
// that resting session for that player. If no item is found, the default
// operation is assumed. In this example, you will see that the inn area's
// configuration container does not need to contain a CS Rest: Default Profile
// item since it can use the same script that is set in the configuration for
// the global container. It does however need to have the CS Rest: No Limit
// item to reset resting to unlimited times otherwise the 4 minute limit set
// in the global container would also apply.
//
// Writing User Scripts
// --------------------
// A user script can be used to add module-specific resting rules to those
// evaluated when determining whether a player should be permitted to rest.
// User scripts can also allow interoperation with other systems, such as
// a wandering monster system, and can be used to implement plot-specific
// logic that is triggered when players rest. User scripts are not required
// for normal use of the resting subsystem, but provide instead a powerful
// facility for extending the resting subsystem to meet specific module
// requirements. Some scripting knowledge is required to properly make use
// of the user scripts facility.
//
// A given user script will be executed potentially at four points during the
// rest cycle for a player: when rules are being evaluated to determine whether
// the player may rest, when the player starts to rest, when the player
// finishes resting, and when resting is cancelled for any reason. Only one of
// finish resting or cancel resting will be executed, and if a player is not
// permitted to rest due to not satisfying an active rest rule, then the user
// script will only be executed for the rule evaluation stage. User scripts
// are always executed with the player who is attempting to rest as the
// current object (i.e. OBJECT_SELF within the user script will refer to
// the player).
//
// When the resting subsystem is determining a player's eligibility to rest,
// a set of builtin rules are evaluated against the player. These rules may
// be enabled through configuration and are things like No Armour, Day Only,
// etc. Each of the rules that has been enabled will be checked and the result
// stored for the rest session. Once all rules have been evaluated, the player
// is only permitted to rest if every single rule allows it.
//
// When a user script is executed for evaluation of rest rules, the builder
// may add additional rules for the module. The builder may also override
// the evaluation of the builtin rules by performing additional checks and
// potentially changing the previously calculated result for a rule. For
// example, the No Weapons rule may be enabled, which will prevent a player
// from resting while holding a weapon. The module builder may however decide
// that daggers are small enough to be no hinderance and so would like to
// allow resting if the equipped weapon is a dagger. When a player rests
// with an equipped dagger, the resting subsystem builtin rule will fail, thus
// indicating that resting is not permitted. A user script may then be written
// that performs an additional check on the weapon to see if it is a dagger.
// If so, the previously calculated result of the builtin rule can be reset,
// allowing the player to rest. Of course, if the player fails any other
// rule, she will still not be allowed to rest anyway.
//
// The other execution points for the user script, i.e. when resting starts,
// finishes or is cancelled, are provided specifically for adding custom
// module-specific functionality rather than interacting with the resting
// subsystem itself. The user script may initiate a cutscene when resting
// finishes for example, or generate a random wandering monster encounter
// when resting starts. These execution points are provided for use by builders
// when integrating the resting subsystem into other aspects of a module.
//
// In structure, a user script closely resembles a standard NWN user-defined
// event handler script. Each execution point is numbered and the script calls
// a function, cs_rest_GetUserScriptState(), to determine the execution point
// for which it has been executed. This function will return one of the
// following constants:
//     CS_REST_START_RESTING
//     CS_REST_STOP_RESTING
//     CS_REST_CANCEL_RESTING
//     CS_REST_RULE_CHECK
//
// Once the execution point number has been obtained, a set of if statements
// or a switch statement is used to perform the necessary functionality. When
// the execution point number is CS_REST_RULE_CHECK, the following functions
// may also be used:
//     cs_rest_GetIsRuleEnabled     returns TRUE if a specific rule is
//                                  enabled in configuration.
//     cs_rest_GetRule              returns TRUE if a specific rule has
//                                  has been failed (i.e. the player may
//                                  not rest.
//     cs_rest_SetRule              used to force a specific rule to a
//                                  given state (TRUE will cause the player
//                                  to be unable to rest).
//     cs_rest_SetOverrideProfile   changes the profile that will be used
//                                  for the current rest session *only*.
//                                  The next time the player rests, the
//                                  profile will automatically revert to
//                                  that which is active for the player
//                                  (should the player be inside a trigger)
//                                  or the default profile if the player
//                                  has no profile.
//
// These functions allow the user script to control the rest rules. The rest
// rules are indicated by using the following constants when calling the
// functions above:
//     CS_REST_RULE_LAST_REST
//     CS_REST_RULE_DAY_ONLY
//     CS_REST_RULE_NIGHT_ONLY
//     CS_REST_RULE_NO_ARMOUR
//     CS_REST_RULE_NO_WEAPONS
//     CS_REST_RULE_NO_BEDROLL
//     CS_REST_RULE_USER_1
//     CS_REST_RULE_USER_2
//     CS_REST_RULE_USER_3
//     CS_REST_RULE_USER_4
//     CS_REST_RULE_USER_5
//     CS_REST_RULE_USER_6
//     CS_REST_RULE_USER_7
//     CS_REST_RULE_USER_8
//     CS_REST_RULE_USER_9
//     CS_REST_RULE_USER_10
//     CS_REST_RULE_USER_11
//     CS_REST_RULE_USER_12
//     CS_REST_RULE_USER_13
//     CS_REST_RULE_USER_14
//     CS_REST_RULE_USER_15
//     CS_REST_RULE_USER_16
//
// These constants identify the builtin rest rules provided by the resting
// subsystem, and also the 16 user rules that are available for use by builders
// within user scripts. The user rules will be honoured by the resting
// subsystem in exactly the same way as the builtin rules, with the single
// exception that the resting subsystem will never change the state of a user
// rule. All user rules are always enabled, and will always be FALSE (i.e.
// the player is allowed to rest) when the user script is executed. Changing
// any user rule to a state of TRUE through the cs_rest_SetRule() function
// will prevent the player from resting.
//
// When the execution point is CS_REST_START_RESTING, the following functions
// may also be used:
//     cs_rest_SetAbort             Causing the rest process to be aborted
//                                  as if it had not started. This allows
//                                  last minute decisions to be made as to
//                                  whether a player may rest, despite the
//                                  player having successfully satisfied all
//                                  enabled rest rules.
//     cs_rest_SetOverrideProfile   changes the profile that will be used
//                                  for the current rest session *only*.
//                                  The next time the player rests, the
//                                  profile will automatically revert to
//                                  that which is active for the player
//                                  (should the player be inside a trigger)
//                                  or the default profile if the player
//                                  has no profile.
//
// Consult the help text for each function for further details, and read
// through the sample code in An Example User Script. The script named
// cs_rest_sample is  template script that may used as a starting point for
// user scripts. The script named cs_rest_wndmonst is a functional example
// that demonstrates how the Bioware HotU wandering monster system may be
// integrated into the resting subsystem.
//
// An Example User Script
// ----------------------
// The sample script in this section shows how to implement the example
// presented in Writing User Scripts.
//
// #include "cs_rest"
// void main() {
//     int state = cs_rest_GetUserScriptState();
//     if (state == CS_REST_START_RESTING) {
//     }
//     else if (state == CS_REST_STOP_RESTING) {
//     }
//     else if (state == CS_REST_CANCEL_RESTING) {
//     }
//     else if (state == CS_REST_RULE_CHECK) {
//         // Has the No Weapons rule actually been enabled?
//         if (cs_rest_GetIsRuleEnabled(CS_REST_RULE_NO_WEAPONS)) {
//             // Retrieve the items in the resting player's hands.
//             object right = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND);
//             // If the player has a dagger in her right hand, we have to
//             // check a little more carefully before resetting the No
//             // Weapons rule and allowing her to rest.
//             if ((GetBaseItemType(right) == BASE_ITEM_DAGGER) {
//                 // If the player has nothing in her left hand, or a dagger,
//                 // a shield, or a torch, she's ok to rest for this rule.
//                 // Anything else will be assumed to be another weapon
//                 // and so the resting state for the No Weapons rule will
//                 // be left unchanged.
//                 int left = GetBaseItemType(
//                     GetItemInSlot(INVENTORY_SLOT_LEFTHAND)
//                 );
//                 if ((left == BASE_ITEM_INVALID) ||
//                     (left == BASE_ITEM_TORCH) ||
//                     (left == BASE_ITEM_SMALLSHIELD) ||
//                     (left == BASE_ITEM_LARGESHIELD) ||
//                     (left == BASE_ITEM_TOWERSHIELD) ||
//                     (left == BASE_ITEM_DAGGER)
//                 ) {
//                     // The player only has a dagger or an allowed item
//                     // equipped, so the No Weapon rule can be reset.
//                     // Note that shields are checked as part of the
//                     // No Armour rule, not the No WSeapons rule.
//                     cs_rest_SetRule(CS_REST_RULE_NO_WEAPONS, FALSE);
//                 }
//             }
//         }
//     }
// }
//
// Creating Your Own Resting Profile
// ---------------------------------
// A resting profile sets the fundamental operation of the resting subsystem.
// Resting profiles are represented by scripts that call the basic functions
// of the resting subsystem in response to the engine's rest events, sometimes
// performing additional checks (such as the safe room profile used by the
// CS Safe Room Trigger, which checks that all doors in a room are closed
// before executing the resting). The three standard profiles provided with
// resting subsystem are:
//     Name             Script
//     --------------   ------
//     Safe/Camp Rest   cs_rest_cmp
//     Safe Room Rest   cs_rest_rom
//     No Rest          cs_rest_cnt
//
// Resting profiles are used in two places: when setting a default profile
// for the global rest configuration or in an area's rest configuration,
// or when creating a resting trigger. Using a resting profile from a
// configuration container is simply a matter of setting the profile script
// name using the Default Profile config item, after writing the profile
// script, of course. Creating a profile for use with a trigger is slightly
// more complex. Creating a rest profile is a task for a moderately skilled
// scripter with good knowledge of how the resting subsystem works.
//
// There are three components to a rest profile when it is used from a
// trigger: a trigger OnEnter script, a trigger OnExit script, and the
// resting profile script itself. The OnEnter script is a simple modification
// of an existing OnEnter script, since the only thing that has to change is
// the name of the resting profile script contained within it. The standard
// OnExit script provided by the resting subsystem should work for all new
// resting profile triggers as well, so the only real work comes about when
// writing the resting profile script itself.
//
// The standard resting profiles should be sufficient for most purposes, as
// most custom operation may be added theough the user script facility in a
// more elegant manner.
//
// An Example Resting Profile
// --------------------------
// To illustrate how resting profiles are created, the Safe Room Rest profile
// script and its corresponding trigger OnEnter script are shown here. The
// standard trigger OnExit script, cs_rest_trg_exit, should be sufficient
// for any new resting profile trigger.
//
// This is the trigger OnEnter script for the Safe Room Rest trigger. It first
// checks to ensure that the creature who has entered the trigger is a player,
// and then it sets the name of the resting profile script ("cs_rest_rom")
// as the player's resting profile using the cs_rest_SetProfile() function.
// The trigger itself is set as the object that the player has entered using
// the cs_rest_SetTriggeringObject() function (this is so the resting subsystem
// knows which configuration container to search for). Finally, a floaty text
// message is displayed indicating that the player has entered a rest trigger.
// The cs_rest_RetrieveConfig() function is used to determine whether floaty
// text messages are enabled (a check is performed within the function call).
// Basically all OnEnter scripts for resting triggers will look identical to
// this script except for the name of the rest profile script.
//
// #include "cs_rest"
// void main() {
//    object pc = GetEnteringObject();
//    if (GetIsPC(pc)) {
//        // Set tracking information for this trigger.
//        cs_rest_SetProfile(pc, "cs_rest_rom");
//        cs_rest_SetTriggeringObject(pc, OBJECT_SELF);
//        struct cs_rest_config cfg = cs_rest_RetrieveConfig(pc);
//        cs_rest_ShowFloatingTextByID(pc, cfg, CS_REST_TXT_ENTER_REST);
//    }
// }
//
// This is the Safe Room Rest profile script. It retrieves the current rest
// session config from the player with the cs_rest_GetConfig() function (all
// resting configuration has by this stage been retrieved from containers and
// stored as local variables on the player). The specific rest event is then
// checked to determine whether a start, stop or cancel event is currently
// being processed. The stop and cancel events simply require standard rest
// processing so the cs_rest_StopResting() and cs_rest_CancelResting()
// functions are called. For a start resting event, the trigger that the
// player has entered is searched for doors, and the state of every door
// is checked to make sure it has been closed. Only if there are actually
// doors, and all of them are closed, is the player allowed to rest. The
// configuration values retrieved from the player at the start of the function
// are passed to most resting subsystem functions to minimise the need to
// continually read local variables.
//
// #include "cs_rest"
// void main() {
//     object pc = GetLastPCRested();
//     if (GetIsPC(pc)) {
//         struct cs_rest_config cfg = cs_rest_GetConfig(pc);
//         int restType = GetLastRestEventType();
//         if (restType == REST_EVENTTYPE_REST_STARTED) {
//             // Find all doors within the area of the triggering object the
//             // PC is within.
//             int doorsFound = 0;
//             int openDoors = 0;
//             if (GetIsObjectValid(cfg.trigger)) {
//                 object door = GetFirstInPersistentObject(
//                     cfg.trigger,
//                     OBJECT_TYPE_DOOR
//                 );
//                 while (GetIsObjectValid(door)) {
//                     doorsFound++;
//                     if (GetIsOpen(door)) {
//                         openDoors++;
//                     }
//                     door = GetNextInPersistentObject(
//                         cfg.trigger,
//                         OBJECT_TYPE_DOOR
//                     );
//                 }
//             }
//
//             // Do we stop the player from resting? This is a safe room rest
//             // check. Players may only rest if they have taken steps to
//             // ensure their own safety, i.e. closing all doors. Resting is
//             // not permitted if:
//             //    - there are no doors in this trigger
//             //    - a door within this trigger is open
//             if (!doorsFound || openDoors) {
//                 cs_rest_ShowFloatingTextByID(pc, cfg, CS_REST_TXT_UNSAFE);
//                 AssignCommand(pc, ClearAllActions());
//             }
//             else {
//                 cs_rest_StartResting(pc, cfg);
//             }
//         }
//         else if (restType == REST_EVENTTYPE_REST_FINISHED) {
//             cs_rest_StopResting(pc, cfg);
//         }
//         else if (restType == REST_EVENTTYPE_REST_CANCELLED) {
//             cs_rest_CancelResting(pc, cfg);
//         }
//     }
// }
//
// Using the CS Resting Subsystem Within A Persistent World
// --------------------------------------------------------
// In general, The CS Resting Subsystem will function correctly when used in a
// persistent world environment. The one thing that may cause issues is the
// last resting time, which must be calculated and stored each time a player
// rests. When a player logs in after a server restart, she will be able to
// rest immediately, which may not be what is desired. There are however a
// set of functions provided that address this situation.
//
// The cs_rest_GetState() and cs_rest_SetState() functions may be used to
// store the information needed to restore a player's resting situation to
// a specific point. The cs_rest_GetState() function may be used to retrieve
// resting information for a player, which may then be stored in a persistence
// mechanism such as a database. This resting information is returned in the
// form of a string for convenience. The cs_rest_SetState() function may be
// used to restore the resting information for when that player logs into
// the server again.
//
// The use of these two functions must be performed through scripting and so
// is not handled as part of the normal configuration of the resting subsystem.
// It is not necessary to use these functions in anything other than a
// persistent world, and not even in every one of those. Please try using the
// resting subsystem first before making use of these functions.
//
// Acknowledgements
// ----------------
// Many thanks go to __Abaddon__ and Elorin for being sounding boards of
// ideas and for telling me when I'm being stupid. Elorin finds bugs and I
// fix them. Sigh. Provsul has been of great assistance in providing better
// support for persistent worlds.
//
// To Do
// -----
//   - Party-only resting.
//   - Rations.
//
// Revision History
// ----------------
// 0.8.0dev     Changed builtin resting rules to act upon a bitmapped flag
//              integer, thus allowing all rules to operate independently.
//              Some internal consistency tweaks, constant renaming and
//              the like to make the code more maintainable. Added execution
//              of user script immediately after evaluating builtin rules,
//              and on start, stop and cancel of resting. Added User Script
//              config item. Updated to release 1.6.0 of CS Debug. Updated
//              documentation. Finally added support for changing the floaty
//              text messages through the usual dynamic configuration model,
//              and noew allow individual floaty messages to be disabled.
//              Added the Effects config item. Added override profile.
// 0.7.3dev     Added the Bedroll HP Multiplier config item. Changed the
//              generation of the remaining minutes message to be more
//              sensible.
// 0.7.2dev     Rewrote the still dysfunctional time manipulation to use
//              a double-int method of tracking times, thus providing more
//              than sufficient scope for managing the DR calendar at any
//              minutes-per-hour resolution that anyone may care to use.
//              Shields are now considered armour with regard to resting.
// 0.7.1dev     More debug messages. Corrected some minor issues related to
//              time manipulation changes. Restored incorrect trigger tags.
// 0.7.0dev     Some documentation updates. Added persistence hooks. Updated
//              to release 1.5.0 of CS Debug. Added internal debug config
//              support to allow debug output for the resting subsystem to
//              be controlled through the resting subsystem configuration.
//              Armour and weapons checks are now controlled through separate
//              configuration items. Internal changes to time manipulation.
// 0.6.6dev     Documentation corrections. No code change.
// 0.6.5dev     Removed Destroy config item and associated code. The idea of
//              destroying the config and keeping static copies of the rules
//              as local variables never really made sense anyway. Added Use
//              CON Bonus, No CON Bonus and HP Multiplier configuration items.
// 0.6.1dev     Fixed No Messages configuration item so that it works.
//              Added separate floaty messages for entering and exiting no
//              rest zones.
// 0.6.0dev     Major refactoring to eliminate proliferation of get/set
//              functions and replace most with a single set of functions.
//              Much cleaner code now. First draft of documentation. Added
//              fade screen to black function and corresponding config item.
//              Even more debug! Added No Messages configuration item.
// 0.5.0dev     Added bedrolls and associated configuration items--still
//              mostly untested. Corrected overzealous armour restrictions
//              (forced resting naked is probably a bit over the top :).
//              Included equipped weapons in the "armour check". Added
//              resting never permitted function for special purposes.
//              Added config cleanup when not destroying config containers,
//              thus allowing defaults to re-apply the next time the container
//              is read.
// 0.4.0dev     Incorporated 3rd ed style resting as a configurable option
//              for all triggers, and removed the 3rd ed trigger. Added
//              global and area configuration facility. Added day/night
//              only configuration items. Added armour configuration items.
// 0.3.0dev     Added 3rd edition rules profile. Improved safe room resting
//              by accounting for multiple doors. Rejigged framework to use
//              two scripts per profile (enter/rest) rather than four
//              (enter/start/stop/cancel). Added dynamic configuration.
//              Added time limits. Added floaty text.
// 0.2.0dev     Added profile support. Added safe room profile.
// 0.1.0dev     Basic framework. Safe campsites.
//
//============================================================================
#include "cs_dbg"
#include "cs_token"
#include "cs_rest_text"

// The version numbering I use is comprised of a tripartite version number
// and a trailing string. The version is arranged into 1.2.3 form, where 1 is
// the major release number, 2 is the minor release number and 3 is the bugfix
// level. Each number in the version starts at 0 and increments each time a
// change is made to the code that classifies as that level. The trailing
// string is either "dev", "beta" or "final" depending upon whether the code
// is still under development (it will have bugs, count on it), is considered
// to be beta quality (i.e. technically finished but probably has bugs), or
// finished, tested and believed to be correctly functional. If you wish to
// report a bug, please include the version number with your bug report.
const string cs_rest_version = "0.8.0dev";

//============================================================================
//
// Data Types and Constants
//
//============================================================================

// The maximum length of tags. This is used for error validation.
const int CS_REST_MAX_TAG_LENGTH = 32;

// Constants for default values when using bedrolls.
const int CS_REST_DEF_BEDROLL_MAX_USE = 20;
const float CS_REST_DEF_BEDROLL_MULTIPLIER = 0.5;
const float CS_REST_DEF_MULTIPLIER = 1.0;

// The tag of the global rest container.
const string CS_REST_TAG_CONFIG = "CS_REST_GLOBAL_CONFIG";

// The suffix added to an area or trigger tag to generate the matching
// rest container tag.
const string CS_REST_TAG_CONFIG_SUFFIX = "_REST_CONFIG";

// Floaty text message IDs.
const int CS_REST_TXT_START = 1;
const int CS_REST_TXT_FINISH = 2;
const int CS_REST_TXT_CANCEL = 3;
const int CS_REST_TXT_CANNOT = 4;
const int CS_REST_TXT_TOO_SOON = 5;
const int CS_REST_TXT_DAY_ONLY = 6;
const int CS_REST_TXT_NIGHT_ONLY = 7;
const int CS_REST_TXT_NO_ARMOUR = 8;
const int CS_REST_TXT_NO_WEAPON = 9;
const int CS_REST_TXT_BEDROLL_RUINED = 10;
const int CS_REST_TXT_NOT_COMFORTABLE = 11;
const int CS_REST_TXT_ENTER_REST = 12;
const int CS_REST_TXT_EXIT_REST = 13;
const int CS_REST_TXT_UNSAFE = 14;
const int CS_REST_TXT_ENTER_NO_REST = 15;
const int CS_REST_TXT_EXIT_NO_REST = 16;
const int CS_REST_TXT_NO_BEDROLL = 17;

// Names of resrefs for configuration items.
const string CS_REST_RR_DEBUG_ON = "cs_rest_cfg_dbgo";
const string CS_REST_RR_ENABLE = "cs_rest_cfg_enbl";
const string CS_REST_RR_DISABLE = "cs_rest_cfg_dsbl";
const string CS_REST_RR_PROFILE = "cs_rest_cfg_prfl";
const string CS_REST_RR_TIME_LIMIT = "cs_rest_cfg_time";
const string CS_REST_RR_TIME_PREFIX = "cs_rest_cfg_tm";
const string CS_REST_RR_HP_3ED = "cs_rest_cfg_3ed";
const string CS_REST_RR_HP_STD = "cs_rest_cfg_std";
const string CS_REST_RR_NO_ARMOUR = "cs_rest_cfg_armn";
const string CS_REST_RR_YES_ARMOUR = "cs_rest_cfg_army";
const string CS_REST_RR_NO_WEAPONS = "cs_rest_cfg_wepn";
const string CS_REST_RR_YES_WEAPONS = "cs_rest_cfg_wepy";
const string CS_REST_RR_NO_BEDROLL = "cs_rest_cfg_bedn";
const string CS_REST_RR_YES_BEDROLL = "cs_rest_cfg_bedy";
const string CS_REST_RR_BEDROLL_MULTIPLIER = "cs_rest_cfg_bedm";
const string CS_REST_RR_DAY_ONLY = "cs_rest_cfg_donl";
const string CS_REST_RR_NIGHT_ONLY = "cs_rest_cfg_nonl";
const string CS_REST_RR_ALL_HOURS = "cs_rest_cfg_allh";
const string CS_REST_RR_DAY_START = "cs_rest_cfg_days";
const string CS_REST_RR_DAY_END = "cs_rest_cfg_daye";
const string CS_REST_RR_FADE_TO_BLACK = "cs_rest_cfg_fade";
const string CS_REST_RR_NO_MESSAGES = "cs_rest_cfg_nmsg";
const string CS_REST_RR_USE_CON_BONUS = "cs_rest_cfg_cony";
const string CS_REST_RR_NO_CON_BONUS = "cs_rest_cfg_conn";
const string CS_REST_RR_HP_MULTIPLIER = "cs_rest_cfg_hmul";
const string CS_REST_RR_USER_SCRIPT = "cs_rest_cfg_uscr";
const string CS_REST_RR_TEXT_MESSAGES = "cs_rest_cfg_text";
const string CS_REST_RR_EFFECTS = "cs_rest_cfg_efct";

// Names of local variables used within the resting code.
const string CS_REST_VAR_DEBUG = "cs_rest_debug";
const string CS_REST_VAR_PERMITTED = "cs_rest_permitted";
const string CS_REST_VAR_RULE_FLAGS = "cs_rest_ruleFlags";
const string CS_REST_VAR_ENABLE = "cs_rest_enable";
const string CS_REST_VAR_STARTED = "cs_rest_started";
const string CS_REST_VAR_NEVER_PERMITTED = "cs_rest_neverPermitted";
//const string CS_REST_VAR_PLAYER_ALLOWED = "cs_rest_playerAllowed";
const string CS_REST_VAR_PLAYER_PROFILE = "cs_rest_playerProfile";
const string CS_REST_VAR_DEFAULT_PROFILE = "cs_rest_defaultProfile";
const string CS_REST_VAR_OVERRIDE_PROFILE = "cs_rest_overrideProfile";
const string CS_REST_VAR_TIME_LIMIT = "cs_rest_timeLimit";
const string CS_REST_VAR_LAST_REST_DAYS = "cs_rest_lastRestDays";
const string CS_REST_VAR_LAST_REST_SECONDS = "cs_rest_lastRestSeconds";
const string CS_REST_VAR_SAVE_HP = "cs_rest_savedHP";
const string CS_REST_VAR_HP_3ED = "cs_rest_3ed";
const string CS_REST_VAR_NO_ARMOUR = "cs_rest_noArmour";
const string CS_REST_VAR_NO_WEAPONS = "cs_rest_noWeapons";
const string CS_REST_VAR_BEDROLL_IN_USE = "cs_rest_bedrollInUse";
const string CS_REST_VAR_BEDROLL_TAG = "cs_rest_bedrollTag";
const string CS_REST_VAR_BEDROLL_USE = "cs_rest_bedrollUse";
const string CS_REST_VAR_BEDROLL_MULTIPLIER = "cs_rest_bedrollMultiplier";
const string CS_REST_VAR_DAY_MODE = "cs_rest_dayMode";
const string CS_REST_VAR_DAY_START = "cs_rest_dayStart";
const string CS_REST_VAR_DAY_END = "cs_rest_dayEnd";
const string CS_REST_VAR_TRIGGER = "cs_rest_trigger";
const string CS_REST_VAR_MULTIPLIER = "cs_rest_multiplier";
const string CS_REST_VAR_FADE_TO_BLACK = "cs_rest_fadeToBlack";
const string CS_REST_VAR_FADE_DONE = "cs_rest_fadeDone";
const string CS_REST_VAR_NO_MESSAGES = "cs_rest_noMessages";
const string CS_REST_VAR_CON_BONUS = "cs_rest_conBonus";
const string CS_REST_VAR_NO_REST_ZONE = "cs_rest_noRestZone";
const string CS_REST_VAR_USER_SCRIPT = "cs_rest_userScript";
const string CS_REST_VAR_USER_SCRIPT_STATE = "cs_rest_userScriptState";
const string CS_REST_VAR_EFFECTS = "cs_rest_effects";

// Constants for indicating when resting is permitted.
const int CS_REST_DAY_AND_NIGHT = 0;
const int CS_REST_DAY_ONLY = 1;
const int CS_REST_NIGHT_ONLY = 2;

// Constants for state tracking whether resting actually began.
const int CS_REST_STARTED = 1;
const int CS_REST_ABORTED = 2;

// Constants for use with user hook scripts.
const int CS_REST_START_RESTING = 1;
const int CS_REST_STOP_RESTING = 2;
const int CS_REST_CANCEL_RESTING = 3;
const int CS_REST_RULE_CHECK = 4;

// Names of variables that hold floaty text message strings.
const string CS_REST_VAR_START_TEXT = "cs_rest_startText";
const string CS_REST_VAR_FINISH_TEXT = "cs_rest_finishText";
const string CS_REST_VAR_CANCEL_TEXT = "cs_rest_cancelText";
const string CS_REST_VAR_CANNOT_TEXT = "cs_rest_cannotText";
const string CS_REST_VAR_TOO_SOON_TEXT = "cs_rest_tooSoonText";
const string CS_REST_VAR_DAY_ONLY_TEXT = "cs_rest_dayOnlyText";
const string CS_REST_VAR_NIGHT_ONLY_TEXT = "cs_rest_nightOnlyText";
const string CS_REST_VAR_NO_ARMOUR_TEXT = "cs_rest_noArmourText";
const string CS_REST_VAR_NO_WEAPON_TEXT = "cs_rest_noWeaponText";
const string CS_REST_VAR_BEDROLL_RUINED_TEXT = "cs_rest_bedrollRuinedText";
const string CS_REST_VAR_NOT_COMFORTABLE_TEXT = "cs_rest_notComfortable";
const string CS_REST_VAR_ENTER_REST_TEXT = "cs_rest_enterZoneText";
const string CS_REST_VAR_EXIT_REST_TEXT = "cs_rest_exitZoneText";
const string CS_REST_VAR_UNSAFE_TEXT = "cs_rest_unsafeText";
const string CS_REST_VAR_ENTER_NO_REST_TEXT = "cs_rest_enterNoZoneText";
const string CS_REST_VAR_EXIT_NO_REST_TEXT = "cs_rest_exitNoZoneText";
const string CS_REST_VAR_NO_BEDROLL_TEXT = "cs_rest_noBedrollText";

// Rest rule bitmapped IDs for rule checks.
const int CS_REST_RULE_LAST_REST   = 0x00000001;
const int CS_REST_RULE_DAY_ONLY    = 0x00000002;
const int CS_REST_RULE_NIGHT_ONLY  = 0x00000004;
const int CS_REST_RULE_NO_ARMOUR   = 0x00000008;
const int CS_REST_RULE_NO_WEAPONS  = 0x00000010;
const int CS_REST_RULE_NO_BEDROLL  = 0x00000020;
const int CS_REST_RULE_RESERVED_1  = 0x00000040;
const int CS_REST_RULE_RESERVED_2  = 0x00000080;
const int CS_REST_RULE_RESERVED_3  = 0x00000100;
const int CS_REST_RULE_RESERVED_4  = 0x00000200;
const int CS_REST_RULE_RESERVED_5  = 0x00000400;
const int CS_REST_RULE_RESERVED_6  = 0x00000800;
const int CS_REST_RULE_RESERVED_7  = 0x00001000;
const int CS_REST_RULE_RESERVED_8  = 0x00002000;
const int CS_REST_RULE_RESERVED_9  = 0x00004000;
const int CS_REST_RULE_RESERVED_10 = 0x00008000;
const int CS_REST_RULE_USER_1      = 0x10000000;
const int CS_REST_RULE_USER_2      = 0x20000000;
const int CS_REST_RULE_USER_3      = 0x40000000;
const int CS_REST_RULE_USER_4      = 0x80000000;
const int CS_REST_RULE_USER_5      = 0x01000000;
const int CS_REST_RULE_USER_6      = 0x02000000;
const int CS_REST_RULE_USER_7      = 0x04000000;
const int CS_REST_RULE_USER_8      = 0x08000000;
const int CS_REST_RULE_USER_9      = 0x00100000;
const int CS_REST_RULE_USER_10     = 0x00200000;
const int CS_REST_RULE_USER_11     = 0x00400000;
const int CS_REST_RULE_USER_12     = 0x00800000;
const int CS_REST_RULE_USER_13     = 0x00010000;
const int CS_REST_RULE_USER_14     = 0x00020000;
const int CS_REST_RULE_USER_15     = 0x00040000;
const int CS_REST_RULE_USER_16     = 0x00080000;

// A configuration structure that tracks the operational parameters for a
// resting session.
struct cs_rest_config {
    object debug;           // The object to which debug messages are sent.
    int permitted;          // If resting is permitted for a given session.
    int ruleFlags;          // A bitmapped flag word for tracking rules.
    int lastRestDays;       // Days since start of calendar since of last rest.
    int lastRestSeconds;    // Seconds within day of last rest.
    int savedHitPoints;     // Number of HP when resting session was begin.
    int enabled;            // If this rest system is actually enabled at all.
    int started;            // If a resting session has been begun.
    int neverPermitted;     // If resting for the creature is never allowed.
//    int allowed;            // XXX Don't know. Might not be used anymore.
    string profile;         // The current profile for the player.
    string defaultProfile;  // The default profile if not otherwise set.
    string overrideProfile; // A profile that exists only for the rest session.
    int limit;              // Number of seconds between rests.
    int use3Ed;             // Use 3rd Edition-style HP regain.
    int noArmour;           // Armour may not be equipped while resting.
    int noWeapons;          // Weapons may not be equipped while resting.
    int useBedroll;         // A bedroll must be available to rest.
    string bedrollTag;      // The tag of bedroll items.
    float bedrollMultiplier;// A multiplier for HP regaing in bedroll resting.
    int dayMode;            // Resting may only occur during the day or night.
    int dayStart;           // The hour at which "day" starts.
    int dayEnd;             // The hour at which "day" ends.
    object trigger;         // The trigger in which the player exists.
    float multiplier;       // A multiplier for HP regain.
    int fadeToBlack;        // If the screen should fade to black while resting.
    int noMessages;         // If floating messages should be displayed.
    int useConBonus;        // If CON bonus should be added to HP regain.
    string userScript;      // Name of rule script provided by builder.
    string effects;         // A set of effect numbers to apply.
};

// A representation of a date and time broken down into number of days
// since the beginning of the calendar and a number of seconds within the
// current day.
struct cs_rest_time {
    int days;       // The number of days since the beginning of the calendar.
    int seconds;    // The number of seconds elapsed within the current day.
};

//============================================================================
//
// Function Prototypes and toolset IDE documentation.
//
//============================================================================

// Set the rest profile to be associated with the specified player.
//
// pc           The player for whom to set the profile.
// profile      The name of the profile to associate with the player.
void cs_rest_SetProfile(object pc, string profile);

// Remove any rest profile that may be associated with the specified
// player.
//
// pc           The player from whom to remove any profile.
void cs_rest_DeleteProfile(object pc);

// Set the session-specific rest profile to be associated with the specified
// player. Session profiles are only used for the specific resting session
// in which they are set.
//
// pc           The player for whom to set the profile.
// profile      The name of the profile to associate with the player.
void cs_rest_SetOverrideProfile(object pc, string profile);

// Remove any session-specific rest profile that may be associated with the
// specified player. Session profiles are only used for the specific resting
// session in which they are set.
//
// pc           The player from whom to remove any profile.
void cs_rest_DeleteOverrideProfile(object pc);

// Set the object that associated the rest profile with the specified player.
//
// pc           The player for whom to set the triggering object.
// profile      The triggering object.
void cs_rest_SetTriggeringObject(object pc, object trigger);

// Remove any triggering object that may be stored for the specified player.
//
// pc           The player from whom to remove any triggering object.
void cs_rest_DeleteTriggeringObject(object pc);

// Set a rule flag to a specific state (i.e. TRUE or FALSE). Rule flags are
// used to track whether a player has satisfied a specific rule that will
// block resting. Resting will only be permitted if *no* rules have been
// satisfied and all rule flags are FALSE.
//
// cfg          The current rest config for a player.
// rule         The specific rule flag that is to be changed.
// value        Either TRUE to set the flag or FALSE to clear it.
// returns      The modified rest config.
struct cs_rest_config cs_rest_SetRuleFlag(struct cs_rest_config cfg, int rule, int value);

// Determine the state of a specific rule flag.
//
// cfg          The current rest config for a player.
// rule         The specific rule flag that is to be checked.
// returns      TRUE if the flag is set otherwise FALSE.
int cs_rest_GetRuleFlag(struct cs_rest_config cfg, int rule);

// Execute the resting script associated with the rest profile that has
// been set for the specified player.
//
// pc           The player for whom to run the script.
// cfg          A set of configuration data for a player.
void cs_rest_ExecuteRestScript(object pc, struct cs_rest_config cfg);

// Return the current time as a since the beginning of the calendar.
//
// returns      The current time.
struct cs_rest_time cs_rest_GetCurrentTime(struct cs_rest_config cfg);

// Process the configuration for the rest subsystem.
//
// pc           The player for whom to process configuration.
// container    A container object containing the configuration.
// cfg          A set of configuration data for a player.
// returns      The updated configuration data.
struct cs_rest_config cs_rest_ProcessConfig(object pc, object container, struct cs_rest_config cfg);

// Search for and compile together all of the configuration that applies to
// a specific player.
//
// pc           The player for whom to search for configuration.
// returns      The player's resting configuration
struct cs_rest_config cs_rest_RetrieveConfig(object pc);

// Read any messages that exist as variables on the specified item and set
// them to be used for the specified player's rest session.
//
// pc           The player for whom to change messages.
// cfg          The player's resting configuration.
// item         The item containing message text variables.
void cs_rest_UpdateMessages(object pc, struct cs_rest_config cfg, object item);

// Display a floating text message on a player from those messages defined
// by the rest system. If floaty messages are disabled for the resting zone
// in which this function is invoked (i.e. cfg.noMessage is TRUE), no
// messages will be displayed.
//
// pc           The player on whom to display the message.
// cfg          The player's resting configuration.
// id           The ID of the message to be displayed. This value may be one
//              of the following constants:
//                  CS_REST_TXT_START
//                  CS_REST_TXT_FINISH
//                  CS_REST_TXT_CANCEL
//                  CS_REST_TXT_CANNOT
//                  CS_REST_TXT_TOO_SOON
//                  CS_REST_TXT_DAY_ONLY
//                  CS_REST_TXT_NIGHT_ONLY
//                  CS_REST_TXT_NO_ARMOUR
//                  CS_REST_TXT_NO_WEAPON
//                  CS_REST_TXT_BEDROLL_RUINED
//                  CS_REST_TXT_NOT_COMFORTABLE
//                  CS_REST_TXT_ENTER_REST_ZONE
//                  CS_REST_TXT_EXIT_REST_ZONE
//                  CS_REST_TXT_UNSAFE
//                  CS_REST_TXT_ENTER_NO_REST_ZONE
//                  CS_REST_TXT_EXIT_NO_REST_ZONE
//                  CS_REST_TXT_NO_BEDROLL
void cs_rest_ShowFloatingTextByID(object pc, struct cs_rest_config cfg, int id);

// Retrieve rest configuration from an object.
//
// returns      The configuration data found.
struct cs_rest_config cs_rest_GetConfig(object container);

// Set current rest configuration on an object.
//
// container    The object on which to set the config.
// cfg          A set of configuration data.
void cs_rest_SetConfig(object container, struct cs_rest_config cfg);

// Delete rest configuration from an object.
//
// container    The object from which to delete the configuration data.
void cs_rest_DeleteConfig(object container);

// Generate a string containing the data needed to restore the resting
// state of a player in the event of a system failure, or after a player
// logs out. This function should be called periodically while a server
// is running, if persistent state is being stored in such a fashion, or
// whenever a player logs out.
//
// NOTE: This function is not needed for the normal operation of the
// resting subsystem and should be used only for integration into modules
// that run as persistent worlds.
//
// pc           The object from which to retrieve the config.
// returns      The persistence state in string form.
string cs_rest_GetState(object container);

// Set the resting state for a player from a previous call to the
// cs_rest_GetState() function. This function should be called when
// a player logs into a server running as a persistent module.
//
// NOTE: This function is not needed for the normal operation of the
// resting subsystem and should be used only for integration into modules
// that run as persistent worlds.
//
// pc           The object on which to set the config.
// state        A string containing state information.
// timeOffset   The amount of time in seconds by which the time values
//              in the resting state should be adjusted. Adjusting the
//              time will usually be unnecessary.
void cs_rest_SetState(object container, string state, int timeOffset=0);

// Set the user hook script state for a resting session.
//
// pc           The player who is resting.
// state        The state for the hook script. This should be one of the
//              following constants:
//                  CS_REST_START_RESTING
//                  CS_REST_STOP_RESTING
//                  CS_REST_CANCEL_RESTING
//                  CS_REST_RULE_CHECK
void cs_rest_SetUserScriptState(object pc, int state);

// Determine the resting state for which the user hook script has been
// executed.
//
// pc           The player who is resting. If not specified, this defaults
//              to the current object.
// returns      The script hook state. This will be one of the following
//              constants:
//                  CS_REST_START_RESTING
//                  CS_REST_STOP_RESTING
//                  CS_REST_CANCEL_RESTING
//                  CS_REST_RULE_CHECK
int cs_rest_GetUserScriptState(object pc=OBJECT_SELF);

// Delete the user script state variable from the specified player.
//
// pc           The player from which the variable is to be deleted.
void cs_rest_DeleteUserScriptState(object pc);

// Determine whether a specific rest rule has been enabled in the resting
// system configuration. Though the user rules can be checked with this
// function, they are always enabled.
//
// This function may only be used within the CS_REST_RULE_CHECK execution
// point for a user script.
//
// rule         The specific rest rule that is to be checked. This value
//              must be one of the following constants:
//                  CS_REST_RULE_LAST_REST
//                  CS_REST_RULE_DAY_ONLY
//                  CS_REST_RULE_NIGHT_ONLY
//                  CS_REST_RULE_NO_ARMOUR
//                  CS_REST_RULE_NO_WEAPONS
//                  CS_REST_RULE_NO_BEDROLL
//                  CS_REST_RULE_USER_1
//                  CS_REST_RULE_USER_2
//                  CS_REST_RULE_USER_3
//                  CS_REST_RULE_USER_4
//                  CS_REST_RULE_USER_5
//                  CS_REST_RULE_USER_6
//                  CS_REST_RULE_USER_7
//                  CS_REST_RULE_USER_8
//                  CS_REST_RULE_USER_9
//                  CS_REST_RULE_USER_10
//                  CS_REST_RULE_USER_11
//                  CS_REST_RULE_USER_12
//                  CS_REST_RULE_USER_13
//                  CS_REST_RULE_USER_14
//                  CS_REST_RULE_USER_15
//                  CS_REST_RULE_USER_16
// state        TRUE if the rule is enabled, which means the rest subsystem
//              will evaluate the rule when determining whether a player is
//              allowed to rest, otherwise FALSE.
// pc           The player who is resting. This value will default to the
//              current object, which will always be the resting player when
//              this function is called from a user script.
// returns      TRUE if the specified rule has been enabled in the rest
//              subsystem configuration, or FALSE if the rule is not enabled.
int cs_rest_GetIsRuleEnabled(int rule, object pc=OBJECT_SELF);

// Determine the current state of a specific rest rule, either a builtin rule
// or a user rule. The builtin rules are evaluated before the user script is
// executed. User rules are provided specifically for use by builders, will
// always be FALSE initially, and will never be changed by the rest subsystem.
//
// This function may only be used within the CS_REST_RULE_CHECK execution
// point for a user script.
//
// rule         The specific rest rule that is to be checked. This value
//              must be one of the following constants:
//                  CS_REST_RULE_LAST_REST
//                  CS_REST_RULE_DAY_ONLY
//                  CS_REST_RULE_NIGHT_ONLY
//                  CS_REST_RULE_NO_ARMOUR
//                  CS_REST_RULE_NO_WEAPONS
//                  CS_REST_RULE_NO_BEDROLL
//                  CS_REST_RULE_USER_1
//                  CS_REST_RULE_USER_2
//                  CS_REST_RULE_USER_3
//                  CS_REST_RULE_USER_4
//                  CS_REST_RULE_USER_5
//                  CS_REST_RULE_USER_6
//                  CS_REST_RULE_USER_7
//                  CS_REST_RULE_USER_8
//                  CS_REST_RULE_USER_9
//                  CS_REST_RULE_USER_10
//                  CS_REST_RULE_USER_11
//                  CS_REST_RULE_USER_12
//                  CS_REST_RULE_USER_13
//                  CS_REST_RULE_USER_14
//                  CS_REST_RULE_USER_15
//                  CS_REST_RULE_USER_16
// pc           The player who is resting. This value will default to the
//              current object, which will always be the resting player when
//              this function is called from a user script.
// returns      TRUE if the rule has been evaluated and the player does not
//              satisfy it (which will prevent the player from resting),
//              otherwise FALSE (which will allow the player to rest).
int cs_rest_GetRule(int rule, object pc=OBJECT_SELF);

// Set or clear the specified rule for a resting player. When the rule is
// set, i.e. the state is TRUE, the player will not be permitted to rest.
// If any rule at all is set to TRUE, the player will be prevented from
// resting. This function may be used to change the state of one of the
// builtin rest rules, or a user rule. User rules are provided specifically
// for use by builders.
//
// If a builtin rest rule is not enabled in the configuration for the resting
// subsystem, then changing the state of that rule will have no effect. The
// user rules are always considered enabled.
//
// This function may only be used within the CS_REST_RULE_CHECK execution
// point for a user script.
//
// rule         The specific rest rule that is to be changed. This value
//              must be one of the following constants:
//                  CS_REST_RULE_LAST_REST
//                  CS_REST_RULE_DAY_ONLY
//                  CS_REST_RULE_NIGHT_ONLY
//                  CS_REST_RULE_NO_ARMOUR
//                  CS_REST_RULE_NO_WEAPONS
//                  CS_REST_RULE_NO_BEDROLL
//                  CS_REST_RULE_USER_1
//                  CS_REST_RULE_USER_2
//                  CS_REST_RULE_USER_3
//                  CS_REST_RULE_USER_4
//                  CS_REST_RULE_USER_5
//                  CS_REST_RULE_USER_6
//                  CS_REST_RULE_USER_7
//                  CS_REST_RULE_USER_8
//                  CS_REST_RULE_USER_9
//                  CS_REST_RULE_USER_10
//                  CS_REST_RULE_USER_11
//                  CS_REST_RULE_USER_12
//                  CS_REST_RULE_USER_13
//                  CS_REST_RULE_USER_14
//                  CS_REST_RULE_USER_15
//                  CS_REST_RULE_USER_16
// state        TRUE if the rule is to be set, which will disallow resting,
//              otherwise FALSE to allow resting.
// pc           The player who is resting. This value will default to the
//              current object, which will always be the resting player when
//              this function is called from a user script.
void cs_rest_SetRule(int rule, int state, object pc=OBJECT_SELF);

// Abort resting for a specific player even if that player has satisfied all
// enabled rest rules.
//
// This function may only be used within the CS_REST_START_RESTING execution
// point for a user script.
//
// pc           The player who is resting. This value will default to the
//              current object, which will always be the resting player when
//              this function is called from a user script.
void cs_rest_SetAbort(object pc=OBJECT_SELF);


//----------------------------------------------------------------------------
// Double integer precision date arithmetic support.
const int cs_rest_secondsPerMinute = 60;
int cs_rest_secondsPerHour = FloatToInt(HoursToSeconds(1));
const int cs_rest_hoursPerDay = 24;
int cs_rest_secondsPerDay = cs_rest_hoursPerDay * cs_rest_secondsPerHour;
const int cs_rest_daysPerMonth = 28;
const int cs_rest_daysPerYear = 336;
struct cs_rest_time cs_rest_GetCurrentTime(struct cs_rest_config cfg) {
    struct cs_rest_time time;
    time.days = GetCalendarYear() * cs_rest_daysPerYear;
    time.days += GetCalendarMonth() * cs_rest_daysPerMonth;
    time.days += GetCalendarDay();
    time.seconds = GetTimeHour() * cs_rest_secondsPerHour;
    time.seconds += GetTimeMinute() * cs_rest_secondsPerMinute;
    time.seconds += GetTimeSecond();
    return time;
}

// Add b to a.
struct cs_rest_time cs_rest_AddTime(struct cs_rest_time a, struct cs_rest_time b) {
    a.seconds += b.seconds;
    if (a.seconds >= cs_rest_secondsPerDay) {
        a.seconds -= cs_rest_secondsPerDay;
        a.days++;
    }
    a.days += b.days;
    return a;
}

// Subtract b from a.
struct cs_rest_time cs_rest_SubtractTime(struct cs_rest_time a, struct cs_rest_time b) {
    a.seconds -= b.seconds;
    if (a.seconds < 0) {
        a.seconds += cs_rest_secondsPerDay;
        a.days--;
    }
    a.days -= b.days;
    return a;
}

// Determine if a is greater than b.
int cs_rest_GetIsTimeGreater(struct cs_rest_time a, struct cs_rest_time b) {
    if (a.days > b.days) return TRUE;
    if (a.days < b.days) return FALSE;
    if (a.seconds > b.seconds) return TRUE;
    return FALSE;
}

// Determine if a is less than b.
int cs_rest_GetIsTimeLess(struct cs_rest_time a, struct cs_rest_time b) {
    if (a.days < b.days) return TRUE;
    if (a.days > b.days) return FALSE;
    if (a.seconds < b.seconds) return TRUE;
    return FALSE;
}

// Determine whether a equals b.
int cs_rest_GetIsTimeEqual(struct cs_rest_time a, struct cs_rest_time b) {
    if ((a.days == b.days) && (a.seconds == b.seconds)) return TRUE;
    return FALSE;
}

// Convert a time to a string representation.
string cs_rest_TimeToString(struct cs_rest_time time) {
    return IntToString(time.days) + "." + IntToString(time.seconds);
}

// Construct a time value from individual values.
struct cs_rest_time cs_rest_Time(int days, int seconds) {
    struct cs_rest_time time;
    time.days = days;
    time.seconds = seconds;
    return time;
}

//----------------------------------------------------------------------------
void cs_rest_ShowConfig(struct cs_rest_config cfg) {
    if (cs_dbg_GetIsEnabled() || GetIsObjectValid(cfg.debug)) {
        // Data generated within the rest system.
        cs_dbg_Trace(
            "cfg.permitted: " + IntToString(cfg.permitted),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.ruleFlags: " + IntToString(cfg.ruleFlags),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.started: " + IntToString(cfg.started),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.savedHitPoints: " + IntToString(cfg.savedHitPoints),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.profile: " + cfg.profile,
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.overrideProfile: " + cfg.overrideProfile,
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.trigger: " + GetName(cfg.trigger),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.lastRest: " +
            cs_rest_TimeToString(cs_rest_Time(cfg.lastRestDays, cfg.lastRestSeconds)),
            cfg.debug
        );

        // Configuration data.
        cs_dbg_Trace(
            "cfg.debug: " + ObjectToString(cfg.debug),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.enabled: " + IntToString(cfg.enabled),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.neverPermitted: " + IntToString(cfg.neverPermitted),
            cfg.debug
        );
//        cs_dbg_Trace(
//            "cfg.allowed: " + IntToString(cfg.allowed),
//            cfg.debug
//        );
        cs_dbg_Trace(
            "cfg.defaultProfile: " + cfg.defaultProfile,
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.limit: " + IntToString(cfg.limit),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.use3Ed: " + IntToString(cfg.use3Ed),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.noArmour: " + IntToString(cfg.noArmour),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.noWeapons: " + IntToString(cfg.noWeapons),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.useBedroll: " + IntToString(cfg.useBedroll),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.bedrollTag: " + cfg.bedrollTag,
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.bedrollMultiplier: " + FloatToString(cfg.bedrollMultiplier),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.dayMode: " + IntToString(cfg.dayMode),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.dayStart: " + IntToString(cfg.dayStart),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.dayEnd: " + IntToString(cfg.dayEnd),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.multiplier: " + FloatToString(cfg.multiplier),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.fadeToBlack: " + IntToString(cfg.fadeToBlack),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.noMessages: " + IntToString(cfg.noMessages),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.useConBonus: " + IntToString(cfg.useConBonus),
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.userScript: " + cfg.userScript,
            cfg.debug
        );
        cs_dbg_Trace(
            "cfg.effects: " + cfg.effects,
            cfg.debug
        );
    }
}

//----------------------------------------------------------------------------
struct cs_rest_config cs_rest_GetConfig(object container) {
    struct cs_rest_config cfg;
    cfg.debug = GetLocalObject(container, CS_REST_VAR_DEBUG);

    cs_dbg_Enter("cs_rest_GetConfig(" + GetName(container) + ")", cfg.debug);

    // Data generated within the rest system.
    cfg.permitted = GetLocalInt(container, CS_REST_VAR_PERMITTED);
    cfg.ruleFlags = GetLocalInt(container, CS_REST_VAR_RULE_FLAGS);
    cfg.started = GetLocalInt(container, CS_REST_VAR_STARTED);
    cfg.savedHitPoints = GetLocalInt(container, CS_REST_VAR_SAVE_HP);
    cfg.profile = GetLocalString(container, CS_REST_VAR_PLAYER_PROFILE);
    cfg.overrideProfile = GetLocalString(container, CS_REST_VAR_OVERRIDE_PROFILE);
    cfg.trigger = GetLocalObject(container, CS_REST_VAR_TRIGGER);
    cfg.lastRestDays = GetLocalInt(container, CS_REST_VAR_LAST_REST_DAYS);
    cfg.lastRestSeconds = GetLocalInt(container, CS_REST_VAR_LAST_REST_SECONDS);

    // Configuration data.
    cfg.enabled = GetLocalInt(container, CS_REST_VAR_ENABLE);
    cfg.neverPermitted = GetLocalInt(container, CS_REST_VAR_NEVER_PERMITTED);
//    cfg.allowed = GetLocalInt(container, CS_REST_VAR_PLAYER_ALLOWED);
    cfg.defaultProfile = GetLocalString(container, CS_REST_VAR_DEFAULT_PROFILE);
    cfg.limit = GetLocalInt(container, CS_REST_VAR_TIME_LIMIT);
    cfg.use3Ed = GetLocalInt(container, CS_REST_VAR_HP_3ED);
    cfg.noArmour = GetLocalInt(container, CS_REST_VAR_NO_ARMOUR);
    cfg.noWeapons = GetLocalInt(container, CS_REST_VAR_NO_WEAPONS);
    cfg.useBedroll = GetLocalInt(container, CS_REST_VAR_BEDROLL_IN_USE);
    cfg.bedrollTag = GetLocalString(container, CS_REST_VAR_BEDROLL_TAG);
    cfg.bedrollMultiplier = GetLocalFloat(container, CS_REST_VAR_BEDROLL_MULTIPLIER);
    cfg.dayMode = GetLocalInt(container, CS_REST_VAR_DAY_MODE);
    cfg.dayStart = GetLocalInt(container, CS_REST_VAR_DAY_START);
    cfg.dayEnd = GetLocalInt(container, CS_REST_VAR_DAY_END);
    cfg.multiplier = GetLocalFloat(container, CS_REST_VAR_MULTIPLIER);
    cfg.fadeToBlack = GetLocalInt(container, CS_REST_VAR_FADE_TO_BLACK);
    cfg.noMessages = GetLocalInt(container, CS_REST_VAR_NO_MESSAGES);
    cfg.useConBonus = GetLocalInt(container, CS_REST_VAR_CON_BONUS);
    cfg.userScript = GetLocalString(container, CS_REST_VAR_USER_SCRIPT);
    cfg.effects = GetLocalString(container, CS_REST_VAR_EFFECTS);
    if (cfg.bedrollMultiplier < 0.0f) {
        cfg.bedrollMultiplier = 0.5f;
    }
    if (cfg.multiplier <= 0.0f) {
        cfg.multiplier = 1.0f;
    }

    cs_rest_ShowConfig(cfg);

    cs_dbg_Exit("cs_rest_GetConfig", cfg.debug);
    return cfg;
}

//----------------------------------------------------------------------------
void cs_rest_SetConfig(object container, struct cs_rest_config cfg) {
    cs_dbg_Enter("cs_rest_SetConfig(" + GetName(container) + ")", cfg.debug);

    cs_rest_ShowConfig(cfg);

    if (cfg.bedrollMultiplier < 0.0f) {
        cfg.bedrollMultiplier = 0.5f;
    }
    if (cfg.multiplier <= 0.0f) {
        cfg.multiplier = 1.0f;
    }

    // Data generated within the rest system.
    SetLocalInt(container, CS_REST_VAR_PERMITTED, cfg.permitted);
    SetLocalInt(container, CS_REST_VAR_RULE_FLAGS, cfg.ruleFlags);
    SetLocalInt(container, CS_REST_VAR_STARTED, cfg.started);
    SetLocalInt(container, CS_REST_VAR_SAVE_HP, cfg.savedHitPoints);
    SetLocalString(container, CS_REST_VAR_PLAYER_PROFILE, cfg.profile);
    SetLocalString(container, CS_REST_VAR_OVERRIDE_PROFILE, cfg.overrideProfile);
    SetLocalObject(container, CS_REST_VAR_TRIGGER, cfg.trigger);
    SetLocalInt(container, CS_REST_VAR_LAST_REST_DAYS, cfg.lastRestDays);
    SetLocalInt(container, CS_REST_VAR_LAST_REST_SECONDS, cfg.lastRestSeconds);

    // Configuration data.
    SetLocalObject(container, CS_REST_VAR_DEBUG, cfg.debug);
    SetLocalInt(container, CS_REST_VAR_ENABLE, cfg.enabled);
    SetLocalInt(container, CS_REST_VAR_NEVER_PERMITTED, cfg.neverPermitted);
//    SetLocalInt(container, CS_REST_VAR_PLAYER_ALLOWED, cfg.allowed);
    SetLocalString(container, CS_REST_VAR_DEFAULT_PROFILE, cfg.defaultProfile);
    SetLocalInt(container, CS_REST_VAR_TIME_LIMIT, cfg.limit);
    SetLocalInt(container, CS_REST_VAR_HP_3ED, cfg.use3Ed);
    SetLocalInt(container, CS_REST_VAR_NO_ARMOUR, cfg.noArmour);
    SetLocalInt(container, CS_REST_VAR_NO_WEAPONS, cfg.noWeapons);
    SetLocalInt(container, CS_REST_VAR_BEDROLL_IN_USE, cfg.useBedroll);
    SetLocalString(container, CS_REST_VAR_BEDROLL_TAG, cfg.bedrollTag);
    SetLocalFloat(container, CS_REST_VAR_BEDROLL_MULTIPLIER, cfg.bedrollMultiplier);
    SetLocalInt(container, CS_REST_VAR_DAY_MODE, cfg.dayMode);
    SetLocalInt(container, CS_REST_VAR_DAY_START, cfg.dayStart);
    SetLocalInt(container, CS_REST_VAR_DAY_END, cfg.dayEnd);
    SetLocalFloat(container, CS_REST_VAR_MULTIPLIER, cfg.multiplier);
    SetLocalInt(container, CS_REST_VAR_FADE_TO_BLACK, cfg.fadeToBlack);
    SetLocalInt(container, CS_REST_VAR_NO_MESSAGES, cfg.noMessages);
    SetLocalInt(container, CS_REST_VAR_CON_BONUS, cfg.useConBonus);
    SetLocalString(container, CS_REST_VAR_USER_SCRIPT, cfg.userScript);
    SetLocalString(container, CS_REST_VAR_EFFECTS, cfg.effects);

    cs_dbg_Exit("cs_rest_SetConfig", cfg.debug);
}

//----------------------------------------------------------------------------
void cs_rest_DeleteConfig(object container) {
    object debug = GetLocalObject(container, CS_REST_VAR_DEBUG);

    cs_dbg_Enter("cs_rest_DeleteConfig(" + GetName(container) + ")", debug);

    // Data generated within the rest system and used only when a player is
    // actually resting. It is not necessary to store this data within a
    // persistence mechanism since should either a player log out or the
    // server crash, the player won't be resting anymore and so this data
    // will be superfluous.
    DeleteLocalInt(container, CS_REST_VAR_PERMITTED);
    DeleteLocalInt(container, CS_REST_VAR_RULE_FLAGS);
    DeleteLocalString(container, CS_REST_VAR_OVERRIDE_PROFILE);
    DeleteLocalInt(container, CS_REST_VAR_STARTED);
    DeleteLocalInt(container, CS_REST_VAR_SAVE_HP);

    // These variables are used by the resting system to track the current
    // state of the player with regard to resting policies, however since
    // the values are set through trigger scripts, it is not necessary to
    // store the data in a persistence mechanism as it will simply be
    // recreated when the player logs in again and reenters the trigger.
    // The following variables are deleted elsewhere and are listed
    // here merely for completion. These deletions should not be
    // uncommented.
//    DeleteLocalString(container, CS_REST_VAR_PLAYER_PROFILE);
//    DeleteLocalObject(container, CS_REST_VAR_TRIGGER);

    // These variables are maintained across rests and constitute the
    // persistent data within the resting subsystem. Should full persistence
    // be desired, this data should be stored for each player and restored
    // to each player when they log in. The following variables are deleted
    // elsewhere if necessary and are listed here merely for completion.
    // These deletions should not be uncommented.
//    DeleteLocalInt(container, CS_REST_VAR_LAST_REST_DAYS);
//    DeleteLocalInt(container, CS_REST_VAR_LAST_REST_SECONDS);

    // Configuration data. This data is generated from the configuration
    // containers each time a player rests and so it is not necessary for
    // any of this data to be stored in a persistence mechanism.
    DeleteLocalObject(container, CS_REST_VAR_DEBUG);
    DeleteLocalInt(container, CS_REST_VAR_ENABLE);
    DeleteLocalInt(container, CS_REST_VAR_NEVER_PERMITTED);
//    DeleteLocalInt(container, CS_REST_VAR_PLAYER_ALLOWED);
    DeleteLocalString(container, CS_REST_VAR_DEFAULT_PROFILE);
    DeleteLocalInt(container, CS_REST_VAR_TIME_LIMIT);
    DeleteLocalInt(container, CS_REST_VAR_HP_3ED);
    DeleteLocalInt(container, CS_REST_VAR_NO_ARMOUR);
    DeleteLocalInt(container, CS_REST_VAR_NO_WEAPONS);
    DeleteLocalInt(container, CS_REST_VAR_BEDROLL_IN_USE);
    DeleteLocalString(container, CS_REST_VAR_BEDROLL_TAG);
    DeleteLocalFloat(container, CS_REST_VAR_BEDROLL_MULTIPLIER);
    DeleteLocalInt(container, CS_REST_VAR_DAY_MODE);
    DeleteLocalInt(container, CS_REST_VAR_DAY_START);
    DeleteLocalInt(container, CS_REST_VAR_DAY_END);
    DeleteLocalFloat(container, CS_REST_VAR_MULTIPLIER);
    DeleteLocalInt(container, CS_REST_VAR_FADE_TO_BLACK);
    DeleteLocalInt(container, CS_REST_VAR_NO_MESSAGES);
    DeleteLocalInt(container, CS_REST_VAR_CON_BONUS);
    DeleteLocalString(container, CS_REST_VAR_USER_SCRIPT);
    DeleteLocalString(container, CS_REST_VAR_EFFECTS);

    // Custom text messages.
    DeleteLocalString(container, CS_REST_VAR_START_TEXT);
    DeleteLocalString(container, CS_REST_VAR_FINISH_TEXT);
    DeleteLocalString(container, CS_REST_VAR_CANCEL_TEXT);
    DeleteLocalString(container, CS_REST_VAR_CANNOT_TEXT);
    DeleteLocalString(container, CS_REST_VAR_TOO_SOON_TEXT);
    DeleteLocalString(container, CS_REST_VAR_DAY_ONLY_TEXT);
    DeleteLocalString(container, CS_REST_VAR_NIGHT_ONLY_TEXT);
    DeleteLocalString(container, CS_REST_VAR_NO_ARMOUR_TEXT);
    DeleteLocalString(container, CS_REST_VAR_NO_WEAPON_TEXT);
    DeleteLocalString(container, CS_REST_VAR_BEDROLL_RUINED_TEXT);
    DeleteLocalString(container, CS_REST_VAR_NOT_COMFORTABLE_TEXT);
    DeleteLocalString(container, CS_REST_VAR_ENTER_REST_TEXT);
    DeleteLocalString(container, CS_REST_VAR_EXIT_REST_TEXT);
    DeleteLocalString(container, CS_REST_VAR_UNSAFE_TEXT);
    DeleteLocalString(container, CS_REST_VAR_ENTER_NO_REST_TEXT);
    DeleteLocalString(container, CS_REST_VAR_EXIT_NO_REST_TEXT);
    DeleteLocalString(container, CS_REST_VAR_NO_BEDROLL_TEXT);

    cs_dbg_Exit("cs_rest_DeleteConfig", debug);
}

//----------------------------------------------------------------------------
string cs_rest_GetProfile(object pc) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    string profile = GetLocalString(pc, CS_REST_VAR_PLAYER_PROFILE);
    cs_dbg_Trace(
        "cs_rest_GetProfile(" + GetName(pc) + "): " + profile,
        debug
    );
    return profile;
}

//----------------------------------------------------------------------------
void cs_rest_SetProfile(object pc, string profile) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    cs_dbg_Trace(
        "cs_rest_SetProfile(" + GetName(pc) + "): " + profile,
        debug
    );
    SetLocalString(pc, CS_REST_VAR_PLAYER_PROFILE, profile);
}

//----------------------------------------------------------------------------
string cs_rest_GetOverrideProfile(object pc) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    string profile = GetLocalString(pc, CS_REST_VAR_OVERRIDE_PROFILE);
    cs_dbg_Trace(
        "cs_rest_GetOverrideProfile(" + GetName(pc) + "): " + profile,
        debug
    );
    return profile;
}

//----------------------------------------------------------------------------
void cs_rest_SetOverrideProfile(object pc, string profile) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    cs_dbg_Trace(
        "cs_rest_SetOverrideProfile(" + GetName(pc) + "): " + profile,
        debug
    );
    SetLocalString(pc, CS_REST_VAR_OVERRIDE_PROFILE, profile);
}

//----------------------------------------------------------------------------
void cs_rest_DeleteProfile(object pc) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    cs_dbg_Trace(
        "cs_rest_DeleteProfile(" + GetName(pc) + ")",
        debug
    );
    DeleteLocalString(pc, CS_REST_VAR_PLAYER_PROFILE);
}

//----------------------------------------------------------------------------
void cs_rest_SetUserScriptState(object pc, int state) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    cs_dbg_Trace(
        "cs_rest_SetUserScriptState(" + GetName(pc) + ", " + IntToString(state) + ")",
        debug
    );
    SetLocalInt(pc, CS_REST_VAR_USER_SCRIPT_STATE, state);
}

//----------------------------------------------------------------------------
int cs_rest_GetUserScriptState(object pc=OBJECT_SELF) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    int state = GetLocalInt(pc, CS_REST_VAR_USER_SCRIPT_STATE);
    cs_dbg_Trace(
        "cs_rest_GetUserScriptState(" + GetName(pc) + "): " + IntToString(state),
        debug
    );
    return state;
}

//----------------------------------------------------------------------------
void cs_rest_DeleteUserScriptState(object pc) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    cs_dbg_Trace(
        "cs_rest_DeleteUserScriptState(" + GetName(pc) + ")",
        debug
    );
    DeleteLocalString(pc, CS_REST_VAR_USER_SCRIPT_STATE);
}

//----------------------------------------------------------------------------
int cs_rest_GetIsRuleEnabled(int rule, object pc=OBJECT_SELF) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    int state = TRUE;
    switch(rule) {
    case CS_REST_RULE_LAST_REST:
        state = (GetLocalInt(pc, CS_REST_VAR_TIME_LIMIT) > 0);
        break;
    case CS_REST_RULE_DAY_ONLY:
        state = (GetLocalInt(pc, CS_REST_VAR_DAY_MODE) == CS_REST_DAY_ONLY);
        break;
    case CS_REST_RULE_NIGHT_ONLY:
        state = (GetLocalInt(pc, CS_REST_VAR_DAY_MODE) == CS_REST_NIGHT_ONLY);
        break;
    case CS_REST_RULE_NO_ARMOUR:
        state = GetLocalInt(pc, CS_REST_VAR_NO_ARMOUR);
        break;
    case CS_REST_RULE_NO_WEAPONS:
        state = GetLocalInt(pc, CS_REST_VAR_NO_WEAPONS);
        break;
    case CS_REST_RULE_NO_BEDROLL:
        state = GetLocalInt(pc, CS_REST_VAR_BEDROLL_IN_USE);
        break;
    }
    cs_dbg_Trace(
        "cs_rest_GetIsRuleEnabled(" +
        IntToString(rule) + ", " +
        GetName(pc) + "): " +
        IntToString(state),
        debug
    );
    return state;
}

//----------------------------------------------------------------------------
int cs_rest_GetRule(int rule, object pc=OBJECT_SELF) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    int flags = GetLocalInt(pc, CS_REST_VAR_RULE_FLAGS);
    int state = flags & rule;
    cs_dbg_Trace(
        "cs_rest_GetRule(" +
        IntToString(rule) + ", " +
        GetName(pc) + "): " +
        IntToString(state),
        debug
    );
    return state;
}

//----------------------------------------------------------------------------
void cs_rest_SetRule(int rule, int state, object pc=OBJECT_SELF) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    cs_dbg_Enter(
        "cs_rest_SetRule(" +
        IntToString(rule) + ", " +
        IntToString(state) + ", " +
        GetName(pc) + ")",
        debug
    );
    if (cs_rest_GetIsRuleEnabled(rule, pc)) {
        int flags = GetLocalInt(pc, CS_REST_VAR_RULE_FLAGS);
        cs_dbg_Trace("flags before: " + IntToHexString(flags));
        if (state) {
            // We want to set the flag.
            flags |= rule;
        }
        else {
            // We want to clear the flag.
            flags &= ~rule;
        }
        cs_dbg_Trace("flags after: " + IntToHexString(flags));
        SetLocalInt(pc, CS_REST_VAR_RULE_FLAGS, flags);
    }
    else {
        cs_dbg_Trace("disabled rule");
    }
    cs_dbg_Exit("cs_rest_SetRule", debug);
}

//----------------------------------------------------------------------------
void cs_rest_SetAbort(object pc=OBJECT_SELF) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    cs_dbg_Trace(
        "cs_rest_SetAbort(" +
        GetName(pc) + ")",
        debug
    );
    SetLocalInt(pc, CS_REST_VAR_PERMITTED, FALSE);
}

//----------------------------------------------------------------------------
struct cs_rest_time cs_rest_GetLastRestTime(object pc) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    struct cs_rest_time time = cs_rest_Time(
        GetLocalInt(pc, CS_REST_VAR_LAST_REST_DAYS),
        GetLocalInt(pc, CS_REST_VAR_LAST_REST_SECONDS)
    );
    cs_dbg_Trace(
        "cs_rest_GetLastRestTime(" + GetName(pc) + "): " +
        cs_rest_TimeToString(time),
        debug
    );
    return time;
}

//----------------------------------------------------------------------------
void cs_rest_SetLastRestTime(object pc, struct cs_rest_time time) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    cs_dbg_Trace(
        "cs_rest_SetLastRestTime(" + GetName(pc) + "): " +
        cs_rest_TimeToString(time),
        debug
    );
    SetLocalInt(pc, CS_REST_VAR_LAST_REST_DAYS, time.days);
    SetLocalInt(pc, CS_REST_VAR_LAST_REST_SECONDS, time.seconds);
}

//----------------------------------------------------------------------------
object cs_rest_GetTriggeringObject(object pc) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    object trigger = GetLocalObject(pc, CS_REST_VAR_TRIGGER);
    cs_dbg_Trace(
        "cs_rest_GetTriggeringObject(" + GetName(pc) + "): " +
        GetName(trigger),
        debug
    );
    return trigger;
}

//----------------------------------------------------------------------------
void cs_rest_SetTriggeringObject(object pc, object trigger) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    cs_dbg_Trace(
        "cs_rest_SetTriggeringObject(" + GetName(pc) + "): " +
        GetName(trigger),
        debug
    );
    SetLocalObject(pc, CS_REST_VAR_TRIGGER, trigger);
}

//----------------------------------------------------------------------------
void cs_rest_DeleteTriggeringObject(object pc) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    cs_dbg_Trace(
        "cs_rest_DeleteTriggeringObject(" + GetName(pc) + ")",
        debug
    );
    DeleteLocalObject(pc, CS_REST_VAR_TRIGGER);
}

//----------------------------------------------------------------------------
string cs_rest_GetState(object pc) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    string state = "";
    struct cs_rest_time lastRest = cs_rest_GetLastRestTime(pc);
    state += IntToString(lastRest.days);
    state += ":";
    state += IntToString(lastRest.seconds);
    cs_dbg_Trace("cs_rest_GetState: " + state, debug);
    return state;
}

//----------------------------------------------------------------------------
void cs_rest_SetState(object pc, string state, int timeOffset=0) {
    object debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    cs_dbg_Trace(
        "cs_rest_SetState(" +
        GetName(pc) + ", " +
        state + ", " +
        IntToString(timeOffset) + ")",
        debug
    );
    cs_tkn_SetTokenString(state, ":");
    int lastRestDays = StringToInt(cs_tkn_GetNextToken());
    int lastRestSeconds = StringToInt(cs_tkn_GetNextToken());
    struct cs_rest_time lastRest = cs_rest_Time(lastRestDays, lastRestSeconds);
    if (timeOffset != 0) {
        lastRest = cs_rest_AddTime(lastRest, cs_rest_Time(0, timeOffset));
    }
    cs_rest_SetLastRestTime(pc, lastRest);
}

//----------------------------------------------------------------------------
int cs_rest_GetIsWearingHelm(object pc, struct cs_rest_config cfg) {
    int result = FALSE;
    int head = GetBaseItemType(GetItemInSlot(INVENTORY_SLOT_HEAD, pc));
    if (head == BASE_ITEM_HELMET) {
        result = TRUE;
    }
    cs_dbg_Trace(
        "cs_rest_GetIsWearingHelm(" + GetName(pc) + "): " +
        IntToString(result),
        cfg.debug
    );
    return result;
}

//----------------------------------------------------------------------------
int cs_rest_GetIsWearingArmour(object pc, struct cs_rest_config cfg) {
    int result = FALSE;
    object armour = GetItemInSlot(INVENTORY_SLOT_CHEST, pc);
    if (GetItemACValue(armour) > 5) {
        result = TRUE;
    }
    cs_dbg_Trace(
        "cs_rest_GetIsWearingArmour(" + GetName(pc) + "): " +
        IntToString(result),
        cfg.debug
    );
    return result;
}

//----------------------------------------------------------------------------
int cs_rest_GetIsArmed(object pc, struct cs_rest_config cfg) {
    int result = 0;
    if (GetIsObjectValid(pc)) {
        int hand1 = GetBaseItemType(
            GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, pc)
        );
        if (hand1 != BASE_ITEM_INVALID) {
            result++;
        }
        int hand2 = GetBaseItemType(
            GetItemInSlot(INVENTORY_SLOT_LEFTHAND, pc)
        );
        if (!((hand2 == BASE_ITEM_INVALID) ||
              (hand2 == BASE_ITEM_TORCH) ||
              (hand2 == BASE_ITEM_SMALLSHIELD) ||
              (hand2 == BASE_ITEM_LARGESHIELD) ||
              (hand2 == BASE_ITEM_TOWERSHIELD))
        ) {
            result++;
        }
    }
    cs_dbg_Trace(
        "cs_rest_GetIsArmed(" + GetName(pc) + "): " + IntToString(result),
        cfg.debug
    );
    return result;
}

//----------------------------------------------------------------------------
int cs_rest_GetIsShieldEquipped(object pc, struct cs_rest_config cfg) {
    int result = 0;
    if (GetIsObjectValid(pc)) {
        int hand2 = GetBaseItemType(
            GetItemInSlot(INVENTORY_SLOT_LEFTHAND, pc)
        );
        if ((hand2 == BASE_ITEM_SMALLSHIELD) ||
            (hand2 == BASE_ITEM_LARGESHIELD) ||
            (hand2 == BASE_ITEM_TOWERSHIELD)
        ) {
            result = TRUE;
        }
    }
    cs_dbg_Trace(
        "cs_rest_GetIsShieldEquipped(" + GetName(pc) + "): " +
        IntToString(result),
        cfg.debug
    );
    return result;
}

//----------------------------------------------------------------------------
struct cs_rest_config cs_rest_SetRuleFlag(struct cs_rest_config cfg, int rule, int value) {
    cs_dbg_Enter(
        "cs_rest_SetRuleFlag(" +
        IntToString(rule) + ", " +
        IntToString(value) + ")",
        cfg.debug
    );
    cs_dbg_Trace("before flags: " + IntToString(cfg.ruleFlags));
    if (value) {
        // We want to set the flag.
        cfg.ruleFlags |= rule;
    }
    else {
        // We want to clear the flag.
        cfg.ruleFlags &= ~rule;
    }
    cs_dbg_Trace("after flags: " + IntToString(cfg.ruleFlags));
    cs_dbg_Exit("cs_rest_SetRuleFlag");
    return cfg;
}

//----------------------------------------------------------------------------
int cs_rest_GetRuleFlag(struct cs_rest_config cfg, int rule) {
    int flag = cfg.ruleFlags &= rule;
    cs_dbg_Trace("cs_rest_GetRuleFlag(" + IntToString(rule) +"): " + IntToString(flag));
    return flag;
}

//----------------------------------------------------------------------------
struct cs_rest_config cs_rest_GetIsRestPermitted(
    object pc,
    struct cs_rest_config cfg
) {
    // By default, resting is permitted and no rule flags are set.
    cfg.permitted = TRUE;
    cfg.ruleFlags = 0;

    // Check whether this creature is ever allowed to rest at all.
    if (cfg.neverPermitted) {
        cfg.permitted = FALSE;
    }

    // Check for a rest time limit.
    if (cfg.permitted && (cfg.limit > 0)) {
        // Determine the current time.
        struct cs_rest_time currentTime = cs_rest_GetCurrentTime(cfg);

        // Determine the time at which resting is next available by adding
        // the current rest limit to the last rest time.
        struct cs_rest_time nextRest = cs_rest_Time(
            cfg.lastRestDays,
            cfg.lastRestSeconds
        );
        nextRest = cs_rest_AddTime(nextRest, cs_rest_Time(0, cfg.limit));
        cs_dbg_Trace(
            "current time: " + cs_rest_TimeToString(currentTime),
            cfg.debug
        );
        cs_dbg_Trace(
            "Next rest time: " + cs_rest_TimeToString(nextRest),
            cfg.debug
        );

        if (cs_rest_GetIsTimeLess(currentTime, nextRest)) {
            // The required time period has not passed so the player
            // may not rest yet.
            cs_rest_ShowFloatingTextByID(pc, cfg, CS_REST_TXT_TOO_SOON);
            cfg = cs_rest_SetRuleFlag(cfg, CS_REST_RULE_LAST_REST, TRUE);
        }
    }

    // Check for day only resting.
    if (cfg.permitted && (cfg.dayMode == CS_REST_DAY_ONLY)) {
        int hour = GetTimeHour();
        if ((hour < cfg.dayStart) || (hour >= cfg.dayEnd)) {
            cs_dbg_Trace("Day-only set and it is not day", cfg.debug);
            cs_rest_ShowFloatingTextByID(pc, cfg, CS_REST_TXT_DAY_ONLY);
            cfg = cs_rest_SetRuleFlag(cfg, CS_REST_RULE_DAY_ONLY, TRUE);
        }
    }

    // Check for night only resting.
    if (cfg.permitted && (cfg.dayMode == CS_REST_NIGHT_ONLY)) {
        int hour = GetTimeHour();
        if ((hour >= cfg.dayStart) && (hour < cfg.dayEnd)) {
            cs_dbg_Trace("Night-only set and it is not night", cfg.debug);
            cs_rest_ShowFloatingTextByID(pc, cfg, CS_REST_TXT_NIGHT_ONLY);
            cfg = cs_rest_SetRuleFlag(cfg, CS_REST_RULE_NIGHT_ONLY, TRUE);
        }
    }

    // Check if wearing armour.
    if (cfg.permitted && cfg.noArmour) {
        if (cs_rest_GetIsWearingHelm(pc, cfg) ||
            cs_rest_GetIsShieldEquipped(pc, cfg) ||
            cs_rest_GetIsWearingArmour(pc, cfg)
        ) {
            cs_dbg_Trace("Armour is not permitted while resting", cfg.debug);
            cs_rest_ShowFloatingTextByID(pc, cfg, CS_REST_TXT_NO_ARMOUR);
            cfg = cs_rest_SetRuleFlag(cfg, CS_REST_RULE_NO_ARMOUR, TRUE);
        }
    }

    // Check if carrying weapons.
    if (cfg.permitted && cfg.noWeapons) {
        if (cs_rest_GetIsArmed(pc, cfg)) {
            cs_dbg_Trace("Arms are not permitted while resting", cfg.debug);
            cs_rest_ShowFloatingTextByID(pc, cfg, CS_REST_TXT_NO_WEAPON);
            cfg = cs_rest_SetRuleFlag(cfg, CS_REST_RULE_NO_WEAPONS, TRUE);
        }
    }

    // Check if carrying a bedroll.
    if (cfg.permitted && cfg.useBedroll) {
        object item = GetItemPossessedBy(pc, cfg.bedrollTag);
        if (GetIsObjectValid(item)) {
            // Increment a usage count on the bedroll.
            // XXX This may need to be persistent.
            int used = GetLocalInt(item, CS_REST_VAR_BEDROLL_USE);
            used++;
            if (used > CS_REST_DEF_BEDROLL_MAX_USE) {
                cs_dbg_Trace("Bedroll has been destroyed through use", cfg.debug);
                DestroyObject(item);
                cs_rest_ShowFloatingTextByID(pc,cfg,CS_REST_TXT_BEDROLL_RUINED);
            }
            else {
                cs_dbg_Trace("Incrementing bedroll use count", cfg.debug);
                SetLocalInt(item, CS_REST_VAR_BEDROLL_USE, used);
            }
        }
        else {
            // Is there hit point regain without a bedroll?
            if (cfg.bedrollMultiplier > 0.0) {
                // No bedroll means only partial hit points regained.
                cs_dbg_Trace("Resting without a bedroll reduces regain", cfg.debug);
                cfg.multiplier = cfg.multiplier * cfg.bedrollMultiplier;
                cs_rest_ShowFloatingTextByID(pc, cfg, CS_REST_TXT_NOT_COMFORTABLE);
            }
            else {
                // No bedroll means no rest allowed at all.
                cs_dbg_Trace("Resting without a bedroll not permitted", cfg.debug);
                cfg = cs_rest_SetRuleFlag(cfg, CS_REST_RULE_NO_BEDROLL, TRUE);
                cs_rest_ShowFloatingTextByID(pc, cfg, CS_REST_TXT_NO_BEDROLL);
            }
        }
    }

    // Execute external script hook for rest rule evaluation. We don't even
    // bother executing the user script if the never permitted flag is set,
    // since by definition the player will not be permitted to rest no matter
    // what.
    if ((cfg.userScript != "") && !cfg.neverPermitted) {
        cs_rest_SetConfig(pc, cfg);
        cs_rest_SetUserScriptState(pc, CS_REST_RULE_CHECK);
        ExecuteScript(cfg.userScript, pc);
        cs_rest_DeleteUserScriptState(pc);
        cfg = cs_rest_GetConfig(pc);
    }

    if (cfg.permitted) {
        // If resting is still permitted at this point, check whether any
        // of the rules should disallow it. Any rule that is set will cause
        // resting to be not permitted.
        cs_dbg_Trace("Checking rule evaluation", cfg.debug);
        cfg.permitted = (cfg.ruleFlags == 0);
        cs_dbg_Trace(
            "Permitted after rule evaluation: " + IntToString(cfg.permitted),
            cfg.debug
        );
    }

    cs_dbg_Trace(
        "cs_rest_GetIsRestPermitted(" + GetName(pc) + "): " +
        IntToString(cfg.permitted),
        cfg.debug
    );
    return cfg;
}

//----------------------------------------------------------------------------
void cs_rest_ExecuteRestScript(object pc, struct cs_rest_config cfg) {
    cs_dbg_Enter("cs_rest_ExecuteRestScript(" + GetName(pc) + ")", cfg.debug);

    // Make sure that we have a rest profile.
    string profile = cfg.profile;
    if (profile == "") {
        // No configured profile so fall back on the default.
        profile = cfg.defaultProfile;
        cs_dbg_Trace("No profile, assuming default: " + profile, cfg.debug);
    }
    if (cfg.overrideProfile != "") {
        // There's an override so use that instead.
        profile = cfg.overrideProfile;
        cs_dbg_Trace("Using override profile instead: " + profile, cfg.debug);
    }

    cs_dbg_Trace("Running rest script: " + profile, cfg.debug);
    ExecuteScript(profile, GetModule());

    cs_dbg_Exit("cs_rest_ExecuteRestScript", cfg.debug);
}

//----------------------------------------------------------------------------
struct cs_rest_config cs_rest_ProcessConfig(
    object pc,
    object container,
    struct cs_rest_config cfg
) {
    cs_dbg_Enter("cs_rest_ProcessConfig(" + GetTag(container) + ")", cfg.debug);

    // Do we have a valid object from which to configure?
    if (GetIsObjectValid(container) && GetHasInventory(container)) {
        cs_dbg_Trace(
            "Valid configuration object: " + GetResRef(container),
            cfg.debug
        );

        // Sort through all items in the configuration object's inventory,
        // checking each one to see if it is something that we recognise.
        object item = GetFirstItemInInventory(container);
        while (GetIsObjectValid(item)) {
            string resref = GetResRef(item);
            if (resref == CS_REST_RR_DEBUG_ON) {
                // Found a debug on value. The presence of this item is
                // enough to indicate that debugging should be enabled.
                // When debug is enabled, messages are sent to the player
                // current resting.
                cfg.debug = pc;
                cs_dbg_EnableDestination(CS_DBG_ALL);
                cs_dbg_Trace("Enabling debugging", cfg.debug);
            }
            else if (resref == CS_REST_RR_ENABLE) {
                // Found an enable value. The presence of this item is
                // enough to indicate that the rest system is active.
                cfg.enabled = TRUE;
                cs_dbg_Trace("Enabling resting", cfg.debug);
            }
            else if (resref == CS_REST_RR_DISABLE) {
                // Found a disable value. The presence of this item is
                // enough to indicate that the rest system is inactive.
                cfg.enabled = FALSE;
                cs_dbg_Trace("Disabling resting", cfg.debug);
            }
            else if (resref == CS_REST_RR_HP_3ED) {
                // Found a 3ed HP value. The presence of this item is
                // enough to indicate that the rest system should use 3rd
                // edition style hit point rules.
                cfg.use3Ed = TRUE;
                cs_dbg_Trace("Using 3rd Ed Style HP", cfg.debug);
            }
            else if (resref == CS_REST_RR_HP_STD) {
                // Found a standard HP value. The presence of this item is
                // enough to indicate that the rest system should use normal
                // hit point rules.
                cfg.use3Ed = FALSE;
                cs_dbg_Trace("Using standard HP", cfg.debug);
            }
            else if (resref == CS_REST_RR_PROFILE) {
                // Found a profile value. Set the default profile to be
                // a script the same name as this item's tag.
                string profile = GetStringLowerCase(GetTag(item));
                cfg.defaultProfile = profile;
                cs_dbg_Trace("Default profile is " + profile, cfg.debug);
            }
            else if (resref == CS_REST_RR_DAY_ONLY) {
                // Found a day-only value. The presence of this item is
                // enough to indicate that resting should only be permitted
                // during daylight hours.
                cfg.dayMode = CS_REST_DAY_ONLY;
                cs_dbg_Trace("Setting day only mode", cfg.debug);
            }
            else if (resref == CS_REST_RR_NIGHT_ONLY) {
                // Found a night-only value. The presence of this item is
                // enough to indicate that resting should only be permitted
                // during night hours.
                cfg.dayMode = CS_REST_NIGHT_ONLY;
                cs_dbg_Trace("Setting night only mode", cfg.debug);
            }
            else if (resref == CS_REST_RR_ALL_HOURS) {
                // Found an all-hours value. The presence of this item is
                // enough to indicate that resting is permitted at all
                // times during the day and night.
                cfg.dayMode = CS_REST_DAY_AND_NIGHT;
                cs_dbg_Trace("Setting resting for all hours", cfg.debug);
            }
            else if (resref == CS_REST_RR_DAY_START) {
                // Found a start of day value. Convert the item's tag into an
                // integer and use it as the starting hour for the "day".
                int hour = StringToInt(GetTag(item));
                if ((hour >= 0) && (hour < 23)) {
                    cfg.dayStart = hour;
                    cs_dbg_Trace(
                        "Setting day start: " + IntToString(hour),
                        cfg.debug
                    );
                }
            }
            else if (resref == CS_REST_RR_DAY_END) {
                // Found an end of day value. Convert the item's tag into an
                // integer and use it as the ending hour for the "day".
                int hour = StringToInt(GetTag(item));
                if ((hour > 0) && (hour <= 23)) {
                    cfg.dayEnd = hour;
                    cs_dbg_Trace(
                        "Setting day end: " + IntToString(hour),
                        cfg.debug
                    );
                }
            }
            else if (resref == CS_REST_RR_NO_ARMOUR) {
                // Found a no armour value. The presence of this item is
                // enough to indicate that resting is not permitted while
                // wearing armour.
                cfg.noArmour = TRUE;
                cs_dbg_Trace("Setting no armour mode", cfg.debug);
            }
            else if (resref == CS_REST_RR_YES_ARMOUR) {
                // Found an armour allowed value. The presence of this item
                // is enough to indicate that resting is permitted even while
                // armour is being worn.
                cfg.noArmour = FALSE;
                cs_dbg_Trace("Setting armour allowed mode", cfg.debug);
            }
            else if (resref == CS_REST_RR_NO_WEAPONS) {
                // Found a no weapons value. The presence of this item is
                // enough to indicate that resting is not permitted while
                // weapons are equipped.
                cfg.noWeapons = TRUE;
                cs_dbg_Trace("Setting no weapons mode", cfg.debug);
            }
            else if (resref == CS_REST_RR_YES_WEAPONS) {
                // Found a weapons allowed value. The presence of this item
                // is enough to indicate that resting is permitted even while
                // weapons are equipped.
                cfg.noWeapons = FALSE;
                cs_dbg_Trace("Setting weapons allowed mode", cfg.debug);
            }
            else if (resref == CS_REST_RR_NO_BEDROLL) {
                // Found a no bedroll value. The presence of this item is
                // enough to indicate that bedrolls are ignored completely
                // for purposes of calculating hit point regain.
                cfg.useBedroll = FALSE;
                cs_dbg_Trace("Setting no bedroll mode", cfg.debug);
            }
            else if (resref == CS_REST_RR_YES_BEDROLL) {
                // Found a bedroll in use value. The presence of this item
                // is enough to indicate that bedrolls are used to determine
                // the number of hit points regained while resting, while
                // its tag contains the tag of the item that is to considered
                // to be a bedroll.
                cfg.useBedroll = TRUE;
                cfg.bedrollTag = GetTag(item);
                cs_dbg_Trace("Setting bedroll mode: " + GetTag(item), cfg.debug);
            }
            else if (resref == CS_REST_RR_BEDROLL_MULTIPLIER) {
                // Found a bedroll HP multiplier value. The tag of this item
                // provides a multiplier for hit points regained by players
                // when bedrolls are in effect and no bedroll is present.
                float multiplier = StringToInt(GetTag(item))/100.0;
                if ((multiplier < 0.0) && (multiplier >= 100.0)) {
                    multiplier = CS_REST_DEF_BEDROLL_MULTIPLIER;
                    cs_dbg_Trace(
                        "Invalid Bedroll HP multiplier: " + GetTag(item),
                        cfg.debug
                    );
                }
                cfg.bedrollMultiplier = multiplier;
                if (multiplier > 0.0) {
                    cs_dbg_Trace(
                        "Bedroll HP multiplier is " + FloatToString(cfg.multiplier),
                        cfg.debug
                    );
                }
                else {
                    cs_dbg_Trace(
                        "Resting without a bedroll is disabled",
                        cfg.debug
                    );
                }
            }
            else if ((resref == CS_REST_RR_TIME_LIMIT) ||
                     (GetStringLeft(resref, 14) == CS_REST_RR_TIME_PREFIX)){
                // Found a time limit value. Set the resting time limit to be
                // an integer taken from this item's tag.
                if (GetStringLowerCase(GetTag(item)) != "none") {
                    int limit = StringToInt(GetTag(item));
                    cfg.limit = limit;
                    cs_dbg_Trace(
                        "Time limit is " + IntToString(limit) + " seconds",
                        cfg.debug
                    );
                }
                else {
                    cs_dbg_Trace("Time limit does not apply", cfg.debug);
                }
            }
            else if (resref == CS_REST_RR_FADE_TO_BLACK) {
                // Found a fade to black value. The presence of this item
                // is enough to indicate that the screen should fade to black
                // when a player rests.
                cfg.fadeToBlack = TRUE;
                cs_dbg_Trace("The screen will fade to black", cfg.debug);
            }
            else if (resref == CS_REST_RR_NO_MESSAGES) {
                // Found a no messages value. The presence of this item
                // is enough to indicate the display of messages should be
                // suppressed.
                cfg.noMessages = TRUE;
                cs_dbg_Trace("No floaty messages will be displayed", cfg.debug);
            }
            else if (resref == CS_REST_RR_USE_CON_BONUS) {
                // Found a use CON bonus value. The presence of this item
                // is enough to indicate that players should receive their
                // CON bonus as hit points when resting.
                cfg.useConBonus = TRUE;
                cs_dbg_Trace("CON bonus will be used", cfg.debug);
            }
            else if (resref == CS_REST_RR_NO_CON_BONUS) {
                // Found a no CON bonus value. The presence of this item
                // is enough to indicate that players should not receive their
                // CON bonus as hit points when resting.
                cfg.useConBonus = FALSE;
                cs_dbg_Trace("CON bonus will be not used", cfg.debug);
            }
            else if (resref == CS_REST_RR_HP_MULTIPLIER) {
                // Found a HP multiplier value. The tag of this item provides
                // a multiplier for hit points regained by players.
                float multiplier = StringToInt(GetTag(item))/100.0;
                if ((multiplier <= 0.0) && (multiplier >= 100.0)) {
                    multiplier = CS_REST_DEF_MULTIPLIER;
                    cs_dbg_Trace(
                        "Invalid HP multiplier: " + GetTag(item),
                        cfg.debug
                    );
                }
                cfg.multiplier = multiplier;
                cs_dbg_Trace(
                    "HP multiplier is " + FloatToString(cfg.multiplier),
                    cfg.debug
                );
            }
            else if (resref == CS_REST_RR_USER_SCRIPT) {
                // Found a user script value. The tag of this item provides
                // the name of a script to be executed at user hook points.
                cfg.userScript = GetStringLowerCase(GetTag(item));
                cs_dbg_Trace("User script is " + cfg.userScript, cfg.debug);
            }
            else if (resref == CS_REST_RR_TEXT_MESSAGES) {
                // Found a container item for text messages. A set of
                // variables on this item may override the default messages.
                cs_dbg_Trace("Floaty messages will be displayed", cfg.debug);
                cfg.noMessages = FALSE;
                cs_rest_UpdateMessages(pc, cfg, item);
            }
            else if (resref == CS_REST_RR_EFFECTS) {
                // Found an effects value. A set of effects may be applied
                // to a player when resting, derived from the value of a
                // variable on this item.
                cs_dbg_Trace("Effects will be applied", cfg.debug);
                cfg.effects = GetLocalString(item, CS_REST_VAR_EFFECTS);
            }
            else {
                cs_dbg_Trace("Unrecognised item: " + resref, cfg.debug);
            }
            item = GetNextItemInInventory(container);
        }
    }
    else {
        cs_dbg_Trace(
            "Not a valid configuration object: " + GetResRef(container),
            cfg.debug
        );
    }

    // Validate and override hour settings if they are obviously screwy.
    if ((cfg.dayStart < 1) || (cfg.dayStart > cfg.dayEnd)) {
        cfg.dayStart = 6;
    }
    if ((cfg.dayEnd > 23) || (cfg.dayEnd < cfg.dayStart)) {
        cfg.dayEnd = 18;
    }

    cs_dbg_Exit("cs_rest_ProcessConfig", cfg.debug);
    return cfg;
}
//----------------------------------------------------------------------------
// Find our configuration. First we find a global configuration object,
// then an area-specific one for the area that the player is in, and
// if there is a profile set for the resting player we look for a profile
// configuration object.
struct cs_rest_config cs_rest_RetrieveConfig(object pc) {
    struct cs_rest_config cfg;
    cfg.debug = GetLocalObject(pc, CS_REST_VAR_DEBUG);
    cs_dbg_Enter("cs_rest_RetrieveConfig(" + GetName(pc) + ")", cfg.debug);

    // Initialise default values.
    cfg.bedrollMultiplier = CS_REST_DEF_BEDROLL_MULTIPLIER;
    cfg.multiplier = CS_REST_DEF_MULTIPLIER;

    // Pull in internal state from the PC if any.
    cfg.profile = cs_rest_GetProfile(pc);
    cfg.overrideProfile = cs_rest_GetOverrideProfile(pc);
    cfg.trigger = cs_rest_GetTriggeringObject(pc);
    cfg.permitted = GetLocalInt(pc, CS_REST_VAR_PERMITTED);
    cfg.ruleFlags = GetLocalInt(pc, CS_REST_VAR_RULE_FLAGS);
    cfg.started = GetLocalInt(pc, CS_REST_VAR_STARTED);
    cfg.savedHitPoints = GetLocalInt(pc, CS_REST_VAR_SAVE_HP);
    struct cs_rest_time time = cs_rest_GetLastRestTime(pc);
    cfg.lastRestDays = time.days;
    cfg.lastRestSeconds = time.seconds;

    // Find the global config container.
    cs_dbg_Trace("Searching global config: " + CS_REST_TAG_CONFIG, cfg.debug);
    object container = GetObjectByTag(CS_REST_TAG_CONFIG);
    if (!GetIsObjectValid(container)) {
        // Try again in lowercase just to be sure.
        container = GetObjectByTag(GetStringLowerCase(CS_REST_TAG_CONFIG));
    }
    if (GetIsObjectValid(container)) {
        cs_dbg_Trace("Global config found", cfg.debug);
        cfg = cs_rest_ProcessConfig(pc, container, cfg);
    }
    else {
        cs_dbg_Trace("Global config not found", cfg.debug);
    }

    // Find any area config container.
    string tag = GetTag(GetArea(pc)) + CS_REST_TAG_CONFIG_SUFFIX;
    cs_dbg_Trace("Searching area config: " + tag, cfg.debug);
    if (GetStringLength(tag) > CS_REST_MAX_TAG_LENGTH) {
        cs_dbg_Trace(
            "Rest config tag > " +
            IntToString(CS_REST_MAX_TAG_LENGTH) +
            " characters. This will cause problems.",
            cfg.debug
        );
    }
    container = GetObjectByTag(tag);
    if (!GetIsObjectValid(container)) {
        // Try again in lowercase just to be sure.
        container = GetObjectByTag(GetStringLowerCase(tag));
    }
    if (GetIsObjectValid(container)) {
        cs_dbg_Trace("Area config found", cfg.debug);
        cfg = cs_rest_ProcessConfig(pc, container, cfg);
    }
    else {
        cs_dbg_Trace("Area config not found", cfg.debug);
    }

    // Find a trigger config container if any.
    container = cfg.trigger;
    if (GetIsObjectValid(container)) {
        tag = GetTag(container) + CS_REST_TAG_CONFIG_SUFFIX;
        cs_dbg_Trace("Searching trigger config: " + tag, cfg.debug);
        if (GetStringLength(tag) > CS_REST_MAX_TAG_LENGTH) {
        cs_dbg_Trace(
            "Rest config tag > " +
            IntToString(CS_REST_MAX_TAG_LENGTH) +
            " characters. This will cause problems.",
            cfg.debug
        );
        }
        container = GetObjectByTag(tag);
        if (!GetIsObjectValid(container)) {
            // Try again in lowercase just to be sure.
            container = GetObjectByTag(GetStringLowerCase(tag));
        }
        if (GetIsObjectValid(container)) {
            cs_dbg_Trace("Trigger config found", cfg.debug);
            cfg = cs_rest_ProcessConfig(pc, container, cfg);
        }
        else {
            cs_dbg_Trace("Trigger config not found", cfg.debug);
        }
    }
    else {
        cs_dbg_Trace("Searching trigger config: not inside a trigger", cfg.debug);
    }

    cs_dbg_Exit("cs_rest_RetrieveConfig", cfg.debug);
    return cfg;
}

//----------------------------------------------------------------------------
void cs_rest_UpdateMessages(object pc, struct cs_rest_config cfg, object item) {
    SetLocalString(pc, CS_REST_VAR_START_TEXT, GetLocalString(item, CS_REST_VAR_START_TEXT));
    SetLocalString(pc, CS_REST_VAR_FINISH_TEXT, GetLocalString(item, CS_REST_VAR_FINISH_TEXT));
    SetLocalString(pc, CS_REST_VAR_CANCEL_TEXT, GetLocalString(item, CS_REST_VAR_CANCEL_TEXT));
    SetLocalString(pc, CS_REST_VAR_CANNOT_TEXT, GetLocalString(item, CS_REST_VAR_CANNOT_TEXT));
    SetLocalString(pc, CS_REST_VAR_TOO_SOON_TEXT, GetLocalString(item, CS_REST_VAR_TOO_SOON_TEXT));
    SetLocalString(pc, CS_REST_VAR_DAY_ONLY_TEXT, GetLocalString(item, CS_REST_VAR_DAY_ONLY_TEXT));
    SetLocalString(pc, CS_REST_VAR_NIGHT_ONLY_TEXT, GetLocalString(item, CS_REST_VAR_NIGHT_ONLY_TEXT));
    SetLocalString(pc, CS_REST_VAR_NO_ARMOUR_TEXT, GetLocalString(item, CS_REST_VAR_NO_ARMOUR_TEXT));
    SetLocalString(pc, CS_REST_VAR_NO_WEAPON_TEXT, GetLocalString(item, CS_REST_VAR_NO_WEAPON_TEXT));
    SetLocalString(pc, CS_REST_VAR_BEDROLL_RUINED_TEXT, GetLocalString(item, CS_REST_VAR_BEDROLL_RUINED_TEXT));
    SetLocalString(pc, CS_REST_VAR_NOT_COMFORTABLE_TEXT, GetLocalString(item, CS_REST_VAR_NOT_COMFORTABLE_TEXT));
    SetLocalString(pc, CS_REST_VAR_ENTER_REST_TEXT, GetLocalString(item, CS_REST_VAR_ENTER_REST_TEXT));
    SetLocalString(pc, CS_REST_VAR_EXIT_REST_TEXT, GetLocalString(item, CS_REST_VAR_EXIT_REST_TEXT));
    SetLocalString(pc, CS_REST_VAR_UNSAFE_TEXT, GetLocalString(item, CS_REST_VAR_UNSAFE_TEXT));
    SetLocalString(pc, CS_REST_VAR_ENTER_NO_REST_TEXT, GetLocalString(item, CS_REST_VAR_ENTER_NO_REST_TEXT));
    SetLocalString(pc, CS_REST_VAR_EXIT_NO_REST_TEXT, GetLocalString(item, CS_REST_VAR_EXIT_NO_REST_TEXT));
    SetLocalString(pc, CS_REST_VAR_NO_BEDROLL_TEXT, GetLocalString(item, CS_REST_VAR_NO_BEDROLL_TEXT));
}

//----------------------------------------------------------------------------
void cs_rest_ShowFloatingTextByID(object pc, struct cs_rest_config cfg, int id){
    cs_dbg_Enter(
        "cs_rest_ShowFloatingTextByID(" +
        GetName(pc) + ", " +
        IntToString(id) + ")",
        cfg.debug
    );

    if (cfg.noMessages) {
        // Bail out if messages are turned off.
        cs_dbg_Trace("message display is disabled");
        cs_dbg_Exit("cs_rest_ShowFloatingTextByID", cfg.debug);
        return;
    }

//    object mod = GetModule();
    string text = "";
    cs_dbg_Trace("Message ID: " + IntToString(id), cfg.debug);
    switch (id) {
        case CS_REST_TXT_START:
            text = GetLocalString(pc, CS_REST_VAR_START_TEXT);
            if (text == "") {
                text = CS_REST_MSG_START;
            }
            break;
        case CS_REST_TXT_FINISH:
            text = GetLocalString(pc, CS_REST_VAR_FINISH_TEXT);
            if (text == "") {
                text = CS_REST_MSG_FINISH;
            }
            break;
        case CS_REST_TXT_CANCEL:
            text = GetLocalString(pc, CS_REST_VAR_CANCEL_TEXT);
            if (text == "") {
                text = CS_REST_MSG_CANCEL;
            }
            break;
        case CS_REST_TXT_CANNOT:
            text = GetLocalString(pc, CS_REST_VAR_CANNOT_TEXT);
            if (text == "") {
                text = CS_REST_MSG_CANNOT;
            }
            break;
        case CS_REST_TXT_TOO_SOON:
            text = GetLocalString(pc, CS_REST_VAR_TOO_SOON_TEXT);
            if (text == "") {
                text = CS_REST_MSG_TOO_SOON;
            }
            break;
        case CS_REST_TXT_DAY_ONLY:
            text = GetLocalString(pc, CS_REST_VAR_DAY_ONLY_TEXT);
            if (text == "") {
                text = CS_REST_MSG_DAY_ONLY;
            }
            break;
        case CS_REST_TXT_NIGHT_ONLY:
            text = GetLocalString(pc, CS_REST_VAR_NIGHT_ONLY_TEXT);
            if (text == "") {
                text = CS_REST_MSG_NIGHT_ONLY;
            }
            break;
        case CS_REST_TXT_NO_ARMOUR:
            text = GetLocalString(pc, CS_REST_VAR_NO_ARMOUR_TEXT);
            if (text == "") {
                text = CS_REST_MSG_NO_ARMOUR;
            }
            break;
        case CS_REST_TXT_NO_WEAPON:
            text = GetLocalString(pc, CS_REST_VAR_NO_WEAPON_TEXT);
            if (text == "") {
                text = CS_REST_MSG_NO_WEAPON;
            }
            break;
        case CS_REST_TXT_BEDROLL_RUINED:
            text = GetLocalString(pc, CS_REST_VAR_BEDROLL_RUINED_TEXT);
            if (text == "") {
                text = CS_REST_MSG_BEDROLL_RUINED;
            }
            break;
        case CS_REST_TXT_NOT_COMFORTABLE:
            text = GetLocalString(pc, CS_REST_VAR_NOT_COMFORTABLE_TEXT);
            if (text == "") {
                text = CS_REST_MSG_NOT_COMFORTABLE;
            }
            break;
        case CS_REST_TXT_ENTER_REST:
            text = GetLocalString(pc, CS_REST_VAR_ENTER_REST_TEXT);
            if (text == "") {
                text = CS_REST_MSG_ENTER_REST;
            }
            break;
        case CS_REST_TXT_EXIT_REST:
            text = GetLocalString(pc, CS_REST_VAR_EXIT_REST_TEXT);
            if (text == "") {
                text = CS_REST_MSG_EXIT_REST;
            }
            break;
        case CS_REST_TXT_UNSAFE:
            text = GetLocalString(pc, CS_REST_VAR_UNSAFE_TEXT);
            if (text == "") {
                text = CS_REST_MSG_UNSAFE;
            }
            break;
        case CS_REST_TXT_ENTER_NO_REST:
            text = GetLocalString(pc, CS_REST_VAR_ENTER_NO_REST_TEXT);
            if (text == "") {
                text = CS_REST_MSG_ENTER_NO_REST;
            }
            break;
        case CS_REST_TXT_EXIT_NO_REST:
            text = GetLocalString(pc, CS_REST_VAR_EXIT_NO_REST_TEXT);
            if (text == "") {
                text = CS_REST_MSG_EXIT_NO_REST;
            }
            break;
        case CS_REST_TXT_NO_BEDROLL:
            text = GetLocalString(pc, CS_REST_VAR_NO_BEDROLL_TEXT);
            if (text == "") {
                text = CS_REST_MSG_NO_BEDROLL;
            }
            break;
        default:
            cs_dbg_Trace("Message ID not recognised", cfg.debug);
            break;
    }
    if (text != "") {
        if (text == "off") {
            cs_dbg_Trace("message is disabled");
        }
        else {
            // Expand references in the string.
            cs_dbg_Trace("Expanding embedded references", cfg.debug);
            int pos = FindSubString(text, "<MIN>");
            if (pos > 0) {
                // Number of minutes before next rest.
                struct cs_rest_time currentTime = cs_rest_GetCurrentTime(cfg);
                struct cs_rest_time nextRest = cs_rest_Time(
                    cfg.lastRestDays,
                    cfg.lastRestSeconds
                );
                nextRest = cs_rest_AddTime(nextRest, cs_rest_Time(0, cfg.limit));
                cs_dbg_Trace(
                    "current time: " + cs_rest_TimeToString(currentTime),
                    cfg.debug
                );
                cs_dbg_Trace(
                    "Next rest time: " + cs_rest_TimeToString(nextRest),
                    cfg.debug
                );
                struct cs_rest_time left = cs_rest_SubtractTime(nextRest, currentTime);
                int minutes = (left.seconds / cs_rest_secondsPerMinute);
                string replace = "";
                if (minutes == 0) {
                    replace = "less than a minute";
                }
                else {
                    replace = IntToString(minutes) + " minute";
                    if (minutes > 1) {
                        replace += "s";
                    }
                }
                text =
                    GetStringLeft(text, pos) +
                    replace +
                    GetStringRight(text, GetStringLength(text) - pos - 5);
            }
            FloatingTextStringOnCreature(text, pc, FALSE);
        }
    }

    cs_dbg_Exit("cs_rest_ShowFloatingTextByID", cfg.debug);
}

//----------------------------------------------------------------------------
void cs_rest_ApplyEffects(object pc, struct cs_rest_config cfg) {
    cs_dbg_Enter("cs_rest_ApplyEffects(" + GetName(pc) + ")", cfg.debug);

    cs_dbg_Trace("effects to apply: " + cfg.effects);
    cs_tkn_SetTokenString(cfg.effects);
    while(!cs_tkn_GetIsLastToken()) {
        string token = cs_tkn_GetNextToken();
        int effectID = StringToInt(token);
        if (effectID > 0) {
            cs_dbg_Trace("applying effect: " + token);
            effect eff = EffectVisualEffect(effectID);
            ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eff, pc, 22.0);
        }
        else {
            cs_dbg_Trace("effect failed to parse: " + token);
        }
    }

    cs_dbg_Exit("cs_rest_ApplyEffects", cfg.debug);
}

//----------------------------------------------------------------------------
// This function is used as a failsafe if, for whatever reason, the resting
// fails to stop and so the screen remains faded to black. Leaving players
// with a black screen is not friendly.
void cs_rest_fadeUpFallback(object pc, object debug) {
    cs_dbg_Enter("cs_rest_fadeUpFallback(" + GetName(pc) + ")", debug);

    if (!GetLocalInt(pc, CS_REST_VAR_FADE_DONE)) {
        cs_dbg_Trace("Fade up failed! Fallback kicked in.", debug);
        FadeFromBlack(pc);
    }
    else {
        cs_dbg_Trace("Fade up worked ok.", debug);
    }
    DeleteLocalInt(pc, CS_REST_VAR_FADE_DONE);

    cs_dbg_Exit("cs_rest_fadeUpFallback", debug);
}

//----------------------------------------------------------------------------
void cs_rest_StartResting(object pc, struct cs_rest_config cfg) {
    cs_dbg_Enter("cs_rest_StartResting(" + GetName(pc) + ")", cfg.debug);

    cfg.savedHitPoints = GetCurrentHitPoints(pc);
    cs_rest_ShowFloatingTextByID(pc, cfg, CS_REST_TXT_START);

    // If we're configured to fade the screen to black, do so here.
    if (cfg.fadeToBlack) {
        cs_dbg_Trace("Fading to black", cfg.debug);
        FadeToBlack(pc);
        // Failsafe fade just in case resting is aborted somehow.
        // Yeah yeah, I'm paranoid. I know.
        DelayCommand(25.0, cs_rest_fadeUpFallback(pc, cfg.debug));
    }

    // Apply any effects indicated by the user.
    cs_rest_ApplyEffects(pc, cfg);

    cs_rest_SetConfig(pc, cfg);

    cs_dbg_Exit("cs_rest_StartResting", cfg.debug);
}

//----------------------------------------------------------------------------
void cs_rest_StopResting(object pc, struct cs_rest_config cfg) {
    cs_dbg_Enter("cs_rest_StopResting(" + GetName(pc) + ")", cfg.debug);

    // Did the rest system actually kick in?
    if (cfg.started == CS_REST_STARTED) {
        cs_rest_ShowFloatingTextByID(pc, cfg, CS_REST_TXT_FINISH);

        if (cfg.use3Ed) {
            // Reduce player's HP back to stored value + level.
            int oldHP = cfg.savedHitPoints;
            if (oldHP > 0) {
                // Determine how many HP the PC regains.
                int gained = GetHitDice(pc);
                gained = FloatToInt(IntToFloat(gained) * cfg.multiplier);
                if (cfg.useConBonus) {
                    gained += GetAbilityModifier(ABILITY_CONSTITUTION, pc);
                }
                if (gained < 1) {
                    // Player always regains at least 1 HP.
                    gained = 1;
                }
                cs_dbg_Trace("gained: " + IntToString(gained), cfg.debug);

                // Determine the number of HP the PC should now have.
                int newHP = oldHP + gained;

                // If the new HP value is less than full HP, we need to reduce
                // the PC's HP down again.
                if (newHP < GetMaxHitPoints(pc)) {
                    effect damage = EffectDamage(
                        GetCurrentHitPoints(pc) - newHP,
                        DAMAGE_TYPE_MAGICAL,
                        DAMAGE_POWER_PLUS_FIVE
                    );
                    ApplyEffectToObject(DURATION_TYPE_INSTANT, damage, pc);

                    // Calculate display value.
                    newHP = newHP - oldHP;
                }
                else {
                    // Calculate display value.
                    newHP = GetMaxHitPoints(pc) - oldHP;
                }
                cs_dbg_Trace(
                    GetName(pc) + " regained " +
                    IntToString(newHP) + " hit points",
                    cfg.debug
                );
            }
            else {
                cs_dbg_Trace(
                    GetName(pc) + " has 0 hit points! This is bad...",
                    cfg.debug
                );
            }
        }

        // Set config that we actually want to keep for next time.
        cs_rest_SetLastRestTime(pc, cs_rest_GetCurrentTime(cfg));

        // If we faded the screen to black, fade back up again.
        if (cfg.fadeToBlack) {
            cs_dbg_Trace("Fading screen up", cfg.debug);
            FadeFromBlack(pc);
            SetLocalInt(pc, CS_REST_VAR_FADE_DONE, TRUE);
        }
    }
    else if (cfg.started != CS_REST_ABORTED) {
        cs_dbg_Trace(
            GetName(pc) + ": resting stopped after never starting!",
            cfg.debug
        );
    }

    cs_dbg_Exit("cs_rest_StopResting", cfg.debug);
}

//----------------------------------------------------------------------------
void cs_rest_CancelResting(object pc, struct cs_rest_config cfg) {
    cs_dbg_Enter("cs_rest_CancelResting(" + GetName(pc) + ")", cfg.debug);

    // Did the rest system actually kick in?
    if (cfg.started == CS_REST_STARTED) {
        cs_rest_ShowFloatingTextByID(pc, cfg, CS_REST_TXT_CANCEL);

        if (cfg.use3Ed) {
            // Reduce player's HP to stored value + proportion of level, i.e.
            // if the player rests for half a normal rest period, he gets half
            // his level in HP. If the player is only slightly injured, i.e.
            // less than his level in damage, then the normal resting is left
            // alone which results in slower healing if interrupted close to
            // full hit points. Ah well.
            int oldHP = cfg.savedHitPoints;
            if (oldHP > 0) {
                int maxHP = GetMaxHitPoints(pc);
                if (oldHP != maxHP) {
                    // Determine how many HP the PC regains.
                    int currentHP = GetCurrentHitPoints(pc);
                    int gained = FloatToInt(IntToFloat(GetHitDice(pc)) *
                                 ((IntToFloat(currentHP - oldHP)) /
                                 (IntToFloat(maxHP - oldHP))));
                    gained = FloatToInt(IntToFloat(gained) * cfg.multiplier);
                    if (cfg.useConBonus) {
                        gained += GetAbilityModifier(ABILITY_CONSTITUTION, pc);
                    }
                    if (gained < 1) {
                        // Player always regains at least 1 HP.
                        gained = 1;
                    }
                    cs_dbg_Trace("gained: " + IntToString(gained), cfg.debug);

                    // Determine the number of HP the PC should now have.
                    int newHP = oldHP + gained;

                    // If the PC has more HP than he should, we need to reduce
                    // the PC's HP down again.
                    if (currentHP > newHP) {
                        effect damage = EffectDamage(
                            currentHP - newHP,
                            DAMAGE_TYPE_MAGICAL,
                            DAMAGE_POWER_PLUS_FIVE
                        );
                        ApplyEffectToObject(DURATION_TYPE_INSTANT, damage, pc);
                    }
                    else {
                        gained = currentHP - oldHP;
                    }
                    cs_dbg_Trace(
                        GetName(pc) + " regained " +
                        IntToString(gained) + " hit points",
                        cfg.debug
                    );
                }
            }
            else {
                cs_dbg_Trace(
                    GetName(pc) + " has 0 hit points! This is bad...",
                    cfg.debug
                );
            }
        }

        // Set config that we actually want to keep for next time.
        cs_rest_SetLastRestTime(pc, cs_rest_GetCurrentTime(cfg));

        // If we faded the screen to black, fade back up again.
        if (cfg.fadeToBlack) {
            cs_dbg_Trace("Fading screen up", cfg.debug);
            FadeFromBlack(pc);
            SetLocalInt(pc, CS_REST_VAR_FADE_DONE, TRUE);
        }
    }
    else if (cfg.started != CS_REST_ABORTED) {
        cs_dbg_Trace(
            GetName(pc) + ": resting cancelled after never starting!",
            cfg.debug
        );
    }

    cs_dbg_Exit("cs_rest_CancelResting", cfg.debug);
}