#ifdef PRECOMPILEDHEADERS
	#include "TileEngine All.h"
#else
	#include <string.h>
	#include "types.h"
	#include "wcheck.h"
	#include "debug.h"
	#include "FileMan.h"
	#include "MemMan.h"
	#include "structure.h"
	#include "tiledef.h"
	#include "worlddef.h"
	#include "worldman.h"

	#include "interface.h"

	#include "isometric utils.h"
	#include "font.h"
	#include "font control.h"
	#include "los.h"

	#include "lighting.h"
	#include "Smell.h"
	#include "SaveLoadMap.h"
	#include "strategicmap.h"
	#include "sgp_logger.h"

	#include "Sys Globals.h"	//for access to gfEditMode flag

	//Kris:
	#ifdef JA2EDITOR
	#include "Editor Undo.h"	//for access to AddToUndoList( iMapIndex )
	#endif

	#include "explosion control.h"
	#include "Sound Control.h"

	#include "Buildings.h"

	#include "Random.h"

	#include "Tile Animation.h"
#endif

#ifdef COUNT_PATHS
	extern UINT32 guiSuccessfulPathChecks;
	extern UINT32 guiTotalPathChecks;
	extern UINT32 guiFailedPathChecks;
	extern UINT32 guiUnsuccessfulPathChecks;
#endif

void DeleteStructureFromTile( MAP_ELEMENT * pMapElement, STRUCTURE * pStructure );
/*
 * NB:	STRUCTURE_SPECIAL
 *
 * Means different things depending on the context.
 *
 * WALLNWINDOW SPECIAL - opaque to sight
 * MULTI SPECIAL - second level (damaged) MULTI structure, should only be deleted if
 *	starting with the deletion of a MULTI SPECIAL structure
 */

UINT8 AtHeight[PROFILE_Z_SIZE] = { 0x01, 0x02, 0x04, 0x08 };

#define FIRST_AVAILABLE_STRUCTURE_ID (INVALID_STRUCTURE_ID + 2)

UINT16 gusNextAvailableStructureID = FIRST_AVAILABLE_STRUCTURE_ID;

STRUCTURE_FILE_REF *		gpStructureFileRefs;


INT32 guiMaterialHitSound[ NUM_MATERIAL_TYPES ] =
{
	-1,
	S_WOOD_IMPACT1,
	S_WOOD_IMPACT2,
	S_WOOD_IMPACT3,
	S_VEG_IMPACT1,
	-1,
	S_PORCELAIN_IMPACT1,
	-1,
	-1,
	-1,

	-1,
	S_STONE_IMPACT1,
	S_STONE_IMPACT1,
	S_STONE_IMPACT1,
	S_STONE_IMPACT1,
	S_RUBBER_IMPACT1,
	-1,
	-1,
	-1,
	-1,

	-1,
	S_METAL_IMPACT1,
	S_METAL_IMPACT2,
	S_METAL_IMPACT3,
	S_STONE_IMPACT1,
	S_METAL_IMPACT3,
};




UINT8 gubMaterialArmour[] =
/*
index	1-10, organics
index 11-20, rocks and concretes
index 21-30, metals

index 1, dry timber
index 2, furniture wood
index 3, tree wood
index 11, stone masonry
index 12, non-reinforced concrete
index 13, reinforced concrete
index 14, rock
index 21, light metal (furniture)
index 22, heavy metal (doors etc)
index 23, really heavy metal
index 24, indestructable stone
index 25, indestructable metal
*/

{ // note: must increase; r.c. should block *AP* 7.62mm rounds
	0,		// nothing
	25,		// dry timber; wood wall +1/2
	20,		// furniture wood (thin!) or plywood wall +1/2
	30,		// wood (live); 1.5x timber
	3,		// light vegetation
	10,		// upholstered furniture
	47,		// porcelain
	10,		// cactus, hay, bamboo
		0,
		0,
		0,
	55,		// stone masonry; 3x timber
	63,		// non-reinforced concrete; 4x timber???
	70,		// reinforced concrete; 6x timber
	85,		// rock? - number invented
	9,		// rubber - tires
	40,		// sand
		1,	// cloth
	40,		// sandbag
		0,
		0,
	37,		// light metal (furniture; NB thin!)
	57,		// thicker metal (dumpster)
	85,		// heavy metal (vault doors) - block everything
	// note that vehicle armour will probably end up in here
	127,	// rock indestructable
	127,	// indestructable
	57,		// like 22 but with screen windows
};

// Function operating on a structure tile
UINT8 FilledTilePositions( DB_STRUCTURE_TILE * pTile )
{
	UINT8				ubFilled = 0, ubShapeValue;
	INT8				bLoopX, bLoopY, bLoopZ;

	// Loop through all parts of a structure and add up the number of
	// filled spots
	for (bLoopX = 0; bLoopX < PROFILE_X_SIZE; bLoopX++)
	{
		for (bLoopY = 0; bLoopY < PROFILE_Y_SIZE; bLoopY++)
		{
			ubShapeValue = pTile->Shape[bLoopX][bLoopY];
			for (bLoopZ = 0; bLoopZ < PROFILE_Z_SIZE; bLoopZ++)
			{
				if (ubShapeValue & AtHeight[bLoopZ])
				{
					ubFilled++;
				}
			}
		}
	}
	return( ubFilled );
}

//
// Structure database functions
//

void FreeStructureFileRef( STRUCTURE_FILE_REF * pFileRef )
{
	// Frees all of the memory associated with a file reference, including
	// the file reference structure itself

	UINT16										usLoop;

	Assert( pFileRef != NULL );
	if (pFileRef->pDBStructureRef != NULL)
	{
		for (usLoop = 0; usLoop < pFileRef->usNumberOfStructures; usLoop++)
		{
			if (pFileRef->pDBStructureRef[usLoop].ppTile)
			{
				MemFree( pFileRef->pDBStructureRef[usLoop].ppTile );
			}
		}
		MemFree( pFileRef->pDBStructureRef );
	}
	if (pFileRef->pubStructureData != NULL)
	{
		MemFree( pFileRef->pubStructureData );
	}
	if (pFileRef->pAuxData != NULL)
	{
		MemFree( pFileRef->pAuxData );
		if (pFileRef->pTileLocData != NULL)
		{
			MemFree( pFileRef->pTileLocData );
		}
	}
	MemFree( pFileRef );
}

void FreeAllStructureFiles( void )
{
	// Frees all of the structure database!
	STRUCTURE_FILE_REF *	pFileRef;
	STRUCTURE_FILE_REF *	pNextRef;

	pFileRef = gpStructureFileRefs;
	while( pFileRef != NULL )
	{
		pNextRef = pFileRef->pNext;
		FreeStructureFileRef( pFileRef );
		pFileRef = pNextRef;
	}
}

BOOLEAN FreeStructureFile( STRUCTURE_FILE_REF * pStructureFile )
{
	CHECKF( pStructureFile );

	// unlink the file ref
	if (pStructureFile->pPrev != NULL)
	{
		pStructureFile->pPrev->pNext = pStructureFile->pNext;
	}
	else
	{
		// freeing the head of the list!
		gpStructureFileRefs = pStructureFile->pNext;
	}
	if (pStructureFile->pNext != NULL)
	{
		pStructureFile->pNext->pPrev = pStructureFile->pPrev;
	}
	if (pStructureFile->pPrev == NULL && pStructureFile->pNext == NULL)
	{
		// toasting the list!
		gpStructureFileRefs = NULL;
	}
	// and free all the structures used!
	FreeStructureFileRef( pStructureFile );
	return( TRUE );
}

BOOLEAN LoadStructureData( STR szFileName, STRUCTURE_FILE_REF *	pFileRef, UINT32 * puiStructureDataSize )
//UINT8 **ppubStructureData, UINT32 * puiDataSize, STRUCTURE_FILE_HEADER * pHeader )
{
	// Loads a structure file's data as a honking chunk o' memory
	HWFILE										hInput;
	STRUCTURE_FILE_HEADER			Header;
	UINT32										uiBytesRead;
	UINT32										uiDataSize;
	BOOLEAN										fOk;

	CHECKF( szFileName );
	CHECKF( pFileRef );
	hInput = FileOpen( szFileName, FILE_ACCESS_READ | FILE_OPEN_EXISTING, FALSE );
	if (hInput == 0)
	{
		return( FALSE );
	}
	fOk = FileRead( hInput, &Header, sizeof( STRUCTURE_FILE_HEADER ), &uiBytesRead );
	if (!fOk || uiBytesRead != sizeof( STRUCTURE_FILE_HEADER ) || strncmp( Header.szId, STRUCTURE_FILE_ID, STRUCTURE_FILE_ID_LEN ) != 0 || Header.usNumberOfStructures == 0)
	{
		FileClose( hInput );
		return( FALSE );
	}
	pFileRef->usNumberOfStructures = Header.usNumberOfStructures;
	if (Header.fFlags & STRUCTURE_FILE_CONTAINS_AUXIMAGEDATA)
	{
		uiDataSize = sizeof( AuxObjectData ) * Header.usNumberOfImages;
		pFileRef->pAuxData = (AuxObjectData *) MemAlloc( uiDataSize );
		if (pFileRef->pAuxData == NULL)
		{
			FileClose( hInput );
			return( FALSE );
		}
		fOk = FileRead( hInput, pFileRef->pAuxData, uiDataSize, &uiBytesRead );
		if (!fOk || uiBytesRead != uiDataSize)
		{
			MemFree( pFileRef->pAuxData );
			FileClose( hInput );
			return( FALSE );
		}
		if (Header.usNumberOfImageTileLocsStored > 0)
		{
			uiDataSize = sizeof( RelTileLoc ) * Header.usNumberOfImageTileLocsStored;
			pFileRef->pTileLocData = (RelTileLoc *) MemAlloc( uiDataSize );
			if (pFileRef->pTileLocData == NULL)
			{
				MemFree( pFileRef->pAuxData );
				FileClose( hInput );
				return( FALSE );
			}
			fOk = FileRead( hInput, pFileRef->pTileLocData, uiDataSize, &uiBytesRead );
			if (!fOk || uiBytesRead != uiDataSize)
			{
				MemFree( pFileRef->pAuxData );
				FileClose( hInput );
				return( FALSE );
			}
		}
	}
	if (Header.fFlags & STRUCTURE_FILE_CONTAINS_STRUCTUREDATA)
	{
		pFileRef->usNumberOfStructuresStored = Header.usNumberOfStructuresStored;
		uiDataSize = Header.usStructureDataSize;
		// Determine the size of the data, from the header just read,
		// allocate enough memory and read it in
		pFileRef->pubStructureData = (UINT8 *) MemAlloc( uiDataSize );
		if (pFileRef->pubStructureData == NULL)
		{
			FileClose( hInput );
			if (pFileRef->pAuxData != NULL)
			{
				MemFree( pFileRef->pAuxData );
				if (pFileRef->pTileLocData != NULL)
				{
					MemFree( pFileRef->pTileLocData );
				}
			}
			return( FALSE );
		}
		fOk = FileRead( hInput, pFileRef->pubStructureData, uiDataSize, &uiBytesRead );
		if (!fOk || uiBytesRead != uiDataSize)
		{
			MemFree( pFileRef->pubStructureData );
			if (pFileRef->pAuxData != NULL)
			{
				MemFree( pFileRef->pAuxData );
				if (pFileRef->pTileLocData != NULL)
				{
					MemFree( pFileRef->pTileLocData );
				}
			}
			FileClose( hInput );
			return( FALSE );
		}
		*puiStructureDataSize = uiDataSize;
	}
	FileClose( hInput );
	return( TRUE );
}

