TeamTurns.c 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050
  1. #ifdef PRECOMPILEDHEADERS
  2. #include "Tactical All.h"
  3. #else
  4. #include "types.h"
  5. #include "soldier control.h"
  6. #include "overhead.h"
  7. #include "animation control.h"
  8. #include "points.h"
  9. #include "opplist.h"
  10. #include "timer.h"
  11. #include "event pump.h"
  12. #include "Sound Control.h"
  13. #include "interface.h"
  14. #include "Isometric Utils.h"
  15. #include "Font Control.H"
  16. #include "ai.h"
  17. #include "interface.h"
  18. #include "message.h"
  19. #include "text.h"
  20. #include "TeamTurns.h"
  21. #include "Smell.h"
  22. #include "game clock.h"
  23. #include "Soldier Functions.h"
  24. #include "cursors.h"
  25. #include "Queen Command.h"
  26. #include "Pathai.h"
  27. #include "Music Control.h"
  28. #include "Strategic Turns.h"
  29. #include "lighting.h"
  30. #include "environment.h"
  31. #include "Explosion Control.h"
  32. #include "dialogue control.h"
  33. #include "Soldier Profile Type.h"
  34. #include "SmokeEffects.h"
  35. #include "lighteffects.h"
  36. #include "air raid.h"
  37. #include "meanwhile.h"
  38. #include "SkillCheck.h"
  39. #include "AIInternals.h"
  40. #include "AIList.h"
  41. #ifdef DEBUG_INTERRUPTS
  42. #include "debug.h"
  43. #endif
  44. #endif
  45. extern void DecayPublicOpplist(INT8 bTeam);
  46. extern void VerifyAndDecayOpplist(SOLDIERTYPE *pSoldier);
  47. void EndInterrupt( BOOLEAN fMarkInterruptOccurred );
  48. void DeleteFromIntList( UINT8 ubIndex, BOOLEAN fCommunicate);
  49. #define END_OF_INTERRUPTS 255
  50. UINT8 gubOutOfTurnOrder[MAXMERCS] = { END_OF_INTERRUPTS, 0 };
  51. UINT8 gubOutOfTurnPersons = 0;
  52. #define LATEST_INTERRUPT_GUY (gubOutOfTurnOrder[gubOutOfTurnPersons])
  53. #define REMOVE_LATEST_INTERRUPT_GUY() (DeleteFromIntList( (UINT8) (gubOutOfTurnPersons), TRUE ))
  54. #define INTERRUPTS_OVER (gubOutOfTurnPersons == 1)
  55. INT16 InterruptOnlyGuynum = NOBODY;
  56. BOOLEAN InterruptsAllowed = TRUE;
  57. BOOLEAN gfHiddenInterrupt = FALSE;
  58. UINT8 gubLastInterruptedGuy = 0;
  59. extern INT16 gsWhoThrewRock;
  60. extern UINT8 gubSightFlags;
  61. typedef struct
  62. {
  63. UINT8 ubOutOfTurnPersons;
  64. INT16 InterruptOnlyGuynum;
  65. INT16 sWhoThrewRock;
  66. BOOLEAN InterruptsAllowed;
  67. BOOLEAN fHiddenInterrupt;
  68. UINT8 ubLastInterruptedGuy;
  69. UINT8 ubFiller[16];
  70. } TEAM_TURN_SAVE_STRUCT;
  71. #define MIN_APS_TO_INTERRUPT 4
  72. void ClearIntList( void )
  73. {
  74. memset( &gubOutOfTurnOrder, 0, MAXMERCS );
  75. gubOutOfTurnOrder[0] = END_OF_INTERRUPTS;
  76. gubOutOfTurnPersons = 0;
  77. }
  78. BOOLEAN BloodcatsPresent( void )
  79. {
  80. INT32 iLoop;
  81. SOLDIERTYPE * pSoldier;
  82. if ( gTacticalStatus.Team[ CREATURE_TEAM ].bTeamActive == FALSE )
  83. {
  84. return( FALSE );
  85. }
  86. for ( iLoop = gTacticalStatus.Team[ CREATURE_TEAM ].bFirstID; iLoop <= gTacticalStatus.Team[ CREATURE_TEAM ].bLastID; iLoop++ )
  87. {
  88. pSoldier = MercPtrs[ iLoop ];
  89. if ( pSoldier->bActive && pSoldier->bInSector && pSoldier->bLife > 0 && pSoldier->ubBodyType == BLOODCAT )
  90. {
  91. return( TRUE );
  92. }
  93. }
  94. return( FALSE );
  95. }
  96. void StartPlayerTeamTurn( BOOLEAN fDoBattleSnd, BOOLEAN fEnteringCombatMode )
  97. {
  98. INT32 cnt;
  99. // SOLDIERTYPE *pSoldier;
  100. // EV_S_BEGINTURN SBeginTurn;
  101. // Start the turn of player charactors
  102. //
  103. // PATCH 1.06:
  104. //
  105. // make sure set properly in gTacticalStatus:
  106. gTacticalStatus.ubCurrentTeam = OUR_TEAM;
  107. cnt = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
  108. InitPlayerUIBar( FALSE );
  109. if ( gTacticalStatus.uiFlags & TURNBASED )
  110. {
  111. // Are we in combat already?
  112. if ( gTacticalStatus.uiFlags & INCOMBAT )
  113. {
  114. PlayJA2Sample( ENDTURN_1, RATE_11025, MIDVOLUME, 1, MIDDLEPAN );
  115. }
  116. // Remove deadlock message
  117. EndDeadlockMsg( );
  118. // Check for victory conditions
  119. // ATE: Commented out - looks like this message is called earlier for our team
  120. // look for all mercs on the same team,
  121. //for ( pSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; cnt++,pSoldier++)
  122. //{
  123. // if ( pSoldier->bActive && pSoldier->bLife > 0 )
  124. // {
  125. // SBeginTurn.usSoldierID = (UINT16)cnt;
  126. // AddGameEvent( S_BEGINTURN, 0, &SBeginTurn );
  127. // }
  128. //}
  129. // Are we in combat already?
  130. if ( gTacticalStatus.uiFlags & INCOMBAT )
  131. {
  132. if ( gusSelectedSoldier != NO_SOLDIER )
  133. {
  134. // Check if this guy is able to be selected....
  135. if ( MercPtrs[ gusSelectedSoldier ]->bLife < OKLIFE )
  136. {
  137. SelectNextAvailSoldier( MercPtrs[ gusSelectedSoldier ] );
  138. }
  139. // Slide to selected guy...
  140. if ( gusSelectedSoldier != NO_SOLDIER )
  141. {
  142. SlideTo( NOWHERE, gusSelectedSoldier, NOBODY ,SETLOCATOR);
  143. if ( fDoBattleSnd )
  144. {
  145. // Say ATTENTION SOUND...
  146. DoMercBattleSound( MercPtrs[ gusSelectedSoldier ], BATTLE_SOUND_ATTN1 );
  147. }
  148. if ( gsInterfaceLevel == 1 )
  149. {
  150. gTacticalStatus.uiFlags |= SHOW_ALL_ROOFS;
  151. InvalidateWorldRedundency( );
  152. SetRenderFlags(RENDER_FLAG_FULL);
  153. ErasePath(FALSE);
  154. }
  155. }
  156. }
  157. }
  158. // Dirty panel interface!
  159. fInterfacePanelDirty = DIRTYLEVEL2;
  160. // Adjust time now!
  161. UpdateClock( );
  162. if ( !fEnteringCombatMode )
  163. {
  164. CheckForEndOfCombatMode( TRUE );
  165. }
  166. }
  167. // Signal UI done enemy's turn
  168. guiPendingOverrideEvent = LU_ENDUILOCK;
  169. // ATE: Reset killed on attack variable.. this is because sometimes timing is such
  170. /// that a baddie can die and still maintain it's attacker ID
  171. gTacticalStatus.fKilledEnemyOnAttack = FALSE;
  172. HandleTacticalUI( );
  173. }
  174. void FreezeInterfaceForEnemyTurn( void )
  175. {
  176. // Reset flags
  177. gfPlotNewMovement = TRUE;
  178. // Erase path
  179. ErasePath( TRUE );
  180. // Setup locked UI
  181. guiPendingOverrideEvent = LU_BEGINUILOCK;
  182. // Remove any UI messages!
  183. if ( giUIMessageOverlay != -1 )
  184. {
  185. EndUIMessage( );
  186. }
  187. }
  188. void EndTurn( UINT8 ubNextTeam )
  189. {
  190. SOLDIERTYPE * pSoldier;
  191. INT32 cnt;
  192. //Check for enemy pooling (add enemies if there happens to be more than the max in the
  193. //current battle. If one or more slots have freed up, we can add them now.
  194. EndDeadlockMsg( );
  195. /*
  196. if ( CheckForEndOfCombatMode( FALSE ) )
  197. {
  198. return;
  199. }
  200. */
  201. if (INTERRUPT_QUEUED)
  202. {
  203. EndInterrupt( FALSE );
  204. }
  205. else
  206. {
  207. AddPossiblePendingEnemiesToBattle();
  208. // InitEnemyUIBar( );
  209. FreezeInterfaceForEnemyTurn();
  210. // Loop through all mercs and set to moved
  211. cnt = gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bFirstID;
  212. for ( pSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bLastID; cnt++,pSoldier++)
  213. {
  214. if ( pSoldier->bActive )
  215. {
  216. pSoldier->bMoved = TRUE;
  217. }
  218. }
  219. gTacticalStatus.ubCurrentTeam = ubNextTeam;
  220. BeginTeamTurn( gTacticalStatus.ubCurrentTeam );
  221. BetweenTurnsVisibilityAdjustments();
  222. }
  223. }
  224. void EndAITurn( void )
  225. {
  226. SOLDIERTYPE * pSoldier;
  227. INT32 cnt;
  228. // Remove any deadlock message
  229. EndDeadlockMsg( );
  230. if (INTERRUPT_QUEUED)
  231. {
  232. EndInterrupt( FALSE );
  233. }
  234. else
  235. {
  236. cnt = gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bFirstID;
  237. for ( pSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bLastID; cnt++,pSoldier++)
  238. {
  239. if ( pSoldier->bActive )
  240. {
  241. pSoldier->bMoved = TRUE;
  242. // record old life value... for creature AI; the human AI might
  243. // want to use this too at some point
  244. pSoldier->bOldLife = pSoldier->bLife;
  245. }
  246. }
  247. gTacticalStatus.ubCurrentTeam++;
  248. BeginTeamTurn( gTacticalStatus.ubCurrentTeam );
  249. }
  250. }
  251. void EndAllAITurns( void )
  252. {
  253. // warp turn to the player's turn
  254. SOLDIERTYPE * pSoldier;
  255. INT32 cnt;
  256. // Remove any deadlock message
  257. EndDeadlockMsg( );
  258. if (INTERRUPT_QUEUED)
  259. {
  260. EndInterrupt( FALSE );
  261. }
  262. if ( gTacticalStatus.ubCurrentTeam != gbPlayerNum )
  263. {
  264. cnt = gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bFirstID;
  265. for ( pSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bLastID; cnt++,pSoldier++)
  266. {
  267. if ( pSoldier->bActive )
  268. {
  269. pSoldier->bMoved = TRUE;
  270. pSoldier->uiStatusFlags &= (~SOLDIER_UNDERAICONTROL);
  271. // record old life value... for creature AI; the human AI might
  272. // want to use this too at some point
  273. pSoldier->bOldLife = pSoldier->bLife;
  274. }
  275. }
  276. gTacticalStatus.ubCurrentTeam = gbPlayerNum;
  277. //BeginTeamTurn( gTacticalStatus.ubCurrentTeam );
  278. }
  279. }
  280. void EndTurnEvents( void )
  281. {
  282. // HANDLE END OF TURN EVENTS
  283. // handle team services like healing
  284. HandleTeamServices( gbPlayerNum );
  285. // handle smell and blood decay
  286. DecaySmells();
  287. // decay bomb timers and maybe set some off!
  288. DecayBombTimers();
  289. DecaySmokeEffects( GetWorldTotalSeconds( ) );
  290. DecayLightEffects( GetWorldTotalSeconds( ) );
  291. // decay AI warning values from corpses
  292. DecayRottingCorpseAIWarnings();
  293. }
  294. void BeginTeamTurn( UINT8 ubTeam )
  295. {
  296. INT32 cnt;
  297. UINT8 ubID;
  298. SOLDIERTYPE *pSoldier;
  299. while( 1 )
  300. {
  301. if ( ubTeam > LAST_TEAM )
  302. {
  303. if ( HandleAirRaidEndTurn( ubTeam ) )
  304. {
  305. // End turn!!
  306. ubTeam = gbPlayerNum;
  307. gTacticalStatus.ubCurrentTeam = gbPlayerNum;
  308. EndTurnEvents();
  309. }
  310. else
  311. {
  312. break;
  313. }
  314. }
  315. else if (!(gTacticalStatus.Team[ ubTeam ].bTeamActive))
  316. {
  317. // inactive team, skip to the next one
  318. ubTeam++;
  319. gTacticalStatus.ubCurrentTeam++;
  320. // skip back to the top, as we are processing another team now.
  321. continue;
  322. }
  323. if ( gTacticalStatus.uiFlags & TURNBASED )
  324. {
  325. BeginLoggingForBleedMeToos( TRUE );
  326. // decay team's public opplist
  327. DecayPublicOpplist( ubTeam );
  328. cnt = gTacticalStatus.Team[ ubTeam ].bFirstID;
  329. for ( pSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ ubTeam ].bLastID; cnt++,pSoldier++)
  330. {
  331. if ( pSoldier->bActive && pSoldier->bLife > 0)
  332. {
  333. // decay personal opplist, and refresh APs and BPs
  334. EVENT_BeginMercTurn( pSoldier, FALSE, 0 );
  335. }
  336. }
  337. if (gTacticalStatus.bBoxingState == LOST_ROUND || gTacticalStatus.bBoxingState == WON_ROUND || gTacticalStatus.bBoxingState == DISQUALIFIED )
  338. {
  339. // we have no business being in here any more!
  340. return;
  341. }
  342. BeginLoggingForBleedMeToos( FALSE );
  343. }
  344. if (ubTeam == gbPlayerNum )
  345. {
  346. // ATE: Check if we are still in a valid battle...
  347. // ( they could have blead to death above )
  348. if ( ( gTacticalStatus.uiFlags & INCOMBAT ) )
  349. {
  350. StartPlayerTeamTurn( TRUE, FALSE );
  351. }
  352. break;
  353. }
  354. else
  355. {
  356. #ifdef NETWORKED
  357. // Only the host should do this
  358. if(!gfAmIHost)
  359. break;
  360. #endif
  361. // Set First enemy merc to AI control
  362. if ( BuildAIListForTeam( ubTeam ) )
  363. {
  364. ubID = RemoveFirstAIListEntry();
  365. if (ubID != NOBODY)
  366. {
  367. // Dirty panel interface!
  368. fInterfacePanelDirty = DIRTYLEVEL2;
  369. if ( ubTeam == CREATURE_TEAM && BloodcatsPresent() )
  370. {
  371. AddTopMessage( COMPUTER_TURN_MESSAGE, Message[ STR_BLOODCATS_TURN ] );
  372. }
  373. else
  374. {
  375. AddTopMessage( COMPUTER_TURN_MESSAGE, TeamTurnString[ ubTeam ] );
  376. }
  377. StartNPCAI( MercPtrs[ ubID ] );
  378. return;
  379. }
  380. }
  381. // This team is dead/inactive/being skipped in boxing
  382. // skip back to the top to process the next team
  383. ubTeam++;
  384. gTacticalStatus.ubCurrentTeam++;
  385. }
  386. }
  387. }
  388. void DisplayHiddenInterrupt( SOLDIERTYPE * pSoldier )
  389. {
  390. // If the AI got an interrupt but this has been hidden from the player until this point,
  391. // this code will display the interrupt
  392. if (!gfHiddenInterrupt)
  393. {
  394. return;
  395. }
  396. EndDeadlockMsg( );
  397. if (pSoldier->bVisible != -1 )
  398. {
  399. SlideTo( NOWHERE, pSoldier->ubID, NOBODY ,SETLOCATOR);
  400. }
  401. guiPendingOverrideEvent = LU_BEGINUILOCK;
  402. // Dirty panel interface!
  403. fInterfacePanelDirty = DIRTYLEVEL2;
  404. // Erase path!
  405. ErasePath( TRUE );
  406. // Reset flags
  407. gfPlotNewMovement = TRUE;
  408. // Stop our guy....
  409. AdjustNoAPToFinishMove( MercPtrs[ LATEST_INTERRUPT_GUY ], TRUE );
  410. // Stop him from going to prone position if doing a turn while prone
  411. MercPtrs[ LATEST_INTERRUPT_GUY ]->fTurningFromPronePosition = FALSE;
  412. // get rid of any old overlay message
  413. if ( pSoldier->bTeam == MILITIA_TEAM )
  414. {
  415. AddTopMessage( MILITIA_INTERRUPT_MESSAGE, Message[ STR_INTERRUPT ] );
  416. }
  417. else
  418. {
  419. AddTopMessage( COMPUTER_INTERRUPT_MESSAGE, Message[ STR_INTERRUPT ] );
  420. }
  421. gfHiddenInterrupt = FALSE;
  422. }
  423. void DisplayHiddenTurnbased( SOLDIERTYPE * pActingSoldier )
  424. {
  425. // This code should put the game in turn-based and give control to the AI-controlled soldier
  426. // whose pointer has been passed in as an argument (we were in non-combat and the AI is doing
  427. // something visible, i.e. making an attack)
  428. if ( AreInMeanwhile( ) )
  429. {
  430. return;
  431. }
  432. if (gTacticalStatus.uiFlags & REALTIME || gTacticalStatus.uiFlags & INCOMBAT)
  433. {
  434. // pointless call here; do nothing
  435. return;
  436. }
  437. // Enter combat mode starting with this side's turn
  438. gTacticalStatus.ubCurrentTeam = pActingSoldier->bTeam;
  439. CommonEnterCombatModeCode( );
  440. //JA2Gold: use function to make sure flags turned off everywhere else
  441. //pActingSoldier->uiStatusFlags |= SOLDIER_UNDERAICONTROL;
  442. SetSoldierAsUnderAiControl( pActingSoldier );
  443. DebugAI( String( "Giving AI control to %d", pActingSoldier->ubID ) );
  444. pActingSoldier->fTurnInProgress = TRUE;
  445. gTacticalStatus.uiTimeSinceMercAIStart = GetJA2Clock();
  446. if ( gTacticalStatus.ubTopMessageType != COMPUTER_TURN_MESSAGE)
  447. {
  448. // Dirty panel interface!
  449. fInterfacePanelDirty = DIRTYLEVEL2;
  450. if ( gTacticalStatus.ubCurrentTeam == CREATURE_TEAM && BloodcatsPresent() )
  451. {
  452. AddTopMessage( COMPUTER_TURN_MESSAGE, Message[ STR_BLOODCATS_TURN ] );
  453. }
  454. else
  455. {
  456. AddTopMessage( COMPUTER_TURN_MESSAGE, TeamTurnString[ gTacticalStatus.ubCurrentTeam ] );
  457. }
  458. }
  459. // freeze the user's interface
  460. FreezeInterfaceForEnemyTurn();
  461. }
  462. BOOLEAN EveryoneInInterruptListOnSameTeam( void )
  463. {
  464. UINT8 ubLoop;
  465. UINT8 ubTeam = 255;
  466. for (ubLoop = 1; ubLoop <= gubOutOfTurnPersons; ubLoop++)
  467. {
  468. if ( ubTeam == 255 )
  469. {
  470. ubTeam = MercPtrs[ gubOutOfTurnOrder[ ubLoop ] ]->bTeam;
  471. }
  472. else
  473. {
  474. if ( MercPtrs[ gubOutOfTurnOrder[ ubLoop ] ]->bTeam != ubTeam )
  475. {
  476. return( FALSE );
  477. }
  478. }
  479. }
  480. return( TRUE );
  481. }
  482. void StartInterrupt( void )
  483. {
  484. UINT8 ubFirstInterrupter;
  485. INT8 bTeam;
  486. SOLDIERTYPE * pSoldier;
  487. SOLDIERTYPE * pTempSoldier;
  488. UINT8 ubInterrupter;
  489. INT32 cnt;
  490. ubFirstInterrupter = LATEST_INTERRUPT_GUY;
  491. pSoldier = MercPtrs[ubFirstInterrupter];
  492. bTeam = pSoldier->bTeam;
  493. ubInterrupter = ubFirstInterrupter;
  494. // display everyone on int queue!
  495. for ( cnt = gubOutOfTurnPersons; cnt > 0; cnt-- )
  496. {
  497. DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("STARTINT: Q position %d: %d", cnt, gubOutOfTurnOrder[ cnt ] ) );
  498. }
  499. //DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("INTERRUPT: %d is now on top of the interrupt queue", ubFirstInterrupter ) );
  500. gTacticalStatus.fInterruptOccurred = TRUE;
  501. cnt = 0;
  502. for ( pTempSoldier = MercPtrs[ cnt ]; cnt <= MAX_NUM_SOLDIERS; cnt++,pTempSoldier++)
  503. {
  504. if ( pTempSoldier->bActive )
  505. {
  506. pTempSoldier->bMovedPriorToInterrupt = pTempSoldier->bMoved;
  507. pTempSoldier->bMoved = TRUE;
  508. }
  509. }
  510. if (pSoldier->bTeam == OUR_TEAM)
  511. {
  512. // start interrupts for everyone on our side at once
  513. INT16 sTemp[ 255 ];
  514. UINT8 ubInterrupters = 0;
  515. INT32 iSquad, iCounter;
  516. // build string for display of who gets interrupt
  517. while( 1 )
  518. {
  519. MercPtrs[ubInterrupter]->bMoved = FALSE;
  520. DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("INTERRUPT: popping %d off of the interrupt queue", ubInterrupter ) );
  521. REMOVE_LATEST_INTERRUPT_GUY();
  522. // now LATEST_INTERRUPT_GUY is the guy before the previous
  523. ubInterrupter = LATEST_INTERRUPT_GUY;
  524. if (ubInterrupter == NOBODY) // previously emptied slot!
  525. {
  526. continue;
  527. }
  528. else if (MercPtrs[ubInterrupter]->bTeam != bTeam)
  529. {
  530. break;
  531. }
  532. }
  533. wcscpy( sTemp, Message[ STR_INTERRUPT_FOR ] );
  534. // build string in separate loop here, want to linearly process squads...
  535. for ( iSquad = 0; iSquad < NUMBER_OF_SQUADS; iSquad++ )
  536. {
  537. for ( iCounter = 0; iCounter < NUMBER_OF_SOLDIERS_PER_SQUAD; iCounter++ )
  538. {
  539. pTempSoldier = Squad[ iSquad ][ iCounter ];
  540. if ( pTempSoldier && pTempSoldier->bActive && pTempSoldier->bInSector && !pTempSoldier->bMoved )
  541. {
  542. // then this guy got an interrupt...
  543. ubInterrupters++;
  544. if ( ubInterrupters > 6 )
  545. {
  546. // flush... display string, then clear it (we could have 20 names!)
  547. // add comma to end, we know we have another person after this...
  548. wcscat( sTemp, L", " );
  549. ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, sTemp );
  550. wcscpy( sTemp, L"" );
  551. ubInterrupters = 1;
  552. }
  553. if ( ubInterrupters > 1 )
  554. {
  555. wcscat( sTemp, L", " );
  556. }
  557. wcscat( sTemp, pTempSoldier->name );
  558. }
  559. }
  560. }
  561. ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, sTemp );
  562. DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("INTERRUPT: starting interrupt for %d", ubFirstInterrupter ) );
  563. // gusSelectedSoldier should become the topmost guy on the interrupt list
  564. //gusSelectedSoldier = ubFirstInterrupter;
  565. // Remove deadlock message
  566. EndDeadlockMsg( );
  567. // Select guy....
  568. SelectSoldier( ubFirstInterrupter, TRUE, TRUE );
  569. // ATE; Slide to guy who got interrupted!
  570. SlideTo( NOWHERE, gubLastInterruptedGuy, NOBODY, SETLOCATOR);
  571. // Dirty panel interface!
  572. fInterfacePanelDirty = DIRTYLEVEL2;
  573. gTacticalStatus.ubCurrentTeam = pSoldier->bTeam;
  574. // Signal UI done enemy's turn
  575. guiPendingOverrideEvent = LU_ENDUILOCK;
  576. HandleTacticalUI( );
  577. InitPlayerUIBar( TRUE );
  578. //AddTopMessage( PLAYER_INTERRUPT_MESSAGE, Message[STR_INTERRUPT] );
  579. PlayJA2Sample( ENDTURN_1, RATE_11025, MIDVOLUME, 1, MIDDLEPAN );
  580. // report any close call quotes for us here
  581. for ( iCounter = gTacticalStatus.Team[ gbPlayerNum ].bFirstID; iCounter <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; iCounter++ )
  582. {
  583. if ( OK_INSECTOR_MERC( MercPtrs[ iCounter ] ) )
  584. {
  585. if ( MercPtrs[ iCounter ]->fCloseCall )
  586. {
  587. if ( MercPtrs[ iCounter ]->bNumHitsThisTurn == 0 && !(MercPtrs[ iCounter ]->usQuoteSaidExtFlags & SOLDIER_QUOTE_SAID_EXT_CLOSE_CALL) && Random( 3 ) == 0 )
  588. {
  589. // say close call quote!
  590. TacticalCharacterDialogue( MercPtrs[ iCounter ], QUOTE_CLOSE_CALL );
  591. MercPtrs[ iCounter ]->usQuoteSaidExtFlags |= SOLDIER_QUOTE_SAID_EXT_CLOSE_CALL;
  592. }
  593. MercPtrs[ iCounter ]->fCloseCall = FALSE;
  594. }
  595. }
  596. }
  597. }
  598. else
  599. {
  600. // start interrupts for everyone on that side at once... and start AI with the lowest # guy
  601. // what we do is set everyone to moved except for people with interrupts at the moment
  602. /*
  603. cnt = gTacticalStatus.Team[ pSoldier->bTeam ].bFirstID;
  604. for ( pTempSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ pSoldier->bTeam ].bLastID; cnt++,pTempSoldier++)
  605. {
  606. if ( pTempSoldier->bActive )
  607. {
  608. pTempSoldier->bMovedPriorToInterrupt = pTempSoldier->bMoved;
  609. pTempSoldier->bMoved = TRUE;
  610. }
  611. }
  612. */
  613. while( 1 )
  614. {
  615. MercPtrs[ubInterrupter]->bMoved = FALSE;
  616. DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("INTERRUPT: popping %d off of the interrupt queue", ubInterrupter ) );
  617. REMOVE_LATEST_INTERRUPT_GUY();
  618. // now LATEST_INTERRUPT_GUY is the guy before the previous
  619. ubInterrupter = LATEST_INTERRUPT_GUY;
  620. if (ubInterrupter == NOBODY) // previously emptied slot!
  621. {
  622. continue;
  623. }
  624. else if (MercPtrs[ubInterrupter]->bTeam != bTeam)
  625. {
  626. break;
  627. }
  628. else if (ubInterrupter < ubFirstInterrupter)
  629. {
  630. ubFirstInterrupter = ubInterrupter;
  631. }
  632. }
  633. // here we have to rebuilt the AI list!
  634. BuildAIListForTeam( bTeam );
  635. // set to the new first interrupter
  636. cnt = RemoveFirstAIListEntry();
  637. pSoldier = MercPtrs[ cnt ];
  638. // pSoldier = MercPtrs[ubFirstInterrupter];
  639. //if ( gTacticalStatus.ubCurrentTeam == OUR_TEAM)
  640. if ( pSoldier->bTeam != OUR_TEAM )
  641. {
  642. // we're being interrupted by the computer!
  643. // we delay displaying any interrupt message until the computer
  644. // does something...
  645. gfHiddenInterrupt = TRUE;
  646. gTacticalStatus.fUnLockUIAfterHiddenInterrupt = FALSE;
  647. }
  648. // otherwise it's the AI interrupting another AI team
  649. gTacticalStatus.ubCurrentTeam = pSoldier->bTeam;
  650. #ifdef JA2BETAVERSION
  651. ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_TESTVERSION, L"Interrupt ( could be hidden )" );
  652. #endif
  653. StartNPCAI( pSoldier );
  654. }
  655. if ( !gfHiddenInterrupt )
  656. {
  657. // Stop this guy....
  658. AdjustNoAPToFinishMove( MercPtrs[ LATEST_INTERRUPT_GUY ], TRUE );
  659. MercPtrs[ LATEST_INTERRUPT_GUY ]->fTurningFromPronePosition = FALSE;
  660. }
  661. }
  662. void EndInterrupt( BOOLEAN fMarkInterruptOccurred )
  663. {
  664. UINT8 ubInterruptedSoldier;
  665. SOLDIERTYPE * pSoldier;
  666. SOLDIERTYPE * pTempSoldier;
  667. INT32 cnt;
  668. BOOLEAN fFound;
  669. UINT8 ubMinAPsToAttack;
  670. for ( cnt = gubOutOfTurnPersons; cnt > 0; cnt-- )
  671. {
  672. DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("ENDINT: Q position %d: %d", cnt, gubOutOfTurnOrder[ cnt ] ) );
  673. }
  674. // ATE: OK, now if this all happended on one frame, we may not have to stop
  675. // guy from walking... so set this flag to false if so...
  676. if ( fMarkInterruptOccurred )
  677. {
  678. // flag as true if an int occurs which ends an interrupt (int loop)
  679. gTacticalStatus.fInterruptOccurred = TRUE;
  680. }
  681. else
  682. {
  683. gTacticalStatus.fInterruptOccurred = FALSE;
  684. }
  685. // Loop through all mercs and see if any passed on this interrupt
  686. cnt = gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bFirstID;
  687. for ( pTempSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bLastID; cnt++,pTempSoldier++)
  688. {
  689. if ( pTempSoldier->bActive && pTempSoldier->bInSector && !pTempSoldier->bMoved && (pTempSoldier->bActionPoints == pTempSoldier->bIntStartAPs))
  690. {
  691. ubMinAPsToAttack = MinAPsToAttack( pTempSoldier, pTempSoldier->sLastTarget, FALSE );
  692. if ( (ubMinAPsToAttack <= pTempSoldier->bActionPoints) && (ubMinAPsToAttack > 0) )
  693. {
  694. pTempSoldier->bPassedLastInterrupt = TRUE;
  695. }
  696. }
  697. }
  698. if ( !EveryoneInInterruptListOnSameTeam() )
  699. {
  700. gfHiddenInterrupt = FALSE;
  701. // resume interrupted interrupt
  702. StartInterrupt();
  703. }
  704. else
  705. {
  706. ubInterruptedSoldier = LATEST_INTERRUPT_GUY;
  707. DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("INTERRUPT: interrupt over, %d's team regains control", ubInterruptedSoldier ) );
  708. pSoldier = MercPtrs[ubInterruptedSoldier];
  709. cnt = 0;
  710. for ( pTempSoldier = MercPtrs[ cnt ]; cnt <= MAX_NUM_SOLDIERS; cnt++,pTempSoldier++)
  711. {
  712. if ( pTempSoldier->bActive )
  713. {
  714. // AI guys only here...
  715. if ( pTempSoldier->bActionPoints == 0 )
  716. {
  717. pTempSoldier->bMoved = TRUE;
  718. }
  719. else if ( pTempSoldier->bTeam != gbPlayerNum && pTempSoldier->bNewSituation == IS_NEW_SITUATION )
  720. {
  721. pTempSoldier->bMoved = FALSE;
  722. }
  723. else
  724. {
  725. pTempSoldier->bMoved = pTempSoldier->bMovedPriorToInterrupt;
  726. }
  727. }
  728. }
  729. // change team
  730. gTacticalStatus.ubCurrentTeam = pSoldier->bTeam;
  731. // switch appropriate messages & flags
  732. if ( pSoldier->bTeam == OUR_TEAM)
  733. {
  734. // set everyone on the team to however they were set moved before the interrupt
  735. // must do this before selecting soldier...
  736. /*
  737. cnt = gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bFirstID;
  738. for ( pTempSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bLastID; cnt++,pTempSoldier++)
  739. {
  740. if ( pTempSoldier->bActive )
  741. {
  742. pTempSoldier->bMoved = pTempSoldier->bMovedPriorToInterrupt;
  743. }
  744. }
  745. */
  746. ClearIntList();
  747. // Select soldier....
  748. if ( MercPtrs[ ubInterruptedSoldier ]->bLife < OKLIFE )
  749. {
  750. SelectNextAvailSoldier( MercPtrs[ ubInterruptedSoldier ] );
  751. }
  752. else
  753. {
  754. SelectSoldier( ubInterruptedSoldier, FALSE, FALSE );
  755. }
  756. if (gfHiddenInterrupt)
  757. {
  758. // Try to make things look like nothing happened at all.
  759. gfHiddenInterrupt = FALSE;
  760. // If we can continue a move, do so!
  761. if ( MercPtrs[ gusSelectedSoldier ]->fNoAPToFinishMove && pSoldier->ubReasonCantFinishMove != REASON_STOPPED_SIGHT )
  762. {
  763. // Continue
  764. AdjustNoAPToFinishMove( MercPtrs[ gusSelectedSoldier ], FALSE );
  765. if ( MercPtrs[ gusSelectedSoldier ]->sGridNo != MercPtrs[ gusSelectedSoldier ]->sFinalDestination )
  766. {
  767. EVENT_GetNewSoldierPath( MercPtrs[ gusSelectedSoldier ], MercPtrs[ gusSelectedSoldier ]->sFinalDestination, MercPtrs[ gusSelectedSoldier ]->usUIMovementMode );
  768. }
  769. else
  770. {
  771. UnSetUIBusy( pSoldier->ubID );
  772. }
  773. }
  774. else
  775. {
  776. UnSetUIBusy( pSoldier->ubID );
  777. }
  778. if ( gTacticalStatus.fUnLockUIAfterHiddenInterrupt )
  779. {
  780. gTacticalStatus.fUnLockUIAfterHiddenInterrupt = FALSE;
  781. UnSetUIBusy( pSoldier->ubID );
  782. }
  783. }
  784. else
  785. {
  786. // Signal UI done enemy's turn
  787. /// ATE: This used to be ablow so it would get done for
  788. // both hidden interrupts as well - NOT good because
  789. // hidden interrupts should leave it locked if it was already...
  790. guiPendingOverrideEvent = LU_ENDUILOCK;
  791. HandleTacticalUI( );
  792. if ( gusSelectedSoldier != NO_SOLDIER )
  793. {
  794. SlideTo( NOWHERE, gusSelectedSoldier, NOBODY ,SETLOCATOR);
  795. // Say ATTENTION SOUND...
  796. DoMercBattleSound( MercPtrs[ gusSelectedSoldier ], BATTLE_SOUND_ATTN1 );
  797. if ( gsInterfaceLevel == 1 )
  798. {
  799. gTacticalStatus.uiFlags |= SHOW_ALL_ROOFS;
  800. InvalidateWorldRedundency( );
  801. SetRenderFlags(RENDER_FLAG_FULL);
  802. ErasePath(FALSE);
  803. }
  804. }
  805. // 2 indicates that we're ending an interrupt and going back to
  806. // normal player's turn without readjusting time left in turn (for
  807. // timed turns)
  808. InitPlayerUIBar( 2 );
  809. }
  810. }
  811. else
  812. {
  813. // this could be set to true for AI-vs-AI interrupts
  814. gfHiddenInterrupt = FALSE;
  815. // Dirty panel interface!
  816. fInterfacePanelDirty = DIRTYLEVEL2;
  817. // Erase path!
  818. ErasePath( TRUE );
  819. // Reset flags
  820. gfPlotNewMovement = TRUE;
  821. // restart AI with first available soldier
  822. fFound = FALSE;
  823. // rebuild list for this team if anyone on the team is still available
  824. cnt = gTacticalStatus.Team[ ENEMY_TEAM ].bFirstID;
  825. for ( pTempSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bLastID; cnt++,pTempSoldier++)
  826. {
  827. if ( pTempSoldier->bActive && pTempSoldier->bInSector && pTempSoldier->bLife >= OKLIFE )
  828. {
  829. fFound = TRUE;
  830. break;
  831. }
  832. }
  833. if ( fFound )
  834. {
  835. // reset found flag because we are rebuilding the AI list
  836. fFound = FALSE;
  837. if ( BuildAIListForTeam( gTacticalStatus.ubCurrentTeam ) )
  838. {
  839. // now bubble up everyone left in the interrupt queue, starting
  840. // at the front of the array
  841. for (cnt = 1; cnt <= gubOutOfTurnPersons; cnt++)
  842. {
  843. MoveToFrontOfAIList( gubOutOfTurnOrder[ cnt ] );
  844. }
  845. cnt = RemoveFirstAIListEntry();
  846. if (cnt != NOBODY)
  847. {
  848. fFound = TRUE;
  849. StartNPCAI( MercPtrs[ cnt ] );
  850. }
  851. }
  852. }
  853. if (fFound)
  854. {
  855. // back to the computer!
  856. if ( gTacticalStatus.ubCurrentTeam == CREATURE_TEAM && BloodcatsPresent() )
  857. {
  858. AddTopMessage( COMPUTER_TURN_MESSAGE, Message[ STR_BLOODCATS_TURN ] );
  859. }
  860. else
  861. {
  862. AddTopMessage( COMPUTER_TURN_MESSAGE, TeamTurnString[ gTacticalStatus.ubCurrentTeam ] );
  863. }
  864. // Signal UI done enemy's turn
  865. guiPendingOverrideEvent = LU_BEGINUILOCK;
  866. ClearIntList();
  867. }
  868. else
  869. {
  870. // back to the computer!
  871. if ( gTacticalStatus.ubCurrentTeam == CREATURE_TEAM && BloodcatsPresent() )
  872. {
  873. AddTopMessage( COMPUTER_TURN_MESSAGE, Message[ STR_BLOODCATS_TURN ] );
  874. }
  875. else
  876. {
  877. AddTopMessage( COMPUTER_TURN_MESSAGE, TeamTurnString[ gTacticalStatus.ubCurrentTeam ] );
  878. }
  879. // Signal UI done enemy's turn
  880. guiPendingOverrideEvent = LU_BEGINUILOCK;
  881. // must clear int list before ending turn
  882. ClearIntList();
  883. EndAITurn();
  884. }
  885. }
  886. // Reset our interface!
  887. fInterfacePanelDirty = DIRTYLEVEL2;
  888. }
  889. }
  890. BOOLEAN StandardInterruptConditionsMet( SOLDIERTYPE * pSoldier, UINT8 ubOpponentID, INT8 bOldOppList)
  891. {
  892. // UINT8 ubAniType;
  893. UINT8 ubMinPtsNeeded;
  894. INT8 bDir;
  895. SOLDIERTYPE * pOpponent;
  896. if ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) && !(gubSightFlags & SIGHT_INTERRUPT) )
  897. {
  898. return( FALSE );
  899. }
  900. if ( gTacticalStatus.ubAttackBusyCount > 0 )
  901. {
  902. return( FALSE );
  903. }
  904. if (ubOpponentID < NOBODY)
  905. {
  906. /*
  907. // only the OPPONENT'S controller's decision matters
  908. if (Menptr[ubOpponentID].controller != Net.pnum)
  909. {
  910. return(FALSE);
  911. }
  912. */
  913. // ALEX
  914. // if interrupts are restricted to a particular opponent only & he's not it
  915. if ((InterruptOnlyGuynum != NOBODY) && (ubOpponentID != InterruptOnlyGuynum))
  916. {
  917. return(FALSE);
  918. }
  919. pOpponent = MercPtrs[ ubOpponentID ];
  920. }
  921. else // no opponent, so controller of 'ptr' makes the call instead
  922. {
  923. // ALEX
  924. if (gsWhoThrewRock >= NOBODY)
  925. {
  926. #ifdef BETAVERSION
  927. NumMessage("StandardInterruptConditions: ERROR - ubOpponentID is NOBODY, don't know who threw rock, guynum = ",pSoldier->guynum);
  928. #endif
  929. return(FALSE);
  930. }
  931. // the machine that controls the guy who threw the rock makes the decision
  932. /*
  933. if (Menptr[WhoThrewRock].controller != Net.pnum)
  934. return(FALSE);
  935. */
  936. pOpponent = NULL;
  937. }
  938. // if interrupts have been disabled for any reason
  939. if (!InterruptsAllowed)
  940. {
  941. return(FALSE);
  942. }
  943. // in non-combat allow interrupt points to be calculated freely (everyone's in control!)
  944. // also allow calculation for storing in AllTeamsLookForAll
  945. if ( (gTacticalStatus.uiFlags & INCOMBAT) && ( gubBestToMakeSightingSize != BEST_SIGHTING_ARRAY_SIZE_ALL_TEAMS_LOOK_FOR_ALL ) )
  946. {
  947. // if his team's already in control
  948. if (pSoldier->bTeam == gTacticalStatus.ubCurrentTeam )
  949. {
  950. // if this is a player's a merc or civilian
  951. if ((pSoldier->uiStatusFlags & SOLDIER_PC) || PTR_CIVILIAN)
  952. {
  953. // then they are not allowed to interrupt their own team
  954. return(FALSE);
  955. }
  956. else
  957. {
  958. // enemies, MAY interrupt each other, but NOT themselves!
  959. //if ( pSoldier->uiStatusFlags & SOLDIER_UNDERAICONTROL )
  960. //{
  961. return(FALSE);
  962. //}
  963. }
  964. // CJC, July 9 1998
  965. // NO ONE EVER interrupts his own team
  966. //return( FALSE );
  967. }
  968. else if ( gTacticalStatus.bBoxingState != NOT_BOXING )
  969. {
  970. // while anything to do with boxing is going on, skip interrupts!
  971. return( FALSE );
  972. }
  973. }
  974. if ( !(pSoldier->bActive) || !(pSoldier->bInSector ) )
  975. {
  976. return( FALSE );
  977. }
  978. // soldiers at less than OKLIFE can't perform any actions
  979. if (pSoldier->bLife < OKLIFE)
  980. {
  981. return(FALSE);
  982. }
  983. // soldiers out of breath are about to fall over, no interrupt
  984. if (pSoldier->bBreath < OKBREATH || pSoldier->bCollapsed )
  985. {
  986. return(FALSE);
  987. }
  988. // if soldier doesn't have enough APs
  989. if ( pSoldier->bActionPoints < MIN_APS_TO_INTERRUPT )
  990. {
  991. return( FALSE );
  992. }
  993. // soldiers gagging on gas are too busy about holding their cookies down...
  994. if ( pSoldier->uiStatusFlags & SOLDIER_GASSED )
  995. {
  996. return(FALSE);
  997. }
  998. // a soldier already engaged in a life & death battle is too busy doing his
  999. // best to survive to worry about "getting the jump" on additional threats
  1000. if (pSoldier->bUnderFire)
  1001. {
  1002. return(FALSE);
  1003. }
  1004. if (pSoldier->bCollapsed)
  1005. {
  1006. return( FALSE );
  1007. }
  1008. // don't allow neutral folks to get interrupts
  1009. if (pSoldier->bNeutral)
  1010. {
  1011. return( FALSE );
  1012. }
  1013. // no EPCs allowed to get interrupts
  1014. if ( AM_AN_EPC( pSoldier ) && !AM_A_ROBOT( pSoldier ) )
  1015. {
  1016. return( FALSE );
  1017. }
  1018. // don't let mercs on assignment get interrupts
  1019. if ( pSoldier->bTeam == gbPlayerNum && pSoldier->bAssignment >= ON_DUTY)
  1020. {
  1021. return( FALSE );
  1022. }
  1023. // the bare minimum default is enough APs left to TURN
  1024. ubMinPtsNeeded = AP_CHANGE_FACING;
  1025. // if the opponent is SOMEBODY
  1026. if (ubOpponentID < NOBODY)
  1027. {
  1028. // if the soldiers are on the same side
  1029. if (pSoldier->bSide == pOpponent->bSide)
  1030. {
  1031. // human/civilians on same side can't interrupt each other
  1032. if ((pSoldier->uiStatusFlags & SOLDIER_PC) || PTR_CIVILIAN)
  1033. {
  1034. return(FALSE);
  1035. }
  1036. else // enemy
  1037. {
  1038. // enemies can interrupt EACH OTHER, but enemies and civilians on the
  1039. // same side (but different teams) can't interrupt each other.
  1040. if (pSoldier->bTeam != pOpponent->bTeam)
  1041. {
  1042. return(FALSE);
  1043. }
  1044. }
  1045. }
  1046. // if the interrupted opponent is not the selected character, then the only
  1047. // people eligible to win an interrupt are those on the SAME SIDE AS
  1048. // the selected character, ie. his friends...
  1049. if ( pOpponent->bTeam == gbPlayerNum )
  1050. {
  1051. if ((ubOpponentID != gusSelectedSoldier) && (pSoldier->bSide != Menptr[gusSelectedSoldier].bSide))
  1052. {
  1053. return( FALSE );
  1054. }
  1055. }
  1056. else
  1057. {
  1058. if ( !(pOpponent->uiStatusFlags & SOLDIER_UNDERAICONTROL) && (pSoldier->bSide != pOpponent->bSide))
  1059. {
  1060. return( FALSE );
  1061. }
  1062. }
  1063. /* old DG code for same:
  1064. if ((ubOpponentID != gusSelectedSoldier) && (pSoldier->bSide != Menptr[gusSelectedSoldier].bSide))
  1065. {
  1066. return(FALSE);
  1067. }
  1068. */
  1069. // an non-active soldier can't interrupt a soldier who is also non-active!
  1070. if ((pOpponent->bTeam != gTacticalStatus.ubCurrentTeam) && (pSoldier->bTeam != gTacticalStatus.ubCurrentTeam))
  1071. {
  1072. return(FALSE);
  1073. }
  1074. // if this is a "SEEING" interrupt
  1075. if (pSoldier->bOppList[ubOpponentID] == SEEN_CURRENTLY)
  1076. {
  1077. // if pSoldier already saw the opponent last "look" or at least this turn
  1078. if ((bOldOppList == SEEN_CURRENTLY) || (bOldOppList == SEEN_THIS_TURN))
  1079. {
  1080. return(FALSE); // no interrupt is possible
  1081. }
  1082. // if the soldier is behind him and not very close, forget it
  1083. bDir = atan8( pSoldier->sX, pSoldier->sY, pOpponent->sX, pOpponent->sY );
  1084. if ( gOppositeDirection[ pSoldier->bDesiredDirection ] == bDir )
  1085. {
  1086. // directly behind; allow interrupts only within # of tiles equal to level
  1087. if ( PythSpacesAway( pSoldier->sGridNo, pOpponent->sGridNo ) > EffectiveExpLevel( pSoldier ) )
  1088. {
  1089. return( FALSE );
  1090. }
  1091. }
  1092. // if the soldier isn't currently crouching
  1093. if (!PTR_CROUCHED)
  1094. {
  1095. ubMinPtsNeeded = AP_CROUCH;
  1096. }
  1097. else
  1098. {
  1099. ubMinPtsNeeded = MinPtsToMove(pSoldier);
  1100. }
  1101. }
  1102. else // this is a "HEARING" interrupt
  1103. {
  1104. // if the opponent can't see the "interrupter" either, OR
  1105. // if the "interrupter" already has any opponents already in sight, OR
  1106. // if the "interrupter" already heard the active soldier this turn
  1107. if ((pOpponent->bOppList[pSoldier->ubID] != SEEN_CURRENTLY) || (pSoldier->bOppCnt > 0) || (bOldOppList == HEARD_THIS_TURN))
  1108. {
  1109. return(FALSE); // no interrupt is possible
  1110. }
  1111. }
  1112. }
  1113. // soldiers without sufficient APs to do something productive can't interrupt
  1114. if (pSoldier->bActionPoints < ubMinPtsNeeded)
  1115. {
  1116. return(FALSE);
  1117. }
  1118. // soldier passed on the chance to react during previous interrupt this turn
  1119. if (pSoldier->bPassedLastInterrupt)
  1120. {
  1121. #ifdef RECORDNET
  1122. fprintf(NetDebugFile,"\tStandardInterruptConditionsMet: FAILING because PassedLastInterrupt %d(%s)\n",
  1123. pSoldier->guynum,ExtMen[pSoldier->guynum].name);
  1124. #endif
  1125. return(FALSE);
  1126. }
  1127. #ifdef RECORDINTERRUPT
  1128. // this usually starts a new series of logs, so that's why the blank line
  1129. fprintf(InterruptFile,"\nStandardInterruptConditionsMet by %d vs. %d\n",pSoldier->guynum,ubOpponentID);
  1130. #endif
  1131. return(TRUE);
  1132. }
  1133. INT8 CalcInterruptDuelPts( SOLDIERTYPE * pSoldier, UINT8 ubOpponentID, BOOLEAN fUseWatchSpots )
  1134. {
  1135. INT8 bPoints;
  1136. INT8 bLightLevel;
  1137. UINT8 ubDistance;
  1138. // extra check to make sure neutral folks never get interrupts
  1139. if (pSoldier->bNeutral)
  1140. {
  1141. return( NO_INTERRUPT );
  1142. }
  1143. // BASE is one point for each experience level.
  1144. // Robot has interrupt points based on the controller...
  1145. // Controller's interrupt points are reduced by 2 for being distracted...
  1146. if ( pSoldier->uiStatusFlags & SOLDIER_ROBOT && CanRobotBeControlled( pSoldier ) )
  1147. {
  1148. bPoints = EffectiveExpLevel( MercPtrs[ pSoldier->ubRobotRemoteHolderID ] ) - 2;
  1149. }
  1150. else
  1151. {
  1152. bPoints = EffectiveExpLevel( pSoldier );
  1153. /*
  1154. if ( pSoldier->bTeam == ENEMY_TEAM )
  1155. {
  1156. // modify by the difficulty level setting
  1157. bPoints += gbDiff[ DIFF_ENEMY_INTERRUPT_MOD ][ SoldierDifficultyLevel( pSoldier ) ];
  1158. bPoints = __max( bPoints, 9 );
  1159. }
  1160. */
  1161. if ( ControllingRobot( pSoldier ) )
  1162. {
  1163. bPoints -= 2;
  1164. }
  1165. }
  1166. if (fUseWatchSpots)
  1167. {
  1168. // if this is a previously noted spot of enemies, give bonus points!
  1169. bPoints += GetWatchedLocPoints( pSoldier->ubID, MercPtrs[ ubOpponentID ]->sGridNo, MercPtrs[ ubOpponentID ]->bLevel );
  1170. }
  1171. // LOSE one point for each 2 additional opponents he currently sees, above 2
  1172. if (pSoldier->bOppCnt > 2)
  1173. {
  1174. // subtract 1 here so there is a penalty of 1 for seeing 3 enemies
  1175. bPoints -= (pSoldier->bOppCnt - 1) / 2;
  1176. }
  1177. // LOSE one point if he's trying to interrupt only by hearing
  1178. if (pSoldier->bOppList[ubOpponentID] == HEARD_THIS_TURN)
  1179. {
  1180. bPoints--;
  1181. }
  1182. // if soldier is still in shock from recent injuries, that penalizes him
  1183. bPoints -= pSoldier->bShock;
  1184. ubDistance = (UINT8) PythSpacesAway( pSoldier->sGridNo, MercPtrs[ ubOpponentID ]->sGridNo );
  1185. // if we are in combat mode - thus doing an interrupt rather than determine who gets first turn -
  1186. // then give bonus
  1187. if ( (gTacticalStatus.uiFlags & INCOMBAT) && (pSoldier->bTeam != gTacticalStatus.ubCurrentTeam) )
  1188. {
  1189. // passive player gets penalty due to range
  1190. bPoints -= (ubDistance / 10);
  1191. }
  1192. else
  1193. {
  1194. // either non-combat or the player with the current turn... i.e. active...
  1195. // unfortunately we can't use opplist here to record whether or not we saw this guy before, because at this point
  1196. // the opplist has been updated to seen. But we can use gbSeenOpponents ...
  1197. // this soldier is moving, so give them a bonus for crawling or swatting at long distances
  1198. if ( !gbSeenOpponents[ ubOpponentID ][ pSoldier->ubID ] )
  1199. {
  1200. if (pSoldier->usAnimState == SWATTING && ubDistance > (MaxDistanceVisible() / 2) ) // more than 1/2 sight distance
  1201. {
  1202. bPoints++;
  1203. }
  1204. else if (pSoldier->usAnimState == CRAWLING && ubDistance > (MaxDistanceVisible() / 4) ) // more than 1/4 sight distance
  1205. {
  1206. bPoints += ubDistance / STRAIGHT;
  1207. }
  1208. }
  1209. }
  1210. // whether active or not, penalize people who are running
  1211. if ( pSoldier->usAnimState == RUNNING && !gbSeenOpponents[ pSoldier->ubID ][ ubOpponentID ] )
  1212. {
  1213. bPoints -= 2;
  1214. }
  1215. if (pSoldier->ubServicePartner != NOBODY)
  1216. {
  1217. // distracted by being bandaged/doing bandaging
  1218. bPoints -= 2;
  1219. }
  1220. if ( HAS_SKILL_TRAIT( pSoldier, NIGHTOPS ) )
  1221. {
  1222. bLightLevel = LightTrueLevel(pSoldier->sGridNo, pSoldier->bLevel);
  1223. if (bLightLevel > NORMAL_LIGHTLEVEL_DAY + 3)
  1224. {
  1225. // it's dark, give a bonus for interrupts
  1226. bPoints += 1 * NUM_SKILL_TRAITS( pSoldier, NIGHTOPS );
  1227. }
  1228. }
  1229. // if he's a computer soldier
  1230. // CJC note: this will affect friendly AI as well...
  1231. if ( pSoldier->uiStatusFlags & SOLDIER_PC )
  1232. {
  1233. if ( pSoldier->bAssignment >= ON_DUTY )
  1234. {
  1235. // make sure don't get interrupts!
  1236. bPoints = -10;
  1237. }
  1238. // GAIN one point if he's previously seen the opponent
  1239. // check for TRUE because -1 means we JUST saw him (always so here)
  1240. if (gbSeenOpponents[pSoldier->ubID][ubOpponentID] == TRUE)
  1241. {
  1242. bPoints++; // seen him before, easier to react to him
  1243. }
  1244. }
  1245. else if ( pSoldier->bTeam == ENEMY_TEAM )
  1246. {
  1247. // GAIN one point if he's previously seen the opponent
  1248. // check for TRUE because -1 means we JUST saw him (always so here)
  1249. if (gbSeenOpponents[pSoldier->ubID][ubOpponentID] == TRUE)
  1250. {
  1251. bPoints++; // seen him before, easier to react to him
  1252. }
  1253. else if (gbPublicOpplist[pSoldier->bTeam][ubOpponentID] != NOT_HEARD_OR_SEEN)
  1254. {
  1255. // GAIN one point if opponent has been recently radioed in by his team
  1256. bPoints++;
  1257. }
  1258. }
  1259. if ( TANK( pSoldier ) )
  1260. {
  1261. // reduce interrupt possibilities for tanks!
  1262. bPoints /= 2;
  1263. }
  1264. if (bPoints >= AUTOMATIC_INTERRUPT)
  1265. {
  1266. #ifdef BETAVERSION
  1267. NumMessage("CalcInterruptDuelPts: ERROR - Invalid bInterruptDuelPts calculated for soldier ",pSoldier->guynum);
  1268. #endif
  1269. bPoints = AUTOMATIC_INTERRUPT - 1; // hack it to one less than max so its legal
  1270. }
  1271. #ifdef DEBUG_INTERRUPTS
  1272. DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Calculating int pts for %d vs %d, number is %d", pSoldier->ubID, ubOpponentID, bPoints ) );
  1273. #endif
  1274. return( bPoints );
  1275. }
  1276. BOOLEAN InterruptDuel( SOLDIERTYPE * pSoldier, SOLDIERTYPE * pOpponent)
  1277. {
  1278. BOOLEAN fResult = FALSE;
  1279. // if opponent can't currently see us and we can see them
  1280. if ( pSoldier->bOppList[ pOpponent->ubID ] == SEEN_CURRENTLY && pOpponent->bOppList[pSoldier->ubID] != SEEN_CURRENTLY )
  1281. {
  1282. fResult = TRUE; // we automatically interrupt
  1283. // fix up our interrupt duel pts if necessary
  1284. if (pSoldier->bInterruptDuelPts < pOpponent->bInterruptDuelPts)
  1285. {
  1286. pSoldier->bInterruptDuelPts = pOpponent->bInterruptDuelPts;
  1287. }
  1288. }
  1289. else
  1290. {
  1291. // If our total points is HIGHER, then we interrupt him anyway
  1292. if (pSoldier->bInterruptDuelPts > pOpponent->bInterruptDuelPts)
  1293. {
  1294. fResult = TRUE;
  1295. }
  1296. }
  1297. // ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Interrupt duel %d (%d pts) vs %d (%d pts)", pSoldier->ubID, pSoldier->bInterruptDuelPts, pOpponent->ubID, pOpponent->bInterruptDuelPts );
  1298. return( fResult );
  1299. }
  1300. void DeleteFromIntList( UINT8 ubIndex, BOOLEAN fCommunicate)
  1301. {
  1302. UINT8 ubLoop;
  1303. UINT8 ubID;
  1304. if ( ubIndex > gubOutOfTurnPersons)
  1305. {
  1306. return;
  1307. }
  1308. // remember who we're getting rid of
  1309. ubID = gubOutOfTurnOrder[ubIndex];
  1310. #ifdef DEBUG_INTERRUPTS
  1311. DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("INTERRUPT: removing ID %d", ubID ) );
  1312. #endif
  1313. // ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%d removed from int list", ubID );
  1314. // if we're NOT deleting the LAST entry in the int list
  1315. if (ubIndex < gubOutOfTurnPersons)
  1316. {
  1317. // not the last entry, must move all those behind it over to fill the gap
  1318. for (ubLoop = ubIndex; ubLoop < gubOutOfTurnPersons; ubLoop++)
  1319. {
  1320. gubOutOfTurnOrder[ubLoop] = gubOutOfTurnOrder[ubLoop + 1];
  1321. }
  1322. }
  1323. // either way, whack the last entry to NOBODY and decrement the list size
  1324. gubOutOfTurnOrder[gubOutOfTurnPersons] = NOBODY;
  1325. gubOutOfTurnPersons--;
  1326. // once the last interrupted guy gets deleted from the list, he's no longer
  1327. // the last interrupted guy!
  1328. /*
  1329. if (Status.lastInterruptedWas == ubID)
  1330. {
  1331. Status.lastInterruptedWas = NOBODY;
  1332. }
  1333. */
  1334. }
  1335. void AddToIntList( UINT8 ubID, BOOLEAN fGainControl, BOOLEAN fCommunicate )
  1336. {
  1337. UINT8 ubLoop;
  1338. // ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%d added to int list", ubID );
  1339. DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("INTERRUPT: adding ID %d who %s", ubID, fGainControl ? "gains control" : "loses control" ) );
  1340. // check whether 'who' is already anywhere on the queue after the first index
  1341. // which we want to preserve so we can restore turn order
  1342. for (ubLoop = 2; ubLoop <= gubOutOfTurnPersons; ubLoop++)
  1343. {
  1344. if (gubOutOfTurnOrder[ubLoop] == ubID)
  1345. {
  1346. if (!fGainControl)
  1347. {
  1348. // he's LOSING control; that's it, we're done, DON'T add him to the queue again
  1349. gubLastInterruptedGuy = ubID;
  1350. return;
  1351. }
  1352. else
  1353. {
  1354. // GAINING control, so delete him from this slot (because later he'll
  1355. // get added to the end and we don't want him listed more than once!)
  1356. DeleteFromIntList( ubLoop, FALSE );
  1357. }
  1358. }
  1359. }
  1360. // increment total (making index valid) and add him to list
  1361. gubOutOfTurnPersons++;
  1362. gubOutOfTurnOrder[gubOutOfTurnPersons] = ubID;
  1363. /*
  1364. // the guy being interrupted HAS to be the currently selected character
  1365. if (Status.lastInterruptedWas != CharacterSelected)
  1366. {
  1367. // if we don't already do so, remember who that was
  1368. Status.lastInterruptedWas = CharacterSelected;
  1369. }
  1370. */
  1371. // if the guy is gaining control
  1372. if (fGainControl)
  1373. {
  1374. // record his initial APs at the start of his interrupt at this time
  1375. // this is not the ideal place for this, but it's the best I could do...
  1376. Menptr[ubID].bIntStartAPs = Menptr[ubID].bActionPoints;
  1377. }
  1378. else
  1379. {
  1380. gubLastInterruptedGuy = ubID;
  1381. // turn off AI control flag if they lost control
  1382. if (Menptr[ubID].uiStatusFlags & SOLDIER_UNDERAICONTROL)
  1383. {
  1384. DebugAI( String( "Taking away AI control from %d", ubID ) );
  1385. Menptr[ubID].uiStatusFlags &= (~SOLDIER_UNDERAICONTROL);
  1386. }
  1387. }
  1388. }
  1389. void VerifyOutOfTurnOrderArray()
  1390. {
  1391. UINT8 ubTeamHighest[ MAXTEAMS ] = { 0 };
  1392. UINT8 ubTeamsInList;
  1393. UINT8 ubNextInArrayOnTeam, ubNextIndex;
  1394. UINT8 ubTeam;
  1395. UINT8 ubLoop, ubLoop2;
  1396. BOOLEAN fFoundLoop = FALSE;
  1397. for (ubLoop = 1; ubLoop <= gubOutOfTurnPersons; ubLoop++)
  1398. {
  1399. ubTeam = Menptr[ gubOutOfTurnOrder[ ubLoop ] ].bTeam;
  1400. if (ubTeamHighest[ ubTeam ] > 0)
  1401. {
  1402. // check the other teams to see if any of them are between our last team's mention in
  1403. // the array and this
  1404. for (ubLoop2 = 0; ubLoop2 < MAXTEAMS; ubLoop2++)
  1405. {
  1406. if (ubLoop2 == ubTeam)
  1407. {
  1408. continue;
  1409. }
  1410. else
  1411. {
  1412. if (ubTeamHighest[ ubLoop2 ] > ubTeamHighest[ ubTeam ])
  1413. {
  1414. // there's a loop!! delete it!
  1415. ubNextInArrayOnTeam = gubOutOfTurnOrder[ ubLoop ];
  1416. ubNextIndex = ubTeamHighest[ ubTeam ] + 1;
  1417. while( gubOutOfTurnOrder[ ubNextIndex ] != ubNextInArrayOnTeam )
  1418. {
  1419. // Pause them...
  1420. AdjustNoAPToFinishMove( MercPtrs[ gubOutOfTurnOrder[ ubNextIndex ] ], TRUE );
  1421. // If they were turning from prone, stop them
  1422. MercPtrs[ gubOutOfTurnOrder[ ubNextIndex ] ]->fTurningFromPronePosition = FALSE;
  1423. DeleteFromIntList( ubNextIndex, FALSE );
  1424. }
  1425. fFoundLoop = TRUE;
  1426. break;
  1427. }
  1428. }
  1429. }
  1430. if (fFoundLoop)
  1431. {
  1432. // at this point we should restart our outside loop (ugh)
  1433. fFoundLoop = FALSE;
  1434. for (ubLoop2 = 0; ubLoop2 < MAXTEAMS; ubLoop2++)
  1435. {
  1436. ubTeamHighest[ ubLoop2 ] = 0;
  1437. }
  1438. ubLoop = 0;
  1439. continue;
  1440. }
  1441. }
  1442. ubTeamHighest[ ubTeam ] = ubLoop;
  1443. }
  1444. // Another potential problem: the player is interrupted by the enemy who is interrupted by
  1445. // the militia. In this situation the enemy should just lose their interrupt.
  1446. // (Or, the militia is interrupted by the enemy who is interrupted by the player.)
  1447. // Check for 3+ teams in the interrupt queue. If three exist then abort all interrupts (return
  1448. // control to the first team)
  1449. ubTeamsInList = 0;
  1450. for ( ubLoop = 0; ubLoop < MAXTEAMS; ubLoop++ )
  1451. {
  1452. if ( ubTeamHighest[ ubLoop ] > 0 )
  1453. {
  1454. ubTeamsInList++;
  1455. }
  1456. }
  1457. if ( ubTeamsInList >= 3 )
  1458. {
  1459. // This is bad. Loop through everyone but the first person in the INT list and remove 'em
  1460. for (ubLoop = 2; ubLoop <= gubOutOfTurnPersons; )
  1461. {
  1462. if ( MercPtrs[ gubOutOfTurnOrder[ ubLoop ] ]->bTeam != MercPtrs[ gubOutOfTurnOrder[ 1 ] ]->bTeam )
  1463. {
  1464. // remove!
  1465. // Pause them...
  1466. AdjustNoAPToFinishMove( MercPtrs[ gubOutOfTurnOrder[ ubLoop ] ], TRUE );
  1467. // If they were turning from prone, stop them
  1468. MercPtrs[ gubOutOfTurnOrder[ ubLoop ] ]->fTurningFromPronePosition = FALSE;
  1469. DeleteFromIntList( ubLoop, FALSE );
  1470. // since we deleted someone from the list, we want to check the same index in the
  1471. // array again, hence we DON'T increment.
  1472. }
  1473. else
  1474. {
  1475. ubLoop++;
  1476. }
  1477. }
  1478. }
  1479. }
  1480. void DoneAddingToIntList( SOLDIERTYPE * pSoldier, BOOLEAN fChange, UINT8 ubInterruptType)
  1481. {
  1482. if (fChange)
  1483. {
  1484. VerifyOutOfTurnOrderArray();
  1485. if ( EveryoneInInterruptListOnSameTeam() )
  1486. {
  1487. EndInterrupt( TRUE );
  1488. }
  1489. else
  1490. {
  1491. StartInterrupt();
  1492. }
  1493. }
  1494. }
  1495. void ResolveInterruptsVs( SOLDIERTYPE * pSoldier, UINT8 ubInterruptType)
  1496. {
  1497. UINT8 ubTeam, ubOpp;
  1498. UINT8 ubIntCnt;
  1499. UINT8 ubIntList[MAXMERCS];
  1500. UINT8 ubIntDiff[MAXMERCS];
  1501. UINT8 ubSmallestDiff;
  1502. UINT8 ubSlot, ubSmallestSlot;
  1503. UINT8 ubLoop;
  1504. BOOLEAN fIntOccurs;
  1505. SOLDIERTYPE * pOpponent;
  1506. BOOLEAN fControlChanged = FALSE;
  1507. if ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) )
  1508. {
  1509. ubIntCnt = 0;
  1510. for (ubTeam = 0; ubTeam < MAXTEAMS; ubTeam++)
  1511. {
  1512. if (gTacticalStatus.Team[ubTeam].bTeamActive && (gTacticalStatus.Team[ubTeam].bSide != pSoldier->bSide) && ubTeam != CIV_TEAM)
  1513. {
  1514. for ( ubOpp = gTacticalStatus.Team[ ubTeam ].bFirstID; ubOpp <= gTacticalStatus.Team[ ubTeam ].bLastID; ubOpp++)
  1515. {
  1516. pOpponent = MercPtrs[ubOpp];
  1517. if ( pOpponent->bActive && pOpponent->bInSector && (pOpponent->bLife >= OKLIFE) && !(pOpponent->bCollapsed) )
  1518. {
  1519. if ( ubInterruptType == NOISEINTERRUPT )
  1520. {
  1521. // don't grant noise interrupts at greater than max. visible distance
  1522. if ( PythSpacesAway( pSoldier->sGridNo, pOpponent->sGridNo ) > MaxDistanceVisible() )
  1523. {
  1524. pOpponent->bInterruptDuelPts = NO_INTERRUPT;
  1525. #ifdef DEBUG_INTERRUPTS
  1526. DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Resetting int pts for %d - NOISE BEYOND SIGHT DISTANCE!?", pOpponent->ubID ) );
  1527. #endif
  1528. continue;
  1529. }
  1530. }
  1531. else if ( pOpponent->bOppList[pSoldier->ubID] != SEEN_CURRENTLY )
  1532. {
  1533. pOpponent->bInterruptDuelPts = NO_INTERRUPT;
  1534. #ifdef DEBUG_INTERRUPTS
  1535. DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Resetting int pts for %d - DOESN'T SEE ON SIGHT INTERRUPT!?", pOpponent->ubID ) );
  1536. #endif
  1537. continue;
  1538. }
  1539. switch (pOpponent->bInterruptDuelPts)
  1540. {
  1541. case NO_INTERRUPT: // no interrupt possible, no duel necessary
  1542. fIntOccurs = FALSE;
  1543. break;
  1544. case AUTOMATIC_INTERRUPT: // interrupts occurs automatically
  1545. pSoldier->bInterruptDuelPts = 0; // just to have a valid intDiff later
  1546. fIntOccurs = TRUE;
  1547. #ifdef DEBUG_INTERRUPTS
  1548. DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("INTERRUPT: automatic interrupt on %d by %d", pSoldier->ubID, pOpponent->ubID ) );
  1549. #endif
  1550. break;
  1551. default: // interrupt is possible, run a duel
  1552. DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "Calculating int duel pts for onlooker in ResolveInterruptsVs" );
  1553. pSoldier->bInterruptDuelPts = CalcInterruptDuelPts(pSoldier,pOpponent->ubID,TRUE);
  1554. fIntOccurs = InterruptDuel(pOpponent,pSoldier);
  1555. #ifdef DEBUG_INTERRUPTS
  1556. if (fIntOccurs)
  1557. {
  1558. DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("INTERRUPT: standard interrupt on %d (%d pts) by %d (%d pts)", pSoldier->ubID, pSoldier->bInterruptDuelPts, pOpponent->ubID, pOpponent->bInterruptDuelPts) );
  1559. }
  1560. #endif
  1561. break;
  1562. }
  1563. if (fIntOccurs)
  1564. {
  1565. // remember that this opponent's scheduled to interrupt us
  1566. ubIntList[ubIntCnt] = pOpponent->ubID;
  1567. // and by how much he beat us in the duel
  1568. ubIntDiff[ubIntCnt] = pOpponent->bInterruptDuelPts - pSoldier->bInterruptDuelPts;
  1569. // increment counter of interrupts lost
  1570. ubIntCnt++;
  1571. }
  1572. else
  1573. {
  1574. /*
  1575. if (pOpponent->bInterruptDuelPts != NO_INTERRUPT)
  1576. {
  1577. ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%d fails to interrupt %d (%d vs %d pts)", pOpponent->ubID, pSoldier->ubID, pOpponent->bInterruptDuelPts, pSoldier->bInterruptDuelPts);
  1578. }
  1579. */
  1580. }
  1581. // either way, clear out both sides' bInterruptDuelPts field to prepare next one
  1582. #ifdef DEBUG_INTERRUPTS
  1583. if (pSoldier->bInterruptDuelPts != NO_INTERRUPT)
  1584. {
  1585. DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Resetting int pts for %d", pSoldier->ubID ) );
  1586. }
  1587. #endif
  1588. pSoldier->bInterruptDuelPts = NO_INTERRUPT;
  1589. #ifdef DEBUG_INTERRUPTS
  1590. if (pOpponent->bInterruptDuelPts != NO_INTERRUPT)
  1591. {
  1592. DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Resetting int pts for %d", pOpponent->ubID ) );
  1593. }
  1594. #endif
  1595. pOpponent->bInterruptDuelPts = NO_INTERRUPT;
  1596. }
  1597. }
  1598. }
  1599. }
  1600. // if any interrupts are scheduled to occur (ie. I lost at least once)
  1601. if (ubIntCnt)
  1602. {
  1603. // First add currently active character to the interrupt queue. This is
  1604. // USUALLY pSoldier->guynum, but NOT always, because one enemy can
  1605. // "interrupt" on another enemy's turn if he hears another team's wound
  1606. // victim's screaming... the guy screaming is pSoldier here, it's not his turn!
  1607. //AddToIntList( (UINT8) gusSelectedSoldier, FALSE, TRUE);
  1608. if ( (gTacticalStatus.ubCurrentTeam != pSoldier->bTeam) && !(gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bHuman) )
  1609. {
  1610. // if anyone on this team is under AI control, remove
  1611. // their AI control flag and put them on the queue instead of this guy
  1612. for ( ubLoop = gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bFirstID; ubLoop <= gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bLastID; ubLoop++ )
  1613. {
  1614. if ( (MercPtrs[ ubLoop ]->uiStatusFlags & SOLDIER_UNDERAICONTROL) )
  1615. {
  1616. // this guy lost control
  1617. MercPtrs[ ubLoop ]->uiStatusFlags &= (~SOLDIER_UNDERAICONTROL);
  1618. AddToIntList( ubLoop, FALSE, TRUE);
  1619. break;
  1620. }
  1621. }
  1622. }
  1623. else
  1624. {
  1625. // this guy lost control
  1626. AddToIntList( pSoldier->ubID, FALSE, TRUE);
  1627. }
  1628. // loop once for each opponent who interrupted
  1629. for (ubLoop = 0; ubLoop < ubIntCnt; ubLoop++)
  1630. {
  1631. // find the smallest intDiff still remaining in the list
  1632. ubSmallestDiff = NO_INTERRUPT;
  1633. ubSmallestSlot = NOBODY;
  1634. for (ubSlot = 0; ubSlot < ubIntCnt; ubSlot++)
  1635. {
  1636. if (ubIntDiff[ubSlot] < ubSmallestDiff)
  1637. {
  1638. ubSmallestDiff = ubIntDiff[ubSlot];
  1639. ubSmallestSlot = ubSlot;
  1640. }
  1641. }
  1642. if (ubSmallestSlot < NOBODY)
  1643. {
  1644. // add this guy to everyone's interrupt queue
  1645. AddToIntList(ubIntList[ubSmallestSlot],TRUE,TRUE);
  1646. if (INTERRUPTS_OVER)
  1647. {
  1648. // a loop was created which removed all the people in the interrupt queue!
  1649. EndInterrupt( TRUE );
  1650. return;
  1651. }
  1652. ubIntDiff[ubSmallestSlot] = NO_INTERRUPT; // mark slot as been handled
  1653. }
  1654. }
  1655. fControlChanged = TRUE;
  1656. }
  1657. // sends off an end-of-list msg telling everyone whether to switch control,
  1658. // unless it's a MOVEMENT interrupt, in which case that is delayed til later
  1659. DoneAddingToIntList(pSoldier,fControlChanged,ubInterruptType);
  1660. }
  1661. }
  1662. BOOLEAN SaveTeamTurnsToTheSaveGameFile( HWFILE hFile )
  1663. {
  1664. UINT32 uiNumBytesWritten;
  1665. TEAM_TURN_SAVE_STRUCT TeamTurnStruct;
  1666. //Save the gubTurn Order Array
  1667. FileWrite( hFile, gubOutOfTurnOrder, sizeof( UINT8 ) * MAXMERCS, &uiNumBytesWritten );
  1668. if( uiNumBytesWritten != sizeof( UINT8 ) * MAXMERCS )
  1669. {
  1670. return( FALSE );
  1671. }
  1672. TeamTurnStruct.ubOutOfTurnPersons = gubOutOfTurnPersons;
  1673. TeamTurnStruct.InterruptOnlyGuynum = InterruptOnlyGuynum;
  1674. TeamTurnStruct.sWhoThrewRock = gsWhoThrewRock;
  1675. TeamTurnStruct.InterruptsAllowed = InterruptsAllowed;
  1676. TeamTurnStruct.fHiddenInterrupt = gfHiddenInterrupt;
  1677. TeamTurnStruct.ubLastInterruptedGuy = gubLastInterruptedGuy;
  1678. //Save the Team turn save structure
  1679. FileWrite( hFile, &TeamTurnStruct, sizeof( TEAM_TURN_SAVE_STRUCT ), &uiNumBytesWritten );
  1680. if( uiNumBytesWritten != sizeof( TEAM_TURN_SAVE_STRUCT ) )
  1681. {
  1682. return( FALSE );
  1683. }
  1684. return( TRUE );
  1685. }
  1686. BOOLEAN LoadTeamTurnsFromTheSavedGameFile( HWFILE hFile )
  1687. {
  1688. UINT32 uiNumBytesRead;
  1689. TEAM_TURN_SAVE_STRUCT TeamTurnStruct;
  1690. //Load the gubTurn Order Array
  1691. FileRead( hFile, gubOutOfTurnOrder, sizeof( UINT8 ) * MAXMERCS, &uiNumBytesRead );
  1692. if( uiNumBytesRead != sizeof( UINT8 ) * MAXMERCS )
  1693. {
  1694. return( FALSE );
  1695. }
  1696. //Load the Team turn save structure
  1697. FileRead( hFile, &TeamTurnStruct, sizeof( TEAM_TURN_SAVE_STRUCT ), &uiNumBytesRead );
  1698. if( uiNumBytesRead != sizeof( TEAM_TURN_SAVE_STRUCT ) )
  1699. {
  1700. return( FALSE );
  1701. }
  1702. gubOutOfTurnPersons = TeamTurnStruct.ubOutOfTurnPersons;
  1703. InterruptOnlyGuynum = TeamTurnStruct.InterruptOnlyGuynum;
  1704. gsWhoThrewRock = TeamTurnStruct.sWhoThrewRock;
  1705. InterruptsAllowed = TeamTurnStruct.InterruptsAllowed;
  1706. gfHiddenInterrupt = TeamTurnStruct.fHiddenInterrupt;
  1707. gubLastInterruptedGuy = TeamTurnStruct.ubLastInterruptedGuy;
  1708. return( TRUE );
  1709. }
  1710. BOOLEAN NPCFirstDraw( SOLDIERTYPE * pSoldier, SOLDIERTYPE * pTargetSoldier )
  1711. {
  1712. // if attacking an NPC check to see who draws first!
  1713. if ( pTargetSoldier->ubProfile != NO_PROFILE && pTargetSoldier->ubProfile != SLAY && pTargetSoldier->bNeutral && pTargetSoldier->bOppList[ pSoldier->ubID ] == SEEN_CURRENTLY && ( FindAIUsableObjClass( pTargetSoldier, IC_WEAPON ) != NO_SLOT ) )
  1714. {
  1715. UINT8 ubLargerHalf, ubSmallerHalf, ubTargetLargerHalf, ubTargetSmallerHalf;
  1716. // roll the dice!
  1717. // e.g. if level 5, roll Random( 3 + 1 ) + 2 for result from 2 to 5
  1718. // if level 4, roll Random( 2 + 1 ) + 2 for result from 2 to 4
  1719. ubSmallerHalf = EffectiveExpLevel( pSoldier ) / 2;
  1720. ubLargerHalf = EffectiveExpLevel( pSoldier ) - ubSmallerHalf;
  1721. ubTargetSmallerHalf = EffectiveExpLevel( pTargetSoldier ) / 2;
  1722. ubTargetLargerHalf = EffectiveExpLevel( pTargetSoldier ) - ubTargetSmallerHalf;
  1723. if ( gMercProfiles[ pTargetSoldier->ubProfile ].bApproached & gbFirstApproachFlags[ APPROACH_THREATEN - 1 ] )
  1724. {
  1725. // gains 1 to 2 points
  1726. ubTargetSmallerHalf += 1;
  1727. ubTargetLargerHalf += 1;
  1728. }
  1729. if ( Random( ubTargetSmallerHalf + 1) + ubTargetLargerHalf > Random( ubSmallerHalf + 1) + ubLargerHalf )
  1730. {
  1731. return( TRUE );
  1732. }
  1733. }
  1734. return( FALSE );
  1735. }