//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #ifndef AI_PLAYERALLY_H #define AI_PLAYERALLY_H #include "AI_Criteria.h" #include "ai_baseactor.h" #include "ai_speechfilter.h" #include "simtimer.h" #include "utlmap.h" #ifndef _WIN32 #undef min #endif #include "stdstring.h" #ifndef _WIN32 #undef MINMAX_H #include "minmax.h" #endif #if defined(_WIN32) #pragma once #endif //----------------------------------------------------------------------------- #define TLK_ANSWER "TLK_ANSWER" #define TLK_ANSWER_HELLO "TLK_ANSWER_HELLO" #define TLK_QUESTION "TLK_QUESTION" #define TLK_IDLE "TLK_IDLE" #define TLK_STARE "TLK_STARE" #define TLK_LOOK "TLK_LOOK" // player looking at player for a second #define TLK_USE "TLK_USE" #define TLK_STARTFOLLOW "TLK_STARTFOLLOW" #define TLK_STOPFOLLOW "TLK_STOPFOLLOW" #define TLK_JOINPLAYER "TLK_JOINPLAYER" #define TLK_STOP "TLK_STOP" #define TLK_NOSHOOT "TLK_NOSHOOT" #define TLK_HELLO "TLK_HELLO" #define TLK_PHELLO "TLK_PHELLO" #define TLK_HELLO_NPC "TLK_HELLO_NPC" #define TLK_PIDLE "TLK_PIDLE" #define TLK_PQUESTION "TLK_PQUESTION" #define TLK_PLHURT1 "TLK_PLHURT1" #define TLK_PLHURT2 "TLK_PLHURT2" #define TLK_PLHURT3 "TLK_PLHURT3" #define TLK_PLHURT "TLK_PLHURT" #define TLK_PLPUSH "TLK_PLPUSH" #define TLK_PLRELOAD "TLK_PLRELOAD" #define TLK_SMELL "TLK_SMELL" #define TLK_SHOT "TLK_SHOT" #define TLK_WOUND "TLK_WOUND" #define TLK_MORTAL "TLK_MORTAL" #define TLK_DANGER "TLK_DANGER" #define TLK_SEE_COMBINE "TLK_SEE_COMBINE" #define TLK_ENEMY_DEAD "TLK_ENEMY_DEAD" #define TLK_ALYX_ENEMY_DEAD "TLK_ALYX_ENEMY_DEAD" #define TLK_SELECTED "TLK_SELECTED" // selected by player in command mode. #define TLK_COMMANDED \ "TLK_COMMANDED" // received orders from player in command mode #define TLK_COMMAND_FAILED "TLK_COMMAND_FAILED" #define TLK_DENY_COMMAND \ "TLK_DENY_COMMAND" // designer has asked this NPC to politely deny player // commands to move the squad #define TLK_BETRAYED "TLK_BETRAYED" // player killed an ally in front of me. #define TLK_ALLY_KILLED \ "TLK_ALLY_KILLED" // witnessed an ally die some other way. #define TLK_ATTACKING "TLK_ATTACKING" // about to fire my weapon at a target #define TLK_HEAL "TLK_HEAL" // healing someone #define TLK_GIVEAMMO "TLK_GIVEAMMO" // giving ammo to someone #define TLK_DEATH "TLK_DEATH" // Death rattle #define TLK_HELP_ME "TLK_HELP_ME" // call out to the player for help #define TLK_PLYR_PHYSATK \ "TLK_PLYR_PHYSATK" // Player's attacked me with a thrown physics object #define TLK_NEWWEAPON "TLK_NEWWEAPON" #define TLK_PLDEAD "TLK_PLDEAD" #define TLK_HIDEANDRELOAD "TLK_HIDEANDRELOAD" #define TLK_STARTCOMBAT "TLK_STARTCOMBAT" #define TLK_WATCHOUT "TLK_WATCHOUT" #define TLK_MOBBED "TLK_MOBBED" #define TLK_MANY_ENEMIES "TLK_MANY_ENEMIES" #define TLK_FLASHLIGHT_ILLUM "TLK_FLASHLIGHT_ILLUM" #define TLK_FLASHLIGHT_ON "TLK_FLASHLIGHT_ON" // player turned on flashlight #define TLK_FLASHLIGHT_OFF "TLK_FLASHLIGHT_OFF" // player turned off flashlight #define TLK_DARKNESS_LOSTPLAYER "TLK_DARKNESS_LOSTPLAYER" #define TLK_DARKNESS_FOUNDPLAYER "TLK_DARKNESS_FOUNDPLAYER" #define TLK_DARKNESS_UNKNOWN_WOUND "TLK_DARKNESS_UNKNOWN_WOUND" #define TLK_DARKNESS_HEARDSOUND "TLK_DARKNESS_HEARDSOUND" #define TLK_DARKNESS_LOSTENEMY_BY_FLASHLIGHT \ "TLK_DARKNESS_LOSTENEMY_BY_FLASHLIGHT" #define TLK_DARKNESS_LOSTENEMY_BY_FLASHLIGHT_EXPIRED \ "TLK_DARKNESS_LOSTENEMY_BY_FLASHLIGHT_EXPIRED" #define TLK_DARKNESS_FOUNDENEMY_BY_FLASHLIGHT \ "TLK_DARKNESS_FOUNDENEMY_BY_FLASHLIGHT" #define TLK_DARKNESS_FLASHLIGHT_EXPIRED \ "TLK_DARKNESS_FLASHLIGHT_EXPIRED" // flashlight expired while not in combat #define TLK_DARKNESS_ENEMY_IN_DARKNESS \ "TLK_DARKNESS_ENEMY_IN_DARKNESS" // have an enemy, but it's in the darkness #define TLK_SPOTTED_INCOMING_HEADCRAB "TLK_SPOTTED_INCOMING_HEADCRAB" #define TLK_CANT_INTERACT_NOW \ "TLK_CANT_INTERACT_NOW" // to busy to interact with an object the player is // holding up to me #define TLK_ALLY_IN_BARNACLE \ "TLK_ALLY_IN_BARNACLE" // Barnacle is lifting my buddy! #define TLK_SELF_IN_BARNACLE \ "TLK_SELF_IN_BARNACLE" // I was grabbed by a barnacle! #define TLK_FOUNDPLAYER "TLK_FOUNDPLAYER" #define TLK_PLAYER_KILLED_NPC "TLK_PLAYER_KILLED_NPC" #define TLK_ENEMY_BURNING "TLK_ENEMY_BURNING" #define TLK_SPOTTED_ZOMBIE_WAKEUP "TLK_SPOTTED_ZOMBIE_WAKEUP" #define TLK_SPOTTED_HEADCRAB_LEAVING_ZOMBIE \ "TLK_SPOTTED_HEADCRAB_LEAVING_ZOMBIE" #define TLK_DANGER_ZOMBINE_GRENADE "TLK_DANGER_ZOMBINE_GRENADE" #define TLK_BALLSOCKETED "TLK_BALLSOCKETED" // Vehicle passenger #define TLK_PASSENGER_WARN_COLLISION \ "TLK_PASSENGER_WARN_COLLISION" // About to collide with something #define TLK_PASSENGER_IMPACT "TLK_PASSENGER_IMPACT" // Just hit something #define TLK_PASSENGER_OVERTURNED \ "TLK_PASSENGER_OVERTURNED" // Vehicle has just overturned #define TLK_PASSENGER_REQUEST_UPRIGHT \ "TLK_PASSENGER_REQUEST_UPRIGHT" // Vehicle needs to be put upright #define TLK_PASSENGER_ERRATIC_DRIVING \ "TLK_PASSENGER_ERRATIC_DRIVING" // Vehicle is moving erratically #define TLK_PASSENGER_VEHICLE_STARTED \ "TLK_PASSENGER_VEHICLE_STARTED" // Vehicle has started moving #define TLK_PASSENGER_VEHICLE_STOPPED \ "TLK_PASSENGER_VEHICLE_STOPPED" // Vehicle has stopped moving #define TLK_PASSENGER_BEGIN_ENTRANCE \ "TLK_PASSENGER_BEGIN_ENTRANCE" // Passenger started entering #define TLK_PASSENGER_FINISH_ENTRANCE \ "TLK_PASSENGER_FINISH_ENTRANCE" // Passenger finished entering (is in seat) #define TLK_PASSENGER_BEGIN_EXIT \ "TLK_PASSENGER_BEGIN_EXIT" // Passenger started exiting #define TLK_PASSENGER_FINISH_EXIT \ "TLK_PASSENGER_FINISH_EXIT" // Passenger finished exiting (seat is vacated) #define TLK_PASSENGER_PLAYER_ENTERED \ "TLK_PASSENGER_PLAYER_ENTERED" // Player entered the vehicle #define TLK_PASSENGER_PLAYER_EXITED \ "TLK_PASSENGER_PLAYER_EXITED" // Player exited the vehicle #define TLK_PASSENGER_NEW_RADAR_CONTACT \ "TLK_PASSENGER_NEW_RADAR_CONTACT" // Noticed a brand new contact on the // radar #define TLK_PASSENGER_PUNTED \ "TLK_PASSENGER_PUNTED" // The player has punted us while we're sitting in // the vehicle // Vortigaunt #define TLK_VORTIGAUNT_DISPEL "TLK_VORTIGAUNT_DISPEL" // Dispel attack starting // resume is "as I was saying..." or "anyhow..." #define TLK_RESUME "TLK_RESUME" // tourguide stuff below #define TLK_TGSTAYPUT "TLK_TGSTAYPUT" #define TLK_TGFIND "TLK_TGFIND" #define TLK_TGSEEK "TLK_TGSEEK" #define TLK_TGLOSTYOU "TLK_TGLOSTYOU" #define TLK_TGCATCHUP "TLK_TGCATCHUP" #define TLK_TGENDTOUR "TLK_TGENDTOUR" //----------------------------------------------------------------------------- #define TALKRANGE_MIN 500.0 // don't talk to anyone farther away than this //----------------------------------------------------------------------------- #define TALKER_STARE_DIST \ 128 // anyone closer than this and looking at me is probably staring at me. #define TALKER_DEFER_IDLE_SPEAK_MIN 10 #define TALKER_DEFER_IDLE_SPEAK_MAX 20 //----------------------------------------------------------------------------- class CAI_PlayerAlly; //----------------------------------------------------------------------------- // // CLASS: CAI_AllySpeechManager // //----------------------------------------------------------------------------- enum ConceptCategory_t { SPEECH_IDLE, SPEECH_IMPORTANT, SPEECH_PRIORITY, SPEECH_NUM_CATEGORIES }; struct ConceptCategoryInfo_t { float minGlobalDelay; float maxGlobalDelay; float minPersonalDelay; float maxPersonalDelay; }; enum AIConceptFlags_t { AICF_DEFAULT = 0, AICF_SPEAK_ONCE = 0x01, AICF_PROPAGATE_SPOKEN = 0x02, AICF_TARGET_PLAYER = 0x04, AICF_QUESTION = 0x08, AICF_ANSWER = 0x10, }; struct ConceptInfo_t { AIConcept_t concept; ConceptCategory_t category; float minGlobalCategoryDelay; float maxGlobalCategoryDelay; float minPersonalCategoryDelay; float maxPersonalCategoryDelay; float minConceptDelay; float maxConceptDelay; int flags; }; //------------------------------------- class CAI_AllySpeechManager : public CLogicalEntity { DECLARE_CLASS(CAI_AllySpeechManager, CLogicalEntity); public: CAI_AllySpeechManager(); ~CAI_AllySpeechManager(); void Spawn(); void AddCustomConcept(const ConceptInfo_t &conceptInfo); ConceptCategoryInfo_t *GetConceptCategoryInfo(ConceptCategory_t category); ConceptInfo_t *GetConceptInfo(AIConcept_t concept); void OnSpokeConcept(CAI_PlayerAlly *pPlayerAlly, AIConcept_t concept, AI_Response *response); void SetCategoryDelay(ConceptCategory_t category, float minDelay, float maxDelay = 0.0); bool CategoryDelayExpired(ConceptCategory_t category); bool ConceptDelayExpired(AIConcept_t concept); private: CSimpleSimTimer m_ConceptCategoryTimers[SPEECH_NUM_CATEGORIES]; CUtlMap m_ConceptTimers; friend CAI_AllySpeechManager *GetAllySpeechManager(); static CAI_AllySpeechManager *gm_pSpeechManager; DECLARE_DATADESC(); }; //------------------------------------- CAI_AllySpeechManager *GetAllySpeechManager(); //----------------------------------------------------------------------------- // // CLASS: CAI_PlayerAlly // //----------------------------------------------------------------------------- class CAI_AllySpeechManager; enum AISpeechTargetSearchFlags_t { AIST_PLAYERS = (1 << 0), AIST_NPCS = (1 << 1), AIST_IGNORE_RELATIONSHIP = (1 << 2), AIST_ANY_QUALIFIED = (1 << 3), AIST_FACING_TARGET = (1 << 4), }; struct AISpeechSelection_t { std::string concept; AI_Response Response; EHANDLE hSpeechTarget; }; //------------------------------------- class CAI_PlayerAlly : public CAI_BaseActor { DECLARE_CLASS(CAI_PlayerAlly, CAI_BaseActor); public: //--------------------------------- int ObjectCaps(void) { return UsableNPCObjectCaps(BaseClass::ObjectCaps()); } void TalkInit(void); //--------------------------------- // Behavior //--------------------------------- void GatherConditions(void); void GatherEnemyConditions(CBaseEntity *pEnemy); void OnStateChange(NPC_STATE OldState, NPC_STATE NewState); void PrescheduleThink(void); int SelectSchedule(void); int SelectNonCombatSpeech(AISpeechSelection_t *pSelection); virtual int SelectNonCombatSpeechSchedule(); int TranslateSchedule(int scheduleType); void OnStartSchedule(int scheduleType); void StartTask(const Task_t *pTask); void RunTask(const Task_t *pTask); void TaskFail(AI_TaskFailureCode_t); void TaskFail(const char *pszGeneralFailText) { BaseClass::TaskFail(pszGeneralFailText); } void ClearTransientConditions(); void Touch(CBaseEntity *pOther); //--------------------------------- // Combat //--------------------------------- void OnKilledNPC(CBaseCombatCharacter *pKilled); //--------------------------------- // Damage handling //--------------------------------- void TraceAttack(const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator); int OnTakeDamage_Alive(const CTakeDamageInfo &info); int TakeHealth(float flHealth, int bitsDamageType); void Event_Killed(const CTakeDamageInfo &info); bool CreateVPhysics(); //--------------------------------- virtual void PainSound(const CTakeDamageInfo &info); //--------------------------------- // Speech & Acting //--------------------------------- CBaseEntity *EyeLookTarget(void); // Override to look at talk target CBaseEntity *FindNamedEntity(const char *pszName, IEntityFindFilter *pFilter = NULL); CBaseEntity *FindSpeechTarget(int flags); virtual bool IsValidSpeechTarget(int flags, CBaseEntity *pEntity); CBaseEntity *GetSpeechTarget() { return m_hTalkTarget.Get(); } void SetSpeechTarget(CBaseEntity *pSpeechTarget) { m_hTalkTarget = pSpeechTarget; } void SetSpeechFilter(CAI_SpeechFilter *pFilter) { m_hSpeechFilter = pFilter; } CAI_SpeechFilter *GetSpeechFilter(void) { return m_hSpeechFilter; } //--------------------------------- virtual bool SelectIdleSpeech(AISpeechSelection_t *pSelection); virtual bool SelectAlertSpeech(AISpeechSelection_t *pSelection); virtual bool SelectInterjection(); virtual bool SelectPlayerUseSpeech(); //--------------------------------- virtual bool SelectQuestionAndAnswerSpeech(AISpeechSelection_t *pSelection); virtual void PostSpeakDispatchResponse(AIConcept_t concept, AI_Response *response); bool SelectQuestionFriend(CBaseEntity *pFriend, AISpeechSelection_t *pSelection); bool SelectAnswerFriend(CBaseEntity *pFriend, AISpeechSelection_t *pSelection, bool bRespondingToHello); //--------------------------------- bool SelectSpeechResponse(AIConcept_t concept, const char *pszModifiers, CBaseEntity *pTarget, AISpeechSelection_t *pSelection); void SetPendingSpeech(AIConcept_t concept, AI_Response &Response); void ClearPendingSpeech(); bool HasPendingSpeech() { return !m_PendingConcept.empty(); } //--------------------------------- bool CanPlaySentence(bool fDisregardState); int PlayScriptedSentence(const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, bool bConcurrent, CBaseEntity *pListener); //--------------------------------- void DeferAllIdleSpeech(float flDelay = -1, CAI_BaseNPC *pIgnore = NULL); //--------------------------------- bool IsOkToSpeak(ConceptCategory_t category, bool fRespondingToPlayer = false); //--------------------------------- bool IsOkToSpeak(void); bool IsOkToCombatSpeak(void); bool IsOkToSpeakInResponseToPlayer(void); bool ShouldSpeakRandom(AIConcept_t concept, int iChance); bool IsAllowedToSpeak(AIConcept_t concept, bool bRespondingToPlayer = false); virtual bool SpeakIfAllowed(AIConcept_t concept, const char *modifiers = NULL, bool bRespondingToPlayer = false, char *pszOutResponseChosen = NULL, size_t bufsize = 0); void ModifyOrAppendCriteria(AI_CriteriaSet &set); //--------------------------------- float GetTimePlayerStaring() { return (m_flTimePlayerStartStare != 0) ? gpGlobals->curtime - m_flTimePlayerStartStare : 0; } //--------------------------------- // NPC Event Response System virtual bool CanRespondToEvent(const char *ResponseConcept); virtual bool RespondedTo(const char *ResponseConcept, bool bForce, bool bCancelScene); //--------------------------------- void OnSpokeConcept(AIConcept_t concept, AI_Response *response); void OnStartSpeaking(); // Inputs virtual void InputIdleRespond(inputdata_t &inputdata){}; void InputSpeakResponseConcept(inputdata_t &inputdata); virtual bool SpeakMapmakerInterruptConcept(string_t iszConcept); void DisplayDeathMessage(void); virtual const char *GetDeathMessageText(void) { return "GAMEOVER_ALLY"; } void InputMakeGameEndAlly(inputdata_t &inputdata); void InputMakeRegularAlly(inputdata_t &inputdata); void InputAnswerQuestion(inputdata_t &inputdata); void InputAnswerQuestionHello(inputdata_t &inputdata); void InputEnableSpeakWhileScripting(inputdata_t &inputdata); void InputDisableSpeakWhileScripting(inputdata_t &inputdata); void AnswerQuestion(CAI_PlayerAlly *pQuestioner, int iQARandomNum, bool bAnsweringHello); protected: #ifdef HL2_DLL // Health regeneration for friendly allies virtual bool ShouldRegenerateHealth(void) { return (Classify() == CLASS_PLAYER_ALLY_VITAL); } #endif inline bool CanSpeakWhileScripting(); // Whether we are a vital ally (useful for wrting Classify() for classes // that are only sometimes vital, such as the Lone Vort in Ep2.) The usual // means by which any other function should determine if a character is // vital is to determine Classify() == CLASS_PLAYER_ALLY_VITAL. Do not use // this function outside that context. inline bool IsGameEndAlly(void) { return m_bGameEndAlly; } //----------------------------------------------------- // Conditions, Schedules, Tasks //----------------------------------------------------- enum { SCHED_TALKER_SPEAK_PENDING_IDLE = BaseClass::NEXT_SCHEDULE, SCHED_TALKER_SPEAK_PENDING_ALERT, SCHED_TALKER_SPEAK_PENDING_COMBAT, NEXT_SCHEDULE, TASK_TALKER_SPEAK_PENDING = BaseClass::NEXT_TASK, NEXT_TASK, COND_TALKER_CLIENTUNSEEN = BaseClass::NEXT_CONDITION, COND_TALKER_PLAYER_DEAD, COND_TALKER_PLAYER_STARING, NEXT_CONDITION }; private: void SetCategoryDelay(ConceptCategory_t category, float minDelay, float maxDelay = 0.0) { m_ConceptCategoryTimers[category].Set(minDelay, maxDelay); } bool CategoryDelayExpired(ConceptCategory_t category) { return m_ConceptCategoryTimers[category].Expired(); } friend class CAI_AllySpeechManager; //--------------------------------- AI_Response m_PendingResponse; std::string m_PendingConcept; float m_TimePendingSet; //--------------------------------- EHANDLE m_hTalkTarget; // who to look at while talking float m_flNextRegenTime; float m_flTimePlayerStartStare; EHANDLE m_hPotentialSpeechTarget; // NPC to tell the response rules about // when trying to find a response to talk // to them with float m_flNextIdleSpeechTime; int m_iQARandomNumber; //--------------------------------- CSimpleSimTimer m_ConceptCategoryTimers[3]; //--------------------------------- CHandle m_hSpeechFilter; bool m_bGameEndAlly; bool m_bCanSpeakWhileScripting; // Allows mapmakers to override // NPC_STATE_SCRIPT or IsScripting() for // responses. float m_flTimeLastRegen; // Last time I regenerated a bit of health. float m_flHealthAccumulator; // Counterpart to the damage accumulator in // CBaseCombatCharacter. So ally health // regeneration is accurate over time. #ifdef _XBOX protected: #endif DECLARE_DATADESC(); protected: DEFINE_CUSTOM_AI; }; bool CAI_PlayerAlly::CanSpeakWhileScripting() { return m_bCanSpeakWhileScripting; } //----------------------------------------------------------------------------- #endif // AI_PLAYERALLY_H