BOOLEAN CreateFileStructureArrays( STRUCTURE_FILE_REF * pFileRef, UINT32 uiDataSize )
{
	// Based on a file chunk, creates all the dynamic arrays for the
	// structure definitions contained within

	UINT8 *										pCurrent;
	DB_STRUCTURE_REF *				pDBStructureRef;
	DB_STRUCTURE_TILE **			ppTileArray;
	UINT16										usLoop;
	UINT16										usIndex;
	UINT16										usTileLoop;
	UINT32										uiHitPoints;

	pCurrent = pFileRef->pubStructureData;
	pDBStructureRef = (DB_STRUCTURE_REF *) MemAlloc( pFileRef->usNumberOfStructures * sizeof( DB_STRUCTURE_REF ) );
	if (pDBStructureRef == NULL)
	{
		return( FALSE );
	}
	memset( pDBStructureRef, 0, pFileRef->usNumberOfStructures * sizeof( DB_STRUCTURE_REF ) );
	pFileRef->pDBStructureRef = pDBStructureRef;
	for (usLoop = 0; usLoop < pFileRef->usNumberOfStructuresStored; usLoop++)
	{
		if (pCurrent + sizeof( DB_STRUCTURE ) > pFileRef->pubStructureData + uiDataSize)
		{	// gone past end of file block?!
			// freeing of memory will occur outside of the function
			return( FALSE );
		}
		usIndex = ((DB_STRUCTURE *) pCurrent)->usStructureNumber;
		pDBStructureRef[usIndex].pDBStructure = (DB_STRUCTURE *) pCurrent;
		ppTileArray = (DB_STRUCTURE_TILE **) MemAlloc( pDBStructureRef[usIndex].pDBStructure->ubNumberOfTiles * sizeof( DB_STRUCTURE_TILE *));
		if (ppTileArray == NULL)
		{ // freeing of memory will occur outside of the function
			return( FALSE );
		}
		pDBStructureRef[usIndex].ppTile = ppTileArray;
		pCurrent += sizeof( DB_STRUCTURE );
		// Set things up to calculate hit points
		uiHitPoints = 0;
		for (usTileLoop = 0; usTileLoop < pDBStructureRef[usIndex].pDBStructure->ubNumberOfTiles; usTileLoop++ )
		{
			if (pCurrent + sizeof( DB_STRUCTURE ) > pFileRef->pubStructureData + uiDataSize)
			{	// gone past end of file block?!
				// freeing of memory will occur outside of the function
				return( FALSE );
			}
			ppTileArray[usTileLoop] = (DB_STRUCTURE_TILE *) pCurrent;
			// set the single-value relative position between this tile and the base tile
			ppTileArray[usTileLoop]->sPosRelToBase = ppTileArray[usTileLoop]->bXPosRelToBase + ppTileArray[usTileLoop]->bYPosRelToBase * WORLD_COLS;
			uiHitPoints += FilledTilePositions( ppTileArray[usTileLoop] );
			pCurrent += sizeof( DB_STRUCTURE_TILE );
		}
		// scale hit points down to something reasonable...
		uiHitPoints = uiHitPoints * 100 / 255;
		/*
		if (uiHitPoints > 255)
		{
			uiHitPoints = 255;
		}
		*/
		pDBStructureRef[usIndex].pDBStructure->ubHitPoints = (UINT8) uiHitPoints;
		/*
		if (pDBStructureRef[usIndex].pDBStructure->usStructureNumber + 1 == pFileRef->usNumberOfStructures)
		{
			break;
		}
		*/
	}
	return( TRUE );
}

#include <vfs/Core/vfs_file_raii.h>
#include <vfs/Core/vfs.h>
#include "XML_StructureData.hpp"

void checkStructureValidity(STRUCTURE_FILE_REF *str1, STRUCTURE_FILE_REF* str2, UINT32 size1, UINT32 size2)
{
	if(str1 == str2 && str1 == NULL)
	{
		return;
	}

	if(str1->pubStructureData && str2->pubStructureData)
	{
		SGP_THROW_IFFALSE( size1 == size2, L"");
		//SGP_THROW_IFFALSE( 0 == memcmp(str1->pubStructureData, str2->pubStructureData, size1), L"" );
		UINT8* ptr1 = str1->pubStructureData;
		UINT8* ptr2 = str2->pubStructureData;
		unsigned int count = 0;
		for(; count < size1 && (*ptr1++ == *ptr2++); count++) {}
		SGP_THROW_IFFALSE(count == size1, L"bad structure data");
	}
	else if(str1->pubStructureData || str2->pubStructureData)
	{
		SGP_THROW(L"bad structure data");
	}

	int count_tiles_1 = 0, count_tiles_2 = 0;
	if(str1->pAuxData && str2->pAuxData)
	{
		SGP_THROW_IFFALSE(str1->usNumberOfStructures == str2->usNumberOfStructures, L"");
		SGP_THROW_IFFALSE(str1->usNumberOfStructuresStored == str2->usNumberOfStructuresStored, L"");
		//SGP_THROW_IFFALSE( 0 == memcmp(str1->pAuxData, str2->pAuxData, sizeof(AuxObjectData)*str2->usNumberOfStructures), L"" );
		for(int i = 0; i < str2->usNumberOfStructures; ++i)
		{
			SGP_THROW_IFFALSE( 0 == memcmp(&str1->pAuxData[i], &str2->pAuxData[i], sizeof(AuxObjectData)), _BS(L"bad aux_image : ") << i << _BS::wget );
		}

		for(int i = 0; i < str1->usNumberOfStructures; ++i){ 
			count_tiles_1 += str1->pAuxData[i].ubNumberOfTiles; 
		}
		for(int j = 0; j < str2->usNumberOfStructures; ++j){ 
			count_tiles_2 += str2->pAuxData[j].ubNumberOfTiles; 
		}
		SGP_THROW_IFFALSE(count_tiles_1 == count_tiles_2, L"");
	}
	else if(str1->pAuxData || str2->pAuxData)
	{
		SGP_THROW(L"bad aux image data");
	}

	if(str1->pTileLocData && str2->pTileLocData)
	{
		SGP_THROW_IFFALSE( 0 == memcmp(str1->pTileLocData, str2->pTileLocData, count_tiles_1*sizeof(RelTileLoc)), L"" );
	}
	else if(str1->pTileLocData || str2->pTileLocData)
	{
		SGP_THROW(L"bad aux tile data");
	}
}

STRUCTURE_FILE_REF * LoadStructureFile( STR szFileName )
{
	// NB should be passed in expected number of structures so we can check equality
	UINT32				uiDataSize = 0;
	BOOLEAN				fOk;
	STRUCTURE_FILE_REF*	pFileRef;

	bool found_jsd_xml_file = false;
	extern bool g_bUseXML_Structures;

	if(g_bUseXML_Structures)
	{
		vfs::Path filename = std::string(szFileName) + ".xml";

		if(getVFS()->fileExists(filename))
		{
			pFileRef = (STRUCTURE_FILE_REF *) MemAlloc( sizeof( STRUCTURE_FILE_REF ) );
			memset( pFileRef, 0, sizeof( STRUCTURE_FILE_REF ) );
		
			CStructureDataReader str_reader(pFileRef);
			xml_auto::TGenericXMLParser<CStructureDataReader> str_parser(&str_reader);
		
			SGP_TRYCATCH_RETHROW( str_parser.parseFile(filename), _BS(L"Could not parse file : ") << filename << _BS::wget);

			uiDataSize = str_reader.structure_data_size;
			fOk = TRUE;

			found_jsd_xml_file = true;
		}
		else
		{
		}
	}
	if(!found_jsd_xml_file)
	{
		pFileRef = (STRUCTURE_FILE_REF *) MemAlloc( sizeof( STRUCTURE_FILE_REF ) );
		if (pFileRef == NULL)
		{
			return( NULL );
		}
		memset( pFileRef, 0, sizeof( STRUCTURE_FILE_REF ) );

		if(g_bUseXML_Structures)
		{
		}

		fOk = LoadStructureData( szFileName, pFileRef, &uiDataSize );
	}

	if (!fOk)
	{
		SGP_WARNING("Could not load structure file : ") << szFileName << "[.jsd|.xml]" << sgp::endl << sgp::flush;
		MemFree( pFileRef );
		return( NULL );
	}

	if (pFileRef->pubStructureData != NULL)
	{
		fOk = CreateFileStructureArrays( pFileRef, uiDataSize );
		if (fOk == FALSE)
		{
			FreeStructureFileRef( pFileRef );
			return( NULL );
		}
	}
	// Add the file reference to the master list, at the head for convenience
	if (gpStructureFileRefs != NULL)
	{
		gpStructureFileRefs->pPrev = pFileRef;
	}
	pFileRef->pNext = gpStructureFileRefs;
	gpStructureFileRefs = pFileRef;
	return( pFileRef );
}


//
// Structure creation functions
//


STRUCTURE * CreateStructureFromDB( DB_STRUCTURE_REF * pDBStructureRef, UINT8 ubTileNum )
{
	// Creates a STRUCTURE struct for one tile of a structure
	STRUCTURE	*						pStructure;
	DB_STRUCTURE *				pDBStructure;
	DB_STRUCTURE_TILE	*		pTile;

	// set pointers to the DBStructure and Tile
	CHECKN( pDBStructureRef );
	CHECKN( pDBStructureRef->pDBStructure );
	pDBStructure = pDBStructureRef->pDBStructure;
	CHECKN( pDBStructureRef->ppTile );
	pTile = pDBStructureRef->ppTile[ubTileNum];
	CHECKN( pTile );

	// allocate memory...
	pStructure = (STRUCTURE *) MemAlloc( sizeof( STRUCTURE ) );
	CHECKN( pStructure );

	memset( pStructure, 0, sizeof( STRUCTURE ) );

	// setup
	pStructure->fFlags = pDBStructure->fFlags;
	pStructure->pShape = &(pTile->Shape);
	pStructure->pDBStructureRef = pDBStructureRef;
	if (pTile->sPosRelToBase == 0)
	{	// base tile
		pStructure->fFlags |= STRUCTURE_BASE_TILE;
		pStructure->ubHitPoints = pDBStructure->ubHitPoints;
	}
	if (pDBStructure->ubWallOrientation != NO_ORIENTATION)
	{
		if (pStructure->fFlags & STRUCTURE_WALL)
		{
			// for multi-tile walls, which are only the special corner pieces,
			// the non-base tile gets no orientation value because this copy
			// will be skipped
			if (pStructure->fFlags & STRUCTURE_BASE_TILE)
			{
				pStructure->ubWallOrientation = pDBStructure->ubWallOrientation;
			}
		}
		else
		{
			pStructure->ubWallOrientation = pDBStructure->ubWallOrientation;
		}
	}
	pStructure->ubVehicleHitLocation = pTile->ubVehicleHitLocation;
	return( pStructure );
}

