mirror of
https://github.com/1dot13/source.git
synced 2026-07-22 13:40:22 +02:00
* Fixing upper/lower case for includes types.h, zconf.h and zlib.h were renamed so that global includes like Types.h are not used.
564 lines
16 KiB
C++
564 lines
16 KiB
C++
#include "types.h"
|
|
#include "Buildings.h"
|
|
#include "PATHAI.H"
|
|
#include "Structure Wrap.h"
|
|
#include "Render Fun.h"
|
|
#include "worldman.h"
|
|
|
|
#include "AIInternals.h"
|
|
|
|
#define ROOF_LOCATION_CHANCE 8
|
|
|
|
UINT8* gubBuildingInfo = NULL;
|
|
BUILDING gBuildings[ MAX_BUILDINGS ];
|
|
UINT8 gubNumberOfBuildings;
|
|
|
|
#ifdef ROOF_DEBUG
|
|
extern INT16 gsCoverValue[WORLD_MAX];
|
|
#include "video.h"
|
|
#include "renderworld.h"
|
|
#endif
|
|
|
|
BUILDING * CreateNewBuilding( UINT8 * pubBuilding )
|
|
{
|
|
if (gGameSettings.fOptions[TOPTION_ALT_PATHFINDING])
|
|
{
|
|
if (gubNumberOfBuildings >= MAX_BUILDINGS)
|
|
{
|
|
return( NULL );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (gubNumberOfBuildings >= MAX_BUILDINGS - 1)
|
|
{
|
|
return(NULL);
|
|
}
|
|
}
|
|
|
|
// increment # of buildings
|
|
gubNumberOfBuildings++;
|
|
|
|
// clear entry
|
|
gBuildings[ gubNumberOfBuildings ].ubNumClimbSpots = 0;
|
|
*pubBuilding = gubNumberOfBuildings;
|
|
|
|
// return pointer (have to subtract 1 since we just added 1
|
|
return( &(gBuildings[ gubNumberOfBuildings ]) );
|
|
}
|
|
|
|
BUILDING * FindBuilding( INT32 sGridNo )
|
|
{
|
|
UINT8 ubBuildingID;
|
|
|
|
if ( TileIsOutOfBounds( sGridNo ) )
|
|
{
|
|
return( NULL );
|
|
}
|
|
|
|
// id 0 indicates no building
|
|
ubBuildingID = gubBuildingInfo[ sGridNo ];
|
|
|
|
if ( ubBuildingID == NO_BUILDING )
|
|
{
|
|
return( NULL );
|
|
|
|
/*
|
|
// need extra checks to see if is valid spot...
|
|
// must have valid room information and be a flat-roofed
|
|
// building
|
|
if ( InARoom( sGridNo, &ubRoomNo ) && (FindStructure( sGridNo, STRUCTURE_NORMAL_ROOF ) != NULL) )
|
|
{
|
|
return( GenerateBuilding( sGridNo ) );
|
|
}
|
|
else
|
|
{
|
|
return( NULL );
|
|
}
|
|
*/
|
|
}
|
|
else if ( ubBuildingID > gubNumberOfBuildings ) // huh?
|
|
{
|
|
return( NULL );
|
|
}
|
|
|
|
return( &(gBuildings[ ubBuildingID ]) );
|
|
}
|
|
|
|
BOOLEAN InBuilding( INT32 sGridNo )
|
|
{
|
|
// sevenfm: FindBuilding() only checks for buildings with flat roofs (those you can climb)
|
|
// since InBuilding() is used to determine flashbang effect, check also if tile is in a room and has any roof above
|
|
//if ( FindBuilding( sGridNo ) == NULL )
|
|
if (FindBuilding(sGridNo) || InARoom(sGridNo, NULL) && FindStructure(sGridNo, STRUCTURE_ROOF))
|
|
{
|
|
return(FALSE);
|
|
}
|
|
return(TRUE);
|
|
}
|
|
|
|
BOOLEAN SameBuilding( INT32 sGridNo1, INT32 sGridNo2 )
|
|
{
|
|
if ( gubBuildingInfo[ sGridNo1 ] == NO_BUILDING )
|
|
{
|
|
return( FALSE );
|
|
}
|
|
if ( gubBuildingInfo[ sGridNo2 ] == NO_BUILDING )
|
|
{
|
|
return( FALSE );
|
|
}
|
|
return( (BOOLEAN) (gubBuildingInfo[ sGridNo1] == gubBuildingInfo[ sGridNo2 ]) );
|
|
}
|
|
|
|
BUILDING * GenerateBuilding( INT32 sDesiredSpot )
|
|
{
|
|
UINT8 ubBuildingID = 0;
|
|
BUILDING* pBuilding = CreateNewBuilding(&ubBuildingID);
|
|
if (!pBuilding)
|
|
{
|
|
return(NULL);
|
|
}
|
|
|
|
// RoofReachableTest() is called in the original codepath so this if conditional is commented out for now.
|
|
// TODO: Convert original pathfinding code to use the same climbing spot flag as A* so we can get rid of the whole else clause
|
|
//if (gGameSettings.fOptions[TOPTION_ALT_PATHFINDING])
|
|
//{
|
|
|
|
// // Set reachable
|
|
// RoofReachableTest(sDesiredSpot, ubBuildingID);
|
|
|
|
// // 0verhaul: The RoofReachableTest now finds ALL of the climb points for each climbable building, instead of a max of
|
|
// // 21 climb points (and a min of 0) for each building. It claims an extended map flag to mark a tile as a climb point.
|
|
// // So the array of up-climbs and down-climbs is now obsolete. FindClosestClimbPoint is now updated to search the map
|
|
// // for these flags.
|
|
// return(pBuilding);
|
|
//}
|
|
//else
|
|
{
|
|
INT32 uiLoop;
|
|
INT32 uiLoop2;
|
|
INT32 sTempGridNo, sNextTempGridNo, sVeryTemporaryGridNo;
|
|
INT32 sStartGridNo, sCurrGridNo, sPrevGridNo = NOWHERE, sRightGridNo;
|
|
UINT8 ubDirection, ubTempDirection;
|
|
BOOLEAN fFoundDir, fFoundWall;
|
|
UINT32 uiChanceIn = ROOF_LOCATION_CHANCE; // chance of a location being considered
|
|
INT32 sWallGridNo;
|
|
INT8 bDesiredOrientation;
|
|
INT8 bSkipSpots = 0;
|
|
SOLDIERTYPE FakeSoldier;
|
|
INT32 iLoopCount = 0;
|
|
|
|
FakeSoldier.sGridNo = sDesiredSpot;
|
|
FakeSoldier.pathing.bLevel = 1;
|
|
FakeSoldier.bTeam = 1;
|
|
|
|
// Set reachable
|
|
RoofReachableTest(sDesiredSpot, ubBuildingID);
|
|
|
|
// From sGridNo, search until we find a spot that isn't part of the building
|
|
ubDirection = NORTHWEST;
|
|
sTempGridNo = sDesiredSpot;
|
|
// using diagonal directions to hopefully prevent picking a
|
|
// spot that
|
|
while ((gpWorldLevelData[sTempGridNo].uiFlags & MAPELEMENT_REACHABLE))
|
|
{
|
|
sNextTempGridNo = NewGridNo(sTempGridNo, DirectionInc(ubDirection));
|
|
if (sTempGridNo == sNextTempGridNo)
|
|
{
|
|
// hit edge of map!??!
|
|
return(NULL);
|
|
}
|
|
else
|
|
{
|
|
sTempGridNo = sNextTempGridNo;
|
|
}
|
|
}
|
|
|
|
// we've got our spot
|
|
sStartGridNo = sTempGridNo;
|
|
|
|
sCurrGridNo = sStartGridNo;
|
|
sVeryTemporaryGridNo = NewGridNo(sCurrGridNo, DirectionInc(EAST));
|
|
if (gpWorldLevelData[sVeryTemporaryGridNo].uiFlags & MAPELEMENT_REACHABLE)
|
|
{
|
|
// go north first
|
|
ubDirection = NORTH;
|
|
}
|
|
else
|
|
{
|
|
// go that way (east)
|
|
ubDirection = EAST;
|
|
}
|
|
|
|
gpWorldLevelData[sStartGridNo].ubExtFlags[0] |= MAPELEMENT_EXT_ROOFCODE_VISITED;
|
|
|
|
uiLoop2 = 0;
|
|
while (uiLoop2 < WORLD_MAX)
|
|
{
|
|
// if point to (2 clockwise) is not part of building and is not visited,
|
|
// or is starting point, turn!
|
|
sRightGridNo = NewGridNo(sCurrGridNo, DirectionInc(gTwoCDirection[ubDirection]));
|
|
sTempGridNo = sRightGridNo;
|
|
if (((!(gpWorldLevelData[sTempGridNo].uiFlags & MAPELEMENT_REACHABLE) && !(gpWorldLevelData[sTempGridNo].ubExtFlags[0] & MAPELEMENT_EXT_ROOFCODE_VISITED)) || (sTempGridNo == sStartGridNo)) && (sCurrGridNo != sStartGridNo))
|
|
{
|
|
iLoopCount++;
|
|
|
|
if (iLoopCount >= 10)
|
|
{
|
|
return(NULL);
|
|
}
|
|
ubDirection = gTwoCDirection[ubDirection];
|
|
// try in that direction
|
|
continue;
|
|
}
|
|
iLoopCount = 0;
|
|
|
|
// if spot ahead is part of building, turn
|
|
sTempGridNo = NewGridNo(sCurrGridNo, DirectionInc(ubDirection));
|
|
if (gpWorldLevelData[sTempGridNo].uiFlags & MAPELEMENT_REACHABLE)
|
|
{
|
|
// first search for a spot that is neither part of the building or visited
|
|
|
|
// we KNOW that the spot in the original direction is blocked, so only loop 3 times
|
|
ubTempDirection = gTwoCDirection[ubDirection];
|
|
fFoundDir = FALSE;
|
|
for (uiLoop = 0; uiLoop < 3; uiLoop++)
|
|
{
|
|
sTempGridNo = NewGridNo(sCurrGridNo, DirectionInc(ubTempDirection));
|
|
if (!(gpWorldLevelData[sTempGridNo].uiFlags & MAPELEMENT_REACHABLE) && !(gpWorldLevelData[sTempGridNo].ubExtFlags[0] & MAPELEMENT_EXT_ROOFCODE_VISITED))
|
|
{
|
|
// this is the way to go!
|
|
fFoundDir = TRUE;
|
|
break;
|
|
}
|
|
ubTempDirection = gTwoCDirection[ubTempDirection];
|
|
}
|
|
if (!fFoundDir)
|
|
{
|
|
// now search for a spot that is just not part of the building
|
|
ubTempDirection = gTwoCDirection[ubDirection];
|
|
fFoundDir = FALSE;
|
|
for (uiLoop = 0; uiLoop < 3; uiLoop++)
|
|
{
|
|
sTempGridNo = NewGridNo(sCurrGridNo, DirectionInc(ubTempDirection));
|
|
if (!(gpWorldLevelData[sTempGridNo].uiFlags & MAPELEMENT_REACHABLE))
|
|
{
|
|
// this is the way to go!
|
|
fFoundDir = TRUE;
|
|
break;
|
|
}
|
|
ubTempDirection = gTwoCDirection[ubTempDirection];
|
|
}
|
|
if (!fFoundDir)
|
|
{
|
|
// WTF is going on?
|
|
return(NULL);
|
|
}
|
|
}
|
|
ubDirection = ubTempDirection;
|
|
// try in that direction
|
|
continue;
|
|
}
|
|
|
|
// move ahead
|
|
sPrevGridNo = sCurrGridNo;
|
|
sCurrGridNo = sTempGridNo;
|
|
sRightGridNo = NewGridNo(sCurrGridNo, DirectionInc(gTwoCDirection[ubDirection]));
|
|
|
|
#ifdef ROOF_DEBUG
|
|
if (gsCoverValue[sCurrGridNo] == 0x7F7F)
|
|
{
|
|
gsCoverValue[sCurrGridNo] = 1;
|
|
}
|
|
else if (gsCoverValue[sCurrGridNo] >= 0)
|
|
{
|
|
gsCoverValue[sCurrGridNo]++;
|
|
}
|
|
|
|
DebugAI(String("Roof code visits %d", sCurrGridNo));
|
|
#endif
|
|
|
|
if (sCurrGridNo == sStartGridNo)
|
|
{
|
|
// done
|
|
break;
|
|
}
|
|
|
|
if (!(gpWorldLevelData[sCurrGridNo].ubExtFlags[0] & MAPELEMENT_EXT_ROOFCODE_VISITED))
|
|
{
|
|
gpWorldLevelData[sCurrGridNo].ubExtFlags[0] |= MAPELEMENT_EXT_ROOFCODE_VISITED;
|
|
|
|
if (InARoom(sCurrGridNo, NULL))
|
|
gubBuildingInfo[sCurrGridNo] = ubBuildingID;
|
|
|
|
// consider this location as possible climb gridno
|
|
// there must be a regular wall adjacent to this for us to consider it a
|
|
// climb gridno
|
|
|
|
// if the direction is east or north, the wall would be in our gridno;
|
|
// if south or west, the wall would be in the gridno two clockwise
|
|
fFoundWall = FALSE;
|
|
|
|
// There must not be roof here either. There are places where a pitched roof butts up against a flat roof.
|
|
// Don't mark such a border as a climb point. Otherwise AI units will get stuck.
|
|
if (FindStructure(sCurrGridNo, STRUCTURE_ROOF) == NULL &&
|
|
NewOKDestination(&FakeSoldier, sCurrGridNo, FALSE, 0))
|
|
{
|
|
switch (ubDirection)
|
|
{
|
|
case NORTH:
|
|
sWallGridNo = sCurrGridNo;
|
|
bDesiredOrientation = OUTSIDE_TOP_RIGHT;
|
|
break;
|
|
case EAST:
|
|
sWallGridNo = sCurrGridNo;
|
|
bDesiredOrientation = OUTSIDE_TOP_LEFT;
|
|
break;
|
|
case SOUTH:
|
|
sWallGridNo = (sCurrGridNo + DirectionInc(gTwoCDirection[ubDirection]));
|
|
bDesiredOrientation = OUTSIDE_TOP_RIGHT;
|
|
break;
|
|
case WEST:
|
|
sWallGridNo = (sCurrGridNo + DirectionInc(gTwoCDirection[ubDirection]));
|
|
bDesiredOrientation = OUTSIDE_TOP_LEFT;
|
|
break;
|
|
default:
|
|
// what the heck?
|
|
return(NULL);
|
|
}
|
|
|
|
if (bDesiredOrientation == OUTSIDE_TOP_LEFT)
|
|
{
|
|
if (WallExistsOfTopLeftOrientation(sWallGridNo))
|
|
{
|
|
fFoundWall = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (WallExistsOfTopRightOrientation(sWallGridNo))
|
|
{
|
|
fFoundWall = TRUE;
|
|
}
|
|
}
|
|
}
|
|
if (fFoundWall)
|
|
{
|
|
#ifdef ROOF_DEBUG
|
|
gsCoverValue[sCurrGridNo] = 50;
|
|
#endif
|
|
if (bSkipSpots > 0)
|
|
{
|
|
bSkipSpots--;
|
|
}
|
|
else if (Random(uiChanceIn) == 0)
|
|
{
|
|
pBuilding->sUpClimbSpots[pBuilding->ubNumClimbSpots] = sCurrGridNo;
|
|
pBuilding->sDownClimbSpots[pBuilding->ubNumClimbSpots] = sRightGridNo;
|
|
pBuilding->ubNumClimbSpots++;
|
|
|
|
if (pBuilding->ubNumClimbSpots == MAX_CLIMBSPOTS_PER_BUILDING)
|
|
{
|
|
// gotta stop!
|
|
return(pBuilding);
|
|
}
|
|
|
|
// if location is added as a spot, reset uiChanceIn
|
|
uiChanceIn = ROOF_LOCATION_CHANCE;
|
|
#ifdef ROOF_DEBUG
|
|
gsCoverValue[sCurrGridNo] = 99;
|
|
#endif
|
|
// skip the next spot
|
|
bSkipSpots = 1;
|
|
}
|
|
else
|
|
{
|
|
// didn't pick this location, so increase chance that next location
|
|
// will be considered
|
|
if (uiChanceIn > 2)
|
|
{
|
|
uiChanceIn--;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// can't select this spot
|
|
if ((!TileIsOutOfBounds(sPrevGridNo)) && (pBuilding->ubNumClimbSpots > 0))
|
|
{
|
|
if (pBuilding->sDownClimbSpots[pBuilding->ubNumClimbSpots - 1] == sCurrGridNo)
|
|
{
|
|
// unselect previous spot
|
|
pBuilding->ubNumClimbSpots--;
|
|
// overwrote a selected spot so go into automatic selection for later
|
|
uiChanceIn = 1;
|
|
#ifdef ROOF_DEBUG
|
|
// reset marker
|
|
gsCoverValue[sPrevGridNo] = 1;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// skip the next gridno
|
|
bSkipSpots = 1;
|
|
}
|
|
}
|
|
uiLoop2++;
|
|
}
|
|
// If we've run out of loop before we've run out of building, then there is
|
|
// something that has gone pear shaped
|
|
if (uiLoop2 >= WORLD_MAX)
|
|
{
|
|
INT32 x = 0;
|
|
INT32 y = 0;
|
|
while ((sDesiredSpot - ((y + 1) * WORLD_COLS)) >= 0)
|
|
{
|
|
++y;
|
|
}
|
|
x = sDesiredSpot - (y * WORLD_COLS);
|
|
DebugMsg(TOPIC_JA2, DBG_LEVEL_2, String("113/UC Warning! Building Walk Algorithm has covered the entire map! Building %d located at [%d,%d] must be bogus.", ubBuildingID, x, y));
|
|
}
|
|
// at end could prune # of locations if there are too many
|
|
return(pBuilding);
|
|
}
|
|
}
|
|
|
|
void GenerateBuildings( void )
|
|
{
|
|
// init building structures and variables
|
|
memset( gubBuildingInfo, 0, WORLD_MAX * sizeof( UINT8 ) );
|
|
memset( &gBuildings, 0, MAX_BUILDINGS * sizeof( BUILDING ) );
|
|
gubNumberOfBuildings = 0;
|
|
|
|
if ( (gbWorldSectorZ > 0) || gfEditMode)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef ROOF_DEBUG
|
|
memset( gsCoverValue, 0x7F, sizeof( INT16 ) * WORLD_MAX );
|
|
#endif
|
|
|
|
// reset ALL reachable flags
|
|
// do once before we start building generation for
|
|
// whole map
|
|
for (INT32 uiLoop = 0; uiLoop < WORLD_MAX; uiLoop++ )
|
|
{
|
|
gpWorldLevelData[ uiLoop ].uiFlags &= ~(MAPELEMENT_REACHABLE);
|
|
gpWorldLevelData[ uiLoop ].ubExtFlags[0] &= ~(MAPELEMENT_EXT_ROOFCODE_VISITED);
|
|
}
|
|
|
|
// search through world
|
|
// for each location in a room try to find building info
|
|
for (INT32 uiLoop = 0; uiLoop < WORLD_MAX; uiLoop++ )
|
|
{
|
|
if ( (gusWorldRoomInfo[ uiLoop ] != NO_ROOM) && (gubBuildingInfo[ uiLoop ] == NO_BUILDING) && (FindStructure( uiLoop, STRUCTURE_NORMAL_ROOF ) != NULL) )
|
|
{
|
|
GenerateBuilding( uiLoop );
|
|
}
|
|
}
|
|
}
|
|
|
|
INT32 FindClosestClimbPoint( SOLDIERTYPE *pSoldier, INT32 sStartGridNo, INT32 sDesiredGridNo, BOOLEAN fClimbUp )
|
|
{
|
|
BUILDING* pBuilding = FindBuilding( sDesiredGridNo );
|
|
if (!pBuilding)
|
|
{
|
|
return( NOWHERE );
|
|
}
|
|
|
|
INT32 sDistance, sClosestDistance = 1000, sClosestSpot = NOWHERE;
|
|
if (gGameSettings.fOptions[TOPTION_ALT_PATHFINDING])
|
|
{
|
|
for (INT32 sGridNo = 0; sGridNo < WORLD_MAX; sGridNo++)
|
|
{
|
|
if (gubBuildingInfo[sGridNo] == gubBuildingInfo[sDesiredGridNo] &&
|
|
gpWorldLevelData[sGridNo].ubExtFlags[1] & MAPELEMENT_EXT_CLIMBPOINT)
|
|
{
|
|
// Found a climb point for this building
|
|
if (fClimbUp)
|
|
{
|
|
for (UINT8 ubTestDir = 0; ubTestDir < NUM_WORLD_DIRECTIONS; ubTestDir += 2)
|
|
{
|
|
INT32 sTestGridNo = NewGridNo(sGridNo, DirectionInc(ubTestDir));
|
|
if (gpWorldLevelData[sTestGridNo].ubExtFlags[0] & MAPELEMENT_EXT_CLIMBPOINT)
|
|
{
|
|
// Found a matching climb point
|
|
if ((WhoIsThere2(sTestGridNo, 0) == NOBODY || sTestGridNo == pSoldier->sGridNo)
|
|
&& (WhoIsThere2(sGridNo, 1) == NOBODY) &&
|
|
(!pSoldier || !InGas(pSoldier, sTestGridNo)))
|
|
{
|
|
// And it's open
|
|
sDistance = PythSpacesAway(sStartGridNo, sTestGridNo);
|
|
if (sDistance < sClosestDistance)
|
|
{
|
|
sClosestDistance = sDistance;
|
|
sClosestSpot = sTestGridNo;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (UINT8 ubTestDir = 0; ubTestDir < NUM_WORLD_DIRECTIONS; ubTestDir += 2)
|
|
{
|
|
INT32 sTestGridNo = NewGridNo(sGridNo, DirectionInc(ubTestDir));
|
|
if (gpWorldLevelData[sTestGridNo].ubExtFlags[0] & MAPELEMENT_EXT_CLIMBPOINT)
|
|
{
|
|
// Found a matching climb point
|
|
if ((WhoIsThere2(sTestGridNo, 0) == NOBODY) &&
|
|
(WhoIsThere2(sGridNo, 1) == NOBODY || sGridNo == pSoldier->sGridNo) &&
|
|
(!pSoldier || !InGas(pSoldier, sTestGridNo)))
|
|
{
|
|
// And it's open
|
|
sDistance = PythSpacesAway(sStartGridNo, sGridNo);
|
|
if (sDistance < sClosestDistance)
|
|
{
|
|
sClosestDistance = sDistance;
|
|
sClosestSpot = sGridNo;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// WANNE: Reworked climbing code from sevenfm
|
|
INT32* psClimbSpots;
|
|
UINT8 ubNumClimbSpots = pBuilding->ubNumClimbSpots;
|
|
|
|
if (fClimbUp)
|
|
{
|
|
psClimbSpots = pBuilding->sUpClimbSpots;
|
|
}
|
|
else
|
|
{
|
|
psClimbSpots = pBuilding->sDownClimbSpots;
|
|
}
|
|
|
|
for (UINT8 ubLoop = 0; ubLoop < ubNumClimbSpots; ubLoop++)
|
|
{
|
|
if ((WhoIsThere2(pBuilding->sUpClimbSpots[ubLoop], 0) == NOBODY ||
|
|
WhoIsThere2(pBuilding->sUpClimbSpots[ubLoop], 0) == pSoldier->ubID) &&
|
|
(WhoIsThere2(pBuilding->sDownClimbSpots[ubLoop], 1) == NOBODY ||
|
|
WhoIsThere2(pBuilding->sDownClimbSpots[ubLoop], 1) == pSoldier->ubID) &&
|
|
!InGas(pSoldier, psClimbSpots[ubLoop]) &&
|
|
!Water(psClimbSpots[ubLoop], pSoldier->pathing.bLevel))
|
|
{
|
|
sDistance = PythSpacesAway(sStartGridNo, psClimbSpots[ubLoop]);
|
|
if (sDistance < sClosestDistance)
|
|
{
|
|
sClosestDistance = sDistance;
|
|
sClosestSpot = psClimbSpots[ubLoop];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return( sClosestSpot );
|
|
}
|