mirror of
https://github.com/1dot13/source.git
synced 2026-07-22 13:40:22 +02:00
Split DecideAction into several specialized DecideAction functions
* Removes the need to constantly check for fCivilian, ARMED_VEHICLE, ENEMY_ROBOT etc inside DecideAction * Move AI decision checks into their own functions, such as DecideActionRadioRedAlert * Add new entry to ActionType. AI_ACTION_INVALID signals that no valid action was found when a function returns, or no valid action is possible * Do a weighted random selection for choosing target under DecideActionBlackSoldier
This commit is contained in:
@@ -6,6 +6,10 @@ set(ModularizedTacticalAISrc
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/LegacyAIPlanFactory.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/LegacyCreaturePlan.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/LegacyZombiePlan.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/BoxerPlan.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/CivilianPlan.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/RobotPlan.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/SoldierPlan.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/NullPlan.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/NullPlanFactory.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/Plan.cpp"
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "Plan.h"
|
||||
|
||||
namespace AI
|
||||
{
|
||||
namespace tactical
|
||||
{
|
||||
/**@class LegacyAIPlan
|
||||
* @brief Component/Concrete Product. Wrapper/Re-Write of DecideAction()
|
||||
*
|
||||
* Wrapper around boxer related AI uplifted from original DecideAction() routines
|
||||
*/
|
||||
class LegacyAIBoxerPlan: public Plan
|
||||
{
|
||||
private:
|
||||
public:
|
||||
LegacyAIBoxerPlan(SOLDIERTYPE* npc);
|
||||
virtual void execute(PlanInputData& environment);
|
||||
virtual bool done() const {return false;}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "Plan.h"
|
||||
|
||||
namespace AI
|
||||
{
|
||||
namespace tactical
|
||||
{
|
||||
/**@class LegacyAIPlan
|
||||
* @brief Component/Concrete Product. Wrapper/Re-Write of DecideAction()
|
||||
*
|
||||
* Wrapper around civilian/noncombatant related AI uplifted from original DecideAction() routines
|
||||
*/
|
||||
class CivilianPlan : public Plan
|
||||
{
|
||||
private:
|
||||
public:
|
||||
CivilianPlan(SOLDIERTYPE* npc);
|
||||
virtual void execute(PlanInputData& environment);
|
||||
virtual bool done() const { return false; }
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "Plan.h"
|
||||
|
||||
namespace AI
|
||||
{
|
||||
namespace tactical
|
||||
{
|
||||
/**@class LegacyAIPlan
|
||||
* @brief Component/Concrete Product. Wrapper/Re-Write of DecideAction()
|
||||
*
|
||||
* Wrapper around robot related AI uplifted from original DecideAction() routines
|
||||
*/
|
||||
class RobotPlan : public Plan
|
||||
{
|
||||
private:
|
||||
public:
|
||||
RobotPlan(SOLDIERTYPE* npc);
|
||||
virtual void execute(PlanInputData& environment);
|
||||
virtual bool done() const { return false; }
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "Plan.h"
|
||||
|
||||
namespace AI
|
||||
{
|
||||
namespace tactical
|
||||
{
|
||||
/**@class LegacyAIPlan
|
||||
* @brief Component/Concrete Product. Wrapper/Re-Write of DecideAction()
|
||||
*
|
||||
* Wrapper around soldier related AI uplifted from original DecideAction() routines
|
||||
*/
|
||||
class SoldierPlan : public Plan
|
||||
{
|
||||
private:
|
||||
public:
|
||||
SoldierPlan(SOLDIERTYPE* npc);
|
||||
virtual void execute(PlanInputData& environment);
|
||||
virtual bool done() const { return false; }
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "../include/BoxerPlan.h"
|
||||
#include "../../TacticalAI/ai.h"
|
||||
|
||||
namespace AI
|
||||
{
|
||||
namespace tactical
|
||||
{
|
||||
LegacyAIBoxerPlan::LegacyAIBoxerPlan(SOLDIERTYPE* npc)
|
||||
: Plan(npc)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void LegacyAIBoxerPlan::execute(PlanInputData& environment)
|
||||
{
|
||||
switch (get_npc()->aiData.bAlertStatus)
|
||||
{
|
||||
case STATUS_GREEN:
|
||||
get_npc()->aiData.bAction = DecideActionGreenBoxer(get_npc());
|
||||
break;
|
||||
case STATUS_YELLOW:
|
||||
get_npc()->aiData.bAction = DecideActionGreenBoxer(get_npc());
|
||||
break;
|
||||
case STATUS_RED:
|
||||
get_npc()->aiData.bAction = DecideActionBlackBoxer(get_npc());
|
||||
break;
|
||||
case STATUS_BLACK:
|
||||
get_npc()->aiData.bAction = DecideActionBlackBoxer(get_npc());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace tactical
|
||||
} // namespace AI
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "../include/CivilianPlan.h"
|
||||
#include "../../TacticalAI/ai.h"
|
||||
|
||||
namespace AI
|
||||
{
|
||||
namespace tactical
|
||||
{
|
||||
CivilianPlan::CivilianPlan(SOLDIERTYPE* npc)
|
||||
: Plan(npc)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void CivilianPlan::execute(PlanInputData& environment)
|
||||
{
|
||||
switch (get_npc()->aiData.bAlertStatus)
|
||||
{
|
||||
case STATUS_GREEN:
|
||||
get_npc()->aiData.bAction = DecideActionGreenCivilian(get_npc());
|
||||
break;
|
||||
case STATUS_YELLOW:
|
||||
get_npc()->aiData.bAction = DecideActionYellowCivilian(get_npc());
|
||||
break;
|
||||
case STATUS_RED:
|
||||
get_npc()->aiData.bAction = DecideActionRedCivilian(get_npc());
|
||||
break;
|
||||
case STATUS_BLACK:
|
||||
get_npc()->aiData.bAction = DecideActionBlackCivilian(get_npc());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace tactical
|
||||
} // namespace AI
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
#include "../include/LegacyCreaturePlan.h"
|
||||
#include "../include/LegacyZombiePlan.h"
|
||||
#include "../include/LegacyArmedVehiclePlan.h"
|
||||
#include "../include/BoxerPlan.h"
|
||||
#include "../include/CivilianPlan.h"
|
||||
#include "../include/RobotPlan.h"
|
||||
#include "../include/SoldierPlan.h"
|
||||
#include "../include/CrowPlan.h"
|
||||
#include "../include/PlanList.h"
|
||||
|
||||
@@ -14,8 +18,7 @@
|
||||
#include "../../Tactical/Soldier Control.h" // For SOLDIERTYPE definition
|
||||
#include "../../Tactical/Animation Data.h" // For the definition of, wait for it... BLOODCAT!
|
||||
#include "Soldier macros.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include "ai.h"
|
||||
|
||||
|
||||
namespace AI
|
||||
@@ -43,6 +46,11 @@ namespace AI
|
||||
if(npc->IsZombie())
|
||||
return new LegacyZombiePlan(npc);
|
||||
|
||||
if (BOXER(npc)) { return new LegacyAIBoxerPlan(npc); }
|
||||
if (IS_CIV_BODY_TYPE(npc)) { return new CivilianPlan(npc); }
|
||||
if (ENEMYROBOT(npc)) { return new RobotPlan(npc); }
|
||||
if (SoldierAI(npc)) { return new SoldierPlan(npc); }
|
||||
|
||||
return new LegacyAIPlan(npc); // no special plan for other cases yet, return default legacy AI wrapper
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "../include/RobotPlan.h"
|
||||
#include "../../TacticalAI/ai.h"
|
||||
|
||||
namespace AI
|
||||
{
|
||||
namespace tactical
|
||||
{
|
||||
RobotPlan::RobotPlan(SOLDIERTYPE* npc)
|
||||
: Plan(npc)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void RobotPlan::execute(PlanInputData& environment)
|
||||
{
|
||||
switch (get_npc()->aiData.bAlertStatus)
|
||||
{
|
||||
case STATUS_GREEN:
|
||||
get_npc()->aiData.bAction = DecideActionGreenRobot(get_npc());
|
||||
break;
|
||||
case STATUS_YELLOW:
|
||||
get_npc()->aiData.bAction = DecideActionYellowRobot(get_npc());
|
||||
break;
|
||||
case STATUS_RED:
|
||||
get_npc()->aiData.bAction = DecideActionRedRobot(get_npc());
|
||||
break;
|
||||
case STATUS_BLACK:
|
||||
get_npc()->aiData.bAction = DecideActionBlackRobot(get_npc());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace tactical
|
||||
} // namespace AI
|
||||
@@ -0,0 +1,104 @@
|
||||
#include "../include/SoldierPlan.h"
|
||||
#include "../../TacticalAI/ai.h"
|
||||
#include "../../TacticalAI/AIInternals.h" // ACTING_ON_SCHEDULE
|
||||
#include "../../TacticalAI/NPC.h" // NPCReachedDestination
|
||||
#include "../../Tactical/Dialogue Control.h" // DialogueQueueIsEmpty
|
||||
#include "../../Utils/Font Control.h" // ScreenMsg about deadlock
|
||||
#include "../../i18n/include/Text.h" // Sniper warning
|
||||
#include "../../Utils/message.h" // ditto
|
||||
|
||||
namespace AI
|
||||
{
|
||||
namespace tactical
|
||||
{
|
||||
SoldierPlan::SoldierPlan(SOLDIERTYPE* npc)
|
||||
: Plan(npc)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void SoldierPlan::execute(PlanInputData& environment)
|
||||
{
|
||||
if (!environment.turn_based())
|
||||
{
|
||||
if ((get_npc()->ubProfile != NO_PROFILE) && (gMercProfiles[get_npc()->ubProfile].ubMiscFlags3 & PROFILE_MISC_FLAG3_HANDLE_DONE_TRAVERSAL))
|
||||
{
|
||||
TriggerNPCWithGivenApproach(get_npc()->ubProfile, APPROACH_DONE_TRAVERSAL, FALSE);
|
||||
gMercProfiles[get_npc()->ubProfile].ubMiscFlags3 &= (~PROFILE_MISC_FLAG3_HANDLE_DONE_TRAVERSAL);
|
||||
get_npc()->ubQuoteActionID = 0;
|
||||
// wait a tiny bit
|
||||
get_npc()->aiData.usActionData = 100;
|
||||
get_npc()->aiData.bAction = AI_ACTION_WAIT;
|
||||
return;
|
||||
}
|
||||
if (get_npc()->bTeam == gbPlayerNum)
|
||||
{
|
||||
if (environment.get_tactical_status().fAutoBandageMode)
|
||||
{
|
||||
get_npc()->aiData.bAction = DecideAutoBandage(get_npc());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (get_npc()->bTeam != MILITIA_TEAM)
|
||||
{
|
||||
if (!sniperwarning && get_npc()->aiData.bOrders == SNIPER)
|
||||
{
|
||||
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_WATHCHOUTFORSNIPERS]);
|
||||
sniperwarning = TRUE;
|
||||
|
||||
// Flugente: additional dialogue
|
||||
AdditionalTacticalCharacterDialogue_AllInCurrentSector(NO_PROFILE, ADE_SNIPERWARNING);
|
||||
}
|
||||
|
||||
if (!biggunwarning && FindRocketLauncherOrCannon(get_npc()) != NO_SLOT)
|
||||
{
|
||||
biggunwarning = TRUE;
|
||||
//TODO: don't say this again after reloading a savegame
|
||||
SayQuoteFromAnyBodyInSector(QUOTE_WEARY_SLASH_SUSPUCIOUS);
|
||||
}
|
||||
}
|
||||
get_npc()->aiData.fAIFlags &= (~AI_CAUTIOUS); // turn off cautious flag
|
||||
// if status override is set, bypass RED/YELLOW and go directly to GREEN!
|
||||
if ((get_npc()->aiData.bBypassToGreen) && (get_npc()->aiData.bAlertStatus < STATUS_BLACK))
|
||||
{
|
||||
get_npc()->aiData.bAction = DecideActionGreenSoldier(get_npc());
|
||||
if (!gfTurnBasedAI)
|
||||
{
|
||||
// reset bypass now
|
||||
get_npc()->aiData.bBypassToGreen = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (get_npc()->aiData.bAlertStatus)
|
||||
{
|
||||
case STATUS_GREEN:
|
||||
get_npc()->aiData.bAction = DecideActionGreenSoldier(get_npc());
|
||||
break;
|
||||
case STATUS_YELLOW:
|
||||
get_npc()->aiData.bAction = DecideActionYellowSoldier(get_npc());
|
||||
break;
|
||||
case STATUS_RED:
|
||||
get_npc()->aiData.bAction = DecideActionRedSoldier(get_npc());
|
||||
break;
|
||||
case STATUS_BLACK:
|
||||
//if ( gGameSettings.fOptions[TOPTION_USE_LEGACY_TACTICALAI] ) // Commented out for now since new AI is WIP
|
||||
{
|
||||
get_npc()->aiData.bAction = DecideActionBlackSoldier(get_npc());
|
||||
}
|
||||
//else
|
||||
//{
|
||||
// get_npc()->aiData.bAction = DecideActionBlackSoldierUtilityAI(get_npc());
|
||||
//}
|
||||
break;
|
||||
}
|
||||
}
|
||||
DEBUGAIMSG("Deciding for guynum " << (int)get_npc()->ubID << " at gridno " << get_npc()->sGridNo << ", APs " << get_npc()->bActionPoints <<
|
||||
", decided action: " << (int)get_npc()->aiData.bAction << ", data " << (int)get_npc()->aiData.usActionData);
|
||||
}
|
||||
|
||||
} // namespace tactical
|
||||
} // namespace AI
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "Overhead.h"
|
||||
#include "random.h"
|
||||
#include "Points.h"
|
||||
|
||||
#include "ai.h"
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
@@ -239,7 +239,7 @@ INT32 GetInterveningClimbingLocation( SOLDIERTYPE * pSoldier, INT32 sDestGridNo,
|
||||
UINT8 GetTraversalQuoteActionID( INT8 bDirection );
|
||||
INT32 GoAsFarAsPossibleTowards(SOLDIERTYPE *pSoldier, INT32 sDesGrid, INT8 bAction);
|
||||
|
||||
INT8 HeadForTheStairCase( SOLDIERTYPE * pSoldier );
|
||||
ActionType HeadForTheStairCase( SOLDIERTYPE * pSoldier );
|
||||
|
||||
BOOLEAN InSmoke(INT32 sGridNo, INT8 bLevel);
|
||||
BOOLEAN InGas( SOLDIERTYPE *pSoldier, INT32 sGridNo );
|
||||
|
||||
@@ -336,7 +336,7 @@ void DebugAI( INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, bool doLog, IN
|
||||
strcat(msg, buf);
|
||||
}
|
||||
|
||||
if (bAction >= AI_ACTION_NONE && bAction <= AI_ACTION_LAST)
|
||||
if (bAction >= AI_ACTION_NONE && bAction < AI_ACTION_INVALID)
|
||||
{
|
||||
strcat(msg, " ");
|
||||
strcat(msg, szAction[bAction]);
|
||||
|
||||
@@ -4234,3 +4234,687 @@ void CheckTossGrenadeAt(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow, INT32 sTa
|
||||
|
||||
pSoldier->bWeaponMode = WM_NORMAL;
|
||||
}
|
||||
|
||||
|
||||
static void CalculatePossibleShots(SOLDIERTYPE* pSoldier, INT8 gun, std::vector<ATTACKTYPE> &possibleShots)
|
||||
{
|
||||
UINT32 uiLoop;
|
||||
INT32 iAttackValue, iThreatValue, iHitRate, iBestHitRate, iPercentBetter, iEstDamage, iTrueLastTarget;
|
||||
UINT16 usTrueState, usTurningCost, usRaiseGunCost;
|
||||
INT16 sMinAPcost;
|
||||
INT16 sRawAPCost;
|
||||
INT16 sAimAPCost;
|
||||
INT16 sBestAPcost;
|
||||
INT16 sChanceToHit;
|
||||
INT16 sAimTime;
|
||||
INT16 sBestAimTime;
|
||||
INT16 sMaxPossibleAimTime;
|
||||
UINT8 ubChanceToGetThrough;
|
||||
UINT8 ubBestChanceToGetThrough;
|
||||
UINT8 ubFriendlyFireChance;
|
||||
UINT8 ubBestFriendlyFireChance;
|
||||
INT16 sBestChanceToHit;
|
||||
INT16 sStanceAPcost;
|
||||
BOOLEAN fAddingTurningCost, fAddingRaiseGunCost;
|
||||
UINT8 ubStance, ubBestStance, ubChanceToReallyHit;
|
||||
INT8 bScopeMode;
|
||||
SOLDIERTYPE* pOpponent;
|
||||
|
||||
// sevenfm:
|
||||
BOOLEAN fSuppression = FALSE;
|
||||
BOOLEAN fReturnFire = FALSE;
|
||||
INT32 sTarget = NOWHERE;
|
||||
INT8 bLevel;
|
||||
|
||||
INT8 bKnowledge;
|
||||
INT8 bPersonalKnowledge;
|
||||
INT8 bPublicKnowledge;
|
||||
|
||||
DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "CalcBestShot");
|
||||
|
||||
|
||||
sBestChanceToHit = sBestAimTime = sChanceToHit = ubBestChanceToGetThrough = ubBestFriendlyFireChance = ubChanceToReallyHit = 0;
|
||||
|
||||
// sevenfm: set attacking hand and target
|
||||
pSoldier->ubAttackingHand = HANDPOS;
|
||||
pSoldier->ubTargetID = NOBODY;
|
||||
|
||||
pSoldier->usAttackingWeapon = pSoldier->inv[HANDPOS].usItem;
|
||||
pSoldier->bWeaponMode = WM_NORMAL;
|
||||
|
||||
std::map<INT8, OBJECTTYPE*> ObjList;
|
||||
GetScopeLists(pSoldier, &pSoldier->inv[HANDPOS], ObjList);
|
||||
pSoldier->bScopeMode = USE_BEST_SCOPE;
|
||||
pSoldier->bDoBurst = 0;
|
||||
pSoldier->bDoAutofire = 0;
|
||||
|
||||
// determine which attack against which target has the greatest attack value
|
||||
for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
|
||||
{
|
||||
ATTACKTYPE pBestShot;
|
||||
pBestShot.ubPossible = FALSE;
|
||||
pBestShot.ubChanceToReallyHit = 0;
|
||||
pBestShot.iAttackValue = 0;
|
||||
pBestShot.ubOpponent = NOBODY;
|
||||
pBestShot.ubFriendlyFireChance = 0;
|
||||
pBestShot.bWeaponIn = gun;
|
||||
|
||||
pOpponent = MercSlots[uiLoop];
|
||||
fSuppression = FALSE;
|
||||
fReturnFire = FALSE;
|
||||
|
||||
// if this merc is inactive, at base, on assignment, or dead
|
||||
if (!pOpponent || !pOpponent->stats.bLife)
|
||||
continue; // next merc
|
||||
|
||||
if (!ValidOpponent(pSoldier, pOpponent))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// determine return fire
|
||||
if (pSoldier->aiData.bUnderFire &&
|
||||
!pSoldier->bBlindedCounter &&
|
||||
pSoldier->ubPreviousAttackerID == pOpponent->ubID)
|
||||
{
|
||||
fReturnFire = TRUE;
|
||||
}
|
||||
|
||||
bKnowledge = Knowledge(pSoldier, pOpponent->ubID);
|
||||
bPersonalKnowledge = PersonalKnowledge(pSoldier, pOpponent->ubID);
|
||||
bPublicKnowledge = PublicKnowledge(pSoldier->bTeam, pOpponent->ubID);
|
||||
|
||||
// check knowledge
|
||||
if (bKnowledge != SEEN_CURRENTLY &&
|
||||
bKnowledge != SEEN_THIS_TURN &&
|
||||
bKnowledge != SEEN_LAST_TURN &&
|
||||
bKnowledge != HEARD_THIS_TURN &&
|
||||
bKnowledge != HEARD_LAST_TURN &&
|
||||
!((bKnowledge == SEEN_2_TURNS_AGO || bKnowledge == SEEN_3_TURNS_AGO || bKnowledge == HEARD_2_TURNS_AGO) && Weapon[pSoldier->usAttackingWeapon].ubWeaponType == GUN_LMG))
|
||||
{
|
||||
continue; // next opponent
|
||||
}
|
||||
|
||||
// sevenfm: blind soldier can only attack seen/heard personally
|
||||
if (pSoldier->bBlindedCounter > 0 &&
|
||||
bPersonalKnowledge != SEEN_THIS_TURN &&
|
||||
bPersonalKnowledge != HEARD_THIS_TURN)
|
||||
{
|
||||
continue; // next opponent
|
||||
}
|
||||
|
||||
// sevenfm: determine if we shoot on unseen target for suppression
|
||||
if (bPersonalKnowledge != SEEN_CURRENTLY &&
|
||||
bPublicKnowledge != SEEN_CURRENTLY &&
|
||||
//!SoldierToSoldierLineOfSightTest(pSoldier, pOpponent, TRUE, CALC_FROM_ALL_DIRS))
|
||||
!LOS_Raised(pSoldier, pOpponent, CALC_FROM_ALL_DIRS))
|
||||
{
|
||||
fSuppression = TRUE;
|
||||
}
|
||||
|
||||
// sevenfm: shooting at unseen opponents is optional
|
||||
if (fSuppression && !gGameExternalOptions.fAIShootUnseen)
|
||||
{
|
||||
continue; // next opponent
|
||||
}
|
||||
|
||||
// determine enemy location
|
||||
if (fSuppression)
|
||||
{
|
||||
// personal/public knowledge
|
||||
sTarget = KnownLocation(pSoldier, pOpponent->ubID);
|
||||
bLevel = KnownLevel(pSoldier, pOpponent->ubID);
|
||||
// try to randomize location
|
||||
sTarget = RandomizeLocation(sTarget, bLevel, 1, pSoldier);
|
||||
}
|
||||
else
|
||||
{
|
||||
// we know exact enemy location
|
||||
sTarget = pOpponent->sGridNo;
|
||||
bLevel = pOpponent->pathing.bLevel;
|
||||
}
|
||||
|
||||
// safety check
|
||||
if (TileIsOutOfBounds(sTarget))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip if we can see location and location is empty
|
||||
if (SoldierToVirtualSoldierLineOfSightTest(pSoldier, sTarget, bLevel, ANIM_PRONE, TRUE, CALC_FROM_ALL_DIRS) &&
|
||||
WhoIsThere2(sTarget, bLevel) == NOBODY)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// no fire on unseen opponents with throwing knives
|
||||
if ((Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE) &&
|
||||
bPersonalKnowledge != SEEN_CURRENTLY &&
|
||||
//!SoldierToSoldierLineOfSightTest(pSoldier, pOpponent, TRUE, CALC_FROM_ALL_DIRS))
|
||||
!LOS_Raised(pSoldier, pOpponent, CALC_FROM_ALL_DIRS))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// sevenfm: only enemy team can use blind suppression fire
|
||||
if (fSuppression &&
|
||||
pSoldier->bTeam != ENEMY_TEAM)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// sevenfm: only try to suppress alive and conscious human targets
|
||||
if (fSuppression &&
|
||||
(pOpponent->stats.bLife < OKLIFE ||
|
||||
pOpponent->bCollapsed && pOpponent->bBreath == 0 ||
|
||||
pOpponent->IsCowering() ||
|
||||
pOpponent->IsZombie() ||
|
||||
!IS_MERC_BODY_TYPE(pOpponent)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// shoot through wall check
|
||||
if (bPersonalKnowledge != SEEN_CURRENTLY &&
|
||||
//!SoldierToSoldierLineOfSightTest(pSoldier, pOpponent, TRUE, CALC_FROM_ALL_DIRS) &&
|
||||
!LOS_Raised(pSoldier, pOpponent, CALC_FROM_ALL_DIRS) &&
|
||||
!SoldierToVirtualSoldierLineOfSightTest(pSoldier, sTarget, bLevel, ANIM_STAND, TRUE, NO_DISTANCE_LIMIT) &&
|
||||
!LocationToLocationLineOfSightTest(pSoldier->sGridNo, pSoldier->pathing.bLevel, sTarget, bLevel, TRUE, NO_DISTANCE_LIMIT) &&
|
||||
!fReturnFire &&
|
||||
!(InARoom(sTarget, NULL) && bLevel == 0 && Weapon[pSoldier->usAttackingWeapon].ubWeaponType == GUN_LMG) && // bPublicKnowledge == SEEN_CURRENTLY &&
|
||||
!(InARoom(sTarget, NULL) && bLevel == 0 && TeamPercentKilled(pSoldier->bTeam) > (100 - 20 * SoldierDifficultyLevel(pSoldier))))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef DEBUGATTACKS
|
||||
DebugAI(String("%s sees %s at gridno %d\n", pSoldier->GetName(), ExtMen[pOpponent->ubID].GetName(), pOpponent->sGridNo));
|
||||
#endif
|
||||
sMinAPcost = MinAPsToAttack(pSoldier, sTarget, DONTADDTURNCOST, 0);
|
||||
// later will be decide if shoot is possible this here is just best guess so ignore turnover
|
||||
|
||||
// if we don't have enough APs left to shoot even a snap-shot at this guy
|
||||
if (sMinAPcost > pSoldier->bActionPoints)
|
||||
continue; // next opponent
|
||||
|
||||
// sevenfm: check CTGT and friendly fire for each stance instead since they can be different
|
||||
// calculate chance to get through the opponent's cover (if any)
|
||||
//dnl ch61 180813
|
||||
/*gUnderFire.Clear();
|
||||
gUnderFire.Enable();
|
||||
ubChanceToGetThrough = AISoldierToSoldierChanceToGetThrough( pSoldier, pOpponent );
|
||||
ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE);
|
||||
gUnderFire.Disable();
|
||||
|
||||
// if we can't possibly get through all the cover
|
||||
if (ubChanceToGetThrough == 0)
|
||||
continue; // next opponent
|
||||
|
||||
// sevenfm: ignore opponent if we can hit friend
|
||||
if (ubFriendlyFireChance > MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE)
|
||||
continue;*/
|
||||
|
||||
if ((pSoldier->flags.uiStatusFlags & SOLDIER_MONSTER) && (pSoldier->ubBodyType != QUEENMONSTER))
|
||||
{
|
||||
STRUCTURE_FILE_REF* pStructureFileRef;
|
||||
UINT16 usAnimSurface;
|
||||
|
||||
usAnimSurface = DetermineSoldierAnimationSurface(pSoldier, pSoldier->usUIMovementMode);
|
||||
pStructureFileRef = GetAnimationStructureRef(pSoldier->ubID, usAnimSurface, pSoldier->usUIMovementMode);
|
||||
|
||||
if (pStructureFileRef)
|
||||
{
|
||||
UINT16 usStructureID;
|
||||
INT8 bDir;
|
||||
|
||||
// must make sure that structure data can be added in the direction of the target
|
||||
bDir = (INT8)GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, sTarget);
|
||||
|
||||
// ATE: Only if we have a levelnode...
|
||||
if (pSoldier->pLevelNode != NULL && pSoldier->pLevelNode->pStructureData != NULL)
|
||||
{
|
||||
usStructureID = pSoldier->pLevelNode->pStructureData->usStructureID;
|
||||
}
|
||||
else
|
||||
{
|
||||
usStructureID = INVALID_STRUCTURE_ID;
|
||||
}
|
||||
|
||||
if (!OkayToAddStructureToWorld(pSoldier->sGridNo, pSoldier->pathing.bLevel, &(pStructureFileRef->pDBStructureRef[gOneCDirection[bDir]]), usStructureID))
|
||||
{
|
||||
// can't turn in that dir.... next opponent
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iBestHitRate = 0; // reset best hit rate to minimum
|
||||
|
||||
//dnl ch69 130913 Hoping to optimize
|
||||
// consider alternate holding mode and different scopes
|
||||
// sevenfm: alt weapon holding scope mode is used only when ubAllowAlternativeWeaponHolding == 3
|
||||
for (pSoldier->bScopeMode = (gGameExternalOptions.ubAllowAlternativeWeaponHolding == 3 ? USE_ALT_WEAPON_HOLD : USE_BEST_SCOPE);
|
||||
pSoldier->bScopeMode <= (gGameExternalOptions.fScopeModes ? NUM_SCOPE_MODES - 1 : USE_BEST_SCOPE);
|
||||
pSoldier->bScopeMode++)
|
||||
{
|
||||
if (pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD)
|
||||
{
|
||||
//dnl ch71 180913 throwing knives cannot be used in fire from hip
|
||||
if (Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE)
|
||||
continue;
|
||||
|
||||
// sevenfm: hip firing allowed only for human bodytypes
|
||||
if (!IS_MERC_BODY_TYPE(pSoldier))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD || (pSoldier->bScopeMode >= USE_BEST_SCOPE && ObjList[pSoldier->bScopeMode] != NULL))
|
||||
{
|
||||
usTrueState = pSoldier->usAnimState; // because is used in CalculateRaiseGunCost, CalcAimingLevelsAvailableWithAP, CalculateTurningCost
|
||||
iTrueLastTarget = pSoldier->sLastTarget; // because is used in MinAPsToShootOrStab
|
||||
|
||||
// --------- Standing ---------
|
||||
ubStance = ANIM_STAND;
|
||||
// sevenfm: take into account direction when checking stance
|
||||
// sevenfm: shoot heavy guns in standing stance only when using hip fire
|
||||
if (pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, sTarget), ubStance) &&
|
||||
(pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD || !Weapon[pSoldier->usAttackingWeapon].HeavyGun || !ItemIsTwoHanded(pSoldier->usAttackingWeapon) || !gGameExternalOptions.ubAllowAlternativeWeaponHolding))
|
||||
{
|
||||
sStanceAPcost = GetAPsToChangeStance(pSoldier, ubStance);
|
||||
if (sStanceAPcost)
|
||||
{
|
||||
// Going up so first is stance change then turnover, do animation change before APs calculation
|
||||
pSoldier->usAnimState = STANDING;
|
||||
pSoldier->sLastTarget = NOWHERE;
|
||||
}
|
||||
GetAPChargeForShootOrStabWRTGunRaises(pSoldier, sTarget, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0);
|
||||
usTurningCost = CalculateTurningCost(pSoldier, pSoldier->usAttackingWeapon, fAddingTurningCost);
|
||||
usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, sTarget, 0);
|
||||
if (fAddingTurningCost && fAddingRaiseGunCost)//dnl ch71 180913
|
||||
{
|
||||
if (usRaiseGunCost > usTurningCost)
|
||||
usTurningCost = 0;
|
||||
else
|
||||
usRaiseGunCost = 0;
|
||||
}
|
||||
sRawAPCost = MinAPsToShootOrStab(pSoldier, sTarget, 0, FALSE, 2);
|
||||
sMinAPcost = sRawAPCost + usTurningCost + sStanceAPcost + usRaiseGunCost;
|
||||
|
||||
if (pSoldier->bActionPoints - sMinAPcost >= 0)
|
||||
{
|
||||
// calc next attack's minimum shooting cost (excludes readying & turning & raise gun)
|
||||
sMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, sTarget, pSoldier->bActionPoints - sMinAPcost);
|
||||
|
||||
// sevenfm: check CTGT and friendly fire chance for every stance
|
||||
gUnderFire.Clear();
|
||||
gUnderFire.Enable();
|
||||
if (fSuppression)
|
||||
ubChanceToGetThrough = SoldierToLocationChanceToGetThrough(pSoldier, sTarget, bLevel, 3, NOBODY);
|
||||
else
|
||||
ubChanceToGetThrough = SoldierToSoldierChanceToGetThrough(pSoldier, pOpponent);
|
||||
ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE);
|
||||
gUnderFire.Disable();
|
||||
|
||||
// sevenfm: only use this stance if we can hit target and cannot hit friends
|
||||
if (ubChanceToGetThrough > 0 && ubFriendlyFireChance <= MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE)
|
||||
{
|
||||
for (sAimTime = 0; sAimTime <= sMaxPossibleAimTime; sAimTime++)
|
||||
{
|
||||
sChanceToHit = AICalcChanceToHitGun(pSoldier, sTarget, sAimTime, AIM_SHOT_TORSO, bLevel, STANDING);
|
||||
sAimAPCost = CalcAPCostForAiming(pSoldier, sTarget, (INT8)sAimTime);
|
||||
iHitRate = sChanceToHit * (pSoldier->bActionPoints - (sMinAPcost - sRawAPCost)) / (sRawAPCost + sAimAPCost);
|
||||
// sevenfm: take into account CTGT for every stance
|
||||
if (iHitRate * ubChanceToGetThrough > iBestHitRate * ubBestChanceToGetThrough ||
|
||||
(Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE) && sChanceToHit > sBestChanceToHit)// rather take best chance for throwing knives
|
||||
{
|
||||
iBestHitRate = iHitRate;
|
||||
sBestAimTime = sAimTime;
|
||||
sBestChanceToHit = sChanceToHit;
|
||||
ubBestChanceToGetThrough = ubChanceToGetThrough;
|
||||
ubBestFriendlyFireChance = ubFriendlyFireChance;
|
||||
bScopeMode = pSoldier->bScopeMode;
|
||||
sBestAPcost = sMinAPcost;
|
||||
ubBestStance = ubStance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pSoldier->usAnimState = usTrueState;
|
||||
pSoldier->sLastTarget = iTrueLastTarget;
|
||||
}
|
||||
|
||||
// no crouched/prone if we are tank/using throwing knife/hip firing
|
||||
if (pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD || ARMED_VEHICLE(pSoldier) || ENEMYROBOT(pSoldier) || (Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE))
|
||||
continue;
|
||||
|
||||
// --------- Crouched ---------
|
||||
ubStance = ANIM_CROUCH;
|
||||
// sevenfm: take into account direction
|
||||
if (pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, sTarget), ubStance))
|
||||
{
|
||||
// change stance then turn
|
||||
sStanceAPcost = GetAPsToChangeStance(pSoldier, ubStance);
|
||||
if (sStanceAPcost)
|
||||
{
|
||||
pSoldier->usAnimState = CROUCHING;
|
||||
pSoldier->sLastTarget = NOWHERE;
|
||||
}
|
||||
GetAPChargeForShootOrStabWRTGunRaises(pSoldier, sTarget, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0);
|
||||
usTurningCost = CalculateTurningCost(pSoldier, pSoldier->usAttackingWeapon, fAddingTurningCost);
|
||||
usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, sTarget, 0);
|
||||
if (fAddingTurningCost && fAddingRaiseGunCost)//dnl ch71 180913
|
||||
{
|
||||
if (usRaiseGunCost > usTurningCost)
|
||||
usTurningCost = 0;
|
||||
else
|
||||
usRaiseGunCost = 0;
|
||||
}
|
||||
sRawAPCost = MinAPsToShootOrStab(pSoldier, sTarget, 0, FALSE, 2);
|
||||
sMinAPcost = sRawAPCost + usTurningCost + sStanceAPcost + usRaiseGunCost;
|
||||
|
||||
if (pSoldier->bActionPoints - sMinAPcost >= 0)
|
||||
{
|
||||
sMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, sTarget, pSoldier->bActionPoints - sMinAPcost);
|
||||
|
||||
// sevenfm: check CTGT and friendly fire chance for every stance
|
||||
gUnderFire.Clear();
|
||||
gUnderFire.Enable();
|
||||
if (fSuppression)
|
||||
ubChanceToGetThrough = SoldierToLocationChanceToGetThrough(pSoldier, sTarget, bLevel, 3, NOBODY);
|
||||
else
|
||||
ubChanceToGetThrough = SoldierToSoldierChanceToGetThrough(pSoldier, pOpponent);
|
||||
ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE);
|
||||
gUnderFire.Disable();
|
||||
|
||||
// sevenfm: only use this stance if we can hit target and cannot hit friends
|
||||
if (ubChanceToGetThrough > 0 && ubFriendlyFireChance <= MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE)
|
||||
{
|
||||
for (sAimTime = 0; sAimTime <= sMaxPossibleAimTime; sAimTime++)
|
||||
{
|
||||
sChanceToHit = AICalcChanceToHitGun(pSoldier, sTarget, sAimTime, AIM_SHOT_TORSO, bLevel, CROUCHING);
|
||||
sAimAPCost = CalcAPCostForAiming(pSoldier, sTarget, (INT8)sAimTime);
|
||||
iHitRate = sChanceToHit * (pSoldier->bActionPoints - (sMinAPcost - sRawAPCost)) / (sRawAPCost + sAimAPCost);
|
||||
// sevenfm: take into account CTGT for every stance
|
||||
if (iHitRate * ubChanceToGetThrough > iBestHitRate * ubBestChanceToGetThrough)
|
||||
{
|
||||
iBestHitRate = iHitRate;
|
||||
sBestAimTime = sAimTime;
|
||||
sBestChanceToHit = sChanceToHit;
|
||||
ubBestChanceToGetThrough = ubChanceToGetThrough;
|
||||
ubBestFriendlyFireChance = ubFriendlyFireChance;
|
||||
bScopeMode = pSoldier->bScopeMode;
|
||||
sBestAPcost = sMinAPcost;
|
||||
ubBestStance = ubStance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pSoldier->usAnimState = usTrueState;
|
||||
pSoldier->sLastTarget = iTrueLastTarget;
|
||||
}
|
||||
|
||||
// no prone stance if we have to change direction and stance at the same time
|
||||
if (pSoldier->ubDirection != AIDirection(pSoldier->sGridNo, sTarget) &&
|
||||
gAnimControl[pSoldier->usAnimState].ubEndHeight > ANIM_PRONE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// --------- Prone ---------
|
||||
ubStance = ANIM_PRONE;
|
||||
if (pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, sTarget), ubStance))
|
||||
{
|
||||
sStanceAPcost = GetAPsToChangeStance(pSoldier, ubStance);
|
||||
if (sStanceAPcost)
|
||||
{
|
||||
pSoldier->usAnimState = PRONE;
|
||||
pSoldier->sLastTarget = NOWHERE;
|
||||
}
|
||||
GetAPChargeForShootOrStabWRTGunRaises(pSoldier, sTarget, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0);
|
||||
usTurningCost = CalculateTurningCost(pSoldier, pSoldier->usAttackingWeapon, fAddingTurningCost);
|
||||
usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, sTarget, 0);
|
||||
sRawAPCost = MinAPsToShootOrStab(pSoldier, sTarget, 0, FALSE, 2);
|
||||
sMinAPcost = sRawAPCost + usTurningCost + sStanceAPcost + usRaiseGunCost;
|
||||
|
||||
if (pSoldier->bActionPoints - sMinAPcost >= 0)
|
||||
{
|
||||
sMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, sTarget, pSoldier->bActionPoints - sMinAPcost);
|
||||
|
||||
// sevenfm: check CTGT and friendly fire chance for every stance
|
||||
gUnderFire.Clear();
|
||||
gUnderFire.Enable();
|
||||
if (fSuppression)
|
||||
ubChanceToGetThrough = SoldierToLocationChanceToGetThrough(pSoldier, sTarget, bLevel, 3, NOBODY);
|
||||
else
|
||||
ubChanceToGetThrough = SoldierToSoldierChanceToGetThrough(pSoldier, pOpponent);
|
||||
ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE);
|
||||
gUnderFire.Disable();
|
||||
|
||||
// sevenfm: only use this stance if we can hit target and cannot hit friends
|
||||
if (ubChanceToGetThrough > 0 && ubFriendlyFireChance <= MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE)
|
||||
{
|
||||
for (sAimTime = 0; sAimTime <= sMaxPossibleAimTime; sAimTime++)
|
||||
{
|
||||
sChanceToHit = AICalcChanceToHitGun(pSoldier, sTarget, sAimTime, AIM_SHOT_TORSO, bLevel, PRONE);
|
||||
sAimAPCost = CalcAPCostForAiming(pSoldier, sTarget, (INT8)sAimTime);
|
||||
iHitRate = sChanceToHit * (pSoldier->bActionPoints - (sMinAPcost - sRawAPCost)) / (sRawAPCost + sAimAPCost);
|
||||
// sevenfm: take into account CTGT for every stance
|
||||
if (iHitRate * ubChanceToGetThrough > iBestHitRate * ubBestChanceToGetThrough)
|
||||
{
|
||||
iBestHitRate = iHitRate;
|
||||
sBestAimTime = sAimTime;
|
||||
sBestChanceToHit = sChanceToHit;
|
||||
ubBestChanceToGetThrough = ubChanceToGetThrough;
|
||||
ubBestFriendlyFireChance = ubFriendlyFireChance;
|
||||
bScopeMode = pSoldier->bScopeMode;
|
||||
sBestAPcost = sMinAPcost;
|
||||
ubBestStance = ubStance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pSoldier->usAnimState = usTrueState;
|
||||
pSoldier->sLastTarget = iTrueLastTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we can't get any kind of hit rate at all
|
||||
if (iBestHitRate == 0)
|
||||
continue; // next opponent
|
||||
|
||||
// calculate chance to REALLY hit: shoot accurately AND get past cover
|
||||
ubChanceToReallyHit = (UINT8)ceil((sBestChanceToHit * ubBestChanceToGetThrough) / 100.0f);
|
||||
|
||||
// if we can't REALLY hit at all
|
||||
if (ubChanceToReallyHit == 0)
|
||||
continue; // next opponent
|
||||
|
||||
// really limit knife throwing so it doesn't look wrong
|
||||
if (Item[pSoldier->usAttackingWeapon].usItemClass == IC_THROWING_KNIFE &&
|
||||
(ubChanceToReallyHit < 25 || (PythSpacesAway(pSoldier->sGridNo, sTarget) > CalcMaxTossRange(pSoldier, pSoldier->usAttackingWeapon, FALSE))))// Madd / 2 ) ) ) //dnl ch69 160913 was ubChanceToReallyHit < 30
|
||||
continue; // don't bother... next opponent
|
||||
|
||||
// calculate this opponent's threat value (factor in my cover from him)
|
||||
iThreatValue = CalcManThreatValue(pOpponent, pSoldier->sGridNo, TRUE, pSoldier);
|
||||
|
||||
// estimate the damage this shot would do to this opponent
|
||||
iEstDamage = EstimateShotDamage(pSoldier, pOpponent, sBestChanceToHit);
|
||||
//NumMessage("SHOT EstDamage = ",iEstDamage);
|
||||
|
||||
// calculate the combined "attack value" for this opponent
|
||||
// highest possible value before division should be about 1.8 billion...
|
||||
// normal value before division should be about 5 million...
|
||||
iAttackValue = (iEstDamage * iBestHitRate * ubChanceToReallyHit * iThreatValue) / 1000;
|
||||
//NumMessage("SHOT AttackValue = ",iAttackValue / 1000);
|
||||
|
||||
// sevenfm: penalize suppression fire
|
||||
if (fSuppression)
|
||||
{
|
||||
// 25% penalty for shooting at invisible target
|
||||
iAttackValue = iAttackValue / 2;
|
||||
}
|
||||
|
||||
// special stuff for assassins to ignore militia more
|
||||
if (pSoldier->IsAssassin() && pOpponent->bTeam == MILITIA_TEAM)
|
||||
{
|
||||
iAttackValue /= 2;
|
||||
}
|
||||
|
||||
// sevenfm: empty vehicles have very low priority
|
||||
if (pOpponent->ubWhatKindOfMercAmI == MERC_TYPE__VEHICLE && GetNumberInVehicle(pOpponent->bVehicleID) == 0)
|
||||
{
|
||||
iAttackValue /= 4;
|
||||
}
|
||||
|
||||
// sevenfm: dying, cowering or unconscious soldiers have very low priority
|
||||
if (pOpponent->stats.bLife < OKLIFE || pOpponent->bCollapsed || pOpponent->bBreathCollapsed)
|
||||
{
|
||||
iAttackValue /= 4;
|
||||
}
|
||||
|
||||
#ifdef DEBUGATTACKS
|
||||
DebugAI(String("CalcBestShot: best AttackValue vs %d = %d\n", uiLoop, iAttackValue));
|
||||
#endif
|
||||
|
||||
// if we can hurt the guy, OR probably not, but at least it's our best
|
||||
// chance to actually hit him and maybe scare him, knock him down, etc.
|
||||
if ((iAttackValue > 0) || (ubChanceToReallyHit > pBestShot.ubChanceToReallyHit))
|
||||
{
|
||||
#if 0 // if there already was another viable target
|
||||
if (pBestShot.ubChanceToReallyHit > 0)
|
||||
{
|
||||
// OK, how does our chance to hit him compare to the previous best one?
|
||||
iPercentBetter = ((ubChanceToReallyHit * 100) / pBestShot.ubChanceToReallyHit) - 100;
|
||||
|
||||
//dnl ch62 180813 ignore firing into breathless targets if there are targets in better condition
|
||||
// sevenfm: check that best opponent exists
|
||||
if (pBestShot.ubOpponent != NOBODY &&
|
||||
(Menptr[pBestShot.ubOpponent].bCollapsed || Menptr[pBestShot.ubOpponent].bBreathCollapsed) &&
|
||||
Menptr[pBestShot.ubOpponent].bBreath < OKBREATH
|
||||
&& Menptr[pBestShot.ubOpponent].bBreath < pOpponent->bBreath)
|
||||
{
|
||||
iPercentBetter = PERCENT_TO_IGNORE_THREAT;
|
||||
}
|
||||
|
||||
// sevenfm: if best opponent is dying and new opponent is ok, use new opponent
|
||||
if (pBestShot.ubOpponent != NOBODY &&
|
||||
Menptr[pBestShot.ubOpponent].stats.bLife < OKLIFE &&
|
||||
pOpponent->stats.bLife >= OKLIFE)
|
||||
{
|
||||
iPercentBetter = PERCENT_TO_IGNORE_THREAT;
|
||||
}
|
||||
|
||||
// if this chance to really hit is more than 50% worse, and the other
|
||||
// guy is conscious at all
|
||||
if (iPercentBetter < -PERCENT_TO_IGNORE_THREAT &&
|
||||
pBestShot.ubOpponent != NOBODY &&
|
||||
Menptr[pBestShot.ubOpponent].stats.bLife >= OKLIFE)
|
||||
{
|
||||
// then stick with the older guy as the better target
|
||||
continue;
|
||||
}
|
||||
|
||||
// if this chance to really hit between 50% worse to 50% better
|
||||
if (iPercentBetter < PERCENT_TO_IGNORE_THREAT)
|
||||
{
|
||||
// then the one with the higher ATTACK VALUE is the better target
|
||||
if (iAttackValue < pBestShot.iAttackValue)
|
||||
// the previous guy is more important since he's more dangerous
|
||||
continue; // next opponent
|
||||
}
|
||||
}
|
||||
|
||||
// sevenfm: if new opponent is dying and best opponent is ok, ignore new opponent
|
||||
if (pBestShot.ubOpponent != NOBODY &&
|
||||
Menptr[pBestShot.ubOpponent].stats.bLife >= OKLIFE &&
|
||||
pOpponent->stats.bLife < OKLIFE)
|
||||
{
|
||||
//DebugShot(pSoldier, String("new opponent is dying, best opponent is ok - skip"));
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
// OOOF! That was a lot of work! But we've got a new viable target!
|
||||
pBestShot.ubPossible = TRUE;
|
||||
pBestShot.ubOpponent = pOpponent->ubID;
|
||||
pBestShot.ubAimTime = sBestAimTime;
|
||||
pBestShot.ubChanceToReallyHit = ubChanceToReallyHit;
|
||||
pBestShot.sTarget = sTarget;
|
||||
pBestShot.bTargetLevel = bLevel;
|
||||
pBestShot.iAttackValue = iAttackValue;
|
||||
pBestShot.ubAPCost = sBestAPcost;
|
||||
pBestShot.ubStance = ubBestStance;
|
||||
pBestShot.bScopeMode = bScopeMode;
|
||||
pBestShot.ubFriendlyFireChance = ubBestFriendlyFireChance;
|
||||
|
||||
possibleShots.push_back(pBestShot);
|
||||
}
|
||||
}
|
||||
|
||||
pSoldier->bScopeMode = USE_BEST_SCOPE; // better reset this back
|
||||
}
|
||||
|
||||
bool CompareAttacks(const ATTACKTYPE& a, const ATTACKTYPE& b)
|
||||
{
|
||||
if (a.iAttackValue > b.iAttackValue)
|
||||
return true;
|
||||
else if (a.iAttackValue < b.iAttackValue)
|
||||
return false;
|
||||
|
||||
if (a.ubChanceToReallyHit > b.ubChanceToReallyHit)
|
||||
return true;
|
||||
else if (a.ubChanceToReallyHit < b.ubChanceToReallyHit)
|
||||
return false;
|
||||
|
||||
if (a.ubFriendlyFireChance < b.ubFriendlyFireChance)
|
||||
return true;
|
||||
else if (a.ubFriendlyFireChance > b.ubFriendlyFireChance)
|
||||
return false;
|
||||
|
||||
if (a.ubAPCost < b.ubAPCost)
|
||||
return true;
|
||||
else if (a.ubAPCost > b.ubAPCost)
|
||||
return false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CheckIfShotsPossible(SOLDIERTYPE* pSoldier, std::vector<ATTACKTYPE>& possibleShots)
|
||||
{
|
||||
INT8 guns[] = { FindAIUsableObjClass(pSoldier, IC_GUN), FindAIUsableObjClass(pSoldier, IC_GUN, TRUE) };
|
||||
// Only merc bodytypes can use a sidearm, also don't bother to check for targets if found sidearm is the same as main gun
|
||||
if (!IS_MERC_BODY_TYPE(pSoldier) || guns[0] == guns[1])
|
||||
{
|
||||
guns[1] = NO_SLOT;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 2; i++)
|
||||
{
|
||||
const auto gun = guns[i];
|
||||
// if the soldier does have a gun
|
||||
if (gun != NO_SLOT)
|
||||
{
|
||||
// if it's in his holster, swap it into his hand temporarily
|
||||
if (gun != HANDPOS)
|
||||
{
|
||||
RearrangePocket(pSoldier, HANDPOS, gun, TEMPORARILY);
|
||||
}
|
||||
|
||||
// get the minimum cost to attack with this item
|
||||
INT16 ubMinAPcost = MinAPsToAttack(pSoldier, pSoldier->sLastTarget, ADDTURNCOST, 0);
|
||||
|
||||
// if we can afford the minimum AP cost
|
||||
if (pSoldier->bActionPoints >= ubMinAPcost)
|
||||
{
|
||||
// then look around for worthy targets
|
||||
CalculatePossibleShots(pSoldier, gun, possibleShots);
|
||||
}
|
||||
|
||||
// if it was in his holster, swap it back into his holster for now
|
||||
if (gun != HANDPOS)
|
||||
{
|
||||
RearrangePocket(pSoldier, HANDPOS, gun, TEMPORARILY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11682
-9
File diff suppressed because it is too large
Load Diff
@@ -244,7 +244,7 @@ void PossiblyMakeThisEnemyChosenOne( SOLDIERTYPE * pSoldier )
|
||||
}
|
||||
|
||||
|
||||
INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
|
||||
ActionType PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
|
||||
{
|
||||
BOOLEAN fFoundRoute = FALSE;
|
||||
INT8 bSlot;
|
||||
@@ -307,7 +307,7 @@ INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
|
||||
if (bPanicTrigger == -1)
|
||||
{
|
||||
// augh!
|
||||
return( -1 );
|
||||
return( AI_ACTION_INVALID );
|
||||
}
|
||||
|
||||
sPanicTriggerGridNo = gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ];
|
||||
@@ -430,7 +430,7 @@ INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
|
||||
}
|
||||
|
||||
// no action decided
|
||||
return(-1);
|
||||
return(AI_ACTION_INVALID);
|
||||
}
|
||||
|
||||
void InitPanicSystem( void )
|
||||
@@ -534,7 +534,7 @@ BOOLEAN NeedToRadioAboutPanicTrigger( void )
|
||||
#define STAIRCASE_GRIDNO 12067
|
||||
#define STAIRCASE_DIRECTION 0
|
||||
|
||||
INT8 HeadForTheStairCase( SOLDIERTYPE * pSoldier )
|
||||
ActionType HeadForTheStairCase( SOLDIERTYPE * pSoldier )
|
||||
{
|
||||
UNDERGROUND_SECTORINFO * pBasementInfo;
|
||||
|
||||
|
||||
+20
-5
@@ -106,7 +106,7 @@ typedef enum
|
||||
AI_ACTION_DOCTOR_SELF, // added by Flugente: AI-ONLY! bandage/surgery on self. DO NOT USE THIS FOR MERCS!!!
|
||||
AI_ACTION_SELFDETONATE, // added by Flugente: blow up an explosive in own inventory
|
||||
AI_ACTION_STOP_MEDIC, // sevenfm: stop giving aid animation
|
||||
AI_ACTION_LAST = AI_ACTION_STOP_MEDIC
|
||||
AI_ACTION_INVALID
|
||||
} ActionType;
|
||||
|
||||
|
||||
@@ -181,11 +181,26 @@ enum { AI_MSG_START, AI_MSG_DECIDE, AI_MSG_INFO, AI_MSG_TOPIC };
|
||||
void DebugAI(INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, bool doLog = true, INT8 bAction = -1);
|
||||
void DebugQuestInfo(STR szOutput);
|
||||
INT8 DecideAction(SOLDIERTYPE *pSoldier);
|
||||
INT8 DecideActionBlack(SOLDIERTYPE *pSoldier);
|
||||
INT8 DecideActionEscort(SOLDIERTYPE *pSoldier);
|
||||
INT8 DecideActionGreen(SOLDIERTYPE *pSoldier);
|
||||
INT8 DecideActionRed(SOLDIERTYPE *pSoldier);
|
||||
INT8 DecideActionYellow(SOLDIERTYPE *pSoldier);
|
||||
INT8 DecideActionRed(SOLDIERTYPE *pSoldier);
|
||||
INT8 DecideActionBlack(SOLDIERTYPE *pSoldier);
|
||||
INT8 DecideActionGreenBoxer(SOLDIERTYPE* pSoldier);
|
||||
INT8 DecideActionBlackBoxer(SOLDIERTYPE* pSoldier);
|
||||
INT8 DecideActionGreenCivilian(SOLDIERTYPE* pSoldier);
|
||||
INT8 DecideActionYellowCivilian(SOLDIERTYPE* pSoldier);
|
||||
INT8 DecideActionRedCivilian(SOLDIERTYPE* pSoldier);
|
||||
INT8 DecideActionBlackCivilian(SOLDIERTYPE* pSoldier);
|
||||
INT8 DecideActionGreenSoldier(SOLDIERTYPE* pSoldier);
|
||||
INT8 DecideActionYellowSoldier(SOLDIERTYPE* pSoldier);
|
||||
INT8 DecideActionRedSoldier(SOLDIERTYPE* pSoldier);
|
||||
INT8 DecideActionBlackSoldier(SOLDIERTYPE* pSoldier);
|
||||
INT8 DecideActionGreenRobot(SOLDIERTYPE* pSoldier);
|
||||
INT8 DecideActionYellowRobot(SOLDIERTYPE* pSoldier);
|
||||
INT8 DecideActionRedRobot(SOLDIERTYPE* pSoldier);
|
||||
INT8 DecideActionBlackRobot(SOLDIERTYPE* pSoldier);
|
||||
|
||||
INT8 DecideActionBlackSoldierUtilityAI(SOLDIERTYPE* pSoldier);
|
||||
|
||||
INT16 DistanceToClosestFriend( SOLDIERTYPE * pSoldier );
|
||||
|
||||
@@ -240,7 +255,7 @@ void ManChecksOnFriends(SOLDIERTYPE *pSoldier);
|
||||
void NewDest(SOLDIERTYPE *pSoldier, INT32 sGridNo);
|
||||
INT32 NextPatrolPoint(SOLDIERTYPE *pSoldier);
|
||||
|
||||
INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove);
|
||||
ActionType PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove);
|
||||
void HaltMoveForSoldierOutOfPoints(SOLDIERTYPE *pSoldier);
|
||||
|
||||
INT32 RandDestWithinRange(SOLDIERTYPE *pSoldier);
|
||||
|
||||
Reference in New Issue
Block a user