![](images/aria/misc/left.gif) |
General Programming Help others and get yourself helped here!
|
|
RealBot Author
Status: Offline
Posts: 3,088
Join Date: Nov 2003
Location: Netherlands
|
Any of you string/pointer/memory guru's out there who can help me? -
09-04-2004
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 ![Yes](images/smilies/yes.gif) ). 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';
?
Last edited by stefanhendriks; 09-04-2004 at 14:27..
|
|
|
|
|
Roi de France
Status: Offline
Posts: 5,049
Join Date: Nov 2003
Location: 46°43'60N 0°43'0W 0.187A
|
Re: Any of you string/pointer/memory guru's out there who can help me? -
09-04-2004
Quote:
Originally Posted by Stefan
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';
?
|
First off, the first one
char achar[80];
achar[80] = 0;
will produce an access violation error.
You declare an array of characters, 80 characters wide.
Then you tell it to put the 81th character to zero. I mean, Stefan, get more sleep, I *mean it*
The second one
char achar[80];
for (int i = 0; i < 80; i++)
achar[i] = 0;
will take each element of this array successively, starting from element #0 (first element) up to element #79 (80th element, i.e. the size of your array), and reset it to zero. ALL your character array will be reset to zero.
What are you up to ??
*edit* OMG, I've just taken a deeper look in the code you posted, it's terrible!! 8o 8o 8o Do you know how many times you are writing beyond the limit of your arrays ??? And you wonder why your bot crashes randomly ?? omfg, if the hwole RealBot is written this way, then it's driven by MAGIC ! Fix that or do something but get this out of my sight quick !!
*edit 2* OK, since I'm a good boy (well, I think...) I'm gonna give you a hint dude.
To clear out an array without wasting time doing a test for each character, use memset().
char achar[80];
memset (achar, 0, sizeof (achar));
And your whole array is reset to zero, completely, it takes a millionth second.
RACC home - Bots-United: beer, babies & bots (especially the latter)
"Learn to think by yourself, else others will do it for you."
Last edited by Pierre-Marie Baty; 09-04-2004 at 15:51..
|
|
|
|
|
Council Member, Author of JoeBOT
Status: Offline
Posts: 1,381
Join Date: Nov 2003
Location: Germany
|
Re: Any of you string/pointer/memory guru's out there who can help me? -
09-04-2004
do you think everybody reads all this code ?
well, some functions which should be used and might be helpful. if you wanna get practice in coding such stuff nothing is against implementing it yourself, but 'inlining' it all the time looks a bit bad : - memset(destination, byte, size); // sets some piece of memory to some value
- strcpy(destination, source ); // copies source to destination. notice that the source string should be small enough to fit in destination, and that source is terminated by a zero
- strncpy(destination, source, size); // copies just size bytes from source to destination
- strcat(destination, source ); // append source to destination. all this stuff is about zero terminated strings. ( the sz in hungarian notation
![Yes](images/smilies/yes.gif)
- strcmp(sz1, sz2 ); // compares 2 strings. zero if equal, otherwise -1 or 1
- sprintf ... // the same like with printf, but to memory. and keep in mind the % stuff ... %s for string, %c for char, %i for int etc.
dunno how much interesting function I forgot which you'd need. also useful might be the string class ( is that a part of STL or where does that come from ) from <string>
|
|
|
|
|
RealBot Author
Status: Offline
Posts: 3,088
Join Date: Nov 2003
Location: Netherlands
|
Re: Any of you string/pointer/memory guru's out there who can help me? -
09-04-2004
lol @ pmb:
First of all, my clearing was always done via those 'for' loops. But they are to long. And since i found in some other bots source that he did:
achar[80]=0;
i thought, heck, it probably means 'clear TILL' 80. Although i also was not 100% sure. Anyway. Since i read your post i bet i must be doing something terrible there. so i'll get on with that.
I do not expect anyone to read my entire code, it mostly is an accumilating problem. Like pmb said, i did it multiple times wrong, it adds up wrong, it fucks up memory.
Btw, i got some sleep now, around 3 hours ![Wink](images/smilies/wink.gif)
|
|
|
|
|
RealBot Author
Status: Offline
Posts: 3,088
Join Date: Nov 2003
Location: Netherlands
|
Re: Any of you string/pointer/memory guru's out there who can help me? -
09-04-2004
thanks guys. I have rechecked my entire code, fixing up any bad allocaiton/initialization stuff. I also replaced some 'sprintf' thingies with strcpy.
AND IT WORKS NOW!!!
yeeeeeehaw.
Man, i am going to play around with this chat database and show off later. I am also gonna build in a 'thanks to' system that prints off a message (yes, this time memset cleared and not on 81 PMB ![Tongue Smilie](images/smilies/tongue_smilie.gif) ) once in a while.
Oh,they also talk about BU, thats okay right? ![Laugh](images/smilies/laugh.gif)
|
|
|
|
|
Council Member
Status: Offline
Posts: 690
Join Date: Dec 2003
Location: Holland
|
Re: Any of you string/pointer/memory guru's out there who can help me? -
09-04-2004
Quote:
Oh,they also talk about BU, thats okay right?
|
I hope so otherwise I have to remove some messages aswell
btw why do you want to clear a string with memset? Just setting the first element to 0 is enough for an 'empty' string
|
|
|
|
|
Council Member, Author of JoeBOT
Status: Offline
Posts: 1,381
Join Date: Nov 2003
Location: Germany
|
Re: Any of you string/pointer/memory guru's out there who can help me? -
09-04-2004
didn't you know that stefan is all the time running from his evil counterpart, a red clothed communist spy, sucking out all information he gets ? Therefore stefan has to erase all obsolete information when he doesnt need it any more.
@stefan : maybe you also just browse a bit in the MSDN - you can browse it on microsoft.com, somewhere. but the functions above are sufficient for some time now I guess
Last edited by @$3.1415rin; 09-04-2004 at 17:47..
|
|
|
|
|
Roi de France
Status: Offline
Posts: 5,049
Join Date: Nov 2003
Location: 46°43'60N 0°43'0W 0.187A
|
Re: Any of you string/pointer/memory guru's out there who can help me? -
09-04-2004
@Asp: 'got something against commies, comrade ? ![Laugh](images/smilies/laugh.gif)
RACC home - Bots-United: beer, babies & bots (especially the latter)
"Learn to think by yourself, else others will do it for you."
|
|
|
|
|
RealBot Author
Status: Offline
Posts: 3,088
Join Date: Nov 2003
Location: Netherlands
|
Re: Any of you string/pointer/memory guru's out there who can help me? -
09-04-2004
lol. I just have the habbit to clear out everything, so i am 100% sure the variable is clean and i dont screw up. I did screw up,but don't mention that.
For your pleassure. I ran my first test with a bigger database of words and such. Its not amazing, its not revolutionary, but its fun ![Yes](images/smilies/yes.gif)
Last edited by stefanhendriks; 01-08-2007 at 07:50..
|
|
|
|
|
Member
Status: Offline
Posts: 127
Join Date: Jan 2004
Location: Netherlands, Arnhem
|
Re: Any of you string/pointer/memory guru's out there who can help me? -
09-04-2004
whoei, that looks nice ![Smokin](images/smilies/cool.gif) .
can't wait to see it in live action.
nice work
btw. can we add more line costumely, and can we turn it off?
Last edited by T!m; 09-04-2004 at 22:59..
Reason: +somequestions
|
|
|
Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
|
|
Posting Rules
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
HTML code is Off
|
|
|
Powered by vBulletin® Version 3.8.2 Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
vBulletin Skin developed by: vBStyles.com
|
![](images/aria/misc/right.gif) |