1 #include "WorldDef.h"
2 #include "Animated_ProgressBar.h"
3 #include "Animation_Data.h"
4 #include "Buildings.h"
5 #include "ContentManager.h"
6 #include "Debug.h"
7 #include "EditorBuildings.h"
8 #include "EditorMapInfo.h"
9 #include "Environment.h"
10 #include "Exit_Grids.h"
11 #include "FileMan.h"
12 #include "Game_Clock.h"
13 #include "GameInstance.h"
14 #include "GameRes.h"
15 #include "GameState.h"
16 #include "Handle_UI.h"
17 #include "HImage.h"
18 #include "Input.h"
19 #include "Isometric_Utils.h"
20 #include "JA2Types.h"
21 #include "JAScreens.h"
22 #include "Keys.h"
23 #include "LightEffects.h"
24 #include "Lighting.h"
25 #include "LoadSaveBasicSoldierCreateStruct.h"
26 #include "LoadSaveData.h"
27 #include "LoadSaveLightSprite.h"
28 #include "LoadSaveSoldierCreate.h"
29 #include "LoadScreen.h"
30 #include "Logger.h"
31 #include "Map_Edgepoints.h"
32 #include "Map_Information.h"
33 #include "Meanwhile.h"
34 #include "OppList.h"
35 #include "Overhead.h"
36 #include "Overhead_Map.h"
37 #include "Overhead_Types.h"
38 #include "PathAI.h"
39 #include "Random.h"
40 #include "Render_Fun.h"
41 #include "RenderWorld.h"
42 #include "Rotting_Corpses.h"
43 #include "Scheduling.h"
44 #include "ScreenIDs.h"
45 #include "SGPFile.h"
46 #include "SGPStrings.h"
47 #include "SmokeEffects.h"
48 #include "Soldier_Control.h"
49 #include "Soldier_Create.h"
50 #include "Soldier_Init_List.h"
51 #include "StrategicMap.h"
52 #include "Structure.h"
53 #include "Structure_Internals.h"
54 #include "Summary_Info.h"
55 #include "Sys_Globals.h"
56 #include "Tile_Animation.h"
57 #include "Tile_Surface.h"
58 #include "TileDat.h"
59 #include "TileDef.h"
60 #include "VObject.h"
61 #include "World_Items.h"
62 #include "WorldDat.h"
63 #include "WorldMan.h"
64 #include <stdexcept>
65 #include <string>
66 #include <string_theory/format>
67
68
69 #define SET_MOVEMENTCOST( a, b, c, d ) ( ( gubWorldMovementCosts[ a ][ b ][ c ] < d ) ? ( gubWorldMovementCosts[ a ][ b ][ c ] = d ) : 0 );
70 #define FORCE_SET_MOVEMENTCOST( a, b, c, d ) ( gubWorldMovementCosts[ a ][ b ][ c ] = d )
71 #define SET_CURRMOVEMENTCOST( a, b ) SET_MOVEMENTCOST( usGridNo, a, 0, b )
72
73 #define TEMP_FILE_FOR_TILESET_CHANGE "jatiles34.dat"
74
75 #define MAP_FULLSOLDIER_SAVED 0x00000001
76 #define MAP_WORLDLIGHTS_SAVED 0x00000004
77 #define MAP_WORLDITEMS_SAVED 0x00000008
78 #define MAP_EXITGRIDS_SAVED 0x00000010
79 #define MAP_DOORTABLE_SAVED 0x00000020
80 #define MAP_EDGEPOINTS_SAVED 0x00000040
81 #define MAP_AMBIENTLIGHTLEVEL_SAVED 0x00000080
82 #define MAP_NPCSCHEDULES_SAVED 0x00000100
83
84
85 TileSetID giCurrentTilesetID = TILESET_INVALID;
86
87 UINT32 gCurrentBackground = FIRSTTEXTURE;
88
89
90 static INT8 gbNewTileSurfaceLoaded[NUMBEROFTILETYPES];
91
92
SetAllNewTileSurfacesLoaded(BOOLEAN fNew)93 void SetAllNewTileSurfacesLoaded( BOOLEAN fNew )
94 {
95 std::fill(std::begin(gbNewTileSurfaceLoaded), std::end(gbNewTileSurfaceLoaded), fNew);
96 }
97
98
99 // Global Variables
100 MAP_ELEMENT *gpWorldLevelData;
101 UINT8 gubWorldMovementCosts[ WORLD_MAX ][MAXDIR][2];
102
103 // set to nonzero (locs of base gridno of structure are good) to have it defined by structure code
104 INT16 gsRecompileAreaTop = 0;
105 INT16 gsRecompileAreaLeft = 0;
106 INT16 gsRecompileAreaRight = 0;
107 INT16 gsRecompileAreaBottom = 0;
108
109 /** Check if the grid number is valid. */
isValidGridNo(INT32 gridNo)110 static BOOLEAN isValidGridNo(INT32 gridNo)
111 {
112 return (gridNo >= 0) && (gridNo < WORLD_MAX);
113 }
114
DoorAtGridNo(const UINT32 iMapIndex)115 BOOLEAN DoorAtGridNo(const UINT32 iMapIndex)
116 {
117 return FindStructure(iMapIndex, STRUCTURE_ANYDOOR) != NULL;
118 }
119
120
OpenableAtGridNo(const UINT32 iMapIndex)121 BOOLEAN OpenableAtGridNo(const UINT32 iMapIndex)
122 {
123 return FindStructure(iMapIndex, STRUCTURE_OPENABLE) != NULL;
124 }
125
126
FloorAtGridNo(UINT32 const map_idx)127 bool FloorAtGridNo(UINT32 const map_idx)
128 {
129 for (LEVELNODE const* i = gpWorldLevelData[map_idx].pLandHead; i;)
130 {
131 if (i->usIndex == NO_TILE) continue;
132
133 UINT32 const tile_type = GetTileType(i->usIndex);
134 if (FIRSTFLOOR <= tile_type && tile_type <= LASTFLOOR) return true;
135 i = i->pNext; // XXX TODO0009 if i->usIndex == NO_TILE this is an endless loop
136 }
137 return false;
138 }
139
140
GridNoIndoors(UINT32 iMapIndex)141 BOOLEAN GridNoIndoors( UINT32 iMapIndex )
142 {
143 if( gfBasement || gfCaves )
144 return TRUE;
145 if( FloorAtGridNo( iMapIndex ) )
146 return TRUE;
147 return FALSE;
148 }
149
150
151 static UINT8 gbDefaultSurfaceUsed[NUMBEROFTILETYPES];
152
153
InitializeWorld()154 void InitializeWorld()
155 {
156 giCurrentTilesetID = TILESET_INVALID;
157
158 // DB Adds the _8 to the names if we're in 8 bit mode.
159 //ProcessTilesetNamesForBPP();
160
161 // ATE: MEMSET LOG HEIGHT VALUES
162 std::fill(std::begin(gTileTypeLogicalHeight), std::end(gTileTypeLogicalHeight), 1);
163
164 // Memset tile database
165 std::fill(std::begin(gTileDatabase), std::end(gTileDatabase), TILE_ELEMENT{});
166
167 // Init surface list
168 std::fill(std::begin(gTileSurfaceArray), std::end(gTileSurfaceArray), nullptr);
169
170 // Init default surface list
171 std::fill(std::begin(gbDefaultSurfaceUsed), std::end(gbDefaultSurfaceUsed), 0);
172
173
174 // Initialize world data
175
176 gpWorldLevelData = new MAP_ELEMENT[WORLD_MAX]{};
177
178 // Init room database
179 InitRoomDatabase( );
180
181 // INit tilesets
182 InitEngineTilesets( );
183 }
184
185
186 static void DestroyTileSurfaces(void);
187
188
DeinitializeWorld()189 void DeinitializeWorld( )
190 {
191 TrashWorld();
192
193 if ( gpWorldLevelData != NULL )
194 {
195 delete[] gpWorldLevelData;
196 gpWorldLevelData = nullptr;
197 }
198
199 DestroyTileSurfaces( );
200 FreeAllStructureFiles( );
201
202 // Shutdown tile database data
203 DeallocateTileDatabase( );
204 }
205
206
207 static void AddTileSurface(ST::string const& filename, UINT32 const tileType);
208
GetDefaultTileset()209 TileSetID GetDefaultTileset() {
210 return (gubNumTilesets == JA25_NUM_TILESETS) // If we have the number of tilesets for JA25 useJA25 default, else vanilla default
211 ? DEFAULT_JA25_TILESET
212 : GENERIC_1;
213 }
214
GetAdjustedTilesetResource(TileSetID tilesetID,UINT32 uiTileType,ST::string const & filePrefix)215 TILE_SURFACE_RESOURCE GetAdjustedTilesetResource(TileSetID tilesetID, UINT32 uiTileType, ST::string const& filePrefix)
216 {
217 if (tilesetID >= gubNumTilesets) throw std::logic_error("invalid tilesetID");
218
219 ST::string *filename = &gTilesets[tilesetID].zTileSurfaceFilenames[uiTileType];
220 if (filename->empty())
221 {
222 // Try loading from default tileset
223 tilesetID = (gubNumTilesets == JA25_NUM_TILESETS && uiTileType == SPECIALTILES)
224 ? DEFAULT_JA25_TILESET // If the map is SPECIALTILES (and JA25 tilesets available), use DEFAULT_JA25_TILESET
225 : GetDefaultTileset() // Else use default
226 ;
227 filename = &gTilesets[tilesetID].zTileSurfaceFilenames[uiTileType];
228 }
229
230 TILE_SURFACE_RESOURCE res;
231 res.tilesetID = tilesetID;
232 res.resourceFileName = GCM->getTilesetResourceName(tilesetID, filePrefix + *filename);
233 return res;
234 }
235
LoadTileSurfaces(TileSetID const tileset_id)236 static void LoadTileSurfaces(TileSetID const tileset_id)
237 try
238 {
239 SetRelativeStartAndEndPercentage(0, 1, 35, "Tile Surfaces");
240 for (UINT32 i = 0; i != NUMBEROFTILETYPES; ++i)
241 {
242 UINT32 const percentage = i * 100 / (NUMBEROFTILETYPES - 1);
243 RenderProgressBar(0, percentage);
244
245 auto res = GetAdjustedTilesetResource(tileset_id, i);
246 BOOLEAN fUseDefault = res.isDefaultTileset();
247
248 // don't load default surface if already loaded
249 if (fUseDefault && gbDefaultSurfaceUsed[i]) continue;
250
251 AddTileSurface(res.resourceFileName, i);
252
253 // OK, if we are the default tileset, set value indicating that!
254 gbDefaultSurfaceUsed[i] = fUseDefault;
255 }
256 }
257 catch (...)
258 {
259 DestroyTileSurfaces();
260 throw;
261 }
262
263
AddTileSurface(ST::string const & filename,UINT32 const type)264 static void AddTileSurface(ST::string const& filename, UINT32 const type)
265 {
266 TILE_IMAGERY*& slot = gTileSurfaceArray[type];
267
268 // Delete the surface first!
269 if (slot)
270 {
271 DeleteTileSurface(slot);
272 slot = NULL;
273 }
274
275 TILE_IMAGERY* const t = LoadTileSurface(filename);
276 t->fType = type;
277 SetRaisedObjectFlag(filename, t);
278
279 slot = t;
280
281 gbNewTileSurfaceLoaded[type] = TRUE;
282 }
283
284
285 extern BOOLEAN gfLoadShadeTablesFromTextFile;
286
287
BuildTileShadeTables()288 void BuildTileShadeTables()
289 {
290 if (gfLoadShadeTablesFromTextFile)
291 { /* Because we're tweaking the RGB values in the text file, always force
292 * rebuild the shadetables so that the user can tweak them in the same exe
293 * session. */
294 std::fill(std::begin(gbNewTileSurfaceLoaded), std::end(gbNewTileSurfaceLoaded), 1);
295 }
296
297 for (UINT32 i = 0; i != NUMBEROFTILETYPES; ++i)
298 {
299 TILE_IMAGERY const* const t = gTileSurfaceArray[i];
300 if (!t) continue;
301
302 // Don't create shade tables if default were already used once!
303 if(GameState::getInstance()->isEditorMode())
304 {
305 if (!gbNewTileSurfaceLoaded[i] && !gfEditorForceShadeTableRebuild) continue;
306 }
307 else
308 {
309 if (!gbNewTileSurfaceLoaded[i]) continue;
310 }
311 RenderProgressBar(0, i * 100 / NUMBEROFTILETYPES);
312 CreateTilePaletteTables(t->vo);
313 }
314 }
315
316
DestroyTileShadeTables(void)317 void DestroyTileShadeTables(void)
318 {
319 for (UINT32 i = 0; i < NUMBEROFTILETYPES; ++i)
320 {
321 const TILE_IMAGERY* const ti = gTileSurfaceArray[i];
322 if (ti == NULL) continue;
323
324 // Don't delete shade tables if default are still being used...
325 if(GameState::getInstance()->isEditorMode())
326 {
327 if (gbNewTileSurfaceLoaded[i] || gfEditorForceShadeTableRebuild)
328 {
329 ti->vo->DestroyPalettes();
330 }
331 }
332 else
333 {
334 if (gbNewTileSurfaceLoaded[i])
335 {
336 ti->vo->DestroyPalettes();
337 }
338 }
339 }
340 }
341
342
DestroyTileSurfaces(void)343 static void DestroyTileSurfaces(void)
344 {
345 FOR_EACH(TILE_IMAGERY*, i, gTileSurfaceArray)
346 {
347 if (!*i) continue;
348 DeleteTileSurface(*i);
349 *i = 0;
350 }
351 }
352
353
CompileWorldTerrainIDs(void)354 static void CompileWorldTerrainIDs(void)
355 {
356 for (INT16 sGridNo = 0; sGridNo < WORLD_MAX; ++sGridNo)
357 {
358 if (!GridNoOnVisibleWorldTile(sGridNo)) continue;
359
360 // Check if we have anything in object layer which has a terrain modifier
361 const LEVELNODE* n = gpWorldLevelData[sGridNo].pObjectHead;
362
363 if (n != NULL && giCurrentTilesetID == TEMP_19)
364 {
365 // ATE: CRAPOLA! Special case stuff here for the friggen pool since art was fu*ked up
366 switch (n->usIndex)
367 {
368 case ANOTHERDEBRIS4:
369 case ANOTHERDEBRIS6:
370 case ANOTHERDEBRIS7:
371 gpWorldLevelData[sGridNo].ubTerrainID = LOW_WATER;
372 continue;
373 }
374 }
375
376 if (n == NULL ||
377 n->usIndex >= NUMBEROFTILES ||
378 gTileDatabase[n->usIndex].ubTerrainID == NO_TERRAIN)
379 { // Try terrain instead!
380 n = gpWorldLevelData[sGridNo].pLandHead;
381 }
382
383 const TILE_ELEMENT* const te = &gTileDatabase[n->usIndex];
384 if (te->ubNumberOfTiles > 1)
385 {
386 for (UINT8 ubLoop = 0; ubLoop < te->ubNumberOfTiles; ++ubLoop)
387 {
388 const INT16 sTempGridNo = sGridNo + te->pTileLocData[ubLoop].bTileOffsetX + te->pTileLocData[ubLoop].bTileOffsetY * WORLD_COLS;
389 gpWorldLevelData[sTempGridNo].ubTerrainID = te->ubTerrainID;
390 }
391 }
392 else
393 {
394 gpWorldLevelData[sGridNo].ubTerrainID = te->ubTerrainID;
395 }
396 }
397 }
398
399
CompileTileMovementCosts(UINT16 usGridNo)400 static void CompileTileMovementCosts(UINT16 usGridNo)
401 {
402 UINT8 ubTerrainID;
403 LEVELNODE * pLand;
404
405 STRUCTURE * pStructure;
406 BOOLEAN fStructuresOnRoof;
407
408 UINT8 ubDirLoop;
409
410 if(!isValidGridNo(usGridNo))
411 {
412 return;
413 }
414
415 if ( GridNoOnVisibleWorldTile( usGridNo ) )
416 {
417 // check for land of a different height in adjacent locations
418 for ( ubDirLoop = 0; ubDirLoop < 8; ubDirLoop++ )
419 {
420 if ( gpWorldLevelData[ usGridNo ].sHeight !=
421 gpWorldLevelData[ usGridNo + DirectionInc( ubDirLoop ) ].sHeight )
422 {
423 SET_CURRMOVEMENTCOST( ubDirLoop, TRAVELCOST_OBSTACLE );
424 }
425 }
426
427 // check for exit grids
428 if ( ExitGridAtGridNo( usGridNo ) )
429 {
430 for (ubDirLoop=0; ubDirLoop < 8; ubDirLoop++)
431 {
432 SET_CURRMOVEMENTCOST( ubDirLoop, TRAVELCOST_EXITGRID );
433 }
434 // leave the roof alone, and continue, so that we can get values for the roof if traversable
435 }
436
437 }
438 else
439 {
440 for (ubDirLoop=0; ubDirLoop < 8; ubDirLoop++)
441 {
442 SET_MOVEMENTCOST( usGridNo, ubDirLoop, 0, TRAVELCOST_OFF_MAP );
443 SET_MOVEMENTCOST( usGridNo, ubDirLoop, 1, TRAVELCOST_OFF_MAP );
444 }
445 if (gpWorldLevelData[usGridNo].pStructureHead == NULL)
446 {
447 return;
448 }
449 }
450
451 if (gpWorldLevelData[usGridNo].pStructureHead != NULL)
452 { // structures in tile
453 // consider the land
454 pLand = gpWorldLevelData[ usGridNo ].pLandHead;
455 if ( pLand != NULL )
456 {
457 // Set TEMPORARY cost here
458
459 // Get terrain type
460 ubTerrainID = gpWorldLevelData[usGridNo].ubTerrainID; // = GetTerrainType( (INT16)usGridNo );
461
462 for (ubDirLoop=0; ubDirLoop < NUM_WORLD_DIRECTIONS; ubDirLoop++)
463 {
464 SET_CURRMOVEMENTCOST( ubDirLoop, gTileTypeMovementCost[ ubTerrainID ] );
465 }
466 }
467
468 // now consider all structures
469 pStructure = gpWorldLevelData[usGridNo].pStructureHead;
470 fStructuresOnRoof = FALSE;
471 do
472 {
473 if (pStructure->sCubeOffset == STRUCTURE_ON_GROUND)
474 {
475 if (pStructure->fFlags & STRUCTURE_PASSABLE)
476 {
477 if (pStructure->fFlags & STRUCTURE_WIREFENCE && pStructure->fFlags & STRUCTURE_OPEN)
478 {
479 // prevent movement along the fence but allow in all other directions
480 switch( pStructure->ubWallOrientation )
481 {
482 case OUTSIDE_TOP_LEFT:
483 case INSIDE_TOP_LEFT:
484 SET_CURRMOVEMENTCOST( NORTH, TRAVELCOST_NOT_STANDING );
485 SET_CURRMOVEMENTCOST( NORTHEAST, TRAVELCOST_NOT_STANDING );
486 SET_CURRMOVEMENTCOST( EAST, TRAVELCOST_OBSTACLE );
487 SET_CURRMOVEMENTCOST( SOUTHEAST, TRAVELCOST_NOT_STANDING );
488 SET_CURRMOVEMENTCOST( SOUTH, TRAVELCOST_NOT_STANDING );
489 SET_CURRMOVEMENTCOST( SOUTHWEST, TRAVELCOST_NOT_STANDING );
490 SET_CURRMOVEMENTCOST( WEST, TRAVELCOST_OBSTACLE );
491 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_NOT_STANDING );
492 break;
493
494 case OUTSIDE_TOP_RIGHT:
495 case INSIDE_TOP_RIGHT:
496 SET_CURRMOVEMENTCOST( NORTH, TRAVELCOST_OBSTACLE );
497 SET_CURRMOVEMENTCOST( NORTHEAST, TRAVELCOST_NOT_STANDING );
498 SET_CURRMOVEMENTCOST( EAST, TRAVELCOST_NOT_STANDING );
499 SET_CURRMOVEMENTCOST( SOUTHEAST, TRAVELCOST_NOT_STANDING );
500 SET_CURRMOVEMENTCOST( SOUTH, TRAVELCOST_OBSTACLE );
501 SET_CURRMOVEMENTCOST( SOUTHWEST, TRAVELCOST_NOT_STANDING );
502 SET_CURRMOVEMENTCOST( WEST, TRAVELCOST_NOT_STANDING );
503 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_NOT_STANDING );
504 break;
505 }
506 }
507 // all other passable structures do not block movement in any way
508 }
509 else if (pStructure->fFlags & STRUCTURE_BLOCKSMOVES)
510 {
511 if ( (pStructure->fFlags & STRUCTURE_FENCE) && !(pStructure->fFlags & STRUCTURE_SPECIAL) )
512 {
513 // jumpable!
514 switch( pStructure->ubWallOrientation )
515 {
516 case OUTSIDE_TOP_LEFT:
517 case INSIDE_TOP_LEFT:
518 // can be jumped north and south
519 SET_CURRMOVEMENTCOST( NORTH, TRAVELCOST_FENCE );
520 SET_CURRMOVEMENTCOST( NORTHEAST, TRAVELCOST_OBSTACLE );
521 SET_CURRMOVEMENTCOST( EAST, TRAVELCOST_OBSTACLE );
522 SET_CURRMOVEMENTCOST( SOUTHEAST, TRAVELCOST_OBSTACLE );
523 SET_CURRMOVEMENTCOST( SOUTH, TRAVELCOST_FENCE );
524 SET_CURRMOVEMENTCOST( SOUTHWEST, TRAVELCOST_OBSTACLE );
525 SET_CURRMOVEMENTCOST( WEST, TRAVELCOST_OBSTACLE );
526 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_OBSTACLE );
527 // set values for the tiles EXITED from this location
528 FORCE_SET_MOVEMENTCOST( usGridNo - WORLD_COLS, NORTH, 0, TRAVELCOST_NONE );
529 SET_MOVEMENTCOST( usGridNo - WORLD_COLS + 1, NORTHEAST, 0, TRAVELCOST_OBSTACLE );
530 SET_MOVEMENTCOST( usGridNo + 1, EAST, 0, TRAVELCOST_OBSTACLE );
531 SET_MOVEMENTCOST( usGridNo + WORLD_COLS + 1, SOUTHEAST, 0, TRAVELCOST_OBSTACLE );
532 FORCE_SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTH, 0, TRAVELCOST_NONE );
533 SET_MOVEMENTCOST( usGridNo + WORLD_COLS - 1, SOUTHWEST, 0, TRAVELCOST_OBSTACLE );
534 SET_MOVEMENTCOST( usGridNo - 1, WEST, 0, TRAVELCOST_OBSTACLE );
535 SET_MOVEMENTCOST( usGridNo - WORLD_COLS - 1, NORTHWEST, 0, TRAVELCOST_OBSTACLE );
536 break;
537
538 case OUTSIDE_TOP_RIGHT:
539 case INSIDE_TOP_RIGHT:
540 // can be jumped east and west
541 SET_CURRMOVEMENTCOST( NORTH, TRAVELCOST_OBSTACLE );
542 SET_CURRMOVEMENTCOST( NORTHEAST, TRAVELCOST_OBSTACLE );
543 SET_CURRMOVEMENTCOST( EAST, TRAVELCOST_FENCE );
544 SET_CURRMOVEMENTCOST( SOUTHEAST, TRAVELCOST_OBSTACLE );
545 SET_CURRMOVEMENTCOST( SOUTH, TRAVELCOST_OBSTACLE );
546 SET_CURRMOVEMENTCOST( SOUTHWEST, TRAVELCOST_OBSTACLE );
547 SET_CURRMOVEMENTCOST( WEST, TRAVELCOST_FENCE );
548 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_OBSTACLE );
549 // set values for the tiles EXITED from this location
550 SET_MOVEMENTCOST( usGridNo - WORLD_COLS, NORTH, 0, TRAVELCOST_OBSTACLE );
551 SET_MOVEMENTCOST( usGridNo - WORLD_COLS + 1, NORTHEAST, 0, TRAVELCOST_OBSTACLE );
552 // make sure no obstacle costs exists before changing path cost to 0
553 if ( gubWorldMovementCosts[ usGridNo + 1 ][ EAST ][ 0 ] < TRAVELCOST_BLOCKED )
554 {
555 FORCE_SET_MOVEMENTCOST( usGridNo + 1, EAST, 0, TRAVELCOST_NONE );
556 }
557 SET_MOVEMENTCOST( usGridNo + WORLD_COLS + 1, SOUTHEAST, 0, TRAVELCOST_OBSTACLE );
558 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTH, 0, TRAVELCOST_OBSTACLE );
559 SET_MOVEMENTCOST( usGridNo + WORLD_COLS - 1, SOUTHWEST, 0, TRAVELCOST_OBSTACLE );
560 if ( gubWorldMovementCosts[ usGridNo - 1 ][ WEST ][ 0 ] < TRAVELCOST_BLOCKED )
561 {
562 FORCE_SET_MOVEMENTCOST( usGridNo - 1, WEST, 0, TRAVELCOST_NONE );
563 }
564 SET_MOVEMENTCOST( usGridNo - WORLD_COLS - 1, NORTHWEST, 0, TRAVELCOST_OBSTACLE );
565 break;
566
567 default:
568 // corners aren't jumpable
569 for (ubDirLoop=0; ubDirLoop < NUM_WORLD_DIRECTIONS; ubDirLoop++)
570 {
571 SET_CURRMOVEMENTCOST( ubDirLoop, TRAVELCOST_OBSTACLE );
572 }
573 break;
574 }
575 }
576 else if ( pStructure->pDBStructureRef->pDBStructure->ubArmour == MATERIAL_SANDBAG && StructureHeight( pStructure ) < 2 )
577 {
578 for (ubDirLoop=0; ubDirLoop < NUM_WORLD_DIRECTIONS; ubDirLoop++)
579 {
580 SET_CURRMOVEMENTCOST( ubDirLoop, TRAVELCOST_OBSTACLE );
581 }
582
583 if (FindStructure(usGridNo - WORLD_COLS, STRUCTURE_OBSTACLE) == NULL &&
584 FindStructure(usGridNo + WORLD_COLS, STRUCTURE_OBSTACLE) == NULL)
585 {
586 FORCE_SET_MOVEMENTCOST( usGridNo, NORTH, 0, TRAVELCOST_FENCE );
587 FORCE_SET_MOVEMENTCOST( usGridNo, SOUTH, 0, TRAVELCOST_FENCE );
588 }
589
590 if (FindStructure(usGridNo - 1, STRUCTURE_OBSTACLE) == NULL &&
591 FindStructure(usGridNo + 1, STRUCTURE_OBSTACLE) == NULL)
592 {
593 FORCE_SET_MOVEMENTCOST( usGridNo, EAST, 0, TRAVELCOST_FENCE );
594 FORCE_SET_MOVEMENTCOST( usGridNo, WEST, 0, TRAVELCOST_FENCE );
595 }
596 }
597 else if ( (pStructure->fFlags & STRUCTURE_CAVEWALL ) )
598 {
599 for (ubDirLoop=0; ubDirLoop < NUM_WORLD_DIRECTIONS; ubDirLoop++)
600 {
601 SET_CURRMOVEMENTCOST( ubDirLoop, TRAVELCOST_CAVEWALL );
602 }
603 }
604 else
605 {
606 for (ubDirLoop=0; ubDirLoop < NUM_WORLD_DIRECTIONS; ubDirLoop++)
607 {
608 SET_CURRMOVEMENTCOST( ubDirLoop, TRAVELCOST_OBSTACLE );
609 }
610 }
611 }
612 else if (pStructure->fFlags & STRUCTURE_ANYDOOR) /*&& (pStructure->fFlags & STRUCTURE_OPEN))*/
613 { // NB closed doors are treated just like walls, in the section after this
614
615 if (pStructure->fFlags & STRUCTURE_DDOOR_LEFT && (pStructure->ubWallOrientation == INSIDE_TOP_RIGHT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT) )
616 {
617 // double door, left side (as you look on the screen)
618 switch( pStructure->ubWallOrientation )
619 {
620 case OUTSIDE_TOP_RIGHT:
621 if (pStructure->fFlags & STRUCTURE_BASE_TILE)
622 { // doorpost
623 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_WALL );
624 SET_CURRMOVEMENTCOST( WEST, TRAVELCOST_DOOR_CLOSED_HERE );
625 SET_CURRMOVEMENTCOST( SOUTHWEST, TRAVELCOST_WALL );
626 SET_MOVEMENTCOST( usGridNo + 1, NORTHEAST, 0, TRAVELCOST_WALL );
627 SET_MOVEMENTCOST( usGridNo + 1, EAST, 0, TRAVELCOST_DOOR_CLOSED_W );
628 SET_MOVEMENTCOST( usGridNo + 1, SOUTHEAST, 0, TRAVELCOST_WALL );
629 // corner
630 SET_MOVEMENTCOST( usGridNo + 1 + WORLD_COLS, SOUTHEAST, 0, TRAVELCOST_WALL );
631 }
632 else
633 { // door
634 SET_CURRMOVEMENTCOST( NORTH, TRAVELCOST_DOOR_OPEN_W );
635 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_DOOR_OPEN_W );
636 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTH, 0, TRAVELCOST_DOOR_OPEN_NW );
637 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHWEST, 0, TRAVELCOST_DOOR_OPEN_NW );
638 SET_MOVEMENTCOST( usGridNo + 1, NORTHEAST, 0, TRAVELCOST_DOOR_OPEN_W_W );
639 SET_MOVEMENTCOST( usGridNo + WORLD_COLS + 1, SOUTHEAST, 0, TRAVELCOST_DOOR_OPEN_NW_W );
640 }
641 break;
642
643 case INSIDE_TOP_RIGHT:
644 // doorpost
645 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_WALL );
646 SET_MOVEMENTCOST( usGridNo + 1,NORTHEAST, 0, TRAVELCOST_WALL );
647 // door
648 SET_CURRMOVEMENTCOST( NORTH, TRAVELCOST_DOOR_OPEN_HERE );
649 SET_CURRMOVEMENTCOST( NORTHEAST, TRAVELCOST_DOOR_OPEN_HERE );
650 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTH, 0, TRAVELCOST_DOOR_OPEN_N );
651 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHEAST, 0, TRAVELCOST_DOOR_OPEN_N );
652 SET_MOVEMENTCOST( usGridNo - 1, NORTHWEST, 0, TRAVELCOST_DOOR_OPEN_E );
653 SET_MOVEMENTCOST( usGridNo + WORLD_COLS - 1, SOUTHWEST, 0, TRAVELCOST_DOOR_OPEN_NE );
654 break;
655
656 default:
657 // door with no orientation specified!?
658 break;
659 }
660 }
661 else if (pStructure->fFlags & STRUCTURE_DDOOR_RIGHT && (pStructure->ubWallOrientation == INSIDE_TOP_LEFT || pStructure->ubWallOrientation == OUTSIDE_TOP_LEFT) )
662 {
663 // double door, right side (as you look on the screen)
664 switch( pStructure->ubWallOrientation )
665 {
666 case OUTSIDE_TOP_LEFT:
667 if (pStructure->fFlags & STRUCTURE_BASE_TILE)
668 { // doorpost
669 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_WALL );
670 SET_CURRMOVEMENTCOST( NORTH, TRAVELCOST_DOOR_CLOSED_HERE );
671 SET_CURRMOVEMENTCOST( NORTHEAST, TRAVELCOST_WALL );
672 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHWEST, 0, TRAVELCOST_WALL );
673 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTH, 0, TRAVELCOST_DOOR_CLOSED_N )
674 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHEAST, 0, TRAVELCOST_WALL ); ;
675 // corner
676 SET_MOVEMENTCOST( usGridNo + 1 ,NORTHEAST, 0, TRAVELCOST_WALL );
677 }
678 else
679 { // door
680 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_DOOR_OPEN_N );
681 SET_CURRMOVEMENTCOST( WEST, TRAVELCOST_DOOR_OPEN_N );
682 SET_MOVEMENTCOST( usGridNo + 1, EAST, 0, TRAVELCOST_DOOR_OPEN_NW );
683 SET_MOVEMENTCOST( usGridNo + 1, NORTHEAST, 0, TRAVELCOST_DOOR_OPEN_NW );
684 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHWEST, 0, TRAVELCOST_DOOR_OPEN_N_N );
685 SET_MOVEMENTCOST( usGridNo + WORLD_COLS + 1, SOUTHEAST, 0, TRAVELCOST_DOOR_OPEN_NW_N );
686 }
687 break;
688
689 case INSIDE_TOP_LEFT:
690 // doorpost
691 SET_CURRMOVEMENTCOST( NORTHEAST, TRAVELCOST_WALL );
692 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHWEST, 0, TRAVELCOST_WALL );
693 // corner
694 SET_MOVEMENTCOST( usGridNo + 1 ,NORTHEAST, 0, TRAVELCOST_WALL );
695 // door
696 SET_CURRMOVEMENTCOST( WEST, TRAVELCOST_DOOR_OPEN_HERE );
697 SET_CURRMOVEMENTCOST( SOUTHWEST, TRAVELCOST_DOOR_OPEN_HERE );
698 SET_MOVEMENTCOST( usGridNo + 1, EAST, 0, TRAVELCOST_DOOR_OPEN_W );
699 SET_MOVEMENTCOST( usGridNo + 1, SOUTHEAST, 0, TRAVELCOST_DOOR_OPEN_W );
700 SET_MOVEMENTCOST( usGridNo - WORLD_COLS, NORTHWEST, 0, TRAVELCOST_DOOR_OPEN_S );
701 SET_MOVEMENTCOST( usGridNo - WORLD_COLS + 1, NORTHEAST, 0, TRAVELCOST_DOOR_OPEN_SW );
702 break;
703 default:
704 // door with no orientation specified!?
705 break;
706 }
707 }
708 else if (pStructure->fFlags & STRUCTURE_SLIDINGDOOR && pStructure->pDBStructureRef->pDBStructure->ubNumberOfTiles > 1)
709 {
710 switch( pStructure->ubWallOrientation )
711 {
712 case OUTSIDE_TOP_LEFT:
713 case INSIDE_TOP_LEFT:
714 // doorframe post in one corner of each of the tiles
715 if (pStructure->fFlags & STRUCTURE_BASE_TILE)
716 {
717 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_WALL );
718 SET_CURRMOVEMENTCOST( NORTH, TRAVELCOST_DOOR_CLOSED_HERE );
719 SET_CURRMOVEMENTCOST( NORTHEAST, TRAVELCOST_DOOR_CLOSED_HERE );
720 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHWEST, 0, TRAVELCOST_WALL );
721 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTH, 0, TRAVELCOST_DOOR_CLOSED_N );
722 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHEAST, 0, TRAVELCOST_DOOR_CLOSED_N );
723
724 }
725 else
726 {
727 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_DOOR_CLOSED_HERE );
728 SET_CURRMOVEMENTCOST( NORTH, TRAVELCOST_DOOR_CLOSED_HERE );
729 SET_CURRMOVEMENTCOST( NORTHEAST, TRAVELCOST_WALL );
730 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHWEST, 0, TRAVELCOST_DOOR_CLOSED_N);
731 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTH, 0, TRAVELCOST_DOOR_CLOSED_N);
732 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHEAST, 0, TRAVELCOST_WALL );
733
734 }
735 break;
736 case OUTSIDE_TOP_RIGHT:
737 case INSIDE_TOP_RIGHT:
738 // doorframe post in one corner of each of the tiles
739 if (pStructure->fFlags & STRUCTURE_BASE_TILE)
740 {
741 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_WALL );
742 SET_CURRMOVEMENTCOST( WEST, TRAVELCOST_DOOR_CLOSED_HERE );
743 SET_CURRMOVEMENTCOST( SOUTHWEST, TRAVELCOST_DOOR_CLOSED_HERE );
744
745 SET_MOVEMENTCOST( usGridNo + 1, NORTHEAST, 0, TRAVELCOST_WALL );
746 SET_MOVEMENTCOST( usGridNo + 1, EAST, 0, TRAVELCOST_DOOR_CLOSED_W );
747 SET_MOVEMENTCOST( usGridNo + 1, SOUTHEAST, 0, TRAVELCOST_DOOR_CLOSED_W );
748 }
749 else
750 {
751 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_DOOR_CLOSED_HERE );
752 SET_CURRMOVEMENTCOST( WEST, TRAVELCOST_DOOR_CLOSED_HERE );
753 SET_CURRMOVEMENTCOST( SOUTHWEST, TRAVELCOST_WALL );
754
755 SET_MOVEMENTCOST( usGridNo + 1, NORTHEAST, 0, TRAVELCOST_DOOR_CLOSED_W );
756 SET_MOVEMENTCOST( usGridNo + 1, EAST, 0, TRAVELCOST_DOOR_CLOSED_W );
757 SET_MOVEMENTCOST( usGridNo + 1, SOUTHEAST, 0, TRAVELCOST_WALL );
758 }
759 break;
760 }
761 }
762 else
763 {
764 // standard door
765 switch( pStructure->ubWallOrientation )
766 {
767 case OUTSIDE_TOP_LEFT:
768 if (pStructure->fFlags & STRUCTURE_BASE_TILE)
769 { // doorframe
770 SET_CURRMOVEMENTCOST( NORTHEAST, TRAVELCOST_WALL );
771 SET_CURRMOVEMENTCOST( NORTH, TRAVELCOST_DOOR_CLOSED_HERE );
772 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_WALL );
773
774 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHEAST, 0, TRAVELCOST_WALL );
775 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTH, 0, TRAVELCOST_DOOR_CLOSED_N );
776 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHWEST, 0, TRAVELCOST_WALL );
777
778 // DO CORNERS
779 SET_MOVEMENTCOST( usGridNo - 1, NORTHWEST, 0, TRAVELCOST_WALL );
780 SET_MOVEMENTCOST( usGridNo + 1, NORTHEAST, 0, TRAVELCOST_WALL );
781 SET_MOVEMENTCOST( usGridNo + WORLD_COLS - 1, SOUTHWEST, 0, TRAVELCOST_WALL );
782 SET_MOVEMENTCOST( usGridNo + WORLD_COLS + 1, SOUTHEAST, 0, TRAVELCOST_WALL );
783
784
785 //SET_CURRMOVEMENTCOST( NORTHEAST, TRAVELCOST_OBSTACLE );
786 //SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_OBSTACLE );
787 //SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHEAST, 0, TRAVELCOST_OBSTACLE );
788 //SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHWEST, 0, TRAVELCOST_OBSTACLE );
789 // corner
790 //SET_MOVEMENTCOST( usGridNo + 1 ,NORTHEAST, 0, TRAVELCOST_OBSTACLE );
791 }
792 else if (!(pStructure->fFlags & STRUCTURE_SLIDINGDOOR))
793 { // door
794 SET_CURRMOVEMENTCOST( NORTHEAST, TRAVELCOST_WALL );
795 SET_CURRMOVEMENTCOST( EAST, TRAVELCOST_DOOR_OPEN_N );
796 SET_MOVEMENTCOST( usGridNo - 1, WEST, 0, TRAVELCOST_DOOR_OPEN_NE );
797 SET_MOVEMENTCOST( usGridNo - 1, NORTHWEST, 0, TRAVELCOST_WALL );
798 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHEAST, 0, TRAVELCOST_DOOR_OPEN_N_N );
799 SET_MOVEMENTCOST( usGridNo + WORLD_COLS - 1, SOUTHWEST, 0, TRAVELCOST_DOOR_OPEN_NE_N );
800 }
801 break;
802
803 case INSIDE_TOP_LEFT:
804 SET_CURRMOVEMENTCOST( NORTHEAST, TRAVELCOST_WALL );
805 SET_CURRMOVEMENTCOST( NORTH, TRAVELCOST_DOOR_CLOSED_HERE );
806 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_WALL );
807
808 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHEAST, 0, TRAVELCOST_OBSTACLE );
809 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTH, 0, TRAVELCOST_DOOR_CLOSED_N );
810 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHWEST, 0, TRAVELCOST_OBSTACLE );
811
812 // DO CORNERS
813 SET_MOVEMENTCOST( usGridNo - 1, NORTHWEST, 0, TRAVELCOST_OBSTACLE );
814 SET_MOVEMENTCOST( usGridNo + 1, NORTHEAST, 0, TRAVELCOST_OBSTACLE );
815 SET_MOVEMENTCOST( usGridNo + WORLD_COLS - 1, SOUTHWEST, 0, TRAVELCOST_OBSTACLE );
816 SET_MOVEMENTCOST( usGridNo + WORLD_COLS + 1, SOUTHEAST, 0, TRAVELCOST_OBSTACLE );
817
818 // doorframe
819 //SET_CURRMOVEMENTCOST( NORTHEAST, TRAVELCOST_OBSTACLE );
820 //SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_OBSTACLE );
821 //SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHEAST, 0, TRAVELCOST_OBSTACLE );
822 //SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHWEST, 0, TRAVELCOST_OBSTACLE );
823 // corner
824 //SET_MOVEMENTCOST( usGridNo + 1 ,NORTHEAST, 0, TRAVELCOST_OBSTACLE );
825 // door
826 if (!(pStructure->fFlags & STRUCTURE_SLIDINGDOOR))
827 {
828 SET_CURRMOVEMENTCOST( EAST, TRAVELCOST_DOOR_OPEN_HERE );
829 SET_CURRMOVEMENTCOST( SOUTHEAST, TRAVELCOST_DOOR_OPEN_HERE );
830 SET_MOVEMENTCOST( usGridNo - 1, WEST, 0, TRAVELCOST_DOOR_OPEN_E );
831 SET_MOVEMENTCOST( usGridNo - 1, SOUTHWEST, 0, TRAVELCOST_DOOR_OPEN_E );
832 SET_MOVEMENTCOST( usGridNo - WORLD_COLS, NORTHEAST, 0, TRAVELCOST_DOOR_OPEN_S );
833 SET_MOVEMENTCOST( usGridNo - WORLD_COLS - 1, NORTHWEST, 0, TRAVELCOST_DOOR_OPEN_SE );
834 }
835 break;
836
837 case OUTSIDE_TOP_RIGHT:
838 if (pStructure->fFlags & STRUCTURE_BASE_TILE)
839 { // doorframe
840 SET_CURRMOVEMENTCOST( SOUTHWEST, TRAVELCOST_OBSTACLE );
841 SET_CURRMOVEMENTCOST( WEST, TRAVELCOST_DOOR_CLOSED_HERE );
842 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_OBSTACLE );
843
844 SET_MOVEMENTCOST( usGridNo + 1, SOUTHEAST, 0, TRAVELCOST_OBSTACLE );
845 SET_MOVEMENTCOST( usGridNo + 1, EAST, 0, TRAVELCOST_DOOR_CLOSED_W );
846 SET_MOVEMENTCOST( usGridNo + 1, NORTHEAST, 0, TRAVELCOST_OBSTACLE );
847
848 // DO CORNERS
849 SET_MOVEMENTCOST( usGridNo - WORLD_COLS + 1, NORTHEAST, 0, TRAVELCOST_OBSTACLE );
850 SET_MOVEMENTCOST( usGridNo - WORLD_COLS, NORTHWEST, 0, TRAVELCOST_OBSTACLE );
851 SET_MOVEMENTCOST( usGridNo + WORLD_COLS + 1, SOUTHEAST, 0, TRAVELCOST_OBSTACLE );
852 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHWEST, 0, TRAVELCOST_OBSTACLE );
853
854 //SET_CURRMOVEMENTCOST( SOUTHWEST, TRAVELCOST_OBSTACLE );
855 //SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_OBSTACLE );
856 //SET_MOVEMENTCOST( usGridNo + 1, SOUTHEAST, 0, TRAVELCOST_OBSTACLE );
857 //SET_MOVEMENTCOST( usGridNo + 1, NORTHEAST, 0, TRAVELCOST_OBSTACLE );
858 // corner
859 //SET_MOVEMENTCOST( usGridNo + 1 + WORLD_COLS, SOUTHEAST, 0, TRAVELCOST_OBSTACLE );
860 }
861 else if (!(pStructure->fFlags & STRUCTURE_SLIDINGDOOR))
862 { // door
863 SET_CURRMOVEMENTCOST( SOUTH, TRAVELCOST_DOOR_OPEN_W );
864 SET_CURRMOVEMENTCOST( SOUTHWEST, TRAVELCOST_DOOR_OPEN_W );
865 SET_MOVEMENTCOST( usGridNo - WORLD_COLS, NORTH, 0, TRAVELCOST_DOOR_OPEN_SW );
866 SET_MOVEMENTCOST( usGridNo - WORLD_COLS, NORTHWEST, 0, TRAVELCOST_DOOR_OPEN_SW );
867 SET_MOVEMENTCOST( usGridNo + 1, SOUTHEAST, 0, TRAVELCOST_DOOR_OPEN_W_W );
868 SET_MOVEMENTCOST( usGridNo - WORLD_COLS + 1, NORTHEAST, 0, TRAVELCOST_DOOR_OPEN_SW_W );
869 }
870 break;
871
872 case INSIDE_TOP_RIGHT:
873 SET_CURRMOVEMENTCOST( SOUTHWEST, TRAVELCOST_OBSTACLE );
874 SET_CURRMOVEMENTCOST( WEST, TRAVELCOST_DOOR_CLOSED_HERE );
875 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_OBSTACLE );
876
877 SET_MOVEMENTCOST( usGridNo + 1, SOUTHEAST, 0, TRAVELCOST_OBSTACLE );
878 SET_MOVEMENTCOST( usGridNo + 1, EAST, 0, TRAVELCOST_DOOR_CLOSED_W );
879 SET_MOVEMENTCOST( usGridNo + 1, NORTHEAST, 0, TRAVELCOST_OBSTACLE );
880
881 // DO CORNERS
882 SET_MOVEMENTCOST( usGridNo - WORLD_COLS + 1, NORTHEAST, 0, TRAVELCOST_OBSTACLE );
883 SET_MOVEMENTCOST( usGridNo - WORLD_COLS, NORTHWEST, 0, TRAVELCOST_OBSTACLE );
884 SET_MOVEMENTCOST( usGridNo + WORLD_COLS + 1, SOUTHEAST, 0, TRAVELCOST_OBSTACLE );
885 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHWEST, 0, TRAVELCOST_OBSTACLE );
886
887 // doorframe
888 /*
889 SET_CURRMOVEMENTCOST( SOUTHWEST, TRAVELCOST_OBSTACLE );
890 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_OBSTACLE );
891 SET_MOVEMENTCOST( usGridNo + 1,SOUTHEAST, 0, TRAVELCOST_OBSTACLE );
892 SET_MOVEMENTCOST( usGridNo + 1,NORTHEAST, 0, TRAVELCOST_OBSTACLE );
893 // corner
894 SET_MOVEMENTCOST( usGridNo - WORLD_COLS, NORTHWEST, 0, TRAVELCOST_OBSTACLE );
895 */
896 if (!(pStructure->fFlags & STRUCTURE_SLIDINGDOOR))
897 {
898 // door
899 SET_CURRMOVEMENTCOST( SOUTH, TRAVELCOST_DOOR_OPEN_HERE );
900 SET_CURRMOVEMENTCOST( SOUTHEAST, TRAVELCOST_DOOR_OPEN_HERE );
901 SET_MOVEMENTCOST( usGridNo - WORLD_COLS, NORTH, 0, TRAVELCOST_DOOR_OPEN_S );
902 SET_MOVEMENTCOST( usGridNo - WORLD_COLS, NORTHEAST, 0, TRAVELCOST_DOOR_OPEN_S );
903 SET_MOVEMENTCOST( usGridNo - 1, SOUTHWEST, 0, TRAVELCOST_DOOR_OPEN_E );
904 SET_MOVEMENTCOST( usGridNo - WORLD_COLS - 1, NORTHWEST, 0, TRAVELCOST_DOOR_OPEN_SE );
905 }
906 break;
907
908 default:
909 // door with no orientation specified!?
910 break;
911 }
912 }
913
914 /*
915 switch( pStructure->ubWallOrientation )
916 {
917 case OUTSIDE_TOP_LEFT:
918 case INSIDE_TOP_LEFT:
919 SET_CURRMOVEMENTCOST( NORTHEAST, TRAVELCOST_OBSTACLE );
920 SET_CURRMOVEMENTCOST( NORTH, TRAVELCOST_DOOR_CLOSED_HERE );
921 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_OBSTACLE );
922
923 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHEAST, 0, TRAVELCOST_OBSTACLE );
924 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTH, 0, TRAVELCOST_DOOR_CLOSED_N );
925 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHWEST, 0, TRAVELCOST_OBSTACLE );
926
927 // DO CORNERS
928 SET_MOVEMENTCOST( usGridNo - 1, NORTHWEST, 0, TRAVELCOST_OBSTACLE );
929 SET_MOVEMENTCOST( usGridNo + 1, NORTHEAST, 0, TRAVELCOST_OBSTACLE );
930 SET_MOVEMENTCOST( usGridNo + WORLD_COLS - 1, SOUTHWEST, 0, TRAVELCOST_OBSTACLE );
931 SET_MOVEMENTCOST( usGridNo + WORLD_COLS + 1, SOUTHEAST, 0, TRAVELCOST_OBSTACLE );
932 break;
933
934 case OUTSIDE_TOP_RIGHT:
935 case INSIDE_TOP_RIGHT:
936 SET_CURRMOVEMENTCOST( SOUTHWEST, TRAVELCOST_OBSTACLE );
937 SET_CURRMOVEMENTCOST( WEST, TRAVELCOST_DOOR_CLOSED_HERE );
938 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_OBSTACLE );
939
940 SET_MOVEMENTCOST( usGridNo + 1, SOUTHEAST, 0, TRAVELCOST_OBSTACLE );
941 SET_MOVEMENTCOST( usGridNo + 1, EAST, 0, TRAVELCOST_DOOR_CLOSED_W );
942 SET_MOVEMENTCOST( usGridNo + 1, NORTHEAST, 0, TRAVELCOST_OBSTACLE );
943
944 // DO CORNERS
945 SET_MOVEMENTCOST( usGridNo - WORLD_COLS + 1, NORTHEAST, 0, TRAVELCOST_OBSTACLE );
946 SET_MOVEMENTCOST( usGridNo - WORLD_COLS, NORTHWEST, 0, TRAVELCOST_OBSTACLE );
947 SET_MOVEMENTCOST( usGridNo + WORLD_COLS + 1, SOUTHEAST, 0, TRAVELCOST_OBSTACLE );
948 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHWEST, 0, TRAVELCOST_OBSTACLE );
949 break;
950
951 default:
952 // wall with no orientation specified!?
953 break;
954 }
955 */
956
957
958 }
959 else if (pStructure->fFlags & STRUCTURE_WALLSTUFF )
960 {
961 //ATE: IF a closed door, set to door value
962 switch( pStructure->ubWallOrientation )
963 {
964 case OUTSIDE_TOP_LEFT:
965 case INSIDE_TOP_LEFT:
966 SET_CURRMOVEMENTCOST( NORTHEAST, TRAVELCOST_WALL );
967 SET_CURRMOVEMENTCOST( NORTH, TRAVELCOST_WALL );
968 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_WALL );
969 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHEAST, 0, TRAVELCOST_WALL );
970 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTH, 0, TRAVELCOST_WALL );
971 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHWEST, 0, TRAVELCOST_WALL );
972
973 // DO CORNERS
974 SET_MOVEMENTCOST( usGridNo - 1, NORTHWEST, 0, TRAVELCOST_WALL );
975 SET_MOVEMENTCOST( usGridNo + 1, NORTHEAST, 0, TRAVELCOST_WALL );
976 SET_MOVEMENTCOST( usGridNo + WORLD_COLS - 1, SOUTHWEST, 0, TRAVELCOST_WALL );
977 SET_MOVEMENTCOST( usGridNo + WORLD_COLS + 1, SOUTHEAST, 0, TRAVELCOST_WALL );
978 break;
979
980 case OUTSIDE_TOP_RIGHT:
981 case INSIDE_TOP_RIGHT:
982 SET_CURRMOVEMENTCOST( SOUTHWEST, TRAVELCOST_WALL );
983 SET_CURRMOVEMENTCOST( WEST, TRAVELCOST_WALL );
984 SET_CURRMOVEMENTCOST( NORTHWEST, TRAVELCOST_WALL );
985 SET_MOVEMENTCOST( usGridNo + 1, SOUTHEAST, 0, TRAVELCOST_WALL );
986 SET_MOVEMENTCOST( usGridNo + 1, EAST, 0, TRAVELCOST_WALL );
987 SET_MOVEMENTCOST( usGridNo + 1, NORTHEAST, 0, TRAVELCOST_WALL );
988
989 // DO CORNERS
990 SET_MOVEMENTCOST( usGridNo - WORLD_COLS + 1, NORTHEAST, 0, TRAVELCOST_WALL );
991 SET_MOVEMENTCOST( usGridNo - WORLD_COLS, NORTHWEST, 0, TRAVELCOST_WALL );
992 SET_MOVEMENTCOST( usGridNo + WORLD_COLS + 1, SOUTHEAST, 0, TRAVELCOST_WALL );
993 SET_MOVEMENTCOST( usGridNo + WORLD_COLS, SOUTHWEST, 0, TRAVELCOST_WALL );
994 break;
995
996 default:
997 // wall with no orientation specified!?
998 break;
999 }
1000 }
1001 }
1002 else
1003 {
1004 if (!(pStructure->fFlags & STRUCTURE_PASSABLE || pStructure->fFlags & STRUCTURE_NORMAL_ROOF))
1005 {
1006 fStructuresOnRoof = TRUE;
1007 }
1008 }
1009 pStructure = pStructure->pNext;
1010 } while (pStructure != NULL);
1011
1012 // HIGHEST LAYER
1013 if ((gpWorldLevelData[ usGridNo ].pRoofHead != NULL))
1014 {
1015 if (!fStructuresOnRoof)
1016 {
1017 for (ubDirLoop=0; ubDirLoop < 8; ubDirLoop++)
1018 {
1019 SET_MOVEMENTCOST( usGridNo, ubDirLoop, 1, TRAVELCOST_FLAT );
1020 }
1021 }
1022 else
1023 {
1024 for (ubDirLoop=0; ubDirLoop < 8; ubDirLoop++)
1025 {
1026 SET_MOVEMENTCOST( usGridNo, ubDirLoop, 1, TRAVELCOST_OBSTACLE );
1027 }
1028 }
1029 }
1030 else
1031 {
1032 for (ubDirLoop=0; ubDirLoop < 8; ubDirLoop++)
1033 {
1034 SET_MOVEMENTCOST( usGridNo, ubDirLoop, 1, TRAVELCOST_OBSTACLE );
1035 }
1036 }
1037 }
1038 else
1039 { // NO STRUCTURES IN TILE
1040 // consider just the land
1041
1042 // Get terrain type
1043 ubTerrainID = gpWorldLevelData[usGridNo].ubTerrainID; // = GetTerrainType( (INT16)usGridNo );
1044 for (ubDirLoop=0; ubDirLoop < 8; ubDirLoop++)
1045 {
1046 SET_MOVEMENTCOST( usGridNo ,ubDirLoop, 0, gTileTypeMovementCost[ ubTerrainID ] );
1047 }
1048
1049 /*
1050 pLand = gpWorldLevelData[ usGridNo ].pLandHead;
1051 if ( pLand != NULL )
1052 {
1053 // Set cost here
1054
1055 // Get terrain type
1056 ubTerrainID = GetTerrainType( (INT16)usGridNo );
1057
1058 for (ubDirLoop=0; ubDirLoop < 8; ubDirLoop++)
1059 {
1060 SET_MOVEMENTCOST( usGridNo ,ubDirLoop, 0, gTileTypeMovementCost[ ubTerrainID ] );
1061 }
1062 }
1063 */
1064 // HIGHEST LEVEL
1065 if (gpWorldLevelData[ usGridNo ].pRoofHead != NULL)
1066 {
1067 for (ubDirLoop=0; ubDirLoop < 8; ubDirLoop++)
1068 {
1069 SET_MOVEMENTCOST( usGridNo, ubDirLoop, 1, TRAVELCOST_FLAT );
1070 }
1071 }
1072 else
1073 {
1074 for (ubDirLoop=0; ubDirLoop < 8; ubDirLoop++)
1075 {
1076 SET_MOVEMENTCOST( usGridNo, ubDirLoop, 1, TRAVELCOST_OBSTACLE );
1077 }
1078 }
1079 }
1080 }
1081
1082 #define LOCAL_RADIUS 4
1083
RecompileLocalMovementCosts(INT16 sCentreGridNo)1084 void RecompileLocalMovementCosts( INT16 sCentreGridNo )
1085 {
1086 INT16 usGridNo;
1087 INT16 sGridX, sGridY;
1088 INT16 sCentreGridX, sCentreGridY;
1089 INT8 bDirLoop;
1090
1091 ConvertGridNoToXY( sCentreGridNo, &sCentreGridX, &sCentreGridY );
1092 for( sGridY = sCentreGridY - LOCAL_RADIUS; sGridY < sCentreGridY + LOCAL_RADIUS; sGridY++ )
1093 {
1094 for( sGridX = sCentreGridX - LOCAL_RADIUS; sGridX < sCentreGridX + LOCAL_RADIUS; sGridX++ )
1095 {
1096 usGridNo = MAPROWCOLTOPOS( sGridY, sGridX );
1097 if (isValidGridNo(usGridNo))
1098 {
1099 for( bDirLoop = 0; bDirLoop < MAXDIR; bDirLoop++)
1100 {
1101 gubWorldMovementCosts[usGridNo][bDirLoop][0] = 0;
1102 gubWorldMovementCosts[usGridNo][bDirLoop][1] = 0;
1103 }
1104 }
1105 }
1106 }
1107
1108 // note the radius used in this loop is larger, to guarantee that the
1109 // edges of the recompiled areas are correct (i.e. there could be spillover)
1110 for( sGridY = sCentreGridY - LOCAL_RADIUS - 1; sGridY < sCentreGridY + LOCAL_RADIUS + 1; sGridY++ )
1111 {
1112 for( sGridX = sCentreGridX - LOCAL_RADIUS - 1; sGridX < sCentreGridX + LOCAL_RADIUS + 1; sGridX++ )
1113 {
1114 usGridNo = MAPROWCOLTOPOS( sGridY, sGridX );
1115 CompileTileMovementCosts( usGridNo );
1116 }
1117 }
1118 }
1119
1120
RecompileLocalMovementCostsFromRadius(INT16 sCentreGridNo,INT8 bRadius)1121 void RecompileLocalMovementCostsFromRadius( INT16 sCentreGridNo, INT8 bRadius )
1122 {
1123 INT16 usGridNo;
1124 INT16 sGridX, sGridY;
1125 INT16 sCentreGridX, sCentreGridY;
1126 INT8 bDirLoop;
1127
1128 ConvertGridNoToXY( sCentreGridNo, &sCentreGridX, &sCentreGridY );
1129 if (bRadius == 0)
1130 {
1131 // one tile check only
1132 for( bDirLoop = 0; bDirLoop < MAXDIR; bDirLoop++)
1133 {
1134 gubWorldMovementCosts[sCentreGridNo][bDirLoop][0] = 0;
1135 gubWorldMovementCosts[sCentreGridNo][bDirLoop][1] = 0;
1136 }
1137 CompileTileMovementCosts( sCentreGridNo );
1138 }
1139 else
1140 {
1141 for( sGridY = sCentreGridY - bRadius; sGridY < sCentreGridY + bRadius; sGridY++ )
1142 {
1143 for( sGridX = sCentreGridX - bRadius; sGridX < sCentreGridX + bRadius; sGridX++ )
1144 {
1145 usGridNo = MAPROWCOLTOPOS( sGridY, sGridX );
1146 if (isValidGridNo(usGridNo))
1147 {
1148 for( bDirLoop = 0; bDirLoop < MAXDIR; bDirLoop++)
1149 {
1150 gubWorldMovementCosts[usGridNo][bDirLoop][0] = 0;
1151 gubWorldMovementCosts[usGridNo][bDirLoop][1] = 0;
1152 }
1153 }
1154 }
1155 }
1156
1157 // note the radius used in this loop is larger, to guarantee that the
1158 // edges of the recompiled areas are correct (i.e. there could be spillover)
1159 for( sGridY = sCentreGridY - bRadius - 1; sGridY < sCentreGridY + bRadius + 1; sGridY++ )
1160 {
1161 for( sGridX = sCentreGridX - bRadius - 1; sGridX < sCentreGridX + bRadius + 1; sGridX++ )
1162 {
1163 usGridNo = MAPROWCOLTOPOS( sGridY, sGridX );
1164 CompileTileMovementCosts( usGridNo );
1165 }
1166 }
1167 }
1168 }
1169
AddTileToRecompileArea(INT16 sGridNo)1170 void AddTileToRecompileArea( INT16 sGridNo )
1171 {
1172 INT16 sCheckGridNo;
1173 INT16 sCheckX;
1174 INT16 sCheckY;
1175
1176 // Set flag to wipe and recompile MPs in this tile
1177 if (!isValidGridNo(sGridNo))
1178 {
1179 return;
1180 }
1181
1182 gpWorldLevelData[ sGridNo ].ubExtFlags[0] |= MAPELEMENT_EXT_RECALCULATE_MOVEMENT;
1183
1184 // check Top/Left of recompile region
1185 sCheckGridNo = NewGridNo( sGridNo, DirectionInc( NORTHWEST ) );
1186 sCheckX = sCheckGridNo % WORLD_COLS;
1187 sCheckY = sCheckGridNo / WORLD_COLS;
1188 if ( sCheckX < gsRecompileAreaLeft )
1189 {
1190 gsRecompileAreaLeft = sCheckX;
1191 }
1192 if ( sCheckY < gsRecompileAreaTop )
1193 {
1194 gsRecompileAreaTop = sCheckY;
1195 }
1196
1197 // check Bottom/Right
1198 sCheckGridNo = NewGridNo( sGridNo, DirectionInc( SOUTHEAST ) );
1199 sCheckX = sCheckGridNo % WORLD_COLS;
1200 sCheckY = sCheckGridNo / WORLD_COLS;
1201 if ( sCheckX > gsRecompileAreaRight )
1202 {
1203 gsRecompileAreaRight = sCheckX;
1204 }
1205 if ( sCheckY > gsRecompileAreaBottom )
1206 {
1207 gsRecompileAreaBottom = sCheckY;
1208 }
1209 }
1210
RecompileLocalMovementCostsInAreaWithFlags(void)1211 void RecompileLocalMovementCostsInAreaWithFlags( void )
1212 {
1213 INT16 usGridNo;
1214 INT16 sGridX, sGridY;
1215 INT8 bDirLoop;
1216
1217 for( sGridY = gsRecompileAreaTop; sGridY <= gsRecompileAreaBottom; sGridY++ )
1218 {
1219 for( sGridX = gsRecompileAreaLeft; sGridX < gsRecompileAreaRight; sGridX++ )
1220 {
1221 usGridNo = MAPROWCOLTOPOS( sGridY, sGridX );
1222 if ( isValidGridNo(usGridNo) && gpWorldLevelData[ usGridNo ].ubExtFlags[0] & MAPELEMENT_EXT_RECALCULATE_MOVEMENT )
1223 {
1224 // wipe MPs in this tile!
1225 for( bDirLoop = 0; bDirLoop < MAXDIR; bDirLoop++)
1226 {
1227 gubWorldMovementCosts[usGridNo][bDirLoop][0] = 0;
1228 gubWorldMovementCosts[usGridNo][bDirLoop][1] = 0;
1229 }
1230 // reset flag
1231 gpWorldLevelData[ usGridNo ].ubExtFlags[0] &= (~MAPELEMENT_EXT_RECALCULATE_MOVEMENT);
1232 }
1233 }
1234 }
1235
1236 for( sGridY = gsRecompileAreaTop; sGridY <= gsRecompileAreaBottom; sGridY++ )
1237 {
1238 for( sGridX = gsRecompileAreaLeft; sGridX <= gsRecompileAreaRight; sGridX++ )
1239 {
1240 usGridNo = MAPROWCOLTOPOS( sGridY, sGridX );
1241 CompileTileMovementCosts( usGridNo );
1242 }
1243 }
1244 }
1245
RecompileLocalMovementCostsForWall(INT16 sGridNo,UINT8 ubOrientation)1246 void RecompileLocalMovementCostsForWall( INT16 sGridNo, UINT8 ubOrientation )
1247 {
1248 INT8 bDirLoop;
1249 INT16 sUp, sDown, sLeft, sRight;
1250 INT16 sX, sY, sTempGridNo;
1251
1252 switch( ubOrientation )
1253 {
1254 case OUTSIDE_TOP_RIGHT:
1255 case INSIDE_TOP_RIGHT:
1256 sUp = -1;
1257 sDown = 1;
1258 sLeft = 0;
1259 sRight = 1;
1260 break;
1261 case OUTSIDE_TOP_LEFT:
1262 case INSIDE_TOP_LEFT:
1263 sUp = 0;
1264 sDown = 1;
1265 sLeft = -1;
1266 sRight = 1;
1267 break;
1268 default:
1269 return;
1270 }
1271
1272 for ( sY = sUp; sY <= sDown; sY++ )
1273 {
1274 for ( sX = sLeft; sX <= sRight; sX++ )
1275 {
1276 sTempGridNo = sGridNo + sX + sY * WORLD_COLS;
1277 for( bDirLoop = 0; bDirLoop < MAXDIR; bDirLoop++)
1278 {
1279 gubWorldMovementCosts[sTempGridNo][bDirLoop][0] = 0;
1280 gubWorldMovementCosts[sTempGridNo][bDirLoop][1] = 0;
1281 }
1282
1283 CompileTileMovementCosts( sTempGridNo );
1284 }
1285 }
1286 }
1287
1288
1289
1290 // GLOBAL WORLD MANIPULATION FUNCTIONS
CompileWorldMovementCosts()1291 void CompileWorldMovementCosts( )
1292 {
1293 UINT16 usGridNo;
1294
1295 for (auto& i : gubWorldMovementCosts)
1296 {
1297 for (auto& j : i)
1298 {
1299 std::fill(std::begin(j), std::end(j), 0);
1300 }
1301 }
1302
1303 CompileWorldTerrainIDs();
1304 for( usGridNo = 0; usGridNo < WORLD_MAX; usGridNo++ )
1305 {
1306 CompileTileMovementCosts( usGridNo );
1307 }
1308 }
1309
1310
LimitCheck(UINT8 n,INT32 gridno,UINT32 & n_warnings,const ST::string & kind)1311 static bool LimitCheck(UINT8 n, INT32 gridno, UINT32& n_warnings, const ST::string& kind)
1312 {
1313 if (n > 15)
1314 {
1315 SetErrorCatchString(
1316 ST::format("SAVE ABORTED! {} count too high ({}) for gridno {}. Need to fix before map can be saved! There are {} additional warnings.",
1317 kind, n, gridno, n_warnings));
1318 return false;
1319 }
1320 if (n > 10)
1321 {
1322 ++n_warnings;
1323 SetErrorCatchString(
1324 ST::format("Warnings {} -- Last warning: {} count warning of {} for gridno {}.",
1325 n_warnings, kind, n, gridno));
1326 }
1327 return true;
1328 }
1329
1330
WriteLevelNode(HWFILE const f,LEVELNODE const * const n)1331 static void WriteLevelNode(HWFILE const f, LEVELNODE const* const n)
1332 {
1333 // Write out object type and sub-index
1334 UINT16 const idx = n->usIndex;
1335 UINT32 const type = GetTileType(idx);
1336 UINT8 const type_sub_index = (UINT8)GetTypeSubIndexFromTileIndex(type, idx);
1337 BYTE data[2];
1338 DataWriter d{data};
1339 INJ_U8(d, (UINT8)type)
1340 INJ_U8(d, (UINT8)type_sub_index)
1341 Assert(d.getConsumed() == lengthof(data));
1342 FileWrite(f, data, sizeof(data));
1343 }
1344
1345
1346 static void RemoveWorldWireFrameTiles();
1347 static void SaveMapLights(HWFILE);
1348
1349
SaveWorld(char const * const filename)1350 BOOLEAN SaveWorld(char const* const filename)
1351 try
1352 {
1353 // Let's save map into Data/maps
1354 ST::string path = GCM->getNewMapFolder();
1355 FileMan::createDir(path.c_str());
1356 path = FileMan::joinPaths(path, filename);
1357 AutoSGPFile f(FileMan::openForWriting(path));
1358
1359 // Write JA2 Version ID
1360 FLOAT mapVersion = getMajorMapVersion();
1361 FileWrite(f, &mapVersion, sizeof(FLOAT));
1362 if (mapVersion >= 4.00)
1363 {
1364 FileWrite(f, &gubMinorMapVersion, sizeof(UINT8));
1365 }
1366
1367 // Write FLAGS FOR WORLD
1368 UINT32 flags = 0;
1369 flags |= MAP_FULLSOLDIER_SAVED;
1370 flags |= MAP_WORLDLIGHTS_SAVED;
1371 flags |= MAP_WORLDITEMS_SAVED;
1372 flags |= MAP_EXITGRIDS_SAVED;
1373 flags |= MAP_DOORTABLE_SAVED;
1374 flags |= MAP_EDGEPOINTS_SAVED;
1375 flags |= MAP_NPCSCHEDULES_SAVED;
1376 if (gfBasement || gfCaves)
1377 flags |= MAP_AMBIENTLIGHTLEVEL_SAVED;
1378
1379 FileWrite(f, &flags, sizeof(INT32));
1380
1381 // Write tileset ID
1382 FileWrite(f, &giCurrentTilesetID, sizeof(INT32));
1383
1384 // Write soldier control size
1385 UINT32 const uiSoldierSize = sizeof(SOLDIERTYPE);
1386 FileWrite(f, &uiSoldierSize, sizeof(UINT32));
1387
1388 // Remove world visibility tiles
1389 RemoveWorldWireFrameTiles();
1390
1391 MAP_ELEMENT const* const world_data = gpWorldLevelData;
1392
1393 { // Write out height values
1394 UINT8 heights[2 * WORLD_MAX];
1395 for (INT32 i = 0; i != WORLD_MAX; ++i)
1396 {
1397 heights[2 * i] = world_data[i].sHeight;
1398 heights[2 * i + 1] = 0; // Filler byte
1399 }
1400 FileWrite(f, heights, sizeof(heights));
1401 }
1402
1403 // Write out # values - we'll have no more than 15 per level!
1404 UINT32 n_warnings = 0;
1405 UINT8 ubCombine;
1406 for (INT32 cnt = 0; cnt < WORLD_MAX; ++cnt)
1407 {
1408 MAP_ELEMENT const& e = world_data[cnt];
1409
1410 // Determine # of land
1411 UINT8 n_layers = 0;
1412 for (LEVELNODE const* i = e.pLandHead; i; i = i->pNext) ++n_layers;
1413 if (!LimitCheck(n_layers, cnt, n_warnings, "Land")) return FALSE;
1414
1415 // Combine # of land layers with worlddef flags (first 4 bits)
1416 ubCombine = (n_layers & 0xf) | ((e.uiFlags & 0xf) << 4);
1417 FileWrite(f, &ubCombine, sizeof(ubCombine));
1418
1419
1420 // Determine # of objects
1421 UINT8 n_objects = 0;
1422 for (LEVELNODE const* i = e.pObjectHead; i; i = i->pNext)
1423 {
1424 // DON'T WRITE ANY ITEMS
1425 if (i->uiFlags & LEVELNODE_ITEM) continue;
1426 //Make sure this isn't a UI Element
1427 UINT32 const uiTileType = GetTileType(i->usIndex);
1428 if (uiTileType >= FIRSTPOINTERS) continue;
1429 ++n_objects;
1430 }
1431 if (!LimitCheck(n_objects, cnt, n_warnings, "Object")) return FALSE;
1432
1433 // Determine # of structs
1434 UINT8 n_structs = 0;
1435 for (LEVELNODE const* i = e.pStructHead; i; i = i->pNext)
1436 {
1437 // DON'T WRITE ANY ITEMS
1438 if (i->uiFlags & LEVELNODE_ITEM) continue;
1439 ++n_structs;
1440 }
1441 if (!LimitCheck(n_structs, cnt, n_warnings, "Struct")) return FALSE;
1442
1443 ubCombine = (n_objects & 0xf) | ((n_structs & 0xf) << 4);
1444 FileWrite(f, &ubCombine, sizeof(ubCombine));
1445
1446
1447 // Determine # of shadows
1448 UINT8 n_shadows = 0;
1449 for (LEVELNODE const* i = e.pShadowHead; i; i = i->pNext)
1450 {
1451 // Don't write any shadowbuddys or exit grids
1452 if (i->uiFlags & (LEVELNODE_BUDDYSHADOW | LEVELNODE_EXITGRID)) continue;
1453 ++n_shadows;
1454 }
1455 if (!LimitCheck(n_shadows, cnt, n_warnings, "Shadow")) return FALSE;
1456
1457 // Determine # of Roofs
1458 UINT8 n_roofs = 0;
1459 for (LEVELNODE const* i = e.pRoofHead; i; i = i->pNext)
1460 {
1461 // ATE: Don't save revealed roof info...
1462 if (i->usIndex == SLANTROOFCEILING1) continue;
1463 ++n_roofs;
1464 }
1465 if (!LimitCheck(n_roofs, cnt, n_warnings, "Roof")) return FALSE;
1466
1467 ubCombine = (n_shadows & 0xf) | ((n_roofs & 0xf) << 4);
1468 FileWrite(f, &ubCombine, sizeof(ubCombine));
1469
1470
1471 // Determine # of OnRoofs
1472 UINT8 n_on_roofs = 0;
1473 for (LEVELNODE const* i = e.pOnRoofHead; i; i = i->pNext)
1474 {
1475 ++n_on_roofs;
1476 }
1477 if (!LimitCheck(n_on_roofs, cnt, n_warnings, "OnRoof")) return FALSE;
1478
1479 // Write combination of onroof and nothing
1480 ubCombine = n_on_roofs & 0xf;
1481 FileWrite(f, &ubCombine, sizeof(ubCombine));
1482 }
1483
1484 if(getMajorMapVersion() == 6.00 && gubMinorMapVersion == 26)
1485 {
1486 // the data appears to be 37 INT32/UINT32 numbers and is present in russian ja2 maps
1487 UINT8 data[148] = {0};
1488 FileWrite(f, &data, sizeof(data));
1489 }
1490
1491 UINT8 const test[] = { 1, 1 };
1492 FOR_EACH_WORLD_TILE(e)
1493 { // Write land layers
1494 LEVELNODE const* i = e->pLandHead;
1495 if (!i)
1496 {
1497 FileWrite(f, &test, sizeof(test));
1498 }
1499 else
1500 { // Write out land pieces backwards so that they are loaded properly
1501 while (i->pNext) i = i->pNext;
1502 for (; i; i = i->pPrevNode)
1503 {
1504 WriteLevelNode(f, i);
1505 }
1506 }
1507 }
1508
1509 FOR_EACH_WORLD_TILE(e)
1510 { // Write object layer
1511 for (LEVELNODE const* i = e->pObjectHead; i; i = i->pNext)
1512 {
1513 // Don't write any items
1514 if (i->uiFlags & LEVELNODE_ITEM) continue;
1515
1516 // Write out object type and sub-index
1517 UINT32 const type = GetTileType(i->usIndex);
1518 // Make sure this isn't a UI Element
1519 if (type >= FIRSTPOINTERS) continue;
1520
1521 /* We are writing 2 bytes for the type subindex in the object layer
1522 * because the ROADPIECES slot contains more than 256 subindices. */
1523 UINT16 const type_sub_index = GetTypeSubIndexFromTileIndex(type, i->usIndex);
1524
1525 BYTE data[3];
1526 DataWriter d{data};
1527 INJ_U8( d, (UINT8)type)
1528 INJ_U16(d, type_sub_index) // XXX misaligned
1529 Assert(d.getConsumed() == lengthof(data));
1530 FileWrite(f, data, sizeof(data));
1531 }
1532 }
1533
1534 FOR_EACH_WORLD_TILE(e)
1535 { // Write struct layer
1536 for (LEVELNODE const* i = e->pStructHead; i; i = i->pNext)
1537 {
1538 // Don't write any items
1539 if (i->uiFlags & LEVELNODE_ITEM) continue;
1540
1541 WriteLevelNode(f, i);
1542 }
1543 }
1544
1545 UINT16 n_exit_grids = 0;
1546 FOR_EACH_WORLD_TILE(e)
1547 { // Write shadows
1548 for (LEVELNODE const* i = e->pShadowHead; i; i = i->pNext)
1549 {
1550 // Dont't write any buddys or exit grids
1551 if (!(i->uiFlags & (LEVELNODE_BUDDYSHADOW | LEVELNODE_EXITGRID)))
1552 {
1553 WriteLevelNode(f, i);
1554 }
1555 else if (i->uiFlags & LEVELNODE_EXITGRID)
1556 { // Count the number of exitgrids
1557 ++n_exit_grids;
1558 }
1559 }
1560 }
1561
1562 FOR_EACH_WORLD_TILE(e)
1563 {
1564 for (LEVELNODE const* i = e->pRoofHead; i; i = i->pNext)
1565 {
1566 // ATE: Don't save revealed roof info
1567 if (i->usIndex == SLANTROOFCEILING1) continue;
1568
1569 WriteLevelNode(f, i);
1570 }
1571 }
1572
1573 FOR_EACH_WORLD_TILE(e)
1574 { // Write OnRoofs
1575 for (LEVELNODE const* i = e->pOnRoofHead; i; i = i->pNext)
1576 {
1577 WriteLevelNode(f, i);
1578 }
1579 }
1580
1581 // Write out room information
1582 FileWrite(f, gubWorldRoomInfo, sizeof(gubWorldRoomInfo));
1583
1584 if (flags & MAP_WORLDITEMS_SAVED)
1585 {
1586 SaveWorldItemsToMap(f);
1587 }
1588
1589 if (flags & MAP_AMBIENTLIGHTLEVEL_SAVED)
1590 {
1591 FileWrite(f, &gfBasement, 1);
1592 FileWrite(f, &gfCaves, 1);
1593 FileWrite(f, &ubAmbientLightLevel, 1);
1594 }
1595
1596 if (flags & MAP_WORLDLIGHTS_SAVED)
1597 {
1598 SaveMapLights(f);
1599 }
1600
1601 SaveMapInformation(f);
1602
1603 if (flags & MAP_FULLSOLDIER_SAVED)
1604 {
1605 SaveSoldiersToMap(f);
1606 }
1607 if (flags & MAP_EXITGRIDS_SAVED)
1608 {
1609 SaveExitGrids(f, n_exit_grids);
1610 }
1611 if (flags & MAP_DOORTABLE_SAVED)
1612 {
1613 SaveDoorTableToMap(f);
1614 }
1615 if (flags & MAP_EDGEPOINTS_SAVED)
1616 {
1617 CompileWorldMovementCosts();
1618 GenerateMapEdgepoints();
1619 SaveMapEdgepoints(f);
1620 }
1621 if (flags & MAP_NPCSCHEDULES_SAVED)
1622 {
1623 SaveSchedules(f);
1624 }
1625
1626 strlcpy(g_filename, filename, lengthof(g_filename));
1627 return TRUE;
1628 }
1629 catch (...) { return FALSE; }
1630
1631
1632
OptimizeMapForShadows()1633 static void OptimizeMapForShadows()
1634 {
1635 UINT8 const bDirectionsForShadowSearch[] =
1636 {
1637 WEST,
1638 SOUTHWEST,
1639 SOUTH,
1640 SOUTHEAST,
1641 EAST
1642 };
1643
1644 for (INT32 cnt = 0; cnt != WORLD_MAX; ++cnt)
1645 {
1646 // Is there a tree here?
1647 if (FindStructure(cnt, STRUCTURE_TREE) == NULL) continue;
1648
1649 // Check for a structure a footprint away
1650 for (UINT8 const* dir = bDirectionsForShadowSearch;; ++dir)
1651 {
1652 if (dir == endof(bDirectionsForShadowSearch))
1653 { // We're full of structures
1654 RemoveAllShadows(cnt);
1655 break;
1656 }
1657 GridNo const gridno = NewGridNo(cnt, DirectionInc(*dir));
1658 if (!gpWorldLevelData[gridno].pStructureHead) break;
1659 }
1660 }
1661 }
1662
1663
SetBlueFlagFlags(void)1664 static void SetBlueFlagFlags(void)
1665 {
1666 FOR_EACH_WORLD_TILE(i)
1667 {
1668 for (LEVELNODE const* k = i->pStructHead; k; k = k->pNext)
1669 {
1670 if (k->usIndex != BLUEFLAG_GRAPHIC) continue;
1671 i->uiFlags |= MAPELEMENT_PLAYER_MINE_PRESENT;
1672 break;
1673 }
1674 }
1675 }
1676
1677
InitLoadedWorld(void)1678 void InitLoadedWorld(void)
1679 {
1680 //if the current sector is not valid, dont init the world
1681 if( gWorldSectorX == 0 || gWorldSectorY == 0 )
1682 {
1683 return;
1684 }
1685
1686 // COMPILE MOVEMENT COSTS
1687 CompileWorldMovementCosts( );
1688
1689 // COMPILE WORLD VISIBLIY TILES
1690 CalculateWorldWireFrameTiles( TRUE );
1691
1692 LightSpriteRenderAll();
1693
1694 OptimizeMapForShadows( );
1695
1696 SetInterfaceHeightLevel( );
1697
1698 // ATE: if we have a slide location, remove it!
1699 gTacticalStatus.sSlideTarget = NOWHERE;
1700
1701 SetBlueFlagFlags();
1702 }
1703
1704
1705 extern double MasterStart, MasterEnd;
1706 extern BOOLEAN gfUpdatingNow;
1707
1708 /* This is a specialty function that is very similar to LoadWorld, except that
1709 * it doesn't actually load the world, it instead evaluates the map and
1710 * generates summary information for use within the summary editor. The header
1711 * is defined in Summary Info.h, not worlddef.h -- though it's not likely this
1712 * is going to be used anywhere where it would matter. */
EvaluateWorld(const char * const pSector,const UINT8 ubLevel)1713 BOOLEAN EvaluateWorld(const char* const pSector, const UINT8 ubLevel)
1714 try
1715 {
1716 // Make sure the file exists... if not, then return false
1717 char filename[40];
1718 snprintf(filename, lengthof(filename), "%s%s%.0d%s.dat",
1719 pSector,
1720 ubLevel % 4 != 0 ? "_b" : "",
1721 ubLevel % 4,
1722 ubLevel >= 4 ? "_a" : ""
1723 );
1724
1725 if (gfMajorUpdate)
1726 {
1727 LoadWorld(filename);
1728 SaveWorld(filename);
1729 }
1730
1731 AutoSGPFile f(GCM->openMapForReading(filename));
1732
1733 ST::string str = ST::format("Analyzing map {}", filename);
1734 if (!gfUpdatingNow)
1735 {
1736 SetRelativeStartAndEndPercentage(0, 0, 100, str);
1737 }
1738 else
1739 {
1740 SetRelativeStartAndEndPercentage(0, (UINT16)MasterStart, (UINT16)MasterEnd, str);
1741 }
1742
1743 RenderProgressBar(0, 0);
1744
1745 //clear the summary file info
1746 SUMMARYFILE* const pSummary = new SUMMARYFILE{};
1747 pSummary->ubSummaryVersion = GLOBAL_SUMMARY_VERSION;
1748 pSummary->dMajorMapVersion = getMajorMapVersion();
1749
1750 //skip JA2 Version ID
1751 FLOAT dMajorMapVersion;
1752 FileRead(f, &dMajorMapVersion, sizeof(dMajorMapVersion));
1753 if (dMajorMapVersion >= 4.00)
1754 {
1755 FileSeek(f, sizeof(UINT8), FILE_SEEK_FROM_CURRENT);
1756 }
1757
1758 //Read FLAGS FOR WORLD
1759 UINT32 uiFlags;
1760 FileRead(f, &uiFlags, sizeof(uiFlags));
1761
1762 //Read tilesetID
1763 INT32 iTilesetID;
1764 FileRead(f, &iTilesetID, sizeof(iTilesetID));
1765 pSummary->ubTilesetID = (UINT8)iTilesetID;
1766
1767 // Skip soldier size and height values
1768 FileSeek(f, sizeof(UINT32) + (1 + 1) * WORLD_MAX, FILE_SEEK_FROM_CURRENT);
1769
1770 // Skip all layers
1771 INT32 skip = 0;
1772 for (UINT32 row = 0; row != WORLD_ROWS; ++row)
1773 {
1774 if (row % 16 == 0) RenderProgressBar(0, row * 90 / WORLD_ROWS + 1); // 1 - 90
1775
1776 UINT8 combine[WORLD_COLS][4];
1777 FileRead(f, combine, sizeof(combine));
1778 for (UINT8 const (*i)[4] = combine; i != endof(combine); ++i)
1779 {
1780 skip +=
1781 ((*i)[0] & 0x0F) * 2 + // #land
1782 ((*i)[1] & 0x0F) * 3 + // #objects
1783 ((*i)[1] >> 4) * 2 + // #structs
1784 ((*i)[2] & 0x0F) * 2 + // #shadows
1785 ((*i)[2] >> 4) * 2 + // #roof
1786 ((*i)[3] & 0x0F) * 2; // #on roof
1787 }
1788 }
1789 FileSeek(f, skip, FILE_SEEK_FROM_CURRENT);
1790
1791 //extract highest room number
1792 UINT8 max_room = 0;
1793 for (INT32 row = 0; row != WORLD_ROWS; ++row)
1794 {
1795 UINT8 room[WORLD_COLS];
1796 FileRead(f, room, sizeof(room));
1797 for (INT32 col = 0; col != WORLD_COLS; ++col)
1798 {
1799 if (max_room < room[col]) max_room = room[col];
1800 }
1801 }
1802 pSummary->ubNumRooms = max_room;
1803
1804 if (uiFlags & MAP_WORLDITEMS_SAVED)
1805 {
1806 RenderProgressBar(0, 91);
1807 //Important: Saves the file position (byte offset) of the position where the numitems
1808 // resides. Checking this value and comparing to usNumItems will ensure validity.
1809 pSummary->uiNumItemsPosition = FileGetPos(f);
1810 //get number of items (for now)
1811 UINT32 n_items;
1812 FileRead(f, &n_items, sizeof(n_items));
1813 pSummary->usNumItems = n_items;
1814 //Skip the contents of the world items.
1815 FileSeek(f, sizeof(WORLDITEM) * n_items, FILE_SEEK_FROM_CURRENT);
1816 }
1817
1818 if (uiFlags & MAP_AMBIENTLIGHTLEVEL_SAVED) FileSeek(f, 3, FILE_SEEK_FROM_CURRENT);
1819
1820 if (uiFlags & MAP_WORLDLIGHTS_SAVED)
1821 {
1822 RenderProgressBar(0, 92);
1823
1824 //skip number of light palette entries
1825 UINT8 n_light_colours;
1826 FileRead(f, &n_light_colours, sizeof(n_light_colours));
1827 FileSeek(f, sizeof(SGPPaletteEntry) * n_light_colours, FILE_SEEK_FROM_CURRENT);
1828
1829 //get number of lights
1830 FileRead(f, &pSummary->usNumLights, sizeof(pSummary->usNumLights));
1831 //skip the light loading
1832 for (INT32 n = pSummary->usNumLights; n != 0; --n)
1833 {
1834 FileSeek(f, 24 /* size of a LIGHT_SPRITE on disk */, FILE_SEEK_FROM_CURRENT);
1835 UINT8 ubStrLen;
1836 FileRead(f, &ubStrLen, sizeof(ubStrLen));
1837 FileSeek(f, ubStrLen, FILE_SEEK_FROM_CURRENT);
1838 }
1839 }
1840
1841 //read the mapinformation
1842 FileRead(f, &pSummary->MapInfo, sizeof(pSummary->MapInfo));
1843
1844 if (uiFlags & MAP_FULLSOLDIER_SAVED)
1845 {
1846 RenderProgressBar(0, 94);
1847
1848 pSummary->uiEnemyPlacementPosition = FileGetPos(f);
1849
1850 for (INT32 i = 0; i < pSummary->MapInfo.ubNumIndividuals; ++i)
1851 {
1852 BASIC_SOLDIERCREATE_STRUCT basic;
1853 ExtractBasicSoldierCreateStructFromFile(f, basic);
1854
1855 TEAMSUMMARY* pTeam = NULL;
1856 switch (basic.bTeam)
1857 {
1858 case ENEMY_TEAM: pTeam = &pSummary->EnemyTeam; break;
1859 case CREATURE_TEAM: pTeam = &pSummary->CreatureTeam; break;
1860 case MILITIA_TEAM: pTeam = &pSummary->RebelTeam; break;
1861 case CIV_TEAM: pTeam = &pSummary->CivTeam; break;
1862 }
1863
1864 if (basic.bOrders == RNDPTPATROL || basic.bOrders == POINTPATROL)
1865 { //make sure the placement has at least one waypoint.
1866 if (!basic.bPatrolCnt)
1867 {
1868 ++pSummary->ubEnemiesReqWaypoints;
1869 }
1870 }
1871 else if (basic.bPatrolCnt)
1872 {
1873 ++pSummary->ubEnemiesHaveWaypoints;
1874 }
1875
1876 if (basic.fPriorityExistance) ++pTeam->ubExistance;
1877
1878 switch (basic.bRelativeAttributeLevel)
1879 {
1880 case 0: ++pTeam->ubBadA; break;
1881 case 1: ++pTeam->ubPoorA; break;
1882 case 2: ++pTeam->ubAvgA; break;
1883 case 3: ++pTeam->ubGoodA; break;
1884 case 4: ++pTeam->ubGreatA; break;
1885 }
1886
1887 switch (basic.bRelativeEquipmentLevel)
1888 {
1889 case 0: ++pTeam->ubBadE; break;
1890 case 1: ++pTeam->ubPoorE; break;
1891 case 2: ++pTeam->ubAvgE; break;
1892 case 3: ++pTeam->ubGoodE; break;
1893 case 4: ++pTeam->ubGreatE; break;
1894 }
1895
1896 SOLDIERCREATE_STRUCT priority;
1897 if (basic.fDetailedPlacement)
1898 { //skip static priority placement
1899
1900 // Always use windows format because here we are loading a map
1901 // file, not a user save
1902 ExtractSoldierCreateFromFile(f, &priority, false);
1903
1904 if (priority.ubProfile != NO_PROFILE)
1905 ++pTeam->ubProfile;
1906 else
1907 ++pTeam->ubDetailed;
1908
1909 if (basic.bTeam == CIV_TEAM)
1910 {
1911 if (priority.ubScheduleID) ++pSummary->ubCivSchedules;
1912 switch (priority.bBodyType)
1913 {
1914 case COW: ++pSummary->ubCivCows; break;
1915 case BLOODCAT: ++pSummary->ubCivBloodcats; break;
1916 }
1917 }
1918 }
1919
1920 if (basic.bTeam == ENEMY_TEAM)
1921 {
1922 switch (basic.ubSoldierClass)
1923 {
1924 case SOLDIER_CLASS_ADMINISTRATOR:
1925 ++pSummary->ubNumAdmins;
1926 if (basic.fPriorityExistance) ++pSummary->ubAdminExistance;
1927 if (basic.fDetailedPlacement)
1928 {
1929 if (priority.ubProfile != NO_PROFILE)
1930 ++pSummary->ubAdminProfile;
1931 else
1932 ++pSummary->ubAdminDetailed;
1933 }
1934 break;
1935
1936 case SOLDIER_CLASS_ELITE:
1937 ++pSummary->ubNumElites;
1938 if (basic.fPriorityExistance) ++pSummary->ubEliteExistance;
1939 if (basic.fDetailedPlacement)
1940 {
1941 if (priority.ubProfile != NO_PROFILE)
1942 ++pSummary->ubEliteProfile;
1943 else
1944 ++pSummary->ubEliteDetailed;
1945 }
1946 break;
1947
1948 case SOLDIER_CLASS_ARMY:
1949 ++pSummary->ubNumTroops;
1950 if (basic.fPriorityExistance) ++pSummary->ubTroopExistance;
1951 if (basic.fDetailedPlacement)
1952 {
1953 if (priority.ubProfile != NO_PROFILE)
1954 ++pSummary->ubTroopProfile;
1955 else
1956 ++pSummary->ubTroopDetailed;
1957 }
1958 break;
1959 }
1960 }
1961 else if (basic.bTeam == CREATURE_TEAM)
1962 {
1963 if (basic.bBodyType == BLOODCAT) ++pTeam->ubNumAnimals;
1964 }
1965 ++pTeam->ubTotal;
1966 }
1967 RenderProgressBar(0, 96);
1968 }
1969
1970 if (uiFlags & MAP_EXITGRIDS_SAVED)
1971 {
1972 RenderProgressBar(0, 98);
1973
1974 UINT16 cnt;
1975 FileRead(f, &cnt, sizeof(cnt));
1976
1977 for (INT32 n = cnt; n != 0; --n)
1978 {
1979 UINT16 usMapIndex;
1980 FileRead(f, &usMapIndex, sizeof(usMapIndex));
1981 EXITGRID exitGrid;
1982 FileRead(f, &exitGrid, 5 /* XXX sic! The 6th byte luckily is padding */);
1983 for (INT32 loop = 0;; ++loop)
1984 {
1985 if (loop >= pSummary->ubNumExitGridDests)
1986 {
1987 if (loop >= 4)
1988 {
1989 pSummary->fTooManyExitGridDests = TRUE;
1990 }
1991 else
1992 {
1993 ++pSummary->ubNumExitGridDests;
1994 ++pSummary->usExitGridSize[loop];
1995 EXITGRID* const eg = &pSummary->ExitGrid[loop];
1996 eg->usGridNo = exitGrid.usGridNo;
1997 eg->ubGotoSectorX = exitGrid.ubGotoSectorX;
1998 eg->ubGotoSectorY = exitGrid.ubGotoSectorY;
1999 eg->ubGotoSectorZ = exitGrid.ubGotoSectorZ;
2000 if (eg->ubGotoSectorX != exitGrid.ubGotoSectorX ||
2001 eg->ubGotoSectorY != exitGrid.ubGotoSectorY)
2002 {
2003 pSummary->fInvalidDest[loop] = TRUE;
2004 }
2005 }
2006 break;
2007 }
2008
2009 const EXITGRID* const eg = &pSummary->ExitGrid[loop];
2010 if (eg->usGridNo == exitGrid.usGridNo &&
2011 eg->ubGotoSectorX == exitGrid.ubGotoSectorX &&
2012 eg->ubGotoSectorY == exitGrid.ubGotoSectorY &&
2013 eg->ubGotoSectorZ == exitGrid.ubGotoSectorZ)
2014 { //same destination.
2015 ++pSummary->usExitGridSize[loop];
2016 break;
2017 }
2018 }
2019 }
2020 }
2021
2022 if (uiFlags & MAP_DOORTABLE_SAVED)
2023 {
2024 FileRead(f, &pSummary->ubNumDoors, sizeof(pSummary->ubNumDoors));
2025
2026 for (INT32 n = pSummary->ubNumDoors; n != 0; --n)
2027 {
2028 DOOR Door;
2029 FileRead(f, &Door, sizeof(Door));
2030
2031 if (Door.ubLockID && Door.ubTrapID) ++pSummary->ubNumDoorsLockedAndTrapped;
2032 else if (Door.ubLockID) ++pSummary->ubNumDoorsLocked;
2033 else if (Door.ubTrapID) ++pSummary->ubNumDoorsTrapped;
2034 }
2035 }
2036
2037 RenderProgressBar(0, 100);
2038
2039 WriteSectorSummaryUpdate(filename, ubLevel, pSummary);
2040 return TRUE;
2041 }
2042 catch (...) { return FALSE; }
2043
2044
2045 static void LoadMapLights(HWFILE);
2046
2047
LoadWorld(char const * const filename)2048 void LoadWorld(char const* const filename)
2049 try
2050 {
2051 LoadShadeTablesFromTextFile();
2052
2053 // Reset flags for outdoors/indoors
2054 gfBasement = FALSE;
2055 gfCaves = FALSE;
2056
2057 AutoSGPFile f(GCM->openMapForReading(filename));
2058
2059 SetRelativeStartAndEndPercentage(0, 0, 1, "Trashing world...");
2060 TrashWorld();
2061
2062 LightReset();
2063
2064 // Read JA2 Version ID
2065 FLOAT dMajorMapVersion;
2066 FileRead(f, &dMajorMapVersion, sizeof(dMajorMapVersion));
2067
2068 UINT8 ubMinorMapVersion;
2069 if (dMajorMapVersion >= 4.00)
2070 {
2071 // major version 4 probably started in minor version 15 since
2072 // this value is needed to detect the change in the object layer
2073 FileRead(f, &ubMinorMapVersion, sizeof(ubMinorMapVersion));
2074 }
2075 else
2076 {
2077 ubMinorMapVersion = 0;
2078 }
2079
2080 if (dMajorMapVersion > 6.00 || ubMinorMapVersion > 26)
2081 {
2082 throw std::runtime_error("newer versions are not supported");
2083 }
2084
2085 // Read flags for world
2086 UINT32 uiFlags;
2087 FileRead(f, &uiFlags, sizeof(uiFlags));
2088
2089 INT32 iTilesetID;
2090 FileRead(f, &iTilesetID, sizeof(iTilesetID));
2091
2092 LoadMapTileset(static_cast<TileSetID>(iTilesetID));
2093
2094 // Skip soldier size
2095 FileSeek(f, 4, FILE_SEEK_FROM_CURRENT);
2096
2097 { // Read height values
2098 MAP_ELEMENT* world = gpWorldLevelData;
2099 for (UINT32 row = 0; row != WORLD_ROWS; ++row)
2100 {
2101 BYTE height[WORLD_COLS * 2];
2102 FileRead(f, height, sizeof(height));
2103 for (BYTE const* i = height; i != endof(height); i += 2)
2104 {
2105 (world++)->sHeight = *i;
2106 }
2107 }
2108 }
2109
2110 SetRelativeStartAndEndPercentage(0, 35, 40, "Counting layers...");
2111 RenderProgressBar(0, 100);
2112
2113 UINT8 bCounts[WORLD_MAX][6];
2114 { // Read layer counts
2115 UINT8 (*cnt)[6] = bCounts;
2116 MAP_ELEMENT* world = gpWorldLevelData;
2117 for (UINT32 row = 0; row != WORLD_ROWS; ++row)
2118 {
2119 BYTE combine[WORLD_COLS][4];
2120 FileRead(f, combine, sizeof(combine));
2121 for (UINT8 const (*i)[4] = combine; i != endof(combine); ++world, ++cnt, ++i)
2122 {
2123 // Read combination of land/world flags
2124 (*cnt)[0] = (*i)[0] & 0x0F;
2125 world->uiFlags |= (*i)[0] >> 4;
2126
2127 // Read #objects, structs
2128 (*cnt)[1] = (*i)[1] & 0x0F;
2129 (*cnt)[2] = (*i)[1] >> 4;
2130
2131 // Read shadows, roof
2132 (*cnt)[3] = (*i)[2] & 0x0F;
2133 (*cnt)[4] = (*i)[2] >> 4;
2134
2135 // Read OnRoof, nothing
2136 (*cnt)[5] = (*i)[3] & 0x0F;
2137 }
2138 }
2139 }
2140
2141 SetRelativeStartAndEndPercentage(0, 40, 43, "Loading land layers...");
2142 RenderProgressBar(0, 100);
2143
2144 for (INT32 cnt = 0; cnt != WORLD_MAX; ++cnt)
2145 {
2146 for (INT32 n = bCounts[cnt][0]; n != 0; --n)
2147 {
2148 BYTE data[2];
2149 FileRead(f, data, sizeof(data));
2150
2151 UINT8 ubType;
2152 UINT8 ubSubIndex;
2153 DataReader d{data};
2154 EXTR_U8(d, ubType)
2155 EXTR_U8(d, ubSubIndex)
2156 Assert(d.getConsumed() == lengthof(data));
2157
2158 UINT16 const usTileIndex = GetTileIndexFromTypeSubIndex(ubType, ubSubIndex);
2159 AddLandToHead(cnt, usTileIndex);
2160 }
2161 }
2162
2163 SetRelativeStartAndEndPercentage(0, 43, 46, "Loading object layer...");
2164 RenderProgressBar(0, 100);
2165
2166 if (ubMinorMapVersion < 15)
2167 { // Old loads
2168 for (INT32 cnt = 0; cnt != WORLD_MAX; ++cnt)
2169 {
2170 for (INT32 n = bCounts[cnt][1]; n != 0; --n)
2171 {
2172 BYTE data[2];
2173 FileRead(f, data, sizeof(data));
2174
2175 UINT8 ubType;
2176 UINT8 ubSubIndex;
2177 DataReader d{data};
2178 EXTR_U8(d, ubType)
2179 EXTR_U8(d, ubSubIndex)
2180 Assert(d.getConsumed() == lengthof(data));
2181
2182 if (ubType >= FIRSTPOINTERS) continue;
2183 UINT16 const usTileIndex = GetTileIndexFromTypeSubIndex(ubType, ubSubIndex);
2184 AddObjectToTail(cnt, usTileIndex);
2185 }
2186 }
2187 }
2188 else
2189 { /* New load: Require UINT16 for the type subindex due to the fact that
2190 * ROADPIECES contains over 300 type subindices. */
2191 for (INT32 cnt = 0; cnt != WORLD_MAX; ++cnt)
2192 {
2193 for (INT32 n = bCounts[cnt][1]; n != 0; --n)
2194 {
2195 BYTE data[3];
2196 FileRead(f, data, sizeof(data));
2197
2198 UINT8 ubType;
2199 UINT16 usTypeSubIndex;
2200 DataReader d{data};
2201 EXTR_U8( d, ubType)
2202 EXTR_U16(d, usTypeSubIndex)
2203 Assert(d.getConsumed() == lengthof(data));
2204
2205 if (ubType >= FIRSTPOINTERS) continue;
2206 UINT16 const usTileIndex = GetTileIndexFromTypeSubIndex(ubType, usTypeSubIndex);
2207 AddObjectToTail(cnt, usTileIndex);
2208 }
2209 }
2210 }
2211
2212 SetRelativeStartAndEndPercentage(0, 46, 49, "Loading struct layer...");
2213 RenderProgressBar(0, 100);
2214
2215 for (INT32 cnt = 0; cnt != WORLD_MAX; ++cnt)
2216 { // Set structs
2217 for (INT32 n = bCounts[cnt][2]; n != 0; --n)
2218 {
2219 BYTE data[2];
2220 FileRead(f, data, sizeof(data));
2221
2222 UINT8 ubType;
2223 UINT8 ubSubIndex;
2224 DataReader d{data};
2225 EXTR_U8(d, ubType)
2226 EXTR_U8(d, ubSubIndex)
2227 Assert(d.getConsumed() == lengthof(data));
2228
2229 UINT16 usTileIndex = GetTileIndexFromTypeSubIndex(ubType, ubSubIndex);
2230
2231 if (ubMinorMapVersion <= 25)
2232 {
2233 // Check patching for phantom menace struct data
2234 if (gTileDatabase[usTileIndex].uiFlags & UNDERFLOW_FILLER)
2235 { /* HACK000F Workaround: Skip underflow fillers, when there is more
2236 * than one struct on this tile, otherwise adding the underflow
2237 * replacement struct will fail */
2238 if (bCounts[cnt][2] > 1) continue;
2239
2240 usTileIndex = GetTileIndexFromTypeSubIndex(ubType, 1);
2241 }
2242 }
2243
2244 try
2245 {
2246 AddStructToTail(cnt, usTileIndex);
2247 }
2248 catch (FailedToAddNode const&)
2249 { /* HACK0010 Workaround: Ignore, because there are defective maps with
2250 * overlapping objects */
2251 }
2252 }
2253 }
2254
2255 SetRelativeStartAndEndPercentage(0, 49, 52, "Loading shadow layer...");
2256 RenderProgressBar(0, 100);
2257
2258 for (INT32 cnt = 0; cnt != WORLD_MAX; ++cnt)
2259 {
2260 for (INT32 n = bCounts[cnt][3]; n != 0; --n)
2261 {
2262 BYTE data[2];
2263 FileRead(f, data, sizeof(data));
2264
2265 UINT8 ubType;
2266 UINT8 ubSubIndex;
2267 DataReader d{data};
2268 EXTR_U8(d, ubType)
2269 EXTR_U8(d, ubSubIndex)
2270 Assert(d.getConsumed() == lengthof(data));
2271
2272 UINT16 const usTileIndex = GetTileIndexFromTypeSubIndex(ubType, ubSubIndex);
2273 AddShadowToTail(cnt, usTileIndex);
2274 }
2275 }
2276
2277 SetRelativeStartAndEndPercentage(0, 52, 55, "Loading roof layer...");
2278 RenderProgressBar(0, 100);
2279
2280 for (INT32 cnt = 0; cnt != WORLD_MAX; ++cnt)
2281 {
2282 for (INT32 n = bCounts[cnt][4]; n != 0; --n)
2283 {
2284 BYTE data[2];
2285 FileRead(f, data, sizeof(data));
2286
2287 UINT8 ubType;
2288 UINT8 ubSubIndex;
2289 DataReader d{data};
2290 EXTR_U8(d, ubType)
2291 EXTR_U8(d, ubSubIndex)
2292 Assert(d.getConsumed() == lengthof(data));
2293
2294 UINT16 const usTileIndex = GetTileIndexFromTypeSubIndex(ubType, ubSubIndex);
2295 AddRoofToTail(cnt, usTileIndex);
2296 }
2297 }
2298
2299 SetRelativeStartAndEndPercentage(0, 55, 58, "Loading on roof layer...");
2300 RenderProgressBar(0, 100);
2301
2302 for (INT32 cnt = 0; cnt != WORLD_MAX; ++cnt)
2303 {
2304 for (INT32 n = bCounts[cnt][5]; n != 0; --n)
2305 {
2306 BYTE data[2];
2307 FileRead(f, data, sizeof(data));
2308
2309 UINT8 ubType;
2310 UINT8 ubSubIndex;
2311 DataReader d{data};
2312 EXTR_U8(d, ubType)
2313 EXTR_U8(d, ubSubIndex)
2314 Assert(d.getConsumed() == lengthof(data));
2315
2316 UINT16 const usTileIndex = GetTileIndexFromTypeSubIndex(ubType, ubSubIndex);
2317 AddOnRoofToTail(cnt, usTileIndex);
2318 }
2319 }
2320
2321 if(dMajorMapVersion == 6.00 && ubMinorMapVersion == 26)
2322 {
2323 // the data appears to be 37 INT32/UINT32 numbers and is present in russian ja2 maps
2324 FileSeek(f, 148, FILE_SEEK_FROM_CURRENT);
2325 }
2326
2327 SetRelativeStartAndEndPercentage(0, 58, 59, "Loading room information...");
2328 RenderProgressBar(0, 100);
2329
2330 FileRead(f, gubWorldRoomInfo, sizeof(gubWorldRoomInfo));
2331
2332 if(GameState::getInstance()->isEditorMode())
2333 {
2334 UINT8 max_room_no = 0;
2335 for (INT32 cnt = 0; cnt != WORLD_MAX; ++cnt)
2336 {
2337 if (max_room_no < gubWorldRoomInfo[cnt])
2338 max_room_no = gubWorldRoomInfo[cnt];
2339 }
2340 if (max_room_no < 255) ++max_room_no;
2341 gubMaxRoomNumber = max_room_no;
2342 }
2343
2344 std::fill(std::begin(gubWorldRoomHidden), std::end(gubWorldRoomHidden), TRUE);
2345
2346 SetRelativeStartAndEndPercentage(0, 59, 61, "Loading items...");
2347 RenderProgressBar(0, 100);
2348
2349 if (uiFlags & MAP_WORLDITEMS_SAVED)
2350 { // Load out item information
2351 LoadWorldItemsFromMap(f);
2352 }
2353
2354 SetRelativeStartAndEndPercentage(0, 62, 85, "Loading lights...");
2355 RenderProgressBar(0, 0);
2356
2357 if (uiFlags & MAP_AMBIENTLIGHTLEVEL_SAVED)
2358 { // Ambient light levels are only saved in underground levels
2359 FileRead(f, &gfBasement, sizeof(gfBasement));
2360 FileRead(f, &gfCaves, sizeof(gfCaves));
2361 FileRead(f, &ubAmbientLightLevel, sizeof(ubAmbientLightLevel));
2362 }
2363 else
2364 { // We are above ground.
2365 gfBasement = FALSE;
2366 gfCaves = FALSE;
2367 if (!gfEditMode)
2368 {
2369 ubAmbientLightLevel = GetTimeOfDayAmbientLightLevel();
2370 }
2371 else
2372 {
2373 ubAmbientLightLevel = 4;
2374 }
2375 }
2376 if (uiFlags & MAP_WORLDLIGHTS_SAVED)
2377 {
2378 LoadMapLights(f);
2379 }
2380 else
2381 { // Set some default value for lighting
2382 SetDefaultWorldLightingColors();
2383 }
2384 LightSetBaseLevel(ubAmbientLightLevel);
2385
2386 SetRelativeStartAndEndPercentage(0, 85, 86, "Loading map information...");
2387 RenderProgressBar(0, 0);
2388
2389 LoadMapInformation(f);
2390
2391 if (dMajorMapVersion >= 4.00 && gMapInformation.ubMapVersion != ubMinorMapVersion)
2392 {
2393 throw new std::runtime_error("map version must match minor version");
2394 }
2395
2396 if (uiFlags & MAP_FULLSOLDIER_SAVED)
2397 {
2398 SetRelativeStartAndEndPercentage(0, 86, 87, "Loading placements...");
2399 RenderProgressBar(0, 0);
2400 LoadSoldiersFromMap(f, false);
2401 }
2402 if (uiFlags & MAP_EXITGRIDS_SAVED)
2403 {
2404 SetRelativeStartAndEndPercentage(0, 87, 88, "Loading exit grids...");
2405 RenderProgressBar(0, 0);
2406 LoadExitGrids(f);
2407 }
2408 if (uiFlags & MAP_DOORTABLE_SAVED)
2409 {
2410 SetRelativeStartAndEndPercentage(0, 89, 90, "Loading door tables...");
2411 RenderProgressBar(0, 0);
2412 LoadDoorTableFromMap(f);
2413 }
2414 bool generate_edge_points;
2415 if (uiFlags & MAP_EDGEPOINTS_SAVED)
2416 {
2417 SetRelativeStartAndEndPercentage(0, 90, 91, "Loading edgepoints...");
2418 RenderProgressBar(0, 0);
2419 // Only if the map had the older edgepoint system
2420 generate_edge_points = !LoadMapEdgepoints(f);
2421 }
2422 else
2423 {
2424 generate_edge_points = true;
2425 }
2426 if (uiFlags & MAP_NPCSCHEDULES_SAVED)
2427 {
2428 SetRelativeStartAndEndPercentage(0, 91, 92, "Loading NPC schedules...");
2429 RenderProgressBar(0, 0);
2430 LoadSchedules(f);
2431 }
2432
2433 // check unexpected versions
2434 if (dMajorMapVersion == 6.00 && ubMinorMapVersion == 26)
2435 {
2436 // the unknown data is skipped
2437 SLOGD("%s is a russian ja2 map", filename);
2438 }
2439 else if (dMajorMapVersion == 5.00 && ubMinorMapVersion >= 24 && ubMinorMapVersion <= 25)
2440 {
2441 SLOGD("%s is a non-russian ja2 map", filename);
2442 }
2443 else if (dMajorMapVersion == 5.00 && ubMinorMapVersion == 26)
2444 {
2445 // file structure is the same but the game has different items
2446 SLOGW("%s is a ja2 wildfire map, expect problems", filename);
2447 }
2448 else
2449 {
2450 // ja2 demo has 3.13
2451 SLOGW("%s has an unexpected version (%f %u), expect problems", filename, dMajorMapVersion, gMapInformation.ubMapVersion);
2452 }
2453
2454 ValidateAndUpdateMapVersionIfNecessary();
2455
2456 SetRelativeStartAndEndPercentage(0, 93, 94, "Init Loaded World...");
2457 RenderProgressBar(0, 0);
2458 InitLoadedWorld();
2459
2460 if (generate_edge_points)
2461 {
2462 SetRelativeStartAndEndPercentage(0, 94, 95, "Generating map edgepoints...");
2463 RenderProgressBar(0, 0);
2464 CompileWorldMovementCosts();
2465 GenerateMapEdgepoints();
2466 }
2467
2468 RenderProgressBar(0, 20);
2469
2470 SetRelativeStartAndEndPercentage(0, 95, 100, "General initialization...");
2471 // Reset AI!
2472 InitOpponentKnowledgeSystem();
2473
2474 RenderProgressBar(0, 30);
2475 RenderProgressBar(0, 40);
2476
2477 // Check if our selected guy is gone!
2478 if (g_selected_man && !g_selected_man->bActive)
2479 {
2480 SetSelectedMan(0);
2481 }
2482
2483 RenderProgressBar(0, 60);
2484
2485 InvalidateWorldRedundency();
2486
2487 RenderProgressBar(0, 80);
2488
2489 gfWorldLoaded = TRUE;
2490
2491 if(GameState::getInstance()->isEditorMode())
2492 {
2493 strlcpy(g_filename, filename, lengthof(g_filename));
2494 }
2495
2496 RenderProgressBar(0, 100);
2497 DequeueAllKeyBoardEvents();
2498 }
2499 catch (const std::runtime_error& err)
2500 {
2501 SET_ERROR(ST::format("Could not load map file '{}': {}", filename, err.what()));
2502 throw;
2503 }
2504
2505
NewWorld()2506 void NewWorld()
2507 {
2508 SetSelectedMan(0);
2509 TrashWorld();
2510
2511 // Create world randomly from tiles
2512 for (INT32 cnt = 0; cnt != WORLD_MAX; ++cnt)
2513 {
2514 // Set land index
2515 UINT16 const idx = Random(10);
2516 AddLandToHead(cnt, idx);
2517 }
2518
2519 InitRoomDatabase();
2520 gfWorldLoaded = TRUE;
2521 }
2522
2523
FreeLevelNodeList(LEVELNODE ** const head)2524 void FreeLevelNodeList(LEVELNODE** const head)
2525 {
2526 LEVELNODE* i = *head;
2527 *head = NULL;
2528
2529 while (i != NULL)
2530 {
2531 LEVELNODE* const next = i->pNext;
2532 delete i;
2533 i = next;
2534 }
2535 }
2536
2537
TrashWorld(void)2538 void TrashWorld(void)
2539 {
2540 if (!gfWorldLoaded) return;
2541
2542 TrashWorldItems();
2543 TrashOverheadMap();
2544 ResetSmokeEffects();
2545 ResetLightEffects();
2546
2547 // Set soldiers to not active!
2548 FOR_EACH_SOLDIER(s)
2549 {
2550 if (s->bTeam == OUR_TEAM)
2551 {
2552 s->pLevelNode = NULL; // Just delete levelnode
2553 }
2554 else
2555 {
2556 TacticalRemoveSoldier(*s); // Delete from world
2557 }
2558 }
2559
2560 RemoveCorpses();
2561 DeleteAniTiles();
2562
2563 // Kill both soldier init lists.
2564 UseEditorAlternateList();
2565 KillSoldierInitList();
2566 UseEditorOriginalList();
2567 KillSoldierInitList();
2568
2569 DestroyAllSchedules();
2570
2571 // On trash world check if we have to set up the first meanwhile
2572 HandleFirstMeanWhileSetUpWithTrashWorld();
2573
2574 FOR_EACH_WORLD_TILE(me)
2575 {
2576 // Free the memory associated with the map tile link lists
2577 FreeLevelNodeList(&me->pLandHead);
2578 FreeLevelNodeList(&me->pObjectHead);
2579 FreeLevelNodeList(&me->pStructHead);
2580 FreeLevelNodeList(&me->pShadowHead);
2581 FreeLevelNodeList(&me->pMercHead);
2582 FreeLevelNodeList(&me->pRoofHead);
2583 FreeLevelNodeList(&me->pOnRoofHead);
2584 FreeLevelNodeList(&me->pTopmostHead);
2585
2586 while (me->pStructureHead != NULL)
2587 {
2588 if (!DeleteStructureFromWorld(me->pStructureHead))
2589 {
2590 // ERROR!!!!!!
2591 break;
2592 }
2593 }
2594 }
2595
2596 // Zero world
2597 std::fill_n(gpWorldLevelData, WORLD_MAX, MAP_ELEMENT{});
2598
2599 // Set some default flags
2600 FOR_EACH_WORLD_TILE(i)
2601 {
2602 i->uiFlags |= MAPELEMENT_RECALCULATE_WIREFRAMES;
2603 }
2604
2605 TrashDoorTable();
2606 TrashMapEdgepoints();
2607 TrashDoorStatusArray();
2608
2609 gfWorldLoaded = FALSE;
2610 if(GameState::getInstance()->isEditorMode())
2611 {
2612 strcpy(g_filename, "none");
2613 }
2614 }
2615
TrashMapTile(const INT16 MapTile)2616 static void TrashMapTile(const INT16 MapTile)
2617 {
2618 MAP_ELEMENT* const me = &gpWorldLevelData[MapTile];
2619
2620 // Free the memory associated with the map tile link lists
2621 me->pLandStart = NULL;
2622 FreeLevelNodeList(&me->pLandHead);
2623 FreeLevelNodeList(&me->pObjectHead);
2624 FreeLevelNodeList(&me->pStructHead);
2625 FreeLevelNodeList(&me->pShadowHead);
2626 FreeLevelNodeList(&me->pMercHead);
2627 FreeLevelNodeList(&me->pRoofHead);
2628 FreeLevelNodeList(&me->pOnRoofHead);
2629 FreeLevelNodeList(&me->pTopmostHead);
2630
2631 while (me->pStructureHead != NULL)
2632 {
2633 DeleteStructureFromWorld(me->pStructureHead);
2634 }
2635 }
2636
LoadMapTileset(TileSetID const id)2637 void LoadMapTileset(TileSetID const id)
2638 {
2639 if (id >= gubNumTilesets)
2640 {
2641 throw std::logic_error("Tried to load tileset with invalid ID");
2642 }
2643
2644 // Init tile surface used values
2645 std::fill(std::begin(gbNewTileSurfaceLoaded), std::end(gbNewTileSurfaceLoaded), 0);
2646
2647 if (id == giCurrentTilesetID) return;
2648
2649 TILESET const& t = gTilesets[id];
2650 LoadTileSurfaces(id);
2651
2652 // Set terrain costs
2653 if (t.MovementCostFnc)
2654 {
2655 t.MovementCostFnc();
2656 }
2657 else
2658 {
2659 SLOGD("Tileset %d has no callback function for movement costs. Using default.", id);
2660 SetTilesetOneTerrainValues();
2661 }
2662
2663 DeallocateTileDatabase();
2664 CreateTileDatabase();
2665
2666 // Set global id for tileset (for saving!)
2667 giCurrentTilesetID = id;
2668 }
2669
2670
AddWireFrame(GridNo const gridno,UINT16 const idx,bool const forced)2671 static void AddWireFrame(GridNo const gridno, UINT16 const idx, bool const forced)
2672 {
2673 for (LEVELNODE* i = gpWorldLevelData[gridno].pTopmostHead; i; i = i->pNext)
2674 { // Check if one of the same type exists!
2675 if (i->usIndex == idx) return;
2676 }
2677
2678 LEVELNODE* const n = AddTopmostToTail(gridno, idx);
2679 if (forced) n->uiFlags |= LEVELNODE_WIREFRAME;
2680 }
2681
2682
GetWireframeGraphicNumToUseForWall(const INT16 sGridNo,STRUCTURE * const s)2683 static UINT16 GetWireframeGraphicNumToUseForWall(const INT16 sGridNo, STRUCTURE* const s)
2684 {
2685 const STRUCTURE* const base_structure = FindBaseStructure(s);
2686 if (base_structure)
2687 {
2688 // Find levelnode...
2689 for (const LEVELNODE* n = gpWorldLevelData[sGridNo].pStructHead; n != NULL; n = n->pNext)
2690 {
2691 if (n->pStructureData == base_structure)
2692 {
2693 // Get Subindex for this wall...
2694 const UINT16 usSubIndex = GetSubIndexFromTileIndex(n->usIndex);
2695 switch (usSubIndex) // Check for broken pieces...
2696 {
2697 case 48:
2698 case 52: return WIREFRAMES12;
2699 case 49:
2700 case 53: return WIREFRAMES13;
2701 case 50:
2702 case 54: return WIREFRAMES10;
2703 case 51:
2704 case 55: return WIREFRAMES11;
2705 }
2706 break;
2707 }
2708 }
2709 }
2710
2711 switch (s->ubWallOrientation)
2712 {
2713 case OUTSIDE_TOP_LEFT:
2714 case INSIDE_TOP_LEFT: return WIREFRAMES6; break;
2715 case OUTSIDE_TOP_RIGHT:
2716 case INSIDE_TOP_RIGHT: return WIREFRAMES5; break;
2717 }
2718
2719 return 0;
2720 }
2721
2722
2723 static bool IsHiddenTileMarkerThere(GridNo);
2724 static bool IsRoofVisibleForWireframe(GridNo);
2725 static void RemoveWireFrameTiles(GridNo);
2726
2727
CalculateWorldWireFrameTiles(BOOLEAN fForce)2728 void CalculateWorldWireFrameTiles( BOOLEAN fForce )
2729 {
2730 INT32 cnt;
2731 STRUCTURE *pStructure;
2732 INT16 sGridNo;
2733 UINT8 ubWallOrientation;
2734 INT8 bNumWallsSameGridNo;
2735 UINT16 usWireFrameIndex;
2736
2737 // Create world randomly from tiles
2738 for ( cnt = 0; cnt < WORLD_MAX; cnt++ )
2739 {
2740 if ( gpWorldLevelData[ cnt ].uiFlags & MAPELEMENT_RECALCULATE_WIREFRAMES || fForce )
2741 {
2742 // Turn off flag
2743 gpWorldLevelData[ cnt ].uiFlags &= (~MAPELEMENT_RECALCULATE_WIREFRAMES );
2744
2745 // Remove old ones
2746 RemoveWireFrameTiles( (INT16)cnt );
2747
2748 bNumWallsSameGridNo = 0;
2749
2750 // Check our gridno, if we have a roof over us that has not beenr evealed, no need for a wiereframe
2751 if ( IsRoofVisibleForWireframe( (UINT16)cnt ) && !( gpWorldLevelData[ cnt ].uiFlags & MAPELEMENT_REVEALED ) )
2752 {
2753 continue;
2754 }
2755
2756 pStructure = gpWorldLevelData[ cnt ].pStructureHead;
2757
2758 while ( pStructure != NULL )
2759 {
2760 // Check for doors
2761 if ( pStructure->fFlags & STRUCTURE_ANYDOOR )
2762 {
2763 // ATE: need this additional check here for hidden doors!
2764 if ( pStructure->fFlags & STRUCTURE_OPENABLE )
2765 {
2766 // Does the gridno we are over have a non-visible tile?
2767 // Based on orientation
2768 ubWallOrientation = pStructure->ubWallOrientation;
2769
2770 switch( ubWallOrientation )
2771 {
2772 case OUTSIDE_TOP_LEFT:
2773 case INSIDE_TOP_LEFT:
2774
2775 // Get gridno
2776 sGridNo = NewGridNo( (INT16)cnt, DirectionInc( SOUTH ) );
2777
2778 if ( IsRoofVisibleForWireframe( sGridNo ) && !( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REVEALED ) )
2779 {
2780 AddWireFrame((INT16)cnt, WIREFRAMES4, (gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REVEALED) != 0);
2781 }
2782 break;
2783
2784 case OUTSIDE_TOP_RIGHT:
2785 case INSIDE_TOP_RIGHT:
2786
2787 // Get gridno
2788 sGridNo = NewGridNo( (INT16)cnt, DirectionInc( EAST ) );
2789
2790 if ( IsRoofVisibleForWireframe( sGridNo ) && !( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REVEALED ) )
2791 {
2792 AddWireFrame((INT16)cnt, WIREFRAMES3, (gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REVEALED) != 0);
2793 }
2794 break;
2795
2796 }
2797 }
2798 }
2799 // Check for windows
2800 else
2801 {
2802 if ( pStructure->fFlags & STRUCTURE_WALLNWINDOW )
2803 {
2804 // Does the gridno we are over have a non-visible tile?
2805 // Based on orientation
2806 ubWallOrientation = pStructure->ubWallOrientation;
2807
2808 switch( ubWallOrientation )
2809 {
2810 case OUTSIDE_TOP_LEFT:
2811 case INSIDE_TOP_LEFT:
2812
2813 // Get gridno
2814 sGridNo = NewGridNo( (INT16)cnt, DirectionInc( SOUTH ) );
2815
2816 if ( IsRoofVisibleForWireframe( sGridNo ) && !( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REVEALED ) )
2817 {
2818 AddWireFrame((INT16)cnt, WIREFRAMES2, (gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REVEALED) != 0);
2819 }
2820 break;
2821
2822 case OUTSIDE_TOP_RIGHT:
2823 case INSIDE_TOP_RIGHT:
2824
2825 // Get gridno
2826 sGridNo = NewGridNo( (INT16)cnt, DirectionInc( EAST ) );
2827
2828 if ( IsRoofVisibleForWireframe( sGridNo ) && !( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REVEALED ) )
2829 {
2830 AddWireFrame((INT16)cnt, WIREFRAMES1, (gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REVEALED) != 0);
2831 }
2832 break;
2833
2834 }
2835
2836 }
2837
2838 // Check for walls
2839 if ( pStructure->fFlags & STRUCTURE_WALLSTUFF )
2840 {
2841 // Does the gridno we are over have a non-visible tile?
2842 // Based on orientation
2843 ubWallOrientation = pStructure->ubWallOrientation;
2844
2845 usWireFrameIndex = GetWireframeGraphicNumToUseForWall( (UINT16)cnt, pStructure );
2846
2847 switch( ubWallOrientation )
2848 {
2849 case OUTSIDE_TOP_LEFT:
2850 case INSIDE_TOP_LEFT:
2851
2852 // Get gridno
2853 sGridNo = NewGridNo( (INT16)cnt, DirectionInc( SOUTH ) );
2854
2855 if ( IsRoofVisibleForWireframe( sGridNo ) )
2856 {
2857 bNumWallsSameGridNo++;
2858
2859 AddWireFrame((INT16)cnt, usWireFrameIndex, (gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REVEALED) != 0);
2860
2861 // Check along our direction to see if we are a corner
2862 sGridNo = NewGridNo( (INT16)cnt, DirectionInc( WEST ) );
2863 sGridNo = NewGridNo( sGridNo, DirectionInc( SOUTH ) );
2864 if (!IsHiddenTileMarkerThere(sGridNo))
2865 {
2866 // Place corner!
2867 AddWireFrame((INT16)cnt, WIREFRAMES9, (gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REVEALED) != 0);
2868 }
2869 }
2870 break;
2871
2872 case OUTSIDE_TOP_RIGHT:
2873 case INSIDE_TOP_RIGHT:
2874
2875 // Get gridno
2876 sGridNo = NewGridNo( (INT16)cnt, DirectionInc( EAST ) );
2877
2878 if ( IsRoofVisibleForWireframe( sGridNo ) )
2879 {
2880 bNumWallsSameGridNo++;
2881
2882 AddWireFrame((INT16)cnt, usWireFrameIndex, (gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REVEALED) != 0);
2883
2884 // Check along our direction to see if we are a corner
2885 sGridNo = NewGridNo( (INT16)cnt, DirectionInc( NORTH ) );
2886 sGridNo = NewGridNo( sGridNo, DirectionInc( EAST ) );
2887 if (!IsHiddenTileMarkerThere(sGridNo))
2888 {
2889 // Place corner!
2890 AddWireFrame((INT16)cnt, WIREFRAMES8, (gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REVEALED) != 0);
2891 }
2892
2893 }
2894 break;
2895
2896 }
2897
2898 // Check for both walls
2899 if ( bNumWallsSameGridNo == 2 )
2900 {
2901 sGridNo = NewGridNo( (INT16)cnt, DirectionInc( EAST ) );
2902 sGridNo = NewGridNo( sGridNo, DirectionInc( SOUTH ) );
2903 AddWireFrame((INT16)cnt, WIREFRAMES7, (gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REVEALED) != 0);
2904 }
2905 }
2906 }
2907
2908 pStructure = pStructure->pNext;
2909 }
2910 }
2911 }
2912 }
2913
2914
RemoveWorldWireFrameTiles()2915 static void RemoveWorldWireFrameTiles()
2916 {
2917 for (INT32 cnt = 0; cnt != WORLD_MAX; ++cnt)
2918 {
2919 RemoveWireFrameTiles(cnt);
2920 }
2921 }
2922
2923
RemoveWireFrameTiles(GridNo const gridno)2924 static void RemoveWireFrameTiles(GridNo const gridno)
2925 {
2926 for (LEVELNODE* i = gpWorldLevelData[gridno].pTopmostHead; i;)
2927 {
2928 LEVELNODE* const next = i->pNext;
2929
2930 if (i->usIndex < NUMBEROFTILES &&
2931 gTileDatabase[i->usIndex].fType == WIREFRAMES)
2932 {
2933 RemoveTopmost(gridno, i->usIndex);
2934 }
2935
2936 i = next;
2937 }
2938 }
2939
2940
IsHiddenTileMarkerThere(GridNo const gridno)2941 static bool IsHiddenTileMarkerThere(GridNo const gridno)
2942 {
2943 return gfBasement || FindStructure(gridno, STRUCTURE_ROOF);
2944 }
2945
2946
ReloadTileset(TileSetID const ubID)2947 void ReloadTileset(TileSetID const ubID)
2948 {
2949 TileSetID const iCurrTilesetID = giCurrentTilesetID;
2950
2951 // Set gloabal
2952 giCurrentTilesetID = ubID;
2953
2954 // Save Map
2955 SaveWorld( TEMP_FILE_FOR_TILESET_CHANGE );
2956
2957 //IMPORTANT: If this is not set, the LoadTileset() will assume that
2958 //it is loading the same tileset and ignore it...
2959 giCurrentTilesetID = iCurrTilesetID;
2960
2961 // Load Map with new tileset
2962 LoadWorld( TEMP_FILE_FOR_TILESET_CHANGE );
2963
2964 // Delete file
2965 FileDelete(GCM->getMapPath(TEMP_FILE_FOR_TILESET_CHANGE).c_str());
2966 }
2967
2968
IsSoldierLight(const LIGHT_SPRITE * const l)2969 BOOLEAN IsSoldierLight(const LIGHT_SPRITE* const l)
2970 {
2971 CFOR_EACH_SOLDIER(s)
2972 {
2973 if (s->light == l) return TRUE;
2974 }
2975 return FALSE;
2976 }
2977
2978
SaveMapLights(HWFILE hfile)2979 static void SaveMapLights(HWFILE hfile)
2980 {
2981 UINT16 usNumLights = 0;
2982
2983 // Save the current light colors!
2984 const UINT8 ubNumColors = 1;
2985 FileWrite(hfile, &ubNumColors, 1);
2986 const SGPPaletteEntry* LColor = LightGetColor();
2987 FileWrite(hfile, LColor, sizeof(*LColor));
2988
2989 //count number of non-merc lights.
2990 CFOR_EACH_LIGHT_SPRITE(l)
2991 {
2992 if (!IsSoldierLight(l)) ++usNumLights;
2993 }
2994
2995 //save the number of lights.
2996 FileWrite(hfile, &usNumLights, 2);
2997
2998 CFOR_EACH_LIGHT_SPRITE(l)
2999 {
3000 if (!IsSoldierLight(l)) InjectLightSpriteIntoFile(hfile, l);
3001 }
3002 }
3003
3004
LoadMapLights(HWFILE const f)3005 static void LoadMapLights(HWFILE const f)
3006 {
3007 SGPPaletteEntry LColors[3];
3008 UINT8 ubNumColors;
3009 UINT16 usNumLights;
3010
3011 //reset the lighting system, so that any current lights are toasted.
3012 LightReset();
3013
3014 // read in the light colors!
3015 FileRead(f, &ubNumColors, sizeof(ubNumColors));
3016 FileRead(f, LColors, sizeof(*LColors) * ubNumColors); // XXX buffer overflow if ubNumColors is too large
3017
3018 LightSetColor(LColors);
3019
3020 //Determine which lights are valid for the current time.
3021 UINT32 light_time = 0;
3022 if( !gfEditMode )
3023 {
3024 const UINT32 uiHour = GetWorldHour();
3025 if( uiHour >= NIGHT_TIME_LIGHT_START_HOUR || uiHour < NIGHT_TIME_LIGHT_END_HOUR )
3026 {
3027 light_time |= LIGHT_NIGHTTIME;
3028 }
3029 if( uiHour >= PRIME_TIME_LIGHT_START_HOUR )
3030 {
3031 light_time |= LIGHT_PRIMETIME;
3032 }
3033 }
3034
3035 FileRead(f, &usNumLights, sizeof(usNumLights));
3036 for (INT32 cnt = 0; cnt < usNumLights; ++cnt)
3037 {
3038 ExtractLightSprite(f, light_time);
3039 }
3040 }
3041
3042
IsRoofVisibleForWireframe(GridNo const sMapPos)3043 static bool IsRoofVisibleForWireframe(GridNo const sMapPos)
3044 {
3045 return gfBasement || FindStructure(sMapPos, STRUCTURE_ROOF);
3046 }
3047
3048
3049 #ifdef WITH_UNITTESTS
3050 #include "gtest/gtest.h"
3051
TEST(WorldDef,asserts)3052 TEST(WorldDef, asserts)
3053 {
3054 EXPECT_EQ(sizeof(TEAMSUMMARY), 15u);
3055 EXPECT_EQ(sizeof(SUMMARYFILE), 408u);
3056 }
3057
3058 #endif
3059