rallyunlimited-engine/code/botlib/be_ai_chat.c
2024-02-24 18:10:00 +03:00

3162 lines
87 KiB
C

/*
===========================================================================
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 "../qcommon/qcommon.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)
{
int len;
index = strlen( bestmatch.string );
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