BOOLEAN OkayToAddStructureToTile( INT32 sBaseGridNo, INT16 sCubeOffset, DB_STRUCTURE_REF * pDBStructureRef, UINT8 ubTileIndex, INT16 sExclusionID, BOOLEAN fIgnorePeople )
{
	// Verifies whether a structure is blocked from being added to the map at a particular point
	DB_STRUCTURE *	pDBStructure;
	DB_STRUCTURE_TILE	**	ppTile;
	STRUCTURE *			pExistingStructure;
	STRUCTURE *			pOtherExistingStructure;
	INT8						bLoop, bLoop2;
	INT32 sGridNo;
	INT32						sOtherGridNo;

	ppTile = pDBStructureRef->ppTile;
	sGridNo = sBaseGridNo + ppTile[ubTileIndex]->sPosRelToBase;
	if (sGridNo < 0 || sGridNo > WORLD_MAX)
	{
		return( FALSE );
	}

	if (gpWorldLevelData[sBaseGridNo].sHeight != gpWorldLevelData[sGridNo].sHeight)
	{
		// uneven terrain, one portion on top of cliff and another not! can't add!
		return( FALSE );
	}

	pDBStructure = pDBStructureRef->pDBStructure;
	pExistingStructure = gpWorldLevelData[sGridNo].pStructureHead;

/*
	// If adding a mobile structure, always allow addition if the mobile structure tile is passable
	if ( (pDBStructure->fFlags & STRUCTURE_MOBILE) && (ppTile[ubTileIndex]->fFlags & TILE_PASSABLE) )
	{
		return( TRUE );
	}
*/

	while (pExistingStructure != NULL)
	{
		if (sCubeOffset == pExistingStructure->sCubeOffset)
		{

			// CJC:
			// If adding a mobile structure, allow addition if existing structure is passable
			if ( (pDBStructure->fFlags & STRUCTURE_MOBILE) && (pExistingStructure->fFlags & STRUCTURE_PASSABLE) )
			{
				// Skip!
				pExistingStructure = pExistingStructure->pNext;
				continue;
			}


			if (pDBStructure->fFlags & STRUCTURE_OBSTACLE)
			{

				// CJC: NB these next two if states are probably COMPLETELY OBSOLETE but I'm leaving
				// them in there for now (no harm done)

				// ATE:
				// ignore this one if it has the same ID num as exclusion
				if ( sExclusionID != INVALID_STRUCTURE_ID )
				{
					if ( pExistingStructure->usStructureID == sExclusionID )
					{
						// Skip!
						pExistingStructure = pExistingStructure->pNext;
						continue;
					}
				}

				if ( fIgnorePeople )
				{
					// If we are a person, skip!
					if ( pExistingStructure->usStructureID < TOTAL_SOLDIERS )
					{
						// Skip!
						pExistingStructure = pExistingStructure->pNext;
						continue;
					}
				}

				// two obstacle structures aren't allowed in the same tile at the same height
				// ATE: There is more sophisticated logic for mobiles, so postpone this check if mobile....
				if ( ( pExistingStructure->fFlags & STRUCTURE_OBSTACLE ) && !( pDBStructure->fFlags & STRUCTURE_MOBILE ) )
				{
					if ( pExistingStructure->fFlags & STRUCTURE_PASSABLE && !(pExistingStructure->fFlags & STRUCTURE_MOBILE) )
					{
						// no mobiles, existing structure is passable
					}
					else
					{
						return( FALSE );
					}
				}
				else if ((pDBStructure->ubNumberOfTiles > 1) && (pExistingStructure->fFlags & STRUCTURE_WALLSTUFF))
				{
					// if not an open door...
					if ( ! ( (pExistingStructure->fFlags & STRUCTURE_ANYDOOR) && (pExistingStructure->fFlags & STRUCTURE_OPEN) ) )
					{

						// we could be trying to place a multi-tile obstacle on top of a wall; we shouldn't
						// allow this if the structure is going to be on both sides of the wall
						for (bLoop = 1; bLoop < 4; bLoop++)
						{
							switch( pExistingStructure->ubWallOrientation)
							{
								case OUTSIDE_TOP_LEFT:
								case INSIDE_TOP_LEFT:
									sOtherGridNo = NewGridNo( sGridNo, DirectionInc( (INT8) (bLoop + 2) ) );
									break;
								case OUTSIDE_TOP_RIGHT:
								case INSIDE_TOP_RIGHT:
									sOtherGridNo = NewGridNo( sGridNo, DirectionInc( bLoop ) );
									break;
								default:
									// @%?@#%?@%
									sOtherGridNo = NewGridNo( sGridNo, DirectionInc( SOUTHEAST ) );
							}
							for (bLoop2 = 0; bLoop2 < pDBStructure->ubNumberOfTiles; bLoop2++)
							{
								if ( sBaseGridNo + ppTile[bLoop2]->sPosRelToBase == sOtherGridNo)
								{
									// obstacle will straddle wall!
									return( FALSE );
								}
							}
						}
					}

				}
			}
			else if (pDBStructure->fFlags & STRUCTURE_WALLSTUFF)
			{
				// two walls with the same alignment aren't allowed in the same tile
				if ((pExistingStructure->fFlags & STRUCTURE_WALLSTUFF) && (pDBStructure->ubWallOrientation == pExistingStructure->ubWallOrientation))
				{
					return( FALSE );
				}
				else if ( !(pExistingStructure->fFlags & (STRUCTURE_CORPSE | STRUCTURE_PERSON)) )
				{
					// it's possible we're trying to insert this wall on top of a multitile obstacle
					for (bLoop = 1; bLoop < 4; bLoop++)
					{
						switch( pDBStructure->ubWallOrientation)
						{
							case OUTSIDE_TOP_LEFT:
							case INSIDE_TOP_LEFT:
								sOtherGridNo = NewGridNo( sGridNo, DirectionInc( (INT8) (bLoop + 2) ) );
								break;
							case OUTSIDE_TOP_RIGHT:
							case INSIDE_TOP_RIGHT:
								sOtherGridNo = NewGridNo( sGridNo, DirectionInc( bLoop ) );
								break;
							default:
								// @%?@#%?@%
								sOtherGridNo = NewGridNo( sGridNo, DirectionInc( SOUTHEAST ) );
								break;
						}
						for (ubTileIndex = 0; ubTileIndex < pDBStructure->ubNumberOfTiles; ubTileIndex++)
						{
							pOtherExistingStructure = FindStructureByID( sOtherGridNo, pExistingStructure->usStructureID );
							if (pOtherExistingStructure)
							{
								return( FALSE );
							}
						}
					}
				}
			}

			if ( pDBStructure->fFlags & STRUCTURE_MOBILE )
			{
				// ATE:
				// ignore this one if it has the same ID num as exclusion
				if ( sExclusionID != INVALID_STRUCTURE_ID )
				{
					if ( pExistingStructure->usStructureID == sExclusionID )
					{
						// Skip!
						pExistingStructure = pExistingStructure->pNext;
						continue;
					}
				}

				if ( fIgnorePeople )
				{
					// If we are a person, skip!
					if ( pExistingStructure->usStructureID < TOTAL_SOLDIERS )
					{
						// Skip!
						pExistingStructure = pExistingStructure->pNext;
						continue;
					}
				}

				// ATE: Added check here - UNLESS the part we are trying to add is PASSABLE!
				if ( pExistingStructure->fFlags & STRUCTURE_MOBILE && !(pExistingStructure->fFlags & STRUCTURE_PASSABLE) && !(ppTile[ubTileIndex]->fFlags & TILE_PASSABLE) )
				{
					// don't allow 2 people in the same tile
					return( FALSE );
				}

		// ATE: Another rule: allow PASSABLE *IF* the PASSABLE is *NOT* MOBILE!
				if ( !( pExistingStructure->fFlags & STRUCTURE_MOBILE ) && (pExistingStructure->fFlags & STRUCTURE_PASSABLE) )
				{
					// Skip!
					pExistingStructure = pExistingStructure->pNext;
					continue;
		}

				// ATE: Added here - UNLESS this part is PASSABLE....
				// two obstacle structures aren't allowed in the same tile at the same height
				if ( (pExistingStructure->fFlags & STRUCTURE_OBSTACLE ) && !(ppTile[ubTileIndex]->fFlags & TILE_PASSABLE) )
				{
					return( FALSE );
				}
			}

			if ((pDBStructure->fFlags & STRUCTURE_OPENABLE))
			{
				if (pExistingStructure->fFlags & STRUCTURE_OPENABLE)
				{
					// don't allow two openable structures in the same tile or things will screw
					// up on an interface level
					return( FALSE );
				}
			}
		}

		pExistingStructure = pExistingStructure->pNext;
	}


	return( TRUE );
}

BOOLEAN InternalOkayToAddStructureToWorld( INT32 sBaseGridNo, INT8 bLevel, DB_STRUCTURE_REF * pDBStructureRef, INT16 sExclusionID, BOOLEAN fIgnorePeople )
{
	UINT8 ubLoop;
	INT16									sCubeOffset;

	CHECKF( pDBStructureRef );
	CHECKF( pDBStructureRef->pDBStructure );
	CHECKF( pDBStructureRef->pDBStructure->ubNumberOfTiles > 0 );
	CHECKF( pDBStructureRef->ppTile );

/*
	if (gpWorldLevelData[sGridNo].sHeight != sBaseTileHeight)
	{
		// not level ground!
		return( FALSE );
	}
*/

	for (ubLoop = 0; ubLoop < pDBStructureRef->pDBStructure->ubNumberOfTiles; ubLoop++)
	{
		if (pDBStructureRef->ppTile[ubLoop]->fFlags & TILE_ON_ROOF)
		{
			if (bLevel == 0)
			{
				sCubeOffset = PROFILE_Z_SIZE;
			}
			else
			{
				return( FALSE );
			}
		}
		else
		{
			sCubeOffset = bLevel * PROFILE_Z_SIZE;
		}
		if (!OkayToAddStructureToTile( sBaseGridNo, sCubeOffset, pDBStructureRef, ubLoop, sExclusionID, fIgnorePeople ))
		{
			return( FALSE );
		}
	}
	return( TRUE );
}

BOOLEAN OkayToAddStructureToWorld( INT32 sBaseGridNo, INT8 bLevel, DB_STRUCTURE_REF * pDBStructureRef, INT16 sExclusionID )
{
	return( InternalOkayToAddStructureToWorld( sBaseGridNo, bLevel, pDBStructureRef, sExclusionID, (BOOLEAN)(sExclusionID == IGNORE_PEOPLE_STRUCTURE_ID) ) );
}

BOOLEAN AddStructureToTile( MAP_ELEMENT * pMapElement, STRUCTURE * pStructure, UINT16 usStructureID )
{
	// adds a STRUCTURE to a MAP_ELEMENT (adds part of a structure to a location on the map)
	STRUCTURE *		pStructureTail;

	CHECKF( pMapElement );
	CHECKF( pStructure );
	pStructureTail = pMapElement->pStructureTail;
	if (pStructureTail == NULL)
	{ // set the head and tail to the new structure
		pMapElement->pStructureHead = pStructure;
	}
	else
	{ // add to the end of the list
		pStructure->pPrev = pStructureTail;
		pStructureTail->pNext = pStructure;
	}
	pMapElement->pStructureTail = pStructure;
	pStructure->usStructureID = usStructureID;
	if (pStructure->fFlags & STRUCTURE_OPENABLE)
	{
		pMapElement->uiFlags |= MAPELEMENT_INTERACTIVETILE;
	}
	return( TRUE );
}


