.:: Bots United ::.

.:: Bots United ::. (http://forums.bots-united.com/index.php)
-   United Bot (http://forums.bots-united.com/forumdisplay.php?f=46)
-   -   Independed Thinking Machine (http://forums.bots-united.com/showthread.php?t=3244)

stefanhendriks 21-12-2004 10:53

Independed Thinking Machine
 
I got the basic principles working, and therefor i find it appropiate to introduce you to my version of a game-independant bot template.

The ITM-Bot, The "Independed Thinking Machine". The architecture is easy, neat, and it works for HL1 (CSTRIKE) atm. There is still work todo, but the bots spawn, choose a team, etc. So the basics work: Bot can recieve messages, its able to update its data, it can send messages (data) back to the server so it 'interacts'.

The interface has only a few 'most needed' functions that the 'empty' interface also offers.

Is there any 'advantage' over other bots, no. Why did i do it? Why not? I looked into PMB's code, and i understand the principle, but that does not make me fully understand the code. I wanted to do it myself, just to create an as naked as possible code.

For instance, the entities list is not updated yet. And , i want to do this for most important entities only. I am not sure yet what is handy to do, update the entire long list of entities, or just the 'important to bots' one.

Anyhow, just was anxious to tell you i got my bot working too. And i hope i can create a HL2 interface soon, so the bot thinking is there, and i only need a few functions to work for the bot. But thats a long way to go ;)

@$3.1415rin 21-12-2004 13:10

Re: Independed Thinking Machine
 
isnt this just engine indep layer augmented with some thinking-functions, like calling behaviours or whatever ...

maybe you also wanna take a look at the joebot xp perception and "thinking" code ... I guess you have the code already, pm me if you don't know where to look despite the lots of comments :P

but nice to hear that there are still people out there, who don't have to spend all their free time for university ... looking forward to see some code ( snippets )

stefanhendriks 21-12-2004 15:21

Re: Independed Thinking Machine
 
yes, its basicly

BOT

INFO

GAME

so, BOT & INFO is my code, GAME is game dependant code (aka like PMB with interface) , the interface feeds the info 'layer' and the bot uses that layer.

botmeister 21-12-2004 21:01

Re: Independed Thinking Machine
 
Well as we discussed in the United Bot thread, the big deal will be in comming up with a common interface from which any bot can be built from replaceable components. We're a long way from realizing this, but what PMB and Stefan have done is a big step forward. The hard part is just getting started with something that can be tinkered with, which was a point made by botman a while long ago.

I wish I had more time for bot programming, but at least the project I'm now working on at my job will be in developing a similar interface, so I should be able to share my experience in here at some point.

Pierre-Marie Baty 21-12-2004 22:17

Re: Independed Thinking Machine
 
lol, engine-independent thingies seem to be quite fashionable these days :D

I wish I had more time here too (...and the HL2 SDK).

you got me interested, botmeister, could you tell us more about that job ?

stefanhendriks 21-12-2004 22:38

Re: Independed Thinking Machine
 
well aside from the hl1 interface i try to write an empty interface with only comments. So you only have to 'insert' code there to feed the information layer so bots actually work.

I try to keep as much as possible out of the bot code. Ie, for joining a team in a game, the bot simply does not know what game it is. All it should do is

(snippet from itm)
Code:

void c_ITM_Bot::BOT_Think()
{       
 
        if (bJoinedTeam(this) == false)
                return; // do nothing yet
 
        // We joined the game, think!
}

The join function differs from game to game, so what i have done is:

Code:

// PURPOSE: Return TRUE when joined a team
bool bJoinedTeam(c_ITM_Bot *pBot)
{
        // we already joined the action
        if (pBot->bJoinedAction)
                return true;
        // In order to keep this part 'engine independant' we run
        // a function shared in the entire framework called "IF_JOINTEAM"
        // which should return TRUE, when its done joining the game, which is
        // game and mod depended.
        pBot->bJoinedAction = IF_JOINTEAM(pBot);
        return pBot->bJoinedAction; // return this
}

where IF_JOINTEAM() is an InterFace function (IF_...) with a pointer of this bot. The IF_JOINTEAM function is a 'standard shared' function in the interface. Which is EMPTY in the 'empty/example' interface.

The contents look similiar to the join function in Real/HPB/Racc/whatever bot.
Code:

/////////////////////////////////////////////////////////////////
// The only BOT / GAME SPECIFIC "join the game" FUNCTION
/////////////////////////////////////////////////////////////////
bool IF_JOINTEAM(c_ITM_Bot *pBot)
{
        // We handle CS joining here, its very basic, as we are lazy
        // and let CS decide what team we are ;) We later on check
        // what team we are with the UTIL_ function at the 'sense'
        // part. Which is, even not needed, because the Update Sequence
        // keeps our bot data up-to-date!
        edict_t *pEdict = UTIL_GetBotEdict(pBot);
 
        if (pEdict == NULL)
        {
                IF_PRINT("ERROR: Could not get pEdict from pBot\n");
                return false;
        }
        // handle Counter-Strike stuff here...
        if (pBot->iStartAction == ITM_MSG_SELECT_A_TEAM)
        {
                pBot->iStartAction = ITM_MSG_NONE; // switch back to idle
                // Select 'auto'
                FakeClientCommand(pEdict, "menuselect", "5", NULL);               
                return false; // we did not complete joining yet
        }
        if (pBot->iStartAction == ITM_MSG_SELECT_CLASS_ALPHA) // counter terrorist
        {
                pBot->iStartAction = ITM_MSG_NONE; // switch back to idle
 
                // Select 'auto'
                FakeClientCommand(pEdict, "menuselect", "5", NULL);
                // bot has now joined the game (doesn't need to be started)               
                return true;
        }
        if (pBot->iStartAction == ITM_MSG_SELECT_CLASS_BETA) // terrorist select
        {
                pBot->iStartAction = ITM_MSG_NONE; // switch back to idle
                // Select 'auto'               
                FakeClientCommand(pEdict, "menuselect", "5", NULL);
// bot has now joined the game (doesn't need to be started)
                return true;
        }
return false;
}

The reason for this way is simple: keep everything game specific out of the bot code.

This is the only example code i have so far that directly calls an interface function from the BOT. Apart frmo the runplayermove logic in the HL1 engine, which i call with "MakeItHappen". This is a more general way of saying to do what we thought about (the bot) and get things in motion. Every game has a different way of doing this, so MakeItHappen was a nice name i thought.

For the moment things are very, very basic and very naked coded. Only the barebones are there, but thats the point. It should not be bigger then nescesary.

Just FYI here the contents of the itm_interface_hl1.h file:
Code:

// Shared interface functions
#ifndef ITM_INTERFACE_HL1_H
#define ITM_INTERFACE_HL1_H
#define MAX_HL1_CLIENTS        32                        // Max 32 clients in HL1
struct t_timeBot
{
int msecnum;
float msecdel;
int msecval;
};
// The function names should not change
// The params may change though
void IF_Init();                                                        // Init any (global) vars we use in the game specific interface
void IF_Spawn(edict_t *pent);                                // Spawns something
void IF_UpdateSequence();                                        // aka Startframe()
int IF_CreateBot (edict_t * pPlayer, const char *arg1, const char *arg2, const char *arg3, const char *arg4);
void IF_PRINT(char *msg);                                        // print a message on the server
void IF_ClientCommand(edict_t * pBot, char *arg1, char *arg2, char *arg3); // command from a bot to the server
int IF_MakeItHappen(c_ITM_Bot *pBot);        // Aka RunPlayerMove()
// "Converts" any game vector of this game into the vector of our own format!
c_ITM_Vector IF_ConvertVector(Vector vec);
Vector IF_ConvertVector(c_ITM_Vector vec);
t_timeBot TimeBot[MAX_HL1_CLIENTS];
/////////////////////////
//
// Newly added functions:
//
/////////////////////////
// UTIL FUNCTIONS
c_ITM_Bot * UTIL_GetBotPointer (edict_t * pEdict);
int                UTIL_GetBotIndex (edict_t * pEdict);
 
// DRAW SOMETHING ON THE HUD
void HUD_DrawString (int r, int g, int b, char *msg, edict_t * edict);
//
// CLIENT STUFF
//
//
// COUNTER-STRIKE
void BotClient_CS_VGUI (void *p, int bot_index);
void BotClient_CS_ShowMenu (void *p, int bot_index);
void BotClient_CS_WeaponList (void *p, int bot_index);
void BotClient_CS_CurrentWeapon (void *p, int bot_index);
void BotClient_CS_AmmoX (void *p, int bot_index);
void BotClient_CS_AmmoPickup (void *p, int bot_index);
void BotClient_CS_WeaponPickup (void *p, int bot_index);
void BotClient_CS_ItemPickup (void *p, int bot_index);
void BotClient_CS_Health (void *p, int bot_index);
void BotClient_CS_Battery (void *p, int bot_index);
void BotClient_CS_Damage (void *p, int bot_index);
void BotClient_CS_Money (void *p, int bot_index);
void BotClient_CS_DeathMsg (void *p, int bot_index);
void BotClient_CS_ScreenFade (void *p, int bot_index);
void BotClient_CS_HLTV(void *p, int bot_index);
void BotClient_CS_SayText(void *p, int bot_index);
// StatusIcon
void BotClient_CS_StatusIcon (void *p, int bot_index);
// VALVE DEATHMATCH
void BotClient_Valve_WeaponList (void *p, int bot_index);
void BotClient_Valve_CurrentWeapon (void *p, int bot_index);
void BotClient_Valve_AmmoX (void *p, int bot_index);
void BotClient_Valve_AmmoPickup (void *p, int bot_index);
void BotClient_Valve_WeaponPickup (void *p, int bot_index);
void BotClient_Valve_ItemPickup (void *p, int bot_index);
void BotClient_Valve_Health (void *p, int bot_index);
void BotClient_Valve_Battery (void *p, int bot_index);
void BotClient_Valve_Damage (void *p, int bot_index);
void BotClient_Valve_DeathMsg (void *p, int bot_index);
void BotClient_Valve_ScreenFade (void *p, int bot_index);
 
#endif // ITM_INTERFACE_HL1_H

as another note, i do use the clients[] var , but in a different way. As for in HPB you see they are not linked to the bots in any way. The clients[] var is handling ALL clients joining the game, and i share the indexes with my bots. So i can simply get a pEdict WITHOUT even using that in my c_ITM_Bot class. All i need is an index...

@$3.1415rin 21-12-2004 23:14

Re: Independed Thinking Machine
 
about how I solved this join stuff : In JoeBOT XP most is inside behaviours, but the joining is not. well, I wanted to write it is, but I looked it up and it isnt ... well, so it's basically the same you are doing stefan, except that I have a general function handling all sort of menues, which is a virtual function and thus is automatically used if a derived class has another handling than the normal HL bot. Maybe putting this into the structure of the behaviuors would be a fine way since that doesnt imply the need of another function ... nice idea, if I had only time.

About the network messages : ( havnt yet implemented all of them, just those I need at the moment ) They produce perceipts, like those perceipts when a bot sees another player, therefore they are processed by behaviours like almost everything else. this is something that's so to say part of the layer "converting" the information which the engine provides.

Code:

CBaseBot *pBot = g_pGame->getBot(bot_index);
if ((damage_armor > 0) || (damage_taken > 0)){
/*if ((damage_bits & (DMG_FALL | DMG_CRUSH)){
// bot received damage by falling down
}*/
// ignore certain types of damage...
if (damage_bits & IGNORE_DAMAGE){
// let the bot 'feel' something strange is going on ... health is going down, m8 !
CPerceipt *pNewPerceipt = pBot->m_pPerception->addPerceipt();
 
pNewPerceipt->m_VOrigin = damage_origin;
pNewPerceipt->m_lType = CPerceipt::PT_DAMAGE;
pNewPerceipt->m_lTypeSpec |= CPerceipt::PTX_DAMAGE_STRANGE;
pNewPerceipt->m_fDistance = (pBot->getOrigin()-damage_origin).length();
pNewPerceipt->m_iAddInfo = damage_bits;
}
else{
// let the bot 'feel' that he's attacked :)
CPerceipt *pNewPerceipt = pBot->m_pPerception->addPerceipt();
 
pNewPerceipt->m_VOrigin = damage_origin;
pNewPerceipt->m_lType = CPerceipt::PT_DAMAGE;
pNewPerceipt->m_fDistance = (pBot->getOrigin()-damage_origin).length();
pNewPerceipt->m_iAddInfo = damage_bits;
g_Map.m_Waypoints.addDamage(pBot->getOrigin(),damage_origin,damage_armor + damage_taken);
}
}

( bah, again that distance code I wanted to change :D )

and then those information is used fully engine indep in a behaviour

Code:

// watch Damage
void CBV_HLDM_WatchDamage::evaluate(list<CGoal> *pGoals,CPerceipt* p){
if(p->m_lType == CPerceipt::PT_DAMAGE
&&!(p->m_lTypeSpec & CPerceipt::PTX_DAMAGE_STRANGE)){
pGoals->push_front(CGoal(
(15)*((p->m_fLifetime - g_pGame->getTime() + p->m_fLUpdate)/p->m_fLifetime),
CGoal::GT_LOOK,this,p));
}
return;
}
 
 
void CBV_HLDM_WatchDamage::execute(CGoal *pGoal){
m_fLastExecution = g_pGame->getTime();
 
if(pGoal->m_pPerceipt){
m_pBot->m_pAction->lookTo(pGoal->m_pPerceipt->m_VOrigin);
}
}


stefanhendriks 21-12-2004 23:26

Re: Independed Thinking Machine
 
i understood the first code snippet perfectly. Thats also a way of doing it. Although i would just set a flag or something, or even better. Just set the health and let the bot think handle the question 'how did my health drop' and determine that from an origin if it was inflicted or not.

THe second piece was just a to big IF statement for me to handle at once heh :)

@$3.1415rin 21-12-2004 23:38

Re: Independed Thinking Machine
 
I have to store more data in the perceptions since the perceptions can exist longer, and an entity inflicting damage might be deleted. the perception might even go to the long term memory, so that's why i'm storing almost everything important I can get in the perceipt.

and always checking if the health might have dropped is called "polling" and not very loved noadays, since you need processing time for it. In my solution only then the code is executed if something happened

stefanhendriks 21-12-2004 23:44

Re: Independed Thinking Machine
 
actually i don't care what is loved or not ;) i go for security, and portability :) And yet easy-to-read code. With machines nowadays, i don't think bots can take up to much cpu power.

Whistler 22-12-2004 08:12

Re: Independed Thinking Machine
 
well I also has made a "engine independant" structure for my new bot, but it's not very advanced; I just stuffed most of engine specific stuff in classes. Here's my bot.cpp file:
PHP Code:

/*
 * The GINA Bot - a computer opponent for Valve Software's game Half-Life
 * Copyright (c) 2004, Wei Mingzhi <whistler_wmz@users.sf.net>
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * In addition, as a special exception, the author gives permission to
 * link the code of this program with the Half-Life Game Engine ("HL
 * Engine") and Modified Game Libraries ("MODs") developed by Valve,
 * L.L.C ("Valve").  You must obey the GNU General Public License in all
 * respects for all of the code used other than the HL Engine and MODs
 * from Valve.  If you modify this file, you may extend this exception
 * to your version of the file, but you are not obligated to do so.  If
 * you do not wish to do so, delete this exception statement from your
 * version.
 *
 * [In other words, you are welcome to use, share and improve this
 * program. You are forbidden to forbid anyone else to use, share
 * and improve what you give them.]
 */

//
// bot.cpp
//
// Basic bot handling.
//

#include "main.h"

CBaseBot::CBaseBot():
m_iStartAction(0),
m_flMoveSpeed(0),
m_flSideMoveSpeed(0),
m_flUpMoveSpeed(0),
m_fIsStarted(false),
m_fNeedToInitialize(true),
m_flTimeThink(0),
m_flTimePrevThink(0),
m_iPrevHealth(0),
m_pAINode(NULL)
{
   
bi.actionflags 0;
   
bi.dir g_vecZero;
   
bi.idealangles g_vecZero;
   
bi.weapon 0;

   
// initialize subsystems...
   
nav = new CBotNav(this);
   
chat = new CBotChat(this);
   if (!
nav || !chat)
      
g_General.TerminateOnError("Memory allocation error !");
}

CBaseBot::~CBaseBot()
{
   
// delete subsystems...
   
delete nav;
   
delete chat;

   if (
m_pAINode)
      
delete m_pAINode// free memory allocated for current AI node

   
m_pProfile->is_used false// this profile is not used now
}

void CBaseBot::BotThink()
{
   if (!
IsValid() || !GetNetName()[0]) {
      
g_General.TerminateOnError("BotThink(): Bot entity is invalid!");
   }

   
MarkAsBot(); // mark this entity as a bot

   
if (!m_fIsStarted) {
      
// if the bot hasn't selected stuff to start the game yet, go do that...
      
StartGame();
   } else if (!
IsAlive()) {
      
// if the bot is dead, run the dead think code (MOD specific)
      
DeadThink();
      
m_fNeedToInitialize true;
      
// delete the AI node data if exist
      
if (m_pAINode)
         
delete m_pAINode;
      
m_pAINode NULL;
   } else {
      if (
m_fNeedToInitialize) {
         
SpawnInit(); // initialize if we need to
         
m_fNeedToInitialize false;
      }

      if (
m_flTimeThink g_pServer->GetTime()) {
         
BotAI(); // run the main bot AI routine at 10Hz

         
m_flTimePrevThink g_pServer->GetTime();
         
m_flTimeThink g_pServer->GetTime() + 0.1;
      }
   }

   
PerformMovement(); // Pass through the engine
}

void CBaseBot::DeadThink()
{
   
// press fire to respawn...
   // NOTE: For some MODs like CS, we needn't press fire.
   // so leave this virtual...
   
bi.actionflags |= ACTION_RESPAWN;
}

void CBaseBot::SpawnInit()
{
   
ResetState();

   
AIEnter(new CAINode_Normal(this)); // enter the normal AI node
}

void CBaseBot::FacePosition(const Vector &vecPos)
{
   
// Adjust bots ideal angles to face an absolute vector
   
bi.idealangles VecToAngles(vecPos GetGunPosition());
   
bi.idealangles.*= -1.0// invert for engine

   
ClampAngles(bi.idealangles);
}

void CBaseBot::TranslateBotInput()
{
   
float flMoveSpeed GetMaxspeed();

   
ReleaseAllButtons();
   
m_flMoveSpeed 0;
   
m_flSideMoveSpeed 0;
   
m_flUpMoveSpeed 0;

   
// if bot is pressing the walk button...
   
if (bi.actionflags ACTION_WALK) {
      
PushButton(IN_RUN); // push the walk button
      
flMoveSpeed *= 0.3// reduce his move speed
   
}

   
// bot want to respawn?
   
if (bi.actionflags ACTION_RESPAWN) {
      
// push attack randomly to respawn
      
if (RandomLong(01))
         
PushButton(IN_ATTACK);
   }

   
// bot want to fire?
   
if (bi.actionflags ACTION_ATTACK) {
      
PushButton(IN_ATTACK); // push attack to fire
   
}

   
// bot want to use secondary attack?
   
if (bi.actionflags ACTION_ATTACK2) {
      
PushButton(IN_ATTACK2); // push attack to fire
   
}

   
// bot want to reload?
   
if (bi.actionflags ACTION_RELOAD) {
      
PushButton(IN_RELOAD); // push the reload button
   
}

   
// bot want to use something?
   
if (bi.actionflags ACTION_USE) {
      
PushButton(IN_USE);
   }

   
// Change bots body angle and view angle
   
float turn_skill 0.3 GetSkill() / 100speed;

   
// if bot is aiming at something, aim fast, else take our time...
   
if (m_pEnemy)
      
speed 0.7 turn_skill// fast aim
   
else
      
speed 0.2 turn_skill 2// slow aim

   
ChangeAngles(bi.idealanglesspeed); // change bot angles

   
float forwardspeed 0sidespeed 0;

   
// bot want to move forward?
   
if (bi.actionflags ACTION_MOVEFORWARD) {
      
PushButton(IN_FORWARD); // push the forward button
      
forwardspeed += flMoveSpeed// set the move speed
   
}

   
// bot want to move back?
   
if (bi.actionflags ACTION_MOVEBACK) {
      
PushButton(IN_BACK); // push the moveback button
      
forwardspeed -= flMoveSpeed// set the move speed
   
}

   
// bot want to move left?
   
if (bi.actionflags ACTION_MOVELEFT) {
      
PushButton(IN_MOVELEFT);
      
sidespeed -= flMoveSpeed// set the sidemove speed
   
}

   
// bot want to move right?
   
if (bi.actionflags ACTION_MOVERIGHT) {
      
PushButton(IN_MOVERIGHT); // press the button
      
sidespeed += flMoveSpeed// set the sidemove speed
   
}

   
// if move direction is specified...
   
if (bi.dir != g_vecZero) {
      
// set the view independent movement
      
Vector vecDirectionNormal bi.dir.Normalize();
      
vecDirectionNormal.0;

      
// movement is relative to the REAL bot angles
      
Vector forward;
      
AngleVectors(Vector(0GetViewAngles().y0), &forwardNULLNULL);

      
// calculate movement speed
      
float flCos DotProduct(forwardvecDirectionNormal);
      
float flSin sqrt(flCos flCos);
      
m_flMoveSpeed flCos forwardspeed;
      
m_flMoveSpeed -=  flSin sidespeed;
      
m_flSideMoveSpeed flCos sidespeed;
      
m_flSideMoveSpeed += flSin forwardspeed;
   } else {
      
m_flMoveSpeed forwardspeed;
      
m_flSideMoveSpeed sidespeed;
   }

   
// bot want to jump ?
   
if (bi.actionflags ACTION_JUMP) {
      
PushButton(IN_JUMP); // press the button
      
m_flUpMoveSpeed += flMoveSpeed// set the upmove speed
   
}

   
// bot want to crouch ?
   
if (bi.actionflags ACTION_CROUCH) {
      
PushButton(IN_DUCK); // press the button
      
m_flUpMoveSpeed -= flMoveSpeed// set the upmove speed
   
}

   
// switch to correct weapon
   
if (bi.weapon != 0)
      
SelectWeapon(bi.weapon);
}

void CBaseBot::SelectWeapon(int iId)
{
   if (
iId != m_iCurrentWeaponId) {
      
// is the specified weapon ID valid ?
      
if (iId || iId MAX_WEAPONS) {
         
g_General.TerminateOnError("CBaseBot::SelectWeapon(): Invalid weapon ID !\n");
      }

      
// issue a client command to select this weapon...
      
g_General.FakeClientCommand(thisg_General.GetWeaponName(iId));
   }
}

void CBaseBot::PerformMovement()
{
   
// Translate bot input
   
TranslateBotInput();

   
// pass to the engine
   
g_engfuncs.pfnRunPlayerMove(edict(), (float *)GetViewAngles(), m_flMoveSpeed,
                               
m_flSideMoveSpeedm_flUpMoveSpeedpev->button,
                               
pev->impulseg_pServer->GetMsec());
}

void CBaseBot::ResetState()
{
   
// reset bot input parameters
   
bi.actionflags 0;
   
bi.dir g_vecZero;
   
bi.idealangles g_vecZero;
   
bi.weapon 0;

   
m_iPrevHealth 0;

   
m_pEnemy NULL// null out the enemy pointer
   
m_vecEnemy g_vecZero;
   
m_ucVisibility 0;

   
Nav()->Init(); // initialize the navigation system
}

void CBaseBot::DebugMsg(int flag, const char *fmt, ...)
{
#ifndef _DEBUG
   
if (!g_Debug.IsDebugOn(flag))
      return; 
// debug for this flag is off; don't proceed
#endif

   
va_list     argptr;
   
char        string[256];

   
va_start(argptrfmt);
   
_vsnprintf(stringsizeof(string), fmtargptr);
   
va_end(argptr);

   
printf("[BOT DEBUG] %s: %s\n"GetNetName(), string); // print this message to the console
   
g_Debug.DebugLog(flag"(%s) %s"GetNetName(), string); // also store it to the log file




All times are GMT +2. The time now is 06:44.

Powered by vBulletin® Version 3.8.2
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.