1 #include "Buffer.h"
2 #include "Map_Edgepoints.h"
3 #include "Soldier_Control.h"
4 #include "PathAI.h"
5 #include "AI.h"
6 #include "Map_Information.h"
7 #include "Isometric_Utils.h"
8 #include "Debug.h"
9 #include "Random.h"
10 #include "Strategic.h"
11 #include "Animation_Control.h"
12 #include "Render_Fun.h"
13 #include "StrategicMap.h"
14 #include "Environment.h"
15 #include "TileDef.h"
16 #include "WorldMan.h"
17 #include "MemMan.h"
18 #include "FileMan.h"
19 
20 #include "Message.h"
21 
22 #include <vector>
23 
24 
25 //dynamic arrays that contain the valid gridno's for each edge
26 std::vector<INT16> gps1stNorthEdgepointArray;
27 std::vector<INT16> gps1stEastEdgepointArray;
28 std::vector<INT16> gps1stSouthEdgepointArray;
29 std::vector<INT16> gps1stWestEdgepointArray;
30 //contains the index value for the first array index of the second row of each edgepoint array.
31 //Because each edgepoint side has two rows, the outside most row is calculated first, then the inside row.
32 //For purposes of AI, it may become necessary to avoid this.
33 UINT16 gus1stNorthEdgepointMiddleIndex		= 0;
34 UINT16 gus1stEastEdgepointMiddleIndex			= 0;
35 UINT16 gus1stSouthEdgepointMiddleIndex		= 0;
36 UINT16 gus1stWestEdgepointMiddleIndex			= 0;
37 
38 //dynamic arrays that contain the valid gridno's for each edge
39 std::vector<INT16> gps2ndNorthEdgepointArray;
40 std::vector<INT16> gps2ndEastEdgepointArray;
41 std::vector<INT16> gps2ndSouthEdgepointArray;
42 std::vector<INT16> gps2ndWestEdgepointArray;
43 //contains the index value for the first array index of the second row of each edgepoint array.
44 //Because each edgepoint side has two rows, the outside most row is calculated first, then the inside row.
45 //For purposes of AI, it may become necessary to avoid this.
46 UINT16 gus2ndNorthEdgepointMiddleIndex		= 0;
47 UINT16 gus2ndEastEdgepointMiddleIndex			= 0;
48 UINT16 gus2ndSouthEdgepointMiddleIndex		= 0;
49 UINT16 gus2ndWestEdgepointMiddleIndex			= 0;
50 
51 BOOLEAN gfEdgepointsExist = FALSE;
52 BOOLEAN gfGeneratingMapEdgepoints = FALSE;
53 
54 INT16 gsTLGridNo = 13286;
55 INT16 gsTRGridNo = 1043;
56 INT16 gsBLGridNo = 24878;
57 INT16 gsBRGridNo = 12635;
58 
59 extern UINT8 gubTacticalDirection;
60 
TrashMapEdgepoints()61 void TrashMapEdgepoints()
62 {
63 	//Primary edgepoints
64 	gps1stNorthEdgepointArray.clear();
65 	gps1stEastEdgepointArray.clear();
66 	gps1stSouthEdgepointArray.clear();
67 	gps1stWestEdgepointArray.clear();
68 	gus1stNorthEdgepointMiddleIndex		= 0;
69 	gus1stEastEdgepointMiddleIndex		= 0;
70 	gus1stSouthEdgepointMiddleIndex		= 0;
71 	gus1stWestEdgepointMiddleIndex		= 0;
72 	//Secondary edgepoints
73 	gps2ndNorthEdgepointArray.clear();
74 	gps2ndEastEdgepointArray.clear();
75 	gps2ndSouthEdgepointArray.clear();
76 	gps2ndWestEdgepointArray.clear();
77 	gus2ndNorthEdgepointMiddleIndex		= 0;
78 	gus2ndEastEdgepointMiddleIndex		= 0;
79 	gus2ndSouthEdgepointMiddleIndex		= 0;
80 	gus2ndWestEdgepointMiddleIndex		= 0;
81 }
82 
83 
84 static BOOLEAN VerifyEdgepoint(SOLDIERTYPE* pSoldier, INT16 sEdgepoint);
85 
86 
ValidateMapEdge(SOLDIERTYPE & s,UINT16 & middle_idx,std::vector<INT16> & edgepoints)87 static void ValidateMapEdge(SOLDIERTYPE& s, UINT16& middle_idx, std::vector<INT16>& edgepoints)
88 {
89 	size_t dst = 0;
90 	size_t middle = middle_idx;
91 	size_t end = edgepoints.size();
92 	Assert(end <= UINT16_MAX);
93 	for (size_t i = 0; i != end; ++i)
94 	{
95 		if (VerifyEdgepoint(&s, edgepoints[i]))
96 		{
97 			edgepoints[dst] = edgepoints[i];
98 			if (middle == i) middle = dst; // Adjust the middle index to the new one
99 			++dst;
100 		}
101 		else if (middle == i)
102 		{ // Increment the middle index because its edgepoint is no longer valid
103 			++middle;
104 		}
105 	}
106 	middle_idx = static_cast<UINT16>(middle);
107 	edgepoints.resize(dst);
108 }
109 
110 
111 /* This final step eliminates some edgepoints which actually don't path directly
112 	* to the edge of the map. Cases would include an area that is close to the
113 	* edge, but a fence blocks it from direct access to the edge of the map. */
ValidateEdgepoints()114 static void ValidateEdgepoints()
115 {
116 	SOLDIERTYPE s;
117 	s = SOLDIERTYPE{};
118 	s.bTeam = ENEMY_TEAM;
119 
120 	ValidateMapEdge(s, gus1stNorthEdgepointMiddleIndex, gps1stNorthEdgepointArray);
121 	ValidateMapEdge(s, gus1stEastEdgepointMiddleIndex,  gps1stEastEdgepointArray);
122 	ValidateMapEdge(s, gus1stSouthEdgepointMiddleIndex, gps1stSouthEdgepointArray);
123 	ValidateMapEdge(s, gus1stWestEdgepointMiddleIndex,  gps1stWestEdgepointArray);
124 
125 	ValidateMapEdge(s, gus2ndNorthEdgepointMiddleIndex, gps2ndNorthEdgepointArray);
126 	ValidateMapEdge(s, gus2ndEastEdgepointMiddleIndex,  gps2ndEastEdgepointArray);
127 	ValidateMapEdge(s, gus2ndSouthEdgepointMiddleIndex, gps2ndSouthEdgepointArray);
128 	ValidateMapEdge(s, gus2ndWestEdgepointMiddleIndex,  gps2ndWestEdgepointArray);
129 }
130 
131 
CompactEdgepointArray(std::vector<INT16> & edgepoints,UINT16 & middle)132 static void CompactEdgepointArray(std::vector<INT16>& edgepoints, UINT16& middle)
133 {
134 	size_t n = 0;
135 	for (size_t i = 0; i < edgepoints.size(); i++)
136 	{
137 		if (edgepoints[ i ] == -1)
138 		{
139 			if (i < middle)
140 			{
141 				middle--;
142 			}
143 		}
144 		else
145 		{
146 			if (n != i)
147 			{
148 				edgepoints[ n ] = edgepoints[ i ];
149 			}
150 			n++;
151 		}
152 	}
153 	edgepoints.resize(n);
154 }
155 
156 
157 static BOOLEAN EdgepointsClose(SOLDIERTYPE* pSoldier, INT16 sEdgepoint1, INT16 sEdgepoint2);
158 
159 
InternallyClassifyEdgepoints(SOLDIERTYPE * pSoldier,INT16 sGridNo,std::vector<INT16> & edgepoints1,UINT16 & middle1,std::vector<INT16> & edgepoints2,UINT16 & middle2)160 static void InternallyClassifyEdgepoints(SOLDIERTYPE* pSoldier, INT16 sGridNo, std::vector<INT16>& edgepoints1, UINT16& middle1, std::vector<INT16>& edgepoints2, UINT16& middle2)
161 {
162 	size_t i;
163 	UINT16 us1stBenchmarkID, us2ndBenchmarkID;
164 	us1stBenchmarkID = us2ndBenchmarkID = 0xffff;
165 	for (i = 0; i < edgepoints1.size(); i++)
166 	{
167 		if (sGridNo == edgepoints1[ i ])
168 		{
169 			if (i < middle1)
170 			{ //in the first half of the array
171 				us1stBenchmarkID = (UINT16)i;
172 				//find the second benchmark
173 				for (i = middle1; i < edgepoints1.size(); i++)
174 				{
175 					if (EdgepointsClose( pSoldier, edgepoints1[ us1stBenchmarkID ], edgepoints1[ i ] ))
176 					{
177 						us2ndBenchmarkID = (UINT16)i;
178 						break;
179 					}
180 				}
181 			}
182 			else
183 			{ //in the second half of the array
184 				us2ndBenchmarkID = (UINT16)i;
185 				//find the first benchmark
186 				for (i = 0; i < middle1; i++)
187 				{
188 					if (EdgepointsClose( pSoldier, edgepoints1[ us2ndBenchmarkID ], edgepoints1[ i ] ))
189 					{
190 						us1stBenchmarkID = (UINT16)i;
191 						break;
192 					}
193 				}
194 			}
195 			break;
196 		}
197 	}
198 	//Now we have found the two benchmarks, so go in both directions for each one to determine which entrypoints
199 	//are going to be used in the primary array.  All rejections will be positioned in the secondary array for
200 	//use for isolated entry when tactically traversing.
201 	if( us1stBenchmarkID != 0xffff )
202 	{
203 		for( i = us1stBenchmarkID; i > 0; i-- )
204 		{
205 			if (!EdgepointsClose( pSoldier, edgepoints1[ i ], edgepoints1[ i-1 ] ))
206 			{ //All edgepoints from index 0 to i-1 are rejected.
207 				while( i )
208 				{
209 					i--;
210 					edgepoints2.push_back(edgepoints1[ i ]);
211 					middle2++;
212 					edgepoints1[ i ] = -1;
213 				}
214 				break;
215 			}
216 		}
217 		for (i = us1stBenchmarkID; (INT32)i < middle1 - 1; i++)
218 		{
219 			if (!EdgepointsClose( pSoldier, edgepoints1[ i ], edgepoints1[ i+1 ] ))
220 			{ //All edgepoints from index i+1 to 1st middle index are rejected.
221 				while ((INT32)i < middle1 - 1)
222 				{
223 					i++;
224 					edgepoints2.push_back(edgepoints1[ i ]);
225 					middle2++;
226 					edgepoints1[ i ] = -1;
227 				}
228 				break;
229 			}
230 		}
231 	}
232 	if( us2ndBenchmarkID != 0xffff )
233 	{
234 		for (i = us2ndBenchmarkID; i > middle1; i--)
235 		{
236 			if (!EdgepointsClose( pSoldier, edgepoints1[ i ], edgepoints1[ i-1 ] ))
237 			{ //All edgepoints from 1st middle index  to i-1 are rejected.
238 				while (i > middle1)
239 				{
240 					i--;
241 					edgepoints2.push_back(edgepoints1[ i ]);
242 					edgepoints1[ i ] = -1;
243 				}
244 				break;
245 			}
246 		}
247 		for (i = us2ndBenchmarkID; i < edgepoints1.size() - 1; i++)
248 		{
249 			if (!EdgepointsClose( pSoldier, edgepoints1[ i ], edgepoints1[ i+1 ] ))
250 			{ //All edgepoints from index 0 to i-1 are rejected.
251 				while (i < edgepoints1.size() - 1)
252 				{
253 					i++;
254 					edgepoints2.push_back(edgepoints1[ i ]);
255 					edgepoints1[ i ] = -1;
256 				}
257 				break;
258 			}
259 		}
260 	}
261 	//Now compact the primary array, because some edgepoints have been removed.
262 	CompactEdgepointArray(edgepoints1, middle1);
263 }
264 
265 
ClassifyEdgepoints(void)266 static void ClassifyEdgepoints(void)
267 {
268 	SOLDIERTYPE Soldier;
269 	INT16 sGridNo = -1;
270 
271 	Soldier = SOLDIERTYPE{};
272 	Soldier.bTeam = 1;
273 
274 	//north
275 	if( gMapInformation.sNorthGridNo != -1 )
276 	{
277 		sGridNo = FindNearestEdgepointOnSpecifiedEdge( gMapInformation.sNorthGridNo, NORTH_EDGEPOINT_SEARCH );
278 		if( sGridNo != NOWHERE )
279 		{
280 			InternallyClassifyEdgepoints( &Soldier, sGridNo,
281 				gps1stNorthEdgepointArray, gus1stNorthEdgepointMiddleIndex,
282 				gps2ndNorthEdgepointArray, gus2ndNorthEdgepointMiddleIndex );
283 		}
284 	}
285 	//east
286 	if( gMapInformation.sEastGridNo != -1 )
287 	{
288 		sGridNo = FindNearestEdgepointOnSpecifiedEdge( gMapInformation.sEastGridNo, EAST_EDGEPOINT_SEARCH );
289 		if( sGridNo != NOWHERE )
290 		{
291 			InternallyClassifyEdgepoints( &Soldier, sGridNo,
292 				gps1stEastEdgepointArray, gus1stEastEdgepointMiddleIndex,
293 				gps2ndEastEdgepointArray, gus2ndEastEdgepointMiddleIndex );
294 		}
295 	}
296 	//south
297 	if( gMapInformation.sSouthGridNo != -1 )
298 	{
299 		sGridNo = FindNearestEdgepointOnSpecifiedEdge( gMapInformation.sSouthGridNo, SOUTH_EDGEPOINT_SEARCH );
300 		if( sGridNo != NOWHERE )
301 		{
302 			InternallyClassifyEdgepoints( &Soldier, sGridNo,
303 				gps1stSouthEdgepointArray, gus1stSouthEdgepointMiddleIndex,
304 				gps2ndSouthEdgepointArray, gus2ndSouthEdgepointMiddleIndex );
305 		}
306 	}
307 	//west
308 	if( gMapInformation.sWestGridNo != -1 )
309 	{
310 		sGridNo = FindNearestEdgepointOnSpecifiedEdge( gMapInformation.sWestGridNo, WEST_EDGEPOINT_SEARCH );
311 		if( sGridNo != NOWHERE )
312 		{
313 			InternallyClassifyEdgepoints( &Soldier, sGridNo,
314 				gps1stWestEdgepointArray, gus1stWestEdgepointMiddleIndex,
315 				gps2ndWestEdgepointArray, gus2ndWestEdgepointMiddleIndex );
316 		}
317 	}
318 }
319 
GenerateMapEdgepoints()320 void GenerateMapEdgepoints()
321 {
322 	INT16 sGridNo=-1;
323 
324 	//Get rid of the current edgepoint lists.
325 	TrashMapEdgepoints();
326 
327 	gfGeneratingMapEdgepoints = TRUE;
328 
329 	if( gMapInformation.sNorthGridNo	!= -1 )
330 		sGridNo = gMapInformation.sNorthGridNo;
331 	else if( gMapInformation.sEastGridNo	!= -1 )
332 		sGridNo = gMapInformation.sEastGridNo;
333 	else if( gMapInformation.sSouthGridNo	!= -1 )
334 		sGridNo = gMapInformation.sSouthGridNo;
335 	else if( gMapInformation.sWestGridNo != -1 )
336 		sGridNo = gMapInformation.sWestGridNo;
337 	else if( gMapInformation.sCenterGridNo != -1 )
338 		sGridNo = gMapInformation.sCenterGridNo;
339 	else
340 		return;
341 
342 	GlobalReachableTest( sGridNo );
343 
344 	//Calculate the north edges
345 	if( gMapInformation.sNorthGridNo != -1 )
346 	{
347 		//1st row
348 		sGridNo = gsTLGridNo;
349 		if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
350 			(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
351 		{
352 			gps1stNorthEdgepointArray.push_back(sGridNo);
353 		}
354 		while( sGridNo > gsTRGridNo )
355 		{
356 			sGridNo++;
357 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
358 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
359 			{
360 				gps1stNorthEdgepointArray.push_back(sGridNo);
361 			}
362 			sGridNo -= 160;
363 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
364 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
365 			{
366 				gps1stNorthEdgepointArray.push_back(sGridNo);
367 			}
368 		}
369 		//2nd row
370 		Assert(gps1stNorthEdgepointArray.size() <= UINT16_MAX);
371 		gus1stNorthEdgepointMiddleIndex = static_cast<UINT16>(gps1stNorthEdgepointArray.size());
372 		sGridNo = gsTLGridNo + 161;
373 		if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
374 			(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
375 		{
376 			gps1stNorthEdgepointArray.push_back(sGridNo);
377 		}
378 		while( sGridNo > gsTRGridNo + 161 )
379 		{
380 			sGridNo++;
381 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
382 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
383 			{
384 				gps1stNorthEdgepointArray.push_back(sGridNo);
385 			}
386 			sGridNo -= 160;
387 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
388 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
389 			{
390 				gps1stNorthEdgepointArray.push_back(sGridNo);
391 			}
392 		}
393 	}
394 	//Calculate the east edges
395 	if( gMapInformation.sEastGridNo != -1 )
396 	{
397 		//1st row
398 		sGridNo = gsTRGridNo;
399 		if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
400 			(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
401 		{
402 			gps1stEastEdgepointArray.push_back(sGridNo);
403 		}
404 		while( sGridNo < gsBRGridNo )
405 		{
406 			sGridNo += 160;
407 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
408 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
409 			{
410 				gps1stEastEdgepointArray.push_back(sGridNo);
411 			}
412 			sGridNo++;
413 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
414 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
415 			{
416 				gps1stEastEdgepointArray.push_back(sGridNo);
417 			}
418 		}
419 		//2nd row
420 		Assert(gps1stEastEdgepointArray.size() <= UINT16_MAX);
421 		gus1stEastEdgepointMiddleIndex = static_cast<UINT16>(gps1stEastEdgepointArray.size());
422 		sGridNo = gsTRGridNo + 159;
423 		if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
424 			(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
425 		{
426 			gps1stEastEdgepointArray.push_back(sGridNo);
427 		}
428 		while( sGridNo < gsBRGridNo + 159 )
429 		{
430 			sGridNo += 160;
431 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
432 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
433 			{
434 				gps1stEastEdgepointArray.push_back(sGridNo);
435 			}
436 			sGridNo++;
437 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
438 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
439 			{
440 				gps1stEastEdgepointArray.push_back(sGridNo);
441 			}
442 		}
443 	}
444 	//Calculate the south edges
445 	if( gMapInformation.sSouthGridNo != -1 )
446 	{
447 		//1st row
448 		sGridNo = gsBLGridNo;
449 		if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
450 			(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
451 		{
452 			gps1stSouthEdgepointArray.push_back(sGridNo);
453 		}
454 		while( sGridNo > gsBRGridNo )
455 		{
456 			sGridNo++;
457 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
458 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
459 			{
460 				gps1stSouthEdgepointArray.push_back(sGridNo);
461 			}
462 			sGridNo -= 160;
463 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
464 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
465 			{
466 				gps1stSouthEdgepointArray.push_back(sGridNo);
467 			}
468 		}
469 		//2nd row
470 		Assert(gps1stSouthEdgepointArray.size() <= UINT16_MAX);
471 		gus1stSouthEdgepointMiddleIndex = static_cast<UINT16>(gps1stSouthEdgepointArray.size());
472 		sGridNo = gsBLGridNo - 161;
473 		if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
474 			(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
475 		{
476 			gps1stSouthEdgepointArray.push_back(sGridNo);
477 		}
478 		while( sGridNo > gsBRGridNo - 161 )
479 		{
480 			sGridNo++;
481 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
482 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
483 			{
484 				gps1stSouthEdgepointArray.push_back(sGridNo);
485 			}
486 			sGridNo -= 160;
487 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
488 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
489 			{
490 				gps1stSouthEdgepointArray.push_back(sGridNo);
491 			}
492 		}
493 	}
494 	//Calculate the west edges
495 	if( gMapInformation.sWestGridNo != -1 )
496 	{
497 		//1st row
498 		sGridNo = gsTLGridNo;
499 		if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
500 			(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
501 		{
502 			gps1stWestEdgepointArray.push_back(sGridNo);
503 		}
504 		while( sGridNo < gsBLGridNo )
505 		{
506 			sGridNo++;
507 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
508 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
509 			{
510 				gps1stWestEdgepointArray.push_back(sGridNo);
511 			}
512 			sGridNo += 160;
513 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
514 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
515 			{
516 				gps1stWestEdgepointArray.push_back(sGridNo);
517 			}
518 		}
519 		//2nd row
520 		Assert(gps1stWestEdgepointArray.size() <= UINT16_MAX);
521 		gus1stWestEdgepointMiddleIndex = static_cast<UINT16>(gps1stWestEdgepointArray.size());
522 		sGridNo = gsTLGridNo - 159;
523 		if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
524 			(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
525 		{
526 			gps1stWestEdgepointArray.push_back(sGridNo);
527 		}
528 		while( sGridNo < gsBLGridNo - 159 )
529 		{
530 			sGridNo++;
531 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
532 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
533 			{
534 				gps1stWestEdgepointArray.push_back(sGridNo);
535 			}
536 			sGridNo += 160;
537 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
538 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
539 			{
540 				gps1stWestEdgepointArray.push_back(sGridNo);
541 			}
542 		}
543 	}
544 
545 	//CHECK FOR ISOLATED EDGEPOINTS (but only if the entrypoint is ISOLATED!!!)
546 	if( gMapInformation.sIsolatedGridNo != -1 && !(gpWorldLevelData[ gMapInformation.sIsolatedGridNo ].uiFlags & MAPELEMENT_REACHABLE) )
547 	{
548 		GlobalReachableTest( gMapInformation.sIsolatedGridNo );
549 		if( gMapInformation.sNorthGridNo != -1 )
550 		{
551 			//1st row
552 			sGridNo = gsTLGridNo;
553 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
554 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
555 			{
556 				gps2ndNorthEdgepointArray.push_back(sGridNo);
557 			}
558 			while( sGridNo > gsTRGridNo )
559 			{
560 				sGridNo++;
561 				if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
562 					(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
563 				{
564 					gps2ndNorthEdgepointArray.push_back(sGridNo);
565 				}
566 				sGridNo -= 160;
567 				if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
568 					(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
569 				{
570 					gps2ndNorthEdgepointArray.push_back(sGridNo);
571 				}
572 			}
573 			//2nd row
574 			Assert(gps2ndNorthEdgepointArray.size() <= UINT16_MAX);
575 			gus2ndNorthEdgepointMiddleIndex = static_cast<UINT16>(gps2ndNorthEdgepointArray.size());
576 			sGridNo = gsTLGridNo + 161;
577 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
578 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
579 			{
580 				gps2ndNorthEdgepointArray.push_back(sGridNo);
581 			}
582 			while( sGridNo > gsTRGridNo + 161 )
583 			{
584 				sGridNo++;
585 				if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
586 					(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
587 				{
588 					gps2ndNorthEdgepointArray.push_back(sGridNo);
589 				}
590 				sGridNo -= 160;
591 				if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
592 					(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
593 				{
594 					gps2ndNorthEdgepointArray.push_back(sGridNo);
595 				}
596 			}
597 		}
598 		//Calculate the east edges
599 		if( gMapInformation.sEastGridNo != -1 )
600 		{
601 			//1st row
602 			sGridNo = gsTRGridNo;
603 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
604 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
605 			{
606 				gps2ndEastEdgepointArray.push_back(sGridNo);
607 			}
608 			while( sGridNo < gsBRGridNo )
609 			{
610 				sGridNo += 160;
611 				if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
612 					(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
613 				{
614 					gps2ndEastEdgepointArray.push_back(sGridNo);
615 				}
616 				sGridNo++;
617 				if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
618 					(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
619 				{
620 					gps2ndEastEdgepointArray.push_back(sGridNo);
621 				}
622 			}
623 			//2nd row
624 			Assert(gps2ndEastEdgepointArray.size() <= UINT16_MAX);
625 			gus2ndEastEdgepointMiddleIndex = static_cast<UINT16>(gps2ndEastEdgepointArray.size());
626 			sGridNo = gsTRGridNo + 159;
627 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
628 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
629 			{
630 				gps2ndEastEdgepointArray.push_back(sGridNo);
631 			}
632 			while( sGridNo < gsBRGridNo + 159 )
633 			{
634 				sGridNo += 160;
635 				if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
636 					(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
637 				{
638 					gps2ndEastEdgepointArray.push_back(sGridNo);
639 				}
640 				sGridNo++;
641 				if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
642 					(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
643 				{
644 					gps2ndEastEdgepointArray.push_back(sGridNo);
645 				}
646 			}
647 		}
648 		//Calculate the south edges
649 		if( gMapInformation.sSouthGridNo != -1 )
650 		{
651 			//1st row
652 			sGridNo = gsBLGridNo;
653 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
654 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
655 			{
656 				gps2ndSouthEdgepointArray.push_back(sGridNo);
657 			}
658 			while( sGridNo > gsBRGridNo )
659 			{
660 				sGridNo++;
661 				if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
662 					(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
663 				{
664 					gps2ndSouthEdgepointArray.push_back(sGridNo);
665 				}
666 				sGridNo -= 160;
667 				if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
668 					(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
669 				{
670 					gps2ndSouthEdgepointArray.push_back(sGridNo);
671 				}
672 			}
673 			//2nd row
674 			Assert(gps2ndSouthEdgepointArray.size() <= UINT16_MAX);
675 			gus2ndSouthEdgepointMiddleIndex = static_cast<UINT16>(gps2ndSouthEdgepointArray.size());
676 			sGridNo = gsBLGridNo - 161;
677 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
678 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
679 			{
680 				gps2ndSouthEdgepointArray.push_back(sGridNo);
681 			}
682 			while( sGridNo > gsBRGridNo - 161 )
683 			{
684 				sGridNo++;
685 				if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
686 					(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
687 				{
688 					gps2ndSouthEdgepointArray.push_back(sGridNo);
689 				}
690 				sGridNo -= 160;
691 				if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
692 					(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
693 				{
694 					gps2ndSouthEdgepointArray.push_back(sGridNo);
695 				}
696 			}
697 		}
698 		//Calculate the west edges
699 		if( gMapInformation.sWestGridNo != -1 )
700 		{
701 			//1st row
702 			sGridNo = gsTLGridNo;
703 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
704 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
705 			{
706 				gps2ndWestEdgepointArray.push_back(sGridNo);
707 			}
708 			while( sGridNo < gsBLGridNo )
709 			{
710 				sGridNo++;
711 				if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
712 					(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
713 				{
714 					gps2ndWestEdgepointArray.push_back(sGridNo);
715 				}
716 				sGridNo += 160;
717 				if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
718 					(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
719 				{
720 					gps2ndWestEdgepointArray.push_back(sGridNo);
721 				}
722 			}
723 			//2nd row
724 			Assert(gps2ndWestEdgepointArray.size() <= UINT16_MAX);
725 			gus2ndWestEdgepointMiddleIndex = static_cast<UINT16>(gps2ndWestEdgepointArray.size());
726 			sGridNo = gsTLGridNo - 159;
727 			if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
728 				(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
729 			{
730 				gps2ndWestEdgepointArray.push_back(sGridNo);
731 			}
732 			while( sGridNo < gsBLGridNo - 159 )
733 			{
734 				sGridNo++;
735 				if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
736 					(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
737 				{
738 					gps2ndWestEdgepointArray.push_back(sGridNo);
739 				}
740 				sGridNo += 160;
741 				if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REACHABLE &&
742 					(!gubWorldRoomInfo[ sGridNo ] || gfBasement) )
743 				{
744 					gps2ndWestEdgepointArray.push_back(sGridNo);
745 				}
746 			}
747 		}
748 	}
749 
750 	//Eliminates any edgepoints not accessible to the edge of the world.  This is done to the primary edgepoints
751 	ValidateEdgepoints();
752 	//Second step is to process the primary edgepoints and determine if any of the edgepoints aren't accessible from
753 	//the associated entrypoint.  These edgepoints that are rejected are placed in the secondary list.
754 	if( gMapInformation.sIsolatedGridNo != -1 )
755 	{ //only if there is an isolated gridno in the map.  There is a flaw in the design of this system.  The classification
756 		//process will automatically assign areas to be isolated if there is an obstacle between one normal edgepoint and another
757 		//causing a 5 tile connection check to fail.  So, all maps with isolated edgepoints will need to be checked manually to
758 		//make sure there are no obstacles causing this to happen (except for obstacles between normal areas and the isolated area)
759 
760 		//Good thing most maps don't have isolated sections.  This is one expensive function to call!  Maybe 200MI!
761 		ClassifyEdgepoints();
762 	}
763 
764 	gfGeneratingMapEdgepoints = FALSE;
765 }
766 
767 
SaveMapEdgepoint(HWFILE const f,UINT16 const & middle,const std::vector<INT16> & edgepoints)768 static void SaveMapEdgepoint(HWFILE const f, UINT16 const& middle, const std::vector<INT16>& edgepoints)
769 {
770 	Assert(edgepoints.size() <= UINT16_MAX);
771 	UINT16 n = static_cast<UINT16>(edgepoints.size());
772 	FileWrite(f, &n,   sizeof(UINT16));
773 	FileWrite(f, &middle, sizeof(UINT16));
774 	if (n != 0) FileWrite(f, edgepoints.data(),  sizeof(*edgepoints.data()) * n);
775 }
776 
777 
SaveMapEdgepoints(HWFILE const f)778 void SaveMapEdgepoints(HWFILE const f)
779 {
780 	// 1st priority edgepoints -- for common entry -- tactical placement gui uses only these points.
781 	SaveMapEdgepoint(f, gus1stNorthEdgepointMiddleIndex, gps1stNorthEdgepointArray);
782 	SaveMapEdgepoint(f, gus1stEastEdgepointMiddleIndex,  gps1stEastEdgepointArray);
783 	SaveMapEdgepoint(f, gus1stSouthEdgepointMiddleIndex, gps1stSouthEdgepointArray);
784 	SaveMapEdgepoint(f, gus1stWestEdgepointMiddleIndex,  gps1stWestEdgepointArray);
785 
786 	// 2nd priority edgepoints -- for isolated areas.  Okay to be zero
787 	SaveMapEdgepoint(f, gus2ndNorthEdgepointMiddleIndex, gps2ndNorthEdgepointArray);
788 	SaveMapEdgepoint(f, gus2ndEastEdgepointMiddleIndex,  gps2ndEastEdgepointArray);
789 	SaveMapEdgepoint(f, gus2ndSouthEdgepointMiddleIndex, gps2ndSouthEdgepointArray);
790 	SaveMapEdgepoint(f, gus2ndWestEdgepointMiddleIndex,  gps2ndWestEdgepointArray);
791 }
792 
793 
LoadMapEdgepoint(HWFILE const f,UINT16 & middle,std::vector<INT16> & edgepoints)794 static void LoadMapEdgepoint(HWFILE const f,  UINT16& middle, std::vector<INT16>& edgepoints)
795 {
796 	UINT16 n = 0;
797 	FileRead(f, &n,   sizeof(UINT16));
798 	FileRead(f, &middle, sizeof(UINT16));
799 	if (n != 0)
800 	{
801 		edgepoints.resize(n);
802 		FileRead(f, edgepoints.data(), sizeof(*edgepoints.data()) * n);
803 	}
804 }
805 
806 
LoadMapEdgepoints(HWFILE const f)807 bool LoadMapEdgepoints(HWFILE const f)
808 {
809 	TrashMapEdgepoints();
810 
811 	LoadMapEdgepoint(f, gus1stNorthEdgepointMiddleIndex, gps1stNorthEdgepointArray);
812 	LoadMapEdgepoint(f, gus1stEastEdgepointMiddleIndex,  gps1stEastEdgepointArray);
813 	LoadMapEdgepoint(f, gus1stSouthEdgepointMiddleIndex, gps1stSouthEdgepointArray);
814 	LoadMapEdgepoint(f, gus1stWestEdgepointMiddleIndex,  gps1stWestEdgepointArray);
815 
816 	if (gMapInformation.ubMapVersion < 17)
817 	{	/* To prevent invalidation of older maps, which only used one layer of
818 		 * edgepoints, and a UINT8 for containing the size, we will preserve that
819 		 * paradigm, then kill the loaded edgepoints and regenerate them. */
820 		TrashMapEdgepoints();
821 		return false;
822 	}
823 
824 	LoadMapEdgepoint(f, gus2ndNorthEdgepointMiddleIndex, gps2ndNorthEdgepointArray);
825 	LoadMapEdgepoint(f, gus2ndEastEdgepointMiddleIndex,  gps2ndEastEdgepointArray);
826 	LoadMapEdgepoint(f, gus2ndSouthEdgepointMiddleIndex, gps2ndSouthEdgepointArray);
827 	LoadMapEdgepoint(f, gus2ndWestEdgepointMiddleIndex,  gps2ndWestEdgepointArray);
828 
829 	if (gMapInformation.ubMapVersion < 22)
830 	{	// Regenerate them
831 		TrashMapEdgepoints();
832 		return false;
833 	}
834 
835 	return true;
836 }
837 
838 
ChooseMapEdgepoint(UINT8 ubStrategicInsertionCode)839 UINT16 ChooseMapEdgepoint( UINT8 ubStrategicInsertionCode )
840 {
841 	std::vector<INT16>* pEdgepoints = nullptr;
842 
843 	//First validate and get access to the correct array based on strategic direction.
844 	//We will use the selected array to choose insertion gridno's.
845 	switch( ubStrategicInsertionCode )
846 	{
847 		case INSERTION_CODE_NORTH:
848 			pEdgepoints = &gps1stNorthEdgepointArray;
849 			break;
850 		case INSERTION_CODE_EAST:
851 			pEdgepoints = &gps1stEastEdgepointArray;
852 			break;
853 		case INSERTION_CODE_SOUTH:
854 			pEdgepoints = &gps1stSouthEdgepointArray;
855 			break;
856 		case INSERTION_CODE_WEST:
857 			pEdgepoints = &gps1stWestEdgepointArray;
858 			break;
859 		default:
860 			SLOGA("ChooseMapEdgepoints:  Failed to pass a valid strategic insertion code." );
861 			break;
862 	}
863 	if (!pEdgepoints || pEdgepoints->size() == 0)
864 	{
865 		return NOWHERE;
866 	}
867 	Assert(pEdgepoints->size() <= UINT32_MAX);
868 	return (*pEdgepoints)[ Random( static_cast<UINT32>(pEdgepoints->size()) ) ];
869 }
870 
871 
ChooseMapEdgepoints(MAPEDGEPOINTINFO * const pMapEdgepointInfo,const UINT8 ubStrategicInsertionCode,UINT8 ubNumDesiredPoints)872 void ChooseMapEdgepoints(MAPEDGEPOINTINFO* const pMapEdgepointInfo, const UINT8 ubStrategicInsertionCode, UINT8 ubNumDesiredPoints)
873 {
874 	AssertMsg(ubNumDesiredPoints > 0 && ubNumDesiredPoints <= 32, String("ChooseMapEdgepoints:  Desired points = %d, valid range is 1-32", ubNumDesiredPoints));
875 
876 	/* First validate and get access to the correct array based on strategic
877 	 * direction.  We will use the selected array to choose insertion gridno's. */
878 	std::vector<INT16>* pEdgepoints = nullptr;
879 	switch (ubStrategicInsertionCode)
880 	{
881 		case INSERTION_CODE_NORTH:
882 			pEdgepoints = &gps1stNorthEdgepointArray;
883 			break;
884 
885 		case INSERTION_CODE_EAST:
886 			pEdgepoints = &gps1stEastEdgepointArray;
887 			break;
888 
889 		case INSERTION_CODE_SOUTH:
890 			pEdgepoints = &gps1stSouthEdgepointArray;
891 			break;
892 
893 		case INSERTION_CODE_WEST:
894 			pEdgepoints = &gps1stWestEdgepointArray;
895 			break;
896 
897 		default:
898 			SLOGA("ChooseMapEdgepoints:  Failed to pass a valid strategic insertion code.");
899 			break;
900 	}
901 	pMapEdgepointInfo->ubStrategicInsertionCode = ubStrategicInsertionCode;
902 
903 	if (!pEdgepoints || pEdgepoints->size() == 0)
904 	{
905 		pMapEdgepointInfo->ubNumPoints = 0;
906 		return;
907 	}
908 
909 	/* JA2 Gold: don't place people in the water.  If any of the waypoints is on a
910 	 * water spot, we're going to have to remove it */
911 	std::vector<INT16> usableEdgepoints;
912 	for (INT16 edgepoint : *pEdgepoints)
913 	{
914 		const UINT8 terrain = GetTerrainType(edgepoint);
915 		if (terrain == MED_WATER || terrain == DEEP_WATER) continue;
916 
917 		usableEdgepoints.push_back(edgepoint);
918 	}
919 
920 	if (ubNumDesiredPoints >= usableEdgepoints.size())
921 	{ //We don't have enough points for everyone, return them all.
922 		Assert(usableEdgepoints.size() <= UINT8_MAX);
923 		pMapEdgepointInfo->ubNumPoints = static_cast<UINT8>(usableEdgepoints.size());
924 		for (size_t i = 0; i < usableEdgepoints.size(); ++i)
925 		{
926 			pMapEdgepointInfo->sGridNo[i] = usableEdgepoints[i];
927 		}
928 		return;
929 	}
930 
931 	// We have more points, so choose them randomly.
932 	Assert(usableEdgepoints.size() <= UINT16_MAX);
933 	UINT16 usSlots    = static_cast<UINT16>(usableEdgepoints.size());
934 	UINT16 usCurrSlot = 0;
935 	pMapEdgepointInfo->ubNumPoints = ubNumDesiredPoints;
936 	for (size_t i = 0; i < usableEdgepoints.size(); ++i)
937 	{
938 		if (Random(usSlots) < ubNumDesiredPoints)
939 		{
940 			pMapEdgepointInfo->sGridNo[usCurrSlot++] = usableEdgepoints[i];
941 			--ubNumDesiredPoints;
942 		}
943 		--usSlots;
944 	}
945 }
946 
947 
948 INT16 *gpReservedGridNos = NULL;
949 INT16 gsReservedIndex	= 0;
950 
BeginMapEdgepointSearch()951 void BeginMapEdgepointSearch()
952 {
953 	INT16 sGridNo;
954 
955 	//Create the reserved list
956 	AssertMsg( !gpReservedGridNos, "Attempting to BeginMapEdgepointSearch that has already been created." );
957 	gpReservedGridNos = new INT16[20]{};
958 	gsReservedIndex   = 0;
959 
960 	if( gMapInformation.sNorthGridNo != -1 )
961 		sGridNo = gMapInformation.sNorthGridNo;
962 	else if( gMapInformation.sEastGridNo != -1 )
963 		sGridNo = gMapInformation.sEastGridNo;
964 	else if( gMapInformation.sSouthGridNo != -1 )
965 		sGridNo = gMapInformation.sSouthGridNo;
966 	else if( gMapInformation.sWestGridNo != -1 )
967 		sGridNo = gMapInformation.sWestGridNo;
968 	else
969 		return;
970 
971 	GlobalReachableTest( sGridNo );
972 
973 	//Now, we have the path values calculated.  Now, we can check for closest edgepoints.
974 }
975 
EndMapEdgepointSearch()976 void EndMapEdgepointSearch()
977 {
978 	AssertMsg( gpReservedGridNos, "Attempting to EndMapEdgepointSearch that has already been removed." );
979 	delete[] gpReservedGridNos;
980 	gpReservedGridNos = NULL;
981 	gsReservedIndex = 0;
982 }
983 
984 
985 //THIS CODE ISN'T RECOMMENDED FOR TIME CRITICAL AREAS.
SearchForClosestPrimaryMapEdgepoint(INT16 sGridNo,UINT8 ubInsertionCode)986 INT16 SearchForClosestPrimaryMapEdgepoint( INT16 sGridNo, UINT8 ubInsertionCode )
987 {
988 	INT32 i, iDirectionLoop;
989 	std::vector<INT16>* pEdgepoints = nullptr;
990 	INT16 sRadius, sDistance, sDirection, sOriginalGridNo;
991 	BOOLEAN fReserved;
992 
993 	if( gsReservedIndex >= 20 )
994 	{ //Everything is reserved.
995 		SLOGA("All closest map edgepoints have been reserved.  We should only have 20 soldiers maximum...");
996 	}
997 	switch( ubInsertionCode )
998 	{
999 		case INSERTION_CODE_NORTH:
1000 			pEdgepoints = &gps1stNorthEdgepointArray;
1001 			AssertMsg(pEdgepoints->size() != 0, String("Sector %c%d level %d doesn't have any north mapedgepoints. LC:1", gWorldSectorY + 'A' - 1, gWorldSectorX, gbWorldSectorZ));
1002 			break;
1003 		case INSERTION_CODE_EAST:
1004 			pEdgepoints = &gps1stEastEdgepointArray;
1005 			AssertMsg(pEdgepoints->size() != 0, String("Sector %c%d level %d doesn't have any east mapedgepoints. LC:1", gWorldSectorY + 'A' - 1, gWorldSectorX, gbWorldSectorZ));
1006 			break;
1007 		case INSERTION_CODE_SOUTH:
1008 			pEdgepoints = &gps1stSouthEdgepointArray;
1009 			AssertMsg(pEdgepoints->size() != 0, String("Sector %c%d level %d doesn't have any south mapedgepoints. LC:1", gWorldSectorY + 'A' - 1, gWorldSectorX, gbWorldSectorZ));
1010 			break;
1011 		case INSERTION_CODE_WEST:
1012 			pEdgepoints = &gps1stWestEdgepointArray;
1013 			AssertMsg(pEdgepoints->size() != 0, String("Sector %c%d level %d doesn't have any west mapedgepoints. LC:1", gWorldSectorY + 'A' - 1, gWorldSectorX, gbWorldSectorZ));
1014 			break;
1015 	}
1016 	if (!pEdgepoints || pEdgepoints->size() == 0)
1017 	{
1018 		return NOWHERE;
1019 	}
1020 
1021 	//Check the initial gridno, to see if it is available and an edgepoint.
1022 	fReserved = FALSE;
1023 	for( i = 0; i < gsReservedIndex; i++ )
1024 	{
1025 		if( gpReservedGridNos[ i ] == sGridNo )
1026 		{
1027 			fReserved = TRUE;
1028 			break;
1029 		}
1030 	}
1031 	if( !fReserved )
1032 	{	//Not reserved, so see if we can find this gridno in the edgepoint array.
1033 		for (INT16 edgepoint : *pEdgepoints)
1034 		{
1035 			if (edgepoint == sGridNo)
1036 			{ //Yes, the gridno is in the edgepoint array.
1037 				gpReservedGridNos[ gsReservedIndex ] = sGridNo;
1038 				gsReservedIndex++;
1039 				return sGridNo;
1040 			}
1041 		}
1042 	}
1043 
1044 	//spiral outwards, until we find an unreserved mapedgepoint.
1045 	//
1046 	// 09 08 07 06
1047 	// 10	01 00 05
1048 	// 11 02 03 04
1049 	// 12 13 14 15 ..
1050 	sRadius = 1;
1051 	sDirection = WORLD_COLS;
1052 	sOriginalGridNo = sGridNo;
1053 	while( sRadius < (INT16)(gbWorldSectorZ ? 30 : 10) )
1054 	{
1055 		sGridNo = sOriginalGridNo + (-1 - WORLD_COLS)*sRadius; //start at the TOP-LEFT gridno
1056 		for( iDirectionLoop = 0; iDirectionLoop < 4; iDirectionLoop++ )
1057 		{
1058 			switch( iDirectionLoop )
1059 			{
1060 				case 0:	sDirection = WORLD_COLS;	break;
1061 				case 1:	sDirection = 1;						break;
1062 				case 2:	sDirection = -WORLD_COLS;	break;
1063 				case 3:	sDirection = -1;					break;
1064 			}
1065 			sDistance = sRadius * 2;
1066 			while( sDistance-- )
1067 			{
1068 				sGridNo += sDirection;
1069 				if( sGridNo < 0 || sGridNo >= WORLD_MAX )
1070 					continue;
1071 				//Check the gridno, to see if it is available and an edgepoint.
1072 				fReserved = FALSE;
1073 				for( i = 0; i < gsReservedIndex; i++ )
1074 				{
1075 					if( gpReservedGridNos[ i ] == sGridNo )
1076 					{
1077 						fReserved = TRUE;
1078 						break;
1079 					}
1080 				}
1081 				if( !fReserved )
1082 				{	//Not reserved, so see if we can find this gridno in the edgepoint array.
1083 					for (INT16 edgepoint : *pEdgepoints)
1084 					{
1085 						if (edgepoint == sGridNo)
1086 						{ //Yes, the gridno is in the edgepoint array.
1087 							gpReservedGridNos[ gsReservedIndex ] = sGridNo;
1088 							gsReservedIndex++;
1089 							return sGridNo;
1090 						}
1091 					}
1092 				}
1093 			}
1094 		}
1095 		sRadius++;
1096 	}
1097 	return NOWHERE ;
1098 }
1099 
SearchForClosestSecondaryMapEdgepoint(INT16 sGridNo,UINT8 ubInsertionCode)1100 INT16 SearchForClosestSecondaryMapEdgepoint( INT16 sGridNo, UINT8 ubInsertionCode )
1101 {
1102 	INT32 i, iDirectionLoop;
1103 	std::vector<INT16>* pEdgepoints = nullptr;
1104 	INT16 sRadius, sDistance, sDirection, sOriginalGridNo;
1105 	BOOLEAN fReserved;
1106 
1107 	if( gsReservedIndex >= 20 )
1108 	{ //Everything is reserved.
1109 		SLOGA("All closest map edgepoints have been reserved.  We should only have 20 soldiers maximum...");
1110 	}
1111 	switch( ubInsertionCode )
1112 	{
1113 		case INSERTION_CODE_NORTH:
1114 			pEdgepoints = &gps2ndNorthEdgepointArray;
1115 			AssertMsg(pEdgepoints->size() != 0, String("Sector %c%d level %d doesn't have any isolated north mapedgepoints. KM:1", gWorldSectorY + 'A' - 1, gWorldSectorX, gbWorldSectorZ));
1116 			break;
1117 		case INSERTION_CODE_EAST:
1118 			pEdgepoints = &gps2ndEastEdgepointArray;
1119 			AssertMsg(pEdgepoints->size() != 0, String("Sector %c%d level %d doesn't have any isolated east mapedgepoints. KM:1", gWorldSectorY + 'A' - 1, gWorldSectorX, gbWorldSectorZ));
1120 			break;
1121 		case INSERTION_CODE_SOUTH:
1122 			pEdgepoints = &gps2ndSouthEdgepointArray;
1123 			AssertMsg(pEdgepoints->size() != 0, String("Sector %c%d level %d doesn't have any isolated south mapedgepoints. KM:1", gWorldSectorY + 'A' - 1, gWorldSectorX, gbWorldSectorZ));
1124 			break;
1125 		case INSERTION_CODE_WEST:
1126 			pEdgepoints = &gps2ndWestEdgepointArray;
1127 			AssertMsg(pEdgepoints->size() != 0, String("Sector %c%d level %d doesn't have any isolated west mapedgepoints. KM:1", gWorldSectorY + 'A' - 1, gWorldSectorX, gbWorldSectorZ));
1128 			break;
1129 	}
1130 	if (!pEdgepoints || pEdgepoints->size() == 0)
1131 	{
1132 		return NOWHERE;
1133 	}
1134 
1135 	//Check the initial gridno, to see if it is available and an edgepoint.
1136 	fReserved = FALSE;
1137 	for( i = 0; i < gsReservedIndex; i++ )
1138 	{
1139 		if( gpReservedGridNos[ i ] == sGridNo )
1140 		{
1141 			fReserved = TRUE;
1142 			break;
1143 		}
1144 	}
1145 	if( !fReserved )
1146 	{	//Not reserved, so see if we can find this gridno in the edgepoint array.
1147 		for (INT16 edgepoint : *pEdgepoints)
1148 		{
1149 			if (edgepoint == sGridNo)
1150 			{ //Yes, the gridno is in the edgepoint array.
1151 				gpReservedGridNos[ gsReservedIndex ] = sGridNo;
1152 				gsReservedIndex++;
1153 				return sGridNo;
1154 			}
1155 		}
1156 	}
1157 
1158 	//spiral outwards, until we find an unreserved mapedgepoint.
1159 	//
1160 	// 09 08 07 06
1161 	// 10	01 00 05
1162 	// 11 02 03 04
1163 	// 12 13 14 15 ..
1164 	sRadius = 1;
1165 	sDirection = WORLD_COLS;
1166 	sOriginalGridNo = sGridNo;
1167 	while( sRadius < (INT16)(gbWorldSectorZ ? 30 : 10) )
1168 	{
1169 		sGridNo = sOriginalGridNo + (-1 - WORLD_COLS)*sRadius; //start at the TOP-LEFT gridno
1170 		for( iDirectionLoop = 0; iDirectionLoop < 4; iDirectionLoop++ )
1171 		{
1172 			switch( iDirectionLoop )
1173 			{
1174 				case 0:	sDirection = WORLD_COLS;	break;
1175 				case 1:	sDirection = 1;						break;
1176 				case 2:	sDirection = -WORLD_COLS;	break;
1177 				case 3:	sDirection = -1;					break;
1178 			}
1179 			sDistance = sRadius * 2;
1180 			while( sDistance-- )
1181 			{
1182 				sGridNo += sDirection;
1183 				if( sGridNo < 0 || sGridNo >= WORLD_MAX )
1184 					continue;
1185 				//Check the gridno, to see if it is available and an edgepoint.
1186 				fReserved = FALSE;
1187 				for( i = 0; i < gsReservedIndex; i++ )
1188 				{
1189 					if( gpReservedGridNos[ i ] == sGridNo )
1190 					{
1191 						fReserved = TRUE;
1192 						break;
1193 					}
1194 				}
1195 				if( !fReserved )
1196 				{	//Not reserved, so see if we can find this gridno in the edgepoint array.
1197 					for (INT16 edgepoint : *pEdgepoints)
1198 					{
1199 						if (edgepoint == sGridNo)
1200 						{ //Yes, the gridno is in the edgepoint array.
1201 							gpReservedGridNos[ gsReservedIndex ] = sGridNo;
1202 							gsReservedIndex++;
1203 							return sGridNo;
1204 						}
1205 					}
1206 				}
1207 			}
1208 		}
1209 		sRadius++;
1210 	}
1211 	return NOWHERE ;
1212 }
1213 
1214 
1215 #define EDGE_OF_MAP_SEARCH 5
1216 
1217 
VerifyEdgepoint(SOLDIERTYPE * pSoldier,INT16 sEdgepoint)1218 static BOOLEAN VerifyEdgepoint(SOLDIERTYPE* pSoldier, INT16 sEdgepoint)
1219 {
1220 	INT32		iSearchRange;
1221 	INT16		sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
1222 	INT16		sGridNo;
1223 	INT8		bDirection;
1224 
1225 	pSoldier->sGridNo = sEdgepoint;
1226 
1227 	iSearchRange = EDGE_OF_MAP_SEARCH;
1228 
1229 	// determine maximum horizontal limits
1230 	sMaxLeft  = MIN( iSearchRange, (pSoldier->sGridNo % MAXCOL));
1231 	sMaxRight = MIN( iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
1232 
1233 	// determine maximum vertical limits
1234 	sMaxUp   = MIN( iSearchRange, (pSoldier->sGridNo / MAXROW));
1235 	sMaxDown = MIN( iSearchRange, MAXROW - ((pSoldier->sGridNo / MAXROW) + 1));
1236 
1237 	// Call FindBestPath to set flags in all locations that we can
1238 	// walk into within range.  We have to set some things up first...
1239 
1240 	// set the distance limit of the square region
1241 	gubNPCDistLimit = EDGE_OF_MAP_SEARCH;
1242 
1243 	// reset the "reachable" flags in the region we're looking at
1244 	for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
1245 	{
1246 		for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
1247 		{
1248 			sGridNo = sEdgepoint + sXOffset + (MAXCOL * sYOffset);
1249 			gpWorldLevelData[sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
1250 		}
1251 	}
1252 
1253 	FindBestPath( pSoldier, NOWHERE, pSoldier->bLevel, WALKING, COPYREACHABLE, PATH_THROUGH_PEOPLE );
1254 
1255 	// Turn off the "reachable" flag for the current location
1256 	// so we don't consider it
1257 	//gpWorldLevelData[sEdgepoint].uiFlags &= ~(MAPELEMENT_REACHABLE);
1258 
1259 	// SET UP DOUBLE-LOOP TO STEP THROUGH POTENTIAL GRID #s
1260 	for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
1261 	{
1262 		for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
1263 		{
1264 			// calculate the next potential gridno
1265 			sGridNo = sEdgepoint + sXOffset + (MAXCOL * sYOffset);
1266 
1267 			if (!(gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE))
1268 			{
1269 				continue;
1270 			}
1271 
1272 			if (GridNoOnEdgeOfMap( sGridNo, &bDirection ) )
1273 			{
1274 				// ok!
1275 				return TRUE;
1276 			}
1277 		}
1278 	}
1279 
1280 	// no spots right on edge of map within 5 tiles
1281 	return FALSE;
1282 }
1283 
1284 
EdgepointsClose(SOLDIERTYPE * pSoldier,INT16 sEdgepoint1,INT16 sEdgepoint2)1285 static BOOLEAN EdgepointsClose(SOLDIERTYPE* pSoldier, INT16 sEdgepoint1, INT16 sEdgepoint2)
1286 {
1287 	INT32		iSearchRange;
1288 	INT16		sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
1289 	INT16		sGridNo;
1290 
1291 	pSoldier->sGridNo = sEdgepoint1;
1292 
1293 	if( gWorldSectorX == 14 && gWorldSectorY == 9 && !gbWorldSectorZ )
1294 	{ //BRUTAL CODE  -- special case map.
1295 		iSearchRange = 250;
1296 	}
1297 	else
1298 	{
1299 		iSearchRange = EDGE_OF_MAP_SEARCH;
1300 	}
1301 
1302 	// determine maximum horizontal limits
1303 	sMaxLeft  = MIN( iSearchRange, (pSoldier->sGridNo % MAXCOL));
1304 	sMaxRight = MIN( iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
1305 
1306 	// determine maximum vertical limits
1307 	sMaxUp   = MIN( iSearchRange, (pSoldier->sGridNo / MAXROW));
1308 	sMaxDown = MIN( iSearchRange, MAXROW - ((pSoldier->sGridNo / MAXROW) + 1));
1309 
1310 	// Call FindBestPath to set flags in all locations that we can
1311 	// walk into within range.  We have to set some things up first...
1312 
1313 	// set the distance limit of the square region
1314 	gubNPCDistLimit = (UINT8)iSearchRange;
1315 
1316 	// reset the "reachable" flags in the region we're looking at
1317 	for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
1318 	{
1319 		for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
1320 		{
1321 			sGridNo = sEdgepoint1 + sXOffset + (MAXCOL * sYOffset);
1322 			gpWorldLevelData[sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
1323 		}
1324 	}
1325 
1326 	if( FindBestPath( pSoldier, sEdgepoint2, pSoldier->bLevel, WALKING, COPYREACHABLE, PATH_THROUGH_PEOPLE ) )
1327 	{
1328 		return TRUE;
1329 	}
1330 	return FALSE;
1331 }
1332 
CalcMapEdgepointClassInsertionCode(INT16 sGridNo)1333 UINT8 CalcMapEdgepointClassInsertionCode( INT16 sGridNo )
1334 {
1335 	SOLDIERTYPE Soldier;
1336 	std::vector<INT16>* pEdgepoints1 = nullptr;
1337 	std::vector<INT16>* pEdgepoints2 = nullptr;
1338 	INT16			sClosestSpot1 = NOWHERE, sClosestDist1 = 0x7FFF, sTempDist;
1339 	INT16			sClosestSpot2 = NOWHERE, sClosestDist2 = 0x7FFF;
1340 	BOOLEAN		fPrimaryValid = FALSE, fSecondaryValid = FALSE;
1341 
1342 	Soldier = SOLDIERTYPE{};
1343 	Soldier.bTeam = 1;
1344 	Soldier.sGridNo = sGridNo;
1345 
1346 	if( gMapInformation.sIsolatedGridNo == -1 )
1347 	{ //If the map has no isolated area, then all edgepoints are primary.
1348 		return INSERTION_CODE_PRIMARY_EDGEINDEX;
1349 	}
1350 
1351 	switch( gubTacticalDirection )
1352 	{
1353 		case NORTH:
1354 			pEdgepoints1 = &gps1stNorthEdgepointArray;
1355 			pEdgepoints2 = &gps2ndNorthEdgepointArray;
1356 			break;
1357 		case EAST:
1358 			pEdgepoints1 = &gps1stEastEdgepointArray;
1359 			pEdgepoints2 = &gps2ndEastEdgepointArray;
1360 			break;
1361 		case SOUTH:
1362 			pEdgepoints1 = &gps1stSouthEdgepointArray;
1363 			pEdgepoints2 = &gps2ndSouthEdgepointArray;
1364 			break;
1365 		case WEST:
1366 			pEdgepoints1 = &gps1stWestEdgepointArray;
1367 			pEdgepoints2 = &gps2ndWestEdgepointArray;
1368 			break;
1369 		default:
1370 			// WTF???
1371 			return INSERTION_CODE_PRIMARY_EDGEINDEX;
1372 	}
1373 
1374 	// Do a 2D search to find the closest map edgepoint and
1375 	// try to create a path there
1376 	for (INT16 edgepoint : *pEdgepoints1)
1377 	{
1378 		sTempDist = PythSpacesAway(sGridNo, edgepoint);
1379 		if ( sTempDist < sClosestDist1 )
1380 		{
1381 			sClosestDist1 = sTempDist;
1382 			sClosestSpot1 = edgepoint;
1383 		}
1384 	}
1385 	for (INT16 edgepoint : *pEdgepoints2)
1386 	{
1387 		sTempDist = PythSpacesAway(sGridNo, edgepoint);
1388 		if ( sTempDist < sClosestDist2 )
1389 		{
1390 			sClosestDist2 = sTempDist;
1391 			sClosestSpot2 = edgepoint;
1392 		}
1393 	}
1394 
1395 	// set the distance limit of the square region
1396 	gubNPCDistLimit = 15;
1397 
1398 	if( !sClosestDist1 || FindBestPath( &Soldier, sClosestSpot1, 0, WALKING, NO_COPYROUTE, PATH_THROUGH_PEOPLE ) )
1399 	{
1400 		fPrimaryValid = TRUE;
1401 	}
1402 	if( !sClosestDist2 || FindBestPath( &Soldier, sClosestSpot2, 0, WALKING, NO_COPYROUTE, PATH_THROUGH_PEOPLE ) )
1403 	{
1404 		fSecondaryValid = TRUE;
1405 	}
1406 
1407 	if( fPrimaryValid == fSecondaryValid )
1408 	{
1409 		if( sClosestDist2 < sClosestDist1 )
1410 		{
1411 			return INSERTION_CODE_SECONDARY_EDGEINDEX;
1412 		}
1413 		return INSERTION_CODE_PRIMARY_EDGEINDEX;
1414 	}
1415 	if( fPrimaryValid )
1416 	{
1417 		return INSERTION_CODE_PRIMARY_EDGEINDEX;
1418 	}
1419 	return INSERTION_CODE_SECONDARY_EDGEINDEX;
1420 }
1421 
1422 
ShowMapEdgepoint(std::vector<INT16> & edgepoints,UINT16 const idx)1423 static bool ShowMapEdgepoint(std::vector<INT16>& edgepoints, UINT16 const idx)
1424 {
1425 	INT32              n_illegal = 0;
1426 	for (INT16 edgepoint : edgepoints)
1427 	{
1428 		if (edgepoint != -1)
1429 		{
1430 			AddTopmostToTail(edgepoint, idx);
1431 		}
1432 		else
1433 		{
1434 			++n_illegal;
1435 		}
1436 	}
1437 	return n_illegal;
1438 }
1439 
1440 
ShowMapEdgepoints()1441 void ShowMapEdgepoints()
1442 {
1443 	INT32 n_illegal1 = 0;
1444 	n_illegal1 += ShowMapEdgepoint(gps1stNorthEdgepointArray, FIRSTPOINTERS5);
1445 	n_illegal1 += ShowMapEdgepoint(gps1stEastEdgepointArray,  FIRSTPOINTERS5);
1446 	n_illegal1 += ShowMapEdgepoint(gps1stSouthEdgepointArray, FIRSTPOINTERS5);
1447 	n_illegal1 += ShowMapEdgepoint(gps1stWestEdgepointArray,  FIRSTPOINTERS5);
1448 
1449 	INT32 n_illegal2 = 0;
1450 	n_illegal2 += ShowMapEdgepoint(gps2ndNorthEdgepointArray, FIRSTPOINTERS6);
1451 	n_illegal2 += ShowMapEdgepoint(gps2ndEastEdgepointArray,  FIRSTPOINTERS6);
1452 	n_illegal2 += ShowMapEdgepoint(gps2ndSouthEdgepointArray, FIRSTPOINTERS6);
1453 	n_illegal2 += ShowMapEdgepoint(gps2ndWestEdgepointArray,  FIRSTPOINTERS6);
1454 
1455 	if (n_illegal1 == 0 && n_illegal2 == 0)
1456 	{
1457 		SLOGD("Showing display of map edgepoints");
1458 	}
1459 	else
1460 	{
1461 		SLOGD("Showing display of map edgepoints (%d illegal primary, %d illegal secondary)", n_illegal1, n_illegal2);
1462 	}
1463 	SLOGD("N:%d:%d E:%d:%d S:%d:%d W:%d:%d",
1464 		static_cast<int>(gps1stNorthEdgepointArray.size()), static_cast<int>(gps2ndNorthEdgepointArray.size()),
1465 		static_cast<int>(gps1stEastEdgepointArray.size()),  static_cast<int>(gps2ndEastEdgepointArray.size()),
1466 		static_cast<int>(gps1stSouthEdgepointArray.size()), static_cast<int>(gps2ndSouthEdgepointArray.size()),
1467 		static_cast<int>(gps1stWestEdgepointArray.size()),  static_cast<int>(gps2ndWestEdgepointArray.size()));
1468 }
1469 
1470 
HideMapEdgepoint(std::vector<INT16> & edgepoints)1471 static void HideMapEdgepoint(std::vector<INT16>& edgepoints)
1472 {
1473 	for (INT16 edgepoint : edgepoints)
1474 	{
1475 		if (edgepoint == -1) continue;
1476 		RemoveAllTopmostsOfTypeRange(edgepoint, FIRSTPOINTERS, FIRSTPOINTERS);
1477 	}
1478 }
1479 
1480 
HideMapEdgepoints()1481 void HideMapEdgepoints()
1482 {
1483 	SLOGD("Removing display of map edgepoints");
1484 
1485 	HideMapEdgepoint(gps1stNorthEdgepointArray);
1486 	HideMapEdgepoint(gps1stEastEdgepointArray);
1487 	HideMapEdgepoint(gps1stSouthEdgepointArray);
1488 	HideMapEdgepoint(gps1stWestEdgepointArray);
1489 
1490 	HideMapEdgepoint(gps2ndNorthEdgepointArray);
1491 	HideMapEdgepoint(gps2ndEastEdgepointArray);
1492 	HideMapEdgepoint(gps2ndSouthEdgepointArray);
1493 	HideMapEdgepoint(gps2ndWestEdgepointArray);
1494 }
1495