//============================================================================ // // Name: CS Resting Subsystem - Main Include File // File: cs_rest // Author: Craig Smith (Galap) // // $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 "" // 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, ""); 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); }