generated from Jaysyn/ModuleTemplate
1578 lines
56 KiB
Plaintext
1578 lines
56 KiB
Plaintext
// 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);
|
|
}
|
|
}
|