Homing device
From NWNWiki
Contents |
[edit] Homing Device
[edit] What it does
An OnActivateItem script that allows an item to work as a homing device. If the object being searched for is in the same area as the PC, the PC will face in the direction of the object and be given the distance and compass direction. If the object is not in the same area, the PC will be pointed toward the area transition that begins the shortest path to the object.
[edit] Notes
Basically, this script does a recursive search through the areas of the module to find an object. Since recursion can be stack intensive, the depth of the search can be controlled by the constant int RECURSION_DEPTH, defined at the beginning of the script. This limits how many areas distant the object can be and still be found. I have tested to a depth of twenty without a problem.
To use, you will need to change three strings in the main function. Change "Compass" to the tag of the item the PC will use as a direction finder. This item should have Unique Power Self Only. Also change the two occurences of "homingstone" to the tag of the item that the player will be guided toward.
In order for this script to work properly, each area should have a unique tag. Also it will not detect if an area is impassable to the PC, for instance if it contains a wall the PC cannot pass through.
The code in the main function first looks to see if the searched for object is in the possession of someone or something. If it is, the PC will be guided toward that object. This could allow for a rather intriguing PvP mod, with everyone searching (and killing each other) for some important object. Or you could modify the code in main to get the searched for object using GetLocalObject on the PC. Then the PCs could have unique searchable items, to be used as a personal homing device, etc.
[edit] The Script
//*******************************************************
// OnActivateItem script for a homing device
// Written by John Werner
// In the main function, change "Compass" to the tag of
// the activatable item, and "homingstone" to the tag of
// the object to search for.
//*******************************************************
// Constant to control depth of recursive search
int RECURSION_DEPTH = 20;
//*******************************************************
// RecursiveSearch
// Recursive function to search areas for an object
// Parameters:
// oGrail - object being searched for
// oTransit - area transition target of the door or
// trigger just traversed
// oDataStore - invisible object used for temporary data
// storage
// fDist - float containing the distance from the PC to
// the oTransit object
// iDepth - int to control recursion depth
//*******************************************************
void RecursiveSearch( object oGrail, object oTransit,
object oDataStore, float fDist, int iDepth )
{
// If the area containing the object has been found
if ( GetArea( oGrail ) == GetArea( oTransit ) )
{
// Calculate the total path distance from PC to object
float fPathDist = fDist + GetDistanceBetween( oTransit, oGrail );
// If the path distance for this branch has not been set
// or the path distance is the shortest path found so far,
// store the path distance on the data store object
if ( GetLocalFloat( oDataStore, "fDistToGrail" ) == -1.0
|| fPathDist < GetLocalFloat( oDataStore, "fDistToGrail" ) )
SetLocalFloat( oDataStore, "fDistToGrail", fPathDist );
return;
}
// If this area has been visited before, return
if ( GetLocalInt( oDataStore, GetTag( GetArea( oTransit ) ) ) == 1 )
return;
// If the recursion depth has been exceded, return
if ( iDepth < 0 )
return;
// Create a variable in the data store object to indicate
// the current area has been visited
SetLocalInt( oDataStore, GetTag( GetArea( oTransit ) ), 1 );
// Create an index variable
int nNth = 1;
// Get the nearest door
object oExit = GetNearestObject( OBJECT_TYPE_DOOR, oTransit, nNth );
// Loop through all doors in the area, get the transition
// target and call the recursive search function on that target
while ( GetIsObjectValid( oExit )
&& GetArea( oExit ) == GetArea( oTransit ) )
{
object oNewTarget = GetTransitionTarget( oExit );
if ( GetIsObjectValid( oNewTarget ) )
{
RecursiveSearch( oGrail, oNewTarget, oDataStore,
fDist + GetDistanceBetween( oTransit, oExit ), iDepth - 1 );
}
nNth++;
oExit = GetNearestObject( OBJECT_TYPE_DOOR, oTransit, nNth );
}
// Reset the index variable
nNth = 1;
// Get the nearest trigger
oExit = GetNearestObject( OBJECT_TYPE_TRIGGER, oTransit, nNth );
// Loop through all triggers in the area, get the transition
// target and call the recursive search function on that target
while ( GetIsObjectValid( oExit )
&& GetArea( oExit ) == GetArea( oTransit ) )
{
object oNewTarget = GetTransitionTarget( oExit );
if ( GetIsObjectValid( oNewTarget ) )
{
RecursiveSearch( oGrail, oNewTarget, oDataStore,
fDist + GetDistanceBetween( oTransit, oExit ), iDepth - 1 );
}
nNth++;
oExit = GetNearestObject( OBJECT_TYPE_TRIGGER, oTransit, nNth );
}
}
//*****************************************************
// SearchAreas
// Function to search surrounding areas for an object
// The object this function returns will be the area
// transition that begins the shortest path to the
// object, or OBJECT_INVALID if the object is not found
// oPC - PC searching for the object
// oGrail - the object of the search
//*****************************************************
object SearchAreas( object oPC, object oGrail )
{
// Initialize a float to track the shortest
// path to the oGrail object
float fNearest = 999999999.9;
// Initialize an object to track the door or trigger
// that begins the shortest path
object oNearestExit = OBJECT_INVALID;
// Initialize an index
int nNth = 1;
// Get nearest door
object oExit = GetNearestObject( OBJECT_TYPE_DOOR, oPC, nNth );
// Loop through all doors in the same area as the PC
while ( GetIsObjectValid( oExit )
&& GetArea( oExit ) == GetArea( oPC ) )
{
// Get the transition target of the door
object oTransit = GetTransitionTarget( oExit );
if ( GetIsObjectValid( oTransit ) )
{
// Create an invisible object to use as a data store
object oDataStore = CreateObject( OBJECT_TYPE_CREATURE,
"plc_invisobj", GetLocation( oPC ) );
// Create a variable on the data store to indicate
// this area has been visited
SetLocalInt( oDataStore, GetTag( GetArea( oPC ) ), 1 );
// Create a float on the data store to record
// the shortest path via the current door
SetLocalFloat( oDataStore, "fDistToGrail", -1.0 );
// Call the recursive search on the transition target
// of the door
RecursiveSearch( oGrail, oTransit, oDataStore,
GetDistanceBetween( oPC, oExit ), RECURSION_DEPTH );
// Get the length of the shortest path through the current door
float fDist = GetLocalFloat( oDataStore, "fDistToGrail" );
// If a path was found and it is shorter than previous paths,
// store the distance and the door that begins the path
if ( fDist != -1.0 && fDist < fNearest )
{
fNearest = fDist;
oNearestExit = oExit;
}
// Destroy the data store
DestroyObject( oDataStore );
}
nNth++;
oExit = GetNearestObject( OBJECT_TYPE_DOOR, oPC, nNth );
}
// Reset the index variable
nNth = 1;
// Get the nearest trigger
oExit = GetNearestObject( OBJECT_TYPE_TRIGGER, oPC, nNth );
// Loop through all triggers in the same area as the PC
while ( GetIsObjectValid( oExit )
&& GetArea( oExit ) == GetArea( oPC ) )
{
// Get the transition target of the trigger
object oTransit = GetTransitionTarget( oExit );
if ( GetIsObjectValid( oTransit ) )
{
// Create an invisible object to use as a data store
object oDataStore = CreateObject( OBJECT_TYPE_CREATURE,
"plc_invisobj", GetLocation( oPC ) );
// Create a variable on the data store to indicate
// this area has been visited
SetLocalInt( oDataStore, GetTag( GetArea( oPC ) ), 1 );
// Create a float on the data store to record
// the shortest path via the current door
SetLocalFloat( oDataStore, "fDistToGrail", -1.0 );
// Call the recursive search on the transition target
// of the door
RecursiveSearch( oGrail, oTransit, oDataStore,
GetDistanceBetween( oPC, oExit ), RECURSION_DEPTH );
// Get the length of the shortest path through the current door
float fDist = GetLocalFloat( oDataStore, "fDistToGrail" );
// If a path was found and it is shorter than previous paths,
// store the distance and the door that begins the path
if ( fDist != -1.0 && fDist < fNearest )
{
fNearest = fDist;
oNearestExit = oExit;
}
// Destroy the data store
DestroyObject( oDataStore );
}
nNth++;
oExit = GetNearestObject( OBJECT_TYPE_TRIGGER, oPC, nNth );
}
// Return the door or trigger that begins the shortest path
return oNearestExit;
}
void main()
{
object oItem = GetItemActivated();
if ( GetIsObjectValid( oItem ) )
{
string sTag = GetTag( oItem );
if ( sTag == "Compass" )
{
object oPC = GetItemActivator();
// Attempt to get the possessor of the object of the search
object oGrail = GetItemPossessor( GetObjectByTag( "homingstone" ) );
// If the object of the search is not possessed by anyone,
// then get the object
if ( !GetIsObjectValid( oGrail ) )
oGrail = GetObjectByTag( "homingstone" );
// If we have a valid PC
if ( GetIsObjectValid( oPC ) )
{
// Create the effect on the PC
ApplyEffectToObject( DURATION_TYPE_INSTANT,
EffectVisualEffect( VFX_IMP_HEAD_MIND ), oPC );
// Create a boolean to indicate if the Grail
// is in the same area
int nInArea = TRUE;
// Check to be sure the PC is in the same area
// as the Grail object. If not, find the Grail
if ( GetArea( oPC ) != GetArea( oGrail ) )
{
oGrail = SearchAreas( oPC, oGrail );
nInArea = FALSE;
}
if ( GetIsObjectValid( oGrail ) )
{
// Get the position of the PC
vector vPCpos = GetPosition( oPC );
// Get the position of the Grail object
vector vGrailPos = GetPosition( oGrail );
// Get the difference in y positions
float fRise = vGrailPos.y - vPCpos.y;
// Get the difference in x positions
float fRun = vGrailPos.x - vPCpos.x;
// Create strings to hold the heading
string sHeading, sDir1, sDir2;
// Get the distance to the Grail object
string sDist = FloatToString( GetDistanceBetween( oPC, oGrail ) );
// Trim the whitespace from the distance
while ( GetSubString( sDist, 0, 1 ) == " " )
sDist = GetStringRight( sDist, GetStringLength( sDist ) - 1 );
// Truncate the distance to tenths
sDist = GetSubString( sDist, 0, FindSubString( sDist, "." ) + 2 );
// Get the correct direction names for the relative quadrant
if ( fRise > 0.0 )
sDir1 = "North";
else
sDir1 = "South";
if ( fRun > 0.0 )
sDir2 = "East";
else
sDir2 = "West";
// Check to prevent a divide-by-zero error
if ( fRun != 0.0 )
{
// Calculate the slope of the line
float fSlope = fabs( fRise / fRun );
// Translate the slope to direction
if ( fSlope < 0.25 )
sHeading = sDir2;
else if ( fSlope < 0.75 )
sHeading = sDir2 + " by " + sDir1 + GetStringLowerCase( sDir2 );
else if ( fSlope < 1.5 )
sHeading = sDir1 + GetStringLowerCase( sDir2 );
else if ( fSlope < 3.0 )
sHeading = sDir1 + " by " + sDir1 + GetStringLowerCase( sDir2 );
else
sHeading = sDir1;
}
else
sHeading = sDir1;
// Turn the PC toward the target
AssignCommand( oPC, SetFacingPoint( GetPosition( oGrail ) ) );
// Give the PC directions
// If the object is in the same area,
// give the direction and distance
if ( nInArea )
{
FloatingTextStringOnCreature( sDist + "m " + sHeading, oPC );
}
// If the object is in another area,
// give the direction and distance to the door
// or trigger that begins the shortest path
else
{
FloatingTextStringOnCreature( "The object you seek is very distant", oPC );
DelayCommand( 2.0, FloatingTextStringOnCreature( "Take the passage " + sDist + "m " + sHeading, oPC ) );
}
}
// The object could not be found
else
FloatingTextStringOnCreature( "Alas, the path is too dim to follow", oPC );
}
}
}
}
