mirror of
https://github.com/Q3Rally-Team/rallyunlimited-engine.git
synced 2024-11-24 21:21:47 +00:00
3162 lines
87 KiB
C
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
|