STRUCTURE * InternalAddStructureToWorld( INT32 sBaseGridNo, INT8 bLevel, DB_STRUCTURE_REF * pDBStructureRef, LEVELNODE * pLevelNode )
{
	// Adds a complete structure to the world at a location plus all other locations covered by the structure
	INT32 sGridNo;
	STRUCTURE **					ppStructure;
	STRUCTURE *						pBaseStructure;
	DB_STRUCTURE *				pDBStructure;
	DB_STRUCTURE_TILE	**	ppTile;
	UINT8 ubLoop;
	UINT8 ubLoop2;
	INT16									sBaseTileHeight=-1;
	UINT16								usStructureID;

	CHECKF( pDBStructureRef );
	CHECKF( pLevelNode );

	pDBStructure = pDBStructureRef->pDBStructure;
	CHECKF( pDBStructure );

	ppTile = pDBStructureRef->ppTile;
	CHECKF( ppTile );

	CHECKF( pDBStructure->ubNumberOfTiles > 0 );

	// first check to see if the structure will be blocked
	if (!OkayToAddStructureToWorld( sBaseGridNo, bLevel, pDBStructureRef, INVALID_STRUCTURE_ID )	)
	{
		return( NULL );
	}

	// We go through a definition stage here and a later stage of
	// adding everything to the world so that we don't have to untangle
	// things if we run out of memory.	First we create an array of
	// pointers to point to all of the STRUCTURE elements created in
	// the first stage.	This array gets given to the base tile so
	// there is an easy way to remove an entire object from the world quickly

	// NB we add 1 because the 0th element is in fact the reference count!
	ppStructure = (STRUCTURE **) MemAlloc( pDBStructure->ubNumberOfTiles * sizeof( STRUCTURE * ) );
	CHECKF( ppStructure );
	memset( ppStructure, 0, pDBStructure->ubNumberOfTiles * sizeof( STRUCTURE * ) );

	for (ubLoop = BASE_TILE; ubLoop < pDBStructure->ubNumberOfTiles; ubLoop++)
	{ // for each tile, create the appropriate STRUCTURE struct
		ppStructure[ubLoop] = CreateStructureFromDB( pDBStructureRef, ubLoop );
		if (ppStructure[ubLoop] == NULL)
		{
			// Free allocated memory and abort!
			for (ubLoop2 = 0; ubLoop2 < ubLoop; ubLoop2++)
			{
				MemFree( ppStructure[ubLoop2] );
			}
			MemFree( ppStructure );
			return( NULL );
		}
		ppStructure[ubLoop]->sGridNo = sBaseGridNo + ppTile[ubLoop]->sPosRelToBase;
		if (ubLoop != BASE_TILE)
		{
			#ifdef JA2EDITOR
				//Kris:
				//Added this undo code if in the editor.
				//It is important to save tiles effected by multitiles.	If the structure placement
				//fails below, it doesn't matter, because it won't hurt the undo code.
				if( gfEditMode )
					AddToUndoList( ppStructure[ ubLoop ]->sGridNo );
			#endif

			ppStructure[ubLoop]->sBaseGridNo = sBaseGridNo;
		}
		if (ppTile[ubLoop]->fFlags & TILE_ON_ROOF)
		{
			ppStructure[ubLoop]->sCubeOffset = (bLevel + 1) * PROFILE_Z_SIZE;
		}
		else
		{
			ppStructure[ubLoop]->sCubeOffset = bLevel * PROFILE_Z_SIZE;
		}
		if (ppTile[ubLoop]->fFlags & TILE_PASSABLE)
		{
			ppStructure[ubLoop]->fFlags |= STRUCTURE_PASSABLE;
		}
		if (pLevelNode->uiFlags & LEVELNODE_SOLDIER)
		{
			// should now be unncessary
			ppStructure[ubLoop]->fFlags |= STRUCTURE_PERSON;
			ppStructure[ubLoop]->fFlags &= ~(STRUCTURE_BLOCKSMOVES);
		}
		else if (pLevelNode->uiFlags & LEVELNODE_ROTTINGCORPSE || pDBStructure->fFlags & STRUCTURE_CORPSE)
		{
			ppStructure[ubLoop]->fFlags |= STRUCTURE_CORPSE;
			// attempted check to screen this out for queen creature or vehicle
			if ( pDBStructure->ubNumberOfTiles < 10 )
			{
				ppStructure[ubLoop]->fFlags |= STRUCTURE_PASSABLE;
				ppStructure[ubLoop]->fFlags &= ~(STRUCTURE_BLOCKSMOVES);
			}
			else
			{
				// make sure not transparent
				ppStructure[ubLoop]->fFlags &= ~(STRUCTURE_TRANSPARENT);
			}
		}
	}

	if (pLevelNode->uiFlags & LEVELNODE_SOLDIER)
	{
		// use the merc's ID as the structure ID for his/her structure
		usStructureID = pLevelNode->pSoldier->ubID;
	}
	else if (pLevelNode->uiFlags & LEVELNODE_ROTTINGCORPSE)
	{
	// ATE: Offset IDs so they don't collide with soldiers
		usStructureID = (UINT16)( TOTAL_SOLDIERS + pLevelNode->pAniTile->uiUserData );
	}
	else
	{
		gusNextAvailableStructureID++;
		if (gusNextAvailableStructureID == 0)
		{
			// skip past the #s for soldiers' structures and the invalid structure #
			gusNextAvailableStructureID = FIRST_AVAILABLE_STRUCTURE_ID;
		}
		usStructureID = gusNextAvailableStructureID;
	}
	// now add all these to the world!
	for (ubLoop = BASE_TILE; ubLoop < pDBStructure->ubNumberOfTiles; ubLoop++)
	{
		sGridNo = ppStructure[ubLoop]->sGridNo;
		if (ubLoop == BASE_TILE)
		{
			sBaseTileHeight = gpWorldLevelData[sGridNo].sHeight;
		}
		else
		{
			if (gpWorldLevelData[sGridNo].sHeight != sBaseTileHeight)
			{
				// not level ground! abort!
				for (ubLoop2 = BASE_TILE; ubLoop2 < ubLoop; ubLoop2++)
				{
					DeleteStructureFromTile( &(gpWorldLevelData[ppStructure[ubLoop2]->sGridNo]), ppStructure[ubLoop2] );
				}
				MemFree( ppStructure );
				return( NULL );
			}
		}
		if (AddStructureToTile( &(gpWorldLevelData[sGridNo]), ppStructure[ubLoop], usStructureID ) == FALSE)
		{
			// error! abort!
			for (ubLoop2 = BASE_TILE; ubLoop2 < ubLoop; ubLoop2++)
			{
				DeleteStructureFromTile( &(gpWorldLevelData[ppStructure[ubLoop2]->sGridNo]), ppStructure[ubLoop2] );
			}
			MemFree( ppStructure );
			return( NULL );
		}
	}

	pBaseStructure = ppStructure[BASE_TILE];
	pLevelNode->pStructureData = pBaseStructure;

	MemFree( ppStructure );
	// And we're done! return a pointer to the base structure!

	return( pBaseStructure );
}

BOOLEAN AddStructureToWorld( INT32 sBaseGridNo, INT8 bLevel, DB_STRUCTURE_REF * pDBStructureRef, PTR pLevelN )
{
	STRUCTURE * pStructure;

	pStructure = InternalAddStructureToWorld( sBaseGridNo, bLevel, pDBStructureRef, (LEVELNODE *) pLevelN );
	if (pStructure == NULL)
	{
		return( FALSE );
	}
	return( TRUE );
}

//
// Structure deletion functions
//

void DeleteStructureFromTile( MAP_ELEMENT * pMapElement, STRUCTURE * pStructure )
{
	// removes a STRUCTURE element at a particular location from the world
	// put location pointer in tile
	if (pMapElement->pStructureHead == pStructure)
	{
		if (pMapElement->pStructureTail == pStructure)
		{
			// only element in the list!
			pMapElement->pStructureHead = NULL;
			pMapElement->pStructureTail = NULL;
		}
		else
		{
			// first element in the list of 2+ members
			pMapElement->pStructureHead = pStructure->pNext;
		}
	}
	else if (pMapElement->pStructureTail == pStructure)
	{
		// last element in the list
		pStructure->pPrev->pNext = NULL;
		pMapElement->pStructureTail = pStructure->pPrev;
	}
	else
	{
		// second or later element in the list; it's guaranteed that there is a
		// previous element but not necessary a next
		pStructure->pPrev->pNext = pStructure->pNext;
		if (pStructure->pNext != NULL)
		{
			pStructure->pNext->pPrev = pStructure->pPrev;
		}
	}
	if (pStructure->fFlags & STRUCTURE_OPENABLE)
	{ // only one allowed in a tile, so we are safe to do this...
		pMapElement->uiFlags &= (~MAPELEMENT_INTERACTIVETILE);
	}
	MemFree( pStructure );
}

BOOLEAN DeleteStructureFromWorld( STRUCTURE * pStructure )
{
	// removes all of the STRUCTURE elements for a structure from the world
	MAP_ELEMENT *					pBaseMapElement;
	STRUCTURE *						pBaseStructure;
	DB_STRUCTURE_TILE	**	ppTile;
	STRUCTURE *						pCurrent;
	UINT8 ubLoop, ubLoop2;
	UINT8									ubNumberOfTiles;
	INT32 sBaseGridNo, sGridNo;
	UINT16								usStructureID;
	BOOLEAN								fMultiStructure;
	BOOLEAN								fRecompileMPs;
	BOOLEAN								fRecompileExtraRadius; // for doors... yuck
	INT32									sCheckGridNo;

	CHECKF( pStructure );

	pBaseStructure = FindBaseStructure( pStructure );
	CHECKF( pBaseStructure );

	usStructureID = pBaseStructure->usStructureID;
	fMultiStructure = ( ( pBaseStructure->fFlags & STRUCTURE_MULTI ) != 0 );
	fRecompileMPs = ( ( gsRecompileAreaLeft != 0 ) && ! ( ( pBaseStructure->fFlags & STRUCTURE_MOBILE ) != 0 ) );
	if (fRecompileMPs)
	{
		fRecompileExtraRadius = ( ( pBaseStructure->fFlags & STRUCTURE_WALLSTUFF ) != 0 );
	}
	else
	{
		fRecompileExtraRadius = FALSE;
	}

	pBaseMapElement = &gpWorldLevelData[pBaseStructure->sGridNo];
	ppTile = pBaseStructure->pDBStructureRef->ppTile;
	sBaseGridNo = pBaseStructure->sGridNo;
	ubNumberOfTiles = pBaseStructure->pDBStructureRef->pDBStructure->ubNumberOfTiles;
	// Free all the tiles
	for (ubLoop = BASE_TILE; ubLoop < ubNumberOfTiles; ubLoop++)
	{
		sGridNo = sBaseGridNo + ppTile[ubLoop]->sPosRelToBase;
		// there might be two structures in this tile, one on each level, but we just want to
		// delete one on each pass
		pCurrent = FindStructureByID( sGridNo, usStructureID );
		if (pCurrent)
		{
			DeleteStructureFromTile( &gpWorldLevelData[sGridNo], pCurrent );
		}

		if ( !gfEditMode && ( fRecompileMPs ) )
		{
			if ( fRecompileMPs )
			{
				AddTileToRecompileArea( sGridNo );
				if ( fRecompileExtraRadius )
				{
					// add adjacent tiles too
					for (ubLoop2 = 0; ubLoop2 < NUM_WORLD_DIRECTIONS; ubLoop2++)
					{
						sCheckGridNo = NewGridNo( sGridNo, DirectionInc( ubLoop2 ) );
						if (sCheckGridNo != sGridNo)
						{
							AddTileToRecompileArea( sCheckGridNo );
						}
					}
				}
			}
		}
	}
	return( TRUE );
}

