Homing device

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.

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