Files
source/Tactical/TeamTurns.cpp
T
Overhaul 7a27e7105d New attack busy system
Added missing #includes

Added LUA scripting and console

Changed the way tracers are visualized, so they work more like real tracers instead of a light fountain

Fixed signedness of many grid variables used in GetMouseMapPos calls

Fixed enemy weapon choosing:  Sometimes a mortar is chosen but later rejected, but the grenade class was not reset.  Caused assertion failure

Added checks so enemies don't try to chuck RPG grenades with their hands

Now possible to shoot a someone in the head if he's in water

Fixed InitSightArrays to also clear soldier interrupt duel points.  This was causing an assertion failure elsewhere in the code because the interrupt list still had soldiers on it sometimes when this function was called.

Soldiers are much less willing to forfeit their turn over an attempt to use more APs than they have

Removed early setting of muzzle flash.  This would allow enemies to get an interrupt before you even fired.

Fixed item dropping by AI.  If AI tried to drop something while standing it would cause deadlock

Change to greatly speed up closing the sector inventory window in an unloaded sector

Added conditional compile flag to always give robot weapon ready advantage, even for 360 degree sighting


Check builddefines.h for the conditional flags.  Of greatest interest might be LUA_CONSOLE.  This will cause the game to bring up a command console when run.  However this console is severely lacking in many areas.  If anybody knows of an open-source terminal/console that could be used instead, it would be appreciated.  The existing console does bad things when you try to close it, and since it counts as a separate app, it pauses the game while it has focus.

