/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code 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. Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_ai_chat.c * * desc: bot chat AI * * $Archive: /MissionPack/code/botlib/be_ai_chat.c $ * *****************************************************************************/ #include "../qcommon/q_shared.h" #include "l_memory.h" #include "l_libvar.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "l_utils.h" #include "l_log.h" #include "aasfile.h" #include "botlib.h" #include "be_aas.h" #include "be_aas_funcs.h" #include "be_interface.h" #include "be_ea.h" #include "be_ai_chat.h" //escape character #define ESCAPE_CHAR 0x01 //'_' // // "hi ", people, " ", 0, " entered the game" //becomes: // "hi _rpeople_ _v0_ entered the game" // //match piece types #define MT_VARIABLE 1 //variable match piece #define MT_STRING 2 //string match piece //reply chat key flags #define RCKFL_AND 1 //key must be present #define RCKFL_NOT 2 //key must be absent #define RCKFL_NAME 4 //name of bot must be present #define RCKFL_STRING 8 //key is a string #define RCKFL_VARIABLES 16 //key is a match template #define RCKFL_BOTNAMES 32 //key is a series of botnames #define RCKFL_GENDERFEMALE 64 //bot must be female #define RCKFL_GENDERMALE 128 //bot must be male #define RCKFL_GENDERLESS 256 //bot must be genderless //time to ignore a chat message after using it #define CHATMESSAGE_RECENTTIME 20 //the actuall chat messages typedef struct bot_chatmessage_s { char *chatmessage; //chat message string float time; //last time used struct bot_chatmessage_s *next; //next chat message in a list } bot_chatmessage_t; //bot chat type with chat lines typedef struct bot_chattype_s { char name[MAX_CHATTYPE_NAME]; int numchatmessages; bot_chatmessage_t *firstchatmessage; struct bot_chattype_s *next; } bot_chattype_t; //bot chat lines typedef struct bot_chat_s { bot_chattype_t *types; } bot_chat_t; //random string typedef struct bot_randomstring_s { char *string; struct bot_randomstring_s *next; } bot_randomstring_t; //list with random strings typedef struct bot_randomlist_s { char *string; int numstrings; bot_randomstring_t *firstrandomstring; struct bot_randomlist_s *next; } bot_randomlist_t; //synonym typedef struct bot_synonym_s { char *string; float weight; struct bot_synonym_s *next; } bot_synonym_t; //list with synonyms typedef struct bot_synonymlist_s { unsigned long int context; float totalweight; bot_synonym_t *firstsynonym; struct bot_synonymlist_s *next; } bot_synonymlist_t; //fixed match string typedef struct bot_matchstring_s { char *string; struct bot_matchstring_s *next; } bot_matchstring_t; //piece of a match template typedef struct bot_matchpiece_s { int type; bot_matchstring_t *firststring; int variable; struct bot_matchpiece_s *next; } bot_matchpiece_t; //match template typedef struct bot_matchtemplate_s { unsigned long int context; int type; int subtype; bot_matchpiece_t *first; struct bot_matchtemplate_s *next; } bot_matchtemplate_t; //reply chat key typedef struct bot_replychatkey_s { int flags; char *string; bot_matchpiece_t *match; struct bot_replychatkey_s *next; } bot_replychatkey_t; //reply chat typedef struct bot_replychat_s { bot_replychatkey_t *keys; float priority; int numchatmessages; bot_chatmessage_t *firstchatmessage; struct bot_replychat_s *next; } bot_replychat_t; //string list typedef struct bot_stringlist_s { char *string; struct bot_stringlist_s *next; } bot_stringlist_t; //chat state of a bot typedef struct bot_chatstate_s { int gender; //0=it, 1=female, 2=male int client; //client number char name[32]; //name of the bot char chatmessage[MAX_MESSAGE_SIZE]; int handle; //the console messages visible to the bot bot_consolemessage_t *firstmessage; //first message is the first typed message bot_consolemessage_t *lastmessage; //last message is the last typed message, bottom of console //number of console messages stored in the state int numconsolemessages; //the bot chat lines bot_chat_t *chat; } bot_chatstate_t; typedef struct { bot_chat_t *chat; char filename[MAX_QPATH]; char chatname[MAX_QPATH]; } bot_ichatdata_t; static bot_ichatdata_t *ichatdata[MAX_CLIENTS]; static bot_chatstate_t *botchatstates[MAX_CLIENTS+1]; //console message heap static bot_consolemessage_t *consolemessageheap = NULL; static bot_consolemessage_t *freeconsolemessages = NULL; //list with match strings static bot_matchtemplate_t *matchtemplates = NULL; //list with synonyms static bot_synonymlist_t *synonyms = NULL; //list with random strings static bot_randomlist_t *randomstrings = NULL; //reply chats static bot_replychat_t *replychats = NULL; //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== static bot_chatstate_t *BotChatStateFromHandle(int handle) { if (handle <= 0 || handle > MAX_CLIENTS) { botimport.Print(PRT_FATAL, "chat state handle %d out of range\n", handle); return NULL; } //end if if (!botchatstates[handle]) { botimport.Print(PRT_FATAL, "invalid chat state %d\n", handle); return NULL; } //end if return botchatstates[handle]; } //end of the function BotChatStateFromHandle //=========================================================================== // initialize the heap with unused console messages // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void InitConsoleMessageHeap(void) { int i, max_messages; if (consolemessageheap) FreeMemory(consolemessageheap); // max_messages = (int) LibVarValue("max_messages", "1024"); consolemessageheap = (bot_consolemessage_t *) GetClearedHunkMemory(max_messages * sizeof(bot_consolemessage_t)); consolemessageheap[0].prev = NULL; consolemessageheap[0].next = &consolemessageheap[1]; for (i = 1; i < max_messages-1; i++) { consolemessageheap[i].prev = &consolemessageheap[i - 1]; consolemessageheap[i].next = &consolemessageheap[i + 1]; } //end for consolemessageheap[max_messages-1].prev = &consolemessageheap[max_messages-2]; consolemessageheap[max_messages-1].next = NULL; //pointer to the free console messages freeconsolemessages = consolemessageheap; } //end of the function InitConsoleMessageHeap //=========================================================================== // allocate one console message from the heap // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static bot_consolemessage_t *AllocConsoleMessage(void) { bot_consolemessage_t *message; message = freeconsolemessages; if (freeconsolemessages) freeconsolemessages = freeconsolemessages->next; if (freeconsolemessages) freeconsolemessages->prev = NULL; return message; } //end of the function AllocConsoleMessage //=========================================================================== // deallocate one console message from the heap // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void FreeConsoleMessage(bot_consolemessage_t *message) { if (freeconsolemessages) freeconsolemessages->prev = message; message->prev = NULL; message->next = freeconsolemessages; freeconsolemessages = message; } //end of the function FreeConsoleMessage //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotRemoveConsoleMessage(int chatstate, int handle) { bot_consolemessage_t *m, *nextm; bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return; for (m = cs->firstmessage; m; m = nextm) { nextm = m->next; if (m->handle == handle) { if (m->next) m->next->prev = m->prev; else cs->lastmessage = m->prev; if (m->prev) m->prev->next = m->next; else cs->firstmessage = m->next; FreeConsoleMessage(m); cs->numconsolemessages--; break; } //end if } //end for } //end of the function BotRemoveConsoleMessage //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotQueueConsoleMessage(int chatstate, int type, const char *message) { bot_consolemessage_t *m; bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return; m = AllocConsoleMessage(); if (!m) { botimport.Print(PRT_ERROR, "empty console message heap\n"); return; } //end if cs->handle++; if (cs->handle <= 0 || cs->handle > 8192) cs->handle = 1; m->handle = cs->handle; m->time = AAS_Time(); m->type = type; Q_strncpyz( m->message, message, sizeof( m->message) ); m->next = NULL; if (cs->lastmessage) { cs->lastmessage->next = m; m->prev = cs->lastmessage; cs->lastmessage = m; } //end if else { cs->lastmessage = m; cs->firstmessage = m; m->prev = NULL; } //end if cs->numconsolemessages++; } //end of the function BotQueueConsoleMessage //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm) { bot_chatstate_t *cs; bot_consolemessage_t *firstmsg; cs = BotChatStateFromHandle(chatstate); if (!cs) return 0; if ( (firstmsg = cs->firstmessage) != NULL ) { cm->handle = firstmsg->handle; cm->time = firstmsg->time; cm->type = firstmsg->type; Q_strncpyz(cm->message, firstmsg->message, sizeof(cm->message)); /* We omit setting the two pointers in cm because pointer * size in the VM differs between the size in the engine on * 64 bit machines, which would lead to a buffer overflow if * this functions is called from the VM. The pointers are * of no interest to functions calling * BotNextConsoleMessage anyways. */ return cm->handle; } //end if return 0; } //end of the function BotConsoleMessage //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotNumConsoleMessages(int chatstate) { bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return 0; return cs->numconsolemessages; } //end of the function BotNumConsoleMessages //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static int IsWhiteSpace(char c) { if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '(' || c == ')' || c == '?' || c == ':' || c == '\''|| c == '/' || c == ',' || c == '.' || c == '[' || c == ']' || c == '-' || c == '_' || c == '+' || c == '=') return qfalse; return qtrue; } //end of the function IsWhiteSpace //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void BotRemoveTildes(char *message) { int i; //remove all tildes from the chat message for (i = 0; message[i]; i++) { if (message[i] == '~') { memmove(&message[i], &message[i+1], strlen(&message[i+1])+1); } //end if } //end for } //end of the function BotRemoveTildes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void UnifyWhiteSpaces(char *string) { char *ptr, *oldptr; for (ptr = oldptr = string; *ptr; oldptr = ptr) { while(*ptr && IsWhiteSpace(*ptr)) ptr++; if (ptr > oldptr) { //if not at the start and not at the end of the string //write only one space if (oldptr > string && *ptr) *oldptr++ = ' '; //remove all other white spaces if (ptr > oldptr) memmove(oldptr, ptr, strlen(ptr)+1); } //end if while(*ptr && !IsWhiteSpace(*ptr)) ptr++; } //end while } //end of the function UnifyWhiteSpaces //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int StringContains( const char *str1, const char *str2, int casesensitive ) { int len, i, j, index; if (str1 == NULL || str2 == NULL) return -1; len = strlen(str1) - strlen(str2); index = 0; for (i = 0; i <= len; i++, str1++, index++) { for (j = 0; str2[j]; j++) { if (casesensitive) { if (str1[j] != str2[j]) break; } //end if else { if (locase[(byte)str1[j]] != locase[(byte)str2[j]]) break; } //end else } //end for if (!str2[j]) return index; } //end for return -1; } //end of the function StringContains //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static const char *StringContainsWord( const char *str1, const char *str2 ) { int len, i, j; len = strlen( str1 ) - strlen( str2 ); for ( i = 0; i <= len; i++, str1++ ) { //if not at the start of the string if ( i ) { //skip to the start of the next word while ( *str1 != '\0' && *str1 != ' ' && *str1 != '.' && *str1 != ',' && *str1 != '!' ) str1++; if ( *str1 == '\0' ) break; str1++; } //compare the word for ( j = 0; str2[j] != '\0'; j++ ) { if ( locase[(byte) str1[j]] != locase[(byte) str2[j]] ) break; } //if there was a word match if ( str2[j] == '\0' ) { //if the first string has an end of word if ( str1[j] == '\0' || str1[j] == ' ' || str1[j] == '.' || str1[j] == ',' || str1[j] == '!' ) return str1; } } return NULL; } //end of the function StringContainsWord //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void StringReplaceWords( char *string, int size, const char *synonym, const char *replacement ) { char *str; const char *str2, *endp; int replen, synlen; synlen = (int) strlen( synonym ); replen = (int) strlen( replacement ); endp = string + size; //find the synonym in the string str = (char *) StringContainsWord( string, synonym ); //if the synonym occurred in the string while ( str && str + replen < endp ) { //if the synonym isn't part of the replacement which is already in the string //useful for abbreviations str2 = StringContainsWord( string, replacement ); while ( str2 ) { if ( str2 <= str && str < str2 + replen ) break; str2 = StringContainsWord( str2 + 1, replacement ); } if ( !str2 ) { memmove( str + replen, str + synlen, strlen( str + synlen ) + 1 ); //append the synonym replacement Com_Memcpy( str, replacement, replen ); } //find the next synonym in the string str = (char *) StringContainsWord( str + replen, synonym ); } //end if } //end of the function StringReplaceWords #if 0 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void BotDumpSynonymList(bot_synonymlist_t *synlist) { FILE *fp; bot_synonymlist_t *syn; bot_synonym_t *synonym; fp = Log_FilePointer(); if (!fp) return; for (syn = synlist; syn; syn = syn->next) { fprintf(fp, "%ld : [", syn->context); for (synonym = syn->firstsynonym; synonym; synonym = synonym->next) { fprintf(fp, "(\"%s\", %1.2f)", synonym->string, synonym->weight); if (synonym->next) fprintf(fp, ", "); } //end for fprintf(fp, "]\n"); } //end for } //end of the function BotDumpSynonymList #endif //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static bot_synonymlist_t *BotLoadSynonyms( const char *filename ) { int pass, size, contextlevel, numsynonyms; unsigned long int context, contextstack[32]; char *ptr = NULL; source_t *source; token_t token; bot_synonymlist_t *synlist, *lastsyn, *syn; bot_synonym_t *synonym, *lastsynonym; size = 0; synlist = NULL; //make compiler happy syn = NULL; //make compiler happy synonym = NULL; //make compiler happy //the synonyms are parsed in two phases for (pass = 0; pass < 2; pass++) { // if (pass && size) ptr = (char *) GetClearedHunkMemory(size); // PC_SetBaseFolder(BOTFILESBASEFOLDER); source = LoadSourceFile(filename); if (!source) { botimport.Print(PRT_ERROR, "couldn't load %s\n", filename); return NULL; } //end if // context = 0; contextlevel = 0; synlist = NULL; //list synonyms lastsyn = NULL; //last synonym in the list // while(PC_ReadToken(source, &token)) { if (token.type == TT_NUMBER) { context |= token.intvalue; contextstack[contextlevel] = token.intvalue; contextlevel++; if (contextlevel >= 32) { SourceError(source, "more than 32 context levels"); FreeSource(source); return NULL; } //end if if (!PC_ExpectTokenString(source, "{")) { FreeSource(source); return NULL; } //end if } //end if else if (token.type == TT_PUNCTUATION) { if (!strcmp(token.string, "}")) { contextlevel--; if (contextlevel < 0) { SourceError(source, "too many }"); FreeSource(source); return NULL; } //end if context &= ~contextstack[contextlevel]; } //end if else if (!strcmp(token.string, "[")) { size += sizeof(bot_synonymlist_t); if (pass && ptr) { syn = (bot_synonymlist_t *) ptr; ptr += sizeof(bot_synonymlist_t); syn->context = context; syn->firstsynonym = NULL; syn->next = NULL; if (lastsyn) lastsyn->next = syn; else synlist = syn; lastsyn = syn; } //end if numsynonyms = 0; lastsynonym = NULL; while(1) { size_t len; if (!PC_ExpectTokenString(source, "(") || !PC_ExpectTokenType(source, TT_STRING, 0, &token)) { FreeSource(source); return NULL; } //end if StripDoubleQuotes(token.string); len = (int)strlen(token.string); if (len==0) { SourceError(source, "empty string"); FreeSource(source); return NULL; } //end if len = PAD(len+1, sizeof(long)); size += sizeof(bot_synonym_t) + len; if (pass && ptr) { synonym = (bot_synonym_t *) ptr; ptr += sizeof(bot_synonym_t); synonym->string = ptr; ptr += len; strcpy(synonym->string, token.string); // if (lastsynonym) lastsynonym->next = synonym; else syn->firstsynonym = synonym; lastsynonym = synonym; } //end if numsynonyms++; if (!PC_ExpectTokenString(source, ",") || !PC_ExpectTokenType(source, TT_NUMBER, 0, &token) || !PC_ExpectTokenString(source, ")")) { FreeSource(source); return NULL; } //end if if (pass && ptr) { synonym->weight = token.floatvalue; syn->totalweight += synonym->weight; } //end if if (PC_CheckTokenString(source, "]")) break; if (!PC_ExpectTokenString(source, ",")) { FreeSource(source); return NULL; } //end if } //end while if (numsynonyms < 2) { SourceError(source, "synonym must have at least two entries"); FreeSource(source); return NULL; } //end if } //end else else { SourceError(source, "unexpected %s", token.string); FreeSource(source); return NULL; } //end if } //end else if } //end while // FreeSource(source); // if (contextlevel > 0) { SourceError(source, "missing }"); return NULL; } //end if } //end for botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); // //BotDumpSynonymList(synlist); // return synlist; } //end of the function BotLoadSynonyms //=========================================================================== // replace all the synonyms in the string // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotReplaceSynonyms( char *string, int size, unsigned long int context ) { const bot_synonymlist_t *syn; const bot_synonym_t *synonym; for ( syn = synonyms; syn; syn = syn->next ) { if ( (syn->context & context) == 0 ) continue; for ( synonym = syn->firstsynonym->next; synonym; synonym = synonym->next ) { StringReplaceWords( string, size, synonym->string, syn->firstsynonym->string ); } //end for } //end for } //end of the function BotReplaceSynonyms //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void BotReplaceWeightedSynonyms( char *string, int size, unsigned long int context ) { bot_synonymlist_t *syn; bot_synonym_t *synonym, *replacement; float weight, curweight; for ( syn = synonyms; syn; syn = syn->next ) { if ( ( syn->context & context ) == 0 ) continue; //choose a weighted random replacement synonym weight = random() * syn->totalweight; if ( !weight ) continue; curweight = 0; for ( replacement = syn->firstsynonym; replacement; replacement = replacement->next ) { curweight += replacement->weight; if ( weight < curweight ) break; } if ( !replacement ) continue; //replace all synonyms with the replacement for ( synonym = syn->firstsynonym; synonym; synonym = synonym->next ) { if ( synonym == replacement ) continue; StringReplaceWords( string, size, synonym->string, replacement->string ); } //end for } //end for } //end of the function BotReplaceWeightedSynonyms //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void BotReplaceReplySynonyms( char *string, int size, unsigned long int context ) { char *str1, *replacement; const char *str2, *endp; bot_synonymlist_t *syn; bot_synonym_t *synonym; int replen; endp = string + size; for ( str1 = string; *str1 != '\0'; ) { //go to the start of the next word while ( *str1 && *str1 <= ' ' ) str1++; if ( *str1 == '\0' ) break; for ( syn = synonyms; syn; syn = syn->next ) { if ( ( syn->context & context ) == 0 ) continue; for ( synonym = syn->firstsynonym->next; synonym; synonym = synonym->next ) { //if the synonym is not at the front of the string continue str2 = StringContainsWord( str1, synonym->string ); if ( !str2 || str2 != str1 ) continue; replacement = syn->firstsynonym->string; replen = strlen( replacement ); //if the replacement IS in front of the string continue str2 = StringContainsWord( str1, replacement ); if ( str2 && str2 == str1 ) continue; if ( str1 + replen >= endp ) continue; memmove( str1 + replen, str1 + strlen( synonym->string ), strlen( str1 + strlen( synonym->string ) ) + 1 ); //append the synonym replacement Com_Memcpy( str1, replacement, replen ); break; } //if a synonym has been replaced if ( synonym ) break; } //skip over this word while( /**str1 &&*/ *str1 > ' ' ) str1++; if ( *str1 == '\0' ) break; } //end while } //end of the function BotReplaceReplySynonyms //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static int BotLoadChatMessage( source_t *source, char *chatmessagestring, int size ) { char *ptr; token_t token; int len; ptr = chatmessagestring; *ptr = '\0'; while ( 1 ) { if ( !PC_ExpectAnyToken( source, &token ) ) return qfalse; //fixed string if ( token.type == TT_STRING ) { StripDoubleQuotes( token.string ); len = strlen( ptr ); if ( len + strlen( token.string ) + 1 > size ) { SourceError( source, "chat message too long" ); return qfalse; } strcpy( &ptr[len], token.string ); } //variable string else if ( token.type == TT_NUMBER && ( token.subtype & TT_INTEGER ) ) { char intbuf[32]; int intlen; len = strlen( ptr ); intlen = sprintf( intbuf, "%cv%ld%c", ESCAPE_CHAR, token.intvalue, ESCAPE_CHAR ); if ( len + intlen + 1 > size ) { SourceError( source, "chat message too long" ); return qfalse; } strcpy( &ptr[len], intbuf ); //sprintf( &ptr[len], "%cv%ld%c", ESCAPE_CHAR, token.intvalue, ESCAPE_CHAR ); } //random string else if ( token.type == TT_NAME ) { len = strlen( ptr ); if ( len + strlen( token.string ) + 4 > size ) { SourceError( source, "chat message too long" ); return qfalse; } sprintf( &ptr[len], "%cr%s%c", ESCAPE_CHAR, token.string, ESCAPE_CHAR ); } else { SourceError( source, "unknown message component %s", token.string ); return qfalse; } if ( PC_CheckTokenString( source, ";" ) ) break; if ( !PC_ExpectTokenString( source, "," ) ) return qfalse; } return qtrue; } //end of the function BotLoadChatMessage #if 0 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void BotDumpRandomStringList(bot_randomlist_t *randomlist) { FILE *fp; bot_randomlist_t *random; bot_randomstring_t *rs; fp = Log_FilePointer(); if (!fp) return; for (random = randomlist; random; random = random->next) { fprintf(fp, "%s = {", random->string); for (rs = random->firstrandomstring; rs; rs = rs->next) { fprintf(fp, "\"%s\"", rs->string); if (rs->next) fprintf(fp, ", "); else fprintf(fp, "}\n"); } //end for } //end for } //end of the function BotDumpRandomStringList #endif //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static bot_randomlist_t *BotLoadRandomStrings( const char *filename ) { int pass, size; char *ptr = NULL, chatmessagestring[MAX_MESSAGE_SIZE]; source_t *source; token_t token; bot_randomlist_t *randomlist, *lastrandom, *random; bot_randomstring_t *randomstring; size_t len; #ifdef DEBUG int starttime = Sys_MilliSeconds(); #endif //DEBUG size = 0; randomlist = NULL; random = NULL; //the synonyms are parsed in two phases for (pass = 0; pass < 2; pass++) { // if (pass && size) ptr = (char *) GetClearedHunkMemory(size); // PC_SetBaseFolder(BOTFILESBASEFOLDER); source = LoadSourceFile(filename); if (!source) { botimport.Print(PRT_ERROR, "couldn't load %s\n", filename); return NULL; } //end if // randomlist = NULL; //list lastrandom = NULL; //last // while(PC_ReadToken(source, &token)) { if (token.type != TT_NAME) { SourceError(source, "unknown random %s", token.string); FreeSource(source); return NULL; } //end if len = strlen(token.string) + 1; len = PAD(len, sizeof(long)); size += sizeof(bot_randomlist_t) + len; if (pass && ptr) { random = (bot_randomlist_t *) ptr; ptr += sizeof(bot_randomlist_t); random->string = ptr; ptr += len; strcpy(random->string, token.string); random->firstrandomstring = NULL; random->numstrings = 0; // if (lastrandom) lastrandom->next = random; else randomlist = random; lastrandom = random; } //end if if (!PC_ExpectTokenString(source, "=") || !PC_ExpectTokenString(source, "{")) { FreeSource(source); return NULL; } //end if while(!PC_CheckTokenString(source, "}")) { if (!BotLoadChatMessage( source, chatmessagestring, sizeof( chatmessagestring ) ) ) { FreeSource(source); return NULL; } //end if len = strlen(chatmessagestring) + 1; len = PAD(len, sizeof(long)); size += sizeof(bot_randomstring_t) + len; if (pass && ptr) { randomstring = (bot_randomstring_t *) ptr; ptr += sizeof(bot_randomstring_t); randomstring->string = ptr; ptr += len; strcpy(randomstring->string, chatmessagestring); // random->numstrings++; randomstring->next = random->firstrandomstring; random->firstrandomstring = randomstring; } //end if } //end while } //end while //free the source after one pass FreeSource(source); } //end for botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); // #ifdef DEBUG botimport.Print(PRT_MESSAGE, "random strings %d msec\n", Sys_MilliSeconds() - starttime); //BotDumpRandomStringList(randomlist); #endif //DEBUG // return randomlist; } //end of the function BotLoadRandomStrings //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static const char *RandomString( const char *name ) { const bot_randomlist_t *random; const bot_randomstring_t *rs; int i; for ( random = randomstrings; random; random = random->next ) { if ( strcmp( random->string, name ) == 0 ) { i = random() * random->numstrings; for ( rs = random->firstrandomstring; rs; rs = rs->next ) { if ( --i < 0 ) break; } if ( rs ) { return rs->string; } } } return NULL; } //end of the function RandomString #if 0 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void BotDumpMatchTemplates(bot_matchtemplate_t *matches) { FILE *fp; bot_matchtemplate_t *mt; bot_matchpiece_t *mp; bot_matchstring_t *ms; fp = Log_FilePointer(); if (!fp) return; for (mt = matches; mt; mt = mt->next) { fprintf(fp, "{ " ); for (mp = mt->first; mp; mp = mp->next) { if (mp->type == MT_STRING) { for (ms = mp->firststring; ms; ms = ms->next) { fprintf(fp, "\"%s\"", ms->string); if (ms->next) fprintf(fp, "|"); } //end for } //end if else if (mp->type == MT_VARIABLE) { fprintf(fp, "%d", mp->variable); } //end else if if (mp->next) fprintf(fp, ", "); } //end for fprintf(fp, " = (%d, %d);}\n", mt->type, mt->subtype); } //end for } //end of the function BotDumpMatchTemplates #endif //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void BotFreeMatchPieces(bot_matchpiece_t *matchpieces) { bot_matchpiece_t *mp, *nextmp; bot_matchstring_t *ms, *nextms; for (mp = matchpieces; mp; mp = nextmp) { nextmp = mp->next; if (mp->type == MT_STRING) { for (ms = mp->firststring; ms; ms = nextms) { nextms = ms->next; FreeMemory(ms); } //end for } //end if FreeMemory(mp); } //end for } //end of the function BotFreeMatchPieces //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static bot_matchpiece_t *BotLoadMatchPieces(source_t *source, char *endtoken) { int lastwasvariable, emptystring; token_t token; bot_matchpiece_t *matchpiece, *firstpiece, *lastpiece; bot_matchstring_t *matchstring, *lastmatchstring; firstpiece = NULL; lastpiece = NULL; // lastwasvariable = qfalse; // while(PC_ReadToken(source, &token)) { if (token.type == TT_NUMBER && (token.subtype & TT_INTEGER)) { if (token.intvalue >= MAX_MATCHVARIABLES) { SourceError(source, "can't have more than %d match variables", MAX_MATCHVARIABLES); FreeSource(source); BotFreeMatchPieces(firstpiece); return NULL; } //end if if (lastwasvariable) { SourceError(source, "not allowed to have adjacent variables"); FreeSource(source); BotFreeMatchPieces(firstpiece); return NULL; } //end if lastwasvariable = qtrue; // matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory(sizeof(bot_matchpiece_t)); matchpiece->type = MT_VARIABLE; matchpiece->variable = token.intvalue; matchpiece->next = NULL; if (lastpiece) lastpiece->next = matchpiece; else firstpiece = matchpiece; lastpiece = matchpiece; } //end if else if (token.type == TT_STRING) { // matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory(sizeof(bot_matchpiece_t)); matchpiece->firststring = NULL; matchpiece->type = MT_STRING; matchpiece->variable = 0; matchpiece->next = NULL; if (lastpiece) lastpiece->next = matchpiece; else firstpiece = matchpiece; lastpiece = matchpiece; // lastmatchstring = NULL; emptystring = qfalse; // do { if (matchpiece->firststring) { if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) { FreeSource(source); BotFreeMatchPieces(firstpiece); return NULL; } //end if } //end if StripDoubleQuotes(token.string); matchstring = (bot_matchstring_t *) GetClearedHunkMemory(sizeof(bot_matchstring_t) + strlen(token.string) + 1); matchstring->string = (char *) matchstring + sizeof(bot_matchstring_t); strcpy(matchstring->string, token.string); if (!strlen(token.string)) emptystring = qtrue; matchstring->next = NULL; if (lastmatchstring) lastmatchstring->next = matchstring; else matchpiece->firststring = matchstring; lastmatchstring = matchstring; } while(PC_CheckTokenString(source, "|")); //if there was no empty string found if (!emptystring) lastwasvariable = qfalse; } //end if else { SourceError(source, "invalid token %s", token.string); FreeSource(source); BotFreeMatchPieces(firstpiece); return NULL; } //end else if (PC_CheckTokenString(source, endtoken)) break; if (!PC_ExpectTokenString(source, ",")) { FreeSource(source); BotFreeMatchPieces(firstpiece); return NULL; } //end if } //end while return firstpiece; } //end of the function BotLoadMatchPieces //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void BotFreeMatchTemplates(bot_matchtemplate_t *mt) { bot_matchtemplate_t *nextmt; for (; mt; mt = nextmt) { nextmt = mt->next; BotFreeMatchPieces(mt->first); FreeMemory(mt); } //end for } //end of the function BotFreeMatchTemplates //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static bot_matchtemplate_t *BotLoadMatchTemplates( const char *matchfile ) { source_t *source; token_t token; bot_matchtemplate_t *matchtemplate, *matches, *lastmatch; unsigned long int context; PC_SetBaseFolder(BOTFILESBASEFOLDER); source = LoadSourceFile(matchfile); if (!source) { botimport.Print(PRT_ERROR, "couldn't load %s\n", matchfile); return NULL; } //end if // matches = NULL; //list with matches lastmatch = NULL; //last match in the list while(PC_ReadToken(source, &token)) { if (token.type != TT_NUMBER || !(token.subtype & TT_INTEGER)) { SourceError(source, "expected integer, found %s", token.string); BotFreeMatchTemplates(matches); FreeSource(source); return NULL; } //end if //the context context = token.intvalue; // if (!PC_ExpectTokenString(source, "{")) { BotFreeMatchTemplates(matches); FreeSource(source); return NULL; } //end if // while(PC_ReadToken(source, &token)) { if (!strcmp(token.string, "}")) break; // PC_UnreadLastToken(source); // matchtemplate = (bot_matchtemplate_t *) GetClearedHunkMemory(sizeof(bot_matchtemplate_t)); matchtemplate->context = context; matchtemplate->next = NULL; //add the match template to the list if (lastmatch) lastmatch->next = matchtemplate; else matches = matchtemplate; lastmatch = matchtemplate; //load the match template matchtemplate->first = BotLoadMatchPieces(source, "="); if (!matchtemplate->first) { BotFreeMatchTemplates(matches); return NULL; } //end if //read the match type if (!PC_ExpectTokenString(source, "(") || !PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) { BotFreeMatchTemplates(matches); FreeSource(source); return NULL; } //end if matchtemplate->type = token.intvalue; //read the match subtype if (!PC_ExpectTokenString(source, ",") || !PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) { BotFreeMatchTemplates(matches); FreeSource(source); return NULL; } //end if matchtemplate->subtype = token.intvalue; //read trailing punctuations if (!PC_ExpectTokenString(source, ")") || !PC_ExpectTokenString(source, ";")) { BotFreeMatchTemplates(matches); FreeSource(source); return NULL; } //end if } //end while } //end while //free the source FreeSource(source); botimport.Print(PRT_MESSAGE, "loaded %s\n", matchfile); // //BotDumpMatchTemplates(matches); // return matches; } //end of the function BotLoadMatchTemplates //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static int StringsMatch(bot_matchpiece_t *pieces, bot_match_t *match) { int lastvariable, index; const char *strptr, *newstrptr; bot_matchpiece_t *mp; bot_matchstring_t *ms; //no last variable lastvariable = -1; //pointer to the string to compare the match string with strptr = match->string; //Log_Write("match: %s", strptr); //compare the string with the current match string for (mp = pieces; mp; mp = mp->next) { //if it is a piece of string if (mp->type == MT_STRING) { newstrptr = NULL; for (ms = mp->firststring; ms; ms = ms->next) { if (!strlen(ms->string)) { newstrptr = strptr; break; } //end if //Log_Write("MT_STRING: %s", mp->string); index = StringContains(strptr, ms->string, qfalse); if (index >= 0) { newstrptr = strptr + index; if (lastvariable >= 0) { match->variables[lastvariable].length = (newstrptr - match->string) - match->variables[lastvariable].offset; //newstrptr - match->variables[lastvariable].ptr; lastvariable = -1; break; } //end if else if (index == 0) { break; } //end else newstrptr = NULL; } //end if } //end for if (!newstrptr) return qfalse; strptr = newstrptr + strlen(ms->string); } //end if //if it is a variable piece of string else if (mp->type == MT_VARIABLE) { //Log_Write("MT_VARIABLE"); match->variables[mp->variable].offset = strptr - match->string; lastvariable = mp->variable; } //end else if } //end for //if a match was found if (!mp && (lastvariable >= 0 || !strlen(strptr))) { //if the last piece was a variable string if (lastvariable >= 0) { assert( match->variables[lastvariable].offset >= 0 ); match->variables[lastvariable].length = strlen(&match->string[ (int) match->variables[lastvariable].offset]); } //end if return qtrue; } //end if return qfalse; } //end of the function StringsMatch //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotFindMatch(const char *str, bot_match_t *match, unsigned long int context) { int i; bot_matchtemplate_t *ms; Q_strncpyz( match->string, str, sizeof( match->string ) ); //remove any trailing enters while(strlen(match->string) && match->string[strlen(match->string)-1] == '\n') { match->string[strlen(match->string)-1] = '\0'; } //end while //compare the string with all the match strings for (ms = matchtemplates; ms; ms = ms->next) { if (!(ms->context & context)) continue; //reset the match variable offsets for (i = 0; i < MAX_MATCHVARIABLES; i++) match->variables[i].offset = -1; // if (StringsMatch(ms->first, match)) { match->type = ms->type; match->subtype = ms->subtype; return qtrue; } //end if } //end for return qfalse; } //end of the function BotFindMatch //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size) { if (variable < 0 || variable >= MAX_MATCHVARIABLES) { botimport.Print(PRT_FATAL, "BotMatchVariable: variable out of range\n"); buf[0] = '\0'; return; } if (match->variables[variable].offset >= 0) { if (match->variables[variable].length < size) size = match->variables[variable].length+1; assert( match->variables[variable].offset >= 0 ); Q_strncpyz( buf, &match->string[ (int) match->variables[variable].offset], size ); } else { buf[0] = '\0'; } //end else } //end of the function BotMatchVariable //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static bot_stringlist_t *BotFindStringInList(bot_stringlist_t *list, const char *string) { bot_stringlist_t *s; for ( s = list; s; s = s->next ) { if ( !strcmp( s->string, string ) ) return s; } return NULL; } //end of the function BotFindStringInList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static bot_stringlist_t *BotCheckChatMessageIntegrety(char *message, bot_stringlist_t *stringlist) { int i; char *msgptr; char temp[MAX_MESSAGE_SIZE]; bot_stringlist_t *s; msgptr = message; // while(*msgptr) { if (*msgptr == ESCAPE_CHAR) { msgptr++; switch(*msgptr) { case 'v': //variable { //step over the 'v' msgptr++; while(*msgptr && *msgptr != ESCAPE_CHAR) msgptr++; //step over the trailing escape char if (*msgptr) msgptr++; break; } //end case case 'r': //random { //step over the 'r' msgptr++; for (i = 0; (*msgptr && *msgptr != ESCAPE_CHAR); i++) { temp[i] = *msgptr++; } //end while temp[i] = '\0'; //step over the trailing escape char if (*msgptr) msgptr++; //find the random keyword if (!RandomString(temp)) { if (!BotFindStringInList(stringlist, temp)) { Log_Write("%s = {\"%s\"} //MISSING RANDOM\r\n", temp, temp); s = GetClearedMemory(sizeof(bot_stringlist_t) + strlen(temp) + 1); s->string = (char *) s + sizeof(bot_stringlist_t); strcpy(s->string, temp); s->next = stringlist; stringlist = s; } //end if } //end if break; } //end case default: { botimport.Print(PRT_FATAL, "BotCheckChatMessageIntegrety: message \"%s\" invalid escape char\n", message); break; } //end default } //end switch } //end if else { msgptr++; } //end else } //end while return stringlist; } //end of the function BotCheckChatMessageIntegrety //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void BotCheckInitialChatIntegrety(bot_chat_t *chat) { bot_chattype_t *t; bot_chatmessage_t *cm; bot_stringlist_t *stringlist, *s, *nexts; stringlist = NULL; for (t = chat->types; t; t = t->next) { for (cm = t->firstchatmessage; cm; cm = cm->next) { stringlist = BotCheckChatMessageIntegrety(cm->chatmessage, stringlist); } //end for } //end for for (s = stringlist; s; s = nexts) { nexts = s->next; FreeMemory(s); } //end for } //end of the function BotCheckInitialChatIntegrety //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void BotCheckReplyChatIntegrety(bot_replychat_t *replychat) { bot_replychat_t *rp; bot_chatmessage_t *cm; bot_stringlist_t *stringlist, *s, *nexts; stringlist = NULL; for (rp = replychat; rp; rp = rp->next) { for (cm = rp->firstchatmessage; cm; cm = cm->next) { stringlist = BotCheckChatMessageIntegrety(cm->chatmessage, stringlist); } //end for } //end for for (s = stringlist; s; s = nexts) { nexts = s->next; FreeMemory(s); } //end for } //end of the function BotCheckReplyChatIntegrety #if 0 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void BotDumpReplyChat(bot_replychat_t *replychat) { FILE *fp; bot_replychat_t *rp; bot_replychatkey_t *key; bot_chatmessage_t *cm; bot_matchpiece_t *mp; fp = Log_FilePointer(); if (!fp) return; fprintf(fp, "BotDumpReplyChat:\n"); for (rp = replychat; rp; rp = rp->next) { fprintf(fp, "["); for (key = rp->keys; key; key = key->next) { if (key->flags & RCKFL_AND) fprintf(fp, "&"); else if (key->flags & RCKFL_NOT) fprintf(fp, "!"); // if (key->flags & RCKFL_NAME) fprintf(fp, "name"); else if (key->flags & RCKFL_GENDERFEMALE) fprintf(fp, "female"); else if (key->flags & RCKFL_GENDERMALE) fprintf(fp, "male"); else if (key->flags & RCKFL_GENDERLESS) fprintf(fp, "it"); else if (key->flags & RCKFL_VARIABLES) { fprintf(fp, "("); for (mp = key->match; mp; mp = mp->next) { if (mp->type == MT_STRING) fprintf(fp, "\"%s\"", mp->firststring->string); else fprintf(fp, "%d", mp->variable); if (mp->next) fprintf(fp, ", "); } //end for fprintf(fp, ")"); } //end if else if (key->flags & RCKFL_STRING) { fprintf(fp, "\"%s\"", key->string); } //end if if (key->next) fprintf(fp, ", "); else fprintf(fp, "] = %1.0f\n", rp->priority); } //end for fprintf(fp, "{\n"); for (cm = rp->firstchatmessage; cm; cm = cm->next) { fprintf(fp, "\t\"%s\";\n", cm->chatmessage); } //end for fprintf(fp, "}\n"); } //end for } //end of the function BotDumpReplyChat #endif //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void BotFreeReplyChat(bot_replychat_t *replychat) { bot_replychat_t *rp, *nextrp; bot_replychatkey_t *key, *nextkey; bot_chatmessage_t *cm, *nextcm; for (rp = replychat; rp; rp = nextrp) { nextrp = rp->next; for (key = rp->keys; key; key = nextkey) { nextkey = key->next; if (key->match) BotFreeMatchPieces(key->match); if (key->string) FreeMemory(key->string); FreeMemory(key); } //end for for (cm = rp->firstchatmessage; cm; cm = nextcm) { nextcm = cm->next; FreeMemory(cm); } //end for FreeMemory(rp); } //end for } //end of the function BotFreeReplyChat //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void BotCheckValidReplyChatKeySet(source_t *source, bot_replychatkey_t *keys) { int allprefixed, hasvariableskey, hasstringkey; bot_matchpiece_t *m; bot_matchstring_t *ms; bot_replychatkey_t *key, *key2; // allprefixed = qtrue; hasvariableskey = hasstringkey = qfalse; for (key = keys; key; key = key->next) { if (!(key->flags & (RCKFL_AND|RCKFL_NOT))) { allprefixed = qfalse; if (key->flags & RCKFL_VARIABLES) { for (m = key->match; m; m = m->next) { if (m->type == MT_VARIABLE) hasvariableskey = qtrue; } //end for } //end if else if (key->flags & RCKFL_STRING) { hasstringkey = qtrue; } //end else if } //end if else if ((key->flags & RCKFL_AND) && (key->flags & RCKFL_STRING)) { for (key2 = keys; key2; key2 = key2->next) { if (key2 == key) continue; if (key2->flags & RCKFL_NOT) continue; if (key2->flags & RCKFL_VARIABLES) { for (m = key2->match; m; m = m->next) { if (m->type == MT_STRING) { for (ms = m->firststring; ms; ms = ms->next) { if (StringContains(ms->string, key->string, qfalse) != -1) { break; } //end if } //end for if (ms) break; } //end if else if (m->type == MT_VARIABLE) { break; } //end if } //end for if (!m) { SourceWarning(source, "one of the match templates does not " "leave space for the key %s with the & prefix", key->string); } //end if } //end if } //end for } //end else if ((key->flags & RCKFL_NOT) && (key->flags & RCKFL_STRING)) { for (key2 = keys; key2; key2 = key2->next) { if (key2 == key) continue; if (key2->flags & RCKFL_NOT) continue; if (key2->flags & RCKFL_STRING) { if (StringContains(key2->string, key->string, qfalse) != -1) { SourceWarning(source, "the key %s with prefix ! is inside the key %s", key->string, key2->string); } //end if } //end if else if (key2->flags & RCKFL_VARIABLES) { for (m = key2->match; m; m = m->next) { if (m->type == MT_STRING) { for (ms = m->firststring; ms; ms = ms->next) { if (StringContains(ms->string, key->string, qfalse) != -1) { SourceWarning(source, "the key %s with prefix ! is inside " "the match template string %s", key->string, ms->string); } //end if } //end for } //end if } //end for } //end else if } //end for } //end if } //end for if (allprefixed) SourceWarning(source, "all keys have a & or ! prefix"); if (hasvariableskey && hasstringkey) { SourceWarning(source, "variables from the match template(s) could be " "invalid when outputting one of the chat messages"); } //end if } //end of the function BotCheckValidReplyChatKeySet //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static bot_replychat_t *BotLoadReplyChat( const char *filename ) { char chatmessagestring[MAX_MESSAGE_SIZE]; char namebuffer[MAX_MESSAGE_SIZE]; source_t *source; token_t token; bot_chatmessage_t *chatmessage = NULL; bot_replychat_t *replychat, *replychatlist; bot_replychatkey_t *key; PC_SetBaseFolder(BOTFILESBASEFOLDER); source = LoadSourceFile(filename); if (!source) { botimport.Print(PRT_ERROR, "couldn't load %s\n", filename); return NULL; } //end if // replychatlist = NULL; // while(PC_ReadToken(source, &token)) { if (strcmp(token.string, "[")) { SourceError(source, "expected [, found %s", token.string); BotFreeReplyChat(replychatlist); FreeSource(source); return NULL; } //end if // replychat = GetClearedHunkMemory(sizeof(bot_replychat_t)); replychat->keys = NULL; replychat->next = replychatlist; replychatlist = replychat; //read the keys, there must be at least one key do { //allocate a key key = (bot_replychatkey_t *) GetClearedHunkMemory(sizeof(bot_replychatkey_t)); key->flags = 0; key->string = NULL; key->match = NULL; key->next = replychat->keys; replychat->keys = key; //check for MUST BE PRESENT and MUST BE ABSENT keys if (PC_CheckTokenString(source, "&")) key->flags |= RCKFL_AND; else if (PC_CheckTokenString(source, "!")) key->flags |= RCKFL_NOT; //special keys if (PC_CheckTokenString(source, "name")) key->flags |= RCKFL_NAME; else if (PC_CheckTokenString(source, "female")) key->flags |= RCKFL_GENDERFEMALE; else if (PC_CheckTokenString(source, "male")) key->flags |= RCKFL_GENDERMALE; else if (PC_CheckTokenString(source, "it")) key->flags |= RCKFL_GENDERLESS; else if (PC_CheckTokenString(source, "(")) //match key { key->flags |= RCKFL_VARIABLES; key->match = BotLoadMatchPieces(source, ")"); if (!key->match) { BotFreeReplyChat(replychatlist); return NULL; } //end if } //end else if else if (PC_CheckTokenString(source, "<")) //bot names { key->flags |= RCKFL_BOTNAMES; strcpy(namebuffer, ""); do { if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) { BotFreeReplyChat(replychatlist); FreeSource(source); return NULL; } //end if StripDoubleQuotes(token.string); if (strlen(namebuffer)) strcat(namebuffer, "\\"); strcat(namebuffer, token.string); } while(PC_CheckTokenString(source, ",")); if (!PC_ExpectTokenString(source, ">")) { BotFreeReplyChat(replychatlist); FreeSource(source); return NULL; } //end if key->string = (char *) GetClearedHunkMemory(strlen(namebuffer) + 1); strcpy(key->string, namebuffer); } //end else if else //normal string key { key->flags |= RCKFL_STRING; if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) { BotFreeReplyChat(replychatlist); FreeSource(source); return NULL; } //end if StripDoubleQuotes(token.string); key->string = (char *) GetClearedHunkMemory(strlen(token.string) + 1); strcpy(key->string, token.string); } //end else // PC_CheckTokenString(source, ","); } while(!PC_CheckTokenString(source, "]")); // BotCheckValidReplyChatKeySet(source, replychat->keys); //read the = sign and the priority if (!PC_ExpectTokenString(source, "=") || !PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) { BotFreeReplyChat(replychatlist); FreeSource(source); return NULL; } //end if replychat->priority = token.floatvalue; //read the leading { if (!PC_ExpectTokenString(source, "{")) { BotFreeReplyChat(replychatlist); FreeSource(source); return NULL; } //end if replychat->numchatmessages = 0; //while the trailing } is not found while(!PC_CheckTokenString(source, "}")) { if ( !BotLoadChatMessage( source, chatmessagestring, sizeof( chatmessagestring ) ) ) { BotFreeReplyChat(replychatlist); FreeSource(source); return NULL; } //end if chatmessage = (bot_chatmessage_t *) GetClearedHunkMemory(sizeof(bot_chatmessage_t) + strlen(chatmessagestring) + 1); chatmessage->chatmessage = (char *) chatmessage + sizeof(bot_chatmessage_t); strcpy(chatmessage->chatmessage, chatmessagestring); chatmessage->time = -2*CHATMESSAGE_RECENTTIME; chatmessage->next = replychat->firstchatmessage; //add the chat message to the reply chat replychat->firstchatmessage = chatmessage; replychat->numchatmessages++; } //end while } //end while FreeSource(source); botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); // //BotDumpReplyChat(replychatlist); if (botDeveloper) { BotCheckReplyChatIntegrety(replychatlist); } //end if // if (!replychatlist) botimport.Print(PRT_MESSAGE, "no rchats\n"); // return replychatlist; } //end of the function BotLoadReplyChat #if 0 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void BotDumpInitialChat(bot_chat_t *chat) { bot_chattype_t *t; bot_chatmessage_t *m; Log_Write("{"); for (t = chat->types; t; t = t->next) { Log_Write(" type \"%s\"", t->name); Log_Write(" {"); Log_Write(" numchatmessages = %d", t->numchatmessages); for (m = t->firstchatmessage; m; m = m->next) { Log_Write(" \"%s\"", m->chatmessage); } //end for Log_Write(" }"); } //end for Log_Write("}"); } //end of the function BotDumpInitialChat #endif //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static bot_chat_t *BotLoadInitialChat(const char *chatfile, const char *chatname) { int pass, foundchat, indent, size; char *ptr = NULL; char chatmessagestring[MAX_MESSAGE_SIZE]; source_t *source; token_t token; bot_chat_t *chat = NULL; bot_chattype_t *chattype = NULL; bot_chatmessage_t *chatmessage = NULL; #ifdef DEBUG int starttime; starttime = Sys_MilliSeconds(); #endif //DEBUG // size = 0; foundchat = qfalse; //a bot chat is parsed in two phases for (pass = 0; pass < 2; pass++) { //allocate memory if (pass && size) ptr = (char *) GetClearedMemory(size); //load the source file PC_SetBaseFolder(BOTFILESBASEFOLDER); source = LoadSourceFile(chatfile); if (!source) { botimport.Print(PRT_ERROR, "couldn't load %s\n", chatfile); return NULL; } //end if //chat structure if (pass) { chat = (bot_chat_t *) ptr; ptr += sizeof(bot_chat_t); } //end if size = sizeof(bot_chat_t); // while(PC_ReadToken(source, &token)) { if (!strcmp(token.string, "chat")) { if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) { FreeSource(source); return NULL; } //end if StripDoubleQuotes(token.string); //after the chat name we expect an opening brace if (!PC_ExpectTokenString(source, "{")) { FreeSource(source); return NULL; } //end if //if the chat name is found if (!Q_stricmp(token.string, chatname)) { foundchat = qtrue; //read the chat types while(1) { if (!PC_ExpectAnyToken(source, &token)) { FreeSource(source); return NULL; } //end if if (!strcmp(token.string, "}")) break; if (strcmp(token.string, "type")) { SourceError(source, "expected type found %s", token.string); FreeSource(source); return NULL; } //end if //expect the chat type name if (!PC_ExpectTokenType(source, TT_STRING, 0, &token) || !PC_ExpectTokenString(source, "{")) { FreeSource(source); return NULL; } //end if StripDoubleQuotes(token.string); if (pass && ptr) { chattype = (bot_chattype_t *) ptr; Q_strncpyz( chattype->name, token.string, sizeof( chattype->name ) ); chattype->firstchatmessage = NULL; //add the chat type to the chat chattype->next = chat->types; chat->types = chattype; // ptr += sizeof(bot_chattype_t); } //end if size += sizeof(bot_chattype_t); //read the chat messages while(!PC_CheckTokenString(source, "}")) { size_t len; if ( !BotLoadChatMessage( source, chatmessagestring, sizeof( chatmessagestring ) ) ) { FreeSource(source); return NULL; } //end if len = strlen(chatmessagestring) + 1; len = PAD(len, sizeof(long)); if (pass && ptr) { chatmessage = (bot_chatmessage_t *) ptr; chatmessage->time = -2*CHATMESSAGE_RECENTTIME; //put the chat message in the list chatmessage->next = chattype->firstchatmessage; chattype->firstchatmessage = chatmessage; //store the chat message ptr += sizeof(bot_chatmessage_t); chatmessage->chatmessage = ptr; strcpy(chatmessage->chatmessage, chatmessagestring); ptr += len; //the number of chat messages increased chattype->numchatmessages++; } //end if size += sizeof(bot_chatmessage_t) + len; } //end if } //end while } //end if else //skip the bot chat { indent = 1; while(indent) { if (!PC_ExpectAnyToken(source, &token)) { FreeSource(source); return NULL; } //end if if (!strcmp(token.string, "{")) indent++; else if (!strcmp(token.string, "}")) indent--; } //end while } //end else } //end if else { SourceError(source, "unknown definition %s", token.string); FreeSource(source); return NULL; } //end else } //end while //free the source FreeSource(source); //if the requested character is not found if (!foundchat) { botimport.Print(PRT_ERROR, "couldn't find chat %s in %s\n", chatname, chatfile); return NULL; } //end if } //end for // botimport.Print(PRT_MESSAGE, "loaded %s from %s\n", chatname, chatfile); // //BotDumpInitialChat(chat); if (botDeveloper) { BotCheckInitialChatIntegrety(chat); } //end if #ifdef DEBUG botimport.Print(PRT_MESSAGE, "initial chats loaded in %d msec\n", Sys_MilliSeconds() - starttime); #endif //DEBUG //character was read successfully return chat; } //end of the function BotLoadInitialChat //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void BotFreeChatFile(int chatstate) { bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return; if (cs->chat) FreeMemory(cs->chat); cs->chat = NULL; } //end of the function BotFreeChatFile //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotLoadChatFile(int chatstate, const char *chatfile, const char *chatname) { bot_chatstate_t *cs; int n, avail = 0; cs = BotChatStateFromHandle(chatstate); if (!cs) return BLERR_CANNOTLOADICHAT; BotFreeChatFile(chatstate); if (!LibVarGetValue("bot_reloadcharacters")) { avail = -1; for( n = 0; n < MAX_CLIENTS; n++ ) { if( !ichatdata[n] ) { if( avail == -1 ) { avail = n; } continue; } if( strcmp( chatfile, ichatdata[n]->filename ) != 0 ) { continue; } if( strcmp( chatname, ichatdata[n]->chatname ) != 0 ) { continue; } cs->chat = ichatdata[n]->chat; // botimport.Print( PRT_MESSAGE, "retained %s from %s\n", chatname, chatfile ); return BLERR_NOERROR; } if( avail == -1 ) { botimport.Print(PRT_FATAL, "ichatdata table full; couldn't load chat %s from %s\n", chatname, chatfile); return BLERR_CANNOTLOADICHAT; } } cs->chat = BotLoadInitialChat(chatfile, chatname); if (!cs->chat) { botimport.Print(PRT_FATAL, "couldn't load chat %s from %s\n", chatname, chatfile); return BLERR_CANNOTLOADICHAT; } //end if if (!LibVarGetValue("bot_reloadcharacters")) { ichatdata[avail] = GetClearedMemory( sizeof(bot_ichatdata_t) ); ichatdata[avail]->chat = cs->chat; Q_strncpyz( ichatdata[avail]->chatname, chatname, sizeof(ichatdata[avail]->chatname) ); Q_strncpyz( ichatdata[avail]->filename, chatfile, sizeof(ichatdata[avail]->filename) ); } //end if return BLERR_NOERROR; } //end of the function BotLoadChatFile //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static int BotExpandChatMessage(char *outmessage, int size, const char *message, unsigned long mcontext, bot_match_t *match, unsigned long vcontext, int reply) { int num, len, i, expansion; char *outputbuf; const char *ptr, *msgptr; char temp[MAX_MESSAGE_SIZE]; expansion = qfalse; msgptr = message; outputbuf = outmessage; len = 0; while( *msgptr != '\0' ) { if ( *msgptr == ESCAPE_CHAR ) { msgptr++; switch( *msgptr ) { case 'v': //variable { msgptr++; num = 0; while( *msgptr !='\0' && *msgptr != ESCAPE_CHAR ) num = num * 10 + (*msgptr++) - '0'; //step over the trailing escape char if ( *msgptr != '\0' ) msgptr++; if ( num >= ARRAY_LEN( match->variables ) ) { botimport.Print( PRT_ERROR, "%s(): message \"%s\" variable %d out of range\n", __func__, message, num ); return qfalse; } if (match->variables[num].offset >= 0) { assert( match->variables[num].offset >= 0 ); ptr = &match->string[ (int) match->variables[num].offset]; for (i = 0; i < match->variables[num].length; i++) { temp[i] = ptr[i]; } temp[i] = 0; //if it's a reply message if ( reply ) { //replace the reply synonyms in the variables BotReplaceReplySynonyms( temp, sizeof( temp ), vcontext ); } else { //replace synonyms in the variable context BotReplaceSynonyms( temp, sizeof( temp ), vcontext ); } if ( len + strlen( temp ) >= size ) { botimport.Print( PRT_ERROR, "%s(): message \"%s\" too long\n", __func__, message ); return qfalse; } strcpy(&outputbuf[len], temp); len += strlen(temp); } //end if break; } case 'r': //random { msgptr++; for (i = 0; (*msgptr && *msgptr != ESCAPE_CHAR); i++) { temp[i] = *msgptr++; } //end while temp[i] = '\0'; //step over the trailing escape char if ( *msgptr != '\0' ) msgptr++; //find the random keyword ptr = RandomString( temp ); if ( !ptr ) { botimport.Print( PRT_ERROR, "%s(): unknown random string \"%s\"\n", __func__, temp ); return qfalse; } if ( len + strlen(ptr) >= size ) { botimport.Print( PRT_ERROR, "%s(): message \"%s\" too long\n", __func__, message ); return qfalse; } strcpy(&outputbuf[len], ptr); len += strlen(ptr); expansion = qtrue; break; } default: { botimport.Print( PRT_FATAL, "%s(): message \"%s\" invalid escape char\n", __func__, message ); break; } } //end switch } //end if else { if ( len >= size ) { botimport.Print( PRT_ERROR, "%s(): message \"%s\" too long\n", __func__, message ); break; } outputbuf[ len++ ] = *msgptr++; } } outputbuf[ len ] = '\0'; //replace synonyms weighted in the message context BotReplaceWeightedSynonyms( outputbuf, size, mcontext ); //return true if a random was expanded return expansion; } //end of the function BotExpandChatMessage //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void BotConstructChatMessage(bot_chatstate_t *chatstate, const char *message, unsigned long mcontext, bot_match_t *match, unsigned long vcontext, int reply) { int i; char srcmessage[ MAX_MESSAGE_SIZE ]; Q_strncpyz( srcmessage, message, sizeof( srcmessage ) ); for ( i = 0; i < 10; i++ ) { if ( !BotExpandChatMessage( chatstate->chatmessage, sizeof( chatstate->chatmessage ), srcmessage, mcontext, match, vcontext, reply ) ) { break; } strcpy( srcmessage, chatstate->chatmessage ); } if ( i >= 10 ) { botimport.Print( PRT_WARNING, "too many expansions in chat message\n" ); botimport.Print( PRT_WARNING, "%s\n", chatstate->chatmessage ); } //end if } //end of the function BotConstructChatMessage //=========================================================================== // randomly chooses one of the chat message of the given type // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static const char *BotChooseInitialChatMessage(bot_chatstate_t *cs, const char *type) { int n, numchatmessages; float besttime; bot_chattype_t *t; bot_chatmessage_t *m, *bestchatmessage; bot_chat_t *chat; chat = cs->chat; for (t = chat->types; t; t = t->next) { if (!Q_stricmp(t->name, type)) { numchatmessages = 0; for (m = t->firstchatmessage; m; m = m->next) { if (m->time > AAS_Time()) continue; numchatmessages++; } //end if //if all chat messages have been used recently if (numchatmessages <= 0) { besttime = 0; bestchatmessage = NULL; for (m = t->firstchatmessage; m; m = m->next) { if (!besttime || m->time < besttime) { bestchatmessage = m; besttime = m->time; } //end if } //end for if (bestchatmessage) return bestchatmessage->chatmessage; } //end if else //choose a chat message randomly { n = random() * numchatmessages; for (m = t->firstchatmessage; m; m = m->next) { if (m->time > AAS_Time()) continue; if (--n < 0) { m->time = AAS_Time() + CHATMESSAGE_RECENTTIME; return m->chatmessage; } //end if } //end for } //end else return NULL; } //end if } //end for return NULL; } //end of the function BotChooseInitialChatMessage //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotNumInitialChats(int chatstate, const char *type) { bot_chatstate_t *cs; bot_chattype_t *t; cs = BotChatStateFromHandle(chatstate); if (!cs) return 0; for (t = cs->chat->types; t; t = t->next) { if (!Q_stricmp(t->name, type)) { if (LibVarGetValue("bot_testichat")) { botimport.Print(PRT_MESSAGE, "%s has %d chat lines\n", type, t->numchatmessages); botimport.Print(PRT_MESSAGE, "-------------------\n"); } return t->numchatmessages; } //end if } //end for return 0; } //end of the function BotNumInitialChats //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotInitialChat(int chatstate, const char *type, int mcontext, const char *var0, const char *var1, const char *var2, const char *var3, const char *var4, const char *var5, const char *var6, const char *var7) { const char *message; int index, len; bot_match_t match; bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return; //if no chat file is loaded if (!cs->chat) return; //choose a chat message randomly of the given type message = BotChooseInitialChatMessage(cs, type); //if there's no message of the given type if (!message) { #ifdef DEBUG botimport.Print(PRT_MESSAGE, "no chat messages of type %s\n", type); #endif //DEBUG return; } //end if // Com_Memset( &match, 0, sizeof( match ) ); index = 0; if ( var0 ) { len = (int)strlen( var0 ); match.variables[0].offset = index; if ( len + index < sizeof( match.string ) ) { match.variables[0].length = len; strcat( match.string, var0 ); index += strlen( var0 ); } } if ( var1 ) { len = (int) strlen( var1 ); match.variables[1].offset = index; if ( len + index < sizeof( match.string ) ) { match.variables[1].length = len; strcat( match.string, var1 ); index += len; } } if ( var2 ) { len = (int) strlen( var2 ); match.variables[2].offset = index; if ( len + index < sizeof( match.string ) ) { match.variables[2].length = len; strcat( match.string, var2 ); index += len; } } if ( var3 ) { len = (int) strlen( var3 ); match.variables[3].offset = index; if ( len + index < sizeof( match.string ) ) { match.variables[3].length = len; strcat( match.string, var3 ); index += len; } } if ( var4 ) { len = (int) strlen( var4 ); match.variables[4].offset = index; if ( len + index < sizeof( match.string ) ) { match.variables[4].length = len; strcat( match.string, var4 ); index += len; } } if ( var5 ) { len = (int) strlen( var5 ); match.variables[5].offset = index; if ( len + index < sizeof( match.string ) ) { match.variables[5].length = len; strcat( match.string, var5 ); index += len; } } if ( var6 ) { len = (int) strlen( var6 ); match.variables[6].offset = index; if ( len + index < sizeof( match.string ) ) { match.variables[6].length = len; strcat( match.string, var6 ); index += len; } } if ( var7 ) { len = (int) strlen( var7 ); match.variables[7].offset = index; if ( len + index < sizeof( match.string ) ) { match.variables[7].length = strlen(var7); strcat( match.string, var7 ); //index += len; } } BotConstructChatMessage( cs, message, mcontext, &match, 0, qfalse ); } #if 0 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static void BotPrintReplyChatKeys(bot_replychat_t *replychat) { bot_replychatkey_t *key; bot_matchpiece_t *mp; botimport.Print(PRT_MESSAGE, "["); for (key = replychat->keys; key; key = key->next) { if (key->flags & RCKFL_AND) botimport.Print(PRT_MESSAGE, "&"); else if (key->flags & RCKFL_NOT) botimport.Print(PRT_MESSAGE, "!"); // if (key->flags & RCKFL_NAME) botimport.Print(PRT_MESSAGE, "name"); else if (key->flags & RCKFL_GENDERFEMALE) botimport.Print(PRT_MESSAGE, "female"); else if (key->flags & RCKFL_GENDERMALE) botimport.Print(PRT_MESSAGE, "male"); else if (key->flags & RCKFL_GENDERLESS) botimport.Print(PRT_MESSAGE, "it"); else if (key->flags & RCKFL_VARIABLES) { botimport.Print(PRT_MESSAGE, "("); for (mp = key->match; mp; mp = mp->next) { if (mp->type == MT_STRING) botimport.Print(PRT_MESSAGE, "\"%s\"", mp->firststring->string); else botimport.Print(PRT_MESSAGE, "%d", mp->variable); if (mp->next) botimport.Print(PRT_MESSAGE, ", "); } //end for botimport.Print(PRT_MESSAGE, ")"); } //end if else if (key->flags & RCKFL_STRING) { botimport.Print(PRT_MESSAGE, "\"%s\"", key->string); } //end if if (key->next) botimport.Print(PRT_MESSAGE, ", "); else botimport.Print(PRT_MESSAGE, "] = %1.0f\n", replychat->priority); } //end for botimport.Print(PRT_MESSAGE, "{\n"); } //end of the function BotPrintReplyChatKeys #endif //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotReplyChat(int chatstate, const char *message, int mcontext, int vcontext, const char *var0, const char *var1, const char *var2, const char *var3, const char *var4, const char *var5, const char *var6, const char *var7) { bot_replychat_t *rchat, *bestrchat; bot_replychatkey_t *key; bot_chatmessage_t *m, *bestchatmessage; bot_match_t match, bestmatch; int bestpriority, num, found, res, numchatmessages, index; bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return qfalse; Com_Memset( &match, 0, sizeof( match ) ); Q_strncpyz( match.string, message, sizeof( match.string ) ); bestpriority = -1; bestchatmessage = NULL; bestrchat = NULL; //go through all the reply chats for (rchat = replychats; rchat; rchat = rchat->next) { found = qfalse; for (key = rchat->keys; key; key = key->next) { res = qfalse; //get the match result if (key->flags & RCKFL_NAME) res = (StringContains(message, cs->name, qfalse) != -1); else if (key->flags & RCKFL_BOTNAMES) res = (StringContains(key->string, cs->name, qfalse) != -1); else if (key->flags & RCKFL_GENDERFEMALE) res = (cs->gender == CHAT_GENDERFEMALE); else if (key->flags & RCKFL_GENDERMALE) res = (cs->gender == CHAT_GENDERMALE); else if (key->flags & RCKFL_GENDERLESS) res = (cs->gender == CHAT_GENDERLESS); else if (key->flags & RCKFL_VARIABLES) res = StringsMatch(key->match, &match); else if (key->flags & RCKFL_STRING) res = (StringContainsWord(message, key->string) != NULL); //if the key must be present if (key->flags & RCKFL_AND) { if (!res) { found = qfalse; break; } //end if } //end else if //if the key must be absent else if (key->flags & RCKFL_NOT) { if (res) { found = qfalse; break; } //end if } //end if else if (res) { found = qtrue; } //end else } //end for // if (found) { if (rchat->priority > bestpriority) { numchatmessages = 0; for (m = rchat->firstchatmessage; m; m = m->next) { if (m->time > AAS_Time()) continue; numchatmessages++; } //end if num = random() * numchatmessages; for (m = rchat->firstchatmessage; m; m = m->next) { if (--num < 0) break; if (m->time > AAS_Time()) continue; } //end for //if the reply chat has a message if (m) { Com_Memcpy(&bestmatch, &match, sizeof(bot_match_t)); bestchatmessage = m; bestrchat = rchat; bestpriority = rchat->priority; } //end if } //end if } //end if } //end for if (bestchatmessage) { index = strlen( bestmatch.string ); int len; if ( var0 ) { len = (int) strlen( var0 ); bestmatch.variables[0].offset = index; if ( len + index < sizeof( bestmatch.string ) ) { bestmatch.variables[0].length = len; strcat( bestmatch.string, var0 ); index += len; } } if ( var1 ) { len = (int) strlen( var1 ); bestmatch.variables[1].offset = index; if ( len + index < sizeof( bestmatch.string ) ) { bestmatch.variables[1].length = len; strcat( bestmatch.string, var1 ); index += len; } } if ( var2 ) { len = (int) strlen( var2 ); bestmatch.variables[2].offset = index; if ( len + index < sizeof( bestmatch.string ) ) { bestmatch.variables[2].length = len; strcat( bestmatch.string, var2 ); index += len; } } if ( var3 ) { len = (int) strlen( var3 ); bestmatch.variables[3].offset = index; if ( len + index < sizeof( bestmatch.string ) ) { bestmatch.variables[3].length = len; strcat( bestmatch.string, var3 ); index += len; } } if ( var4 ) { len = (int) strlen( var4 ); bestmatch.variables[4].offset = index; if ( len + index < sizeof( bestmatch.string ) ) { bestmatch.variables[4].length = len; strcat( bestmatch.string, var4 ); index += len; } } if ( var5 ) { len = (int) strlen( var5 ); bestmatch.variables[5].offset = index; if ( len + index < sizeof( bestmatch.string ) ) { bestmatch.variables[5].length = len; strcat( bestmatch.string, var5 ); index += len; } } if ( var6 ) { len = (int) strlen( var6 ); bestmatch.variables[6].offset = index; if ( len + index < sizeof( bestmatch.string ) ) { bestmatch.variables[6].length = len; strcat( bestmatch.string, var6 ); index += len; } } if ( var7 ) { len = (int) strlen( var7 ); bestmatch.variables[7].offset = index; if ( len + index < sizeof( bestmatch.string ) ) { bestmatch.variables[7].length = len; strcat( bestmatch.string, var7 ); //index += len; } } if (LibVarGetValue("bot_testrchat")) { for (m = bestrchat->firstchatmessage; m; m = m->next) { BotConstructChatMessage(cs, m->chatmessage, mcontext, &bestmatch, vcontext, qtrue); BotRemoveTildes(cs->chatmessage); botimport.Print(PRT_MESSAGE, "%s\n", cs->chatmessage); } } else { bestchatmessage->time = AAS_Time() + CHATMESSAGE_RECENTTIME; BotConstructChatMessage(cs, bestchatmessage->chatmessage, mcontext, &bestmatch, vcontext, qtrue); } return qtrue; } return qfalse; } //end of the function BotReplyChat //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotChatLength(int chatstate) { bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return 0; return strlen(cs->chatmessage); } //end of the function BotChatLength //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotEnterChat(int chatstate, int clientto, int sendto) { bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return; if (strlen(cs->chatmessage)) { BotRemoveTildes(cs->chatmessage); if (LibVarGetValue("bot_testichat")) { botimport.Print(PRT_MESSAGE, "%s\n", cs->chatmessage); } else { switch(sendto) { case CHAT_TEAM: EA_Command(cs->client, va("say_team %s", cs->chatmessage)); break; case CHAT_TELL: EA_Command(cs->client, va("tell %d %s", clientto, cs->chatmessage)); break; default: //CHAT_ALL EA_Command(cs->client, va("say %s", cs->chatmessage)); break; } } //clear the chat message from the state strcpy(cs->chatmessage, ""); } //end if } //end of the function BotEnterChat //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotGetChatMessage(int chatstate, char *buf, int size) { bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return; BotRemoveTildes(cs->chatmessage); Q_strncpyz( buf, cs->chatmessage, size ); //clear the chat message from the state cs->chatmessage[0] = '\0'; } //end of the function BotGetChatMessage //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotSetChatGender(int chatstate, int gender) { bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return; switch(gender) { case CHAT_GENDERFEMALE: cs->gender = CHAT_GENDERFEMALE; break; case CHAT_GENDERMALE: cs->gender = CHAT_GENDERMALE; break; default: cs->gender = CHAT_GENDERLESS; break; } //end switch } //end of the function BotSetChatGender //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotSetChatName(int chatstate, const char *name, int client) { bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return; cs->client = client; Q_strncpyz( cs->name, name, sizeof( cs->name ) ); } //end of the function BotSetChatName //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotResetChatAI(void) { bot_replychat_t *rchat; bot_chatmessage_t *m; for (rchat = replychats; rchat; rchat = rchat->next) { for (m = rchat->firstchatmessage; m; m = m->next) { m->time = 0; } //end for } //end for } //end of the function BotResetChatAI //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== int BotAllocChatState(void) { int i; for (i = 1; i <= MAX_CLIENTS; i++) { if (!botchatstates[i]) { botchatstates[i] = GetClearedMemory(sizeof(bot_chatstate_t)); return i; } //end if } //end for return 0; } //end of the function BotAllocChatState //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== void BotFreeChatState(int handle) { bot_consolemessage_t m; int h; if (handle <= 0 || handle > MAX_CLIENTS) { botimport.Print(PRT_FATAL, "chat state handle %d out of range\n", handle); return; } //end if if (!botchatstates[handle]) { botimport.Print(PRT_FATAL, "invalid chat state %d\n", handle); return; } //end if if (LibVarGetValue("bot_reloadcharacters")) { BotFreeChatFile(handle); } //end if //free all the console messages left in the chat state for (h = BotNextConsoleMessage(handle, &m); h; h = BotNextConsoleMessage(handle, &m)) { //remove the console message BotRemoveConsoleMessage(handle, h); } //end for FreeMemory(botchatstates[handle]); botchatstates[handle] = NULL; } //end of the function BotFreeChatState //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotSetupChatAI(void) { const char *file; #ifdef DEBUG int starttime = Sys_MilliSeconds(); #endif //DEBUG file = LibVarString("synfile", "syn.c"); synonyms = BotLoadSynonyms(file); file = LibVarString("rndfile", "rnd.c"); randomstrings = BotLoadRandomStrings(file); file = LibVarString("matchfile", "match.c"); matchtemplates = BotLoadMatchTemplates(file); // if (!LibVarValue("nochat", "0")) { file = LibVarString("rchatfile", "rchat.c"); replychats = BotLoadReplyChat(file); } //end if InitConsoleMessageHeap(); #ifdef DEBUG botimport.Print(PRT_MESSAGE, "setup chat AI %d msec\n", Sys_MilliSeconds() - starttime); #endif //DEBUG return BLERR_NOERROR; } //end of the function BotSetupChatAI //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotShutdownChatAI(void) { int i; //free all remaining chat states for(i = 0; i < MAX_CLIENTS; i++) { if (botchatstates[i]) { BotFreeChatState(i); } //end if } //end for //free all cached chats for(i = 0; i < MAX_CLIENTS; i++) { if (ichatdata[i]) { FreeMemory(ichatdata[i]->chat); FreeMemory(ichatdata[i]); ichatdata[i] = NULL; } //end if } //end for if (consolemessageheap) FreeMemory(consolemessageheap); consolemessageheap = NULL; if (matchtemplates) BotFreeMatchTemplates(matchtemplates); matchtemplates = NULL; if (randomstrings) FreeMemory(randomstrings); randomstrings = NULL; if (synonyms) FreeMemory(synonyms); synonyms = NULL; if (replychats) BotFreeReplyChat(replychats); replychats = NULL; } //end of the function BotShutdownChatAI