Well, i got my chat system work basicly. THanks to you guys i got the identification of a character working and for a moment i thought it was done. Until i began cleaning code and i saw there where tons of shit in it. So as a good boy i rewritten some tiny pieces in a more neat way and in point of few i did not fix or break a thing. It should simply work. Let me explain how the 'chat engine' works, its simple:
the ChatEngine class loads an ini file which consists of 'reply blocks'. Each reply block contains at max 10 words it should identify from a sentence typed by any user. When the reply block matches with most words, the corresponding 'reply sentences' given should be used to give a more sensable reply.
I do not expect miracles, i do not expect super smart talks. But i dont want dumbshit things like "hehe, i killed you %n", etc.
My
crucial problem:
When i type a sentence, it crashes. Not 100% of the time immidiatly, but mostly after the 2nd sentence i type, it does crash. It crashes randomly which makes it harder for me to track down the bug(s). I marked 2 spots where it crashes already. It also crashes randomly in some adress of HL.EXE.
If you have any comments on how things should really be changed, you are free to mention. Although i am just looking for a bug, i find it hard to write good code with 'pointers' and such. I always mess up, and i never know why. I had a book which helped me mostly, but i lost it, darn.
The chatengine.h contents:
Code:
// Chatting Engine
#define MAX_BLOCKS 100
#define BLOCK_DEATHS MAX_BLOCKS-1
// Reply block
typedef struct
{
// words, hinting that in this block a logical sentence will be to reply with
char word[10][25];
char sentence[50][80]; // at max 50 sentences of 80 characters to reply with
bool bUsed;
} tReplyBlock;
class cChatEngine
{
public:
// variables
tReplyBlock ReplyBlock[MAX_BLOCKS]; // 100 reply blocks reserved in memory
float fThinkTimer; // The chatengine has a 'think timer'.
char sender[30];
char sentence[80];
// functions
void init(); // initialize database/blocks
void load(); // load database (loads blocks)
void think(); // make the chat engine think
bool cChatEngine::CompareNames(char name[30], char name2[30], int length);
// add sentence from any player/bot into memory to handle
void set_sentence(char csender[30], char csentence[80]);
// handles a sentence, decides to reply on it or not.
void handle_sentence(); // DOES NOT WORK, IS NOT CODED
};
Okay, and the ChatEngine.cpp content:
Code:
/**
* RealBot : Artificial Intelligence
* Version : unknown
* Author : Stefan Hendriks
*
* DISCLAIMER
*
* Source code is (c) copyrighted by Stefan Hendriks unless stated otherwise.
* Source code may never be redistributed without explicit permission of the
* author.
*
* History, Information & Credits:
* RealBot is based partially uppon the HPB-Bot Template #3 by Botman
* Thanks to Ditlew (NNBot), Pierre Marie Baty (RACCBOT), Tub (RB AI PR1/2/3)
* Greg Slocum & Shivan (RB V1.0), Botman (HPB-Bot) and Aspirin (JOEBOT).
*
* Storage of Visibility Table using BITS by Cheesemonster.
**/
// Chatting Engine
#ifndef _WIN32
#include <string.h>
#endif
#include <extdll.h>
#include <dllapi.h>
#include <h_export.h>
#include <meta_api.h>
#include <entity_state.h>
#include "bot.h"
#include "bot_ini.h"
#include "bot_weapons.h"
#include "game.h"
#include "bot_func.h"
#include "ChatEngine.h"
extern edict_t *listenserver_edict;
extern cGame Game;
extern cBot bots[32];
// initialize all
void cChatEngine::init()
{
// clear all blocks
for (int iB=0; iB < MAX_BLOCKS; iB++)
{
for (int iBs=0; iBs < 50; iBs++)
ReplyBlock[iB].sentence[iBs][0] = '\0';
for (int iBw=0; iBw < 10; iBw++)
ReplyBlock[iB].word[iBw][0] = '\0';
ReplyBlock[iB].bUsed=false;
}
// init sentence
sender[0] = '\0';
sentence[0] = '\0';
}
// load
void cChatEngine::load()
{
init();
// load blocks using INI parser
INI_PARSE_CHATFILE();
/*
// now show results
for (int iB=0; iB < MAX_BLOCKS; iB++)
{
SERVER_PRINT("BLOCK WORDS\n");
for (int iBw = 0; iBw < 10; iBw++)
if (ReplyBlock[iB].word[iBw][0] != '\0')
{
SERVER_PRINT(ReplyBlock[iB].word[iBw]);
SERVER_PRINT("\n");
}
SERVER_PRINT("BLOCK SENTENCES\n");
for (int iBs = 0; iBs < 50; iBs++)
if (ReplyBlock[iB].sentence[iBs][0] != '\0')
{
SERVER_PRINT(ReplyBlock[iB].sentence[iBs]);
SERVER_PRINT("\n");
}
}*/
}
bool cChatEngine::CompareNames(char name[30], char name2[30], int length)
{
for (int i=0; i < length; i++)
{
char msg[80];
sprintf(msg, "Letter %d, char %s =? %s\n", i, name[i], name2[i]);
SERVER_PRINT(msg);
if (name[i] != name2[i])
{
char msg[80];
sprintf(msg, "MISMATCH AT %d - length %d \n", i, length);
SERVER_PRINT(msg);
return false;
}
else
{
}
}
return true;
}
// think
void cChatEngine::think()
{
if (fThinkTimer + 1.0 < gpGlobals->time)
{
if (sender[0] != '\0')
{
// Run some checks on the 'sender'. We need the edict from that
edict_t *pSender = NULL;
for (int i = 1; i <= gpGlobals->maxClients; i++)
{
edict_t *pPlayer = INDEXENT (i);
if (pPlayer && (!pPlayer->free))
{
char name[30], name2[30];
// clear
name[30]=0;
name2[30]=0;
// copy
strcpy(name, STRING(pPlayer->v.netname));
strcpy(name2, sender);
if (strcmp(name, name2) == 0)
{
pSender=pPlayer;
break;
}
}
}
// Scan the message so we know in what block we should be to reply:
char word[20];
word[20]=0;
int c=0;
//for (c=0; c < 20; c++)
// word[c] = '\0';
c=0;
int wc=0;
int length = strlen(sentence);
// When length is not valid, get out.
if (length == 0 || length >= 80)
{
// clear sentence and such
for (int i=0; i < 80; i++)
{
sentence[i] = '\0';
if (i < 30) sender[i] = '\0';
}
// reset timer
fThinkTimer = gpGlobals->time;
return;
}
// Define word block scores:
int WordBlockScore[MAX_BLOCKS];
// Init, none of the block has a score yet (set to -1)
for (int wbs=0; wbs < MAX_BLOCKS; wbs++)
WordBlockScore[wbs] = -1;
// chSentence
char chSentence[80];
// clear first
chSentence[80]=0;
// copy
sprintf(chSentence, "%s", sentence);
// C
while (c < (length))
{
// protection matters:
if (c > length)
break;
if (c < 0)
break;
// End of protection matters
// Step: Check character to identify the end of a word.
if (sentence[c] == ' ' || sentence[c] == '\n' ||
sentence[c] == '.' || sentence[c] == '?' ||
c == length)
{
// Now find the word and add up scors on the proper score blocks.
if (c == length)
word[wc] = sentence[c];
// not a good word (too small)
if (strlen(word) <= 0)
{
SERVER_PRINT("This is not a good word!\n");
}
else
{
for (int iB=0; iB < MAX_BLOCKS; iB++)
{
if (ReplyBlock[iB].bUsed)
{
for (int iBw=0; iBw < 10; iBw++)
{
// skip any word in the reply block that is not valid
if (ReplyBlock[iB].word[iBw][0] == '\0')
continue; // not filled in
if (strlen(ReplyBlock[iB].word[iBw]) <= 0)
continue; // not long enough (a space?)
// add score to matching word
if (strcmp(ReplyBlock[iB].word[iBw], word) == 0)
WordBlockScore[iB]++;
} // all words in this block
} // any used block
} // for all blocks
} // good word
// clear out entire word.
for (int cw=0; cw < 20; cw++)
word[cw] = '\0';
wc=0; // reset WC position (start writing 'word[WC]' at 0 again)
c++; // next position in sentence
continue; // go to top again.
}
// when we end up here, we are still reading a 'non finishing word' character.
// we will fill that in word[wc]. Then add up wc and c, until we find a character
// that marks the end of a word again.
// fill in the word:
word[wc] = sentence[c];
// add up.
c++;
wc++;
} // end of loop
// now loop through all blocks and find the one with the most score:
int iMaxScore=-1;
int iTheBlock=-1;
// for all blocks
for (int rB=0; rB < MAX_BLOCKS; rB++)
{
// Any block that has the highest score
if (WordBlockScore[rB] > iMaxScore)
{
iMaxScore = WordBlockScore[rB];
iTheBlock = rB;
}
}
// When we have found pSender edict AND we have a block to reply from
// we continue here.
if (pSender && iTheBlock > -1)
{
int iMax=-1;
// now choose a sentence to reply with
for (int iS=0; iS < 50; iS++)
{
// Find max sentences of this reply block
if (ReplyBlock[iTheBlock].sentence[iS][0] != '\0')
iMax++;
}
// loop through all bots:
for (int i = 1; i <= gpGlobals->maxClients; i++)
{
edict_t *pPlayer = INDEXENT (i);
// skip invalid players and skip self (i.e. this bot)
if ((pPlayer) && (!pPlayer->free) && pSender != pPlayer)
{
// only reply to the living when alive, and otherwise
bool bSenderAlive=false;
bool bPlayerAlive=false;
bSenderAlive=IsAlive(pSender); // CRASH : it sometimes crashes here
bPlayerAlive=IsAlive(pPlayer);
if (bSenderAlive != bPlayerAlive)
continue;
cBot *pBotPointer = UTIL_GetBotPointer(pPlayer);
if (pBotPointer != NULL)
// if (RANDOM_LONG(0,100) < pBotPointer->ipChatRate)
{
// When we have at least 1 sentence...
if (iMax > -1)
{
// choose randomly a reply
int the_c=RANDOM_LONG(0,iMax);
// the_c is choosen, it is the sentence we reply with.
// do a check if its valid:
if (ReplyBlock[iTheBlock].sentence[the_c][0] != '\0')
{
// chSentence is eventually what the bot will say.
char chSentence[80];
char temp[80];
// super clear it.
for (int iC=0; iC < 80; iC++)
{
chSentence[iC] = '\0';
temp[iC] = '\0';
}
// get character position
char *name_pos = strstr(ReplyBlock[iTheBlock].sentence[the_c], "%n");
// when name_pos var is found, fill it in.
if (name_pos != NULL)
{
// when name is in this one:
int name_offset = name_pos - ReplyBlock[iTheBlock].sentence[the_c];
// copy every character till name_offset
for (int nC=0; nC < name_offset; nC++)
{
//chSentence[nC] = ReplyBlock[iTheBlock].sentence[the_c][nC];
temp[nC] = ReplyBlock[iTheBlock].sentence[the_c][nC];
}
temp[nC] = ' ';
// copy senders name to chSentence
strcat(temp, sender);
// From here us 'tc' to keep track of chSentence and use
// nC to keep reading from ReplyBlock
int tc=nC;
// Skip %n part in ReplyBlock
nC = name_offset + 2;
// we just copied a name to chSentence
// set our cursor after the name now (name length + 1)
tc = strlen(temp);
//tc = tc + strlen(sender) + 1;
// now finish the sentence
// get entire length of ReplyBlock and go until we reach the end
int length=strlen(ReplyBlock[iTheBlock].sentence[the_c]);
// for every nC , read character from ReplyBlock
for (nC; nC <= length; nC++)
{
// ... and copy it into chSentence
temp[tc] = ReplyBlock[iTheBlock].sentence[the_c][nC];
//char tmsg[80];
//sprintf(tmsg,"Copying char %c , tc = %d, nC = %d\n", temp[tc], tc, nC);
//SERVER_PRINT(tmsg);
tc++; // add up tc.
}
// terminate
temp[tc] = '\n';
sprintf(chSentence, "%s.", temp);
}
// when no name pos is found, we just copy the string and say that (works ok)
else
sprintf(chSentence, "%s.", ReplyBlock[iTheBlock].sentence[the_c]);
// reply:
UTIL_SayTextBot(chSentence, pBotPointer);
}
}
}
}
}
}
// clear sentence and such
for (i=0; i < 80; i++)
{
sentence[i] = '\0'; // CRASH : it sometimes crashes here
if (i < 30) sender[i] = '\0';
}
}
fThinkTimer = gpGlobals->time;
}
}
//
void cChatEngine::set_sentence(char csender[30], char csentence[80])
{
if (sender[0] == ' ' || sender[0] == '\0')
{
SERVER_PRINT("Sender '");
SERVER_PRINT(csender);
SERVER_PRINT("' and sentence '");
SERVER_PRINT(csentence);
SERVER_PRINT("'\n");
strcpy(sender, csender);
//sprintf(sender, "%s", csender);
_strupr(csentence);
strcpy(sentence, csentence);
//sprintf(sentence, "%s", csentence);
}
else
SERVER_PRINT("Tried to set sentence while we did not handle it yet");
}
Now, also crucial are the functions to intercept the SayText message of course:
bot_client.cpp
Code:
void BotClient_CS_SayText(void *p, int bot_index)
{
static unsigned char ucEntIndex;
static int state = 0; // current state machine state
if (state == 0)
{
SERVER_PRINT("state==0\n");
ucEntIndex = *(unsigned char *)p;
}
else if (state == 1)
{
cBot *pBot=&bots[bot_index];
SERVER_PRINT("state==1\n");
if(ENTINDEX(pBot->pEdict) != ucEntIndex)
{
SERVER_PRINT("state==1(phase 2)\n");
char sentence[80];
char chSentence[80];
char netname[30];
for (int iClear=0; iClear < 80; iClear++)
{
sentence[iClear] = '\0';
chSentence[iClear] = '\0';
if (iClear < 30) netname[iClear] = '\0';
}
strcpy(sentence,(char *)p);
// remove first part of sentence.
int length = strlen (sentence) - strlen (strstr (sentence, " : "));
int tc=0;
for (int c=length; c < 80; c++)
{
chSentence[tc] = sentence[c];
tc++;
}
//strncpy (chSentence, (char *)sentence[length], 80-length);
//strcpy(netname, STRING(pBot->pEdict->v.netname));
//strcpy(netname, sentence, strlen(strstr(sentence, " : "));
int nc=0;
c=2;
if (strstr(sentence, "*DEAD*"))
c+= 6;
for (c; c < length; c++)
{
netname[nc] = sentence[c];
nc++;
}
SERVER_PRINT("Recieved SayText");
ChatEngine.set_sentence(netname, chSentence);
state = -1;
}
}
state++;
}
The above works, although i am not 100% satisfied. Its a bit messy code.
There is another function that is important, its UTIL_SayTextBot(), its partial code from Podbots saytext. i figured that when i use this code the bots DO NOT recieve a SayText and they CANNOT intercept it via the above function. So i had to use the ChatEngine.set_sentence() to make it work.
i really do not like that. It will make it harder for humans to interact with bots, it would be a real bot chat i'm afraid.
Anyway:
Code:
// POD SAYING:
void UTIL_SayTextBot( const char *pText,cBot *pBot)
{
if (gmsgSayText == 0)
gmsgSayText = REG_USER_MSG ("SayText", -1);
char szTemp[164];
char szName[30];
int i;
// init
szTemp[0]=2;
// clear out
szTemp[164]=0;
szName[30]=0;
int entind=ENTINDEX(pBot->pEdict);
if(IsAlive(pBot->pEdict))
{
strcpy(szName,pBot->name);
for (i = 1; i <= gpGlobals->maxClients; i++)
{
edict_t *pPlayer = INDEXENT (i);
if (pPlayer)
if (IsAlive(pPlayer))
{
MESSAGE_BEGIN( MSG_ONE, gmsgSayText,NULL,pPlayer);
WRITE_BYTE(entind);
sprintf(&szTemp[1],"%s : %s",szName,pText);
WRITE_STRING(&szTemp[0]);
MESSAGE_END();
}
}
}
else
{
strcpy(szName,pBot->name);
for (i = 1; i <= gpGlobals->maxClients; i++)
{
edict_t *pPlayer = INDEXENT (i);
if (pPlayer)
if (!IsAlive(pPlayer))
{
MESSAGE_BEGIN( MSG_ONE, gmsgSayText,NULL,pPlayer);
WRITE_BYTE(entind);
sprintf(&szTemp[1],"*DEAD*%s : %s",szName,pText);
WRITE_STRING(&szTemp[0]);
MESSAGE_END();
}
}
}
// Put the text in the chatengine
char chSentence[80];
chSentence[80]=0;
// copy pText to chSentence
sprintf(&chSentence[0], "%s", pText);
// pass through on ChatEngine
ChatEngine.set_sentence(pBot->name,chSentence);
}
If anyone could / can help me with figuring out what crucial messup i made to cause random crashes. I bet its a wrong memory allocation, writing something that should not be written. etc. Oh, also , keep in mind i have been up whole night , watching 5 movies (aprox 2 hrs per movie) straight with school. I won a sort of 'award' for this (due i did not fell a sleep, no wonder though

). I am not as clear now. Yet i am fiddling with this for too long, especially when i have it in my head how it should work, but it does not.
Thanks in advance for your help!
EDIT: One question
what is the difference between:
Code:
char achar[80];
achar[80]=0;
and:
Code:
char achar[80];
for (int i=0; i < 80; i++)
achar[i]=0; // or achar[i]='\0';
?