1 #include "Buffer.h"
2 #include "HImage.h"
3 #include "LoadSaveData.h"
4 #include "Soldier_Control.h"
5 #include "Types.h"
6 #include "VObject.h"
7 #include "WCheck.h"
8 #include "Debug.h"
9 #include "FileMan.h"
10 #include "MemMan.h"
11 #include "Structure.h"
12 #include "TileDef.h"
13 #include "WorldDef.h"
14 #include "WorldMan.h"
15 #include "Interface.h"
16 #include "Isometric_Utils.h"
17 #include "Font.h"
18 #include "Font_Control.h"
19 #include "Debug_Pages.h"
20 #include "LOS.h"
21 #include "Smell.h"
22 #include "SaveLoadMap.h"
23 #include "StrategicMap.h"
24 #include "Sys_Globals.h" //for access to gfEditMode flag
25 //Kris:
26 #include "Editor_Undo.h" //for access to AddToUndoList( iMapIndex )
27 
28 #include "Explosion_Control.h"
29 #include "Buildings.h"
30 #include "Random.h"
31 #include "Tile_Animation.h"
32 #include "GameState.h"
33 
34 #include "ContentManager.h"
35 #include "GameInstance.h"
36 
37 #include <climits>
38 #include <string_theory/format>
39 #include <string_theory/string>
40 
41 #include <stdexcept>
42 
43 
44 #ifdef COUNT_PATHS
45 	extern UINT32 guiSuccessfulPathChecks;
46 	extern UINT32 guiTotalPathChecks;
47 	extern UINT32 guiFailedPathChecks;
48 	extern UINT32 guiUnsuccessfulPathChecks;
49 #endif
50 
51 /*
52  * NB:  STRUCTURE_SPECIAL
53  *
54  * Means different things depending on the context.
55  *
56  * WALLNWINDOW SPECIAL - opaque to sight
57  * MULTI SPECIAL - second level (damaged) MULTI structure, should only be deleted if
58  *    starting with the deletion of a MULTI SPECIAL structure
59  */
60 
61 UINT8 AtHeight[PROFILE_Z_SIZE] = { 0x01, 0x02, 0x04, 0x08 };
62 
63 #define FIRST_AVAILABLE_STRUCTURE_ID (INVALID_STRUCTURE_ID + 2)
64 
65 static UINT16 gusNextAvailableStructureID = FIRST_AVAILABLE_STRUCTURE_ID;
66 
67 static STRUCTURE_FILE_REF* gpStructureFileRefs;
68 
69 
70 static SoundID const guiMaterialHitSound[NUM_MATERIAL_TYPES] =
71 {
72 	NO_SOUND,
73 	S_WOOD_IMPACT1,
74 	S_WOOD_IMPACT2,
75 	S_WOOD_IMPACT3,
76 	S_VEG_IMPACT1,
77 	NO_SOUND,
78 	S_PORCELAIN_IMPACT1,
79 	NO_SOUND,
80 	NO_SOUND,
81 	NO_SOUND,
82 
83 	NO_SOUND,
84 	S_STONE_IMPACT1,
85 	S_STONE_IMPACT1,
86 	S_STONE_IMPACT1,
87 	S_STONE_IMPACT1,
88 	S_RUBBER_IMPACT1,
89 	NO_SOUND,
90 	NO_SOUND,
91 	NO_SOUND,
92 	NO_SOUND,
93 
94 	NO_SOUND,
95 	S_METAL_IMPACT1,
96 	S_METAL_IMPACT2,
97 	S_METAL_IMPACT3,
98 	S_STONE_IMPACT1,
99 	S_METAL_IMPACT3,
100 };
101 
102 
103 /*
104 index  1-10, organics
105 index 11-20, rocks and concretes
106 index 21-30, metals
107 
108 index 1, dry timber
109 index 2, furniture wood
110 index 3, tree wood
111 index 11, stone masonry
112 index 12, non-reinforced concrete
113 index 13, reinforced concrete
114 index 14, rock
115 index 21, light metal (furniture)
116 index 22, heavy metal (doors etc)
117 index 23, really heavy metal
118 index 24, indestructable stone
119 index 25, indestructable metal
120 */
121 UINT8 const gubMaterialArmour[] =
122 { // note: must increase; r.c. should block *AP* 7.62mm rounds
123 	0, // nothing
124 	25, // dry timber; wood wall +1/2
125 	20, // furniture wood (thin!) or plywood wall +1/2
126 	30, // wood (live); 1.5x timber
127 	3, // light vegetation
128 	10, // upholstered furniture
129 	47, // porcelain
130 	10, // cactus, hay, bamboo
131 	0,
132 	0,
133 	0,
134 	55, // stone masonry; 3x timber
135 	63, // non-reinforced concrete; 4x timber???
136 	70, // reinforced concrete; 6x timber
137 	85, // rock? - number invented
138 	9, // rubber - tires
139 	40, // sand
140 	1, // cloth
141 	40, // sandbag
142 	0,
143 	0,
144 	37, // light metal (furniture; NB thin!)
145 	57, // thicker metal (dumpster)
146 	85, // heavy metal (vault doors) - block everything
147 	// note that vehicle armour will probably end up in here
148 	127, // rock indestructable
149 	127, // indestructable
150 	57, // like 22 but with screen windows
151 };
152 
153 
154 // Function operating on a structure tile
FilledTilePositions(DB_STRUCTURE_TILE const * const t)155 static UINT8 FilledTilePositions(DB_STRUCTURE_TILE const* const t)
156 {
157 	// Loop through all parts of a structure and add up the number of filled spots
158 	UINT8 filled = 0;
159 	for (INT8 x = 0; x != PROFILE_X_SIZE; ++x)
160 	{
161 		for (INT8 y = 0; y != PROFILE_Y_SIZE; ++y)
162 		{
163 			UINT8 const shape_value = t->Shape[x][y];
164 			for (INT8 z = 0; z != PROFILE_Z_SIZE; ++z)
165 			{
166 				if (shape_value & AtHeight[z]) ++filled;
167 			}
168 		}
169 	}
170 	return filled;
171 }
172 
173 //
174 // Structure database functions
175 //
176 namespace
177 {
178 	/* Free all of the memory associated with a file reference, including the file
179 	 * reference structure itself */
FreeStructureFileRef(STRUCTURE_FILE_REF * const f)180 	void FreeStructureFileRef(STRUCTURE_FILE_REF* const f)
181 	{
182 		if (DB_STRUCTURE_REF* const sr = f->pDBStructureRef)
183 		{
184 			DB_STRUCTURE_REF const* const end = sr + f->usNumberOfStructures;
185 			for (DB_STRUCTURE_REF* i = sr; i != end; ++i)
186 			{
187 				if (i->ppTile) delete[] i->ppTile;
188 			}
189 			delete[] sr;
190 		}
191 		if (f->pubStructureData) delete[] f->pubStructureData;
192 		if (f->pAuxData)
193 		{
194 			delete[] f->pAuxData;
195 			if (f->pTileLocData) delete[] f->pTileLocData;
196 		}
197 		delete f;
198 	}
199 }
200 
201 
FreeAllStructureFiles()202 void FreeAllStructureFiles()
203 { // Free all of the structure database
204 	STRUCTURE_FILE_REF* next;
205 	for (STRUCTURE_FILE_REF* i = gpStructureFileRefs; i; i = next)
206 	{
207 		next = i->pNext;
208 		FreeStructureFileRef(i);
209 	}
210 }
211 
212 
FreeStructureFile(STRUCTURE_FILE_REF * const sfr)213 void FreeStructureFile(STRUCTURE_FILE_REF* const sfr)
214 {
215 	CHECKV(sfr);
216 
217 	STRUCTURE_FILE_REF* const next = sfr->pNext;
218 	STRUCTURE_FILE_REF* const prev = sfr->pPrev;
219 	Assert((prev == NULL) == (gpStructureFileRefs == sfr));
220 	*(prev != NULL ? &prev->pNext : &gpStructureFileRefs) = next;
221 	if (next) next->pPrev = prev;
222 
223 	FreeStructureFileRef(sfr);
224 }
225 
226 
227 /* IMPORTANT THING TO REMEMBER
228  * Although the number of structures and images about which information may be
229  * stored in a file, the two are stored very differently.
230  * The structure data stored amounts to a sparse array, with no data saved for
231  * any structures that are not defined.
232  * For image information, however, an array is stored with every entry filled
233  * regardless of whether there is non-zero data defined for that graphic! */
234 
235 // "J2SD" = Jagged 2 Structure Data
236 #define STRUCTURE_FILE_ID "J2SD"
237 #define STRUCTURE_FILE_ID_LEN 4
238 
239 
240 // Loads a structure file's data as a honking chunk o' memory
LoadStructureData(char const * const filename,STRUCTURE_FILE_REF * const sfr,UINT32 * const structure_data_size)241 static void LoadStructureData(char const* const filename, STRUCTURE_FILE_REF* const sfr, UINT32* const structure_data_size)
242 {
243 	AutoSGPFile f(GCM->openGameResForReading(filename));
244 
245 	BYTE data[16];
246 	FileRead(f, data, sizeof(data));
247 
248 	char   id[4];
249 	UINT16 n_structures;
250 	UINT16 n_structures_stored;
251 	UINT16 data_size;
252 	UINT8  flags;
253 	UINT16 n_tile_locs_stored;
254 
255 	DataReader d{data};
256 	EXTR_STR(d, id, lengthof(id))
257 	EXTR_U16(d, n_structures);
258 	EXTR_U16( d, n_structures_stored)
259 	EXTR_U16( d, data_size)
260 	EXTR_U8(  d, flags)
261 	EXTR_SKIP(d, 3)
262 	EXTR_U16( d, n_tile_locs_stored)
263 	Assert(d.getConsumed() == lengthof(data));
264 
265 	if (strncmp(id, STRUCTURE_FILE_ID, STRUCTURE_FILE_ID_LEN) != 0 ||
266 			n_structures == 0)
267 	{
268 		throw std::runtime_error("Failed to load structure file, because header is invalid");
269 	}
270 
271 	SGP::Buffer<AuxObjectData> aux_data;
272 	SGP::Buffer<RelTileLoc>    tile_loc_data;
273 	sfr->usNumberOfStructures = n_structures;
274 	if (flags & STRUCTURE_FILE_CONTAINS_AUXIMAGEDATA)
275 	{
276 		aux_data.Allocate(n_structures);
277 		FileRead(f, aux_data, sizeof(*aux_data) * n_structures);
278 
279 		if (n_tile_locs_stored > 0)
280 		{
281 			tile_loc_data.Allocate(n_tile_locs_stored);
282 			FileRead(f, tile_loc_data, sizeof(*tile_loc_data) * n_tile_locs_stored);
283 		}
284 	}
285 
286 	SGP::Buffer<UINT8> structure_data;
287 	if (flags & STRUCTURE_FILE_CONTAINS_STRUCTUREDATA)
288 	{
289 		sfr->usNumberOfStructuresStored = n_structures_stored;
290 		structure_data.Allocate(data_size);
291 		FileRead(f, structure_data, data_size);
292 
293 		*structure_data_size = data_size;
294 	}
295 
296 	sfr->pAuxData         = aux_data.Release();
297 	sfr->pTileLocData     = tile_loc_data.Release();
298 	sfr->pubStructureData = structure_data.Release();
299 }
300 
NormalizeStructureTiles(DB_STRUCTURE_TILE ** pTiles,UINT8 ubNumTiles)301 void NormalizeStructureTiles(DB_STRUCTURE_TILE** pTiles, UINT8 ubNumTiles)
302 {
303 	/**
304 	 * In #1107, it was discovered that some of the Copter structures do not
305 	 * have a base tile. RemoveStruct will not work without a base tile, as
306 	 * it tries to find a non-existent base structure.
307 	 *
308 	 * This function attempts to correct such issues with JSD data, by
309 	 * ensuring there is a tile with (0, 0) position relative to base.
310 	 */
311 	int minDistFromBase = INT_MAX;
312 	for (UINT8 i = 0; i < ubNumTiles; i++)
313 	{
314 		DB_STRUCTURE_TILE* tile = pTiles[i];
315 		if (abs(minDistFromBase) > abs(tile->sPosRelToBase))
316 		{
317 			minDistFromBase = tile->sPosRelToBase;
318 		}
319 	}
320 
321 	if (minDistFromBase == 0)
322 	{
323 		// Data is fine. Nothing to do.
324 		return;
325 	}
326 
327 	STLOGD("Adjusting tiles relative positions by {}", -minDistFromBase);
328 	int xDist = minDistFromBase % WORLD_COLS;
329 	int yDist = minDistFromBase / WORLD_COLS;
330 	for (UINT8 i = 0; i < ubNumTiles; i++)
331 	{
332 		DB_STRUCTURE_TILE* tile = pTiles[i];
333 		tile->sPosRelToBase -= minDistFromBase;
334 		tile->bXPosRelToBase -= xDist;
335 		tile->bYPosRelToBase -= yDist;
336 	}
337 }
338 
CreateFileStructureArrays(STRUCTURE_FILE_REF * const pFileRef,UINT32 uiDataSize)339 static void CreateFileStructureArrays(STRUCTURE_FILE_REF* const pFileRef, UINT32 uiDataSize)
340 { /* Based on a file chunk, creates all the dynamic arrays for the structure
341 	 * definitions contained within */
342 	UINT8*                  pCurrent        = pFileRef->pubStructureData;
343 	DB_STRUCTURE_REF* const pDBStructureRef = new DB_STRUCTURE_REF[pFileRef->usNumberOfStructures]{};
344 	pFileRef->pDBStructureRef = pDBStructureRef;
345 	for (UINT16 usLoop = 0; usLoop < pFileRef->usNumberOfStructuresStored; ++usLoop)
346 	{
347 		if (uiDataSize < sizeof(DB_STRUCTURE))
348 		{	// gone past end of file block?!
349 			// freeing of memory will occur outside of the function
350 			throw std::runtime_error("Failed to create structure arrays, because input data is too short");
351 		}
352 		DB_STRUCTURE* const dbs = (DB_STRUCTURE*)pCurrent;
353 		pCurrent   += sizeof(DB_STRUCTURE);
354 		uiDataSize -= sizeof(DB_STRUCTURE);
355 
356 		DB_STRUCTURE_TILE** const tiles       = new DB_STRUCTURE_TILE*[dbs->ubNumberOfTiles]{};
357 		UINT16              const usIndex     = dbs->usStructureNumber;
358 		pDBStructureRef[usIndex].pDBStructure = dbs;
359 		pDBStructureRef[usIndex].ppTile       = tiles;
360 
361 		// Set things up to calculate hit points
362 		UINT32 uiHitPoints = 0;
363 		for (UINT16 usTileLoop = 0; usTileLoop < dbs->ubNumberOfTiles; ++usTileLoop)
364 		{
365 			if (uiDataSize < sizeof(DB_STRUCTURE_TILE))
366 			{	// gone past end of file block?!
367 				// freeing of memory will occur outside of the function
368 				throw std::runtime_error("Failed to create structure arrays, because input data is too short");
369 			}
370 			DB_STRUCTURE_TILE* const tile = (DB_STRUCTURE_TILE*)pCurrent;
371 			pCurrent   += sizeof(DB_STRUCTURE_TILE);
372 			uiDataSize -= sizeof(DB_STRUCTURE_TILE);
373 
374 			tiles[usTileLoop] = tile;
375 			// set the single-value relative position between this tile and the base tile
376 			tile->sPosRelToBase = tile->bXPosRelToBase + tile->bYPosRelToBase * WORLD_COLS;
377 			uiHitPoints += FilledTilePositions(tile);
378 		}
379 
380 		NormalizeStructureTiles(tiles, dbs->ubNumberOfTiles);
381 
382 		// scale hit points down to something reasonable...
383 		uiHitPoints = uiHitPoints * 100 / 255;
384 		dbs->ubHitPoints = (UINT8)uiHitPoints;
385 	}
386 }
387 
388 
LoadStructureFile(char const * const filename)389 STRUCTURE_FILE_REF* LoadStructureFile(char const* const filename)
390 { // NB should be passed in expected number of structures so we can check equality
391 	SGP::AutoObj<STRUCTURE_FILE_REF, FreeStructureFileRef> sfr(new STRUCTURE_FILE_REF{});
392 	UINT32 data_size = 0;
393 	LoadStructureData(filename, sfr, &data_size);
394 	if (sfr->pubStructureData) CreateFileStructureArrays(sfr, data_size);
395 	// Add the file reference to the master list, at the head for convenience
396 	if (gpStructureFileRefs) gpStructureFileRefs->pPrev = sfr;
397 	sfr->pNext = gpStructureFileRefs;
398 	gpStructureFileRefs = sfr;
399 	return sfr.Release();
400 }
401 
402 
403 //
404 // Structure creation functions
405 //
406 
407 
CreateStructureFromDB(DB_STRUCTURE_REF const * const pDBStructureRef,UINT8 const ubTileNum)408 static STRUCTURE* CreateStructureFromDB(DB_STRUCTURE_REF const* const pDBStructureRef, UINT8 const ubTileNum)
409 { // Creates a STRUCTURE struct for one tile of a structure
410 	DB_STRUCTURE const* const pDBStructure = pDBStructureRef->pDBStructure;
411 	DB_STRUCTURE_TILE*  const pTile        = pDBStructureRef->ppTile[ubTileNum];
412 
413 	STRUCTURE* const pStructure = new STRUCTURE{};
414 
415 	pStructure->fFlags          = pDBStructure->fFlags;
416 	pStructure->pShape          = &pTile->Shape;
417 	pStructure->pDBStructureRef = pDBStructureRef;
418 	if (pTile->sPosRelToBase != 0 && ubTileNum == 0)
419 	{
420 		STLOGW("Possible bad structure {}", pDBStructureRef->pDBStructure->usStructureNumber);
421 	}
422 	if (pTile->sPosRelToBase == 0)
423 	{	// base tile
424 		pStructure->fFlags      |= STRUCTURE_BASE_TILE;
425 		pStructure->ubHitPoints  = pDBStructure->ubHitPoints;
426 	}
427 	if (pDBStructure->ubWallOrientation != NO_ORIENTATION)
428 	{
429 		/* for multi-tile walls, which are only the special corner pieces, the
430 		 * non-base tile gets no orientation value because this copy will be
431 		 * skipped */
432 		if (!(pStructure->fFlags & STRUCTURE_WALL) ||
433 				pStructure->fFlags & STRUCTURE_BASE_TILE)
434 		{
435 			pStructure->ubWallOrientation = pDBStructure->ubWallOrientation;
436 		}
437 	}
438 	pStructure->ubVehicleHitLocation = pTile->ubVehicleHitLocation;
439 	return pStructure;
440 }
441 
442 
OkayToAddStructureToTile(INT16 const sBaseGridNo,INT16 const sCubeOffset,DB_STRUCTURE_REF const * const pDBStructureRef,UINT8 ubTileIndex,INT16 const sExclusionID,BOOLEAN const fIgnorePeople)443 static BOOLEAN OkayToAddStructureToTile(INT16 const sBaseGridNo, INT16 const sCubeOffset, DB_STRUCTURE_REF const* const pDBStructureRef, UINT8 ubTileIndex, INT16 const sExclusionID, BOOLEAN const fIgnorePeople)
444 { // Verifies whether a structure is blocked from being added to the map at a particular point
445 	DB_STRUCTURE_TILE const* const* const ppTile = pDBStructureRef->ppTile;
446 	INT16 const sGridNo = sBaseGridNo + ppTile[ubTileIndex]->sPosRelToBase;
447 	if (sGridNo < 0 || WORLD_MAX < sGridNo) return FALSE;
448 
449 	if (gpWorldLevelData[sBaseGridNo].sHeight != gpWorldLevelData[sGridNo].sHeight)
450 	{
451 		// uneven terrain, one portion on top of cliff and another not! can't add!
452 		return FALSE;
453 	}
454 
455 	DB_STRUCTURE const* const pDBStructure = pDBStructureRef->pDBStructure;
456 	for (STRUCTURE const* pExistingStructure = gpWorldLevelData[sGridNo].pStructureHead; pExistingStructure != NULL; pExistingStructure = pExistingStructure->pNext)
457 	{
458 		if (sCubeOffset != pExistingStructure->sCubeOffset) continue;
459 
460 		// CJC:
461 		// If adding a mobile structure, allow addition if existing structure is passable
462 		if (pDBStructure->fFlags & STRUCTURE_MOBILE && pExistingStructure->fFlags & STRUCTURE_PASSABLE)
463 		{
464 			continue;
465 		}
466 
467 		if (pDBStructure->fFlags & STRUCTURE_OBSTACLE)
468 		{
469 			// CJC: NB these next two if states are probably COMPLETELY OBSOLETE but I'm leaving
470 			// them in there for now (no harm done)
471 
472 			// ATE:
473 			// ignore this one if it has the same ID num as exclusion
474 			if (sExclusionID != INVALID_STRUCTURE_ID &&
475 					sExclusionID == pExistingStructure->usStructureID)
476 			{
477 				continue;
478 			}
479 
480 			// If we are a person, skip!
481 			if (fIgnorePeople && pExistingStructure->usStructureID < TOTAL_SOLDIERS)
482 			{
483 				continue;
484 			}
485 
486 			// two obstacle structures aren't allowed in the same tile at the same height
487 			// ATE: There is more sophisticated logic for mobiles, so postpone this check if mobile....
488 			if (pExistingStructure->fFlags & STRUCTURE_OBSTACLE && !(pDBStructure->fFlags & STRUCTURE_MOBILE))
489 			{
490 				if (pExistingStructure->fFlags & STRUCTURE_PASSABLE && !(pExistingStructure->fFlags & STRUCTURE_MOBILE))
491 				{
492 					// no mobiles, existing structure is passable
493 				}
494 				else
495 				{
496 					return FALSE;
497 				}
498 			}
499 			else if (pDBStructure->ubNumberOfTiles > 1 && pExistingStructure->fFlags & STRUCTURE_WALLSTUFF)
500 			{
501 				// if not an open door...
502 				if (!(pExistingStructure->fFlags & STRUCTURE_ANYDOOR) ||
503 						!(pExistingStructure->fFlags & STRUCTURE_OPEN))
504 				{
505 					// we could be trying to place a multi-tile obstacle on top of a wall; we shouldn't
506 					// allow this if the structure is going to be on both sides of the wall
507 					for (INT8 bLoop = 1; bLoop < 4; ++bLoop)
508 					{
509 						INT16 sOtherGridNo;
510 						switch (pExistingStructure->ubWallOrientation)
511 						{
512 							case OUTSIDE_TOP_LEFT:
513 							case INSIDE_TOP_LEFT:
514 								sOtherGridNo = NewGridNo(sGridNo, DirectionInc( bLoop + 2 ));
515 								break;
516 
517 							case OUTSIDE_TOP_RIGHT:
518 							case INSIDE_TOP_RIGHT:
519 								sOtherGridNo = NewGridNo(sGridNo, DirectionInc(bLoop));
520 								break;
521 
522 							default:
523 								// @%?@#%?@%
524 								sOtherGridNo = NewGridNo(sGridNo, DirectionInc(SOUTHEAST));
525 								break;
526 						}
527 						for (INT8 bLoop2 = 0; bLoop2 < pDBStructure->ubNumberOfTiles; ++bLoop2)
528 						{
529 							if (sBaseGridNo + ppTile[bLoop2]->sPosRelToBase != sOtherGridNo) continue;
530 
531 							// obstacle will straddle wall!
532 							return FALSE;
533 						}
534 					}
535 				}
536 			}
537 		}
538 		else if (pDBStructure->fFlags & STRUCTURE_WALLSTUFF)
539 		{
540 			// two walls with the same alignment aren't allowed in the same tile
541 			if (pExistingStructure->fFlags & STRUCTURE_WALLSTUFF &&
542 					pExistingStructure->ubWallOrientation == pDBStructure->ubWallOrientation)
543 			{
544 				return FALSE;
545 			}
546 			else if (!(pExistingStructure->fFlags & (STRUCTURE_CORPSE | STRUCTURE_PERSON)))
547 			{
548 				// it's possible we're trying to insert this wall on top of a multitile obstacle
549 				for (INT8 bLoop = 1; bLoop < 4; ++bLoop)
550 				{
551 					INT16 sOtherGridNo;
552 					switch (pDBStructure->ubWallOrientation)
553 					{
554 						case OUTSIDE_TOP_LEFT:
555 						case INSIDE_TOP_LEFT:
556 							sOtherGridNo = NewGridNo(sGridNo, DirectionInc( bLoop + 2 ));
557 							break;
558 
559 						case OUTSIDE_TOP_RIGHT:
560 						case INSIDE_TOP_RIGHT:
561 							sOtherGridNo = NewGridNo(sGridNo, DirectionInc(bLoop));
562 							break;
563 
564 						default:
565 							// @%?@#%?@%
566 							sOtherGridNo = NewGridNo(sGridNo, DirectionInc(SOUTHEAST));
567 							break;
568 					}
569 					for (ubTileIndex = 0; ubTileIndex < pDBStructure->ubNumberOfTiles; ++ubTileIndex)
570 					{
571 						STRUCTURE const* const pOtherExistingStructure = FindStructureByID(sOtherGridNo, pExistingStructure->usStructureID);
572 						if (pOtherExistingStructure) return FALSE;
573 					}
574 				}
575 			}
576 		}
577 
578 		if (pDBStructure->fFlags & STRUCTURE_MOBILE)
579 		{
580 			// ATE:
581 			// ignore this one if it has the same ID num as exclusion
582 			if (sExclusionID != INVALID_STRUCTURE_ID)
583 			{
584 				if (pExistingStructure->usStructureID == sExclusionID) continue;
585 			}
586 
587 			// If we are a person, skip!
588 			if (fIgnorePeople && pExistingStructure->usStructureID < TOTAL_SOLDIERS)
589 			{
590 				continue;
591 			}
592 
593 			// ATE: Added check here - UNLESS the part we are trying to add is PASSABLE!
594 			if (pExistingStructure->fFlags & STRUCTURE_MOBILE &&
595 					!(pExistingStructure->fFlags & STRUCTURE_PASSABLE) &&
596 					!(ppTile[ubTileIndex]->fFlags & TILE_PASSABLE))
597 			{
598 				// don't allow 2 people in the same tile
599 				return FALSE;
600 			}
601 
602 			// ATE: Another rule: allow PASSABLE *IF* the PASSABLE is *NOT* MOBILE!
603 			if (!(pExistingStructure->fFlags & STRUCTURE_MOBILE) &&
604 					pExistingStructure->fFlags & STRUCTURE_PASSABLE)
605 			{
606 				continue;
607 			}
608 
609 			// ATE: Added here - UNLESS this part is PASSABLE....
610 			// two obstacle structures aren't allowed in the same tile at the same height
611 			if (pExistingStructure->fFlags & STRUCTURE_OBSTACLE &&
612 					!(ppTile[ubTileIndex]->fFlags & TILE_PASSABLE))
613 			{
614 				return FALSE;
615 			}
616 		}
617 
618 		if (pDBStructure->fFlags & STRUCTURE_OPENABLE &&
619 				pExistingStructure->fFlags & STRUCTURE_OPENABLE)
620 		{
621 			/* Don't allow two openable structures in the same tile or things will
622 			 * screw up on an interface level */
623 			return FALSE;
624 		}
625 	}
626 
627 	return TRUE;
628 }
629 
630 
InternalOkayToAddStructureToWorld(INT16 const sBaseGridNo,INT8 const bLevel,DB_STRUCTURE_REF const * const pDBStructureRef,INT16 const sExclusionID,BOOLEAN const fIgnorePeople)631 BOOLEAN InternalOkayToAddStructureToWorld(INT16 const sBaseGridNo, INT8 const bLevel, DB_STRUCTURE_REF const* const pDBStructureRef, INT16 const sExclusionID, BOOLEAN const fIgnorePeople)
632 {
633 	CHECKF(pDBStructureRef);
634 	CHECKF(pDBStructureRef->pDBStructure);
635 	UINT8 const n_tiles = pDBStructureRef->pDBStructure->ubNumberOfTiles;
636 	CHECKF(n_tiles > 0);
637 	DB_STRUCTURE_TILE const* const* const tiles = pDBStructureRef->ppTile;
638 	CHECKF(tiles);
639 
640 	for (UINT8 i = 0; i < n_tiles; ++i)
641 	{
642 		INT16 cube_offset;
643 		if (!(tiles[i]->fFlags & TILE_ON_ROOF))
644 		{
645 			cube_offset = bLevel * PROFILE_Z_SIZE;
646 		}
647 		else if (bLevel == 0)
648 		{
649 			cube_offset = PROFILE_Z_SIZE;
650 		}
651 		else
652 		{
653 			return FALSE;
654 		}
655 
656 		if (!OkayToAddStructureToTile(sBaseGridNo, cube_offset, pDBStructureRef, i, sExclusionID, fIgnorePeople))
657 		{
658 			return FALSE;
659 		}
660 	}
661 	return TRUE;
662 }
663 
664 
OkayToAddStructureToWorld(const INT16 sBaseGridNo,const INT8 bLevel,const DB_STRUCTURE_REF * const pDBStructureRef,const INT16 sExclusionID)665 BOOLEAN OkayToAddStructureToWorld(const INT16 sBaseGridNo, const INT8 bLevel, const DB_STRUCTURE_REF* const pDBStructureRef, const INT16 sExclusionID)
666 {
667 	return InternalOkayToAddStructureToWorld(sBaseGridNo, bLevel, pDBStructureRef, sExclusionID, sExclusionID == IGNORE_PEOPLE_STRUCTURE_ID);
668 }
669 
670 
AddStructureToTile(MAP_ELEMENT * const me,STRUCTURE * const s,UINT16 const structure_id)671 static void AddStructureToTile(MAP_ELEMENT* const me, STRUCTURE* const s, UINT16 const structure_id)
672 { // Add a STRUCTURE to a MAP_ELEMENT (Add part of a structure to a location on the map)
673 	STRUCTURE* const tail = me->pStructureTail;
674 	s->usStructureID = structure_id;
675 	s->pPrev         = tail;
676 	*(tail ? &tail->pNext : &me->pStructureHead) = s;
677 	me->pStructureTail = s;
678 	if (s->fFlags & STRUCTURE_OPENABLE) me->uiFlags |= MAPELEMENT_INTERACTIVETILE;
679 }
680 
681 
682 static void DeleteStructureFromTile(MAP_ELEMENT* pMapElement, STRUCTURE* pStructure);
683 
684 
AddStructureToWorld(INT16 const sBaseGridNo,INT8 const bLevel,DB_STRUCTURE_REF const * const pDBStructureRef,LEVELNODE * const pLevelNode)685 STRUCTURE* AddStructureToWorld(INT16 const sBaseGridNo, INT8 const bLevel, DB_STRUCTURE_REF const* const pDBStructureRef, LEVELNODE* const pLevelNode)
686 try
687 { // Adds a complete structure to the world at a location plus all other locations covered by the structure
688 	CHECKN(pDBStructureRef);
689 	CHECKN(pLevelNode);
690 
691 	DB_STRUCTURE const* const pDBStructure = pDBStructureRef->pDBStructure;
692 	CHECKN(pDBStructure);
693 
694 	DB_STRUCTURE_TILE const* const* const ppTile = pDBStructureRef->ppTile;
695 	CHECKN(ppTile);
696 
697 	CHECKN(pDBStructure->ubNumberOfTiles > 0);
698 
699 	// first check to see if the structure will be blocked
700 	if (!OkayToAddStructureToWorld(sBaseGridNo, bLevel, pDBStructureRef, INVALID_STRUCTURE_ID))
701 	{
702 		return 0;
703 	}
704 
705 	/* We go through a definition stage here and a later stage of adding
706 	 * everything to the world so that we don't have to untangle things if we run
707 	 * out of memory.  First we create an array of pointers to point to all of the
708 	 * STRUCTURE elements created in the first stage.  This array gets given to
709 	 * the base tile so there is an easy way to remove an entire object from the
710 	 * world quickly */
711 	SGP::Buffer<STRUCTURE*> structures(pDBStructure->ubNumberOfTiles);
712 
713 	for (UINT8 i = BASE_TILE; i < pDBStructure->ubNumberOfTiles; ++i)
714 	{ // for each tile, create the appropriate STRUCTURE struct
715 		STRUCTURE* s;
716 		try
717 		{
718 			s = CreateStructureFromDB(pDBStructureRef, i);
719 			structures[i] = s;
720 		}
721 		catch (...)
722 		{
723 			// Free allocated memory and abort!
724 			for (UINT8 k = 0; k < i; ++k)
725 			{
726 				delete structures[k];
727 			}
728 			return 0;
729 		}
730 		DB_STRUCTURE_TILE const* const t = ppTile[i];
731 		s->sGridNo = sBaseGridNo + t->sPosRelToBase;
732 		if (i != BASE_TILE)
733 		{
734 			if(GameState::getInstance()->isEditorMode())
735 			{
736 				/* Kris:
737 				* Added this undo code if in the editor.
738 				* It is important to save tiles effected by multitiles.  If the
739 				* structure placement fails below, it doesn't matter, because it won't
740 				* hurt the undo code. */
741 				if (gfEditMode) AddToUndoList(s->sGridNo);
742 			}
743 			s->sBaseGridNo = sBaseGridNo;
744 		}
745 		s->sCubeOffset =
746 			(t->fFlags & TILE_ON_ROOF ? bLevel + 1 : bLevel) * PROFILE_Z_SIZE;
747 		if (t->fFlags & TILE_PASSABLE) s->fFlags |= STRUCTURE_PASSABLE;
748 		if (pLevelNode->uiFlags & LEVELNODE_SOLDIER)
749 		{
750 			// should now be unncessary
751 			s->fFlags |= STRUCTURE_PERSON;
752 			s->fFlags &= ~STRUCTURE_BLOCKSMOVES;
753 		}
754 		else if (pLevelNode->uiFlags & LEVELNODE_ROTTINGCORPSE || pDBStructure->fFlags & STRUCTURE_CORPSE)
755 		{
756 			s->fFlags |= STRUCTURE_CORPSE;
757 			// attempted check to screen this out for queen creature or vehicle
758 			if (pDBStructure->ubNumberOfTiles < 10)
759 			{
760 				s->fFlags |= STRUCTURE_PASSABLE;
761 				s->fFlags &= ~STRUCTURE_BLOCKSMOVES;
762 			}
763 			else
764 			{
765 				// make sure not transparent
766 				s->fFlags &= ~STRUCTURE_TRANSPARENT;
767 			}
768 		}
769 	}
770 
771 	UINT16 usStructureID;
772 	if (pLevelNode->uiFlags & LEVELNODE_SOLDIER)
773 	{
774 		// use the merc's ID as the structure ID for his/her structure
775 		usStructureID = pLevelNode->pSoldier->ubID;
776 	}
777 	else if (pLevelNode->uiFlags & LEVELNODE_ROTTINGCORPSE)
778 	{
779 		// ATE: Offset IDs so they don't collide with soldiers
780 		usStructureID = (UINT16)(TOTAL_SOLDIERS + pLevelNode->pAniTile->v.user.uiData);
781 	}
782 	else
783 	{
784 		gusNextAvailableStructureID++;
785 		if (gusNextAvailableStructureID == 0)
786 		{
787 			// skip past the #s for soldiers' structures and the invalid structure #
788 			gusNextAvailableStructureID = FIRST_AVAILABLE_STRUCTURE_ID;
789 		}
790 		usStructureID = gusNextAvailableStructureID;
791 	}
792 	// now add all these to the world!
793 	INT16 sBaseTileHeight = -1;
794 	for (UINT8 i = BASE_TILE; i < pDBStructure->ubNumberOfTiles; ++i)
795 	{
796 		STRUCTURE*   const s  = structures[i];
797 		MAP_ELEMENT* const me = &gpWorldLevelData[s->sGridNo];
798 		if (i == BASE_TILE)
799 		{
800 			sBaseTileHeight = me->sHeight;
801 		}
802 		else if (me->sHeight != sBaseTileHeight)
803 		{
804 			// not level ground! abort!
805 			for (UINT8 k = BASE_TILE; k < i; ++k)
806 			{
807 				STRUCTURE* const s = structures[k];
808 				DeleteStructureFromTile(&gpWorldLevelData[s->sGridNo], s);
809 			}
810 			return 0;
811 		}
812 		AddStructureToTile(me, s, usStructureID);
813 	}
814 
815 	STRUCTURE* const base = structures[BASE_TILE];
816 	pLevelNode->pStructureData = base;
817 	return base;
818 }
819 catch (...) { return 0; }
820 
821 //
822 // Structure deletion functions
823 //
824 
825 
DeleteStructureFromTile(MAP_ELEMENT * const me,STRUCTURE * const s)826 static void DeleteStructureFromTile(MAP_ELEMENT* const me, STRUCTURE* const s)
827 { // removes a STRUCTURE element at a particular location from the world
828 	// put location pointer in tile
829 	STRUCTURE* const next = s->pNext;
830 	STRUCTURE* const prev = s->pPrev;
831 	Assert((prev == NULL) == (me->pStructureHead == s));
832 	Assert((next == NULL) == (me->pStructureTail == s));
833 	*(prev != NULL ? &prev->pNext : &me->pStructureHead) = next;
834 	*(next != NULL ? &next->pPrev : &me->pStructureTail) = prev;
835 
836 	// only one allowed in a tile, so we are safe to do this
837 	if (s->fFlags & STRUCTURE_OPENABLE) me->uiFlags &= ~MAPELEMENT_INTERACTIVETILE;
838 
839 	delete s;
840 }
841 
842 
DeleteStructureFromWorld(STRUCTURE * const structure)843 BOOLEAN DeleteStructureFromWorld(STRUCTURE* const structure)
844 { // removes all of the STRUCTURE elements for a structure from the world
845 	CHECKF(structure);
846 
847 	STRUCTURE* const base = FindBaseStructure(structure);
848 	CHECKF(base);
849 
850 	UINT16              const structure_id           = base->usStructureID;
851 	bool                const recompile_mps          = gsRecompileAreaLeft != 0 && !(base->fFlags & STRUCTURE_MOBILE);
852 	bool                const recompile_extra_radius = recompile_mps && base->fFlags & STRUCTURE_WALLSTUFF; // For doors, yuck
853 	GridNo              const base_grid_no           = base->sGridNo;
854 	DB_STRUCTURE_TILE** const tile                   = base->pDBStructureRef->ppTile;
855 	DB_STRUCTURE_TILE** const end_tile               = tile + base->pDBStructureRef->pDBStructure->ubNumberOfTiles;
856 	// Free all the tiles
857 	for (DB_STRUCTURE_TILE* const* i = tile; i != end_tile; ++i)
858 	{
859 		GridNo const grid_no = base_grid_no + (*i)->sPosRelToBase;
860 		/* There might be two structures in this tile, one on each level, but we
861 		 * just want to delete one on each pass */
862 		if (STRUCTURE* const current = FindStructureByID(grid_no, structure_id))
863 		{
864 			DeleteStructureFromTile(&gpWorldLevelData[grid_no], current);
865 		}
866 
867 		if (gfEditMode || !recompile_mps) continue;
868 
869 		AddTileToRecompileArea(grid_no);
870 
871 		if (!recompile_extra_radius) continue;
872 
873 		// Add adjacent tiles too
874 		for (UINT8 k = 0; k != NUM_WORLD_DIRECTIONS; ++k)
875 		{
876 			GridNo const check_grid_no = NewGridNo(grid_no, DirectionInc(k));
877 			if (check_grid_no == grid_no) continue;
878 			AddTileToRecompileArea(check_grid_no);
879 		}
880 	}
881 	return TRUE;
882 }
883 
884 
InternalSwapStructureForPartner(STRUCTURE * const s,bool const store_in_map)885 static STRUCTURE* InternalSwapStructureForPartner(STRUCTURE* const s, bool const store_in_map)
886 try
887 {
888 	if (!s) return 0;
889 
890 	STRUCTURE* const base = FindBaseStructure(s);
891 	CHECKN(base);
892 
893 	INT8 const delta = base->pDBStructureRef->pDBStructure->bPartnerDelta;
894 	if (delta == NO_PARTNER_STRUCTURE) return 0;
895 
896 	// Record values, base is deleted
897 	GridNo                  const grid_no     = base->sGridNo;
898 	LEVELNODE*              const node        = FindLevelNodeBasedOnStructure(base);
899 	LEVELNODE*              const shadow      = FindShadow(grid_no, node->usIndex);
900 	bool                    const is_door     = base->fFlags & STRUCTURE_ANYDOOR;
901 	DB_STRUCTURE_REF const* const partner     = base->pDBStructureRef + delta;
902 	UINT8                   const hit_points  = base->ubHitPoints;
903 	INT16                   const cube_offset = base->sCubeOffset;
904 
905 	// Delete the old structure and add the new one
906 	if (!DeleteStructureFromWorld(base)) return 0;
907 
908 	STRUCTURE* const new_base = AddStructureToWorld(grid_no, (INT8)(cube_offset / PROFILE_Z_SIZE), partner, node);
909 	if (!new_base) return 0;
910 
911 	// Set values in the new structure
912 	new_base->ubHitPoints = hit_points;
913 
914 	if (!is_door)
915 	{ // Swap the graphics
916 		if (store_in_map) // Store removal of previous if necessary
917 		{
918 			ApplyMapChangesToMapTempFile app;
919 			RemoveStructFromMapTempFile(grid_no, node->usIndex);
920 			node->usIndex += delta;
921 			AddStructToMapTempFile(grid_no, node->usIndex);
922 		}
923 		else
924 		{
925 			node->usIndex += delta;
926 		}
927 
928 		if (shadow) shadow->usIndex += delta;
929 	}
930 
931 	return new_base;
932 }
933 catch (...) { return 0; }
934 
935 
SwapStructureForPartner(STRUCTURE * const s)936 STRUCTURE* SwapStructureForPartner(STRUCTURE* const s)
937 {
938 	return InternalSwapStructureForPartner(s, false);
939 }
940 
941 
SwapStructureForPartnerAndStoreChangeInMap(STRUCTURE * const s)942 STRUCTURE* SwapStructureForPartnerAndStoreChangeInMap(STRUCTURE* const s)
943 {
944 	return InternalSwapStructureForPartner(s, true);
945 }
946 
947 
FindStructure(INT16 const sGridNo,StructureFlags const flags)948 STRUCTURE* FindStructure(INT16 const sGridNo, StructureFlags const flags)
949 {
950 	Assert(flags != 0);
951 	for (STRUCTURE* i = gpWorldLevelData[sGridNo].pStructureHead;; i = i->pNext)
952 	{
953 		if (i == NULL || i->fFlags & flags) return i;
954 	}
955 }
956 
957 
FindNextStructure(STRUCTURE const * const s,StructureFlags const flags)958 STRUCTURE* FindNextStructure(STRUCTURE const* const s, StructureFlags const flags)
959 {
960 	Assert(flags != 0);
961 	CHECKN(s);
962 	for (STRUCTURE* i = s->pNext;; i = i->pNext)
963 	{
964 		if (i == NULL || i->fFlags & flags) return i;
965 	}
966 }
967 
968 
FindStructureByID(const INT16 sGridNo,const UINT16 structure_id)969 STRUCTURE* FindStructureByID(const INT16 sGridNo, const UINT16 structure_id)
970 {
971 	for (STRUCTURE* i = gpWorldLevelData[sGridNo].pStructureHead;; i = i->pNext)
972 	{
973 		if (i == NULL || i->usStructureID == structure_id) return i;
974 	}
975 }
976 
977 
FindBaseStructure(STRUCTURE * const s)978 STRUCTURE* FindBaseStructure(STRUCTURE* const s)
979 {
980 	CHECKN(s);
981 	if (s->fFlags & STRUCTURE_BASE_TILE) return s;
982 	return FindStructureByID(s->sBaseGridNo, s->usStructureID);
983 }
984 
985 
StructureHeight(STRUCTURE * pStructure)986 INT8 StructureHeight( STRUCTURE * pStructure )
987 { // return the height of an object from 1-4
988 	UINT8				ubLoopX, ubLoopY;
989 	PROFILE *		pShape;
990 	UINT8				ubShapeValue;
991 	INT8				bLoopZ;
992 	INT8				bGreatestHeight = -1;
993 
994 	if (pStructure == NULL || pStructure->pShape == NULL)
995 	{
996 		return( 0 );
997 	}
998 
999 	if (pStructure->ubStructureHeight != 0)
1000 	{
1001 		return( pStructure->ubStructureHeight );
1002 	}
1003 
1004 	pShape = pStructure->pShape;
1005 
1006 	// loop horizontally on the X and Y planes
1007 	for (ubLoopX = 0; ubLoopX < PROFILE_X_SIZE; ubLoopX++)
1008 	{
1009 		for (ubLoopY = 0; ubLoopY < PROFILE_Y_SIZE; ubLoopY++)
1010 		{
1011 			ubShapeValue = (*pShape)[ubLoopX][ubLoopY];
1012 			// loop DOWN vertically so that we find the tallest point first
1013 			// and don't need to check any below it
1014 			for (bLoopZ = PROFILE_Z_SIZE - 1; bLoopZ > bGreatestHeight; bLoopZ--)
1015 			{
1016 				if (ubShapeValue & AtHeight[bLoopZ])
1017 				{
1018 					bGreatestHeight = bLoopZ;
1019 					if (bGreatestHeight == PROFILE_Z_SIZE - 1)
1020 					{
1021 						// store height
1022 						pStructure->ubStructureHeight = bGreatestHeight + 1;
1023 						return( bGreatestHeight + 1);
1024 					}
1025 					break;
1026 				}
1027 			}
1028 		}
1029 	}
1030 	// store height
1031 	pStructure->ubStructureHeight = bGreatestHeight + 1;
1032 	return( bGreatestHeight + 1);
1033 }
1034 
GetTallestStructureHeight(INT16 sGridNo,BOOLEAN fOnRoof)1035 INT8 GetTallestStructureHeight( INT16 sGridNo, BOOLEAN fOnRoof )
1036 {
1037 	STRUCTURE *		pCurrent;
1038 	INT8					iHeight;
1039 	INT8					iTallest = 0;
1040 	INT16					sDesiredHeight;
1041 
1042 	if (fOnRoof)
1043 	{
1044 		sDesiredHeight = STRUCTURE_ON_ROOF;
1045 	}
1046 	else
1047 	{
1048 		sDesiredHeight = STRUCTURE_ON_GROUND;
1049 	}
1050 	pCurrent = gpWorldLevelData[sGridNo].pStructureHead;
1051 	while (pCurrent != NULL)
1052 	{
1053 		if (pCurrent->sCubeOffset == sDesiredHeight)
1054 		{
1055 			iHeight = StructureHeight( pCurrent );
1056 			if (iHeight > iTallest)
1057 			{
1058 				iTallest = iHeight;
1059 			}
1060 		}
1061 		pCurrent = pCurrent->pNext;
1062 	}
1063 	return( iTallest );
1064 }
1065 
1066 
GetStructureTargetHeight(INT16 sGridNo,BOOLEAN fOnRoof)1067 INT8 GetStructureTargetHeight( INT16 sGridNo, BOOLEAN fOnRoof )
1068 {
1069 	STRUCTURE *		pCurrent;
1070 	INT8					iHeight;
1071 	INT8					iTallest = 0;
1072 	INT16					sDesiredHeight;
1073 
1074 	if (fOnRoof)
1075 	{
1076 		sDesiredHeight = STRUCTURE_ON_ROOF;
1077 	}
1078 	else
1079 	{
1080 		sDesiredHeight = STRUCTURE_ON_GROUND;
1081 	}
1082 
1083 	// prioritize openable structures and doors
1084 	pCurrent = FindStructure( sGridNo, (STRUCTURE_DOOR | STRUCTURE_OPENABLE ) );
1085 	if ( pCurrent )
1086 	{
1087 		// use this structure
1088 		if ( pCurrent->fFlags & STRUCTURE_DOOR )
1089 		{
1090 			iTallest = 3; // don't aim at the very top of the door
1091 		}
1092 		else
1093 		{
1094 			iTallest = StructureHeight( pCurrent );
1095 		}
1096 	}
1097 	else
1098 	{
1099 		pCurrent = gpWorldLevelData[sGridNo].pStructureHead;
1100 		while (pCurrent != NULL)
1101 		{
1102 			if (pCurrent->sCubeOffset == sDesiredHeight)
1103 			{
1104 				iHeight = StructureHeight( pCurrent );
1105 
1106 				if (iHeight > iTallest)
1107 				{
1108 					iTallest = iHeight;
1109 				}
1110 			}
1111 			pCurrent = pCurrent->pNext;
1112 		}
1113 	}
1114 	return( iTallest );
1115 }
1116 
1117 
StructureBottomLevel(STRUCTURE * pStructure)1118 INT8 StructureBottomLevel( STRUCTURE * pStructure )
1119 { // return the bottom level of an object, from 1-4
1120 	UINT8				ubLoopX, ubLoopY;
1121 	PROFILE *		pShape;
1122 	UINT8				ubShapeValue;
1123 	INT8				bLoopZ;
1124 	INT8				bLowestHeight = PROFILE_Z_SIZE;
1125 
1126 	if (pStructure == NULL || pStructure->pShape == NULL)
1127 	{
1128 		return( 0 );
1129 	}
1130 	pShape = pStructure->pShape;
1131 
1132 	// loop horizontally on the X and Y planes
1133 	for (ubLoopX = 0; ubLoopX < PROFILE_X_SIZE; ubLoopX++)
1134 	{
1135 		for (ubLoopY = 0; ubLoopY < PROFILE_Y_SIZE; ubLoopY++)
1136 		{
1137 			ubShapeValue = (*pShape)[ubLoopX][ubLoopY];
1138 			// loop DOWN vertically so that we find the tallest point first
1139 			// and don't need to check any below it
1140 			for (bLoopZ = 0; bLoopZ < bLowestHeight; bLoopZ++)
1141 			{
1142 				if (ubShapeValue & AtHeight[bLoopZ])
1143 				{
1144 					bLowestHeight = bLoopZ;
1145 					if (bLowestHeight == 0)
1146 					{
1147 						return( 1 );
1148 					}
1149 					break;
1150 				}
1151 			}
1152 		}
1153 	}
1154 	return( bLowestHeight + 1);
1155 }
1156 
1157 
StructureDensity(STRUCTURE * pStructure,UINT8 * pubLevel0,UINT8 * pubLevel1,UINT8 * pubLevel2,UINT8 * pubLevel3)1158 BOOLEAN StructureDensity( STRUCTURE * pStructure, UINT8 * pubLevel0, UINT8 * pubLevel1, UINT8 * pubLevel2, UINT8 * pubLevel3 )
1159 {
1160 	UINT8				ubLoopX, ubLoopY;
1161 	UINT8				ubShapeValue;
1162 	PROFILE *		pShape;
1163 
1164 	CHECKF( pStructure );
1165 	CHECKF( pubLevel0 );
1166 	CHECKF( pubLevel1 );
1167 	CHECKF( pubLevel2 );
1168 	CHECKF( pubLevel3 );
1169 	*pubLevel0 = 0;
1170 	*pubLevel1 = 0;
1171 	*pubLevel2 = 0;
1172 	*pubLevel3 = 0;
1173 
1174 	pShape = pStructure->pShape;
1175 
1176 	for (ubLoopX = 0; ubLoopX < PROFILE_X_SIZE; ubLoopX++)
1177 	{
1178 		for (ubLoopY = 0; ubLoopY < PROFILE_Y_SIZE; ubLoopY++)
1179 		{
1180 			ubShapeValue = (*pShape)[ubLoopX][ubLoopY];
1181 			if (ubShapeValue & AtHeight[0])
1182 			{
1183 				(*pubLevel0)++;
1184 			}
1185 			if (ubShapeValue & AtHeight[1])
1186 			{
1187 				(*pubLevel1)++;
1188 			}
1189 			if (ubShapeValue & AtHeight[2])
1190 			{
1191 				(*pubLevel2)++;
1192 			}
1193 			if (ubShapeValue & AtHeight[3])
1194 			{
1195 				(*pubLevel3)++;
1196 			}
1197 
1198 		}
1199 	}
1200 	// convert values to percentages!
1201 	*pubLevel0 *= 4;
1202 	*pubLevel1 *= 4;
1203 	*pubLevel2 *= 4;
1204 	*pubLevel3 *= 4;
1205 	return( TRUE );
1206 }
1207 
DamageStructure(STRUCTURE * const s,UINT8 damage,StructureDamageReason const reason,GridNo const grid_no,INT16 const x,INT16 const y,SOLDIERTYPE * const owner)1208 StructureDamageResult DamageStructure(STRUCTURE* const s, UINT8 damage, StructureDamageReason const reason, GridNo const grid_no, INT16 const x, INT16 const y, SOLDIERTYPE* const owner)
1209 {	// Do damage to a structure; returns TRUE if the structure should be removed
1210 	if (!s)
1211 	{
1212 		SLOGW("Structure is not defined");
1213 		return STRUCTURE_NOT_DAMAGED;
1214 	}
1215 
1216 	if (s->fFlags & (STRUCTURE_PERSON | STRUCTURE_CORPSE))
1217 	{ // Don't hurt this structure, it's used for hit detection only
1218 		return STRUCTURE_NOT_DAMAGED;
1219 	}
1220 
1221 	UINT8 const armour_kind = s->pDBStructureRef->pDBStructure->ubArmour;
1222 	if (armour_kind == MATERIAL_INDESTRUCTABLE_METAL) return STRUCTURE_NOT_DAMAGED;
1223 	if (armour_kind == MATERIAL_INDESTRUCTABLE_STONE) return STRUCTURE_NOT_DAMAGED;
1224 
1225 	if (reason == STRUCTURE_DAMAGE_EXPLOSION)
1226 	{
1227 		// Account for armour!
1228 		UINT8 const base_armour = gubMaterialArmour[armour_kind];
1229 		UINT8 const armour      = s->fFlags & STRUCTURE_EXPLOSIVE ? base_armour / 3 : base_armour / 2;
1230 		if (damage < armour) return STRUCTURE_NOT_DAMAGED; // Didn't even scratch the paint
1231 		// Did some damage to the structure
1232 		damage -= armour;
1233 	}
1234 	else if (reason == STRUCTURE_DAMAGE_GUNFIRE)
1235 	{
1236 		// If here, we have penetrated, check flags
1237 		// Are we an explodable structure?
1238 		if (s->fFlags & STRUCTURE_EXPLOSIVE && Random(2))
1239 		{ // Remove struct
1240 			// ATE: Set hit points to zero
1241 			STRUCTURE* const base = FindBaseStructure(s);
1242 			base->ubHitPoints = 0;
1243 
1244 			IgniteExplosionXY(owner, x, y, 0, grid_no, STRUCTURE_IGNITE, 0);
1245 
1246 			// ATE: Return negative here, as we are dealing with deleting the graphic here
1247 			return STRUCTURE_NOT_DAMAGED;
1248 		}
1249 
1250 		// Make hit sound
1251 		SoundID const snd =
1252 			s->fFlags & STRUCTURE_CAVEWALL ? S_VEG_IMPACT1 :
1253 			guiMaterialHitSound[armour_kind];
1254 		if (snd != NO_SOUND) PlayLocationJA2Sample(grid_no, snd, HIGHVOLUME, 1);
1255 
1256 		// Don't update damage HPs
1257 		return STRUCTURE_DESTROYED;
1258 	}
1259 	else
1260 	{
1261 		damage = 0;
1262 	}
1263 
1264 	// Find the base so we can reduce the hit points!
1265 	STRUCTURE* const base = FindBaseStructure(s);
1266 	if (!base) return STRUCTURE_NOT_DAMAGED;
1267 
1268 	if (base->ubHitPoints <= damage) return STRUCTURE_DESTROYED; // boom! structure destroyed!
1269 	base->ubHitPoints -= damage;
1270 
1271 	/* Since the structure is being damaged, set the map element that a structure
1272 	 * is damaged */
1273 	gpWorldLevelData[grid_no].uiFlags |= MAPELEMENT_STRUCTURE_DAMAGED;
1274 
1275 	// We are a little damaged
1276 	return STRUCTURE_DAMAGED;
1277 }
1278 
1279 
1280 #define LINE_HEIGHT 20
DebugStructurePage1()1281 void DebugStructurePage1()
1282 {
1283 	static const ST::string WallOrientationString[] =
1284 	{
1285 		"None",
1286 		"Inside left",
1287 		"Inside right",
1288 		"Outside left",
1289 		"Outside right"
1290 	};
1291 
1292 	GridNo const grid_no = GetMouseMapPos();
1293 	if (grid_no == NOWHERE) {
1294 		MPageHeader("DEBUG STRUCTURES PAGE ONE");
1295 		return;
1296 	} else {
1297 		MPageHeader(ST::format("DEBUG STRUCTURES PAGE ONE, GRIDNO {}", grid_no));
1298 	}
1299 
1300 	INT32 const h = DEBUG_PAGE_LINE_HEIGHT;
1301 	INT32 y = DEBUG_PAGE_START_Y;
1302 
1303 	MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y+=h, "Building:", gubBuildingInfo[grid_no]);
1304 
1305 
1306 	bool might_have_structures = GridNoOnVisibleWorldTile(grid_no);
1307 	INT8 n_structures = 0;
1308 	if (might_have_structures) {
1309 		for (STRUCTURE* i = gpWorldLevelData[grid_no].pStructureHead; i; i = i->pNext)
1310 		{
1311 			++n_structures;
1312 		}
1313 	}
1314 	MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Number of structures:", n_structures);
1315 
1316 	if (!might_have_structures) return;
1317 
1318 	MHeader(DEBUG_PAGE_FIRST_COLUMN, y += h, "Movement Costs:");
1319 	MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("N {} NE {} E {} SE {} S {} SW {} W {} NW {}",
1320 		gubWorldMovementCosts[grid_no][NORTH    ][gsInterfaceLevel],
1321 		gubWorldMovementCosts[grid_no][NORTHEAST][gsInterfaceLevel],
1322 		gubWorldMovementCosts[grid_no][EAST     ][gsInterfaceLevel],
1323 		gubWorldMovementCosts[grid_no][SOUTHEAST][gsInterfaceLevel],
1324 		gubWorldMovementCosts[grid_no][SOUTH    ][gsInterfaceLevel],
1325 		gubWorldMovementCosts[grid_no][SOUTHWEST][gsInterfaceLevel],
1326 		gubWorldMovementCosts[grid_no][WEST     ][gsInterfaceLevel],
1327 		gubWorldMovementCosts[grid_no][NORTHWEST][gsInterfaceLevel]));
1328 	MHeader(DEBUG_PAGE_FIRST_COLUMN, y += h, "Ground smell:");
1329 	MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("{} of strength {}",
1330 		SMELL_TYPE(gpWorldLevelData[grid_no].ubSmellInfo),
1331 		SMELL_STRENGTH(gpWorldLevelData[grid_no].ubSmellInfo)));
1332 
1333 	INT16 const desired_level = gsInterfaceLevel == I_GROUND_LEVEL ? STRUCTURE_ON_GROUND : STRUCTURE_ON_ROOF;
1334 	for (STRUCTURE* s = gpWorldLevelData[grid_no].pStructureHead; s; s = s->pNext)
1335 	{
1336 		if (s->sCubeOffset != desired_level) continue;
1337 
1338 		y += h;
1339 
1340 		MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Structure ID:", s->usStructureID);
1341 		MHeader(DEBUG_PAGE_FIRST_COLUMN, y += h, "Type:");
1342 		if (s->fFlags & STRUCTURE_GENERIC)
1343 		{
1344 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("Generic structure no {}", s->pDBStructureRef->pDBStructure->usStructureNumber));
1345 		}
1346 		else if (s->fFlags & STRUCTURE_TREE)
1347 		{
1348 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, "Tree");
1349 		}
1350 		else if (s->fFlags & STRUCTURE_FENCE)
1351 		{
1352 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("Fence with orientation {}", WallOrientationString[s->ubWallOrientation]));
1353 		}
1354 		else if (s->fFlags & STRUCTURE_WIREFENCE)
1355 		{
1356 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("Wirefence with orientation {}", WallOrientationString[s->ubWallOrientation]));
1357 		}
1358 		else if (s->fFlags & STRUCTURE_WALL)
1359 		{
1360 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("Wall with orientation {}", WallOrientationString[s->ubWallOrientation]));
1361 		}
1362 		else if (s->fFlags & STRUCTURE_WALLNWINDOW)
1363 		{
1364 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("Wall with window with orientation {}", WallOrientationString[s->ubWallOrientation]));
1365 		}
1366 		else if (s->fFlags & STRUCTURE_VEHICLE)
1367 		{
1368 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("Vehicle {}", s->pDBStructureRef->pDBStructure->usStructureNumber));
1369 		}
1370 		else if (s->fFlags & STRUCTURE_NORMAL_ROOF)
1371 		{
1372 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, "Roof");
1373 		}
1374 		else if (s->fFlags & STRUCTURE_SLANTED_ROOF)
1375 		{
1376 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, "Slanted roof");
1377 		}
1378 		else if (s->fFlags & STRUCTURE_TALL_ROOF)
1379 		{
1380 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, "Tall roof");
1381 		}
1382 		else if (s->fFlags & STRUCTURE_SWITCH)
1383 		{
1384 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, "Switch");
1385 		}
1386 		else if (s->fFlags & STRUCTURE_CORPSE)
1387 		{
1388 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, "Corpse");
1389 		}
1390 		else if (s->fFlags & STRUCTURE_PERSON)
1391 		{
1392 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, "Person");
1393 		}
1394 		else if (s->fFlags & STRUCTURE_CAVEWALL)
1395 		{
1396 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, "Cave wall");
1397 		}
1398 		else if (s->fFlags & STRUCTURE_DOOR)
1399 		{
1400 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("Door with orientation {}", WallOrientationString[s->ubWallOrientation]));
1401 		}
1402 		else if (s->fFlags & STRUCTURE_SLIDINGDOOR)
1403 		{
1404 			ST::string state = s->fFlags & STRUCTURE_OPEN ? "Open" : "Closed";
1405 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("{} sliding door with orientation {}", state, WallOrientationString[s->ubWallOrientation]));
1406 		}
1407 		else if (s->fFlags & STRUCTURE_DDOOR_LEFT)
1408 		{
1409 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("DDoorLft with orientation {}", WallOrientationString[s->ubWallOrientation]));
1410 		}
1411 		else if (s->fFlags & STRUCTURE_DDOOR_RIGHT)
1412 		{
1413 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("DDoorRt with orientation {}", WallOrientationString[s->ubWallOrientation]));
1414 		}
1415 		else
1416 		{
1417 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, "Unknown Structure");
1418 		}
1419 
1420 		MHeader(DEBUG_PAGE_FIRST_COLUMN, y += h, "Flags:");
1421 		ST::string flagString;
1422 		if (s->fFlags & STRUCTURE_MOBILE) {
1423 			flagString += "MOB ";
1424 		}
1425 		if (s->fFlags & STRUCTURE_PASSABLE) {
1426 			flagString += "PAS ";
1427 		}
1428 		if (s->fFlags & STRUCTURE_EXPLOSIVE) {
1429 			flagString += "EXP ";
1430 		}
1431 		if (s->fFlags & STRUCTURE_TRANSPARENT) {
1432 			flagString += "TRA ";
1433 		}
1434 		if (s->fFlags & STRUCTURE_HASITEMONTOP) {
1435 			flagString += "HIT ";
1436 		}
1437 		if (s->fFlags & STRUCTURE_SPECIAL) {
1438 			flagString += "SPE ";
1439 		}
1440 		if (s->fFlags & STRUCTURE_LIGHTSOURCE) {
1441 			flagString += "LIG ";
1442 		}
1443 		MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, flagString);
1444 
1445 		INT8             const height = StructureHeight(s);
1446 		STRUCTURE const* const base   = FindBaseStructure(s);
1447 		UINT8            const armour = gubMaterialArmour[s->pDBStructureRef->pDBStructure->ubArmour];
1448 		MHeader(DEBUG_PAGE_FIRST_COLUMN, y += h, "Structure info:");
1449 		MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("Structure height {}, cube offset {}, armour {}, HP {}}", height, s->sCubeOffset, armour, base->ubHitPoints));
1450 
1451 		UINT8 dens0;
1452 		UINT8 dens1;
1453 		UINT8 dens2;
1454 		UINT8 dens3;
1455 		if (StructureDensity(s, &dens0, &dens1, &dens2, &dens3))
1456 		{
1457 			MHeader(DEBUG_PAGE_FIRST_COLUMN, y += h, "Structure fill:");
1458 			MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format(" {}%/{}%/{}%/{}% density {}", dens0, dens1, dens2, dens3, s->pDBStructureRef->pDBStructure->ubDensity));
1459 		}
1460 	}
1461 
1462 #ifdef LOS_DEBUG
1463 	LOSResults const& los = gLOSTestResults;
1464 	if (los.fLOSTestPerformed)
1465 	{
1466 		MPrint(DEBUG_PAGE_FIRST_COLUMN, y+=h, ST::format("LOS from ({7d},{7d},{7d})", los.iStartX, los.iStartY, los.iStartZ));
1467 		MPrint(DEBUG_PAGE_FIRST_COLUMN, y+=h, ST::format("to ({7d},{7d},{7d})", los.iEndX, los.iEndY, los.iEndZ));
1468 		if (los.fOutOfRange)
1469 		{
1470 			MPrint(DEBUG_PAGE_FIRST_COLUMN, y+=h, "is out of range");
1471 		}
1472 		else if (los.fLOSClear)
1473 		{
1474 			MPrint(DEBUG_PAGE_FIRST_COLUMN, y+=h, "is clear!");
1475 		}
1476 		else
1477 		{
1478 			MPrint(DEBUG_PAGE_FIRST_COLUMN, y+=h, ST::format("is blocked at ({7d},{7d},{7d})!", los.iStoppedX, los.iStoppedY, los.iStoppedZ));
1479 			MPrint(DEBUG_PAGE_FIRST_COLUMN, y+=h, ST::format("Blocked at cube level {}", los.iCurrCubesZ));
1480 		}
1481 		MPrint(DEBUG_PAGE_FIRST_COLUMN, y+=h, ST::format("Passed through {} tree bits!", los.ubTreeSpotsHit));
1482 		MPrint(DEBUG_PAGE_FIRST_COLUMN, y+=h, ST::format("Maximum range was {7d}", los.iMaxDistance));
1483 		MPrint(DEBUG_PAGE_FIRST_COLUMN, y+=h, ST::format("actual range was {7d}", los.iDistance));
1484 		if (los.ubChanceToGetThrough <= 100)
1485 		{
1486 			MPrint(DEBUG_PAGE_FIRST_COLUMN, y+=h, ST::format("Chance to get through was {}", los.ubChanceToGetThrough));
1487 		}
1488 	}
1489 #endif
1490 
1491 #ifdef COUNT_PATHS
1492 	if (guiTotalPathChecks > 0)
1493 	{
1494 		MPrint(DEBUG_PAGE_FIRST_COLUMN, y+=h,
1495 			ST::format("Total {}, %succ {3d} | %failed {3d} | %unsucc {3d}",
1496 			guiTotalPathChecks,
1497 			100 * guiSuccessfulPathChecks   / guiTotalPathChecks,
1498 			100 * guiFailedPathChecks       / guiTotalPathChecks,
1499 			100 * guiUnsuccessfulPathChecks / guiTotalPathChecks));
1500 	}
1501 #endif
1502 }
1503 
1504 
AddZStripInfoToVObject(HVOBJECT const hVObject,STRUCTURE_FILE_REF const * const pStructureFileRef,BOOLEAN const fFromAnimation,INT16 sSTIStartIndex)1505 void AddZStripInfoToVObject(HVOBJECT const hVObject, STRUCTURE_FILE_REF const* const pStructureFileRef, BOOLEAN const fFromAnimation, INT16 sSTIStartIndex)
1506 {
1507 	if (pStructureFileRef->usNumberOfStructuresStored == 0) return;
1508 
1509 	BOOLEAN             fFound       = FALSE;
1510 	const DB_STRUCTURE* pDBStructure = NULL;
1511 	for (UINT32 uiLoop = 0; uiLoop < pStructureFileRef->usNumberOfStructures; ++uiLoop)
1512 	{
1513 		const DB_STRUCTURE_REF* const pDBStructureRef = &pStructureFileRef->pDBStructureRef[uiLoop];
1514 		pDBStructure = pDBStructureRef->pDBStructure;
1515 		//if (pDBStructure != NULL && pDBStructure->ubNumberOfTiles > 1 && !(pDBStructure->fFlags & STRUCTURE_WALLSTUFF) )
1516 		if (pDBStructure != NULL && pDBStructure->ubNumberOfTiles > 1)
1517 		{
1518 			for (UINT8 ubLoop2 = 1; ubLoop2 < pDBStructure->ubNumberOfTiles; ++ubLoop2)
1519 			{
1520 				if (pDBStructureRef->ppTile[ubLoop2]->sPosRelToBase != 0)
1521 				{
1522 					// spans multiple tiles! (could be two levels high in one tile)
1523 					fFound = TRUE;
1524 					break;
1525 				}
1526 			}
1527 		}
1528 	}
1529 
1530 	// ATE: Make all corpses use z-strip info..
1531 	if (pDBStructure != NULL && pDBStructure->fFlags & STRUCTURE_CORPSE)
1532 	{
1533 		fFound = TRUE;
1534 	}
1535 
1536 	// if no multi-tile images in this vobject, that's okay... return!
1537 	if (!fFound) return;
1538 
1539 	UINT         const zcount = hVObject->SubregionCount();
1540 	ZStripInfo** const zinfo  = new ZStripInfo*[zcount]{};
1541 
1542 	INT16 sSTIStep;
1543 	if (fFromAnimation)
1544 	{
1545 		// Determine step index for STI
1546 		if (sSTIStartIndex == -1)
1547 		{
1548 			// one-direction only for this anim structure
1549 			sSTIStep = zcount;
1550 			sSTIStartIndex = 0;
1551 		}
1552 		else
1553 		{
1554 			sSTIStep = zcount / pStructureFileRef->usNumberOfStructures;
1555 		}
1556 	}
1557 	else
1558 	{
1559 		sSTIStep = 1;
1560 	}
1561 
1562 	INT16   sLeftHalfWidth;
1563 	INT16   sRightHalfWidth;
1564 	INT16   sStructIndex    = 0;
1565 	INT16   sNext           = sSTIStartIndex + sSTIStep;
1566 	BOOLEAN fFirstTime      = TRUE;
1567 	for (UINT32 uiLoop = sSTIStartIndex; uiLoop < zcount; ++uiLoop)
1568 	try
1569 	{
1570 		// Defualt to true
1571 		BOOLEAN fCopyIntoVo = TRUE;
1572 
1573 		// Increment struct index....
1574 		if (uiLoop == (UINT32)sNext)
1575 		{
1576 			sNext = uiLoop + sSTIStep;
1577 			sStructIndex++;
1578 		}
1579 		else
1580 		{
1581 			if (fFirstTime)
1582 			{
1583 				fFirstTime = FALSE;
1584 			}
1585 			else
1586 			{
1587 				fCopyIntoVo = FALSE;
1588 			}
1589 		}
1590 
1591 		const UINT32 uiDestVoIndex = (fFromAnimation ? sStructIndex : uiLoop);
1592 
1593 		if (fCopyIntoVo && sStructIndex < pStructureFileRef->usNumberOfStructures)
1594 		{
1595 			const DB_STRUCTURE* const pDBStructure = pStructureFileRef->pDBStructureRef[sStructIndex].pDBStructure;
1596 			if (pDBStructure != NULL && (pDBStructure->ubNumberOfTiles > 1 || pDBStructure->fFlags & STRUCTURE_CORPSE))
1597 			//if (pDBStructure != NULL && pDBStructure->ubNumberOfTiles > 1 )
1598 			{
1599 				// ATE: We allow SLIDING DOORS of 2 tile sizes...
1600 				if (!(pDBStructure->fFlags & STRUCTURE_ANYDOOR) || pDBStructure->fFlags & STRUCTURE_SLIDINGDOOR)
1601 				{
1602 					ZStripInfo* const pCurr = new ZStripInfo{};
1603 					Assert(uiDestVoIndex < zcount);
1604 					zinfo[uiDestVoIndex] = pCurr;
1605 
1606 					UINT8 ubNumIncreasing = 0;
1607 					UINT8 ubNumStable     = 0;
1608 					UINT8 ubNumDecreasing = 0;
1609 
1610 					// time to do our calculations!
1611 					ETRLEObject const& e        = hVObject->SubregionProperties(uiLoop);
1612 					INT16              sOffsetX = e.sOffsetX;
1613 					INT16              sOffsetY = e.sOffsetY;
1614 					UINT16      const  usWidth  = e.usWidth;
1615 					if (pDBStructure->fFlags & (STRUCTURE_MOBILE | STRUCTURE_CORPSE))
1616 					{
1617 						// adjust for the difference between the animation and structure base tile
1618 
1619 						//if (pDBStructure->fFlags & (STRUCTURE_MOBILE ) )
1620 						{
1621 							sOffsetX += WORLD_TILE_X / 2;
1622 							sOffsetY += WORLD_TILE_Y / 2;
1623 						}
1624 						// adjust for the tile offset
1625 						sOffsetX = sOffsetX - pDBStructure->bZTileOffsetX * (WORLD_TILE_X / 2) + pDBStructure->bZTileOffsetY * (WORLD_TILE_X / 2);
1626 						sOffsetY = sOffsetY - pDBStructure->bZTileOffsetY * (WORLD_TILE_Y / 2);
1627 					}
1628 
1629 					// figure out how much of the image is on each side of
1630 					// the bottom corner of the base tile
1631 					if (sOffsetX <= 0)
1632 					{
1633 						// note that the adjustments here by (WORLD_TILE_X / 2) are to account for the X difference
1634 						// between the blit position and the bottom corner of the base tile
1635 						sRightHalfWidth = usWidth + sOffsetX - (WORLD_TILE_X / 2);
1636 
1637 						if (sRightHalfWidth >= 0)
1638 						{
1639 							// Case 1: negative image offset, image straddles bottom corner
1640 
1641 							// negative of a negative is positive
1642 							sLeftHalfWidth = -sOffsetX + (WORLD_TILE_X / 2);
1643 						}
1644 						else
1645 						{
1646 							// Case 2: negative image offset, image all on left side
1647 
1648 							// bump up the LeftHalfWidth to the right edge of the last tile-half,
1649 							// so we can calculate the size of the leftmost portion accurately
1650 							// NB subtracting a negative to add the absolute value
1651 							sLeftHalfWidth = usWidth - (sRightHalfWidth % (WORLD_TILE_X / 2));
1652 							sRightHalfWidth = 0;
1653 						}
1654 					}
1655 					else if (sOffsetX < (WORLD_TILE_X / 2))
1656 					{
1657 						sLeftHalfWidth = (WORLD_TILE_X / 2) - sOffsetX;
1658 						sRightHalfWidth = usWidth - sLeftHalfWidth;
1659 						if (sRightHalfWidth <= 0)
1660 						{
1661 							// Case 3: positive offset < 20, image all on left side
1662 							// should never happen because these images are multi-tile!
1663 							sRightHalfWidth = 0;
1664 							// fake the left width to one half-tile
1665 							sLeftHalfWidth = (WORLD_TILE_X / 2);
1666 						}
1667 						else
1668 						{
1669 							// Case 4: positive offset < 20, image straddles bottom corner
1670 
1671 							// all okay?
1672 						}
1673 					}
1674 					else
1675 					{
1676 						// Case 5: positive offset, image all on right side
1677 						// should never happen either
1678 						sLeftHalfWidth = 0;
1679 						sRightHalfWidth = usWidth;
1680 					}
1681 
1682 					if (sLeftHalfWidth > 0)
1683 					{
1684 						ubNumIncreasing = sLeftHalfWidth / (WORLD_TILE_X / 2);
1685 					}
1686 					if (sRightHalfWidth > 0)
1687 					{
1688 						ubNumStable = 1;
1689 						if (sRightHalfWidth > (WORLD_TILE_X / 2))
1690 						{
1691 							ubNumDecreasing = sRightHalfWidth / (WORLD_TILE_X / 2);
1692 						}
1693 					}
1694 					if (sLeftHalfWidth > 0)
1695 					{
1696 						pCurr->ubFirstZStripWidth = sLeftHalfWidth % (WORLD_TILE_X / 2);
1697 						if (pCurr->ubFirstZStripWidth == 0)
1698 						{
1699 							ubNumIncreasing--;
1700 							pCurr->ubFirstZStripWidth = (WORLD_TILE_X / 2);
1701 						}
1702 					}
1703 					else // right side only; offset is at least 20 (= WORLD_TILE_X / 2)
1704 					{
1705 						if (sOffsetX > WORLD_TILE_X)
1706 						{
1707 							pCurr->ubFirstZStripWidth = (WORLD_TILE_X / 2) - (sOffsetX - WORLD_TILE_X) % (WORLD_TILE_X / 2);
1708 						}
1709 						else
1710 						{
1711 							pCurr->ubFirstZStripWidth = WORLD_TILE_X - sOffsetX;
1712 						}
1713 						if (pCurr->ubFirstZStripWidth == 0)
1714 						{
1715 							ubNumDecreasing--;
1716 							pCurr->ubFirstZStripWidth = (WORLD_TILE_X / 2);
1717 						}
1718 					}
1719 
1720 					// now create the array!
1721 					pCurr->ubNumberOfZChanges = ubNumIncreasing + ubNumStable + ubNumDecreasing;
1722 					pCurr->pbZChange = new INT8[pCurr->ubNumberOfZChanges]{};
1723 
1724 					UINT8 ubLoop2;
1725 					for (ubLoop2 = 0; ubLoop2 < ubNumIncreasing; ubLoop2++)
1726 					{
1727 						pCurr->pbZChange[ubLoop2] = 1;
1728 					}
1729 					for (; ubLoop2 < ubNumIncreasing + ubNumStable; ubLoop2++)
1730 					{
1731 						pCurr->pbZChange[ubLoop2] = 0;
1732 					}
1733 					for (; ubLoop2 < pCurr->ubNumberOfZChanges; ubLoop2++)
1734 					{
1735 						pCurr->pbZChange[ubLoop2] = -1;
1736 					}
1737 					if (ubNumIncreasing > 0)
1738 					{
1739 						pCurr->bInitialZChange = -ubNumIncreasing;
1740 					}
1741 					else if (ubNumStable > 0)
1742 					{
1743 						pCurr->bInitialZChange = 0;
1744 					}
1745 					else
1746 					{
1747 						pCurr->bInitialZChange = -ubNumDecreasing;
1748 					}
1749 				}
1750 			}
1751 		}
1752 	}
1753 	catch (...)
1754 	{
1755 		for (UINT ubLoop2 = 0; ubLoop2 < uiLoop; ++ubLoop2)
1756 		{
1757 			if (zinfo[ubLoop2] != NULL)
1758 			{
1759 				delete zinfo[uiLoop];
1760 			}
1761 		}
1762 		delete[] zinfo;
1763 		throw;
1764 	}
1765 
1766 	hVObject->ppZStripInfo = zinfo;
1767 }
1768 
1769 
GetBlockingStructureInfo(INT16 sGridNo,INT8 bDir,INT8 bNextDir,INT8 bLevel,INT8 * pStructHeight,STRUCTURE ** ppTallestStructure,BOOLEAN fWallsBlock)1770 INT8 GetBlockingStructureInfo( INT16 sGridNo, INT8 bDir, INT8 bNextDir, INT8 bLevel, INT8 *pStructHeight, STRUCTURE ** ppTallestStructure, BOOLEAN fWallsBlock )
1771 {
1772 	STRUCTURE* pStructure = NULL; // XXX HACK000E
1773 	STRUCTURE* pCurrent;
1774 	INT16      sDesiredLevel;
1775 	BOOLEAN    fOKStructOnLevel = FALSE;
1776 	BOOLEAN    fMinimumBlockingFound = FALSE;
1777 
1778 	if ( bLevel == 0)
1779 	{
1780 		sDesiredLevel = STRUCTURE_ON_GROUND;
1781 	}
1782 	else
1783 	{
1784 		sDesiredLevel = STRUCTURE_ON_ROOF;
1785 	}
1786 
1787 	pCurrent =  gpWorldLevelData[sGridNo].pStructureHead;
1788 
1789 	// If no struct, return
1790 	if ( pCurrent == NULL )
1791 	{
1792 		(*pStructHeight) = StructureHeight( pCurrent );
1793 		(*ppTallestStructure) = NULL;
1794 		return( NOTHING_BLOCKING );
1795 	}
1796 
1797 	while (pCurrent != NULL)
1798 	{
1799 		// Check level!
1800 		if (pCurrent->sCubeOffset == sDesiredLevel )
1801 		{
1802 			fOKStructOnLevel = TRUE;
1803 			pStructure       = pCurrent;
1804 
1805 			// Turn off if we are on upper level!
1806 			if ( pCurrent->fFlags & STRUCTURE_ROOF && bLevel == 1 )
1807 			{
1808 				fOKStructOnLevel = FALSE;
1809 			}
1810 
1811 			// Don't stop FOV for people
1812 			if ( pCurrent->fFlags & ( STRUCTURE_CORPSE | STRUCTURE_PERSON ) )
1813 			{
1814 				fOKStructOnLevel = FALSE;
1815 			}
1816 
1817 
1818 			if ( pCurrent->fFlags & ( STRUCTURE_TREE | STRUCTURE_ANYFENCE ) )
1819 			{
1820 				fMinimumBlockingFound = TRUE;
1821 			}
1822 
1823 			// Default, if we are a wall, set full blocking
1824 			if ( ( pCurrent->fFlags & STRUCTURE_WALL ) && !fWallsBlock )
1825 			{
1826 				// Return full blocking!
1827 				// OK! This will be handled by movement costs......!
1828 				fOKStructOnLevel = FALSE;
1829 			}
1830 
1831 			// CHECK FOR WINDOW
1832 			if ( pCurrent->fFlags & STRUCTURE_WALLNWINDOW )
1833 			{
1834 				switch( pCurrent->ubWallOrientation )
1835 				{
1836 					case OUTSIDE_TOP_LEFT:
1837 					case INSIDE_TOP_LEFT:
1838 
1839 						(*pStructHeight) = StructureHeight( pCurrent );
1840 						(*ppTallestStructure) = pCurrent;
1841 
1842 						if ( pCurrent->fFlags & STRUCTURE_OPEN )
1843 						{
1844 							return( BLOCKING_TOPLEFT_OPEN_WINDOW );
1845 						}
1846 						else
1847 						{
1848 							return( BLOCKING_TOPLEFT_WINDOW );
1849 						}
1850 
1851 					case OUTSIDE_TOP_RIGHT:
1852 					case INSIDE_TOP_RIGHT:
1853 
1854 						(*pStructHeight) = StructureHeight( pCurrent );
1855 						(*ppTallestStructure) = pCurrent;
1856 
1857 						if ( pCurrent->fFlags & STRUCTURE_OPEN )
1858 						{
1859 							return( BLOCKING_TOPRIGHT_OPEN_WINDOW );
1860 						}
1861 						else
1862 						{
1863 							return( BLOCKING_TOPRIGHT_WINDOW );
1864 						}
1865 				}
1866 			}
1867 
1868 			// Check for door
1869 			if ( pCurrent->fFlags & STRUCTURE_ANYDOOR )
1870 			{
1871 				// If we are not opem, we are full blocking!
1872 				if ( !(pCurrent->fFlags & STRUCTURE_OPEN ) )
1873 				{
1874 					(*pStructHeight) = StructureHeight( pCurrent );
1875 					(*ppTallestStructure) = pCurrent;
1876 					return( FULL_BLOCKING );
1877 				}
1878 				else
1879 				{
1880 					switch( pCurrent->ubWallOrientation )
1881 					{
1882 						case OUTSIDE_TOP_LEFT:
1883 						case INSIDE_TOP_LEFT:
1884 
1885 							(*pStructHeight) = StructureHeight( pCurrent );
1886 							(*ppTallestStructure) = pCurrent;
1887 							return( BLOCKING_TOPLEFT_DOOR );
1888 
1889 						case OUTSIDE_TOP_RIGHT:
1890 						case INSIDE_TOP_RIGHT:
1891 
1892 							(*pStructHeight) = StructureHeight( pCurrent );
1893 							(*ppTallestStructure) = pCurrent;
1894 							return( BLOCKING_TOPRIGHT_DOOR );
1895 					}
1896 				}
1897 			}
1898 		}
1899 		pCurrent = pCurrent->pNext;
1900 	}
1901 
1902 	// OK, here, we default to we've seen a struct, reveal just this one
1903 	if ( fOKStructOnLevel )
1904 	{
1905 		if ( fMinimumBlockingFound )
1906 		{
1907 			(*pStructHeight) = StructureHeight( pStructure );
1908 			(*ppTallestStructure) = pStructure;
1909 			return( BLOCKING_REDUCE_RANGE );
1910 		}
1911 		else
1912 		{
1913 			(*pStructHeight) = StructureHeight( pStructure );
1914 			(*ppTallestStructure) = pStructure;
1915 			return( BLOCKING_NEXT_TILE );
1916 		}
1917 	}
1918 	else
1919 	{
1920 		(*pStructHeight) = 0;
1921 		(*ppTallestStructure) = NULL;
1922 		return( NOTHING_BLOCKING );
1923 	}
1924 }
1925 
1926 
1927 
1928 
StructureFlagToType(UINT32 uiFlag)1929 UINT8 StructureFlagToType( UINT32 uiFlag )
1930 {
1931 	UINT8  ubLoop;
1932 	UINT32 uiBit = STRUCTURE_GENERIC;
1933 
1934 	for ( ubLoop = 8; ubLoop < 32; ubLoop++ )
1935 	{
1936 		if ( (uiFlag & uiBit) != 0 )
1937 		{
1938 			return( ubLoop );
1939 		}
1940 		uiBit = uiBit << 1;
1941 	}
1942 	return( 0 );
1943 }
1944 
1945 
FindStructureBySavedInfo(INT16 const grid_no,UINT8 const type,UINT8 const wall_orientation,INT8 const level)1946 STRUCTURE* FindStructureBySavedInfo(INT16 const grid_no, UINT8 const type, UINT8 const wall_orientation, INT8 const level)
1947 {
1948 	for (STRUCTURE* i = gpWorldLevelData[grid_no].pStructureHead; i; i = i->pNext)
1949 	{
1950 		if (!(i->fFlags & 1U << type))                continue;
1951 		if (i->ubWallOrientation != wall_orientation) continue;
1952 		if ((i->sCubeOffset == 0) != (level == 0))    continue;
1953 		return i;
1954 	}
1955 	return 0;
1956 }
1957 
1958 
GetStructureOpenSound(STRUCTURE const * const s,bool const closing)1959 SoundID GetStructureOpenSound(STRUCTURE const* const s, bool const closing)
1960 {
1961 	SoundID sound_id;
1962 	switch (s->pDBStructureRef->pDBStructure->ubArmour)
1963 	{
1964 		case MATERIAL_LIGHT_METAL:
1965 		case MATERIAL_THICKER_METAL: sound_id = OPEN_LOCKER;           break;
1966 		case MATERIAL_WOOD_WALL:
1967 		case MATERIAL_PLYWOOD_WALL:
1968 		case MATERIAL_FURNITURE:     sound_id = OPEN_WOODEN_BOX;       break;
1969 		default:                     sound_id = OPEN_DEFAULT_OPENABLE; break;
1970 	}
1971 
1972 	if (closing) sound_id = static_cast<SoundID>(sound_id + 1);
1973 
1974 	return sound_id;
1975 }
1976