STRUCTURE * InternalSwapStructureForPartner( INT32 sGridNo, STRUCTURE * pStructure, BOOLEAN fFlipSwitches, BOOLEAN fStoreInMap )
{
	// switch structure
	LEVELNODE *				pLevelNode;
	LEVELNODE *				pShadowNode;
	STRUCTURE *				pBaseStructure;
	STRUCTURE *				pNewBaseStructure;
	DB_STRUCTURE_REF *	pPartnerDBStructure;
	BOOLEAN						fDoor;

	INT8							bDelta;
	UINT8							ubHitPoints;
	INT16							sCubeOffset;

	if (pStructure == NULL)
	{
		return( NULL );
	}
	pBaseStructure = FindBaseStructure( pStructure );
	CHECKF( pBaseStructure );
	if ((pBaseStructure->pDBStructureRef->pDBStructure)->bPartnerDelta == NO_PARTNER_STRUCTURE)
	{
		return( NULL );
	}
	fDoor = ((pBaseStructure->fFlags & STRUCTURE_ANYDOOR) > 0);
	pLevelNode = FindLevelNodeBasedOnStructure( pBaseStructure->sGridNo, pBaseStructure );
	if (pLevelNode == NULL)
	{
		return( NULL );
	}
	pShadowNode = FindShadow( pBaseStructure->sGridNo, pLevelNode->usIndex );

	// record values
	bDelta = pBaseStructure->pDBStructureRef->pDBStructure->bPartnerDelta;
	pPartnerDBStructure = pBaseStructure->pDBStructureRef + bDelta;
	sGridNo = pBaseStructure->sGridNo;
	ubHitPoints = pBaseStructure->ubHitPoints;
	sCubeOffset = pBaseStructure->sCubeOffset;
	// delete the old structure and add the new one
	if (DeleteStructureFromWorld( pBaseStructure ) == FALSE)
	{
		return( NULL );
	}
	pNewBaseStructure = InternalAddStructureToWorld( sGridNo, (INT8) (sCubeOffset / PROFILE_Z_SIZE), pPartnerDBStructure, pLevelNode );
	if (pNewBaseStructure == NULL)
	{
		return( NULL );
	}
	// set values in the new structure
	pNewBaseStructure->ubHitPoints = ubHitPoints;
	if (!fDoor)
	{ // swap the graphics

		// store removal of previous if necessary
		if ( fStoreInMap )
		{
			ApplyMapChangesToMapTempFile( TRUE );
			RemoveStructFromMapTempFile( sGridNo, pLevelNode->usIndex );
		}

		pLevelNode->usIndex += bDelta;

		// store removal of new one if necessary
		if ( fStoreInMap )
		{
			AddStructToMapTempFile( sGridNo, pLevelNode->usIndex );
			ApplyMapChangesToMapTempFile( FALSE );
		}

		if (pShadowNode != NULL)
		{
			pShadowNode->usIndex += bDelta;
		}
	}

	//if ( (pNewBaseStructure->fFlags & STRUCTURE_SWITCH) && (pNewBaseStructure->fFlags & STRUCTURE_OPEN) )
	if ( 0 /*fFlipSwitches*/ )
	{
		if ( pNewBaseStructure->fFlags & STRUCTURE_SWITCH )
		{
			// just turned a switch on!
			ActivateSwitchInGridNo( NOBODY, sGridNo );
		}
	}
	return( pNewBaseStructure );
}

STRUCTURE * SwapStructureForPartner( INT32 sGridNo, STRUCTURE * pStructure )
{
	return( InternalSwapStructureForPartner( sGridNo, pStructure, TRUE, FALSE ) );
}

STRUCTURE * SwapStructureForPartnerWithoutTriggeringSwitches( INT32 sGridNo, STRUCTURE * pStructure )
{
	return( InternalSwapStructureForPartner( sGridNo, pStructure, FALSE, FALSE ) );
}

STRUCTURE * SwapStructureForPartnerAndStoreChangeInMap( INT32 sGridNo, STRUCTURE * pStructure )
{
	return( InternalSwapStructureForPartner( sGridNo, pStructure, TRUE, TRUE ) );
}

#ifdef JA2UB
STRUCTURE * SwapStructureForPartnerForcingGraphicalChange( INT32 sGridNo, STRUCTURE * pStructure )
{
	return( InternalSwapStructureForPartner( sGridNo, pStructure, FALSE, TRUE ) );
}
#endif

STRUCTURE * FindStructure( INT32 sGridNo, UINT32 fFlags )
{
	// finds a structure that matches any of the given flags
	STRUCTURE * pCurrent;
	
	//bug fix for win98 crash when traveling between sectors
	if ( TileIsOutOfBounds( sGridNo ) )
	{
		return( NULL );
	}

	pCurrent =	gpWorldLevelData[sGridNo].pStructureHead;
	while (pCurrent != NULL)
	{
		if ((pCurrent->fFlags & fFlags) != 0)
		{
			return( pCurrent );
		}
		pCurrent = pCurrent->pNext;
	}
	return( NULL );
}

STRUCTURE * FindNextStructure( STRUCTURE * pStructure, UINT32 fFlags )
{
	STRUCTURE * pCurrent;

	CHECKF( pStructure );
	pCurrent = pStructure->pNext;
	while (pCurrent != NULL)
	{
		if ((pCurrent->fFlags & fFlags) != 0)
		{
			return( pCurrent );
		}
		pCurrent = pCurrent->pNext;
	}
	return( NULL );
}

STRUCTURE * FindStructureByID( INT32 sGridNo, UINT16 usStructureID )
{
	// finds a structure that matches any of the given flags
	STRUCTURE * pCurrent;

	pCurrent =	gpWorldLevelData[sGridNo].pStructureHead;
	while (pCurrent != NULL)
	{
		if (pCurrent->usStructureID == usStructureID)
		{
			return( pCurrent );
		}
		pCurrent = pCurrent->pNext;
	}
	return( NULL );
}

STRUCTURE * FindBaseStructure( STRUCTURE * pStructure )
{
	// finds the base structure for any structure
	CHECKF( pStructure );
	if (pStructure->fFlags & STRUCTURE_BASE_TILE)
	{
		return( pStructure );
	}
	return( FindStructureByID( pStructure->sBaseGridNo, pStructure->usStructureID ) );
}

STRUCTURE * FindNonBaseStructure( INT32 sGridNo, STRUCTURE * pStructure )
{
	// finds a non-base structure in a location
	CHECKF( pStructure );
	if (!(pStructure->fFlags & STRUCTURE_BASE_TILE))
	{	// error!
		return( NULL );
	}

	return( FindStructureByID( sGridNo, pStructure->usStructureID ) );
}

INT16 GetBaseTile( STRUCTURE * pStructure )
{
	if (pStructure == NULL)
	{
		return( -1 );
	}
	if (pStructure->fFlags & STRUCTURE_BASE_TILE)
	{
		return( pStructure->sGridNo );
	}
	else
	{
		return( pStructure->sBaseGridNo );
	}
}

INT8 StructureHeight( STRUCTURE * pStructure )
{
	// return the height of an object from 1-4
	UINT8 ubLoopX, ubLoopY;
	PROFILE *		pShape;
	UINT8				ubShapeValue;
	INT8				bLoopZ;
	INT8				bGreatestHeight = -1;

	if (pStructure == NULL || pStructure->pShape == NULL)
	{
		return( 0 );
	}

	if (pStructure->ubStructureHeight != 0)
	{
		return( pStructure->ubStructureHeight );
	}

	pShape = pStructure->pShape;

	// loop horizontally on the X and Y planes
	for (ubLoopX = 0; ubLoopX < PROFILE_X_SIZE; ubLoopX++)
	{
		for (ubLoopY = 0; ubLoopY < PROFILE_Y_SIZE; ubLoopY++)
		{
			ubShapeValue = (*pShape)[ubLoopX][ubLoopY];
			// loop DOWN vertically so that we find the tallest point first
			// and don't need to check any below it
			for (bLoopZ = PROFILE_Z_SIZE - 1; bLoopZ > bGreatestHeight; bLoopZ--)
			{
				if (ubShapeValue & AtHeight[bLoopZ])
				{
					bGreatestHeight = bLoopZ;
					if (bGreatestHeight == PROFILE_Z_SIZE - 1)
					{
						// store height
						pStructure->ubStructureHeight = bGreatestHeight + 1;
						return( bGreatestHeight + 1);
					}
					break;
				}
			}
		}
	}
	// store height
	pStructure->ubStructureHeight = bGreatestHeight + 1;
	return( bGreatestHeight + 1);
}

INT8 GetTallestStructureHeight( INT32 sGridNo, BOOLEAN fOnRoof )
{
	STRUCTURE *		pCurrent;
	INT8					iHeight;
	INT8					iTallest = 0;
	INT16					sDesiredHeight;

	if (fOnRoof)
	{
		sDesiredHeight = STRUCTURE_ON_ROOF;
	}
	else
	{
		sDesiredHeight = STRUCTURE_ON_GROUND;
	}
	pCurrent = gpWorldLevelData[sGridNo].pStructureHead;
	while (pCurrent != NULL)
	{
		if (pCurrent->sCubeOffset == sDesiredHeight)
		{
			iHeight = StructureHeight( pCurrent );
			if (iHeight > iTallest)
			{
				iTallest = iHeight;
			}
		}
		pCurrent = pCurrent->pNext;
	}
	return( iTallest );
}


INT8 GetStructureTargetHeight( INT32 sGridNo, BOOLEAN fOnRoof )
{
	STRUCTURE *		pCurrent;
	INT8					iHeight;
	INT8					iTallest = 0;
	INT16					sDesiredHeight;

	if (fOnRoof)
	{
		sDesiredHeight = STRUCTURE_ON_ROOF;
	}
	else
	{
		sDesiredHeight = STRUCTURE_ON_GROUND;
	}

	// prioritize openable structures and doors
	pCurrent = FindStructure( sGridNo, (STRUCTURE_DOOR | STRUCTURE_OPENABLE ) );
	if ( pCurrent )
	{
		// use this structure
		if ( pCurrent->fFlags & STRUCTURE_DOOR )
		{
			iTallest = 3; // don't aim at the very top of the door
		}
		else
		{
			iTallest = StructureHeight( pCurrent );
		}
	}
	else
	{
		pCurrent = gpWorldLevelData[sGridNo].pStructureHead;
		while (pCurrent != NULL)
		{
			if (pCurrent->sCubeOffset == sDesiredHeight)
			{
				iHeight = StructureHeight( pCurrent );

				if (iHeight > iTallest)
				{
					iTallest = iHeight;
				}
			}
			pCurrent = pCurrent->pNext;
		}
	}
	return( iTallest );
}


INT8 StructureBottomLevel( STRUCTURE * pStructure )
{
	// return the bottom level of an object, from 1-4
	UINT8 ubLoopX, ubLoopY;
	PROFILE *		pShape;
	UINT8				ubShapeValue;
	INT8				bLoopZ;
	INT8				bLowestHeight = PROFILE_Z_SIZE;

	if (pStructure == NULL || pStructure->pShape == NULL)
	{
		return( 0 );
	}
	pShape = pStructure->pShape;

	// loop horizontally on the X and Y planes
	for (ubLoopX = 0; ubLoopX < PROFILE_X_SIZE; ubLoopX++)
	{
		for (ubLoopY = 0; ubLoopY < PROFILE_Y_SIZE; ubLoopY++)
		{
			ubShapeValue = (*pShape)[ubLoopX][ubLoopY];
			// loop DOWN vertically so that we find the tallest point first
			// and don't need to check any below it
			for (bLoopZ = 0; bLoopZ < bLowestHeight; bLoopZ++)
			{
				if (ubShapeValue & AtHeight[bLoopZ])
				{
					bLowestHeight = bLoopZ;
					if (bLowestHeight == 0)
					{
						return( 1 );
					}
					break;
				}
			}
		}
	}
	return( bLowestHeight + 1);
}


