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