LUA scripting is very limited, basically just proof of concept.  There is one global variable, the Soldiers array.  An array index gives you the soldier in that slot in the currently loaded sector.  The soldier has a few things that can be accessed:  name (short name); fullname (long name); grid (current grid #, can be changed); walkto(grid) (function to walk to another grid); runto(grid) (function to run to another grid)


git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@924 3b4a5df2-a311-0410-b5c6-a8a6f20db521
2007-06-09 09:01:04 +00:00

2154 lines
60 KiB
C++

#ifdef PRECOMPILEDHEADERS
#include "Tactical All.h"
#else
#include "types.h"
#include "soldier control.h"
#include "overhead.h"
#include "animation control.h"
#include "points.h"
#include "opplist.h"
#include "timer.h"
#include "event pump.h"
#include "Sound Control.h"
#include "interface.h"
#include "Isometric Utils.h"
#include "Font Control.H"
#include "ai.h"
#include "interface.h"
#include "message.h"
#include "text.h"
#include "TeamTurns.h"
#include "Smell.h"
#include "game clock.h"
#include "Soldier Functions.h"
#include "cursors.h"
#include "Queen Command.h"
#include "Pathai.h"
#include "Music Control.h"
#include "Strategic Turns.h"
#include "lighting.h"
#include "environment.h"
#include "Explosion Control.h"
#include "dialogue control.h"
#include "Soldier Profile Type.h"
#include "SmokeEffects.h"
#include "lighteffects.h"
#include "air raid.h"
#include "meanwhile.h"
#include "SkillCheck.h"
#include "AIInternals.h"
#include "AIList.h"
#ifdef DEBUG_INTERRUPTS
#include "debug.h"
#endif
#include "renderworld.h"
#include "rotting corpses.h"
#include "Squads.h"
#include "Soldier macros.h"
#include "Soldier Profile.h"
#include "NPC.h"
#endif
#include "Reinforcement.h"
extern INT8 STRAIGHT;
//extern UINT8 gubSpeedUpAnimationFactor;
void SetSoldierAniSpeed( SOLDIERTYPE *pSoldier );
void RecalculateSoldiersAniSpeed()
{
UINT32 uiLoop;
SOLDIERTYPE *pSoldier;
// if( gubSpeedUpAnimationFactor == 1 )return;
for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
{
pSoldier = MercSlots[ uiLoop ];
// if this merc is inactive, at base, on assignment, dead, unconscious
if (!pSoldier)
{
continue; // next merc
}
SetSoldierAniSpeed( pSoldier );
}
}
extern void DecayPublicOpplist(INT8 bTeam);
extern void VerifyAndDecayOpplist(SOLDIERTYPE *pSoldier);
void EndInterrupt( BOOLEAN fMarkInterruptOccurred );
void DeleteFromIntList( UINT8 ubIndex, BOOLEAN fCommunicate);
#define END_OF_INTERRUPTS 255
UINT8 gubOutOfTurnOrder[MAXMERCS] = { END_OF_INTERRUPTS, 0 };
UINT8 gubOutOfTurnPersons = 0;
#define LATEST_INTERRUPT_GUY (gubOutOfTurnOrder[gubOutOfTurnPersons])
#define REMOVE_LATEST_INTERRUPT_GUY() (DeleteFromIntList( (UINT8) (gubOutOfTurnPersons), TRUE ))
#define INTERRUPTS_OVER (gubOutOfTurnPersons == 1)
INT16 InterruptOnlyGuynum = NOBODY;
BOOLEAN InterruptsAllowed = TRUE;
BOOLEAN gfHiddenInterrupt = FALSE;
UINT8 gubLastInterruptedGuy = 0;
extern UINT16 gsWhoThrewRock;
extern UINT8 gubSightFlags;
typedef struct
{
UINT8 ubOutOfTurnPersons;
INT16 InterruptOnlyGuynum;
INT16 sWhoThrewRock;
BOOLEAN InterruptsAllowed;
BOOLEAN fHiddenInterrupt;
UINT8 ubLastInterruptedGuy;
UINT8 ubFiller[16];
} TEAM_TURN_SAVE_STRUCT;
#define MIN_APS_TO_INTERRUPT 4
void ClearIntList( void )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"ClearIntList");
memset( &gubOutOfTurnOrder, 0, MAXMERCS );
gubOutOfTurnOrder[0] = END_OF_INTERRUPTS;
gubOutOfTurnPersons = 0;
}
BOOLEAN BloodcatsPresent( void )
{
INT32 iLoop;
SOLDIERTYPE * pSoldier;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"BloodcatsPresent");
if ( gTacticalStatus.Team[ CREATURE_TEAM ].bTeamActive == FALSE )
{
return( FALSE );
}
for ( iLoop = gTacticalStatus.Team[ CREATURE_TEAM ].bFirstID; iLoop <= gTacticalStatus.Team[ CREATURE_TEAM ].bLastID; iLoop++ )
{
pSoldier = MercPtrs[ iLoop ];
if ( pSoldier->bActive && pSoldier->bInSector && pSoldier->bLife > 0 && pSoldier->ubBodyType == BLOODCAT )
{
return( TRUE );
}
}
return( FALSE );
}
void StartPlayerTeamTurn( BOOLEAN fDoBattleSnd, BOOLEAN fEnteringCombatMode )
{
INT32 cnt;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"StartPlayerTeamTurn");
// SOLDIERTYPE *pSoldier;
// EV_S_BEGINTURN SBeginTurn;
// Start the turn of player charactors
//
// PATCH 1.06:
//
// make sure set properly in gTacticalStatus:
gTacticalStatus.ubCurrentTeam = OUR_TEAM;
cnt = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
InitPlayerUIBar( FALSE );
if ( gTacticalStatus.uiFlags & TURNBASED )
{
// Are we in combat already?
if ( gTacticalStatus.uiFlags & INCOMBAT )
{
PlayJA2Sample( ENDTURN_1, RATE_11025, MIDVOLUME, 1, MIDDLEPAN );
}
// Remove deadlock message
EndDeadlockMsg( );
// Check for victory conditions
// ATE: Commented out - looks like this message is called earlier for our team
// look for all mercs on the same team,
//for ( pSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; cnt++,pSoldier++)
//{
// if ( pSoldier->bActive && pSoldier->bLife > 0 )
// {
// SBeginTurn.usSoldierID = (UINT16)cnt;
// AddGameEvent( S_BEGINTURN, 0, &SBeginTurn );
// }
//}
// Are we in combat already?
if ( gTacticalStatus.uiFlags & INCOMBAT )
{
if ( gusSelectedSoldier != NO_SOLDIER )
{
// Check if this guy is able to be selected....
if ( MercPtrs[ gusSelectedSoldier ]->bLife < OKLIFE )
{
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("StartPlayerTeamTurn: SelectNextAvailSoldier"));
SelectNextAvailSoldier( MercPtrs[ gusSelectedSoldier ] );
}
else
{
SelectSoldier( gusSelectedSoldier, FALSE, FALSE);
}
// Slide to selected guy...
if ( gusSelectedSoldier != NO_SOLDIER )
{
SlideTo( NOWHERE, gusSelectedSoldier, NOBODY ,SETLOCATOR);
if ( fDoBattleSnd )
{
// Say ATTENTION SOUND...
DoMercBattleSound( MercPtrs[ gusSelectedSoldier ], BATTLE_SOUND_ATTN1 );
}
if ( gsInterfaceLevel == 1 )
{
gTacticalStatus.uiFlags |= SHOW_ALL_ROOFS;
InvalidateWorldRedundency( );
SetRenderFlags(RENDER_FLAG_FULL);
ErasePath(FALSE);
}
}
}
}
// Dirty panel interface!
fInterfacePanelDirty = DIRTYLEVEL2;
// Adjust time now!
UpdateClock( );
if ( !fEnteringCombatMode )
{
CheckForEndOfCombatMode( TRUE );
}
}
// Signal UI done enemy's turn
guiPendingOverrideEvent = LU_ENDUILOCK;
// ATE: Reset killed on attack variable.. this is because sometimes timing is such
/// that a baddie can die and still maintain it's attacker ID
gTacticalStatus.fKilledEnemyOnAttack = FALSE;
HandleTacticalUI( );
}
void FreezeInterfaceForEnemyTurn( void )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"FreezeInterfaceForEnemyTurn");
// Reset flags
gfPlotNewMovement = TRUE;
// Erase path
ErasePath( TRUE );
// Setup locked UI
guiPendingOverrideEvent = LU_BEGINUILOCK;
// Remove any UI messages!
if ( giUIMessageOverlay != -1 )
{
EndUIMessage( );
}
}
void EndTurn( UINT8 ubNextTeam )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"EndTurn");
SOLDIERTYPE * pSoldier;
INT32 cnt;
//Check for enemy pooling (add enemies if there happens to be more than the max in the
//current battle. If one or more slots have freed up, we can add them now.
EndDeadlockMsg( );
/*
if ( CheckForEndOfCombatMode( FALSE ) )
{
return;
}
*/
if (INTERRUPT_QUEUED)
{
EndInterrupt( FALSE );
}
else
{
AddPossiblePendingEnemiesToBattle();
AddPossiblePendingMilitiaToBattle();
// InitEnemyUIBar( );
FreezeInterfaceForEnemyTurn();
// Loop through all mercs and set to moved
cnt = gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bFirstID;
for ( pSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bLastID; cnt++,pSoldier++)
{
if ( pSoldier->bActive )
{
pSoldier->bMoved = TRUE;
}
}
gTacticalStatus.ubCurrentTeam = ubNextTeam;
BeginTeamTurn( gTacticalStatus.ubCurrentTeam );
BetweenTurnsVisibilityAdjustments();
}
}
void EndAITurn( void )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"EndAITurn");
SOLDIERTYPE * pSoldier;
INT32 cnt;
// Remove any deadlock message
EndDeadlockMsg( );
if (INTERRUPT_QUEUED)
{
EndInterrupt( FALSE );
}
else
{
cnt = gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bFirstID;
for ( pSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bLastID; cnt++,pSoldier++)
{
if ( pSoldier->bActive )
{
pSoldier->bMoved = TRUE;
// record old life value... for creature AI; the human AI might
// want to use this too at some point
pSoldier->bOldLife = pSoldier->bLife;
}
}
gTacticalStatus.ubCurrentTeam++;
BeginTeamTurn( gTacticalStatus.ubCurrentTeam );
}
}
void EndAllAITurns( void )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"EndAllAITurns");
// warp turn to the player's turn
SOLDIERTYPE * pSoldier;
INT32 cnt;
// Remove any deadlock message
EndDeadlockMsg( );
if (INTERRUPT_QUEUED)
{
EndInterrupt( FALSE );
}
if ( gTacticalStatus.ubCurrentTeam != gbPlayerNum )
{
cnt = gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bFirstID;
for ( pSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bLastID; cnt++,pSoldier++)
{
if ( pSoldier->bActive )
{
pSoldier->bMoved = TRUE;
pSoldier->uiStatusFlags &= (~SOLDIER_UNDERAICONTROL);
// record old life value... for creature AI; the human AI might
// want to use this too at some point
pSoldier->bOldLife = pSoldier->bLife;
}
}
gTacticalStatus.ubCurrentTeam = gbPlayerNum;
//BeginTeamTurn( gTacticalStatus.ubCurrentTeam );
}
}
void EndTurnEvents( void )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"EndTurnEvents");
// HANDLE END OF TURN EVENTS
// handle team services like healing
HandleTeamServices( gbPlayerNum );
// handle smell and blood decay
DecaySmells();
// decay bomb timers and maybe set some off!
DecayBombTimers();
DecaySmokeEffects( GetWorldTotalSeconds( ) );
DecayLightEffects( GetWorldTotalSeconds( ) );
// decay AI warning values from corpses
DecayRottingCorpseAIWarnings();
}
//rain
BOOLEAN LightningEndOfTurn( UINT8 ubTeam );
//end rain
void BeginTeamTurn( UINT8 ubTeam )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"BeginTeamTurn");
INT32 cnt;
UINT8 ubID;
SOLDIERTYPE *pSoldier;
//rain
if( !LightningEndOfTurn( ubTeam ) )return;
//end rain
while( 1 )
{
if ( ubTeam > LAST_TEAM )
{
if ( HandleAirRaidEndTurn( ubTeam ) )
{
// End turn!!
ubTeam = gbPlayerNum;
gTacticalStatus.ubCurrentTeam = gbPlayerNum;
EndTurnEvents();
}
else
{
break;
}
}
else if (!(gTacticalStatus.Team[ ubTeam ].bTeamActive))
{
// inactive team, skip to the next one
ubTeam++;
gTacticalStatus.ubCurrentTeam++;
// skip back to the top, as we are processing another team now.
continue;
}
if ( gTacticalStatus.uiFlags & TURNBASED )
{
BeginLoggingForBleedMeToos( TRUE );
// decay team's public opplist
DecayPublicOpplist( ubTeam );
cnt = gTacticalStatus.Team[ ubTeam ].bFirstID;
for ( pSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ ubTeam ].bLastID; cnt++,pSoldier++)
{
if ( pSoldier->bActive && pSoldier->bLife > 0)
{
// decay personal opplist, and refresh APs and BPs
EVENT_BeginMercTurn( pSoldier, FALSE, 0 );
}
}
if (gTacticalStatus.bBoxingState == LOST_ROUND || gTacticalStatus.bBoxingState == WON_ROUND || gTacticalStatus.bBoxingState == DISQUALIFIED )
{
// we have no business being in here any more!
return;
}
BeginLoggingForBleedMeToos( FALSE );
}
RecalculateSoldiersAniSpeed();
if (ubTeam == gbPlayerNum )
{
// ATE: Check if we are still in a valid battle...
// ( they could have blead to death above )
if ( ( gTacticalStatus.uiFlags & INCOMBAT ) )
{
StartPlayerTeamTurn( TRUE, FALSE );
}
break;
}
else
{
#ifdef NETWORKED
// Only the host should do this
if(!gfAmIHost)
break;
#endif
// Set First enemy merc to AI control
if ( BuildAIListForTeam( ubTeam ) )
{
ubID = RemoveFirstAIListEntry();
if (ubID != NOBODY)
{
// Dirty panel interface!
fInterfacePanelDirty = DIRTYLEVEL2;
if ( ubTeam == CREATURE_TEAM && BloodcatsPresent() )
{
AddTopMessage( COMPUTER_TURN_MESSAGE, Message[ STR_BLOODCATS_TURN ] );
}
else
{
AddTopMessage( COMPUTER_TURN_MESSAGE, TeamTurnString[ ubTeam ] );
}
StartNPCAI( MercPtrs[ ubID ] );
return;
}
}
// This team is dead/inactive/being skipped in boxing
// skip back to the top to process the next team
ubTeam++;
gTacticalStatus.ubCurrentTeam++;
}
}
}
void DisplayHiddenInterrupt( SOLDIERTYPE * pSoldier )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DisplayHiddenInterrupt");
// If the AI got an interrupt but this has been hidden from the player until this point,
// this code will display the interrupt
if (!gfHiddenInterrupt)
{
return;
}
EndDeadlockMsg( );
if (pSoldier->bVisible != -1 )
{
SlideTo( NOWHERE, pSoldier->ubID, NOBODY ,SETLOCATOR);
}
guiPendingOverrideEvent = LU_BEGINUILOCK;
// Dirty panel interface!
fInterfacePanelDirty = DIRTYLEVEL2;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"about to call ErasePath");
// Erase path!
ErasePath( TRUE );
// Reset flags
gfPlotNewMovement = TRUE;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"about to call AdjustNoAPToFinishMove");
// Stop our guy....
AdjustNoAPToFinishMove( MercPtrs[ LATEST_INTERRUPT_GUY ], TRUE );
// Stop him from going to prone position if doing a turn while prone
MercPtrs[ LATEST_INTERRUPT_GUY ]->fTurningFromPronePosition = FALSE;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"about to call AddTopMessage");
// get rid of any old overlay message
if ( pSoldier->bTeam == MILITIA_TEAM )
{
AddTopMessage( MILITIA_INTERRUPT_MESSAGE, Message[ STR_INTERRUPT ] );
}
else
{
AddTopMessage( COMPUTER_INTERRUPT_MESSAGE, Message[ STR_INTERRUPT ] );
}
gfHiddenInterrupt = FALSE;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DisplayHiddenInterrupt completed");
}
void DisplayHiddenTurnbased( SOLDIERTYPE * pActingSoldier )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DisplayHiddenTurnBased");
// This code should put the game in turn-based and give control to the AI-controlled soldier
// whose pointer has been passed in as an argument (we were in non-combat and the AI is doing
// something visible, i.e. making an attack)
if ( AreInMeanwhile( ) )
{
return;
}
if (gTacticalStatus.uiFlags & REALTIME || gTacticalStatus.uiFlags & INCOMBAT)
{
// pointless call here; do nothing
return;
}
// Enter combat mode starting with this side's turn
gTacticalStatus.ubCurrentTeam = pActingSoldier->bTeam;
CommonEnterCombatModeCode( );
//JA2Gold: use function to make sure flags turned off everywhere else
//pActingSoldier->uiStatusFlags |= SOLDIER_UNDERAICONTROL;
SetSoldierAsUnderAiControl( pActingSoldier );
DebugAI( String( "Giving AI control to %d", pActingSoldier->ubID ) );
pActingSoldier->fTurnInProgress = TRUE;
gTacticalStatus.uiTimeSinceMercAIStart = GetJA2Clock();
if ( gTacticalStatus.ubTopMessageType != COMPUTER_TURN_MESSAGE)
{
// Dirty panel interface!
fInterfacePanelDirty = DIRTYLEVEL2;
if ( gTacticalStatus.ubCurrentTeam == CREATURE_TEAM && BloodcatsPresent() )
{
AddTopMessage( COMPUTER_TURN_MESSAGE, Message[ STR_BLOODCATS_TURN ] );
}
else
{
AddTopMessage( COMPUTER_TURN_MESSAGE, TeamTurnString[ gTacticalStatus.ubCurrentTeam ] );
}
}
// freeze the user's interface
FreezeInterfaceForEnemyTurn();
}
BOOLEAN EveryoneInInterruptListOnSameTeam( void )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"EveryoneInInterruptListOnSameTeam");
UINT8 ubLoop;
UINT8 ubTeam = 255;
for (ubLoop = 1; ubLoop <= gubOutOfTurnPersons; ubLoop++)
{
if ( ubTeam == 255 )
{
ubTeam = MercPtrs[ gubOutOfTurnOrder[ ubLoop ] ]->bTeam;
}
else
{
if ( MercPtrs[ gubOutOfTurnOrder[ ubLoop ] ]->bTeam != ubTeam )
{
return( FALSE );
}
}
}
return( TRUE );
}
void StartInterrupt( void )
{
UINT8 ubFirstInterrupter;
INT8 bTeam;
SOLDIERTYPE * pSoldier;
SOLDIERTYPE * pTempSoldier;
UINT8 ubInterrupter;
INT32 cnt;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"StartInterrupt");
ubFirstInterrupter = LATEST_INTERRUPT_GUY;
pSoldier = MercPtrs[ubFirstInterrupter];
bTeam = pSoldier->bTeam;
ubInterrupter = ubFirstInterrupter;
// display everyone on int queue!
for ( cnt = gubOutOfTurnPersons; cnt > 0; cnt-- )
{
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("STARTINT: Q position %d: %d", cnt, gubOutOfTurnOrder[ cnt ] ) );
}
//DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("INTERRUPT: %d is now on top of the interrupt queue", ubFirstInterrupter ) );
gTacticalStatus.fInterruptOccurred = TRUE;
cnt = 0;
for ( pTempSoldier = MercPtrs[ cnt ]; cnt <= MAX_NUM_SOLDIERS; cnt++,pTempSoldier++)
{
if ( pTempSoldier->bActive )
{
pTempSoldier->bMovedPriorToInterrupt = pTempSoldier->bMoved;
pTempSoldier->bMoved = TRUE;
}
}
if (pSoldier->bTeam == OUR_TEAM)
{
// start interrupts for everyone on our side at once
CHAR16 sTemp[ 255 ];
UINT8 ubInterrupters = 0;
INT32 iSquad, iCounter;
// build string for display of who gets interrupt
while( 1 )
{
MercPtrs[ubInterrupter]->bMoved = FALSE;
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("INTERRUPT: popping %d off of the interrupt queue", ubInterrupter ) );
REMOVE_LATEST_INTERRUPT_GUY();
// now LATEST_INTERRUPT_GUY is the guy before the previous
ubInterrupter = LATEST_INTERRUPT_GUY;
if (ubInterrupter == NOBODY) // previously emptied slot!
{
continue;
}
else if (MercPtrs[ubInterrupter]->bTeam != bTeam)
{
break;
}
}
wcscpy( sTemp, Message[ STR_INTERRUPT_FOR ] );
// build string in separate loop here, want to linearly process squads...
for ( iSquad = 0; iSquad < NUMBER_OF_SQUADS; iSquad++ )
{
for ( iCounter = 0; iCounter < NUMBER_OF_SOLDIERS_PER_SQUAD; iCounter++ )
{
pTempSoldier = Squad[ iSquad ][ iCounter ];
if ( pTempSoldier && pTempSoldier->bActive && pTempSoldier->bInSector && !pTempSoldier->bMoved )
{
// then this guy got an interrupt...
ubInterrupters++;
if ( ubInterrupters > 6 )
{
// flush... display string, then clear it (we could have 20 names!)
// add comma to end, we know we have another person after this...
wcscat( sTemp, L", " );
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, sTemp );
wcscpy( sTemp, L"" );
ubInterrupters = 1;
}
if ( ubInterrupters > 1 )
{
wcscat( sTemp, L", " );
}
wcscat( sTemp, pTempSoldier->name );
}
}
}
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, sTemp );
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("INTERRUPT: starting interrupt for %d", ubFirstInterrupter ) );
// gusSelectedSoldier should become the topmost guy on the interrupt list
//gusSelectedSoldier = ubFirstInterrupter;
// Remove deadlock message
EndDeadlockMsg( );
// Select guy....
SelectSoldier( ubFirstInterrupter, TRUE, TRUE );
// ATE; Slide to guy who got interrupted!
SlideTo( NOWHERE, gubLastInterruptedGuy, NOBODY, SETLOCATOR);
// Dirty panel interface!
fInterfacePanelDirty = DIRTYLEVEL2;
gTacticalStatus.ubCurrentTeam = pSoldier->bTeam;
// Signal UI done enemy's turn
guiPendingOverrideEvent = LU_ENDUILOCK;
HandleTacticalUI( );
InitPlayerUIBar( TRUE );
//AddTopMessage( PLAYER_INTERRUPT_MESSAGE, Message[STR_INTERRUPT] );
PlayJA2Sample( ENDTURN_1, RATE_11025, MIDVOLUME, 1, MIDDLEPAN );
// report any close call quotes for us here
for ( iCounter = gTacticalStatus.Team[ gbPlayerNum ].bFirstID; iCounter <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; iCounter++ )
{
if ( OK_INSECTOR_MERC( MercPtrs[ iCounter ] ) )
{
if ( MercPtrs[ iCounter ]->fCloseCall )
{
if ( MercPtrs[ iCounter ]->bNumHitsThisTurn == 0 && !(MercPtrs[ iCounter ]->usQuoteSaidExtFlags & SOLDIER_QUOTE_SAID_EXT_CLOSE_CALL) && Random( 3 ) == 0 )
{
// say close call quote!
TacticalCharacterDialogue( MercPtrs[ iCounter ], QUOTE_CLOSE_CALL );
MercPtrs[ iCounter ]->usQuoteSaidExtFlags |= SOLDIER_QUOTE_SAID_EXT_CLOSE_CALL;
}
MercPtrs[ iCounter ]->fCloseCall = FALSE;
}
}
}
}
else
{
// start interrupts for everyone on that side at once... and start AI with the lowest # guy
// what we do is set everyone to moved except for people with interrupts at the moment
/*
cnt = gTacticalStatus.Team[ pSoldier->bTeam ].bFirstID;
for ( pTempSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ pSoldier->bTeam ].bLastID; cnt++,pTempSoldier++)
{
if ( pTempSoldier->bActive )
{
pTempSoldier->bMovedPriorToInterrupt = pTempSoldier->bMoved;
pTempSoldier->bMoved = TRUE;
}
}
*/
while( 1 )
{
MercPtrs[ubInterrupter]->bMoved = FALSE;
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("INTERRUPT: popping %d off of the interrupt queue", ubInterrupter ) );
REMOVE_LATEST_INTERRUPT_GUY();
// now LATEST_INTERRUPT_GUY is the guy before the previous
ubInterrupter = LATEST_INTERRUPT_GUY;
if (ubInterrupter == NOBODY) // previously emptied slot!
{
continue;
}
else if (MercPtrs[ubInterrupter]->bTeam != bTeam)
{
break;
}
else if (ubInterrupter < ubFirstInterrupter)
{
ubFirstInterrupter = ubInterrupter;
}
}
// here we have to rebuilt the AI list!
BuildAIListForTeam( bTeam );
// set to the new first interrupter
cnt = RemoveFirstAIListEntry();
pSoldier = MercPtrs[ cnt ];
// pSoldier = MercPtrs[ubFirstInterrupter];
//if ( gTacticalStatus.ubCurrentTeam == OUR_TEAM)
if ( pSoldier->bTeam != OUR_TEAM )
{
// we're being interrupted by the computer!
// we delay displaying any interrupt message until the computer
// does something...
gfHiddenInterrupt = TRUE;
gTacticalStatus.fUnLockUIAfterHiddenInterrupt = FALSE;
}
// otherwise it's the AI interrupting another AI team
gTacticalStatus.ubCurrentTeam = pSoldier->bTeam;
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_TESTVERSION, L"Interrupt ( could be hidden )" );
#endif
StartNPCAI( pSoldier );
}
if ( !gfHiddenInterrupt )
{
// Stop this guy....
AdjustNoAPToFinishMove( MercPtrs[ LATEST_INTERRUPT_GUY ], TRUE );
MercPtrs[ LATEST_INTERRUPT_GUY ]->fTurningFromPronePosition = FALSE;
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"StartInterrupt done");
}
void EndInterrupt( BOOLEAN fMarkInterruptOccurred )
{
UINT8 ubInterruptedSoldier;
SOLDIERTYPE * pSoldier;
SOLDIERTYPE * pTempSoldier;
INT32 cnt;
BOOLEAN fFound;
UINT8 ubMinAPsToAttack;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"EndInterrupt");
for ( cnt = gubOutOfTurnPersons; cnt > 0; cnt-- )
{
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("ENDINT: Q position %d: %d", cnt, gubOutOfTurnOrder[ cnt ] ) );
}
// ATE: OK, now if this all happended on one frame, we may not have to stop
// guy from walking... so set this flag to false if so...
if ( fMarkInterruptOccurred )
{
// flag as true if an int occurs which ends an interrupt (int loop)
gTacticalStatus.fInterruptOccurred = TRUE;
}
else
{
gTacticalStatus.fInterruptOccurred = FALSE;
}
// Loop through all mercs and see if any passed on this interrupt
cnt = gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bFirstID;
for ( pTempSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bLastID; cnt++,pTempSoldier++)
{
if ( pTempSoldier->bActive && pTempSoldier->bInSector && !pTempSoldier->bMoved && (pTempSoldier->bActionPoints == pTempSoldier->bIntStartAPs))
{
ubMinAPsToAttack = MinAPsToAttack( pTempSoldier, pTempSoldier->sLastTarget, FALSE );
if ( (ubMinAPsToAttack <= pTempSoldier->bActionPoints) && (ubMinAPsToAttack > 0) )
{
pTempSoldier->bPassedLastInterrupt = TRUE;
}
}
}
if ( !EveryoneInInterruptListOnSameTeam() )
{
gfHiddenInterrupt = FALSE;
// resume interrupted interrupt
StartInterrupt();
}
else
{
ubInterruptedSoldier = LATEST_INTERRUPT_GUY;
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("INTERRUPT: interrupt over, %d's team regains control", ubInterruptedSoldier ) );
pSoldier = MercPtrs[ubInterruptedSoldier];
cnt = 0;
for ( pTempSoldier = MercPtrs[ cnt ]; cnt <= MAX_NUM_SOLDIERS; cnt++,pTempSoldier++)
{
if ( pTempSoldier->bActive )
{
// AI guys only here...
if ( pTempSoldier->bActionPoints == 0 )
{
pTempSoldier->bMoved = TRUE;
}
else if ( pTempSoldier->bTeam != gbPlayerNum && pTempSoldier->bNewSituation == IS_NEW_SITUATION )
{
pTempSoldier->bMoved = FALSE;
}
else
{
pTempSoldier->bMoved = pTempSoldier->bMovedPriorToInterrupt;
}
}
}
// change team
gTacticalStatus.ubCurrentTeam = pSoldier->bTeam;
// switch appropriate messages & flags
if ( pSoldier->bTeam == OUR_TEAM)
{
// set everyone on the team to however they were set moved before the interrupt
// must do this before selecting soldier...
/*
cnt = gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bFirstID;
for ( pTempSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bLastID; cnt++,pTempSoldier++)
{
if ( pTempSoldier->bActive )
{
pTempSoldier->bMoved = pTempSoldier->bMovedPriorToInterrupt;
}
}
*/
ClearIntList();
// Select soldier....
if ( MercPtrs[ ubInterruptedSoldier ]->bLife < OKLIFE )
{
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("EndInterrupt: SelectNextAvailSoldier"));
SelectNextAvailSoldier( MercPtrs[ ubInterruptedSoldier ] );
}
else
{
SelectSoldier( ubInterruptedSoldier, FALSE, FALSE );
}
if (gfHiddenInterrupt)
{
// Try to make things look like nothing happened at all.
gfHiddenInterrupt = FALSE;
// If we can continue a move, do so!
if ( MercPtrs[ gusSelectedSoldier ]->fNoAPToFinishMove && pSoldier->ubReasonCantFinishMove != REASON_STOPPED_SIGHT )
{
// Continue
AdjustNoAPToFinishMove( MercPtrs[ gusSelectedSoldier ], FALSE );
if ( MercPtrs[ gusSelectedSoldier ]->sGridNo != MercPtrs[ gusSelectedSoldier ]->sFinalDestination )
{
EVENT_GetNewSoldierPath( MercPtrs[ gusSelectedSoldier ], MercPtrs[ gusSelectedSoldier ]->sFinalDestination, MercPtrs[ gusSelectedSoldier ]->usUIMovementMode );
}
else
{
UnSetUIBusy( pSoldier->ubID );
}
}
else
{
UnSetUIBusy( pSoldier->ubID );
}
if ( gTacticalStatus.fUnLockUIAfterHiddenInterrupt )
{
gTacticalStatus.fUnLockUIAfterHiddenInterrupt = FALSE;
UnSetUIBusy( pSoldier->ubID );
}
}
else
{
// Signal UI done enemy's turn
/// ATE: This used to be ablow so it would get done for
// both hidden interrupts as well - NOT good because
// hidden interrupts should leave it locked if it was already...
guiPendingOverrideEvent = LU_ENDUILOCK;
HandleTacticalUI( );
if ( gusSelectedSoldier != NO_SOLDIER )
{
SlideTo( NOWHERE, gusSelectedSoldier, NOBODY ,SETLOCATOR);
// Say ATTENTION SOUND...
DoMercBattleSound( MercPtrs[ gusSelectedSoldier ], BATTLE_SOUND_ATTN1 );
if ( gsInterfaceLevel == 1 )
{
gTacticalStatus.uiFlags |= SHOW_ALL_ROOFS;
InvalidateWorldRedundency( );
SetRenderFlags(RENDER_FLAG_FULL);
ErasePath(FALSE);
}
}
// 2 indicates that we're ending an interrupt and going back to
// normal player's turn without readjusting time left in turn (for
// timed turns)
InitPlayerUIBar( 2 );
}
}
else
{
// this could be set to true for AI-vs-AI interrupts
gfHiddenInterrupt = FALSE;
// Dirty panel interface!
fInterfacePanelDirty = DIRTYLEVEL2;
// Erase path!
ErasePath( TRUE );
// Reset flags
gfPlotNewMovement = TRUE;
// restart AI with first available soldier
fFound = FALSE;
// rebuild list for this team if anyone on the team is still available
cnt = gTacticalStatus.Team[ ENEMY_TEAM ].bFirstID;
for ( pTempSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bLastID; cnt++,pTempSoldier++)
{
if ( pTempSoldier->bActive && pTempSoldier->bInSector && pTempSoldier->bLife >= OKLIFE )
{
fFound = TRUE;
break;
}
}
if ( fFound )
{
// reset found flag because we are rebuilding the AI list
fFound = FALSE;
if ( BuildAIListForTeam( gTacticalStatus.ubCurrentTeam ) )
{
// now bubble up everyone left in the interrupt queue, starting
// at the front of the array
for (cnt = 1; cnt <= gubOutOfTurnPersons; cnt++)
{
MoveToFrontOfAIList( gubOutOfTurnOrder[ cnt ] );
}
cnt = RemoveFirstAIListEntry();
if (cnt != NOBODY)
{
fFound = TRUE;
StartNPCAI( MercPtrs[ cnt ] );
}
}
}
if (fFound)
{
// back to the computer!
if ( gTacticalStatus.ubCurrentTeam == CREATURE_TEAM && BloodcatsPresent() )
{
AddTopMessage( COMPUTER_TURN_MESSAGE, Message[ STR_BLOODCATS_TURN ] );
}
else
{
AddTopMessage( COMPUTER_TURN_MESSAGE, TeamTurnString[ gTacticalStatus.ubCurrentTeam ] );
}
// Signal UI done enemy's turn
guiPendingOverrideEvent = LU_BEGINUILOCK;
ClearIntList();
}
else
{
// back to the computer!
if ( gTacticalStatus.ubCurrentTeam == CREATURE_TEAM && BloodcatsPresent() )
{
AddTopMessage( COMPUTER_TURN_MESSAGE, Message[ STR_BLOODCATS_TURN ] );
}
else
{
AddTopMessage( COMPUTER_TURN_MESSAGE, TeamTurnString[ gTacticalStatus.ubCurrentTeam ] );
}
// Signal UI done enemy's turn
guiPendingOverrideEvent = LU_BEGINUILOCK;
// must clear int list before ending turn
ClearIntList();
EndAITurn();
}
}
// Reset our interface!
fInterfacePanelDirty = DIRTYLEVEL2;
}
}
BOOLEAN StandardInterruptConditionsMet( SOLDIERTYPE * pSoldier, UINT8 ubOpponentID, INT8 bOldOppList)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"StandardInterruptConditionsMet");
// UINT8 ubAniType;
UINT8 ubMinPtsNeeded;
INT8 bDir;
SOLDIERTYPE * pOpponent;
if ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) && !(gubSightFlags & SIGHT_INTERRUPT) )
{
return( FALSE );
}
if ( gTacticalStatus.ubAttackBusyCount > 0 )
{
return( FALSE );
}
if (ubOpponentID < NOBODY)
{
/*
// only the OPPONENT'S controller's decision matters
if (Menptr[ubOpponentID].controller != Net.pnum)
{
return(FALSE);
}
*/
// ALEX
// if interrupts are restricted to a particular opponent only & he's not it
if ((InterruptOnlyGuynum != NOBODY) && (ubOpponentID != InterruptOnlyGuynum))
{
return(FALSE);
}
pOpponent = MercPtrs[ ubOpponentID ];
}
else // no opponent, so controller of 'ptr' makes the call instead
{
// ALEX
if (gsWhoThrewRock >= NOBODY)
{
#ifdef BETAVERSION
NumMessage("StandardInterruptConditions: ERROR - ubOpponentID is NOBODY, don't know who threw rock, guynum = ",pSoldier->guynum);
#endif
return(FALSE);
}
// the machine that controls the guy who threw the rock makes the decision
/*
if (Menptr[WhoThrewRock].controller != Net.pnum)
return(FALSE);
*/
pOpponent = NULL;
}
// if interrupts have been disabled for any reason
if (!InterruptsAllowed)
{
return(FALSE);
}
// in non-combat allow interrupt points to be calculated freely (everyone's in control!)
// also allow calculation for storing in AllTeamsLookForAll
if ( (gTacticalStatus.uiFlags & INCOMBAT) && ( gubBestToMakeSightingSize != BEST_SIGHTING_ARRAY_SIZE_ALL_TEAMS_LOOK_FOR_ALL ) )
{
// if his team's already in control
if (pSoldier->bTeam == gTacticalStatus.ubCurrentTeam )
{
// if this is a player's a merc or civilian
if ((pSoldier->uiStatusFlags & SOLDIER_PC) || PTR_CIVILIAN)
{
// then they are not allowed to interrupt their own team
return(FALSE);
}
else
{
// enemies, MAY interrupt each other, but NOT themselves!
//if ( pSoldier->uiStatusFlags & SOLDIER_UNDERAICONTROL )
//{
return(FALSE);
//}
}
// CJC, July 9 1998
// NO ONE EVER interrupts his own team
//return( FALSE );
}
else if ( gTacticalStatus.bBoxingState != NOT_BOXING )
{
// while anything to do with boxing is going on, skip interrupts!
return( FALSE );
}
}
if ( !(pSoldier->bActive) || !(pSoldier->bInSector ) )
{
return( FALSE );
}
// soldiers at less than OKLIFE can't perform any actions
if (pSoldier->bLife < OKLIFE)
{
return(FALSE);
}
// soldiers out of breath are about to fall over, no interrupt
if (pSoldier->bBreath < OKBREATH || pSoldier->bCollapsed )
{
return(FALSE);
}
// if soldier doesn't have enough APs
if ( pSoldier->bActionPoints < MIN_APS_TO_INTERRUPT )
{
return( FALSE );
}
// soldiers gagging on gas are too busy about holding their cookies down...
if ( pSoldier->uiStatusFlags & SOLDIER_GASSED )
{
return(FALSE);
}
// a soldier already engaged in a life & death battle is too busy doing his
// best to survive to worry about "getting the jump" on additional threats
if (pSoldier->bUnderFire)
{
return(FALSE);
}
if (pSoldier->bCollapsed)
{
return( FALSE );
}
// don't allow neutral folks to get interrupts
if (pSoldier->bNeutral)
{
return( FALSE );
}
// no EPCs allowed to get interrupts
if ( AM_AN_EPC( pSoldier ) && !AM_A_ROBOT( pSoldier ) )
{
return( FALSE );
}
// don't let mercs on assignment get interrupts
if ( pSoldier->bTeam == gbPlayerNum && pSoldier->bAssignment >= ON_DUTY)
{
return( FALSE );
}
// the bare minimum default is enough APs left to TURN
ubMinPtsNeeded = AP_CHANGE_FACING;
// if the opponent is SOMEBODY
if (ubOpponentID < NOBODY)
{
// if the soldiers are on the same side
if (pSoldier->bSide == pOpponent->bSide)
{
// human/civilians on same side can't interrupt each other
if ((pSoldier->uiStatusFlags & SOLDIER_PC) || PTR_CIVILIAN)
{
return(FALSE);
}
else // enemy
{
// enemies can interrupt EACH OTHER, but enemies and civilians on the
// same side (but different teams) can't interrupt each other.
if (pSoldier->bTeam != pOpponent->bTeam)
{
return(FALSE);
}
}
}
// if the interrupted opponent is not the selected character, then the only
// people eligible to win an interrupt are those on the SAME SIDE AS
// the selected character, ie. his friends...
if ( pOpponent->bTeam == gbPlayerNum )
{
if ((ubOpponentID != gusSelectedSoldier) && (pSoldier->bSide != Menptr[gusSelectedSoldier].bSide))
{
return( FALSE );
}
}
else
{
if ( !(pOpponent->uiStatusFlags & SOLDIER_UNDERAICONTROL) && (pSoldier->bSide != pOpponent->bSide))
{
return( FALSE );
}
}
/* old DG code for same:
if ((ubOpponentID != gusSelectedSoldier) && (pSoldier->bSide != Menptr[gusSelectedSoldier].bSide))
{
return(FALSE);
}
*/
// an non-active soldier can't interrupt a soldier who is also non-active!
if ((pOpponent->bTeam != gTacticalStatus.ubCurrentTeam) && (pSoldier->bTeam != gTacticalStatus.ubCurrentTeam))
{
return(FALSE);
}
// if this is a "SEEING" interrupt
if (pSoldier->bOppList[ubOpponentID] == SEEN_CURRENTLY)
{
// if pSoldier already saw the opponent last "look" or at least this turn
if ((bOldOppList == SEEN_CURRENTLY) || (bOldOppList == SEEN_THIS_TURN))
{
return(FALSE); // no interrupt is possible
}
// if the soldier is behind him and not very close, forget it
bDir = atan8( pSoldier->sX, pSoldier->sY, pOpponent->sX, pOpponent->sY );
if ( gOppositeDirection[ pSoldier->bDesiredDirection ] == bDir )
{
// directly behind; allow interrupts only within # of tiles equal to level
if ( PythSpacesAway( pSoldier->sGridNo, pOpponent->sGridNo ) > EffectiveExpLevel( pSoldier ) )
{
return( FALSE );
}
}
// if the soldier isn't currently crouching
if (!PTR_CROUCHED)
{
ubMinPtsNeeded = AP_CROUCH;
}
else
{
ubMinPtsNeeded = MinPtsToMove(pSoldier);
}
}
else // this is a "HEARING" interrupt
{
// if the opponent can't see the "interrupter" either, OR
// if the "interrupter" already has any opponents already in sight, OR
// if the "interrupter" already heard the active soldier this turn
if ((pOpponent->bOppList[pSoldier->ubID] != SEEN_CURRENTLY) || (pSoldier->bOppCnt > 0) || (bOldOppList == HEARD_THIS_TURN))
{
return(FALSE); // no interrupt is possible
}
}
}
// soldiers without sufficient APs to do something productive can't interrupt
if (pSoldier->bActionPoints < ubMinPtsNeeded)
{
return(FALSE);
}
// soldier passed on the chance to react during previous interrupt this turn
if (pSoldier->bPassedLastInterrupt)
{
#ifdef RECORDNET
fprintf(NetDebugFile,"\tStandardInterruptConditionsMet: FAILING because PassedLastInterrupt %d(%s)\n",
pSoldier->guynum,ExtMen[pSoldier->guynum].name);
#endif
// return(FALSE);
}
#ifdef RECORDINTERRUPT
// this usually starts a new series of logs, so that's why the blank line
fprintf(InterruptFile,"\nStandardInterruptConditionsMet by %d vs. %d\n",pSoldier->guynum,ubOpponentID);
#endif
return(TRUE);
}
INT8 CalcInterruptDuelPts( SOLDIERTYPE * pSoldier, UINT8 ubOpponentID, BOOLEAN fUseWatchSpots )
{
INT32 iPoints;
INT8 bLightLevel;
UINT8 ubDistance;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalcInterruptDuelPts");
// extra check to make sure neutral folks never get interrupts
if (pSoldier->bNeutral)
{
return( NO_INTERRUPT );
}
// Old: BASE is one point for each experience level.
// Snap: Agility should be a factor, since it is a measure of
// a person's reactions. We'll give more weight to experience
// though, since combat initiative is too important to be left up
// to an ordinary skill like agility.
// BASE = (2*lev + agi/10) / 3
// Robot has interrupt points based on the controller...
// Controller's interrupt points are reduced by 2 for being distracted...
if ( pSoldier->uiStatusFlags & SOLDIER_ROBOT && CanRobotBeControlled( pSoldier ) )
{
//iPoints = EffectiveExpLevel( MercPtrs[ pSoldier->ubRobotRemoteHolderID ] ) - 2;
// Snap: (do some proper rounding here)
iPoints = ( 20*EffectiveExpLevel( MercPtrs[ pSoldier->ubRobotRemoteHolderID ] )
+ EffectiveAgility( MercPtrs[ pSoldier->ubRobotRemoteHolderID ] ) + 15 ) / 30 - 2;
}
else
{
//iPoints = EffectiveExpLevel( pSoldier );
// Snap:
iPoints = ( 20*EffectiveExpLevel( pSoldier ) + EffectiveAgility( pSoldier ) + 15 ) / 30;
/*
if ( pSoldier->bTeam == ENEMY_TEAM )
{
// modify by the difficulty level setting
iPoints += gbDiff[ DIFF_ENEMY_INTERRUPT_MOD ][ SoldierDifficultyLevel( pSoldier ) ];
iPoints = __max( iPoints, 9 );
}
*/
if ( ControllingRobot( pSoldier ) )
{
iPoints -= 2;
}
}
if (fUseWatchSpots)
{
// if this is a previously noted spot of enemies, give bonus points!
iPoints += GetWatchedLocPoints( pSoldier->ubID, MercPtrs[ ubOpponentID ]->sGridNo, MercPtrs[ ubOpponentID ]->bLevel );
}
// LOSE one point for each 2 additional opponents he currently sees, above 2
if (pSoldier->bOppCnt > 2)
{
// subtract 1 here so there is a penalty of 1 for seeing 3 enemies
iPoints -= (pSoldier->bOppCnt - 1) / 2;
}
// LOSE one point if he's trying to interrupt only by hearing
if (pSoldier->bOppList[ubOpponentID] == HEARD_THIS_TURN)
{
iPoints--;
}
// if soldier is still in shock from recent injuries, that penalizes him
iPoints -= pSoldier->bShock;
ubDistance = (UINT8) PythSpacesAway( pSoldier->sGridNo, MercPtrs[ ubOpponentID ]->sGridNo );
// if we are in combat mode - thus doing an interrupt rather than determine who gets first turn -
// then give bonus
if ( (gTacticalStatus.uiFlags & INCOMBAT) && (pSoldier->bTeam != gTacticalStatus.ubCurrentTeam) )
{
// passive player gets penalty due to range
iPoints -= (ubDistance / 10);
}
else
{
// either non-combat or the player with the current turn... i.e. active...
// unfortunately we can't use opplist here to record whether or not we saw this guy before, because at this point
// the opplist has been updated to seen. But we can use gbSeenOpponents ...
// this soldier is moving, so give them a bonus for crawling or swatting at long distances
if ( !gbSeenOpponents[ ubOpponentID ][ pSoldier->ubID ] )
{
if (pSoldier->usAnimState == SWATTING && ubDistance > (MaxDistanceVisible() / 2) ) // more than 1/2 sight distance
{
iPoints++;
}
else if (pSoldier->usAnimState == CRAWLING && ubDistance > (MaxDistanceVisible() / 4) ) // more than 1/4 sight distance
{
iPoints += ubDistance / STRAIGHT;
}
}
}
// whether active or not, penalize people who are running
if ( pSoldier->usAnimState == RUNNING && !gbSeenOpponents[ pSoldier->ubID ][ ubOpponentID ] )
{
iPoints -= 2;
}
if (pSoldier->ubServicePartner != NOBODY)
{
// distracted by being bandaged/doing bandaging
iPoints -= 2;
}
if ( HAS_SKILL_TRAIT( pSoldier, NIGHTOPS ) )
{
bLightLevel = LightTrueLevel(pSoldier->sGridNo, pSoldier->bLevel);
if (bLightLevel > NORMAL_LIGHTLEVEL_DAY + 3)
{
// it's dark, give a bonus for interrupts
iPoints += 1 * NUM_SKILL_TRAITS( pSoldier, NIGHTOPS );
}
}
// if he's a computer soldier
// CJC note: this will affect friendly AI as well...
if ( pSoldier->uiStatusFlags & SOLDIER_PC )
{
if ( pSoldier->bAssignment >= ON_DUTY )
{
// make sure don't get interrupts!
iPoints = -10;
}
// GAIN one point if he's previously seen the opponent
// check for TRUE because -1 means we JUST saw him (always so here)
if (gbSeenOpponents[pSoldier->ubID][ubOpponentID] == TRUE)
{
iPoints++; // seen him before, easier to react to him
}
}
else if ( pSoldier->bTeam == ENEMY_TEAM )
{
// GAIN one point if he's previously seen the opponent
// check for TRUE because -1 means we JUST saw him (always so here)
if (gbSeenOpponents[pSoldier->ubID][ubOpponentID] == TRUE)
{
iPoints++; // seen him before, easier to react to him
}
else if (gbPublicOpplist[pSoldier->bTeam][ubOpponentID] != NOT_HEARD_OR_SEEN)
{
// GAIN one point if opponent has been recently radioed in by his team
iPoints++;
}
}
if ( TANK( pSoldier ) )
{
// reduce interrupt possibilities for tanks!
iPoints /= 2;
}
if (iPoints >= AUTOMATIC_INTERRUPT)
{
#ifdef BETAVERSION
NumMessage("CalcInterruptDuelPts: ERROR - Invalid bInterruptDuelPts calculated for soldier ",pSoldier->guynum);
#endif
iPoints = AUTOMATIC_INTERRUPT - 1; // hack it to one less than max so its legal
}
#ifdef DEBUG_INTERRUPTS
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Calculating int pts for %d vs %d, number is %d", pSoldier->ubID, ubOpponentID, iPoints ) );
#endif
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalcInterruptDuelPts done");
return( (INT8)iPoints );
}
BOOLEAN InterruptDuel( SOLDIERTYPE * pSoldier, SOLDIERTYPE * pOpponent)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"InterruptDuel");
BOOLEAN fResult = FALSE;
// if opponent can't currently see us and we can see them
if ( pSoldier->bOppList[ pOpponent->ubID ] == SEEN_CURRENTLY && pOpponent->bOppList[pSoldier->ubID] != SEEN_CURRENTLY )
{
fResult = TRUE; // we automatically interrupt
// fix up our interrupt duel pts if necessary
if (pSoldier->bInterruptDuelPts < pOpponent->bInterruptDuelPts)
{
pSoldier->bInterruptDuelPts = pOpponent->bInterruptDuelPts;
}
}
else
{
// If our total points is HIGHER, then we interrupt him anyway
if (pSoldier->bInterruptDuelPts > pOpponent->bInterruptDuelPts)
{
fResult = TRUE;
}
}
// ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Interrupt duel %d (%d pts) vs %d (%d pts)", pSoldier->ubID, pSoldier->bInterruptDuelPts, pOpponent->ubID, pOpponent->bInterruptDuelPts );
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"InterruptDuel done");
return( fResult );
}
void DeleteFromIntList( UINT8 ubIndex, BOOLEAN fCommunicate)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DeleteFromIntList");
UINT8 ubLoop;
UINT8 ubID;
if ( ubIndex > gubOutOfTurnPersons)
{
return;
}
// remember who we're getting rid of
ubID = gubOutOfTurnOrder[ubIndex];
#ifdef DEBUG_INTERRUPTS
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("INTERRUPT: removing ID %d", ubID ) );
#endif
// ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%d removed from int list", ubID );
// if we're NOT deleting the LAST entry in the int list
if (ubIndex < gubOutOfTurnPersons)
{
// not the last entry, must move all those behind it over to fill the gap
for (ubLoop = ubIndex; ubLoop < gubOutOfTurnPersons; ubLoop++)
{
gubOutOfTurnOrder[ubLoop] = gubOutOfTurnOrder[ubLoop + 1];
}
}
// either way, whack the last entry to NOBODY and decrement the list size
gubOutOfTurnOrder[gubOutOfTurnPersons] = NOBODY;
gubOutOfTurnPersons--;
// once the last interrupted guy gets deleted from the list, he's no longer
// the last interrupted guy!
/*
if (Status.lastInterruptedWas == ubID)
{
Status.lastInterruptedWas = NOBODY;
}
*/
}
void AddToIntList( UINT8 ubID, BOOLEAN fGainControl, BOOLEAN fCommunicate )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"AddToIntList");
UINT8 ubLoop;
// ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%d added to int list", ubID );
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("INTERRUPT: adding ID %d who %s", ubID, fGainControl ? "gains control" : "loses control" ) );
// check whether 'who' is already anywhere on the queue after the first index
// which we want to preserve so we can restore turn order
for (ubLoop = 2; ubLoop <= gubOutOfTurnPersons; ubLoop++)
{
if (gubOutOfTurnOrder[ubLoop] == ubID)
{
if (!fGainControl)
{
// he's LOSING control; that's it, we're done, DON'T add him to the queue again
gubLastInterruptedGuy = ubID;
return;
}
else
{
// GAINING control, so delete him from this slot (because later he'll
// get added to the end and we don't want him listed more than once!)
DeleteFromIntList( ubLoop, FALSE );
}
}
}
// increment total (making index valid) and add him to list
gubOutOfTurnPersons++;
gubOutOfTurnOrder[gubOutOfTurnPersons] = ubID;
/*
// the guy being interrupted HAS to be the currently selected character
if (Status.lastInterruptedWas != CharacterSelected)
{
// if we don't already do so, remember who that was
Status.lastInterruptedWas = CharacterSelected;
}
*/
// if the guy is gaining control
if (fGainControl)
{
// record his initial APs at the start of his interrupt at this time
// this is not the ideal place for this, but it's the best I could do...
Menptr[ubID].bIntStartAPs = Menptr[ubID].bActionPoints;
}
else
{
gubLastInterruptedGuy = ubID;
// turn off AI control flag if they lost control
if (Menptr[ubID].uiStatusFlags & SOLDIER_UNDERAICONTROL)
{
DebugAI( String( "Taking away AI control from %d", ubID ) );
Menptr[ubID].uiStatusFlags &= (~SOLDIER_UNDERAICONTROL);
}
}
}
void VerifyOutOfTurnOrderArray()
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"VerifyOutOfTurnOrderArray");
UINT8 ubTeamHighest[ MAXTEAMS ] = { 0 };
UINT8 ubTeamsInList;
UINT8 ubNextInArrayOnTeam, ubNextIndex;
UINT8 ubTeam;
UINT8 ubLoop, ubLoop2;
BOOLEAN fFoundLoop = FALSE;
for (ubLoop = 1; ubLoop <= gubOutOfTurnPersons; ubLoop++)
{
ubTeam = Menptr[ gubOutOfTurnOrder[ ubLoop ] ].bTeam;
if (ubTeamHighest[ ubTeam ] > 0)
{
// check the other teams to see if any of them are between our last team's mention in
// the array and this
for (ubLoop2 = 0; ubLoop2 < MAXTEAMS; ubLoop2++)
{
if (ubLoop2 == ubTeam)
{
continue;
}
else
{
if (ubTeamHighest[ ubLoop2 ] > ubTeamHighest[ ubTeam ])
{
// there's a loop!! delete it!
ubNextInArrayOnTeam = gubOutOfTurnOrder[ ubLoop ];
ubNextIndex = ubTeamHighest[ ubTeam ] + 1;
while( gubOutOfTurnOrder[ ubNextIndex ] != ubNextInArrayOnTeam )
{
// Pause them...
AdjustNoAPToFinishMove( MercPtrs[ gubOutOfTurnOrder[ ubNextIndex ] ], TRUE );
// If they were turning from prone, stop them
MercPtrs[ gubOutOfTurnOrder[ ubNextIndex ] ]->fTurningFromPronePosition = FALSE;
DeleteFromIntList( ubNextIndex, FALSE );
}
fFoundLoop = TRUE;
break;
}
}
}
if (fFoundLoop)
{
// at this point we should restart our outside loop (ugh)
fFoundLoop = FALSE;
for (ubLoop2 = 0; ubLoop2 < MAXTEAMS; ubLoop2++)
{
ubTeamHighest[ ubLoop2 ] = 0;
}
ubLoop = 0;
continue;
}
}
ubTeamHighest[ ubTeam ] = ubLoop;
}
// Another potential problem: the player is interrupted by the enemy who is interrupted by
// the militia. In this situation the enemy should just lose their interrupt.
// (Or, the militia is interrupted by the enemy who is interrupted by the player.)
// Check for 3+ teams in the interrupt queue. If three exist then abort all interrupts (return
// control to the first team)
ubTeamsInList = 0;
for ( ubLoop = 0; ubLoop < MAXTEAMS; ubLoop++ )
{
if ( ubTeamHighest[ ubLoop ] > 0 )
{
ubTeamsInList++;
}
}
if ( ubTeamsInList >= 3 )
{
// This is bad. Loop through everyone but the first person in the INT list and remove 'em
for (ubLoop = 2; ubLoop <= gubOutOfTurnPersons; )
{
if ( MercPtrs[ gubOutOfTurnOrder[ ubLoop ] ]->bTeam != MercPtrs[ gubOutOfTurnOrder[ 1 ] ]->bTeam )
{
// remove!
// Pause them...
AdjustNoAPToFinishMove( MercPtrs[ gubOutOfTurnOrder[ ubLoop ] ], TRUE );
// If they were turning from prone, stop them
MercPtrs[ gubOutOfTurnOrder[ ubLoop ] ]->fTurningFromPronePosition = FALSE;
DeleteFromIntList( ubLoop, FALSE );
// since we deleted someone from the list, we want to check the same index in the
// array again, hence we DON'T increment.
}
else
{
ubLoop++;
}
}
}
}
void DoneAddingToIntList( SOLDIERTYPE * pSoldier, BOOLEAN fChange, UINT8 ubInterruptType)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DoneAddingToIntList");
if (fChange)
{
VerifyOutOfTurnOrderArray();
if ( EveryoneInInterruptListOnSameTeam() )
{
EndInterrupt( TRUE );
}
else
{
StartInterrupt();
}
}
}
void ResolveInterruptsVs( SOLDIERTYPE * pSoldier, UINT8 ubInterruptType)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("ResolveInterruptsVs: Soldier ID = %d, APs = %d",pSoldier->ubID,pSoldier->bActionPoints));
UINT8 ubTeam, ubOpp;
UINT8 ubIntCnt;
UINT8 ubIntList[MAXMERCS];
UINT8 ubIntDiff[MAXMERCS];
UINT8 ubSmallestDiff;
UINT8 ubSlot, ubSmallestSlot;
UINT8 ubLoop;
BOOLEAN fIntOccurs;
SOLDIERTYPE * pOpponent;
BOOLEAN fControlChanged = FALSE;
if ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) )
{
ubIntCnt = 0;
for (ubTeam = 0; ubTeam < MAXTEAMS; ubTeam++)
{
if (gTacticalStatus.Team[ubTeam].bTeamActive && (gTacticalStatus.Team[ubTeam].bSide != pSoldier->bSide) && ubTeam != CIV_TEAM)
{
for ( ubOpp = gTacticalStatus.Team[ ubTeam ].bFirstID; ubOpp <= gTacticalStatus.Team[ ubTeam ].bLastID; ubOpp++)
{
pOpponent = MercPtrs[ubOpp];
if ( pOpponent->bActive && pOpponent->bInSector && (pOpponent->bLife >= OKLIFE) && !(pOpponent->bCollapsed) )
{
if ( ubInterruptType == NOISEINTERRUPT )
{
// don't grant noise interrupts at greater than max. visible distance
if ( PythSpacesAway( pSoldier->sGridNo, pOpponent->sGridNo ) > MaxDistanceVisible() )
{
pOpponent->bInterruptDuelPts = NO_INTERRUPT;
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Resetting int pts for %d - NOISE BEYOND SIGHT DISTANCE!?", pOpponent->ubID ) );
continue;
}
}
else if ( pOpponent->bOppList[pSoldier->ubID] != SEEN_CURRENTLY )
{
pOpponent->bInterruptDuelPts = NO_INTERRUPT;
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Resetting int pts for %d - DOESN'T SEE ON SIGHT INTERRUPT!?", pOpponent->ubID ) );
continue;
}
switch (pOpponent->bInterruptDuelPts)
{
case NO_INTERRUPT: // no interrupt possible, no duel necessary
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("ResolveInterruptsVs: No interrupt for opponent %d", pOpponent->ubID ) );
fIntOccurs = FALSE;
break;
case AUTOMATIC_INTERRUPT: // interrupts occurs automatically
pSoldier->bInterruptDuelPts = 0; // just to have a valid intDiff later
fIntOccurs = TRUE;
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("INTERRUPT: automatic interrupt on %d by %d", pSoldier->ubID, pOpponent->ubID ) );
break;
default: // interrupt is possible, run a duel
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "Calculating int duel pts for onlooker in ResolveInterruptsVs" );
pSoldier->bInterruptDuelPts = CalcInterruptDuelPts(pSoldier,pOpponent->ubID,TRUE);
fIntOccurs = InterruptDuel(pOpponent,pSoldier);
#ifdef DEBUG_INTERRUPTS
if (fIntOccurs)
{
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) );
}
#endif
break;
}
if (fIntOccurs)
{
// remember that this opponent's scheduled to interrupt us
ubIntList[ubIntCnt] = pOpponent->ubID;
// and by how much he beat us in the duel
ubIntDiff[ubIntCnt] = pOpponent->bInterruptDuelPts - pSoldier->bInterruptDuelPts;
// increment counter of interrupts lost
ubIntCnt++;
}
else
{
/*
if (pOpponent->bInterruptDuelPts != NO_INTERRUPT)
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%d fails to interrupt %d (%d vs %d pts)", pOpponent->ubID, pSoldier->ubID, pOpponent->bInterruptDuelPts, pSoldier->bInterruptDuelPts);
}
*/
}
// either way, clear out both sides' bInterruptDuelPts field to prepare next one
if (pSoldier->bInterruptDuelPts != NO_INTERRUPT)
{
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Resetting int pts for %d", pSoldier->ubID ) );
}
pSoldier->bInterruptDuelPts = NO_INTERRUPT;
if (pOpponent->bInterruptDuelPts != NO_INTERRUPT)
{
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Resetting int pts for %d", pOpponent->ubID ) );
}
pOpponent->bInterruptDuelPts = NO_INTERRUPT;
}
}
}
}
// if any interrupts are scheduled to occur (ie. I lost at least once)
if (ubIntCnt)
{
// First add currently active character to the interrupt queue. This is
// USUALLY pSoldier->guynum, but NOT always, because one enemy can
// "interrupt" on another enemy's turn if he hears another team's wound
// victim's screaming... the guy screaming is pSoldier here, it's not his turn!
//AddToIntList( (UINT8) gusSelectedSoldier, FALSE, TRUE);
if ( (gTacticalStatus.ubCurrentTeam != pSoldier->bTeam) && !(gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bHuman) )
{
// if anyone on this team is under AI control, remove
// their AI control flag and put them on the queue instead of this guy
for ( ubLoop = gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bFirstID; ubLoop <= gTacticalStatus.Team[ gTacticalStatus.ubCurrentTeam ].bLastID; ubLoop++ )
{
if ( (MercPtrs[ ubLoop ]->uiStatusFlags & SOLDIER_UNDERAICONTROL) )
{
// this guy lost control
MercPtrs[ ubLoop ]->uiStatusFlags &= (~SOLDIER_UNDERAICONTROL);
AddToIntList( ubLoop, FALSE, TRUE);
break;
}
}
}
else
{
// this guy lost control
AddToIntList( pSoldier->ubID, FALSE, TRUE);
}
// loop once for each opponent who interrupted
for (ubLoop = 0; ubLoop < ubIntCnt; ubLoop++)
{
// find the smallest intDiff still remaining in the list
ubSmallestDiff = NO_INTERRUPT;
ubSmallestSlot = NOBODY;
for (ubSlot = 0; ubSlot < ubIntCnt; ubSlot++)
{
if (ubIntDiff[ubSlot] < ubSmallestDiff)
{
ubSmallestDiff = ubIntDiff[ubSlot];
ubSmallestSlot = ubSlot;
}
}
if (ubSmallestSlot < NOBODY)
{
// add this guy to everyone's interrupt queue
AddToIntList(ubIntList[ubSmallestSlot],TRUE,TRUE);
if (INTERRUPTS_OVER)
{
// a loop was created which removed all the people in the interrupt queue!
EndInterrupt( TRUE );
return;
}
ubIntDiff[ubSmallestSlot] = NO_INTERRUPT; // mark slot as been handled
}
}
fControlChanged = TRUE;
}
// sends off an end-of-list msg telling everyone whether to switch control,
// unless it's a MOVEMENT interrupt, in which case that is delayed til later
DoneAddingToIntList(pSoldier,fControlChanged,ubInterruptType);
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"ResolveInterruptsVs done");
}
BOOLEAN SaveTeamTurnsToTheSaveGameFile( HWFILE hFile )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"SaveTeamTurnsToTheSaveGameFile");
UINT32 uiNumBytesWritten;
TEAM_TURN_SAVE_STRUCT TeamTurnStruct;
//Save the gubTurn Order Array
FileWrite( hFile, gubOutOfTurnOrder, sizeof( UINT8 ) * MAXMERCS, &uiNumBytesWritten );
if( uiNumBytesWritten != sizeof( UINT8 ) * MAXMERCS )
{
return( FALSE );
}
TeamTurnStruct.ubOutOfTurnPersons = gubOutOfTurnPersons;
TeamTurnStruct.InterruptOnlyGuynum = InterruptOnlyGuynum;
TeamTurnStruct.sWhoThrewRock = gsWhoThrewRock;
TeamTurnStruct.InterruptsAllowed = InterruptsAllowed;
TeamTurnStruct.fHiddenInterrupt = gfHiddenInterrupt;
TeamTurnStruct.ubLastInterruptedGuy = gubLastInterruptedGuy;
//Save the Team turn save structure
FileWrite( hFile, &TeamTurnStruct, sizeof( TEAM_TURN_SAVE_STRUCT ), &uiNumBytesWritten );
if( uiNumBytesWritten != sizeof( TEAM_TURN_SAVE_STRUCT ) )
{
return( FALSE );
}
return( TRUE );
}
BOOLEAN LoadTeamTurnsFromTheSavedGameFile( HWFILE hFile )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"LoadTeamTurnsFromTheSavedGameFile");
UINT32 uiNumBytesRead;
TEAM_TURN_SAVE_STRUCT TeamTurnStruct;
//Load the gubTurn Order Array
FileRead( hFile, gubOutOfTurnOrder, sizeof( UINT8 ) * MAXMERCS, &uiNumBytesRead );
if( uiNumBytesRead != sizeof( UINT8 ) * MAXMERCS )
{
return( FALSE );
}
//Load the Team turn save structure
FileRead( hFile, &TeamTurnStruct, sizeof( TEAM_TURN_SAVE_STRUCT ), &uiNumBytesRead );
if( uiNumBytesRead != sizeof( TEAM_TURN_SAVE_STRUCT ) )
{
return( FALSE );
}
gubOutOfTurnPersons = TeamTurnStruct.ubOutOfTurnPersons;
InterruptOnlyGuynum = TeamTurnStruct.InterruptOnlyGuynum;
gsWhoThrewRock = TeamTurnStruct.sWhoThrewRock;
InterruptsAllowed = TeamTurnStruct.InterruptsAllowed;
gfHiddenInterrupt = TeamTurnStruct.fHiddenInterrupt;
gubLastInterruptedGuy = TeamTurnStruct.ubLastInterruptedGuy;
return( TRUE );
}
BOOLEAN NPCFirstDraw( SOLDIERTYPE * pSoldier, SOLDIERTYPE * pTargetSoldier )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"NPCFirstDraw");
// if attacking an NPC check to see who draws first!
if ( pTargetSoldier->ubProfile != NO_PROFILE && pTargetSoldier->ubProfile != SLAY && pTargetSoldier->bNeutral && pTargetSoldier->bOppList[ pSoldier->ubID ] == SEEN_CURRENTLY && ( FindAIUsableObjClass( pTargetSoldier, IC_WEAPON ) != NO_SLOT ) )
{
UINT8 ubLargerHalf, ubSmallerHalf, ubTargetLargerHalf, ubTargetSmallerHalf;
// roll the dice!
// e.g. if level 5, roll Random( 3 + 1 ) + 2 for result from 2 to 5
// if level 4, roll Random( 2 + 1 ) + 2 for result from 2 to 4
ubSmallerHalf = EffectiveExpLevel( pSoldier ) / 2;
ubLargerHalf = EffectiveExpLevel( pSoldier ) - ubSmallerHalf;
ubTargetSmallerHalf = EffectiveExpLevel( pTargetSoldier ) / 2;
ubTargetLargerHalf = EffectiveExpLevel( pTargetSoldier ) - ubTargetSmallerHalf;
if ( gMercProfiles[ pTargetSoldier->ubProfile ].bApproached & gbFirstApproachFlags[ APPROACH_THREATEN - 1 ] )
{
// gains 1 to 2 points
ubTargetSmallerHalf += 1;
ubTargetLargerHalf += 1;
}
if ( Random( ubTargetSmallerHalf + 1) + ubTargetLargerHalf > Random( ubSmallerHalf + 1) + ubLargerHalf )
{
return( TRUE );
}
}
return( FALSE );
}