mirror of
https://github.com/1dot13/source.git
synced 2026-07-22 13:40:22 +02:00
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
545 lines
14 KiB
C++
545 lines
14 KiB
C++
#ifdef PRECOMPILEDHEADERS
|
|
#include "AI All.h"
|
|
#else
|
|
#include "AIInternals.h"
|
|
#include "ai.h"
|
|
#include "pathai.h"
|
|
#include "items.h"
|
|
#include "World Items.h"
|
|
#include "strategicmap.h"
|
|
#include "Map Screen Interface Map.h"
|
|
#include "Soldier Profile.h"
|
|
#include "Quests.h"
|
|
#endif
|
|
|
|
#include "Queen Command.h"
|
|
|
|
void MakeClosestEnemyChosenOne()
|
|
{
|
|
UINT32 cnt;
|
|
INT16 sPathCost, sShortestPath = 1000;
|
|
INT8 bOldKeys = -1;
|
|
UINT8 ubClosestEnemy = NOBODY;
|
|
SOLDIERTYPE * pSoldier;
|
|
INT8 bPanicTrigger;
|
|
INT16 sPanicTriggerGridNo;
|
|
|
|
if ( ! (gTacticalStatus.fPanicFlags & PANIC_TRIGGERS_HERE) )
|
|
{
|
|
#ifdef BETAVERSION
|
|
PopMessage("MakeClosestEnemyChosenOne: ERROR - Panic Trigger is NOWHERE");
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
if (!NeedToRadioAboutPanicTrigger() )
|
|
{
|
|
// no active panic triggers
|
|
return;
|
|
}
|
|
|
|
// consider every enemy, looking for the closest capable, unbusy one
|
|
for (cnt = 0; cnt < guiNumMercSlots; cnt++)
|
|
{
|
|
pSoldier = MercSlots[cnt];
|
|
|
|
if (!pSoldier) // if this merc is inactive, or not here
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// if this merc is unconscious, or dead
|
|
if (pSoldier->bLife < OKLIFE)
|
|
{
|
|
continue; // next soldier
|
|
}
|
|
|
|
// if this guy's too tired to go
|
|
if (pSoldier->bBreath < OKBREATH)
|
|
{
|
|
continue; // next soldier
|
|
}
|
|
|
|
if ( gWorldSectorX == TIXA_SECTOR_X && gWorldSectorY == TIXA_SECTOR_Y )
|
|
{
|
|
if ( pSoldier->ubProfile != WARDEN )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// only consider for army guys
|
|
if (pSoldier->bTeam != ENEMY_TEAM )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// if this guy is in battle with opponent(s)
|
|
if (pSoldier->bOppCnt > 0)
|
|
{
|
|
continue; // next soldier
|
|
}
|
|
|
|
// if this guy is still in serious shock
|
|
if (pSoldier->bShock > 2)
|
|
{
|
|
continue; // next soldier
|
|
}
|
|
|
|
if ( pSoldier->bLevel != 0 )
|
|
{
|
|
// screw having guys on the roof go for panic triggers!
|
|
continue; // next soldier
|
|
}
|
|
|
|
bPanicTrigger = ClosestPanicTrigger( pSoldier );
|
|
if (bPanicTrigger == -1)
|
|
{
|
|
continue; // next soldier
|
|
}
|
|
|
|
sPanicTriggerGridNo = gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ];
|
|
if (sPanicTriggerGridNo == NOWHERE)
|
|
{
|
|
// this should never happen!
|
|
continue;
|
|
}
|
|
|
|
// remember whether this guy had keys before
|
|
//bOldKeys = pSoldier->bHasKeys;
|
|
|
|
// give him keys to see if with them he can get to the panic trigger
|
|
pSoldier->bHasKeys = (pSoldier->bHasKeys << 1) | 1;
|
|
|
|
// we now path directly to the panic trigger
|
|
|
|
|
|
// if he can't get to a spot where he could get at the panic trigger
|
|
/*
|
|
if ( FindAdjacentGridEx( pSoldier, gTacticalStatus.sPanicTriggerGridno, &ubDirection, &sAdjSpot, FALSE, FALSE ) == -1 )
|
|
{
|
|
pSoldier->bHasKeys = bOldKeys;
|
|
continue; // next merc
|
|
}
|
|
*/
|
|
|
|
|
|
// ok, this enemy appears to be eligible
|
|
|
|
// FindAdjacentGrid set HandGrid for us. If we aren't at that spot already
|
|
if (pSoldier->sGridNo != sPanicTriggerGridNo)
|
|
{
|
|
// get the AP cost for this enemy to go to target position
|
|
sPathCost = PlotPath( pSoldier, sPanicTriggerGridNo, FALSE, FALSE, FALSE, WALKING, FALSE, FALSE, 0);
|
|
}
|
|
else
|
|
{
|
|
sPathCost = 0;
|
|
}
|
|
|
|
// set his keys value back to what it was before this hack
|
|
pSoldier->bHasKeys = (pSoldier->bHasKeys >> 1 );
|
|
|
|
// if he can get there (or is already there!)
|
|
if (sPathCost || (pSoldier->sGridNo == sPanicTriggerGridNo))
|
|
{
|
|
if (sPathCost < sShortestPath)
|
|
{
|
|
sShortestPath = sPathCost;
|
|
ubClosestEnemy = pSoldier->ubID;
|
|
}
|
|
}
|
|
//else
|
|
//NameMessage(pSoldier,"can't get there...");
|
|
}
|
|
|
|
// if we found have an eligible enemy, make him our "chosen one"
|
|
if (ubClosestEnemy < NOBODY)
|
|
{
|
|
gTacticalStatus.ubTheChosenOne = ubClosestEnemy; // flag him as the chosen one
|
|
|
|
#ifdef TESTVERSION
|
|
NumMessage("TEST MSG: The chosen one is ",TheChosenOne);
|
|
#endif
|
|
|
|
pSoldier = MercPtrs[gTacticalStatus.ubTheChosenOne];
|
|
if ( pSoldier->bAlertStatus < STATUS_RED )
|
|
{
|
|
pSoldier->bAlertStatus = STATUS_RED;
|
|
CheckForChangingOrders( pSoldier );
|
|
}
|
|
SetNewSituation( pSoldier ); // set new situation for the chosen one
|
|
pSoldier->bHasKeys = (pSoldier->bHasKeys << 1) | 1; // cheat and give him keys to every door
|
|
//pSoldier->bHasKeys = TRUE;
|
|
}
|
|
#ifdef TESTVERSION
|
|
else
|
|
PopMessage("TEST MSG: Couldn't find anyone eligible to become TheChosenOne!");
|
|
#endif
|
|
}
|
|
|
|
void PossiblyMakeThisEnemyChosenOne( SOLDIERTYPE * pSoldier )
|
|
{
|
|
INT32 iAPCost, iPathCost;
|
|
//INT8 bOldKeys;
|
|
INT8 bPanicTrigger;
|
|
INT16 sPanicTriggerGridNo;
|
|
UINT32 uiPercentEnemiesKilled;
|
|
|
|
if ( ! (gTacticalStatus.fPanicFlags & PANIC_TRIGGERS_HERE) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( pSoldier->bLevel != 0 )
|
|
{
|
|
// screw having guys on the roof go for panic triggers!
|
|
return;
|
|
}
|
|
|
|
|
|
bPanicTrigger = ClosestPanicTrigger( pSoldier );
|
|
if (bPanicTrigger == -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
sPanicTriggerGridNo = gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ];
|
|
|
|
uiPercentEnemiesKilled = (UINT32)( 100 * (UINT32)(gTacticalStatus.ubArmyGuysKilled) / (UINT32)( gTacticalStatus.Team[ ENEMY_TEAM ].bMenInSector + gTacticalStatus.ubArmyGuysKilled ) );
|
|
if ( gTacticalStatus.ubPanicTolerance[ bPanicTrigger ] > uiPercentEnemiesKilled )
|
|
{
|
|
// not yet... not yet
|
|
return;
|
|
}
|
|
|
|
//bOldKeys = pSoldier->bHasKeys;
|
|
pSoldier->bHasKeys = (pSoldier->bHasKeys << 1) | 1;
|
|
|
|
// if he can't get to a spot where he could get at the panic trigger
|
|
iAPCost = AP_PULL_TRIGGER;
|
|
if (pSoldier->sGridNo != sPanicTriggerGridNo)
|
|
{
|
|
iPathCost = PlotPath( pSoldier, sPanicTriggerGridNo, FALSE, FALSE, FALSE, RUNNING, FALSE, FALSE, 0);
|
|
if (iPathCost == 0)
|
|
{
|
|
//pSoldier->bHasKeys = bOldKeys;
|
|
pSoldier->bHasKeys = (pSoldier->bHasKeys >> 1);
|
|
return;
|
|
}
|
|
iAPCost += iPathCost;
|
|
|
|
}
|
|
|
|
if ( iAPCost <= CalcActionPoints( pSoldier ) * 2)
|
|
{
|
|
// go!!!
|
|
gTacticalStatus.ubTheChosenOne = pSoldier->ubID;
|
|
return;
|
|
}
|
|
// else return keys to normal
|
|
//pSoldier->bHasKeys = bOldKeys;
|
|
pSoldier->bHasKeys = (pSoldier->bHasKeys >> 1);
|
|
}
|
|
|
|
|
|
INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
|
|
{
|
|
BOOLEAN fFoundRoute = FALSE;
|
|
INT8 bSlot;
|
|
INT32 iPathCost;
|
|
INT8 bPanicTrigger;
|
|
INT16 sPanicTriggerGridNo;
|
|
#ifdef DEBUGDECISIONS
|
|
STR16 tempstr;
|
|
#endif
|
|
|
|
// if there are panic bombs here
|
|
if (gTacticalStatus.fPanicFlags & PANIC_BOMBS_HERE)
|
|
{
|
|
// if enemy is holding a portable panic bomb detonator, he tries to use it
|
|
bSlot = FindTrigger( pSoldier );
|
|
if (bSlot != NO_SLOT)
|
|
{
|
|
//////////////////////////////////////////////////////////////////////
|
|
// ACTIVATE DETONATOR: blow up sector's panic bombs
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
// if we have enough APs to activate it now
|
|
if (pSoldier->bActionPoints >= AP_USE_REMOTE)
|
|
{
|
|
#ifdef TESTVERSION
|
|
sprintf(tempstr,"TEST MSG: %s - ACTIVATING his DETONATOR!",pSoldier->name);
|
|
PopMessage(tempstr);
|
|
#endif
|
|
// blow up all the PANIC bombs!
|
|
return(AI_ACTION_USE_DETONATOR);
|
|
}
|
|
else // otherwise, wait a turn
|
|
{
|
|
pSoldier->usActionData = NOWHERE;
|
|
return(AI_ACTION_NONE);
|
|
}
|
|
}
|
|
}
|
|
|
|
// no panic bombs, or no portable detonator
|
|
|
|
// if there's a panic trigger here (DOESN'T MATTER IF ANY PANIC BOMBS EXIST!)
|
|
if ( gTacticalStatus.fPanicFlags & PANIC_TRIGGERS_HERE )
|
|
{
|
|
// Have WE been chosen to go after the trigger?
|
|
if (pSoldier->ubID == gTacticalStatus.ubTheChosenOne)
|
|
{
|
|
bPanicTrigger = ClosestPanicTrigger( pSoldier );
|
|
if (bPanicTrigger == -1)
|
|
{
|
|
// augh!
|
|
return( -1 );
|
|
}
|
|
sPanicTriggerGridNo = gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ];
|
|
|
|
// if not standing on the panic trigger
|
|
if (pSoldier->sGridNo != sPanicTriggerGridNo)
|
|
{
|
|
// determine whether we can still get there
|
|
iPathCost = PlotPath( pSoldier, sPanicTriggerGridNo, FALSE, FALSE, FALSE, RUNNING, FALSE, FALSE, 0);
|
|
if (iPathCost != 0)
|
|
{
|
|
fFoundRoute = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fFoundRoute = TRUE;
|
|
}
|
|
|
|
// if we managed to find an adjacent spot
|
|
if (fFoundRoute)
|
|
{
|
|
|
|
/*
|
|
*** COMMENTED OUT BECAUSE WE DON'T HAVE SUPPORT ROUTINES YET
|
|
|
|
// make sure it's not in water (those triggers can't be pulled)
|
|
if (Water(Terrain(gTacticalStatus.sHandGrid),Structure(gTacticalStatus.sHandGrid)))
|
|
{
|
|
#ifdef BETAVERSION
|
|
PopMessage("BAD SCENARIO DESIGN: Enemies can't use this panic trigger!");
|
|
#endif
|
|
gTacticalStatus.ubTheChosenOne = NOBODY; // strip him of his Chosen One status
|
|
// don't bother replacing him either, the next won't have more luck!
|
|
return(-1);
|
|
}
|
|
|
|
*/
|
|
// if we are at that spot now
|
|
if (pSoldier->sGridNo == sPanicTriggerGridNo)
|
|
{
|
|
////////////////////////////////////////////////////////////////
|
|
// PULL THE PANIC TRIGGER!
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// and we have enough APs left to pull the trigger
|
|
if (pSoldier->bActionPoints >= AP_PULL_TRIGGER)
|
|
{
|
|
// blow up the all the PANIC bombs (or just the journal)
|
|
pSoldier->usActionData = sPanicTriggerGridNo;
|
|
|
|
#ifdef TESTVERSION
|
|
sprintf(tempstr,"TEST MSG: %s - PULLS PANIC TRIGGER at grid %d",
|
|
pSoldier->name,pSoldier->usActionData);
|
|
PopMessage(tempstr);
|
|
#endif
|
|
|
|
return(AI_ACTION_PULL_TRIGGER);
|
|
}
|
|
else // otherwise, wait a turn
|
|
{
|
|
pSoldier->usActionData = NOWHERE;
|
|
return(AI_ACTION_NONE);
|
|
}
|
|
}
|
|
else // we are NOT at the HandGrid spot
|
|
{
|
|
// if we can move at least 1 square's worth
|
|
if (ubCanMove)
|
|
{
|
|
// if we can get to the HandGrid spot to yank the trigger
|
|
// animations don't allow trigger-pulling from water, so we won't!
|
|
if (LegalNPCDestination(pSoldier,sPanicTriggerGridNo,ENSURE_PATH,NOWATER,0))
|
|
{
|
|
pSoldier->usActionData = sPanicTriggerGridNo;
|
|
pSoldier->bPathStored = TRUE;
|
|
|
|
#ifdef DEBUGDECISIONS
|
|
sprintf(tempstr,"%s - GETTING CLOSER to PANIC TRIGGER at grid %d (Trigger at %d)", pSoldier->name,pSoldier->usActionData,sPanicTriggerGridNo);
|
|
AIPopMessage(tempstr);
|
|
#endif
|
|
|
|
return(AI_ACTION_GET_CLOSER);
|
|
}
|
|
else // Oh oh, the chosen one can't get to the trigger!
|
|
{
|
|
#ifdef TESTVERSION
|
|
PopMessage("TEST MSG: Oh oh! !legalDest - ChosenOne can't get to the trigger!");
|
|
#endif
|
|
gTacticalStatus.ubTheChosenOne = NOBODY; // strip him of his Chosen One status
|
|
MakeClosestEnemyChosenOne(); // and replace him!
|
|
}
|
|
}
|
|
else // can't move, wait 1 turn
|
|
{
|
|
pSoldier->usActionData = NOWHERE;
|
|
return(AI_ACTION_NONE);
|
|
}
|
|
}
|
|
}
|
|
else // Oh oh, the chosen one can't get to the trigger!
|
|
{
|
|
#ifdef TESTVERSION
|
|
PopMessage("TEST MSG: Oh oh! !adjacentFound - ChosenOne can't get to the trigger!");
|
|
#endif
|
|
gTacticalStatus.ubTheChosenOne = NOBODY; // strip him of his Chosen One status
|
|
MakeClosestEnemyChosenOne(); // and replace him!
|
|
}
|
|
}
|
|
}
|
|
|
|
// no action decided
|
|
return(-1);
|
|
}
|
|
|
|
void InitPanicSystem( void )
|
|
{
|
|
// start by assuming there is no panic bombs or triggers here
|
|
gTacticalStatus.ubTheChosenOne = NOBODY;
|
|
FindPanicBombsAndTriggers();
|
|
}
|
|
|
|
INT8 ClosestPanicTrigger( SOLDIERTYPE * pSoldier )
|
|
{
|
|
INT8 bLoop;
|
|
INT16 sDistance;
|
|
INT16 sClosestDistance = 1000;
|
|
INT8 bClosestTrigger = -1;
|
|
UINT32 uiPercentEnemiesKilled;
|
|
|
|
uiPercentEnemiesKilled = (UINT32)( 100 * (UINT32)(gTacticalStatus.ubArmyGuysKilled) / (UINT32)( gTacticalStatus.Team[ ENEMY_TEAM ].bMenInSector + gTacticalStatus.ubArmyGuysKilled ) );
|
|
|
|
for ( bLoop = 0; bLoop < NUM_PANIC_TRIGGERS; bLoop++ )
|
|
{
|
|
if ( gTacticalStatus.sPanicTriggerGridNo[ bLoop ] != NOWHERE )
|
|
{
|
|
|
|
if ( gTacticalStatus.ubPanicTolerance[ bLoop ] > uiPercentEnemiesKilled )
|
|
{
|
|
// not yet... not yet...
|
|
continue; // next trigger
|
|
}
|
|
|
|
// in Tixa
|
|
if ( gWorldSectorX == TIXA_SECTOR_X && gWorldSectorY == TIXA_SECTOR_Y )
|
|
{
|
|
// screen out everyone but the warden
|
|
if ( pSoldier->ubProfile != WARDEN )
|
|
{
|
|
break;
|
|
}
|
|
|
|
// screen out the second/later panic trigger if the first one hasn't been triggered
|
|
if ( bLoop > 0 && gTacticalStatus.sPanicTriggerGridNo[ bLoop - 1 ] != NOWHERE )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
sDistance = PythSpacesAway( pSoldier->sGridNo, gTacticalStatus.sPanicTriggerGridNo[ bLoop ] );
|
|
if (sDistance < sClosestDistance)
|
|
{
|
|
sClosestDistance = sDistance;
|
|
bClosestTrigger = bLoop;
|
|
}
|
|
}
|
|
}
|
|
|
|
return( bClosestTrigger );
|
|
}
|
|
|
|
BOOLEAN NeedToRadioAboutPanicTrigger( void )
|
|
{
|
|
UINT32 uiPercentEnemiesKilled;
|
|
INT8 bLoop;
|
|
|
|
if ( !(gTacticalStatus.fPanicFlags & PANIC_TRIGGERS_HERE) || gTacticalStatus.ubTheChosenOne != NOBODY )
|
|
{
|
|
// already done!
|
|
return( FALSE );
|
|
}
|
|
|
|
if ( gTacticalStatus.Team[ ENEMY_TEAM ].bMenInSector == 0 )
|
|
{
|
|
return( FALSE );
|
|
}
|
|
|
|
if ( gWorldSectorX == TIXA_SECTOR_X && gWorldSectorY == TIXA_SECTOR_Y )
|
|
{
|
|
SOLDIERTYPE * pSoldier;
|
|
pSoldier = FindSoldierByProfileID( WARDEN, FALSE );
|
|
if ( !pSoldier || pSoldier->ubID == gTacticalStatus.ubTheChosenOne )
|
|
{
|
|
return( FALSE );
|
|
}
|
|
}
|
|
|
|
|
|
uiPercentEnemiesKilled = (UINT32)( 100 * (UINT32)(gTacticalStatus.ubArmyGuysKilled) / (UINT32)( gTacticalStatus.Team[ ENEMY_TEAM ].bMenInSector + gTacticalStatus.ubArmyGuysKilled ) );
|
|
|
|
for ( bLoop = 0; bLoop < NUM_PANIC_TRIGGERS; bLoop++ )
|
|
{
|
|
// if the bomb exists and its tolerance has been exceeded
|
|
if ( (gTacticalStatus.sPanicTriggerGridNo[ bLoop ] != NOWHERE) && ( uiPercentEnemiesKilled >= gTacticalStatus.ubPanicTolerance[ bLoop ] ) )
|
|
{
|
|
return( TRUE );
|
|
}
|
|
}
|
|
|
|
return( FALSE );
|
|
}
|
|
|
|
|
|
#define STAIRCASE_GRIDNO 12067
|
|
#define STAIRCASE_DIRECTION 0
|
|
|
|
INT8 HeadForTheStairCase( SOLDIERTYPE * pSoldier )
|
|
{
|
|
UNDERGROUND_SECTORINFO * pBasementInfo;
|
|
|
|
pBasementInfo = FindUnderGroundSector( 3, MAP_ROW_P, 1 );
|
|
if ( pBasementInfo && pBasementInfo->uiTimeCurrentSectorWasLastLoaded != 0 && ( pBasementInfo->ubNumElites + pBasementInfo->ubNumTroops + pBasementInfo->ubNumAdmins ) < 5 )
|
|
{
|
|
return( AI_ACTION_NONE );
|
|
}
|
|
|
|
if ( PythSpacesAway( pSoldier->sGridNo, STAIRCASE_GRIDNO ) < 2 )
|
|
{
|
|
return( AI_ACTION_TRAVERSE_DOWN );
|
|
}
|
|
else
|
|
{
|
|
if ( LegalNPCDestination( pSoldier, STAIRCASE_GRIDNO, ENSURE_PATH, WATEROK, 0 ) )
|
|
{
|
|
pSoldier->usActionData = STAIRCASE_GRIDNO;
|
|
return( AI_ACTION_GET_CLOSER );
|
|
}
|
|
}
|
|
return( AI_ACTION_NONE );
|
|
}
|
|
|
|
#define WARDEN_ALARM_GRIDNO 9376
|
|
#define WARDEN_GAS_GRIDNO 9216
|
|
// in both cases, direction 6
|