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