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