BOOLEAN StructureDensity( STRUCTURE * pStructure, UINT8 * pubLevel0, UINT8 * pubLevel1, UINT8 * pubLevel2, UINT8 * pubLevel3 )
{
	UINT8 ubLoopX, ubLoopY;
	UINT8				ubShapeValue;
	PROFILE *		pShape;

	CHECKF( pStructure );
	CHECKF( pubLevel0 );
	CHECKF( pubLevel1 );
	CHECKF( pubLevel2 );
	CHECKF( pubLevel3 );
	*pubLevel0 = 0;
	*pubLevel1 = 0;
	*pubLevel2 = 0;
	*pubLevel3 = 0;

	pShape = pStructure->pShape;

	for (ubLoopX = 0; ubLoopX < PROFILE_X_SIZE; ubLoopX++)
	{
		for (ubLoopY = 0; ubLoopY < PROFILE_Y_SIZE; ubLoopY++)
		{
			ubShapeValue = (*pShape)[ubLoopX][ubLoopY];
			if (ubShapeValue & AtHeight[0])
			{
				(*pubLevel0)++;
			}
			if (ubShapeValue & AtHeight[1])
			{
				(*pubLevel1)++;
			}
			if (ubShapeValue & AtHeight[2])
			{
				(*pubLevel2)++;
			}
			if (ubShapeValue & AtHeight[3])
			{
				(*pubLevel3)++;
			}

		}
	}
	// convert values to percentages!
	*pubLevel0 *= 4;
	*pubLevel1 *= 4;
	*pubLevel2 *= 4;
	*pubLevel3 *= 4;
	return( TRUE );
}

BOOLEAN DamageStructure( STRUCTURE * pStructure, UINT8 ubDamage, UINT8 ubReason, INT32 sGridNo, INT16 sX, INT16 sY, UINT8 ubOwner )
{
	// do damage to a structure; returns TRUE if the structure should be removed

	STRUCTURE			*pBase;
	UINT8					ubArmour;
	//LEVELNODE			*pNode;

	CHECKF( pStructure );
	if (pStructure->fFlags & STRUCTURE_PERSON || pStructure->fFlags & STRUCTURE_CORPSE)
	{
		// don't hurt this structure, it's used for hit detection only!
		return( FALSE );
	}

	if ( (pStructure->pDBStructureRef->pDBStructure->ubArmour == MATERIAL_INDESTRUCTABLE_METAL) || (pStructure->pDBStructureRef->pDBStructure->ubArmour == MATERIAL_INDESTRUCTABLE_STONE) )
	{
		return( FALSE );
	}

	// Account for armour!
	if (ubReason == STRUCTURE_DAMAGE_EXPLOSION)
	{
		if ( pStructure->fFlags & STRUCTURE_EXPLOSIVE )
		{
			ubArmour = gubMaterialArmour[ pStructure->pDBStructureRef->pDBStructure->ubArmour ] / 3;
		}
		else
		{
			ubArmour = gubMaterialArmour[ pStructure->pDBStructureRef->pDBStructure->ubArmour ] / 2;
		}

		if (ubArmour > ubDamage)
		{
			// didn't even scratch the paint
			return( FALSE );
		}
		else
		{
			// did some damage to the structure
			ubDamage -= ubArmour;
		}
	}
	else
	{
		ubDamage = 0;
	}

	// OK, Let's check our reason
	if ( ubReason == STRUCTURE_DAMAGE_GUNFIRE )
	{
		// If here, we have penetrated, check flags
		// Are we an explodable structure?
		if ( (pStructure->fFlags & STRUCTURE_EXPLOSIVE) && Random( 2 ) )
		{
			// Remove struct!
			pBase = FindBaseStructure( pStructure );

			// ATE: Set hit points to zero....
			pBase->ubHitPoints = 0;

			// Get LEVELNODE for struct and remove!
			// pNode = FindLevelNodeBasedOnStructure( pBase->sGridNo, pBase );


			//Set a flag indicating that the following changes are to go the the maps temp file
			// ApplyMapChangesToMapTempFile( TRUE );
			// Remove!
			// RemoveStructFromLevelNode( pBase->sGridNo, pNode );
			// ApplyMapChangesToMapTempFile( FALSE );

			// Generate an explosion here!
			IgniteExplosion( ubOwner, sX, sY, 0, sGridNo, STRUCTURE_IGNITE, 0 );

			// ATE: Return false here, as we are dealing with deleting the graphic here...
			return( FALSE );
		}

		// Make hit sound....
	if ( pStructure->fFlags & STRUCTURE_CAVEWALL )
	{
			PlayJA2Sample( S_VEG_IMPACT1, RATE_11025, SoundVolume( HIGHVOLUME, sGridNo ), 1, SoundDir( sGridNo ) );
	}
	else
	{
		if ( guiMaterialHitSound[ pStructure->pDBStructureRef->pDBStructure->ubArmour ] != -1 )
		{
			PlayJA2Sample( guiMaterialHitSound[ pStructure->pDBStructureRef->pDBStructure->ubArmour ], RATE_11025, SoundVolume( HIGHVOLUME, sGridNo ), 1, SoundDir( sGridNo ) );
		}
	}
		// Don't update damage HPs....
		return( TRUE );
	}

	// OK, LOOK FOR A SAM SITE, UPDATE....
	UpdateAndDamageSAMIfFound( gWorldSectorX, gWorldSectorY, gbWorldSectorZ, sGridNo, ubDamage );

	// find the base so we can reduce the hit points!
	pBase = FindBaseStructure( pStructure );
	CHECKF( pBase );
	if (pBase->ubHitPoints <= ubDamage)
	{
		// boom! structure destroyed!
		return( TRUE );
	}
	else
	{
		pBase->ubHitPoints -= ubDamage;

		//Since the structure is being damaged, set the map element that a structure is damaged
		gpWorldLevelData[ sGridNo ].uiFlags |= MAPELEMENT_STRUCTURE_DAMAGED;

		// We are a little damaged....
		return( 2 );
	}
}

#define LINE_HEIGHT 20
void DebugStructurePage1( void )
{
	STRUCTURE *		pStructure;
	STRUCTURE *		pBase;
	//LEVELNODE *		pLand;
	INT32					sGridNo, sDesiredLevel;
	INT8					bHeight, bDens0, bDens1, bDens2, bDens3;
	INT8					bStructures;

	static CHAR16 WallOrientationString[5][15] =
	{
		L"None",
		L"Inside left",
		L"Inside right",
		L"Outside left",
		L"Outside right"
	};

	SetFont( LARGEFONT1 );
	gprintf( 0, 0, L"DEBUG STRUCTURES PAGE 1 OF 1" );
	if (GetMouseMapPos( &sGridNo ) == FALSE)
	{
		return;
		//gprintf( 0, LINE_HEIGHT * 1, L"No structure selected" );
	}

	if (gsInterfaceLevel == I_GROUND_LEVEL)
	{
		sDesiredLevel = STRUCTURE_ON_GROUND;
	}
	else
	{
		sDesiredLevel = STRUCTURE_ON_ROOF;
	}

	gprintf( 320, 0, L"Building %d", gubBuildingInfo[ sGridNo ] );
	/*
	pLand = gpWorldLevelData[sGridNo].pLandHead;
	gprintf( 320, 0, L"Fake light %d", pLand->ubFakeShadeLevel );
	gprintf( 320, LINE_HEIGHT, L"Real light: ground %d roof %d", LightTrueLevel( sGridNo, 0 ), LightTrueLevel( sGridNo, 1 ) );
	*/

	pStructure = gpWorldLevelData[sGridNo].pStructureHead;
	while (pStructure != NULL)
	{
		if (pStructure->sCubeOffset == sDesiredLevel)
		{
			break;
		}
		pStructure = pStructure->pNext;
	}

	if (pStructure != NULL)
	{
		if (pStructure->fFlags & STRUCTURE_GENERIC)
		{
			gprintf( 0, LINE_HEIGHT * 1, L"Generic structure %x #%d", pStructure->fFlags, pStructure->pDBStructureRef->pDBStructure->usStructureNumber );
		}
		else if (pStructure->fFlags & STRUCTURE_TREE)
		{
			gprintf( 0, LINE_HEIGHT * 1, L"Tree");
		}
		else if (pStructure->fFlags & STRUCTURE_WALL)
		{
			gprintf( 0, LINE_HEIGHT * 1, L"Wall with orientation %s", WallOrientationString[pStructure->ubWallOrientation] );
		}
		else if (pStructure->fFlags & STRUCTURE_WALLNWINDOW)
		{
			gprintf( 0, LINE_HEIGHT * 1, L"Wall with window" );
		}
		else if (pStructure->fFlags & STRUCTURE_VEHICLE)
		{
			gprintf( 0, LINE_HEIGHT * 1, L"Vehicle %d", pStructure->pDBStructureRef->pDBStructure->usStructureNumber );
		}
		else if (pStructure->fFlags & STRUCTURE_NORMAL_ROOF)
		{
			gprintf( 0, LINE_HEIGHT * 1, L"Roof" );
		}
		else if (pStructure->fFlags & STRUCTURE_SLANTED_ROOF)
		{
			gprintf( 0, LINE_HEIGHT * 1, L"Slanted roof" );
		}
		else if (pStructure->fFlags & STRUCTURE_DOOR)
		{
			gprintf( 0, LINE_HEIGHT * 1, L"Door with orientation %s", WallOrientationString[pStructure->ubWallOrientation] );
		}
		else if (pStructure->fFlags & STRUCTURE_SLIDINGDOOR)
		{
			gprintf( 0, LINE_HEIGHT * 1, L"%s sliding door with orientation %s",
				(pStructure->fFlags & STRUCTURE_OPEN) ? L"Open" : L"Closed",
				WallOrientationString[pStructure->ubWallOrientation] );
		}
		else if (pStructure->fFlags & STRUCTURE_DDOOR_LEFT)
		{
			gprintf( 0, LINE_HEIGHT * 1, L"DDoorLft with orientation %s", WallOrientationString[pStructure->ubWallOrientation] );
		}
		else if (pStructure->fFlags & STRUCTURE_DDOOR_RIGHT)
		{
			gprintf( 0, LINE_HEIGHT * 1, L"DDoorRt with orientation %s", WallOrientationString[pStructure->ubWallOrientation] );
		}
		else
		{
			gprintf( 0, LINE_HEIGHT * 1, L"UNKNOWN STRUCTURE! (%x)", pStructure->fFlags );
		}
		bHeight = StructureHeight( pStructure );
		pBase = FindBaseStructure( pStructure );
		gprintf( 0, LINE_HEIGHT * 2, L"Structure height %d, cube offset %d, armour %d, HP %d", bHeight, pStructure->sCubeOffset, gubMaterialArmour[pStructure->pDBStructureRef->pDBStructure->ubArmour], pBase->ubHitPoints );
		if (StructureDensity( pStructure, (UINT8 *)&bDens0, (UINT8 *)&bDens1, (UINT8 *)&bDens2, (UINT8 *)&bDens3 ) == TRUE)
		{
			gprintf( 0, LINE_HEIGHT * 3, L"Structure fill %d%%/%d%%/%d%%/%d%% density %d", bDens0, bDens1, bDens2, bDens3,
				pStructure->pDBStructureRef->pDBStructure->ubDensity );
		}

		#ifndef LOS_DEBUG
		gprintf( 0, LINE_HEIGHT * 4, L"Structure ID %d", pStructure->usStructureID );
		#endif

		pStructure = gpWorldLevelData[sGridNo].pStructureHead;
		for ( bStructures = 0; pStructure != NULL; pStructure = pStructure->pNext)
		{
			bStructures++;
		}
		gprintf( 0, LINE_HEIGHT * 12, L"Number of structures = %d", bStructures );
	}
	#ifdef LOS_DEBUG
		if (gLOSTestResults.fLOSTestPerformed)
		{
			gprintf( 0, LINE_HEIGHT * 4, L"LOS from (%7d,%7d,%7d)", gLOSTestResults.iStartX, gLOSTestResults.iStartY, gLOSTestResults.iStartZ);
			gprintf( 0, LINE_HEIGHT * 5, L"to (%7d,%7d,%7d)", gLOSTestResults.iEndX, gLOSTestResults.iEndY, gLOSTestResults.iEndZ);
			if (gLOSTestResults.fOutOfRange)
			{
				gprintf( 0, LINE_HEIGHT * 6, L"is out of range" );
			}
			else if (gLOSTestResults.fLOSClear)
			{
				gprintf( 0, LINE_HEIGHT * 6, L"is clear!" );
			}
			else
			{
				gprintf( 0, LINE_HEIGHT * 6, L"is blocked at (%7d,%7d,%7d)!", gLOSTestResults.iStoppedX, gLOSTestResults.iStoppedY, gLOSTestResults.iStoppedZ );
				gprintf( 0, LINE_HEIGHT * 10, L"Blocked at cube level %d", gLOSTestResults.iCurrCubesZ );
			}
			gprintf( 0, LINE_HEIGHT * 7, L"Passed through %d tree bits!", gLOSTestResults.ubTreeSpotsHit );
			gprintf( 0, LINE_HEIGHT * 8, L"Maximum range was %7d", gLOSTestResults.iMaxDistance );
			gprintf( 0, LINE_HEIGHT * 9, L"actual range was %7d", gLOSTestResults.iDistance );
			if (gLOSTestResults.ubChanceToGetThrough <= 100)
			{
				gprintf( 0, LINE_HEIGHT * 11, L"Chance to get through was %d", gLOSTestResults.ubChanceToGetThrough );
			}
		}
	#endif
	gprintf( 0, LINE_HEIGHT * 13, L"N %d NE %d E %d SE %d",
		gubWorldMovementCosts[ sGridNo ][ NORTH ][ gsInterfaceLevel ],
		gubWorldMovementCosts[ sGridNo ][ NORTHEAST ][ gsInterfaceLevel ],
		gubWorldMovementCosts[ sGridNo ][ EAST ][ gsInterfaceLevel ],
		gubWorldMovementCosts[ sGridNo ][ SOUTHEAST ][ gsInterfaceLevel ] );
	gprintf( 0, LINE_HEIGHT * 14, L"S %d SW %d W %d NW %d",
		gubWorldMovementCosts[ sGridNo ][ SOUTH ][ gsInterfaceLevel ],
		gubWorldMovementCosts[ sGridNo ][ SOUTHWEST ][ gsInterfaceLevel ],
		gubWorldMovementCosts[ sGridNo ][ WEST ][ gsInterfaceLevel ],
		gubWorldMovementCosts[ sGridNo ][ NORTHWEST ][ gsInterfaceLevel ] );
	gprintf( 0, LINE_HEIGHT * 15, L"Ground smell %d strength %d",
		SMELL_TYPE( gpWorldLevelData[ sGridNo ].ubSmellInfo ),
		SMELL_STRENGTH( gpWorldLevelData[ sGridNo ].ubSmellInfo ) );

	#ifdef COUNT_PATHS
	if (guiTotalPathChecks > 0)
	{
		gprintf( 0, LINE_HEIGHT * 16,
			L"Total %ld, %%succ %3ld | %%failed %3ld | %%unsucc %3ld",
			guiTotalPathChecks,
			100 * guiSuccessfulPathChecks / guiTotalPathChecks,
			100 * guiFailedPathChecks / guiTotalPathChecks,
			100 * guiUnsuccessfulPathChecks / guiTotalPathChecks );

	}
	#else
	gprintf( 0, LINE_HEIGHT * 16,
		L"Adj soldiers %d", gpWorldLevelData[sGridNo].ubAdjacentSoldierCnt );
	#endif
}

