Files
HeroesStone_PRC8/_module/nss/bh_travel_inc.nss
Jaysyn904 1eefc84201 Initial Commit
Initial Commit.
2025-09-14 15:40:46 -04:00

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);
}
}