1 #include "Directories.h"
2 #include "Font_Control.h"
3 #include "Handle_Items.h"
4 #include "LoadSaveData.h"
5 #include "Soldier_Profile.h"
6 #include "Types.h"
7 #include "Soldier_Control.h"
8 #include "Keys.h"
9 #include "Debug.h"
10 #include "SkillCheck.h"
11 #include "OppList.h"
12 #include "Items.h"
13 #include "Weapons.h"
14 #include "AI.h"
15 #include "Message.h"
16 #include "Text.h"
17 #include "Explosion_Control.h"
18 #include "Isometric_Utils.h"
19 #include "StrategicMap.h"
20 #include "Tactical_Save.h"
21 #include "Campaign_Types.h"
22 #include "LOS.h"
23 #include "TileDat.h"
24 #include "Overhead.h"
25 #include "Structure.h"
26 #include "RenderWorld.h"
27 #include "WorldMan.h"
28 #include "Random.h"
29 #include "WorldDef.h"
30 #include "Campaign.h"
31 #include "Sound_Control.h"
32 #include "Interface.h"
33 #include "MapScreen.h"
34 #include "Game_Clock.h"
35 #include "Handle_Doors.h"
36 #include "Map_Screen_Interface.h"
37 #include "MemMan.h"
38 #include "FileMan.h"
39 
40 #include "ContentManager.h"
41 #include "GameInstance.h"
42 #include "Logger.h"
43 
44 #include <string_theory/string>
45 
46 #include <vector>
47 
48 static std::vector<DOOR_STATUS> gpDoorStatus;
49 
50 
51 #define FOR_EACH_DOOR_STATUS(iter) \
52 	for (DOOR_STATUS& iter : gpDoorStatus)
53 
54 
55 KEY KeyTable[NUM_KEYS];
56 
57 LOCK LockTable[NUM_LOCKS];
58 
59 /*
60 LOCK LockTable[NUM_LOCKS] =
61 {
62 	// Keys that will open the lock,	Lock type,		Pick diff,	Smash diff
63 	{{NO_KEY, NO_KEY, NO_KEY, NO_KEY},	LOCK_REGULAR,		0,		0},
64 	{{0,	NO_KEY, NO_KEY, NO_KEY},	LOCK_REGULAR,		-25,		-25},
65 	{{1,	NO_KEY, NO_KEY, NO_KEY},	LOCK_REGULAR,		-60,		-55},
66 	{{2,	NO_KEY, NO_KEY, NO_KEY},	LOCK_REGULAR,		-75,		-80},
67 	{{3,	NO_KEY, NO_KEY, NO_KEY},	LOCK_REGULAR,		-35,		-45},
68 	{{4,	NO_KEY, NO_KEY, NO_KEY},	LOCK_REGULAR,		-45,		-60},
69 	{{5,	NO_KEY, NO_KEY, NO_KEY},	LOCK_REGULAR,		-65,		-90},
70 	{{6,	NO_KEY, NO_KEY, NO_KEY},	LOCK_PADLOCK,		-60,		-70},
71 	{{7,	NO_KEY, NO_KEY, NO_KEY},	LOCK_ELECTRONIC,	-50,		-60},
72 	{{8,	NO_KEY, NO_KEY, NO_KEY},	LOCK_ELECTRONIC,	-75,		-80},
73 	{{9,	NO_KEY, NO_KEY, NO_KEY},	LOCK_CARD,		-50,		-40},
74 	{{10,	NO_KEY, NO_KEY, NO_KEY},	LOCK_CARD,		-85,		-80},
75 	{{11,	NO_KEY, NO_KEY, NO_KEY},	LOCK_REGULAR,		-50,		-50}
76 };
77 */
78 
79 DOORTRAP const DoorTrapTable[NUM_DOOR_TRAPS] =
80 {
81 	{0}, // nothing
82 	{DOOR_TRAP_STOPS_ACTION}, // explosion
83 	{DOOR_TRAP_STOPS_ACTION | DOOR_TRAP_RECURRING}, // electric
84 	{DOOR_TRAP_RECURRING}, // siren
85 	{DOOR_TRAP_RECURRING | DOOR_TRAP_SILENT}, // silent alarm
86 	{DOOR_TRAP_RECURRING}, // brothel siren
87 	{DOOR_TRAP_STOPS_ACTION | DOOR_TRAP_RECURRING}, // super electric
88 };
89 
90 
91 
92 //Dynamic array of Doors.  For general game purposes, the doors that are locked and/or trapped
93 //are permanently saved within the map, and are loaded and allocated when the map is loaded.  Because
94 //the editor allows more doors to be added, or removed, the actual size of the DoorTable may change.
95 std::vector<DOOR> DoorTable;
96 
97 
LoadLockTable(void)98 void LoadLockTable(void)
99 try
100 {
101 	UINT32 uiBytesToRead;
102 	const char *pFileName = BINARYDATADIR "/Locks.bin";
103 
104 	// Load the Lock Table
105 
106 	AutoSGPFile hFile(GCM->openGameResForReading(pFileName));
107 
108 	uiBytesToRead = sizeof( LOCK ) * NUM_LOCKS;
109 	FileRead(hFile, LockTable, uiBytesToRead);
110 }
111 catch (...)
112 {
113 	SLOGE("FAILED to LoadLockTable");
114 	throw;
115 }
116 
117 
118 static bool KeyExistsInInventory(SOLDIERTYPE const&, UINT8 key_id);
119 
120 
SoldierHasKey(SOLDIERTYPE const & s,UINT8 const key_id)121 bool SoldierHasKey(SOLDIERTYPE const& s, UINT8 const key_id)
122 {
123 	return KeyExistsInKeyRing(s, key_id) ||
124 		KeyExistsInInventory(s, key_id);
125 }
126 
127 
KeyExistsInKeyRing(SOLDIERTYPE const & s,UINT8 const key_id)128 bool KeyExistsInKeyRing(SOLDIERTYPE const& s, UINT8 const key_id)
129 {
130 	if (!s.pKeyRing) return FALSE; // No key ring
131 
132 	KEY_ON_RING const* const end = s.pKeyRing + NUM_KEYS;
133 	for (KEY_ON_RING const* i = s.pKeyRing; i != end; ++i)
134 	{
135 		if (i->ubNumber == 0) continue;
136 		if (i->ubKeyID != key_id && key_id != ANYKEY) continue;
137 		return true;
138 	}
139 	return false;
140 }
141 
142 
KeyExistsInInventory(SOLDIERTYPE const & s,UINT8 const key_id)143 static bool KeyExistsInInventory(SOLDIERTYPE const& s, UINT8 const key_id)
144 {
145 	CFOR_EACH_SOLDIER_INV_SLOT(i, s)
146 	{
147 		if (GCM->getItem(i->usItem)->getItemClass() != IC_KEY)    continue;
148 		if (i->ubKeyID != key_id && key_id != ANYKEY) continue;
149 		return true;
150 	}
151 	return false;
152 }
153 
154 
ValidKey(DOOR * pDoor,UINT8 ubKeyID)155 static BOOLEAN ValidKey(DOOR* pDoor, UINT8 ubKeyID)
156 {
157 	return (pDoor->ubLockID == ubKeyID);
158 }
159 
160 
DoLockDoor(DOOR * pDoor,UINT8 ubKeyID)161 static void DoLockDoor(DOOR* pDoor, UINT8 ubKeyID)
162 {
163 	// if the door is unlocked and this is the right key, lock the door
164 	if (!(pDoor->fLocked) && ValidKey( pDoor, ubKeyID ))
165 	{
166 		pDoor->fLocked = TRUE;
167 	}
168 }
169 
170 
DoUnlockDoor(DOOR * pDoor,UINT8 ubKeyID)171 static void DoUnlockDoor(DOOR* pDoor, UINT8 ubKeyID)
172 {
173 	// if the door is locked and this is the right key, unlock the door
174 	if ( (pDoor->fLocked) && ValidKey( pDoor, ubKeyID ))
175 	{
176 		// Play lockpicking
177 		PlayLocationJA2Sample(pDoor->sGridNo, UNLOCK_DOOR_1, MIDVOLUME, 1);
178 
179 		pDoor->fLocked = FALSE;
180 	}
181 }
182 
183 
AttemptToUnlockDoor(const SOLDIERTYPE * pSoldier,DOOR * pDoor)184 BOOLEAN AttemptToUnlockDoor(const SOLDIERTYPE* pSoldier, DOOR* pDoor)
185 {
186 	const UINT8 ubKeyID = pDoor->ubLockID;
187 	if (SoldierHasKey(*pSoldier, ubKeyID))
188 	{
189 		DoUnlockDoor(pDoor, ubKeyID);
190 		return TRUE;
191 	}
192 
193 	// drat, couldn't find the key
194 	PlayJA2Sample(KEY_FAILURE, MIDVOLUME, 1, MIDDLEPAN);
195 
196 	return FALSE;
197 }
198 
199 
AttemptToLockDoor(const SOLDIERTYPE * pSoldier,DOOR * pDoor)200 BOOLEAN AttemptToLockDoor(const SOLDIERTYPE* pSoldier, DOOR* pDoor)
201 {
202 	const UINT8 ubKeyID = pDoor->ubLockID;
203 	if (SoldierHasKey(*pSoldier, ubKeyID))
204 	{
205 		DoLockDoor(pDoor, ubKeyID);
206 		return TRUE;
207 	}
208 
209 	// drat, couldn't find the key
210 	return FALSE;
211 }
212 
213 
AttemptToCrowbarLock(SOLDIERTYPE * pSoldier,DOOR * pDoor)214 BOOLEAN AttemptToCrowbarLock( SOLDIERTYPE * pSoldier, DOOR * pDoor )
215 {
216 	INT32 iResult;
217 	INT8  bStress, bSlot;
218 
219 	bSlot = FindUsableObj( pSoldier, CROWBAR );
220 	if ( bSlot == ITEM_NOT_FOUND )
221 	{
222 		// error!
223 		return( FALSE );
224 	}
225 
226 	// generate a noise for thumping on the door
227 	MakeNoise(pSoldier, pSoldier->sGridNo, pSoldier->bLevel, CROWBAR_DOOR_VOLUME, NOISE_DOOR_SMASHING);
228 
229 	if ( !pDoor->fLocked )
230 	{
231 		// auto success but no XP
232 
233 		// succeeded! door can never be locked again, so remove from door list...
234 		RemoveDoorInfoFromTable( pDoor->sGridNo );
235 		// award experience points?
236 
237 		// Play lock busted sound
238 		PlayLocationJA2Sample(pSoldier->sGridNo, BREAK_LOCK, MIDVOLUME, 1);
239 
240 		return( TRUE );
241 	}
242 
243 	if ( pDoor->ubLockID == LOCK_UNOPENABLE )
244 	{
245 		// auto failure!
246 		return( FALSE );
247 	}
248 
249 	// possibly damage crowbar
250 	bStress = __min( EffectiveStrength( pSoldier ), LockTable[pDoor->ubLockID].ubSmashDifficulty + 30 );
251 	// reduce crowbar status by random % between 0 and 5%
252 	DamageObj( &(pSoldier->inv[ bSlot ]), (INT8) PreRandom( bStress / 20 ) );
253 
254 	// did we succeed?
255 
256 	if ( LockTable[pDoor->ubLockID].ubSmashDifficulty == OPENING_NOT_POSSIBLE )
257 	{
258 		// do this to get 'can't do this' messages
259 		iResult = SkillCheck( pSoldier, OPEN_WITH_CROWBAR, (INT8) ( -100 ) );
260 		iResult = -100;
261 	}
262 	else
263 	{
264 		iResult = SkillCheck( pSoldier, OPEN_WITH_CROWBAR, (INT8) ( - (INT8) (LockTable[pDoor->ubLockID].ubSmashDifficulty - pDoor->bLockDamage) ) );
265 	}
266 
267 	if (iResult > 0)
268 	{
269 		// STR GAIN (20) - Pried open a lock
270 		StatChange(*pSoldier, STRAMT, 20, FROM_SUCCESS);
271 
272 		// succeeded! door can never be locked again, so remove from door list...
273 		RemoveDoorInfoFromTable( pDoor->sGridNo );
274 
275 		// Play lock busted sound
276 		PlayLocationJA2Sample(pSoldier->sGridNo, BREAK_LOCK, MIDVOLUME, 1);
277 
278 		return( TRUE );
279 	}
280 	else
281 	{
282 		if (iResult > -10)
283 		{
284 			// STR GAIN - Damaged a lock by prying
285 			StatChange(*pSoldier, STRAMT, 5, FROM_SUCCESS);
286 
287 			// we came close... so do some damage to the lock
288 			pDoor->bLockDamage += (INT8) (10 + iResult);
289 		}
290 		else if ( iResult > -40 && pSoldier->sGridNo != pSoldier->sSkillCheckGridNo )
291 		{
292 			// give token point for effort :-)
293 			StatChange(*pSoldier, STRAMT, 1, FROM_SUCCESS);
294 		}
295 
296 		return( FALSE );
297 	}
298 
299 }
300 
301 
AttemptToSmashDoor(SOLDIERTYPE * pSoldier,DOOR * pDoor)302 BOOLEAN AttemptToSmashDoor( SOLDIERTYPE * pSoldier, DOOR * pDoor )
303 {
304 	INT32 iResult;
305 	LOCK  *pLock;
306 
307 	// generate a noise for thumping on the door
308 	MakeNoise(pSoldier, pSoldier->sGridNo, pSoldier->bLevel, SMASHING_DOOR_VOLUME, NOISE_DOOR_SMASHING);
309 
310 	if ( !pDoor->fLocked )
311 	{
312 		// auto success but no XP
313 
314 		// succeeded! door can never be locked again, so remove from door list...
315 		RemoveDoorInfoFromTable( pDoor->sGridNo );
316 		// award experience points?
317 
318 		// Play lock busted sound
319 		PlayLocationJA2Sample(pSoldier->sGridNo, BREAK_LOCK, MIDVOLUME, 1);
320 
321 		return( TRUE );
322 	}
323 
324 	if ( pDoor->ubLockID == LOCK_UNOPENABLE )
325 	{
326 		// auto failure!
327 		return( FALSE );
328 	}
329 
330 	pLock = &(LockTable[pDoor->ubLockID]);
331 
332 	// did we succeed?
333 	if ( pLock->ubSmashDifficulty == OPENING_NOT_POSSIBLE )
334 	{
335 		// do this to get 'can't do this' messages
336 		iResult = SkillCheck( pSoldier, SMASH_DOOR_CHECK, (INT8) ( -100 ) );
337 		iResult = -100;
338 	}
339 	else
340 	{
341 		iResult = SkillCheck( pSoldier, SMASH_DOOR_CHECK, (INT8) ( - (INT8) (LockTable[pDoor->ubLockID].ubSmashDifficulty - pDoor->bLockDamage) ) );
342 	}
343 	if (iResult > 0)
344 	{
345 		// STR GAIN (20) - Pried open a lock
346 		StatChange(*pSoldier, STRAMT, 20, FROM_SUCCESS);
347 
348 		// succeeded! door can never be locked again, so remove from door list...
349 		RemoveDoorInfoFromTable( pDoor->sGridNo );
350 		// award experience points?
351 
352 		// Play lock busted sound
353 		PlayLocationJA2Sample(pSoldier->sGridNo, BREAK_LOCK, MIDVOLUME, 1);
354 
355 		return( TRUE );
356 	}
357 	else
358 	{
359 		if (iResult > -10)
360 		{
361 			// STR GAIN - Damaged a lock by prying
362 			StatChange(*pSoldier, STRAMT, 5, FROM_SUCCESS);
363 
364 			// we came close... so do some damage to the lock
365 			pDoor->bLockDamage += (INT8) (10 + iResult);
366 		}
367 		else if ( iResult > -40 && pSoldier->sGridNo != pSoldier->sSkillCheckGridNo )
368 		{
369 			// give token point for effort :-)
370 			StatChange(*pSoldier, STRAMT, 1, FROM_SUCCESS);
371 		}
372 		return( FALSE );
373 	}
374 }
375 
AttemptToPickLock(SOLDIERTYPE * pSoldier,DOOR * pDoor)376 BOOLEAN AttemptToPickLock( SOLDIERTYPE * pSoldier, DOOR * pDoor )
377 {
378 	INT32 iResult;
379 	INT8  bReason;
380 	LOCK  *pLock;
381 
382 	if ( pDoor->ubLockID == LOCK_UNOPENABLE )
383 	{
384 		// auto failure!
385 		return( FALSE );
386 	}
387 
388 	pLock = &(LockTable[pDoor->ubLockID]);
389 
390 	// look up the type of lock to see if it is electronic or not
391 	if (pLock->ubLockType == LOCK_CARD || pLock->ubLockType == LOCK_ELECTRONIC )
392 	{
393 		bReason = ELECTRONIC_LOCKPICKING_CHECK;
394 	}
395 	else
396 	{
397 		bReason = LOCKPICKING_CHECK;
398 	}
399 
400 	// Play lockpicking
401 	// ATE: Moved to animation
402 	//PlayLocationJA2Sample(pSoldier->sGridNo, PICKING_LOCK, MIDVOLUME, 1);
403 
404 	// See if we measure up to the task.
405 	// The difficulty is negated here to make it a skill adjustment
406 	if ( pLock->ubPickDifficulty == OPENING_NOT_POSSIBLE )
407 	{
408 		// do this to get 'can't do this' messages
409 		iResult = SkillCheck( pSoldier, bReason, (INT8) ( -100 ) );
410 		iResult = -100;
411 	}
412 	else
413 	{
414 		iResult = SkillCheck( pSoldier, bReason, (INT8) ( - (INT8) (pLock->ubPickDifficulty) ) );
415 	}
416 	if (iResult > 0)
417 	{
418 		// MECHANICAL GAIN:  Picked open a lock
419 		StatChange(*pSoldier, MECHANAMT, pLock->ubPickDifficulty / 5, FROM_SUCCESS);
420 
421 		// DEXTERITY GAIN:  Picked open a lock
422 		StatChange(*pSoldier, DEXTAMT, pLock->ubPickDifficulty / 10, FROM_SUCCESS);
423 
424 		// succeeded!
425 		pDoor->fLocked = FALSE;
426 		return( TRUE );
427 	}
428 	else
429 	{
430 		// NOTE: failures are not rewarded, since you can keep trying indefinitely...
431 
432 		// check for traps
433 		return( FALSE );
434 	}
435 }
436 
AttemptToUntrapDoor(SOLDIERTYPE * pSoldier,DOOR * pDoor)437 BOOLEAN AttemptToUntrapDoor( SOLDIERTYPE * pSoldier, DOOR * pDoor )
438 {
439 	INT32 iResult;
440 
441 	// See if we measure up to the task.
442 	if ( pDoor->ubTrapID == EXPLOSION  )
443 	{
444 		iResult = SkillCheck( pSoldier, DISARM_TRAP_CHECK, (INT8) (pDoor->ubTrapLevel * 7) );
445 	}
446 	else
447 	{
448 		iResult = SkillCheck( pSoldier, DISARM_ELECTRONIC_TRAP_CHECK, (INT8) (pDoor->ubTrapLevel * 7) );
449 	}
450 
451 	if (iResult > 0)
452 	{
453 		// succeeded!
454 		pDoor->ubTrapLevel = 0;
455 		pDoor->ubTrapID = NO_TRAP;
456 		return( TRUE );
457 	}
458 	else
459 	{
460 		// trap should REALLY go off now!
461 		return( FALSE );
462 	}
463 }
464 
ExamineDoorForTraps(SOLDIERTYPE * pSoldier,DOOR * pDoor)465 BOOLEAN ExamineDoorForTraps( SOLDIERTYPE * pSoldier, DOOR * pDoor )
466 {
467 	// Check to see if there is a trap or not on this door
468 	INT8 bDetectLevel;
469 
470 	if (pDoor->ubTrapID == NO_TRAP)
471 	{
472 		// No trap!
473 		pDoor->bPerceivedTrapped = DOOR_PERCEIVED_UNTRAPPED;
474 	}
475 	else
476 	{
477 		if (pDoor->bPerceivedTrapped == DOOR_PERCEIVED_TRAPPED)
478 		{
479 			return( TRUE );
480 		}
481 		else
482 		{
483 			bDetectLevel = CalcTrapDetectLevel( pSoldier, TRUE );
484 			if (bDetectLevel < pDoor->ubTrapLevel)
485 			{
486 				pDoor->bPerceivedTrapped = DOOR_PERCEIVED_UNTRAPPED;
487 			}
488 			else
489 			{
490 				pDoor->bPerceivedTrapped = DOOR_PERCEIVED_TRAPPED;
491 				return( TRUE );
492 			}
493 		}
494 	}
495 	return( FALSE );
496 }
497 
HasDoorTrapGoneOff(SOLDIERTYPE * pSoldier,DOOR * pDoor)498 BOOLEAN HasDoorTrapGoneOff( SOLDIERTYPE * pSoldier, DOOR * pDoor )
499 {
500 	// Check to see if the soldier causes the trap to go off
501 	INT8 bDetectLevel;
502 
503 	if (pDoor->ubTrapID != NO_TRAP)
504 	{
505 		// one quick check to see if the guy sees the trap ahead of time!
506 		bDetectLevel = CalcTrapDetectLevel( pSoldier, FALSE );
507 		if (bDetectLevel < pDoor->ubTrapLevel)
508 		{
509 			// trap goes off!
510 			return( TRUE );
511 		}
512 	}
513 	return( FALSE );
514 }
515 
516 
GetTrapName(DOOR const & d)517 ST::string GetTrapName(DOOR const& d)
518 {
519 	UINT8 trap_kind = d.ubTrapID;
520 	switch (trap_kind)
521 	{
522 		case BROTHEL_SIREN:  trap_kind = SIREN;    break;
523 		case SUPER_ELECTRIC: trap_kind = ELECTRIC; break;
524 	}
525 	return pDoorTrapStrings[trap_kind];
526 }
527 
528 
HandleDoorTrap(SOLDIERTYPE & s,DOOR const & d)529 void HandleDoorTrap(SOLDIERTYPE& s, DOOR const& d)
530 {
531 	if (!(DoorTrapTable[d.ubTrapID].fFlags & DOOR_TRAP_SILENT))
532 	{
533 		ST::string trap_name = GetTrapName(d);
534 		ScreenMsg(MSG_FONT_YELLOW, MSG_INTERFACE, st_format_printf(TacticalStr[LOCK_TRAP_HAS_GONE_OFF_STR], trap_name));
535 	}
536 
537 	// set trap off
538 	switch (d.ubTrapID)
539 	{
540 		case EXPLOSION:
541 			// cause damage as a regular hand grenade
542 			IgniteExplosion(NULL, 25, s.sGridNo, HAND_GRENADE, 0);
543 			break;
544 
545 		case SIREN:
546 			// play siren sound effect but otherwise treat as silent alarm, calling
547 			// available enemies to this location
548 			PlayLocationJA2Sample(d.sGridNo, KLAXON_ALARM, MIDVOLUME, 5);
549 			/* FALLTHROUGH */
550 
551 		case SILENT_ALARM:
552 			// Get all available enemies running here
553 			CallAvailableEnemiesTo(d.sGridNo);
554 			break;
555 
556 		case BROTHEL_SIREN:
557 			PlayLocationJA2Sample(d.sGridNo, KLAXON_ALARM, MIDVOLUME, 5);
558 			CallAvailableKingpinMenTo(d.sGridNo);
559 			// no one is authorized any more!
560 			gMercProfiles[MADAME].bNPCData = 0;
561 			break;
562 
563 		case ELECTRIC:
564 			// insert electrical sound effect here
565 			PlayLocationJA2Sample(d.sGridNo, DOOR_ELECTRICITY, MIDVOLUME, 1);
566 
567 			s.attacker = &s;
568 			s.bBeingAttackedCount++;
569 			gTacticalStatus.ubAttackBusyCount++;
570 			SLOGD("Trap gone off. Busy count: %d", gTacticalStatus.ubAttackBusyCount);
571 
572 			SoldierTakeDamage(&s, 10 + PreRandom(10), 3 + PreRandom(3) * 1000,
573 						TAKE_DAMAGE_ELECTRICITY, NULL);
574 			break;
575 
576 		case SUPER_ELECTRIC:
577 			// insert electrical sound effect here
578 			PlayLocationJA2Sample(d.sGridNo, DOOR_ELECTRICITY, MIDVOLUME, 1);
579 
580 			s.attacker = &s;
581 			s.bBeingAttackedCount++;
582 			gTacticalStatus.ubAttackBusyCount++;
583 			SLOGD("Trap gone off. Busy count: %d", gTacticalStatus.ubAttackBusyCount);
584 
585 			SoldierTakeDamage(&s, 20 + PreRandom(20), 6 + PreRandom(6) * 1000, TAKE_DAMAGE_ELECTRICITY, NULL);
586 			break;
587 
588 		default:
589 			// no trap
590 			break;
591 	}
592 }
593 
594 
AttemptToBlowUpLock(SOLDIERTYPE * pSoldier,DOOR * pDoor)595 BOOLEAN AttemptToBlowUpLock( SOLDIERTYPE * pSoldier, DOOR * pDoor )
596 {
597 	INT32 iResult;
598 	INT8  bSlot = NO_SLOT;
599 
600 	bSlot = FindObj( pSoldier, SHAPED_CHARGE );
601 	if (bSlot == NO_SLOT)
602 	{
603 		return( FALSE );
604 	}
605 
606 	iResult = SkillCheck( pSoldier, PLANTING_BOMB_CHECK, 0 );
607 	if (iResult >= -20)
608 	{
609 		// Do explosive graphic....
610 		{
611 			ANITILE_PARAMS AniParams;
612 			INT16 sGridNo;
613 			INT16 sX, sY, sZ;
614 
615 			// Get gridno
616 			sGridNo = pDoor->sGridNo;
617 
618 			// Get sX, sy;
619 			sX = CenterX( sGridNo );
620 			sY = CenterY( sGridNo );
621 
622 			// Get Z position, based on orientation....
623 			sZ = 20;
624 
625 			AniParams = ANITILE_PARAMS{};
626 			AniParams.sGridNo = sGridNo;
627 			AniParams.ubLevelID = ANI_TOPMOST_LEVEL;
628 			AniParams.sDelay = (INT16)( 100 );
629 			AniParams.sStartFrame = 0;
630 			AniParams.uiFlags = ANITILE_FORWARD | ANITILE_ALWAYS_TRANSLUCENT;
631 			AniParams.sX = sX;
632 			AniParams.sY = sY;
633 			AniParams.sZ = sZ;
634 			AniParams.zCachedFile = TILECACHEDIR "/miniboom.sti";
635 			CreateAnimationTile( &AniParams );
636 
637 			PlayLocationJA2Sample(sGridNo, SMALL_EXPLODE_1, HIGHVOLUME, 1);
638 
639 			// Remove the explosive.....
640 			bSlot = FindObj( pSoldier, SHAPED_CHARGE );
641 			if (bSlot != NO_SLOT)
642 			{
643 				RemoveObjs( &(pSoldier->inv[ bSlot ]), 1 );
644 				DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
645 			}
646 		}
647 
648 		// Not sure if this makes sense, but the explosive is small.
649 		// Double the damage here as we are damaging a lock rather than a person
650 		pDoor->bLockDamage += Explosive[GCM->getItem(SHAPED_CHARGE)->getClassIndex()].ubDamage * 2;
651 		if (pDoor->bLockDamage > LockTable[ pDoor->ubLockID ].ubSmashDifficulty )
652 		{
653 			// succeeded! door can never be locked again, so remove from door list...
654 			RemoveDoorInfoFromTable( pDoor->sGridNo );
655 			// award experience points?
656 			return( TRUE );
657 		}
658 	}
659 	else
660 	{
661 		bSlot = FindObj( pSoldier, SHAPED_CHARGE );
662 		if (bSlot != NO_SLOT)
663 		{
664 			RemoveObjs( &(pSoldier->inv[ bSlot ]), 1 );
665 			DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
666 		}
667 
668 		// OOPS! ... BOOM!
669 		IgniteExplosionXY(NULL, pSoldier->sX, pSoldier->sY, gpWorldLevelData[pSoldier->sGridNo].sHeight, pSoldier->sGridNo, SHAPED_CHARGE, 0);
670 	}
671 	return( FALSE );
672 }
673 
674 //File I/O for loading the door information from the map.  This automatically allocates
675 //the exact number of slots when loading.
LoadDoorTableFromMap(HWFILE const f)676 void LoadDoorTableFromMap(HWFILE const f)
677 {
678 	TrashDoorTable();
679 
680 	UINT8 numDoors = 0;
681 	FileRead(f, &numDoors, sizeof(numDoors));
682 	if (numDoors == 0) return;
683 
684 	DoorTable.assign(numDoors, DOOR{});
685 	FileRead(f, DoorTable.data(), sizeof(DOOR) * numDoors);
686 
687 	// OK, reset perceived values to nothing...
688 	FOR_EACH_DOOR(d)
689 	{
690 		d.bPerceivedLocked  = DOOR_PERCEIVED_UNKNOWN;
691 		d.bPerceivedTrapped = DOOR_PERCEIVED_UNKNOWN;
692 	}
693 }
694 
695 
696 //Saves the existing door information to the map.  Before it actually saves, it'll verify that the
697 //door still exists.  Otherwise, it'll ignore it.  It is possible in the editor to delete doors in
698 //many different ways, so I opted to put it in the saving routine.
SaveDoorTableToMap(HWFILE fp)699 void SaveDoorTableToMap( HWFILE fp )
700 {
701 	size_t i = 0;
702 
703 	while (i < DoorTable.size())
704 	{
705 		if (!OpenableAtGridNo( DoorTable[ i ].sGridNo ))
706 			RemoveDoorInfoFromTable( DoorTable[ i ].sGridNo );
707 		else
708 			i++;
709 	}
710 	Assert(DoorTable.size() <= UINT8_MAX);
711 	UINT8 numDoors = static_cast<UINT8>(DoorTable.size());
712 	FileWriteArray(fp, numDoors, DoorTable.data());
713 }
714 
715 
716 //The editor adds locks to the world.  If the gridno already exists, then the currently existing door
717 //information is overwritten.
AddDoorInfoToTable(DOOR * pDoor)718 void AddDoorInfoToTable( DOOR *pDoor )
719 {
720 	FOR_EACH_DOOR(d)
721 	{
722 		if (d.sGridNo != pDoor->sGridNo) continue;
723 		d = *pDoor;
724 		return;
725 	}
726 
727 	//no existing door found, so add a new one.
728 	DoorTable.push_back(*pDoor);
729 }
730 
731 //When the editor removes a door from the world, this function looks for and removes accompanying door
732 //information.  If the entry is not the last entry, the last entry is move to it's current slot, to keep
733 //everything contiguous.
RemoveDoorInfoFromTable(INT32 iMapIndex)734 void RemoveDoorInfoFromTable( INT32 iMapIndex )
735 {
736 	for (size_t i = 0; i < DoorTable.size(); i++)
737 	{
738 		if (DoorTable[ i ].sGridNo == iMapIndex)
739 		{
740 			DoorTable.erase(DoorTable.begin() + i);
741 			return;
742 		}
743 	}
744 }
745 
746 //This is the link to see if a door exists at a gridno.
FindDoorInfoAtGridNo(INT32 iMapIndex)747 DOOR* FindDoorInfoAtGridNo( INT32 iMapIndex )
748 {
749 	FOR_EACH_DOOR(d)
750 	{
751 		if (d.sGridNo == iMapIndex) return &d;
752 	}
753 	return NULL;
754 }
755 
756 //Upon world deallocation, the door table needs to be deallocated.
TrashDoorTable()757 void TrashDoorTable()
758 {
759 	DoorTable.clear();
760 }
761 
UpdateDoorPerceivedValue(DOOR * pDoor)762 void UpdateDoorPerceivedValue( DOOR *pDoor )
763 {
764 	if ( pDoor->fLocked )
765 	{
766 		pDoor->bPerceivedLocked = DOOR_PERCEIVED_LOCKED;
767 	}
768 	else if ( !pDoor->fLocked )
769 	{
770 		pDoor->bPerceivedLocked = DOOR_PERCEIVED_UNLOCKED;
771 	}
772 
773 	if (pDoor->ubTrapID != NO_TRAP)
774 	{
775 		pDoor->bPerceivedTrapped = DOOR_PERCEIVED_TRAPPED;
776 	}
777 	else
778 	{
779 		pDoor->bPerceivedTrapped = DOOR_PERCEIVED_UNTRAPPED;
780 	}
781 
782 }
783 
784 
SaveDoorTableToDoorTableTempFile(INT16 const x,INT16 const y,INT8 const z)785 void SaveDoorTableToDoorTableTempFile(INT16 const x, INT16 const y, INT8 const z)
786 {
787 	AutoSGPFile f(FileMan::openForWriting(GetMapTempFileName(SF_DOOR_TABLE_TEMP_FILES_EXISTS, x, y, z)));
788 	Assert(DoorTable.size() <= UINT8_MAX);
789 	UINT8 numDoors = static_cast<UINT8>(DoorTable.size());
790 	FileWriteArray(f, numDoors, DoorTable.data());
791 	// Set the sector flag indicating that there is a Door table temp file present
792 	SetSectorFlag(x, y, z, SF_DOOR_TABLE_TEMP_FILES_EXISTS);
793 }
794 
795 
LoadDoorTableFromDoorTableTempFile()796 void LoadDoorTableFromDoorTableTempFile()
797 {
798 	ST::string const zMapName = GetMapTempFileName( SF_DOOR_TABLE_TEMP_FILES_EXISTS, gWorldSectorX, gWorldSectorY, gbWorldSectorZ );
799 
800 	//If the file doesnt exists, its no problem.
801 	if (!GCM->doesGameResExists(zMapName)) return;
802 
803 	//Get rid of the existing door table
804 	TrashDoorTable();
805 
806 	AutoSGPFile hFile(GCM->openGameResForReading(zMapName));
807 
808 	//Read in the number of doors
809 	UINT8 numDoors = 0;
810 	FileRead(hFile, &numDoors, sizeof(UINT8));
811 
812 	//if there is no doors to load
813 	if (numDoors != 0)
814 	{
815 		DoorTable.assign(numDoors, DOOR{});
816 		FileRead(hFile, DoorTable.data(), sizeof(DOOR) * numDoors);
817 	}
818 }
819 
820 
821 // is_open is True if the door is open, false if it is closed
ModifyDoorStatus(GridNo const gridno,BOOLEAN const is_open,BOOLEAN const perceived_open)822 bool ModifyDoorStatus(GridNo const gridno, BOOLEAN const is_open, BOOLEAN const perceived_open)
823 {
824 	// Find the base tile for the door structure and use that gridno
825 	STRUCTURE* const structure = FindStructure(gridno, STRUCTURE_ANYDOOR);
826 	if (!structure) return false;
827 
828 	STRUCTURE* const base = FindBaseStructure(structure);
829 	if (!base) return false;
830 	GridNo const base_gridno = base->sGridNo;
831 
832 	// Check to see if the user is adding an existing door
833 	FOR_EACH_DOOR_STATUS(d)
834 	{
835 		if (d.sGridNo != base_gridno) continue;
836 
837 		// Set the status
838 		if (perceived_open != DONTSETDOORSTATUS)
839 		{
840 			if (perceived_open)
841 				d.ubFlags |= DOOR_PERCEIVED_OPEN;
842 			else
843 				d.ubFlags &= ~DOOR_PERCEIVED_OPEN;
844 
845 			// Turn off perceived not set flag
846 			d.ubFlags &= ~DOOR_PERCEIVED_NOTSET;
847 		}
848 
849 		if (is_open != DONTSETDOORSTATUS)
850 		{
851 			if (is_open)
852 				d.ubFlags |= DOOR_OPEN;
853 			else
854 				d.ubFlags &= ~DOOR_OPEN;
855 		}
856 
857 		return true;
858 	}
859 
860 	// Add a new door status structure
861 	gpDoorStatus.push_back(DOOR_STATUS{});
862 
863 	DOOR_STATUS& d = gpDoorStatus.back();
864 	d.sGridNo = base_gridno;
865 
866 	// Init the flags
867 	d.ubFlags = 0;
868 
869 	if (is_open) d.ubFlags |= DOOR_OPEN;
870 
871 	// if a new door, use same as actual
872 	if (perceived_open == DONTSETDOORSTATUS)
873 	{
874 		d.ubFlags |= DOOR_PERCEIVED_NOTSET;
875 	}
876 	else if (is_open)
877 	{
878 		d.ubFlags |= DOOR_PERCEIVED_OPEN;
879 	}
880 
881 	// Flag the tile as containing a door status
882 	gpWorldLevelData[base_gridno].ubExtFlags[0] |= MAPELEMENT_EXT_DOOR_STATUS_PRESENT;
883 	return true;
884 }
885 
886 
TrashDoorStatusArray()887 void TrashDoorStatusArray( )
888 {
889 	gpDoorStatus.clear();
890 }
891 
892 
893 // Returns a doors status value, NULL if not found
GetDoorStatus(INT16 const sGridNo)894 DOOR_STATUS* GetDoorStatus(INT16 const sGridNo)
895 {
896 	if (gpDoorStatus.empty()) return 0;
897 
898 	// Find the base tile for the door structure and use that gridno
899 	STRUCTURE* const structure = FindStructure(sGridNo, STRUCTURE_ANYDOOR);
900 	if (!structure) return 0;
901 	STRUCTURE const* const base = FindBaseStructure(structure);
902 	if (!base) return 0;
903 
904 	FOR_EACH_DOOR_STATUS(d)
905 	{
906 		if (d.sGridNo == base->sGridNo) return &d;
907 	}
908 
909 	return 0;
910 }
911 
912 
IsCloseEnoughAndHasLOS(SOLDIERTYPE const & s,GridNo const gridno,INT16 const dist_visible)913 static bool IsCloseEnoughAndHasLOS(SOLDIERTYPE const& s, GridNo const gridno, INT16 const dist_visible)
914 {
915 	return
916 		// Is he close enough to see that gridno if he turns his head?
917 		PythSpacesAway(s.sGridNo, gridno) <= dist_visible &&
918 		// Can we trace a line of sight to his x,y coordinates? (taking into account
919 		// we are definitely aware of this guy now)
920 		SoldierTo3DLocationLineOfSightTest(&s, gridno, 0, 0, dist_visible, TRUE);
921 }
922 
923 
924 static INT8 const g_dirs[] = { NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST };
925 
926 
AllMercsLookForDoor(GridNo const gridno)927 bool AllMercsLookForDoor(GridNo const gridno)
928 {
929 	if (!GetDoorStatus(gridno)) return false;
930 
931 	CFOR_EACH_IN_TEAM(i, OUR_TEAM)
932 	{
933 		SOLDIERTYPE const& s = *i;
934 		if (s.bLife < OKLIFE)     continue;
935 		if (s.sGridNo == NOWHERE) continue;
936 		if (!s.bInSector)         continue;
937 
938 		INT16 const dist_visible = DistanceVisible(&s, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, gridno, 0);
939 		if (IsCloseEnoughAndHasLOS(s, gridno, dist_visible)) return true;
940 
941 		// Now try other adjacent gridnos
942 		for (INT32 dir = 0; dir != 8; ++dir)
943 		{
944 			GridNo const new_gridno   = NewGridNo(gridno, DirectionInc(g_dirs[dir]));
945 			INT16  const dist_visible = DistanceVisible(&s, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, new_gridno, 0);
946 			if (IsCloseEnoughAndHasLOS(s, new_gridno, dist_visible)) return true;
947 		}
948 	}
949 
950 	return false;
951 }
952 
953 
954 static bool InternalIsPerceivedDifferentThanReality(DOOR_STATUS const&);
955 static void InternalUpdateDoorGraphicFromStatus(DOOR_STATUS const&, bool dirty);
956 static void InternalUpdateDoorsPerceivedValue(DOOR_STATUS&);
957 
958 
MercLooksForDoors(SOLDIERTYPE const & s)959 void MercLooksForDoors(SOLDIERTYPE const& s)
960 {
961 	FOR_EACH_DOOR_STATUS(d)
962 	{
963 		if (!InternalIsPerceivedDifferentThanReality(d)) continue;
964 
965 		GridNo const gridno       = d.sGridNo;
966 		INT16  const dist_visible = DistanceVisible(&s, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, gridno, 0);
967 
968 		if (IsCloseEnoughAndHasLOS(s, gridno, dist_visible))
969 		{
970 sees_door:
971 			InternalUpdateDoorsPerceivedValue(d);
972 			InternalUpdateDoorGraphicFromStatus(d, true);
973 			break;
974 		}
975 
976 		// Now try other adjacent gridnos
977 		for (INT32 dir = 0; dir != 8; ++dir)
978 		{
979 			GridNo const new_gridno = NewGridNo(gridno, DirectionInc(g_dirs[dir]));
980 			// XXX It seems strage that AllMercsLookForDoor() calculates a new dist_visible, but MercLooksForDoors() does not
981 			if (IsCloseEnoughAndHasLOS(s, new_gridno, dist_visible)) goto sees_door;
982 		}
983 	}
984 }
985 
986 
SynchronizeDoorStatusToStructureData(DOOR_STATUS const & d)987 static void SynchronizeDoorStatusToStructureData(DOOR_STATUS const& d)
988 {
989 	// First look for a door structure here
990 	STRUCTURE* const s = FindStructure(d.sGridNo, STRUCTURE_ANYDOOR);
991 	if (!s) return;
992 
993 	// ATE: One of the purposes of this function is to MAKE sure the door status
994 	// MATCHES the struct data value - if not - change (REGARDLESS of perceived
995 	// being used or not)
996 	bool const door_open   = d.ubFlags & DOOR_OPEN;
997 	bool const struct_open = s->fFlags & STRUCTURE_OPEN;
998 	if (door_open == struct_open) return;
999 
1000 	// Swap!
1001 	STRUCTURE *base = FindBaseStructure(s);
1002 	if (!base)
1003 	{
1004 		STLOGW("Door structure data at {} was not found", d.sGridNo);
1005 		return;
1006 	}
1007 	INT16 sBaseGridNo = base->sGridNo;
1008 	SwapStructureForPartner(base);
1009 	RecompileLocalMovementCosts(sBaseGridNo);
1010 }
1011 
1012 
UpdateDoorGraphicsFromStatus()1013 void UpdateDoorGraphicsFromStatus()
1014 {
1015 	FOR_EACH_DOOR_STATUS(d)
1016 	{
1017 		// ATE: Make sure door status flag and struct info are synchronized
1018 		SynchronizeDoorStatusToStructureData(d);
1019 		InternalUpdateDoorGraphicFromStatus(d, false);
1020 	}
1021 }
1022 
1023 
InternalUpdateDoorGraphicFromStatus(DOOR_STATUS const & d,bool const dirty)1024 static void InternalUpdateDoorGraphicFromStatus(DOOR_STATUS const& d, bool const dirty)
1025 {
1026 	// OK, look at perceived status and adjust graphic
1027 	// First look for a door structure here...
1028 	STRUCTURE* const s = FindStructure(d.sGridNo, STRUCTURE_ANYDOOR);
1029 	if (!s) return;
1030 
1031 	STRUCTURE* const base = FindBaseStructure(s);
1032 	LEVELNODE* const node = FindLevelNodeBasedOnStructure(base);
1033 
1034 	// Get status we want to change to
1035 	bool const want_to_be_open = d.ubFlags & DOOR_PERCEIVED_OPEN;
1036 
1037 	// First look for an opened door
1038 	// get what it is now
1039 	bool openend_graphic = false;
1040 	INT32 cnt;
1041 	for (cnt = 0; gClosedDoorList[cnt] != -1; ++cnt)
1042 	{
1043 		// IF WE ARE A SHADOW TYPE
1044 		if (node->usIndex == gClosedDoorList[cnt])
1045 		{
1046 			openend_graphic = true;
1047 			break;
1048 		}
1049 	}
1050 
1051 	// We either have an opened graphic, in which case we want to switch to the
1052 	// closed, or a closed in which case we want to switch to opened
1053 	// adjust o' graphic
1054 
1055 	// We now need to test these things against the true structure data we may
1056 	// need to only adjust the graphic here
1057 	if (want_to_be_open && s->fFlags & STRUCTURE_OPEN)
1058 	{
1059 		// Adjust graphic
1060 
1061 		// Loop through and and find opened graphic for the closed one
1062 		for (INT32 i = 0; gOpenDoorList[i] != -1; ++i)
1063 		{
1064 			// IF WE ARE A SHADOW TYPE
1065 			if (node->usIndex == gOpenDoorList[i])
1066 			{
1067 				// OK, now use opened graphic.
1068 				node->usIndex = gClosedDoorList[i];
1069 				goto dirty_end;
1070 			}
1071 		}
1072 		return;
1073 	}
1074 
1075 	// If we want to be closed but structure is closed
1076 	if (!want_to_be_open && !(s->fFlags & STRUCTURE_OPEN))
1077 	{
1078 		// Adjust graphic
1079 
1080 		// Loop through and and find closed graphic for the opend one
1081 		for (INT32 i = 0; gClosedDoorList[i] != -1; ++i)
1082 		{
1083 			// IF WE ARE A SHADOW TYPE
1084 			if (node->usIndex == gClosedDoorList[i])
1085 			{
1086 				node->usIndex = gOpenDoorList[i];
1087 				goto dirty_end;
1088 			}
1089 		}
1090 		return;
1091 	}
1092 
1093 	if (openend_graphic && !want_to_be_open)
1094 	{
1095 		// Close the beast!
1096 		node->usIndex = gOpenDoorList[cnt];
1097 	}
1098 	else if (!openend_graphic && want_to_be_open)
1099 	{
1100 		bool different = false;
1101 		// Find the closed door graphic and adjust
1102 		for (INT32 i = 0; gOpenDoorList[i] != -1; ++i)
1103 		{
1104 			// IF WE ARE A SHADOW TYPE
1105 			if (node->usIndex == gOpenDoorList[i])
1106 			{
1107 				// Open the beast!
1108 				different = true;
1109 				node->usIndex = gClosedDoorList[i];
1110 				break;
1111 			}
1112 		}
1113 		if (!different) return;
1114 	}
1115 	else
1116 	{
1117 		return;
1118 	}
1119 
1120 	{ GridNo const base_grid_no = base->sGridNo;
1121 		SwapStructureForPartner(base);
1122 		RecompileLocalMovementCosts(base_grid_no);
1123 	}
1124 
1125 dirty_end:
1126 	if (dirty)
1127 	{
1128 		InvalidateWorldRedundency();
1129 		SetRenderFlags(RENDER_FLAG_FULL);
1130 	}
1131 }
1132 
1133 
InternalIsPerceivedDifferentThanReality(DOOR_STATUS const & d)1134 static bool InternalIsPerceivedDifferentThanReality(DOOR_STATUS const& d)
1135 {
1136 	return d.ubFlags & DOOR_PERCEIVED_NOTSET ||
1137 		((d.ubFlags & DOOR_OPEN) != 0) != ((d.ubFlags & DOOR_PERCEIVED_OPEN) != 0);
1138 }
1139 
1140 
InternalUpdateDoorsPerceivedValue(DOOR_STATUS & d)1141 static void InternalUpdateDoorsPerceivedValue(DOOR_STATUS& d)
1142 {
1143 	// Set perceived value the same as actual
1144 	d.ubFlags &= ~(DOOR_PERCEIVED_NOTSET | DOOR_PERCEIVED_OPEN);
1145 	d.ubFlags |= (d.ubFlags & DOOR_OPEN ? DOOR_PERCEIVED_OPEN : 0);
1146 }
1147 
1148 
SaveDoorStatusArrayToDoorStatusTempFile(INT16 const x,INT16 const y,INT8 const z)1149 void SaveDoorStatusArrayToDoorStatusTempFile(INT16 const x, INT16 const y, INT8 const z)
1150 {
1151 	// Turn off any door busy flags
1152 	FOR_EACH_DOOR_STATUS(d) d.ubFlags &= ~DOOR_BUSY;
1153 
1154 	AutoSGPFile f(FileMan::openForWriting(GetMapTempFileName(SF_DOOR_STATUS_TEMP_FILE_EXISTS, x, y, z)));
1155 	Assert(gpDoorStatus.size() <= UINT8_MAX);
1156 	UINT8 numDoorStatus = static_cast<UINT8>(gpDoorStatus.size());
1157 	FileWriteArray(f, numDoorStatus, gpDoorStatus.data());
1158 
1159 	// Set the flag indicating that there is a door status array
1160 	SetSectorFlag(x, y, z, SF_DOOR_STATUS_TEMP_FILE_EXISTS);
1161 }
1162 
1163 
LoadDoorStatusArrayFromDoorStatusTempFile()1164 void LoadDoorStatusArrayFromDoorStatusTempFile()
1165 {
1166 	TrashDoorStatusArray();
1167 
1168 	AutoSGPFile f(GCM->openGameResForReading(GetMapTempFileName(SF_DOOR_STATUS_TEMP_FILE_EXISTS, gWorldSectorX, gWorldSectorY, gbWorldSectorZ)));
1169 
1170 	// Load the number of elements in the door status array
1171 	UINT8 numDoorStatus = 0;
1172 	FileRead(f, &numDoorStatus, sizeof(UINT8));
1173 	if (numDoorStatus == 0) return;
1174 
1175 	gpDoorStatus.assign(numDoorStatus, DOOR_STATUS{});
1176 	FileRead(f, gpDoorStatus.data(), sizeof(DOOR_STATUS) * numDoorStatus);
1177 
1178 	// Set flags in map for containing a door status
1179 	FOR_EACH_DOOR_STATUS(d)
1180 	{
1181 		gpWorldLevelData[d.sGridNo].ubExtFlags[0] |= MAPELEMENT_EXT_DOOR_STATUS_PRESENT;
1182 	}
1183 
1184 	// The graphics will be updated later in the loading process
1185 	UpdateDoorGraphicsFromStatus();
1186 }
1187 
1188 
SaveKeyTableToSaveGameFile(HWFILE const f)1189 void SaveKeyTableToSaveGameFile(HWFILE const f)
1190 {
1191 	FOR_EACH(KEY const, i, KeyTable)
1192 	{
1193 		KEY const& k = *i;
1194 		BYTE       data[8];
1195 		DataWriter d{data};
1196 		INJ_SKIP(d, 4)
1197 		INJ_U16( d, k.usSectorFound)
1198 		INJ_U16( d, k.usDateFound)
1199 		Assert(d.getConsumed() == lengthof(data));
1200 		FileWrite(f, data, sizeof(data));
1201 	}
1202 }
1203 
1204 
LoadKeyTableFromSaveedGameFile(HWFILE const f)1205 void LoadKeyTableFromSaveedGameFile(HWFILE const f)
1206 {
1207 	FOR_EACH(KEY, i, KeyTable)
1208 	{
1209 		BYTE data[8];
1210 		FileRead(f, data, sizeof(data));
1211 		KEY&  k = *i;
1212 		DataReader d{data};
1213 		EXTR_SKIP(d, 4)
1214 		EXTR_U16( d, k.usSectorFound)
1215 		EXTR_U16( d, k.usDateFound)
1216 		Assert(d.getConsumed() == lengthof(data));
1217 	}
1218 }
1219 
1220 
ExamineDoorsOnEnteringSector()1221 void ExamineDoorsOnEnteringSector()
1222 {
1223 	// If this is Omerta, don't do it
1224 	if (GetTownIdForSector(SECTOR(gWorldSectorX, gWorldSectorY)) == OMERTA) return;
1225 
1226 	// Check time
1227 	if (GetWorldTotalMin() - gTacticalStatus.uiTimeSinceLastInTactical < 30) return;
1228 
1229 	// If there is at least one human being in that sector, close doors.
1230 	CFOR_EACH_NON_PLAYER_SOLDIER(s)
1231 	{
1232 		if (!s->bInSector) continue;
1233 
1234 		FOR_EACH_DOOR_STATUS(d)
1235 		{
1236 			// If open, close
1237 			if (!(d.ubFlags & DOOR_OPEN)) continue;
1238 			HandleDoorChangeFromGridNo(0, d.sGridNo, TRUE);
1239 		}
1240 		break;
1241 	}
1242 }
1243 
1244 
DropKeysInKeyRing(SOLDIERTYPE & s,GridNo const gridno,INT8 const level,Visibility const visible,bool const add_to_drop_list,INT32 const drop_list_slot,bool const use_unloaded)1245 void DropKeysInKeyRing(SOLDIERTYPE& s, GridNo const gridno, INT8 const level, Visibility const visible, bool const add_to_drop_list, INT32 const drop_list_slot, bool const use_unloaded)
1246 {
1247 	KEY_ON_RING* const key_ring = s.pKeyRing;
1248 	if (!key_ring) return; // No key ring
1249 
1250 	bool const here = !use_unloaded && s.sSectorX == gWorldSectorX && s.sSectorY == gWorldSectorY && s.bSectorZ == gbWorldSectorZ;
1251 	for (KEY_ON_RING* i = key_ring; i != key_ring + NUM_KEYS; ++i)
1252 	{
1253 		KEY_ON_RING& k = *i;
1254 		if (k.ubNumber == 0) continue;
1255 
1256 		OBJECTTYPE o;
1257 		CreateKeyObject(&o, k.ubNumber, k.ubKeyID);
1258 
1259 		// Zero out entry
1260 		k.ubNumber = 0;
1261 		k.ubKeyID  = INVALID_KEY_NUMBER;
1262 
1263 		if (add_to_drop_list)
1264 		{
1265 			AddItemToLeaveIndex(&o, drop_list_slot);
1266 		}
1267 		else if (here)
1268 		{
1269 			AddItemToPool(gridno, &o, visible, level, 0, 0);
1270 		}
1271 		else
1272 		{
1273 			AddItemsToUnLoadedSector(s.sSectorX, s.sSectorY, s.bSectorZ, gridno, 1, &o, level, WOLRD_ITEM_FIND_SWEETSPOT_FROM_GRIDNO | WORLD_ITEM_REACHABLE, 0, visible);
1274 		}
1275 	}
1276 }
1277 
1278 
1279 #ifdef WITH_UNITTESTS
1280 #include "gtest/gtest.h"
1281 
TEST(Keys,asserts)1282 TEST(Keys, asserts)
1283 {
1284 	EXPECT_EQ(sizeof(LOCK), 46u);
1285 	EXPECT_EQ(sizeof(DOOR), 14u);
1286 	EXPECT_EQ(sizeof(DOOR_STATUS), 4u);
1287 }
1288 
1289 #endif
1290