1 #include "Font.h"
2 #include "AI.h"
3 #include "Debug_Pages.h"
4 #include "Isometric_Utils.h"
5 #include "Overhead.h"
6 #include "Event_Pump.h"
7 #include "Random.h"
8 #include "Overhead_Types.h"
9 #include "OppList.h"
10 #include "AI.h"
11 #include "Font_Control.h"
12 #include "Soldier_Find.h"
13 #include "Animation_Control.h"
14 #include "LOS.h"
15 #include "FOV.h"
16 #include "Dialogue_Control.h"
17 #include "Lighting.h"
18 #include "Environment.h"
19 #include "Points.h"
20 #include "Interface_Dialogue.h"
21 #include "Message.h"
22 #include "Soldier_Profile.h"
23 #include "Structure.h"
24 #include "TeamTurns.h"
25 #include "Interactive_Tiles.h"
26 #include "Render_Fun.h"
27 #include "Text.h"
28 #include "TileDef.h"
29 #include "Timer_Control.h"
30 #include "Soldier_Macros.h"
31 #include "Soldier_Functions.h"
32 #include "Handle_UI.h"
33 #include "Queen_Command.h"
34 #include "Keys.h"
35 #include "Campaign.h"
36 #include "Soldier_Init_List.h"
37 #include "Music_Control.h"
38 #include "ContentMusic.h"
39 #include "StrategicMap.h"
40 #include "Quests.h"
41 #include "Meanwhile.h"
42 #include "WorldMan.h"
43 #include "SkillCheck.h"
44 #include "Smell.h"
45 #include "GameSettings.h"
46 #include "Game_Clock.h"
47 #include "Civ_Quotes.h"
48 #include "Sound_Control.h"
49 #include "Drugs_And_Alcohol.h"
50 #include "Debug.h"
51 #include "Items.h"
52 #include "GameRes.h"
53 #include "MercProfile.h"
54
55 #include "ContentManager.h"
56 #include "GameInstance.h"
57
58 #include <string_theory/format>
59 #include <string_theory/string>
60
61 #include <algorithm>
62 #include <iterator>
63
64 #define WE_SEE_WHAT_MILITIA_SEES_AND_VICE_VERSA
65
66
67 static const BOOLEAN gbShowEnemies = FALSE; // XXX seems to be a debug switch
68
69 // for ManLooksForMan()
70 #define MANLOOKSFOROTHERTEAMS 0
71 #define OTHERTEAMSLOOKFORMAN 1
72 #define VERIFYANDDECAYOPPLIST 2
73 #define HANDLESTEPPEDLOOKAT 3
74 #define LOOKANDLISTEN 4
75 #define UPDATEPUBLIC 5
76 #define CALLER_UNKNOWN 6
77
78 // this variable is a flag used in HandleSight to determine whether (while in non-combat RT)
79 // someone has just been seen, EITHER THE MOVER OR SOMEONE THE MOVER SEES
80 static BOOLEAN gfPlayerTeamSawCreatures = FALSE;
81 BOOLEAN gfPlayerTeamSawJoey = FALSE;
82 BOOLEAN gfMikeShouldSayHi = FALSE;
83
84 static SOLDIERTYPE* gBestToMakeSighting[BEST_SIGHTING_ARRAY_SIZE];
85 UINT8 gubBestToMakeSightingSize = 0;
86 //BOOLEAN gfHumanSawSomeoneInRealtime;
87
88 static BOOLEAN gfDelayResolvingBestSightingDueToDoor = FALSE;
89
90 #define SHOULD_BECOME_HOSTILE_SIZE 32
91
92 static SOLDIERTYPE* gShouldBecomeHostileOrSayQuote[SHOULD_BECOME_HOSTILE_SIZE];
93 static UINT8 gubNumShouldBecomeHostileOrSayQuote;
94
95 // NB this ID is set for someone opening a door
96 SOLDIERTYPE* gInterruptProvoker = NULL;
97
98 INT8 gbPublicOpplist[MAXTEAMS][TOTAL_SOLDIERS];
99 INT8 gbSeenOpponents[TOTAL_SOLDIERS][TOTAL_SOLDIERS];
100 INT16 gsLastKnownOppLoc[TOTAL_SOLDIERS][TOTAL_SOLDIERS]; // merc vs. merc
101 INT8 gbLastKnownOppLevel[TOTAL_SOLDIERS][TOTAL_SOLDIERS];
102 INT16 gsPublicLastKnownOppLoc[MAXTEAMS][TOTAL_SOLDIERS]; // team vs. merc
103 INT8 gbPublicLastKnownOppLevel[MAXTEAMS][TOTAL_SOLDIERS];
104 UINT8 gubPublicNoiseVolume[MAXTEAMS];
105 INT16 gsPublicNoiseGridno[MAXTEAMS];
106 INT8 gbPublicNoiseLevel[MAXTEAMS];
107
108 UINT8 gubKnowledgeValue[10][10] =
109 {
110 // P E R S O N A L O P P L I S T //
111 // -4 -3 -2 -1 0 1 2 3 4 5 //
112 { 0, 1, 2, 3, 0, 5, 4, 3, 2, 1}, // -4
113 { 0, 0, 1, 2, 0, 4, 3, 2, 1, 0}, // -3 O
114 { 0, 0, 0, 1, 0, 3, 2, 1, 0, 0}, // -2 P P
115 { 0, 0, 0, 0, 0, 2, 1, 0, 0, 0}, // -1 U P
116 { 0, 1, 2, 3, 0, 5, 4, 3, 2, 1}, // 0 B L
117 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 1 L I
118 { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}, // 2 I S
119 { 0, 0, 0, 1, 0, 2, 1, 0, 0, 0}, // 3 C T
120 { 0, 0, 1, 2, 0, 3, 2, 1, 0, 0}, // 4
121 { 0, 1, 2, 3, 0, 4, 3, 2, 1, 0} // 5
122
123 /*
124 // P E R S O N A L O P P L I S T //
125 // -3 -2 -1 0 1 2 3 4 //
126 { 0, 1, 2, 0, 4, 3, 2, 1 }, // -3 O
127 { 0, 0, 1, 0, 3, 2, 1, 0 }, // -2 P P
128 { 0, 0, 0, 0, 2, 1, 0, 0 }, // -1 U P
129 { 1, 2, 3, 0, 5, 4, 3, 2 }, // 0 B L
130 { 0, 0, 0, 0, 0, 0, 0, 0 }, // 1 L I
131 { 0, 0, 0, 0, 1, 0, 0, 0 }, // 2 I S
132 { 0, 0, 1, 0, 2, 1, 0, 0 }, // 3 C T
133 { 0, 1, 2, 0, 3, 2, 1, 0 } // 4
134 */
135 };
136
137 #define MAX_WATCHED_LOC_POINTS 4
138 #define WATCHED_LOC_RADIUS 1
139
140 INT16 gsWatchedLoc[ TOTAL_SOLDIERS ][ NUM_WATCHED_LOCS ];
141 INT8 gbWatchedLocLevel[ TOTAL_SOLDIERS ][ NUM_WATCHED_LOCS ];
142 UINT8 gubWatchedLocPoints[ TOTAL_SOLDIERS ][ NUM_WATCHED_LOCS ];
143 BOOLEAN gfWatchedLocReset[ TOTAL_SOLDIERS ][ NUM_WATCHED_LOCS ];
144 static BOOLEAN gfWatchedLocHasBeenIncremented[TOTAL_SOLDIERS][NUM_WATCHED_LOCS];
145
146 static const INT8 gbLookDistance[8][8] =
147 {
148 // LOOKER DIR LOOKEE DIR
149 // NORTH | NORTHEAST | EAST | SOUTHEAST | SOUTH | SOUTHWEST | WEST | NORTHWEST
150 /* NORTH */ { STRAIGHT, ANGLE, SIDE, SBEHIND, BEHIND, SBEHIND, SIDE, ANGLE },
151 /* NORTHEAST */ { ANGLE, STRAIGHT, ANGLE, SIDE, SBEHIND, BEHIND, SBEHIND, SIDE },
152 /* EAST */ { SIDE, ANGLE, STRAIGHT, ANGLE, SIDE, SBEHIND, BEHIND, SBEHIND },
153 /* SOUTHEAST */ { SBEHIND, SIDE, ANGLE, STRAIGHT, ANGLE, SIDE, SBEHIND, BEHIND },
154 /* SOUTH */ { BEHIND, SBEHIND, SIDE, ANGLE, STRAIGHT, ANGLE, SIDE, SBEHIND },
155 /* SOUTHWEST */ { SBEHIND, BEHIND, SBEHIND, SIDE, ANGLE, STRAIGHT, ANGLE, SIDE },
156 /* WEST */ { SIDE, SBEHIND, BEHIND, SBEHIND, SIDE, ANGLE, STRAIGHT, ANGLE },
157 /* NORTHWEST */ { ANGLE, SIDE, SBEHIND, BEHIND, SBEHIND, SIDE, ANGLE, STRAIGHT }
158 };
159
160 const SOLDIERTYPE* gWhoThrewRock = NULL;
161
162 #define NIGHTSIGHTGOGGLES_BONUS 2
163 #define UVGOGGLES_BONUS 4
164
165 // % values of sighting distance at various light levels
166
167 INT8 gbLightSighting[1][16] =
168 {{
169 // human
170 80, // brightest
171 86,
172 93,
173 100, // normal daylight, 3
174 94,
175 88,
176 82,
177 76,
178 70, // mid-dawn, 8
179 64,
180 58,
181 51,
182 43, // normal nighttime, 12 (11 tiles)
183 30,
184 17,
185 9
186 }};
187 /*
188 {{
189 // human
190 80, // brightest
191 86,
192 93,
193 100, // normal daylight, 3
194 93,
195 86,
196 79,
197 72,
198 65, // mid-dawn, 8
199 58,
200 53,
201 43, // normal nighttime, 11 (11 tiles)
202 35,
203 26,
204 17,
205 9
206 }};*/
207
208 SightFlags gubSightFlags;
209
210 #define DECAY_OPPLIST_VALUE( value )\
211 {\
212 if ( (value) >= SEEN_THIS_TURN)\
213 {\
214 (value)++;\
215 if ( (value) > OLDEST_SEEN_VALUE )\
216 {\
217 (value) = NOT_HEARD_OR_SEEN;\
218 }\
219 }\
220 else\
221 {\
222 if ( (value) <= HEARD_THIS_TURN)\
223 {\
224 (value)--;\
225 if ( (value) < OLDEST_HEARD_VALUE)\
226 {\
227 (value) = NOT_HEARD_OR_SEEN;\
228 }\
229 }\
230 }\
231 }
232
233
AdjustMaxSightRangeForEnvEffects(INT8 bLightLevel,INT16 sDistVisible)234 INT16 AdjustMaxSightRangeForEnvEffects(INT8 bLightLevel, INT16 sDistVisible)
235 {
236 INT16 sNewDist = 0;
237
238 sNewDist = sDistVisible * gbLightSighting[ 0 ][ bLightLevel ] / 100;
239
240 // Adjust it based on weather...
241 if ( guiEnvWeather & ( WEATHER_FORECAST_SHOWERS | WEATHER_FORECAST_THUNDERSHOWERS ) )
242 {
243 sNewDist = sNewDist * 70 / 100;
244 }
245
246 return( sNewDist );
247 }
248
249
SwapBestSightingPositions(INT8 bPos1,INT8 bPos2)250 static void SwapBestSightingPositions(INT8 bPos1, INT8 bPos2)
251 {
252 SOLDIERTYPE* const temp = gBestToMakeSighting[bPos1];
253 gBestToMakeSighting[bPos1] = gBestToMakeSighting[bPos2];
254 gBestToMakeSighting[bPos2] = temp;
255 }
256
257
ReevaluateBestSightingPosition(SOLDIERTYPE * pSoldier,INT8 bInterruptDuelPts)258 static void ReevaluateBestSightingPosition(SOLDIERTYPE* pSoldier, INT8 bInterruptDuelPts)
259 {
260 UINT8 ubLoop, ubLoop2;
261 BOOLEAN fFound = FALSE;
262 BOOLEAN fPointsGotLower = FALSE;
263
264 if ( bInterruptDuelPts == NO_INTERRUPT )
265 {
266 return;
267 }
268
269 if ( !( pSoldier->uiStatusFlags & SOLDIER_MONSTER ) )
270 {
271 //gfHumanSawSomeoneInRealtime = TRUE;
272 }
273
274 if ( (pSoldier->bInterruptDuelPts != NO_INTERRUPT) && (bInterruptDuelPts < pSoldier->bInterruptDuelPts) )
275 {
276 fPointsGotLower = TRUE;
277 }
278
279 if ( fPointsGotLower )
280 {
281 // loop to end of array less 1 entry since we can't swap the last entry out of the array
282 for ( ubLoop = 0; ubLoop < gubBestToMakeSightingSize - 1; ubLoop++ )
283 {
284 if (pSoldier == gBestToMakeSighting[ubLoop])
285 {
286 fFound = TRUE;
287 break;
288 }
289 }
290
291 // this guy has fewer interrupt pts vs another enemy! reduce position unless in last place
292 if (fFound)
293 {
294 // set new points
295 SLOGD(
296 "RBSP: reducing points for %d to %d",
297 pSoldier->ubID, bInterruptDuelPts);
298 pSoldier->bInterruptDuelPts = bInterruptDuelPts;
299
300 // must percolate him down
301 for ( ubLoop2 = ubLoop + 1; ubLoop2 < gubBestToMakeSightingSize; ubLoop2++ )
302 {
303 if (gBestToMakeSighting[ubLoop2] != NULL && gBestToMakeSighting[ubLoop2 - 1]->bInterruptDuelPts < gBestToMakeSighting[ubLoop2]->bInterruptDuelPts)
304 {
305 SwapBestSightingPositions( (UINT8) (ubLoop2 - 1), ubLoop2 );
306 }
307 else
308 {
309 break;
310 }
311 }
312 }
313 else if (pSoldier == gBestToMakeSighting[gubBestToMakeSightingSize - 1])
314 {
315 // in list but can't be bumped down... set his new points
316 SLOGD(
317 "RBSP: reduced points for last individual %d to %d",
318 pSoldier->ubID, bInterruptDuelPts);
319 pSoldier->bInterruptDuelPts = bInterruptDuelPts;
320 }
321 }
322 else
323 {
324 // loop through whole array
325 for ( ubLoop = 0; ubLoop < gubBestToMakeSightingSize; ubLoop++ )
326 {
327 if (pSoldier == gBestToMakeSighting[ubLoop])
328 {
329 fFound = TRUE;
330 break;
331 }
332 }
333
334 if (!fFound)
335 {
336 for ( ubLoop = 0; ubLoop < gubBestToMakeSightingSize; ubLoop++ )
337 {
338 if (gBestToMakeSighting[ubLoop] == NULL || bInterruptDuelPts > gBestToMakeSighting[ubLoop]->bInterruptDuelPts)
339 {
340 if (gBestToMakeSighting[gubBestToMakeSightingSize - 1] != NULL)
341 {
342 gBestToMakeSighting[gubBestToMakeSightingSize - 1]->bInterruptDuelPts = NO_INTERRUPT;
343 SLOGD(
344 "RBSP: resetting points for %d to zilch",
345 pSoldier->ubID);
346 }
347
348 // set new points
349 SLOGD("RBSP: setting points for %d to %d",
350 pSoldier->ubID, bInterruptDuelPts);
351 pSoldier->bInterruptDuelPts = bInterruptDuelPts;
352
353 // insert here!
354 for ( ubLoop2 = gubBestToMakeSightingSize - 1; ubLoop2 > ubLoop; ubLoop2-- )
355 {
356 gBestToMakeSighting[ubLoop2] = gBestToMakeSighting[ubLoop2 - 1];
357 }
358 gBestToMakeSighting[ubLoop] = pSoldier;
359 break;
360 }
361 }
362 }
363 // else points didn't get lower, so do nothing (because we want to leave each merc with
364 // as low int points as possible)
365 }
366
367 for ( ubLoop = 0; ubLoop < BEST_SIGHTING_ARRAY_SIZE; ubLoop++ )
368 {
369 if (gBestToMakeSighting[ubLoop] != NULL)
370 {
371 SLOGD("RBSP entry %d: %d (%d pts)",
372 ubLoop, gBestToMakeSighting[ubLoop]->ubID,
373 gBestToMakeSighting[ubLoop]->bInterruptDuelPts);
374 }
375 }
376 }
377
378
HandleBestSightingPositionInRealtime(void)379 static void HandleBestSightingPositionInRealtime(void)
380 {
381 // This function is called for handling interrupts when opening a door in non-combat or
382 // just sighting in non-combat, deciding who gets the first turn
383
384 if ( gfDelayResolvingBestSightingDueToDoor )
385 {
386 SLOGD("HBSPIR: skipping due to door flag" );
387 return;
388 }
389
390 if (gBestToMakeSighting[0] != NULL)
391 {
392 SLOGD("HBSPIR called and there is someone in the list" );
393
394 //if (gfHumanSawSomeoneInRealtime)
395 {
396 if (gBestToMakeSighting[1] == NULL)
397 {
398 // award turn
399 EnterCombatMode(gBestToMakeSighting[0]->bTeam);
400 }
401 else
402 {
403 // if 1st and 2nd on same team, or 1st and 3rd on same team, or there IS no 3rd, award turn to 1st
404 if (gBestToMakeSighting[0]->bTeam == gBestToMakeSighting[1]->bTeam ||
405 gBestToMakeSighting[2] == NULL ||
406 gBestToMakeSighting[0]->bTeam == gBestToMakeSighting[2]->bTeam)
407 {
408 EnterCombatMode(gBestToMakeSighting[0]->bTeam);
409 }
410 else // give turn to 2nd best but interrupt to 1st
411 {
412 SLOGD(
413 "Entering combat mode: turn for 2nd best, int for best" );
414
415 EnterCombatMode(gBestToMakeSighting[1]->bTeam);
416 // 2nd guy loses control
417 AddToIntList(gBestToMakeSighting[1], FALSE, TRUE);
418 // 1st guy gains control
419 AddToIntList(gBestToMakeSighting[0], TRUE, TRUE);
420 DoneAddingToIntList();
421 }
422 }
423 }
424
425 for (UINT8 ubLoop = 0; ubLoop < BEST_SIGHTING_ARRAY_SIZE; ++ubLoop)
426 {
427 if (gBestToMakeSighting[ubLoop] != NULL)
428 {
429 gBestToMakeSighting[ubLoop]->bInterruptDuelPts = NO_INTERRUPT;
430 SLOGD("RBSP: done, resetting points for %d to zilch",
431 gBestToMakeSighting[ubLoop]->ubID);
432 }
433 }
434
435 #if defined FORCE_ASSERTS_ON
436 FOR_EACH_MERC(i)
437 {
438 SOLDIERTYPE* const s = *i;
439 AssertMsg(s->bInterruptDuelPts == NO_INTERRUPT,
440 String("%s (%d) still has interrupt pts!", s->name.c_str(), s->ubID));
441 }
442 #endif
443 }
444 }
445
446
HandleBestSightingPositionInTurnbased(void)447 static void HandleBestSightingPositionInTurnbased(void)
448 {
449 // This function is called for handling interrupts when opening a door in turnbased
450 BOOLEAN fOk = FALSE;
451
452 if (gBestToMakeSighting[0] != NULL)
453 {
454 if (gBestToMakeSighting[0]->bTeam != gTacticalStatus.ubCurrentTeam)
455 {
456 // interrupt!
457 UINT8 ubLoop;
458 for (ubLoop = 0; ubLoop < gubBestToMakeSightingSize; ++ubLoop)
459 {
460 if (gBestToMakeSighting[ubLoop] == NULL)
461 {
462 // If nobody, do nothing (for now) - abort!
463 if (gInterruptProvoker == NULL) return;
464
465 // use this guy as the "interrupted" fellow
466 gBestToMakeSighting[ubLoop] = gInterruptProvoker;
467 fOk = TRUE;
468 break;
469 }
470 else if (gBestToMakeSighting[ubLoop]->bTeam == gTacticalStatus.ubCurrentTeam)
471 {
472 fOk = TRUE;
473 break;
474 }
475 }
476
477 if ( fOk )
478 {
479 // this is the guy who gets "interrupted"; all else before him interrupted him
480 AddToIntList(gBestToMakeSighting[ubLoop], FALSE, TRUE);
481 for (UINT8 ubLoop2 = 0; ubLoop2 < ubLoop; ++ubLoop2)
482 {
483 AddToIntList(gBestToMakeSighting[ubLoop2], TRUE, TRUE);
484 }
485 DoneAddingToIntList();
486 }
487
488 }
489 for (UINT8 ubLoop = 0; ubLoop < BEST_SIGHTING_ARRAY_SIZE; ++ubLoop)
490 {
491 if (gBestToMakeSighting[ubLoop] != NULL)
492 {
493 gBestToMakeSighting[ubLoop]->bInterruptDuelPts = NO_INTERRUPT;
494 SLOGD("RBSP (TB): done, resetting points for %d to zilch",
495 gBestToMakeSighting[ubLoop]->ubID);
496 }
497 }
498
499 #if defined FORCE_ASSERTS_ON
500 FOR_EACH_MERC(i)
501 {
502 SOLDIERTYPE* const s = *i;
503 AssertMsg(s->bInterruptDuelPts == NO_INTERRUPT, String("%s (%d) still has interrupt pts!", s->name.c_str(), s->ubID));
504 }
505 #endif
506 }
507 }
508
509
InitSightArrays()510 static void InitSightArrays()
511 {
512 FOR_EACH(SOLDIERTYPE*, i, gBestToMakeSighting) *i = 0;
513 }
514
515
AddToShouldBecomeHostileOrSayQuoteList(SOLDIERTYPE * const s)516 void AddToShouldBecomeHostileOrSayQuoteList(SOLDIERTYPE* const s)
517 {
518 UINT8 ubLoop;
519
520 Assert( gubNumShouldBecomeHostileOrSayQuote < SHOULD_BECOME_HOSTILE_SIZE );
521
522 if (s->bLife < OKLIFE) return;
523
524 // make sure not already in list
525 for ( ubLoop = 0; ubLoop < gubNumShouldBecomeHostileOrSayQuote; ubLoop++ )
526 {
527 if (gShouldBecomeHostileOrSayQuote[ubLoop] == s) return;
528 }
529
530 gShouldBecomeHostileOrSayQuote[gubNumShouldBecomeHostileOrSayQuote++] = s;
531 }
532
533
SelectSpeakerFromHostileOrSayQuoteList(void)534 static SOLDIERTYPE* SelectSpeakerFromHostileOrSayQuoteList(void)
535 {
536 SOLDIERTYPE* speaker_list[SHOULD_BECOME_HOSTILE_SIZE];
537 UINT8 ubLoop, ubNumProfiles = 0;
538
539 for ( ubLoop = 0; ubLoop < gubNumShouldBecomeHostileOrSayQuote; ubLoop++ )
540 {
541 SOLDIERTYPE* const pSoldier = gShouldBecomeHostileOrSayQuote[ubLoop];
542 if ( pSoldier->ubProfile != NO_PROFILE )
543 {
544
545 // make sure person can say quote!!!!
546 gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags2 |= PROFILE_MISC_FLAG2_NEEDS_TO_SAY_HOSTILE_QUOTE;
547
548 if ( NPCHasUnusedHostileRecord( pSoldier->ubProfile, APPROACH_DECLARATION_OF_HOSTILITY ) )
549 {
550 speaker_list[ubNumProfiles++] = pSoldier;
551 }
552 else
553 {
554 // turn flag off again
555 gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags2 &= ~PROFILE_MISC_FLAG2_NEEDS_TO_SAY_HOSTILE_QUOTE;
556 }
557
558 }
559 }
560
561 return ubNumProfiles == 0 ? NULL : speaker_list[Random(ubNumProfiles)];
562 }
563
564
CheckHostileOrSayQuoteList(void)565 void CheckHostileOrSayQuoteList( void )
566 {
567 if ( gubNumShouldBecomeHostileOrSayQuote == 0 || !DialogueQueueIsEmpty() ||
568 gfInTalkPanel || gfWaitingForTriggerTimer )
569 {
570 return;
571 }
572 else
573 {
574 UINT8 ubLoop;
575
576 SOLDIERTYPE* const speaker = SelectSpeakerFromHostileOrSayQuoteList();
577 if (speaker == NULL)
578 {
579 // make sure everyone on this list is hostile
580 for ( ubLoop = 0; ubLoop < gubNumShouldBecomeHostileOrSayQuote; ubLoop++ )
581 {
582 SOLDIERTYPE* const pSoldier = gShouldBecomeHostileOrSayQuote[ubLoop];
583 if ( pSoldier->bNeutral )
584 {
585 MakeCivHostile( pSoldier, 2 );
586 // make civ group, if any, hostile
587 if ( pSoldier->bTeam == CIV_TEAM && pSoldier->ubCivilianGroup != NON_CIV_GROUP && gTacticalStatus.fCivGroupHostile[ pSoldier->ubCivilianGroup ] == CIV_GROUP_WILL_BECOME_HOSTILE )
588 {
589 gTacticalStatus.fCivGroupHostile[ pSoldier->ubCivilianGroup ] = CIV_GROUP_HOSTILE;
590 }
591 }
592 }
593
594 // unpause all AI
595 UnPauseAI();
596 // reset the list
597 std::fill(std::begin(gShouldBecomeHostileOrSayQuote), std::end(gShouldBecomeHostileOrSayQuote), nullptr);
598 gubNumShouldBecomeHostileOrSayQuote = 0;
599 //and return/go into combat
600 if ( !(gTacticalStatus.uiFlags & INCOMBAT ) )
601 {
602 EnterCombatMode( CIV_TEAM );
603 }
604 }
605 else
606 {
607 // pause all AI
608 PauseAIUntilManuallyUnpaused();
609 // stop everyone?
610
611 // We want to make this guy visible to the player.
612 if (speaker->bVisible != TRUE)
613 {
614 gbPublicOpplist[OUR_TEAM][speaker->ubID] = HEARD_THIS_TURN;
615 HandleSight(*speaker, SIGHT_LOOK | SIGHT_RADIO);
616 }
617 // trigger hater
618 TriggerNPCWithIHateYouQuote(speaker->ubProfile);
619 }
620 }
621 }
622
623
624 static void ManLooksForOtherTeams(SOLDIERTYPE* pSoldier);
625 static void OurTeamRadiosRandomlyAbout(SOLDIERTYPE* about);
626 static void OtherTeamsLookForMan(SOLDIERTYPE* pOpponent);
627
628
HandleSight(SOLDIERTYPE & s,SightFlags const sight_flags)629 void HandleSight(SOLDIERTYPE& s, SightFlags const sight_flags)
630 {
631 if (!s.bActive) return;
632 if (!s.bInSector) return;
633 if (s.uiStatusFlags & SOLDIER_DEAD) return;
634
635 gubSightFlags = sight_flags;
636
637 if (gubBestToMakeSightingSize != BEST_SIGHTING_ARRAY_SIZE_ALL_TEAMS_LOOK_FOR_ALL)
638 {
639 // If this is not being called as a result of all teams look for all, reset
640 // array size
641 if (gTacticalStatus.uiFlags & INCOMBAT)
642 {
643 // NB the incombat size is 0
644 gubBestToMakeSightingSize = BEST_SIGHTING_ARRAY_SIZE_INCOMBAT;
645 }
646 else
647 {
648 gubBestToMakeSightingSize = BEST_SIGHTING_ARRAY_SIZE_NONCOMBAT;
649 }
650
651 InitSightArrays();
652 }
653
654 for (UINT32 i = 0; i != NUM_WATCHED_LOCS; ++i)
655 {
656 gfWatchedLocHasBeenIncremented[s.ubID][i] = FALSE;
657 }
658
659 gfPlayerTeamSawCreatures = FALSE;
660
661 // store new situation value
662 INT8 const temp_new_situation = s.bNewSituation;
663 s.bNewSituation = FALSE;
664
665 // If we've been told to make this soldier look (& others look back at him)
666 if (sight_flags & SIGHT_LOOK)
667 {
668 // If this soldier's under our control and well enough to look
669 if (s.bLife >= OKLIFE)
670 {
671 // He looks for all other soldiers not on his own team
672 ManLooksForOtherTeams(&s);
673 }
674
675 // All soldiers under our control but not on ptr's team look for him
676 OtherTeamsLookForMan(&s);
677 }
678
679 // If we've been told that interrupts are possible as a result of sighting
680 if ((gTacticalStatus.uiFlags & INCOMBAT) &&
681 sight_flags & SIGHT_INTERRUPT)
682 {
683 ResolveInterruptsVs(&s, SIGHTINTERRUPT);
684 }
685
686 if (gubBestToMakeSightingSize == BEST_SIGHTING_ARRAY_SIZE_NONCOMBAT)
687 {
688 HandleBestSightingPositionInRealtime();
689 }
690
691 if (s.bNewSituation && !(s.uiStatusFlags & SOLDIER_PC))
692 {
693 HaultSoldierFromSighting(&s, TRUE);
694 }
695 s.bNewSituation = __max(s.bNewSituation, temp_new_situation);
696
697 // If we've been told to radio the results
698 if (sight_flags & SIGHT_RADIO)
699 {
700 if (s.uiStatusFlags & SOLDIER_PC)
701 {
702 // update our team's public knowledge
703 RadioSightings(&s, EVERYBODY, s.bTeam);
704
705 #ifdef WE_SEE_WHAT_MILITIA_SEES_AND_VICE_VERSA
706 RadioSightings(&s, EVERYBODY, MILITIA_TEAM);
707 #endif
708
709 // If it's our local player's merc, revealing roofs and looking for items
710 // is handled here, too
711 if (IsOnOurTeam(s)) RevealRoofsAndItems(&s, TRUE);
712 }
713 else if (gGameOptions.ubDifficultyLevel >= DIF_LEVEL_MEDIUM)
714 {
715 // Unless in easy mode allow alerted enemies to radio
716 // Don't allow admins to radio
717 if (s.bTeam == ENEMY_TEAM &&
718 gTacticalStatus.Team[ENEMY_TEAM].bAwareOfOpposition &&
719 s.ubSoldierClass != SOLDIER_CLASS_ADMINISTRATOR)
720 {
721 RadioSightings(&s, EVERYBODY, s.bTeam);
722 }
723 }
724
725 s.bNewOppCnt = 0;
726
727 // If this soldier's NOT on our team (MAY be under our control, though!)
728 if (!IsOnOurTeam(s)) OurTeamRadiosRandomlyAbout(&s); // radio about him only
729
730 // All non-humans under our control would now radio, if they were allowed to
731 // radio automatically (but they're not). So just nuke new opp cnt.
732 // NEW: under LOCALOPPLIST, humans on other teams now also radio in here
733 FOR_EACH_MERC(i)
734 {
735 SOLDIERTYPE& them = **i;
736 if (them.bLife < OKLIFE) continue;
737
738 // if this merc is on the same team as the target soldier
739 if (them.bTeam == s.bTeam) continue; // he doesn't look (he ALWAYS knows about him)
740
741 // Other human team's merc report sightings to their teams now
742 if (them.uiStatusFlags & SOLDIER_PC)
743 {
744 // Temporary for opplist synching - disable random order radioing
745 // Exclude our own team, we've already done them, randomly
746 if (them.bTeam != OUR_TEAM)
747 RadioSightings(&them, &s, them.bTeam);
748 }
749 else if (gGameOptions.ubDifficultyLevel >= DIF_LEVEL_MEDIUM)
750 {
751 // Unless in easy mode allow alerted enemies to radio
752 // Don't allow admins to radio
753 if (them.bTeam == ENEMY_TEAM &&
754 gTacticalStatus.Team[ENEMY_TEAM].bAwareOfOpposition &&
755 them.ubSoldierClass != SOLDIER_CLASS_ADMINISTRATOR)
756 {
757 RadioSightings(&them, EVERYBODY, them.bTeam);
758 }
759 }
760
761 them.bNewOppCnt = 0;
762 }
763 }
764
765 // CJC August 13 2002: At the end of handling sight, reset sight flags to
766 // allow interrupts in case an audio cue should cause someone to see an enemy
767 gubSightFlags |= SIGHT_INTERRUPT;
768 }
769
770
771 // All mercs on our local team check if they should radio about him
OurTeamRadiosRandomlyAbout(SOLDIERTYPE * const about)772 static void OurTeamRadiosRandomlyAbout(SOLDIERTYPE* const about)
773 {
774 // make a list of all of our team's mercs
775 UINT radio_cnt = 0;
776 SOLDIERTYPE* radio_men[20];
777 FOR_EACH_IN_TEAM(s, OUR_TEAM)
778 {
779 // if this merc is in this sector, and well enough to look, then put him on
780 // our list
781 if (s->bInSector && s->bLife >= OKLIFE)
782 radio_men[radio_cnt++] = s;
783 }
784
785 // Now RANDOMLY handle each of the mercs on our list, until none remain (this
786 // is all being done ONLY so that the mercs in the earliest merc slots do not
787 // arbitrarily get the bulk of the sighting speech quote action, while the
788 // later ones almost never pipe up, and is NOT strictly necessary, but a nice
789 // improvement over original JA)
790 for (; radio_cnt > 0; --radio_cnt)
791 {
792 // Pick a merc from one of the remaining slots at random
793 const UINT chosen_idx = Random(radio_cnt);
794 SOLDIERTYPE* const chosen = radio_men[chosen_idx];
795
796 // Handle radioing for that merc
797 RadioSightings(chosen, about, chosen->bTeam);
798 chosen->bNewOppCnt = 0;
799
800 // Move the contents of the last slot into the one just handled
801 radio_men[chosen_idx] = radio_men[radio_cnt - 1];
802 }
803 }
804
805
TeamNoLongerSeesMan(const UINT8 ubTeam,SOLDIERTYPE * const pOpponent,const SOLDIERTYPE * const exclude,const INT8 bIteration)806 static INT16 TeamNoLongerSeesMan(const UINT8 ubTeam, SOLDIERTYPE* const pOpponent, const SOLDIERTYPE* const exclude, const INT8 bIteration)
807 {
808 // look for all mercs on the same team, check opplists for this soldier
809 CFOR_EACH_IN_TEAM(pMate, ubTeam)
810 {
811 // if this "teammate" is me, myself, or I (whom we want to exclude)
812 if (pMate == exclude)
813 continue; // skip to next teammate, I KNOW I don't see him...
814
815 // if this merc is not on the same team
816 if (pMate->bTeam != ubTeam)
817 continue; // skip him, he's no teammate at all!
818
819 // if this merc is at base, on assignment, dead, unconscious
820 if (!pMate->bInSector || pMate->bLife < OKLIFE)
821 continue; // next merc
822
823 // if this teammate currently sees this opponent
824 if (pMate->bOppList[pOpponent->ubID] == SEEN_CURRENTLY)
825 return(FALSE); // that's all I need to know, get out of here
826 }
827
828 #ifdef WE_SEE_WHAT_MILITIA_SEES_AND_VICE_VERSA
829 if ( bIteration == 0 )
830 {
831 if (ubTeam == OUR_TEAM && IsTeamActive(MILITIA_TEAM))
832 {
833 // check militia team as well
834 return TeamNoLongerSeesMan(MILITIA_TEAM, pOpponent, exclude, 1);
835 }
836 else if (ubTeam == MILITIA_TEAM && IsTeamActive(OUR_TEAM))
837 {
838 // check player team as well
839 return TeamNoLongerSeesMan(OUR_TEAM, pOpponent, exclude, 1);
840 }
841 }
842 #endif
843
844 // none of my friends is currently seeing the guy, so return success
845 return(TRUE);
846 }
847
848
DistanceSmellable(const SOLDIERTYPE * const pSubject)849 static INT16 DistanceSmellable(const SOLDIERTYPE* const pSubject)
850 {
851 INT16 sDistVisible = STRAIGHT; // as a base
852
853 sDistVisible *= 2;
854 //In hypothetical non-turnbased mode:
855 //sDistVisible += 3;
856
857 if (pSubject)
858 {
859 if (pSubject->uiStatusFlags & SOLDIER_MONSTER)
860 {
861 // trying to smell a friend; change nothing
862 }
863 else
864 {
865 // smelling a human or animal; if they are coated with monster smell, distance shrinks
866 sDistVisible = sDistVisible * (pSubject->bNormalSmell - pSubject->bMonsterSmell) /
867 NORMAL_HUMAN_SMELL_STRENGTH;
868 if (sDistVisible < 0)
869 {
870 sDistVisible = 0;
871 }
872 }
873 }
874 return( sDistVisible );
875 }
876
MaxDistanceVisible(void)877 INT16 MaxDistanceVisible( void )
878 {
879 return( STRAIGHT * 2 );
880 }
881
882
DistanceVisible(const SOLDIERTYPE * pSoldier,INT8 bFacingDir,INT8 bSubjectDir,INT16 sSubjectGridNo,INT8 bLevel)883 INT16 DistanceVisible(const SOLDIERTYPE* pSoldier, INT8 bFacingDir, INT8 bSubjectDir, INT16 sSubjectGridNo, INT8 bLevel)
884 {
885 INT16 sDistVisible;
886 INT8 bLightLevel;
887
888 // IMPORTANT! WhoIsThere2 can return a null-pointer for grid calcs
889 const SOLDIERTYPE* const pSubject = WhoIsThere2(sSubjectGridNo, bLevel);
890 // muzzleflash can only considered if there is actually a soldier on the grid_no
891 BOOLEAN const hasMuzzleFlash = pSubject && pSubject->fMuzzleFlash;
892
893 if (pSoldier->uiStatusFlags & SOLDIER_MONSTER)
894 {
895 if ( !pSubject )
896 {
897 return( FALSE );
898 }
899 return DistanceSmellable(pSubject);
900 }
901
902 if (pSoldier->bBlindedCounter > 0)
903 {
904 // we're bliiiiiiiiind!!!
905 return( 0 );
906 }
907
908 if ( bFacingDir == DIRECTION_IRRELEVANT && TANK( pSoldier ) )
909 {
910 // always calculate direction for tanks so we have something to work with
911 bFacingDir = pSoldier->bDesiredDirection;
912 bSubjectDir = (INT8) GetDirectionToGridNoFromGridNo( pSoldier->sGridNo, sSubjectGridNo );
913 //bSubjectDir = atan8(pSoldier->sX,pSoldier->sY,pOpponent->sX,pOpponent->sY);
914 }
915
916 if ( !TANK( pSoldier ) && ( bFacingDir == DIRECTION_IRRELEVANT || (pSoldier->uiStatusFlags & SOLDIER_ROBOT) || hasMuzzleFlash ) )
917 {
918 sDistVisible = MaxDistanceVisible();
919 }
920 else
921 {
922
923 if (pSoldier->sGridNo == sSubjectGridNo)
924 {
925 // looking up or down or two people accidentally in same tile... don't want it to be 0!
926 sDistVisible = MaxDistanceVisible();
927 }
928 else
929 {
930 sDistVisible = gbLookDistance[bFacingDir][bSubjectDir];
931
932 if ( sDistVisible == ANGLE && (pSoldier->bTeam == OUR_TEAM ||
933 pSoldier->bAlertStatus >= STATUS_RED ) )
934 {
935 sDistVisible = STRAIGHT;
936 }
937
938 sDistVisible *= 2;
939
940 if ( pSoldier->usAnimState == RUNNING )
941 {
942 if ( gbLookDistance[bFacingDir][bSubjectDir] != STRAIGHT )
943 {
944 // reduce sight when we're not looking in that direction...
945 // (20%?)
946 sDistVisible = (sDistVisible * 8) / 10;
947 }
948 }
949 }
950 }
951
952 if (pSoldier->bLevel != bLevel)
953 {
954 // add two tiles distance to visibility to/from roofs
955 sDistVisible += 2;
956 }
957
958 // now reduce based on light level; SHADE_MIN is the define for the
959 // highest number the light can be
960 bLightLevel = LightTrueLevel(sSubjectGridNo, bLevel);
961
962 if (hasMuzzleFlash)
963 bLightLevel = MIN(bLightLevel, NORMAL_LIGHTLEVEL_DAY);
964 sDistVisible = AdjustMaxSightRangeForEnvEffects(bLightLevel, sDistVisible);
965
966 // if we wanted to simulate desert-blindness, we'd bump up the light level
967 // under certain conditions (daytime in the desert, for instance)
968 if (bLightLevel < NORMAL_LIGHTLEVEL_DAY)
969 {
970 // greater than normal daylight level; check for sun goggles
971 if (IsWearingHeadGear(*pSoldier, SUNGOGGLES))
972 {
973 // increase sighting distance by up to 2 tiles
974 sDistVisible++;
975 if (bLightLevel < NORMAL_LIGHTLEVEL_DAY - 1)
976 {
977 sDistVisible++;
978 }
979 }
980 }
981 else if (bLightLevel > NORMAL_LIGHTLEVEL_DAY + 5)
982 {
983 if (IsWearingHeadGear(*pSoldier, NIGHTGOGGLES) || IsWearingHeadGear(*pSoldier, UVGOGGLES) ||
984 pSoldier->ubBodyType == BLOODCAT || AM_A_ROBOT(pSoldier))
985 {
986 if (IsWearingHeadGear(*pSoldier, NIGHTGOGGLES) || AM_A_ROBOT(pSoldier))
987 {
988 if (bLightLevel > NORMAL_LIGHTLEVEL_NIGHT)
989 {
990 // when it gets really dark, light-intensification goggles become less effective
991 if ( bLightLevel < NORMAL_LIGHTLEVEL_NIGHT + 3 )
992 {
993 sDistVisible += (NIGHTSIGHTGOGGLES_BONUS / 2);
994 }
995 // else no help at all!
996 }
997 else
998 {
999 sDistVisible += NIGHTSIGHTGOGGLES_BONUS;
1000 }
1001
1002 }
1003 // UV goggles only function above ground... ditto for bloodcats
1004 else if ( gbWorldSectorZ == 0 )
1005 {
1006 sDistVisible += UVGOGGLES_BONUS;
1007 }
1008
1009 }
1010
1011 // give one step better vision for people with nightops
1012 sDistVisible += 1 * NUM_SKILL_TRAITS(pSoldier, NIGHTOPS);
1013 }
1014
1015
1016 // let tanks see and be seen further (at night)
1017 if ( (TANK( pSoldier ) && sDistVisible > 0) || (pSubject && TANK( pSubject ) ) )
1018 {
1019 sDistVisible = __max( sDistVisible + 5, MaxDistanceVisible() );
1020 }
1021
1022 if ( gpWorldLevelData[ pSoldier->sGridNo ].ubExtFlags[ bLevel ] & (MAPELEMENT_EXT_TEARGAS | MAPELEMENT_EXT_MUSTARDGAS) )
1023 {
1024 if (!IsWearingHeadGear(*pSoldier, GASMASK))
1025 {
1026 // in gas without a gas mask; reduce max distance visible to 2 tiles at most
1027 sDistVisible = __min( sDistVisible, 2 );
1028 }
1029 }
1030
1031 return(sDistVisible);
1032 }
1033
1034
1035 static void HandleManNoLongerSeen(SOLDIERTYPE* pSoldier, SOLDIERTYPE* pOpponent, INT8* pPersOL, INT8* pbPublOL);
1036 static void DecideTrueVisibility(SOLDIERTYPE* pSoldier);
1037
1038
EndMuzzleFlash(SOLDIERTYPE * pSoldier)1039 void EndMuzzleFlash( SOLDIERTYPE * pSoldier )
1040 {
1041 pSoldier->fMuzzleFlash = FALSE;
1042
1043 #ifdef WE_SEE_WHAT_MILITIA_SEES_AND_VICE_VERSA
1044 if ( pSoldier->bTeam != OUR_TEAM && pSoldier->bTeam != MILITIA_TEAM )
1045 #else
1046 if ( pSoldier->bTeam != OUR_TEAM )
1047 #endif
1048 {
1049 pSoldier->bVisible = 0; // indeterminate state
1050 }
1051
1052 FOR_EACH_MERC(i)
1053 {
1054 SOLDIERTYPE* const pOtherSoldier = *i;
1055 if ( pOtherSoldier->bOppList[ pSoldier->ubID ] == SEEN_CURRENTLY )
1056 {
1057 if ( pOtherSoldier->sGridNo != NOWHERE )
1058 {
1059 if ( PythSpacesAway( pOtherSoldier->sGridNo, pSoldier->sGridNo ) > DistanceVisible( pOtherSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, pSoldier->sGridNo, pSoldier->bLevel ) )
1060 {
1061 // if this guy can no longer see us, change to seen this turn
1062 HandleManNoLongerSeen(pOtherSoldier, pSoldier,
1063 &(pOtherSoldier->bOppList[pSoldier->ubID]),
1064 &(gbPublicOpplist[pOtherSoldier->bTeam][pSoldier->ubID]));
1065 }
1066 // else this person is still seen, if the looker is on our side or the militia the person should stay visible
1067 #ifdef WE_SEE_WHAT_MILITIA_SEES_AND_VICE_VERSA
1068 else if ( pOtherSoldier->bTeam == OUR_TEAM || pOtherSoldier->bTeam == MILITIA_TEAM )
1069 #else
1070 else if ( pOtherSoldier->bTeam == OUR_TEAM )
1071 #endif
1072 {
1073 pSoldier->bVisible = TRUE; // yes, still seen
1074 }
1075 }
1076 }
1077 }
1078 DecideTrueVisibility(pSoldier);
1079 }
1080
1081
TurnOffEveryonesMuzzleFlashes(void)1082 void TurnOffEveryonesMuzzleFlashes(void)
1083 {
1084 FOR_EACH_MERC(i)
1085 {
1086 SOLDIERTYPE* const s = *i;
1087 if (s->fMuzzleFlash) EndMuzzleFlash(s);
1088 }
1089 }
1090
1091
TurnOffTeamsMuzzleFlashes(UINT8 ubTeam)1092 void TurnOffTeamsMuzzleFlashes( UINT8 ubTeam )
1093 {
1094 FOR_EACH_IN_TEAM(s, ubTeam)
1095 {
1096 if (s->fMuzzleFlash) EndMuzzleFlash(s);
1097 }
1098 }
1099
1100
DecideHearing(const SOLDIERTYPE * pSoldier)1101 static INT8 DecideHearing(const SOLDIERTYPE* pSoldier)
1102 {
1103 // calculate the hearing value for the merc...
1104
1105 INT8 bSlot;
1106 INT8 bHearing;
1107
1108 if ( TANK( pSoldier ) )
1109 {
1110 return( -5 );
1111 }
1112 else if ( pSoldier->uiStatusFlags & SOLDIER_MONSTER )
1113 {
1114 return( -10 );
1115 }
1116
1117 bHearing = 0;
1118
1119 if (pSoldier->bExpLevel > 3)
1120 {
1121 bHearing++;
1122 }
1123
1124 // sharper hearing generally
1125 bHearing += 1 * NUM_SKILL_TRAITS(pSoldier, NIGHTOPS);
1126
1127 bSlot = FindObj( pSoldier, EXTENDEDEAR );
1128 if ( bSlot == HEAD1POS || bSlot == HEAD2POS)
1129 {
1130 // at 81-100% adds +5, at 61-80% adds +4, at 41-60% adds +3, etc.
1131 bHearing += pSoldier->inv[bSlot].bStatus[0] / 20 + 1;
1132 }
1133
1134 // adjust for dark conditions
1135 switch ( ubAmbientLightLevel )
1136 {
1137 case 8:
1138 case 9:
1139 bHearing += 1;
1140 break;
1141 case 10:
1142 bHearing += 2;
1143 break;
1144 case 11:
1145 case 12:
1146 case 13:
1147 case 14:
1148 case 15:
1149 bHearing += 3;
1150 // yet another bonus for nighttime
1151 bHearing += 1 * NUM_SKILL_TRAITS(pSoldier, NIGHTOPS);
1152 break;
1153 default:
1154 break;
1155 }
1156
1157 return( bHearing );
1158 }
1159
InitOpplistForDoorOpening(void)1160 void InitOpplistForDoorOpening( void )
1161 {
1162 // this is called before generating a noise for opening a door so that
1163 // the results of hearing the noise are lumped in with the results from AllTeamsLookForAll
1164 gubBestToMakeSightingSize = BEST_SIGHTING_ARRAY_SIZE_ALL_TEAMS_LOOK_FOR_ALL;
1165 gfDelayResolvingBestSightingDueToDoor = TRUE; // will be turned off in allteamslookforall
1166 SLOGD("HBSPIR: setting door flag on" );
1167 // must init sight arrays here
1168 InitSightArrays();
1169 }
1170
1171
AllTeamsLookForAll(UINT8 ubAllowInterrupts)1172 void AllTeamsLookForAll(UINT8 ubAllowInterrupts)
1173 {
1174 if( ( gTacticalStatus.uiFlags & LOADING_SAVED_GAME ) )
1175 {
1176 return;
1177 }
1178
1179 if (ubAllowInterrupts || !(gTacticalStatus.uiFlags & INCOMBAT) )
1180 {
1181 gubBestToMakeSightingSize = BEST_SIGHTING_ARRAY_SIZE_ALL_TEAMS_LOOK_FOR_ALL;
1182 if ( gfDelayResolvingBestSightingDueToDoor )
1183 {
1184 // turn off flag now, and skip init of sight arrays
1185 SLOGD("HBSPIR: turning door flag off" );
1186 gfDelayResolvingBestSightingDueToDoor = FALSE;
1187 }
1188 else
1189 {
1190 InitSightArrays();
1191 }
1192 }
1193
1194 FOR_EACH_MERC(i)
1195 {
1196 SOLDIERTYPE& s = **i;
1197 if (s.bLife >= OKLIFE) HandleSight(s, SIGHT_LOOK); // no radio or interrupts yet
1198 }
1199
1200 // the player team now radios about all sightings
1201 FOR_EACH_IN_TEAM(i, OUR_TEAM)
1202 {
1203 HandleSight(*i, SIGHT_RADIO); // looking was done above
1204 }
1205
1206 if ( !(gTacticalStatus.uiFlags & INCOMBAT) )
1207 {
1208 // decide who should get first turn
1209 HandleBestSightingPositionInRealtime();
1210 // this could have made us switch to combat mode
1211 if ( (gTacticalStatus.uiFlags & INCOMBAT) )
1212 {
1213 gubBestToMakeSightingSize = BEST_SIGHTING_ARRAY_SIZE_INCOMBAT;
1214 }
1215 else
1216 {
1217 gubBestToMakeSightingSize = BEST_SIGHTING_ARRAY_SIZE_NONCOMBAT;
1218 }
1219 }
1220 else if ( ubAllowInterrupts )
1221 {
1222 HandleBestSightingPositionInTurnbased();
1223 // reset sighting size to 0
1224 gubBestToMakeSightingSize = BEST_SIGHTING_ARRAY_SIZE_INCOMBAT;
1225 }
1226
1227 // reset interrupt only guynum which may have been used
1228 gInterruptProvoker = NULL;
1229 }
1230
1231
1232 static INT16 ManLooksForMan(SOLDIERTYPE* pSoldier, SOLDIERTYPE* pOpponent, UINT8 ubCaller);
1233
1234
ManLooksForOtherTeams(SOLDIERTYPE * pSoldier)1235 static void ManLooksForOtherTeams(SOLDIERTYPE* pSoldier)
1236 {
1237 SLOGD("MANLOOKSFOROTHERTEAMS ID %d(%s) team %d side %d",
1238 pSoldier->ubID, pSoldier->name.c_str(), pSoldier->bTeam, pSoldier->bSide);
1239
1240 // one soldier (pSoldier) looks for every soldier on another team (pOpponent)
1241 FOR_EACH_MERC(i)
1242 {
1243 SOLDIERTYPE* const pOpponent = *i;
1244 if (pOpponent->bLife)
1245 {
1246 // and if he's on another team...
1247 if (pSoldier->bTeam != pOpponent->bTeam)
1248 {
1249
1250 // use both sides actual x,y co-ordinates (neither side's moving)
1251 // if he sees this opponent...
1252 ManLooksForMan(pSoldier,pOpponent,MANLOOKSFOROTHERTEAMS);
1253
1254 // OK, We now want to , if in non-combat, set visiblity to 0 if not visible still....
1255 // This allows us to walk away from buddy and have them disappear instantly
1256 if (!(gTacticalStatus.uiFlags & INCOMBAT))
1257 {
1258 if ( pOpponent->bVisible == 0)
1259 {
1260 pOpponent->bVisible = -1;
1261 }
1262 }
1263
1264 }
1265 }
1266 }
1267 }
1268
1269
1270 static void RemoveOneOpponent(SOLDIERTYPE* pSoldier);
1271
1272
HandleManNoLongerSeen(SOLDIERTYPE * pSoldier,SOLDIERTYPE * pOpponent,INT8 * pPersOL,INT8 * pbPublOL)1273 static void HandleManNoLongerSeen(SOLDIERTYPE* pSoldier, SOLDIERTYPE* pOpponent, INT8* pPersOL, INT8* pbPublOL)
1274 {
1275 // if neither side is neutral AND
1276 // if this soldier is an opponent (fights for different side)
1277 if (!CONSIDERED_NEUTRAL( pOpponent, pSoldier ) && !CONSIDERED_NEUTRAL( pSoldier, pOpponent ) &&
1278 (pSoldier->bSide != pOpponent->bSide))
1279 {
1280 RemoveOneOpponent(pSoldier);
1281 }
1282
1283 // change personal opplist to indicate "seen this turn"
1284 // don't use UpdatePersonal() here, because we're changing to a *lower*
1285 // opplist value (which UpdatePersonal ignores) and we're not updating
1286 // the lastKnown gridno at all, we're keeping it at its previous value
1287
1288 *pPersOL = SEEN_THIS_TURN;
1289
1290 if ( (pSoldier->ubCivilianGroup == KINGPIN_CIV_GROUP) && (pOpponent->bTeam == OUR_TEAM ) )
1291 {
1292 UINT8 const ubRoom = GetRoom(pOpponent->sGridNo);
1293 if (IN_BROTHEL(ubRoom) && IN_BROTHEL_GUARD_ROOM(ubRoom))
1294 {
1295 // unauthorized!
1296 // make guard run to block guard room
1297 CancelAIAction(pSoldier);
1298 RESETTIMECOUNTER( pSoldier->AICounter, 0 );
1299 pSoldier->bNextAction = AI_ACTION_RUN;
1300 pSoldier->usNextActionData = 13250;
1301 }
1302 }
1303
1304 // if opponent was seen publicly last time
1305 if (*pbPublOL == SEEN_CURRENTLY)
1306 {
1307 // check if I was the only one who was seeing this guy (exlude ourselves)
1308 // THIS MUST HAPPEN EVEN FOR ENEMIES, TO MAKE THEIR PUBLIC opplist DECAY!
1309 if (TeamNoLongerSeesMan(pSoldier->bTeam, pOpponent, pSoldier, 0))
1310 {
1311 SLOGD("TeamNoLongerSeesMan: ID %d(%s) to ID %d",
1312 pSoldier->ubID, pSoldier->name.c_str(), pOpponent->ubID);
1313
1314 // don't use UpdatePublic() here, because we're changing to a *lower*
1315 // opplist value (which UpdatePublic ignores) and we're not updating
1316 // the lastKnown gridno at all, we're keeping it at its previous value
1317 *pbPublOL = SEEN_THIS_TURN;
1318
1319 // ATE: Set visiblity to 0
1320 #ifdef WE_SEE_WHAT_MILITIA_SEES_AND_VICE_VERSA
1321 if ( (pSoldier->bTeam == OUR_TEAM || pSoldier->bTeam == MILITIA_TEAM) && !(pOpponent->bTeam == OUR_TEAM || pOpponent->bTeam == MILITIA_TEAM ) )
1322 #else
1323 if ( pSoldier->bTeam == OUR_TEAM && pOpponent->bTeam != OUR_TEAM )
1324 #endif
1325 {
1326 pOpponent->bVisible = 0;
1327 }
1328 }
1329 }
1330 else
1331 SLOGD("ManLooksForMan: ID %d(%s) to ID %d Personally seen, public %d",
1332 pSoldier->ubID, pSoldier->name.c_str(), pOpponent->ubID, *pbPublOL);
1333
1334 // if we had only seen the guy for an instant and now lost sight of him
1335 if (gbSeenOpponents[pSoldier->ubID][pOpponent->ubID] == -1)
1336 // we can't leave it -1, because InterruptDuel() uses the special -1
1337 // value to know if we're only JUST seen the guy and screw up otherwise
1338 // it's enough to know we have seen him before
1339 gbSeenOpponents[pSoldier->ubID][pOpponent->ubID] = TRUE;
1340
1341 }
1342
1343
1344 static void ManSeesMan(SOLDIERTYPE& s, SOLDIERTYPE& opponent, UINT8 caller2);
1345
1346
ManLooksForMan(SOLDIERTYPE * pSoldier,SOLDIERTYPE * pOpponent,UINT8 ubCaller)1347 static INT16 ManLooksForMan(SOLDIERTYPE* pSoldier, SOLDIERTYPE* pOpponent, UINT8 ubCaller)
1348 {
1349 INT8 bDir,bAware = FALSE,bSuccess = FALSE;
1350 INT16 sDistVisible,sDistAway;
1351 INT8 *pPersOL,*pbPublOL;
1352
1353
1354 /*
1355 if (ptr->guynum >= MAX_NUM_SOLDIERS)
1356 {
1357 return(success);
1358 }
1359
1360 if (oppPtr->guynum >= MAX_NUM_SOLDIERS)
1361 {
1362 return(success);
1363 }*/
1364
1365 if (pSoldier == pOpponent)
1366 {
1367 SLOGD(ST::format("ManLooksForMan - Looking for self - ID {}({})",
1368 pSoldier->ubID, pSoldier->name));
1369 return(FALSE);
1370 }
1371
1372 // if we're somehow looking while inactive, at base, dead or dying
1373 if (!pSoldier->bActive || !pSoldier->bInSector || (pSoldier->bLife < OKLIFE))
1374 {
1375 SLOGD("ManLooksForMan - WE are inactive/dead etc ID %d(%s)to ID %d",
1376 pSoldier->ubID, pSoldier->name.c_str(), pOpponent->ubID);
1377 return(FALSE);
1378 }
1379
1380
1381
1382 // if we're somehow looking for a guy who is inactive, at base, or already dead
1383 if (!pOpponent->bActive || !pOpponent->bInSector || pOpponent->bLife <= 0 ||
1384 pOpponent->sGridNo == NOWHERE )
1385 {
1386 return(FALSE);
1387 }
1388
1389
1390 // if he's looking for a guy who is on the same team
1391 if (pSoldier->bTeam == pOpponent->bTeam)
1392 {
1393 SLOGD("ManLooksForMan - SAME TEAM ID %d(%s)to ID %d",
1394 pSoldier->ubID, pSoldier->name.c_str(), pOpponent->ubID);
1395 return(FALSE);
1396 }
1397
1398 if (pSoldier->bLife < OKLIFE || pSoldier->fMercAsleep)
1399 {
1400 return( FALSE );
1401 }
1402
1403 // NEED TO CHANGE THIS
1404 /*
1405 // don't allow unconscious persons to look, but COLLAPSED, etc. is OK
1406 if (ptr->anitype[ptr->anim] == UNCONSCIOUS)
1407 return(success);*/
1408
1409 if (pSoldier->ubBodyType == LARVAE_MONSTER || (pSoldier->uiStatusFlags & SOLDIER_VEHICLE &&
1410 pSoldier->bTeam == OUR_TEAM))
1411 {
1412 // don't do sight for these
1413 return( FALSE );
1414 }
1415
1416
1417 /*
1418 if (ptrProjected)
1419 {
1420 // use looker's PROJECTED x,y co-ordinates (those of his next gridno)
1421 fromX = ptr->destx;
1422 fromY = ptr->desty;
1423 fromGridno = ExtMen[ptr->guynum].nextGridno;
1424 }
1425 else
1426 {
1427 // use looker's ACTUAL x,y co-ordinates (those of gridno he's in now)
1428 fromX = ptr->x;
1429 fromY = ptr->y;
1430 fromGridno = ptr->sGridNo;
1431 }
1432
1433
1434 if (oppPtrProjected)
1435 {
1436 // use target's PROJECTED x,y co-ordinates (those of his next gridno)
1437 toX = oppPtr->destx;
1438 toY = oppPtr->desty;
1439 toGridno = ExtMen[oppPtr->guynum].nextGridno;
1440 }
1441 else
1442 {
1443 // use target's ACTUAL x,y co-ordinates (those of gridno he's in now)
1444 toX = oppPtr->x;
1445 toY = oppPtr->y;
1446 toGridno = oppPtr->gridno;
1447 }*/
1448
1449 pPersOL = &(pSoldier->bOppList[pOpponent->ubID]);
1450 pbPublOL = &(gbPublicOpplist[pSoldier->bTeam][pOpponent->ubID]);
1451
1452
1453 // if soldier is known about (SEEN or HEARD within last few turns)
1454 if (*pPersOL || *pbPublOL)
1455 {
1456 bAware = TRUE;
1457
1458 // then we look for him full viewing distance in EVERY direction
1459 sDistVisible = DistanceVisible(pSoldier, DIRECTION_IRRELEVANT, 0, pOpponent->sGridNo,
1460 pOpponent->bLevel );
1461 }
1462 else // soldier is not currently known about
1463 {
1464 // distance we "see" then depends on the direction he is located from us
1465 bDir = atan8(pSoldier->sX,pSoldier->sY,pOpponent->sX,pOpponent->sY);
1466 // BIG NOTE: must use desdir instead of direction, since in a projected
1467 // situation, the direction may still be changing if it's one of the first
1468 // few animation steps when this guy's turn to do his stepped look comes up
1469 sDistVisible = DistanceVisible(pSoldier,pSoldier->bDesiredDirection,bDir, pOpponent->sGridNo,
1470 pOpponent->bLevel);
1471 }
1472
1473 // calculate how many spaces away soldier is (using Pythagoras' theorem)
1474 sDistAway = PythSpacesAway(pSoldier->sGridNo,pOpponent->sGridNo);
1475 SLOGD("MANLOOKSFORMAN: ID %d(%s) to ID %d: sDistAway %d sDistVisible %d",
1476 pSoldier->ubID, pSoldier->name.c_str(), pOpponent->ubID, sDistAway, sDistVisible);
1477
1478 // if we see close enough to see the soldier
1479 if (sDistAway <= sDistVisible)
1480 {
1481 // and we can trace a line of sight to his x,y coordinates
1482 // must use the REAL opplist value here since we may or may not know of him
1483 if (SoldierToSoldierLineOfSightTest(pSoldier,pOpponent,(UINT8)sDistVisible,bAware))
1484 {
1485 ManSeesMan(*pSoldier, *pOpponent, ubCaller);
1486 bSuccess = TRUE;
1487 }
1488 else
1489 {
1490 SLOGD("FAILED LINEOFSIGHT: ID %d (%s)to ID %d Personally %d, public %d",
1491 pSoldier->ubID, pSoldier->name.c_str(), pOpponent->ubID, *pPersOL, *pbPublOL);
1492 }
1493 }
1494
1495 // if soldier seen personally LAST time could not be seen THIS time
1496 if (!bSuccess && (*pPersOL == SEEN_CURRENTLY))
1497 {
1498 HandleManNoLongerSeen( pSoldier, pOpponent, pPersOL, pbPublOL );
1499 }
1500 else
1501 {
1502 if (!bSuccess)
1503 {
1504 SLOGD(
1505 "NO LONGER VISIBLE ID %d (%s)to ID %d Personally %d, public %d success: %d",
1506 pSoldier->ubID, pSoldier->name.c_str(), pOpponent->ubID, *pPersOL, *pbPublOL, bSuccess);
1507
1508
1509 // we didn't see the opponent, but since we didn't last time, we should be
1510 //if (*pbPublOL)
1511 //pOpponent->bVisible = TRUE;
1512 }
1513 else
1514 {
1515 SLOGD(
1516 "COOL. STILL VISIBLE ID %d (%s)to ID %d Personally %d, public %d success: %d",
1517 pSoldier->ubID, pSoldier->name.c_str(), pOpponent->ubID, *pPersOL, *pbPublOL, bSuccess);
1518 }
1519 }
1520 return(bSuccess);
1521 }
1522
1523
1524 static void IncrementWatchedLoc(const SOLDIERTYPE* watcher, INT16 sGridNo, INT8 bLevel);
1525 static void SetWatchedLocAsUsed(UINT8 ubID, INT16 sGridNo, INT8 bLevel);
1526 static void MakeBloodcatsHostile(void);
1527 static void AddOneOpponent(SOLDIERTYPE* pSoldier);
1528 static void UpdatePersonal(SOLDIERTYPE* pSoldier, UINT8 ubID, INT8 bNewOpplist, INT16 sGridno, INT8 bLevel);
1529
1530
ManSeesMan(SOLDIERTYPE & s,SOLDIERTYPE & opponent,UINT8 const caller2)1531 static void ManSeesMan(SOLDIERTYPE& s, SOLDIERTYPE& opponent, UINT8 const caller2)
1532 {
1533 if (s.ubID >= MAX_NUM_SOLDIERS) return;
1534 if (opponent.ubID >= MAX_NUM_SOLDIERS) return;
1535
1536 // if we're somehow looking while inactive, at base, dying or already dead
1537 if (!s.bActive) return;
1538 if (!s.bInSector) return;
1539 if (s.bLife < OKLIFE) return;
1540
1541 // if we're somehow seeing a guy who is inactive, at base, or already dead
1542 if (!opponent.bActive) return;
1543 if (!opponent.bInSector) return;
1544 if (opponent.bLife <= 0) return;
1545
1546 // If we're somehow seeing a guy who is on the same team
1547 if (s.bTeam == opponent.bTeam) return;
1548
1549 INT8 const old_opp_list = s.bOppList[opponent.ubID];
1550 GridNo const opp_gridno = opponent.sGridNo;
1551 INT8 const opp_level = opponent.bLevel;
1552
1553 bool new_opponent = false;
1554 // If we're seeing a guy we didn't see on our last chance to look for him
1555 if (s.bOppList[opponent.ubID] != SEEN_CURRENTLY)
1556 {
1557 if (opponent.bTeam == OUR_TEAM)
1558 {
1559 if (s.ubProfile != NO_PROFILE)
1560 {
1561 if (s.bTeam == CIV_TEAM)
1562 {
1563 bool not_added_to_list = true;
1564 // If this person doing the sighting is a member of a civ group that
1565 // hates us but this fact hasn't been revealed, change the side of
1566 // these people now. This will make them non-neutral so AddOneOpponent
1567 // will be called, and the guy will say his "I hate you" quote
1568 if (s.bNeutral)
1569 {
1570 if (s.ubCivilianGroup != NON_CIV_GROUP &&
1571 gTacticalStatus.fCivGroupHostile[s.ubCivilianGroup] >= CIV_GROUP_WILL_BECOME_HOSTILE)
1572 {
1573 AddToShouldBecomeHostileOrSayQuoteList(&s);
1574 not_added_to_list = false;
1575 }
1576 }
1577 else if (NPCHasUnusedRecordWithGivenApproach(s.ubProfile, APPROACH_DECLARATION_OF_HOSTILITY))
1578 {
1579 // only add if have something to say
1580 AddToShouldBecomeHostileOrSayQuoteList(&s);
1581 not_added_to_list = false;
1582 }
1583
1584 if (not_added_to_list)
1585 {
1586 switch (s.ubProfile)
1587 {
1588 case CARMEN:
1589 // Carmen goes to war (against Slay)
1590 if (opponent.ubProfile == SLAY && s.bNeutral)
1591 {
1592 s.bAttitude = ATTACKSLAYONLY;
1593 TriggerNPCRecord(s.ubProfile, 28);
1594 }
1595 break;
1596
1597 case ELDIN:
1598 if (s.bNeutral)
1599 {
1600 // If player is in behind the ropes of the museum display or
1601 // if alarm has gone off (status red)
1602 UINT8 const room = GetRoom(opponent.sGridNo);
1603 if ((!CheckFact(FACT_MUSEUM_OPEN, 0) &&
1604 22 <= room && room <= 41) ||
1605 CheckFact(FACT_MUSEUM_ALARM_WENT_OFF, 0) ||
1606 room == 39 ||
1607 room == 40 ||
1608 FindObj(&opponent, CHALICE) != NO_SLOT)
1609 {
1610 SetFactTrue(FACT_MUSEUM_ALARM_WENT_OFF);
1611 AddToShouldBecomeHostileOrSayQuoteList(&s);
1612 }
1613 }
1614 break;
1615
1616 case JIM:
1617 case JACK:
1618 case OLAF:
1619 case RAY:
1620 case OLGA:
1621 case TYRONE:
1622 // change orders, reset action!
1623 if (s.bOrders != SEEKENEMY)
1624 {
1625 s.bOrders = SEEKENEMY;
1626 if (s.bOppCnt == 0)
1627 {
1628 // Didn't see anyone before!
1629 CancelAIAction(&s);
1630 SetNewSituation(&s);
1631 }
1632 }
1633 break;
1634
1635 case ANGEL:
1636 if (opponent.ubProfile == MARIA)
1637 {
1638 if (CheckFact(FACT_MARIA_ESCORTED_AT_LEATHER_SHOP, MARIA) == TRUE)
1639 {
1640 // She was rescued! yay!
1641 TriggerNPCRecord(ANGEL, 12);
1642 }
1643 }
1644 else if (CheckFact(FACT_ANGEL_LEFT_DEED, ANGEL) == TRUE &&
1645 !CheckFact(FACT_ANGEL_MENTIONED_DEED, ANGEL))
1646 {
1647 CancelAIAction(&s);
1648 s.sAbsoluteFinalDestination = NOWHERE;
1649 EVENT_StopMerc(&s);
1650 TriggerNPCRecord(ANGEL, 20);
1651 }
1652 break;
1653
1654 case JOE:
1655 case ELLIOT:
1656 {
1657 MERCPROFILESTRUCT& p = GetProfile(s.ubProfile);
1658 if (!(p.ubMiscFlags2 & PROFILE_MISC_FLAG2_SAID_FIRSTSEEN_QUOTE) &&
1659 !AreInMeanwhile())
1660 {
1661 TriggerNPCRecord(s.ubProfile, 4);
1662 p.ubMiscFlags2 |= PROFILE_MISC_FLAG2_SAID_FIRSTSEEN_QUOTE;
1663 }
1664 break;
1665 }
1666
1667 default:
1668 break;
1669 }
1670 }
1671 }
1672 else
1673 {
1674 switch (s.ubProfile)
1675 {
1676 case IGGY:
1677 {
1678 MERCPROFILESTRUCT& iggy = GetProfile(s.ubProfile);
1679 if (!(iggy.ubMiscFlags2 & PROFILE_MISC_FLAG2_SAID_FIRSTSEEN_QUOTE))
1680 {
1681 TriggerNPCRecord(s.ubProfile, 9);
1682 iggy.ubMiscFlags2 |= PROFILE_MISC_FLAG2_SAID_FIRSTSEEN_QUOTE;
1683 gbPublicOpplist[OUR_TEAM][s.ubID] = HEARD_THIS_TURN;
1684 }
1685 break;
1686 }
1687 }
1688 }
1689 }
1690 else if (s.bTeam == CIV_TEAM)
1691 {
1692 if (s.ubCivilianGroup != NON_CIV_GROUP &&
1693 gTacticalStatus.fCivGroupHostile[s.ubCivilianGroup] >= CIV_GROUP_WILL_BECOME_HOSTILE && s.bNeutral)
1694 {
1695 AddToShouldBecomeHostileOrSayQuoteList(&s);
1696 }
1697 else if (s.ubCivilianGroup == KINGPIN_CIV_GROUP)
1698 {
1699 // Generic kingpin goon
1700
1701 // Check to see if we are looking at Maria or unauthorized personnel in the brothel
1702 if (opponent.ubProfile == MARIA)
1703 {
1704 MakeCivHostile(&s, 2);
1705 if (!(gTacticalStatus.uiFlags & INCOMBAT))
1706 {
1707 EnterCombatMode(s.bTeam);
1708 }
1709 SetFactTrue(FACT_MARIA_ESCAPE_NOTICED);
1710 }
1711 else
1712 {
1713 // JA2 Gold: only go hostile if see player IN guard room
1714 UINT8 const room = GetRoom(opponent.sGridNo);
1715 if (IN_BROTHEL_GUARD_ROOM(room))
1716 {
1717 // Unauthorized
1718 MakeCivHostile(&s, 2);
1719 if (!(gTacticalStatus.uiFlags & INCOMBAT))
1720 {
1721 EnterCombatMode(s.bTeam);
1722 }
1723 }
1724 }
1725 }
1726 else if (s.ubCivilianGroup == HICKS_CIV_GROUP &&
1727 !CheckFact(FACT_HICKS_MARRIED_PLAYER_MERC, 0))
1728 {
1729 // if before 6:05 or after 22:00, make hostile and enter combat
1730 UINT32 const uiTime = GetWorldMinutesInDay();
1731 if (uiTime < 365 || 1320 < uiTime)
1732 {
1733 // get off our farm!
1734 MakeCivHostile(&s, 2);
1735 if (!(gTacticalStatus.uiFlags & INCOMBAT))
1736 {
1737 EnterCombatMode(s.bTeam);
1738 LocateSoldier(&s, TRUE);
1739 INT16 sX;
1740 INT16 sY;
1741 GetSoldierScreenPos(&s, &sX, &sY);
1742 BeginCivQuote(&s, CIV_QUOTE_HICKS_SEE_US_AT_NIGHT, 0, sX, sY);
1743 }
1744 }
1745 }
1746 }
1747 }
1748 else if (s.bTeam == OUR_TEAM)
1749 {
1750 switch (opponent.ubProfile)
1751 {
1752 case MIKE:
1753 if (s.ubWhatKindOfMercAmI == MERC_TYPE__AIM_MERC &&
1754 !(s.usQuoteSaidExtFlags & SOLDIER_QUOTE_SAID_EXT_MIKE))
1755 {
1756 if (gfMikeShouldSayHi == FALSE) gfMikeShouldSayHi = TRUE;
1757 TacticalCharacterDialogue(&s, QUOTE_AIM_SEEN_MIKE);
1758 s.usQuoteSaidExtFlags |= SOLDIER_QUOTE_SAID_EXT_MIKE;
1759 }
1760 break;
1761
1762 case JOEY:
1763 if (!gfPlayerTeamSawJoey)
1764 {
1765 TacticalCharacterDialogue(&s, QUOTE_SPOTTED_JOEY);
1766 gfPlayerTeamSawJoey = TRUE;
1767 }
1768 break;
1769 }
1770 }
1771
1772 // As soon as a bloodcat sees someone, it becomes hostile. This is safe to
1773 // do here because we haven't made this new person someone we've seen yet
1774 // (so we are assured we won't count 'em twice for oppcnt purposes)
1775 if (s.ubBodyType == BLOODCAT)
1776 {
1777 bool do_roar = false;
1778 if (s.bNeutral)
1779 {
1780 MakeBloodcatsHostile();
1781 do_roar = true;
1782 }
1783 else if (s.bOppCnt == 0 && Random(2) == 0)
1784 {
1785 do_roar = true;
1786 }
1787 if (do_roar) PlayJA2Sample(BLOODCAT_ROAR, HIGHVOLUME, 1, MIDDLEPAN);
1788 }
1789 else if (opponent.ubBodyType == BLOODCAT && opponent.bNeutral)
1790 {
1791 MakeBloodcatsHostile();
1792 }
1793
1794 // if both of us are not neutral, AND
1795 // if this man is actually a true opponent (we're not on the same side)
1796 if (!CONSIDERED_NEUTRAL(&opponent, &s) &&
1797 !CONSIDERED_NEUTRAL(&s, &opponent) &&
1798 s.bSide != opponent.bSide)
1799 {
1800 AddOneOpponent(&s);
1801 SLOGD("ManSeesMan: ID %d(%s) to ID %d NEW TO ME",
1802 s.ubID, s.name.c_str(), opponent.ubID);
1803
1804 // if we also haven't seen him earlier this turn
1805 if (s.bOppList[opponent.ubID] != SEEN_THIS_TURN)
1806 {
1807 new_opponent = true;
1808 ++s.bNewOppCnt; // increment looker's NEW opponent count
1809
1810 IncrementWatchedLoc(&s, opponent.sGridNo, opponent.bLevel);
1811
1812 if (s.bTeam == OUR_TEAM &&
1813 opponent.bTeam == ENEMY_TEAM &&
1814 !CheckFact(FACT_FIRST_BATTLE_FOUGHT, 0))
1815 {
1816 SetFactTrue(FACT_FIRST_BATTLE_BEING_FOUGHT);
1817 }
1818 }
1819 else
1820 {
1821 SetWatchedLocAsUsed(s.ubID, opponent.sGridNo, opponent.bLevel);
1822 }
1823
1824 // we already know the soldier isn't SEEN_CURRENTLY,
1825 // now check if he is really "NEW" ie. not expected to be there
1826
1827 // if the looker hasn't seen this opponent at all earlier this turn, OR
1828 // if the opponent is not where the looker last thought him to be
1829 if (s.bOppList[opponent.ubID] != SEEN_THIS_TURN ||
1830 gsLastKnownOppLoc[s.ubID][opponent.ubID] != opp_gridno)
1831 {
1832 SetNewSituation(&s); // force the looker to re-evaluate
1833 }
1834 else
1835 {
1836 // if we in a non-combat movement decision, presumably this is not
1837 // something we were quite expecting, so make a new decision. For
1838 // other (combat) movement decisions, we took his position into account
1839 // when we made it, so don't make us think again & slow things down.
1840 switch (s.bAction)
1841 {
1842 case AI_ACTION_RANDOM_PATROL:
1843 case AI_ACTION_SEEK_OPPONENT:
1844 case AI_ACTION_SEEK_FRIEND:
1845 case AI_ACTION_POINT_PATROL:
1846 case AI_ACTION_LEAVE_WATER_GAS:
1847 case AI_ACTION_SEEK_NOISE:
1848 SetNewSituation(&s); // force the looker to re-evaluate
1849 break;
1850 }
1851 }
1852 }
1853 }
1854 else
1855 {
1856 SLOGD("ManSeesMan: ID %d(%s) to ID %d ALREADYSEENCURRENTLY",
1857 s.ubID, s.name.c_str(), opponent.ubID);
1858 }
1859 // Remember that the soldier is currently seen and his new location
1860 UpdatePersonal(&s, opponent.ubID, SEEN_CURRENTLY, opp_gridno, opp_level);
1861
1862 if (caller2 == MANLOOKSFOROTHERTEAMS ||
1863 caller2 == OTHERTEAMSLOOKFORMAN ||
1864 caller2 == CALLER_UNKNOWN) // unknown->hearing
1865 {
1866 if (gubBestToMakeSightingSize != BEST_SIGHTING_ARRAY_SIZE_INCOMBAT &&
1867 gTacticalStatus.bBoxingState == NOT_BOXING &&
1868 new_opponent)
1869 {
1870 if (gTacticalStatus.uiFlags & INCOMBAT)
1871 {
1872 // Presumably a door opening, we do require standard interrupt
1873 // conditions
1874 if (StandardInterruptConditionsMet(&s, &opponent, old_opp_list))
1875 {
1876 ReevaluateBestSightingPosition(&s, CalcInterruptDuelPts(&s, &opponent, TRUE));
1877 }
1878 }
1879 else if (s.bTeam != OUR_TEAM || opponent.bLife >= OKLIFE)
1880 {
1881 // Require the enemy not to be dying if we are the sighter; in other
1882 // words, always add for AI guys, and always add for people with life >=
1883 // OKLIFE
1884 ReevaluateBestSightingPosition(&s, CalcInterruptDuelPts(&s, &opponent, TRUE));
1885 }
1886 }
1887 }
1888
1889 // if this man has never seen this opponent before in this sector
1890 INT8& seen = gbSeenOpponents[s.ubID][opponent.ubID];
1891 if (seen == FALSE)
1892 {
1893 // Remember that he is just seeing him now for the first time (-1)
1894 seen = -1;
1895 }
1896 else
1897 {
1898 // Man is seeing an opponent AGAIN whom he has seen at least once before
1899 seen = TRUE;
1900 }
1901
1902 // if looker is on local team, and the enemy was invisible or "maybe"
1903 // visible just prior to this
1904 #ifdef WE_SEE_WHAT_MILITIA_SEES_AND_VICE_VERSA
1905 if ((IsOnOurTeam(s) || s.bTeam == MILITIA_TEAM) && opponent.bVisible <= 0)
1906 #else
1907 if (IsOnOurTeam(s) && opponent.bVisible <= 0)
1908 #endif
1909 {
1910 // If opponent was truly invisible, not just turned off temporarily (FALSE)
1911 // then locate to him and set his locator flag
1912 bool const do_locate = opponent.bVisible == -1;
1913
1914 // Make opponent visible (to us). Must do this BEFORE the locate since it
1915 // checks for visibility
1916 opponent.bVisible = TRUE;
1917
1918 // ATE: Cancel any fading going on
1919 // ATE: Added for fade in
1920 if (opponent.fBeginFade == 1 || opponent.fBeginFade == 2)
1921 {
1922 opponent.fBeginFade = FALSE;
1923
1924 MAP_ELEMENT& me = gpWorldLevelData[opponent.sGridNo];
1925 opponent.ubFadeLevel = opponent.bLevel > 0 && me.pRoofHead ? me.pRoofHead->ubShadeLevel :
1926 me.pLandHead->ubShadeLevel;
1927
1928 // Set levelnode shade level
1929 if (opponent.pLevelNode)
1930 {
1931 opponent.pLevelNode->ubShadeLevel = opponent.ubFadeLevel;
1932 }
1933 }
1934 SLOGD("ID %d (%s) MAKING %d VISIBLE",
1935 s.ubID, s.name.c_str(), opponent.ubID);
1936
1937 if (do_locate)
1938 {
1939 // Change his anim speed
1940 SetSoldierAniSpeed(&opponent);
1941
1942 if ((gTacticalStatus.uiFlags & INCOMBAT || gTacticalStatus.fVirginSector) &&
1943 !opponent.bNeutral &&
1944 s.bSide != opponent.bSide)
1945 {
1946 SlideTo(&opponent, SETLOCATOR);
1947 }
1948 }
1949 }
1950 else if (!IsOnOurTeam(s))
1951 {
1952 // ATE: Check stance, change to threatending
1953 ReevaluateEnemyStance(&s, s.usAnimState);
1954 }
1955 }
1956
1957
DecideTrueVisibility(SOLDIERTYPE * pSoldier)1958 static void DecideTrueVisibility(SOLDIERTYPE* pSoldier)
1959 {
1960 // if his visibility is still in the special "limbo" state (FALSE)
1961 if (pSoldier->bVisible == FALSE)
1962 {
1963 // then none of our team's merc turned him visible,
1964 // therefore he now becomes truly invisible
1965 pSoldier->bVisible = -1;
1966
1967 // Don;t adjust anim speed here, it's done once fade is over!
1968 }
1969 }
1970
1971
OtherTeamsLookForMan(SOLDIERTYPE * pOpponent)1972 static void OtherTeamsLookForMan(SOLDIERTYPE* pOpponent)
1973 {
1974 INT8 bOldOppList;
1975
1976 // if the guy we're looking for is NOT on our team AND is currently visible
1977 #ifdef WE_SEE_WHAT_MILITIA_SEES_AND_VICE_VERSA
1978 if ((pOpponent->bTeam != OUR_TEAM && pOpponent->bTeam != MILITIA_TEAM) &&
1979 (pOpponent->bVisible >= 0 && pOpponent->bVisible < 2) && pOpponent->bLife)
1980 #else
1981 if ((pOpponent->bTeam != OUR_TEAM) && (pOpponent->bVisible >= 0 &&
1982 pOpponent->bVisible < 2) && pOpponent->bLife)
1983 #endif
1984 {
1985 // assume he's no longer visible, until one of our mercs sees him again
1986 pOpponent->bVisible = 0;
1987 }
1988 SLOGD("OTHERTEAMSLOOKFORMAN ID %d(%s) team %d side %d",
1989 pOpponent->ubID, pOpponent->name.c_str(), pOpponent->bTeam, pOpponent->bSide);
1990
1991 // all soldiers not on oppPtr's team now look for him
1992 FOR_EACH_MERC(i)
1993 {
1994 SOLDIERTYPE* const pSoldier = *i;
1995
1996 // if this merc is active, in this sector, and well enough to look
1997 if (pSoldier->bLife >= OKLIFE && (pSoldier->ubBodyType != LARVAE_MONSTER))
1998 {
1999 // if this merc is on the same team as the target soldier
2000 if (pSoldier->bTeam == pOpponent->bTeam)
2001 {
2002 continue; // he doesn't look (he ALWAYS knows about him)
2003 }
2004
2005 bOldOppList = pSoldier->bOppList[pOpponent->ubID];
2006
2007 // this merc looks for the soldier in question
2008 // use both sides actual x,y co-ordinates (neither side's moving)
2009 if (ManLooksForMan(pSoldier,pOpponent,OTHERTEAMSLOOKFORMAN))
2010 {
2011 // if a new opponent is seen (which must be oppPtr himself)
2012 //if ((gTacticalStatus.uiFlags & INCOMBAT) && pSoldier->bNewOppCnt)
2013 // Calc interrupt points in non-combat because we might get an interrupt or be interrupted
2014 // on our first turn
2015
2016 // if doing regular in-combat sighting (not on opening doors!)
2017 if ( gubBestToMakeSightingSize == BEST_SIGHTING_ARRAY_SIZE_INCOMBAT )
2018 {
2019 if ((gTacticalStatus.uiFlags & INCOMBAT) && pSoldier->bNewOppCnt)
2020 {
2021 // as long as viewer meets minimum interrupt conditions
2022 if (gubSightFlags & SIGHT_INTERRUPT && StandardInterruptConditionsMet(pSoldier, pOpponent, bOldOppList))
2023 {
2024 // calculate the interrupt duel points
2025 pSoldier->bInterruptDuelPts = CalcInterruptDuelPts(pSoldier, pOpponent, TRUE);
2026 SLOGD(
2027 "Calculating int duel pts in OtherTeamsLookForMan, %d has %d points",
2028 pSoldier->ubID, pSoldier->bInterruptDuelPts);
2029 }
2030 else
2031 {
2032 pSoldier->bInterruptDuelPts = NO_INTERRUPT;
2033 }
2034 }
2035 }
2036 }
2037 }
2038 }
2039
2040
2041 // if he's not on our team
2042 if (pOpponent->bTeam != OUR_TEAM)
2043 {
2044 DecideTrueVisibility(pOpponent);
2045 }
2046 }
2047
2048
AddOneOpponent(SOLDIERTYPE * pSoldier)2049 static void AddOneOpponent(SOLDIERTYPE* pSoldier)
2050 {
2051 INT8 bOldOppCnt = pSoldier->bOppCnt;
2052
2053 pSoldier->bOppCnt++;
2054
2055 if (!bOldOppCnt)
2056 {
2057 // if we hadn't known about opponents being here for sure prior to this
2058 if (pSoldier->ubBodyType == LARVAE_MONSTER)
2059 {
2060 // never become aware of you!
2061 return;
2062 }
2063
2064 if (pSoldier->bAlertStatus < STATUS_RED)
2065 {
2066 CheckForChangingOrders(pSoldier);
2067 }
2068
2069 pSoldier->bAlertStatus = STATUS_BLACK; // force black AI status right away
2070
2071 if (pSoldier->uiStatusFlags & SOLDIER_MONSTER)
2072 {
2073 pSoldier->ubCaller = NOBODY;
2074 pSoldier->bCallPriority = 0;
2075 }
2076 }
2077
2078 if (pSoldier->bTeam == OUR_TEAM)
2079 {
2080 // adding an opponent for player; reset # of turns that we haven't seen an enemy
2081 gTacticalStatus.bConsNumTurnsNotSeen = 0;
2082 }
2083
2084 }
2085
2086
RemoveOneOpponent(SOLDIERTYPE * pSoldier)2087 static void RemoveOneOpponent(SOLDIERTYPE* pSoldier)
2088 {
2089 pSoldier->bOppCnt--;
2090
2091 if ( pSoldier->bOppCnt < 0 )
2092 {
2093 SLOGD("Oppcnt for %d (%s) tried to go below 0",
2094 pSoldier->ubID, pSoldier->name.c_str());
2095 pSoldier->bOppCnt = 0;
2096 }
2097
2098 // if no opponents remain in sight, drop status to RED (but NOT newSit.!)
2099 if (!pSoldier->bOppCnt)
2100 pSoldier->bAlertStatus = STATUS_RED;
2101 }
2102
2103
2104 static void UpdatePublic(UINT8 ubTeam, SOLDIERTYPE* s, INT8 bNewOpplist, INT16 sGridno, INT8 bLevel);
2105 static void ResetLastKnownLocs(SOLDIERTYPE const&);
2106
2107
RemoveManAsTarget(SOLDIERTYPE * pSoldier)2108 void RemoveManAsTarget(SOLDIERTYPE *pSoldier)
2109 {
2110
2111 SOLDIERTYPE *pOpponent;
2112 UINT8 ubTarget,ubLoop;
2113
2114
2115 ubTarget = pSoldier->ubID;
2116
2117 // clean up the public opponent lists and locations
2118 for (ubLoop = 0; ubLoop < MAXTEAMS; ubLoop++)
2119 // never causes any additional looks
2120 UpdatePublic(ubLoop, pSoldier, NOT_HEARD_OR_SEEN, NOWHERE, 0);
2121
2122 // clean up all opponent's opplists
2123 for (ubLoop = 0; ubLoop < guiNumMercSlots; ubLoop++)
2124 {
2125 pOpponent = MercSlots[ ubLoop ];
2126
2127 // if the target is active, a true opponent and currently seen by this merc
2128 if (pOpponent)
2129 {
2130 // check to see if OPPONENT considers US neutral
2131 if ((pOpponent->bOppList[ubTarget] == SEEN_CURRENTLY) && !pOpponent->bNeutral &&
2132 !CONSIDERED_NEUTRAL(pOpponent, pSoldier) && (pSoldier->bSide != pOpponent->bSide))
2133 {
2134 RemoveOneOpponent(pOpponent);
2135 }
2136 UpdatePersonal(pOpponent, ubTarget, NOT_HEARD_OR_SEEN,NOWHERE,0);
2137 gbSeenOpponents[ubLoop][ubTarget] = FALSE;
2138 }
2139 }
2140
2141 ResetLastKnownLocs(*pSoldier);
2142
2143 TacticalTeamType* const tt = &gTacticalStatus.Team[pSoldier->bTeam];
2144 if (tt->last_merc_to_radio == pSoldier) tt->last_merc_to_radio = NULL;
2145 }
2146
2147
UpdatePublic(const UINT8 ubTeam,SOLDIERTYPE * const s,const INT8 bNewOpplist,const INT16 sGridno,const INT8 bLevel)2148 static void UpdatePublic(const UINT8 ubTeam, SOLDIERTYPE* const s, const INT8 bNewOpplist, const INT16 sGridno, const INT8 bLevel)
2149 {
2150 UINT8 ubTeamMustLookAgain = FALSE;
2151
2152 INT8* const pbPublOL = &gbPublicOpplist[ubTeam][s->ubID];
2153
2154 // if new opplist is more up-to-date, or we are just wiping it for some reason
2155 if ((gubKnowledgeValue[*pbPublOL - OLDEST_HEARD_VALUE][bNewOpplist - OLDEST_HEARD_VALUE] > 0) ||
2156 (bNewOpplist == NOT_HEARD_OR_SEEN))
2157 {
2158 // if this team is becoming aware of a soldier it wasn't previously aware of
2159 if ((bNewOpplist != NOT_HEARD_OR_SEEN) && (*pbPublOL == NOT_HEARD_OR_SEEN))
2160 ubTeamMustLookAgain = TRUE;
2161
2162 // change the public opplist *BEFORE* anyone looks again or we'll recurse!
2163 *pbPublOL = bNewOpplist;
2164 }
2165
2166
2167 // always update the gridno, no matter what
2168 gsPublicLastKnownOppLoc[ubTeam][s->ubID] = sGridno;
2169 gbPublicLastKnownOppLevel[ubTeam][s->ubID] = bLevel;
2170
2171 // if team has been told about a guy the team was completely unaware of
2172 if (ubTeamMustLookAgain)
2173 {
2174 // then everyone on team who's not aware of guynum must look for him
2175 FOR_EACH_IN_TEAM(pSoldier, ubTeam)
2176 {
2177 // if this soldier is active, in this sector, and well enough to look
2178 if (pSoldier->bInSector && pSoldier->bLife >= OKLIFE && !(pSoldier->uiStatusFlags & SOLDIER_GASSED))
2179 {
2180 // if soldier isn't aware of guynum, give him another chance to see
2181 if (pSoldier->bOppList[s->ubID] == NOT_HEARD_OR_SEEN)
2182 {
2183 if (ManLooksForMan(pSoldier, s, UPDATEPUBLIC))
2184 {
2185 // then he actually saw guynum because of our new public knowledge
2186 // whether successful or not, whack newOppCnt. Since this is a
2187 // delayed reaction to a radio call, there's no chance of interrupt!
2188 pSoldier->bNewOppCnt = 0;
2189 }
2190 }
2191 }
2192 }
2193 }
2194 }
2195
2196
UpdatePersonal(SOLDIERTYPE * pSoldier,UINT8 ubID,INT8 bNewOpplist,INT16 sGridno,INT8 bLevel)2197 static void UpdatePersonal(SOLDIERTYPE* pSoldier, UINT8 ubID, INT8 bNewOpplist, INT16 sGridno, INT8 bLevel)
2198 {
2199 // if new opplist is more up-to-date, or we are just wiping it for some reason
2200 if ((gubKnowledgeValue[pSoldier->bOppList[ubID] - OLDEST_HEARD_VALUE][bNewOpplist - OLDEST_HEARD_VALUE] > 0) ||
2201 (bNewOpplist == NOT_HEARD_OR_SEEN))
2202 {
2203 pSoldier->bOppList[ubID] = bNewOpplist;
2204 }
2205
2206 // always update the gridno, no matter what
2207 gsLastKnownOppLoc[pSoldier->ubID][ubID] = sGridno;
2208 gbLastKnownOppLevel[pSoldier->ubID][ubID] = bLevel;
2209 }
2210
2211
ResetLastKnownLocs(SOLDIERTYPE const & s)2212 static void ResetLastKnownLocs(SOLDIERTYPE const& s)
2213 {
2214 FOR_EACH_MERC(i)
2215 {
2216 const SoldierID tgt_id = (*i)->ubID;
2217 gsLastKnownOppLoc[s.ubID][tgt_id] = NOWHERE;
2218 // IAN added this June 14/97
2219 gsPublicLastKnownOppLoc[s.bTeam][tgt_id] = NOWHERE;
2220 }
2221 }
2222
2223
2224 /*
2225 // INITIALIZATION STUFF
2226 -------------------------
2227 // Upon loading a scenario, call these:
2228 InitOpponentKnowledgeSystem();
2229
2230 // loop through all soldiers and for each soldier call
2231 InitSoldierOpplist(pSoldier);
2232
2233 // call this once
2234 AllTeamsLookForAll(NO_INTERRUPTS); // no interrupts permitted this early
2235
2236
2237 // for each additional soldier created, call
2238 InitSoldierOpplist(pSoldier);
2239 HandleSight(pSoldier,SIGHT_LOOK);
2240
2241
2242
2243 MOVEMENT STUFF
2244 -----------------
2245 // whenever new tile is reached, call
2246 HandleSight(pSoldier,SIGHT_LOOK);*/
2247
2248
InitOpponentKnowledgeSystem(void)2249 void InitOpponentKnowledgeSystem(void)
2250 {
2251 INT32 iTeam, cnt, cnt2;
2252
2253 for (auto& i : gbSeenOpponents)
2254 {
2255 std::fill(std::begin(i), std::end(i), 0);
2256 }
2257 for (auto& i : gbPublicOpplist)
2258 {
2259 std::fill(std::begin(i), std::end(i), NOT_HEARD_OR_SEEN);
2260 }
2261
2262 for (iTeam=0; iTeam < MAXTEAMS; iTeam++)
2263 {
2264 gubPublicNoiseVolume[iTeam] = 0;
2265 gsPublicNoiseGridno[iTeam] = NOWHERE;
2266 gbPublicNoiseLevel[iTeam] = 0;
2267 for (cnt = 0; cnt < MAX_NUM_SOLDIERS; cnt++)
2268 {
2269 gsPublicLastKnownOppLoc[ iTeam ][ cnt ] = NOWHERE;
2270 }
2271 }
2272
2273 // initialize public last known locations for all teams
2274 for (cnt = 0; cnt < MAX_NUM_SOLDIERS; cnt++)
2275 {
2276 for (cnt2 = 0; cnt2 < NUM_WATCHED_LOCS; cnt2++ )
2277 {
2278 gsWatchedLoc[ cnt ][ cnt2 ] = NOWHERE;
2279 gubWatchedLocPoints[ cnt ][ cnt2 ] = 0;
2280 gfWatchedLocReset[ cnt ][ cnt2 ] = FALSE;
2281 }
2282 }
2283
2284 for ( cnt = 0; cnt < SHOULD_BECOME_HOSTILE_SIZE; cnt++ )
2285 {
2286 gShouldBecomeHostileOrSayQuote[cnt] = NULL;
2287 }
2288
2289 gubNumShouldBecomeHostileOrSayQuote = 0;
2290 }
2291
2292
2293
InitSoldierOppList(SOLDIERTYPE & s)2294 void InitSoldierOppList(SOLDIERTYPE& s)
2295 {
2296 std::fill(std::begin(s.bOppList), std::end(s.bOppList), NOT_HEARD_OR_SEEN);
2297 s.bOppCnt = 0;
2298 ResetLastKnownLocs(s);
2299 std::fill_n(gbSeenOpponents[s.ubID], MAXMERCS, 0);
2300 }
2301
2302
BetweenTurnsVisibilityAdjustments()2303 void BetweenTurnsVisibilityAdjustments()
2304 {
2305 // Make all soldiers on other teams, that are no longer seen, not visible
2306 FOR_EACH_SOLDIER(i)
2307 {
2308 SOLDIERTYPE& s = *i;
2309 if (!s.bInSector) continue;
2310 if (s.bLife == 0) continue;
2311 if (IsOnOurTeam(s)) continue;
2312 #ifdef WE_SEE_WHAT_MILITIA_SEES_AND_VICE_VERSA
2313 if (s.bTeam == MILITIA_TEAM) continue;
2314 #endif
2315 // Check if anyone on our team currently sees him (exclude NOBODY)
2316 if (!TeamNoLongerSeesMan(OUR_TEAM, &s, 0, 0)) continue;
2317 // then our team has lost sight of him
2318 s.bVisible = -1; // Make him fully invisible
2319 }
2320 }
2321
2322
SaySeenQuote(SOLDIERTYPE * pSoldier,BOOLEAN fSeenCreature,BOOLEAN fVirginSector)2323 static void SaySeenQuote(SOLDIERTYPE* pSoldier, BOOLEAN fSeenCreature, BOOLEAN fVirginSector)
2324 {
2325 UINT8 ubNumEnemies = 0;
2326 UINT8 ubNumAllies = 0;
2327
2328 if ( AreInMeanwhile( ) )
2329 {
2330 return;
2331 }
2332
2333 // Check out for our under large fire quote
2334 if ( !(pSoldier->usQuoteSaidFlags & SOLDIER_QUOTE_SAID_IN_SHIT ) )
2335 {
2336 // Get total enemies.
2337 // Loop through all mercs in sector and count # of enemies
2338 FOR_EACH_MERC(i)
2339 {
2340 const SOLDIERTYPE* const t = *i;
2341 if (OK_ENEMY_MERC(t)) ++ubNumEnemies;
2342 }
2343
2344 // OK, after this, check our guys
2345 FOR_EACH_MERC(i)
2346 {
2347 const SOLDIERTYPE* const t = *i;
2348 if (!OK_ENEMY_MERC(t) && t->bOppCnt >= ubNumEnemies / 2) ++ubNumAllies;
2349 }
2350
2351 // now check!
2352 if ( ( pSoldier->bOppCnt - ubNumAllies ) > 2 )
2353 {
2354 // Say quote!
2355 TacticalCharacterDialogue( pSoldier, QUOTE_IN_TROUBLE_SLASH_IN_BATTLE );
2356
2357 pSoldier->usQuoteSaidFlags |= SOLDIER_QUOTE_SAID_IN_SHIT;
2358
2359 return;
2360 }
2361
2362 }
2363
2364
2365 if ( fSeenCreature == 1 )
2366 {
2367
2368 // Is this our first time seeing them?
2369 if ( gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags & PROFILE_MISC_FLAG_HAVESEENCREATURE )
2370 {
2371 // Are there multiplaes and we have not said this quote during this battle?
2372 if ( !( pSoldier->usQuoteSaidFlags & SOLDIER_QUOTE_SAID_MULTIPLE_CREATURES ) )
2373 {
2374 // Check for multiples!
2375 ubNumEnemies = 0;
2376
2377 // Get total enemies.
2378 // Loop through all mercs in sector and count # of enemies
2379 FOR_EACH_MERC(i)
2380 {
2381 const SOLDIERTYPE* const t = *i;
2382 if (OK_ENEMY_MERC(t) &&
2383 t->uiStatusFlags & SOLDIER_MONSTER &&
2384 pSoldier->bOppList[t->ubID] == SEEN_CURRENTLY)
2385 {
2386 ++ubNumEnemies;
2387 }
2388 }
2389
2390 if ( ubNumEnemies > 2 )
2391 {
2392 // Yes, set flag
2393 pSoldier->usQuoteSaidFlags |= SOLDIER_QUOTE_SAID_MULTIPLE_CREATURES;
2394
2395 // Say quote
2396 TacticalCharacterDialogue( pSoldier, QUOTE_ATTACKED_BY_MULTIPLE_CREATURES );
2397 }
2398 else
2399 {
2400 TacticalCharacterDialogue( pSoldier, QUOTE_SEE_CREATURE );
2401 }
2402 }
2403 else
2404 {
2405 TacticalCharacterDialogue( pSoldier, QUOTE_SEE_CREATURE );
2406 }
2407 }
2408 else
2409 {
2410 // Yes, set flag
2411 gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags |= PROFILE_MISC_FLAG_HAVESEENCREATURE;
2412
2413 TacticalCharacterDialogue( pSoldier, QUOTE_FIRSTTIME_GAME_SEE_CREATURE );
2414 }
2415 }
2416 // 2 is for bloodcat...
2417 else if ( fSeenCreature == 2 )
2418 {
2419 TacticalCharacterDialogue( pSoldier, QUOTE_SPOTTED_BLOODCAT );
2420 }
2421 else
2422 {
2423 if ( fVirginSector )
2424 {
2425 // First time we've seen a guy this sector
2426 TacticalCharacterDialogue( pSoldier, QUOTE_SEE_ENEMY_VARIATION );
2427 }
2428 else
2429 {
2430 if(isEnglishVersion())
2431 {
2432 if ( Random( 100 ) < 30 )
2433 {
2434 DoMercBattleSound( pSoldier, BATTLE_SOUND_ENEMY );
2435 }
2436 else
2437 {
2438 TacticalCharacterDialogue( pSoldier, QUOTE_SEE_ENEMY );
2439 }
2440 }
2441 else
2442 {
2443 TacticalCharacterDialogue( pSoldier, QUOTE_SEE_ENEMY );
2444 }
2445 }
2446 }
2447 }
2448
2449
OurTeamSeesSomeone(SOLDIERTYPE * pSoldier,INT8 bNumReRevealed,INT8 bNumNewEnemies)2450 static void OurTeamSeesSomeone(SOLDIERTYPE* pSoldier, INT8 bNumReRevealed, INT8 bNumNewEnemies)
2451 {
2452 if ( gTacticalStatus.fVirginSector )
2453 {
2454 // If we are in NPC dialogue now... stop!
2455 DeleteTalkingMenu( );
2456
2457 // Say quote!
2458 SaySeenQuote( pSoldier, gfPlayerTeamSawCreatures, TRUE);
2459
2460 HaultSoldierFromSighting( pSoldier, TRUE );
2461
2462 // Set virgin sector to false....
2463 gTacticalStatus.fVirginSector = FALSE;
2464 }
2465 else
2466 {
2467 if ( pSoldier->bTeam == OUR_TEAM )
2468 {
2469 // STOP IF WE WERE MOVING....
2470 /// Speek up!
2471 if ( bNumReRevealed > 0 && bNumNewEnemies == 0 )
2472 {
2473 DoMercBattleSound( pSoldier, BATTLE_SOUND_CURSE1 );
2474 }
2475 else
2476 {
2477 SaySeenQuote( pSoldier, gfPlayerTeamSawCreatures, FALSE);
2478 }
2479
2480 HaultSoldierFromSighting( pSoldier, TRUE );
2481
2482 if ( gTacticalStatus.fEnemySightingOnTheirTurn )
2483 {
2484 // Locate to our guy, then slide to enemy
2485 LocateSoldier(pSoldier, SETLOCATOR);
2486
2487 // Now slide to other guy....
2488 SlideTo(gTacticalStatus.enemy_sighting_on_their_turn_enemy, SETLOCATOR);
2489 }
2490
2491 // Unset User's turn UI
2492 UnSetUIBusy(pSoldier);
2493 }
2494 }
2495
2496 // OK, check what music mode we are in, change to battle if we're in battle
2497 // If we are in combat....
2498 if ( ( gTacticalStatus.uiFlags & INCOMBAT ) )
2499 {
2500 // If we are NOT in any music mode...
2501 if ( gubMusicMode == MUSIC_NONE )
2502 {
2503 SetMusicMode( MUSIC_TACTICAL_BATTLE );
2504 }
2505 }
2506 }
2507
2508
RadioSightings(SOLDIERTYPE * const pSoldier,SOLDIERTYPE * const about,UINT8 ubTeamToRadioTo)2509 void RadioSightings(SOLDIERTYPE* const pSoldier, SOLDIERTYPE* const about, UINT8 ubTeamToRadioTo)
2510 {
2511 INT32 iLoop;
2512 UINT8 start,end,revealedEnemies = 0,unknownEnemies = 0;
2513 //UINT8 oppIsCivilian;
2514 INT8 *pPersOL,*pbPublOL; //,dayQuote;
2515 BOOLEAN fContactSeen;
2516 BOOLEAN fSawCreatureForFirstTime = FALSE;
2517
2518 SLOGD("RADIO SIGHTINGS: for %d about %d",
2519 pSoldier->ubID, SOLDIER2ID(about));
2520
2521 gTacticalStatus.Team[pSoldier->bTeam].last_merc_to_radio = pSoldier;
2522
2523 // who are we radioing about?
2524 if (about == EVERYBODY)
2525 {
2526 start = 0;
2527 end = MAXMERCS;
2528 }
2529 else
2530 {
2531 start = about->ubID;
2532 end = start + 1;
2533 }
2534
2535 // hang a pointer to the start of our this guy's personal opplist
2536 pPersOL = &(pSoldier->bOppList[start]);
2537
2538 // hang a pointer to the start of this guy's opponents in the public opplist
2539 pbPublOL = &(gbPublicOpplist[ubTeamToRadioTo][start]);
2540
2541 SOLDIERTYPE* pOpponent = &GetMan(start);
2542
2543 // loop through every one of this guy's opponents
2544 for (iLoop = start; iLoop < end; iLoop++,pOpponent++,pPersOL++,pbPublOL++)
2545 {
2546 fContactSeen = FALSE;
2547 SLOGD("RS: checking %d", pOpponent->ubID);
2548
2549
2550 // make sure this merc is active, here & still alive (unconscious OK)
2551 if (!pOpponent->bActive || !pOpponent->bInSector || !pOpponent->bLife)
2552 {
2553 SLOGD("RS: inactive/notInSector/life %d",
2554 pOpponent->ubID);
2555 continue; // skip to the next merc
2556 }
2557
2558 // if these two mercs are on the same SIDE, then they're NOT opponents
2559 // NEW: Apr. 21 '96: must allow ALL non-humans to get radioed about
2560 if ((pSoldier->bSide == pOpponent->bSide) && (pOpponent->uiStatusFlags & SOLDIER_PC))
2561 {
2562 SLOGD("RS: same side %d", pSoldier->bSide);
2563 continue; // skip to the next merc
2564 }
2565
2566 // if we personally don't know a thing about this opponent
2567 if (*pPersOL == NOT_HEARD_OR_SEEN)
2568 {
2569 SLOGD("RS: not heard or seen");
2570 continue; // skip to the next opponent
2571 }
2572
2573 // if personal knowledge is NOT more up to date and NOT the same as public
2574 if ((!gubKnowledgeValue[*pbPublOL - OLDEST_HEARD_VALUE][*pPersOL - OLDEST_HEARD_VALUE]) &&
2575 (*pbPublOL != *pPersOL))
2576 {
2577 SLOGD("RS: no new knowledge per %d pub %d",
2578 *pPersOL, *pbPublOL);
2579 continue; // skip to the next opponent
2580 }
2581 SLOGD("RS: made it!");
2582
2583 // if it's our merc, and he currently sees this opponent
2584 if (IsOnOurTeam(*pSoldier) &&
2585 *pPersOL == SEEN_CURRENTLY &&
2586 pOpponent->bSide != pSoldier->bSide &&
2587 !pOpponent->bNeutral)
2588 {
2589 // don't care whether and how many new enemies are seen if everyone visible
2590 // and he's healthy enough to be a threat (so is worth talking about)
2591
2592 // do the following if we're radioing to our own team; if radioing to militia
2593 // then alert them instead
2594 if ( ubTeamToRadioTo != MILITIA_TEAM )
2595 {
2596 if (!gbShowEnemies && (pOpponent->bLife >= OKLIFE))
2597 {
2598 // if this enemy has not been publicly seen or heard recently
2599 if (*pbPublOL == NOT_HEARD_OR_SEEN)
2600 {
2601 // chalk up another "unknown" enemy
2602 unknownEnemies++;
2603
2604 fContactSeen = TRUE;
2605
2606 // now the important part: does this enemy see him/her back?
2607 if (pOpponent->bOppList[pSoldier->ubID] != SEEN_CURRENTLY)
2608 {
2609 // EXPERIENCE GAIN (10): Discovered a new enemy without being seen
2610 StatChange(*pSoldier, EXPERAMT, 10, FROM_SUCCESS);
2611 }
2612 }
2613 else
2614 {
2615
2616 // if he has publicly not been seen now, or anytime during this turn
2617 if ((*pbPublOL != SEEN_CURRENTLY) && (*pbPublOL != SEEN_THIS_TURN))
2618 {
2619 // chalk up another "revealed" enemy
2620 revealedEnemies++;
2621 fContactSeen = TRUE;
2622 }
2623 }
2624
2625 if ( fContactSeen )
2626 {
2627 if ( pSoldier->bTeam == OUR_TEAM )
2628 {
2629 if ( gTacticalStatus.ubCurrentTeam != OUR_TEAM )
2630 {
2631 // Save some stuff!
2632 if (gTacticalStatus.fEnemySightingOnTheirTurn)
2633 {
2634 // this has already come up so turn OFF the
2635 // pause-all-anims flag for the previous
2636 // person and set it for this next person
2637 gTacticalStatus.enemy_sighting_on_their_turn_enemy->fPauseAllAnimation = FALSE;
2638 }
2639 else
2640 {
2641 gTacticalStatus.fEnemySightingOnTheirTurn = TRUE;
2642 }
2643 gTacticalStatus.enemy_sighting_on_their_turn_enemy = pOpponent;
2644 gTacticalStatus.uiTimeSinceDemoOn = GetJA2Clock( );
2645
2646 pOpponent->fPauseAllAnimation = TRUE;
2647
2648 }
2649 }
2650
2651 if ( pOpponent->uiStatusFlags & SOLDIER_MONSTER )
2652 {
2653 gfPlayerTeamSawCreatures = TRUE;
2654 }
2655
2656 // ATE: Added for bloodcat...
2657 if ( pOpponent->ubBodyType == BLOODCAT )
2658 {
2659 // 2 is for bloodcat
2660 gfPlayerTeamSawCreatures = 2;
2661 }
2662
2663 }
2664
2665 if ( pOpponent->uiStatusFlags & SOLDIER_MONSTER )
2666 {
2667 if ( !(gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags & PROFILE_MISC_FLAG_HAVESEENCREATURE) )
2668 {
2669 fSawCreatureForFirstTime = TRUE;
2670 }
2671 }
2672
2673 }
2674 }
2675 else
2676 {
2677 // radioing to militia that we saw someone! alert them!
2678 if (IsTeamActive(MILITIA_TEAM) &&
2679 !gTacticalStatus.Team[MILITIA_TEAM].bAwareOfOpposition)
2680 {
2681 HandleInitialRedAlert(MILITIA_TEAM);
2682 }
2683 }
2684 } // end of our team's merc sees new opponent
2685
2686 // IF WE'RE HERE, OUR PERSONAL INFORMATION IS AT LEAST AS UP-TO-DATE
2687 // AS THE PUBLIC KNOWLEDGE, SO WE WILL REPLACE THE PUBLIC KNOWLEDGE
2688 SLOGD("UPDATE PUBLIC: soldier %d SEEING soldier %d",
2689 pSoldier->ubID, pOpponent->ubID);
2690 UpdatePublic(ubTeamToRadioTo, pOpponent, *pPersOL,
2691 gsLastKnownOppLoc[pSoldier->ubID][pOpponent->ubID],
2692 gbLastKnownOppLevel[pSoldier->ubID][pOpponent->ubID]);
2693 }
2694
2695
2696 // if soldier heard a misc noise more important that his team's public one
2697 if (pSoldier->ubNoiseVolume > gubPublicNoiseVolume[ubTeamToRadioTo])
2698 {
2699 // replace the soldier's team's public noise with his
2700 gsPublicNoiseGridno[ubTeamToRadioTo] = pSoldier->sNoiseGridno;
2701 gbPublicNoiseLevel[ubTeamToRadioTo] = pSoldier->bNoiseLevel;
2702 gubPublicNoiseVolume[ubTeamToRadioTo] = pSoldier->ubNoiseVolume;
2703 }
2704
2705
2706 // if this soldier is on the local team
2707 if (IsOnOurTeam(*pSoldier))
2708 {
2709 // don't trigger sighting quotes or stop merc's movement if everyone visible
2710 //if (!(gTacticalStatus.uiFlags & SHOW_ALL_MERCS))
2711 {
2712 // if we've revealed any enemies, or seen any previously unknown enemies
2713 if (revealedEnemies || unknownEnemies)
2714 {
2715 // First check for a virgin map and set to false if we see our first guy....
2716 // Only if this guy is an ememy!
2717
2718 OurTeamSeesSomeone( pSoldier, revealedEnemies, unknownEnemies );
2719 }
2720 else if (fSawCreatureForFirstTime)
2721 {
2722 gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags |= PROFILE_MISC_FLAG_HAVESEENCREATURE;
2723 TacticalCharacterDialogue( pSoldier, QUOTE_FIRSTTIME_GAME_SEE_CREATURE );
2724 }
2725
2726 }
2727 }
2728 }
2729
2730
2731
DebugSoldierPage1()2732 void DebugSoldierPage1()
2733 {
2734 INT32 const h = DEBUG_PAGE_LINE_HEIGHT;
2735
2736 const SOLDIERTYPE* const s = FindSoldierFromMouse();
2737 if (s != NULL)
2738 {
2739 MPageHeader(ST::format("DEBUG SOLDIER PAGE ONE, GRIDNO {}", s->sGridNo));
2740
2741 INT32 y = DEBUG_PAGE_START_Y;
2742
2743 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "ID:", s->ubID);
2744 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "TEAM:", s->bTeam);
2745 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "SIDE:", s->bSide);
2746
2747 MHeader(DEBUG_PAGE_FIRST_COLUMN, y += h, "STATUS FLAGS:");
2748 MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("{x}", s->uiStatusFlags));
2749
2750 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "HUMAN:", gTacticalStatus.Team[s->bTeam].bHuman);
2751 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "APs:", s->bActionPoints);
2752 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Breath:", s->bBreath);
2753 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Life:", s->bLife);
2754 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "LifeMax:", s->bLifeMax);
2755 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Bleeding:", s->bBleeding);
2756
2757 y = DEBUG_PAGE_START_Y;
2758
2759 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "Agility:", s->bAgility, EffectiveAgility(s));
2760 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "Dexterity:", s->bDexterity, EffectiveDexterity(s));
2761 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "Strength:", s->bStrength);
2762 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "Wisdom:", s->bWisdom, EffectiveWisdom(s));
2763 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "Exp Lvl:", s->bExpLevel, EffectiveExpLevel(s));
2764 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "Mrksmnship", s->bMarksmanship, EffectiveMarksmanship(s));
2765 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "Mechanical:", s->bMechanical);
2766 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "Explosive:", s->bExplosive);
2767 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "Medical:", s->bMedical);
2768 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "Drug Effects:", s->bDrugEffect[0]);
2769 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "Drug Side Effects:", s->bDrugSideEffect[0]);
2770 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "Booze Effects:", s->bDrugEffect[1]);
2771 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "Hangover Side Effects:", s->bDrugSideEffect[1]);
2772 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "AI has Keys:", s->bHasKeys);
2773 }
2774 else if (GetMouseMapPos() != NOWHERE)
2775 {
2776 MPageHeader("DEBUG LAND PAGE ONE");
2777 }
2778 }
2779
DebugSoldierPage2()2780 void DebugSoldierPage2()
2781 {
2782 static const char* const gzDirectionStr[] =
2783 {
2784 "NORTHEAST",
2785 "EAST",
2786 "SOUTHEAST",
2787 "SOUTH",
2788 "SOUTHWEST",
2789 "WEST",
2790 "NORTHWEST",
2791 "NORTH"
2792 };
2793
2794 INT32 const h = DEBUG_PAGE_LINE_HEIGHT;
2795
2796 const SOLDIERTYPE* const s = FindSoldierFromMouse();
2797 if (s != NULL)
2798 {
2799 MPageHeader(ST::format("DEBUG SOLDIER PAGE TWO, GRIDNO {}", s->sGridNo));
2800
2801 INT32 y = DEBUG_PAGE_START_Y;
2802
2803 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "ID:", s->ubID);
2804 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Body Type:", s->ubBodyType);
2805 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Opp Cnt:", s->bOppCnt);
2806
2807 ST::string opp_header;
2808 INT8 const* opp_list = s->bOppList;
2809 if (s->bTeam == OUR_TEAM || s->bTeam == MILITIA_TEAM) // look at 8 to 15 opplist entries
2810 {
2811 opp_header = "Opplist B:";
2812 opp_list += 20;
2813 }
2814 else // team 1 - enemies so look at first 8 (0-7) opplist entries
2815 {
2816 opp_header = "OppList A:";
2817 }
2818 MHeader(DEBUG_PAGE_FIRST_COLUMN, y += h, opp_header);
2819 MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("{} {} {} {} {} {} {} {}",
2820 opp_list[0], opp_list[1], opp_list[2], opp_list[3],
2821 opp_list[4], opp_list[5], opp_list[6], opp_list[7])
2822 );
2823
2824 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Visible:", s->bVisible);
2825 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Direction:", gzDirectionStr[s->bDirection]);
2826 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "DesDirection", gzDirectionStr[s->bDesiredDirection]);
2827 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "GridNo:", s->sGridNo);
2828 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Dest:", s->sFinalDestination);
2829 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Path Size:", s->ubPathDataSize);
2830 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Path Index:", s->ubPathIndex);
2831
2832 MHeader(DEBUG_PAGE_FIRST_COLUMN, y += h, "First 3 Steps:");
2833 MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("{} {} {}",
2834 s->ubPathingData[0],
2835 s->ubPathingData[1],
2836 s->ubPathingData[2])
2837 );
2838
2839 MHeader(DEBUG_PAGE_FIRST_COLUMN, y += h, "Next 3 Steps:");
2840 MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("{} {} {}",
2841 s->ubPathingData[s->ubPathIndex],
2842 s->ubPathingData[s->ubPathIndex + 1],
2843 s->ubPathingData[s->ubPathIndex + 2])
2844 );
2845
2846 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "FlashInd:", s->fFlashLocator);
2847 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "ShowInd:", s->fShowLocator);
2848 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Main hand:", ShortItemNames[s->inv[HANDPOS].usItem]);
2849 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Second hand:", ShortItemNames[s->inv[SECONDHANDPOS].usItem]);
2850
2851 const GridNo map_pos = GetMouseMapPos();
2852 if (map_pos != NOWHERE)
2853 {
2854 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "CurrGridNo:", map_pos);
2855 }
2856 }
2857 else
2858 {
2859 const GridNo map_pos = GetMouseMapPos();
2860 if (map_pos == NOWHERE) return;
2861
2862 MPageHeader("DEBUG LAND PAGE TWO");
2863
2864 INT32 y = DEBUG_PAGE_START_Y;
2865 MAP_ELEMENT const* const me = &gpWorldLevelData[map_pos];
2866
2867 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Land Raised:", me->sHeight);
2868
2869 LEVELNODE const* const land_head = me->pLandHead;
2870 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Land Node:", land_head);
2871 if (land_head != NULL)
2872 {
2873 UINT16 const idx = land_head->usIndex;
2874 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Land Node:", idx);
2875 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Full Land:", gTileDatabase[idx].ubFullTile);
2876 }
2877
2878 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Land St Node:", me->pLandStart);
2879 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "GRIDNO:", map_pos);
2880
2881 if (me->uiFlags & MAPELEMENT_MOVEMENT_RESERVED)
2882 {
2883 MPrint( DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y += h, ST::format("Merc: {}", me->ubReservedSoldierID));
2884 MPrint( DEBUG_PAGE_FIRST_COLUMN, y, "RESERVED MOVEMENT FLAG ON:");
2885 }
2886
2887 LEVELNODE const* const node = GetCurInteractiveTile();
2888 if (node != NULL)
2889 {
2890 MPrint( DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y += h, ST::format("Tile: {}", node->usIndex));
2891 MPrint( DEBUG_PAGE_FIRST_COLUMN, y, "ON INT TILE");
2892 }
2893
2894 if (me->uiFlags & MAPELEMENT_REVEALED)
2895 {
2896 MPrint(DEBUG_PAGE_FIRST_COLUMN, y += h, "REVEALED");
2897 }
2898
2899 if (me->uiFlags & MAPELEMENT_RAISE_LAND_START)
2900 {
2901 MPrint(DEBUG_PAGE_FIRST_COLUMN, y += h, "Land Raise Start");
2902 }
2903
2904 if (me->uiFlags & MAPELEMENT_RAISE_LAND_END)
2905 {
2906 MPrint(DEBUG_PAGE_FIRST_COLUMN, y += h, "Raise Land End");
2907 }
2908
2909 if (gubWorldRoomInfo[map_pos] != NO_ROOM)
2910 {
2911 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Room Number", gubWorldRoomInfo[map_pos]);
2912 }
2913
2914 if (me->ubExtFlags[0] & MAPELEMENT_EXT_NOBURN_STRUCT)
2915 {
2916 MPrint(0, y += h, "Don't Use Burn Through For Soldier");
2917 }
2918 }
2919 }
2920
2921
DebugSoldierPage3()2922 void DebugSoldierPage3()
2923 {
2924 static const char* const gzAlertStr[] =
2925 {
2926 "GREEN",
2927 "YELLOW",
2928 "RED",
2929 "BLACK"
2930 };
2931
2932 INT32 const h = DEBUG_PAGE_LINE_HEIGHT;
2933
2934 const SOLDIERTYPE* const s = FindSoldierFromMouse();
2935 if (s != NULL)
2936 {
2937 MPageHeader(ST::format("DEBUG SOLDIER PAGE THREE, GRIDNO {}", s->sGridNo));
2938
2939 INT32 y = DEBUG_PAGE_START_Y;
2940
2941 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "ID:", s->ubID);
2942 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Action:", gzActionStr[s->bAction]);
2943
2944 if (s->uiStatusFlags & SOLDIER_ENEMY)
2945 {
2946 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Alert:", gzAlertStr[s->bAlertStatus]);
2947 }
2948
2949 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Action Data:", s->usActionData);
2950
2951 if (s->uiStatusFlags & SOLDIER_ENEMY)
2952 {
2953 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "AIMorale", s->bAIMorale);
2954 }
2955 else
2956 {
2957 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Morale", s->bMorale);
2958 }
2959
2960 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Delayed Movement:", s->fDelayedMovement);
2961
2962 if (gubWatchedLocPoints[s->ubID][0] > 0)
2963 {
2964 MPrint(DEBUG_PAGE_FIRST_COLUMN, y += h, ST::format("Watch {}/{} for {} pts",
2965 gsWatchedLoc[s->ubID][0],
2966 gbWatchedLocLevel[s->ubID][0],
2967 gubWatchedLocPoints[s->ubID][0])
2968 );
2969 }
2970
2971 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "ActionInProg:", s->bActionInProgress);
2972
2973 if (gubWatchedLocPoints[s->ubID][1] > 0)
2974 {
2975 MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y += h, ST::format("Watch {}/{} for {} pts",
2976 gsWatchedLoc[s->ubID][1],
2977 gbWatchedLocLevel[s->ubID][1],
2978 gubWatchedLocPoints[s->ubID][1])
2979 );
2980 }
2981
2982 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Last Action:", gzActionStr[s->bLastAction]);
2983
2984 if (gubWatchedLocPoints[s->ubID][2] > 0)
2985 {
2986 MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y += h, ST::format("Watch {}/{} for {} pts",
2987 gsWatchedLoc[s->ubID][2],
2988 gbWatchedLocLevel[s->ubID][2],
2989 gubWatchedLocPoints[s->ubID][2])
2990 );
2991 }
2992
2993 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Animation:", gAnimControl[s->usAnimState].zAnimStr);
2994 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Getting Hit:", s->fGettingHit);
2995
2996 if (s->ubCivilianGroup != 0)
2997 {
2998 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Civ group", s->ubCivilianGroup);
2999 }
3000
3001 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Suppress pts:", s->ubSuppressionPoints);
3002 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Attacker ID:", SOLDIER2ID(s->attacker));
3003 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "EndAINotCalled:", s->fTurnInProgress);
3004
3005 y = DEBUG_PAGE_START_Y;
3006
3007 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "PrevAnimation:", gAnimControl[s->usOldAniState].zAnimStr);
3008 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "PrevAniCode:", gusAnimInst[s->usOldAniState][s->sOldAniCode]);
3009 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "GridNo:", s->sGridNo);
3010 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "AniCode:", gusAnimInst[s->usAnimState][s->usAniCode]);
3011 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "No APS To fin Move:", s->fNoAPToFinishMove);
3012 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "Bullets out:", s->bBulletsLeft);
3013 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "Anim non-int:", s->fInNonintAnim);
3014 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "RT Anim non-int:", s->fRTInNonintAnim);
3015
3016 // OPINION OF SELECTED MERC
3017 const SOLDIERTYPE* const sel = GetSelectedMan();
3018 if (sel != NULL && s->ubProfile != NO_PROFILE)
3019 {
3020 MercProfile const p = MercProfile(s->ubProfile);
3021 if (p.isPlayerMerc() || p.isRPC())
3022 {
3023 MPrintStat(DEBUG_PAGE_SECOND_COLUMN, y += h, "NPC Opinion:", GetProfile(s->ubProfile).bMercOpinion[sel->ubProfile]);
3024 }
3025 }
3026 }
3027 else
3028 {
3029 const GridNo map_pos = GetMouseMapPos();
3030 if (map_pos == NOWHERE) return;
3031
3032 MPageHeader("DEBUG LAND PAGE THREE");
3033
3034 INT32 y = DEBUG_PAGE_START_Y;
3035
3036 // OK, display door information here.....
3037 DOOR_STATUS const* const door = GetDoorStatus(map_pos);
3038 if (door == NULL)
3039 {
3040 MHeader(DEBUG_PAGE_FIRST_COLUMN, y += h, "No Door Status");
3041 }
3042 else
3043 {
3044 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Door Status Found:", map_pos);
3045
3046 ST::string door_state =
3047 door->ubFlags & DOOR_OPEN ? "OPEN" : "CLOSED";
3048 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Actual Status:", door_state);
3049
3050 ST::string perceived_state =
3051 door->ubFlags & DOOR_PERCEIVED_NOTSET ? "NOT SET" :
3052 door->ubFlags & DOOR_PERCEIVED_OPEN ? "OPEN" :
3053 "CLOSED";
3054 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Perceived Status:", perceived_state);
3055 }
3056
3057 //Find struct data and se what it says......
3058 STRUCTURE const* const structure = FindStructure(map_pos, STRUCTURE_ANYDOOR);
3059 if (structure == NULL)
3060 {
3061 MHeader(DEBUG_PAGE_FIRST_COLUMN, y += h, "No Door Struct Data");
3062 }
3063 else
3064 {
3065 ST::string structure_state =
3066 structure->fFlags & STRUCTURE_OPEN ? "OPEN" : "CLOSED";
3067 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "State:", structure_state);
3068 }
3069 }
3070 }
3071
3072
AppendAttachmentCode(UINT16 item,ST::string & str)3073 static void AppendAttachmentCode(UINT16 item, ST::string& str)
3074 {
3075 switch (item)
3076 {
3077 case SILENCER: str += " Sil"; break;
3078 case SNIPERSCOPE: str += " Scp"; break;
3079 case BIPOD: str += " Bip"; break;
3080 case LASERSCOPE: str += " Las"; break;
3081 }
3082 }
3083
3084
WriteQuantityAndAttachments(OBJECTTYPE const * o,INT32 const y)3085 static void WriteQuantityAndAttachments(OBJECTTYPE const* o, INT32 const y)
3086 {
3087 //100% Qty: 2 Attach:
3088 //100% Qty: 2
3089 //100% Attach:
3090 //100%
3091 if (GCM->getItem(o->usItem)->getItemClass() == IC_AMMO)
3092 {
3093 if (o->ubNumberOfObjects > 1)
3094 {
3095 ST::string str = ST::format("Clips: {} ({}", o->ubNumberOfObjects, o->bStatus[0]);
3096 for (UINT8 i = 1; i < o->ubNumberOfObjects; ++i)
3097 {
3098 ST::string temp = ST::format(", {}", o->bStatus[0]);
3099 str += temp;
3100 }
3101 str += ")";
3102 MPrint(DEBUG_PAGE_SECOND_COLUMN, y, str);
3103 }
3104 else
3105 {
3106 MPrint(DEBUG_PAGE_SECOND_COLUMN, y, ST::format("{} rounds", o->bStatus[0]));
3107 }
3108 }
3109 else
3110 {
3111 // Build attachment string
3112 ST::string attachments;
3113 if (o->usAttachItem[0] ||
3114 o->usAttachItem[1] ||
3115 o->usAttachItem[2] ||
3116 o->usAttachItem[3])
3117 {
3118 attachments = " (";
3119 AppendAttachmentCode(o->usAttachItem[0], attachments);
3120 AppendAttachmentCode(o->usAttachItem[1], attachments);
3121 AppendAttachmentCode(o->usAttachItem[2], attachments);
3122 AppendAttachmentCode(o->usAttachItem[3], attachments);
3123 attachments += " )";
3124 }
3125 else
3126 {
3127 attachments = ST::null;
3128 }
3129
3130 if (o->ubNumberOfObjects > 1)
3131 {
3132 //everything
3133 MPrint(DEBUG_PAGE_SECOND_COLUMN, y, ST::format("{}% Qty: {}{}", o->bStatus[0], o->ubNumberOfObjects, attachments));
3134 }
3135 else
3136 {
3137 //condition and attachments
3138 MPrint(DEBUG_PAGE_SECOND_COLUMN, y, ST::format("{}%{}", o->bStatus[0], attachments));
3139 }
3140 }
3141 }
3142
3143
PrintItem(INT32 y,const ST::string & header,const OBJECTTYPE * o)3144 static void PrintItem(INT32 y, const ST::string& header, const OBJECTTYPE* o)
3145 {
3146 MHeader(DEBUG_PAGE_FIRST_COLUMN, y, header);
3147 if (!o->usItem) return;
3148 MPrint(DEBUG_PAGE_FIRST_COLUMN+DEBUG_PAGE_LABEL_WIDTH, y, ST::format("{}", ShortItemNames[o->usItem]));
3149 WriteQuantityAndAttachments(o, y);
3150 }
3151
3152
DebugSoldierPage4()3153 void DebugSoldierPage4()
3154 {
3155 const SOLDIERTYPE* const s = FindSoldierFromMouse();
3156
3157 INT32 const h = DEBUG_PAGE_LINE_HEIGHT;
3158
3159 if (s != NULL)
3160 {
3161 MPageHeader(ST::format("DEBUG SOLDIER PAGE FOUR, GRIDNO {}", s->sGridNo));
3162
3163 INT32 y = DEBUG_PAGE_START_Y;
3164
3165 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "Exp. Level:", s->bExpLevel);
3166 ST::string sclass;
3167 switch (s->ubSoldierClass)
3168 {
3169 case SOLDIER_CLASS_ADMINISTRATOR:
3170 sclass = "(Administrator)";
3171 break;
3172 case SOLDIER_CLASS_ELITE:
3173 sclass = "(Army Elite)";
3174 break;
3175 case SOLDIER_CLASS_ARMY:
3176 sclass = "(Army Troop)";
3177 break;
3178 case SOLDIER_CLASS_CREATURE:
3179 sclass = "(Creature)";
3180 break;
3181 case SOLDIER_CLASS_GREEN_MILITIA:
3182 sclass = "(Green Militia)";
3183 break;
3184 case SOLDIER_CLASS_REG_MILITIA:
3185 sclass = "(Reg Militia)";
3186 break;
3187 case SOLDIER_CLASS_ELITE_MILITIA:
3188 sclass = "(Elite Militia)";
3189 break;
3190 case SOLDIER_CLASS_MINER:
3191 sclass = "(Miner)";
3192 break;
3193 // don't care (don't write anything)
3194 default:
3195 sclass = ST::null;
3196 break;
3197 }
3198 if (!sclass.empty())
3199 MPrint(DEBUG_PAGE_SECOND_COLUMN, y, ST::format("{}", sclass));
3200
3201 if (s->bTeam != OUR_TEAM)
3202 {
3203 ST::string orders;
3204 switch (s->bOrders)
3205 {
3206 case STATIONARY: orders = "STATIONARY"; break;
3207 case ONGUARD: orders = "ON GUARD"; break;
3208 case ONCALL: orders = "ON CALL"; break;
3209 case SEEKENEMY: orders = "SEEK ENEMY"; break;
3210 case CLOSEPATROL: orders = "CLOSE PATROL"; break;
3211 case FARPATROL: orders = "FAR PATROL"; break;
3212 case POINTPATROL: orders = "POINT PATROL"; break;
3213 case RNDPTPATROL: orders = "RND PT PATROL"; break;
3214 default: orders = "UNKNOWN"; break;
3215 }
3216 ST::string attitude;
3217 switch (s->bAttitude)
3218 {
3219 case DEFENSIVE: attitude = "DEFENSIVE"; break;
3220 case BRAVESOLO: attitude = "BRAVE SOLO"; break;
3221 case BRAVEAID: attitude = "BRAVE AID"; break;
3222 case AGGRESSIVE: attitude = "AGGRESSIVE"; break;
3223 case CUNNINGSOLO: attitude = "CUNNING SOLO"; break;
3224 case CUNNINGAID: attitude = "CUNNING AID"; break;
3225 default: attitude = "UNKNOWN"; break;
3226 }
3227 SOLDIERINITNODE const* const node = FindSoldierInitNodeBySoldier(*s);
3228 y += h;
3229 if (node)
3230 {
3231 MPrint(DEBUG_PAGE_FIRST_COLUMN, y, ST::format("{}, {}, REL EQUIP: {}, REL ATTR: {}",
3232 orders, attitude,
3233 node->pBasicPlacement->bRelativeEquipmentLevel,
3234 node->pBasicPlacement->bRelativeAttributeLevel)
3235 );
3236 }
3237 else
3238 {
3239 MPrint(DEBUG_PAGE_FIRST_COLUMN, y, ST::format("{}, {}", orders, attitude));
3240 }
3241 }
3242
3243 MPrintStat(DEBUG_PAGE_FIRST_COLUMN, y += h, "ID:", s->ubID);
3244
3245 PrintItem(y += h, "HELMETPOS:", &s->inv[HELMETPOS]);
3246 PrintItem(y += h, "VESTPOS:", &s->inv[VESTPOS]);
3247 PrintItem(y += h, "LEGPOS:", &s->inv[LEGPOS]);
3248 PrintItem(y += h, "HEAD1POS:", &s->inv[HEAD1POS]);
3249 PrintItem(y += h, "HEAD2POS:", &s->inv[HEAD2POS]);
3250 PrintItem(y += h, "HANDPOS:", &s->inv[HANDPOS]);
3251 PrintItem(y += h, "SECONDHANDPOS:", &s->inv[SECONDHANDPOS]);
3252 PrintItem(y += h, "BIGPOCK1POS:", &s->inv[BIGPOCK1POS]);
3253 PrintItem(y += h, "BIGPOCK2POS:", &s->inv[BIGPOCK2POS]);
3254 PrintItem(y += h, "BIGPOCK3POS:", &s->inv[BIGPOCK3POS]);
3255 PrintItem(y += h, "BIGPOCK4POS:", &s->inv[BIGPOCK4POS]);
3256 PrintItem(y += h, "SMALLPOCK1POS:", &s->inv[SMALLPOCK1POS]);
3257 PrintItem(y += h, "SMALLPOCK2POS:", &s->inv[SMALLPOCK2POS]);
3258 PrintItem(y += h, "SMALLPOCK3POS:", &s->inv[SMALLPOCK3POS]);
3259 PrintItem(y += h, "SMALLPOCK4POS:", &s->inv[SMALLPOCK4POS]);
3260 PrintItem(y += h, "SMALLPOCK5POS:", &s->inv[SMALLPOCK5POS]);
3261 PrintItem(y += h, "SMALLPOCK6POS:", &s->inv[SMALLPOCK6POS]);
3262 PrintItem(y += h, "SMALLPOCK7POS:", &s->inv[SMALLPOCK7POS]);
3263 PrintItem(y += h, "SMALLPOCK8POS:", &s->inv[SMALLPOCK8POS]);
3264 }
3265 else
3266 {
3267 MPageHeader("DEBUG LAND PAGE FOUR");
3268 }
3269 }
3270
3271
3272 //
3273 // Noise stuff
3274 //
3275
MovementNoise(SOLDIERTYPE const * const pSoldier)3276 UINT8 MovementNoise(SOLDIERTYPE const * const pSoldier)
3277 {
3278 constexpr UINT8 MAX_MOVEMENT_NOISE = 9;
3279
3280 if ( pSoldier->bTeam == ENEMY_TEAM )
3281 {
3282 return( (UINT8) (MAX_MOVEMENT_NOISE - PreRandom( 2 )) );
3283 }
3284
3285 INT32 iStealthSkill = 20 + 4 * EffectiveExpLevel( pSoldier ) + ((EffectiveDexterity( pSoldier ) * 4) / 10); // 24-100
3286
3287 // big bonus for those "extra stealthy" mercs
3288 if ( pSoldier->ubBodyType == BLOODCAT )
3289 {
3290 iStealthSkill += 50;
3291 }
3292 else
3293 {
3294 iStealthSkill += 25 * NUM_SKILL_TRAITS(pSoldier, STEALTHY);
3295 }
3296
3297 UINT8 const ubBandaged = pSoldier->bLifeMax - pSoldier->bLife - pSoldier->bBleeding;
3298 UINT8 const ubEffLife = pSoldier->bLife + (ubBandaged / 2);
3299
3300 // IF "SNEAKER'S" "EFFECTIVE LIFE" IS AT LESS THAN 50
3301 if (ubEffLife < 50)
3302 {
3303 // reduce effective stealth skill by up to 50% for low life
3304 iStealthSkill -= (iStealthSkill * (50 - ubEffLife)) / 100;
3305 }
3306
3307 // if breath is below 50%
3308 if (pSoldier->bBreath < 50)
3309 {
3310 // reduce effective stealth skill by up to 50%
3311 iStealthSkill -= (iStealthSkill * (50 - pSoldier->bBreath)) / 100;
3312 }
3313
3314 // if sneaker is moving through water
3315 // DeepWater() must be checked first because Water() matches all types of water
3316 if (DeepWater(pSoldier->sGridNo))
3317 {
3318 iStealthSkill -= 20;
3319 }
3320 else if (Water(pSoldier->sGridNo))
3321 {
3322 iStealthSkill -= 10;
3323 }
3324
3325 // minus 3 percent per bonus AP from adrenaline
3326 iStealthSkill -= 3 * pSoldier->bDrugEffect[ DRUG_TYPE_ADRENALINE ];
3327
3328 iStealthSkill = std::max(iStealthSkill, 0);
3329
3330 UINT8 ubVolume;
3331 if (!pSoldier->bStealthMode) // REGULAR movement
3332 {
3333 UINT8 ubMaxVolume = MAX_MOVEMENT_NOISE - (iStealthSkill / 16); // 9 - (0 to 9) => 0 to 9
3334
3335 if (Water(pSoldier->sGridNo))
3336 {
3337 ubMaxVolume++; // in water, can be even louder
3338 }
3339 switch (pSoldier->usAnimState)
3340 {
3341 case CRAWLING:
3342 ubMaxVolume = ubMaxVolume <= 2 ? 0 : ubMaxVolume - 2;
3343 break;
3344 case SWATTING:
3345 ubMaxVolume = ubMaxVolume <= 1 ? 0 : ubMaxVolume - 1;
3346 break;
3347 case RUNNING:
3348 ubMaxVolume += 3;
3349 break;
3350 }
3351
3352 if (ubMaxVolume < 2)
3353 {
3354 ubVolume = ubMaxVolume;
3355 }
3356 else
3357 {
3358 ubVolume = 1 + (UINT8) PreRandom(ubMaxVolume); // actual volume is 1 to max volume
3359 }
3360 }
3361 else // in STEALTH mode
3362 {
3363 INT32 iRoll = (INT32) PreRandom(100); // roll them bones!
3364
3365 if (iRoll >= iStealthSkill) // v1.13 modification: give a second chance!
3366 {
3367 iRoll = (INT32) PreRandom(100);
3368 }
3369
3370 if (iRoll < iStealthSkill)
3371 {
3372 ubVolume = 0; // made it, stayed quiet moving through this tile
3373 }
3374 else // OOPS!
3375 {
3376 ubVolume = 1 + ((iRoll - iStealthSkill + 1) / 16); // volume is 1 - 7 ...
3377 switch (pSoldier->usAnimState)
3378 {
3379 case CRAWLING:
3380 ubVolume = ubVolume <= 2 ? 0 : ubVolume - 2;
3381 break;
3382 case SWATTING:
3383 ubVolume -= 1;
3384 break;
3385 case RUNNING:
3386 ubVolume += 3;
3387 break;
3388 }
3389 }
3390 }
3391
3392 return( ubVolume );
3393 }
3394
DoorOpeningNoise(SOLDIERTYPE * pSoldier)3395 UINT8 DoorOpeningNoise( SOLDIERTYPE *pSoldier )
3396 {
3397 INT16 sGridNo;
3398 DOOR_STATUS *pDoorStatus;
3399 UINT8 ubDoorNoise;
3400
3401 // door being opened gridno is always the pending-action-data2 value
3402 sGridNo = pSoldier->sPendingActionData2;
3403 pDoorStatus = GetDoorStatus( sGridNo );
3404
3405 if ( pDoorStatus && pDoorStatus->ubFlags & DOOR_HAS_TIN_CAN )
3406 {
3407 // double noise possible!
3408 ubDoorNoise = DOOR_NOISE_VOLUME * 3;
3409 }
3410 else
3411 {
3412 ubDoorNoise = DOOR_NOISE_VOLUME;
3413 }
3414 if ( MovementNoise( pSoldier ) )
3415 {
3416 // failed any stealth checks
3417 return( ubDoorNoise );
3418 }
3419 else
3420 {
3421 // succeeded in being stealthy!
3422 return( 0 );
3423 }
3424 }
3425
3426
MakeNoise(SOLDIERTYPE * const noise_maker,INT16 const sGridNo,INT8 const bLevel,UINT8 const ubVolume,NoiseKind const ubNoiseType)3427 void MakeNoise(SOLDIERTYPE* const noise_maker, INT16 const sGridNo, INT8 const bLevel, UINT8 const ubVolume, NoiseKind const ubNoiseType)
3428 {
3429 if ( gTacticalStatus.ubAttackBusyCount )
3430 {
3431 // delay these events until the attack is over!
3432 EV_S_NOISE SNoise;
3433 SNoise.ubNoiseMaker = SOLDIER2ID(noise_maker);
3434 SNoise.sGridNo = sGridNo;
3435 SNoise.bLevel = bLevel;
3436 SNoise.ubVolume = ubVolume;
3437 SNoise.ubNoiseType = ubNoiseType;
3438 AddGameEvent(SNoise, DEMAND_EVENT_DELAY);
3439 }
3440 else
3441 {
3442 OurNoise(noise_maker, sGridNo, bLevel, ubVolume, ubNoiseType);
3443 }
3444 }
3445
3446
3447 static void ProcessNoise(SOLDIERTYPE* noise_maker, INT16 sGridNo, INT8 bLevel, UINT8 ubBaseVolume, NoiseKind);
3448
3449
OurNoise(SOLDIERTYPE * const noise_maker,INT16 const sGridNo,INT8 const bLevel,UINT8 const ubVolume,NoiseKind const ubNoiseType)3450 void OurNoise(SOLDIERTYPE* const noise_maker, INT16 const sGridNo, INT8 const bLevel, UINT8 const ubVolume, NoiseKind const ubNoiseType)
3451 {
3452 #ifdef BYPASSNOISE
3453 return;
3454 #endif
3455
3456 // see if anyone actually hears this noise, sees noise_maker, etc.
3457 ProcessNoise(noise_maker, sGridNo, bLevel, ubVolume, ubNoiseType);
3458
3459 if (noise_maker != NULL &&
3460 (gTacticalStatus.uiFlags & INCOMBAT) &&
3461 !gfDelayResolvingBestSightingDueToDoor)
3462 {
3463 // interrupts are possible, resolve them now (we're in control here)
3464 // (you can't interrupt NOBODY, even if you hear the noise)
3465 ResolveInterruptsVs(noise_maker, NOISEINTERRUPT);
3466 }
3467 }
3468
3469
3470 static void HearNoise(SOLDIERTYPE* pSoldier, SOLDIERTYPE* noise_maker, UINT16 sGridNo, INT8 bLevel, UINT8 ubVolume, NoiseKind, BOOLEAN* ubSeen);
3471 static UINT8 CalcEffVolume(const SOLDIERTYPE* pSoldier, INT16 sGridNo, INT8 bLevel, NoiseKind, UINT8 ubBaseVolume, UINT8 bCheckTerrain, UINT8 ubTerrType1, UINT8 ubTerrType2);
3472 static void TellPlayerAboutNoise(SOLDIERTYPE* pSoldier, const SOLDIERTYPE* noise_maker, INT16 sGridNo, INT8 bLevel, UINT8 ubVolume, NoiseKind, UINT8 ubNoiseDir);
3473
3474
ProcessNoise(SOLDIERTYPE * const noise_maker,INT16 const sGridNo,INT8 const bLevel,UINT8 const ubBaseVolume,NoiseKind const ubNoiseType)3475 static void ProcessNoise(SOLDIERTYPE* const noise_maker, INT16 const sGridNo, INT8 const bLevel, UINT8 const ubBaseVolume, NoiseKind const ubNoiseType)
3476 {
3477 UINT8 ubLoudestEffVolume, ubEffVolume;
3478 //UINT8 ubPlayVolume;
3479 INT8 bCheckTerrain = FALSE;
3480 UINT8 ubNoiseDir = (UINT8)-1; // XXX HACK000E probably ubLoudestNoiseDir should be used
3481 UINT8 ubLoudestNoiseDir = (UINT8)-1; // XXX HACK000E
3482
3483 // if the base volume itself was negligible
3484 if (!ubBaseVolume)
3485 return;
3486
3487
3488 // EXPLOSIONs are special, because they COULD be caused by a merc who is
3489 // no longer alive (but he placed the bomb or flaky grenade in the past).
3490 // Later noiseMaker gets whacked to NOBODY anyway, so that's OK. So a
3491 // dead noiseMaker is only used here to decide WHICH soldiers HearNoise().
3492
3493 // if noise is made by a person, AND it's not noise from an explosion
3494 if (noise_maker != NULL && ubNoiseType != NOISE_EXPLOSION)
3495 {
3496 // inactive/not in sector/dead soldiers, shouldn't be making noise!
3497 if (!noise_maker->bActive ||
3498 !noise_maker->bInSector ||
3499 noise_maker->uiStatusFlags & SOLDIER_DEAD)
3500 {
3501 return;
3502 }
3503
3504 // if he's out of life, and this isn't just his "dying scream" which is OK
3505 if (noise_maker->bLife == 0 && ubNoiseType != NOISE_SCREAM)
3506 {
3507 return;
3508 }
3509 }
3510
3511
3512 // DETERMINE THE TERRAIN TYPE OF THE GRIDNO WHERE NOISE IS COMING FROM
3513
3514 const UINT8 ubSourceTerrType = gpWorldLevelData[sGridNo].ubTerrainID;
3515
3516 // if we have now somehow obtained a valid terrain type
3517 if ((ubSourceTerrType >= FLAT_GROUND) || (ubSourceTerrType <= DEEP_WATER))
3518 {
3519 bCheckTerrain = TRUE;
3520 }
3521 // else give up trying to get terrain type, just assume sound isn't muffled
3522
3523
3524 // DETERMINE THE *PERCEIVED* SOURCE OF THE NOISE
3525 SOLDIERTYPE* source;
3526 switch (ubNoiseType)
3527 {
3528 // for noise generated by an OBJECT shot/thrown/dropped by the noiseMaker
3529 case NOISE_ROCK_IMPACT:
3530 gWhoThrewRock = noise_maker;
3531 /* FALLTHROUGH */
3532 case NOISE_BULLET_IMPACT:
3533 case NOISE_GRENADE_IMPACT:
3534 case NOISE_EXPLOSION:
3535 // the source of the noise is not at all obvious, so hide it from
3536 // the listener and maintain noiseMaker's cover by making source NOBODY
3537 source = NULL;
3538 break;
3539
3540 default:
3541 // normal situation: the noiseMaker is obviously the source of the noise
3542 source = noise_maker;
3543 break;
3544 }
3545
3546 // LOOP THROUGH EACH TEAM
3547 for (UINT8 bTeam = 0; bTeam < MAXTEAMS; ++bTeam)
3548 {
3549 // skip any inactive teams
3550 if (!IsTeamActive(bTeam)) continue;
3551
3552 // if a the noise maker is a person, not just NOBODY
3553 if (noise_maker != NULL)
3554 {
3555 // if this team is the same TEAM as the noise maker's
3556 // (for now, assume we will report noises by unknown source on same SIDE)
3557 // OR, if the noise maker is currently in sight to this HUMAN team
3558
3559 // CJC: changed to if the side is the same side as the noise maker's!
3560 // CJC: changed back!
3561
3562 if (bTeam == noise_maker->bTeam) continue;
3563
3564 if (gTacticalStatus.Team[bTeam].bHuman &&
3565 gbPublicOpplist[bTeam][noise_maker->ubID] == SEEN_CURRENTLY)
3566 {
3567 continue;
3568 }
3569 }
3570
3571 // tell player about noise if enemies are present
3572 BOOLEAN bTellPlayer = gTacticalStatus.fEnemyInSector && ( !(gTacticalStatus.uiFlags & INCOMBAT) || (gTacticalStatus.ubCurrentTeam) );
3573
3574 switch (ubNoiseType)
3575 {
3576 case NOISE_GUNFIRE:
3577 case NOISE_BULLET_IMPACT:
3578 case NOISE_ROCK_IMPACT:
3579 case NOISE_GRENADE_IMPACT:
3580 // It's noise caused by a projectile. If the projectile was seen by
3581 // the local player while in flight (PublicBullet), then don't bother
3582 // giving him a message about the noise it made, he's obviously aware.
3583 if (1 /*PublicBullet*/)
3584 {
3585 bTellPlayer = FALSE;
3586 }
3587
3588 break;
3589
3590 case NOISE_EXPLOSION:
3591 // if center of explosion is in visual range of team, don't report
3592 // noise, because the player is already watching the thing go BOOM!
3593 if (TeamMemberNear(bTeam,sGridNo,STRAIGHT))
3594 {
3595 bTellPlayer = FALSE;
3596 }
3597 break;
3598
3599 case NOISE_SILENT_ALARM:
3600 bTellPlayer = FALSE;
3601 break;
3602 default:
3603 break;
3604 }
3605
3606 // if noise was made by a person
3607 if (noise_maker != NULL)
3608 {
3609 switch (gbPublicOpplist[bTeam][noise_maker->ubID])
3610 {
3611 case SEEN_CURRENTLY:
3612 case SEEN_THIS_TURN:
3613 case HEARD_THIS_TURN:
3614 // If noisemaker has been *PUBLICLY* SEEN OR HEARD during THIS TURN,
3615 // then don't bother reporting any noise made by him to the player
3616 bTellPlayer = FALSE;
3617 break;
3618
3619 default:
3620 if (noise_maker->bVisible == TRUE && bTeam == OUR_TEAM)
3621 {
3622 SLOGD(
3623 "Handling noise from person not currently seen in player's public opplist");
3624 }
3625 break;
3626 }
3627
3628 if (noise_maker->bLife == 0)
3629 {
3630 // this guy is dead (just dying) so don't report to player
3631 bTellPlayer = FALSE;
3632 }
3633 }
3634
3635 // refresh flags for this new team
3636 BOOLEAN bHeard = FALSE;
3637 BOOLEAN bSeen = FALSE;
3638 ubLoudestEffVolume = 0;
3639 SOLDIERTYPE* heard_loudest_by = NULL;
3640
3641 // All mercs on this team check if they are eligible to hear this noise
3642 FOR_EACH_IN_TEAM(pSoldier, bTeam)
3643 {
3644 // if this "listener" is in no condition to care
3645 if (!pSoldier->bInSector || pSoldier->uiStatusFlags & SOLDIER_DEAD ||
3646 (pSoldier->bLife < OKLIFE) || pSoldier->ubBodyType == LARVAE_MONSTER)
3647 {
3648 continue; // skip him!
3649 }
3650
3651 if ( pSoldier->uiStatusFlags & SOLDIER_VEHICLE && pSoldier->bTeam == OUR_TEAM )
3652 {
3653 continue; // skip
3654 }
3655
3656 if ( bTeam == OUR_TEAM && pSoldier->bAssignment == ASSIGNMENT_POW )
3657 {
3658 // POWs should not be processed for noise
3659 continue;
3660 }
3661
3662 // if a the noise maker is a person, not just NOBODY
3663 if (noise_maker != NULL)
3664 {
3665 // if this listener can see this noise maker
3666 if (pSoldier->bOppList[noise_maker->ubID] == SEEN_CURRENTLY)
3667 {
3668 // civilians care about gunshots even if they come from someone they can see
3669 if ( !( pSoldier->bNeutral && ubNoiseType == NOISE_GUNFIRE ) )
3670 {
3671 continue; // then who cares whether he can also hear the guy?
3672 }
3673 }
3674
3675 // screen out allied militia from hearing us
3676 switch (noise_maker->bTeam)
3677 {
3678 case OUR_TEAM:
3679 // if the listener is militia and still on our side, ignore noise from us
3680 if ( pSoldier->bTeam == MILITIA_TEAM && pSoldier->bSide == 0 )
3681 {
3682 continue;
3683 }
3684 break;
3685 case ENEMY_TEAM:
3686 switch( pSoldier->ubProfile )
3687 {
3688 case WARDEN:
3689 case GENERAL:
3690 case SERGEANT:
3691 case CONRAD:
3692 // ignore soldier team
3693 continue;
3694 default:
3695 break;
3696 }
3697 break;
3698 case MILITIA_TEAM:
3699 // if the noisemaker is militia and still on our side, ignore
3700 // noise if we're listening
3701 if (pSoldier->bTeam == OUR_TEAM && noise_maker->bSide == 0)
3702 {
3703 continue;
3704 }
3705 break;
3706 }
3707
3708 if ( gWorldSectorX == 5 && gWorldSectorY == MAP_ROW_N )
3709 {
3710 // in the bloodcat arena sector, skip noises between army & bloodcats
3711 if (pSoldier->bTeam == ENEMY_TEAM && noise_maker->bTeam == CREATURE_TEAM)
3712 {
3713 continue;
3714 }
3715 if (pSoldier->bTeam == CREATURE_TEAM && noise_maker->bTeam == ENEMY_TEAM)
3716 {
3717 continue;
3718 }
3719 }
3720
3721
3722 }
3723 else
3724 {
3725 // screen out allied militia from hearing us
3726 if (noise_maker == NULL && pSoldier->bTeam == MILITIA_TEAM && pSoldier->bSide == 0)
3727 {
3728 continue;
3729 }
3730 }
3731
3732 if ((pSoldier->bTeam == CIV_TEAM) && (ubNoiseType == NOISE_GUNFIRE ||
3733 ubNoiseType == NOISE_EXPLOSION))
3734 {
3735 pSoldier->ubMiscSoldierFlags |= SOLDIER_MISC_HEARD_GUNSHOT;
3736 }
3737
3738 // Can the listener hear noise of that volume given his circumstances?
3739 ubEffVolume = CalcEffVolume(pSoldier, sGridNo, bLevel, ubNoiseType,
3740 ubBaseVolume, bCheckTerrain,
3741 pSoldier->bOverTerrainType, ubSourceTerrType);
3742
3743 if (ubEffVolume > 0)
3744 {
3745 // ALL RIGHT! Passed all the tests, this listener hears this noise!!!
3746 HearNoise(pSoldier, source, sGridNo, bLevel, ubEffVolume, ubNoiseType, &bSeen);
3747
3748 bHeard = TRUE;
3749
3750 ubNoiseDir = GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, sGridNo);
3751
3752 // check the 'noise heard & reported' bit for that soldier & direction
3753 if ( ubNoiseType != NOISE_MOVEMENT || bTeam != OUR_TEAM ||
3754 (pSoldier->bInterruptDuelPts != NO_INTERRUPT) ||
3755 !(pSoldier->ubMovementNoiseHeard & (1 << ubNoiseDir) ) )
3756 {
3757 if (ubEffVolume > ubLoudestEffVolume)
3758 {
3759 ubLoudestEffVolume = ubEffVolume;
3760 heard_loudest_by = pSoldier;
3761 ubLoudestNoiseDir = ubNoiseDir;
3762 }
3763 }
3764
3765 }
3766 else
3767 {
3768 ubEffVolume = 0;
3769 }
3770 }
3771
3772
3773 // if the noise was heard at all
3774 if (bHeard)
3775 {
3776 // and we're doing our team
3777 if (bTeam == OUR_TEAM)
3778 {
3779 // if we are to tell the player about this type of noise
3780 if (bTellPlayer && heard_loudest_by != NULL)
3781 {
3782 // the merc that heard it the LOUDEST is the one to comment
3783 // should add level to this function call
3784 TellPlayerAboutNoise(heard_loudest_by, noise_maker, sGridNo, bLevel, ubLoudestEffVolume, ubNoiseType, ubLoudestNoiseDir);
3785
3786 if ( ubNoiseType == NOISE_MOVEMENT)
3787 {
3788 heard_loudest_by->ubMovementNoiseHeard |= 1 << ubNoiseDir;
3789 }
3790
3791 }
3792 //if ( !(pSoldier->ubMovementNoiseHeard & (1 << ubNoiseDir) ) )
3793 }
3794 #ifdef REPORTTHEIRNOISE
3795 else // debugging: report noise heard by other team's soldiers
3796 {
3797 if (bTellPlayer)
3798 {
3799 TellPlayerAboutNoise(heard_loudest_by, noise_maker, sGridNo, bLevel, ubLoudestEffVolume, ubNoiseType, ubLoudestNoiseDir);
3800 }
3801 }
3802 #endif
3803 }
3804
3805 // if the listening team is human-controlled AND
3806 // the noise's source is another soldier
3807 // (computer-controlled teams don't radio or automatically report NOISE)
3808 if (source != NULL && gTacticalStatus.Team[bTeam].bHuman)
3809 {
3810 // if noise_maker was seen by at least one member of this team
3811 if (bSeen)
3812 {
3813 // this team is now allowed to report sightings and set Public flags
3814 OurTeamRadiosRandomlyAbout(source);
3815 }
3816 else // not seen
3817 {
3818 if (bHeard)
3819 {
3820 // mark noise maker as having been PUBLICLY heard THIS TURN
3821 UpdatePublic(bTeam, source, HEARD_THIS_TURN, sGridNo, bLevel);
3822 }
3823 }
3824 }
3825 }
3826
3827 gWhoThrewRock = NULL;
3828 }
3829
3830
CalcEffVolume(SOLDIERTYPE const * const pSoldier,INT16 const sGridNo,INT8 const bLevel,NoiseKind const ubNoiseType,UINT8 const ubBaseVolume,UINT8 const bCheckTerrain,UINT8 const ubTerrType1,UINT8 const ubTerrType2)3831 static UINT8 CalcEffVolume(SOLDIERTYPE const* const pSoldier, INT16 const sGridNo, INT8 const bLevel, NoiseKind const ubNoiseType, UINT8 const ubBaseVolume, UINT8 const bCheckTerrain, UINT8 const ubTerrType1, UINT8 const ubTerrType2)
3832 {
3833 INT32 iEffVolume, iDistance;
3834
3835 if (IsWearingHeadGear(*pSoldier, WALKMAN))
3836 {
3837 return( 0 );
3838 }
3839
3840 if ( gTacticalStatus.uiFlags & INCOMBAT )
3841 {
3842 // ATE: Funny things happen to ABC stuff if bNewSituation set....
3843 if ( gTacticalStatus.ubCurrentTeam == pSoldier->bTeam )
3844 {
3845 return( 0 );
3846 }
3847 }
3848
3849 // adjust default noise volume by listener's hearing capability
3850 iEffVolume = (INT32) ubBaseVolume + (INT32) DecideHearing( pSoldier );
3851
3852
3853 // effective volume reduced by listener's number of opponents in sight
3854 iEffVolume -= pSoldier->bOppCnt;
3855
3856
3857 // calculate the distance (in adjusted pixels) between the source of the
3858 // noise (gridno) and the location of the would-be listener (pSoldier->gridno)
3859 iDistance = (INT32) PythSpacesAway( pSoldier->sGridNo, sGridNo );
3860
3861 // effective volume fades over distance beyond 1 tile away
3862 iEffVolume -= (iDistance - 1);
3863
3864 /*
3865 if (pSoldier->bTeam == CIV_TEAM && pSoldier->ubBodyType != CROW )
3866 {
3867 if (pSoldier->ubCivilianGroup == 0 && pSoldier->ubProfile == NO_PROFILE)
3868 {
3869 // nameless civs reduce effective volume by 2 for gunshots etc
3870 // (double the reduction due to distance)
3871 // so that they don't cower from attacks that are really far away
3872 switch (ubNoiseType)
3873 {
3874 case NOISE_GUNFIRE:
3875 case NOISE_BULLET_IMPACT:
3876 case NOISE_GRENADE_IMPACT:
3877 case NOISE_EXPLOSION:
3878 iEffVolume -= iDistance;
3879 break;
3880 default:
3881 break;
3882 }
3883 }
3884 else if (pSoldier->bNeutral)
3885 {
3886 // NPCs and people in groups ignore attack noises unless they are no longer neutral
3887 switch (ubNoiseType)
3888 {
3889 case NOISE_GUNFIRE:
3890 case NOISE_BULLET_IMPACT:
3891 case NOISE_GRENADE_IMPACT:
3892 case NOISE_EXPLOSION:
3893 iEffVolume = 0;
3894 break;
3895 default:
3896 break;
3897 }
3898 }
3899 }*/
3900
3901 if (pSoldier->usAnimState == RUNNING)
3902 {
3903 iEffVolume -= 5;
3904 }
3905
3906 #if 0 /* XXX Something is very wrong here... */
3907 if (pSoldier->bAssignment == SLEEPING )
3908 {
3909 // decrease effective volume since we're asleep!
3910 iEffVolume -= 5;
3911 }
3912 #endif
3913
3914 // check for floor/roof difference
3915 if (bLevel > pSoldier->bLevel)
3916 {
3917 // sound is amplified by roof
3918 iEffVolume += 5;
3919 }
3920 else if (bLevel < pSoldier->bLevel)
3921 {
3922 // sound is muffled
3923 iEffVolume -= 5;
3924 }
3925
3926 // if we still have a chance of hearing this, and the terrain types are known
3927 if (iEffVolume > 0)
3928 {
3929 if (bCheckTerrain)
3930 {
3931 // if, between noise and listener, one is outside and one is inside
3932
3933 // NOTE: This is a pretty dumb way of doing things, since it won't detect
3934 // the presence of walls between 2 spots both inside or both outside, but
3935 // given our current system it's the best that we can do
3936
3937 if (((ubTerrType1 == FLAT_FLOOR) && (ubTerrType2 != FLAT_FLOOR)) ||
3938 ((ubTerrType1 != FLAT_FLOOR) && (ubTerrType2 == FLAT_FLOOR)))
3939 {
3940 // sound is muffled, reduce the effective volume of the noise
3941 iEffVolume -= 5;
3942 }
3943 }
3944
3945 }
3946 if (iEffVolume > 0)
3947 {
3948 return( (UINT8) iEffVolume );
3949 }
3950 else
3951 {
3952 return( 0 );
3953 }
3954 }
3955
3956
HearNoise(SOLDIERTYPE * const pSoldier,SOLDIERTYPE * const noise_maker,UINT16 const sGridNo,INT8 const bLevel,UINT8 const ubVolume,NoiseKind const ubNoiseType,BOOLEAN * const ubSeen)3957 static void HearNoise(SOLDIERTYPE* const pSoldier, SOLDIERTYPE* const noise_maker, UINT16 const sGridNo, INT8 const bLevel, UINT8 const ubVolume, NoiseKind const ubNoiseType, BOOLEAN* const ubSeen)
3958 {
3959 INT16 sNoiseX, sNoiseY;
3960 INT8 bHadToTurn = FALSE, bSourceSeen = FALSE;
3961 INT8 bOldOpplist;
3962 INT16 sDistVisible;
3963 INT8 bDirection;
3964 BOOLEAN fMuzzleFlash = FALSE;
3965
3966 SLOGD("%d hears noise from %d (%d/%d) volume %d",
3967 pSoldier->ubID, SOLDIER2ID(noise_maker), sGridNo, bLevel, ubVolume);
3968
3969
3970 if ( pSoldier->ubBodyType == CROW )
3971 {
3972 CrowsFlyAway( pSoldier->bTeam );
3973 return;
3974 }
3975
3976 // "Turn head" towards the source of the noise and try to see what's there
3977
3978 // don't use DistanceVisible here, but use maximum visibility distance
3979 // in as straight line instead. Represents guy "turning just his head"
3980
3981 // CJC 97/10: CHANGE! Since STRAIGHT can not reliably be used as a
3982 // max sighting distance (varies based on realtime/turnbased), call
3983 // the function with the new DIRECTION_IRRELEVANT define
3984
3985 // is he close enough to see that gridno if he turns his head?
3986
3987 // ignore muzzle flashes when turning head to see noise
3988 if (ubNoiseType == NOISE_GUNFIRE && noise_maker != NULL && noise_maker->fMuzzleFlash)
3989 {
3990 sNoiseX = CenterX(sGridNo);
3991 sNoiseY = CenterY(sGridNo);
3992 bDirection = atan8(pSoldier->sX,pSoldier->sY,sNoiseX,sNoiseY);
3993 if (pSoldier->bDirection != bDirection &&
3994 pSoldier->bDirection != OneCDirection(bDirection) &&
3995 pSoldier->bDirection != OneCCDirection(bDirection))
3996 {
3997 // temporarily turn off muzzle flash so DistanceVisible can be calculated without it
3998 noise_maker->fMuzzleFlash = FALSE;
3999 fMuzzleFlash = TRUE;
4000 }
4001 }
4002
4003 sDistVisible = DistanceVisible( pSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, sGridNo, bLevel );
4004
4005 if ( fMuzzleFlash )
4006 {
4007 // turn flash on again
4008 noise_maker->fMuzzleFlash = TRUE;
4009 }
4010
4011 if (PythSpacesAway(pSoldier->sGridNo,sGridNo) <= sDistVisible )
4012 {
4013 // just use the XXadjustedXX center of the gridno
4014 sNoiseX = CenterX(sGridNo);
4015 sNoiseY = CenterY(sGridNo);
4016
4017 if (pSoldier->bDirection != atan8(pSoldier->sX,pSoldier->sY,sNoiseX,sNoiseY))
4018 {
4019 bHadToTurn = TRUE;
4020 }
4021 else
4022 {
4023 bHadToTurn = FALSE;
4024 }
4025
4026 // and we can trace a line of sight to his x,y coordinates?
4027 // (taking into account we are definitely aware of this guy now)
4028
4029 // skip LOS check if we had to turn and we're a tank. sorry Mr Tank, no looking out of the sides for you!
4030 if ( !( bHadToTurn && TANK( pSoldier ) ) )
4031 {
4032 if ( SoldierTo3DLocationLineOfSightTest( pSoldier, sGridNo, bLevel, 0, (UINT8) sDistVisible, TRUE ) )
4033 {
4034 // he can actually see the spot where the noise came from!
4035 bSourceSeen = TRUE;
4036
4037 // if this sounds like a door opening/closing (could also be a crate)
4038 if (ubNoiseType == NOISE_CREAKING)
4039 {
4040 // then look around and update ALL doors that have secretly changed
4041 //LookForDoors(pSoldier,AWARE);
4042 }
4043 }
4044 }
4045 }
4046
4047 // if noise is made by a person
4048 if (noise_maker != NULL)
4049 {
4050 bOldOpplist = pSoldier->bOppList[noise_maker->ubID];
4051
4052 // WE ALREADY KNOW THAT HE'S ON ANOTHER TEAM, AND HE'S NOT BEING SEEN
4053 // ProcessNoise() ALREADY DID THAT WORK FOR US
4054
4055 if (bSourceSeen)
4056 {
4057 ManSeesMan(*pSoldier, *noise_maker, CALLER_UNKNOWN);
4058
4059 // if it's an AI soldier, he is not allowed to automatically radio any
4060 // noise heard, but manSeesMan has set his newOppCnt, so clear it here
4061 if (!(pSoldier->uiStatusFlags & SOLDIER_PC))
4062 {
4063 pSoldier->bNewOppCnt = 0;
4064 }
4065
4066 *ubSeen = TRUE;
4067 // RadioSightings() must only be called later on by ProcessNoise() itself
4068 // because we want the soldier who heard noise the LOUDEST to report it
4069
4070 if ( pSoldier->bNeutral )
4071 {
4072 // could be a civilian watching us shoot at an enemy
4073 if (((ubNoiseType == NOISE_GUNFIRE) || (ubNoiseType == NOISE_BULLET_IMPACT)) &&
4074 (ubVolume >= 3))
4075 {
4076 // if status is only GREEN or YELLOW
4077 if (pSoldier->bAlertStatus < STATUS_RED)
4078 {
4079 // then this soldier goes to status RED, has proof of enemy presence
4080 pSoldier->bAlertStatus = STATUS_RED;
4081 CheckForChangingOrders(pSoldier);
4082 }
4083 }
4084 }
4085
4086 }
4087 else // noise maker still can't be seen
4088 {
4089 SetNewSituation( pSoldier ); // re-evaluate situation
4090
4091 // if noise type was unmistakably that of gunfire
4092 if (((ubNoiseType == NOISE_GUNFIRE) || (ubNoiseType == NOISE_BULLET_IMPACT)) && (ubVolume >= 3))
4093 {
4094 // if status is only GREEN or YELLOW
4095 if (pSoldier->bAlertStatus < STATUS_RED)
4096 {
4097 // then this soldier goes to status RED, has proof of enemy presence
4098 pSoldier->bAlertStatus = STATUS_RED;
4099 CheckForChangingOrders(pSoldier);
4100 }
4101 }
4102
4103 // remember that the soldier has been heard and his new location
4104 UpdatePersonal(pSoldier, noise_maker->ubID ,HEARD_THIS_TURN, sGridNo, bLevel);
4105
4106 // Public info is not set unless EVERYONE on the team fails to see the
4107 // ubnoisemaker, leaving the 'seen' flag FALSE. See ProcessNoise().
4108
4109 // CJC: set the noise gridno for the soldier, if appropriate - this is what is looked at by the AI!
4110 if (ubVolume >= pSoldier->ubNoiseVolume)
4111 {
4112 // yes it is, so remember this noise INSTEAD (old noise is forgotten)
4113 pSoldier->sNoiseGridno = sGridNo;
4114 pSoldier->bNoiseLevel = bLevel;
4115
4116 // no matter how loud noise was, don't remember it for than 12 turns!
4117 if (ubVolume < MAX_MISC_NOISE_DURATION)
4118 {
4119 pSoldier->ubNoiseVolume = ubVolume;
4120 }
4121 else
4122 {
4123 pSoldier->ubNoiseVolume = MAX_MISC_NOISE_DURATION;
4124 }
4125
4126 SetNewSituation( pSoldier ); // force a fresh AI decision to be made
4127 }
4128
4129 }
4130
4131 if ( pSoldier->fAIFlags & AI_ASLEEP )
4132 {
4133 switch( ubNoiseType )
4134 {
4135 case NOISE_BULLET_IMPACT:
4136 case NOISE_GUNFIRE:
4137 case NOISE_EXPLOSION:
4138 case NOISE_SCREAM:
4139 case NOISE_WINDOW_SMASHING:
4140 case NOISE_DOOR_SMASHING:
4141 // WAKE UP!
4142 pSoldier->fAIFlags &= (~AI_ASLEEP);
4143 break;
4144 default:
4145 break;
4146 }
4147 }
4148
4149 // FIRST REQUIRE MUTUAL HOSTILES!
4150 if (!CONSIDERED_NEUTRAL(noise_maker, pSoldier) && !CONSIDERED_NEUTRAL(pSoldier, noise_maker) &&
4151 pSoldier->bSide != noise_maker->bSide)
4152 {
4153 // regardless of whether the noisemaker (who's not NOBODY) was seen or not,
4154 // as long as listener meets minimum interrupt conditions
4155 if ( gfDelayResolvingBestSightingDueToDoor)
4156 {
4157 if (bSourceSeen && (!(gTacticalStatus.uiFlags & INCOMBAT) ||
4158 (gubSightFlags & SIGHTINTERRUPT &&
4159 StandardInterruptConditionsMet(pSoldier, noise_maker, bOldOpplist))))
4160 {
4161 // we should be adding this to the array for the AllTeamLookForAll to handle
4162 // since this is a door opening noise, add a bonus equal to half the door volume
4163 UINT8 ubPoints;
4164
4165 ubPoints = CalcInterruptDuelPts(pSoldier, noise_maker, TRUE);
4166 if ( ubPoints != NO_INTERRUPT )
4167 {
4168 // require the enemy not to be dying if we are the sighter; in other words,
4169 // always add for AI guys, and always add for people with life >= OKLIFE
4170 if (pSoldier->bTeam != OUR_TEAM || noise_maker->bLife >= OKLIFE)
4171 {
4172 ReevaluateBestSightingPosition( pSoldier, (UINT8) (ubPoints + (ubVolume / 2)) );
4173 }
4174 }
4175 }
4176 }
4177 else
4178 {
4179 if (gTacticalStatus.uiFlags & INCOMBAT)
4180 {
4181 if (StandardInterruptConditionsMet(pSoldier, noise_maker, bOldOpplist))
4182 {
4183 // he gets a chance to interrupt the noisemaker
4184 pSoldier->bInterruptDuelPts = CalcInterruptDuelPts(pSoldier, noise_maker, TRUE);
4185 SLOGD(
4186 "Calculating int duel pts in noise code, %d has %d points",
4187 pSoldier->ubID, pSoldier->bInterruptDuelPts);
4188 }
4189 else
4190 {
4191 pSoldier->bInterruptDuelPts = NO_INTERRUPT;
4192 }
4193 }
4194 else if ( bSourceSeen )
4195 {
4196 // seen source, in realtime, so check for sighting stuff
4197 HandleBestSightingPositionInRealtime();
4198 }
4199 }
4200
4201 }
4202 }
4203 else // noise made by NOBODY
4204 {
4205 // if noise type was unmistakably that of an explosion (seen or not) or alarm
4206 if (!(pSoldier->uiStatusFlags & SOLDIER_PC))
4207 {
4208 if ( ( ubNoiseType == NOISE_EXPLOSION || ubNoiseType == NOISE_SILENT_ALARM ) && (ubVolume >= 3) )
4209 {
4210 if ( ubNoiseType == NOISE_SILENT_ALARM )
4211 {
4212 WearGasMaskIfAvailable( pSoldier );
4213 }
4214 // if status is only GREEN or YELLOW
4215 if (pSoldier->bAlertStatus < STATUS_RED)
4216 {
4217 // then this soldier goes to status RED, has proof of enemy presence
4218 pSoldier->bAlertStatus = STATUS_RED;
4219 CheckForChangingOrders(pSoldier);
4220 }
4221 }
4222 }
4223 // if the source of the noise can't be seen,
4224 // OR if it's a rock and the listener had to turn so that by the time he
4225 // looked all his saw was a bunch of rocks lying still
4226 if (!bSourceSeen || ((ubNoiseType == NOISE_ROCK_IMPACT) && (bHadToTurn) ) ||
4227 ubNoiseType == NOISE_SILENT_ALARM )
4228 {
4229 // check if the effective volume of this new noise is greater than or at
4230 // least equal to the volume of the currently noticed noise stored
4231 if (ubVolume >= pSoldier->ubNoiseVolume)
4232 {
4233 // yes it is, so remember this noise INSTEAD (old noise is forgotten)
4234 pSoldier->sNoiseGridno = sGridNo;
4235 pSoldier->bNoiseLevel = bLevel;
4236
4237 // no matter how loud noise was, don't remember it for than 12 turns!
4238 if (ubVolume < MAX_MISC_NOISE_DURATION)
4239 {
4240 pSoldier->ubNoiseVolume = ubVolume;
4241 }
4242 else
4243 {
4244 pSoldier->ubNoiseVolume = MAX_MISC_NOISE_DURATION;
4245 }
4246
4247 SetNewSituation( pSoldier ); // force a fresh AI decision to be made
4248 }
4249 }
4250 else
4251 // if listener sees the source of the noise, AND it's either a grenade,
4252 // or it's a rock that he watched land (didn't need to turn)
4253 {
4254 SetNewSituation( pSoldier ); // re-evaluate situation
4255
4256 // if status is only GREEN or YELLOW
4257 if (pSoldier->bAlertStatus < STATUS_RED)
4258 {
4259 // then this soldier goes to status RED, has proof of enemy presence
4260 pSoldier->bAlertStatus = STATUS_RED;
4261 CheckForChangingOrders(pSoldier);
4262 }
4263 }
4264
4265 if ( gubBestToMakeSightingSize == BEST_SIGHTING_ARRAY_SIZE_INCOMBAT )
4266 {
4267 // if the noise heard was the fall of a rock
4268 if ((gTacticalStatus.uiFlags & INCOMBAT) && ubNoiseType == NOISE_ROCK_IMPACT)
4269 {
4270 // give every ELIGIBLE listener an automatic interrupt, since it's
4271 // reasonable to assume the guy throwing wants to wait for their reaction!
4272 if (StandardInterruptConditionsMet(pSoldier, NULL, FALSE))
4273 {
4274 pSoldier->bInterruptDuelPts = AUTOMATIC_INTERRUPT; // force automatic interrupt
4275 SLOGD(
4276 "Calculating int duel pts in noise code, %d has %d points",
4277 pSoldier->ubID, pSoldier->bInterruptDuelPts);
4278 }
4279 else
4280 {
4281 pSoldier->bInterruptDuelPts = NO_INTERRUPT;
4282 }
4283 }
4284 }
4285 }
4286 }
4287
4288
TellPlayerAboutNoise(SOLDIERTYPE * const s,SOLDIERTYPE const * const noise_maker,INT16 const sGridNo,INT8 const level,UINT8 const volume,NoiseKind const noise_type,UINT8 const noise_dir)4289 static void TellPlayerAboutNoise(SOLDIERTYPE* const s, SOLDIERTYPE const* const noise_maker, INT16 const sGridNo, INT8 const level, UINT8 const volume, NoiseKind const noise_type, UINT8 const noise_dir)
4290 {
4291 // CJC: tweaked the noise categories upwards a bit because our movement noises
4292 // can be louder now.
4293 UINT8 const volume_idx =
4294 volume < 4 ? 0 : // 1-3: faint noise
4295 volume < 8 ? 1 : // 4-7: definite noise
4296 volume < 12 ? 2 : // 8-11: loud noise
4297 3; // 12+: very loud noise
4298
4299 if (noise_maker && s->bTeam == OUR_TEAM && s->bTeam == noise_maker->bTeam)
4300 {
4301 SLOGE("%s (%d) heard noise from %s (%d), noise at %dL%d, type %d",
4302 s->name.c_str(), s->ubID, noise_maker->name.c_str(), noise_maker->ubID, sGridNo, level, noise_type);
4303 }
4304
4305 // display a message about a noise, e.g. Sidney hears a loud splash from/to?
4306 // the north.
4307 ST::string direction =
4308 level == s->bLevel ||
4309 noise_type == NOISE_EXPLOSION ||
4310 noise_type == NOISE_SCREAM ||
4311 noise_type == NOISE_ROCK_IMPACT ||
4312 noise_type == NOISE_GRENADE_IMPACT ? pDirectionStr[noise_dir] :
4313 level > s->bLevel ? gzLateLocalizedString[STR_LATE_06] : // From above
4314 gzLateLocalizedString[STR_LATE_07]; // From below
4315 ScreenMsg(MSG_FONT_YELLOW, MSG_INTERFACE, st_format_printf(pNewNoiseStr[noise_type], s->name, pNoiseVolStr[volume_idx], direction));
4316
4317 // If the sound was faint, say something
4318 if (volume_idx == 0 &&
4319 !AreInMeanwhile() &&
4320 !(gTacticalStatus.uiFlags & ENGAGED_IN_CONV) &&
4321 s->ubTurnsUntilCanSayHeardNoise == 0)
4322 {
4323 TacticalCharacterDialogue(s, QUOTE_HEARD_SOMETHING);
4324 s->ubTurnsUntilCanSayHeardNoise = gTacticalStatus.uiFlags & INCOMBAT ? 2 : 5;
4325 }
4326 }
4327
4328
VerifyAndDecayOpplist(SOLDIERTYPE * pSoldier)4329 void VerifyAndDecayOpplist(SOLDIERTYPE *pSoldier)
4330 {
4331 INT8 *pPersOL; // pointer into soldier's opponent list
4332
4333 // reduce all seen/known opponent's turn counters by 1 (towards 0)
4334 // 1) verify accuracy of the opplist by testing sight vs known opponents
4335 // 2) increment opplist value if opponent is known but not currenly seen
4336 // 3) forget about known opponents who haven't been noticed in some time
4337
4338 // if soldier is unconscious, make sure his opplist is wiped out & bail out
4339 if (pSoldier->bLife < OKLIFE)
4340 {
4341 std::fill(std::begin(pSoldier->bOppList), std::end(pSoldier->bOppList), NOT_HEARD_OR_SEEN);
4342 pSoldier->bOppCnt = 0;
4343 return;
4344 }
4345
4346 // if any new opponents were seen earlier and not yet radioed
4347 if (pSoldier->bNewOppCnt)
4348 {
4349 if (pSoldier->uiStatusFlags & SOLDIER_PC)
4350 {
4351 RadioSightings(pSoldier,EVERYBODY,pSoldier->bTeam);
4352 }
4353
4354 pSoldier->bNewOppCnt = 0;
4355 }
4356
4357 // man looks for each of his opponents WHO ARE ALREADY KNOWN TO HIM
4358 FOR_EACH_MERC(i)
4359 {
4360 SOLDIERTYPE* const pOpponent = *i;
4361 // if this merc is active, here, and alive
4362 if (pOpponent->bLife)
4363 {
4364 // if this merc is on the same team, he's no opponent, so skip him
4365 if (pSoldier->bTeam == pOpponent->bTeam)
4366 {
4367 continue;
4368 }
4369
4370 pPersOL = pSoldier->bOppList + pOpponent->ubID;
4371
4372 // if this opponent is "known" in any way (seen or heard recently)
4373 if (*pPersOL != NOT_HEARD_OR_SEEN)
4374 {
4375 // use both sides actual x,y co-ordinates (neither side's moving)
4376 ManLooksForMan(pSoldier,pOpponent,VERIFYANDDECAYOPPLIST);
4377
4378 // decay opplist value if necessary
4379 DECAY_OPPLIST_VALUE( *pPersOL );
4380 /*
4381 // if opponent was SEEN recently but is NOT visible right now
4382 if (*pPersOL >= SEEN_THIS_TURN)
4383 {
4384 (*pPersOL)++; // increment #turns it's been since last seen
4385
4386 // if it's now been longer than the maximum we care to remember
4387 if (*pPersOL > SEEN_2_TURNS_AGO)
4388 *pPersOL = 0; // forget that we knew this guy
4389 }
4390 else
4391 {
4392 // if opponent was merely HEARD recently, not actually seen
4393 if (*pPersOL <= HEARD_THIS_TURN)
4394 {
4395 (*pPersOL)--; // increment #turns it's been since last heard
4396
4397 // if it's now been longer than the maximum we care to remember
4398 if (*pPersOL < HEARD_2_TURNS_AGO)
4399 *pPersOL = 0; // forget that we knew this guy
4400 }
4401 }*/
4402 }
4403
4404 }
4405 }
4406
4407
4408 // if any new opponents were seen
4409 if (pSoldier->bNewOppCnt)
4410 {
4411 // turns out this is NOT an error! If this guy was gassed last time he
4412 // looked, his sight limit was 2 tiles, and now he may no longer be gassed
4413 // and thus he sees opponents much further away for the first time!
4414 // - Always happens if you STUNGRENADE an opponent by surprise...
4415 if (pSoldier->uiStatusFlags & SOLDIER_PC)
4416 RadioSightings(pSoldier,EVERYBODY,pSoldier->bTeam);
4417
4418 pSoldier->bNewOppCnt = 0;
4419 }
4420 }
4421
DecayIndividualOpplist(SOLDIERTYPE * pSoldier)4422 void DecayIndividualOpplist(SOLDIERTYPE *pSoldier)
4423 {
4424 INT8 *pPersOL; // pointer into soldier's opponent list
4425
4426 // reduce all currently seen opponent's turn counters by 1 (towards 0)
4427
4428 // if soldier is unconscious, make sure his opplist is wiped out & bail out
4429 if (pSoldier->bLife < OKLIFE)
4430 {
4431 // must make sure that public opplist is kept to match...
4432 FOR_EACH_SOLDIER(tgt)
4433 {
4434 if (pSoldier->bOppList[tgt->ubID] == SEEN_CURRENTLY)
4435 {
4436 HandleManNoLongerSeen(pSoldier, tgt, &pSoldier->bOppList[tgt->ubID], &gbPublicOpplist[pSoldier->bTeam][tgt->ubID]);
4437 }
4438 }
4439
4440 std::fill(std::begin(pSoldier->bOppList), std::end(pSoldier->bOppList), NOT_HEARD_OR_SEEN);
4441 pSoldier->bOppCnt = 0;
4442 return;
4443 }
4444
4445 // man looks for each of his opponents WHO IS CURRENTLY SEEN
4446 FOR_EACH_MERC(i)
4447 {
4448 const SOLDIERTYPE* const pOpponent = *i;
4449 // if this merc is active, here, and alive
4450 if (pOpponent->bLife)
4451 {
4452 // if this merc is on the same team, he's no opponent, so skip him
4453 if (pSoldier->bTeam == pOpponent->bTeam)
4454 {
4455 continue;
4456 }
4457
4458 pPersOL = pSoldier->bOppList + pOpponent->ubID;
4459
4460 // if this opponent is seen currently
4461 if (*pPersOL == SEEN_CURRENTLY)
4462 {
4463 // they are NOT visible now!
4464 (*pPersOL)++;
4465 if (!CONSIDERED_NEUTRAL(pOpponent, pSoldier) &&
4466 !CONSIDERED_NEUTRAL(pSoldier, pOpponent) &&
4467 (pSoldier->bSide != pOpponent->bSide))
4468 {
4469 RemoveOneOpponent(pSoldier);
4470 }
4471
4472 }
4473 }
4474 }
4475 }
4476
4477
4478
VerifyPublicOpplistDueToDeath(SOLDIERTYPE * pSoldier)4479 void VerifyPublicOpplistDueToDeath(SOLDIERTYPE *pSoldier)
4480 {
4481 // OK, someone died. Anyone that the deceased ALONE saw has to decay
4482 // immediately in the Public Opplist.
4483
4484 // If deceased didn't see ANYONE, don't bother
4485 if (pSoldier->bOppCnt == 0)
4486 {
4487 return;
4488 }
4489
4490
4491 // Deceased looks for each of his opponents who is "seen currently"
4492 FOR_EACH_MERC(i)
4493 {
4494 SOLDIERTYPE* const pOpponent = *i;
4495
4496 // first, initialize flag since this will be a "new" opponent
4497 BOOLEAN bOpponentStillSeen = FALSE;
4498
4499 // if this opponent is active, here, and alive
4500 if (pOpponent->bLife)
4501 {
4502 // if this opponent is on the same team, he's no opponent, so skip him
4503 if (pSoldier->bTeam == pOpponent->bTeam)
4504 {
4505 continue;
4506 }
4507
4508 // point to what the deceased's personal opplist value is
4509 const INT8* const pPersOL = pSoldier->bOppList + pOpponent->ubID;
4510
4511 // if this opponent was CURRENTLY SEEN by the deceased (before his
4512 // untimely demise)
4513 if (*pPersOL == SEEN_CURRENTLY)
4514 {
4515 // then we need to know if any teammates ALSO see this opponent, so loop through
4516 // trying to find ONE witness to the death...
4517 FOR_EACH_MERC(j)
4518 {
4519 const SOLDIERTYPE* const pTeamMate = *j;
4520 // if this teammate is active, here, and alive
4521 if (pTeamMate->bLife)
4522 {
4523 // if this opponent is NOT on the same team, then skip him
4524 if (pTeamMate->bTeam != pSoldier->bTeam)
4525 {
4526 continue;
4527 }
4528
4529 // point to what the teammate's personal opplist value is
4530 const INT8* const pMatePersOL = pTeamMate->bOppList + pOpponent->ubID;
4531
4532 // test to see if this value is "seen currently"
4533 if (*pMatePersOL == SEEN_CURRENTLY)
4534 {
4535 // this opponent HAS been verified!
4536 bOpponentStillSeen = TRUE;
4537
4538 // we can stop looking for other witnesses now
4539 break;
4540 }
4541 }
4542 }
4543 }
4544
4545 // if no witnesses for this opponent, then decay the Public Opplist
4546 if ( !bOpponentStillSeen )
4547 {
4548 DECAY_OPPLIST_VALUE( gbPublicOpplist[pSoldier->bTeam][pOpponent->ubID] );
4549 }
4550 }
4551 }
4552 }
4553
4554
4555 static void DecayWatchedLocs(INT8 bTeam);
4556
4557
DecayPublicOpplist(INT8 bTeam)4558 void DecayPublicOpplist(INT8 bTeam)
4559 {
4560 INT8 bNoPubliclyKnownOpponents = TRUE;
4561 INT8 *pbPublOL;
4562
4563 // decay the team's public noise volume, forget public noise gridno if <= 0
4564 // used to be -1 per turn but that's not fast enough!
4565 if (gubPublicNoiseVolume[bTeam] > 0)
4566 {
4567 if ( gTacticalStatus.uiFlags & INCOMBAT )
4568 {
4569 gubPublicNoiseVolume[bTeam] = (UINT8) ( (UINT32) (gubPublicNoiseVolume[bTeam] * 7) / 10 );
4570 }
4571 else
4572 {
4573 gubPublicNoiseVolume[bTeam] = gubPublicNoiseVolume[bTeam] / 2;
4574 }
4575
4576 if (gubPublicNoiseVolume[bTeam] <= 0)
4577 {
4578 gsPublicNoiseGridno[bTeam] = NOWHERE;
4579 }
4580 }
4581
4582 // decay the team's Public Opplist
4583 FOR_EACH_MERC(i)
4584 {
4585 SOLDIERTYPE* const pSoldier = *i;
4586 // for every living soldier on ANOTHER team
4587 if (pSoldier->bLife && pSoldier->bTeam != bTeam)
4588 {
4589 // hang a pointer to the byte holding team's public opplist for this merc
4590 pbPublOL = &gbPublicOpplist[bTeam][pSoldier->ubID];
4591
4592 if (*pbPublOL == NOT_HEARD_OR_SEEN)
4593 {
4594 continue;
4595 }
4596
4597 // well, that make this a "publicly known opponent", so nuke that flag
4598 bNoPubliclyKnownOpponents = FALSE;
4599
4600 // if this person has been SEEN recently, but is not currently visible
4601 if (*pbPublOL >= SEEN_THIS_TURN)
4602 {
4603 (*pbPublOL)++; // increment how long it's been
4604 }
4605 else
4606 {
4607 // if this person has been only HEARD recently
4608 if (*pbPublOL <= HEARD_THIS_TURN)
4609 {
4610 (*pbPublOL)--; // increment how long it's been
4611 }
4612 }
4613
4614 // if it's been longer than the maximum we care to remember
4615 if ((*pbPublOL > OLDEST_SEEN_VALUE) || (*pbPublOL < OLDEST_HEARD_VALUE))
4616 {
4617 // forget about him,
4618 // and also forget where he was last seen (it's been too long)
4619 // this is mainly so POINT_PATROL guys don't SEEK_OPPONENTs forever
4620 UpdatePublic(bTeam, pSoldier, NOT_HEARD_OR_SEEN, NOWHERE, 0);
4621 }
4622 }
4623 }
4624
4625 // if all opponents are publicly unknown (NOT_HEARD_OR_SEEN)
4626 if (bNoPubliclyKnownOpponents)
4627 {
4628 // forget about the last radio alert (ie. throw away who made the call)
4629 // this is mainly so POINT_PATROL guys don't SEEK_FRIEND forever after
4630 gTacticalStatus.Team[bTeam].last_merc_to_radio = NULL;
4631 }
4632
4633 // decay watched locs as well
4634 DecayWatchedLocs( bTeam );
4635 }
4636
4637 // bit of a misnomer; this is now decay all opplists
NonCombatDecayPublicOpplist(UINT32 uiTime)4638 void NonCombatDecayPublicOpplist( UINT32 uiTime )
4639 {
4640 if ( uiTime - gTacticalStatus.uiTimeSinceLastOpplistDecay >= TIME_BETWEEN_RT_OPPLIST_DECAYS)
4641 {
4642 // decay!
4643 FOR_EACH_MERC(i) VerifyAndDecayOpplist(*i);
4644
4645 for (UINT32 cnt = 0; cnt < MAXTEAMS; ++cnt)
4646 {
4647 if (IsTeamActive(cnt)) DecayPublicOpplist((INT8)cnt);
4648 }
4649 // update time
4650 gTacticalStatus.uiTimeSinceLastOpplistDecay = uiTime;
4651 }
4652 }
4653
RecalculateOppCntsDueToNoLongerNeutral(SOLDIERTYPE * pSoldier)4654 void RecalculateOppCntsDueToNoLongerNeutral( SOLDIERTYPE * pSoldier )
4655 {
4656 pSoldier->bOppCnt = 0;
4657
4658 if (!pSoldier->bNeutral)
4659 {
4660 FOR_EACH_MERC(i)
4661 {
4662 SOLDIERTYPE* const pOpponent = *i;
4663 // for every living soldier on ANOTHER team
4664 if (pOpponent->bLife &&
4665 !pOpponent->bNeutral &&
4666 pOpponent->bTeam != pSoldier->bTeam &&
4667 !CONSIDERED_NEUTRAL(pOpponent, pSoldier) &&
4668 !CONSIDERED_NEUTRAL(pSoldier, pOpponent) &&
4669 pSoldier->bSide != pOpponent->bSide)
4670 {
4671 if ( pSoldier->bOppList[pOpponent->ubID] == SEEN_CURRENTLY )
4672 {
4673 AddOneOpponent( pSoldier );
4674 }
4675 if ( pOpponent->bOppList[pSoldier->ubID] == SEEN_CURRENTLY )
4676 {
4677 // have to add to opponent's oppcount as well since we just became non-neutral
4678 AddOneOpponent( pOpponent );
4679 }
4680 }
4681 }
4682 }
4683 }
4684
RecalculateOppCntsDueToBecomingNeutral(SOLDIERTYPE * pSoldier)4685 void RecalculateOppCntsDueToBecomingNeutral( SOLDIERTYPE * pSoldier )
4686 {
4687 if (pSoldier->bNeutral)
4688 {
4689 pSoldier->bOppCnt = 0;
4690
4691 FOR_EACH_MERC(i)
4692 {
4693 SOLDIERTYPE* const pOpponent = *i;
4694 // for every living soldier on ANOTHER team
4695 if (pOpponent->bLife &&
4696 !pOpponent->bNeutral &&
4697 pOpponent->bTeam != pSoldier->bTeam &&
4698 !CONSIDERED_NEUTRAL(pSoldier, pOpponent) &&
4699 pSoldier->bSide != pOpponent->bSide)
4700 {
4701 if ( pOpponent->bOppList[pSoldier->ubID] == SEEN_CURRENTLY )
4702 {
4703 // have to rem from opponent's oppcount as well since we just became neutral
4704 RemoveOneOpponent( pOpponent );
4705 }
4706 }
4707 }
4708 }
4709 }
4710
NoticeUnseenAttacker(SOLDIERTYPE * pAttacker,SOLDIERTYPE * pDefender,INT8 bReason)4711 void NoticeUnseenAttacker( SOLDIERTYPE * pAttacker, SOLDIERTYPE * pDefender, INT8 bReason )
4712 {
4713 INT8 bOldOppList;
4714 UINT8 ubTileSightLimit;
4715 BOOLEAN fSeesAttacker = FALSE;
4716 INT8 bDirection;
4717 BOOLEAN fMuzzleFlash = FALSE;
4718
4719 if ( !(gTacticalStatus.uiFlags & INCOMBAT) )
4720 {
4721 return;
4722 }
4723
4724 if ( pAttacker->usAttackingWeapon == DART_GUN )
4725 {
4726 // rarely noticed
4727 if ( SkillCheck( pDefender, NOTICE_DART_CHECK, 0 ) < 0)
4728 {
4729 return;
4730 }
4731 }
4732
4733 // do we need to do checks for life/breath here?
4734
4735 if ( pDefender->ubBodyType == LARVAE_MONSTER || (pDefender->uiStatusFlags & SOLDIER_VEHICLE &&
4736 pDefender->bTeam == OUR_TEAM) )
4737 {
4738 return;
4739 }
4740
4741 bOldOppList = pDefender->bOppList[ pAttacker->ubID ];
4742 if ( PythSpacesAway( pAttacker->sGridNo, pDefender->sGridNo ) <= MaxDistanceVisible() )
4743 {
4744 // check LOS, considering we are now aware of the attacker
4745 // ignore muzzle flashes when must turning head
4746 if ( pAttacker->fMuzzleFlash )
4747 {
4748 bDirection = atan8( pDefender->sX,pDefender->sY, pAttacker->sX, pAttacker->sY );
4749 if (pDefender->bDirection != bDirection &&
4750 pDefender->bDirection != OneCDirection(bDirection) &&
4751 pDefender->bDirection != OneCCDirection(bDirection))
4752 {
4753 // temporarily turn off muzzle flash so DistanceVisible can be calculated without it
4754 pAttacker->fMuzzleFlash = FALSE;
4755 fMuzzleFlash = TRUE;
4756 }
4757 }
4758
4759 ubTileSightLimit = (UINT8) DistanceVisible( pDefender, DIRECTION_IRRELEVANT, 0, pAttacker->sGridNo, pAttacker->bLevel );
4760 if (SoldierToSoldierLineOfSightTest( pDefender, pAttacker, ubTileSightLimit, TRUE ) != 0)
4761 {
4762 fSeesAttacker = TRUE;
4763 }
4764 if ( fMuzzleFlash )
4765 {
4766 pAttacker->fMuzzleFlash = TRUE;
4767 }
4768 }
4769
4770 if (fSeesAttacker)
4771 {
4772 ManSeesMan(*pDefender, *pAttacker, CALLER_UNKNOWN);
4773
4774 // newOppCnt not needed here (no radioing), must get reset right away
4775 // CJC: Huh? well, leave it in for now
4776 pDefender->bNewOppCnt = 0;
4777
4778
4779 if (pDefender->bTeam == OUR_TEAM)
4780 {
4781 // EXPERIENCE GAIN (5): Victim notices/sees a previously UNSEEN attacker
4782 StatChange(*pDefender, EXPERAMT, 5, FROM_SUCCESS);
4783
4784 // mark attacker as being SEEN right now
4785 RadioSightings(pDefender, pAttacker, pDefender->bTeam);
4786 }
4787 // NOTE: ENEMIES DON'T REPORT A SIGHTING PUBLICLY UNTIL THEY RADIO IT IN!
4788 else
4789 {
4790 // go to threatening stance
4791 ReevaluateEnemyStance( pDefender, pDefender->usAnimState );
4792 }
4793 }
4794 else // victim NOTICED the attack, but CAN'T SEE the actual attacker
4795 {
4796 SetNewSituation( pDefender ); // re-evaluate situation
4797
4798 // if victim's alert status is only GREEN or YELLOW
4799 if (pDefender->bAlertStatus < STATUS_RED)
4800 {
4801 // then this soldier goes to status RED, has proof of enemy presence
4802 pDefender->bAlertStatus = STATUS_RED;
4803 CheckForChangingOrders( pDefender );
4804 }
4805
4806 UpdatePersonal( pDefender, pAttacker->ubID, HEARD_THIS_TURN, pAttacker->sGridNo, pAttacker->bLevel );
4807
4808 // if the victim is a human-controlled soldier, instantly report publicly
4809 if (pDefender->uiStatusFlags & SOLDIER_PC)
4810 {
4811 // mark attacker as having been PUBLICLY heard THIS TURN & remember where
4812 UpdatePublic(pDefender->bTeam, pAttacker, HEARD_THIS_TURN, pAttacker->sGridNo, pAttacker->bLevel);
4813 }
4814 }
4815
4816 if (StandardInterruptConditionsMet(pDefender, pAttacker, bOldOppList))
4817 {
4818 SLOGD(
4819 "INTERRUPT: NoticeUnseenAttacker, standard\
4820 conditions are met; defender %d, attacker %d\n\
4821 Calculating int duel pts for defender in NUA",
4822 pDefender->ubID, pAttacker->ubID);
4823
4824 // calculate the interrupt duel points
4825 pDefender->bInterruptDuelPts = CalcInterruptDuelPts(pDefender, pAttacker, FALSE);
4826 }
4827 else
4828 {
4829 pDefender->bInterruptDuelPts = NO_INTERRUPT;
4830 }
4831
4832 // say quote
4833
4834 if (pDefender->bInterruptDuelPts != NO_INTERRUPT)
4835 {
4836 // check for possible interrupt and handle control change if it happens
4837 // this code is basically ResolveInterruptsVs for 1 man only...
4838
4839 // calculate active soldier's dueling pts for the upcoming interrupt duel
4840 SLOGD("Calculating int duel pts for attacker in NUA");
4841 pAttacker->bInterruptDuelPts = CalcInterruptDuelPts(pAttacker, pDefender, FALSE);
4842 if ( InterruptDuel( pDefender, pAttacker ) )
4843 {
4844 SLOGD(
4845 "INTERRUPT: NoticeUnseenAttacker, defender\
4846 pts %d, attacker pts %d, defender gets interrupt",
4847 pDefender->bInterruptDuelPts, pAttacker->bInterruptDuelPts);
4848 AddToIntList(pAttacker, FALSE, TRUE);
4849 AddToIntList(pDefender, TRUE, TRUE);
4850 DoneAddingToIntList();
4851 }
4852 // either way, clear out both sides' duelPts fields to prepare next duel
4853 SLOGD("Resetting int pts for %d and %d in NUA",
4854 pDefender->ubID, pAttacker->ubID );
4855 pDefender->bInterruptDuelPts = NO_INTERRUPT;
4856 pAttacker->bInterruptDuelPts = NO_INTERRUPT;
4857 }
4858 }
4859
4860
CheckForAlertWhenEnemyDies(SOLDIERTYPE * pDyingSoldier)4861 void CheckForAlertWhenEnemyDies(SOLDIERTYPE* pDyingSoldier)
4862 {
4863 INT8 bDir;
4864 INT16 sDistAway, sDistVisible;
4865
4866 FOR_EACH_IN_TEAM(pSoldier, pDyingSoldier->bTeam)
4867 {
4868 if (pSoldier->bInSector && pSoldier != pDyingSoldier && pSoldier->bLife >= OKLIFE &&
4869 pSoldier->bAlertStatus < STATUS_RED)
4870 {
4871 // this guy might have seen the man die
4872
4873 // distance we "see" then depends on the direction he is located from us
4874 bDir = atan8(pSoldier->sX,pSoldier->sY,pDyingSoldier->sX,pDyingSoldier->sY);
4875 sDistVisible = DistanceVisible(pSoldier, pSoldier->bDesiredDirection, bDir,
4876 pDyingSoldier->sGridNo, pDyingSoldier->bLevel);
4877 sDistAway = PythSpacesAway( pSoldier->sGridNo, pDyingSoldier->sGridNo );
4878
4879 // if we see close enough to see the soldier
4880 if (sDistAway <= sDistVisible)
4881 {
4882 // and we can trace a line of sight to his x,y coordinates
4883 // assume enemies are always aware of their buddies...
4884 if (SoldierTo3DLocationLineOfSightTest(pSoldier, pDyingSoldier->sGridNo,
4885 pDyingSoldier->bLevel, 0,
4886 (UINT8) sDistVisible, TRUE))
4887 {
4888 pSoldier->bAlertStatus = STATUS_RED;
4889 CheckForChangingOrders( pSoldier );
4890 }
4891 }
4892 }
4893 }
4894 }
4895
4896
MercSeesCreature(SOLDIERTYPE const & s)4897 bool MercSeesCreature(SOLDIERTYPE const& s)
4898 {
4899 if (s.bOppCnt <= 0) return false;
4900 CFOR_EACH_IN_TEAM(i, CREATURE_TEAM)
4901 {
4902 SOLDIERTYPE const& tgt = *i;
4903 if (!(tgt.uiStatusFlags & SOLDIER_MONSTER)) continue;
4904 if (!s.bOppList[tgt.ubID]) continue;
4905 return true;
4906 }
4907 return false;
4908 }
4909
4910
FindUnusedWatchedLoc(UINT8 ubID)4911 static INT8 FindUnusedWatchedLoc(UINT8 ubID)
4912 {
4913 INT8 bLoop;
4914
4915 for ( bLoop = 0; bLoop < NUM_WATCHED_LOCS; bLoop++ )
4916 {
4917 if ( gsWatchedLoc[ ubID ][ bLoop ] == NOWHERE )
4918 {
4919 return( bLoop );
4920 }
4921 }
4922 return( -1 );
4923 }
4924
4925
FindWatchedLocWithLessThanXPointsLeft(UINT8 ubID,UINT8 ubPointLimit)4926 static INT8 FindWatchedLocWithLessThanXPointsLeft(UINT8 ubID, UINT8 ubPointLimit)
4927 {
4928 INT8 bLoop;
4929
4930 for ( bLoop = 0; bLoop < NUM_WATCHED_LOCS; bLoop++ )
4931 {
4932 if ( gsWatchedLoc[ ubID ][ bLoop ] != NOWHERE && gubWatchedLocPoints[ ubID ][ bLoop ] <= ubPointLimit )
4933 {
4934 return( bLoop );
4935 }
4936 }
4937 return( -1 );
4938 }
4939
4940
FindWatchedLoc(UINT8 ubID,INT16 sGridNo,INT8 bLevel)4941 static INT8 FindWatchedLoc(UINT8 ubID, INT16 sGridNo, INT8 bLevel)
4942 {
4943 INT8 bLoop;
4944
4945 for ( bLoop = 0; bLoop < NUM_WATCHED_LOCS; bLoop++ )
4946 {
4947 if ( gsWatchedLoc[ ubID ][ bLoop ] != NOWHERE && gbWatchedLocLevel[ ubID ][ bLoop ] == bLevel )
4948 {
4949 if ( SpacesAway( gsWatchedLoc[ ubID ][ bLoop ], sGridNo ) <= WATCHED_LOC_RADIUS )
4950 {
4951 return( bLoop );
4952 }
4953 }
4954 }
4955 return( -1 );
4956 }
4957
GetWatchedLocPoints(UINT8 ubID,INT16 sGridNo,INT8 bLevel)4958 INT8 GetWatchedLocPoints( UINT8 ubID, INT16 sGridNo, INT8 bLevel )
4959 {
4960 INT8 bLoc;
4961
4962 bLoc = FindWatchedLoc( ubID, sGridNo, bLevel );
4963 if (bLoc != -1)
4964 {
4965 if (gubWatchedLocPoints[ ubID ][ bLoc ] > 1)
4966 {
4967 SLOGD("Soldier %d getting %d points for interrupt in watched location", ubID, gubWatchedLocPoints[ ubID ][ bLoc ] - 1 );
4968 }
4969 // one loc point is worth nothing, so return number minus 1
4970
4971 // experiment with 1 loc point being worth 1 point
4972 return( gubWatchedLocPoints[ ubID ][ bLoc ] );
4973 }
4974
4975 return( 0 );
4976 }
4977
4978
GetHighestVisibleWatchedLoc(const SOLDIERTYPE * const s)4979 INT8 GetHighestVisibleWatchedLoc(const SOLDIERTYPE* const s)
4980 {
4981 const INT16* const loc_id = gsWatchedLoc[s->ubID];
4982 const UINT8* const pts_id = gubWatchedLocPoints[s->ubID];
4983 const INT8* const lvl_id = gbWatchedLocLevel[s->ubID];
4984 INT8 bHighestLoc = -1;
4985 INT8 bHighestPoints = 0;
4986 for (INT8 bLoop = 0; bLoop < NUM_WATCHED_LOCS; ++bLoop)
4987 {
4988 const INT16 loc = loc_id[bLoop];
4989 if (loc == NOWHERE) continue;
4990
4991 const UINT8 pts = pts_id[bLoop];
4992 if (pts <= bHighestPoints) continue;
4993
4994 const INT8 lvl = lvl_id[bLoop];
4995 const INT16 sDistVisible = DistanceVisible(s, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, loc, lvl);
4996 // look at standing height
4997 if (SoldierTo3DLocationLineOfSightTest(s, loc, lvl, 3, sDistVisible, TRUE))
4998 {
4999 bHighestLoc = bLoop;
5000 bHighestPoints = pts;
5001 }
5002 }
5003 return bHighestLoc;
5004 }
5005
5006
GetHighestWatchedLocPoints(const SOLDIERTYPE * const s)5007 INT8 GetHighestWatchedLocPoints(const SOLDIERTYPE* const s)
5008 {
5009 const INT16* const loc_id = gsWatchedLoc[s->ubID];
5010 const UINT8* const pts_id = gubWatchedLocPoints[s->ubID];
5011 INT8 bHighestPoints = 0;
5012 for (INT8 bLoop = 0; bLoop < NUM_WATCHED_LOCS; ++bLoop)
5013 {
5014 if (loc_id[bLoop] == NOWHERE) continue;
5015
5016 const UINT8 pts = pts_id[bLoop];
5017 if (pts > bHighestPoints) bHighestPoints = pts;
5018 }
5019 return bHighestPoints;
5020 }
5021
5022
CommunicateWatchedLoc(const SOLDIERTYPE * const watcher,const INT16 sGridNo,const INT8 bLevel,const UINT8 ubPoints)5023 static void CommunicateWatchedLoc(const SOLDIERTYPE* const watcher, const INT16 sGridNo, const INT8 bLevel, const UINT8 ubPoints)
5024 {
5025 INT8 bLoopPoint, bPoint;
5026
5027 const INT8 bTeam = watcher->bTeam;
5028
5029 CFOR_EACH_IN_TEAM(s, bTeam)
5030 {
5031 if (s == watcher) continue;
5032 if (!s->bInSector || s->bLife < OKLIFE)
5033 {
5034 continue;
5035 }
5036 const SoldierID ubLoop = s->ubID;
5037 bLoopPoint = FindWatchedLoc( ubLoop, sGridNo, bLevel );
5038 if ( bLoopPoint == -1 )
5039 {
5040 // add this as a watched point
5041 bPoint = FindUnusedWatchedLoc( ubLoop );
5042 if (bPoint == -1)
5043 {
5044 // if we have a point with only 1 point left, replace it
5045 bPoint = FindWatchedLocWithLessThanXPointsLeft( ubLoop, ubPoints );
5046 }
5047 if (bPoint != -1)
5048 {
5049 gsWatchedLoc[ ubLoop ][ bPoint ] = sGridNo;
5050 gbWatchedLocLevel[ ubLoop ][ bPoint ] = bLevel;
5051 gubWatchedLocPoints[ ubLoop ][ bPoint ] = ubPoints;
5052 gfWatchedLocReset[ ubLoop ][ bPoint ] = FALSE;
5053 gfWatchedLocHasBeenIncremented[ ubLoop ][ bPoint ] = TRUE;
5054 }
5055 // else no points available!
5056 }
5057 else
5058 {
5059 // increment to max
5060 gubWatchedLocPoints[ ubLoop ][ bLoopPoint ] = __max( gubWatchedLocPoints[ ubLoop ][ bLoopPoint ], ubPoints );
5061
5062 gfWatchedLocReset[ ubLoop ][ bLoopPoint ] = FALSE;
5063 gfWatchedLocHasBeenIncremented[ ubLoop ][ bLoopPoint ] = TRUE;
5064 }
5065 }
5066 }
5067
5068
IncrementWatchedLoc(const SOLDIERTYPE * const watcher,const INT16 sGridNo,const INT8 bLevel)5069 static void IncrementWatchedLoc(const SOLDIERTYPE* const watcher, const INT16 sGridNo, const INT8 bLevel)
5070 {
5071 const SoldierID ubID = watcher->ubID;
5072 INT8 bPoint;
5073
5074 bPoint = FindWatchedLoc( ubID, sGridNo, bLevel );
5075 if (bPoint == -1)
5076 {
5077 // try adding point
5078 bPoint = FindUnusedWatchedLoc( ubID );
5079 if (bPoint == -1)
5080 {
5081 // if we have a point with only 1 point left, replace it
5082 bPoint = FindWatchedLocWithLessThanXPointsLeft( ubID, 1 );
5083 }
5084
5085 if (bPoint != -1)
5086 {
5087 gsWatchedLoc[ ubID ][ bPoint ] = sGridNo;
5088 gbWatchedLocLevel[ ubID ][ bPoint ] = bLevel;
5089 gubWatchedLocPoints[ ubID ][ bPoint ] = 1;
5090 gfWatchedLocReset[ ubID ][ bPoint ] = FALSE;
5091 gfWatchedLocHasBeenIncremented[ ubID ][ bPoint ] = TRUE;
5092
5093 CommunicateWatchedLoc(watcher, sGridNo, bLevel, 1);
5094 }
5095 // otherwise abort; no points available
5096 }
5097 else
5098 {
5099 if ( !gfWatchedLocHasBeenIncremented[ ubID ][ bPoint ] && gubWatchedLocPoints[ ubID ][ bPoint ] < MAX_WATCHED_LOC_POINTS )
5100 {
5101 gubWatchedLocPoints[ ubID ][ bPoint ]++;
5102 CommunicateWatchedLoc(watcher, sGridNo, bLevel, gubWatchedLocPoints[ubID][bPoint]);
5103 }
5104 gfWatchedLocReset[ ubID ][ bPoint ] = FALSE;
5105 gfWatchedLocHasBeenIncremented[ ubID ][ bPoint ] = TRUE;
5106 }
5107 }
5108
5109
SetWatchedLocAsUsed(UINT8 ubID,INT16 sGridNo,INT8 bLevel)5110 static void SetWatchedLocAsUsed(UINT8 ubID, INT16 sGridNo, INT8 bLevel)
5111 {
5112 INT8 bPoint;
5113
5114 bPoint = FindWatchedLoc( ubID, sGridNo, bLevel );
5115 if (bPoint != -1)
5116 {
5117 gfWatchedLocReset[ ubID ][ bPoint ] = FALSE;
5118 }
5119 }
5120
5121
WatchedLocLocationIsEmpty(INT16 sGridNo,INT8 bLevel,INT8 bTeam)5122 static BOOLEAN WatchedLocLocationIsEmpty(INT16 sGridNo, INT8 bLevel, INT8 bTeam)
5123 {
5124 // look to see if there is anyone near the watched loc who is not on this team
5125 INT16 sTempGridNo, sX, sY;
5126
5127 for ( sY = -WATCHED_LOC_RADIUS; sY <= WATCHED_LOC_RADIUS; sY++ )
5128 {
5129 for ( sX = -WATCHED_LOC_RADIUS; sX <= WATCHED_LOC_RADIUS; sX++ )
5130 {
5131 sTempGridNo = sGridNo + sX + sY * WORLD_ROWS;
5132 if ( sTempGridNo < 0 || sTempGridNo >= WORLD_MAX )
5133 {
5134 continue;
5135 }
5136 const SOLDIERTYPE* const tgt = WhoIsThere2(sTempGridNo, bLevel);
5137 if (tgt != NULL && tgt->bTeam != bTeam) return FALSE;
5138 }
5139 }
5140 return( TRUE );
5141 }
5142
5143
DecayWatchedLocs(INT8 bTeam)5144 static void DecayWatchedLocs(INT8 bTeam)
5145 {
5146 UINT8 cnt, cnt2;
5147
5148 // loop through all soldiers
5149 for ( cnt = gTacticalStatus.Team[ bTeam ].bFirstID; cnt <= gTacticalStatus.Team[ bTeam ].bLastID; cnt++ )
5150 {
5151 // for each watched location
5152 for ( cnt2 = 0; cnt2 < NUM_WATCHED_LOCS; cnt2++ )
5153 {
5154 if (gsWatchedLoc[cnt][cnt2] != NOWHERE &&
5155 WatchedLocLocationIsEmpty(gsWatchedLoc[cnt][cnt2], gbWatchedLocLevel[cnt][cnt2], bTeam))
5156 {
5157 // if the reset flag is still set, then we should decay this point
5158 if (gfWatchedLocReset[ cnt ][ cnt2 ])
5159 {
5160 // turn flag off again
5161 gfWatchedLocReset[ cnt ][ cnt2 ] = FALSE;
5162
5163 // halve points
5164 gubWatchedLocPoints[ cnt ][ cnt2 ] /= 2;
5165 // if points have reached 0, then reset the location
5166 if (gubWatchedLocPoints[ cnt ][ cnt2 ] == 0)
5167 {
5168 gsWatchedLoc[ cnt ][ cnt2 ] = NOWHERE;
5169 }
5170 }
5171 else
5172 {
5173 // flag was false so set to true (will be reset if new people seen there next turn)
5174 gfWatchedLocReset[ cnt ][ cnt2 ] = TRUE;
5175 }
5176 }
5177 }
5178 }
5179 }
5180
5181
MakeBloodcatsHostile(void)5182 static void MakeBloodcatsHostile(void)
5183 {
5184 FOR_EACH_IN_TEAM(s, CREATURE_TEAM)
5185 {
5186 if (s->ubBodyType == BLOODCAT && s->bInSector && s->bLife > 0)
5187 {
5188 SetSoldierNonNeutral(s);
5189 RecalculateOppCntsDueToNoLongerNeutral(s);
5190 if (gTacticalStatus.uiFlags & INCOMBAT)
5191 {
5192 CheckForPotentialAddToBattleIncrement(s);
5193 }
5194 }
5195 }
5196 }
5197