// Travel Builder // Include file // Author : Proleric // Modified : 26-Mar-2007 // Contains the scripts at the heart of the system, with documentation of // the functions and internal data structures. #include "x0_i0_position" #include "nw_o0_itemmaker" #include "prc_x2_itemprop" #include "x3_inc_horse" // Public Function prototypes // -------------------------- // This function validates the 2da files for Travel Builder. If there // are errors, they are reported as feedback and logged, then a death panel // is displayed. void bhtValidateTravelNetwork(); // Set PC's current position in the travel network. // Any nodes in the current area become "known" to the PC, with feedback // if they were previously unknown. // If the PC has travelled from a different node, feedback describes the // journey (unless the distance was zero, i.e. trivial). // If the caller is a trigger, a waypoint tag xxxx can be specified as the current // node in the trigger tag TR_xxxx (or in local strings Waypoint1, Waypoint2 ...). // In this case, other nodes on that network are ignored. void bhtSetTravelPosition(object oPC); // Set the "known" status of node sNode on network sNetwork for the PC. // if bFeedback is TRUE, a feedback message is sent to the PC. // if bKnown is FALSE, the node is reset to "unknown". void bhtSetTravelNodeKnown(object oPC, string sNetwork, string sNode, int bFeedback=FALSE, int bKnown=TRUE); // Initialise travel shortcut option menu conversation with the PC on network sNetwork. // If bIncludeCurrentNode is TRUE, the current node is listed as an option. // If bCallerTravels is TRUE, the caller travels. // If bPC_Travels is TRUE, the PC travels. void bhtTravelOptionMenu(object oPC, string sNetwork, int bIncludeCurrentNode=TRUE, int bCallerTravels=FALSE, int bPC_Travels=TRUE); // Get encumbrance level : // 0 for non-encumbered character // 1 for lightly encumbered character // 2 for heavily encumbered character int bhtGetEncumbranceLevel(object oTarget = OBJECT_SELF); // Jumps PC to the sNode on sNetwork. Return values are // 0 = Jump executed. // 1 = Network not defined in network 2da. // 2 = Node not defined in node 2da. // 3 = No path found (which can only happen if bLegalJumpsOnly = TRUE). // This can mean current network position is unknown, party is immobile, // horse system violation, or no path via "known" nodes. // If bOnlyCallerJumps is TRUE, the caller is sent to the new node instead. int bhtJumpToTravelNode(object oPC, string sNetwork, string sNode, int bLegalJumpsOnly = TRUE, int bOnlyCallerJumps = FALSE); // Jumps associates to PC's target location. void bhtJumpAssociates(object oPC, object oTarget); // Returns the area corresponding to the nth node through which a requested // shortcut will travel, in reverse order (i.e. the first area returned is the // requested destination, the last is the current area). OBJECT_INVALID when // route is exhausted. This function may only be used in the OnTransitionRequest // user exit. object bhtGetTravelRoute(object oPC, int n=1); // Determine whether party can move int bhtGetIsPartyMobile(object oPC); // Check whether horses are forbidden in an area // Returns 0 = No Restriction, 1 = Must Dismount, 2 = Forbidden int bhtGetIsHorseForbiddenInArea(object oAreaTarget); // Check whether party has a horse // Returns 0 = No Horse, 1 = Dismounted Horse Only, 2 = Mounted Horse int bhtGetIsHorseInParty(object oPC); // Jumps any masterless horses which are assigned to the party to oTarget, // e.g. horses which have been forcibly removed on entry to a "no horse" area. // Horses which are members of the party are unaffected. void bhtJumpHorsesToObject(object oPC, object oTarget); // Export system variables for PC to a data base void bhtExportTravelDataBase(object oPC, string sDatabase); // Import system variables for PC from a data base void bhtImportTravelDataBase(object oPC, string sDatabase); // Public variables // ---------------- // On the creature that triggered the "OnTransitionRequest" pseudo-event : // // bhTargetObject object The door or waypoint the creature wishes to jump to. // bhShortcutRequest int TRUE if event is a shortcut request // PRIVATE SYSTEM INFORMATION // -------------------------- // All private information is subject to change without notice in future // releases. Use it at your own risk. // Private system variables // ------------------------ // Module local variables : // // XptLog int TRUE if feedback should be logged // PC permanent local variables : // // ptNetwork string Current network // ptNodexxxx string Current node on network xxxx // ptNodeKnown# int TRUE if PC has been to node number # before // or knows where it is for plot reasons. // PC temporary local variables during conversation : // // ptInDialog int Set if PC is in travel conversation. // ptDialogLine int Conversation option number for shortcuts // ptNoPC_Travel int TRUE if only the specified companion will travel. // In this case, the PC does NOT travel. // ptCompanion object Companion who will travel. // By default, the PC travels, and henchmen travel // with them. ptCompanion only needs to be set in // two situations : // 1. NPC who is not in the party (e.g. Ferry owner) // is required to travel with the PC. // 2. Henchman or NPC is required to travel without // the PC. // ptIncludeCurrent int TRUE if current node should be listed as an option. // ptIsHorseInParty int 0 = No Horse, 1 = Dismounted Horse Only, 2 = Mounted Horse // PC temporary local arrays used for pathfind, indexed by node number # : // // ptNodeDistance# float Distance of node from current node // ptNodeParent# int The previous node on the shortest path // ptNodeFound# int 0 = Node not found from current node // 1 = Node found but not analysed // 2 = Node found and analysed // // The arrays above work like a variable extension of the Node 2da table by PC. // PC temporary local arrays used for trigger processing : // // ptTriggerWaypoint object Unique node to be processed // ptTriggerNetwork string The corresponding network // Creature local variables set on entry to xx_travel_exit : // // bhTargetObject object The door or waypoint the creature wishes to jump to. // bhShortcutRequest int TRUE if event is a Travel Builder shortcut request. // bhPC object The PC requesting the Travel Builder shortcut. // Creature local variables set on exit from xx_travel_exit : // // bhTargetObject object Set to OBJECT_INVALID to stop the transition. // For doors and triggers only, you can also // change so that the transition is to a different // location (see comment below on shortcuts). // bhJumpAssociates int TRUE if associates should jump to the PC. // Structures // ---------- struct ptPartySpeed {object oSlowestMember; float fSpeed;}; // Global constants // ---------------- const string Xpt2daNetwork = "bhTravelNetwork"; const string Xpt2daNode = "bhTravelNode"; const string Xpt2daPath = "bhTravelPath"; const string Xpt2daFeedback = "bhTravelFeedback"; const string Xpt2daShipType = "bhTravelShipType"; const string DUMMY_JOURNAL = "jt_temp"; const float HOURS_IN_DAY = 24.0; const int CUSTOM_TOKEN_OFFSET = 2300; const int HIGHVALUE = 2147483647; const int HORSE_FORBIDDEN = 2; const int HORSE_DISMOUNT = 1; const int HORSE_ALLOWED = 0; const int HORSE_MOUNTED = 2; const int HORSE_DISMOUNTED = 1; const int HORSE_NOT_FOUND = 0; // Azbest's GetEncumbranceLevel() constants const string s2DA_FILE = "encumbrance"; const string s2DA_COL1 = "Normal"; const string s2DA_COL2 = "Heavy"; const int n2DA_PROP = ABILITY_STRENGTH; // Private function prototypes : Logical // ------------------------------------- // Returns TRUE if sNetwork is defined in the network 2da int ptGetIsNetworkValid(string sNetwork); // Check that a node is valid int ptGetIsNodeValid(string sTestNode, string sNetwork); // TRUE if the node specified in the sNodetype column of row n2da in the path // 2da is defined for sNetwork in the node 2da. int ptGetIsPathNodeValid(object oPC, string sNodeType, string sNetwork, int n2da); // Check whether PC can travel via a node to which a path has been established. // Node is available when known, subject to horse restrictions. int ptGetIsNodeAvailable(object oPC, int nNode, string sNode, int nIsHorseInParty); // TRUE if object is on ship. // vObject is a vector from the centre of the ship to the object with magnitude fObjectDistance. int ptGetIsObjectOnShip(vector vObject, float fObjectDistance, float fShipFacing, float fShipWidth, float fShipLength); // Private function prototypes : Information // ----------------------------------------- // Returns the row number for sNetwork in the network 2da (0 if not found) int ptGetNetworkID(string sNetwork); // Get the row number for sNode in the node 2da for the current network int ptGetNodeID(string sNetwork, string sNode); // Returns the row number in the node 2da for the nth node which should be // presented to the PC as a travel option (-1 if nodes are exhausted). int ptGetNodeIDForOption(object oPC, int nOption, int nIsHorseInParty, int nFeedback); // Get the node corresponding to a waypoint and return a node 2da field string ptGetNodeFieldFromWaypoint(object oWaypoint, string sField); // Returns travel time in hours for fDistance. // If nModify_For_Party_Speed is TRUE, take party speed into account. int ptGetTravelTime(object oPC, float fDistance, int nModify_For_Party_Speed); // Get party speed and slowest member struct ptPartySpeed ptGetSlowestPartyMember(object oPC, float fBaseSpeed, struct ptPartySpeed PartySpeed); // Returns the speed of an NPC. // fBaseSpeed is the default travel speed for the current network. float ptGetSpeed(object oNPC, float fBaseSpeed); // Get the centre of as ship. // The cabin door is used as a fixed reference point, if there is one. // Otherwise, the node waypoint is used as the centre of the ship. location ptGetShipCentre(object oShipNode, float fDoorPosition); // Private function prototypes : Messages // -------------------------------------- // Outputs line nFeedbackMessage from bhTravelFeedback as feedback to the PC // (and as a log message, if logging is enabled). Optionally, placeholders #1 to // #4 can be replaced with strings s1-s4. Returns TRUE (for convenience). int ptSendFeedback(object oPC, int nFeedbackMessage, string s1="", string s2="", string s3="", string s4=""); // Report a duplicate waypoint for node 2da line n2da int ptReportDuplicateWaypoint(object oPC, int n2da, string sWaypoint, int nWaypoint); // Format the journey time for presentation as a message to the PC. string ptTravelTime(int nTime); // Format the journey distance for presentation as a message to the PC. string ptTravelDistance(float fDistance); // Private function prototypes : Pathfind // -------------------------------------- // Find paths for PC on sNetwork void ptFindPaths(object oPC, string sNetwork); // Initialise network for Dijkstra shortest path algorithm void ptFindPathsInitialise(object oPC); // Finds the shortest path from node sSource to all connected nodes on the current network. void ptFindPathsExecute(object oPC, string sSource); // Iterative routine to find shortest paths in a network (Dijkstra algorithm) void ptFindPathsSingleNode(object oPC, int nNode, int nGetIsHorseInParty); // Private function prototypes : Execution // --------------------------------------- // Set the current network for the PC to the default and do housekeeping void ptSetDefaultNetwork(object oPC); // Jump PC or caller to node identified by 2da line nTargetnode void ptJumpToNode(object oPC, int nTargetNode, object oCaller = OBJECT_INVALID, int bOnlyCallerJumps = FALSE); // Jump associates of a given type to a target location void ptJumpAssociate(object i_oPC, int i_type, object i_oWP); // Moves any items on the deck of a ship at sSourceNode to another ship at // sTargetNode on network sNetwork. void ptMoveShipItems(object oPC, string sNetwork, string sSourceNode, string sTargetNode, int nShipType); // Move items from one container to another void ptMoveItems(object oOldChest, object oNewChest); // Private function prototypes : Utility // ------------------------------------- // Set a local array float variable void SetLocalArrayFloat(object oObject, string sName, int nIndex, float fValue); void SetLocalArrayFloat(object oObject, string sName, int nIndex, float fValue) {SetLocalFloat(oObject, sName + IntToString(nIndex), fValue);} // Get a local array float value float GetLocalArrayFloat(object oObject, string sName, int nIndex); float GetLocalArrayFloat(object oObject, string sName, int nIndex) {return GetLocalFloat(oObject, sName + IntToString(nIndex));} // Set a local array object variable void SetLocalArrayObject(object oObject, string sName, int nIndex, object oValue); void SetLocalArrayObject(object oObject, string sName, int nIndex, object oValue) {SetLocalObject(oObject, sName + IntToString(nIndex), oValue);} // Get a local array object value object GetLocalArrayObject(object oObject, string sName, int nIndex); object GetLocalArrayObject(object oObject, string sName, int nIndex) {return GetLocalObject(oObject, sName + IntToString(nIndex));} // FUNCTION DEFINITIONS // Validate the travel map information void bhtValidateTravelNetwork() { object oPC = GetFirstPC(); int n2da; int bError = FALSE; string sNetwork; string sWaypoint; int nWaypoint; // Enable logging SetLocalInt(GetModule(), "XptLog", TRUE); // Check that nodes are valid n2da = 0; sNetwork = Get2DAString(Xpt2daNode, "Network", n2da); while (sNetwork != "") { if (!ptGetIsNetworkValid(sNetwork)) bError = ptSendFeedback(oPC, 0, IntToString(n2da), sNetwork); sWaypoint = Get2DAString(Xpt2daNode, "Waypoint", n2da); if (!GetIsObjectValid(GetObjectByTag(sWaypoint))) bError = ptSendFeedback(oPC, 1, IntToString(n2da), sWaypoint); nWaypoint = 0; while (GetIsObjectValid(GetObjectByTag(sWaypoint, ++nWaypoint))) { if (nWaypoint == 1) bError = ptReportDuplicateWaypoint(oPC, n2da, sWaypoint, 0); // Report 1st instance if at least two. bError = ptReportDuplicateWaypoint(oPC, n2da, sWaypoint, nWaypoint); // Report 2nd and subsequent instances. } sNetwork = Get2DAString(Xpt2daNode, "Network", ++n2da); } // Check that paths are valid. n2da = 0; sNetwork = Get2DAString(Xpt2daPath, "Network", n2da); while (sNetwork != "") { if (!ptGetIsNetworkValid(sNetwork)) bError = ptSendFeedback(oPC, 2, IntToString(n2da), sNetwork); if (!ptGetIsPathNodeValid(oPC, "Source_Node", sNetwork, n2da)) bError = TRUE; if (!ptGetIsPathNodeValid(oPC, "Target_Node", sNetwork, n2da)) bError = TRUE; sNetwork = Get2DAString(Xpt2daPath, "Network", ++n2da); } if (bError) if (StringToInt(Get2DAString(Xpt2daFeedback, "Enable", 4))) PopUpDeathGUIPanel(GetFirstPC(), TRUE, FALSE, 0, Get2DAString(Xpt2daFeedback, "Message", 4)); // Disable logging SetLocalInt(GetModule(), "XptLog", FALSE); } // Set PC's current position in the travel network. // Any nodes in the current area become "known" to the PC, with feedback // if they were previously unknown. // If the PC has travelled from a different node, feedback describes the // journey (unless the distance was zero, i.e. trivial). // If the caller is a trigger, a waypoint tag xxxx can be specified as the current // node in the trigger tag TR_xxxx (or in local strings Waypoint1, Waypoint2 ...). // In this case, other nodes on that network are ignored. void bhtSetTravelPosition(object oPC) { object oArea = GetArea(oPC); object oCaller = OBJECT_SELF; string sCallerTag = GetTag(oCaller); string sCurrentNetwork = GetLocalString(oPC, "ptNetwork"); string sNetwork; string sNode; string sWaypoint; object oWaypoint; object oTriggerWaypoint = OBJECT_INVALID; string sTriggerNetwork = ""; int nTriggerCount = 0; int n; int bCurrentNode; object oCompanion; int bFeedback = TRUE; float fDistance; string sDistance; int nTime; string sTime; int bModify_For_Party_Speed; string sSourceNode; string sTargetNode; int nTargetNode; int nShip_Type; int nNode; // If current network is uninitialised for PC, set to default. if (sCurrentNetwork == "") { ptSetDefaultNetwork(oPC); sCurrentNetwork = GetLocalString(oPC, "ptNetwork"); } // Exception processing for triggers. If a network has more than one // node in the same area, this provides a means of identifying which // node the PC has arrived at, without making the others "known" automatically. if (GetObjectType(oCaller) == OBJECT_TYPE_TRIGGER) { sWaypoint = StringReplace(sCallerTag, "TR_", ""); oTriggerWaypoint = GetObjectByTag(sWaypoint); sTriggerNetwork = ptGetNodeFieldFromWaypoint(oTriggerWaypoint, "Network"); if (sTriggerNetwork != "") { ++nTriggerCount; SetLocalArrayObject(oPC, "ptTriggerWaypoint", nTriggerCount, oTriggerWaypoint); SetLocalArrayString(oPC, "ptTriggerNetwork", nTriggerCount, sTriggerNetwork); } sWaypoint = GetLocalArrayString(oCaller, "Waypoint", ++nTriggerCount); while (sWaypoint != "") { oTriggerWaypoint = GetObjectByTag(sWaypoint); sTriggerNetwork = ptGetNodeFieldFromWaypoint(oTriggerWaypoint, "Network"); if (sTriggerNetwork != "") { SetLocalArrayObject(oPC, "ptTriggerWaypoint", nTriggerCount, oTriggerWaypoint); SetLocalArrayString(oPC, "ptTriggerNetwork", nTriggerCount, sTriggerNetwork); } sWaypoint = GetLocalArrayString(oCaller, "Waypoint", ++nTriggerCount); } } // Get previous node on current network (which may be null initially) sSourceNode = GetLocalString(oPC, "ptNode" + sCurrentNetwork); // Flag nodes in area as "known" and reset current position on all networks nNode = -1; while (Get2DAString(Xpt2daNode, "Node_Name", ++nNode) != "") { sWaypoint = Get2DAString(Xpt2daNode, "Waypoint", nNode); oWaypoint = GetObjectByTag(sWaypoint); if (GetArea(oWaypoint) == oArea) { sNetwork = Get2DAString(Xpt2daNode, "Network", nNode); sNode = Get2DAString(Xpt2daNode, "Node_Name", nNode); bCurrentNode = TRUE; nTriggerCount = 1; sTriggerNetwork = GetLocalArrayString(oPC, "ptTriggerNetwork", nTriggerCount); while (sTriggerNetwork != "") { if ((sNetwork == sTriggerNetwork ) && (oWaypoint != GetLocalArrayObject(oPC, "ptTriggerWaypoint", nTriggerCount))) bCurrentNode = FALSE; sTriggerNetwork = GetLocalArrayString(oPC, "ptTriggerNetwork", ++nTriggerCount); } if (bCurrentNode) { SetLocalString(oPC, "ptNode" + sNetwork, sNode); bhtSetTravelNodeKnown(oPC, sNetwork, sNode, bFeedback); } } } // If node has changed on current network, analyse and report on journey sTargetNode = GetLocalString(oPC, "ptNode" + sCurrentNetwork); if (sSourceNode == "") sSourceNode = sTargetNode; // May be uninitialised if (sTargetNode != sSourceNode) { ptFindPathsExecute(oPC, sSourceNode); nTargetNode = ptGetNodeID(sCurrentNetwork, sTargetNode); if (!GetLocalArrayInt(oPC, "ptNodeFound", nTargetNode)) // Error : path not found { fDistance = 0.0; ptSendFeedback(oPC, 12, sSourceNode, sTargetNode, sCurrentNetwork); } else fDistance = GetLocalArrayFloat(oPC, "ptNodeDistance", nTargetNode); bModify_For_Party_Speed = StringToInt(Get2DAString(Xpt2daNetwork, "Modify_For_Party_Speed", ptGetNetworkID(sCurrentNetwork))); nTime = ptGetTravelTime(oPC, fDistance, bModify_For_Party_Speed); sTime = ptTravelTime(nTime); sDistance = ptTravelDistance(fDistance); if (fDistance > 0.0) ptSendFeedback(oPC, 6, sSourceNode, sTargetNode, sDistance, sTime); oCompanion = GetLocalObject(oPC, "ptCompanion"); oWaypoint = GetObjectByTag(Get2DAString(Xpt2daNode, "Waypoint", nTargetNode)); if (GetIsObjectValid(oCompanion)) { AssignCommand(oCompanion, ClearAllActions()); AssignCommand(oCompanion, ActionJumpToObject(oWaypoint)); } nShip_Type = StringToInt(Get2DAString(Xpt2daNetwork, "Ship_Type", ptGetNetworkID(sCurrentNetwork))); if (nShip_Type) { ptMoveShipItems(oPC, sCurrentNetwork, sSourceNode, sTargetNode, nShip_Type); } if (!GetLocalInt(GetModule(), "bh_TravelMultiplayer")) if (fDistance > 0.0) SetTime(GetTimeHour() + nTime, GetTimeMinute(), 0, 0); } // End journey processing // Reset current network to default (only conversation can use other networks) ptSetDefaultNetwork(oPC); } // Flag a node as known to the PC void bhtSetTravelNodeKnown(object oPC, string sNetwork, string sNode, int bFeedback=FALSE, int bKnown=TRUE) { int nNode = -1; while (Get2DAString(Xpt2daNode, "Node_Name", ++nNode) != "") { if (Get2DAString(Xpt2daNode, "Network", nNode) == sNetwork) if (Get2DAString(Xpt2daNode, "Node_Name", nNode) == sNode) { if (bFeedback) if (!GetLocalArrayInt(oPC, "ptNodeKnown", nNode)) if (bKnown) ptSendFeedback(oPC, 5, sNode); SetLocalArrayInt(oPC, "ptNodeKnown", nNode, bKnown); break; } } } // Initialise travel option menu conversation with PC on a network // If travel is impossible, all nodes are disabled. This can happen when // 1. The PC has not yet found any nodes on the network. // 2. The party is immobile (e.g. one of the members is paralysed). void bhtTravelOptionMenu(object oPC, string sNetwork, int bIncludeCurrentNode=TRUE, int bCallerTravels=FALSE, int bPC_Travels=TRUE) { object oCaller = OBJECT_SELF; SetLocalInt(oPC, "ptInDialog", 1); SetLocalInt(oPC, "ptIncludeCurrent", bIncludeCurrentNode); SetLocalInt(oPC, "ptDialogLine", 0); SetLocalInt(oPC, "ptIsHorseInParty", bhtGetIsHorseInParty(oPC)); if (StringToInt(Get2DAString(Xpt2daFeedback, "Enable", 11))) SetCustomToken(CUSTOM_TOKEN_OFFSET, Get2DAString(Xpt2daFeedback, "Message", 11)); else SetCustomToken(CUSTOM_TOKEN_OFFSET, ""); if (bCallerTravels) SetLocalObject(oPC, "ptCompanion", oCaller); if (!bPC_Travels) { SetLocalInt (oPC, "ptNoPC_Travel", TRUE); if (StringToInt(Get2DAString(Xpt2daFeedback, "Enable", 18))) SetCustomToken(CUSTOM_TOKEN_OFFSET, Get2DAString(Xpt2daFeedback, "Message", 18)); else SetCustomToken(CUSTOM_TOKEN_OFFSET, ""); } ptFindPaths(oPC, sNetwork); AssignCommand(oCaller, ActionStartConversation(oPC, "bh_travel", FALSE, FALSE)); } // Jumps PC to the sNode on sNetwork. Return values are // 0 = Jump executed. // 1 = Network not defined in network 2da. // 2 = Node not defined in node 2da. // 3 = No path found (which can only happen if bLegalJumpsOnly = TRUE). // This can mean current network position is unknown, party is immobile, // horse system violation, or no path via "known" nodes. // If bOnlyCallerJumps is TRUE, the caller is sent to the new node instead. int bhtJumpToTravelNode(object oPC, string sNetwork, string sNode, int bLegalJumpsOnly = TRUE, int bOnlyCallerJumps = FALSE) { int JUMP_EXECUTED = 0; int NETWORK_INVALID = 1; int NODE_INVALID = 2; int NO_PATH_FOUND = 3; object oCaller = OBJECT_SELF; string sSourceNode = GetLocalString(oPC, "ptNode" + sNetwork); int nTargetNode; if (!ptGetIsNetworkValid(sNetwork)) return NO_PATH_FOUND; if (!ptGetIsNodeValid(sNode, sNetwork)) return NODE_INVALID; nTargetNode = ptGetNodeID(sNetwork, sNode); if (bLegalJumpsOnly) { ptFindPaths(oPC, sNetwork); if (!GetLocalArrayInt(oPC, "ptNodeFound", nTargetNode)) return NO_PATH_FOUND; } ptJumpToNode(oPC, nTargetNode, oCaller, bOnlyCallerJumps); return JUMP_EXECUTED; } // Returns the area corresponding to the nth node through which a requested // shortcut will travel, in reverse order (i.e. the first area returned is the // requested destination, the last is the current area). OBJECT_INVALID when // route is exhausted. This function may only be used in the OnTransitionRequest // user exit. object bhtGetTravelRoute(object oPC, int n=1) { string sNetwork = GetLocalString(oPC, "ptNetwork"); string sSourceNode = GetLocalString(oPC, "ptNode" + sNetwork); object oWaypoint = GetLocalObject(oPC, "bhTargetObject"); string sTargetNode = ptGetNodeFieldFromWaypoint(oWaypoint, "Node_Name"); int nSourceNode = ptGetNodeID(sNetwork, sSourceNode); int nTargetNode = ptGetNodeID(sNetwork, sTargetNode); int nNodeCount = 0; while (++nNodeCount < n) { if (nTargetNode == nSourceNode) break; // Route exhausted nTargetNode = GetLocalArrayInt(oPC, "ptNodeParent", nTargetNode); } if (nNodeCount < n) return OBJECT_INVALID; oWaypoint = GetObjectByTag(Get2DAString(Xpt2daNode, "Waypoint", nTargetNode)); return GetArea(oWaypoint); } // Jump PC or Caller to node identified by 2da line nTargetnode void ptJumpToNode(object oPC, int nTargetNode, object oCaller = OBJECT_INVALID, int bOnlyCallerJumps = FALSE) { string sWaypoint; object oWaypoint; int bUserAbort = FALSE; // Identify target waypoint sWaypoint = Get2DAString(Xpt2daNode, "Waypoint", nTargetNode); oWaypoint = GetObjectByTag(sWaypoint); // User-defined exit if (!bOnlyCallerJumps) oCaller = oPC; SetLocalObject(oCaller, "bhTargetObject", oWaypoint); SetLocalInt (oCaller, "bhShortcutRequest", TRUE); SetLocalObject(oCaller, "bhPC", oPC); DeleteLocalInt(oCaller, "bhJumpAssociates"); ExecuteScript("xx_travel_exit", oCaller); if (!GetIsObjectValid(GetLocalObject(oCaller, "bhTargetObject"))) bUserAbort = TRUE; DeleteLocalObject(oCaller, "bhTargetObject"); DeleteLocalInt (oCaller, "bhShortcutRequest"); DeleteLocalObject(oCaller, "bhPC"); if (bUserAbort) return; // If caller is a henchman, remove from party if (GetIsObjectValid(GetMaster(oCaller))) RemoveHenchman (GetMaster(oCaller), oCaller); // Execute jump AssignCommand(oCaller, ClearAllActions(TRUE)); AssignCommand(oCaller, ActionJumpToObject(oWaypoint)); if (GetLocalInt(oCaller, "bhJumpAssociates")) { DeleteLocalInt(oCaller, "bhJumpAssociates"); bhtJumpAssociates(oCaller, oWaypoint); } } // Find paths for PC on sNetwork void ptFindPaths(object oPC, string sNetwork) { string sCurrentNode = GetLocalString(oPC, "ptNode" + sNetwork); SetLocalString(oPC, "ptNetwork", sNetwork); if (sCurrentNode == "") ptFindPathsInitialise(oPC); else ptFindPathsExecute(oPC, sCurrentNode); if (!bhtGetIsPartyMobile(oPC)) ptFindPathsInitialise(oPC); } // Determine whether a network name is defined in the network 2da int ptGetIsNetworkValid(string sNetwork) { string sCurrentNetwork; int n2da = 0; sCurrentNetwork = Get2DAString(Xpt2daNetwork, "Network", n2da); while (sCurrentNetwork != "") { if (sCurrentNetwork == sNetwork) return TRUE; sCurrentNetwork = Get2DAString(Xpt2daNetwork, "Network", ++n2da); } return FALSE; } // Find a network row in the network 2da int ptGetNetworkID(string sNetwork) { string sTestNetwork; int n2da = 0; sTestNetwork = Get2DAString(Xpt2daNetwork, "Network", n2da); while (sTestNetwork != "") { if (sTestNetwork == sNetwork) return n2da; sTestNetwork = Get2DAString(Xpt2daNetwork, "Network", ++n2da); } return 0; } // Find the nth node current PC can travel to (return -1 if nodes exhausted) // Path find has already excluded nodes in which no horses are allowed, but // allows the PC to pass through nodes which require dismounting. // Here, we exclude ARRIVAL at a "dismount" node if the party is mounted, // ensuring that the warning message is only given once (at the point at // which the node would normally have been selected). int ptGetNodeIDForOption(object oPC, int nOption, int nIsHorseInParty, int nFeedback) { int nNode = -1; int nNodeCount = 0; int nNodeFound = -1; string sNetwork = GetLocalString(oPC, "ptNetwork"); string sCurrentNode = GetLocalString(oPC, "ptNode" + sNetwork); int nCurrentNode = -1; string sWaypoint; object oWaypoint; if (sCurrentNode == "") return nCurrentNode; // Network uninitialised if (!GetLocalInt(oPC, "ptIncludeCurrent")) nCurrentNode = ptGetNodeID(sNetwork, sCurrentNode); while (Get2DAString(Xpt2daNode, "Node_Name", ++nNode) != "") { if (GetLocalArrayInt(oPC, "ptNodeFound", nNode)) if (nNode != nCurrentNode) { ++nNodeCount; sWaypoint = Get2DAString(Xpt2daNode, "Waypoint", nNode); oWaypoint = GetObjectByTag(sWaypoint); if ((nIsHorseInParty == HORSE_MOUNTED) && (bhtGetIsHorseForbiddenInArea(GetArea(oWaypoint)) == HORSE_DISMOUNT)) { if (nFeedback) if (nNodeCount == nOption) ptSendFeedback(oPC, 15, Get2DAString(Xpt2daNode, "Node_Name", nNode)); --nNodeCount; } else if (nNodeCount == nOption) nNodeFound = nNode; } } return nNodeFound; } // Check that a node in the path table is valid int ptGetIsPathNodeValid(object oPC, string sNodeType, string sNetwork, int n2da) { string sTestNode = Get2DAString(Xpt2daPath, sNodeType, n2da); if (!ptGetIsNodeValid(sTestNode, sNetwork)) return (!ptSendFeedback(oPC, 3, IntToString(n2da), sTestNode, sNetwork)); return TRUE; } // Check that a node is valid int ptGetIsNodeValid(string sTestNode, string sNetwork) { int nNode = 0; string sNode = Get2DAString(Xpt2daNode, "Node_Name", nNode); while (sNode != "") { if ((sTestNode == sNode) && (Get2DAString(Xpt2daNode, "Network", nNode) == sNetwork)) break; sNode = Get2DAString(Xpt2daNode, "Node_Name", ++nNode); } return (sNode != ""); } // Send feedback to PC int ptSendFeedback(object oPC, int nFeedbackMessage, string s1="", string s2="", string s3="", string s4="") { string sErrorMessage = Get2DAString(Xpt2daFeedback, "Message", nFeedbackMessage); if (!StringToInt(Get2DAString(Xpt2daFeedback, "Enable", nFeedbackMessage))) return TRUE; if (s1 != "") sErrorMessage = StringReplace(sErrorMessage, "#1", s1); if (s2 != "") sErrorMessage = StringReplace(sErrorMessage, "#2", s2); if (s3 != "") sErrorMessage = StringReplace(sErrorMessage, "#3", s3); if (s4 != "") sErrorMessage = StringReplace(sErrorMessage, "#4", s4); SendMessageToPC(oPC, sErrorMessage); if (GetLocalInt(GetModule(), "XptLog")) WriteTimestampedLogEntry(sErrorMessage); return TRUE; } // Find the shortest path to all nodes the PC can travel to on the current network // The algorithm is inefficient, but fine for small networks. void ptFindPathsExecute(object oPC, string sSource) { string sNetwork = GetLocalString(oPC, "ptNetwork"); int nSource; nSource = ptGetNodeID(sNetwork, sSource); ptFindPathsInitialise(oPC); SetLocalArrayFloat(oPC, "ptNodeDistance", nSource, 0.0); ptFindPathsSingleNode(oPC, nSource, bhtGetIsHorseInParty(oPC)); } // Get the node row number in the node 2da int ptGetNodeID(string sNetwork, string sNode) { int nNode = 0; while ((Get2DAString(Xpt2daNode, "Node_Name", nNode) != sNode) || (Get2DAString(Xpt2daNode, "Network", nNode) != sNetwork)) ++nNode; return nNode; } // Initialise network for Dijkstra shortest path algorithm // All nodes are set to "not found" and "far away". void ptFindPathsInitialise(object oPC) { int nNode = -1; while (Get2DAString(Xpt2daNode, "Node_Name", ++nNode) != "") { SetLocalArrayFloat(oPC, "ptNodeDistance", nNode, IntToFloat(HIGHVALUE)); SetLocalArrayInt (oPC, "ptNodeFound" , nNode, 0); } } // Iterative routine to find shortest paths in a network (Dijkstra algorithm) // Flag current node as found and complete. // Select paths from the current node which are known to the PC, noting that // source and target may be in reverse order in the 2da. // If the target node was not previously found, flag as found but incomplete. // If the cumulative distance to the target is the shortest so far, store it. // After all the paths are analysed, iterate for the first incomplete node // until no incomplete nodes remain. void ptFindPathsSingleNode(object oPC, int nNode, int nGetIsHorseInParty) { int nPath = 0; string sNode = Get2DAString(Xpt2daNode, "Node_Name", nNode); float fDistanceFromSource = GetLocalArrayFloat(oPC, "ptNodeDistance" , nNode); int nTarget; float fTargetDistance; string sNetwork; string sSource; string sTarget; int INCOMPLETE = 1; int COMPLETE = 2; SetLocalArrayInt(oPC, "ptNodeFound", nNode, COMPLETE); sNetwork = Get2DAString(Xpt2daPath, "Network", nPath); while (sNetwork != "") { if (sNetwork == GetLocalString(oPC, "ptNetwork")) // Current network only { sSource = Get2DAString(Xpt2daPath, "Source_Node", nPath); sTarget = Get2DAString(Xpt2daPath, "Target_Node", nPath); if (sTarget == sNode) {sTarget = sSource; sSource = sNode;} if (sSource == sNode) // Current node only { nTarget = ptGetNodeID(sNetwork, sTarget); if (ptGetIsNodeAvailable(oPC, nTarget, sTarget, nGetIsHorseInParty)) // Available nodes only { if (!GetLocalArrayInt(oPC, "ptNodeFound", nTarget)) SetLocalArrayInt(oPC, "ptNodeFound", nTarget, INCOMPLETE); fTargetDistance = fDistanceFromSource + StringToFloat(Get2DAString(Xpt2daPath, "Length", nPath)); if (fTargetDistance < GetLocalArrayFloat(oPC, "ptNodeDistance", nTarget)) { SetLocalArrayFloat(oPC, "ptNodeDistance", nTarget, fTargetDistance); SetLocalArrayInt (oPC, "ptNodeParent", nTarget, nNode); } } // End available nodes only } // End current node only } // End current network only sNetwork = Get2DAString(Xpt2daPath, "Network", ++nPath); } nNode = 0; sNode = Get2DAString(Xpt2daNode, "Node_Name", nNode); while (sNode != "") { if (GetLocalArrayInt(oPC, "ptNodeFound", nNode) == INCOMPLETE) { ptFindPathsSingleNode(oPC, nNode, nGetIsHorseInParty); break; } sNode = Get2DAString(Xpt2daNode, "Node_Name", ++nNode); } } // Format the journey time for presentation as a message to the PC. // By default, journeys longer than 23 hours are described in days. // This conversion to "day" can be disabled in the 2da. // "Hour" and "hours" can be disabled, to force reporting in days. string ptTravelTime(int nTime) { string sTime = ""; int nUnitsDefined = FALSE; int nTimeInWeeks = FALSE; if (StringToInt(Get2DAString(Xpt2daFeedback, "Enable", 7))) { sTime = Get2DAString(Xpt2daFeedback, "Message", 7); // "Hour" nUnitsDefined = TRUE; } if ((nTime != 1) || !nUnitsDefined) if (StringToInt(Get2DAString(Xpt2daFeedback, "Enable", 8))) { sTime = Get2DAString(Xpt2daFeedback, "Message", 8); // "Hours" nUnitsDefined = TRUE; } if ((nTime > 23) || !nUnitsDefined) if (StringToInt(Get2DAString(Xpt2daFeedback, "Enable", 9))) { sTime = Get2DAString(Xpt2daFeedback, "Message", 9); // "Day" nUnitsDefined = TRUE; nTime = FloatToInt((IntToFloat(nTime) / HOURS_IN_DAY) + 0.5); // Convert to "Days" nTimeInWeeks = TRUE; } if (nTimeInWeeks && (nTime > 1)) if (StringToInt(Get2DAString(Xpt2daFeedback, "Enable", 10))) sTime = Get2DAString(Xpt2daFeedback, "Message", 10); // "Days" if (!nUnitsDefined) sTime = ""; return IntToString(nTime) + " " + sTime; } // Format the journey distance for presentation as a message to the PC. string ptTravelDistance(float fDistance) { string sDistance = ""; int nUnitsDefined = FALSE; if (StringToInt(Get2DAString(Xpt2daFeedback, "Enable", 19))) { sDistance = Get2DAString(Xpt2daFeedback, "Message", 19); // "Mile" nUnitsDefined = TRUE; } if ((fDistance != 1.0) || !nUnitsDefined) if (StringToInt(Get2DAString(Xpt2daFeedback, "Enable", 20))) { sDistance = Get2DAString(Xpt2daFeedback, "Message", 20); // "Miles" nUnitsDefined = TRUE; } return FloatToString(fDistance, 0, 0) + " " + sDistance; } // Calculate travel time (in hours) int ptGetTravelTime(object oPC, float fDistance, int nModify_For_Party_Speed) { string sNetwork = GetLocalString(oPC, "ptNetwork"); int nNetwork = ptGetNetworkID(sNetwork); float fTime; float fNPC_Speed; float fTravelDay = StringToFloat(Get2DAString(Xpt2daNetwork, "Travel_Hours_Per_Day", nNetwork)); float fBaseSpeed = StringToFloat(Get2DAString(Xpt2daNetwork, "Base_Speed", nNetwork)); struct ptPartySpeed PartySpeed; if (fDistance == 0.0) return 0; // Determine the speed of the slowest party member PartySpeed.oSlowestMember = OBJECT_INVALID; PartySpeed.fSpeed = IntToFloat(HIGHVALUE); PartySpeed = ptGetSlowestPartyMember(oPC, fBaseSpeed, PartySpeed); // Party speed may not be relevant (e.g. ship travel) if (!nModify_For_Party_Speed) PartySpeed.fSpeed = fBaseSpeed; else if (PartySpeed.oSlowestMember != oPC) if (fDistance > 0.0) ptSendFeedback(oPC, 13, GetName(PartySpeed.oSlowestMember)); // Calculate actual travel time fTime = fDistance / PartySpeed.fSpeed; // Add resting time fTime += (HOURS_IN_DAY - fTravelDay) * FloatToInt(fTime / fTravelDay); // Round to nearest hour return (FloatToInt(fTime + 0.5)); } // Get party speed and slowest member // Recursive because henchmen can have horses as henchmen struct ptPartySpeed ptGetSlowestPartyMember(object oPC, float fBaseSpeed, struct ptPartySpeed PartySpeed) { int nHench = 1; object oNPC = GetHenchman(oPC, nHench); struct ptPartySpeed NewPartySpeed = PartySpeed; float fSpeed = ptGetSpeed(oPC, fBaseSpeed); if (fSpeed < NewPartySpeed.fSpeed) {NewPartySpeed.fSpeed = fSpeed; NewPartySpeed.oSlowestMember = oPC;} while (GetIsObjectValid(oNPC)) { NewPartySpeed = ptGetSlowestPartyMember(oNPC, fBaseSpeed, NewPartySpeed); oNPC = GetHenchman(oPC, ++nHench); } return NewPartySpeed; } // Get creature speed for long-distance travel. // The approach is consistent with the Bioware horse system. // The speed returned may be slightly different from the factor used in // the Bioware engine for normal movement within an area. // Base speed is the default passed to the function. // The standard Monk speed increase of 10% for every 3 levels is applied. // Encumbrance is taken into account. // Haste and Slow effects, and Haste items such as Boots of Speed, are applied. // Mounted characters get the speed increase used by the Bioware horse system. // Movement rate and Movement Speed effects are ignored, to avoid double-counting // with the horse system. // Temporary effects are ignored, because this is long-distance travel. float ptGetSpeed(object oNPC, float fBaseSpeed) { float fSpeed; effect eEffect; int nEffectType; int nMonk = GetLevelByClass(CLASS_TYPE_MONK, oNPC); int nEncumbranceLevel = bhtGetEncumbranceLevel(oNPC); int nHorseSpeedIncrease; fSpeed = fBaseSpeed; fSpeed = fSpeed * (1.0 + (0.1 * (nMonk / 3))); while (nEncumbranceLevel) { fSpeed = 0.5 * fSpeed; --nEncumbranceLevel; } if (IPGetHasItemPropertyOnCharacter(oNPC, ITEM_PROPERTY_HASTE)) fSpeed = 1.5 * fSpeed; eEffect = GetFirstEffect(oNPC); while (GetIsEffectValid(eEffect)) { nEffectType = GetEffectType(eEffect); if (GetEffectDurationType(eEffect) == DURATION_TYPE_PERMANENT) { if (nEffectType == EFFECT_TYPE_HASTE) fSpeed = 1.5 * fSpeed; if (nEffectType == EFFECT_TYPE_SLOW) fSpeed = 0.5 * fSpeed; } eEffect = GetNextEffect(oNPC); } if (HorseGetIsMounted(oNPC)) { nHorseSpeedIncrease = GetLocalInt(oNPC, "nX3_SpeedIncrease"); fSpeed = fSpeed * (1.0 + (IntToFloat(nHorseSpeedIncrease) / 100.0)); } return fSpeed; } // Set the current network for the PC to the default and do housekeeping // Clears local variables set in conversation, pathfinding and trigger processing void ptSetDefaultNetwork(object oPC) { int nNode = -1; int nTriggerCount = 0; SetLocalString(oPC, "ptNetwork", Get2DAString(Xpt2daNetwork, "Network", 0)); DeleteLocalInt (oPC, "ptInDialog"); DeleteLocalInt (oPC, "ptDialogLine"); DeleteLocalInt (oPC, "ptIncludeCurrent"); DeleteLocalInt (oPC, "ptNoPC_Travel"); DeleteLocalInt (oPC, "ptIsHorseInParty"); DeleteLocalObject(oPC, "ptCompanion"); while (Get2DAString(Xpt2daNode, "Network", ++nNode) != "") { DeleteLocalFloat(oPC, "ptNodeDistance" + IntToString(nNode)); DeleteLocalInt (oPC, "ptNodeParent" + IntToString(nNode)); DeleteLocalInt (oPC, "ptNodeFound" + IntToString(nNode)); } while (GetLocalArrayString(oPC, "ptTriggerNetwork", ++nTriggerCount) != "") { DeleteLocalString(oPC, "ptTriggerNetwork" + IntToString(nTriggerCount)); DeleteLocalObject(oPC, "ptTriggerWaypoint" + IntToString(nTriggerCount)); } } // Get encumbrance level // Author : Azbest 2008 int bhtGetEncumbranceLevel(object oTarget) { int nRowData; int nColData1; int nColData2; int nRealEnc; nRowData = GetAbilityScore(oTarget, n2DA_PROP); nColData1 = StringToInt(Get2DAString(s2DA_FILE, s2DA_COL1, nRowData)); nColData2 = StringToInt(Get2DAString(s2DA_FILE, s2DA_COL2, nRowData)); nRealEnc = GetWeight(oTarget); if (nRealEnc <= nColData1) return 0; else if (nRealEnc <= nColData2) return 1; else return 2; } // Move items on the deck of a ship to a new location void ptMoveShipItems(object oPC, string sNetwork, string sSourceNode, string sTargetNode, int nShipType) { object oSourceNode = GetObjectByTag(Get2DAString(Xpt2daNode, "Waypoint", ptGetNodeID(sNetwork, sSourceNode))); object oTargetNode = GetObjectByTag(Get2DAString(Xpt2daNode, "Waypoint", ptGetNodeID(sNetwork, sTargetNode))); object oArea = GetArea(oSourceNode); float fShipLength = StringToFloat(Get2DAString(Xpt2daShipType, "Ship_Length", nShipType)); float fShipWidth = StringToFloat(Get2DAString(Xpt2daShipType, "Ship_Width", nShipType)); float fDoorPosition = StringToFloat(Get2DAString(Xpt2daShipType, "Door_Position", nShipType)); location lSourceCentre = ptGetShipCentre(oSourceNode, fDoorPosition); // Centre of old ship location lTargetCentre = ptGetShipCentre(oTargetNode, fDoorPosition); // Centre of new ship vector vSourceCentre = GetPositionFromLocation(lSourceCentre); vector vTargetCentre = GetPositionFromLocation(lTargetCentre); float fSourceFacing = GetFacingFromLocation(lSourceCentre); float fTargetFacing = GetFacingFromLocation(lTargetCentre); float fRotation = fTargetFacing - fSourceFacing; // Rotation to new ship orientation object oSourceObject; // Object to be moved object oTargetObject; // Object that has been moved location lTargetObject; // New object location vector vObject; // Object position relative to ship's centre float fObjectDistance; // Object distance from ship's centre float fObjectAngle; // Angle from new ship's centre to the object int nObjectType; oSourceObject = GetFirstObjectInArea(oArea); while (GetIsObjectValid(oSourceObject)) { nObjectType = GetObjectType(oSourceObject); vObject = GetPosition(oSourceObject) - vSourceCentre; // Object position relative to old ship centre vObject.z = 0.0; // Work in two dimensions initially to simplify maths fObjectDistance = sqrt((vObject.x * vObject.x) + (vObject.y * vObject.y)); // Distance from centre = Pythagorus if (ptGetIsObjectOnShip(vObject, fObjectDistance, fSourceFacing, fShipWidth, fShipLength)) // Is object on ship? if (!(GetMaster(oSourceObject)==oPC) &&!(GetMaster(GetMaster(oSourceObject))==oPC)) // Move everything except party { fObjectAngle = VectorToAngle(vObject) + fRotation; // Angle from centre of new ship to object vObject.x = fObjectDistance * cos(fObjectAngle); vObject.y = fObjectDistance * sin(fObjectAngle); // Vector from new centre to object vObject = vObject + vTargetCentre; // Absolute position of new object vObject.z = GetPosition(oSourceObject).z; // Restore third dimension (object height) lTargetObject = Location(GetArea(oTargetNode), vObject, GetFacing(oSourceObject) + fRotation); if (nObjectType == OBJECT_TYPE_CREATURE) { AssignCommand(oSourceObject, ActionJumpToLocation(lTargetObject)); } else if (nObjectType == OBJECT_TYPE_PLACEABLE) { oTargetObject = CreateObject(OBJECT_TYPE_PLACEABLE, GetResRef(oSourceObject), lTargetObject); SetPlotFlag(oSourceObject, FALSE); DelayCommand(2.0, ptMoveItems(oSourceObject, oTargetObject)); DelayCommand(3.0, DestroyObject(oSourceObject)); } else if (nObjectType == OBJECT_TYPE_ITEM) { oTargetObject = CreateObject(OBJECT_TYPE_ITEM, GetResRef(oSourceObject), lTargetObject); SetItemStackSize(oTargetObject, GetItemStackSize(oSourceObject)); SetPlotFlag(oSourceObject, FALSE); DelayCommand(1.0, DestroyObject(oSourceObject)); } } // End move oSourceObject = GetNextObjectInArea(oArea); } } // Get the centre of as ship. // The cabin door is used as a fixed reference point, if there is one. // Otherwise, the node waypoint is used as the centre of the ship. // Note : in-game, Facing is the same as polar coordinate angle (0 = East) // unlike the toolset, where Facing = Polar + 90 (0 = North). location ptGetShipCentre(object oShipNode, float fDoorPosition) { object oDoor; vector vCentre; float fShipFacing; if (fDoorPosition == 0.0) return (GetLocation(oShipNode)); oDoor = GetNearestObject(OBJECT_TYPE_DOOR, oShipNode); fShipFacing = GetFacing(oDoor) + 180.0; // Door faces stern vCentre = GetPosition(oDoor); vCentre.x += fDoorPosition * cos(fShipFacing); vCentre.y += fDoorPosition * sin(fShipFacing); return Location(GetArea(oDoor), vCentre, fShipFacing); } // Is object on ship? // vObject is a vector from the centre of the ship to the object with magnitude fObjectDistance. int ptGetIsObjectOnShip(vector vObject, float fObjectDistance, float fShipFacing, float fShipWidth, float fShipLength) { float fAngleBetweenShipAndObject = fShipFacing - VectorToAngle(vObject); float fObjectDistanceSideways = fObjectDistance * sin(fAngleBetweenShipAndObject); float fObjectDistanceAhead = fObjectDistance * cos(fAngleBetweenShipAndObject); return ((fabs(fObjectDistanceSideways) <= 0.5 * fShipWidth) && (fabs(fObjectDistanceAhead) <= 0.5 * fShipLength)); } // Move items from one container to another void ptMoveItems(object oOldChest, object oNewChest) { object oItem = GetFirstItemInInventory(oOldChest); while(GetIsObjectValid(oItem)) { CopyItem(oItem, oNewChest, TRUE); SetPlotFlag(oItem, FALSE); DestroyObject(oItem); oItem = GetNextItemInInventory(oOldChest); } } // Get the node corresponding to a waypoint and return a node 2da field string ptGetNodeFieldFromWaypoint(object oWaypoint, string sField) { string sWaypoint; int nNode = -1; if (!GetIsObjectValid(oWaypoint)) return (""); while (Get2DAString(Xpt2daNode, "Node_Name", ++nNode) != "") { sWaypoint = Get2DAString(Xpt2daNode, "Waypoint", nNode); if (GetObjectByTag(sWaypoint) == oWaypoint) return Get2DAString(Xpt2daNode, sField, nNode); } return (""); } // Determine whether party can move // Recursive because henchmen can have horses as henchmen int bhtGetIsPartyMobile(object oPC) { int nHench = 1; object oNPC = GetHenchman(oPC, nHench); if (GetMovementRate(oPC) == 1) return !ptSendFeedback(oPC, 14, GetName(oPC)); while (GetIsObjectValid(oNPC)) { if (!bhtGetIsPartyMobile(oNPC)) return FALSE; oNPC = GetHenchman(oPC, ++nHench); } return TRUE; } // Check whether PC can travel via a node to which a path has been established // Node is available when known, subject to horse restrictions. // If the party has horses, and horses are forbidden, the node is unavailable. // By implication, a mounted party CAN travel via a "dismount" zone - // we presume the party will dismount for that section of the journey. int ptGetIsNodeAvailable(object oPC, int nNode, string sNode, int nIsHorseInParty) { string sWaypoint; object oWaypoint; if (nIsHorseInParty) { sWaypoint = Get2DAString(Xpt2daNode, "Waypoint", nNode); oWaypoint = GetObjectByTag(sWaypoint); if (bhtGetIsHorseForbiddenInArea(GetArea(oWaypoint)) == HORSE_FORBIDDEN) { if (GetLocalInt(oPC, "ptInDialog")) if (GetLocalArrayInt(oPC, "ptNodeKnown", nNode)) ptSendFeedback(oPC, 16, sNode); return FALSE; } } return (GetLocalArrayInt(oPC, "ptNodeKnown", nNode)); } // Check whether party has a horse // Returns 0 = No Horse, 1 = Dismounted Horse Only, 2 = Mounted Horse int bhtGetIsHorseInParty(object oPC) { int nHorseFound = HORSE_NOT_FOUND; int nHench = 0; object oNPC = oPC; while (GetIsObjectValid(oNPC)) { if (HorseGetIsMounted(oNPC)) nHorseFound = HORSE_MOUNTED; if (HorseGetIsAMount(oNPC) && !nHorseFound) nHorseFound = HORSE_DISMOUNTED; if (HorseGetHasAHorse(oNPC) && !nHorseFound) nHorseFound = HORSE_DISMOUNTED; oNPC = GetHenchman(oPC, ++nHench); } return nHorseFound; } // Check whether horses are forbidden in an area // Returns 0 = No Restriction, 1 = Must Dismount, 2 = Forbidden int bhtGetIsHorseForbiddenInArea(object oAreaTarget) { int nNoMounts = HORSE_ALLOWED; if (GetLocalInt(oAreaTarget, "X3_NO_MOUNTING")) nNoMounts = HORSE_DISMOUNT; if (!GetLocalInt(oAreaTarget, "X3_MOUNT_OK_EXCEPTION")) if (GetLocalInt(GetModule(), "X3_MOUNTS_EXTERNAL_ONLY") && GetIsAreaInterior(oAreaTarget)) nNoMounts = HORSE_FORBIDDEN; else if (GetLocalInt(GetModule(), "X3_MOUNTS_NO_UNDERGROUND") && !GetIsAreaAboveGround(oAreaTarget)) nNoMounts = HORSE_FORBIDDEN; if (GetLocalInt(oAreaTarget, "X3_NO_HORSES")) nNoMounts = HORSE_FORBIDDEN; return nNoMounts; } // Report a duplicate waypoint int ptReportDuplicateWaypoint(object oPC, int n2da, string sWaypoint, int nWaypoint) { object oWaypoint = GetObjectByTag(sWaypoint, nWaypoint); location lWaypoint = GetLocation(oWaypoint); return ptSendFeedback(oPC, 17, IntToString(n2da), sWaypoint, LocationToString(lWaypoint)); } // Export system variables for PC to a data base void bhtExportTravelDataBase(object oPC, string sDatabase) { int nNetwork = 0; int nNode = 0; string sNetwork; string sVariable; string sNode; SetCampaignString(sDatabase, "ptNetwork", GetLocalString(oPC, "ptNetwork"), oPC); sNetwork = Get2DAString(Xpt2daNetwork, "Network", nNetwork); while (sNetwork != "") { sVariable = "ptNode" + sNetwork; sNode = GetLocalString(oPC, sVariable); if (sNode != "") SetCampaignString(sDatabase, sVariable, sNode, oPC); sNetwork = Get2DAString(Xpt2daNetwork, "Network", ++nNetwork); } sNode = Get2DAString(Xpt2daNode, "Node_Name", nNode); while (sNode != "") { SetCampaignInt(sDatabase, "ptNodeKnown" + IntToString(nNode), GetLocalArrayInt(oPC, "ptNodeKnown", nNode), oPC); sNode = Get2DAString(Xpt2daNode, "Node_Name", ++nNode); } } // Import system variables for PC from a data base void bhtImportTravelDataBase(object oPC, string sDatabase) { int nNetwork = 0; int nNode = 0; int bKnown; string sNetwork; string sVariable; string sNode; SetLocalString(oPC, "ptNetwork", GetCampaignString(sDatabase, "ptNetwork", oPC)); sNetwork = Get2DAString(Xpt2daNetwork, "Network", nNetwork); while (sNetwork != "") { sVariable = "ptNode" + sNetwork; sNode = GetCampaignString(sDatabase, sVariable, oPC); if (sNode != "") SetLocalString(oPC, sVariable, sNode); sNetwork = Get2DAString(Xpt2daNetwork, "Network", ++nNetwork); } sNode = Get2DAString(Xpt2daNode, "Node_Name", nNode); while (sNode != "") { bKnown = GetCampaignInt(sDatabase, "ptNodeKnown" + IntToString(nNode), oPC); if (bKnown) SetLocalArrayInt(oPC, "ptNodeKnown", nNode, bKnown); sNode = Get2DAString(Xpt2daNode, "Node_Name", ++nNode); } } // Jump associates of oPC to oTarget if it's in the same area. // Bloodsong swiped this from Amurayi's ultimate teleport script // Proleric made this recursive because henchman can have horses in 1.69 void bhtJumpAssociates(object oPC, object oTarget) { if (GetArea(oTarget) == GetArea(oPC)) { DelayCommand(2.0, ptJumpAssociate(oPC, ASSOCIATE_TYPE_ANIMALCOMPANION, oTarget)); DelayCommand(2.0, ptJumpAssociate(oPC, ASSOCIATE_TYPE_DOMINATED, oTarget)); DelayCommand(2.0, ptJumpAssociate(oPC, ASSOCIATE_TYPE_FAMILIAR, oTarget)); DelayCommand(2.0, ptJumpAssociate(oPC, ASSOCIATE_TYPE_HENCHMAN, oTarget)); DelayCommand(2.0, ptJumpAssociate(oPC, ASSOCIATE_TYPE_SUMMONED, oTarget)); } } // Jump associates of a given type to a target location void ptJumpAssociate(object i_oPC, int i_type, object i_oWP) { int i = 0; object oAssociate = GetAssociate(i_type, i_oPC, ++i); while (GetIsObjectValid(oAssociate)) { AssignCommand(oAssociate, ClearAllActions(TRUE)); AssignCommand(oAssociate, JumpToObject(i_oWP)); bhtJumpAssociates(oAssociate, i_oWP); // Recursive line added by Proleric oAssociate = GetAssociate(i_type, i_oPC, ++i); } } // Jumps any masterless horses which are assigned to the party to oTarget, // e.g. horses which have been forcibly removed on entry to a "no horse" area. // Horses which are members of the party are unaffected. void bhtJumpHorsesToObject(object oPC, object oTarget) { object oHorse; object oPartyMember = oPC; int nHench = 0; while (GetIsObjectValid(oPartyMember)) { oHorse = HorseGetMyHorse(oPartyMember); if (GetIsObjectValid(oHorse)) if (!GetIsObjectValid(GetMaster(oHorse))) { AssignCommand(oHorse, ClearAllActions()); AssignCommand(oHorse, ActionJumpToLocation(GetLocation(oTarget))); } oPartyMember = GetHenchman(oPC, ++nHench); } }