BOOLEAN AddZStripInfoToVObject( HVOBJECT hVObject, STRUCTURE_FILE_REF * pStructureFileRef, BOOLEAN fFromAnimation, INT16 sSTIStartIndex )
{
	UINT32					uiLoop;
	UINT8 ubLoop2;
	UINT8						ubNumIncreasing = 0;
	UINT8						ubNumStable = 0;
	UINT8						ubNumDecreasing = 0;
	BOOLEAN					fFound = FALSE;
	ZStripInfo *		pCurr;
	INT16						sLeftHalfWidth;
	INT16						sRightHalfWidth;
	INT16						sOffsetX;
	INT16						sOffsetY;
	UINT16					usWidth;
	UINT16					usHeight;
	DB_STRUCTURE_REF *	pDBStructureRef;
	DB_STRUCTURE *	pDBStructure = NULL;
	INT16						sSTIStep = 0;
	INT16						sStructIndex = 0;
	INT16						sNext;
	UINT32					uiDestVoIndex;
	BOOLEAN					fCopyIntoVo;
	BOOLEAN					fFirstTime;


	if (pStructureFileRef->usNumberOfStructuresStored == 0)
	{
		return( TRUE );
	}
	for (uiLoop = 0; uiLoop < pStructureFileRef->usNumberOfStructures; uiLoop++)
	{
		pDBStructureRef = &(pStructureFileRef->pDBStructureRef[uiLoop]);
		pDBStructure = pDBStructureRef->pDBStructure;
		//if (pDBStructure != NULL && pDBStructure->ubNumberOfTiles > 1 && !(pDBStructure->fFlags & STRUCTURE_WALLSTUFF) )
		if (pDBStructure != NULL && pDBStructure->ubNumberOfTiles > 1 )
		{
			for (ubLoop2 = 1; ubLoop2 < pDBStructure->ubNumberOfTiles; ubLoop2++)
			{
				if (pDBStructureRef->ppTile[ubLoop2]->sPosRelToBase != 0)
				{
					// spans multiple tiles! (could be two levels high in one tile)
					fFound = TRUE;
					break;
				}
			}
		}
	}

	// ATE: Make all corpses use z-strip info..
	if ( pDBStructure != NULL && pDBStructure->fFlags & STRUCTURE_CORPSE )
	{
		fFound = TRUE;
	}

	if (!fFound)
	{
		// no multi-tile images in this vobject; that's okay... return!
		return( TRUE );
	}
	hVObject->ppZStripInfo = (ZStripInfo **) MemAlloc( sizeof( ZStripInfo * ) * hVObject->usNumberOfObjects );
	if (hVObject->ppZStripInfo == NULL)
	{
		return( FALSE );
	}
	memset( hVObject->ppZStripInfo, 0, sizeof( ZStripInfo * ) * hVObject->usNumberOfObjects );


	if ( fFromAnimation )
	{
		// Determine step index for STI
		if ( sSTIStartIndex == -1 )
		{
			// one-direction only for this anim structure
			sSTIStep = hVObject->usNumberOfObjects;
			sSTIStartIndex = 0;
		}
		else
		{
			sSTIStep = ( hVObject->usNumberOfObjects / pStructureFileRef->usNumberOfStructures );
		}
	}
	else
	{
		sSTIStep = 1;
	}

	sStructIndex = 0;
	sNext				= sSTIStartIndex + sSTIStep;
	fFirstTime	= TRUE;

	for (uiLoop = (UINT8)sSTIStartIndex; uiLoop < hVObject->usNumberOfObjects; uiLoop++ )
	{
		// Defualt to true
		fCopyIntoVo = TRUE;

		// Increment struct index....
		if ( uiLoop == (UINT32)sNext )
		{
			sNext					= (UINT16)( uiLoop + sSTIStep );
			sStructIndex++;
		}
		else
		{
			if ( fFirstTime )
			{
				fFirstTime = FALSE;
			}
			else
			{
				fCopyIntoVo = FALSE;
			}
		}

		if ( fFromAnimation )
		{
			uiDestVoIndex = sStructIndex;
		}
		else
		{
			uiDestVoIndex = uiLoop;
		}


		if ( fCopyIntoVo && sStructIndex < pStructureFileRef->usNumberOfStructures )
		{
			pDBStructure = pStructureFileRef->pDBStructureRef[ sStructIndex ].pDBStructure;
			if (pDBStructure != NULL && ( pDBStructure->ubNumberOfTiles > 1 || ( pDBStructure->fFlags & STRUCTURE_CORPSE ) ) )
			//if (pDBStructure != NULL && pDBStructure->ubNumberOfTiles > 1 )
			{
				// ATE: We allow SLIDING DOORS of 2 tile sizes...
				if ( !(pDBStructure->fFlags & STRUCTURE_ANYDOOR) || ( (pDBStructure->fFlags & ( STRUCTURE_ANYDOOR ) ) && ( pDBStructure->fFlags & STRUCTURE_SLIDINGDOOR ) ) )
				{
					hVObject->ppZStripInfo[ uiDestVoIndex ] = (ZStripInfo *) MemAlloc( sizeof( ZStripInfo ) );
					if (hVObject->ppZStripInfo[ uiDestVoIndex ] == NULL)
					{
						// augh!! out of memory!	free everything allocated and abort
						for (ubLoop2 = 0; ubLoop2 < uiLoop; ubLoop2++)
						{
							if (hVObject->ppZStripInfo[ubLoop2] != NULL)
							{
								MemFree(hVObject->ppZStripInfo[uiLoop]);
							}
						}
						MemFree( hVObject->ppZStripInfo );
						hVObject->ppZStripInfo = NULL;
						return( FALSE );
					}
					else
					{
						pCurr = hVObject->ppZStripInfo[ uiDestVoIndex ];

						ubNumIncreasing = 0;
						ubNumStable = 0;
						ubNumDecreasing = 0;

						// time to do our calculations!
						sOffsetX = hVObject->pETRLEObject[uiLoop].sOffsetX;
						sOffsetY = hVObject->pETRLEObject[uiLoop].sOffsetY;
						usWidth = hVObject->pETRLEObject[uiLoop].usWidth;
						usHeight = hVObject->pETRLEObject[uiLoop].usHeight;
						if (pDBStructure->fFlags & (STRUCTURE_MOBILE | STRUCTURE_CORPSE) )
						{
							// adjust for the difference between the animation and structure base tile

							//if (pDBStructure->fFlags & (STRUCTURE_MOBILE ) )
							{
								sOffsetX = sOffsetX + (WORLD_TILE_X / 2);
								sOffsetY = sOffsetY + (WORLD_TILE_Y / 2);
							}
							// adjust for the tile offset
							sOffsetX = sOffsetX - pDBStructure->bZTileOffsetX * (WORLD_TILE_X / 2) + pDBStructure->bZTileOffsetY * (WORLD_TILE_X / 2);
							sOffsetY = sOffsetY - pDBStructure->bZTileOffsetY * (WORLD_TILE_Y / 2);
						}

						// figure out how much of the image is on each side of
						// the bottom corner of the base tile
						if (sOffsetX <= 0)
						{
							// note that the adjustments here by (WORLD_TILE_X / 2) are to account for the X difference
							// between the blit position and the bottom corner of the base tile
							sRightHalfWidth = usWidth + sOffsetX - (WORLD_TILE_X / 2);

							if (sRightHalfWidth >= 0)
							{
								// Case 1: negative image offset, image straddles bottom corner

								// negative of a negative is positive
								sLeftHalfWidth = -sOffsetX + (WORLD_TILE_X / 2);
							}
							else
							{
								// Case 2: negative image offset, image all on left side

								// bump up the LeftHalfWidth to the right edge of the last tile-half,
								// so we can calculate the size of the leftmost portion accurately
								// NB subtracting a negative to add the absolute value
								sLeftHalfWidth = usWidth - (sRightHalfWidth % (WORLD_TILE_X / 2));
								sRightHalfWidth = 0;
							}
						}
						else if (sOffsetX < (WORLD_TILE_X / 2))
						{
							sLeftHalfWidth = (WORLD_TILE_X / 2) - sOffsetX;
							sRightHalfWidth = usWidth - sLeftHalfWidth;
							if (sRightHalfWidth <= 0)
							{
								// Case 3: positive offset < 20, image all on left side
								// should never happen because these images are multi-tile!
								sRightHalfWidth = 0;
								// fake the left width to one half-tile
								sLeftHalfWidth = (WORLD_TILE_X / 2);
							}
							else
							{
								// Case 4: positive offset < 20, image straddles bottom corner

								// all okay?
							}
						}
						else
						{
							// Case 5: positive offset, image all on right side
							// should never happen either
							sLeftHalfWidth = 0;
							sRightHalfWidth = usWidth;
						}

						if (sLeftHalfWidth > 0)
						{
							ubNumIncreasing = sLeftHalfWidth / (WORLD_TILE_X / 2);
						}
						if (sRightHalfWidth > 0)
						{
							ubNumStable = 1;
							if (sRightHalfWidth > (WORLD_TILE_X / 2))
							{
								ubNumDecreasing = sRightHalfWidth / (WORLD_TILE_X / 2);
							}
						}
						if (sLeftHalfWidth > 0)
						{
							pCurr->ubFirstZStripWidth = sLeftHalfWidth % (WORLD_TILE_X / 2);
							if (pCurr->ubFirstZStripWidth == 0)
							{
								ubNumIncreasing--;
								pCurr->ubFirstZStripWidth = (WORLD_TILE_X / 2);
							}
						}
						else // right side only; offset is at least 20 (= WORLD_TILE_X / 2)
						{
							if (sOffsetX > WORLD_TILE_X)
							{
								pCurr->ubFirstZStripWidth = (WORLD_TILE_X / 2) - (sOffsetX - WORLD_TILE_X) % (WORLD_TILE_X / 2);
							}
							else
							{
								pCurr->ubFirstZStripWidth = WORLD_TILE_X - sOffsetX;
							}
							if (pCurr->ubFirstZStripWidth == 0)
							{
								ubNumDecreasing--;
								pCurr->ubFirstZStripWidth = (WORLD_TILE_X / 2);
							}

						}

						// now create the array!
						pCurr->ubNumberOfZChanges = ubNumIncreasing + ubNumStable + ubNumDecreasing;
						pCurr->pbZChange = (INT8 *) MemAlloc( pCurr->ubNumberOfZChanges );
						if ( pCurr->pbZChange == NULL)
						{
							// augh!
							for (ubLoop2 = 0; ubLoop2 < uiLoop; ubLoop2++)
							{
								if (hVObject->ppZStripInfo[ubLoop2] != NULL)
								{
									MemFree(hVObject->ppZStripInfo[uiLoop]);
								}
							}
							MemFree( hVObject->ppZStripInfo );
							hVObject->ppZStripInfo = NULL;
							return( FALSE );
						}
						for (ubLoop2 = 0; ubLoop2 < ubNumIncreasing; ubLoop2++)
						{
							pCurr->pbZChange[ubLoop2] = 1;
						}
						for (; ubLoop2 < ubNumIncreasing + ubNumStable; ubLoop2++)
						{
							pCurr->pbZChange[ubLoop2] = 0;
						}
						for (; ubLoop2 < pCurr->ubNumberOfZChanges; ubLoop2++)
						{
							pCurr->pbZChange[ubLoop2] = -1;
						}
						if (ubNumIncreasing > 0)
						{
							pCurr->bInitialZChange = -(ubNumIncreasing);
						}
						else if (ubNumStable > 0)
						{
							pCurr->bInitialZChange = 0;
						}
						else
						{
							pCurr->bInitialZChange = -(ubNumDecreasing);
						}
					}
				}
			}
		}
	}
	return( TRUE );
}

