mirror of
https://github.com/1dot13/source.git
synced 2026-09-16 14:47:17 +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/LegacyAIPlanFactory.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/LegacyCreaturePlan.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/LegacyCreaturePlan.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/LegacyZombiePlan.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/NullPlan.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/NullPlanFactory.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/NullPlanFactory.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/Plan.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/LegacyCreaturePlan.h"
|
||||||
#include "../include/LegacyZombiePlan.h"
|
#include "../include/LegacyZombiePlan.h"
|
||||||
#include "../include/LegacyArmedVehiclePlan.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/CrowPlan.h"
|
||||||
#include "../include/PlanList.h"
|
#include "../include/PlanList.h"
|
||||||
|
|
||||||
@@ -14,8 +18,7 @@
|
|||||||
#include "../../Tactical/Soldier Control.h" // For SOLDIERTYPE definition
|
#include "../../Tactical/Soldier Control.h" // For SOLDIERTYPE definition
|
||||||
#include "../../Tactical/Animation Data.h" // For the definition of, wait for it... BLOODCAT!
|
#include "../../Tactical/Animation Data.h" // For the definition of, wait for it... BLOODCAT!
|
||||||
#include "Soldier macros.h"
|
#include "Soldier macros.h"
|
||||||
|
#include "ai.h"
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace AI
|
namespace AI
|
||||||
@@ -43,6 +46,11 @@ namespace AI
|
|||||||
if(npc->IsZombie())
|
if(npc->IsZombie())
|
||||||
return new LegacyZombiePlan(npc);
|
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
|
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 "Overhead.h"
|
||||||
#include "random.h"
|
#include "random.h"
|
||||||
#include "Points.h"
|
#include "Points.h"
|
||||||
|
#include "ai.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
|
||||||
@@ -239,7 +239,7 @@ INT32 GetInterveningClimbingLocation( SOLDIERTYPE * pSoldier, INT32 sDestGridNo,
|
|||||||
UINT8 GetTraversalQuoteActionID( INT8 bDirection );
|
UINT8 GetTraversalQuoteActionID( INT8 bDirection );
|
||||||
INT32 GoAsFarAsPossibleTowards(SOLDIERTYPE *pSoldier, INT32 sDesGrid, INT8 bAction);
|
INT32 GoAsFarAsPossibleTowards(SOLDIERTYPE *pSoldier, INT32 sDesGrid, INT8 bAction);
|
||||||
|
|
||||||
INT8 HeadForTheStairCase( SOLDIERTYPE * pSoldier );
|
ActionType HeadForTheStairCase( SOLDIERTYPE * pSoldier );
|
||||||
|
|
||||||
BOOLEAN InSmoke(INT32 sGridNo, INT8 bLevel);
|
BOOLEAN InSmoke(INT32 sGridNo, INT8 bLevel);
|
||||||
BOOLEAN InGas( SOLDIERTYPE *pSoldier, INT32 sGridNo );
|
BOOLEAN InGas( SOLDIERTYPE *pSoldier, INT32 sGridNo );
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ void DebugAI( INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, bool doLog, IN
|
|||||||
strcat(msg, buf);
|
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, " ");
|
||||||
strcat(msg, szAction[bAction]);
|
strcat(msg, szAction[bAction]);
|
||||||
|
|||||||
@@ -4234,3 +4234,687 @@ void CheckTossGrenadeAt(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow, INT32 sTa
|
|||||||
|
|
||||||
pSoldier->bWeaponMode = WM_NORMAL;
|
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;
|
BOOLEAN fFoundRoute = FALSE;
|
||||||
INT8 bSlot;
|
INT8 bSlot;
|
||||||
@@ -307,7 +307,7 @@ INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
|
|||||||
if (bPanicTrigger == -1)
|
if (bPanicTrigger == -1)
|
||||||
{
|
{
|
||||||
// augh!
|
// augh!
|
||||||
return( -1 );
|
return( AI_ACTION_INVALID );
|
||||||
}
|
}
|
||||||
|
|
||||||
sPanicTriggerGridNo = gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ];
|
sPanicTriggerGridNo = gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ];
|
||||||
@@ -430,7 +430,7 @@ INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// no action decided
|
// no action decided
|
||||||
return(-1);
|
return(AI_ACTION_INVALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitPanicSystem( void )
|
void InitPanicSystem( void )
|
||||||
@@ -534,7 +534,7 @@ BOOLEAN NeedToRadioAboutPanicTrigger( void )
|
|||||||
#define STAIRCASE_GRIDNO 12067
|
#define STAIRCASE_GRIDNO 12067
|
||||||
#define STAIRCASE_DIRECTION 0
|
#define STAIRCASE_DIRECTION 0
|
||||||
|
|
||||||
INT8 HeadForTheStairCase( SOLDIERTYPE * pSoldier )
|
ActionType HeadForTheStairCase( SOLDIERTYPE * pSoldier )
|
||||||
{
|
{
|
||||||
UNDERGROUND_SECTORINFO * pBasementInfo;
|
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_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_SELFDETONATE, // added by Flugente: blow up an explosive in own inventory
|
||||||
AI_ACTION_STOP_MEDIC, // sevenfm: stop giving aid animation
|
AI_ACTION_STOP_MEDIC, // sevenfm: stop giving aid animation
|
||||||
AI_ACTION_LAST = AI_ACTION_STOP_MEDIC
|
AI_ACTION_INVALID
|
||||||
} ActionType;
|
} 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 DebugAI(INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, bool doLog = true, INT8 bAction = -1);
|
||||||
void DebugQuestInfo(STR szOutput);
|
void DebugQuestInfo(STR szOutput);
|
||||||
INT8 DecideAction(SOLDIERTYPE *pSoldier);
|
INT8 DecideAction(SOLDIERTYPE *pSoldier);
|
||||||
INT8 DecideActionBlack(SOLDIERTYPE *pSoldier);
|
|
||||||
INT8 DecideActionEscort(SOLDIERTYPE *pSoldier);
|
|
||||||
INT8 DecideActionGreen(SOLDIERTYPE *pSoldier);
|
INT8 DecideActionGreen(SOLDIERTYPE *pSoldier);
|
||||||
INT8 DecideActionRed(SOLDIERTYPE *pSoldier);
|
|
||||||
INT8 DecideActionYellow(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 );
|
INT16 DistanceToClosestFriend( SOLDIERTYPE * pSoldier );
|
||||||
|
|
||||||
@@ -240,7 +255,7 @@ void ManChecksOnFriends(SOLDIERTYPE *pSoldier);
|
|||||||
void NewDest(SOLDIERTYPE *pSoldier, INT32 sGridNo);
|
void NewDest(SOLDIERTYPE *pSoldier, INT32 sGridNo);
|
||||||
INT32 NextPatrolPoint(SOLDIERTYPE *pSoldier);
|
INT32 NextPatrolPoint(SOLDIERTYPE *pSoldier);
|
||||||
|
|
||||||
INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove);
|
ActionType PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove);
|
||||||
void HaltMoveForSoldierOutOfPoints(SOLDIERTYPE *pSoldier);
|
void HaltMoveForSoldierOutOfPoints(SOLDIERTYPE *pSoldier);
|
||||||
|
|
||||||
INT32 RandDestWithinRange(SOLDIERTYPE *pSoldier);
|
INT32 RandDestWithinRange(SOLDIERTYPE *pSoldier);
|
||||||
|
|||||||
Reference in New Issue
Block a user