Further refinement of new ModularizedTacticalAI architecture; added plans for Crows

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@6017 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
feynman
2013-04-18 20:02:54 +00:00
parent 52cb4ff424
commit 26383d83a8
22 changed files with 451 additions and 490 deletions
@@ -338,25 +338,49 @@
Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
>
<File
RelativePath=".\src\AbstractPlanFactory.cpp"
>
<\File>
<File
RelativePath=".\src\CrowPlan.cpp"
>
<\File>
<File
RelativePath=".\src\LegacyAIPlan.cpp"
>
</File>
<\File>
<File
RelativePath=".\src\LegacyAIPlanFactory.cpp"
>
</File>
<\File>
<File
RelativePath=".\src\LegacyCreaturePlan.cpp"
>
<\File>
<File
RelativePath=".\src\LegacyZombiePlan.cpp"
>
<\File>
<File
RelativePath=".\src\NullPlan.cpp"
>
</File>
<\File>
<File
RelativePath=".\src\NullPlanFactory.cpp"
>
</File>
<\File>
<File
RelativePath=".\src\Plan.cpp"
>
<\File>
<File
RelativePath=".\src\PlanFactoryLibrary.cpp"
>
<\File>
<File
RelativePath=".\src\PlanList.cpp"
>
</File>
</Filter>
<Filter
@@ -369,7 +393,7 @@
>
</File>
<File
RelativePath=".\include\LegacyAIPlan.h"
RelativePath=".\include\CrowPlan.h"
>
</File>
<File
@@ -377,7 +401,15 @@
>
</File>
<File
RelativePath=".\include\NullPlan.h"
RelativePath=".\include\LegacyAIPlan.h"
>
</File>
<File
RelativePath=".\include\LegacyCreaturePlan.h"
>
</File>
<File
RelativePath=".\include\LegacyZombiePlan.h"
>
</File>
<File
@@ -385,13 +417,21 @@
>
</File>
<File
RelativePath=".\include\Plan.h"
RelativePath=".\include\NullPlan.h"
>
</File>
<File
RelativePath=".\include\PlanFactoryLibrary.h"
>
</File>
<File
RelativePath=".\include\Plan.h"
>
</File>
<File
RelativePath=".\include\PlanList.h"
>
</File>
</Filter>
</Files>
<Globals>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioProject
ProjectType="Visual C++"
Version="9,00"
Version="9.00"
Name="ModularizedTacticalAI"
ProjectGUID="{FF0A809E-A370-4640-A301-17B76D7A5B4E}"
RootNamespace="TacticalAI"
@@ -340,6 +340,14 @@
Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
>
<File
RelativePath=".\src\AbstractPlanFactory.cpp"
>
</File>
<File
RelativePath=".\src\CrowPlan.cpp"
>
</File>
<File
RelativePath=".\src\LegacyAIPlan.cpp"
>
@@ -348,6 +356,14 @@
RelativePath=".\src\LegacyAIPlanFactory.cpp"
>
</File>
<File
RelativePath=".\src\LegacyCreaturePlan.cpp"
>
</File>
<File
RelativePath=".\src\LegacyZombiePlan.cpp"
>
</File>
<File
RelativePath=".\src\NullPlan.cpp"
>
@@ -356,10 +372,18 @@
RelativePath=".\src\NullPlanFactory.cpp"
>
</File>
<File
RelativePath=".\src\Plan.cpp"
>
</File>
<File
RelativePath=".\src\PlanFactoryLibrary.cpp"
>
</File>
<File
RelativePath=".\src\PlanList.cpp"
>
</File>
</Filter>
<Filter
Name="Header Files"
@@ -370,6 +394,10 @@
RelativePath=".\include\AbstractPlanFactory.h"
>
</File>
<File
RelativePath=".\include\CrowPlan.h"
>
</File>
<File
RelativePath=".\include\LegacyAIPlan.h"
>
@@ -378,6 +406,14 @@
RelativePath=".\include\LegacyAIPlanFactory.h"
>
</File>
<File
RelativePath=".\include\LegacyCreaturePlan.h"
>
</File>
<File
RelativePath=".\include\LegacyZombiePlan.h"
>
</File>
<File
RelativePath=".\include\NullPlan.h"
>
@@ -394,6 +430,10 @@
RelativePath=".\include\PlanFactoryLibrary.h"
>
</File>
<File
RelativePath=".\include\PlanList.h"
>
</File>
</Filter>
</Files>
<Globals>
@@ -128,22 +128,32 @@
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="src\AbstractPlanFactory.cpp" />
<ClCompile Include="src\CrowPlan.cpp" />
<ClCompile Include="src\LegacyAIPlan.cpp" />
<ClCompile Include="src\LegacyAIPlanFactory.cpp" />
<ClCompile Include="src\LegacyCreaturePlan.cpp" />
<ClCompile Include="src\LegacyZombiePlan.cpp" />
<ClCompile Include="src\NullPlan.cpp" />
<ClCompile Include="src\NullPlanFactory.cpp" />
<ClCompile Include="src\Plan.cpp" />
<ClCompile Include="src\PlanFactoryLibrary.cpp" />
<ClCompile Include="src\PlanList.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\AbstractPlanFactory.h" />
<ClInclude Include="include\LegacyAIPlan.h" />
<ClInclude Include="include\CrowPlan.h" />
<ClInclude Include="include\LegacyAIPlanFactory.h" />
<ClInclude Include="include\NullPlan.h" />
<ClInclude Include="include\LegacyAIPlan.h" />
<ClInclude Include="include\LegacyCreaturePlan.h" />
<ClInclude Include="include\LegacyZombiePlan.h" />
<ClInclude Include="include\NullPlanFactory.h" />
<ClInclude Include="include\Plan.h" />
<ClInclude Include="include\NullPlan.h" />
<ClInclude Include="include\PlanFactoryLibrary.h" />
<ClInclude Include="include\Plan.h" />
<ClInclude Include="include\PlanList.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>
@@ -6,6 +6,8 @@
#ifndef ABSTRACT_PLAN_FACTORY_H_
#define ABSTRACT_PLAN_FACTORY_H_
#include <iosfwd>
class SOLDIERTYPE;
namespace AI
@@ -13,16 +15,48 @@ namespace AI
namespace tactical
{
/**@class AIInputData
* @brief Wrapper class around the environmental data required to build an AI plan
* @brief Wrapper class around the environmental data required to build or update an AI plan
*
* At the moment, simply a wrapper around SOLDIERTYPE. Future versions might require more data; without the
* wrapper, *every* Concrete Factory ever created would need to be changed in this event. With the wrapper, no
* change is required at all.
* The data contained in objects of this class are currently collected in HanldeNoise() and ManSeesMan(), both
* defined in Tactical/opplist.cpp
* Future versions might require more or different data; without the wrapper, *every* Concrete Factory ever
* created would need to be changed in this event. With the wrapper, no change is required at all.
*/
struct AIInputData
class AIInputData
{
SOLDIERTYPE* npc_to_plan_for_;
private:
enum event_type {no_event, auditive_event, visual_event};
event_type event_type_;
/// Only visual: the opponent seen by the updated NPC
SOLDIERTYPE* opponent_;
/// Only auditive: ID of the noise maker; (at least for NPC-generated sounds) ID of NPC
int noise_maker_;
/// Location of event
int grid_no_;
int level_;
/// Only auditive: volume of heard sound (at the hearing NPC, not the source)
int volume_;
int type_;
int caller1_;
int caller2_;
public:
/// Empty types are used for overload resolution in order to generate different types of events with
/// simple constructor calls.
struct Auditive{ };
struct Visual{ };
/// Constructor for noise events
AIInputData(Auditive, int noise_maker, int grid_no, int level, int volume, int type);
/// Constructor for sight events
AIInputData(Visual, SOLDIERTYPE* opponent, int grid_no, int level, int caller1, int caller2);
/// Default constructor, called when no event occurred
AIInputData();
bool is_auditive_event() const {return event_type_ == auditive_event;}
bool is_visual_event() const {return event_type_ == visual_event;}
/// Allow the output function used for debugging to access the members directly
friend std::ostream& operator<<(std::ostream& os, const AIInputData& i);
};
std::ostream& operator<<(std::ostream& os, const AIInputData& i);
class Plan;
/**@class AbstractPlanFactory
* @brief Abstract Factory. Base class for all plan factories.
@@ -37,6 +71,7 @@ namespace AI
class AbstractPlanFactory
{
private:
/// Flag set iff delayed initialization was already performed
bool initialize_called_;
public:
AbstractPlanFactory() : initialize_called_(false) { }
@@ -54,21 +89,25 @@ namespace AI
* The subclasses (i.e., Concrete Factories) implpmenting this routine contain
* the decision-making AI core.
*
* @param input A pointer to a structure containing all required input data in order for the AI
* algorithms to perform their task. At the moment, simply a wrapper around SOLDIERTYPE. Future versions
* @param npc The NPC the plan is for
* @param input A reference to a structure containing all required input data in order for the AI
* algorithms to perform their task. At the moment, empty. Future versions
* might require other datatypes; without the wrapper, *every* Concrete Factory ever created would need
* to be changed in this event.
* @return A Plan object tree representing the produced strategy
*/
virtual Plan* create_plan(const AIInputData& input) = 0;
/**@brief Abstract Plan object hierarchy update function, called in order to update an already created
* plan
virtual Plan* create_plan(SOLDIERTYPE* npc, const AIInputData& input) = 0;
/**@brief Abstract Plan object hierarchy update function, called in order to update an already created plan
*
* @param input A pointer to a structure containing all required input data in order for the AI
* algorithms to perform their task
* @param plan_to_change The Plan object hierarchy that is to be updated
* If no plan exists, it is created; this should not be the general case, but might happen for
* 'interrupted' NPCs that haven't had the chance to plan yet.
*
* @param npc A pointer to the npc owning the ai_masterplan_ to be changed.
* @param input A reference to a structure containing all required input data in order for the AI
* @post npc->ai_masterplan_ is not null
*/
virtual void update_plan(const AIInputData& input, Plan* plan_to_change) = 0;
virtual void update_plan(SOLDIERTYPE* npc, const AIInputData& input) = 0;
};
}
}
+7 -5
View File
@@ -13,17 +13,19 @@ namespace AI
namespace tactical
{
/**@class LegacyAIPlan
* @brief Component/Concrete Product. Generates a Plan that executes the AI as it was before modularization.
* @brief Component/Concrete Product. Wrapper/Re-Write of DecideAction()
*
* A simple encapsulation of the functions TurnBasedHandleNPCAI() and RTHandleAI(), defined in
* TacticalAI/AIMain.cpp and TacticalAI/Realtime.cpp, respectively. The AI handles decisions for all kinds of
* NPCs (enemies, zombies, civilians, ...)
* This plan began as a simple forwarding object for DecideAction(), and will, in the course of the AI redesign,
* be split into elementary NPC actions. It is as such only a intermediate product used to bring structure where
* the is currently none.
*/
class LegacyAIPlan: public Plan
{
private:
public:
virtual void execute(bool turn_based, PlanInputData& manipulated_object);
LegacyAIPlan(SOLDIERTYPE* npc);
virtual void execute(PlanInputData& environment);
virtual bool done() const {return false;}
};
}
}
@@ -14,16 +14,21 @@ namespace AI
namespace tactical
{
/**@class LegacyAIPlanFactory
* @brief Concrete Factory. Generates the LegacyAIPlan, making NPCs do exactly what it did before this AI
* modularization/re-write.
* @brief Concrete Factory. Generates the appropriate LegacyAIPlan, making NPCs do exactly what it did before
* this AI modularization/re-write.
*
* Currently, the factory handles the "outermost" layer of selection statements by instantiating plans that will
* call CreatureDecideAction(), ZombieDecideAction() or DecideAction(), respectively. This is not the intended
* use of factories, but for the time of the AI redesign a necessity nonetheless.
*/
class LegacyAIPlanFactory : public AbstractPlanFactory
{
private:
public:
/// The name is required for registration in the PlanFactoryLibrary
static std::string get_name() {return "LegacyAIPlanFactory";}
virtual Plan* create_plan(const AIInputData& input);
virtual void update_plan(const AIInputData& input, Plan* plan_to_change);
virtual Plan* create_plan(SOLDIERTYPE* npc, const AIInputData& input);
virtual void update_plan(SOLDIERTYPE* npc, const AIInputData& input);
};
}
}
+3 -1
View File
@@ -32,7 +32,9 @@ namespace AI
{
private:
public:
virtual void execute(bool turn_based, PlanInputData& manipulated_object);
NullPlan(SOLDIERTYPE* npc);
virtual void execute(PlanInputData& environment);
bool done() const {return false;}
};
}
}
@@ -36,8 +36,8 @@ namespace AI
private:
public:
static std::string get_name() {return "NullPlanFactory";}
virtual Plan* create_plan(const AIInputData& input);
virtual void update_plan(const AIInputData& input, Plan* plan_to_change);
virtual Plan* create_plan(SOLDIERTYPE* npc, const AIInputData& input);
virtual void update_plan(SOLDIERTYPE* npc, const AIInputData& input);
};
}
}
+53 -13
View File
@@ -6,44 +6,84 @@
#ifndef PLAN_H_
#define PLAN_H_
#include <iostream>
class SOLDIERTYPE;
struct TacticalStatusType;
namespace AI
{
namespace tactical
{
/**@class PlanInputData
* @brief Wrapper class around the entity manipulated through plan execution
* @brief Wrapper class for environmental data required for plan execution
*
* At the moment, simply a wrapper around SOLDIERTYPE. Future versions might require other datatypes; without
* the wrapper, *every* Plan subclass ever created would need to be changed in this event. With the wrapper, no
* change is required at all.
* Future versions might require other data types; without the wrapper, *every* Plan subclass ever created would
* need to be changed in this event. With the wrapper, no change is required at all (see also AIInputData).
*/
struct PlanInputData
class PlanInputData
{
SOLDIERTYPE* controlled_npc_;
private:
/// True iff we are in turn based mode
bool turn_based_;
/// Reference to the global variable gTacticalStatus (we want to keep this place tidy and don't use global variables here)
const TacticalStatusType& tactical_status_;
/// left undefined to prevent copying
PlanInputData(const TacticalStatusType&);
/// left undefined to prevent assignment
PlanInputData& operator=(const TacticalStatusType&);
public:
/// Trivial member initialization
PlanInputData(bool turn_based, const TacticalStatusType& tactical_status);
/** @name Accessor functions */ /*@{*/
/// Return a reference to a global "tactical status" struct, formerly known as gTacticalStatus
const TacticalStatusType& get_tactical_status() const { return tactical_status_; }
bool turn_based() const { return turn_based_; }
/*@}*/
};
/**@class Plan
* @brief Composite/Abstract Product. Base class for all plan compositions and components.
*
* The Plan class provides a common interface for both plan compositions and plan components.
* The Plan class provides a common interface for both plan compositions and plan components. The composite
* design pattern chosen for plan representation allows objects inheriting from this class to form trees,
* allowing a clean and easy representation of sub- and super-plans
*/
class Plan
{
private:
/// A pointer to the NPC who owns this plan.
SOLDIERTYPE* npc_;
protected:
/// Return a pointer to the controlled NPC
SOLDIERTYPE* get_npc() {return npc_;}
/// Return a pointer to the controlled NPC
const SOLDIERTYPE* get_npc() const {return npc_;}
public:
/** @brief Plan execution is the encapsulation of an action sequence, making up higher-level building blocks
Plan(SOLDIERTYPE* npc);
/// A virtual destructor is essential here, destruction will be handled through this interface.
virtual ~Plan() { };
/** @brief Plan execution is the encapsulation of an "action sequence", which can be used to make up
* higher-level building blocks
*
* Each plan objects represents a high-level action, implemented by means of lower-level actions. These
* lower level actions can be other Plans subtypes; at some point, a Plan object has no further
* sub-plans, i.e., the objet is no composition, but a component, forming a leaf in the Plan hierarchy.
* The components' lower level actions are formed by the fundamental npc actions made available outside
* the AI framework.
* The components' lower level actions are formed by the fundamental NPC actions which must be made
* available outside the AI framework.
*
* @param turn_based true if turn-based mode is active, false for real-time mode
* @param manipulated_object Encapsulation of around object(s) a plan may manipulate.
* @param environment Encapsulation of environmental data required for plan execution
*/
virtual void execute(bool turn_based, PlanInputData& manipulated_object) = 0;
virtual void execute(PlanInputData& environment) = 0;
/** Determine if the plan is finished.
*
* The semantics are not "this plan *can* be aborted" - every plan must anticipate that - but instead
* "this plan cannot be continued"; for example, a movement plan is finished once the destination is
* reached, as no further movement can be carried out by the plan.
*@return true iff the plan can no longer be executed (or execution will always lead to AI_ACTION_NONE)
*/
virtual bool done() const = 0;
};
}
}
@@ -10,6 +10,8 @@
#include <map>
#include <deque>
class SOLDIERTYPE;
namespace AI
{
namespace tactical
@@ -17,23 +19,29 @@ namespace AI
// forward declarations
class AbstractPlanFactory;
class Plan;
struct AIInputData;
class AIInputData;
/**@class PlanFactoryLibrary
* @brief Singleton. A "library" containing all available PlanFactories, accessible via the strings returned by
* their get_name() function.
*
* Used as one "entry point" for the new AI system within the legacy code (the other being the 'execute()' methods of Plan)
* Handles the mapping of a SOLDIERTYPE's bAIIndex to a concrete plan factory instantiation.
*/
class PlanFactoryLibrary
{
private:
/// Only instance, required for singleton pattern implementation
static PlanFactoryLibrary* instance_;
/// A mapping of factory names to pointers of said factories
std::map<std::string, AbstractPlanFactory*> registred_factories_;
/// A mapping of bAIIndex to factory pointers
std::deque<AbstractPlanFactory*> ai_index_to_factory_mapping_;
PlanFactoryLibrary();
public:
static PlanFactoryLibrary* instance();
Plan* create_plan(size_t index, AIInputData& input) const;
Plan* create_plan(size_t index, SOLDIERTYPE* npc, const AIInputData& input) const;
void update_plan(size_t intdex, SOLDIERTYPE* npc, const AIInputData& input) const;
};
}
}
+91 -13
View File
@@ -1,28 +1,106 @@
/**
/**
* @file
* @author feynman (bears-pit.com)
*/
#include "../include/LegacyAIPlan.h"
#include "../../TacticalAI/ai.h" // for EndAIGuysTurn
#include "../../TacticalAI/ai.h"
#include "../../TacticalAI/AIInternals.h" // ACTING_ON_SCHEDULE
#include "../../TacticalAI/NPC.h" // NPCReachedDestination
#include "../../Tactical/Animation Control.h" // defines ANIM_...
#include "../../Tactical/Soldier Macros.h" // CREATURE_OR_BLOODCAT
#include "../../Tactical/opplist.h" // EndMuzzleFlash
#include "../../Tactical/Dialogue Control.h" // DialogueQueueIsEmpty
#include "../../TileEngine/Isometric Utils.h" // defines NOWHERE
#include "../../Utils/Debug Control.h" // LiveMessage
#include "../../Utils/Font Control.h" // ScreenMsg about deadlock
#include "../../Utils/Text.h" // Sniper warning
#include "../../Utils/message.h" // ditto
// Forward declarations for the two legacy functions called here
void RTHandleAI( SOLDIERTYPE * pSoldier ); // defined in TacticalAI/Realtime.cpp
void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier); // defined in TacticalAI/AIMain.cpp
namespace AI
{
namespace tactical
{
void LegacyAIPlan::execute(bool turn_based, PlanInputData& manipulated_object)
LegacyAIPlan::LegacyAIPlan(SOLDIERTYPE* npc)
: Plan(npc)
{
if(turn_based)
TurnBasedHandleNPCAI(manipulated_object.controlled_npc_);
else
RTHandleAI(manipulated_object.controlled_npc_);
}
}
}
void LegacyAIPlan::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;
}
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 = DecideActionGreen(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 = DecideActionGreen(get_npc());
break;
case STATUS_YELLOW:
get_npc()->aiData.bAction = DecideActionYellow(get_npc());
break;
case STATUS_RED:
get_npc()->aiData.bAction = DecideActionRed(get_npc(),TRUE);
break;
case STATUS_BLACK:
get_npc()->aiData.bAction = DecideActionBlack(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,21 +4,49 @@
*/
#include "../include/LegacyAIPlanFactory.h"
#include "../include/LegacyAIPlan.h"
#include "../include/LegacyCreaturePlan.h"
#include "../include/LegacyZombiePlan.h"
#include "../include/CrowPlan.h"
#include "../include/PlanList.h"
#include "../../TacticalAI/AIInternals.h" // DEBUGAIMSG
#include "../../Tactical/Soldier Control.h" // For SOLDIERTYPE definition
#include "../../Tactical/Animation Data.h" // For the definition of, wait for it... BLOODCAT!
#include <stdio.h>
namespace AI
{
namespace tactical
{
Plan* LegacyAIPlanFactory::create_plan(const AIInputData& input)
Plan* LegacyAIPlanFactory::create_plan(SOLDIERTYPE* npc, const AIInputData& input)
{
return new LegacyAIPlan();
DEBUGAIMSG("Planning for "<<(int)npc->ubID);
if((npc->flags.uiStatusFlags & SOLDIER_MONSTER) || npc->ubBodyType == BLOODCAT )
return new LegacyCreaturePlan(npc);
if(npc->ubBodyType == CROW)
{
PlanList* find_supper = new PlanList(npc);
CrowSeekCorpsePlan* seek_corpse = new CrowSeekCorpsePlan(npc);
CrowPeckPlan* peck = new CrowPeckPlan(npc, seek_corpse->get_corpse_grid());
find_supper->add_subplan(seek_corpse);
find_supper->add_subplan(peck);
return find_supper;
}
#ifdef ENABLE_ZOMBIES
if(npc->IsZombie())
return new LegacyZombiePlan(npc);
#endif
return new LegacyAIPlan(npc); // no special plan for other cases yet, return default legacy AI wrapper
}
void LegacyAIPlanFactory::update_plan(const AIInputData& input, Plan* plan_to_change)
void LegacyAIPlanFactory::update_plan(SOLDIERTYPE* npc, const AIInputData& input)
{
// TODO: currently, everything is handled in the LegacyAIPlan. The goal is to modularize the AI, so that in
// the end, the LegacyAIPlan is split into so many "new" AI Plan subclasses, that every case is covered.
// Then the behavior of the old AI would be implemented here.
DEBUGAIMSG("Update called for "<<(int)npc->ubID<<" event: "<<input);
if(!npc->ai_masterplan_)
npc->ai_masterplan_ = create_plan(npc, input);
}
}
}
+8 -3
View File
@@ -11,10 +11,15 @@ namespace AI
{
namespace tactical
{
/// Simply end the turn for the npc passed in the manipulated_object wrapper, then return.
void NullPlan::execute(bool turn_based, PlanInputData& manipulated_object)
NullPlan::NullPlan(SOLDIERTYPE* npc)
: Plan(npc)
{
EndAIGuysTurn(manipulated_object.controlled_npc_);
}
/// Simply set the action to be performed to AI_ACTION_NONE
void NullPlan::execute(PlanInputData& environment)
{
get_npc()->aiData.bAction = AI_ACTION_NONE;
}
}
}
@@ -11,12 +11,12 @@ namespace AI
{
namespace tactical
{
Plan* NullPlanFactory::create_plan(const AIInputData& input)
Plan* NullPlanFactory::create_plan(SOLDIERTYPE* npc, const AIInputData& input)
{
return new NullPlan();
return new NullPlan(npc);
}
void NullPlanFactory::update_plan(const AIInputData& input, Plan* plan_to_change)
void NullPlanFactory::update_plan(SOLDIERTYPE* npc, const AIInputData& input)
{
// the idea is to do nothing, so let's do it...
}
@@ -3,21 +3,20 @@
* @author feynman (bears-pit.com)
*/
// XXX Add includes for new factories here XXX
#include "../include/PlanFactoryLibrary.h"
#include "../include/NullPlanFactory.h"
#include "../include/LegacyAIPlanFactory.h"
// XXX Add includes for new factories here XXX
#include "../../Utils/INIReader.h"
#undef max // Who the fuck writes MACROS not using CAPITAL LETTERS??? You, sir, have lost your coding license. Please hand over your compiler and leave. Now.
#include <stdexcept>
#include <limits>
#include <string>
#include <sstream>
#undef max
namespace AI
{
namespace tactical
@@ -28,6 +27,7 @@ namespace AI
*
* This is the place where new factories need to be "registred" in order for them to be available using the
* settings in AI.ini
* The constructor is private due to the singleton pattern
*/
PlanFactoryLibrary::PlanFactoryLibrary()
{
@@ -66,18 +66,35 @@ namespace AI
/**@brief Create a plan for the given input using the factory at the given index
* @param index Index of the concrete plan factory to use, set in the constructor via ini settings
* @param npc The NPC the created plan is for
* @param input The environmental data required for a factory to plan
* @throws std::out_of_range for invalid index
* @throws std::logic_error for valid index, but undefined factory (most likely due to typo in AI.ini)
* @return Pointer to the only instance of the PlanFactoryLibrary
* @return A new plan
*/
Plan* PlanFactoryLibrary::create_plan(size_t index, AIInputData& input) const
Plan* PlanFactoryLibrary::create_plan(size_t index, SOLDIERTYPE* npc, const AIInputData& input) const
{
if(index >= ai_index_to_factory_mapping_.size())
throw std::out_of_range("PlanFactoryLibrary detected invalid factory index");
if(!ai_index_to_factory_mapping_[index])
throw std::logic_error("PlanFactoryLibrary encountered a nullptr for a valid index (typo in AI.ini?)");
return ai_index_to_factory_mapping_[index]->create_plan(input);
return ai_index_to_factory_mapping_[index]->create_plan(npc, input);
}
/**@brief Update a plan for the given input using the factory at the given index
* @param index Index of the concrete plan factory to use, set in the constructor via ini settings
* @param npc The NPC the updated plan is for
* @param input The environmental data required for a factory to plan
* @throws std::out_of_range for invalid index
* @throws std::logic_error for valid index, but undefined factory (most likely due to typo in AI.ini)
*/
void PlanFactoryLibrary::update_plan(size_t index, SOLDIERTYPE* npc, const AIInputData& input) const
{
if(index >= ai_index_to_factory_mapping_.size())
throw std::out_of_range("PlanFactoryLibrary detected invalid factory index");
if(!ai_index_to_factory_mapping_[index])
throw std::logic_error("PlanFactoryLibrary encountered a nullptr for a valid index (typo in AI.ini?)");
return ai_index_to_factory_mapping_[index]->update_plan(npc, input);
}
}
}
+2 -2
View File
@@ -57,7 +57,7 @@ enum
#define NUM_PANIC_TRIGGERS 3
#define ENEMY_OFFERED_SURRENDER 0x01
typedef struct
struct TacticalStatusType
{
UINT32 uiFlags;
TacticalTeamType Team[ MAXTEAMS ];
@@ -163,7 +163,7 @@ typedef struct
// PADDING GONE!!!!!
} TacticalStatusType;
};
extern UINT8 gbPlayerNum;
+12 -59
View File
@@ -57,6 +57,9 @@
#endif
#include "connect.h"
#include "../ModularizedTacticalAI/include/Plan.h"
#include "../ModularizedTacticalAI/include/PlanFactoryLibrary.h"
#include "../ModularizedTacticalAI/include/AbstractPlanFactory.h"
//rain
//#define VIS_DIST_DECREASE_PER_RAIN_INTENSITY 20
@@ -2154,67 +2157,21 @@ void ManSeesMan(SOLDIERTYPE *pSoldier, SOLDIERTYPE *pOpponent, INT32 sOppGridNo,
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"ManSeesMan");
if (pSoldier->ubID >= TOTAL_SOLDIERS)
{
/*
#ifdef BETAVERSION
NumMessage("ManSeesMan: ERROR - ptr->guynum = ",ptr->guynum);
#endif
*/
return;
}
if (pOpponent->ubID >= TOTAL_SOLDIERS)
{
/*
#ifdef BETAVERSION
NumMessage("ManSeesMan: ERROR - oppPtr->guynum = ",oppPtr->guynum);
#endif
*/
return;
}
// if we're somehow looking while inactive, at base, dying or already dead
if (!pSoldier->bActive || !pSoldier->bInSector || (pSoldier->stats.bLife < OKLIFE))
{
/*
#ifdef BETAVERSION
sprintf(tempstr,"ManSeesMan: ERROR - %s is SEEING ManSeesMan while inactive/at base/dead/dying",ExtMen[ptr->guynum].name);
PopMessage(tempstr);
#endif
*/
return;
}
// if we're somehow seeing a guy who is inactive, at base, or already dead
if (!pOpponent->bActive || !pOpponent->bInSector || pOpponent->stats.bLife <= 0)
{
/*
#ifdef BETAVERSION
sprintf(tempstr,"ManSeesMan: ERROR - %s sees %s, ManSeesMan, who is inactive/at base/dead",ExtMen[ptr->guynum].name,ExtMen[oppPtr->guynum].name);
PopMessage(tempstr);
#endif
*/
return;
}
// if we're somehow seeing a guy who is on the same team
if (pSoldier->bTeam == pOpponent->bTeam)
{
/*
#ifdef BETAVERSION
sprintf(tempstr,"ManSeesMan: ERROR - on SAME TEAM. ptr->guynum = %d, oppPtr->guynum = %d",
ptr->guynum,oppPtr->guynum);
PopMessage(tempstr);
#endif
*/
return;
}
// Flugente: if the other guy is in med or deep water and wearing scua gear, then we cannot see him as he is submerged
if ( pOpponent->UsesScubaGear() )
return;
// Flugente: update our sight concerning this guy, otherwise we could get way with open attacks because this does not get updated
pSoldier->RecognizeAsCombatant(pOpponent->ubID);
@@ -2355,15 +2312,6 @@ void ManSeesMan(SOLDIERTYPE *pSoldier, SOLDIERTYPE *pOpponent, INT32 sOppGridNo,
{
switch( pSoldier->ubProfile )
{
/*
case MIKE:
if ( gfPlayerTeamSawMike && !( gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags2 & PROFILE_MISC_FLAG2_SAID_FIRSTSEEN_QUOTE ) )
{
InitiateConversation( pSoldier, pOpponent, NPC_INITIAL_QUOTE, 0 );
gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags2 |= PROFILE_MISC_FLAG2_SAID_FIRSTSEEN_QUOTE;
}
break;
*/
case IGGY:
if ( ! ( gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags2 & PROFILE_MISC_FLAG2_SAID_FIRSTSEEN_QUOTE ) )
{
@@ -2813,7 +2761,10 @@ if(SEE_MENT)
// ATE: Check stance, change to threatending
ReevaluateEnemyStance( pSoldier, pSoldier->usAnimState );
}
AI::tactical::AIInputData ai_input(AI::tactical::AIInputData::Visual(), pOpponent, sOppGridNo, bOppLevel, ubCaller, ubCaller2);
AI::tactical::PlanInputData plan_input((gTacticalStatus.uiFlags & TURNBASED)!=0, gTacticalStatus);
AI::tactical::PlanFactoryLibrary* plan_lib(AI::tactical::PlanFactoryLibrary::instance());
plan_lib->update_plan(pSoldier->bAIIndex, pSoldier, ai_input);
}
@@ -6333,14 +6284,12 @@ void HearNoise(SOLDIERTYPE *pSoldier, UINT8 ubNoiseMaker, INT32 sGridNo, INT8 bL
INT8 bDirection;
BOOLEAN fMuzzleFlash = FALSE;
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "%d hears noise from %d (%d/%d) volume %d", pSoldier->ubID, ubNoiseMaker, sGridNo, bLevel, ubVolume ) );
if ( pSoldier->ubBodyType == CROW )
{
CrowsFlyAway( pSoldier->bTeam );
return;
}
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "%d hears noise from %d (%d/%d) volume %d", pSoldier->ubID, ubNoiseMaker, sGridNo, bLevel, ubVolume ) );
// "Turn head" towards the source of the noise and try to see what's there
@@ -6649,6 +6598,10 @@ void HearNoise(SOLDIERTYPE *pSoldier, UINT8 ubNoiseMaker, INT32 sGridNo, INT8 bL
}
}
}
AI::tactical::AIInputData ai_input(AI::tactical::AIInputData::Auditive(), ubNoiseMaker, sGridNo, bLevel, ubVolume, ubNoiseType);
AI::tactical::PlanInputData plan_input((gTacticalStatus.uiFlags & TURNBASED)!=0, gTacticalStatus);
AI::tactical::PlanFactoryLibrary* plan_lib(AI::tactical::PlanFactoryLibrary::instance());
plan_lib->update_plan(pSoldier->bAIIndex, pSoldier, ai_input);
}
void TellPlayerAboutNoise( SOLDIERTYPE *pSoldier, UINT8 ubNoiseMaker, INT32 sGridNo, INT8 bLevel, UINT8 ubVolume, UINT8 ubNoiseType, UINT8 ubNoiseDir )
+11
View File
@@ -5,6 +5,9 @@
#include "random.h"
#include "Points.h"
#include <iostream>
#include <iomanip>
extern BOOLEAN gfTurnBasedAI;
@@ -104,6 +107,14 @@ enum
#undef max
#define max(a,b) ((a) > (b) ? (a) : (b))
// Added by feynman - remove all the ugly #ifdefs printf combinations for required for DebugAI
//#define DEBUGAI
#ifdef DEBUGAI
#define DEBUGAIMSG(X) std::cerr<<"AIDEBUGMSG "<<std::setw(40)<<__FILE__<<":"<<std::setw(4)<<__LINE__<<": "<< X<<std::endl
#else
#define DEBUGAIMSG(X)
#endif
typedef struct
{
SOLDIERTYPE * pOpponent;
+12 -33
View File
@@ -63,8 +63,6 @@
#include "ModularizedTacticalAI/include/Plan.h"
#include "ModularizedTacticalAI/include/PlanFactoryLibrary.h"
#include "ModularizedTacticalAI/include/AbstractPlanFactory.h"
#include <stdexcept>
#include <iostream>
#ifdef JA2UB
#include "Ja25_Tactical.h"
@@ -500,19 +498,10 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named
gubAICounter++;
if(!pSoldier->ai_masterplan_) // if the Soldier has no plan, create one
{
if(pSoldier->bAIIndex == 0) // not yet initialized, use bTeam+1 as default
pSoldier->bAIIndex = pSoldier->bTeam + 1;
AI::tactical::AIInputData ai_input;
ai_input.npc_to_plan_for_ = pSoldier;
AI::tactical::PlanFactoryLibrary* plan_lib(AI::tactical::PlanFactoryLibrary::instance());
pSoldier->ai_masterplan_ = plan_lib->create_plan(pSoldier->bAIIndex, ai_input);
}
AI::tactical::PlanInputData plan_input;
plan_input.controlled_npc_ = pSoldier;
pSoldier->ai_masterplan_->execute(gfTurnBasedAI, plan_input);
if(gfTurnBasedAI)
TurnBasedHandleNPCAI(pSoldier);
else
RTHandleAI( pSoldier );
}
else
{
@@ -1445,26 +1434,16 @@ void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier)
{
if (!(gTacticalStatus.uiFlags & ENGAGED_IN_CONV))
{
#ifdef ENABLE_ZOMBIES
if ( pSoldier->IsZombie() )
{
pSoldier->aiData.bAction = ZombieDecideAction(pSoldier);
}
else if (CREATURE_OR_BLOODCAT( pSoldier ))
#else
if (CREATURE_OR_BLOODCAT( pSoldier ))
#endif
if(!pSoldier->ai_masterplan_) // if the Soldier has no plan, create one
{
pSoldier->aiData.bAction = CreatureDecideAction( pSoldier );
}
else if (pSoldier->ubBodyType == CROW)
{
pSoldier->aiData.bAction = CrowDecideAction( pSoldier );
}
else
{
pSoldier->aiData.bAction = DecideAction(pSoldier);
if(pSoldier->bAIIndex == 0) // not yet initialized, use bTeam+1 as default
pSoldier->bAIIndex = pSoldier->bTeam + 1;
AI::tactical::AIInputData ai_input;
AI::tactical::PlanFactoryLibrary* plan_lib(AI::tactical::PlanFactoryLibrary::instance());
pSoldier->ai_masterplan_ = plan_lib->create_plan(pSoldier->bAIIndex, pSoldier, ai_input);
}
AI::tactical::PlanInputData plan_input(true, gTacticalStatus);
pSoldier->ai_masterplan_->execute(plan_input);
}
}
-88
View File
@@ -1670,91 +1670,3 @@ void CreatureDecideAlertStatus( SOLDIERTYPE *pSoldier )
}
}
INT8 CrowDecideActionRed( SOLDIERTYPE * pSoldier )
{
// OK, Fly away!
//HandleCrowFlyAway( pSoldier );
if (!gfTurnBasedAI)
{
pSoldier->aiData.usActionData = 30000;
return( AI_ACTION_WAIT );
}
else
{
return( AI_ACTION_NONE );
}
}
INT8 CrowDecideActionGreen( SOLDIERTYPE * pSoldier )
{
INT32 sCorpseGridNo;
UINT8 ubDirection;
INT16 sFacingDir;
// Look for a corse!
sCorpseGridNo = FindNearestRottingCorpse( pSoldier );
if (!TileIsOutOfBounds(sCorpseGridNo))
{
// Are we close, if so , peck!
if ( SpacesAway( pSoldier->sGridNo, sCorpseGridNo ) < 2 )
{
// Change facing
sFacingDir = GetDirectionFromGridNo( sCorpseGridNo, pSoldier );
if ( sFacingDir != pSoldier->ubDirection )
{
pSoldier->aiData.usActionData = sFacingDir;
return(AI_ACTION_CHANGE_FACING);
}
else if (!gfTurnBasedAI)
{
pSoldier->aiData.usActionData = 30000;
return( AI_ACTION_WAIT );
}
else
{
return( AI_ACTION_NONE );
}
}
else
{
// Walk to nearest one!
pSoldier->aiData.usActionData = FindGridNoFromSweetSpot( pSoldier, sCorpseGridNo, 4, &ubDirection );
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))
{
return( AI_ACTION_GET_CLOSER );
}
}
}
return( AI_ACTION_NONE );
}
INT8 CrowDecideAction( SOLDIERTYPE * pSoldier )
{
if ( pSoldier->usAnimState == CROW_FLY )
{
return( AI_ACTION_NONE );
}
switch( pSoldier->aiData.bAlertStatus )
{
case STATUS_GREEN:
case STATUS_YELLOW:
return( CrowDecideActionGreen( pSoldier ) );
case STATUS_RED:
case STATUS_BLACK:
return( CrowDecideActionRed( pSoldier ) );
default:
Assert( FALSE );
return( AI_ACTION_NONE );
}
}
-157
View File
@@ -5766,163 +5766,6 @@ INT16 ubMinAPCost;
}
INT8 DecideAction(SOLDIERTYPE *pSoldier)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideAction");
INT8 bAction=AI_ACTION_NONE;
#ifdef DEBUGDECISIONS
STR16 tempstr;
#endif
#ifdef AI_TIMING_TESTS
UINT32 uiStartTime, uiEndTime;
#endif
if ( pSoldier->bTeam != MILITIA_TEAM )
{
if ( !sniperwarning && pSoldier->aiData.bOrders == SNIPER )
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_WATHCHOUTFORSNIPERS] );
sniperwarning = TRUE;
}
if (!biggunwarning && FindRocketLauncherOrCannon(pSoldier) != NO_SLOT )
{
biggunwarning = TRUE;
//TODO: don't say this again after reloading a savegame
SayQuoteFromAnyBodyInSector( QUOTE_WEARY_SLASH_SUSPUCIOUS );
}
}
// turn off cautious flag
pSoldier->aiData.fAIFlags &= (~AI_CAUTIOUS);
//reset flank count
//NumMessage("DecideAction - Guy#",pSoldier->ubID);
// if the NPC is being "ESCORTED" (seen civilians only for now)
if (pSoldier->aiData.bUnderEscort)
{
#ifdef DEBUGDECISIONS
AIPopMessage("AlertStatus = ESCORT");
#endif
//bAction = DecideActionEscort(pSoldier);
}
else // "normal" NPC AI
{
// if status over-ride is set, bypass RED/YELLOW and go directly to GREEN!
if ((pSoldier->aiData.bBypassToGreen) && (pSoldier->aiData.bAlertStatus < STATUS_BLACK))
{
bAction = DecideActionGreen(pSoldier);
if ( !gfTurnBasedAI )
{
// reset bypass now
pSoldier->aiData.bBypassToGreen = 0;
}
}
else
{
switch (pSoldier->aiData.bAlertStatus)
{
case STATUS_GREEN:
#ifdef DEBUGDECISIONS
AIPopMessage("AlertStatus = GREEN");
#endif
#ifdef AI_TIMING_TESTS
uiStartTime = GetJA2Clock();
#endif
bAction = DecideActionGreen(pSoldier);
#ifdef AI_TIMING_TESTS
uiEndTime = GetJA2Clock();
guiGreenTimeTotal += (uiEndTime - uiStartTime);
guiGreenCounter++;
#endif
break;
case STATUS_YELLOW:
#ifdef DEBUGDECISIONS
AIPopMessage("AlertStatus = YELLOW");
#endif
#ifdef AI_TIMING_TESTS
uiStartTime = GetJA2Clock();
#endif
bAction = DecideActionYellow(pSoldier);
#ifdef AI_TIMING_TESTS
uiEndTime = GetJA2Clock();
guiYellowTimeTotal += (uiEndTime - uiStartTime);
guiYellowCounter++;
#endif
break;
case STATUS_RED:
#ifdef DEBUGDECISIONS
AIPopMessage("AlertStatus = RED");
#endif
#ifdef AI_TIMING_TESTS
uiStartTime = GetJA2Clock();
#endif
bAction = DecideActionRed(pSoldier,TRUE);
#ifdef AI_TIMING_TESTS
uiEndTime = GetJA2Clock();
guiRedTimeTotal += (uiEndTime - uiStartTime);
guiRedCounter++;
#endif
break;
case STATUS_BLACK:
#ifdef DEBUGDECISIONS
AIPopMessage("AlertStatus = BLACK");
#endif
#ifdef AI_TIMING_TESTS
uiStartTime = GetJA2Clock();
#endif
bAction = DecideActionBlack(pSoldier);
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("DecideActionBlack=%d",bAction));
#ifdef AI_TIMING_TESTS
uiEndTime = GetJA2Clock();
guiBlackTimeTotal += (uiEndTime - uiStartTime);
guiBlackCounter++;
#endif
break;
}
}
}
#ifdef DEBUGDECISIONS
sprintf( tempstr,"DecideAction: selected action %d, actionData %d\n\n",bAction,pSoldier->aiData.usActionData);
DebugAI( tempstr );
#endif
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideAction done");
return(bAction);
}
INT8 DecideActionEscort(SOLDIERTYPE *pSoldier)
{
#ifdef DEBUGDECISIONS
STR16 tempstr;
#endif
// if he has a place to go, and isn't already there... go!
if (!TileIsOutOfBounds(pSoldier->aiData.usActionData) && (pSoldier->sGridNo != pSoldier->aiData.usActionData))
{
#ifdef DEBUGDECISIONS
sprintf(tempstr,"%s - ESCORTED NPC GOING to grid %d",pSoldier->name,pSoldier->aiData.usActionData);
AIPopMessage(tempstr);
#endif
return(AI_ACTION_ESCORTED_MOVE);
}
else
return(AI_ACTION_NONE);
}
void DecideAlertStatus( SOLDIERTYPE *pSoldier )
{
#ifdef DEBUGDECISIONS
+14 -65
View File
@@ -19,71 +19,11 @@
#include "Quests.h"
#include "GameSettings.h"
#endif
// needed to use the modularized tactical AI:
#include "ModularizedTacticalAI/include/Plan.h"
#include "ModularizedTacticalAI/include/PlanFactoryLibrary.h"
#include "ModularizedTacticalAI/include/AbstractPlanFactory.h"
INT8 RTPlayerDecideAction( SOLDIERTYPE * pSoldier )
{
INT8 bAction=AI_ACTION_NONE;
if (gTacticalStatus.fAutoBandageMode)
{
bAction = DecideAutoBandage( pSoldier );
}
#ifdef ENABLE_ZOMBIES
else if ( pSoldier->IsZombie() )
{
bAction = ZombieDecideAction( pSoldier );
}
#endif
else
{
bAction = DecideAction( pSoldier );
}
#ifdef DEBUGDECISIONS
STR tempstr;
sprintf(tempstr,"DecideAction: selected action %d, actionData %d\n\n",bAction,pSoldier->aiData.usActionData);
DebugAI( tempstr );
#endif
return(bAction);
}
INT8 RTDecideAction(SOLDIERTYPE *pSoldier)
{
if (CREATURE_OR_BLOODCAT( pSoldier ) )
{
return( CreatureDecideAction( pSoldier ) );
}
#ifdef ENABLE_ZOMBIES
else if ( pSoldier->IsZombie() )
{
return( ZombieDecideAction( pSoldier ) );
}
#endif
else if (pSoldier->ubBodyType == CROW)
{
return( CrowDecideAction( pSoldier ) );
}
else if (pSoldier->bTeam == gbPlayerNum)
{
return( RTPlayerDecideAction( pSoldier ) );
}
else
{
// handle traversal
if ( (pSoldier->ubProfile != NO_PROFILE) && (gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags3 & PROFILE_MISC_FLAG3_HANDLE_DONE_TRAVERSAL ) )
{
TriggerNPCWithGivenApproach( pSoldier->ubProfile, APPROACH_DONE_TRAVERSAL, FALSE );
gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags3 &= (~PROFILE_MISC_FLAG3_HANDLE_DONE_TRAVERSAL);
pSoldier->ubQuoteActionID = 0;
// wait a tiny bit
pSoldier->aiData.usActionData = 100;
return( AI_ACTION_WAIT );
}
return( DecideAction( pSoldier ) );
}
}
UINT16 RealtimeDelay( SOLDIERTYPE * pSoldier )
{
@@ -236,7 +176,16 @@ void RTHandleAI( SOLDIERTYPE * pSoldier )
{
if (!(gTacticalStatus.uiFlags & ENGAGED_IN_CONV))
{
pSoldier->aiData.bAction = RTDecideAction( pSoldier );
if(!pSoldier->ai_masterplan_) // if the Soldier has no plan, create one
{
if(pSoldier->bAIIndex == 0) // not yet initialized, use bTeam+1 as default
pSoldier->bAIIndex = pSoldier->bTeam + 1;
AI::tactical::AIInputData ai_input;
AI::tactical::PlanFactoryLibrary* plan_lib(AI::tactical::PlanFactoryLibrary::instance());
pSoldier->ai_masterplan_ = plan_lib->create_plan(pSoldier->bAIIndex, pSoldier, ai_input);
}
AI::tactical::PlanInputData plan_input(false, gTacticalStatus);
pSoldier->ai_masterplan_->execute(plan_input);
}
}
}