You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
607 lines
18 KiB
607 lines
18 KiB
/* |
|
* |
|
* This program is free software; you can redistribute it and/or modify it |
|
* under the terms of the GNU General Public License as published by the |
|
* Free Software Foundation; either version 2 of the License, or (at |
|
* your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, but |
|
* WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
* General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License |
|
* along with this program; if not, write to the Free Software Foundation, |
|
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
* |
|
* In addition, as a special exception, the author gives permission to |
|
* link the code of this program with the Half-Life Game Engine ("HL |
|
* Engine") and Modified Game Libraries ("MODs") developed by Valve, |
|
* L.L.C ("Valve"). You must obey the GNU General Public License in all |
|
* respects for all of the code used other than the HL Engine and MODs |
|
* from Valve. If you modify this file, you may extend this exception |
|
* to your version of the file, but you are not obligated to do so. If |
|
* you do not wish to do so, delete this exception statement from your |
|
* version. |
|
* |
|
*/ |
|
|
|
#ifndef CS_BOT_CHATTER_H |
|
#define CS_BOT_CHATTER_H |
|
#ifdef _WIN32 |
|
#pragma once |
|
#endif |
|
|
|
#define UNDEFINED_COUNT 0xFFFF |
|
#define MAX_PLACES_PER_MAP 64 |
|
#define UNDEFINED_SUBJECT (-1) |
|
#define COUNT_MANY 4 // equal to or greater than this is "many" |
|
|
|
class CCSBot; |
|
class BotChatterInterface; |
|
|
|
typedef unsigned int PlaceCriteria; |
|
typedef unsigned int CountCriteria; |
|
|
|
// A meme is a unit information that bots use to |
|
// transmit information to each other via the radio |
|
|
|
class BotMeme |
|
{ |
|
public: |
|
virtual ~BotMeme(){} |
|
|
|
void Transmit(CCSBot *sender) const; // transmit meme to other bots |
|
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const = 0; // cause the given bot to act on this meme |
|
}; |
|
|
|
class BotAllHostagesGoneMeme: public BotMeme |
|
{ |
|
public: |
|
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme |
|
}; |
|
|
|
class BotHostageBeingTakenMeme: public BotMeme |
|
{ |
|
public: |
|
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme |
|
}; |
|
|
|
class BotHelpMeme: public BotMeme |
|
{ |
|
public: |
|
BotHelpMeme(Place place = UNDEFINED_PLACE) |
|
{ |
|
m_place = place; |
|
} |
|
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme |
|
|
|
private: |
|
Place m_place; |
|
}; |
|
|
|
class BotBombsiteStatusMeme: public BotMeme |
|
{ |
|
public: |
|
enum StatusType { CLEAR, PLANTED }; |
|
|
|
BotBombsiteStatusMeme(int zoneIndex, StatusType status) |
|
{ |
|
m_zoneIndex = zoneIndex; |
|
m_status = status; |
|
} |
|
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme |
|
|
|
private: |
|
int m_zoneIndex; // the bombsite |
|
StatusType m_status; // whether it is cleared or the bomb is there (planted) |
|
}; |
|
|
|
class BotBombStatusMeme: public BotMeme |
|
{ |
|
public: |
|
BotBombStatusMeme(CSGameState::BombState state, const Vector &pos) |
|
{ |
|
m_state = state; |
|
m_pos = pos; |
|
} |
|
|
|
public: |
|
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme |
|
|
|
private: |
|
CSGameState::BombState m_state; |
|
Vector m_pos; |
|
}; |
|
|
|
class BotFollowMeme: public BotMeme |
|
{ |
|
public: |
|
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme |
|
}; |
|
|
|
class BotDefendHereMeme: public BotMeme |
|
{ |
|
public: |
|
BotDefendHereMeme(const Vector &pos) |
|
{ |
|
m_pos = pos; |
|
} |
|
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme |
|
|
|
private: |
|
Vector m_pos; |
|
}; |
|
|
|
class BotWhereBombMeme: public BotMeme |
|
{ |
|
public: |
|
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme |
|
}; |
|
|
|
class BotRequestReportMeme: public BotMeme |
|
{ |
|
public: |
|
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme |
|
}; |
|
|
|
enum BotStatementType |
|
{ |
|
REPORT_VISIBLE_ENEMIES, |
|
REPORT_ENEMY_ACTION, |
|
REPORT_MY_CURRENT_TASK, |
|
REPORT_MY_INTENTION, |
|
REPORT_CRITICAL_EVENT, |
|
REPORT_REQUEST_HELP, |
|
REPORT_REQUEST_INFORMATION, |
|
REPORT_ROUND_END, |
|
REPORT_MY_PLAN, |
|
REPORT_INFORMATION, |
|
REPORT_EMOTE, |
|
REPORT_ACKNOWLEDGE, // affirmative or negative |
|
REPORT_ENEMIES_REMAINING, |
|
REPORT_FRIENDLY_FIRE, |
|
REPORT_KILLED_FRIEND, |
|
//REPORT_ENEMY_LOST |
|
|
|
NUM_BOT_STATEMENT_TYPES, |
|
}; |
|
|
|
// BotSpeakables are the smallest unit of bot chatter. |
|
// They represent a specific wav file of a phrase, and the criteria for which it is useful |
|
class BotSpeakable |
|
{ |
|
public: |
|
BotSpeakable(); |
|
~BotSpeakable(); |
|
|
|
char *m_phrase; |
|
float m_duration; |
|
PlaceCriteria m_place; |
|
CountCriteria m_count; |
|
}; |
|
|
|
typedef CUtlVector<BotSpeakable *> BotSpeakableVector; |
|
typedef CUtlVector<BotSpeakableVector *> BotVoiceBankVector; |
|
|
|
// The BotPhrase class is a collection of Speakables associated with a name, ID, and criteria |
|
class BotPhrase |
|
{ |
|
public: |
|
char *GetSpeakable(int bankIndex, float *duration = NULL) const; // return a random speakable and its duration in seconds that meets the current criteria |
|
|
|
// NOTE: Criteria must be set just before the GetSpeakable() call, since they are shared among all bots |
|
void ClearCriteria() const; |
|
void SetPlaceCriteria(PlaceCriteria place) const; // all returned phrases must have this place criteria |
|
void SetCountCriteria(CountCriteria count) const; // all returned phrases must have this count criteria |
|
|
|
const char *GetName() const { return m_name; } |
|
Place GetID() const { return m_id; } |
|
GameEventType GetRadioEquivalent() const { return m_radioEvent; } |
|
bool IsImportant() const { return m_isImportant; } // return true if this phrase is part of an important statement |
|
|
|
bool IsPlace() const { return m_isPlace; } |
|
void Randomize(); // randomly shuffle the speakable order |
|
|
|
private: |
|
friend class BotPhraseManager; |
|
BotPhrase(unsigned int id, bool isPlace); |
|
~BotPhrase(); |
|
|
|
char *m_name; |
|
Place m_id; |
|
bool m_isPlace; // true if this is a Place phrase |
|
GameEventType m_radioEvent; |
|
bool m_isImportant; // mission-critical statement |
|
|
|
mutable BotVoiceBankVector m_voiceBank; // array of voice banks (arrays of speakables) |
|
CUtlVector<int> m_count; // number of speakables |
|
mutable CUtlVector< int > m_index; // index of next speakable to return |
|
int m_numVoiceBanks; // number of voice banks that have been initialized |
|
void InitVoiceBank(int bankIndex); // sets up the vector of voice banks for the first bankIndex voice banks |
|
|
|
mutable PlaceCriteria m_placeCriteria; |
|
mutable CountCriteria m_countCriteria; |
|
}; |
|
|
|
typedef CUtlLinkedList<BotPhrase *, int> BotPhraseList; |
|
|
|
inline void BotPhrase::ClearCriteria() const |
|
{ |
|
m_placeCriteria = ANY_PLACE; |
|
m_countCriteria = UNDEFINED_COUNT; |
|
} |
|
|
|
inline void BotPhrase::SetPlaceCriteria(PlaceCriteria place) const |
|
{ |
|
m_placeCriteria = place; |
|
} |
|
|
|
inline void BotPhrase::SetCountCriteria(CountCriteria count) const |
|
{ |
|
m_countCriteria = count; |
|
} |
|
|
|
// The BotPhraseManager is a singleton that provides an interface to all BotPhrase collections |
|
class BotPhraseManager |
|
{ |
|
public: |
|
BotPhraseManager(); |
|
~BotPhraseManager(); |
|
|
|
// initialize phrase system from database file for a specific voice bank (0 is the default voice bank) |
|
bool Initialize(const char *filename, int bankIndex); |
|
|
|
// invoked when round resets |
|
void OnRoundRestart(); |
|
|
|
// invoked when map changes |
|
void OnMapChange(); |
|
Place NameToID(const char *name) const; |
|
const char *IDToName(Place id) const; |
|
|
|
// given a name, return the associated phrase collection |
|
const BotPhrase *GetPhrase(const char *name) const; |
|
|
|
// given a name, return the associated Place phrase collection |
|
const BotPhrase *GetPlace(const char *name) const; |
|
|
|
// given an id, return the associated Place phrase collection |
|
const BotPhrase *GetPlace(PlaceCriteria place) const; |
|
|
|
const BotPhraseList *GetPlaceList() const { return &m_placeList; } |
|
|
|
// return time last statement of given type was emitted by a teammate for the given place |
|
float GetPlaceStatementInterval(Place place) const; |
|
|
|
// set time of last statement of given type was emitted by a teammate for the given place |
|
void ResetPlaceStatementInterval(Place place) const; |
|
|
|
private: |
|
int FindPlaceIndex(Place where) const; |
|
|
|
// master list of all phrase collections |
|
BotPhraseList m_list; |
|
|
|
// master list of all Place phrases |
|
BotPhraseList m_placeList; |
|
|
|
struct PlaceTimeInfo |
|
{ |
|
Place placeID; |
|
IntervalTimer timer; |
|
}; |
|
|
|
mutable PlaceTimeInfo m_placeStatementHistory[ MAX_PLACES_PER_MAP ]; |
|
mutable int m_placeCount; |
|
}; |
|
|
|
inline int BotPhraseManager::FindPlaceIndex(Place where) const |
|
{ |
|
for (int i = 0; i < m_placeCount; ++i) |
|
{ |
|
if (m_placeStatementHistory[i].placeID == where) |
|
return i; |
|
} |
|
|
|
if (m_placeCount < MAX_PLACES_PER_MAP) |
|
{ |
|
m_placeStatementHistory[++m_placeCount].placeID = where; |
|
m_placeStatementHistory[++m_placeCount].timer.Invalidate(); |
|
return m_placeCount - 1; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
inline float BotPhraseManager::GetPlaceStatementInterval(Place place) const |
|
{ |
|
int index = FindPlaceIndex(place); |
|
|
|
if (index < 0) |
|
return 999999.9f; |
|
|
|
if (index >= m_placeCount) |
|
return 999999.9f; |
|
|
|
return m_placeStatementHistory[ index ].timer.GetElapsedTime(); |
|
} |
|
|
|
inline void BotPhraseManager::ResetPlaceStatementInterval(Place place) const |
|
{ |
|
int index = FindPlaceIndex(place); |
|
|
|
if (index < 0) |
|
return; |
|
|
|
if (index >= m_placeCount) |
|
return; |
|
|
|
m_placeStatementHistory[index].timer.Reset(); |
|
} |
|
|
|
// Statements are meaningful collections of phrases |
|
class BotStatement |
|
{ |
|
public: |
|
BotStatement(BotChatterInterface *chatter, BotStatementType type, float expireDuration); |
|
~BotStatement(); |
|
|
|
public: |
|
BotChatterInterface *GetChatter() const { return m_chatter; } |
|
CCSBot *GetOwner() const; |
|
|
|
BotStatementType GetType() const { return m_type; } // return the type of statement this is |
|
bool IsImportant() const; // return true if this statement is "important" and not personality chatter |
|
|
|
bool HasSubject() const { return (m_subject != UNDEFINED_SUBJECT); } |
|
void SetSubject(int playerID) { m_subject = playerID; } // who this statement is about |
|
int GetSubject() const { return m_subject; } // who this statement is about |
|
|
|
bool HasPlace() const { return (GetPlace()) ? true : false; } |
|
Place GetPlace() const; // if this statement refers to a specific place, return that place |
|
void SetPlace(Place where) { m_place = where; } // explicitly set place |
|
|
|
bool HasCount() const; // return true if this statement has an associated count |
|
|
|
bool IsRedundant(const BotStatement *say) const; // return true if this statement is the same as the given one |
|
bool IsObsolete() const; // return true if this statement is no longer appropriate to say |
|
void Convert(const BotStatement *say); // possibly change what were going to say base on what teammate is saying |
|
|
|
void AppendPhrase(const BotPhrase *phrase); |
|
|
|
void SetStartTime(float timestamp) { m_startTime = timestamp; } // define the earliest time this statement can be spoken |
|
float GetStartTime() const { return m_startTime; } |
|
|
|
enum ConditionType |
|
{ |
|
IS_IN_COMBAT, |
|
RADIO_SILENCE, |
|
ENEMIES_REMAINING, |
|
NUM_CONDITIONS, |
|
}; |
|
|
|
void AddCondition(ConditionType condition); // conditions must be true for the statement to be spoken |
|
bool IsValid() const; // verify all attached conditions |
|
|
|
enum ContextType |
|
{ |
|
CURRENT_ENEMY_COUNT, |
|
REMAINING_ENEMY_COUNT, |
|
SHORT_DELAY, |
|
LONG_DELAY, |
|
ACCUMULATE_ENEMIES_DELAY, |
|
}; |
|
void AppendPhrase(ContextType contextPhrase); // special phrases that depend on the context |
|
|
|
bool Update(); // emit statement over time, return false if statement is done |
|
bool IsSpeaking() const { return m_isSpeaking; } // return true if this statement is currently being spoken |
|
float GetTimestamp() const { return m_timestamp; } // get time statement was created (but not necessarily started talking) |
|
|
|
void AttachMeme(BotMeme *meme); // attach a meme to this statement, to be transmitted to other friendly bots when spoken |
|
|
|
public: |
|
friend class BotChatterInterface; |
|
|
|
BotChatterInterface *m_chatter; // the chatter system this statement is part of |
|
|
|
BotStatement *m_next, *m_prev; // linked list hooks |
|
|
|
BotStatementType m_type; // what kind of statement this is |
|
int m_subject; // who this subject is about |
|
Place m_place; // explicit place - note some phrases have implicit places as well |
|
BotMeme *m_meme; // a statement can only have a single meme for now |
|
|
|
float m_timestamp; // time when message was created |
|
float m_startTime; // the earliest time this statement can be spoken |
|
float m_expireTime; // time when this statement is no longer valid |
|
float m_speakTimestamp; // time when message began being spoken |
|
bool m_isSpeaking; // true if this statement is current being spoken |
|
|
|
float m_nextTime; // time for next phrase to begin |
|
|
|
enum { MAX_BOT_PHRASES = 4 }; |
|
struct |
|
{ |
|
bool isPhrase; |
|
union |
|
{ |
|
const BotPhrase *phrase; |
|
ContextType context; |
|
}; |
|
|
|
} |
|
m_statement[ MAX_BOT_PHRASES ]; |
|
|
|
enum { MAX_BOT_CONDITIONS = 4 }; |
|
ConditionType m_condition[ MAX_BOT_CONDITIONS ]; // conditions that must be true for the statement to be said |
|
int m_conditionCount; |
|
|
|
int m_index; // m_index refers to the phrase currently being spoken, or -1 if we havent started yet |
|
int m_count; |
|
}; |
|
|
|
// This class defines the interface to the bot radio chatter system |
|
class BotChatterInterface |
|
{ |
|
public: |
|
BotChatterInterface() {}; |
|
|
|
BotChatterInterface(CCSBot *me); |
|
~BotChatterInterface(); |
|
|
|
void Reset(); // reset to initial state |
|
void Update(); // process ongoing chatter |
|
|
|
void OnEvent(GameEventType event, CBaseEntity *entity, CBaseEntity *other); // invoked when event occurs in the game (some events have NULL entities) |
|
void OnDeath(); // invoked when we die |
|
|
|
enum VerbosityType |
|
{ |
|
NORMAL, // full chatter |
|
MINIMAL, // only scenario-critical events |
|
RADIO, // use the standard radio instead |
|
OFF // no chatter at all |
|
}; |
|
VerbosityType GetVerbosity() const; // return our current level of verbosity |
|
|
|
CCSBot *GetOwner() const { return m_me; } |
|
bool IsTalking() const; // return true if we are currently talking |
|
float GetRadioSilenceDuration(); // return time since any teammate said anything |
|
void ResetRadioSilenceDuration(); |
|
|
|
enum { MUST_ADD = 1 }; |
|
void AddStatement(BotStatement *statement, bool mustAdd = false); // register a statement for speaking |
|
void RemoveStatement(BotStatement *statement); // remove a statement |
|
|
|
BotStatement *GetActiveStatement(); // returns the statement that is being spoken, or is next to be spoken if no-one is speaking now |
|
BotStatement *GetStatement() const; // returns our current statement, or NULL if we aren't speaking |
|
|
|
int GetPitch() const { return m_pitch; } |
|
|
|
// things the bots can say |
|
void Say(const char *phraseName, float lifetime = 3.0f, float delay = 0.0f); |
|
|
|
void AnnouncePlan(const char *phraseName, Place place); |
|
void Affirmative(); |
|
void Negative(); |
|
|
|
void EnemySpotted(); // report enemy sightings |
|
void KilledMyEnemy(int victimID); |
|
void EnemiesRemaining(); |
|
|
|
NOXREF void Clear(Place place); |
|
|
|
void ReportIn(); // ask for current situation |
|
void ReportingIn(); // report current situation |
|
|
|
bool NeedBackup(); |
|
void PinnedDown(); |
|
void Scared(); |
|
void HeardNoise(const Vector *pos); |
|
|
|
void TheyPickedUpTheBomb(); |
|
void GoingToPlantTheBomb(Place place); |
|
void BombsiteClear(int zoneIndex); |
|
void FoundPlantedBomb(int zoneIndex); |
|
void PlantingTheBomb(Place place); |
|
void SpottedBomber(CBasePlayer *bomber); |
|
void SpottedLooseBomb(CBaseEntity *bomb); |
|
NOXREF void GuardingLooseBomb(CBaseEntity *bomb); |
|
void RequestBombLocation(); |
|
|
|
#define IS_PLAN true |
|
void GuardingHostages(Place place, bool isPlan = false); |
|
void GuardingHostageEscapeZone(bool isPlan = false); |
|
void HostagesBeingTaken(); |
|
void HostagesTaken(); |
|
void TalkingToHostages(); |
|
void EscortingHostages(); |
|
NOXREF void HostageDown(); |
|
|
|
void CelebrateWin(); |
|
|
|
void Encourage(const char *phraseName, float repeatInterval = 10.0f, float lifetime = 3.0f); // "encourage" the player to do the scenario |
|
|
|
void KilledFriend(); |
|
void FriendlyFire(); |
|
|
|
bool SeesAtLeastOneEnemy() const { return m_seeAtLeastOneEnemy; } |
|
|
|
private: |
|
BotStatement *m_statementList; // list of all active/pending messages for this bot |
|
void ReportEnemies(); // track nearby enemy count and generate enemy activity statements |
|
bool ShouldSpeak() const; // return true if we speaking makes sense now |
|
|
|
CCSBot *m_me; // the bot this chatter is for |
|
|
|
bool m_seeAtLeastOneEnemy; |
|
float m_timeWhenSawFirstEnemy; |
|
bool m_reportedEnemies; |
|
bool m_requestedBombLocation; // true if we already asked where the bomb has been planted |
|
|
|
int m_pitch; |
|
|
|
static IntervalTimer m_radioSilenceInterval[2]; // one timer for each team |
|
|
|
IntervalTimer m_needBackupInterval; |
|
IntervalTimer m_spottedBomberInterval; |
|
IntervalTimer m_scaredInterval; |
|
IntervalTimer m_planInterval; |
|
CountdownTimer m_spottedLooseBombTimer; |
|
CountdownTimer m_heardNoiseTimer; |
|
CountdownTimer m_escortingHostageTimer; |
|
static CountdownTimer m_encourageTimer; // timer to know when we can "encourage" the human player again - shared by all bots |
|
}; |
|
|
|
inline BotChatterInterface::VerbosityType BotChatterInterface::GetVerbosity() const |
|
{ |
|
const char *string = cv_bot_chatter.string; |
|
|
|
if (string == NULL) |
|
return NORMAL; |
|
|
|
if (string[0] == 'm' || string[0] == 'M') |
|
return MINIMAL; |
|
|
|
if (string[0] == 'r' || string[0] == 'R') |
|
return RADIO; |
|
|
|
if (string[0] == 'o' || string[0] == 'O') |
|
return OFF; |
|
|
|
return NORMAL; |
|
} |
|
|
|
inline bool BotChatterInterface::IsTalking() const |
|
{ |
|
if (m_statementList != NULL) |
|
{ |
|
return m_statementList->IsSpeaking(); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
inline BotStatement *BotChatterInterface::GetStatement() const |
|
{ |
|
return m_statementList; |
|
} |
|
|
|
extern BotPhraseManager *TheBotPhrases; |
|
|
|
inline void BotChatterInterface::Say(const char *phraseName, float lifetime, float delay) |
|
{ |
|
BotStatement *say = new BotStatement(this, REPORT_MY_INTENTION, lifetime); |
|
|
|
say->AppendPhrase(TheBotPhrases->GetPhrase(phraseName)); |
|
|
|
if (delay > 0.0f) |
|
say->SetStartTime(gpGlobals->time + delay); |
|
|
|
AddStatement(say); |
|
} |
|
|
|
const Vector *GetRandomSpotAtPlace(Place place); |
|
|
|
#endif // CS_BOT_CHATTER_H
|
|
|