BOOLEAN InitStructureDB( void )
{
	gusNextAvailableStructureID = FIRST_AVAILABLE_STRUCTURE_ID;
	return( TRUE );
}

BOOLEAN FiniStructureDB( void )
{
	gusNextAvailableStructureID = FIRST_AVAILABLE_STRUCTURE_ID;
	return( TRUE );
}


INT8 GetBlockingStructureInfo( INT32 sGridNo, INT8 bDir, INT8 bNextDir, INT8 bLevel, INT8 *pStructHeight, STRUCTURE ** ppTallestStructure, BOOLEAN fWallsBlock )
{
	STRUCTURE * pCurrent, *pStructure = 0;
	INT16				sDesiredLevel;
	BOOLEAN			fOKStructOnLevel = FALSE;
	BOOLEAN			fMinimumBlockingFound = FALSE;

	if ( bLevel == 0)
	{
		sDesiredLevel = STRUCTURE_ON_GROUND;
	}
	else
	{
		sDesiredLevel = STRUCTURE_ON_ROOF;
	}

	pCurrent =	gpWorldLevelData[sGridNo].pStructureHead;

	// If no struct, return
	if ( pCurrent == NULL )
	{
	(*pStructHeight) = StructureHeight( pCurrent );
		(*ppTallestStructure) = NULL;
		return( NOTHING_BLOCKING );
	}

	while (pCurrent != NULL)
	{
		// Check level!
		if (pCurrent->sCubeOffset == sDesiredLevel )
		{
			fOKStructOnLevel = TRUE;
			pStructure		= pCurrent;

			// Turn off if we are on upper level!
			if ( pCurrent->fFlags & STRUCTURE_ROOF && bLevel == 1 )
			{
				fOKStructOnLevel = FALSE;
			}

			// Don't stop FOV for people
			if ( pCurrent->fFlags & ( STRUCTURE_CORPSE | STRUCTURE_PERSON ) )
			{
				fOKStructOnLevel = FALSE;
			}


			if ( pCurrent->fFlags & ( STRUCTURE_TREE | STRUCTURE_ANYFENCE ) )
			{
				fMinimumBlockingFound = TRUE;
			}

			// Default, if we are a wall, set full blocking
			if ( ( pCurrent->fFlags & STRUCTURE_WALL ) && !fWallsBlock )
			{
				// Return full blocking!
				// OK! This will be handled by movement costs......!
				fOKStructOnLevel = FALSE;
			}

			// CHECK FOR WINDOW
			if ( pCurrent->fFlags & STRUCTURE_WALLNWINDOW )
			{
				switch( pCurrent->ubWallOrientation )
				{
					case OUTSIDE_TOP_LEFT:
					case INSIDE_TOP_LEFT:

			(*pStructHeight) = StructureHeight( pCurrent );
						(*ppTallestStructure) = pCurrent;

			if ( pCurrent->fFlags & STRUCTURE_OPEN )
			{
						return( BLOCKING_TOPLEFT_OPEN_WINDOW );
			}
			else
			{
						return( BLOCKING_TOPLEFT_WINDOW );
			}
						break;

					case OUTSIDE_TOP_RIGHT:
					case INSIDE_TOP_RIGHT:

			(*pStructHeight) = StructureHeight( pCurrent );
						(*ppTallestStructure) = pCurrent;

			if ( pCurrent->fFlags & STRUCTURE_OPEN )
			{
						return( BLOCKING_TOPRIGHT_OPEN_WINDOW );
			}
			else
			{
						return( BLOCKING_TOPRIGHT_WINDOW );
			}
						break;
				}
			}

			// Check for door
			if ( pCurrent->fFlags & STRUCTURE_ANYDOOR )
			{
				// If we are not opem, we are full blocking!
				if ( !(pCurrent->fFlags & STRUCTURE_OPEN ) )
				{
			(*pStructHeight) = StructureHeight( pCurrent );
					(*ppTallestStructure) = pCurrent;
					return( FULL_BLOCKING );
					break;
				}
				else
				{
					switch( pCurrent->ubWallOrientation )
					{
						case OUTSIDE_TOP_LEFT:
						case INSIDE_TOP_LEFT:

				(*pStructHeight) = StructureHeight( pCurrent );
							(*ppTallestStructure) = pCurrent;
							return( BLOCKING_TOPLEFT_DOOR );
							break;

						case OUTSIDE_TOP_RIGHT:
						case INSIDE_TOP_RIGHT:

				(*pStructHeight) = StructureHeight( pCurrent );
							(*ppTallestStructure) = pCurrent;
							return( BLOCKING_TOPRIGHT_DOOR );
							break;
					}
				}
			}
		}
		pCurrent = pCurrent->pNext;
	}

	// OK, here, we default to we've seen a struct, reveal just this one
	if ( fOKStructOnLevel )
	{
		if ( fMinimumBlockingFound )
		{
			(*pStructHeight) = StructureHeight( pStructure );
			(*ppTallestStructure) = pStructure;
			return( BLOCKING_REDUCE_RANGE );
		}
		else
		{
			(*pStructHeight) = StructureHeight( pStructure );
			(*ppTallestStructure) = pStructure;
			return( BLOCKING_NEXT_TILE );
		}
	}
	else
	{
		(*pStructHeight) = 0;
		(*ppTallestStructure) = NULL;
		return( NOTHING_BLOCKING );
	}
}




UINT8 StructureFlagToType( UINT32 uiFlag )
{
	UINT8 ubLoop;
	UINT32		uiBit = STRUCTURE_GENERIC;

	for ( ubLoop = 8; ubLoop < 32; ubLoop++ )
	{
		if ( (uiFlag & uiBit) != 0 )
		{
			return( ubLoop );
		}
		uiBit = uiBit << 1;
	}
	return( 0 );
}

UINT32 StructureTypeToFlag( UINT8 ubType )
{
	UINT32		uiFlag = 0x1;

	uiFlag = uiFlag << ubType;
	return( uiFlag );
}

STRUCTURE * FindStructureBySavedInfo( INT32 sGridNo, UINT8 ubType, UINT8 ubWallOrientation, INT8 bLevel )
{
	STRUCTURE *	pCurrent;
	UINT32		uiTypeFlag;

	uiTypeFlag = StructureTypeToFlag( ubType );

	pCurrent =	gpWorldLevelData[sGridNo].pStructureHead;
	while (pCurrent != NULL)
	{
		if (pCurrent->fFlags & uiTypeFlag && pCurrent->ubWallOrientation == ubWallOrientation &&
			( (bLevel == 0 && pCurrent->sCubeOffset == 0) || (bLevel > 0 && pCurrent->sCubeOffset > 0) ) )
		{
			return( pCurrent );
		}
		pCurrent = pCurrent->pNext;
	}
	return( NULL );
}


UINT32 GetStructureOpenSound( STRUCTURE * pStructure, BOOLEAN fClose )
{
	UINT32 uiSoundID;

	switch( pStructure->pDBStructureRef->pDBStructure->ubArmour )
	{
	case MATERIAL_LIGHT_METAL:
	case MATERIAL_THICKER_METAL:

		uiSoundID = OPEN_LOCKER;
		break;

	case MATERIAL_WOOD_WALL:
	case MATERIAL_PLYWOOD_WALL:
	case MATERIAL_FURNITURE:

		uiSoundID = OPEN_WOODEN_BOX;
		break;

	default:
		uiSoundID = OPEN_DEFAULT_OPENABLE;
	}

	if ( fClose )
	{
	uiSoundID++;
	}

	return( uiSoundID );
}
