diff --git a/ModularizedTacticalAI/ModularizedTacticalAI_VS2005.vcproj b/ModularizedTacticalAI/ModularizedTacticalAI_VS2005.vcproj index 397979e3..e59ef278 100644 --- a/ModularizedTacticalAI/ModularizedTacticalAI_VS2005.vcproj +++ b/ModularizedTacticalAI/ModularizedTacticalAI_VS2005.vcproj @@ -338,25 +338,49 @@ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx" UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" > + + <\File> + + <\File> - + <\File> - + <\File> + + <\File> + + <\File> - + <\File> - + <\File> + + <\File> + <\File> + + + + + + + + + diff --git a/ModularizedTacticalAI/ModularizedTacticalAI_VS2008.vcproj b/ModularizedTacticalAI/ModularizedTacticalAI_VS2008.vcproj index 0b3bdd83..698fe51f 100644 --- a/ModularizedTacticalAI/ModularizedTacticalAI_VS2008.vcproj +++ b/ModularizedTacticalAI/ModularizedTacticalAI_VS2008.vcproj @@ -1,7 +1,7 @@ + + + + @@ -348,6 +356,14 @@ RelativePath=".\src\LegacyAIPlanFactory.cpp" > + + + + @@ -356,10 +372,18 @@ RelativePath=".\src\NullPlanFactory.cpp" > + + + + + + @@ -378,6 +406,14 @@ RelativePath=".\include\LegacyAIPlanFactory.h" > + + + + @@ -394,6 +430,10 @@ RelativePath=".\include\PlanFactoryLibrary.h" > + + diff --git a/ModularizedTacticalAI/ModularizedTacticalAI_VS2010.vcxproj b/ModularizedTacticalAI/ModularizedTacticalAI_VS2010.vcxproj index b27a54a1..3486aed5 100644 --- a/ModularizedTacticalAI/ModularizedTacticalAI_VS2010.vcxproj +++ b/ModularizedTacticalAI/ModularizedTacticalAI_VS2010.vcxproj @@ -128,22 +128,32 @@ + + + + + + - + - + + + - + + + - \ No newline at end of file + diff --git a/ModularizedTacticalAI/include/AbstractPlanFactory.h b/ModularizedTacticalAI/include/AbstractPlanFactory.h index 8ef0c977..57975972 100644 --- a/ModularizedTacticalAI/include/AbstractPlanFactory.h +++ b/ModularizedTacticalAI/include/AbstractPlanFactory.h @@ -6,6 +6,8 @@ #ifndef ABSTRACT_PLAN_FACTORY_H_ #define ABSTRACT_PLAN_FACTORY_H_ +#include + 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; }; } } diff --git a/ModularizedTacticalAI/include/LegacyAIPlan.h b/ModularizedTacticalAI/include/LegacyAIPlan.h index 786d9a34..c2ac8f12 100644 --- a/ModularizedTacticalAI/include/LegacyAIPlan.h +++ b/ModularizedTacticalAI/include/LegacyAIPlan.h @@ -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;} }; } } diff --git a/ModularizedTacticalAI/include/LegacyAIPlanFactory.h b/ModularizedTacticalAI/include/LegacyAIPlanFactory.h index 6982ac7c..d3fc6506 100644 --- a/ModularizedTacticalAI/include/LegacyAIPlanFactory.h +++ b/ModularizedTacticalAI/include/LegacyAIPlanFactory.h @@ -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); }; } } diff --git a/ModularizedTacticalAI/include/NullPlan.h b/ModularizedTacticalAI/include/NullPlan.h index 8a1975ef..85f9480f 100644 --- a/ModularizedTacticalAI/include/NullPlan.h +++ b/ModularizedTacticalAI/include/NullPlan.h @@ -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;} }; } } diff --git a/ModularizedTacticalAI/include/NullPlanFactory.h b/ModularizedTacticalAI/include/NullPlanFactory.h index 5c370fec..b5d07a7d 100644 --- a/ModularizedTacticalAI/include/NullPlanFactory.h +++ b/ModularizedTacticalAI/include/NullPlanFactory.h @@ -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); }; } } diff --git a/ModularizedTacticalAI/include/Plan.h b/ModularizedTacticalAI/include/Plan.h index 34a56528..ca64503e 100644 --- a/ModularizedTacticalAI/include/Plan.h +++ b/ModularizedTacticalAI/include/Plan.h @@ -6,44 +6,84 @@ #ifndef PLAN_H_ #define PLAN_H_ +#include + 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; }; } } diff --git a/ModularizedTacticalAI/include/PlanFactoryLibrary.h b/ModularizedTacticalAI/include/PlanFactoryLibrary.h index 56a07a00..a562cae9 100644 --- a/ModularizedTacticalAI/include/PlanFactoryLibrary.h +++ b/ModularizedTacticalAI/include/PlanFactoryLibrary.h @@ -10,6 +10,8 @@ #include #include +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 registred_factories_; + /// A mapping of bAIIndex to factory pointers std::deque 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; }; } } diff --git a/ModularizedTacticalAI/src/LegacyAIPlan.cpp b/ModularizedTacticalAI/src/LegacyAIPlan.cpp index e35f637e..8e068e1c 100644 --- a/ModularizedTacticalAI/src/LegacyAIPlan.cpp +++ b/ModularizedTacticalAI/src/LegacyAIPlan.cpp @@ -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 "<sGridNo<<", APs "<bActionPoints<< + ", decided action: "<<(int)get_npc()->aiData.bAction<<", data "<<(int)get_npc()->aiData.usActionData); + } + + } // namespace tactical +} // namespace AI diff --git a/ModularizedTacticalAI/src/LegacyAIPlanFactory.cpp b/ModularizedTacticalAI/src/LegacyAIPlanFactory.cpp index 912120c1..3ac06ee4 100644 --- a/ModularizedTacticalAI/src/LegacyAIPlanFactory.cpp +++ b/ModularizedTacticalAI/src/LegacyAIPlanFactory.cpp @@ -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 + 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: "<ai_masterplan_) + npc->ai_masterplan_ = create_plan(npc, input); } } } diff --git a/ModularizedTacticalAI/src/NullPlan.cpp b/ModularizedTacticalAI/src/NullPlan.cpp index 7da836a0..a5f60c42 100644 --- a/ModularizedTacticalAI/src/NullPlan.cpp +++ b/ModularizedTacticalAI/src/NullPlan.cpp @@ -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; } } } diff --git a/ModularizedTacticalAI/src/NullPlanFactory.cpp b/ModularizedTacticalAI/src/NullPlanFactory.cpp index d9f58437..2afc5227 100644 --- a/ModularizedTacticalAI/src/NullPlanFactory.cpp +++ b/ModularizedTacticalAI/src/NullPlanFactory.cpp @@ -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... } diff --git a/ModularizedTacticalAI/src/PlanFactoryLibrary.cpp b/ModularizedTacticalAI/src/PlanFactoryLibrary.cpp index f0821132..1be13e9f 100644 --- a/ModularizedTacticalAI/src/PlanFactoryLibrary.cpp +++ b/ModularizedTacticalAI/src/PlanFactoryLibrary.cpp @@ -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 #include #include #include +#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); } } } diff --git a/Tactical/Overhead.h b/Tactical/Overhead.h index 7b9ce81d..d45869dd 100644 --- a/Tactical/Overhead.h +++ b/Tactical/Overhead.h @@ -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; diff --git a/Tactical/opplist.cpp b/Tactical/opplist.cpp index 7d01c0e3..c5b78257 100644 --- a/Tactical/opplist.cpp +++ b/Tactical/opplist.cpp @@ -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 ) diff --git a/TacticalAI/AIInternals.h b/TacticalAI/AIInternals.h index d5891728..12db4b0c 100644 --- a/TacticalAI/AIInternals.h +++ b/TacticalAI/AIInternals.h @@ -5,6 +5,9 @@ #include "random.h" #include "Points.h" +#include +#include + 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 "< -#include #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); } } diff --git a/TacticalAI/CreatureDecideAction.cpp b/TacticalAI/CreatureDecideAction.cpp index c664ad89..6f83f836 100644 --- a/TacticalAI/CreatureDecideAction.cpp +++ b/TacticalAI/CreatureDecideAction.cpp @@ -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 ); - } -} - diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index 98caaa53..72a84e0d 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -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 diff --git a/TacticalAI/Realtime.cpp b/TacticalAI/Realtime.cpp index 420763a6..1db3eddd 100644 --- a/TacticalAI/Realtime.cpp +++ b/TacticalAI/Realtime.cpp @@ -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); } } }